329 lines
9.6 KiB
JavaScript
329 lines
9.6 KiB
JavaScript
const SOURCE_KIND_LABELS = {
|
||
api_missing: '等待接口',
|
||
config_toml: '配置文件',
|
||
local_sample: '示例数据',
|
||
plan_file: '计划文件',
|
||
runtime_missing: '运行数据缺失',
|
||
sqlite_missing: 'SQLite 缺失',
|
||
sqlite_missing_table: 'SQLite 表缺失',
|
||
sqlite_partial: 'SQLite 部分可用',
|
||
sqlite_readonly: 'SQLite 只读',
|
||
sqlite_schema_drift: 'SQLite 结构变化',
|
||
sqlite_table: 'SQLite 表',
|
||
test: '测试数据',
|
||
}
|
||
|
||
const CONFIDENCE_LABELS = {
|
||
high: '高',
|
||
medium: '中',
|
||
low: '低',
|
||
高: '高',
|
||
中: '中',
|
||
低: '低',
|
||
}
|
||
|
||
const STATUS_LABELS = {
|
||
blocked: '受阻',
|
||
clean: '无草稿',
|
||
complete: '已完成',
|
||
done: '已完成',
|
||
failed: '失败',
|
||
idle: '空闲',
|
||
invalid: '无效',
|
||
pending: '待处理',
|
||
recent: '最近活跃',
|
||
running: '运行中',
|
||
unknown: '未知',
|
||
valid: '有效',
|
||
}
|
||
|
||
const PARSE_STATUS_LABELS = {
|
||
invalid: '解析失败',
|
||
valid: '解析通过',
|
||
}
|
||
|
||
const TRUST_LABELS = {
|
||
trusted: '受信任',
|
||
untrusted: '未信任',
|
||
unknown: '未知',
|
||
}
|
||
|
||
export function formatSourceKind(kind) {
|
||
if (!kind) {
|
||
return '来源未知'
|
||
}
|
||
return SOURCE_KIND_LABELS[kind] ?? '来源未知'
|
||
}
|
||
|
||
export function formatConfidence(confidence) {
|
||
if (!confidence) {
|
||
return '低'
|
||
}
|
||
return CONFIDENCE_LABELS[confidence] ?? '低'
|
||
}
|
||
|
||
export function formatStatus(status) {
|
||
if (!status) {
|
||
return '未知'
|
||
}
|
||
return STATUS_LABELS[status] ?? '未知'
|
||
}
|
||
|
||
export function formatParseStatus(status) {
|
||
if (!status) {
|
||
return '未知'
|
||
}
|
||
return PARSE_STATUS_LABELS[status] ?? formatStatus(status)
|
||
}
|
||
|
||
export function normalizeSource(source, fallback = {}) {
|
||
const kind = source?.kind ?? fallback.kind ?? 'api_missing'
|
||
const confidence = source?.confidence ?? fallback.confidence ?? 'low'
|
||
return {
|
||
kind,
|
||
label: formatSourceKind(kind),
|
||
confidence,
|
||
confidenceLabel: formatConfidence(confidence),
|
||
path: source?.path ?? fallback.path ?? '',
|
||
message: source?.message ?? fallback.message ?? '',
|
||
}
|
||
}
|
||
|
||
export function normalizeProject(project) {
|
||
const source = normalizeSource(project?.source, { kind: 'config_toml', confidence: 'high' })
|
||
const path = project?.path ?? ''
|
||
return {
|
||
id: path || project?.displayName || 'project',
|
||
name: project?.displayName || basename(path) || '未命名项目',
|
||
path,
|
||
status: project?.directoryExists ? 'complete' : 'unknown',
|
||
statusZh: project?.directoryExists ? '目录存在' : '目录不可用',
|
||
trust: TRUST_LABELS[project?.trustLevel] ?? '未知',
|
||
directoryExists: Boolean(project?.directoryExists),
|
||
source: source.label,
|
||
confidence: source.confidenceLabel,
|
||
sourceDetail: source,
|
||
}
|
||
}
|
||
|
||
export function normalizeProjects(payload = {}) {
|
||
const projects = Array.isArray(payload.items) ? payload.items.map(normalizeProject) : []
|
||
return {
|
||
projects,
|
||
isEmpty: projects.length === 0,
|
||
emptyTitle: '没有项目配置',
|
||
emptyText: '后端没有返回项目条目。请确认 .codex/config.toml 中是否存在项目配置;这里不会用示例数据伪装真实结果。',
|
||
}
|
||
}
|
||
|
||
export function normalizeAgent(agent = {}) {
|
||
const fileName = agent.fileName || basename(agent.filePath) || agent.id || '未命名智能体'
|
||
const parseStatus = agent.parseStatus || 'unknown'
|
||
const isInvalid = parseStatus === 'invalid'
|
||
const name = agent.name || fileName
|
||
const description = isInvalid
|
||
? `TOML 解析失败:${agent.parseError || '后端未返回具体错误'}`
|
||
: agent.description || '没有描述'
|
||
const source = normalizeSource(null, {
|
||
kind: isInvalid ? 'api_missing' : 'config_toml',
|
||
confidence: isInvalid ? 'low' : 'high',
|
||
})
|
||
|
||
return {
|
||
id: agent.id || fileName,
|
||
file: agent.filePath || fileName,
|
||
fileName,
|
||
name,
|
||
description,
|
||
role: agent.developerInstructions || '没有角色设定字段',
|
||
status: isInvalid ? 'unknown' : 'complete',
|
||
statusLabel: isInvalid ? 'TOML 无效' : 'TOML 有效',
|
||
parseStatus,
|
||
parseStatusLabel: formatParseStatus(parseStatus),
|
||
parseError: agent.parseError || '',
|
||
draftStatus: agent.draftStatus || 'unknown',
|
||
draftStatusLabel: formatStatus(agent.draftStatus),
|
||
extraFields: agent.extraFields || {},
|
||
modifiedAt: formatDateTime(agent.modifiedAt),
|
||
source: source.label,
|
||
confidence: source.confidenceLabel,
|
||
toml: synthesizeAgentPreview(agent, { isInvalid, description }),
|
||
}
|
||
}
|
||
|
||
export function normalizeAgents(payload = {}) {
|
||
const agents = Array.isArray(payload.items) ? payload.items.map(normalizeAgent) : []
|
||
return {
|
||
agents,
|
||
isEmpty: agents.length === 0,
|
||
emptyTitle: '没有智能体定义',
|
||
emptyText: '后端没有返回 .codex/agents/*.toml 条目。当前显示为空状态,不会把示例智能体当作真实数据。',
|
||
}
|
||
}
|
||
|
||
export function normalizeRuntime(payload = {}) {
|
||
const threads = Array.isArray(payload.items) ? payload.items : []
|
||
const goals = Array.isArray(payload.goals) ? payload.goals : []
|
||
const goalsByThread = groupBy(goals, (goal) => goal.threadId)
|
||
const agents = threads.map((thread) => {
|
||
const source = normalizeSource(thread.source, payload.source)
|
||
const threadGoals = goalsByThread.get(thread.id) ?? []
|
||
return {
|
||
id: thread.id,
|
||
name: thread.role || thread.id || '未命名线程',
|
||
role: thread.id,
|
||
status: thread.status || 'unknown',
|
||
statusZh: formatStatus(thread.status),
|
||
goal: threadGoals.map((goal) => goal.goal).filter(Boolean).join(';') || '没有目标记录',
|
||
process: 'SQLite 只读快照',
|
||
source: source.label,
|
||
confidence: source.confidenceLabel,
|
||
lastActivity: thread.updatedAt || thread.createdAt || '没有时间记录',
|
||
}
|
||
})
|
||
const source = normalizeSource(payload.source)
|
||
|
||
return {
|
||
agents,
|
||
threads,
|
||
goals,
|
||
edges: Array.isArray(payload.edges) ? payload.edges : [],
|
||
source,
|
||
isEmpty: threads.length === 0,
|
||
emptyTitle: '没有运行线程',
|
||
emptyText: source.message || '后端返回了空的运行线程列表;这里保持空状态并展示来源证据。',
|
||
}
|
||
}
|
||
|
||
export function normalizeWorkflow(payload = {}) {
|
||
const source = normalizeSource(payload.source)
|
||
const phases = Array.isArray(payload.phases)
|
||
? payload.phases.filter(isDisplayPhase).map((phase) => {
|
||
const phaseSource = normalizeSource(phase.source, payload.source)
|
||
const phaseName = normalizePhaseName(phase.name)
|
||
return {
|
||
name: phaseName,
|
||
status: phase.status || 'unknown',
|
||
label: formatStatus(phase.status),
|
||
gate: phaseName,
|
||
evidence: phaseSource.label,
|
||
confidence: phaseSource.confidenceLabel,
|
||
source: phaseSource,
|
||
}
|
||
})
|
||
: []
|
||
const handoffs = Array.isArray(payload.handoffEdges)
|
||
? payload.handoffEdges.map((edge) => {
|
||
const edgeSource = normalizeSource(edge.source, payload.source)
|
||
return {
|
||
from: edge.fromThreadId || '未知线程',
|
||
to: edge.toThreadId || '未知线程',
|
||
summary: edge.label || '没有交接说明',
|
||
time: '后端事件',
|
||
source: edgeSource.label,
|
||
confidence: edgeSource.confidenceLabel,
|
||
}
|
||
})
|
||
: []
|
||
const edges = handoffs.map((handoff) => ({
|
||
parent: handoff.from,
|
||
child: handoff.to,
|
||
status: handoff.summary,
|
||
source: handoff.source,
|
||
confidence: handoff.confidence,
|
||
}))
|
||
const events = Array.isArray(payload.items) ? payload.items : []
|
||
|
||
return {
|
||
events,
|
||
phases,
|
||
handoffs,
|
||
edges,
|
||
source,
|
||
isEmpty: events.length === 0 && phases.length === 0 && handoffs.length === 0,
|
||
emptyTitle: '没有工作流事件',
|
||
emptyText: source.message || '后端返回了空的工作流事件流;这里不会回退到伪装真实的示例关系。',
|
||
emptyHandoffsText: '后端没有返回交接边;当前保持空状态。',
|
||
}
|
||
}
|
||
|
||
function synthesizeAgentPreview(agent, { isInvalid, description }) {
|
||
if (isInvalid) {
|
||
return `# TOML 无效,仅只读展示解析错误\n# ${description}`
|
||
}
|
||
|
||
const lines = [
|
||
'# 接口未返回原始 TOML;以下为只读展示的已解析字段',
|
||
`name = ${quote(agent.name || '')}`,
|
||
`description = ${quote(agent.description || '')}`,
|
||
]
|
||
if (agent.developerInstructions) {
|
||
lines.push(`角色设定 = ${quote(agent.developerInstructions)}`)
|
||
}
|
||
for (const [key, value] of Object.entries(agent.extraFields || {})) {
|
||
lines.push(`${key} = ${quote(value)}`)
|
||
}
|
||
return lines.join('\n')
|
||
}
|
||
|
||
function basename(path) {
|
||
if (!path) {
|
||
return ''
|
||
}
|
||
return String(path).split('/').filter(Boolean).at(-1) ?? String(path)
|
||
}
|
||
|
||
function formatDateTime(value) {
|
||
if (!value) {
|
||
return '没有时间记录'
|
||
}
|
||
const date = new Date(value)
|
||
if (Number.isNaN(date.getTime())) {
|
||
return value
|
||
}
|
||
return new Intl.DateTimeFormat('zh-CN', {
|
||
month: '2-digit',
|
||
day: '2-digit',
|
||
hour: '2-digit',
|
||
minute: '2-digit',
|
||
}).format(date)
|
||
}
|
||
|
||
function groupBy(items, keyOf) {
|
||
const groups = new Map()
|
||
for (const item of items) {
|
||
const key = keyOf(item)
|
||
if (!groups.has(key)) {
|
||
groups.set(key, [])
|
||
}
|
||
groups.get(key).push(item)
|
||
}
|
||
return groups
|
||
}
|
||
|
||
function quote(value) {
|
||
return JSON.stringify(String(value ?? ''))
|
||
}
|
||
|
||
function isDisplayPhase(phase) {
|
||
const name = String(phase?.name ?? '').trim().toLowerCase()
|
||
const status = String(phase?.status ?? '').trim().toLowerCase()
|
||
if (!name) {
|
||
return false
|
||
}
|
||
if (name === 'phase' || status === 'status') {
|
||
return false
|
||
}
|
||
if (name === 'time' && status === 'phase') {
|
||
return false
|
||
}
|
||
return true
|
||
}
|
||
|
||
function normalizePhaseName(name) {
|
||
if (!name) {
|
||
return '未命名阶段'
|
||
}
|
||
return /^\d+$/.test(String(name)) ? `阶段 ${name}` : String(name)
|
||
}
|