diff --git a/packages/app/pr/screenshots/steps-grouping-after-docker.png b/packages/app/pr/screenshots/steps-grouping-after-docker.png new file mode 100644 index 00000000..55c8995c Binary files /dev/null and b/packages/app/pr/screenshots/steps-grouping-after-docker.png differ diff --git a/packages/app/pr/screenshots/steps-grouping-before-docker.png b/packages/app/pr/screenshots/steps-grouping-before-docker.png new file mode 100644 index 00000000..c36edca0 Binary files /dev/null and b/packages/app/pr/screenshots/steps-grouping-before-docker.png differ diff --git a/packages/app/src/app/components/session/message-list.tsx b/packages/app/src/app/components/session/message-list.tsx index 172900e9..6740a087 100644 --- a/packages/app/src/app/components/session/message-list.tsx +++ b/packages/app/src/app/components/session/message-list.tsx @@ -87,12 +87,7 @@ function latestStepPart(partsGroups: Part[][]): Part | undefined { const parts = partsGroups[groupIndex] ?? []; for (let partIndex = parts.length - 1; partIndex >= 0; partIndex -= 1) { const part = parts[partIndex]; - if ( - part.type === "tool" || - part.type === "reasoning" || - part.type === "step-start" || - part.type === "step-finish" - ) { + if (part.type === "tool" || part.type === "reasoning") { return part; } } @@ -187,7 +182,7 @@ export default function MessageList(props: MessageListProps) { } if (part.type === "step-start" || part.type === "step-finish") { - return props.developerMode; + return false; } if (part.type === "text" || part.type === "tool" || part.type === "agent" || part.type === "file") { @@ -225,22 +220,22 @@ export default function MessageList(props: MessageListProps) { const groupId = String((message.info as any).id ?? "message"); const groups = groupMessageParts(renderableParts, groupId); const isUser = (message.info as any).role === "user"; - const isStepsOnly = groups.length === 1 && groups[0].kind === "steps"; + const isStepsOnly = groups.length > 0 && groups.every((group) => group.kind === "steps"); + const stepGroups = isStepsOnly ? (groups as { kind: "steps"; id: string; parts: Part[] }[]) : []; stepGroupCount += groups.reduce((count, group) => (group.kind === "steps" ? count + 1 : count), 0); if (isStepsOnly) { - const stepGroup = groups[0] as { kind: "steps"; id: string; parts: Part[] }; const lastBlock = blocks[blocks.length - 1]; if (lastBlock && lastBlock.kind === "steps-cluster" && lastBlock.isUser === isUser) { - lastBlock.partsGroups.push(stepGroup.parts); - lastBlock.stepIds.push(stepGroup.id); + lastBlock.partsGroups.push(...stepGroups.map((group) => group.parts)); + lastBlock.stepIds.push(...stepGroups.map((group) => group.id)); lastBlock.messageIds.push(messageId); } else { blocks.push({ kind: "steps-cluster", - id: stepGroup.id, - stepIds: [stepGroup.id], - partsGroups: [stepGroup.parts], + id: stepGroups[0].id, + stepIds: stepGroups.map((group) => group.id), + partsGroups: stepGroups.map((group) => group.parts), messageIds: [messageId], isUser, }); diff --git a/packages/app/src/app/utils/index.ts b/packages/app/src/app/utils/index.ts index f72c0061..fb35cd6c 100644 --- a/packages/app/src/app/utils/index.ts +++ b/packages/app/src/app/utils/index.ts @@ -494,13 +494,14 @@ export function lastUserModelFromMessages(list: MessageWithParts[]): ModelRef | } export function isStepPart(part: Part) { - return part.type === "reasoning" || part.type === "tool" || part.type === "step-start" || part.type === "step-finish"; + return part.type === "reasoning" || part.type === "tool"; } export function groupMessageParts(parts: Part[], messageId: string): MessageGroup[] { const groups: MessageGroup[] = []; const steps: Part[] = []; let textBuffer = ""; + let stepGroupIndex = 0; const flushText = () => { if (!textBuffer) return; @@ -508,33 +509,49 @@ export function groupMessageParts(parts: Part[], messageId: string): MessageGrou textBuffer = ""; }; + const flushSteps = () => { + if (!steps.length) return; + groups.push({ kind: "steps", id: `steps-${messageId}-${stepGroupIndex}`, parts: steps.splice(0, steps.length) }); + stepGroupIndex += 1; + }; + parts.forEach((part) => { if (part.type === "text") { + flushSteps(); textBuffer += (part as { text?: string }).text ?? ""; return; } if (part.type === "agent") { + flushSteps(); const name = (part as { name?: string }).name ?? ""; textBuffer += name ? `@${name}` : "@agent"; return; } if (part.type === "file") { + flushSteps(); flushText(); groups.push({ kind: "text", part }); return; } + if (part.type === "step-start" || part.type === "step-finish") { + return; + } + flushText(); + + if (part.type === "reasoning" && steps.length > 0) { + flushSteps(); + } + steps.push(part); }); flushText(); - if (steps.length) { - groups.push({ kind: "steps", id: `steps-${messageId}`, parts: steps }); - } + flushSteps(); return groups; }