81 lines
3.5 KiB
TypeScript
81 lines
3.5 KiB
TypeScript
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);
|
||
}
|