feat(client): show digital employee sync status

This commit is contained in:
baiyanyun
2026-06-05 17:25:46 +08:00
parent ab1d022905
commit 35d2cb5ea5
3 changed files with 63 additions and 28 deletions

View File

@@ -71,6 +71,7 @@ interface QimingclawSnapshot {
active: number; active: number;
items: Array<{ id: string; title?: string; status?: string; engineType?: string; projectId?: string }>; items: Array<{ id: string; title?: string; status?: string; engineType?: string; projectId?: string }>;
}; };
sync?: QimingclawSyncStatus | null;
} }
const SKILLS: SkillSpec[] = [ const SKILLS: SkillSpec[] = [
@@ -283,7 +284,10 @@ function writeState(state: AdapterState): void {
async function readQimingclawSnapshot(): Promise<QimingclawSnapshot | null> { async function readQimingclawSnapshot(): Promise<QimingclawSnapshot | null> {
try { try {
return await window.QimingClawBridge?.digital?.getSnapshot?.() ?? null; const snapshot = await window.QimingClawBridge?.digital?.getSnapshot?.() ?? null;
if (!snapshot) return null;
const sync = await window.QimingClawBridge?.digital?.getSyncStatus?.().catch(() => null) ?? null;
return { ...snapshot, sync };
} catch { } catch {
return null; return null;
} }
@@ -357,6 +361,17 @@ function serviceLine(snapshot: QimingclawSnapshot | null, key: string, label: st
return `${label}:未运行${service.error ? `${service.error}` : ''}`; return `${label}:未运行${service.error ? `${service.error}` : ''}`;
} }
function syncLine(snapshot: QimingclawSnapshot | null): string {
const sync = snapshot?.sync;
if (!sync) return '管理端同步:未连接';
if (!sync.enabled) return '管理端同步:已停用';
if (!sync.endpoint) return '管理端同步:未配置地址';
if (sync.syncing) return `管理端同步:同步中,待同步 ${sync.pending}`;
if (sync.failed > 0) return `管理端同步:${sync.failed} 条失败,待同步 ${sync.pending}`;
if (sync.pending > 0) return `管理端同步:待同步 ${sync.pending}`;
return sync.lastSyncAt ? `管理端同步:已同步,最近 ${sync.lastSyncAt}` : '管理端同步:等待本地数据';
}
function withRuntimePlan(plan: DebugPlan, snapshot: QimingclawSnapshot | null): DebugPlan { function withRuntimePlan(plan: DebugPlan, snapshot: QimingclawSnapshot | null): DebugPlan {
return { ...plan, status: runtimeStatusForPlan(plan, snapshot) }; return { ...plan, status: runtimeStatusForPlan(plan, snapshot) };
} }
@@ -511,7 +526,7 @@ function buildEvents(duties: DigitalEmployeeDuty[], confirmed: boolean, snapshot
} }
const selectedDuties = duties.filter((duty) => duty.selected); const selectedDuties = duties.filter((duty) => duty.selected);
return selectedDuties.map((duty) => ({ const events = selectedDuties.map((duty) => ({
id: `event-${duty.id}`, id: `event-${duty.id}`,
time: duty.status === 'running' ? '实时' : '待执行', time: duty.status === 'running' ? '实时' : '待执行',
plan_title: duty.title, plan_title: duty.title,
@@ -526,6 +541,23 @@ function buildEvents(duties: DigitalEmployeeDuty[], confirmed: boolean, snapshot
updated_at: new Date().toISOString(), updated_at: new Date().toISOString(),
conversation_id: duty.conversation_id, conversation_id: duty.conversation_id,
})); }));
const sync = snapshot?.sync;
if (sync && (sync.pending > 0 || sync.failed > 0 || sync.lastSyncAt)) {
events.push({
id: 'event-management-sync',
time: sync.syncing ? '实时' : '同步',
plan_title: '管理端联动',
task_title: '同步数字员工本地状态',
step_title: sync.failed > 0 ? '同步补偿' : 'outbox 推送',
detail: `${syncLine(snapshot)}${sync.lastSyncError ? `。错误:${sync.lastSyncError}` : ''}`,
status: sync.failed > 0 ? 'failed' : sync.syncing || sync.pending > 0 ? 'running' : 'completed',
status_label: sync.failed > 0 ? '异常' : sync.syncing || sync.pending > 0 ? '同步中' : '已同步',
status_tone: sync.failed > 0 ? 'danger' : sync.syncing || sync.pending > 0 ? 'running' : 'success',
updated_at: sync.lastSyncAt ?? new Date().toISOString(),
conversation_id: 'qimingclaw-management-sync',
});
}
return events;
} }
function buildExecutionSummaries(duties: DigitalEmployeeDuty[], events: DigitalEmployeeWorkEvent[]): DigitalEmployeeTaskExecutionSummary[] { function buildExecutionSummaries(duties: DigitalEmployeeDuty[], events: DigitalEmployeeWorkEvent[]): DigitalEmployeeTaskExecutionSummary[] {
@@ -578,6 +610,7 @@ function buildWorkday(date: string, snapshot: QimingclawSnapshot | null): Digita
const generatedAt = state.reportGeneratedAtByDate[date] ?? null; const generatedAt = state.reportGeneratedAtByDate[date] ?? null;
const selectedCount = duties.filter((duty) => duty.selected).length; const selectedCount = duties.filter((duty) => duty.selected).length;
const runningCount = events.filter((event) => event.status === 'running').length; const runningCount = events.filter((event) => event.status === 'running').length;
const sync = snapshot?.sync;
return { return {
source: 'live', source: 'live',
@@ -614,7 +647,8 @@ function buildWorkday(date: string, snapshot: QimingclawSnapshot | null): Digita
{ label: '已选任务', value: String(selectedCount) }, { label: '已选任务', value: String(selectedCount) },
{ label: '运行记录', value: String(events.length) }, { label: '运行记录', value: String(events.length) },
{ label: '执行中', value: String(runningCount) }, { label: '执行中', value: String(runningCount) },
{ label: '异常', value: '0' }, { label: '待同步', value: String(sync?.pending ?? 0) },
{ label: '同步异常', value: String(sync?.failed ?? 0) },
], ],
}, },
duties, duties,
@@ -631,6 +665,7 @@ function buildWorkday(date: string, snapshot: QimingclawSnapshot | null): Digita
serviceLine(snapshot, 'agent', 'Agent'), serviceLine(snapshot, 'agent', 'Agent'),
serviceLine(snapshot, 'mcp', 'MCP'), serviceLine(snapshot, 'mcp', 'MCP'),
serviceLine(snapshot, 'computerServer', 'Computer Server'), serviceLine(snapshot, 'computerServer', 'Computer Server'),
syncLine(snapshot),
`活跃会话:${snapshot?.sessions.active ?? 0} / ${snapshot?.sessions.total ?? 0}`, `活跃会话:${snapshot?.sessions.active ?? 0} / ${snapshot?.sessions.total ?? 0}`,
], ],
artifacts: [ artifacts: [
@@ -647,7 +682,7 @@ function buildWorkday(date: string, snapshot: QimingclawSnapshot | null): Digita
model: 'qimingclaw-adapter', model: 'qimingclaw-adapter',
source: 'qimingclaw', source: 'qimingclaw',
summary: generatedAt summary: generatedAt
? '今日已完成数字员工页面到 qimingclaw adapter 的接入,并读取了 qimingclaw 服务状态。' ? `今日已完成数字员工页面到 qimingclaw adapter 的接入,并读取了 qimingclaw 服务状态。${syncLine(snapshot)}`
: '日报将在生成后汇总任务选择、运行明细和后续接入建议。', : '日报将在生成后汇总任务选择、运行明细和后续接入建议。',
totals: { totals: {
task_count: selectedCount, task_count: selectedCount,

View File

@@ -13,7 +13,7 @@
console.warn("[qimingclaw] digital employee auth bootstrap skipped", error); console.warn("[qimingclaw] digital employee auth bootstrap skipped", error);
} }
</script> </script>
<script type="module" crossorigin src="./assets/index-DcEd9LJg.js"></script> <script type="module" crossorigin src="./assets/index-CCHg55lx.js"></script>
<link rel="stylesheet" crossorigin href="./assets/index-DqV9Vw1H.css"> <link rel="stylesheet" crossorigin href="./assets/index-DqV9Vw1H.css">
</head> </head>
<body> <body>