fix: improve sorted chat activity rendering
Show tools and reasoning live in sorted mode Keep ambiguous text deferred until final output Avoid duplicate reasoning outside Activity
This commit is contained in:
@@ -36,19 +36,6 @@ const getMessageFinish = (message: ChatMessageEntry): string | undefined => {
|
|||||||
return typeof finish === 'string' ? finish : undefined;
|
return typeof finish === 'string' ? finish : undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
const isAssistantMessageCompleted = (message: ChatMessageEntry): boolean => {
|
|
||||||
const info = message.info as { time?: { completed?: unknown }; status?: unknown };
|
|
||||||
const completed = info.time?.completed;
|
|
||||||
const status = info.status;
|
|
||||||
if (typeof completed !== 'number' || completed <= 0) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (typeof status === 'string') {
|
|
||||||
return status === 'completed';
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
|
|
||||||
const buildTurnPartRecord = (
|
const buildTurnPartRecord = (
|
||||||
turnId: string,
|
turnId: string,
|
||||||
messageId: string,
|
messageId: string,
|
||||||
@@ -69,6 +56,7 @@ interface ProjectActivityInput {
|
|||||||
turnId: string;
|
turnId: string;
|
||||||
assistantMessages: ChatMessageEntry[];
|
assistantMessages: ChatMessageEntry[];
|
||||||
summarySourceMessageId?: string;
|
summarySourceMessageId?: string;
|
||||||
|
summarySourcePartId?: string;
|
||||||
showTextJustificationActivity: boolean;
|
showTextJustificationActivity: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -84,35 +72,42 @@ export const projectTurnActivity = (input: ProjectActivityInput): ProjectActivit
|
|||||||
let hasTools = false;
|
let hasTools = false;
|
||||||
let hasReasoning = false;
|
let hasReasoning = false;
|
||||||
|
|
||||||
|
input.assistantMessages.forEach((message) => {
|
||||||
|
message.parts.forEach((part) => {
|
||||||
|
if (part.type === 'tool') {
|
||||||
|
hasTools = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (part.type === 'reasoning' && getPartText(part)) {
|
||||||
|
hasReasoning = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
const taskMessageById = new Map<string, string>();
|
const taskMessageById = new Map<string, string>();
|
||||||
const taskOrder: string[] = [];
|
const taskOrder: string[] = [];
|
||||||
const partsByAfterTool = new Map<string | null, TurnActivityRecord[]>();
|
const partsByAfterTool = new Map<string | null, TurnActivityRecord[]>();
|
||||||
let currentAfterToolPartId: string | null = null;
|
let currentAfterToolPartId: string | null = null;
|
||||||
|
|
||||||
input.assistantMessages.forEach((message) => {
|
input.assistantMessages.forEach((message) => {
|
||||||
const messageCompleted = isAssistantMessageCompleted(message);
|
|
||||||
const finish = getMessageFinish(message);
|
const finish = getMessageFinish(message);
|
||||||
|
const messageHasTool = message.parts.some((part) => part.type === 'tool');
|
||||||
|
|
||||||
message.parts.forEach((part, partIndex) => {
|
message.parts.forEach((part, partIndex) => {
|
||||||
const isTool = part.type === 'tool';
|
const isTool = part.type === 'tool';
|
||||||
if (isTool) {
|
|
||||||
hasTools = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
const text = part.type === 'reasoning' || part.type === 'text'
|
const text = part.type === 'reasoning' || part.type === 'text'
|
||||||
? getPartText(part)
|
? getPartText(part)
|
||||||
: undefined;
|
: undefined;
|
||||||
|
const partId = part.id ?? `${message.info.id}-part-${partIndex}-${part.type}`;
|
||||||
if (part.type === 'reasoning' && text) {
|
|
||||||
hasReasoning = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
const toolName = isTool
|
const toolName = isTool
|
||||||
? (part as { tool?: unknown }).tool
|
? (part as { tool?: unknown }).tool
|
||||||
: undefined;
|
: undefined;
|
||||||
const standaloneTool = isTool && isStandaloneTool(toolName);
|
const standaloneTool = isTool && isStandaloneTool(toolName);
|
||||||
if (standaloneTool) {
|
if (standaloneTool) {
|
||||||
const toolPartId = part.id ?? `${message.info.id}-part-${partIndex}-${part.type}`;
|
const toolPartId = partId;
|
||||||
if (!taskMessageById.has(toolPartId)) {
|
if (!taskMessageById.has(toolPartId)) {
|
||||||
taskMessageById.set(toolPartId, message.info.id);
|
taskMessageById.set(toolPartId, message.info.id);
|
||||||
taskOrder.push(toolPartId);
|
taskOrder.push(toolPartId);
|
||||||
@@ -120,6 +115,12 @@ export const projectTurnActivity = (input: ProjectActivityInput): ProjectActivit
|
|||||||
currentAfterToolPartId = toolPartId;
|
currentAfterToolPartId = toolPartId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const isConfirmedSummaryText = part.type === 'text'
|
||||||
|
&& typeof text === 'string'
|
||||||
|
&& finish === 'stop'
|
||||||
|
&& input.summarySourceMessageId === message.info.id
|
||||||
|
&& input.summarySourcePartId === partId;
|
||||||
|
|
||||||
let kind: TurnActivityRecord['kind'] | null = null;
|
let kind: TurnActivityRecord['kind'] | null = null;
|
||||||
if (isTool) {
|
if (isTool) {
|
||||||
kind = 'tool';
|
kind = 'tool';
|
||||||
@@ -130,10 +131,9 @@ export const projectTurnActivity = (input: ProjectActivityInput): ProjectActivit
|
|||||||
} else if (
|
} else if (
|
||||||
input.showTextJustificationActivity
|
input.showTextJustificationActivity
|
||||||
&& part.type === 'text'
|
&& part.type === 'text'
|
||||||
&& messageCompleted
|
|
||||||
&& typeof finish === 'string'
|
|
||||||
&& finish !== 'stop'
|
|
||||||
&& text
|
&& text
|
||||||
|
&& !isConfirmedSummaryText
|
||||||
|
&& (messageHasTool || (typeof finish === 'string' && finish !== 'stop'))
|
||||||
) {
|
) {
|
||||||
kind = 'justification';
|
kind = 'justification';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -168,6 +168,7 @@ export const projectTurnRecords = (
|
|||||||
turnId: turn.turnId,
|
turnId: turn.turnId,
|
||||||
assistantMessages: turn.assistantMessages,
|
assistantMessages: turn.assistantMessages,
|
||||||
summarySourceMessageId: turn.summary.sourceMessageId,
|
summarySourceMessageId: turn.summary.sourceMessageId,
|
||||||
|
summarySourcePartId: turn.summary.sourcePartId,
|
||||||
showTextJustificationActivity: effectiveOptions.showTextJustificationActivity,
|
showTextJustificationActivity: effectiveOptions.showTextJustificationActivity,
|
||||||
});
|
});
|
||||||
turn.activityParts = activity.activityParts;
|
turn.activityParts = activity.activityParts;
|
||||||
|
|||||||
@@ -1106,6 +1106,8 @@ const AssistantMessageBody: React.FC<Omit<MessageBodyProps, 'isUser'>> = ({
|
|||||||
&& hasAnchoredActivitySegments
|
&& hasAnchoredActivitySegments
|
||||||
&& Boolean(toggleActivityGroup);
|
&& Boolean(toggleActivityGroup);
|
||||||
|
|
||||||
|
const shouldDeferSortedInlineText = isSortedRenderMode && !hasStopFinish;
|
||||||
|
|
||||||
|
|
||||||
const renderedParts = React.useMemo(() => {
|
const renderedParts = React.useMemo(() => {
|
||||||
const rendered: React.ReactNode[] = [];
|
const rendered: React.ReactNode[] = [];
|
||||||
@@ -1152,7 +1154,7 @@ const AssistantMessageBody: React.FC<Omit<MessageBodyProps, 'isUser'>> = ({
|
|||||||
|
|
||||||
if (part.type === 'text') {
|
if (part.type === 'text') {
|
||||||
const activity = activityByPart.get(part);
|
const activity = activityByPart.get(part);
|
||||||
if (isSortedRenderMode && !hasStopFinish) {
|
if (shouldDeferSortedInlineText) {
|
||||||
i += 1;
|
i += 1;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -1177,10 +1179,6 @@ const AssistantMessageBody: React.FC<Omit<MessageBodyProps, 'isUser'>> = ({
|
|||||||
|
|
||||||
if (part.type === 'reasoning') {
|
if (part.type === 'reasoning') {
|
||||||
const activity = activityByPart.get(part);
|
const activity = activityByPart.get(part);
|
||||||
if (isSortedRenderMode && !hasStopFinish) {
|
|
||||||
i += 1;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (activity?.kind === 'reasoning') {
|
if (activity?.kind === 'reasoning') {
|
||||||
i += 1;
|
i += 1;
|
||||||
continue;
|
continue;
|
||||||
@@ -1293,7 +1291,6 @@ const AssistantMessageBody: React.FC<Omit<MessageBodyProps, 'isUser'>> = ({
|
|||||||
chatRenderMode,
|
chatRenderMode,
|
||||||
collapsedPreviewCount,
|
collapsedPreviewCount,
|
||||||
expandedTools,
|
expandedTools,
|
||||||
hasStopFinish,
|
|
||||||
isMobile,
|
isMobile,
|
||||||
isActivityOwnerMessage,
|
isActivityOwnerMessage,
|
||||||
isSortedRenderMode,
|
isSortedRenderMode,
|
||||||
@@ -1306,6 +1303,7 @@ const AssistantMessageBody: React.FC<Omit<MessageBodyProps, 'isUser'>> = ({
|
|||||||
shouldShowTool,
|
shouldShowTool,
|
||||||
streamPhase,
|
streamPhase,
|
||||||
showReasoningTraces,
|
showReasoningTraces,
|
||||||
|
shouldDeferSortedInlineText,
|
||||||
syntaxTheme,
|
syntaxTheme,
|
||||||
toggleActivityGroup,
|
toggleActivityGroup,
|
||||||
turnGroupingContext,
|
turnGroupingContext,
|
||||||
|
|||||||
Reference in New Issue
Block a user