// consoleDataAdapter.ts — API 类型 → 业务 ViewModel 映射 // API 类型参考: src/types/api.ts (DebugStoreResponse, DebugPlan, SkillSpec, etc.) // ── View Models ── export interface DashboardOverview { generatedAt: string; isPartial: boolean; sources: DataSourceStatus[]; taskTotals: { total: number; queued: number; running: number; succeeded: number; failed: number }; approvals: { total: number; pending: number }; health: { activeTasks: number; failedTasks: number; pendingConfirmations: number }; } export interface Mission { id: string; title: string; objective: string; status: string; createdAt: string | null; updatedAt: string | null; steps: MissionStep[]; sourceIds: { planId?: string; taskIds: string[] }; } export interface MissionStep { id: string; title: string; description?: string; status: string; preferredSkills: string[]; tasks: Array<{ id: string; title: string; description?: string; status: string; assignedSkillId: string | null; startedAt: string | null; finishedAt: string | null; }>; } export interface ApprovalItem { id: string; missionId: string | null; taskId: string | null; status: string; title: string; createdAt: string | null; reversible: boolean; } export interface JournalEntry { id: string; kind: string; source: string; message: string; occurredAt: string | null; planId: string | null; taskId: string | null; sourceLink?: string; } export interface SkillSummary { id: string; name: string; version: string | null; enabled: boolean; description: string; category: string; capabilityLevel: 'core' | 'standard' | 'specialized' | 'experimental'; source: 'skill-api'; } export interface DataSourceStatus { endpoint: string; ok: boolean; loadedAt?: string; errorMessage?: string; } // ── 内部 helper ── type Obj = Record; function arr(v: unknown): Obj[] { return Array.isArray(v) ? (v as Obj[]) : []; } function str(v: unknown, fallback = ''): string { if (typeof v === 'string') return v; if (typeof v === 'number') return String(v); return fallback; } function hasChinese(v: string): boolean { return /[\u4e00-\u9fff]/.test(v); } const STATUS_ZH: Record = { queued: '排队中', ready: '就绪', idle: '待执行', pending: '待处理', pending_authorization: '待授权', waiting_input: '待输入', draft: '草稿', reviewing: '审核中', approved: '已批准', preparing: '准备中', running: '执行中', active: '运行中', blocked: '已阻塞', succeeded: '已完成', success: '成功', finished: '已完成', complete: '已完成', completed: '已完成', failed: '失败', error: '错误', cancelled: '已取消', canceled: '已取消', aborted: '已终止', rejected: '已拒绝', deferred: '已暂缓', dispatched: '已下发', scheduled: '已设置定时', enabled: '已启用', disabled: '已停用', paused: '已暂停', unknown: '未知', }; const EVENT_KIND_ZH: Record = { plan: '计划', task: '任务', task_running: '处理中', task_done: '任务完成', run: '执行开始', run_done: '执行完成', skill_tool: '技能调用', artifact: '产物', task_summary: '结果', entered_session: '会话', start: '目标', RunStarted: '开始调用', RequestDispatched: '请求已发出', ResponseReceived: '收到返回', RunSkipped: '本次跳过', RunCompleted: '执行完成', waiting_for_authorization: '等待授权', pending_authorization: '等待授权', authorization_approved: '授权通过', authorization_rejected: '授权拒绝', authorization_deferred: '稍后处理', failed: '执行失败', completed: '执行完成', }; export function displayStatus(status: unknown, fallback = '未知'): string { const raw = str(status).trim(); if (!raw) return fallback; const mapped = STATUS_ZH[raw] ?? STATUS_ZH[raw.toLowerCase()]; if (mapped) return mapped; return hasChinese(raw) ? raw : fallback; } export function displayEventKind(kind: unknown): string { const raw = str(kind).trim(); if (!raw) return '事件'; return EVENT_KIND_ZH[raw] ?? EVENT_KIND_ZH[raw.toLowerCase()] ?? (hasChinese(raw) ? raw : '事件'); } export function displayBackendMessage(message: unknown, fallback = '暂无事件内容'): string { const raw = str(message).trim(); if (!raw) return fallback; if (hasChinese(raw)) return raw; const status = displayStatus(raw, ''); if (status) return status; const attempt = raw.match(/^(Succeeded|Failed|Running|Completed)\s*\(attempt\s*#(\d+)\)$/i); if (attempt) { return `${displayStatus(attempt[1])}(第 ${attempt[2]} 次)`; } if (/request failed/i.test(raw)) return '请求失败'; if (/backend unavailable/i.test(raw)) return '后端服务不可达'; if (/not found/i.test(raw)) return '未找到对应资源'; return fallback; } export function displayBusinessText(value: unknown, fallback = '未命名内容'): string { const raw = str(value).trim(); if (!raw) return fallback; if (/unnamed/i.test(raw)) return fallback; if (hasChinese(raw)) return raw; const status = displayStatus(raw, ''); if (status) return status; const skillName = knownSkillName(raw); if (skillName) return skillName; return raw; } function isTerminalDoneStatus(status: string): boolean { return ['succeeded', 'success', 'finished', 'complete', 'completed'].includes(status.toLowerCase()); } function isTerminalFailedStatus(status: string): boolean { return ['failed', 'error', 'cancelled', 'canceled', 'aborted', 'rejected', 'deadletter'].includes(status.toLowerCase()); } function deriveStepStatus(rawStatus: unknown, tasks: MissionStep['tasks']): string { const status = str(rawStatus).trim().toLowerCase(); if (tasks.length === 0) return status || 'pending'; if (tasks.some((task) => isTerminalFailedStatus(task.status))) return 'failed'; if (tasks.some((task) => ['running', 'active', 'leased'].includes(task.status.toLowerCase()))) return 'running'; if (tasks.some((task) => ['queued', 'ready', 'pending'].includes(task.status.toLowerCase()))) return 'queued'; if (tasks.every((task) => isTerminalDoneStatus(task.status))) return 'succeeded'; return status || 'pending'; } function deriveMissionStatus(rawStatus: unknown, steps: MissionStep[]): string { const status = str(rawStatus, 'unknown').trim().toLowerCase(); if (isTerminalDoneStatus(status) || isTerminalFailedStatus(status)) return status; if (steps.some((step) => isTerminalFailedStatus(step.status))) return 'failed'; if (steps.some((step) => ['running', 'active', 'queued', 'leased'].includes(step.status.toLowerCase()))) return 'running'; if (steps.length > 0 && steps.every((step) => isTerminalDoneStatus(step.status))) return 'succeeded'; return status || 'unknown'; } // ── 适配函数 ── /** * @param storeData — getDebugStore() 返回值 (DebugStoreResponse) * @param skillsData — getSkills() 返回值 (SkillSpec[]) * * DebugStoreResponse 形状: { tables: { tasks, runs, approvals, ... } } * SkillSpec 形状: { skill_id, name, version, enabled, description } * DebugPlan 形状: { plan_id, title, objective, status, created_at, steps } */ export function adaptDashboardOverview( storeData: Obj | null ): DashboardOverview { const now = new Date().toISOString(); const tables = (storeData?.tables ?? {}) as Obj; const tasks = arr(tables.tasks); const approvals = arr(tables.approvals); const taskTotals = { total: tasks.length, queued: tasks.filter(t => t.status === 'queued').length, running: tasks.filter(t => t.status === 'running').length, succeeded: tasks.filter(t => t.status === 'succeeded').length, failed: tasks.filter(t => t.status === 'failed').length, }; return { generatedAt: now, isPartial: !storeData, sources: [ { endpoint: '/api/debug/store', ok: storeData !== null, loadedAt: now }, ], taskTotals, approvals: { total: approvals.length, pending: approvals.filter(a => a.status === 'pending').length, }, health: { activeTasks: taskTotals.running, failedTasks: taskTotals.failed, pendingConfirmations: approvals.filter(a => a.status === 'pending').length, }, }; } export function adaptMissions(plansData: Obj[] | null): Mission[] { if (!plansData) return []; return plansData.map((p: Obj) => { const planId = str(p.plan_id); const stepsRaw = arr(p.steps); const steps: MissionStep[] = stepsRaw.map((s: Obj) => { const taskList = arr(s.tasks); const tasks = taskList.map((t: Obj) => ({ id: str(t.task_id), title: displayBusinessText(t.title, '子任务'), description: str(t.description), status: str(t.status, 'pending'), assignedSkillId: t.assigned_skill_id ? str(t.assigned_skill_id) : null, startedAt: t.started_at ? str(t.started_at) : null, finishedAt: t.finished_at ? str(t.finished_at) : null, })); return { id: str(s.step_id), title: displayBusinessText(s.title, '任务步骤'), description: str(s.description), status: deriveStepStatus(s.status, tasks), preferredSkills: Array.isArray(s.preferred_skills) ? (s.preferred_skills as string[]) : [], tasks, }; }); const taskIds: string[] = []; for (const s of steps) { for (const t of s.tasks) { taskIds.push(t.id); } } return { id: planId, title: displayBusinessText(p.title, '未命名任务'), objective: displayBusinessText(p.objective || p.description, '暂无任务说明'), status: deriveMissionStatus(p.status, steps), createdAt: p.created_at ? str(p.created_at) : null, updatedAt: p.updated_at ? str(p.updated_at) : null, steps, sourceIds: { planId: planId || undefined, taskIds }, }; }); } const SKILL_ZH: Record = { 'decision-analyzer': '决策分析', 'git-log-collector': 'Git 日志采集', 'number-generator': '数据生成', 'office-export-xlsx': 'Excel 导出', 'task-tracker': '任务跟踪', 'weekly-report-writer': '周报撰写', 'zhihu-hotlist-detail': '知乎热榜明细', 'zhihu-hotlist': '知乎热榜', 'tq-lineloss-report': '台区线损报表', 'archive-workorder-grid-push-monitor': '归档工单推送监控', 'available-balance-below-zero-monitor': '可用余额监控', 'command-center-fee-control-monitor': '费用管控监控', 'sgcc-todo-crawler': 'SGCC 待办爬取', 'appointment-workorder-monitor': '约时工单监控', 'fault-location-monitor': '故障定位监控', 'fudian-failure-monitor': '复电失败监控', 'important-service-expiry-monitor': '服务到期预警', 'by-95598-customer-satisfaction-report': '95598 满意率日报', 'by-daily-report-statistics': '日报统计', 'by-fire-fault-monthly-report': '火警故障月报', 'by-risk-control-next-day-plan-report': '风控次日计划', 'by-risk-control-week-plan-report': '风控周计划', 'by-supervision-workorder-detail-report': '督办工单明细', 'age-file-encryption': '文件加解密', 'anonymous-file-upload': '匿名文件上传', 'browser-automation-agent': '浏览器自动化', 'bulk-github-star': '批量仓库标星', 'changelog-generator': '更新日志生成', 'code-reviewer': '代码审查', 'commit-automation': '自动提交', 'crypto-trading-signal': '交易信号分析', 'csv-to-json': 'CSV 转 JSON', 'data-analysis': '数据分析', 'deep-research': '深度调研', 'dependency-updater': '依赖更新', 'deploy-automation': '自动部署', 'dns-lookup': 'DNS 查询', 'document-generator': '文档生成', 'email-automation': '邮件自动化', 'file-converter': '文件格式转换', 'git-history-analyzer': 'Git 历史分析', 'github-issue-manager': 'Issue 管理', 'image-optimizer': '图片优化', 'json-schema-validator': 'JSON Schema 校验', 'log-analyzer': '日志分析', 'markdown-to-pdf': 'Markdown 转 PDF', 'meeting-summarizer': '会议纪要', 'news-aggregator': '新闻聚合', 'pdf-extractor': 'PDF 提取', 'performance-profiler': '性能分析', 'pr-review-bot': 'PR 审查', 'release-notes-generator': '发布说明生成', 'screenshot-automation': '截图自动化', 'search-engine-scraper': '搜索引擎抓取', 'sentiment-analysis': '情感分析', 'shell-command-runner': '命令执行', 'slack-notification': 'Slack 通知', 'spreadsheet-generator': '电子表格生成', 'text-summarizer': '文本摘要', 'torrent-search': '资源搜索', 'translate-text': '文本翻译', 'web-scraper': '网页抓取', 'website-monitor': '网站监控', 'whitepaper-analyzer': '白皮书分析', 'xml-to-json': 'XML 转 JSON', }; function knownSkillName(skillId: string): string | null { if (SKILL_ZH[skillId]) return SKILL_ZH[skillId]; for (const [key, val] of Object.entries(SKILL_ZH)) { if (skillId.includes(key) || key.includes(skillId)) return val; } return null; } function zhName(skillId: string, fallback: string): string { const known = knownSkillName(skillId); if (known) return known; return hasChinese(fallback) ? fallback : '自动化技能'; } const SKILL_CATEGORY: Record = { 'decision-analyzer': '数字化', 'git-log-collector': '数字化', 'number-generator': '数字化', 'office-export-xlsx': '办公', 'task-tracker': '办公', 'weekly-report-writer': '报表', 'zhihu-hotlist-detail': '数字化', 'zhihu-hotlist': '数字化', 'tq-lineloss-report': '报表', 'archive-workorder-grid-push-monitor': '监控巡检', 'available-balance-below-zero-monitor': '监控巡检', 'command-center-fee-control-monitor': '监控巡检', 'sgcc-todo-crawler': '监控巡检', 'appointment-workorder-monitor': '监控巡检', 'fault-location-monitor': '监控巡检', 'fudian-failure-monitor': '监控巡检', 'important-service-expiry-monitor': '监控巡检', 'by-95598-customer-satisfaction-report': '报表', 'by-daily-report-statistics': '报表', 'by-fire-fault-monthly-report': '报表', 'by-risk-control-next-day-plan-report': '报表', 'by-risk-control-week-plan-report': '报表', 'by-supervision-workorder-detail-report': '报表', 'age-file-encryption': '数字化', 'anonymous-file-upload': '通信', 'browser-automation-agent': '数字化', 'bulk-github-star': '发展', 'changelog-generator': '发展', 'chat-logger': '通信', 'check-crypto-address-balance': '财务', 'city-distance': '发展', 'city-tourism-website-builder': '营销', 'crawl-websites-at-scale': '数字化', 'csv-data-summarizer': '财务', 'd3js-data-visualization': '数字化', 'database-query-and-export': '数字化', 'file-tracker': '综合', 'free-geocoding-and-maps': '发展', 'free-translation-api': '综合', 'free-weather-data': '综合', 'generate-asset-price-chart': '财务', 'generate-qr-code-natively': '数字化', 'get-crypto-price': '财务', 'git-worktree-cli': '发展', 'github-stats': '发展', 'html-to-markdown': '数字化', 'image-compressor': '综合', 'json-to-csv': '数字化', 'llm-council': '数字化', 'markdown-table-generator': '综合', 'multi-format-converter': '综合', 'npm-package-info': '发展', 'pdf-generator': '综合', 'random-number-generator': '综合', 'readability-score': '综合', 'rss-feed-reader': '通信', 'sql-formatter': '数字化', 'text-diff': '综合', 'torrent-search': '通信', 'unit-converter': '综合', 'url-shortener': '通信', 'youtube-transcript': '通信', 'zip-extractor': '综合', }; function categoryFor(skillId: string): string { if (SKILL_CATEGORY[skillId]) return SKILL_CATEGORY[skillId]; return '综合'; } function zhDescription(skillId: string, rawDescription: unknown, name: string): string { const description = str(rawDescription).trim(); if (description && hasChinese(description)) return description; if (skillId.includes('monitor')) return `${name}能力,用于按排班自动巡检并提示异常。`; if (skillId.includes('report')) return `${name}能力,用于自动生成和整理业务报表。`; return `${name}能力,可由数字员工按需执行或定时处理。`; } export function adaptSkillSummaries(skillsData: Obj[] | null): SkillSummary[] { if (!skillsData) return []; return skillsData.map((s: Obj) => { const sid = str(s.skill_id, str(s.id)); return { id: sid, name: zhName(sid, str(s.name, sid)), version: s.version ? str(s.version) : null, enabled: Boolean(s.enabled), description: zhDescription(sid, s.description, zhName(sid, str(s.name, sid))), category: categoryFor(sid), capabilityLevel: 'standard' as const, source: 'skill-api' as const, }; }); } export function adaptJournalEntries(storeData: Obj | null): JournalEntry[] { if (!storeData) return []; const tables = (storeData.tables ?? {}) as Obj; const events = arr(tables.canonical_events); return events.map((e: Obj) => ({ id: str(e.event_id, str(e.id)), kind: str(e.event_type ?? e.kind, 'unknown'), source: 'debug-store', message: str(e.message, ''), occurredAt: e.occurred_at ? str(e.occurred_at) : e.timestamp ? str(e.timestamp) : null, planId: e.plan_id ? str(e.plan_id) : null, taskId: e.task_id ? str(e.task_id) : null, })); } export function adaptApprovals(storeData: Obj | null): ApprovalItem[] { if (!storeData) return []; const tables = (storeData.tables ?? {}) as Obj; const approvals = arr(tables.approvals); return approvals.map((a: Obj) => ({ id: str(a.approval_id, str(a.id)), missionId: a.plan_id ? str(a.plan_id) : null, taskId: a.task_id ? str(a.task_id) : null, status: str(a.status, 'pending'), title: displayBusinessText(a.title, '待处理授权'), createdAt: a.created_at ? str(a.created_at) : null, reversible: Boolean(a.reversible), })); }