assistant/renderer/transcript.ts

81 lines
3.5 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { STATUS_LABEL, type ChatMessage, type Task, type TaskStatus } from './types';
interface ProgressBlock {
ts: string;
title: string;
status: string;
detail: string;
}
let counter = 0;
export function nextMessageId(prefix = 'm'): string {
counter += 1;
return `${prefix}-${Date.now()}-${counter}`;
}
/** 解析 progress.md 的事件块:### 图标 [时间] 标题 / - **状态**:x / - **详情**:y */
export function parseProgressBlocks(content: string): ProgressBlock[] {
const blocks: ProgressBlock[] = [];
let current: ProgressBlock | null = null;
for (const line of content.split('\n')) {
const heading = line.match(/^### (\S+) \[(.+?)\] (.+)$/);
if (heading) {
current = { ts: heading[2], title: heading[3].trim(), status: '', detail: '' };
blocks.push(current);
continue;
}
if (line.startsWith('## ') || line.startsWith('---')) {
current = null;
continue;
}
if (!current) continue;
const status = line.match(/^- \*\*状态\*\*:(.*)$/);
const detail = line.match(/^- \*\*详情\*\*:(.*)$/);
if (status) current.status = status[1].trim();
else if (detail) current.detail = detail[1].trim();
else if (current.detail && line.trim() && !line.startsWith('#')) current.detail += `\n${line}`;
}
return blocks;
}
function labelOf(status: string): string {
return (STATUS_LABEL as Record<string, string>)[status] ?? status;
}
/** 用进度文档把一个历史任务回放成对话。 */
export function buildTranscript(task: Task, content: string | null): ChatMessage[] {
const messages: ChatMessage[] = [];
const push = (role: ChatMessage['role'], text: string): void => {
if (text.trim()) messages.push({ id: nextMessageId('t'), role, text });
};
if (task.inputs.goal) push('user', task.inputs.goal);
if (!content) return messages;
for (const block of parseProgressBlocks(content)) {
if (/^步骤 \d+/.test(block.title)) continue;
if (block.title.startsWith('跟踪回查')) {
if (/^(状态|审批)/.test(block.detail)) push('system', `${block.title}:${block.detail}`);
continue;
}
if (block.title === '提交后跟踪' || block.title === '切换前置事项' || block.title === '用户接管' || block.title === '接管结束') {
push('system', block.detail);
continue;
}
if (block.title === '识别业务') push('system', `识别业务:${block.detail}`);
else if (block.title === '角色准入') push('assistant', block.detail);
else if (block.title === '收集资料') push('assistant', `请补充:${block.detail.replace(/^缺少:/, '')}`);
else if (block.title === '用户补充资料' || block.title === '用户回答') push('user', block.detail.replace(/(抽取:.*)$/, ''));
else if (block.title === '等待用户' || block.title === '前置阻断') {
if (block.status === 'paused') push('assistant', block.detail);
else if (block.status === 'blocked') push('system', block.detail);
} else if (block.status === 'completed' || block.status === 'submitted') push('assistant', `${labelOf(block.status)}\n\n${block.detail}`);
else if (block.status === 'failed') push('system', `办理未完成:${block.detail}`);
else if (block.status === 'rejected') push('assistant', block.detail);
else if (block.title === '沉淀草稿') push('system', `沉淀:${block.detail.split('(')[0]}`);
}
return messages;
}
export function isTerminal(status: TaskStatus): boolean {
return ['submitted', 'completed', 'failed', 'blocked', 'rejected'].includes(status);
}