Refine WebUI commands and data skills

This commit is contained in:
武阳
2026-05-06 19:35:48 +08:00
parent 93a0eb74d7
commit a0da3c2423
13 changed files with 731 additions and 219 deletions
@@ -337,6 +337,9 @@ export function toReplayRepository(
): ExportedMessageRepository {
const messages: ExportedMessageRepository["messages"] = [];
const toolResults = collectToolResults(session.messages ?? []);
const lastAssistantContentIndex = findLastAssistantContentIndex(
session.messages ?? [],
);
let previousId: string | null = null;
for (const [index, message] of (session.messages ?? []).entries()) {
if (message.role === "tool") continue;
@@ -347,26 +350,17 @@ export function toReplayRepository(
const id = `${fallbackSessionId}-${index}`;
const toolParts =
message.role === "assistant"
? toToolParts(message.tool_calls ?? [], toolResults)
: [];
const elapsedMs =
typeof message.metadata?.elapsed_ms === "number"
? message.metadata.elapsed_ms
: undefined;
const reasoningParts =
message.role === "assistant" &&
(toolParts.length > 0 || elapsedMs !== undefined)
? [
{
type: "reasoning",
text: formatHistoricalReasoning(toolParts.length, elapsedMs),
},
]
? toToolParts(message.tool_calls ?? [], toolResults, content)
: [];
const shouldShowAssistantText =
message.role !== "assistant" ||
!toolParts.length ||
index === lastAssistantContentIndex;
const parts = (
message.role === "assistant" &&
(reasoningParts.length || toolParts.length)
? [...reasoningParts, ...toolParts, { type: "text", text: content }]
message.role === "assistant" && toolParts.length
? shouldShowAssistantText
? [...toolParts, { type: "text", text: content }]
: toolParts
: [{ type: "text", text: content }]
) as UIMessage["parts"];
const uiMessage: UIMessage = {
@@ -405,6 +399,17 @@ export function toReplayRepository(
};
}
function findLastAssistantContentIndex(messages: readonly ClawStoredMessage[]) {
for (let index = messages.length - 1; index >= 0; index -= 1) {
const message = messages[index];
if (message?.role !== "assistant") continue;
const content = cleanStoredContent(message.content ?? "");
if (!content) continue;
return index;
}
return -1;
}
function cleanStoredContent(content: string) {
const markers = ["\n\n[当前会话目录]", "\n[当前会话目录]"];
for (const marker of markers) {
@@ -426,12 +431,16 @@ function collectToolResults(messages: readonly ClawStoredMessage[]) {
function toToolParts(
toolCalls: readonly ClawStoredToolCall[],
toolResults: Map<string, string>,
stageNote?: string,
) {
return toolCalls.flatMap((call) => {
const toolCallId = call.id;
const toolName = call.function?.name ?? call.name;
if (!toolCallId || !toolName) return [];
const input = parseToolInput(call.function?.arguments ?? call.arguments);
const input = attachStageNote(
parseToolInput(call.function?.arguments ?? call.arguments),
stageNote,
);
const output = toolResults.get(toolCallId);
return [
{
@@ -446,16 +455,13 @@ function toToolParts(
});
}
function formatHistoricalReasoning(toolCount: number, elapsedMs?: number) {
const duration =
elapsedMs === undefined ? "" : `,用时 ${formatDuration(elapsedMs)}`;
if (toolCount <= 0) return `历史记录:思考完成${duration}`;
return `历史记录:本轮调用了 ${toolCount} 个工具${duration}`;
}
function formatDuration(ms: number) {
if (ms < 1000) return `${ms}ms`;
return `${(ms / 1000).toFixed(1)}s`;
function attachStageNote(input: unknown, stageNote?: string) {
const note = stageNote?.trim();
if (!note) return input;
if (input && typeof input === "object" && !Array.isArray(input)) {
return { ...input, __claw_stage_note: note };
}
return { value: input, __claw_stage_note: note };
}
function toThreadMessageContent(parts: UIMessage["parts"]) {