| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109 |
- import type { Task, Message, CapsuleFileItem, CapsuleLinkItem, CapsuleAgentItem } from './types';
- import { extractMarkdownLinks, groupStepsByAgent } from './utils';
- // 胶囊聚合数据构建(纯函数):由主弹框的 computed 接线调用,
- // 与响应式状态管理解耦,便于单独测试
- // 聚合文件:当前对话下用户上传的附件 + AI 返回的下载链接(含 Word 报告,按 url 去重)
- export const buildCapsuleFiles = (task: Task | undefined, messages: Message[]): CapsuleFileItem[] => {
- if (!task) return [];
- const items: CapsuleFileItem[] = [];
- const seenUrls = new Set<string>();
- task.attachedFiles.forEach((f) => {
- items.push({ kind: 'upload', name: f.name, size: f.size, file: f });
- });
- for (const msg of messages) {
- if (msg.type !== 'ai') continue;
- msg.downloadFiles?.forEach((d) => {
- if (!d.url || seenUrls.has(d.url)) return;
- seenUrls.add(d.url);
- items.push({ kind: 'download', name: d.filename || '文件', url: d.url });
- });
- if (msg.wordDownloadUrl && !seenUrls.has(msg.wordDownloadUrl)) {
- seenUrls.add(msg.wordDownloadUrl);
- items.push({ kind: 'download', name: '报告文件.docx', url: msg.wordDownloadUrl });
- }
- }
- if (task.wordUrl && !seenUrls.has(task.wordUrl)) {
- seenUrls.add(task.wordUrl);
- items.push({ kind: 'download', name: '报告文件.docx', url: task.wordUrl });
- }
- return items;
- };
- // 聚合链接:AI 回复文本中的可跳转链接(与消息内 markdown 渲染共用同一套解析,按 href 去重)。
- // 已在"文件"区展示的下载地址不再重复列入链接区;
- // 除精确匹配外,还按"路径+查询+哈希"匹配,覆盖正文相对路径与后端完整地址写法不一致的情况
- export const buildCapsuleLinks = (files: CapsuleFileItem[], messages: Message[]): CapsuleLinkItem[] => {
- const normalize = (u: string) => u.trim().replace(/&/g, '&');
- const pathKey = (u: string): string => {
- try {
- const parsed = new URL(u, window.location.href);
- return `${parsed.pathname}${parsed.search}${parsed.hash}`;
- } catch {
- return '';
- }
- };
- const seenDownload = new Set<string>();
- const seenDownloadPath = new Set<string>();
- for (const item of files) {
- if (!item.url) continue;
- const href = normalize(item.url);
- seenDownload.add(href);
- const key = pathKey(href);
- if (key) seenDownloadPath.add(key);
- }
- const seenLink = new Set<string>();
- const links: CapsuleLinkItem[] = [];
- for (const msg of messages) {
- if (msg.type !== 'ai' || !msg.content) continue;
- for (const link of extractMarkdownLinks(msg.content)) {
- const href = normalize(link.href);
- if (!href || seenLink.has(href)) continue;
- const key = pathKey(href);
- if (seenDownload.has(href) || (key && seenDownloadPath.has(key))) continue;
- seenLink.add(href);
- links.push({ title: link.title, href });
- }
- }
- return links;
- };
- // 聚合智能体:thinking 卡片中的智能体调用记录,按(消息、智能体)聚合,
- // 每次调用一个条目、状态独立(start→done 生命周期),条目身份与右侧详情面板对齐。
- // 仅收录有实际执行活动的组(含 start/done/工具调用/执行中),纯思考 token 的"系统"组不进入。
- // isAnswerVisible 由调用方传入(多回答折叠组中该消息是否为当前显示的回答)
- export const buildCapsuleAgents = (messages: Message[], isAnswerVisible?: (index: number) => boolean | undefined): CapsuleAgentItem[] => {
- const agents: CapsuleAgentItem[] = [];
- for (let i = 0; i < messages.length; i++) {
- const msg = messages[i];
- if (msg.type !== 'ai' || !msg.thinkingSteps?.length) continue;
- // 该消息所属问答轮次:数到当前消息为止(含本轮提问)的用户消息条数,
- // 无前置提问的 AI 消息兜底为第 1 轮
- let round = 0;
- for (let j = 0; j <= i; j++) {
- if (messages[j].type === 'user') round++;
- }
- round = Math.max(1, round);
- for (const group of groupStepsByAgent(msg.thinkingSteps)) {
- const hasActivity = group.steps.some((s) => s.type !== 'token');
- if (!hasActivity) continue;
- const done = group.steps.some((s) => s.type === 'agent_done');
- const toolCount = group.steps.filter((s) => s.type === 'tool_call').length;
- // 状态优先级:完成 > 审批等待(暂停) > 流式中 > 已中断
- const status: CapsuleAgentItem['status'] = done ? 'done' : msg.isPendingApproval ? 'waiting' : msg.isLoading ? 'running' : 'interrupted';
- agents.push({
- messageIndex: i,
- agent: group.agent,
- agentId: group.agentId,
- status,
- round,
- toolCount,
- stepCount: group.steps.length,
- // 多回答折叠组中被隐藏的历史回答(重新生成产生),供胶囊面板标注
- hiddenAnswer: isAnswerVisible ? isAnswerVisible(i) === false : false,
- });
- }
- }
- return agents;
- };
|