feat(client): add manual digital employee sync

This commit is contained in:
baiyanyun
2026-06-05 18:24:04 +08:00
parent b7aecad092
commit 17475bf0d7
6 changed files with 170 additions and 120 deletions

View File

@@ -1,6 +1,6 @@
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import type { KeyboardEvent as ReactKeyboardEvent, MouseEvent as ReactMouseEvent } from 'react';
import { Clock3, Download, Eye, PlayCircle } from 'lucide-react';
import { Clock3, Download, Eye, PlayCircle, RefreshCw } from 'lucide-react';
import Avatar3D from '@/components/digital/Avatar3D';
import { basePath } from '@/lib/basePath';
import {
@@ -851,6 +851,36 @@ export default function CommandDeck({ onNavigate }: { onNavigate: (target: Digit
}
}, [isResting, selectedDate, selectedDutyIds, selectedSubmissionDutyIds]);
const flushManagementSync = useCallback(async () => {
setActionBusy('flush_management_sync');
setActionError(null);
setActionNotice(null);
try {
const bridge = window.QimingClawBridge?.digital;
if (!bridge?.flushSync) {
setActionNotice('当前环境未连接 qimingclaw 同步桥。');
return;
}
const syncStatus = await bridge.flushSync();
if (!mountedRef.current) return;
const latestProjection = await getDigitalEmployeeWorkday(selectedDate);
if (!mountedRef.current) return;
setWorkdayProjection(latestProjection);
const statusText = syncStatus.failed > 0
? `同步完成,仍有 ${syncStatus.failed} 条失败记录。`
: syncStatus.pending > 0
? `同步完成,仍有 ${syncStatus.pending} 条待同步记录。`
: '管理端同步完成。';
setActionNotice(statusText);
} catch (error) {
if (mountedRef.current) {
setActionError(error instanceof Error ? error.message : '同步管理端失败');
}
} finally {
if (mountedRef.current) setActionBusy(null);
}
}, [selectedDate]);
const cancelAllTasks = useCallback(() => {
setSelectedDutyIds(new Set());
selectionDirtyRef.current = true;
@@ -1209,6 +1239,16 @@ export default function CommandDeck({ onNavigate }: { onNavigate: (target: Digit
>
</button>
<button
type="button"
className="de-btn-secondary de-btn-sm"
disabled={projectionLoading || actionBusy === 'flush_management_sync'}
onClick={flushManagementSync}
title="立即同步数字员工本地状态到管理端"
>
<RefreshCw size={13} aria-hidden="true" />
<span>{actionBusy === 'flush_management_sync' ? '同步中...' : '同步管理端'}</span>
</button>
</div>
{(actionNotice || actionError || planDataError || workdayProjection?.demo_reason) && (

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

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

View File

@@ -97,7 +97,9 @@ export function getDigitalEmployeeSyncStatus(): DigitalEmployeeSyncStatus {
};
}
export async function flushDigitalEmployeeSyncOutbox(options: { force?: boolean } = {}): Promise<DigitalEmployeeSyncStatus> {
export async function flushDigitalEmployeeSyncOutbox(
options: { force?: boolean } = {},
): Promise<DigitalEmployeeSyncStatus> {
if (syncing) return getDigitalEmployeeSyncStatus();
const config = resolveSyncConfig();
@@ -213,7 +215,9 @@ function readAuthToken(serverHost: string | null): string | null {
function readPendingOutbox(limit: number, force: boolean): OutboxRow[] {
const db = getDb();
if (!db) return [];
const candidateLimit = force ? limit : limit * FAILED_RETRY_CANDIDATE_MULTIPLIER;
const candidateLimit = force
? limit
: limit * FAILED_RETRY_CANDIDATE_MULTIPLIER;
const rows = db
.prepare(
`

View File

@@ -443,6 +443,7 @@ crates/agent-electron-client/src/preload/webviewPerfBridge.ts
- 使用管理端登录态 token 作为 `Authorization: Bearer <token>`,同时携带 `X-Qiming-Client-Key`
- 同步成功后回填 outbox 状态,并根据后端 ack 回填实体 `remote_id` / `last_synced_at`
- 同步失败时标记 failed 和错误信息;定时 worker 按指数退避补偿,手动 `flushSync` 可强制立即重试,不影响 `/computer/chat` 和 ACP 执行链路。
- 数字员工任务设置工具栏已增加“同步管理端”按钮,可通过 bridge 立即触发 `flushSync` 并刷新页面投影。
新增 IPC / Bridge