feat(client): add manual digital employee sync
This commit is contained in:
@@ -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
@@ -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>
|
||||
|
||||
@@ -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(
|
||||
`
|
||||
|
||||
@@ -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:
|
||||
|
||||
|
||||
Reference in New Issue
Block a user