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.
- package/.DS_Store +0 -0
- package/README.md +47 -0
- package/_headers +84 -0
- package/_redirects +2 -0
- package/assets/ChatMessagesArea-CkUX81uB.js +251 -0
- package/assets/ChatMessagesArea-Di0Z80Zh.css +1 -0
- package/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
- package/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
- package/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
- package/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
- package/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
- package/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
- package/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
- package/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
- package/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
- package/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
- package/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
- package/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
- package/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
- package/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
- package/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
- package/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
- package/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
- package/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
- package/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
- package/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
- package/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
- package/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
- package/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
- package/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
- package/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
- package/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
- package/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
- package/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
- package/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
- package/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
- package/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
- package/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
- package/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
- package/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
- package/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
- package/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
- package/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
- package/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
- package/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
- package/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
- package/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
- package/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
- package/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
- package/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
- package/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
- package/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
- package/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
- package/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
- package/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
- package/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
- package/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
- package/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
- package/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
- package/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
- package/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
- package/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
- package/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
- package/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
- package/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
- package/assets/Tableau10-B-NsZVaP.js +1 -0
- package/assets/Tableau10-Dnlau_Wv.js +1 -0
- package/assets/ToolboxDrawer-By1XMh8B.js +87 -0
- package/assets/ToolboxDrawer-fPqvDLQE.css +1 -0
- package/assets/__vite-browser-external-BIHI7g3E.js +1 -0
- package/assets/ai-analyze-Db-iXol6.js +1 -0
- package/assets/arc-BZXVqUcI.js +1 -0
- package/assets/arc-ajYHRRnk.js +1 -0
- package/assets/array-B5oSNiGi.js +1 -0
- package/assets/array-BKyUJesY.js +1 -0
- package/assets/batch-image-generation-Baqb01Lm.js +6 -0
- package/assets/batch-image-generation-CbLMWmjk.css +1 -0
- package/assets/blockDiagram-38ab4fdb-BT3H_WVv.js +118 -0
- package/assets/blockDiagram-38ab4fdb-u0xYP3Lt.js +118 -0
- package/assets/c4Diagram-3d4e48cf-CBvM6zjM.js +10 -0
- package/assets/c4Diagram-3d4e48cf-WOIEVidH.js +10 -0
- package/assets/channel-BP25wTsw.js +1 -0
- package/assets/channel-HzrLNFUg.js +1 -0
- package/assets/classDiagram-70f12bd4-BMutcvFi.js +2 -0
- package/assets/classDiagram-70f12bd4-Cl9U1r5F.js +2 -0
- package/assets/classDiagram-v2-f2320105-C0agtbR4.js +2 -0
- package/assets/classDiagram-v2-f2320105-tCBzATK6.js +2 -0
- package/assets/clone-B69pF7Y_.js +1 -0
- package/assets/clone-oX7o-l4R.js +1 -0
- package/assets/createText-2e5e7dd3-CZ9_fscE.js +5 -0
- package/assets/createText-2e5e7dd3-idrqgJjU.js +7 -0
- package/assets/edges-e0da2a9e-C-RyePMV.js +4 -0
- package/assets/edges-e0da2a9e-DJXAjJSL.js +4 -0
- package/assets/erDiagram-9861fffd-DWJR_3zL.js +51 -0
- package/assets/erDiagram-9861fffd-x2Kcy95-.js +51 -0
- package/assets/flowDb-956e92f1-BgKjOIdz.js +10 -0
- package/assets/flowDb-956e92f1-CF6y18Tn.js +10 -0
- package/assets/flowDiagram-66a62f08-BPPw0wPU.js +4 -0
- package/assets/flowDiagram-66a62f08-CSAllSFf.js +4 -0
- package/assets/flowDiagram-v2-96b9c2cf-B-UGyXRi.js +1 -0
- package/assets/flowDiagram-v2-96b9c2cf-Cm596kxZ.js +1 -0
- package/assets/flowchart-elk-definition-4a651766-9XSRJbsr.js +139 -0
- package/assets/flowchart-elk-definition-4a651766-DWFN9DN3.js +139 -0
- package/assets/ganttDiagram-c361ad54-D9tbz9tQ.js +257 -0
- package/assets/ganttDiagram-c361ad54-ot5pUYpT.js +257 -0
- package/assets/gitGraphDiagram-72cf32ee-BFV3Mt8C.js +70 -0
- package/assets/gitGraphDiagram-72cf32ee-C6qFzgGh.js +70 -0
- package/assets/graph-BxwlF7JS.js +1 -0
- package/assets/graph-D-2Ldvxg.js +1 -0
- package/assets/grid-image-cM9AmYC8.js +1 -0
- package/assets/has-CgdIPiQG.js +1 -0
- package/assets/hasIn-4iY02rGN.js +1 -0
- package/assets/index-3862675e-CVZnpwDN.js +1 -0
- package/assets/index-3862675e-DqdI9cab.js +1 -0
- package/assets/index-B2dvADz8.css +1 -0
- package/assets/index-BicRPzXC.js +1 -0
- package/assets/index-Bs7-jmv6.css +1 -0
- package/assets/index-BwSGXyRr.js +99 -0
- package/assets/index-C1XdOOAn.css +1 -0
- package/assets/index-C4AKKbpQ.css +1 -0
- package/assets/index-CkpXFt8n.js +1 -0
- package/assets/index-CrxF9gFe.js +42 -0
- package/assets/index-DBWqXBIQ.js +93 -0
- package/assets/index-DI_5V2-m.js +3 -0
- package/assets/index-DWUAFoZG.js +2064 -0
- package/assets/index-Dn0YtZ2R.js +3 -0
- package/assets/index-e05Rs4M6.js +12 -0
- package/assets/index.dom-C3-224fz.js +1 -0
- package/assets/infoDiagram-f8f76790-CnrpwoOt.js +7 -0
- package/assets/infoDiagram-f8f76790-FKC1Sy9Y.js +7 -0
- package/assets/init-A0kIFD9x.js +1 -0
- package/assets/init-Gi6I4Gst.js +1 -0
- package/assets/inspiration-board-B_-BBBHt.js +1 -0
- package/assets/isEmpty-Dj2GV0v-.js +1 -0
- package/assets/journeyDiagram-49397b02-B7fP21sU.js +139 -0
- package/assets/journeyDiagram-49397b02-Dp3X9XWq.js +139 -0
- package/assets/katex-BbEIqZs1.js +261 -0
- package/assets/katex-Cu_Erd72.js +261 -0
- package/assets/layout-BD3yCK_X.js +1 -0
- package/assets/layout-DHHYqX7p.js +1 -0
- package/assets/line-B3bNrkzn.js +1 -0
- package/assets/line-B86HLuqu.js +1 -0
- package/assets/linear-DU2Ciymb.js +1 -0
- package/assets/linear-wCAlMhOS.js +1 -0
- package/assets/mermaid.core-DfVvnpgz.js +91 -0
- package/assets/mindmap-definition-fc14e90a-D1sxE3xG.js +425 -0
- package/assets/mindmap-definition-fc14e90a-YuSOJC7P.js +425 -0
- package/assets/ordinal-BRr1uYdk.js +1 -0
- package/assets/ordinal-Cboi1Yqb.js +1 -0
- package/assets/path-CY0bYimO.js +1 -0
- package/assets/path-CbwjOpE9.js +1 -0
- package/assets/photo-wall-splitter-BVU2e0aS.js +1 -0
- package/assets/pick-Cvlwra4g.js +1 -0
- package/assets/pieDiagram-8a3498a8-B6mJUqro.js +35 -0
- package/assets/pieDiagram-8a3498a8-B91bWgo_.js +35 -0
- package/assets/quadrantDiagram-120e2f19-BxS8fQEz.js +7 -0
- package/assets/quadrantDiagram-120e2f19-DwudONqx.js +7 -0
- package/assets/requirementDiagram-deff3bca-DygaMIoy.js +52 -0
- package/assets/requirementDiagram-deff3bca-v9xlgfS8.js +52 -0
- package/assets/sankeyDiagram-04a897e0-BV23dp4l.js +8 -0
- package/assets/sankeyDiagram-04a897e0-BXCiXiyw.js +8 -0
- package/assets/sequenceDiagram-704730f1-CObRpNi4.js +122 -0
- package/assets/sequenceDiagram-704730f1-Ck69A6wI.js +122 -0
- package/assets/settings-dialog-BlCO49C4.js +1 -0
- package/assets/settings-dialog-QUxXj54T.css +1 -0
- package/assets/stateDiagram-587899a1-J_G6I0oo.js +1 -0
- package/assets/stateDiagram-587899a1-z-tKclr3.js +1 -0
- package/assets/stateDiagram-v2-d93cdb3a-DsThtOzP.js +1 -0
- package/assets/stateDiagram-v2-d93cdb3a-XIvq5t8a.js +1 -0
- package/assets/styles-6aaf32cf-1fjuNMUk.js +207 -0
- package/assets/styles-6aaf32cf-DT2rVNfQ.js +207 -0
- package/assets/styles-9a916d00-fLeUSina.js +160 -0
- package/assets/styles-9a916d00-q64Umkis.js +160 -0
- package/assets/styles-c10674c1-BWlxVc3Q.js +116 -0
- package/assets/styles-c10674c1-CtYpjMYU.js +116 -0
- package/assets/svgDrawCommon-08f97a94-C_DhKfny.js +1 -0
- package/assets/svgDrawCommon-08f97a94-DSBqmUv2.js +1 -0
- package/assets/timeline-definition-85554ec2-AKpzwLPN.js +61 -0
- package/assets/timeline-definition-85554ec2-dTkYwoLF.js +61 -0
- package/assets/ttd-dialog-CxiaIUuJ.js +47 -0
- package/assets/ttd-dialog-DCapefb6.css +1 -0
- package/assets/upload-4sxUU7q_.js +1 -0
- package/assets/video-recovery-service-BckHbSyK.js +1 -0
- package/assets/web-vitals-DcvjKPr-.js +1 -0
- package/assets/winbox.bundle.min-CoRPjCs5.js +1 -0
- package/assets/xlsx-CkFp8p6R.js +105 -0
- package/assets/xychartDiagram-e933f94c-DCmvL0ag.js +7 -0
- package/assets/xychartDiagram-e933f94c-aqOiXp_u.js +7 -0
- package/batch-image.html +1616 -0
- package/favicon.ico +0 -0
- package/icons/README.md +55 -0
- package/icons/aitu10.png +0 -0
- package/icons/android-chrome-192x192.png +0 -0
- package/icons/android-chrome-512x512.png +0 -0
- package/icons/apple-touch-icon.png +0 -0
- package/icons/favicon-16x16.png +0 -0
- package/icons/favicon-16x16.svg +539 -0
- package/icons/favicon-32x32.png +0 -0
- package/icons/favicon-32x32.svg +539 -0
- package/icons/favicon-new.svg +539 -0
- package/icons/favicon-new.svg.png +0 -0
- package/icons/icon-192x192.svg +539 -0
- package/icons/icon-512x512.svg +539 -0
- package/icons/icon-96x96.png +0 -0
- package/icons/icon-96x96.svg +539 -0
- package/iframe-test.html +340 -0
- package/index.html +105 -0
- package/init.json +6 -0
- package/logo/cardid.jpg +0 -0
- package/logo/group-qr.png +0 -0
- package/logo/logo_drawnix_h.svg +539 -0
- package/logo/logo_drawnix_h_dark.svg +539 -0
- package/logo/logo_drawnix_new.svg +539 -0
- package/manifest.json +52 -0
- package/package.json +31 -0
- package/product_showcase/aitu-01.png +0 -0
- package/product_showcase/aitu-02.png +0 -0
- package/product_showcase/aitu-03.png +0 -0
- package/product_showcase/aitu-04.png +0 -0
- package/product_showcase/aitu-05.png +0 -0
- package/product_showcase/aitu-06.png +0 -0
- package/product_showcase/case-1.png +0 -0
- package/product_showcase/case-2.png +0 -0
- package/robots.txt +13 -0
- package/sitemap.xml +29 -0
- package/sw-debug/app.js +3069 -0
- package/sw-debug/console-entry.js +80 -0
- package/sw-debug/log-entry.js +452 -0
- package/sw-debug/log-panel.js +309 -0
- package/sw-debug/postmessage-entry.js +117 -0
- package/sw-debug/status-panel.js +125 -0
- package/sw-debug/styles.css +2103 -0
- package/sw-debug/sw-communication.js +208 -0
- package/sw-debug/utils.js +112 -0
- package/sw-debug.html +685 -0
- package/sw.js +58 -0
- 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
|
+
}
|