aitu-app 0.5.14

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 (237) hide show
  1. package/.DS_Store +0 -0
  2. package/README.md +47 -0
  3. package/_headers +84 -0
  4. package/_redirects +2 -0
  5. package/assets/ChatMessagesArea-CkUX81uB.js +251 -0
  6. package/assets/ChatMessagesArea-Di0Z80Zh.css +1 -0
  7. package/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
  8. package/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
  9. package/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
  10. package/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
  11. package/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
  12. package/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
  13. package/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
  14. package/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
  15. package/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
  16. package/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
  17. package/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
  18. package/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
  19. package/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
  20. package/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
  21. package/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
  22. package/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
  23. package/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
  24. package/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
  25. package/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
  26. package/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
  27. package/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
  28. package/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
  29. package/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
  30. package/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
  31. package/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
  32. package/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
  33. package/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
  34. package/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
  35. package/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
  36. package/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
  37. package/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
  38. package/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
  39. package/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
  40. package/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
  41. package/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
  42. package/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
  43. package/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
  44. package/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
  45. package/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
  46. package/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
  47. package/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
  48. package/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
  49. package/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
  50. package/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
  51. package/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
  52. package/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
  53. package/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
  54. package/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
  55. package/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
  56. package/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
  57. package/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
  58. package/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
  59. package/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
  60. package/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
  61. package/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
  62. package/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
  63. package/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
  64. package/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
  65. package/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
  66. package/assets/Tableau10-B-NsZVaP.js +1 -0
  67. package/assets/Tableau10-Dnlau_Wv.js +1 -0
  68. package/assets/ToolboxDrawer-By1XMh8B.js +87 -0
  69. package/assets/ToolboxDrawer-fPqvDLQE.css +1 -0
  70. package/assets/__vite-browser-external-BIHI7g3E.js +1 -0
  71. package/assets/ai-analyze-Db-iXol6.js +1 -0
  72. package/assets/arc-BZXVqUcI.js +1 -0
  73. package/assets/arc-ajYHRRnk.js +1 -0
  74. package/assets/array-B5oSNiGi.js +1 -0
  75. package/assets/array-BKyUJesY.js +1 -0
  76. package/assets/batch-image-generation-Baqb01Lm.js +6 -0
  77. package/assets/batch-image-generation-CbLMWmjk.css +1 -0
  78. package/assets/blockDiagram-38ab4fdb-BT3H_WVv.js +118 -0
  79. package/assets/blockDiagram-38ab4fdb-u0xYP3Lt.js +118 -0
  80. package/assets/c4Diagram-3d4e48cf-CBvM6zjM.js +10 -0
  81. package/assets/c4Diagram-3d4e48cf-WOIEVidH.js +10 -0
  82. package/assets/channel-BP25wTsw.js +1 -0
  83. package/assets/channel-HzrLNFUg.js +1 -0
  84. package/assets/classDiagram-70f12bd4-BMutcvFi.js +2 -0
  85. package/assets/classDiagram-70f12bd4-Cl9U1r5F.js +2 -0
  86. package/assets/classDiagram-v2-f2320105-C0agtbR4.js +2 -0
  87. package/assets/classDiagram-v2-f2320105-tCBzATK6.js +2 -0
  88. package/assets/clone-B69pF7Y_.js +1 -0
  89. package/assets/clone-oX7o-l4R.js +1 -0
  90. package/assets/createText-2e5e7dd3-CZ9_fscE.js +5 -0
  91. package/assets/createText-2e5e7dd3-idrqgJjU.js +7 -0
  92. package/assets/edges-e0da2a9e-C-RyePMV.js +4 -0
  93. package/assets/edges-e0da2a9e-DJXAjJSL.js +4 -0
  94. package/assets/erDiagram-9861fffd-DWJR_3zL.js +51 -0
  95. package/assets/erDiagram-9861fffd-x2Kcy95-.js +51 -0
  96. package/assets/flowDb-956e92f1-BgKjOIdz.js +10 -0
  97. package/assets/flowDb-956e92f1-CF6y18Tn.js +10 -0
  98. package/assets/flowDiagram-66a62f08-BPPw0wPU.js +4 -0
  99. package/assets/flowDiagram-66a62f08-CSAllSFf.js +4 -0
  100. package/assets/flowDiagram-v2-96b9c2cf-B-UGyXRi.js +1 -0
  101. package/assets/flowDiagram-v2-96b9c2cf-Cm596kxZ.js +1 -0
  102. package/assets/flowchart-elk-definition-4a651766-9XSRJbsr.js +139 -0
  103. package/assets/flowchart-elk-definition-4a651766-DWFN9DN3.js +139 -0
  104. package/assets/ganttDiagram-c361ad54-D9tbz9tQ.js +257 -0
  105. package/assets/ganttDiagram-c361ad54-ot5pUYpT.js +257 -0
  106. package/assets/gitGraphDiagram-72cf32ee-BFV3Mt8C.js +70 -0
  107. package/assets/gitGraphDiagram-72cf32ee-C6qFzgGh.js +70 -0
  108. package/assets/graph-BxwlF7JS.js +1 -0
  109. package/assets/graph-D-2Ldvxg.js +1 -0
  110. package/assets/grid-image-cM9AmYC8.js +1 -0
  111. package/assets/has-CgdIPiQG.js +1 -0
  112. package/assets/hasIn-4iY02rGN.js +1 -0
  113. package/assets/index-3862675e-CVZnpwDN.js +1 -0
  114. package/assets/index-3862675e-DqdI9cab.js +1 -0
  115. package/assets/index-B2dvADz8.css +1 -0
  116. package/assets/index-BicRPzXC.js +1 -0
  117. package/assets/index-Bs7-jmv6.css +1 -0
  118. package/assets/index-BwSGXyRr.js +99 -0
  119. package/assets/index-C1XdOOAn.css +1 -0
  120. package/assets/index-C4AKKbpQ.css +1 -0
  121. package/assets/index-CkpXFt8n.js +1 -0
  122. package/assets/index-CrxF9gFe.js +42 -0
  123. package/assets/index-DBWqXBIQ.js +93 -0
  124. package/assets/index-DI_5V2-m.js +3 -0
  125. package/assets/index-DWUAFoZG.js +2064 -0
  126. package/assets/index-Dn0YtZ2R.js +3 -0
  127. package/assets/index-e05Rs4M6.js +12 -0
  128. package/assets/index.dom-C3-224fz.js +1 -0
  129. package/assets/infoDiagram-f8f76790-CnrpwoOt.js +7 -0
  130. package/assets/infoDiagram-f8f76790-FKC1Sy9Y.js +7 -0
  131. package/assets/init-A0kIFD9x.js +1 -0
  132. package/assets/init-Gi6I4Gst.js +1 -0
  133. package/assets/inspiration-board-B_-BBBHt.js +1 -0
  134. package/assets/isEmpty-Dj2GV0v-.js +1 -0
  135. package/assets/journeyDiagram-49397b02-B7fP21sU.js +139 -0
  136. package/assets/journeyDiagram-49397b02-Dp3X9XWq.js +139 -0
  137. package/assets/katex-BbEIqZs1.js +261 -0
  138. package/assets/katex-Cu_Erd72.js +261 -0
  139. package/assets/layout-BD3yCK_X.js +1 -0
  140. package/assets/layout-DHHYqX7p.js +1 -0
  141. package/assets/line-B3bNrkzn.js +1 -0
  142. package/assets/line-B86HLuqu.js +1 -0
  143. package/assets/linear-DU2Ciymb.js +1 -0
  144. package/assets/linear-wCAlMhOS.js +1 -0
  145. package/assets/mermaid.core-DfVvnpgz.js +91 -0
  146. package/assets/mindmap-definition-fc14e90a-D1sxE3xG.js +425 -0
  147. package/assets/mindmap-definition-fc14e90a-YuSOJC7P.js +425 -0
  148. package/assets/ordinal-BRr1uYdk.js +1 -0
  149. package/assets/ordinal-Cboi1Yqb.js +1 -0
  150. package/assets/path-CY0bYimO.js +1 -0
  151. package/assets/path-CbwjOpE9.js +1 -0
  152. package/assets/photo-wall-splitter-BVU2e0aS.js +1 -0
  153. package/assets/pick-Cvlwra4g.js +1 -0
  154. package/assets/pieDiagram-8a3498a8-B6mJUqro.js +35 -0
  155. package/assets/pieDiagram-8a3498a8-B91bWgo_.js +35 -0
  156. package/assets/quadrantDiagram-120e2f19-BxS8fQEz.js +7 -0
  157. package/assets/quadrantDiagram-120e2f19-DwudONqx.js +7 -0
  158. package/assets/requirementDiagram-deff3bca-DygaMIoy.js +52 -0
  159. package/assets/requirementDiagram-deff3bca-v9xlgfS8.js +52 -0
  160. package/assets/sankeyDiagram-04a897e0-BV23dp4l.js +8 -0
  161. package/assets/sankeyDiagram-04a897e0-BXCiXiyw.js +8 -0
  162. package/assets/sequenceDiagram-704730f1-CObRpNi4.js +122 -0
  163. package/assets/sequenceDiagram-704730f1-Ck69A6wI.js +122 -0
  164. package/assets/settings-dialog-BlCO49C4.js +1 -0
  165. package/assets/settings-dialog-QUxXj54T.css +1 -0
  166. package/assets/stateDiagram-587899a1-J_G6I0oo.js +1 -0
  167. package/assets/stateDiagram-587899a1-z-tKclr3.js +1 -0
  168. package/assets/stateDiagram-v2-d93cdb3a-DsThtOzP.js +1 -0
  169. package/assets/stateDiagram-v2-d93cdb3a-XIvq5t8a.js +1 -0
  170. package/assets/styles-6aaf32cf-1fjuNMUk.js +207 -0
  171. package/assets/styles-6aaf32cf-DT2rVNfQ.js +207 -0
  172. package/assets/styles-9a916d00-fLeUSina.js +160 -0
  173. package/assets/styles-9a916d00-q64Umkis.js +160 -0
  174. package/assets/styles-c10674c1-BWlxVc3Q.js +116 -0
  175. package/assets/styles-c10674c1-CtYpjMYU.js +116 -0
  176. package/assets/svgDrawCommon-08f97a94-C_DhKfny.js +1 -0
  177. package/assets/svgDrawCommon-08f97a94-DSBqmUv2.js +1 -0
  178. package/assets/timeline-definition-85554ec2-AKpzwLPN.js +61 -0
  179. package/assets/timeline-definition-85554ec2-dTkYwoLF.js +61 -0
  180. package/assets/ttd-dialog-CxiaIUuJ.js +47 -0
  181. package/assets/ttd-dialog-DCapefb6.css +1 -0
  182. package/assets/upload-4sxUU7q_.js +1 -0
  183. package/assets/video-recovery-service-BckHbSyK.js +1 -0
  184. package/assets/web-vitals-DcvjKPr-.js +1 -0
  185. package/assets/winbox.bundle.min-CoRPjCs5.js +1 -0
  186. package/assets/xlsx-CkFp8p6R.js +105 -0
  187. package/assets/xychartDiagram-e933f94c-DCmvL0ag.js +7 -0
  188. package/assets/xychartDiagram-e933f94c-aqOiXp_u.js +7 -0
  189. package/batch-image.html +1616 -0
  190. package/favicon.ico +0 -0
  191. package/icons/README.md +55 -0
  192. package/icons/aitu10.png +0 -0
  193. package/icons/android-chrome-192x192.png +0 -0
  194. package/icons/android-chrome-512x512.png +0 -0
  195. package/icons/apple-touch-icon.png +0 -0
  196. package/icons/favicon-16x16.png +0 -0
  197. package/icons/favicon-16x16.svg +539 -0
  198. package/icons/favicon-32x32.png +0 -0
  199. package/icons/favicon-32x32.svg +539 -0
  200. package/icons/favicon-new.svg +539 -0
  201. package/icons/favicon-new.svg.png +0 -0
  202. package/icons/icon-192x192.svg +539 -0
  203. package/icons/icon-512x512.svg +539 -0
  204. package/icons/icon-96x96.png +0 -0
  205. package/icons/icon-96x96.svg +539 -0
  206. package/iframe-test.html +340 -0
  207. package/index.html +105 -0
  208. package/init.json +6 -0
  209. package/logo/cardid.jpg +0 -0
  210. package/logo/group-qr.png +0 -0
  211. package/logo/logo_drawnix_h.svg +539 -0
  212. package/logo/logo_drawnix_h_dark.svg +539 -0
  213. package/logo/logo_drawnix_new.svg +539 -0
  214. package/manifest.json +52 -0
  215. package/package.json +31 -0
  216. package/product_showcase/aitu-01.png +0 -0
  217. package/product_showcase/aitu-02.png +0 -0
  218. package/product_showcase/aitu-03.png +0 -0
  219. package/product_showcase/aitu-04.png +0 -0
  220. package/product_showcase/aitu-05.png +0 -0
  221. package/product_showcase/aitu-06.png +0 -0
  222. package/product_showcase/case-1.png +0 -0
  223. package/product_showcase/case-2.png +0 -0
  224. package/robots.txt +13 -0
  225. package/sitemap.xml +29 -0
  226. package/sw-debug/app.js +3069 -0
  227. package/sw-debug/console-entry.js +80 -0
  228. package/sw-debug/log-entry.js +452 -0
  229. package/sw-debug/log-panel.js +309 -0
  230. package/sw-debug/postmessage-entry.js +117 -0
  231. package/sw-debug/status-panel.js +125 -0
  232. package/sw-debug/styles.css +2103 -0
  233. package/sw-debug/sw-communication.js +208 -0
  234. package/sw-debug/utils.js +112 -0
  235. package/sw-debug.html +685 -0
  236. package/sw.js +58 -0
  237. package/version.json +10 -0
@@ -0,0 +1,80 @@
1
+ /**
2
+ * SW Debug Panel - Console Entry Component
3
+ */
4
+
5
+ import { formatTime, escapeHtml } from './utils.js';
6
+
7
+ /**
8
+ * Format stack trace for better readability
9
+ * @param {string} stack
10
+ * @returns {string}
11
+ */
12
+ function formatStack(stack) {
13
+ if (!stack) return '';
14
+
15
+ // Split by newlines and format each line
16
+ return stack.split('\n').map(line => {
17
+ // Highlight file paths and line numbers
18
+ return escapeHtml(line.trim());
19
+ }).filter(Boolean).join('\n');
20
+ }
21
+
22
+ /**
23
+ * Create a console log entry DOM element
24
+ * @param {object} log
25
+ * @param {boolean} isExpanded - Initial expanded state for stack
26
+ * @param {Function} onToggle - Callback when expand state changes (id, expanded)
27
+ * @returns {HTMLElement}
28
+ */
29
+ export function createConsoleEntry(log, isExpanded = false, onToggle = null) {
30
+ const entry = document.createElement('div');
31
+ entry.className = `console-entry ${log.logLevel || 'log'}`;
32
+ entry.dataset.id = log.id;
33
+
34
+ const hasStack = log.logStack && log.logStack.trim();
35
+ const stackToggle = hasStack
36
+ ? `<span class="stack-toggle" title="展开/收起堆栈"><span class="arrow">▶</span> 堆栈</span>`
37
+ : '';
38
+
39
+ entry.innerHTML = `
40
+ <div class="console-header">
41
+ <span class="log-time">${formatTime(log.timestamp)}</span>
42
+ <span class="console-level ${log.logLevel || 'log'}">${(log.logLevel || 'log').toUpperCase()}</span>
43
+ <span class="console-message">${escapeHtml(log.logMessage || '')}</span>
44
+ </div>
45
+ ${log.logSource ? `<div class="console-source">${escapeHtml(log.logSource)}</div>` : ''}
46
+ ${log.url ? `<div class="console-source">页面: ${escapeHtml(log.url)}</div>` : ''}
47
+ ${hasStack ? `
48
+ <div class="console-stack-container${isExpanded ? ' expanded' : ''}">
49
+ ${stackToggle}
50
+ <pre class="console-stack">${formatStack(log.logStack)}</pre>
51
+ </div>
52
+ ` : ''}
53
+ `;
54
+
55
+ // Add toggle functionality for stack
56
+ if (hasStack) {
57
+ const stackContainer = entry.querySelector('.console-stack-container');
58
+ const toggle = entry.querySelector('.stack-toggle');
59
+
60
+ if (toggle && stackContainer) {
61
+ toggle.addEventListener('click', (e) => {
62
+ e.stopPropagation();
63
+ const isNowExpanded = stackContainer.classList.toggle('expanded');
64
+ if (onToggle) {
65
+ onToggle(log.id, isNowExpanded);
66
+ }
67
+ });
68
+ }
69
+ }
70
+
71
+ return entry;
72
+ }
73
+
74
+ /**
75
+ * Get the inject code for capturing console logs
76
+ * @returns {string}
77
+ */
78
+ export function getInjectCode() {
79
+ return `(function(){const o=console.error,w=console.warn,i=console.info,l=console.log;function s(t,m,k){if(navigator.serviceWorker?.controller){const e=m instanceof Error?m.message:String(m);const st=m instanceof Error?m.stack:'';navigator.serviceWorker.controller.postMessage({type:'SW_CONSOLE_LOG_REPORT',logLevel:t,logMessage:e,logStack:st,logSource:k||'',url:location.href});}}console.error=function(...a){o.apply(console,a);s('error',a[0]);};console.warn=function(...a){w.apply(console,a);s('warn',a[0]);};window.addEventListener('error',e=>s('error',e.message,e.filename+':'+e.lineno));window.addEventListener('unhandledrejection',e=>s('error','Unhandled Promise: '+e.reason));console.log('[SW Debug] 日志捕获已启用');})()`;
80
+ }
@@ -0,0 +1,452 @@
1
+ /**
2
+ * SW Debug Panel - Log Entry Component
3
+ */
4
+
5
+ import { formatTime, formatDuration, formatSize, getStatusClass, extractDisplayUrl, formatJsonOrText } from './utils.js';
6
+
7
+ /**
8
+ * Render FormData fields as HTML
9
+ * @param {Array} formData
10
+ * @returns {string}
11
+ */
12
+ function renderFormData(formData) {
13
+ if (!formData || formData.length === 0) return '';
14
+
15
+ const rows = formData.map(field => {
16
+ let valueHtml;
17
+
18
+ if (field.isFile) {
19
+ if (field.dataUrl) {
20
+ // Render image preview
21
+ valueHtml = `
22
+ <div class="form-data-image">
23
+ <img src="${field.dataUrl}" alt="${field.fileName || 'image'}" style="max-width: 200px; max-height: 150px; border-radius: 4px; border: 1px solid var(--border-color);">
24
+ <div class="form-data-file-info">${field.fileName || ''} (${field.mimeType || 'binary'})</div>
25
+ </div>
26
+ `;
27
+ } else {
28
+ valueHtml = `<span class="form-data-binary">${field.value}</span>`;
29
+ }
30
+ } else {
31
+ valueHtml = `<span class="form-data-value">${field.value}</span>`;
32
+ }
33
+
34
+ return `
35
+ <tr>
36
+ <td class="form-data-name">${field.name}</td>
37
+ <td>${valueHtml}</td>
38
+ </tr>
39
+ `;
40
+ }).join('');
41
+
42
+ return `
43
+ <div class="detail-section">
44
+ <h4>请求参数 (FormData)</h4>
45
+ <table class="form-data-table">
46
+ <tbody>${rows}</tbody>
47
+ </table>
48
+ </div>
49
+ `;
50
+ }
51
+
52
+ /**
53
+ * Extract base64 images from JSON object
54
+ * @param {object} obj
55
+ * @returns {Array<{key: string, dataUrl: string}>}
56
+ */
57
+ function extractBase64Images(obj) {
58
+ const images = [];
59
+
60
+ function traverse(value, path = '') {
61
+ if (typeof value === 'string') {
62
+ // Check if it's a base64 data URL for an image
63
+ if (value.startsWith('data:image/')) {
64
+ images.push({ key: path, dataUrl: value });
65
+ }
66
+ } else if (Array.isArray(value)) {
67
+ value.forEach((item, index) => {
68
+ traverse(item, path ? `${path}[${index}]` : `[${index}]`);
69
+ });
70
+ } else if (value && typeof value === 'object') {
71
+ Object.entries(value).forEach(([key, val]) => {
72
+ traverse(val, path ? `${path}.${key}` : key);
73
+ });
74
+ }
75
+ }
76
+
77
+ traverse(obj);
78
+ return images;
79
+ }
80
+
81
+ /**
82
+ * Extract base64 images from a string (even if JSON is truncated)
83
+ * @param {string} str
84
+ * @returns {Array<{key: string, dataUrl: string}>}
85
+ */
86
+ function extractBase64ImagesFromString(str) {
87
+ const images = [];
88
+ // Match data:image/xxx;base64,... patterns (capture until quote or bracket)
89
+ const regex = /data:image\/([^;]+);base64,([A-Za-z0-9+/=]+)/g;
90
+ let match;
91
+ let index = 0;
92
+
93
+ while ((match = regex.exec(str)) !== null) {
94
+ const mimeType = match[1];
95
+ const base64Data = match[2];
96
+ // Only include if we have substantial data (at least 100 chars)
97
+ if (base64Data.length > 100) {
98
+ images.push({
99
+ key: `image[${index}]`,
100
+ dataUrl: `data:image/${mimeType};base64,${base64Data}`,
101
+ mimeType: `image/${mimeType}`,
102
+ size: Math.round(base64Data.length * 0.75 / 1024)
103
+ });
104
+ index++;
105
+ }
106
+ }
107
+
108
+ return images;
109
+ }
110
+
111
+ /**
112
+ * Format JSON body with base64 truncation for display
113
+ * @param {string} jsonStr
114
+ * @returns {{ formatted: string, images: Array }}
115
+ */
116
+ function formatJsonWithBase64(jsonStr) {
117
+ // First, try to extract images from raw string (works even if truncated)
118
+ const rawImages = extractBase64ImagesFromString(jsonStr);
119
+
120
+ // Truncate base64 in display string
121
+ let formatted = jsonStr.replace(
122
+ /data:image\/([^;]+);base64,([A-Za-z0-9+/=]{50,})/g,
123
+ (match, mimeType, base64) => {
124
+ const sizeKB = Math.round(base64.length * 0.75 / 1024);
125
+ const truncated = base64.substring(0, 40) + '...';
126
+ return `[image/${mimeType} ~${sizeKB}KB] ${truncated}`;
127
+ }
128
+ );
129
+
130
+ // Try to pretty-print if it's valid JSON
131
+ try {
132
+ // First clean up the truncated base64 for parsing
133
+ const cleanedForParse = jsonStr.replace(
134
+ /data:image\/([^;]+);base64,([A-Za-z0-9+/=]+)/g,
135
+ '[BASE64_IMAGE]'
136
+ );
137
+ const obj = JSON.parse(cleanedForParse);
138
+
139
+ // Re-process original to create pretty display
140
+ const displayStr = jsonStr.replace(
141
+ /data:image\/([^;]+);base64,([A-Za-z0-9+/=]+)/g,
142
+ (match, mimeType, base64) => {
143
+ const sizeKB = Math.round(base64.length * 0.75 / 1024);
144
+ return `[📷 image/${mimeType} ~${sizeKB}KB]`;
145
+ }
146
+ );
147
+
148
+ try {
149
+ const displayObj = JSON.parse(displayStr);
150
+ formatted = JSON.stringify(displayObj, null, 2);
151
+ } catch {
152
+ // Use the replaced string as-is
153
+ }
154
+ } catch {
155
+ // JSON parse failed (possibly truncated), use replaced string
156
+ }
157
+
158
+ return {
159
+ formatted,
160
+ images: rawImages
161
+ };
162
+ }
163
+
164
+ /**
165
+ * Render base64 image previews
166
+ * @param {Array<{key: string, dataUrl: string, mimeType?: string, size?: number}>} images
167
+ * @returns {string}
168
+ */
169
+ function renderBase64Previews(images) {
170
+ if (!images || images.length === 0) return '';
171
+
172
+ // Generate unique IDs for each image to update dimensions after load
173
+ const previews = images.map((img, idx) => {
174
+ const imgId = `base64-img-${Date.now()}-${idx}`;
175
+ return `
176
+ <div class="base64-preview-item">
177
+ <div class="base64-preview-label">
178
+ ${img.key}
179
+ ${img.size ? `<span class="base64-size">~${img.size}KB</span>` : ''}
180
+ <span class="base64-dimensions" id="${imgId}-dims"></span>
181
+ </div>
182
+ <img
183
+ src="${img.dataUrl}"
184
+ alt="${img.key}"
185
+ class="base64-preview-img"
186
+ onload="this.parentElement.querySelector('.base64-dimensions').textContent = this.naturalWidth + '×' + this.naturalHeight"
187
+ >
188
+ </div>
189
+ `;
190
+ }).join('');
191
+
192
+ return `
193
+ <div class="base64-previews">
194
+ <h5>📷 请求中的图片 (${images.length}张)</h5>
195
+ <div class="base64-preview-grid">${previews}</div>
196
+ </div>
197
+ `;
198
+ }
199
+
200
+ /**
201
+ * Parse request/response body from log details
202
+ * @param {object} log
203
+ * @returns {{ requestBodySection: string, responseBodySection: string, formDataSection: string }}
204
+ */
205
+ function parseBodySections(log) {
206
+ let requestBodySection = '';
207
+ let responseBodySection = '';
208
+ let formDataSection = '';
209
+
210
+ // Handle FormData for sw-internal requests
211
+ if (log.formData && log.formData.length > 0) {
212
+ formDataSection = renderFormData(log.formData);
213
+ }
214
+
215
+ // Handle JSON request body
216
+ if (log.requestBody && !log.formData) {
217
+ // Prefer pre-extracted base64Images from debugFetch (complete images)
218
+ // Fall back to extracting from requestBody (may be truncated)
219
+ const images = log.base64Images || formatJsonWithBase64(log.requestBody).images;
220
+ const previewHtml = renderBase64Previews(images);
221
+
222
+ // Format the request body for display (already has base64 replaced with placeholders if from debugFetch)
223
+ const displayBody = formatJsonOrText(log.requestBody);
224
+
225
+ requestBodySection = `
226
+ <div class="detail-section">
227
+ <h4>请求体 (Request Body)</h4>
228
+ <pre>${displayBody}</pre>
229
+ ${previewHtml}
230
+ </div>
231
+ `;
232
+ }
233
+
234
+ // Handle response body
235
+ if (log.responseBody) {
236
+ responseBodySection = `
237
+ <div class="detail-section">
238
+ <h4>响应体 (Response Body)</h4>
239
+ <pre>${formatJsonOrText(log.responseBody)}</pre>
240
+ </div>
241
+ `;
242
+ }
243
+
244
+ // Fallback: parse from details for XHR type
245
+ if (log.details && log.requestType === 'xhr' && !requestBodySection && !responseBodySection) {
246
+ const parts = log.details.split('\n\nResponse Body:\n');
247
+ if (parts.length === 2) {
248
+ const reqParts = parts[0].split('\n\nRequest Body:\n');
249
+ if (reqParts.length === 2) {
250
+ requestBodySection = `
251
+ <div class="detail-section">
252
+ <h4>请求体 (Request Body)</h4>
253
+ <pre>${formatJsonOrText(reqParts[1])}</pre>
254
+ </div>
255
+ `;
256
+ }
257
+ responseBodySection = `
258
+ <div class="detail-section">
259
+ <h4>响应体 (Response Body)</h4>
260
+ <pre>${formatJsonOrText(parts[1])}</pre>
261
+ </div>
262
+ `;
263
+ } else if (log.details.includes('\n\nResponse Body:\n')) {
264
+ const respParts = log.details.split('\n\nResponse Body:\n');
265
+ if (respParts.length === 2) {
266
+ responseBodySection = `
267
+ <div class="detail-section">
268
+ <h4>响应体 (Response Body)</h4>
269
+ <pre>${formatJsonOrText(respParts[1])}</pre>
270
+ </div>
271
+ `;
272
+ }
273
+ }
274
+ }
275
+
276
+ return { requestBodySection, responseBodySection, formDataSection };
277
+ }
278
+
279
+ /**
280
+ * Create a log entry DOM element
281
+ * @param {object} log
282
+ * @param {boolean} isExpanded - Initial expanded state
283
+ * @param {Function} onToggle - Callback when expand state changes (id, expanded)
284
+ * @returns {HTMLElement}
285
+ */
286
+ export function createLogEntry(log, isExpanded = false, onToggle = null, isBookmarked = false, onBookmark = null, isSelectMode = false, isSelected = false, onSelect = null) {
287
+ const entry = document.createElement('div');
288
+ entry.className = 'log-entry' + (isExpanded ? ' expanded' : '') + (isBookmarked ? ' bookmarked' : '') + (isSelected ? ' selected' : '');
289
+ entry.dataset.id = log.id;
290
+
291
+ const statusClass = getStatusClass(log.status);
292
+ const cachedBadge = log.cached ? '<span class="log-cached">缓存</span>' : '';
293
+ const typeClass = log.requestType === 'xhr' ? 'xhr' :
294
+ (log.requestType === 'passthrough' ? 'passthrough' :
295
+ (log.requestType === 'sw-internal' ? 'sw-internal' : ''));
296
+ const displayUrl = extractDisplayUrl(log.url);
297
+ const { requestBodySection, responseBodySection, formDataSection } = parseBodySections(log);
298
+
299
+ // Determine if this is a network error (status 0 or has error with no status)
300
+ const isNetworkError = log.error && (log.status === 0 || log.status === undefined);
301
+ const errorBadgeText = isNetworkError
302
+ ? (log.statusText || '网络错误')
303
+ : '';
304
+
305
+ // Extract purpose label for sw-internal requests
306
+ const purposeLabel = log.requestType === 'sw-internal' && log.details
307
+ ? `<span class="log-purpose">${log.details}</span>`
308
+ : '';
309
+
310
+ // Streaming badge
311
+ const streamingBadge = log.isStreaming
312
+ ? '<span class="log-streaming" title="流式响应 (SSE/Stream)">Stream</span>'
313
+ : '';
314
+
315
+ // Build status display
316
+ let statusDisplay;
317
+ if (isNetworkError) {
318
+ // Network error - show error badge instead of status code
319
+ statusDisplay = `<span class="log-status network-error" title="${log.error || ''}">${errorBadgeText}</span>`;
320
+ } else if (log.status) {
321
+ statusDisplay = `<span class="log-status ${statusClass}">${log.status}</span>`;
322
+ } else {
323
+ statusDisplay = '<span class="log-status pending">...</span>';
324
+ }
325
+
326
+ const bookmarkIcon = isBookmarked ? '⭐' : '☆';
327
+ const selectCheckbox = isSelectMode
328
+ ? `<input type="checkbox" class="log-select-checkbox" data-id="${log.id}" ${isSelected ? 'checked' : ''} style="margin-right: 6px; cursor: pointer;">`
329
+ : '';
330
+
331
+ entry.innerHTML = `
332
+ <div class="log-header">
333
+ ${selectCheckbox}
334
+ <span class="log-bookmark" title="收藏/取消收藏" data-id="${log.id}">${bookmarkIcon}</span>
335
+ <span class="log-toggle" title="展开/收起详情"><span class="arrow">▶</span></span>
336
+ <span class="log-time">${formatTime(log.timestamp)}</span>
337
+ <span class="log-method">${log.method || 'GET'}</span>
338
+ ${statusDisplay}
339
+ ${streamingBadge}
340
+ ${purposeLabel}
341
+ <span class="log-url" title="${log.url || ''}">${displayUrl}</span>
342
+ <span class="log-duration ${log.duration >= 3000 ? 'very-slow' : (log.duration >= 1000 ? 'slow' : '')}">${formatDuration(log.duration)}</span>
343
+ ${cachedBadge}
344
+ </div>
345
+ <div class="log-details">
346
+ ${log.url ? `
347
+ <div class="detail-section">
348
+ <h4>完整 URL</h4>
349
+ <pre>${log.url}</pre>
350
+ </div>
351
+ ` : ''}
352
+ ${log.headers && Object.keys(log.headers).length > 0 ? `
353
+ <div class="detail-section">
354
+ <h4>请求头 (Request Headers)</h4>
355
+ <pre>${JSON.stringify(log.headers, null, 2)}</pre>
356
+ </div>
357
+ ` : ''}
358
+ ${formDataSection}
359
+ ${requestBodySection}
360
+ ${log.responseHeaders && Object.keys(log.responseHeaders).length > 0 ? `
361
+ <div class="detail-section">
362
+ <h4>响应头 (Response Headers)</h4>
363
+ <pre>${JSON.stringify(log.responseHeaders, null, 2)}</pre>
364
+ </div>
365
+ ` : ''}
366
+ ${responseBodySection}
367
+ ${log.error ? `
368
+ <div class="detail-section">
369
+ <h4>错误</h4>
370
+ <pre style="color: var(--error-color);">${log.error}</pre>
371
+ </div>
372
+ ` : ''}
373
+ ${log.size && log.size > 0 ? `
374
+ <div class="detail-section">
375
+ <h4>响应大小</h4>
376
+ <pre>${formatSize(log.size)}</pre>
377
+ </div>
378
+ ` : ''}
379
+ ${log.details && log.requestType !== 'xhr' && log.requestType !== 'sw-internal' ? `
380
+ <div class="detail-section">
381
+ <h4>详情</h4>
382
+ <pre>${log.details}</pre>
383
+ </div>
384
+ ` : ''}
385
+ <div class="related-requests-placeholder" data-log-id="${log.id}"></div>
386
+ </div>
387
+ `;
388
+
389
+ // Toggle function
390
+ const toggleExpand = () => {
391
+ const isNowExpanded = entry.classList.toggle('expanded');
392
+ if (onToggle) {
393
+ onToggle(log.id, isNowExpanded);
394
+ }
395
+
396
+ // Lazy load related requests when expanding
397
+ if (isNowExpanded && window.renderRelatedRequests) {
398
+ const placeholder = entry.querySelector('.related-requests-placeholder');
399
+ if (placeholder && !placeholder.dataset.loaded) {
400
+ placeholder.innerHTML = window.renderRelatedRequests(log);
401
+ placeholder.dataset.loaded = 'true';
402
+
403
+ // Add click handler for related requests
404
+ placeholder.querySelectorAll('.related-request').forEach(el => {
405
+ el.addEventListener('click', () => {
406
+ const targetId = el.dataset.id;
407
+ const targetEntry = document.querySelector(`.log-entry[data-id="${targetId}"]`);
408
+ if (targetEntry) {
409
+ targetEntry.scrollIntoView({ behavior: 'smooth', block: 'center' });
410
+ targetEntry.classList.add('highlight');
411
+ setTimeout(() => targetEntry.classList.remove('highlight'), 2000);
412
+ }
413
+ });
414
+ });
415
+ }
416
+ }
417
+ };
418
+
419
+ // Toggle expand/collapse on button click
420
+ const toggleBtn = entry.querySelector('.log-toggle');
421
+ toggleBtn.addEventListener('click', (e) => {
422
+ e.stopPropagation();
423
+ toggleExpand();
424
+ });
425
+
426
+ // Bookmark button click
427
+ const bookmarkBtn = entry.querySelector('.log-bookmark');
428
+ if (bookmarkBtn && onBookmark) {
429
+ bookmarkBtn.addEventListener('click', (e) => {
430
+ e.stopPropagation();
431
+ onBookmark(log.id);
432
+ });
433
+ }
434
+
435
+ // Selection checkbox click
436
+ const selectCheckboxEl = entry.querySelector('.log-select-checkbox');
437
+ if (selectCheckboxEl && onSelect) {
438
+ selectCheckboxEl.addEventListener('change', (e) => {
439
+ e.stopPropagation();
440
+ onSelect(log.id);
441
+ });
442
+ }
443
+
444
+ // Toggle on header click (except toggle button, bookmark, and checkbox)
445
+ const header = entry.querySelector('.log-header');
446
+ header.addEventListener('click', (e) => {
447
+ if (e.target.closest('.log-toggle') || e.target.closest('.log-bookmark') || e.target.closest('.log-select-checkbox')) return;
448
+ toggleExpand();
449
+ });
450
+
451
+ return entry;
452
+ }