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 { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||||
import type { KeyboardEvent as ReactKeyboardEvent, MouseEvent as ReactMouseEvent } 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 Avatar3D from '@/components/digital/Avatar3D';
|
||||||
import { basePath } from '@/lib/basePath';
|
import { basePath } from '@/lib/basePath';
|
||||||
import {
|
import {
|
||||||
@@ -851,6 +851,36 @@ export default function CommandDeck({ onNavigate }: { onNavigate: (target: Digit
|
|||||||
}
|
}
|
||||||
}, [isResting, selectedDate, selectedDutyIds, selectedSubmissionDutyIds]);
|
}, [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(() => {
|
const cancelAllTasks = useCallback(() => {
|
||||||
setSelectedDutyIds(new Set());
|
setSelectedDutyIds(new Set());
|
||||||
selectionDirtyRef.current = true;
|
selectionDirtyRef.current = true;
|
||||||
@@ -1209,6 +1239,16 @@ export default function CommandDeck({ onNavigate }: { onNavigate: (target: Digit
|
|||||||
>
|
>
|
||||||
任务设置
|
任务设置
|
||||||
</button>
|
</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>
|
</div>
|
||||||
|
|
||||||
{(actionNotice || actionError || planDataError || workdayProjection?.demo_reason) && (
|
{(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);
|
console.warn("[qimingclaw] digital employee auth bootstrap skipped", error);
|
||||||
}
|
}
|
||||||
</script>
|
</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">
|
<link rel="stylesheet" crossorigin href="./assets/index-DqV9Vw1H.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<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();
|
if (syncing) return getDigitalEmployeeSyncStatus();
|
||||||
|
|
||||||
const config = resolveSyncConfig();
|
const config = resolveSyncConfig();
|
||||||
@@ -213,7 +215,9 @@ function readAuthToken(serverHost: string | null): string | null {
|
|||||||
function readPendingOutbox(limit: number, force: boolean): OutboxRow[] {
|
function readPendingOutbox(limit: number, force: boolean): OutboxRow[] {
|
||||||
const db = getDb();
|
const db = getDb();
|
||||||
if (!db) return [];
|
if (!db) return [];
|
||||||
const candidateLimit = force ? limit : limit * FAILED_RETRY_CANDIDATE_MULTIPLIER;
|
const candidateLimit = force
|
||||||
|
? limit
|
||||||
|
: limit * FAILED_RETRY_CANDIDATE_MULTIPLIER;
|
||||||
const rows = db
|
const rows = db
|
||||||
.prepare(
|
.prepare(
|
||||||
`
|
`
|
||||||
|
|||||||
@@ -443,6 +443,7 @@ crates/agent-electron-client/src/preload/webviewPerfBridge.ts
|
|||||||
- 使用管理端登录态 token 作为 `Authorization: Bearer <token>`,同时携带 `X-Qiming-Client-Key`。
|
- 使用管理端登录态 token 作为 `Authorization: Bearer <token>`,同时携带 `X-Qiming-Client-Key`。
|
||||||
- 同步成功后回填 outbox 状态,并根据后端 ack 回填实体 `remote_id` / `last_synced_at`。
|
- 同步成功后回填 outbox 状态,并根据后端 ack 回填实体 `remote_id` / `last_synced_at`。
|
||||||
- 同步失败时标记 failed 和错误信息;定时 worker 按指数退避补偿,手动 `flushSync` 可强制立即重试,不影响 `/computer/chat` 和 ACP 执行链路。
|
- 同步失败时标记 failed 和错误信息;定时 worker 按指数退避补偿,手动 `flushSync` 可强制立即重试,不影响 `/computer/chat` 和 ACP 执行链路。
|
||||||
|
- 数字员工任务设置工具栏已增加“同步管理端”按钮,可通过 bridge 立即触发 `flushSync` 并刷新页面投影。
|
||||||
|
|
||||||
新增 IPC / Bridge:
|
新增 IPC / Bridge:
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user