fix(FN-6950): show effective model icons in task chat
This commit is contained in:
5
.changeset/task-chat-effective-model-icons.md
Normal file
5
.changeset/task-chat-effective-model-icons.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
"@runfusion/fusion": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
Show provider icons in task detail chat for default-backed executor, reviewer, planner, and merger models.
|
||||||
@@ -25,6 +25,7 @@ interface TaskChatTabProps {
|
|||||||
onTaskUpdated?: (task: Task) => void;
|
onTaskUpdated?: (task: Task) => void;
|
||||||
expanded?: boolean;
|
expanded?: boolean;
|
||||||
onToggleExpanded?: () => void;
|
onToggleExpanded?: () => void;
|
||||||
|
effectiveModels?: Partial<Record<"triage" | "executor" | "reviewer" | "merger", TaskChatModelInfo | null>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
type AgentLogRole = AgentRole | undefined;
|
type AgentLogRole = AgentRole | undefined;
|
||||||
@@ -122,12 +123,28 @@ function getRuntimeModelForRole(entries: readonly AgentLogEntry[], role: AgentLo
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getModelForRole(task: Task | TaskDetail, role: AgentLogRole, entries: readonly AgentLogEntry[]): TaskChatModelInfo | null {
|
function getEffectiveModelForRole(
|
||||||
|
effectiveModels: TaskChatTabProps["effectiveModels"] | undefined,
|
||||||
|
role: AgentLogRole,
|
||||||
|
): TaskChatModelInfo | null {
|
||||||
|
if (!role) return null;
|
||||||
|
return effectiveModels?.[role] ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getModelForRole(
|
||||||
|
task: Task | TaskDetail,
|
||||||
|
role: AgentLogRole,
|
||||||
|
entries: readonly AgentLogEntry[],
|
||||||
|
effectiveModels?: TaskChatTabProps["effectiveModels"],
|
||||||
|
): TaskChatModelInfo | null {
|
||||||
/*
|
/*
|
||||||
FNXC:TaskDetailChat 2026-06-23-21:18:
|
FNXC:TaskDetailChat 2026-06-23-21:18:
|
||||||
Task-detail chat agent headers should identify the AI provider actually backing each role, not a generic/role avatar. Prefer explicit task model overrides because they are stable before logs stream, then fall back to the runtime "using model" log marker emitted by active planner/executor/reviewer sessions. Merger output uses the validator/reviewer lane provider because merge-fix/review flows share that model family in the UI.
|
Task-detail chat agent headers should identify the AI provider actually backing each role, not a generic/role avatar. Prefer explicit task model overrides because they are stable before logs stream, then fall back to the runtime "using model" log marker emitted by active planner/executor/reviewer sessions. Merger output uses the validator/reviewer lane provider because merge-fix/review flows share that model family in the UI.
|
||||||
|
|
||||||
|
FNXC:TaskDetailChat 2026-06-23-00:54:
|
||||||
|
Default executor models such as OpenAI Codex GPT-5.5 can resolve through settings rather than task overrides or log markers. Task chat receives the same effective model resolution used by the task-detail model header so role icons match Chat and Agent Log instead of falling back to CPU for default-backed agents.
|
||||||
*/
|
*/
|
||||||
return getExplicitModelForRole(task, role) ?? getRuntimeModelForRole(entries, role);
|
return getExplicitModelForRole(task, role) ?? getRuntimeModelForRole(entries, role) ?? getEffectiveModelForRole(effectiveModels, role);
|
||||||
}
|
}
|
||||||
|
|
||||||
function TaskChatAgentIcon({ label, modelInfo, role }: { label: string; modelInfo: TaskChatModelInfo | null; role: AgentLogRole }) {
|
function TaskChatAgentIcon({ label, modelInfo, role }: { label: string; modelInfo: TaskChatModelInfo | null; role: AgentLogRole }) {
|
||||||
@@ -532,7 +549,7 @@ function TaskChatUserMessage({ message }: { message: UserChatMessage }) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function TaskChatTab({ task, projectId, active, addToast, sessionLive, onTaskUpdated, expanded = false, onToggleExpanded }: TaskChatTabProps) {
|
export function TaskChatTab({ task, projectId, active, addToast, sessionLive, onTaskUpdated, expanded = false, onToggleExpanded, effectiveModels }: TaskChatTabProps) {
|
||||||
const { t } = useTranslation("app");
|
const { t } = useTranslation("app");
|
||||||
const { entries, loading, loadMore, hasMore, loadingMore } = useAgentLogs(task.id, active, projectId);
|
const { entries, loading, loadMore, hasMore, loadingMore } = useAgentLogs(task.id, active, projectId);
|
||||||
const [draft, setDraft] = useState("");
|
const [draft, setDraft] = useState("");
|
||||||
@@ -868,7 +885,7 @@ export function TaskChatTab({ task, projectId, active, addToast, sessionLive, on
|
|||||||
const segments = segmentGroupEntries(item.entries);
|
const segments = segmentGroupEntries(item.entries);
|
||||||
const latestEntryTimestamp = item.entries[item.entries.length - 1]?.timestamp ?? "";
|
const latestEntryTimestamp = item.entries[item.entries.length - 1]?.timestamp ?? "";
|
||||||
const relativeTime = formatRelativeTimeAgo(latestEntryTimestamp);
|
const relativeTime = formatRelativeTimeAgo(latestEntryTimestamp);
|
||||||
const modelInfo = getModelForRole(task, item.role, item.entries);
|
const modelInfo = getModelForRole(task, item.role, item.entries, effectiveModels);
|
||||||
return (
|
return (
|
||||||
<section className="task-chat-group" key={`${item.role ?? "agent"}-${itemIndex}`} aria-label={t("taskChat.agentMessages", "{{label}} messages", { label: item.label })}>
|
<section className="task-chat-group" key={`${item.role ?? "agent"}-${itemIndex}`} aria-label={t("taskChat.agentMessages", "{{label}} messages", { label: item.label })}>
|
||||||
<header className="task-chat-group-header">
|
<header className="task-chat-group-header">
|
||||||
|
|||||||
@@ -263,6 +263,11 @@ function resolveEffectivePlanning(
|
|||||||
return resolveTaskPlanningModel(task, settings);
|
return resolveTaskPlanningModel(task, settings);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function toTaskChatModelInfo(model: ModelSelection): { provider: string; modelId?: string } | null {
|
||||||
|
if (!model.provider) return null;
|
||||||
|
return model.modelId ? { provider: model.provider, modelId: model.modelId } : { provider: model.provider };
|
||||||
|
}
|
||||||
|
|
||||||
function getStepStatusColor(status: string): string {
|
function getStepStatusColor(status: string): string {
|
||||||
switch (status) {
|
switch (status) {
|
||||||
case "done":
|
case "done":
|
||||||
@@ -3304,6 +3309,12 @@ export function TaskDetailContent({
|
|||||||
onTaskUpdated={handleChatTaskUpdated}
|
onTaskUpdated={handleChatTaskUpdated}
|
||||||
expanded={chatExpanded}
|
expanded={chatExpanded}
|
||||||
onToggleExpanded={() => setChatExpanded((value) => !value)}
|
onToggleExpanded={() => setChatExpanded((value) => !value)}
|
||||||
|
effectiveModels={{
|
||||||
|
triage: toTaskChatModelInfo(resolveEffectivePlanning(workingTask, agentLogEntries, settings)),
|
||||||
|
executor: toTaskChatModelInfo(resolveEffectiveExecutor(workingTask, agentLogEntries, assignedAgent, settings)),
|
||||||
|
reviewer: toTaskChatModelInfo(resolveEffectiveValidator(workingTask, agentLogEntries, assignedAgent, settings)),
|
||||||
|
merger: toTaskChatModelInfo(resolveEffectiveValidator(workingTask, agentLogEntries, assignedAgent, settings)),
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
) : activeTab === "logs" ? (
|
) : activeTab === "logs" ? (
|
||||||
|
|||||||
@@ -468,6 +468,24 @@ describe("TaskChatTab", () => {
|
|||||||
expect(document.querySelector(".task-chat-provider-icon [data-provider='anthropic']")).toBeTruthy();
|
expect(document.querySelector(".task-chat-provider-icon [data-provider='anthropic']")).toBeTruthy();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("renders provider icons for task chat roles from effective default models", () => {
|
||||||
|
mockLogs([
|
||||||
|
makeEntry({ agent: "executor", text: "executor output without model marker" }),
|
||||||
|
]);
|
||||||
|
|
||||||
|
render(
|
||||||
|
<TaskChatTab
|
||||||
|
task={makeTask()}
|
||||||
|
active
|
||||||
|
addToast={vi.fn()}
|
||||||
|
effectiveModels={{ executor: { provider: "openai-codex", modelId: "gpt-5.5" } }}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(document.querySelector(".task-chat-provider-icon [data-provider='openai-codex']")).toBeTruthy();
|
||||||
|
expect(screen.queryByLabelText("Executor: model provider unknown")).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
it("groups consecutive entries by agent role", () => {
|
it("groups consecutive entries by agent role", () => {
|
||||||
mockLogs([
|
mockLogs([
|
||||||
makeEntry({ agent: "executor", text: "first" }),
|
makeEntry({ agent: "executor", text: "first" }),
|
||||||
|
|||||||
Reference in New Issue
Block a user