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.
Files changed (203) hide show
  1. package/README.md +3 -3
  2. package/assets/CanvasAudioPlayer-CSFENxi6.js +2 -0
  3. package/assets/ChatDrawer-CWZQP5R5.css +1 -0
  4. package/assets/ChatDrawer-sVEVW11M.js +11 -0
  5. package/assets/ChatMessagesArea-Cnb94V8-.js +7 -0
  6. package/assets/ChatMessagesArea-PlF5AwzD.css +1 -0
  7. package/assets/DeferredAIInputBar-D99l6sBS.js +8 -0
  8. package/assets/DrawnixDeferredFeatures-78cIu-ev.js +2 -0
  9. package/assets/{KnowledgeBaseContent-DcX5Jofx.js → KnowledgeBaseContent-DZ-1fPZa.js} +10 -10
  10. package/assets/{MVCreator-DllHozYg.js → MVCreator-BV8KDR6E.js} +8 -8
  11. package/assets/ModelBenchmarkWorkbench-DL6r0Z_5.js +2 -0
  12. package/assets/MusicAnalyzer-D9Bpmhym.css +1 -0
  13. package/assets/{MusicAnalyzer-DDeT6g3c.js → MusicAnalyzer-DQtjf36o.js} +2 -2
  14. package/assets/{MusicAnalyzer-CHcQ5t4P.js → MusicAnalyzer-u_5sy56T.js} +1 -1
  15. package/assets/PromptHistoryTool-B-ktaOpK.js +2 -0
  16. package/assets/PromptHistoryTool-BH7hq916.css +1 -0
  17. package/assets/ResizableDivider-BpUFdkte.js +1 -0
  18. package/assets/TaskQueuePanel-BLD1r7_I.js +1 -0
  19. package/assets/{VideoAnalyzer-BSLz1FaI.js → VideoAnalyzer-Bjv9pyDs.js} +2 -2
  20. package/assets/ai-generation-preferences-service-BSpgVCHz.js +1 -0
  21. package/assets/arc-CnA_FoJK.js +1 -0
  22. package/assets/{arc-BU2KKCa_.js → arc-DDvk_-VM.js} +1 -1
  23. package/assets/{batch-image-generation-Bg68r_Ql.js → batch-image-generation-N-lwbjRE.js} +3 -3
  24. package/assets/{blockDiagram-38ab4fdb-BCGEnMME.js → blockDiagram-38ab4fdb-B2G4wjZe.js} +6 -6
  25. package/assets/{blockDiagram-38ab4fdb-CsZR5uC5.js → blockDiagram-38ab4fdb-DBkZWrKk.js} +1 -1
  26. package/assets/{browser-DpICcNby.js → browser-ZXTF4AKv.js} +1 -1
  27. package/assets/{c4Diagram-3d4e48cf-9fuiuFdV.js → c4Diagram-3d4e48cf-BFg-wccg.js} +1 -1
  28. package/assets/{c4Diagram-3d4e48cf-WM4wd_GR.js → c4Diagram-3d4e48cf-CmyNnfip.js} +1 -1
  29. package/assets/{canvas-insertion-CdUQF7IV.js → canvas-insertion-CKIoVZ9a.js} +1 -1
  30. package/assets/channel-BQh3J7kz.js +1 -0
  31. package/assets/channel-CPsF_uSp.js +1 -0
  32. package/assets/{classDiagram-70f12bd4-DcYDedz1.js → classDiagram-70f12bd4-BoP-e23u.js} +2 -2
  33. package/assets/{classDiagram-70f12bd4-CBVHTwQu.js → classDiagram-70f12bd4-UTHJc2XA.js} +1 -1
  34. package/assets/classDiagram-v2-f2320105-BeBc2H-D.js +2 -0
  35. package/assets/{classDiagram-v2-f2320105-jpCwaHEd.js → classDiagram-v2-f2320105-Bjwz5ljW.js} +1 -1
  36. package/assets/{clipper-jvd5rF5a.js → clipper-NoHaeWV4.js} +1 -1
  37. package/assets/clone-CA2KqVCK.js +1 -0
  38. package/assets/createText-2e5e7dd3-Bsu7vT-L.js +8 -0
  39. package/assets/{createText-2e5e7dd3-x5lOIwut.js → createText-2e5e7dd3-DrWUfa-s.js} +1 -1
  40. package/assets/{edges-e0da2a9e-CbrF4nbD.js → edges-e0da2a9e-B9sYOHRt.js} +1 -1
  41. package/assets/{edges-e0da2a9e-Ch6PBwWs.js → edges-e0da2a9e-DX4IQMED.js} +1 -1
  42. package/assets/{erDiagram-9861fffd-DSlNVuAZ.js → erDiagram-9861fffd-B-LfsIMf.js} +1 -1
  43. package/assets/{erDiagram-9861fffd-Bq07elcI.js → erDiagram-9861fffd-DPdud_LA.js} +1 -1
  44. package/assets/{flowDb-956e92f1-Ti5YC4Vv.js → flowDb-956e92f1-C3xKlizM.js} +1 -1
  45. package/assets/{flowDb-956e92f1-BqPCJlZp.js → flowDb-956e92f1-Ck5oiEta.js} +1 -1
  46. package/assets/{flowDiagram-66a62f08-CPDuG3rY.js → flowDiagram-66a62f08-Bkt2NoHM.js} +4 -4
  47. package/assets/{flowDiagram-66a62f08-D3GSABFi.js → flowDiagram-66a62f08-CJnxo6dI.js} +1 -1
  48. package/assets/flowDiagram-v2-96b9c2cf-MnwxRDMO.js +1 -0
  49. package/assets/flowDiagram-v2-96b9c2cf-U7nNPvdl.js +1 -0
  50. package/assets/{flowchart-elk-definition-4a651766-BH09NeLA.js → flowchart-elk-definition-4a651766-2XkrsdKI.js} +2 -2
  51. package/assets/{flowchart-elk-definition-4a651766-BLaLPCDV.js → flowchart-elk-definition-4a651766-ec8y1a8S.js} +1 -1
  52. package/assets/{ganttDiagram-c361ad54-DL9ZeaEc.js → ganttDiagram-c361ad54-1TA0mZY_.js} +3 -3
  53. package/assets/{ganttDiagram-c361ad54-Sp9M1KHP.js → ganttDiagram-c361ad54-D-OrVtXV.js} +3 -3
  54. package/assets/{gitGraphDiagram-72cf32ee-BWwmRKo-.js → gitGraphDiagram-72cf32ee-Blt3RsBD.js} +1 -1
  55. package/assets/{gitGraphDiagram-72cf32ee-BSf1fsDV.js → gitGraphDiagram-72cf32ee-CKf6xgO1.js} +1 -1
  56. package/assets/{graph-g-2U7grQ.js → graph-CUYgHJmb.js} +1 -1
  57. package/assets/{graph--nPw8zXQ.js → graph-WeSRtsZO.js} +1 -1
  58. package/assets/{index-ZrVeQUWo.js → index--MqiaN5t.js} +1 -1
  59. package/assets/{index-C5eu4uSl.js → index-0b_TeH8L.js} +11 -11
  60. package/assets/{index-3862675e-BXwbZ2pz.js → index-3862675e-C2ipEQC8.js} +1 -1
  61. package/assets/{index-3862675e-D6Zy95zZ.js → index-3862675e-CeX6VlsU.js} +1 -1
  62. package/assets/{index-CLJUfzvO.js → index-B-J0JJak.js} +1 -1
  63. package/assets/{index-TkfyNewx.js → index-B7Hj7g41.js} +1 -1
  64. package/assets/{index-eZfwJ9bV.js → index-BS_v5cOh.js} +1 -1
  65. package/assets/index-Btk31p7r.js +1 -0
  66. package/assets/{index-DH12_HTj.js → index-C-Samsu3.js} +2 -2
  67. package/assets/index-C-llNX01.js +1 -0
  68. package/assets/index-C8c9icsH.js +2 -0
  69. package/assets/{index-D7b5fX1d.js → index-CAn2jfNg.js} +1 -1
  70. package/assets/{index-Dz8sghkK.js → index-CK2zt94U.js} +1 -1
  71. package/assets/{index-CfgvFFx3.js → index-CP51k4n6.js} +17 -17
  72. package/assets/{index-DI0OV6F8.js → index-CXBhIy30.js} +1 -1
  73. package/assets/index-C_Mj1wbj.js +2 -0
  74. package/assets/{index-BwSnJN93.js → index-CbfE0MBd.js} +1 -1
  75. package/assets/{index-C3qD42ws.js → index-CjeDxq0c.js} +1 -1
  76. package/assets/{index-DqMndQiP.js → index-Ct7OFnWK.js} +1 -1
  77. package/assets/{index-BaCHOxhI.js → index-CxxUIkx1.js} +1 -1
  78. package/assets/{index-B2dttXd7.js → index-D2Kc-3MO.js} +1 -1
  79. package/assets/{index-Bq8mPedC.js → index-DK0XEByD.js} +1 -1
  80. package/assets/{index-DllL1sx1.js → index-DQcV2DYj.js} +1 -1
  81. package/assets/{index-BP77pG9S.js → index-DSKRBuhn.js} +73 -73
  82. package/assets/{index-BXzGHPXz.js → index-DXqL8NRE.js} +2 -2
  83. package/assets/{index-B7V7-Vxz.js → index-DcLrRIlS.js} +1 -1
  84. package/assets/{index-D4pIkh3S.js → index-DdA-dF0D.js} +1 -1
  85. package/assets/{index-QkP3EC02.js → index-DdxTnVj5.js} +1 -1
  86. package/assets/{index-CmyOpFTN.js → index-DmrnShhc.js} +1 -1
  87. package/assets/{index-Bti3m-iW.js → index-QO0uGVDP.js} +1 -1
  88. package/assets/{index-BPR_DyGD.js → index-aIKXDB1M.js} +1 -1
  89. package/assets/{index-DIiIUyb-.js → index-n6JhNACm.js} +1 -1
  90. package/assets/{index-BWrcsh8u.js → index-qbS_W7PW.js} +1 -1
  91. package/assets/index-v7iKPBEu.css +1 -0
  92. package/assets/{infoDiagram-f8f76790-DpFqCCNv.js → infoDiagram-f8f76790-DEWQ3NXc.js} +1 -1
  93. package/assets/{infoDiagram-f8f76790-G2JpJJP7.js → infoDiagram-f8f76790-u_qjHFCm.js} +3 -3
  94. package/assets/{journeyDiagram-49397b02-DLLTobRM.js → journeyDiagram-49397b02-C8A30CBr.js} +1 -1
  95. package/assets/{journeyDiagram-49397b02-BdESGMrv.js → journeyDiagram-49397b02-hQJfqZih.js} +1 -1
  96. package/assets/{layout-WSR0vNPO.js → layout-CFKTKeqQ.js} +1 -1
  97. package/assets/{layout-CVkuImk4.js → layout-DvRyabpG.js} +1 -1
  98. package/assets/{line-D3bnG227.js → line-C72F34Po.js} +1 -1
  99. package/assets/{line-DQuMJveY.js → line-W5R41tQ1.js} +1 -1
  100. package/assets/{linear-CciP-97N.js → linear-7FXykgHM.js} +1 -1
  101. package/assets/{linear-Z6vFvuwc.js → linear-CVyOPUvz.js} +1 -1
  102. package/assets/main-BO0HWBB1.js +42 -0
  103. package/assets/{main-8DNjLY6L.css → main-DAuE07rf.css} +1 -1
  104. package/assets/{mermaid.core-CwZQb0ud.js → mermaid.core-2VcXhn43.js} +4 -4
  105. package/assets/{mindmap-definition-fc14e90a-BnJ8OOTI.js → mindmap-definition-fc14e90a-CcVXKGg4.js} +1 -1
  106. package/assets/{mindmap-definition-fc14e90a-CxQMydCW.js → mindmap-definition-fc14e90a-DRDYXe84.js} +2 -2
  107. package/assets/model-benchmark-launcher-C2qBiDlM.js +1 -0
  108. package/assets/{photo-wall-splitter-BZ88W5Zy.js → photo-wall-splitter-CFntSOdS.js} +1 -1
  109. package/assets/{pieDiagram-8a3498a8-BSDdkTeu.js → pieDiagram-8a3498a8-Be9dl9pU.js} +1 -1
  110. package/assets/{pieDiagram-8a3498a8-D6VtAoja.js → pieDiagram-8a3498a8-DXZOJYBv.js} +5 -5
  111. package/assets/ppt-generation-B5fKT_by.js +48 -0
  112. package/assets/{quadrantDiagram-120e2f19-C8k2xuBd.js → quadrantDiagram-120e2f19-DoU7aQSp.js} +3 -3
  113. package/assets/{quadrantDiagram-120e2f19-BbFV0VXY.js → quadrantDiagram-120e2f19-l5K9mx26.js} +1 -1
  114. package/assets/{requirementDiagram-deff3bca-C3xMdzJV.js → requirementDiagram-deff3bca-DN_87Wxe.js} +1 -1
  115. package/assets/{requirementDiagram-deff3bca-CpjqKjYZ.js → requirementDiagram-deff3bca-DOooq7kx.js} +1 -1
  116. package/assets/retry-image-BmIVuAVu.js +6 -0
  117. package/assets/{sankeyDiagram-04a897e0-Bjvig9bW.js → sankeyDiagram-04a897e0-BB33nO6_.js} +1 -1
  118. package/assets/{sankeyDiagram-04a897e0-BBJJgsFv.js → sankeyDiagram-04a897e0-CCa0hwml.js} +2 -2
  119. package/assets/{sequenceDiagram-704730f1-PFC_9IFI.js → sequenceDiagram-704730f1-CNhXZiix.js} +1 -1
  120. package/assets/{sequenceDiagram-704730f1-DX1MmBN_.js → sequenceDiagram-704730f1-ZqQ1QrY-.js} +1 -1
  121. package/assets/settings-dialog-BFuWTeQR.js +1 -0
  122. package/assets/{skill-dsl-parser-syNWYxFu.js → skill-dsl-parser-b-aB3P5u.js} +1 -1
  123. package/assets/{stateDiagram-587899a1-Dc3mI_B_.js → stateDiagram-587899a1-BBlOIJr7.js} +1 -1
  124. package/assets/{stateDiagram-587899a1-SFMUVAam.js → stateDiagram-587899a1-BhomZ0AN.js} +1 -1
  125. package/assets/{stateDiagram-v2-d93cdb3a-P4Yt2p_j.js → stateDiagram-v2-d93cdb3a-BpJsgMa0.js} +1 -1
  126. package/assets/stateDiagram-v2-d93cdb3a-CuOn9du2.js +1 -0
  127. package/assets/{styles-6aaf32cf-C3jnr3hV.js → styles-6aaf32cf-C2WEewoA.js} +1 -1
  128. package/assets/{styles-6aaf32cf-b6AdP7fu.js → styles-6aaf32cf-CTtf8moq.js} +1 -1
  129. package/assets/{styles-9a916d00-BUiD9iBe.js → styles-9a916d00-44Xi2-39.js} +1 -1
  130. package/assets/{styles-9a916d00-ii0XTdIm.js → styles-9a916d00-DUU3VgNe.js} +4 -4
  131. package/assets/{styles-c10674c1-DbTSpy3W.js → styles-c10674c1--AhWSGg7.js} +1 -1
  132. package/assets/styles-c10674c1-DS-wE3tK.js +116 -0
  133. package/assets/{svgDrawCommon-08f97a94-D4gIV05W.js → svgDrawCommon-08f97a94-CXcvtSSy.js} +1 -1
  134. package/assets/{svgDrawCommon-08f97a94-BK6lD4D6.js → svgDrawCommon-08f97a94-Dyqh4V2s.js} +1 -1
  135. package/assets/{timeline-definition-85554ec2-Bl2y548G.js → timeline-definition-85554ec2-k788753O.js} +1 -1
  136. package/assets/{timeline-definition-85554ec2-CWKg_JxG.js → timeline-definition-85554ec2-sw6-UuBe.js} +1 -1
  137. package/assets/tool-windows-BEnZdJqF.js +2948 -0
  138. package/assets/{tool-windows-CUNbW1Tz.css → tool-windows-D61Le6TE.css} +1 -1
  139. package/assets/ttd-dialog-BJzXAZ0l.js +99 -0
  140. package/assets/useCharacters-DRBoWvvj.css +1 -0
  141. package/assets/useCharacters-gsV4K8Hn.js +2 -0
  142. package/assets/{useWorkflowSubmission-BizuDmuw.js → useWorkflowSubmission-XRR21P6Y.js} +10 -10
  143. package/assets/{video-analyze-6v3Y-rUE.js → video-analyze-CpbnpSiI.js} +1 -1
  144. package/assets/{video-generation-DnmTlWnm.js → video-generation-BXhS7x-a.js} +2 -2
  145. package/assets/video-recovery-service-6G2lkWcu.js +1 -0
  146. package/assets/workflow-generation-utils-CYxtSHl0.js +3 -0
  147. package/assets/workflow-submission-service-DygSzydV.js +4 -0
  148. package/assets/{xychartDiagram-e933f94c-CJX9_9WF.js → xychartDiagram-e933f94c-CvTTDwPb.js} +3 -3
  149. package/assets/{xychartDiagram-e933f94c-Cep8CQEH.js → xychartDiagram-e933f94c-Ga9zi9ha.js} +1 -1
  150. package/changelog.json +45 -0
  151. package/idle-prefetch-manifest.json +310 -306
  152. package/manifest.json +3 -3
  153. package/package.json +1 -1
  154. package/precache-manifest.json +10 -10
  155. package/sitemap.xml +37 -0
  156. package/sw-debug/app.js +269 -156
  157. package/sw-debug/backup-restore.js +235 -103
  158. package/sw-debug/crypto-helper.js +116 -120
  159. package/sw-debug/duplex-client.js +33 -19
  160. package/sw-debug/gist-management.js +938 -655
  161. package/sw-debug/sw-communication.js +87 -50
  162. package/sw.js +5 -5
  163. package/version.json +7 -17
  164. package/assets/CanvasAudioPlayer-DutgwdiU.js +0 -2
  165. package/assets/ChatDrawer-0jNvKIoI.css +0 -1
  166. package/assets/ChatDrawer-E6Fog16I.js +0 -11
  167. package/assets/ChatMessagesArea-Bgwiz_XP.js +0 -250
  168. package/assets/ChatMessagesArea-DyR_wHFQ.css +0 -1
  169. package/assets/DeferredAIInputBar-CPkD8Cv9.js +0 -8
  170. package/assets/DrawnixDeferredFeatures-Cul76-88.js +0 -2
  171. package/assets/ModelBenchmarkWorkbench-Dv9SNYTi.js +0 -2
  172. package/assets/MusicAnalyzer-DhtRGpAp.css +0 -1
  173. package/assets/ResizableDivider-CaG9JQbJ.js +0 -1
  174. package/assets/TaskQueuePanel-BNhNzGp5.js +0 -1
  175. package/assets/ai-generation-preferences-service-BF7QkuJp.js +0 -1
  176. package/assets/arc-DmGoDCEL.js +0 -1
  177. package/assets/channel-CZjE-WNp.js +0 -1
  178. package/assets/channel-SXZD6bG3.js +0 -1
  179. package/assets/classDiagram-v2-f2320105-CsxWGkSy.js +0 -2
  180. package/assets/clone-C4mvvMDb.js +0 -1
  181. package/assets/createText-2e5e7dd3-CkXS3l7I.js +0 -5
  182. package/assets/flowDiagram-v2-96b9c2cf-BCcJ-cre.js +0 -1
  183. package/assets/flowDiagram-v2-96b9c2cf-DCVaFEgV.js +0 -1
  184. package/assets/index-3eqA7Bag.js +0 -1
  185. package/assets/index-7Q-aiwln.js +0 -4
  186. package/assets/index-B1XP5g7P.css +0 -1
  187. package/assets/index-BHPTfCZM.js +0 -1
  188. package/assets/index-BqHZCcen.js +0 -2
  189. package/assets/index-DcZrElEK.js +0 -2
  190. package/assets/index-gQ7LgvSv.js +0 -12
  191. package/assets/main-BFEkxCOK.js +0 -56
  192. package/assets/model-benchmark-launcher-Ct3lYXVq.js +0 -1
  193. package/assets/ppt-generation-FzjP_rp0.js +0 -48
  194. package/assets/settings-dialog-CQM4v791.js +0 -1
  195. package/assets/stateDiagram-v2-d93cdb3a-DmnBjHpC.js +0 -1
  196. package/assets/styles-c10674c1-Bt89Yy8Q.js +0 -116
  197. package/assets/tool-windows-DOIu9TAD.js +0 -2975
  198. package/assets/ttd-dialog-DI4kQSg8.js +0 -99
  199. package/assets/useCharacters-Chv4Ky7x.js +0 -7
  200. package/assets/useCharacters-OrDbG69o.css +0 -1
  201. package/assets/video-recovery-service-BeRoh0s2.js +0 -1
  202. package/assets/workflow-generation-utils-me381Cax.js +0 -3
  203. 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.querySelector('[data-section="synclogs"]')?.closest('.gist-section');
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.querySelector('[data-section="diagnostics"]')?.closest('.gist-section');
156
- if (diagnosticsSection && !diagnosticsSection.classList.contains('collapsed')) {
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.getElementById('refreshLocalDiagnostics')?.addEventListener('click', () => {
172
- console.log('[Diagnostics] Manual refresh triggered');
173
- loadLocalDiagnostics();
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.getElementById('loadRemoteDiagnostics')?.addEventListener('click', () => {
178
- console.log('[Diagnostics] Loading remote data...');
179
- loadFullDiagnostics();
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 = type === 'error' ? 'var(--error-color)' :
213
- type === 'success' ? 'var(--success-color)' :
214
- 'var(--text-secondary)';
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 ? deviceId.substring(0, 16) + '...' : '-';
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
- 'Authorization': `token ${currentToken}`,
350
- 'Accept': 'application/vnd.github.v3+json'
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
- console.log('[parseGistFile] Content length:', content?.length, 'preview:', content?.substring(0, 100));
371
- const decrypted = await decryptGistFile(content, currentGistId, currentCustomPassword);
372
- console.log('[parseGistFile] Decrypted length:', decrypted?.length, 'preview:', decrypted?.substring(0, 100));
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 = stats.activeShards ||
399
- shardList.filter(s => s.status === 'active').length;
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 = stats.fullShards ||
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 = stats.totalFiles ||
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 = '<div class="empty-state" style="padding: 20px;">无分片数据</div>';
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.map(shard => `
435
+ elements.shardList.innerHTML = shardList
436
+ .map(
437
+ (shard) => `
421
438
  <div class="gist-shard-item">
422
- <span class="gist-shard-id">${escapeHtml(shard.alias || shard.gistId?.substring(0, 8))}</span>
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
- `).join('');
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 = '<div class="empty-state" style="padding: 20px;">无文件</div>';
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.map(fileName => {
448
- let type = 'Text';
449
- if (fileName.endsWith('.png') || fileName.endsWith('.jpg') || fileName.endsWith('.jpeg')) {
450
- type = 'Image';
451
- } else if (fileName.startsWith('board_') && fileName.endsWith('.json')) {
452
- type = 'Board';
453
- } else if (fileName.endsWith('.drawnix')) {
454
- type = 'Canvas';
455
- } else if (fileName === 'tasks.json' || fileName === 'master-index.json' || fileName === 'workspace.json' || fileName === 'prompts.json') {
456
- type = 'Data';
457
- } else if (fileName === 'shard-manifest.json') {
458
- type = 'Manifest';
459
- } else if (fileName.startsWith('media_') && fileName.endsWith('.json')) {
460
- type = 'Media';
461
- }
462
-
463
- return `
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
- }).join('');
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.querySelectorAll('.gist-file-item').forEach(i => i.classList.remove('active'));
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 = '<div class="loading-state">正在加载...</div>';
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(content, currentGistId, currentCustomPassword);
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 (fileName.startsWith('media_') && fileName.endsWith('.json') && parsedData) {
556
+ if (
557
+ fileName.startsWith('media_') &&
558
+ fileName.endsWith('.json') &&
559
+ parsedData
560
+ ) {
515
561
  renderMediaFilePreview(parsedData, decryptFailed);
516
- } else if (fileName.startsWith('board_') && fileName.endsWith('.json') && parsedData) {
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 ? JSON.stringify(parsedData, null, 2) : decryptedContent;
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(displayContent)}</pre>`;
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(e.message)}</div>`;
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(url)}">${escapeHtml(url)}</span>
650
+ <span class="info-value url-value" title="${escapeHtml(
651
+ url
652
+ )}">${escapeHtml(url)}</span>
593
653
  </div>
594
- ${decryptFailed ? '<div class="warning-badge">解密失败,显示原始数据</div>' : ''}
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 (type === 'geometry' || type === 'line' || type === 'arrow' || type.includes('shape')) {
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
- ${decryptFailed ? '<div class="warning-badge">解密失败,显示原始数据</div>' : ''}
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(JSON.stringify(data, null, 2))}</pre>
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
- ${recentTasks.length > 0 ? `
794
+ ${
795
+ recentTasks.length > 0
796
+ ? `
720
797
  <div class="preview-list">
721
798
  <h4>最近任务</h4>
722
- ${recentTasks.map(task => `
799
+ ${recentTasks
800
+ .map(
801
+ (task) => `
723
802
  <div class="list-item">
724
- <span class="item-type ${task.type || 'other'}">${task.type === 'image' ? '图片' : task.type === 'video' ? '视频' : '其他'}</span>
725
- <span class="item-name" title="${escapeHtml(task.name || task.prompt || '')}">${escapeHtml((task.name || task.prompt || '').substring(0, 50))}</span>
726
- <span class="item-time">${task.completedAt ? formatTime(task.completedAt) : ''}</span>
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
- `).join('')}
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(JSON.stringify(data, null, 2))}</pre>
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
- ${boardMetadata.length > 0 ? `
866
+ ${
867
+ boardMetadata.length > 0
868
+ ? `
770
869
  <div class="preview-list">
771
870
  <h4>画板列表</h4>
772
- ${boardMetadata.slice(0, 10).map(board => `
871
+ ${boardMetadata
872
+ .slice(0, 10)
873
+ .map(
874
+ (board) => `
773
875
  <div class="list-item">
774
- <span class="item-name">${escapeHtml(board.name || board.id || '未命名')}</span>
775
- <span class="item-time">${board.updatedAt ? formatTime(board.updatedAt) : ''}</span>
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
- `).join('')}
778
- ${boardMetadata.length > 10 ? `<div class="list-more">还有 ${boardMetadata.length - 10} 个...</div>` : ''}
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(JSON.stringify(data, null, 2))}</pre>
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(JSON.stringify(data, null, 2))}</pre>
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('remoteOnlyBoardCount');
1053
+ const remoteOnlyBoardCountEl = document.getElementById(
1054
+ 'remoteOnlyBoardCount'
1055
+ );
955
1056
  const localOnlyMediaCountEl = document.getElementById('localOnlyMediaCount');
956
- const remoteOnlyMediaCountEl = document.getElementById('remoteOnlyMediaCount');
1057
+ const remoteOnlyMediaCountEl = document.getElementById(
1058
+ 'remoteOnlyMediaCount'
1059
+ );
957
1060
  const taskComparisonTableEl = document.getElementById('taskComparisonTable');
958
- const boardComparisonTableEl = document.getElementById('boardComparisonTable');
959
- const mediaComparisonTableEl = document.getElementById('mediaComparisonTable');
960
-
961
- console.log('[Diagnostics] Elements found:', {
962
- localTaskCount: !!localTaskCountEl,
963
- localBoardCount: !!localBoardCountEl,
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(taskComparisonTableEl, localTasks.slice(0, 20), 'task');
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(dbDiagnosis.taskQueue.stores, null, 2)}` : '';
1007
- taskComparisonTableEl.innerHTML = `<div class="empty-state" style="padding: 20px;">本地无任务数据${dbInfo ? '<pre style="text-align:left;font-size:10px;margin-top:10px;white-space:pre-wrap;">' + escapeHtml(dbInfo) + '</pre>' : ''}</div>`;
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(boardComparisonTableEl, localBoards.slice(0, 20), 'board');
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(dbDiagnosis.workspace.stores, null, 2)}` : '';
1017
- boardComparisonTableEl.innerHTML = `<div class="empty-state" style="padding: 20px;">本地无画板数据${dbInfo ? '<pre style="text-align:left;font-size:10px;margin-top:10px;white-space:pre-wrap;">' + escapeHtml(dbInfo) + '</pre>' : ''}</div>`;
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(mediaComparisonTableEl, localMedia.slice(0, 30));
1147
+ renderLocalOnlyMediaTable(
1148
+ mediaComparisonTableEl,
1149
+ localMedia.slice(0, 30)
1150
+ );
1023
1151
  } else {
1024
1152
  if (mediaComparisonTableEl) {
1025
- mediaComparisonTableEl.innerHTML = '<div class="empty-state" style="padding: 20px;">本地无缓存媒体</div>';
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(error.message)}</div>`;
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 = '<div class="empty-state" style="padding: 20px;">无数据</div>';
1186
+ container.innerHTML =
1187
+ '<div class="empty-state" style="padding: 20px;">无数据</div>';
1059
1188
  return;
1060
1189
  }
1061
-
1062
- container.innerHTML = items.map(item => {
1063
- const id = item.id || '';
1064
- const details = getItemDetails(item, type);
1065
- return `
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(truncateId(id))}</span>
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
- }).join('');
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 = '<div class="empty-state" style="padding: 20px;">无缓存媒体</div>';
1223
+ container.innerHTML =
1224
+ '<div class="empty-state" style="padding: 20px;">无缓存媒体</div>';
1091
1225
  return;
1092
1226
  }
1093
-
1094
- container.innerHTML = items.map(item => {
1095
- const filename = item.filename || extractFilename(item.url) || item.url;
1096
- return `
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(filename)}</span>
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
- }).join('');
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
- console.log('[Diagnostics] Found tasks.json, parsing...');
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) localOnlyTaskCountEl.textContent = localOnly.length;
1172
- if (remoteOnlyTaskCountEl) remoteOnlyTaskCountEl.textContent = remoteOnly.length;
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(taskComparisonTableEl, {
1177
- localOnly: localOnly.map(id => ({ id, data: localTaskMap.get(id) })),
1178
- remoteOnly: remoteOnly.map(id => ({ id, data: remoteTaskMap.get(id) })),
1179
- synced: synced.map(id => ({
1180
- id,
1181
- local: localTaskMap.get(id),
1182
- remote: remoteTaskMap.get(id),
1183
- })),
1184
- }, 'task');
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('remoteOnlyBoardCount');
1202
- const boardComparisonTableEl = document.getElementById('boardComparisonTable');
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) remoteBoardCountEl.textContent = remoteBoards.length;
1242
- if (localOnlyBoardCountEl) localOnlyBoardCountEl.textContent = localOnly.length;
1243
- if (remoteOnlyBoardCountEl) remoteOnlyBoardCountEl.textContent = remoteOnly.length;
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(boardComparisonTableEl, {
1247
- localOnly: localOnly.map(id => ({ id, data: localBoardMap.get(id) })),
1248
- remoteOnly: remoteOnly.map(id => ({ id, data: remoteBoardMap.get(id) })),
1249
- synced: synced.map(id => ({
1250
- id,
1251
- local: localBoardMap.get(id),
1252
- remote: remoteBoardMap.get(id),
1253
- })),
1254
- }, 'board');
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('remoteOnlyMediaCount');
1272
- const mediaComparisonTableEl = document.getElementById('mediaComparisonTable');
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 ? Object.keys(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) localOnlyMediaCountEl.textContent = localOnly.length;
1295
- if (remoteOnlyMediaCountEl) remoteOnlyMediaCountEl.textContent = remoteOnly.length;
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 = '<div class="empty-state" style="padding: 20px;">无数据</div>';
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 = { 'local-only': 0, 'remote-only': 1, 'conflict': 2, 'synced': 3 };
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.map(row => `
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(truncateId(row.id))}</span>
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
- `).join('');
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 = '<div class="empty-state" style="padding: 20px;">无媒体数据</div>';
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 = { 'synced': 0, 'local-only': 1, 'remote-only': 2 };
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.map(row => {
1424
- const isVideo = isVideoUrl(row.url);
1425
- const previewHtml = getMediaPreviewHtml(row.url, isVideo);
1426
- const filename = extractFilename(row.url);
1427
-
1428
- return `
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(filename)}</span>
1435
- <span class="item-details">${row.shard ? `分片: ${row.shard}` : ''}</span>
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
- }).join('');
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 lowerUrl.includes('video') ||
1510
- lowerUrl.endsWith('.mp4') ||
1511
- lowerUrl.endsWith('.webm') ||
1512
- lowerUrl.endsWith('.mov') ||
1513
- lowerUrl.includes('/v/');
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.getElementById('debugTestConnection')?.addEventListener('click', debugTestConnection);
1596
- document.getElementById('debugListGists')?.addEventListener('click', debugListGists);
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.getElementById('debugPreviewTaskMerge')?.addEventListener('click', debugPreviewTaskMerge);
1600
- document.getElementById('debugImportRemoteTasks')?.addEventListener('click', debugImportRemoteTasks);
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.getElementById('debugPreviewBoardMerge')?.addEventListener('click', debugPreviewBoardMerge);
1604
- document.getElementById('debugDownloadBoard')?.addEventListener('click', debugDownloadBoard);
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.getElementById('debugListShardFiles')?.addEventListener('click', debugListShardFiles);
1608
- document.getElementById('debugTestMediaDownload')?.addEventListener('click', debugTestMediaDownload);
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.getElementById('debugCheckSyncLogDb')?.addEventListener('click', debugCheckSyncLogDb);
1612
- document.getElementById('debugWriteTestLog')?.addEventListener('click', debugWriteTestLog);
1613
- document.getElementById('debugPerformanceTest')?.addEventListener('click', debugPerformanceTest);
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.getElementById('debugDiagnoseDb')?.addEventListener('click', debugDiagnoseAllDatabases);
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
- 'info': 'info',
1629
- 'success': 'success',
1630
- 'warning': 'warning',
1631
- 'error': 'error',
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
- 'Authorization': `token ${token}`,
1705
- 'Accept': 'application/vnd.github.v3+json'
1706
- }
1893
+ Authorization: `token ${token}`,
1894
+ Accept: 'application/vnd.github.v3+json',
1895
+ },
1707
1896
  });
1708
-
1897
+
1709
1898
  if (!response.ok) {
1710
- debugLog('error', `API 请求失败: ${response.status}`, await response.text());
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(parseInt(response.headers.get('x-ratelimit-reset')) * 1000).toLocaleTimeString(),
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
- 'Authorization': `token ${currentToken}`,
1745
- 'Accept': 'application/vnd.github.v3+json'
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(g =>
1758
- g.description?.includes('Opentu') ||
1759
- Object.keys(g.files).some(f =>
1760
- (f.startsWith('board_') && f.endsWith('.json')) ||
1761
- f.endsWith('.drawnix') ||
1762
- f === 'tasks.json' ||
1763
- f === 'master-index.json'
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('success', `找到 ${syncGists.length} 个同步相关的 Gists`,
1768
- syncGists.map(g => ({
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(currentGistData.files['tasks.json']);
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({ id, type: local.type, status: local.status });
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({ id, type: remote.type, status: remote.status });
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('warning', `有 ${mergeResult.remoteOnly.length} 个远程任务在本地不存在,可能需要导入`);
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(currentGistData.files['tasks.json']);
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('success', `成功导入 ${tasksToImport.length} 个任务`,
1916
- tasksToImport.map(t => ({ id: t.id, type: t.type, status: t.status }))
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(currentGistData.files)) {
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.filter(b => !remoteIds.has(b.id)).map(b => ({
1956
- id: b.id,
1957
- name: b.name || b.title,
1958
- elements: b.elements?.length || 0,
1959
- })),
1960
- remoteOnly: remoteBoards.filter(b => !localIds.has(b.id)).map(b => ({
1961
- id: b.id,
1962
- fileName: b.fileName,
1963
- })),
1964
- both: [...localIds].filter(id => remoteIds.has(id)),
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 = remoteMasterIndex || localMasterIndex || await getLocalMasterIndex();
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('info', `分片 ${shard.alias}: ${shard.fileCount} 文件, ${formatSize(shard.totalSize)}`, {
2057
- gistId: shard.gistId,
2058
- status: shard.status,
2059
- createdAt: formatTime(shard.createdAt),
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('info', '示例文件', sampleFiles.map(([url, info]) => ({
2072
- url: extractFilename(url),
2073
- shard: info.shardId,
2074
- size: formatSize(info.size),
2075
- type: info.type,
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 = remoteMasterIndex || localMasterIndex || await getLocalMasterIndex();
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('success', `找到 ${logs.length} 条最新日志`,
2180
- logs.map(l => ({
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, { keyPath: 'id' });
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'], { unique: false });
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('success', `内存写入: ${memoryTimeMs.toFixed(2)}ms (${memoryAvgUs.toFixed(2)}μs/条)`, {
2286
- iterations,
2287
- totalMs: memoryTimeMs.toFixed(2),
2288
- avgMicroseconds: memoryAvgUs.toFixed(2),
2289
- logsPerSecond: Math.round(iterations / (memoryTimeMs / 1000)),
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('success', `IndexedDB 写入: ${dbTimeMs.toFixed(2)}ms (${dbAvgUs.toFixed(2)}μs/条)`, {
2313
- iterations,
2314
- totalMs: dbTimeMs.toFixed(2),
2315
- avgMicroseconds: dbAvgUs.toFixed(2),
2316
- logsPerSecond: Math.round(iterations / (dbTimeMs / 1000)),
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('info', `系统中共有 ${allDbs.length} 个数据库`,
2358
- allDbs.map(db => `${db.name} (v${db.version})`).join(', ')
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(([name, info]) => ({
2367
- store: name,
2368
- count: info.count,
2369
- keyPath: info.keyPath,
2370
- sampleKeys: info.sampleKeys?.join(', ') || '-',
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('info', `本地任务: ${localTasks.length} 条`,
2386
- localTasks.slice(0, 3).map(t => ({ id: t.id?.substring(0, 12), type: t.type, status: t.status }))
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('info', `本地画板: ${localBoards.length} 个`,
2391
- localBoards.slice(0, 3).map(b => ({ id: b.id?.substring(0, 12), name: b.name }))
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('info', `本地缓存媒体: ${localMedia.length} 个`,
2396
- localMedia.slice(0, 3).map(m => m.filename)
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.getElementById('refreshSyncLogs')?.addEventListener('click', refreshSyncLogs);
2450
-
2701
+ document
2702
+ .getElementById('refreshSyncLogs')
2703
+ ?.addEventListener('click', refreshSyncLogs);
2704
+
2451
2705
  // Auto-refresh button
2452
- document.getElementById('toggleAutoRefresh')?.addEventListener('click', toggleAutoRefresh);
2453
-
2706
+ document
2707
+ .getElementById('toggleAutoRefresh')
2708
+ ?.addEventListener('click', toggleAutoRefresh);
2709
+
2454
2710
  // Copy button
2455
- document.getElementById('copySyncLogs')?.addEventListener('click', handleCopySyncLogs);
2456
-
2711
+ document
2712
+ .getElementById('copySyncLogs')
2713
+ ?.addEventListener('click', handleCopySyncLogs);
2714
+
2457
2715
  // Export button
2458
- document.getElementById('exportSyncLogs')?.addEventListener('click', handleExportSyncLogs);
2459
-
2716
+ document
2717
+ .getElementById('exportSyncLogs')
2718
+ ?.addEventListener('click', handleExportSyncLogs);
2719
+
2460
2720
  // Clear button
2461
- document.getElementById('clearSyncLogs')?.addEventListener('click', handleClearSyncLogs);
2462
-
2721
+ document
2722
+ .getElementById('clearSyncLogs')
2723
+ ?.addEventListener('click', handleClearSyncLogs);
2724
+
2463
2725
  // Filters
2464
- document.getElementById('syncLogLevelFilter')?.addEventListener('change', handleSyncLogFilterChange);
2465
- document.getElementById('syncLogCategoryFilter')?.addEventListener('change', handleSyncLogFilterChange);
2466
- document.getElementById('syncLogSessionFilter')?.addEventListener('change', handleSyncLogFilterChange);
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.map(log => {
2546
- const time = new Date(log.timestamp).toLocaleString('zh-CN');
2547
- const level = log.level?.toUpperCase() || 'INFO';
2548
- const category = log.category || '';
2549
- const message = log.message || '';
2550
- const data = log.data ? JSON.stringify(log.data) : '';
2551
- const error = log.error ? `\n Error: ${log.error.message || log.error}` : '';
2552
-
2553
- return `[${time}] [${level}]${category ? ` [${category}]` : ''} ${message}${data ? `\n Data: ${data}` : ''}${error}`;
2554
- }).join('\n\n');
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 = stats.byLevel?.error || 0;
2646
- document.getElementById('syncLogWarnings').textContent = stats.byLevel?.warning || 0;
2647
- document.getElementById('syncLogSuccesses').textContent = stats.byLevel?.success || 0;
2648
- document.getElementById('syncLogSessions').textContent = stats.sessionCount || 0;
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', { month: 'short', day: 'numeric' });
2664
- const timeStr = date.toLocaleTimeString('zh-CN', { hour: '2-digit', minute: '2-digit' });
2665
- option.textContent = `${dateStr} ${timeStr} (${session.logCount}条${session.hasErrors ? ', 有错误' : ''})`;
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 = '<div class="empty-state" style="padding: 40px;">没有找到匹配的日志</div>';
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(log, isExpanded, (id, expanded) => {
2714
- if (expanded) {
2715
- syncLogExpandedIds.add(id);
2716
- } else {
2717
- syncLogExpandedIds.delete(id);
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('syncLogPageInfo').textContent =
2737
- `第 ${syncLogCurrentPage} / ${totalPages} 页 (共 ${syncLogTotalEntries} 条)`;
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}`);