From 2bf9192fc51402d45d8fe581e81dddf30cc652b5 Mon Sep 17 00:00:00 2001 From: baiyanyun Date: Sun, 7 Jun 2026 02:22:37 +0800 Subject: [PATCH] feat(client): export digital sync diagnostics --- .../src/pages/digital/CommandDeck.tsx | 92 ++++++++++++- .../sgrobot-digital/assets/index-Dfb8OO8K.js | 126 ++++++++++++++++++ .../sgrobot-digital/assets/index-gtzF6MAO.js | 121 ----------------- .../public/sgrobot-digital/index.html | 2 +- ...ital-employee-frontend-integration-plan.md | 2 +- 5 files changed, 219 insertions(+), 124 deletions(-) create mode 100644 qimingclaw/crates/agent-electron-client/public/sgrobot-digital/assets/index-Dfb8OO8K.js delete mode 100644 qimingclaw/crates/agent-electron-client/public/sgrobot-digital/assets/index-gtzF6MAO.js diff --git a/qimingclaw/crates/agent-electron-client/embedded/sgrobot-digital/src/pages/digital/CommandDeck.tsx b/qimingclaw/crates/agent-electron-client/embedded/sgrobot-digital/src/pages/digital/CommandDeck.tsx index b93c75ab..2172ab03 100644 --- a/qimingclaw/crates/agent-electron-client/embedded/sgrobot-digital/src/pages/digital/CommandDeck.tsx +++ b/qimingclaw/crates/agent-electron-client/embedded/sgrobot-digital/src/pages/digital/CommandDeck.tsx @@ -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, RefreshCw } from 'lucide-react'; +import { Clipboard, Clock3, Download, Eye, PlayCircle, RefreshCw } from 'lucide-react'; import Avatar3D from '@/components/digital/Avatar3D'; import { basePath } from '@/lib/basePath'; import { @@ -551,6 +551,57 @@ function syncAttemptTone(status: string): 'success' | 'danger' | 'info' { return 'info'; } +function buildSyncFailureDiagnostic( + failure: ManagementSyncFailure, + status: ManagementSyncStatus | null, +): string { + return JSON.stringify( + { + generatedAt: new Date().toISOString(), + source: 'qimingclaw-digital-employee', + failure, + sync: status + ? { + running: status.running, + syncing: status.syncing, + enabled: status.enabled, + endpoint: status.endpoint, + missingCredentials: status.missingCredentials ?? [], + pending: status.pending, + failed: status.failed, + lastSyncAt: status.lastSyncAt, + lastSyncError: status.lastSyncError, + failureSummary: status.failureSummary ?? null, + } + : null, + }, + null, + 2, + ); +} + +function syncFailureDiagnosticFileName(failure: ManagementSyncFailure): string { + const entity = [failure.entityType, failure.entityId] + .filter(Boolean) + .join('-') + .replace(/[^a-z0-9._-]+/gi, '-') + .replace(/^-+|-+$/g, '') + .slice(0, 80); + return `qimingclaw-sync-failure-${entity || failure.id || 'diagnostic'}.json`; +} + +function downloadTextFile(filename: string, content: string): void { + const blob = new Blob([content], { type: 'application/json;charset=utf-8' }); + const url = URL.createObjectURL(blob); + const link = document.createElement('a'); + link.href = url; + link.download = filename; + document.body.appendChild(link); + link.click(); + link.remove(); + window.setTimeout(() => URL.revokeObjectURL(url), 1000); +} + async function readManagementSyncStatus(): Promise { const bridge = window.QimingClawBridge?.digital; if (!bridge?.getSyncStatus) return null; @@ -1062,6 +1113,27 @@ export default function CommandDeck({ onNavigate }: { onNavigate: (target: Digit } }, [selectedDate]); + const copySyncFailureDiagnostic = useCallback(async (failure: ManagementSyncFailure) => { + const diagnostic = buildSyncFailureDiagnostic(failure, managementSyncStatus); + try { + await navigator.clipboard.writeText(diagnostic); + setActionError(null); + setActionNotice('同步失败诊断已复制。'); + } catch (error) { + setActionNotice(null); + setActionError(error instanceof Error ? error.message : '复制同步失败诊断失败'); + } + }, [managementSyncStatus]); + + const exportSyncFailureDiagnostic = useCallback((failure: ManagementSyncFailure) => { + downloadTextFile( + syncFailureDiagnosticFileName(failure), + buildSyncFailureDiagnostic(failure, managementSyncStatus), + ); + setActionError(null); + setActionNotice('同步失败诊断已导出。'); + }, [managementSyncStatus]); + const cancelAllTasks = useCallback(() => { setSelectedDutyIds(new Set()); selectionDirtyRef.current = true; @@ -1926,6 +1998,24 @@ export default function CommandDeck({ onNavigate }: { onNavigate: (target: Digit > 关闭 + + {selectedSyncFailure.managementRecordUrl && (