Refine chat tool status dedupe (#786)
Co-authored-by: zuolingxuan <zuolingxuan@bytedance.com>
This commit is contained in:
committed by
GitHub
Unverified
parent
32d14b8cf9
commit
25b13ab912
@@ -18,6 +18,7 @@ interface ChatMessageProps {
|
||||
message: RawMessage;
|
||||
showThinking: boolean;
|
||||
suppressToolCards?: boolean;
|
||||
suppressProcessAttachments?: boolean;
|
||||
isStreaming?: boolean;
|
||||
streamingTools?: Array<{
|
||||
id?: string;
|
||||
@@ -42,6 +43,7 @@ export const ChatMessage = memo(function ChatMessage({
|
||||
message,
|
||||
showThinking,
|
||||
suppressToolCards = false,
|
||||
suppressProcessAttachments = false,
|
||||
isStreaming = false,
|
||||
streamingTools = [],
|
||||
}: ChatMessageProps) {
|
||||
@@ -55,8 +57,12 @@ export const ChatMessage = memo(function ChatMessage({
|
||||
const tools = extractToolUse(message);
|
||||
const visibleThinking = showThinking ? thinking : null;
|
||||
const visibleTools = suppressToolCards ? [] : tools;
|
||||
const shouldHideProcessAttachments = suppressProcessAttachments
|
||||
&& (hasText || !!visibleThinking || images.length > 0 || visibleTools.length > 0);
|
||||
|
||||
const attachedFiles = message._attachedFiles || [];
|
||||
const attachedFiles = shouldHideProcessAttachments
|
||||
? (message._attachedFiles || []).filter((file) => file.source !== 'tool-result')
|
||||
: (message._attachedFiles || []);
|
||||
const [lightboxImg, setLightboxImg] = useState<{ src: string; fileName: string; filePath?: string; base64?: string; mimeType?: string } | null>(null);
|
||||
|
||||
// Never render tool result messages in chat UI
|
||||
|
||||
@@ -221,7 +221,7 @@ export function Chat() {
|
||||
active: isLatestOpenRun,
|
||||
agentLabel: segmentAgentLabel,
|
||||
sessionLabel: segmentSessionLabel,
|
||||
segmentEnd: replyIndex ?? (nextUserIndex === -1 ? messages.length - 1 : nextUserIndex - 1),
|
||||
segmentEnd: nextUserIndex === -1 ? messages.length - 1 : nextUserIndex - 1,
|
||||
steps,
|
||||
}];
|
||||
});
|
||||
@@ -257,6 +257,7 @@ export function Chat() {
|
||||
message={msg}
|
||||
showThinking={showThinking}
|
||||
suppressToolCards={suppressToolCards}
|
||||
suppressProcessAttachments={suppressToolCards}
|
||||
/>
|
||||
{userRunCards
|
||||
.filter((card) => card.triggerIndex === idx)
|
||||
|
||||
@@ -166,23 +166,64 @@ export function deriveTaskSteps({
|
||||
showThinking,
|
||||
}: DeriveTaskStepsInput): TaskStep[] {
|
||||
const steps: TaskStep[] = [];
|
||||
const seenIds = new Set<string>();
|
||||
const activeToolNames = new Set<string>();
|
||||
const stepIndexById = new Map<string, number>();
|
||||
|
||||
const pushStep = (step: TaskStep): void => {
|
||||
if (seenIds.has(step.id)) return;
|
||||
seenIds.add(step.id);
|
||||
steps.push(step);
|
||||
const upsertStep = (step: TaskStep): void => {
|
||||
const existingIndex = stepIndexById.get(step.id);
|
||||
if (existingIndex == null) {
|
||||
stepIndexById.set(step.id, steps.length);
|
||||
steps.push(step);
|
||||
return;
|
||||
}
|
||||
const existing = steps[existingIndex];
|
||||
steps[existingIndex] = {
|
||||
...existing,
|
||||
...step,
|
||||
detail: step.detail ?? existing.detail,
|
||||
};
|
||||
};
|
||||
|
||||
const streamMessage = streamingMessage && typeof streamingMessage === 'object'
|
||||
? streamingMessage as RawMessage
|
||||
: null;
|
||||
|
||||
const relevantAssistantMessages = messages.filter((message) => {
|
||||
if (!message || message.role !== 'assistant') return false;
|
||||
if (extractToolUse(message).length > 0) return true;
|
||||
return showThinking && !!extractThinking(message);
|
||||
});
|
||||
|
||||
for (const [messageIndex, assistantMessage] of relevantAssistantMessages.entries()) {
|
||||
if (showThinking) {
|
||||
const thinking = extractThinking(assistantMessage);
|
||||
if (thinking) {
|
||||
upsertStep({
|
||||
id: `history-thinking-${assistantMessage.id || messageIndex}`,
|
||||
label: 'Thinking',
|
||||
status: 'completed',
|
||||
kind: 'thinking',
|
||||
detail: normalizeText(thinking),
|
||||
depth: 1,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
extractToolUse(assistantMessage).forEach((tool, index) => {
|
||||
upsertStep({
|
||||
id: tool.id || makeToolId(`history-tool-${assistantMessage.id || messageIndex}`, tool.name, index),
|
||||
label: tool.name,
|
||||
status: 'completed',
|
||||
kind: 'tool',
|
||||
detail: normalizeText(JSON.stringify(tool.input, null, 2)),
|
||||
depth: 1,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
if (streamMessage && showThinking) {
|
||||
const thinking = extractThinking(streamMessage);
|
||||
if (thinking) {
|
||||
pushStep({
|
||||
upsertStep({
|
||||
id: 'stream-thinking',
|
||||
label: 'Thinking',
|
||||
status: 'running',
|
||||
@@ -193,10 +234,16 @@ export function deriveTaskSteps({
|
||||
}
|
||||
}
|
||||
|
||||
const activeToolIds = new Set<string>();
|
||||
const activeToolNamesWithoutIds = new Set<string>();
|
||||
streamingTools.forEach((tool, index) => {
|
||||
activeToolNames.add(tool.name);
|
||||
pushStep({
|
||||
id: tool.toolCallId || tool.id || makeToolId('stream-status', tool.name, index),
|
||||
const id = tool.toolCallId || tool.id || makeToolId('stream-status', tool.name, index);
|
||||
activeToolIds.add(id);
|
||||
if (!tool.toolCallId && !tool.id) {
|
||||
activeToolNamesWithoutIds.add(tool.name);
|
||||
}
|
||||
upsertStep({
|
||||
id,
|
||||
label: tool.name,
|
||||
status: tool.status,
|
||||
kind: 'tool',
|
||||
@@ -207,9 +254,10 @@ export function deriveTaskSteps({
|
||||
|
||||
if (streamMessage) {
|
||||
extractToolUse(streamMessage).forEach((tool, index) => {
|
||||
if (activeToolNames.has(tool.name)) return;
|
||||
pushStep({
|
||||
id: tool.id || makeToolId('stream-tool', tool.name, index),
|
||||
const id = tool.id || makeToolId('stream-tool', tool.name, index);
|
||||
if (activeToolIds.has(id) || activeToolNamesWithoutIds.has(tool.name)) return;
|
||||
upsertStep({
|
||||
id,
|
||||
label: tool.name,
|
||||
status: 'running',
|
||||
kind: 'tool',
|
||||
@@ -220,59 +268,27 @@ export function deriveTaskSteps({
|
||||
}
|
||||
|
||||
if (sending && pendingFinal) {
|
||||
pushStep({
|
||||
id: 'system-finalizing',
|
||||
label: 'Finalizing answer',
|
||||
status: 'running',
|
||||
upsertStep({
|
||||
id: 'system-finalizing',
|
||||
label: 'Finalizing answer',
|
||||
status: 'running',
|
||||
kind: 'system',
|
||||
detail: 'Waiting for the assistant to finish this run.',
|
||||
depth: 1,
|
||||
});
|
||||
} else if (sending && steps.length === 0) {
|
||||
pushStep({
|
||||
id: 'system-preparing',
|
||||
label: 'Preparing run',
|
||||
status: 'running',
|
||||
upsertStep({
|
||||
id: 'system-preparing',
|
||||
label: 'Preparing run',
|
||||
status: 'running',
|
||||
kind: 'system',
|
||||
detail: 'Waiting for the first streaming update.',
|
||||
depth: 1,
|
||||
});
|
||||
}
|
||||
|
||||
if (steps.length === 0) {
|
||||
const relevantAssistantMessages = messages.filter((message) => {
|
||||
if (!message || message.role !== 'assistant') return false;
|
||||
if (extractToolUse(message).length > 0) return true;
|
||||
return showThinking && !!extractThinking(message);
|
||||
});
|
||||
|
||||
for (const [messageIndex, assistantMessage] of relevantAssistantMessages.entries()) {
|
||||
if (showThinking) {
|
||||
const thinking = extractThinking(assistantMessage);
|
||||
if (thinking) {
|
||||
pushStep({
|
||||
id: `history-thinking-${assistantMessage.id || messageIndex}`,
|
||||
label: 'Thinking',
|
||||
status: 'completed',
|
||||
kind: 'thinking',
|
||||
detail: normalizeText(thinking),
|
||||
depth: 1,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
extractToolUse(assistantMessage).forEach((tool, index) => {
|
||||
pushStep({
|
||||
id: tool.id || makeToolId(`history-tool-${assistantMessage.id || messageIndex}`, tool.name, index),
|
||||
label: tool.name,
|
||||
status: 'completed',
|
||||
kind: 'tool',
|
||||
detail: normalizeText(JSON.stringify(tool.input, null, 2)),
|
||||
depth: 1,
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return attachTopology(steps).slice(0, MAX_TASK_STEPS);
|
||||
const withTopology = attachTopology(steps);
|
||||
return withTopology.length > MAX_TASK_STEPS
|
||||
? withTopology.slice(-MAX_TASK_STEPS)
|
||||
: withTopology;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user