feat: improve conversation timeline and agent plans
This commit is contained in:
@@ -1472,14 +1472,20 @@
|
||||
const title = String(event.title || '').replace(/\s+/g, ' ').trim().slice(0, 120);
|
||||
const changedAtMs = Date.parse(String(event.changedAt || ''));
|
||||
const messageIndex = Number(event.messageIndex);
|
||||
const anchorMessageIndex = event.anchorMessageIndex;
|
||||
const source = String(event.source || '').trim().toLowerCase();
|
||||
if (!title || !Number.isFinite(changedAtMs) || !Number.isFinite(messageIndex) || messageIndex < 0 || source !== 'llm') return;
|
||||
normalized.push({
|
||||
const normalizedMessageIndex = Math.trunc(messageIndex);
|
||||
const normalizedEvent = {
|
||||
title,
|
||||
changedAt: new Date(changedAtMs).toISOString(),
|
||||
messageIndex: Math.trunc(messageIndex),
|
||||
messageIndex: normalizedMessageIndex,
|
||||
source: 'llm',
|
||||
});
|
||||
};
|
||||
if (Number.isInteger(anchorMessageIndex) && anchorMessageIndex >= 0 && anchorMessageIndex < normalizedMessageIndex) {
|
||||
normalizedEvent.anchorMessageIndex = anchorMessageIndex;
|
||||
}
|
||||
normalized.push(normalizedEvent);
|
||||
});
|
||||
return normalized.slice(-100);
|
||||
}
|
||||
@@ -1493,10 +1499,11 @@
|
||||
|
||||
function buildUserOutlineTimelineItems(messageItems, titleHistory = []) {
|
||||
const timeline = [];
|
||||
const timelineMessages = [];
|
||||
(Array.isArray(messageItems) ? messageItems : []).forEach((message, order) => {
|
||||
const messageIndex = Number(message.messageIndex);
|
||||
const timestampMs = Date.parse(String(message.timestamp || ''));
|
||||
timeline.push({
|
||||
const timelineMessage = {
|
||||
...message,
|
||||
type: 'message',
|
||||
messageNumber: order + 1,
|
||||
@@ -1504,17 +1511,31 @@
|
||||
sortPosition: Number.isFinite(messageIndex) && messageIndex >= 0 ? messageIndex * 2 : null,
|
||||
sortTime: Number.isFinite(timestampMs) ? timestampMs : null,
|
||||
sortOrder: order,
|
||||
});
|
||||
};
|
||||
timelineMessages.push(timelineMessage);
|
||||
timeline.push(timelineMessage);
|
||||
});
|
||||
normalizeOutlineTitleHistory(titleHistory).forEach((event, order) => {
|
||||
const timestampMs = Date.parse(event.changedAt);
|
||||
const exactAnchor = Number.isInteger(event.anchorMessageIndex)
|
||||
? timelineMessages.find((message) => message.messageIndex === event.anchorMessageIndex) || null
|
||||
: null;
|
||||
const precedingAnchor = timelineMessages.reduce((latest, message) => {
|
||||
if (!Number.isFinite(message.messageIndex) || message.messageIndex >= event.messageIndex) return latest;
|
||||
if (!latest || message.messageIndex > latest.messageIndex) return message;
|
||||
return latest;
|
||||
}, null);
|
||||
const anchor = exactAnchor || precedingAnchor;
|
||||
const anchorMessageIndex = Number.isFinite(anchor?.messageIndex) ? anchor.messageIndex : null;
|
||||
timeline.push({
|
||||
type: 'title',
|
||||
label: event.title,
|
||||
timestamp: event.changedAt,
|
||||
timestamp: anchor?.timestamp || event.changedAt,
|
||||
changedAt: event.changedAt,
|
||||
messageIndex: event.messageIndex,
|
||||
...(anchorMessageIndex !== null ? { anchorMessageIndex } : {}),
|
||||
source: event.source,
|
||||
sortPosition: event.messageIndex * 2 - 1,
|
||||
sortPosition: anchorMessageIndex !== null ? anchorMessageIndex * 2 - 1 : event.messageIndex * 2 - 1,
|
||||
sortTime: Number.isFinite(timestampMs) ? timestampMs : null,
|
||||
sortOrder: order,
|
||||
});
|
||||
@@ -7475,6 +7496,45 @@
|
||||
];
|
||||
}
|
||||
|
||||
function markMergedCollabAgentElement(element, tools, mergedTool = null) {
|
||||
if (!element) return element;
|
||||
const map = tools instanceof Map ? tools : new Map();
|
||||
if (!(tools instanceof Map)) {
|
||||
const list = Array.isArray(tools) ? tools : [];
|
||||
list.forEach((tool, index) => {
|
||||
if (toolKind(tool) !== 'collab_agent_tool_call') return;
|
||||
const key = String(tool?.id || `collab-${index + 1}`);
|
||||
map.set(key, tool);
|
||||
});
|
||||
}
|
||||
const displayTool = mergedTool || mergeCollabAgentTools(Array.from(map.values()));
|
||||
element.dataset.collabMerged = 'true';
|
||||
if (displayTool?.id) element.dataset.toolUseId = String(displayTool.id);
|
||||
element.dataset.childIds = displayTool ? getCollabAgentIdsFromTool(displayTool).join(',') : '';
|
||||
element.__collabTools = map;
|
||||
return element;
|
||||
}
|
||||
|
||||
function collabAgentToolElementMatches(element, toolUseId) {
|
||||
if (!element || !toolUseId) return false;
|
||||
const targetId = String(toolUseId);
|
||||
if (String(element.dataset?.toolUseId || '') === targetId) return true;
|
||||
const map = element.__collabTools;
|
||||
if (!(map instanceof Map)) return false;
|
||||
if (map.has(targetId)) return true;
|
||||
return Array.from(map.keys()).some((key) => String(key) === targetId);
|
||||
}
|
||||
|
||||
function findCollabAgentToolElement(toolUseId, preferredElement = null) {
|
||||
if (preferredElement?.isConnected && collabAgentToolElementMatches(preferredElement, toolUseId)) {
|
||||
return preferredElement;
|
||||
}
|
||||
return findLatestToolCallElement(messagesDiv, (candidate) => (
|
||||
candidate.dataset?.collabMerged === 'true'
|
||||
&& collabAgentToolElementMatches(candidate, toolUseId)
|
||||
));
|
||||
}
|
||||
|
||||
function collabStateTone(statusText) {
|
||||
const normalized = String(statusText || '').toLowerCase();
|
||||
if (!normalized) return 'pending';
|
||||
@@ -7722,10 +7782,14 @@
|
||||
const toolMount = bubble.querySelector(':scope > .cross-conversation-reply-body') || bubble;
|
||||
const FOLD_AT = 3;
|
||||
let grouped = false;
|
||||
const savedCollabTools = m.toolCalls.filter((tc) => toolKind(tc) === 'collab_agent_tool_call');
|
||||
const renderToolCalls = renderToolCallsWithMergedCollab(m.toolCalls);
|
||||
for (const tc of renderToolCalls) {
|
||||
if (isEmptyReasoningTool(tc)) continue;
|
||||
const details = createToolCallElement(tc.id || `saved-${Math.random().toString(36).slice(2)}`, tc, true);
|
||||
if (toolKind(tc) === 'collab_agent_tool_call') {
|
||||
markMergedCollabAgentElement(details, savedCollabTools, tc);
|
||||
}
|
||||
|
||||
// 散落的 .tool-call 达到 FOLD_AT 个时,移入唯一 .tool-group
|
||||
const loose = Array.from(toolMount.children).filter(isGroupableToolCall);
|
||||
@@ -8151,18 +8215,14 @@
|
||||
if (!merged) return existing;
|
||||
|
||||
if (existing) {
|
||||
existing.__collabTools = map;
|
||||
existing.dataset.toolUseId = merged.id || existing.dataset.toolUseId || '';
|
||||
existing.dataset.childIds = getCollabAgentIdsFromTool(merged).join(',');
|
||||
markMergedCollabAgentElement(existing, map, merged);
|
||||
existing.replaceChildren(buildToolContentElement(merged));
|
||||
removeDuplicateCollabAgentNodes(toolsDiv);
|
||||
return existing;
|
||||
}
|
||||
|
||||
const el = createToolCallElement(merged.id, merged, !!merged.done);
|
||||
el.dataset.collabMerged = 'true';
|
||||
el.dataset.childIds = getCollabAgentIdsFromTool(merged).join(',');
|
||||
el.__collabTools = map;
|
||||
markMergedCollabAgentElement(el, map, merged);
|
||||
toolsDiv.appendChild(el);
|
||||
removeDuplicateCollabAgentNodes(toolsDiv);
|
||||
return el;
|
||||
@@ -8270,14 +8330,16 @@
|
||||
function updateToolCall(toolUseId, result, done = true) {
|
||||
const tool = activeToolCalls.get(toolUseId) || null;
|
||||
const toolUseIdText = toolUseId ? String(toolUseId) : '';
|
||||
const scope = getLatestAssistantToolScope();
|
||||
if (toolKind(tool) === 'collab_agent_tool_call') {
|
||||
const existingCollabElement = findCollabAgentToolElement(toolUseId, tool?.domElement);
|
||||
const scope = existingCollabElement?.parentElement || getLatestAssistantToolScope();
|
||||
const toolsDiv = scope?.querySelector?.('.msg-tools') || scope?.querySelector?.('.msg-bubble') || scope;
|
||||
const nextTool = { ...tool, result, done };
|
||||
const el = upsertCollabAgentToolCall(toolsDiv, toolUseId, nextTool, done);
|
||||
if (el) rememberToolCallTarget(toolUseId, nextTool, el);
|
||||
return;
|
||||
}
|
||||
const scope = getLatestAssistantToolScope();
|
||||
let el = tool?.domElement && tool.domElement.isConnected ? tool.domElement : null;
|
||||
|
||||
if (!el) {
|
||||
@@ -8363,6 +8425,7 @@
|
||||
});
|
||||
|
||||
if (!isCurrentSessionUpdate) return;
|
||||
const previousTool = activeToolCalls.get(toolUseId) || null;
|
||||
activeToolCalls.set(toolUseId, {
|
||||
id: toolUseId,
|
||||
name: tool.name,
|
||||
@@ -8371,6 +8434,7 @@
|
||||
kind: tool.kind || null,
|
||||
meta: tool.meta || null,
|
||||
done: !!tool.done,
|
||||
domElement: previousTool?.domElement || null,
|
||||
});
|
||||
updateToolCall(toolUseId, tool.result, !!tool.done);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user