aitu-app 0.6.78 → 0.6.80
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -3
- package/assets/CanvasAudioPlayer-CSFENxi6.js +2 -0
- package/assets/ChatDrawer-CWZQP5R5.css +1 -0
- package/assets/ChatDrawer-sVEVW11M.js +11 -0
- package/assets/ChatMessagesArea-Cnb94V8-.js +7 -0
- package/assets/ChatMessagesArea-PlF5AwzD.css +1 -0
- package/assets/DeferredAIInputBar-D99l6sBS.js +8 -0
- package/assets/DrawnixDeferredFeatures-78cIu-ev.js +2 -0
- package/assets/{KnowledgeBaseContent-DcX5Jofx.js → KnowledgeBaseContent-DZ-1fPZa.js} +10 -10
- package/assets/{MVCreator-DllHozYg.js → MVCreator-BV8KDR6E.js} +8 -8
- package/assets/ModelBenchmarkWorkbench-DL6r0Z_5.js +2 -0
- package/assets/MusicAnalyzer-D9Bpmhym.css +1 -0
- package/assets/{MusicAnalyzer-DDeT6g3c.js → MusicAnalyzer-DQtjf36o.js} +2 -2
- package/assets/{MusicAnalyzer-CHcQ5t4P.js → MusicAnalyzer-u_5sy56T.js} +1 -1
- package/assets/PromptHistoryTool-B-ktaOpK.js +2 -0
- package/assets/PromptHistoryTool-BH7hq916.css +1 -0
- package/assets/ResizableDivider-BpUFdkte.js +1 -0
- package/assets/TaskQueuePanel-BLD1r7_I.js +1 -0
- package/assets/{VideoAnalyzer-BSLz1FaI.js → VideoAnalyzer-Bjv9pyDs.js} +2 -2
- package/assets/ai-generation-preferences-service-BSpgVCHz.js +1 -0
- package/assets/arc-CnA_FoJK.js +1 -0
- package/assets/{arc-BU2KKCa_.js → arc-DDvk_-VM.js} +1 -1
- package/assets/{batch-image-generation-Bg68r_Ql.js → batch-image-generation-N-lwbjRE.js} +3 -3
- package/assets/{blockDiagram-38ab4fdb-BCGEnMME.js → blockDiagram-38ab4fdb-B2G4wjZe.js} +6 -6
- package/assets/{blockDiagram-38ab4fdb-CsZR5uC5.js → blockDiagram-38ab4fdb-DBkZWrKk.js} +1 -1
- package/assets/{browser-DpICcNby.js → browser-ZXTF4AKv.js} +1 -1
- package/assets/{c4Diagram-3d4e48cf-9fuiuFdV.js → c4Diagram-3d4e48cf-BFg-wccg.js} +1 -1
- package/assets/{c4Diagram-3d4e48cf-WM4wd_GR.js → c4Diagram-3d4e48cf-CmyNnfip.js} +1 -1
- package/assets/{canvas-insertion-CdUQF7IV.js → canvas-insertion-CKIoVZ9a.js} +1 -1
- package/assets/channel-BQh3J7kz.js +1 -0
- package/assets/channel-CPsF_uSp.js +1 -0
- package/assets/{classDiagram-70f12bd4-DcYDedz1.js → classDiagram-70f12bd4-BoP-e23u.js} +2 -2
- package/assets/{classDiagram-70f12bd4-CBVHTwQu.js → classDiagram-70f12bd4-UTHJc2XA.js} +1 -1
- package/assets/classDiagram-v2-f2320105-BeBc2H-D.js +2 -0
- package/assets/{classDiagram-v2-f2320105-jpCwaHEd.js → classDiagram-v2-f2320105-Bjwz5ljW.js} +1 -1
- package/assets/{clipper-jvd5rF5a.js → clipper-NoHaeWV4.js} +1 -1
- package/assets/clone-CA2KqVCK.js +1 -0
- package/assets/createText-2e5e7dd3-Bsu7vT-L.js +8 -0
- package/assets/{createText-2e5e7dd3-x5lOIwut.js → createText-2e5e7dd3-DrWUfa-s.js} +1 -1
- package/assets/{edges-e0da2a9e-CbrF4nbD.js → edges-e0da2a9e-B9sYOHRt.js} +1 -1
- package/assets/{edges-e0da2a9e-Ch6PBwWs.js → edges-e0da2a9e-DX4IQMED.js} +1 -1
- package/assets/{erDiagram-9861fffd-DSlNVuAZ.js → erDiagram-9861fffd-B-LfsIMf.js} +1 -1
- package/assets/{erDiagram-9861fffd-Bq07elcI.js → erDiagram-9861fffd-DPdud_LA.js} +1 -1
- package/assets/{flowDb-956e92f1-Ti5YC4Vv.js → flowDb-956e92f1-C3xKlizM.js} +1 -1
- package/assets/{flowDb-956e92f1-BqPCJlZp.js → flowDb-956e92f1-Ck5oiEta.js} +1 -1
- package/assets/{flowDiagram-66a62f08-CPDuG3rY.js → flowDiagram-66a62f08-Bkt2NoHM.js} +4 -4
- package/assets/{flowDiagram-66a62f08-D3GSABFi.js → flowDiagram-66a62f08-CJnxo6dI.js} +1 -1
- package/assets/flowDiagram-v2-96b9c2cf-MnwxRDMO.js +1 -0
- package/assets/flowDiagram-v2-96b9c2cf-U7nNPvdl.js +1 -0
- package/assets/{flowchart-elk-definition-4a651766-BH09NeLA.js → flowchart-elk-definition-4a651766-2XkrsdKI.js} +2 -2
- package/assets/{flowchart-elk-definition-4a651766-BLaLPCDV.js → flowchart-elk-definition-4a651766-ec8y1a8S.js} +1 -1
- package/assets/{ganttDiagram-c361ad54-DL9ZeaEc.js → ganttDiagram-c361ad54-1TA0mZY_.js} +3 -3
- package/assets/{ganttDiagram-c361ad54-Sp9M1KHP.js → ganttDiagram-c361ad54-D-OrVtXV.js} +3 -3
- package/assets/{gitGraphDiagram-72cf32ee-BWwmRKo-.js → gitGraphDiagram-72cf32ee-Blt3RsBD.js} +1 -1
- package/assets/{gitGraphDiagram-72cf32ee-BSf1fsDV.js → gitGraphDiagram-72cf32ee-CKf6xgO1.js} +1 -1
- package/assets/{graph-g-2U7grQ.js → graph-CUYgHJmb.js} +1 -1
- package/assets/{graph--nPw8zXQ.js → graph-WeSRtsZO.js} +1 -1
- package/assets/{index-ZrVeQUWo.js → index--MqiaN5t.js} +1 -1
- package/assets/{index-C5eu4uSl.js → index-0b_TeH8L.js} +11 -11
- package/assets/{index-3862675e-BXwbZ2pz.js → index-3862675e-C2ipEQC8.js} +1 -1
- package/assets/{index-3862675e-D6Zy95zZ.js → index-3862675e-CeX6VlsU.js} +1 -1
- package/assets/{index-CLJUfzvO.js → index-B-J0JJak.js} +1 -1
- package/assets/{index-TkfyNewx.js → index-B7Hj7g41.js} +1 -1
- package/assets/{index-eZfwJ9bV.js → index-BS_v5cOh.js} +1 -1
- package/assets/index-Btk31p7r.js +1 -0
- package/assets/{index-DH12_HTj.js → index-C-Samsu3.js} +2 -2
- package/assets/index-C-llNX01.js +1 -0
- package/assets/index-C8c9icsH.js +2 -0
- package/assets/{index-D7b5fX1d.js → index-CAn2jfNg.js} +1 -1
- package/assets/{index-Dz8sghkK.js → index-CK2zt94U.js} +1 -1
- package/assets/{index-CfgvFFx3.js → index-CP51k4n6.js} +17 -17
- package/assets/{index-DI0OV6F8.js → index-CXBhIy30.js} +1 -1
- package/assets/index-C_Mj1wbj.js +2 -0
- package/assets/{index-BwSnJN93.js → index-CbfE0MBd.js} +1 -1
- package/assets/{index-C3qD42ws.js → index-CjeDxq0c.js} +1 -1
- package/assets/{index-DqMndQiP.js → index-Ct7OFnWK.js} +1 -1
- package/assets/{index-BaCHOxhI.js → index-CxxUIkx1.js} +1 -1
- package/assets/{index-B2dttXd7.js → index-D2Kc-3MO.js} +1 -1
- package/assets/{index-Bq8mPedC.js → index-DK0XEByD.js} +1 -1
- package/assets/{index-DllL1sx1.js → index-DQcV2DYj.js} +1 -1
- package/assets/{index-BP77pG9S.js → index-DSKRBuhn.js} +73 -73
- package/assets/{index-BXzGHPXz.js → index-DXqL8NRE.js} +2 -2
- package/assets/{index-B7V7-Vxz.js → index-DcLrRIlS.js} +1 -1
- package/assets/{index-D4pIkh3S.js → index-DdA-dF0D.js} +1 -1
- package/assets/{index-QkP3EC02.js → index-DdxTnVj5.js} +1 -1
- package/assets/{index-CmyOpFTN.js → index-DmrnShhc.js} +1 -1
- package/assets/{index-Bti3m-iW.js → index-QO0uGVDP.js} +1 -1
- package/assets/{index-BPR_DyGD.js → index-aIKXDB1M.js} +1 -1
- package/assets/{index-DIiIUyb-.js → index-n6JhNACm.js} +1 -1
- package/assets/{index-BWrcsh8u.js → index-qbS_W7PW.js} +1 -1
- package/assets/index-v7iKPBEu.css +1 -0
- package/assets/{infoDiagram-f8f76790-DpFqCCNv.js → infoDiagram-f8f76790-DEWQ3NXc.js} +1 -1
- package/assets/{infoDiagram-f8f76790-G2JpJJP7.js → infoDiagram-f8f76790-u_qjHFCm.js} +3 -3
- package/assets/{journeyDiagram-49397b02-DLLTobRM.js → journeyDiagram-49397b02-C8A30CBr.js} +1 -1
- package/assets/{journeyDiagram-49397b02-BdESGMrv.js → journeyDiagram-49397b02-hQJfqZih.js} +1 -1
- package/assets/{layout-WSR0vNPO.js → layout-CFKTKeqQ.js} +1 -1
- package/assets/{layout-CVkuImk4.js → layout-DvRyabpG.js} +1 -1
- package/assets/{line-D3bnG227.js → line-C72F34Po.js} +1 -1
- package/assets/{line-DQuMJveY.js → line-W5R41tQ1.js} +1 -1
- package/assets/{linear-CciP-97N.js → linear-7FXykgHM.js} +1 -1
- package/assets/{linear-Z6vFvuwc.js → linear-CVyOPUvz.js} +1 -1
- package/assets/main-BO0HWBB1.js +42 -0
- package/assets/{main-8DNjLY6L.css → main-DAuE07rf.css} +1 -1
- package/assets/{mermaid.core-CwZQb0ud.js → mermaid.core-2VcXhn43.js} +4 -4
- package/assets/{mindmap-definition-fc14e90a-BnJ8OOTI.js → mindmap-definition-fc14e90a-CcVXKGg4.js} +1 -1
- package/assets/{mindmap-definition-fc14e90a-CxQMydCW.js → mindmap-definition-fc14e90a-DRDYXe84.js} +2 -2
- package/assets/model-benchmark-launcher-C2qBiDlM.js +1 -0
- package/assets/{photo-wall-splitter-BZ88W5Zy.js → photo-wall-splitter-CFntSOdS.js} +1 -1
- package/assets/{pieDiagram-8a3498a8-BSDdkTeu.js → pieDiagram-8a3498a8-Be9dl9pU.js} +1 -1
- package/assets/{pieDiagram-8a3498a8-D6VtAoja.js → pieDiagram-8a3498a8-DXZOJYBv.js} +5 -5
- package/assets/ppt-generation-B5fKT_by.js +48 -0
- package/assets/{quadrantDiagram-120e2f19-C8k2xuBd.js → quadrantDiagram-120e2f19-DoU7aQSp.js} +3 -3
- package/assets/{quadrantDiagram-120e2f19-BbFV0VXY.js → quadrantDiagram-120e2f19-l5K9mx26.js} +1 -1
- package/assets/{requirementDiagram-deff3bca-C3xMdzJV.js → requirementDiagram-deff3bca-DN_87Wxe.js} +1 -1
- package/assets/{requirementDiagram-deff3bca-CpjqKjYZ.js → requirementDiagram-deff3bca-DOooq7kx.js} +1 -1
- package/assets/retry-image-BmIVuAVu.js +6 -0
- package/assets/{sankeyDiagram-04a897e0-Bjvig9bW.js → sankeyDiagram-04a897e0-BB33nO6_.js} +1 -1
- package/assets/{sankeyDiagram-04a897e0-BBJJgsFv.js → sankeyDiagram-04a897e0-CCa0hwml.js} +2 -2
- package/assets/{sequenceDiagram-704730f1-PFC_9IFI.js → sequenceDiagram-704730f1-CNhXZiix.js} +1 -1
- package/assets/{sequenceDiagram-704730f1-DX1MmBN_.js → sequenceDiagram-704730f1-ZqQ1QrY-.js} +1 -1
- package/assets/settings-dialog-BFuWTeQR.js +1 -0
- package/assets/{skill-dsl-parser-syNWYxFu.js → skill-dsl-parser-b-aB3P5u.js} +1 -1
- package/assets/{stateDiagram-587899a1-Dc3mI_B_.js → stateDiagram-587899a1-BBlOIJr7.js} +1 -1
- package/assets/{stateDiagram-587899a1-SFMUVAam.js → stateDiagram-587899a1-BhomZ0AN.js} +1 -1
- package/assets/{stateDiagram-v2-d93cdb3a-P4Yt2p_j.js → stateDiagram-v2-d93cdb3a-BpJsgMa0.js} +1 -1
- package/assets/stateDiagram-v2-d93cdb3a-CuOn9du2.js +1 -0
- package/assets/{styles-6aaf32cf-C3jnr3hV.js → styles-6aaf32cf-C2WEewoA.js} +1 -1
- package/assets/{styles-6aaf32cf-b6AdP7fu.js → styles-6aaf32cf-CTtf8moq.js} +1 -1
- package/assets/{styles-9a916d00-BUiD9iBe.js → styles-9a916d00-44Xi2-39.js} +1 -1
- package/assets/{styles-9a916d00-ii0XTdIm.js → styles-9a916d00-DUU3VgNe.js} +4 -4
- package/assets/{styles-c10674c1-DbTSpy3W.js → styles-c10674c1--AhWSGg7.js} +1 -1
- package/assets/styles-c10674c1-DS-wE3tK.js +116 -0
- package/assets/{svgDrawCommon-08f97a94-D4gIV05W.js → svgDrawCommon-08f97a94-CXcvtSSy.js} +1 -1
- package/assets/{svgDrawCommon-08f97a94-BK6lD4D6.js → svgDrawCommon-08f97a94-Dyqh4V2s.js} +1 -1
- package/assets/{timeline-definition-85554ec2-Bl2y548G.js → timeline-definition-85554ec2-k788753O.js} +1 -1
- package/assets/{timeline-definition-85554ec2-CWKg_JxG.js → timeline-definition-85554ec2-sw6-UuBe.js} +1 -1
- package/assets/tool-windows-BEnZdJqF.js +2948 -0
- package/assets/{tool-windows-CUNbW1Tz.css → tool-windows-D61Le6TE.css} +1 -1
- package/assets/ttd-dialog-BJzXAZ0l.js +99 -0
- package/assets/useCharacters-DRBoWvvj.css +1 -0
- package/assets/useCharacters-gsV4K8Hn.js +2 -0
- package/assets/{useWorkflowSubmission-BizuDmuw.js → useWorkflowSubmission-XRR21P6Y.js} +10 -10
- package/assets/{video-analyze-6v3Y-rUE.js → video-analyze-CpbnpSiI.js} +1 -1
- package/assets/{video-generation-DnmTlWnm.js → video-generation-BXhS7x-a.js} +2 -2
- package/assets/video-recovery-service-6G2lkWcu.js +1 -0
- package/assets/workflow-generation-utils-CYxtSHl0.js +3 -0
- package/assets/workflow-submission-service-DygSzydV.js +4 -0
- package/assets/{xychartDiagram-e933f94c-CJX9_9WF.js → xychartDiagram-e933f94c-CvTTDwPb.js} +3 -3
- package/assets/{xychartDiagram-e933f94c-Cep8CQEH.js → xychartDiagram-e933f94c-Ga9zi9ha.js} +1 -1
- package/changelog.json +45 -0
- package/idle-prefetch-manifest.json +310 -306
- package/manifest.json +3 -3
- package/package.json +1 -1
- package/precache-manifest.json +10 -10
- package/sitemap.xml +37 -0
- package/sw-debug/app.js +269 -156
- package/sw-debug/backup-restore.js +235 -103
- package/sw-debug/crypto-helper.js +116 -120
- package/sw-debug/duplex-client.js +33 -19
- package/sw-debug/gist-management.js +938 -655
- package/sw-debug/sw-communication.js +87 -50
- package/sw.js +5 -5
- package/version.json +7 -17
- package/assets/CanvasAudioPlayer-DutgwdiU.js +0 -2
- package/assets/ChatDrawer-0jNvKIoI.css +0 -1
- package/assets/ChatDrawer-E6Fog16I.js +0 -11
- package/assets/ChatMessagesArea-Bgwiz_XP.js +0 -250
- package/assets/ChatMessagesArea-DyR_wHFQ.css +0 -1
- package/assets/DeferredAIInputBar-CPkD8Cv9.js +0 -8
- package/assets/DrawnixDeferredFeatures-Cul76-88.js +0 -2
- package/assets/ModelBenchmarkWorkbench-Dv9SNYTi.js +0 -2
- package/assets/MusicAnalyzer-DhtRGpAp.css +0 -1
- package/assets/ResizableDivider-CaG9JQbJ.js +0 -1
- package/assets/TaskQueuePanel-BNhNzGp5.js +0 -1
- package/assets/ai-generation-preferences-service-BF7QkuJp.js +0 -1
- package/assets/arc-DmGoDCEL.js +0 -1
- package/assets/channel-CZjE-WNp.js +0 -1
- package/assets/channel-SXZD6bG3.js +0 -1
- package/assets/classDiagram-v2-f2320105-CsxWGkSy.js +0 -2
- package/assets/clone-C4mvvMDb.js +0 -1
- package/assets/createText-2e5e7dd3-CkXS3l7I.js +0 -5
- package/assets/flowDiagram-v2-96b9c2cf-BCcJ-cre.js +0 -1
- package/assets/flowDiagram-v2-96b9c2cf-DCVaFEgV.js +0 -1
- package/assets/index-3eqA7Bag.js +0 -1
- package/assets/index-7Q-aiwln.js +0 -4
- package/assets/index-B1XP5g7P.css +0 -1
- package/assets/index-BHPTfCZM.js +0 -1
- package/assets/index-BqHZCcen.js +0 -2
- package/assets/index-DcZrElEK.js +0 -2
- package/assets/index-gQ7LgvSv.js +0 -12
- package/assets/main-BFEkxCOK.js +0 -56
- package/assets/model-benchmark-launcher-Ct3lYXVq.js +0 -1
- package/assets/ppt-generation-FzjP_rp0.js +0 -48
- package/assets/settings-dialog-CQM4v791.js +0 -1
- package/assets/stateDiagram-v2-d93cdb3a-DmnBjHpC.js +0 -1
- package/assets/styles-c10674c1-Bt89Yy8Q.js +0 -116
- package/assets/tool-windows-DOIu9TAD.js +0 -2975
- package/assets/ttd-dialog-DI4kQSg8.js +0 -99
- package/assets/useCharacters-Chv4Ky7x.js +0 -7
- package/assets/useCharacters-OrDbG69o.css +0 -1
- package/assets/video-recovery-service-BeRoh0s2.js +0 -1
- package/assets/workflow-generation-utils-me381Cax.js +0 -3
- package/assets/workflow-submission-service-YOpo7g37.js +0 -4
|
@@ -57,7 +57,7 @@ export function initGistManagement() {
|
|
|
57
57
|
// Toolbar
|
|
58
58
|
refreshBtn: document.getElementById('refreshGistBtn'),
|
|
59
59
|
status: document.getElementById('gistStatus'),
|
|
60
|
-
|
|
60
|
+
|
|
61
61
|
// Config section
|
|
62
62
|
tokenStatus: document.getElementById('gistTokenStatus'),
|
|
63
63
|
gistId: document.getElementById('gistId'),
|
|
@@ -65,7 +65,7 @@ export function initGistManagement() {
|
|
|
65
65
|
passwordStatus: document.getElementById('gistPasswordStatus'),
|
|
66
66
|
deviceId: document.getElementById('gistDeviceId'),
|
|
67
67
|
lastSync: document.getElementById('gistLastSync'),
|
|
68
|
-
|
|
68
|
+
|
|
69
69
|
// Shard section
|
|
70
70
|
shardTotalCount: document.getElementById('shardTotalCount'),
|
|
71
71
|
shardActiveCount: document.getElementById('shardActiveCount'),
|
|
@@ -73,7 +73,7 @@ export function initGistManagement() {
|
|
|
73
73
|
shardFileCount: document.getElementById('shardFileCount'),
|
|
74
74
|
shardTotalSize: document.getElementById('shardTotalSize'),
|
|
75
75
|
shardList: document.getElementById('gistShardList'),
|
|
76
|
-
|
|
76
|
+
|
|
77
77
|
// Diagnostics - Tasks
|
|
78
78
|
localTaskCount: document.getElementById('localTaskCount'),
|
|
79
79
|
remoteTaskCount: document.getElementById('remoteTaskCount'),
|
|
@@ -81,21 +81,21 @@ export function initGistManagement() {
|
|
|
81
81
|
remoteOnlyTaskCount: document.getElementById('remoteOnlyTaskCount'),
|
|
82
82
|
syncedTaskCount: document.getElementById('syncedTaskCount'),
|
|
83
83
|
taskComparisonTable: document.getElementById('taskComparisonTable'),
|
|
84
|
-
|
|
84
|
+
|
|
85
85
|
// Diagnostics - Boards
|
|
86
86
|
localBoardCount: document.getElementById('localBoardCount'),
|
|
87
87
|
remoteBoardCount: document.getElementById('remoteBoardCount'),
|
|
88
88
|
localOnlyBoardCount: document.getElementById('localOnlyBoardCount'),
|
|
89
89
|
remoteOnlyBoardCount: document.getElementById('remoteOnlyBoardCount'),
|
|
90
90
|
boardComparisonTable: document.getElementById('boardComparisonTable'),
|
|
91
|
-
|
|
91
|
+
|
|
92
92
|
// Diagnostics - Media
|
|
93
93
|
localMediaCount: document.getElementById('localMediaCount'),
|
|
94
94
|
remoteMediaCount: document.getElementById('remoteMediaCount'),
|
|
95
95
|
localOnlyMediaCount: document.getElementById('localOnlyMediaCount'),
|
|
96
96
|
remoteOnlyMediaCount: document.getElementById('remoteOnlyMediaCount'),
|
|
97
97
|
mediaComparisonTable: document.getElementById('mediaComparisonTable'),
|
|
98
|
-
|
|
98
|
+
|
|
99
99
|
// Files browser
|
|
100
100
|
fileList: document.getElementById('gistFileList'),
|
|
101
101
|
fileContent: document.getElementById('gistFileContent'),
|
|
@@ -108,14 +108,14 @@ export function initGistManagement() {
|
|
|
108
108
|
}
|
|
109
109
|
|
|
110
110
|
// Section collapse/expand
|
|
111
|
-
document.querySelectorAll('.gist-section-header').forEach(header => {
|
|
111
|
+
document.querySelectorAll('.gist-section-header').forEach((header) => {
|
|
112
112
|
header.addEventListener('click', () => {
|
|
113
113
|
const section = header.closest('.gist-section');
|
|
114
114
|
section.classList.toggle('collapsed');
|
|
115
|
-
|
|
115
|
+
|
|
116
116
|
const sectionType = header.dataset.section;
|
|
117
117
|
const isExpanded = !section.classList.contains('collapsed');
|
|
118
|
-
|
|
118
|
+
|
|
119
119
|
// Auto-refresh sync logs when section is expanded
|
|
120
120
|
if (sectionType === 'synclogs') {
|
|
121
121
|
if (isExpanded) {
|
|
@@ -131,16 +131,18 @@ export function initGistManagement() {
|
|
|
131
131
|
}
|
|
132
132
|
}
|
|
133
133
|
}
|
|
134
|
-
|
|
134
|
+
|
|
135
135
|
// Auto-load diagnostics data when section is expanded
|
|
136
136
|
if (sectionType === 'diagnostics' && isExpanded) {
|
|
137
137
|
loadFullDiagnostics();
|
|
138
138
|
}
|
|
139
139
|
});
|
|
140
140
|
});
|
|
141
|
-
|
|
141
|
+
|
|
142
142
|
// Auto-start sync log refresh if section is already expanded (on page load)
|
|
143
|
-
const syncLogsSection = document
|
|
143
|
+
const syncLogsSection = document
|
|
144
|
+
.querySelector('[data-section="synclogs"]')
|
|
145
|
+
?.closest('.gist-section');
|
|
144
146
|
if (syncLogsSection && !syncLogsSection.classList.contains('collapsed')) {
|
|
145
147
|
// Delay to ensure initialization is complete
|
|
146
148
|
setTimeout(() => {
|
|
@@ -150,34 +152,41 @@ export function initGistManagement() {
|
|
|
150
152
|
}
|
|
151
153
|
}, 500);
|
|
152
154
|
}
|
|
153
|
-
|
|
155
|
+
|
|
154
156
|
// Auto-load diagnostics if section is already expanded (on page load)
|
|
155
|
-
const diagnosticsSection = document
|
|
156
|
-
|
|
157
|
+
const diagnosticsSection = document
|
|
158
|
+
.querySelector('[data-section="diagnostics"]')
|
|
159
|
+
?.closest('.gist-section');
|
|
160
|
+
if (
|
|
161
|
+
diagnosticsSection &&
|
|
162
|
+
!diagnosticsSection.classList.contains('collapsed')
|
|
163
|
+
) {
|
|
157
164
|
setTimeout(() => {
|
|
158
165
|
loadFullDiagnostics();
|
|
159
166
|
}, 600);
|
|
160
167
|
}
|
|
161
168
|
|
|
162
169
|
// Sub-tab switching
|
|
163
|
-
document.querySelectorAll('.gist-subtab').forEach(tab => {
|
|
170
|
+
document.querySelectorAll('.gist-subtab').forEach((tab) => {
|
|
164
171
|
tab.addEventListener('click', () => {
|
|
165
172
|
const subtabName = tab.dataset.subtab;
|
|
166
173
|
switchSubtab(subtabName);
|
|
167
174
|
});
|
|
168
175
|
});
|
|
169
|
-
|
|
176
|
+
|
|
170
177
|
// Refresh local diagnostics button
|
|
171
|
-
document
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
178
|
+
document
|
|
179
|
+
.getElementById('refreshLocalDiagnostics')
|
|
180
|
+
?.addEventListener('click', () => {
|
|
181
|
+
loadLocalDiagnostics();
|
|
182
|
+
});
|
|
183
|
+
|
|
176
184
|
// Load remote diagnostics button (triggers full diagnostics including remote)
|
|
177
|
-
document
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
185
|
+
document
|
|
186
|
+
.getElementById('loadRemoteDiagnostics')
|
|
187
|
+
?.addEventListener('click', () => {
|
|
188
|
+
loadFullDiagnostics();
|
|
189
|
+
});
|
|
181
190
|
|
|
182
191
|
// Debug operation buttons
|
|
183
192
|
initDebugOperations();
|
|
@@ -192,12 +201,12 @@ export function initGistManagement() {
|
|
|
192
201
|
|
|
193
202
|
function switchSubtab(subtabName) {
|
|
194
203
|
// Update tab buttons
|
|
195
|
-
document.querySelectorAll('.gist-subtab').forEach(tab => {
|
|
204
|
+
document.querySelectorAll('.gist-subtab').forEach((tab) => {
|
|
196
205
|
tab.classList.toggle('active', tab.dataset.subtab === subtabName);
|
|
197
206
|
});
|
|
198
207
|
|
|
199
208
|
// Update tab content
|
|
200
|
-
document.querySelectorAll('.gist-subtab-content').forEach(content => {
|
|
209
|
+
document.querySelectorAll('.gist-subtab-content').forEach((content) => {
|
|
201
210
|
content.classList.toggle('active', content.id === `${subtabName}Subtab`);
|
|
202
211
|
});
|
|
203
212
|
}
|
|
@@ -209,9 +218,12 @@ function switchSubtab(subtabName) {
|
|
|
209
218
|
function updateStatus(msg, type = 'info') {
|
|
210
219
|
if (!elements.status) return;
|
|
211
220
|
elements.status.textContent = msg;
|
|
212
|
-
elements.status.style.color =
|
|
213
|
-
|
|
214
|
-
|
|
221
|
+
elements.status.style.color =
|
|
222
|
+
type === 'error'
|
|
223
|
+
? 'var(--error-color)'
|
|
224
|
+
: type === 'success'
|
|
225
|
+
? 'var(--success-color)'
|
|
226
|
+
: 'var(--text-secondary)';
|
|
215
227
|
}
|
|
216
228
|
|
|
217
229
|
// ====================================
|
|
@@ -223,7 +235,9 @@ async function loadLocalConfigInfo() {
|
|
|
223
235
|
// Device ID
|
|
224
236
|
const deviceId = getDeviceIdExported();
|
|
225
237
|
if (elements.deviceId) {
|
|
226
|
-
elements.deviceId.textContent = deviceId
|
|
238
|
+
elements.deviceId.textContent = deviceId
|
|
239
|
+
? deviceId.substring(0, 16) + '...'
|
|
240
|
+
: '-';
|
|
227
241
|
elements.deviceId.title = deviceId || '';
|
|
228
242
|
}
|
|
229
243
|
|
|
@@ -231,15 +245,17 @@ async function loadLocalConfigInfo() {
|
|
|
231
245
|
const config = await getSyncConfig();
|
|
232
246
|
if (config) {
|
|
233
247
|
if (elements.gistId) {
|
|
234
|
-
elements.gistId.textContent = config.gistId
|
|
235
|
-
config.gistId.substring(0, 12) + '...'
|
|
248
|
+
elements.gistId.textContent = config.gistId
|
|
249
|
+
? config.gistId.substring(0, 12) + '...'
|
|
250
|
+
: '未配置';
|
|
236
251
|
elements.gistId.title = config.gistId || '';
|
|
237
252
|
elements.gistId.classList.toggle('warning', !config.gistId);
|
|
238
253
|
}
|
|
239
|
-
|
|
254
|
+
|
|
240
255
|
if (elements.lastSync) {
|
|
241
|
-
elements.lastSync.textContent = config.lastSyncTime
|
|
242
|
-
formatTime(config.lastSyncTime)
|
|
256
|
+
elements.lastSync.textContent = config.lastSyncTime
|
|
257
|
+
? formatTime(config.lastSyncTime)
|
|
258
|
+
: '从未同步';
|
|
243
259
|
elements.lastSync.classList.toggle('warning', !config.lastSyncTime);
|
|
244
260
|
}
|
|
245
261
|
}
|
|
@@ -264,7 +280,6 @@ async function loadLocalConfigInfo() {
|
|
|
264
280
|
if (localMasterIndex) {
|
|
265
281
|
renderShardStats(localMasterIndex);
|
|
266
282
|
}
|
|
267
|
-
|
|
268
283
|
} catch (error) {
|
|
269
284
|
console.error('Failed to load local config:', error);
|
|
270
285
|
}
|
|
@@ -276,14 +291,14 @@ async function loadLocalConfigInfo() {
|
|
|
276
291
|
|
|
277
292
|
async function refreshGistData() {
|
|
278
293
|
updateStatus('正在加载...', 'info');
|
|
279
|
-
|
|
294
|
+
|
|
280
295
|
try {
|
|
281
296
|
// 1. Get Token
|
|
282
297
|
const encryptedToken = localStorage.getItem('github_sync_token');
|
|
283
298
|
if (!encryptedToken) {
|
|
284
299
|
throw new Error('未找到 GitHub Token');
|
|
285
300
|
}
|
|
286
|
-
|
|
301
|
+
|
|
287
302
|
try {
|
|
288
303
|
currentToken = await decryptToken(encryptedToken);
|
|
289
304
|
if (elements.tokenStatus) {
|
|
@@ -302,7 +317,7 @@ async function refreshGistData() {
|
|
|
302
317
|
const creds = await getGistCredentials();
|
|
303
318
|
currentGistId = creds.gistId;
|
|
304
319
|
currentCustomPassword = creds.customPassword;
|
|
305
|
-
|
|
320
|
+
|
|
306
321
|
if (!currentGistId) {
|
|
307
322
|
throw new Error('未配置 Gist ID');
|
|
308
323
|
}
|
|
@@ -332,7 +347,6 @@ async function refreshGistData() {
|
|
|
332
347
|
await runDiagnostics(gist);
|
|
333
348
|
|
|
334
349
|
updateStatus('加载完成', 'success');
|
|
335
|
-
|
|
336
350
|
} catch (error) {
|
|
337
351
|
console.error('Gist Refresh Error:', error);
|
|
338
352
|
updateStatus(error.message, 'error');
|
|
@@ -346,9 +360,9 @@ async function refreshGistData() {
|
|
|
346
360
|
async function fetchGist(gistId) {
|
|
347
361
|
const response = await fetch(`https://api.github.com/gists/${gistId}`, {
|
|
348
362
|
headers: {
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
}
|
|
363
|
+
Authorization: `token ${currentToken}`,
|
|
364
|
+
Accept: 'application/vnd.github.v3+json',
|
|
365
|
+
},
|
|
352
366
|
});
|
|
353
367
|
|
|
354
368
|
if (!response.ok) {
|
|
@@ -362,16 +376,15 @@ async function parseGistFile(fileData) {
|
|
|
362
376
|
try {
|
|
363
377
|
let content = fileData.content;
|
|
364
378
|
if (fileData.truncated) {
|
|
365
|
-
console.log('[parseGistFile] File is truncated, fetching from raw_url...');
|
|
366
379
|
const res = await fetch(fileData.raw_url);
|
|
367
380
|
content = await res.text();
|
|
368
381
|
}
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
382
|
+
const decrypted = await decryptGistFile(
|
|
383
|
+
content,
|
|
384
|
+
currentGistId,
|
|
385
|
+
currentCustomPassword
|
|
386
|
+
);
|
|
373
387
|
const parsed = JSON.parse(decrypted);
|
|
374
|
-
console.log('[parseGistFile] Parsed result type:', typeof parsed, Array.isArray(parsed) ? 'array' : 'object');
|
|
375
388
|
return parsed;
|
|
376
389
|
} catch (e) {
|
|
377
390
|
console.error('[parseGistFile] Failed to parse gist file:', e);
|
|
@@ -395,16 +408,17 @@ function renderShardStats(masterIndex) {
|
|
|
395
408
|
elements.shardTotalCount.textContent = shardList.length;
|
|
396
409
|
}
|
|
397
410
|
if (elements.shardActiveCount) {
|
|
398
|
-
elements.shardActiveCount.textContent =
|
|
399
|
-
|
|
411
|
+
elements.shardActiveCount.textContent =
|
|
412
|
+
stats.activeShards ||
|
|
413
|
+
shardList.filter((s) => s.status === 'active').length;
|
|
400
414
|
}
|
|
401
415
|
if (elements.shardFullCount) {
|
|
402
|
-
elements.shardFullCount.textContent =
|
|
403
|
-
shardList.filter(s => s.status === 'full').length;
|
|
416
|
+
elements.shardFullCount.textContent =
|
|
417
|
+
stats.fullShards || shardList.filter((s) => s.status === 'full').length;
|
|
404
418
|
}
|
|
405
419
|
if (elements.shardFileCount) {
|
|
406
|
-
elements.shardFileCount.textContent =
|
|
407
|
-
Object.keys(masterIndex.fileIndex || {}).length;
|
|
420
|
+
elements.shardFileCount.textContent =
|
|
421
|
+
stats.totalFiles || Object.keys(masterIndex.fileIndex || {}).length;
|
|
408
422
|
}
|
|
409
423
|
if (elements.shardTotalSize) {
|
|
410
424
|
elements.shardTotalSize.textContent = formatSize(stats.totalSize || 0);
|
|
@@ -413,20 +427,27 @@ function renderShardStats(masterIndex) {
|
|
|
413
427
|
// Render shard list
|
|
414
428
|
if (elements.shardList) {
|
|
415
429
|
if (shardList.length === 0) {
|
|
416
|
-
elements.shardList.innerHTML =
|
|
430
|
+
elements.shardList.innerHTML =
|
|
431
|
+
'<div class="empty-state" style="padding: 20px;">无分片数据</div>';
|
|
417
432
|
return;
|
|
418
433
|
}
|
|
419
434
|
|
|
420
|
-
elements.shardList.innerHTML = shardList
|
|
435
|
+
elements.shardList.innerHTML = shardList
|
|
436
|
+
.map(
|
|
437
|
+
(shard) => `
|
|
421
438
|
<div class="gist-shard-item">
|
|
422
|
-
<span class="gist-shard-id">${escapeHtml(
|
|
439
|
+
<span class="gist-shard-id">${escapeHtml(
|
|
440
|
+
shard.alias || shard.gistId?.substring(0, 8)
|
|
441
|
+
)}</span>
|
|
423
442
|
<span class="gist-shard-info">
|
|
424
443
|
<span>${shard.fileCount || 0} 文件</span>
|
|
425
444
|
<span>${formatSize(shard.totalSize || 0)}</span>
|
|
426
445
|
</span>
|
|
427
446
|
<span class="gist-shard-status ${shard.status}">${shard.status}</span>
|
|
428
447
|
</div>
|
|
429
|
-
`
|
|
448
|
+
`
|
|
449
|
+
)
|
|
450
|
+
.join('');
|
|
430
451
|
}
|
|
431
452
|
}
|
|
432
453
|
|
|
@@ -436,45 +457,59 @@ function renderShardStats(masterIndex) {
|
|
|
436
457
|
|
|
437
458
|
function renderFileList(files) {
|
|
438
459
|
if (!elements.fileList) return;
|
|
439
|
-
|
|
460
|
+
|
|
440
461
|
const fileNames = Object.keys(files).sort();
|
|
441
|
-
|
|
462
|
+
|
|
442
463
|
if (fileNames.length === 0) {
|
|
443
|
-
elements.fileList.innerHTML =
|
|
464
|
+
elements.fileList.innerHTML =
|
|
465
|
+
'<div class="empty-state" style="padding: 20px;">无文件</div>';
|
|
444
466
|
return;
|
|
445
467
|
}
|
|
446
468
|
|
|
447
|
-
elements.fileList.innerHTML = fileNames
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
469
|
+
elements.fileList.innerHTML = fileNames
|
|
470
|
+
.map((fileName) => {
|
|
471
|
+
let type = 'Text';
|
|
472
|
+
if (
|
|
473
|
+
fileName.endsWith('.png') ||
|
|
474
|
+
fileName.endsWith('.jpg') ||
|
|
475
|
+
fileName.endsWith('.jpeg')
|
|
476
|
+
) {
|
|
477
|
+
type = 'Image';
|
|
478
|
+
} else if (fileName.startsWith('board_') && fileName.endsWith('.json')) {
|
|
479
|
+
type = 'Board';
|
|
480
|
+
} else if (fileName.endsWith('.drawnix')) {
|
|
481
|
+
type = 'Canvas';
|
|
482
|
+
} else if (
|
|
483
|
+
fileName === 'tasks.json' ||
|
|
484
|
+
fileName === 'master-index.json' ||
|
|
485
|
+
fileName === 'workspace.json' ||
|
|
486
|
+
fileName === 'prompts.json'
|
|
487
|
+
) {
|
|
488
|
+
type = 'Data';
|
|
489
|
+
} else if (fileName === 'shard-manifest.json') {
|
|
490
|
+
type = 'Manifest';
|
|
491
|
+
} else if (fileName.startsWith('media_') && fileName.endsWith('.json')) {
|
|
492
|
+
type = 'Media';
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
return `
|
|
464
496
|
<div class="gist-file-item" data-filename="${escapeHtml(fileName)}">
|
|
465
497
|
<span class="file-name">${escapeHtml(fileName)}</span>
|
|
466
498
|
<span class="file-type">${type}</span>
|
|
467
499
|
</div>
|
|
468
500
|
`;
|
|
469
|
-
|
|
501
|
+
})
|
|
502
|
+
.join('');
|
|
470
503
|
|
|
471
504
|
// Add click handlers
|
|
472
|
-
elements.fileList.querySelectorAll('.gist-file-item').forEach(item => {
|
|
505
|
+
elements.fileList.querySelectorAll('.gist-file-item').forEach((item) => {
|
|
473
506
|
item.addEventListener('click', () => {
|
|
474
507
|
// Update active state
|
|
475
|
-
elements.fileList
|
|
508
|
+
elements.fileList
|
|
509
|
+
.querySelectorAll('.gist-file-item')
|
|
510
|
+
.forEach((i) => i.classList.remove('active'));
|
|
476
511
|
item.classList.add('active');
|
|
477
|
-
|
|
512
|
+
|
|
478
513
|
const fileName = item.dataset.filename;
|
|
479
514
|
showFileContent(fileName, files[fileName]);
|
|
480
515
|
});
|
|
@@ -483,10 +518,11 @@ function renderFileList(files) {
|
|
|
483
518
|
|
|
484
519
|
async function showFileContent(fileName, fileData) {
|
|
485
520
|
if (!elements.previewName || !elements.fileContent) return;
|
|
486
|
-
|
|
521
|
+
|
|
487
522
|
elements.previewName.textContent = fileName;
|
|
488
|
-
elements.fileContent.innerHTML =
|
|
489
|
-
|
|
523
|
+
elements.fileContent.innerHTML =
|
|
524
|
+
'<div class="loading-state">正在加载...</div>';
|
|
525
|
+
|
|
490
526
|
try {
|
|
491
527
|
let content = fileData.content;
|
|
492
528
|
if (fileData.truncated) {
|
|
@@ -498,22 +534,36 @@ async function showFileContent(fileName, fileData) {
|
|
|
498
534
|
let decryptedContent = content;
|
|
499
535
|
let decryptFailed = false;
|
|
500
536
|
try {
|
|
501
|
-
decryptedContent = await decryptGistFile(
|
|
537
|
+
decryptedContent = await decryptGistFile(
|
|
538
|
+
content,
|
|
539
|
+
currentGistId,
|
|
540
|
+
currentCustomPassword
|
|
541
|
+
);
|
|
502
542
|
} catch (e) {
|
|
503
543
|
console.warn('Decryption failed, showing raw', e);
|
|
504
544
|
decryptFailed = true;
|
|
505
545
|
}
|
|
506
|
-
|
|
546
|
+
|
|
507
547
|
// Parse JSON if possible
|
|
508
548
|
let parsedData = null;
|
|
509
549
|
try {
|
|
510
550
|
parsedData = JSON.parse(decryptedContent);
|
|
511
|
-
} catch {
|
|
512
|
-
|
|
551
|
+
} catch (err) {
|
|
552
|
+
void err;
|
|
553
|
+
}
|
|
554
|
+
|
|
513
555
|
// Render based on file type
|
|
514
|
-
if (
|
|
556
|
+
if (
|
|
557
|
+
fileName.startsWith('media_') &&
|
|
558
|
+
fileName.endsWith('.json') &&
|
|
559
|
+
parsedData
|
|
560
|
+
) {
|
|
515
561
|
renderMediaFilePreview(parsedData, decryptFailed);
|
|
516
|
-
} else if (
|
|
562
|
+
} else if (
|
|
563
|
+
fileName.startsWith('board_') &&
|
|
564
|
+
fileName.endsWith('.json') &&
|
|
565
|
+
parsedData
|
|
566
|
+
) {
|
|
517
567
|
renderBoardFilePreview(parsedData, decryptFailed);
|
|
518
568
|
} else if (fileName === 'tasks.json' && parsedData) {
|
|
519
569
|
renderTasksFilePreview(parsedData, decryptFailed);
|
|
@@ -523,14 +573,20 @@ async function showFileContent(fileName, fileData) {
|
|
|
523
573
|
renderMasterIndexPreview(parsedData, decryptFailed);
|
|
524
574
|
} else {
|
|
525
575
|
// Default: show formatted JSON or raw text
|
|
526
|
-
let displayContent = parsedData
|
|
576
|
+
let displayContent = parsedData
|
|
577
|
+
? JSON.stringify(parsedData, null, 2)
|
|
578
|
+
: decryptedContent;
|
|
527
579
|
if (decryptFailed) {
|
|
528
580
|
displayContent = content + '\n\n[解密失败]';
|
|
529
581
|
}
|
|
530
|
-
elements.fileContent.innerHTML = `<pre class="file-raw-content">${escapeHtml(
|
|
582
|
+
elements.fileContent.innerHTML = `<pre class="file-raw-content">${escapeHtml(
|
|
583
|
+
displayContent
|
|
584
|
+
)}</pre>`;
|
|
531
585
|
}
|
|
532
586
|
} catch (e) {
|
|
533
|
-
elements.fileContent.innerHTML = `<div class="error-state">加载失败: ${escapeHtml(
|
|
587
|
+
elements.fileContent.innerHTML = `<div class="error-state">加载失败: ${escapeHtml(
|
|
588
|
+
e.message
|
|
589
|
+
)}</div>`;
|
|
534
590
|
}
|
|
535
591
|
}
|
|
536
592
|
|
|
@@ -542,7 +598,7 @@ function renderMediaFilePreview(data, decryptFailed) {
|
|
|
542
598
|
const isVideo = isVideoUrl(url);
|
|
543
599
|
const mimeType = data.mimeType || (isVideo ? 'video/*' : 'image/*');
|
|
544
600
|
const size = data.size ? formatSize(data.size) : '未知';
|
|
545
|
-
|
|
601
|
+
|
|
546
602
|
// Get preview URL - use the stored data URL if available
|
|
547
603
|
let previewUrl = '';
|
|
548
604
|
if (data.data && data.data.startsWith('data:')) {
|
|
@@ -552,8 +608,9 @@ function renderMediaFilePreview(data, decryptFailed) {
|
|
|
552
608
|
} else {
|
|
553
609
|
previewUrl = url;
|
|
554
610
|
}
|
|
555
|
-
|
|
556
|
-
const previewHtml = isVideo
|
|
611
|
+
|
|
612
|
+
const previewHtml = isVideo
|
|
613
|
+
? `
|
|
557
614
|
<video
|
|
558
615
|
class="media-file-preview"
|
|
559
616
|
src="${escapeHtml(previewUrl)}"
|
|
@@ -561,14 +618,15 @@ function renderMediaFilePreview(data, decryptFailed) {
|
|
|
561
618
|
muted
|
|
562
619
|
onerror="this.outerHTML='<div class=\\'preview-error\\'>视频加载失败</div>'"
|
|
563
620
|
></video>
|
|
564
|
-
`
|
|
621
|
+
`
|
|
622
|
+
: `
|
|
565
623
|
<img
|
|
566
624
|
class="media-file-preview"
|
|
567
625
|
src="${escapeHtml(previewUrl)}"
|
|
568
626
|
onerror="this.outerHTML='<div class=\\'preview-error\\'>图片加载失败</div>'"
|
|
569
627
|
/>
|
|
570
628
|
`;
|
|
571
|
-
|
|
629
|
+
|
|
572
630
|
elements.fileContent.innerHTML = `
|
|
573
631
|
<div class="file-preview-card">
|
|
574
632
|
<div class="preview-section">
|
|
@@ -589,9 +647,15 @@ function renderMediaFilePreview(data, decryptFailed) {
|
|
|
589
647
|
</div>
|
|
590
648
|
<div class="info-row">
|
|
591
649
|
<span class="info-label">原始 URL</span>
|
|
592
|
-
<span class="info-value url-value" title="${escapeHtml(
|
|
650
|
+
<span class="info-value url-value" title="${escapeHtml(
|
|
651
|
+
url
|
|
652
|
+
)}">${escapeHtml(url)}</span>
|
|
593
653
|
</div>
|
|
594
|
-
${
|
|
654
|
+
${
|
|
655
|
+
decryptFailed
|
|
656
|
+
? '<div class="warning-badge">解密失败,显示原始数据</div>'
|
|
657
|
+
: ''
|
|
658
|
+
}
|
|
595
659
|
</div>
|
|
596
660
|
</div>
|
|
597
661
|
`;
|
|
@@ -605,7 +669,7 @@ function renderBoardFilePreview(data, decryptFailed) {
|
|
|
605
669
|
const id = data.id || '';
|
|
606
670
|
const createdAt = data.createdAt ? formatTime(data.createdAt) : '未知';
|
|
607
671
|
const updatedAt = data.updatedAt ? formatTime(data.updatedAt) : '未知';
|
|
608
|
-
|
|
672
|
+
|
|
609
673
|
// Count elements
|
|
610
674
|
const elements_arr = data.elements || [];
|
|
611
675
|
let imageCount = 0;
|
|
@@ -613,8 +677,8 @@ function renderBoardFilePreview(data, decryptFailed) {
|
|
|
613
677
|
let textCount = 0;
|
|
614
678
|
let shapeCount = 0;
|
|
615
679
|
let otherCount = 0;
|
|
616
|
-
|
|
617
|
-
elements_arr.forEach(el => {
|
|
680
|
+
|
|
681
|
+
elements_arr.forEach((el) => {
|
|
618
682
|
const type = el.type || '';
|
|
619
683
|
if (type === 'image' || type.includes('image')) {
|
|
620
684
|
imageCount++;
|
|
@@ -622,15 +686,20 @@ function renderBoardFilePreview(data, decryptFailed) {
|
|
|
622
686
|
videoCount++;
|
|
623
687
|
} else if (type === 'text' || type.includes('text')) {
|
|
624
688
|
textCount++;
|
|
625
|
-
} else if (
|
|
689
|
+
} else if (
|
|
690
|
+
type === 'geometry' ||
|
|
691
|
+
type === 'line' ||
|
|
692
|
+
type === 'arrow' ||
|
|
693
|
+
type.includes('shape')
|
|
694
|
+
) {
|
|
626
695
|
shapeCount++;
|
|
627
696
|
} else {
|
|
628
697
|
otherCount++;
|
|
629
698
|
}
|
|
630
699
|
});
|
|
631
|
-
|
|
700
|
+
|
|
632
701
|
const totalElements = elements_arr.length;
|
|
633
|
-
|
|
702
|
+
|
|
634
703
|
elements.fileContent.innerHTML = `
|
|
635
704
|
<div class="file-preview-card">
|
|
636
705
|
<div class="preview-header">
|
|
@@ -668,11 +737,17 @@ function renderBoardFilePreview(data, decryptFailed) {
|
|
|
668
737
|
<span class="info-label">更新时间</span>
|
|
669
738
|
<span class="info-value">${updatedAt}</span>
|
|
670
739
|
</div>
|
|
671
|
-
${
|
|
740
|
+
${
|
|
741
|
+
decryptFailed
|
|
742
|
+
? '<div class="warning-badge">解密失败,显示原始数据</div>'
|
|
743
|
+
: ''
|
|
744
|
+
}
|
|
672
745
|
</div>
|
|
673
746
|
<details class="raw-data-toggle">
|
|
674
747
|
<summary>查看原始数据</summary>
|
|
675
|
-
<pre class="file-raw-content">${escapeHtml(
|
|
748
|
+
<pre class="file-raw-content">${escapeHtml(
|
|
749
|
+
JSON.stringify(data, null, 2)
|
|
750
|
+
)}</pre>
|
|
676
751
|
</details>
|
|
677
752
|
</div>
|
|
678
753
|
`;
|
|
@@ -684,19 +759,19 @@ function renderBoardFilePreview(data, decryptFailed) {
|
|
|
684
759
|
function renderTasksFilePreview(data, decryptFailed) {
|
|
685
760
|
const tasks = data.completedTasks || (Array.isArray(data) ? data : []);
|
|
686
761
|
const taskCount = tasks.length;
|
|
687
|
-
|
|
762
|
+
|
|
688
763
|
// Count by type
|
|
689
764
|
let imageTaskCount = 0;
|
|
690
765
|
let videoTaskCount = 0;
|
|
691
|
-
|
|
692
|
-
tasks.forEach(task => {
|
|
766
|
+
|
|
767
|
+
tasks.forEach((task) => {
|
|
693
768
|
if (task.type === 'image') imageTaskCount++;
|
|
694
769
|
else if (task.type === 'video') videoTaskCount++;
|
|
695
770
|
});
|
|
696
|
-
|
|
771
|
+
|
|
697
772
|
// Show recent tasks
|
|
698
773
|
const recentTasks = tasks.slice(-5).reverse();
|
|
699
|
-
|
|
774
|
+
|
|
700
775
|
elements.fileContent.innerHTML = `
|
|
701
776
|
<div class="file-preview-card">
|
|
702
777
|
<div class="preview-header">
|
|
@@ -716,22 +791,44 @@ function renderTasksFilePreview(data, decryptFailed) {
|
|
|
716
791
|
<span class="stat-label">视频任务</span>
|
|
717
792
|
</div>
|
|
718
793
|
</div>
|
|
719
|
-
${
|
|
794
|
+
${
|
|
795
|
+
recentTasks.length > 0
|
|
796
|
+
? `
|
|
720
797
|
<div class="preview-list">
|
|
721
798
|
<h4>最近任务</h4>
|
|
722
|
-
${recentTasks
|
|
799
|
+
${recentTasks
|
|
800
|
+
.map(
|
|
801
|
+
(task) => `
|
|
723
802
|
<div class="list-item">
|
|
724
|
-
<span class="item-type ${task.type || 'other'}">${
|
|
725
|
-
|
|
726
|
-
|
|
803
|
+
<span class="item-type ${task.type || 'other'}">${
|
|
804
|
+
task.type === 'image'
|
|
805
|
+
? '图片'
|
|
806
|
+
: task.type === 'video'
|
|
807
|
+
? '视频'
|
|
808
|
+
: '其他'
|
|
809
|
+
}</span>
|
|
810
|
+
<span class="item-name" title="${escapeHtml(
|
|
811
|
+
task.name || task.prompt || ''
|
|
812
|
+
)}">${escapeHtml(
|
|
813
|
+
(task.name || task.prompt || '').substring(0, 50)
|
|
814
|
+
)}</span>
|
|
815
|
+
<span class="item-time">${
|
|
816
|
+
task.completedAt ? formatTime(task.completedAt) : ''
|
|
817
|
+
}</span>
|
|
727
818
|
</div>
|
|
728
|
-
`
|
|
819
|
+
`
|
|
820
|
+
)
|
|
821
|
+
.join('')}
|
|
729
822
|
</div>
|
|
730
|
-
`
|
|
823
|
+
`
|
|
824
|
+
: ''
|
|
825
|
+
}
|
|
731
826
|
${decryptFailed ? '<div class="warning-badge">解密失败</div>' : ''}
|
|
732
827
|
<details class="raw-data-toggle">
|
|
733
828
|
<summary>查看原始数据</summary>
|
|
734
|
-
<pre class="file-raw-content">${escapeHtml(
|
|
829
|
+
<pre class="file-raw-content">${escapeHtml(
|
|
830
|
+
JSON.stringify(data, null, 2)
|
|
831
|
+
)}</pre>
|
|
735
832
|
</details>
|
|
736
833
|
</div>
|
|
737
834
|
`;
|
|
@@ -744,7 +841,7 @@ function renderWorkspaceFilePreview(data, decryptFailed) {
|
|
|
744
841
|
const folders = data.folders || [];
|
|
745
842
|
const boardMetadata = data.boardMetadata || [];
|
|
746
843
|
const currentBoardId = data.currentBoardId || '无';
|
|
747
|
-
|
|
844
|
+
|
|
748
845
|
elements.fileContent.innerHTML = `
|
|
749
846
|
<div class="file-preview-card">
|
|
750
847
|
<div class="preview-header">
|
|
@@ -766,22 +863,43 @@ function renderWorkspaceFilePreview(data, decryptFailed) {
|
|
|
766
863
|
<span class="info-value">${escapeHtml(currentBoardId)}</span>
|
|
767
864
|
</div>
|
|
768
865
|
</div>
|
|
769
|
-
${
|
|
866
|
+
${
|
|
867
|
+
boardMetadata.length > 0
|
|
868
|
+
? `
|
|
770
869
|
<div class="preview-list">
|
|
771
870
|
<h4>画板列表</h4>
|
|
772
|
-
${boardMetadata
|
|
871
|
+
${boardMetadata
|
|
872
|
+
.slice(0, 10)
|
|
873
|
+
.map(
|
|
874
|
+
(board) => `
|
|
773
875
|
<div class="list-item">
|
|
774
|
-
<span class="item-name">${escapeHtml(
|
|
775
|
-
|
|
876
|
+
<span class="item-name">${escapeHtml(
|
|
877
|
+
board.name || board.id || '未命名'
|
|
878
|
+
)}</span>
|
|
879
|
+
<span class="item-time">${
|
|
880
|
+
board.updatedAt ? formatTime(board.updatedAt) : ''
|
|
881
|
+
}</span>
|
|
776
882
|
</div>
|
|
777
|
-
`
|
|
778
|
-
|
|
883
|
+
`
|
|
884
|
+
)
|
|
885
|
+
.join('')}
|
|
886
|
+
${
|
|
887
|
+
boardMetadata.length > 10
|
|
888
|
+
? `<div class="list-more">还有 ${
|
|
889
|
+
boardMetadata.length - 10
|
|
890
|
+
} 个...</div>`
|
|
891
|
+
: ''
|
|
892
|
+
}
|
|
779
893
|
</div>
|
|
780
|
-
`
|
|
894
|
+
`
|
|
895
|
+
: ''
|
|
896
|
+
}
|
|
781
897
|
${decryptFailed ? '<div class="warning-badge">解密失败</div>' : ''}
|
|
782
898
|
<details class="raw-data-toggle">
|
|
783
899
|
<summary>查看原始数据</summary>
|
|
784
|
-
<pre class="file-raw-content">${escapeHtml(
|
|
900
|
+
<pre class="file-raw-content">${escapeHtml(
|
|
901
|
+
JSON.stringify(data, null, 2)
|
|
902
|
+
)}</pre>
|
|
785
903
|
</details>
|
|
786
904
|
</div>
|
|
787
905
|
`;
|
|
@@ -796,7 +914,7 @@ function renderMasterIndexPreview(data, decryptFailed) {
|
|
|
796
914
|
const version = data.version || '未知';
|
|
797
915
|
const appVersion = data.appVersion || '未知';
|
|
798
916
|
const updatedAt = data.updatedAt ? formatTime(data.updatedAt) : '未知';
|
|
799
|
-
|
|
917
|
+
|
|
800
918
|
elements.fileContent.innerHTML = `
|
|
801
919
|
<div class="file-preview-card">
|
|
802
920
|
<div class="preview-header">
|
|
@@ -829,7 +947,9 @@ function renderMasterIndexPreview(data, decryptFailed) {
|
|
|
829
947
|
${decryptFailed ? '<div class="warning-badge">解密失败</div>' : ''}
|
|
830
948
|
<details class="raw-data-toggle">
|
|
831
949
|
<summary>查看原始数据</summary>
|
|
832
|
-
<pre class="file-raw-content">${escapeHtml(
|
|
950
|
+
<pre class="file-raw-content">${escapeHtml(
|
|
951
|
+
JSON.stringify(data, null, 2)
|
|
952
|
+
)}</pre>
|
|
833
953
|
</details>
|
|
834
954
|
</div>
|
|
835
955
|
`;
|
|
@@ -844,74 +964,55 @@ function renderMasterIndexPreview(data, decryptFailed) {
|
|
|
844
964
|
* Automatically tries to get remote data if Token is available
|
|
845
965
|
*/
|
|
846
966
|
async function loadFullDiagnostics() {
|
|
847
|
-
console.log('[Diagnostics] Loading full diagnostics (local + remote)...');
|
|
848
|
-
|
|
849
967
|
// First load local data immediately
|
|
850
968
|
await loadLocalDiagnostics();
|
|
851
|
-
|
|
969
|
+
|
|
852
970
|
// Then try to load remote data
|
|
853
971
|
try {
|
|
854
972
|
// Check if Token is available
|
|
855
973
|
const encryptedToken = localStorage.getItem('github_sync_token');
|
|
856
974
|
if (!encryptedToken) {
|
|
857
|
-
console.log('[Diagnostics] No GitHub Token configured, skipping remote data');
|
|
858
975
|
updateRemoteStatus('未配置 Token');
|
|
859
976
|
return;
|
|
860
977
|
}
|
|
861
|
-
|
|
978
|
+
|
|
862
979
|
// Try to decrypt token
|
|
863
980
|
let token;
|
|
864
981
|
try {
|
|
865
982
|
token = await decryptToken(encryptedToken);
|
|
866
983
|
currentToken = token;
|
|
867
|
-
console.log('[Diagnostics] Token decrypted successfully');
|
|
868
984
|
} catch (e) {
|
|
869
985
|
console.warn('[Diagnostics] Failed to decrypt token:', e);
|
|
870
986
|
updateRemoteStatus('Token 解密失败');
|
|
871
987
|
return;
|
|
872
988
|
}
|
|
873
|
-
|
|
989
|
+
|
|
874
990
|
// Get Gist credentials
|
|
875
991
|
const creds = await getGistCredentials();
|
|
876
992
|
if (!creds.gistId) {
|
|
877
|
-
console.log('[Diagnostics] No Gist ID configured');
|
|
878
993
|
updateRemoteStatus('未配置 Gist');
|
|
879
994
|
return;
|
|
880
995
|
}
|
|
881
|
-
|
|
996
|
+
|
|
882
997
|
currentGistId = creds.gistId;
|
|
883
998
|
currentCustomPassword = creds.customPassword;
|
|
884
|
-
|
|
999
|
+
|
|
885
1000
|
// Update status
|
|
886
1001
|
updateRemoteStatus('正在加载...');
|
|
887
|
-
|
|
888
|
-
// Fetch Gist data
|
|
889
|
-
console.log('[Diagnostics] Fetching Gist data...');
|
|
890
1002
|
const gist = await fetchGist(currentGistId);
|
|
891
1003
|
currentGistData = gist;
|
|
892
|
-
|
|
1004
|
+
|
|
893
1005
|
// Parse master-index.json if exists
|
|
894
1006
|
if (gist.files && gist.files['master-index.json']) {
|
|
895
|
-
console.log('[Diagnostics] Parsing master-index.json...');
|
|
896
1007
|
remoteMasterIndex = await parseGistFile(gist.files['master-index.json']);
|
|
897
|
-
console.log('[Diagnostics] Parsed master-index:', remoteMasterIndex);
|
|
898
|
-
if (remoteMasterIndex) {
|
|
899
|
-
console.log('[Diagnostics] master-index.boards:', remoteMasterIndex.boards);
|
|
900
|
-
console.log('[Diagnostics] master-index keys:', Object.keys(remoteMasterIndex));
|
|
901
|
-
}
|
|
902
|
-
} else {
|
|
903
|
-
console.log('[Diagnostics] No master-index.json found in gist files');
|
|
904
1008
|
}
|
|
905
|
-
|
|
1009
|
+
|
|
906
1010
|
// Run full diagnostics comparison
|
|
907
1011
|
await runDiagnostics(gist);
|
|
908
|
-
|
|
1012
|
+
|
|
909
1013
|
// Render remote files list
|
|
910
1014
|
renderFileList(gist.files || {});
|
|
911
|
-
|
|
912
|
-
console.log('[Diagnostics] Full diagnostics loaded successfully');
|
|
913
1015
|
updateRemoteStatus(null); // Clear status
|
|
914
|
-
|
|
915
1016
|
} catch (error) {
|
|
916
1017
|
console.error('[Diagnostics] Failed to load remote data:', error);
|
|
917
1018
|
updateRemoteStatus(`加载失败: ${error.message}`);
|
|
@@ -925,7 +1026,7 @@ function updateRemoteStatus(message) {
|
|
|
925
1026
|
const remoteTaskCountEl = document.getElementById('remoteTaskCount');
|
|
926
1027
|
const remoteBoardCountEl = document.getElementById('remoteBoardCount');
|
|
927
1028
|
const remoteMediaCountEl = document.getElementById('remoteMediaCount');
|
|
928
|
-
|
|
1029
|
+
|
|
929
1030
|
if (message) {
|
|
930
1031
|
if (remoteTaskCountEl) remoteTaskCountEl.textContent = message;
|
|
931
1032
|
if (remoteBoardCountEl) remoteBoardCountEl.textContent = message;
|
|
@@ -938,8 +1039,6 @@ function updateRemoteStatus(message) {
|
|
|
938
1039
|
* Called when diagnostics section is expanded
|
|
939
1040
|
*/
|
|
940
1041
|
async function loadLocalDiagnostics() {
|
|
941
|
-
console.log('[Diagnostics] Loading local diagnostics data...');
|
|
942
|
-
|
|
943
1042
|
// Get elements directly instead of relying on cached elements
|
|
944
1043
|
const localTaskCountEl = document.getElementById('localTaskCount');
|
|
945
1044
|
const localBoardCountEl = document.getElementById('localBoardCount');
|
|
@@ -951,81 +1050,111 @@ async function loadLocalDiagnostics() {
|
|
|
951
1050
|
const remoteOnlyTaskCountEl = document.getElementById('remoteOnlyTaskCount');
|
|
952
1051
|
const syncedTaskCountEl = document.getElementById('syncedTaskCount');
|
|
953
1052
|
const localOnlyBoardCountEl = document.getElementById('localOnlyBoardCount');
|
|
954
|
-
const remoteOnlyBoardCountEl = document.getElementById(
|
|
1053
|
+
const remoteOnlyBoardCountEl = document.getElementById(
|
|
1054
|
+
'remoteOnlyBoardCount'
|
|
1055
|
+
);
|
|
955
1056
|
const localOnlyMediaCountEl = document.getElementById('localOnlyMediaCount');
|
|
956
|
-
const remoteOnlyMediaCountEl = document.getElementById(
|
|
1057
|
+
const remoteOnlyMediaCountEl = document.getElementById(
|
|
1058
|
+
'remoteOnlyMediaCount'
|
|
1059
|
+
);
|
|
957
1060
|
const taskComparisonTableEl = document.getElementById('taskComparisonTable');
|
|
958
|
-
const boardComparisonTableEl = document.getElementById(
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
localMediaCount: !!localMediaCountEl,
|
|
965
|
-
taskComparisonTable: !!taskComparisonTableEl,
|
|
966
|
-
});
|
|
967
|
-
|
|
1061
|
+
const boardComparisonTableEl = document.getElementById(
|
|
1062
|
+
'boardComparisonTable'
|
|
1063
|
+
);
|
|
1064
|
+
const mediaComparisonTableEl = document.getElementById(
|
|
1065
|
+
'mediaComparisonTable'
|
|
1066
|
+
);
|
|
968
1067
|
try {
|
|
969
1068
|
// First, diagnose the databases to understand the structure
|
|
970
1069
|
const dbDiagnosis = {};
|
|
971
1070
|
try {
|
|
972
1071
|
dbDiagnosis.workspace = await diagnoseDatabase('aitu-workspace');
|
|
973
1072
|
dbDiagnosis.taskQueue = await diagnoseDatabase('sw-task-queue');
|
|
974
|
-
console.log('[Diagnostics] Database structure:', dbDiagnosis);
|
|
975
1073
|
} catch (e) {
|
|
976
1074
|
console.warn('[Diagnostics] Failed to diagnose databases:', e);
|
|
977
1075
|
}
|
|
978
|
-
|
|
1076
|
+
|
|
979
1077
|
// Load local tasks
|
|
980
1078
|
const localTasks = await getLocalTasks();
|
|
981
|
-
console.log('[Diagnostics] Local tasks:', localTasks.length, localTasks.slice(0, 2));
|
|
982
1079
|
if (localTaskCountEl) {
|
|
983
1080
|
localTaskCountEl.textContent = localTasks.length;
|
|
984
1081
|
}
|
|
985
|
-
|
|
1082
|
+
|
|
986
1083
|
// Load local boards
|
|
987
1084
|
const localBoards = await getLocalBoards();
|
|
988
|
-
console.log('[Diagnostics] Local boards:', localBoards.length, localBoards.slice(0, 2));
|
|
989
1085
|
if (localBoardCountEl) {
|
|
990
1086
|
localBoardCountEl.textContent = localBoards.length;
|
|
991
1087
|
}
|
|
992
|
-
|
|
1088
|
+
|
|
993
1089
|
// Load local media
|
|
994
1090
|
const localMedia = await listCacheStorageMedia();
|
|
995
|
-
console.log('[Diagnostics] Local media:', localMedia.length);
|
|
996
1091
|
if (localMediaCountEl) {
|
|
997
1092
|
localMediaCountEl.textContent = localMedia.length;
|
|
998
1093
|
}
|
|
999
|
-
|
|
1094
|
+
|
|
1000
1095
|
// Show local-only data in tables
|
|
1001
1096
|
if (localTasks.length > 0) {
|
|
1002
|
-
renderLocalOnlyTable(
|
|
1097
|
+
renderLocalOnlyTable(
|
|
1098
|
+
taskComparisonTableEl,
|
|
1099
|
+
localTasks.slice(0, 20),
|
|
1100
|
+
'task'
|
|
1101
|
+
);
|
|
1003
1102
|
} else {
|
|
1004
1103
|
if (taskComparisonTableEl) {
|
|
1005
|
-
const dbInfo = dbDiagnosis.taskQueue
|
|
1006
|
-
`数据库信息: ${JSON.stringify(
|
|
1007
|
-
|
|
1104
|
+
const dbInfo = dbDiagnosis.taskQueue
|
|
1105
|
+
? `数据库信息: ${JSON.stringify(
|
|
1106
|
+
dbDiagnosis.taskQueue.stores,
|
|
1107
|
+
null,
|
|
1108
|
+
2
|
|
1109
|
+
)}`
|
|
1110
|
+
: '';
|
|
1111
|
+
taskComparisonTableEl.innerHTML = `<div class="empty-state" style="padding: 20px;">本地无任务数据${
|
|
1112
|
+
dbInfo
|
|
1113
|
+
? '<pre style="text-align:left;font-size:10px;margin-top:10px;white-space:pre-wrap;">' +
|
|
1114
|
+
escapeHtml(dbInfo) +
|
|
1115
|
+
'</pre>'
|
|
1116
|
+
: ''
|
|
1117
|
+
}</div>`;
|
|
1008
1118
|
}
|
|
1009
1119
|
}
|
|
1010
|
-
|
|
1120
|
+
|
|
1011
1121
|
if (localBoards.length > 0) {
|
|
1012
|
-
renderLocalOnlyTable(
|
|
1122
|
+
renderLocalOnlyTable(
|
|
1123
|
+
boardComparisonTableEl,
|
|
1124
|
+
localBoards.slice(0, 20),
|
|
1125
|
+
'board'
|
|
1126
|
+
);
|
|
1013
1127
|
} else {
|
|
1014
1128
|
if (boardComparisonTableEl) {
|
|
1015
|
-
const dbInfo = dbDiagnosis.workspace
|
|
1016
|
-
`数据库信息: ${JSON.stringify(
|
|
1017
|
-
|
|
1129
|
+
const dbInfo = dbDiagnosis.workspace
|
|
1130
|
+
? `数据库信息: ${JSON.stringify(
|
|
1131
|
+
dbDiagnosis.workspace.stores,
|
|
1132
|
+
null,
|
|
1133
|
+
2
|
|
1134
|
+
)}`
|
|
1135
|
+
: '';
|
|
1136
|
+
boardComparisonTableEl.innerHTML = `<div class="empty-state" style="padding: 20px;">本地无画板数据${
|
|
1137
|
+
dbInfo
|
|
1138
|
+
? '<pre style="text-align:left;font-size:10px;margin-top:10px;white-space:pre-wrap;">' +
|
|
1139
|
+
escapeHtml(dbInfo) +
|
|
1140
|
+
'</pre>'
|
|
1141
|
+
: ''
|
|
1142
|
+
}</div>`;
|
|
1018
1143
|
}
|
|
1019
1144
|
}
|
|
1020
|
-
|
|
1145
|
+
|
|
1021
1146
|
if (localMedia.length > 0) {
|
|
1022
|
-
renderLocalOnlyMediaTable(
|
|
1147
|
+
renderLocalOnlyMediaTable(
|
|
1148
|
+
mediaComparisonTableEl,
|
|
1149
|
+
localMedia.slice(0, 30)
|
|
1150
|
+
);
|
|
1023
1151
|
} else {
|
|
1024
1152
|
if (mediaComparisonTableEl) {
|
|
1025
|
-
mediaComparisonTableEl.innerHTML =
|
|
1153
|
+
mediaComparisonTableEl.innerHTML =
|
|
1154
|
+
'<div class="empty-state" style="padding: 20px;">本地无缓存媒体</div>';
|
|
1026
1155
|
}
|
|
1027
1156
|
}
|
|
1028
|
-
|
|
1157
|
+
|
|
1029
1158
|
// Update remote counts to show "未加载"
|
|
1030
1159
|
if (remoteTaskCountEl) remoteTaskCountEl.textContent = '未加载';
|
|
1031
1160
|
if (remoteBoardCountEl) remoteBoardCountEl.textContent = '未加载';
|
|
@@ -1037,13 +1166,12 @@ async function loadLocalDiagnostics() {
|
|
|
1037
1166
|
if (remoteOnlyBoardCountEl) remoteOnlyBoardCountEl.textContent = '-';
|
|
1038
1167
|
if (localOnlyMediaCountEl) localOnlyMediaCountEl.textContent = '-';
|
|
1039
1168
|
if (remoteOnlyMediaCountEl) remoteOnlyMediaCountEl.textContent = '-';
|
|
1040
|
-
|
|
1041
|
-
console.log('[Diagnostics] Local diagnostics loaded successfully');
|
|
1042
|
-
|
|
1043
1169
|
} catch (error) {
|
|
1044
1170
|
console.error('[Diagnostics] Failed to load local data:', error);
|
|
1045
1171
|
if (taskComparisonTableEl) {
|
|
1046
|
-
taskComparisonTableEl.innerHTML = `<div class="empty-state" style="padding: 20px; color: var(--error-color);">加载失败: ${escapeHtml(
|
|
1172
|
+
taskComparisonTableEl.innerHTML = `<div class="empty-state" style="padding: 20px; color: var(--error-color);">加载失败: ${escapeHtml(
|
|
1173
|
+
error.message
|
|
1174
|
+
)}</div>`;
|
|
1047
1175
|
}
|
|
1048
1176
|
}
|
|
1049
1177
|
}
|
|
@@ -1053,24 +1181,29 @@ async function loadLocalDiagnostics() {
|
|
|
1053
1181
|
*/
|
|
1054
1182
|
function renderLocalOnlyTable(container, items, type) {
|
|
1055
1183
|
if (!container) return;
|
|
1056
|
-
|
|
1184
|
+
|
|
1057
1185
|
if (items.length === 0) {
|
|
1058
|
-
container.innerHTML =
|
|
1186
|
+
container.innerHTML =
|
|
1187
|
+
'<div class="empty-state" style="padding: 20px;">无数据</div>';
|
|
1059
1188
|
return;
|
|
1060
1189
|
}
|
|
1061
|
-
|
|
1062
|
-
container.innerHTML = items
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1190
|
+
|
|
1191
|
+
container.innerHTML = items
|
|
1192
|
+
.map((item) => {
|
|
1193
|
+
const id = item.id || '';
|
|
1194
|
+
const details = getItemDetails(item, type);
|
|
1195
|
+
return `
|
|
1066
1196
|
<div class="gist-comparison-row">
|
|
1067
|
-
<span class="item-id" title="${escapeHtml(id)}">${escapeHtml(
|
|
1197
|
+
<span class="item-id" title="${escapeHtml(id)}">${escapeHtml(
|
|
1198
|
+
truncateId(id)
|
|
1199
|
+
)}</span>
|
|
1068
1200
|
<span class="item-details">${escapeHtml(details)}</span>
|
|
1069
1201
|
<span class="item-status local-only">本地</span>
|
|
1070
1202
|
</div>
|
|
1071
1203
|
`;
|
|
1072
|
-
|
|
1073
|
-
|
|
1204
|
+
})
|
|
1205
|
+
.join('');
|
|
1206
|
+
|
|
1074
1207
|
if (items.length >= 20) {
|
|
1075
1208
|
container.innerHTML += `
|
|
1076
1209
|
<div class="gist-comparison-row" style="justify-content: center; color: var(--text-muted); font-style: italic;">
|
|
@@ -1085,23 +1218,28 @@ function renderLocalOnlyTable(container, items, type) {
|
|
|
1085
1218
|
*/
|
|
1086
1219
|
function renderLocalOnlyMediaTable(container, items) {
|
|
1087
1220
|
if (!container) return;
|
|
1088
|
-
|
|
1221
|
+
|
|
1089
1222
|
if (items.length === 0) {
|
|
1090
|
-
container.innerHTML =
|
|
1223
|
+
container.innerHTML =
|
|
1224
|
+
'<div class="empty-state" style="padding: 20px;">无缓存媒体</div>';
|
|
1091
1225
|
return;
|
|
1092
1226
|
}
|
|
1093
|
-
|
|
1094
|
-
container.innerHTML = items
|
|
1095
|
-
|
|
1096
|
-
|
|
1227
|
+
|
|
1228
|
+
container.innerHTML = items
|
|
1229
|
+
.map((item) => {
|
|
1230
|
+
const filename = item.filename || extractFilename(item.url) || item.url;
|
|
1231
|
+
return `
|
|
1097
1232
|
<div class="gist-comparison-row">
|
|
1098
|
-
<span class="item-id" title="${escapeHtml(item.url)}">${escapeHtml(
|
|
1233
|
+
<span class="item-id" title="${escapeHtml(item.url)}">${escapeHtml(
|
|
1234
|
+
filename
|
|
1235
|
+
)}</span>
|
|
1099
1236
|
<span class="item-details"></span>
|
|
1100
1237
|
<span class="item-status local-only">本地缓存</span>
|
|
1101
1238
|
</div>
|
|
1102
1239
|
`;
|
|
1103
|
-
|
|
1104
|
-
|
|
1240
|
+
})
|
|
1241
|
+
.join('');
|
|
1242
|
+
|
|
1105
1243
|
if (items.length >= 30) {
|
|
1106
1244
|
container.innerHTML += `
|
|
1107
1245
|
<div class="gist-comparison-row" style="justify-content: center; color: var(--text-muted); font-style: italic;">
|
|
@@ -1129,60 +1267,57 @@ async function compareTasksData(gist) {
|
|
|
1129
1267
|
const remoteOnlyTaskCountEl = document.getElementById('remoteOnlyTaskCount');
|
|
1130
1268
|
const syncedTaskCountEl = document.getElementById('syncedTaskCount');
|
|
1131
1269
|
const taskComparisonTableEl = document.getElementById('taskComparisonTable');
|
|
1132
|
-
|
|
1270
|
+
|
|
1133
1271
|
try {
|
|
1134
1272
|
// Get local tasks
|
|
1135
1273
|
const localTasks = await getLocalTasks();
|
|
1136
|
-
const localTaskMap = new Map(localTasks.map(t => [t.id, t]));
|
|
1137
|
-
console.log('[Diagnostics] compareTasksData - Local tasks:', localTasks.length);
|
|
1138
|
-
|
|
1274
|
+
const localTaskMap = new Map(localTasks.map((t) => [t.id, t]));
|
|
1139
1275
|
// Get remote tasks
|
|
1140
1276
|
let remoteTasks = [];
|
|
1141
1277
|
if (gist.files && gist.files['tasks.json']) {
|
|
1142
|
-
|
|
1143
|
-
const tasksData = await parseGistFile(gist.files['tasks.json']);
|
|
1144
|
-
console.log('[Diagnostics] Parsed tasks.json:', tasksData);
|
|
1145
|
-
// TasksData 结构: { completedTasks: Task[] }
|
|
1278
|
+
const tasksData = await parseGistFile(gist.files['tasks.json']); // TasksData 结构: { completedTasks: Task[] }
|
|
1146
1279
|
if (tasksData && Array.isArray(tasksData.completedTasks)) {
|
|
1147
1280
|
remoteTasks = tasksData.completedTasks;
|
|
1148
|
-
console.log('[Diagnostics] Extracted completedTasks:', remoteTasks.length);
|
|
1149
1281
|
} else if (Array.isArray(tasksData)) {
|
|
1150
1282
|
// 兼容旧格式(直接数组)
|
|
1151
1283
|
remoteTasks = tasksData;
|
|
1152
|
-
console.log('[Diagnostics] Using legacy array format:', remoteTasks.length);
|
|
1153
1284
|
}
|
|
1154
|
-
} else {
|
|
1155
|
-
console.log('[Diagnostics] No tasks.json found in gist files:', Object.keys(gist.files || {}));
|
|
1156
1285
|
}
|
|
1157
|
-
const remoteTaskMap = new Map(remoteTasks.map(t => [t.id, t]));
|
|
1158
|
-
console.log('[Diagnostics] compareTasksData - Remote tasks:', remoteTasks.length);
|
|
1159
|
-
|
|
1286
|
+
const remoteTaskMap = new Map(remoteTasks.map((t) => [t.id, t]));
|
|
1160
1287
|
// Compare
|
|
1161
1288
|
const localIds = new Set(localTaskMap.keys());
|
|
1162
1289
|
const remoteIds = new Set(remoteTaskMap.keys());
|
|
1163
|
-
|
|
1164
|
-
const localOnly = [...localIds].filter(id => !remoteIds.has(id));
|
|
1165
|
-
const remoteOnly = [...remoteIds].filter(id => !localIds.has(id));
|
|
1166
|
-
const synced = [...localIds].filter(id => remoteIds.has(id));
|
|
1290
|
+
|
|
1291
|
+
const localOnly = [...localIds].filter((id) => !remoteIds.has(id));
|
|
1292
|
+
const remoteOnly = [...remoteIds].filter((id) => !localIds.has(id));
|
|
1293
|
+
const synced = [...localIds].filter((id) => remoteIds.has(id));
|
|
1167
1294
|
|
|
1168
1295
|
// Update summary (use direct element references)
|
|
1169
1296
|
if (localTaskCountEl) localTaskCountEl.textContent = localTasks.length;
|
|
1170
1297
|
if (remoteTaskCountEl) remoteTaskCountEl.textContent = remoteTasks.length;
|
|
1171
|
-
if (localOnlyTaskCountEl)
|
|
1172
|
-
|
|
1298
|
+
if (localOnlyTaskCountEl)
|
|
1299
|
+
localOnlyTaskCountEl.textContent = localOnly.length;
|
|
1300
|
+
if (remoteOnlyTaskCountEl)
|
|
1301
|
+
remoteOnlyTaskCountEl.textContent = remoteOnly.length;
|
|
1173
1302
|
if (syncedTaskCountEl) syncedTaskCountEl.textContent = synced.length;
|
|
1174
1303
|
|
|
1175
1304
|
// Render comparison table
|
|
1176
|
-
renderComparisonTable(
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
id
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1305
|
+
renderComparisonTable(
|
|
1306
|
+
taskComparisonTableEl,
|
|
1307
|
+
{
|
|
1308
|
+
localOnly: localOnly.map((id) => ({ id, data: localTaskMap.get(id) })),
|
|
1309
|
+
remoteOnly: remoteOnly.map((id) => ({
|
|
1310
|
+
id,
|
|
1311
|
+
data: remoteTaskMap.get(id),
|
|
1312
|
+
})),
|
|
1313
|
+
synced: synced.map((id) => ({
|
|
1314
|
+
id,
|
|
1315
|
+
local: localTaskMap.get(id),
|
|
1316
|
+
remote: remoteTaskMap.get(id),
|
|
1317
|
+
})),
|
|
1318
|
+
},
|
|
1319
|
+
'task'
|
|
1320
|
+
);
|
|
1186
1321
|
} catch (error) {
|
|
1187
1322
|
console.error('Task comparison failed:', error);
|
|
1188
1323
|
if (taskComparisonTableEl) {
|
|
@@ -1198,22 +1333,22 @@ async function compareBoardsData(gist) {
|
|
|
1198
1333
|
const localBoardCountEl = document.getElementById('localBoardCount');
|
|
1199
1334
|
const remoteBoardCountEl = document.getElementById('remoteBoardCount');
|
|
1200
1335
|
const localOnlyBoardCountEl = document.getElementById('localOnlyBoardCount');
|
|
1201
|
-
const remoteOnlyBoardCountEl = document.getElementById(
|
|
1202
|
-
|
|
1203
|
-
|
|
1336
|
+
const remoteOnlyBoardCountEl = document.getElementById(
|
|
1337
|
+
'remoteOnlyBoardCount'
|
|
1338
|
+
);
|
|
1339
|
+
const boardComparisonTableEl = document.getElementById(
|
|
1340
|
+
'boardComparisonTable'
|
|
1341
|
+
);
|
|
1342
|
+
|
|
1204
1343
|
try {
|
|
1205
1344
|
// Get local boards
|
|
1206
1345
|
const localBoards = await getLocalBoards();
|
|
1207
|
-
const localBoardMap = new Map(localBoards.map(b => [b.id, b]));
|
|
1208
|
-
console.log('[Diagnostics] compareBoardsData - Local boards:', localBoards.length);
|
|
1209
|
-
|
|
1346
|
+
const localBoardMap = new Map(localBoards.map((b) => [b.id, b]));
|
|
1210
1347
|
// Get remote boards from gist files (board_{id}.json format)
|
|
1211
1348
|
const remoteBoards = [];
|
|
1212
|
-
|
|
1349
|
+
|
|
1213
1350
|
if (gist.files) {
|
|
1214
1351
|
const fileNames = Object.keys(gist.files);
|
|
1215
|
-
console.log('[Diagnostics] Gist files:', fileNames);
|
|
1216
|
-
|
|
1217
1352
|
// 查找 board_{id}.json 格式的文件
|
|
1218
1353
|
for (const [fileName, fileData] of Object.entries(gist.files)) {
|
|
1219
1354
|
if (fileName.startsWith('board_') && fileName.endsWith('.json')) {
|
|
@@ -1222,37 +1357,43 @@ async function compareBoardsData(gist) {
|
|
|
1222
1357
|
remoteBoards.push({ id: boardId, fileName, fileData });
|
|
1223
1358
|
}
|
|
1224
1359
|
}
|
|
1225
|
-
console.log('[Diagnostics] Remote boards (board_*.json):', remoteBoards.length, remoteBoards.map(b => b.fileName));
|
|
1226
1360
|
}
|
|
1227
|
-
|
|
1228
|
-
const remoteBoardMap = new Map(remoteBoards.map(b => [b.id, b]));
|
|
1229
|
-
console.log('[Diagnostics] compareBoardsData - Total remote boards:', remoteBoards.length);
|
|
1230
1361
|
|
|
1362
|
+
const remoteBoardMap = new Map(remoteBoards.map((b) => [b.id, b]));
|
|
1231
1363
|
// Compare
|
|
1232
1364
|
const localIds = new Set(localBoardMap.keys());
|
|
1233
1365
|
const remoteIds = new Set(remoteBoardMap.keys());
|
|
1234
|
-
|
|
1235
|
-
const localOnly = [...localIds].filter(id => !remoteIds.has(id));
|
|
1236
|
-
const remoteOnly = [...remoteIds].filter(id => !localIds.has(id));
|
|
1237
|
-
const synced = [...localIds].filter(id => remoteIds.has(id));
|
|
1366
|
+
|
|
1367
|
+
const localOnly = [...localIds].filter((id) => !remoteIds.has(id));
|
|
1368
|
+
const remoteOnly = [...remoteIds].filter((id) => !localIds.has(id));
|
|
1369
|
+
const synced = [...localIds].filter((id) => remoteIds.has(id));
|
|
1238
1370
|
|
|
1239
1371
|
// Update summary (use direct element references)
|
|
1240
1372
|
if (localBoardCountEl) localBoardCountEl.textContent = localBoards.length;
|
|
1241
|
-
if (remoteBoardCountEl)
|
|
1242
|
-
|
|
1243
|
-
if (
|
|
1373
|
+
if (remoteBoardCountEl)
|
|
1374
|
+
remoteBoardCountEl.textContent = remoteBoards.length;
|
|
1375
|
+
if (localOnlyBoardCountEl)
|
|
1376
|
+
localOnlyBoardCountEl.textContent = localOnly.length;
|
|
1377
|
+
if (remoteOnlyBoardCountEl)
|
|
1378
|
+
remoteOnlyBoardCountEl.textContent = remoteOnly.length;
|
|
1244
1379
|
|
|
1245
1380
|
// Render comparison table
|
|
1246
|
-
renderComparisonTable(
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
id
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1381
|
+
renderComparisonTable(
|
|
1382
|
+
boardComparisonTableEl,
|
|
1383
|
+
{
|
|
1384
|
+
localOnly: localOnly.map((id) => ({ id, data: localBoardMap.get(id) })),
|
|
1385
|
+
remoteOnly: remoteOnly.map((id) => ({
|
|
1386
|
+
id,
|
|
1387
|
+
data: remoteBoardMap.get(id),
|
|
1388
|
+
})),
|
|
1389
|
+
synced: synced.map((id) => ({
|
|
1390
|
+
id,
|
|
1391
|
+
local: localBoardMap.get(id),
|
|
1392
|
+
remote: remoteBoardMap.get(id),
|
|
1393
|
+
})),
|
|
1394
|
+
},
|
|
1395
|
+
'board'
|
|
1396
|
+
);
|
|
1256
1397
|
} catch (error) {
|
|
1257
1398
|
console.error('Board comparison failed:', error);
|
|
1258
1399
|
if (boardComparisonTableEl) {
|
|
@@ -1268,31 +1409,35 @@ async function compareMediaData() {
|
|
|
1268
1409
|
const localMediaCountEl = document.getElementById('localMediaCount');
|
|
1269
1410
|
const remoteMediaCountEl = document.getElementById('remoteMediaCount');
|
|
1270
1411
|
const localOnlyMediaCountEl = document.getElementById('localOnlyMediaCount');
|
|
1271
|
-
const remoteOnlyMediaCountEl = document.getElementById(
|
|
1272
|
-
|
|
1273
|
-
|
|
1412
|
+
const remoteOnlyMediaCountEl = document.getElementById(
|
|
1413
|
+
'remoteOnlyMediaCount'
|
|
1414
|
+
);
|
|
1415
|
+
const mediaComparisonTableEl = document.getElementById(
|
|
1416
|
+
'mediaComparisonTable'
|
|
1417
|
+
);
|
|
1418
|
+
|
|
1274
1419
|
try {
|
|
1275
1420
|
// Get local cache media
|
|
1276
1421
|
const localMedia = await listCacheStorageMedia();
|
|
1277
|
-
const localUrls = new Set(localMedia.map(m => m.url));
|
|
1278
|
-
console.log('[Diagnostics] compareMediaData - Local media:', localMedia.length);
|
|
1279
|
-
|
|
1422
|
+
const localUrls = new Set(localMedia.map((m) => m.url));
|
|
1280
1423
|
// Get remote media from master index
|
|
1281
1424
|
const masterIndex = remoteMasterIndex || localMasterIndex;
|
|
1282
|
-
const remoteMedia = masterIndex?.fileIndex
|
|
1425
|
+
const remoteMedia = masterIndex?.fileIndex
|
|
1426
|
+
? Object.keys(masterIndex.fileIndex)
|
|
1427
|
+
: [];
|
|
1283
1428
|
const remoteUrls = new Set(remoteMedia);
|
|
1284
|
-
console.log('[Diagnostics] compareMediaData - Remote media (from master index):', remoteMedia.length);
|
|
1285
|
-
|
|
1286
1429
|
// Compare
|
|
1287
|
-
const localOnly = [...localUrls].filter(url => !remoteUrls.has(url));
|
|
1288
|
-
const remoteOnly = [...remoteUrls].filter(url => !localUrls.has(url));
|
|
1289
|
-
const synced = [...localUrls].filter(url => remoteUrls.has(url));
|
|
1430
|
+
const localOnly = [...localUrls].filter((url) => !remoteUrls.has(url));
|
|
1431
|
+
const remoteOnly = [...remoteUrls].filter((url) => !localUrls.has(url));
|
|
1432
|
+
const synced = [...localUrls].filter((url) => remoteUrls.has(url));
|
|
1290
1433
|
|
|
1291
1434
|
// Update summary (use direct element references)
|
|
1292
1435
|
if (localMediaCountEl) localMediaCountEl.textContent = localMedia.length;
|
|
1293
1436
|
if (remoteMediaCountEl) remoteMediaCountEl.textContent = remoteMedia.length;
|
|
1294
|
-
if (localOnlyMediaCountEl)
|
|
1295
|
-
|
|
1437
|
+
if (localOnlyMediaCountEl)
|
|
1438
|
+
localOnlyMediaCountEl.textContent = localOnly.length;
|
|
1439
|
+
if (remoteOnlyMediaCountEl)
|
|
1440
|
+
remoteOnlyMediaCountEl.textContent = remoteOnly.length;
|
|
1296
1441
|
|
|
1297
1442
|
// Render comparison table
|
|
1298
1443
|
renderMediaComparisonTable(mediaComparisonTableEl, {
|
|
@@ -1301,7 +1446,6 @@ async function compareMediaData() {
|
|
|
1301
1446
|
synced,
|
|
1302
1447
|
masterIndex,
|
|
1303
1448
|
});
|
|
1304
|
-
|
|
1305
1449
|
} catch (error) {
|
|
1306
1450
|
console.error('Media comparison failed:', error);
|
|
1307
1451
|
if (mediaComparisonTableEl) {
|
|
@@ -1320,7 +1464,7 @@ function renderComparisonTable(container, data, type) {
|
|
|
1320
1464
|
const rows = [];
|
|
1321
1465
|
|
|
1322
1466
|
// Local only items
|
|
1323
|
-
data.localOnly.forEach(item => {
|
|
1467
|
+
data.localOnly.forEach((item) => {
|
|
1324
1468
|
rows.push({
|
|
1325
1469
|
id: item.id,
|
|
1326
1470
|
status: 'local-only',
|
|
@@ -1330,7 +1474,7 @@ function renderComparisonTable(container, data, type) {
|
|
|
1330
1474
|
});
|
|
1331
1475
|
|
|
1332
1476
|
// Remote only items
|
|
1333
|
-
data.remoteOnly.forEach(item => {
|
|
1477
|
+
data.remoteOnly.forEach((item) => {
|
|
1334
1478
|
rows.push({
|
|
1335
1479
|
id: item.id,
|
|
1336
1480
|
status: 'remote-only',
|
|
@@ -1340,7 +1484,7 @@ function renderComparisonTable(container, data, type) {
|
|
|
1340
1484
|
});
|
|
1341
1485
|
|
|
1342
1486
|
// Synced items (show first 10 to avoid overwhelming)
|
|
1343
|
-
data.synced.slice(0, 10).forEach(item => {
|
|
1487
|
+
data.synced.slice(0, 10).forEach((item) => {
|
|
1344
1488
|
const hasConflict = checkConflict(item.local, item.remote);
|
|
1345
1489
|
rows.push({
|
|
1346
1490
|
id: item.id,
|
|
@@ -1351,21 +1495,33 @@ function renderComparisonTable(container, data, type) {
|
|
|
1351
1495
|
});
|
|
1352
1496
|
|
|
1353
1497
|
if (rows.length === 0) {
|
|
1354
|
-
container.innerHTML =
|
|
1498
|
+
container.innerHTML =
|
|
1499
|
+
'<div class="empty-state" style="padding: 20px;">无数据</div>';
|
|
1355
1500
|
return;
|
|
1356
1501
|
}
|
|
1357
1502
|
|
|
1358
1503
|
// Sort: local-only first, then remote-only, then conflicts, then synced
|
|
1359
|
-
const statusOrder = {
|
|
1504
|
+
const statusOrder = {
|
|
1505
|
+
'local-only': 0,
|
|
1506
|
+
'remote-only': 1,
|
|
1507
|
+
conflict: 2,
|
|
1508
|
+
synced: 3,
|
|
1509
|
+
};
|
|
1360
1510
|
rows.sort((a, b) => statusOrder[a.status] - statusOrder[b.status]);
|
|
1361
1511
|
|
|
1362
|
-
container.innerHTML = rows
|
|
1512
|
+
container.innerHTML = rows
|
|
1513
|
+
.map(
|
|
1514
|
+
(row) => `
|
|
1363
1515
|
<div class="gist-comparison-row">
|
|
1364
|
-
<span class="item-id" title="${escapeHtml(row.id)}">${escapeHtml(
|
|
1516
|
+
<span class="item-id" title="${escapeHtml(row.id)}">${escapeHtml(
|
|
1517
|
+
truncateId(row.id)
|
|
1518
|
+
)}</span>
|
|
1365
1519
|
<span class="item-details">${escapeHtml(row.details)}</span>
|
|
1366
1520
|
<span class="item-status ${row.status}">${row.statusText}</span>
|
|
1367
1521
|
</div>
|
|
1368
|
-
`
|
|
1522
|
+
`
|
|
1523
|
+
)
|
|
1524
|
+
.join('');
|
|
1369
1525
|
|
|
1370
1526
|
// Add note if there are more synced items
|
|
1371
1527
|
if (data.synced.length > 10) {
|
|
@@ -1383,7 +1539,7 @@ function renderMediaComparisonTable(container, data) {
|
|
|
1383
1539
|
const rows = [];
|
|
1384
1540
|
|
|
1385
1541
|
// Synced (show first 20, priority display)
|
|
1386
|
-
data.synced.slice(0, 20).forEach(url => {
|
|
1542
|
+
data.synced.slice(0, 20).forEach((url) => {
|
|
1387
1543
|
rows.push({
|
|
1388
1544
|
url,
|
|
1389
1545
|
status: 'synced',
|
|
@@ -1392,7 +1548,7 @@ function renderMediaComparisonTable(container, data) {
|
|
|
1392
1548
|
});
|
|
1393
1549
|
|
|
1394
1550
|
// Local only
|
|
1395
|
-
data.localOnly.slice(0, 20).forEach(url => {
|
|
1551
|
+
data.localOnly.slice(0, 20).forEach((url) => {
|
|
1396
1552
|
rows.push({
|
|
1397
1553
|
url,
|
|
1398
1554
|
status: 'local-only',
|
|
@@ -1401,7 +1557,7 @@ function renderMediaComparisonTable(container, data) {
|
|
|
1401
1557
|
});
|
|
1402
1558
|
|
|
1403
1559
|
// Remote only
|
|
1404
|
-
data.remoteOnly.slice(0, 20).forEach(url => {
|
|
1560
|
+
data.remoteOnly.slice(0, 20).forEach((url) => {
|
|
1405
1561
|
const fileInfo = data.masterIndex?.fileIndex?.[url];
|
|
1406
1562
|
rows.push({
|
|
1407
1563
|
url,
|
|
@@ -1412,39 +1568,46 @@ function renderMediaComparisonTable(container, data) {
|
|
|
1412
1568
|
});
|
|
1413
1569
|
|
|
1414
1570
|
if (rows.length === 0) {
|
|
1415
|
-
container.innerHTML =
|
|
1571
|
+
container.innerHTML =
|
|
1572
|
+
'<div class="empty-state" style="padding: 20px;">无媒体数据</div>';
|
|
1416
1573
|
return;
|
|
1417
1574
|
}
|
|
1418
1575
|
|
|
1419
1576
|
// Sort: synced first, then local-only, then remote-only
|
|
1420
|
-
const statusOrder = {
|
|
1577
|
+
const statusOrder = { synced: 0, 'local-only': 1, 'remote-only': 2 };
|
|
1421
1578
|
rows.sort((a, b) => statusOrder[a.status] - statusOrder[b.status]);
|
|
1422
1579
|
|
|
1423
|
-
container.innerHTML = rows
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1580
|
+
container.innerHTML = rows
|
|
1581
|
+
.map((row) => {
|
|
1582
|
+
const isVideo = isVideoUrl(row.url);
|
|
1583
|
+
const previewHtml = getMediaPreviewHtml(row.url, isVideo);
|
|
1584
|
+
const filename = extractFilename(row.url);
|
|
1585
|
+
|
|
1586
|
+
return `
|
|
1429
1587
|
<div class="gist-comparison-row media-row">
|
|
1430
1588
|
<div class="media-preview-container">
|
|
1431
1589
|
${previewHtml}
|
|
1432
1590
|
</div>
|
|
1433
1591
|
<div class="media-info">
|
|
1434
|
-
<span class="item-id" title="${escapeHtml(row.url)}">${escapeHtml(
|
|
1435
|
-
|
|
1592
|
+
<span class="item-id" title="${escapeHtml(row.url)}">${escapeHtml(
|
|
1593
|
+
filename
|
|
1594
|
+
)}</span>
|
|
1595
|
+
<span class="item-details">${
|
|
1596
|
+
row.shard ? `分片: ${row.shard}` : ''
|
|
1597
|
+
}</span>
|
|
1436
1598
|
</div>
|
|
1437
1599
|
<span class="item-status ${row.status}">${row.statusText}</span>
|
|
1438
1600
|
</div>
|
|
1439
1601
|
`;
|
|
1440
|
-
|
|
1602
|
+
})
|
|
1603
|
+
.join('');
|
|
1441
1604
|
|
|
1442
1605
|
// Add notes for truncated lists
|
|
1443
|
-
const totalHidden =
|
|
1444
|
-
Math.max(0, data.localOnly.length - 20) +
|
|
1445
|
-
Math.max(0, data.remoteOnly.length - 20) +
|
|
1606
|
+
const totalHidden =
|
|
1607
|
+
Math.max(0, data.localOnly.length - 20) +
|
|
1608
|
+
Math.max(0, data.remoteOnly.length - 20) +
|
|
1446
1609
|
Math.max(0, data.synced.length - 10);
|
|
1447
|
-
|
|
1610
|
+
|
|
1448
1611
|
if (totalHidden > 0) {
|
|
1449
1612
|
container.innerHTML += `
|
|
1450
1613
|
<div class="gist-comparison-row" style="justify-content: center; color: var(--text-muted); font-style: italic;">
|
|
@@ -1460,18 +1623,18 @@ function renderMediaComparisonTable(container, data) {
|
|
|
1460
1623
|
|
|
1461
1624
|
function getItemDetails(item, type) {
|
|
1462
1625
|
if (!item) return '';
|
|
1463
|
-
|
|
1626
|
+
|
|
1464
1627
|
if (type === 'task') {
|
|
1465
1628
|
const status = item.status || 'unknown';
|
|
1466
1629
|
const taskType = item.type || '';
|
|
1467
1630
|
return `${taskType} - ${status}`;
|
|
1468
1631
|
}
|
|
1469
|
-
|
|
1632
|
+
|
|
1470
1633
|
if (type === 'board') {
|
|
1471
1634
|
const name = item.name || item.title || '';
|
|
1472
1635
|
return name;
|
|
1473
1636
|
}
|
|
1474
|
-
|
|
1637
|
+
|
|
1475
1638
|
return '';
|
|
1476
1639
|
}
|
|
1477
1640
|
|
|
@@ -1506,11 +1669,13 @@ function extractFilename(url) {
|
|
|
1506
1669
|
function isVideoUrl(url) {
|
|
1507
1670
|
if (!url) return false;
|
|
1508
1671
|
const lowerUrl = url.toLowerCase();
|
|
1509
|
-
return
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1672
|
+
return (
|
|
1673
|
+
lowerUrl.includes('video') ||
|
|
1674
|
+
lowerUrl.endsWith('.mp4') ||
|
|
1675
|
+
lowerUrl.endsWith('.webm') ||
|
|
1676
|
+
lowerUrl.endsWith('.mov') ||
|
|
1677
|
+
lowerUrl.includes('/v/')
|
|
1678
|
+
);
|
|
1514
1679
|
}
|
|
1515
1680
|
|
|
1516
1681
|
/**
|
|
@@ -1518,14 +1683,14 @@ function isVideoUrl(url) {
|
|
|
1518
1683
|
*/
|
|
1519
1684
|
function getMediaPreviewHtml(url, isVideo) {
|
|
1520
1685
|
if (!url) return '<div class="media-preview-placeholder">?</div>';
|
|
1521
|
-
|
|
1686
|
+
|
|
1522
1687
|
// Handle virtual cache URLs
|
|
1523
1688
|
let previewUrl = url;
|
|
1524
1689
|
if (url.startsWith('/__aitu_cache__/')) {
|
|
1525
1690
|
// Virtual URL, use as-is (SW will intercept)
|
|
1526
1691
|
previewUrl = url;
|
|
1527
1692
|
}
|
|
1528
|
-
|
|
1693
|
+
|
|
1529
1694
|
if (isVideo) {
|
|
1530
1695
|
return `
|
|
1531
1696
|
<video
|
|
@@ -1555,11 +1720,11 @@ function formatTime(timestamp) {
|
|
|
1555
1720
|
const date = new Date(timestamp);
|
|
1556
1721
|
const now = new Date();
|
|
1557
1722
|
const diff = now - date;
|
|
1558
|
-
|
|
1723
|
+
|
|
1559
1724
|
if (diff < 60000) return '刚刚';
|
|
1560
1725
|
if (diff < 3600000) return `${Math.floor(diff / 60000)} 分钟前`;
|
|
1561
1726
|
if (diff < 86400000) return `${Math.floor(diff / 3600000)} 小时前`;
|
|
1562
|
-
|
|
1727
|
+
|
|
1563
1728
|
return date.toLocaleDateString('zh-CN', {
|
|
1564
1729
|
month: 'short',
|
|
1565
1730
|
day: 'numeric',
|
|
@@ -1592,29 +1757,53 @@ function escapeHtml(str) {
|
|
|
1592
1757
|
|
|
1593
1758
|
function initDebugOperations() {
|
|
1594
1759
|
// Connection tests
|
|
1595
|
-
document
|
|
1596
|
-
|
|
1597
|
-
|
|
1760
|
+
document
|
|
1761
|
+
.getElementById('debugTestConnection')
|
|
1762
|
+
?.addEventListener('click', debugTestConnection);
|
|
1763
|
+
document
|
|
1764
|
+
.getElementById('debugListGists')
|
|
1765
|
+
?.addEventListener('click', debugListGists);
|
|
1766
|
+
|
|
1598
1767
|
// Task operations
|
|
1599
|
-
document
|
|
1600
|
-
|
|
1601
|
-
|
|
1768
|
+
document
|
|
1769
|
+
.getElementById('debugPreviewTaskMerge')
|
|
1770
|
+
?.addEventListener('click', debugPreviewTaskMerge);
|
|
1771
|
+
document
|
|
1772
|
+
.getElementById('debugImportRemoteTasks')
|
|
1773
|
+
?.addEventListener('click', debugImportRemoteTasks);
|
|
1774
|
+
|
|
1602
1775
|
// Board operations
|
|
1603
|
-
document
|
|
1604
|
-
|
|
1605
|
-
|
|
1776
|
+
document
|
|
1777
|
+
.getElementById('debugPreviewBoardMerge')
|
|
1778
|
+
?.addEventListener('click', debugPreviewBoardMerge);
|
|
1779
|
+
document
|
|
1780
|
+
.getElementById('debugDownloadBoard')
|
|
1781
|
+
?.addEventListener('click', debugDownloadBoard);
|
|
1782
|
+
|
|
1606
1783
|
// Media operations
|
|
1607
|
-
document
|
|
1608
|
-
|
|
1609
|
-
|
|
1784
|
+
document
|
|
1785
|
+
.getElementById('debugListShardFiles')
|
|
1786
|
+
?.addEventListener('click', debugListShardFiles);
|
|
1787
|
+
document
|
|
1788
|
+
.getElementById('debugTestMediaDownload')
|
|
1789
|
+
?.addEventListener('click', debugTestMediaDownload);
|
|
1790
|
+
|
|
1610
1791
|
// Log diagnostics
|
|
1611
|
-
document
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1792
|
+
document
|
|
1793
|
+
.getElementById('debugCheckSyncLogDb')
|
|
1794
|
+
?.addEventListener('click', debugCheckSyncLogDb);
|
|
1795
|
+
document
|
|
1796
|
+
.getElementById('debugWriteTestLog')
|
|
1797
|
+
?.addEventListener('click', debugWriteTestLog);
|
|
1798
|
+
document
|
|
1799
|
+
.getElementById('debugPerformanceTest')
|
|
1800
|
+
?.addEventListener('click', debugPerformanceTest);
|
|
1801
|
+
|
|
1615
1802
|
// Database diagnostics
|
|
1616
|
-
document
|
|
1617
|
-
|
|
1803
|
+
document
|
|
1804
|
+
.getElementById('debugDiagnoseDb')
|
|
1805
|
+
?.addEventListener('click', debugDiagnoseAllDatabases);
|
|
1806
|
+
|
|
1618
1807
|
// Initialize Sync Log Viewer
|
|
1619
1808
|
initSyncLogViewer();
|
|
1620
1809
|
}
|
|
@@ -1625,13 +1814,13 @@ function initDebugOperations() {
|
|
|
1625
1814
|
async function debugLog(level, message, details = null) {
|
|
1626
1815
|
// Map level to sync log level
|
|
1627
1816
|
const levelMap = {
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1817
|
+
info: 'info',
|
|
1818
|
+
success: 'success',
|
|
1819
|
+
warning: 'warning',
|
|
1820
|
+
error: 'error',
|
|
1632
1821
|
};
|
|
1633
1822
|
const syncLevel = levelMap[level] || 'debug';
|
|
1634
|
-
|
|
1823
|
+
|
|
1635
1824
|
// Write to IndexedDB sync logs
|
|
1636
1825
|
const logEntry = {
|
|
1637
1826
|
id: `debug-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
|
|
@@ -1642,7 +1831,7 @@ async function debugLog(level, message, details = null) {
|
|
|
1642
1831
|
message: `[调试] ${message}`,
|
|
1643
1832
|
data: details,
|
|
1644
1833
|
};
|
|
1645
|
-
|
|
1834
|
+
|
|
1646
1835
|
try {
|
|
1647
1836
|
const db = await openSyncLogDb();
|
|
1648
1837
|
const tx = db.transaction('logs', 'readwrite');
|
|
@@ -1652,7 +1841,7 @@ async function debugLog(level, message, details = null) {
|
|
|
1652
1841
|
req.onsuccess = resolve;
|
|
1653
1842
|
req.onerror = () => reject(req.error);
|
|
1654
1843
|
});
|
|
1655
|
-
|
|
1844
|
+
|
|
1656
1845
|
// Trigger a silent refresh of sync logs if visible
|
|
1657
1846
|
if (syncLogAutoRefreshEnabled) {
|
|
1658
1847
|
silentRefreshSyncLogs();
|
|
@@ -1686,7 +1875,7 @@ function openSyncLogDb() {
|
|
|
1686
1875
|
|
|
1687
1876
|
async function debugTestConnection() {
|
|
1688
1877
|
debugLog('info', '开始测试 GitHub API 连接...');
|
|
1689
|
-
|
|
1878
|
+
|
|
1690
1879
|
try {
|
|
1691
1880
|
// Check token
|
|
1692
1881
|
const encryptedToken = localStorage.getItem('github_sync_token');
|
|
@@ -1694,23 +1883,27 @@ async function debugTestConnection() {
|
|
|
1694
1883
|
debugLog('error', 'Token 未配置', '请先在应用中配置 GitHub Token');
|
|
1695
1884
|
return;
|
|
1696
1885
|
}
|
|
1697
|
-
|
|
1886
|
+
|
|
1698
1887
|
const token = await decryptToken(encryptedToken);
|
|
1699
1888
|
debugLog('success', 'Token 解密成功');
|
|
1700
|
-
|
|
1889
|
+
|
|
1701
1890
|
// Test API
|
|
1702
1891
|
const response = await fetch('https://api.github.com/user', {
|
|
1703
1892
|
headers: {
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
}
|
|
1893
|
+
Authorization: `token ${token}`,
|
|
1894
|
+
Accept: 'application/vnd.github.v3+json',
|
|
1895
|
+
},
|
|
1707
1896
|
});
|
|
1708
|
-
|
|
1897
|
+
|
|
1709
1898
|
if (!response.ok) {
|
|
1710
|
-
debugLog(
|
|
1899
|
+
debugLog(
|
|
1900
|
+
'error',
|
|
1901
|
+
`API 请求失败: ${response.status}`,
|
|
1902
|
+
await response.text()
|
|
1903
|
+
);
|
|
1711
1904
|
return;
|
|
1712
1905
|
}
|
|
1713
|
-
|
|
1906
|
+
|
|
1714
1907
|
const user = await response.json();
|
|
1715
1908
|
debugLog('success', `连接成功! 用户: ${user.login}`, {
|
|
1716
1909
|
id: user.id,
|
|
@@ -1719,15 +1912,16 @@ async function debugTestConnection() {
|
|
|
1719
1912
|
email: user.email,
|
|
1720
1913
|
plan: user.plan?.name,
|
|
1721
1914
|
});
|
|
1722
|
-
|
|
1915
|
+
|
|
1723
1916
|
// Check rate limit
|
|
1724
1917
|
const rateLimit = {
|
|
1725
1918
|
limit: response.headers.get('x-ratelimit-limit'),
|
|
1726
1919
|
remaining: response.headers.get('x-ratelimit-remaining'),
|
|
1727
|
-
reset: new Date(
|
|
1920
|
+
reset: new Date(
|
|
1921
|
+
parseInt(response.headers.get('x-ratelimit-reset')) * 1000
|
|
1922
|
+
).toLocaleTimeString(),
|
|
1728
1923
|
};
|
|
1729
1924
|
debugLog('info', 'API 速率限制', rateLimit);
|
|
1730
|
-
|
|
1731
1925
|
} catch (error) {
|
|
1732
1926
|
debugLog('error', `连接测试失败: ${error.message}`);
|
|
1733
1927
|
}
|
|
@@ -1735,37 +1929,41 @@ async function debugTestConnection() {
|
|
|
1735
1929
|
|
|
1736
1930
|
async function debugListGists() {
|
|
1737
1931
|
debugLog('info', '获取用户的所有 Gists...');
|
|
1738
|
-
|
|
1932
|
+
|
|
1739
1933
|
try {
|
|
1740
1934
|
await ensureToken();
|
|
1741
|
-
|
|
1935
|
+
|
|
1742
1936
|
const response = await fetch('https://api.github.com/gists?per_page=100', {
|
|
1743
1937
|
headers: {
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
}
|
|
1938
|
+
Authorization: `token ${currentToken}`,
|
|
1939
|
+
Accept: 'application/vnd.github.v3+json',
|
|
1940
|
+
},
|
|
1747
1941
|
});
|
|
1748
|
-
|
|
1942
|
+
|
|
1749
1943
|
if (!response.ok) {
|
|
1750
1944
|
debugLog('error', `获取 Gists 失败: ${response.status}`);
|
|
1751
1945
|
return;
|
|
1752
1946
|
}
|
|
1753
|
-
|
|
1947
|
+
|
|
1754
1948
|
const gists = await response.json();
|
|
1755
|
-
|
|
1949
|
+
|
|
1756
1950
|
// Filter for sync gists
|
|
1757
|
-
const syncGists = gists.filter(
|
|
1758
|
-
g
|
|
1759
|
-
|
|
1760
|
-
(
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1951
|
+
const syncGists = gists.filter(
|
|
1952
|
+
(g) =>
|
|
1953
|
+
g.description?.includes('Opentu') ||
|
|
1954
|
+
Object.keys(g.files).some(
|
|
1955
|
+
(f) =>
|
|
1956
|
+
(f.startsWith('board_') && f.endsWith('.json')) ||
|
|
1957
|
+
f.endsWith('.drawnix') ||
|
|
1958
|
+
f === 'tasks.json' ||
|
|
1959
|
+
f === 'master-index.json'
|
|
1960
|
+
)
|
|
1765
1961
|
);
|
|
1766
|
-
|
|
1767
|
-
debugLog(
|
|
1768
|
-
|
|
1962
|
+
|
|
1963
|
+
debugLog(
|
|
1964
|
+
'success',
|
|
1965
|
+
`找到 ${syncGists.length} 个同步相关的 Gists`,
|
|
1966
|
+
syncGists.map((g) => ({
|
|
1769
1967
|
id: g.id,
|
|
1770
1968
|
description: g.description?.substring(0, 50),
|
|
1771
1969
|
files: Object.keys(g.files).length,
|
|
@@ -1773,7 +1971,6 @@ async function debugListGists() {
|
|
|
1773
1971
|
updated: new Date(g.updated_at).toLocaleDateString(),
|
|
1774
1972
|
}))
|
|
1775
1973
|
);
|
|
1776
|
-
|
|
1777
1974
|
} catch (error) {
|
|
1778
1975
|
debugLog('error', `获取 Gists 失败: ${error.message}`);
|
|
1779
1976
|
}
|
|
@@ -1783,19 +1980,21 @@ async function debugListGists() {
|
|
|
1783
1980
|
|
|
1784
1981
|
async function debugPreviewTaskMerge() {
|
|
1785
1982
|
debugLog('info', '预览任务合并...');
|
|
1786
|
-
|
|
1983
|
+
|
|
1787
1984
|
try {
|
|
1788
1985
|
await ensureToken();
|
|
1789
1986
|
await ensureGistData();
|
|
1790
|
-
|
|
1987
|
+
|
|
1791
1988
|
// Get local tasks
|
|
1792
1989
|
const localTasks = await getLocalTasks();
|
|
1793
1990
|
debugLog('info', `本地任务: ${localTasks.length} 个`);
|
|
1794
|
-
|
|
1991
|
+
|
|
1795
1992
|
// Get remote tasks (TasksData 结构: { completedTasks: Task[] })
|
|
1796
1993
|
let remoteTasks = [];
|
|
1797
1994
|
if (currentGistData?.files?.['tasks.json']) {
|
|
1798
|
-
const tasksData = await parseGistFile(
|
|
1995
|
+
const tasksData = await parseGistFile(
|
|
1996
|
+
currentGistData.files['tasks.json']
|
|
1997
|
+
);
|
|
1799
1998
|
if (tasksData && Array.isArray(tasksData.completedTasks)) {
|
|
1800
1999
|
remoteTasks = tasksData.completedTasks;
|
|
1801
2000
|
} else if (Array.isArray(tasksData)) {
|
|
@@ -1804,11 +2003,11 @@ async function debugPreviewTaskMerge() {
|
|
|
1804
2003
|
}
|
|
1805
2004
|
}
|
|
1806
2005
|
debugLog('info', `远程任务: ${remoteTasks.length} 个`);
|
|
1807
|
-
|
|
2006
|
+
|
|
1808
2007
|
// Build maps
|
|
1809
|
-
const localMap = new Map(localTasks.map(t => [t.id, t]));
|
|
1810
|
-
const remoteMap = new Map(remoteTasks.map(t => [t.id, t]));
|
|
1811
|
-
|
|
2008
|
+
const localMap = new Map(localTasks.map((t) => [t.id, t]));
|
|
2009
|
+
const remoteMap = new Map(remoteTasks.map((t) => [t.id, t]));
|
|
2010
|
+
|
|
1812
2011
|
// Analyze merge
|
|
1813
2012
|
const mergeResult = {
|
|
1814
2013
|
localOnly: [],
|
|
@@ -1816,17 +2015,21 @@ async function debugPreviewTaskMerge() {
|
|
|
1816
2015
|
conflicts: [],
|
|
1817
2016
|
identical: [],
|
|
1818
2017
|
};
|
|
1819
|
-
|
|
2018
|
+
|
|
1820
2019
|
// Check local tasks
|
|
1821
2020
|
for (const [id, local] of localMap) {
|
|
1822
2021
|
const remote = remoteMap.get(id);
|
|
1823
2022
|
if (!remote) {
|
|
1824
|
-
mergeResult.localOnly.push({
|
|
2023
|
+
mergeResult.localOnly.push({
|
|
2024
|
+
id,
|
|
2025
|
+
type: local.type,
|
|
2026
|
+
status: local.status,
|
|
2027
|
+
});
|
|
1825
2028
|
} else {
|
|
1826
2029
|
// Compare
|
|
1827
2030
|
const localUpdated = local.updatedAt || local.createdAt || 0;
|
|
1828
2031
|
const remoteUpdated = remote.updatedAt || remote.createdAt || 0;
|
|
1829
|
-
|
|
2032
|
+
|
|
1830
2033
|
if (localUpdated === remoteUpdated && local.status === remote.status) {
|
|
1831
2034
|
mergeResult.identical.push({ id });
|
|
1832
2035
|
} else {
|
|
@@ -1839,20 +2042,26 @@ async function debugPreviewTaskMerge() {
|
|
|
1839
2042
|
}
|
|
1840
2043
|
}
|
|
1841
2044
|
}
|
|
1842
|
-
|
|
2045
|
+
|
|
1843
2046
|
// Check remote only
|
|
1844
2047
|
for (const [id, remote] of remoteMap) {
|
|
1845
2048
|
if (!localMap.has(id)) {
|
|
1846
|
-
mergeResult.remoteOnly.push({
|
|
2049
|
+
mergeResult.remoteOnly.push({
|
|
2050
|
+
id,
|
|
2051
|
+
type: remote.type,
|
|
2052
|
+
status: remote.status,
|
|
2053
|
+
});
|
|
1847
2054
|
}
|
|
1848
2055
|
}
|
|
1849
|
-
|
|
2056
|
+
|
|
1850
2057
|
debugLog('success', '任务合并预览完成', mergeResult);
|
|
1851
|
-
|
|
2058
|
+
|
|
1852
2059
|
if (mergeResult.remoteOnly.length > 0) {
|
|
1853
|
-
debugLog(
|
|
2060
|
+
debugLog(
|
|
2061
|
+
'warning',
|
|
2062
|
+
`有 ${mergeResult.remoteOnly.length} 个远程任务在本地不存在,可能需要导入`
|
|
2063
|
+
);
|
|
1854
2064
|
}
|
|
1855
|
-
|
|
1856
2065
|
} catch (error) {
|
|
1857
2066
|
debugLog('error', `预览任务合并失败: ${error.message}`);
|
|
1858
2067
|
}
|
|
@@ -1860,18 +2069,20 @@ async function debugPreviewTaskMerge() {
|
|
|
1860
2069
|
|
|
1861
2070
|
async function debugImportRemoteTasks() {
|
|
1862
2071
|
debugLog('info', '开始导入远程任务...');
|
|
1863
|
-
|
|
2072
|
+
|
|
1864
2073
|
try {
|
|
1865
2074
|
await ensureToken();
|
|
1866
2075
|
await ensureGistData();
|
|
1867
|
-
|
|
2076
|
+
|
|
1868
2077
|
// Get local and remote tasks
|
|
1869
2078
|
const localTasks = await getLocalTasks();
|
|
1870
|
-
const localIds = new Set(localTasks.map(t => t.id));
|
|
1871
|
-
|
|
2079
|
+
const localIds = new Set(localTasks.map((t) => t.id));
|
|
2080
|
+
|
|
1872
2081
|
let remoteTasks = [];
|
|
1873
2082
|
if (currentGistData?.files?.['tasks.json']) {
|
|
1874
|
-
const tasksData = await parseGistFile(
|
|
2083
|
+
const tasksData = await parseGistFile(
|
|
2084
|
+
currentGistData.files['tasks.json']
|
|
2085
|
+
);
|
|
1875
2086
|
// TasksData 结构: { completedTasks: Task[] }
|
|
1876
2087
|
if (tasksData && Array.isArray(tasksData.completedTasks)) {
|
|
1877
2088
|
remoteTasks = tasksData.completedTasks;
|
|
@@ -1880,42 +2091,43 @@ async function debugImportRemoteTasks() {
|
|
|
1880
2091
|
remoteTasks = tasksData;
|
|
1881
2092
|
}
|
|
1882
2093
|
}
|
|
1883
|
-
|
|
2094
|
+
|
|
1884
2095
|
// Find tasks to import
|
|
1885
|
-
const tasksToImport = remoteTasks.filter(t => !localIds.has(t.id));
|
|
1886
|
-
|
|
2096
|
+
const tasksToImport = remoteTasks.filter((t) => !localIds.has(t.id));
|
|
2097
|
+
|
|
1887
2098
|
if (tasksToImport.length === 0) {
|
|
1888
2099
|
debugLog('info', '没有需要导入的任务');
|
|
1889
2100
|
return;
|
|
1890
2101
|
}
|
|
1891
|
-
|
|
2102
|
+
|
|
1892
2103
|
debugLog('info', `将导入 ${tasksToImport.length} 个任务`);
|
|
1893
|
-
|
|
2104
|
+
|
|
1894
2105
|
// Import to SW task queue via IndexedDB
|
|
1895
2106
|
const db = await openDatabase('sw-task-queue', 2, (db) => {
|
|
1896
2107
|
if (!db.objectStoreNames.contains('tasks')) {
|
|
1897
2108
|
db.createObjectStore('tasks', { keyPath: 'id' });
|
|
1898
2109
|
}
|
|
1899
2110
|
});
|
|
1900
|
-
|
|
2111
|
+
|
|
1901
2112
|
const tx = db.transaction('tasks', 'readwrite');
|
|
1902
2113
|
const store = tx.objectStore('tasks');
|
|
1903
|
-
|
|
2114
|
+
|
|
1904
2115
|
for (const task of tasksToImport) {
|
|
1905
2116
|
store.put(task);
|
|
1906
2117
|
}
|
|
1907
|
-
|
|
2118
|
+
|
|
1908
2119
|
await new Promise((resolve, reject) => {
|
|
1909
2120
|
tx.oncomplete = resolve;
|
|
1910
2121
|
tx.onerror = () => reject(tx.error);
|
|
1911
2122
|
});
|
|
1912
|
-
|
|
2123
|
+
|
|
1913
2124
|
db.close();
|
|
1914
|
-
|
|
1915
|
-
debugLog(
|
|
1916
|
-
|
|
2125
|
+
|
|
2126
|
+
debugLog(
|
|
2127
|
+
'success',
|
|
2128
|
+
`成功导入 ${tasksToImport.length} 个任务`,
|
|
2129
|
+
tasksToImport.map((t) => ({ id: t.id, type: t.type, status: t.status }))
|
|
1917
2130
|
);
|
|
1918
|
-
|
|
1919
2131
|
} catch (error) {
|
|
1920
2132
|
debugLog('error', `导入任务失败: ${error.message}`);
|
|
1921
2133
|
}
|
|
@@ -1925,19 +2137,21 @@ async function debugImportRemoteTasks() {
|
|
|
1925
2137
|
|
|
1926
2138
|
async function debugPreviewBoardMerge() {
|
|
1927
2139
|
debugLog('info', '预览画板合并...');
|
|
1928
|
-
|
|
2140
|
+
|
|
1929
2141
|
try {
|
|
1930
2142
|
await ensureToken();
|
|
1931
2143
|
await ensureGistData();
|
|
1932
|
-
|
|
2144
|
+
|
|
1933
2145
|
// Get local boards
|
|
1934
2146
|
const localBoards = await getLocalBoards();
|
|
1935
2147
|
debugLog('info', `本地画板: ${localBoards.length} 个`);
|
|
1936
|
-
|
|
2148
|
+
|
|
1937
2149
|
// Get remote boards (board_{id}.json format)
|
|
1938
2150
|
const remoteBoards = [];
|
|
1939
2151
|
if (currentGistData?.files) {
|
|
1940
|
-
for (const [fileName, fileData] of Object.entries(
|
|
2152
|
+
for (const [fileName, fileData] of Object.entries(
|
|
2153
|
+
currentGistData.files
|
|
2154
|
+
)) {
|
|
1941
2155
|
if (fileName.startsWith('board_') && fileName.endsWith('.json')) {
|
|
1942
2156
|
// 提取 board ID: board_{id}.json -> {id}
|
|
1943
2157
|
const boardId = fileName.replace('board_', '').replace('.json', '');
|
|
@@ -1946,33 +2160,36 @@ async function debugPreviewBoardMerge() {
|
|
|
1946
2160
|
}
|
|
1947
2161
|
}
|
|
1948
2162
|
debugLog('info', `远程画板: ${remoteBoards.length} 个`);
|
|
1949
|
-
|
|
2163
|
+
|
|
1950
2164
|
// Build comparison
|
|
1951
|
-
const localIds = new Set(localBoards.map(b => b.id));
|
|
1952
|
-
const remoteIds = new Set(remoteBoards.map(b => b.id));
|
|
1953
|
-
|
|
2165
|
+
const localIds = new Set(localBoards.map((b) => b.id));
|
|
2166
|
+
const remoteIds = new Set(remoteBoards.map((b) => b.id));
|
|
2167
|
+
|
|
1954
2168
|
const result = {
|
|
1955
|
-
localOnly: localBoards
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
2169
|
+
localOnly: localBoards
|
|
2170
|
+
.filter((b) => !remoteIds.has(b.id))
|
|
2171
|
+
.map((b) => ({
|
|
2172
|
+
id: b.id,
|
|
2173
|
+
name: b.name || b.title,
|
|
2174
|
+
elements: b.elements?.length || 0,
|
|
2175
|
+
})),
|
|
2176
|
+
remoteOnly: remoteBoards
|
|
2177
|
+
.filter((b) => !localIds.has(b.id))
|
|
2178
|
+
.map((b) => ({
|
|
2179
|
+
id: b.id,
|
|
2180
|
+
fileName: b.fileName,
|
|
2181
|
+
})),
|
|
2182
|
+
both: [...localIds].filter((id) => remoteIds.has(id)),
|
|
1965
2183
|
};
|
|
1966
|
-
|
|
2184
|
+
|
|
1967
2185
|
debugLog('success', '画板对比完成', result);
|
|
1968
|
-
|
|
2186
|
+
|
|
1969
2187
|
if (result.localOnly.length > 0) {
|
|
1970
2188
|
debugLog('info', `${result.localOnly.length} 个本地画板未同步到远程`);
|
|
1971
2189
|
}
|
|
1972
2190
|
if (result.remoteOnly.length > 0) {
|
|
1973
2191
|
debugLog('warning', `${result.remoteOnly.length} 个远程画板在本地不存在`);
|
|
1974
2192
|
}
|
|
1975
|
-
|
|
1976
2193
|
} catch (error) {
|
|
1977
2194
|
debugLog('error', `预览画板合并失败: ${error.message}`);
|
|
1978
2195
|
}
|
|
@@ -1980,11 +2197,11 @@ async function debugPreviewBoardMerge() {
|
|
|
1980
2197
|
|
|
1981
2198
|
async function debugDownloadBoard() {
|
|
1982
2199
|
debugLog('info', '下载画板...');
|
|
1983
|
-
|
|
2200
|
+
|
|
1984
2201
|
try {
|
|
1985
2202
|
await ensureToken();
|
|
1986
2203
|
await ensureGistData();
|
|
1987
|
-
|
|
2204
|
+
|
|
1988
2205
|
// Get available boards (board_{id}.json format)
|
|
1989
2206
|
const remoteBoards = [];
|
|
1990
2207
|
if (currentGistData?.files) {
|
|
@@ -1994,18 +2211,18 @@ async function debugDownloadBoard() {
|
|
|
1994
2211
|
}
|
|
1995
2212
|
}
|
|
1996
2213
|
}
|
|
1997
|
-
|
|
2214
|
+
|
|
1998
2215
|
if (remoteBoards.length === 0) {
|
|
1999
2216
|
debugLog('warning', '没有可下载的画板');
|
|
2000
2217
|
return;
|
|
2001
2218
|
}
|
|
2002
|
-
|
|
2219
|
+
|
|
2003
2220
|
// Download first board as example
|
|
2004
2221
|
const boardFile = remoteBoards[0];
|
|
2005
2222
|
debugLog('info', `下载画板: ${boardFile}`);
|
|
2006
|
-
|
|
2223
|
+
|
|
2007
2224
|
const boardData = await parseGistFile(currentGistData.files[boardFile]);
|
|
2008
|
-
|
|
2225
|
+
|
|
2009
2226
|
if (boardData) {
|
|
2010
2227
|
debugLog('success', `画板下载成功: ${boardFile}`, {
|
|
2011
2228
|
id: boardData.id,
|
|
@@ -2013,11 +2230,11 @@ async function debugDownloadBoard() {
|
|
|
2013
2230
|
elements: boardData.elements?.length || 0,
|
|
2014
2231
|
updatedAt: boardData.updatedAt,
|
|
2015
2232
|
});
|
|
2016
|
-
|
|
2233
|
+
|
|
2017
2234
|
// Show element types
|
|
2018
2235
|
if (boardData.elements?.length > 0) {
|
|
2019
2236
|
const elementTypes = {};
|
|
2020
|
-
boardData.elements.forEach(el => {
|
|
2237
|
+
boardData.elements.forEach((el) => {
|
|
2021
2238
|
const type = el.type || 'unknown';
|
|
2022
2239
|
elementTypes[type] = (elementTypes[type] || 0) + 1;
|
|
2023
2240
|
});
|
|
@@ -2026,7 +2243,6 @@ async function debugDownloadBoard() {
|
|
|
2026
2243
|
} else {
|
|
2027
2244
|
debugLog('error', '画板解析失败');
|
|
2028
2245
|
}
|
|
2029
|
-
|
|
2030
2246
|
} catch (error) {
|
|
2031
2247
|
debugLog('error', `下载画板失败: ${error.message}`);
|
|
2032
2248
|
}
|
|
@@ -2036,46 +2252,56 @@ async function debugDownloadBoard() {
|
|
|
2036
2252
|
|
|
2037
2253
|
async function debugListShardFiles() {
|
|
2038
2254
|
debugLog('info', '获取分片文件列表...');
|
|
2039
|
-
|
|
2255
|
+
|
|
2040
2256
|
try {
|
|
2041
2257
|
await ensureToken();
|
|
2042
|
-
|
|
2258
|
+
|
|
2043
2259
|
// Get master index
|
|
2044
|
-
const masterIndex =
|
|
2045
|
-
|
|
2260
|
+
const masterIndex =
|
|
2261
|
+
remoteMasterIndex || localMasterIndex || (await getLocalMasterIndex());
|
|
2262
|
+
|
|
2046
2263
|
if (!masterIndex) {
|
|
2047
2264
|
debugLog('warning', '未找到分片索引');
|
|
2048
2265
|
return;
|
|
2049
2266
|
}
|
|
2050
|
-
|
|
2267
|
+
|
|
2051
2268
|
const shards = Object.values(masterIndex.shards || {});
|
|
2052
2269
|
debugLog('info', `找到 ${shards.length} 个分片`);
|
|
2053
|
-
|
|
2270
|
+
|
|
2054
2271
|
// List files in each shard
|
|
2055
2272
|
for (const shard of shards) {
|
|
2056
|
-
debugLog(
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2273
|
+
debugLog(
|
|
2274
|
+
'info',
|
|
2275
|
+
`分片 ${shard.alias}: ${shard.fileCount} 文件, ${formatSize(
|
|
2276
|
+
shard.totalSize
|
|
2277
|
+
)}`,
|
|
2278
|
+
{
|
|
2279
|
+
gistId: shard.gistId,
|
|
2280
|
+
status: shard.status,
|
|
2281
|
+
createdAt: formatTime(shard.createdAt),
|
|
2282
|
+
}
|
|
2283
|
+
);
|
|
2061
2284
|
}
|
|
2062
|
-
|
|
2285
|
+
|
|
2063
2286
|
// List file index
|
|
2064
2287
|
const fileIndex = masterIndex.fileIndex || {};
|
|
2065
2288
|
const fileCount = Object.keys(fileIndex).length;
|
|
2066
2289
|
debugLog('success', `文件索引: ${fileCount} 个文件`);
|
|
2067
|
-
|
|
2290
|
+
|
|
2068
2291
|
// Sample files
|
|
2069
2292
|
const sampleFiles = Object.entries(fileIndex).slice(0, 5);
|
|
2070
2293
|
if (sampleFiles.length > 0) {
|
|
2071
|
-
debugLog(
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2294
|
+
debugLog(
|
|
2295
|
+
'info',
|
|
2296
|
+
'示例文件',
|
|
2297
|
+
sampleFiles.map(([url, info]) => ({
|
|
2298
|
+
url: extractFilename(url),
|
|
2299
|
+
shard: info.shardId,
|
|
2300
|
+
size: formatSize(info.size),
|
|
2301
|
+
type: info.type,
|
|
2302
|
+
}))
|
|
2303
|
+
);
|
|
2304
|
+
}
|
|
2079
2305
|
} catch (error) {
|
|
2080
2306
|
debugLog('error', `获取分片文件失败: ${error.message}`);
|
|
2081
2307
|
}
|
|
@@ -2083,46 +2309,47 @@ async function debugListShardFiles() {
|
|
|
2083
2309
|
|
|
2084
2310
|
async function debugTestMediaDownload() {
|
|
2085
2311
|
debugLog('info', '测试媒体下载...');
|
|
2086
|
-
|
|
2312
|
+
|
|
2087
2313
|
try {
|
|
2088
2314
|
await ensureToken();
|
|
2089
|
-
|
|
2315
|
+
|
|
2090
2316
|
// Get master index
|
|
2091
|
-
const masterIndex =
|
|
2092
|
-
|
|
2317
|
+
const masterIndex =
|
|
2318
|
+
remoteMasterIndex || localMasterIndex || (await getLocalMasterIndex());
|
|
2319
|
+
|
|
2093
2320
|
if (!masterIndex?.fileIndex) {
|
|
2094
2321
|
debugLog('warning', '未找到文件索引');
|
|
2095
2322
|
return;
|
|
2096
2323
|
}
|
|
2097
|
-
|
|
2324
|
+
|
|
2098
2325
|
// Get a sample file
|
|
2099
2326
|
const files = Object.entries(masterIndex.fileIndex);
|
|
2100
2327
|
if (files.length === 0) {
|
|
2101
2328
|
debugLog('warning', '索引中没有文件');
|
|
2102
2329
|
return;
|
|
2103
2330
|
}
|
|
2104
|
-
|
|
2331
|
+
|
|
2105
2332
|
const [url, fileInfo] = files[0];
|
|
2106
2333
|
debugLog('info', `测试下载: ${extractFilename(url)}`, fileInfo);
|
|
2107
|
-
|
|
2334
|
+
|
|
2108
2335
|
// Get shard info
|
|
2109
2336
|
const shard = masterIndex.shards?.[fileInfo.shardId];
|
|
2110
2337
|
if (!shard) {
|
|
2111
2338
|
debugLog('error', `找不到分片: ${fileInfo.shardId}`);
|
|
2112
2339
|
return;
|
|
2113
2340
|
}
|
|
2114
|
-
|
|
2341
|
+
|
|
2115
2342
|
// Fetch shard gist
|
|
2116
2343
|
debugLog('info', `从分片 ${shard.alias} (${shard.gistId}) 下载...`);
|
|
2117
|
-
|
|
2344
|
+
|
|
2118
2345
|
const shardGist = await fetchGist(shard.gistId);
|
|
2119
2346
|
const fileName = fileInfo.filename;
|
|
2120
|
-
|
|
2347
|
+
|
|
2121
2348
|
if (!shardGist.files?.[fileName]) {
|
|
2122
2349
|
debugLog('error', `分片中找不到文件: ${fileName}`);
|
|
2123
2350
|
return;
|
|
2124
2351
|
}
|
|
2125
|
-
|
|
2352
|
+
|
|
2126
2353
|
const fileData = shardGist.files[fileName];
|
|
2127
2354
|
debugLog('success', '文件获取成功', {
|
|
2128
2355
|
filename: fileName,
|
|
@@ -2130,13 +2357,12 @@ async function debugTestMediaDownload() {
|
|
|
2130
2357
|
truncated: fileData.truncated,
|
|
2131
2358
|
type: fileData.type,
|
|
2132
2359
|
});
|
|
2133
|
-
|
|
2360
|
+
|
|
2134
2361
|
// Check if it's base64 encoded
|
|
2135
2362
|
if (fileData.content) {
|
|
2136
2363
|
const isBase64 = fileData.content.match(/^[A-Za-z0-9+/=]+$/);
|
|
2137
2364
|
debugLog('info', `内容格式: ${isBase64 ? 'Base64 编码' : '文本'}`);
|
|
2138
2365
|
}
|
|
2139
|
-
|
|
2140
2366
|
} catch (error) {
|
|
2141
2367
|
debugLog('error', `测试媒体下载失败: ${error.message}`);
|
|
2142
2368
|
}
|
|
@@ -2146,7 +2372,7 @@ async function debugTestMediaDownload() {
|
|
|
2146
2372
|
|
|
2147
2373
|
async function debugCheckSyncLogDb() {
|
|
2148
2374
|
debugLog('info', '检查同步日志数据库...');
|
|
2149
|
-
|
|
2375
|
+
|
|
2150
2376
|
try {
|
|
2151
2377
|
// 1. Check if IndexedDB is available
|
|
2152
2378
|
if (!window.indexedDB) {
|
|
@@ -2154,30 +2380,32 @@ async function debugCheckSyncLogDb() {
|
|
|
2154
2380
|
return;
|
|
2155
2381
|
}
|
|
2156
2382
|
debugLog('success', 'IndexedDB 可用');
|
|
2157
|
-
|
|
2383
|
+
|
|
2158
2384
|
// 2. List all databases
|
|
2159
2385
|
if (indexedDB.databases) {
|
|
2160
2386
|
const dbs = await indexedDB.databases();
|
|
2161
|
-
const dbNames = dbs.map(db => `${db.name} (v${db.version})`);
|
|
2387
|
+
const dbNames = dbs.map((db) => `${db.name} (v${db.version})`);
|
|
2162
2388
|
debugLog('info', `已有数据库: ${dbs.length} 个`, dbNames);
|
|
2163
|
-
|
|
2164
|
-
const hasUnifiedLogDb = dbs.some(db => db.name === 'aitu-unified-logs');
|
|
2389
|
+
|
|
2390
|
+
const hasUnifiedLogDb = dbs.some((db) => db.name === 'aitu-unified-logs');
|
|
2165
2391
|
if (!hasUnifiedLogDb) {
|
|
2166
2392
|
debugLog('warning', '统一日志数据库 aitu-unified-logs 不存在');
|
|
2167
2393
|
}
|
|
2168
2394
|
}
|
|
2169
|
-
|
|
2395
|
+
|
|
2170
2396
|
// 3. Try to open the sync log database
|
|
2171
2397
|
const stats = await getSyncLogStats();
|
|
2172
2398
|
debugLog('success', '日志数据库连接成功', stats);
|
|
2173
|
-
|
|
2399
|
+
|
|
2174
2400
|
// 4. Query recent logs
|
|
2175
2401
|
const logs = await querySyncLogs({ limit: 5 });
|
|
2176
2402
|
if (logs.length === 0) {
|
|
2177
2403
|
debugLog('warning', '日志数据库为空 - 主应用可能尚未写入日志');
|
|
2178
2404
|
} else {
|
|
2179
|
-
debugLog(
|
|
2180
|
-
|
|
2405
|
+
debugLog(
|
|
2406
|
+
'success',
|
|
2407
|
+
`找到 ${logs.length} 条最新日志`,
|
|
2408
|
+
logs.map((l) => ({
|
|
2181
2409
|
time: new Date(l.timestamp).toLocaleTimeString(),
|
|
2182
2410
|
level: l.level,
|
|
2183
2411
|
category: l.category,
|
|
@@ -2185,7 +2413,6 @@ async function debugCheckSyncLogDb() {
|
|
|
2185
2413
|
}))
|
|
2186
2414
|
);
|
|
2187
2415
|
}
|
|
2188
|
-
|
|
2189
2416
|
} catch (error) {
|
|
2190
2417
|
debugLog('error', `检查日志数据库失败: ${error.message}`);
|
|
2191
2418
|
}
|
|
@@ -2193,11 +2420,11 @@ async function debugCheckSyncLogDb() {
|
|
|
2193
2420
|
|
|
2194
2421
|
async function debugWriteTestLog() {
|
|
2195
2422
|
debugLog('info', '写入测试日志到统一日志数据库...');
|
|
2196
|
-
|
|
2423
|
+
|
|
2197
2424
|
try {
|
|
2198
2425
|
const UNIFIED_LOG_DB = 'aitu-unified-logs';
|
|
2199
2426
|
const UNIFIED_LOG_STORE = 'logs';
|
|
2200
|
-
|
|
2427
|
+
|
|
2201
2428
|
// Open database
|
|
2202
2429
|
const db = await new Promise((resolve, reject) => {
|
|
2203
2430
|
const request = indexedDB.open(UNIFIED_LOG_DB, 1);
|
|
@@ -2206,16 +2433,20 @@ async function debugWriteTestLog() {
|
|
|
2206
2433
|
request.onupgradeneeded = (event) => {
|
|
2207
2434
|
const database = event.target.result;
|
|
2208
2435
|
if (!database.objectStoreNames.contains(UNIFIED_LOG_STORE)) {
|
|
2209
|
-
const store = database.createObjectStore(UNIFIED_LOG_STORE, {
|
|
2436
|
+
const store = database.createObjectStore(UNIFIED_LOG_STORE, {
|
|
2437
|
+
keyPath: 'id',
|
|
2438
|
+
});
|
|
2210
2439
|
store.createIndex('timestamp', 'timestamp', { unique: false });
|
|
2211
2440
|
store.createIndex('category', 'category', { unique: false });
|
|
2212
2441
|
store.createIndex('level', 'level', { unique: false });
|
|
2213
2442
|
store.createIndex('sessionId', 'sessionId', { unique: false });
|
|
2214
|
-
store.createIndex('category_timestamp', ['category', 'timestamp'], {
|
|
2443
|
+
store.createIndex('category_timestamp', ['category', 'timestamp'], {
|
|
2444
|
+
unique: false,
|
|
2445
|
+
});
|
|
2215
2446
|
}
|
|
2216
2447
|
};
|
|
2217
2448
|
});
|
|
2218
|
-
|
|
2449
|
+
|
|
2219
2450
|
// Write test log (unified log format)
|
|
2220
2451
|
const testEntry = {
|
|
2221
2452
|
id: `log-${Date.now()}-${Math.random().toString(36).slice(2, 10)}`,
|
|
@@ -2225,21 +2456,20 @@ async function debugWriteTestLog() {
|
|
|
2225
2456
|
message: '这是来自 sw-debug 面板的测试日志',
|
|
2226
2457
|
data: { source: 'sw-debug', test: true },
|
|
2227
2458
|
};
|
|
2228
|
-
|
|
2459
|
+
|
|
2229
2460
|
const tx = db.transaction(UNIFIED_LOG_STORE, 'readwrite');
|
|
2230
2461
|
const store = tx.objectStore(UNIFIED_LOG_STORE);
|
|
2231
2462
|
store.add(testEntry);
|
|
2232
|
-
|
|
2463
|
+
|
|
2233
2464
|
await new Promise((resolve, reject) => {
|
|
2234
2465
|
tx.oncomplete = () => resolve();
|
|
2235
2466
|
tx.onerror = () => reject(tx.error);
|
|
2236
2467
|
});
|
|
2237
|
-
|
|
2468
|
+
|
|
2238
2469
|
db.close();
|
|
2239
|
-
|
|
2470
|
+
|
|
2240
2471
|
debugLog('success', '测试日志写入成功', testEntry);
|
|
2241
2472
|
debugLog('info', '请刷新同步日志区域查看');
|
|
2242
|
-
|
|
2243
2473
|
} catch (error) {
|
|
2244
2474
|
debugLog('error', `写入测试日志失败: ${error.message}`);
|
|
2245
2475
|
}
|
|
@@ -2250,23 +2480,23 @@ async function debugWriteTestLog() {
|
|
|
2250
2480
|
*/
|
|
2251
2481
|
async function debugPerformanceTest() {
|
|
2252
2482
|
debugLog('info', '开始性能基准测试 (1000 次日志写入)...');
|
|
2253
|
-
|
|
2483
|
+
|
|
2254
2484
|
try {
|
|
2255
2485
|
const UNIFIED_LOG_DB = 'aitu-unified-logs';
|
|
2256
2486
|
const UNIFIED_LOG_STORE = 'logs';
|
|
2257
2487
|
const iterations = 1000;
|
|
2258
|
-
|
|
2488
|
+
|
|
2259
2489
|
// Open database
|
|
2260
2490
|
const db = await new Promise((resolve, reject) => {
|
|
2261
2491
|
const request = indexedDB.open(UNIFIED_LOG_DB, 1);
|
|
2262
2492
|
request.onerror = () => reject(request.error);
|
|
2263
2493
|
request.onsuccess = () => resolve(request.result);
|
|
2264
2494
|
});
|
|
2265
|
-
|
|
2495
|
+
|
|
2266
2496
|
// Test 1: Memory write speed (sync)
|
|
2267
2497
|
const memoryLogs = [];
|
|
2268
2498
|
const memoryStart = performance.now();
|
|
2269
|
-
|
|
2499
|
+
|
|
2270
2500
|
for (let i = 0; i < iterations; i++) {
|
|
2271
2501
|
memoryLogs.push({
|
|
2272
2502
|
id: `perf-${Date.now()}-${i}`,
|
|
@@ -2277,47 +2507,55 @@ async function debugPerformanceTest() {
|
|
|
2277
2507
|
data: { iteration: i },
|
|
2278
2508
|
});
|
|
2279
2509
|
}
|
|
2280
|
-
|
|
2510
|
+
|
|
2281
2511
|
const memoryEnd = performance.now();
|
|
2282
2512
|
const memoryTimeMs = memoryEnd - memoryStart;
|
|
2283
2513
|
const memoryAvgUs = (memoryTimeMs / iterations) * 1000;
|
|
2284
|
-
|
|
2285
|
-
debugLog(
|
|
2286
|
-
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
|
|
2291
|
-
|
|
2514
|
+
|
|
2515
|
+
debugLog(
|
|
2516
|
+
'success',
|
|
2517
|
+
`内存写入: ${memoryTimeMs.toFixed(2)}ms (${memoryAvgUs.toFixed(2)}μs/条)`,
|
|
2518
|
+
{
|
|
2519
|
+
iterations,
|
|
2520
|
+
totalMs: memoryTimeMs.toFixed(2),
|
|
2521
|
+
avgMicroseconds: memoryAvgUs.toFixed(2),
|
|
2522
|
+
logsPerSecond: Math.round(iterations / (memoryTimeMs / 1000)),
|
|
2523
|
+
}
|
|
2524
|
+
);
|
|
2525
|
+
|
|
2292
2526
|
// Test 2: IndexedDB write speed (async batch)
|
|
2293
2527
|
const dbStart = performance.now();
|
|
2294
|
-
|
|
2528
|
+
|
|
2295
2529
|
const tx = db.transaction(UNIFIED_LOG_STORE, 'readwrite');
|
|
2296
2530
|
const store = tx.objectStore(UNIFIED_LOG_STORE);
|
|
2297
|
-
|
|
2531
|
+
|
|
2298
2532
|
// Batch write
|
|
2299
2533
|
for (const log of memoryLogs) {
|
|
2300
2534
|
store.add(log);
|
|
2301
2535
|
}
|
|
2302
|
-
|
|
2536
|
+
|
|
2303
2537
|
await new Promise((resolve, reject) => {
|
|
2304
2538
|
tx.oncomplete = () => resolve();
|
|
2305
2539
|
tx.onerror = () => reject(tx.error);
|
|
2306
2540
|
});
|
|
2307
|
-
|
|
2541
|
+
|
|
2308
2542
|
const dbEnd = performance.now();
|
|
2309
2543
|
const dbTimeMs = dbEnd - dbStart;
|
|
2310
2544
|
const dbAvgUs = (dbTimeMs / iterations) * 1000;
|
|
2311
|
-
|
|
2312
|
-
debugLog(
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
|
|
2545
|
+
|
|
2546
|
+
debugLog(
|
|
2547
|
+
'success',
|
|
2548
|
+
`IndexedDB 写入: ${dbTimeMs.toFixed(2)}ms (${dbAvgUs.toFixed(2)}μs/条)`,
|
|
2549
|
+
{
|
|
2550
|
+
iterations,
|
|
2551
|
+
totalMs: dbTimeMs.toFixed(2),
|
|
2552
|
+
avgMicroseconds: dbAvgUs.toFixed(2),
|
|
2553
|
+
logsPerSecond: Math.round(iterations / (dbTimeMs / 1000)),
|
|
2554
|
+
}
|
|
2555
|
+
);
|
|
2556
|
+
|
|
2319
2557
|
db.close();
|
|
2320
|
-
|
|
2558
|
+
|
|
2321
2559
|
// Summary
|
|
2322
2560
|
const passed = memoryAvgUs < 100; // Target: < 100μs per log
|
|
2323
2561
|
if (passed) {
|
|
@@ -2331,7 +2569,6 @@ async function debugPerformanceTest() {
|
|
|
2331
2569
|
actual: `${memoryAvgUs.toFixed(2)}μs/条`,
|
|
2332
2570
|
});
|
|
2333
2571
|
}
|
|
2334
|
-
|
|
2335
2572
|
} catch (error) {
|
|
2336
2573
|
debugLog('error', `性能测试失败: ${error.message}`);
|
|
2337
2574
|
}
|
|
@@ -2342,34 +2579,38 @@ async function debugPerformanceTest() {
|
|
|
2342
2579
|
*/
|
|
2343
2580
|
async function debugDiagnoseAllDatabases() {
|
|
2344
2581
|
debugLog('info', '开始诊断所有数据库...');
|
|
2345
|
-
|
|
2582
|
+
|
|
2346
2583
|
const dbsToCheck = [
|
|
2347
2584
|
'aitu-workspace',
|
|
2348
2585
|
'sw-task-queue',
|
|
2349
2586
|
'aitu-unified-logs',
|
|
2350
2587
|
'aitu-storage',
|
|
2351
2588
|
];
|
|
2352
|
-
|
|
2589
|
+
|
|
2353
2590
|
try {
|
|
2354
2591
|
// List all databases
|
|
2355
2592
|
if (indexedDB.databases) {
|
|
2356
2593
|
const allDbs = await indexedDB.databases();
|
|
2357
|
-
debugLog(
|
|
2358
|
-
|
|
2594
|
+
debugLog(
|
|
2595
|
+
'info',
|
|
2596
|
+
`系统中共有 ${allDbs.length} 个数据库`,
|
|
2597
|
+
allDbs.map((db) => `${db.name} (v${db.version})`).join(', ')
|
|
2359
2598
|
);
|
|
2360
2599
|
}
|
|
2361
|
-
|
|
2600
|
+
|
|
2362
2601
|
// Diagnose each database
|
|
2363
2602
|
for (const dbName of dbsToCheck) {
|
|
2364
2603
|
try {
|
|
2365
2604
|
const diagnosis = await diagnoseDatabase(dbName);
|
|
2366
|
-
const storeInfo = Object.entries(diagnosis.stores).map(
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
|
|
2605
|
+
const storeInfo = Object.entries(diagnosis.stores).map(
|
|
2606
|
+
([name, info]) => ({
|
|
2607
|
+
store: name,
|
|
2608
|
+
count: info.count,
|
|
2609
|
+
keyPath: info.keyPath,
|
|
2610
|
+
sampleKeys: info.sampleKeys?.join(', ') || '-',
|
|
2611
|
+
})
|
|
2612
|
+
);
|
|
2613
|
+
|
|
2373
2614
|
debugLog('success', `${dbName} (v${diagnosis.version})`, {
|
|
2374
2615
|
stores: storeInfo,
|
|
2375
2616
|
});
|
|
@@ -2377,27 +2618,38 @@ async function debugDiagnoseAllDatabases() {
|
|
|
2377
2618
|
debugLog('warning', `${dbName}: ${error.message}`);
|
|
2378
2619
|
}
|
|
2379
2620
|
}
|
|
2380
|
-
|
|
2621
|
+
|
|
2381
2622
|
// Test actual data retrieval
|
|
2382
2623
|
debugLog('info', '测试数据获取...');
|
|
2383
|
-
|
|
2624
|
+
|
|
2384
2625
|
const localTasks = await getLocalTasks();
|
|
2385
|
-
debugLog(
|
|
2386
|
-
|
|
2626
|
+
debugLog(
|
|
2627
|
+
'info',
|
|
2628
|
+
`本地任务: ${localTasks.length} 条`,
|
|
2629
|
+
localTasks.slice(0, 3).map((t) => ({
|
|
2630
|
+
id: t.id?.substring(0, 12),
|
|
2631
|
+
type: t.type,
|
|
2632
|
+
status: t.status,
|
|
2633
|
+
}))
|
|
2387
2634
|
);
|
|
2388
|
-
|
|
2635
|
+
|
|
2389
2636
|
const localBoards = await getLocalBoards();
|
|
2390
|
-
debugLog(
|
|
2391
|
-
|
|
2637
|
+
debugLog(
|
|
2638
|
+
'info',
|
|
2639
|
+
`本地画板: ${localBoards.length} 个`,
|
|
2640
|
+
localBoards
|
|
2641
|
+
.slice(0, 3)
|
|
2642
|
+
.map((b) => ({ id: b.id?.substring(0, 12), name: b.name }))
|
|
2392
2643
|
);
|
|
2393
|
-
|
|
2644
|
+
|
|
2394
2645
|
const localMedia = await listCacheStorageMedia();
|
|
2395
|
-
debugLog(
|
|
2396
|
-
|
|
2646
|
+
debugLog(
|
|
2647
|
+
'info',
|
|
2648
|
+
`本地缓存媒体: ${localMedia.length} 个`,
|
|
2649
|
+
localMedia.slice(0, 3).map((m) => m.filename)
|
|
2397
2650
|
);
|
|
2398
|
-
|
|
2651
|
+
|
|
2399
2652
|
debugLog('success', '数据库诊断完成');
|
|
2400
|
-
|
|
2401
2653
|
} catch (error) {
|
|
2402
2654
|
debugLog('error', `数据库诊断失败: ${error.message}`);
|
|
2403
2655
|
}
|
|
@@ -2407,7 +2659,7 @@ async function debugDiagnoseAllDatabases() {
|
|
|
2407
2659
|
|
|
2408
2660
|
async function ensureToken() {
|
|
2409
2661
|
if (currentToken) return;
|
|
2410
|
-
|
|
2662
|
+
|
|
2411
2663
|
const encryptedToken = localStorage.getItem('github_sync_token');
|
|
2412
2664
|
if (!encryptedToken) {
|
|
2413
2665
|
throw new Error('未配置 GitHub Token');
|
|
@@ -2417,7 +2669,7 @@ async function ensureToken() {
|
|
|
2417
2669
|
|
|
2418
2670
|
async function ensureGistData() {
|
|
2419
2671
|
if (currentGistData) return;
|
|
2420
|
-
|
|
2672
|
+
|
|
2421
2673
|
const creds = await getGistCredentials();
|
|
2422
2674
|
if (!creds.gistId) {
|
|
2423
2675
|
throw new Error('未配置 Gist ID');
|
|
@@ -2446,33 +2698,49 @@ function openDatabase(name, version, upgradeCallback) {
|
|
|
2446
2698
|
|
|
2447
2699
|
function initSyncLogViewer() {
|
|
2448
2700
|
// Refresh button
|
|
2449
|
-
document
|
|
2450
|
-
|
|
2701
|
+
document
|
|
2702
|
+
.getElementById('refreshSyncLogs')
|
|
2703
|
+
?.addEventListener('click', refreshSyncLogs);
|
|
2704
|
+
|
|
2451
2705
|
// Auto-refresh button
|
|
2452
|
-
document
|
|
2453
|
-
|
|
2706
|
+
document
|
|
2707
|
+
.getElementById('toggleAutoRefresh')
|
|
2708
|
+
?.addEventListener('click', toggleAutoRefresh);
|
|
2709
|
+
|
|
2454
2710
|
// Copy button
|
|
2455
|
-
document
|
|
2456
|
-
|
|
2711
|
+
document
|
|
2712
|
+
.getElementById('copySyncLogs')
|
|
2713
|
+
?.addEventListener('click', handleCopySyncLogs);
|
|
2714
|
+
|
|
2457
2715
|
// Export button
|
|
2458
|
-
document
|
|
2459
|
-
|
|
2716
|
+
document
|
|
2717
|
+
.getElementById('exportSyncLogs')
|
|
2718
|
+
?.addEventListener('click', handleExportSyncLogs);
|
|
2719
|
+
|
|
2460
2720
|
// Clear button
|
|
2461
|
-
document
|
|
2462
|
-
|
|
2721
|
+
document
|
|
2722
|
+
.getElementById('clearSyncLogs')
|
|
2723
|
+
?.addEventListener('click', handleClearSyncLogs);
|
|
2724
|
+
|
|
2463
2725
|
// Filters
|
|
2464
|
-
document
|
|
2465
|
-
|
|
2466
|
-
|
|
2467
|
-
|
|
2726
|
+
document
|
|
2727
|
+
.getElementById('syncLogLevelFilter')
|
|
2728
|
+
?.addEventListener('change', handleSyncLogFilterChange);
|
|
2729
|
+
document
|
|
2730
|
+
.getElementById('syncLogCategoryFilter')
|
|
2731
|
+
?.addEventListener('change', handleSyncLogFilterChange);
|
|
2732
|
+
document
|
|
2733
|
+
.getElementById('syncLogSessionFilter')
|
|
2734
|
+
?.addEventListener('change', handleSyncLogFilterChange);
|
|
2735
|
+
|
|
2468
2736
|
// Clickable stat cards for filtering
|
|
2469
|
-
document.querySelectorAll('.sync-log-stat.clickable').forEach(card => {
|
|
2737
|
+
document.querySelectorAll('.sync-log-stat.clickable').forEach((card) => {
|
|
2470
2738
|
card.addEventListener('click', () => {
|
|
2471
2739
|
const filterLevel = card.dataset.filter;
|
|
2472
2740
|
handleStatCardClick(card, filterLevel);
|
|
2473
2741
|
});
|
|
2474
2742
|
});
|
|
2475
|
-
|
|
2743
|
+
|
|
2476
2744
|
// Search with debounce
|
|
2477
2745
|
let searchTimeout;
|
|
2478
2746
|
document.getElementById('syncLogSearch')?.addEventListener('input', (e) => {
|
|
@@ -2483,7 +2751,7 @@ function initSyncLogViewer() {
|
|
|
2483
2751
|
loadSyncLogs();
|
|
2484
2752
|
}, 300);
|
|
2485
2753
|
});
|
|
2486
|
-
|
|
2754
|
+
|
|
2487
2755
|
// Pagination
|
|
2488
2756
|
document.getElementById('syncLogPrevPage')?.addEventListener('click', () => {
|
|
2489
2757
|
if (syncLogCurrentPage > 1) {
|
|
@@ -2491,7 +2759,7 @@ function initSyncLogViewer() {
|
|
|
2491
2759
|
loadSyncLogs();
|
|
2492
2760
|
}
|
|
2493
2761
|
});
|
|
2494
|
-
|
|
2762
|
+
|
|
2495
2763
|
document.getElementById('syncLogNextPage')?.addEventListener('click', () => {
|
|
2496
2764
|
const totalPages = Math.ceil(syncLogTotalEntries / syncLogPageSize);
|
|
2497
2765
|
if (syncLogCurrentPage < totalPages) {
|
|
@@ -2506,17 +2774,17 @@ function initSyncLogViewer() {
|
|
|
2506
2774
|
*/
|
|
2507
2775
|
function handleStatCardClick(clickedCard, filterLevel) {
|
|
2508
2776
|
// Update active state
|
|
2509
|
-
document.querySelectorAll('.sync-log-stat.clickable').forEach(card => {
|
|
2777
|
+
document.querySelectorAll('.sync-log-stat.clickable').forEach((card) => {
|
|
2510
2778
|
card.classList.remove('active');
|
|
2511
2779
|
});
|
|
2512
2780
|
clickedCard.classList.add('active');
|
|
2513
|
-
|
|
2781
|
+
|
|
2514
2782
|
// Update dropdown filter to match
|
|
2515
2783
|
const levelFilter = document.getElementById('syncLogLevelFilter');
|
|
2516
2784
|
if (levelFilter) {
|
|
2517
2785
|
levelFilter.value = filterLevel;
|
|
2518
2786
|
}
|
|
2519
|
-
|
|
2787
|
+
|
|
2520
2788
|
// Apply filter
|
|
2521
2789
|
syncLogCurrentFilters.level = filterLevel;
|
|
2522
2790
|
syncLogCurrentPage = 1;
|
|
@@ -2530,32 +2798,37 @@ async function handleCopySyncLogs() {
|
|
|
2530
2798
|
try {
|
|
2531
2799
|
// Query all logs with current filters (no pagination limit)
|
|
2532
2800
|
const query = { ...syncLogCurrentFilters, limit: 10000 };
|
|
2533
|
-
Object.keys(query).forEach(key => {
|
|
2801
|
+
Object.keys(query).forEach((key) => {
|
|
2534
2802
|
if (!query[key]) delete query[key];
|
|
2535
2803
|
});
|
|
2536
|
-
|
|
2804
|
+
|
|
2537
2805
|
const logs = await querySyncLogs(query);
|
|
2538
|
-
|
|
2806
|
+
|
|
2539
2807
|
if (logs.length === 0) {
|
|
2540
2808
|
alert('没有可复制的日志');
|
|
2541
2809
|
return;
|
|
2542
2810
|
}
|
|
2543
|
-
|
|
2811
|
+
|
|
2544
2812
|
// Format logs for clipboard
|
|
2545
|
-
const formattedLogs = logs
|
|
2546
|
-
|
|
2547
|
-
|
|
2548
|
-
|
|
2549
|
-
|
|
2550
|
-
|
|
2551
|
-
|
|
2552
|
-
|
|
2553
|
-
|
|
2554
|
-
|
|
2555
|
-
|
|
2813
|
+
const formattedLogs = logs
|
|
2814
|
+
.map((log) => {
|
|
2815
|
+
const time = new Date(log.timestamp).toLocaleString('zh-CN');
|
|
2816
|
+
const level = log.level?.toUpperCase() || 'INFO';
|
|
2817
|
+
const category = log.category || '';
|
|
2818
|
+
const message = log.message || '';
|
|
2819
|
+
const data = log.data ? JSON.stringify(log.data) : '';
|
|
2820
|
+
const error = log.error
|
|
2821
|
+
? `\n Error: ${log.error.message || log.error}`
|
|
2822
|
+
: '';
|
|
2823
|
+
|
|
2824
|
+
return `[${time}] [${level}]${
|
|
2825
|
+
category ? ` [${category}]` : ''
|
|
2826
|
+
} ${message}${data ? `\n Data: ${data}` : ''}${error}`;
|
|
2827
|
+
})
|
|
2828
|
+
.join('\n\n');
|
|
2829
|
+
|
|
2556
2830
|
await navigator.clipboard.writeText(formattedLogs);
|
|
2557
2831
|
debugLog('success', `已复制 ${logs.length} 条日志到剪贴板`);
|
|
2558
|
-
|
|
2559
2832
|
} catch (error) {
|
|
2560
2833
|
console.error('Failed to copy logs:', error);
|
|
2561
2834
|
debugLog('error', `复制日志失败: ${error.message}`);
|
|
@@ -2567,21 +2840,20 @@ async function handleCopySyncLogs() {
|
|
|
2567
2840
|
*/
|
|
2568
2841
|
function toggleAutoRefresh() {
|
|
2569
2842
|
syncLogAutoRefreshEnabled = !syncLogAutoRefreshEnabled;
|
|
2570
|
-
|
|
2843
|
+
|
|
2571
2844
|
const btn = document.getElementById('toggleAutoRefresh');
|
|
2572
2845
|
const label = document.getElementById('autoRefreshLabel');
|
|
2573
|
-
|
|
2846
|
+
|
|
2574
2847
|
if (syncLogAutoRefreshEnabled) {
|
|
2575
2848
|
// Start auto-refresh
|
|
2576
2849
|
syncLogAutoRefreshInterval = setInterval(async () => {
|
|
2577
|
-
console.log('[SyncLogViewer] Auto-refreshing...');
|
|
2578
2850
|
await silentRefreshSyncLogs();
|
|
2579
2851
|
}, AUTO_REFRESH_INTERVAL);
|
|
2580
|
-
|
|
2852
|
+
|
|
2581
2853
|
// Update UI
|
|
2582
2854
|
btn?.classList.add('active');
|
|
2583
2855
|
if (label) label.textContent = '停止';
|
|
2584
|
-
|
|
2856
|
+
|
|
2585
2857
|
// Initial refresh
|
|
2586
2858
|
silentRefreshSyncLogs();
|
|
2587
2859
|
} else {
|
|
@@ -2590,7 +2862,7 @@ function toggleAutoRefresh() {
|
|
|
2590
2862
|
clearInterval(syncLogAutoRefreshInterval);
|
|
2591
2863
|
syncLogAutoRefreshInterval = null;
|
|
2592
2864
|
}
|
|
2593
|
-
|
|
2865
|
+
|
|
2594
2866
|
// Update UI
|
|
2595
2867
|
btn?.classList.remove('active');
|
|
2596
2868
|
if (label) label.textContent = '自动';
|
|
@@ -2605,36 +2877,31 @@ async function silentRefreshSyncLogs() {
|
|
|
2605
2877
|
// Load stats
|
|
2606
2878
|
const stats = await getSyncLogStats();
|
|
2607
2879
|
updateSyncLogStats(stats);
|
|
2608
|
-
|
|
2880
|
+
|
|
2609
2881
|
// Check if there are new logs
|
|
2610
2882
|
if (stats.total !== syncLogTotalEntries && syncLogCurrentPage === 1) {
|
|
2611
2883
|
syncLogTotalEntries = stats.total;
|
|
2612
2884
|
await loadSyncLogs();
|
|
2613
2885
|
}
|
|
2614
|
-
|
|
2615
2886
|
} catch (error) {
|
|
2616
2887
|
console.error('Failed to auto-refresh sync logs:', error);
|
|
2617
2888
|
}
|
|
2618
2889
|
}
|
|
2619
2890
|
|
|
2620
2891
|
async function refreshSyncLogs() {
|
|
2621
|
-
console.log('[SyncLogViewer] Refreshing sync logs...');
|
|
2622
2892
|
try {
|
|
2623
2893
|
// Load stats
|
|
2624
2894
|
const stats = await getSyncLogStats();
|
|
2625
|
-
console.log('[SyncLogViewer] Stats:', stats);
|
|
2626
2895
|
updateSyncLogStats(stats);
|
|
2627
|
-
|
|
2896
|
+
|
|
2628
2897
|
// Load sessions for filter
|
|
2629
2898
|
const sessions = await getSyncSessions();
|
|
2630
|
-
console.log('[SyncLogViewer] Sessions:', sessions.length);
|
|
2631
2899
|
populateSessionFilter(sessions);
|
|
2632
|
-
|
|
2900
|
+
|
|
2633
2901
|
// Reset to first page and load logs
|
|
2634
2902
|
syncLogCurrentPage = 1;
|
|
2635
2903
|
syncLogTotalEntries = stats.total;
|
|
2636
2904
|
await loadSyncLogs();
|
|
2637
|
-
|
|
2638
2905
|
} catch (error) {
|
|
2639
2906
|
console.error('Failed to refresh sync logs:', error);
|
|
2640
2907
|
}
|
|
@@ -2642,27 +2909,39 @@ async function refreshSyncLogs() {
|
|
|
2642
2909
|
|
|
2643
2910
|
function updateSyncLogStats(stats) {
|
|
2644
2911
|
document.getElementById('syncLogTotal').textContent = stats.total || 0;
|
|
2645
|
-
document.getElementById('syncLogErrors').textContent =
|
|
2646
|
-
|
|
2647
|
-
document.getElementById('
|
|
2648
|
-
|
|
2912
|
+
document.getElementById('syncLogErrors').textContent =
|
|
2913
|
+
stats.byLevel?.error || 0;
|
|
2914
|
+
document.getElementById('syncLogWarnings').textContent =
|
|
2915
|
+
stats.byLevel?.warning || 0;
|
|
2916
|
+
document.getElementById('syncLogSuccesses').textContent =
|
|
2917
|
+
stats.byLevel?.success || 0;
|
|
2918
|
+
document.getElementById('syncLogSessions').textContent =
|
|
2919
|
+
stats.sessionCount || 0;
|
|
2649
2920
|
}
|
|
2650
2921
|
|
|
2651
2922
|
function populateSessionFilter(sessions) {
|
|
2652
2923
|
const select = document.getElementById('syncLogSessionFilter');
|
|
2653
2924
|
if (!select) return;
|
|
2654
|
-
|
|
2925
|
+
|
|
2655
2926
|
// Keep first option
|
|
2656
2927
|
select.innerHTML = '<option value="">全部会话</option>';
|
|
2657
|
-
|
|
2928
|
+
|
|
2658
2929
|
// Add sessions (most recent first)
|
|
2659
|
-
sessions.slice(0, 20).forEach(session => {
|
|
2930
|
+
sessions.slice(0, 20).forEach((session) => {
|
|
2660
2931
|
const option = document.createElement('option');
|
|
2661
2932
|
option.value = session.sessionId;
|
|
2662
2933
|
const date = new Date(session.startTime);
|
|
2663
|
-
const dateStr = date.toLocaleDateString('zh-CN', {
|
|
2664
|
-
|
|
2665
|
-
|
|
2934
|
+
const dateStr = date.toLocaleDateString('zh-CN', {
|
|
2935
|
+
month: 'short',
|
|
2936
|
+
day: 'numeric',
|
|
2937
|
+
});
|
|
2938
|
+
const timeStr = date.toLocaleTimeString('zh-CN', {
|
|
2939
|
+
hour: '2-digit',
|
|
2940
|
+
minute: '2-digit',
|
|
2941
|
+
});
|
|
2942
|
+
option.textContent = `${dateStr} ${timeStr} (${session.logCount}条${
|
|
2943
|
+
session.hasErrors ? ', 有错误' : ''
|
|
2944
|
+
})`;
|
|
2666
2945
|
select.appendChild(option);
|
|
2667
2946
|
});
|
|
2668
2947
|
}
|
|
@@ -2681,49 +2960,54 @@ function handleSyncLogFilterChange() {
|
|
|
2681
2960
|
async function loadSyncLogs() {
|
|
2682
2961
|
const container = document.getElementById('syncLogList');
|
|
2683
2962
|
if (!container) return;
|
|
2684
|
-
|
|
2963
|
+
|
|
2685
2964
|
try {
|
|
2686
2965
|
const query = {
|
|
2687
2966
|
...syncLogCurrentFilters,
|
|
2688
2967
|
limit: syncLogPageSize * syncLogCurrentPage, // We'll slice later
|
|
2689
2968
|
};
|
|
2690
|
-
|
|
2969
|
+
|
|
2691
2970
|
// Remove empty filters
|
|
2692
|
-
Object.keys(query).forEach(key => {
|
|
2971
|
+
Object.keys(query).forEach((key) => {
|
|
2693
2972
|
if (!query[key]) delete query[key];
|
|
2694
2973
|
});
|
|
2695
|
-
|
|
2974
|
+
|
|
2696
2975
|
const allLogs = await querySyncLogs(query);
|
|
2697
|
-
|
|
2976
|
+
|
|
2698
2977
|
// Calculate pagination
|
|
2699
2978
|
const startIndex = (syncLogCurrentPage - 1) * syncLogPageSize;
|
|
2700
2979
|
const logs = allLogs.slice(startIndex, startIndex + syncLogPageSize);
|
|
2701
2980
|
syncLogTotalEntries = allLogs.length;
|
|
2702
|
-
|
|
2981
|
+
|
|
2703
2982
|
if (logs.length === 0) {
|
|
2704
|
-
container.innerHTML =
|
|
2983
|
+
container.innerHTML =
|
|
2984
|
+
'<div class="empty-state" style="padding: 40px;">没有找到匹配的日志</div>';
|
|
2705
2985
|
updatePagination();
|
|
2706
2986
|
return;
|
|
2707
2987
|
}
|
|
2708
|
-
|
|
2988
|
+
|
|
2709
2989
|
// 使用 createConsoleEntry 渲染日志(复用控制台日志组件)
|
|
2710
2990
|
container.innerHTML = '';
|
|
2711
|
-
logs.forEach(log => {
|
|
2991
|
+
logs.forEach((log) => {
|
|
2712
2992
|
const isExpanded = syncLogExpandedIds.has(log.id);
|
|
2713
|
-
const entry = createConsoleEntry(
|
|
2714
|
-
|
|
2715
|
-
|
|
2716
|
-
|
|
2717
|
-
|
|
2993
|
+
const entry = createConsoleEntry(
|
|
2994
|
+
log,
|
|
2995
|
+
isExpanded,
|
|
2996
|
+
(id, expanded) => {
|
|
2997
|
+
if (expanded) {
|
|
2998
|
+
syncLogExpandedIds.add(id);
|
|
2999
|
+
} else {
|
|
3000
|
+
syncLogExpandedIds.delete(id);
|
|
3001
|
+
}
|
|
3002
|
+
},
|
|
3003
|
+
{
|
|
3004
|
+
showDate: true, // 同步日志显示日期
|
|
3005
|
+
showLevelLabel: true, // 同步日志显示中文级别标签
|
|
2718
3006
|
}
|
|
2719
|
-
|
|
2720
|
-
showDate: true, // 同步日志显示日期
|
|
2721
|
-
showLevelLabel: true // 同步日志显示中文级别标签
|
|
2722
|
-
});
|
|
3007
|
+
);
|
|
2723
3008
|
container.appendChild(entry);
|
|
2724
3009
|
});
|
|
2725
3010
|
updatePagination();
|
|
2726
|
-
|
|
2727
3011
|
} catch (error) {
|
|
2728
3012
|
console.error('Failed to load sync logs:', error);
|
|
2729
3013
|
container.innerHTML = `<div class="empty-state" style="padding: 40px;">加载日志失败: ${error.message}</div>`;
|
|
@@ -2732,13 +3016,14 @@ async function loadSyncLogs() {
|
|
|
2732
3016
|
|
|
2733
3017
|
function updatePagination() {
|
|
2734
3018
|
const totalPages = Math.ceil(syncLogTotalEntries / syncLogPageSize) || 1;
|
|
2735
|
-
|
|
2736
|
-
document.getElementById(
|
|
2737
|
-
|
|
2738
|
-
|
|
3019
|
+
|
|
3020
|
+
document.getElementById(
|
|
3021
|
+
'syncLogPageInfo'
|
|
3022
|
+
).textContent = `第 ${syncLogCurrentPage} / ${totalPages} 页 (共 ${syncLogTotalEntries} 条)`;
|
|
3023
|
+
|
|
2739
3024
|
const prevBtn = document.getElementById('syncLogPrevPage');
|
|
2740
3025
|
const nextBtn = document.getElementById('syncLogNextPage');
|
|
2741
|
-
|
|
3026
|
+
|
|
2742
3027
|
if (prevBtn) prevBtn.disabled = syncLogCurrentPage <= 1;
|
|
2743
3028
|
if (nextBtn) nextBtn.disabled = syncLogCurrentPage >= totalPages;
|
|
2744
3029
|
}
|
|
@@ -2746,7 +3031,7 @@ function updatePagination() {
|
|
|
2746
3031
|
async function handleExportSyncLogs() {
|
|
2747
3032
|
try {
|
|
2748
3033
|
const json = await exportSyncLogs(syncLogCurrentFilters);
|
|
2749
|
-
|
|
3034
|
+
|
|
2750
3035
|
// Create download
|
|
2751
3036
|
const blob = new Blob([json], { type: 'application/json' });
|
|
2752
3037
|
const url = URL.createObjectURL(blob);
|
|
@@ -2757,9 +3042,8 @@ async function handleExportSyncLogs() {
|
|
|
2757
3042
|
a.click();
|
|
2758
3043
|
document.body.removeChild(a);
|
|
2759
3044
|
URL.revokeObjectURL(url);
|
|
2760
|
-
|
|
3045
|
+
|
|
2761
3046
|
debugLog('success', '日志导出成功');
|
|
2762
|
-
|
|
2763
3047
|
} catch (error) {
|
|
2764
3048
|
console.error('Failed to export logs:', error);
|
|
2765
3049
|
debugLog('error', `日志导出失败: ${error.message}`);
|
|
@@ -2770,12 +3054,11 @@ async function handleClearSyncLogs() {
|
|
|
2770
3054
|
if (!confirm('确定要清空所有同步日志吗?此操作不可恢复。')) {
|
|
2771
3055
|
return;
|
|
2772
3056
|
}
|
|
2773
|
-
|
|
3057
|
+
|
|
2774
3058
|
try {
|
|
2775
3059
|
await clearSyncLogs();
|
|
2776
3060
|
debugLog('success', '日志已清空');
|
|
2777
3061
|
await refreshSyncLogs();
|
|
2778
|
-
|
|
2779
3062
|
} catch (error) {
|
|
2780
3063
|
console.error('Failed to clear logs:', error);
|
|
2781
3064
|
debugLog('error', `清空日志失败: ${error.message}`);
|