@mseep/anything-analyzer 3.6.50

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 (172) hide show
  1. package/.codeartsdoer/.codebaseignore +0 -0
  2. package/.codeartsdoer/AGENTS.md +12 -0
  3. package/.github/workflows/build.yml +146 -0
  4. package/README.en.md +264 -0
  5. package/README.md +276 -0
  6. package/RELEASE_NOTES.md +16 -0
  7. package/USAGE.md +490 -0
  8. package/color-preview-r3.html +414 -0
  9. package/color-preview.html +414 -0
  10. package/dev-app-update.yml +3 -0
  11. package/electron-builder.yml +36 -0
  12. package/electron.vite.config.ts +40 -0
  13. package/package.json +53 -0
  14. package/report-2026-04-13-copilot-claude-sonnet-4.6.md +955 -0
  15. package/resources/doloffer-logo.png +0 -0
  16. package/resources/entitlements.mac.plist +12 -0
  17. package/resources/icon.ico +0 -0
  18. package/resources/icon.png +0 -0
  19. package/src/main/ai/ai-analyzer.ts +517 -0
  20. package/src/main/ai/crypto-script-extractor.ts +206 -0
  21. package/src/main/ai/data-assembler.ts +205 -0
  22. package/src/main/ai/llm-router.ts +1120 -0
  23. package/src/main/ai/prompt-builder.ts +349 -0
  24. package/src/main/ai/scene-detector.ts +302 -0
  25. package/src/main/capture/capture-engine.ts +130 -0
  26. package/src/main/capture/interaction-recorder.ts +171 -0
  27. package/src/main/capture/js-injector.ts +57 -0
  28. package/src/main/capture/replay-engine.ts +256 -0
  29. package/src/main/capture/storage-collector.ts +76 -0
  30. package/src/main/cdp/cdp-manager.ts +233 -0
  31. package/src/main/db/database.ts +41 -0
  32. package/src/main/db/migrations.ts +235 -0
  33. package/src/main/db/repositories.ts +574 -0
  34. package/src/main/fingerprint/http-spoofing.ts +48 -0
  35. package/src/main/fingerprint/presets.ts +173 -0
  36. package/src/main/fingerprint/profile-generator.ts +115 -0
  37. package/src/main/fingerprint/profile-store.ts +52 -0
  38. package/src/main/index.ts +260 -0
  39. package/src/main/ipc.ts +856 -0
  40. package/src/main/logger.ts +42 -0
  41. package/src/main/mcp/mcp-config.ts +66 -0
  42. package/src/main/mcp/mcp-manager.ts +155 -0
  43. package/src/main/mcp/mcp-server.ts +1038 -0
  44. package/src/main/prompt-templates.ts +170 -0
  45. package/src/main/proxy/ca-manager.ts +204 -0
  46. package/src/main/proxy/cert-download-page.ts +171 -0
  47. package/src/main/proxy/cert-installer.ts +242 -0
  48. package/src/main/proxy/mitm-proxy-config.ts +37 -0
  49. package/src/main/proxy/mitm-proxy-server.ts +1085 -0
  50. package/src/main/proxy/system-proxy.ts +248 -0
  51. package/src/main/session/session-manager.ts +724 -0
  52. package/src/main/tab-manager.ts +582 -0
  53. package/src/main/updater.ts +111 -0
  54. package/src/main/window.ts +235 -0
  55. package/src/preload/hook-script.ts +270 -0
  56. package/src/preload/index.ts +211 -0
  57. package/src/preload/interaction-hook.ts +286 -0
  58. package/src/preload/stealth-script.ts +302 -0
  59. package/src/preload/target-preload.ts +15 -0
  60. package/src/renderer/App.tsx +656 -0
  61. package/src/renderer/components/AiLogDetail.tsx +173 -0
  62. package/src/renderer/components/AiLogList.tsx +101 -0
  63. package/src/renderer/components/AiLogView.module.css +364 -0
  64. package/src/renderer/components/AiLogView.tsx +86 -0
  65. package/src/renderer/components/AnalyzeBar.module.css +79 -0
  66. package/src/renderer/components/AnalyzeBar.tsx +104 -0
  67. package/src/renderer/components/BrowserPanel.module.css +67 -0
  68. package/src/renderer/components/BrowserPanel.tsx +90 -0
  69. package/src/renderer/components/ControlBar.module.css +47 -0
  70. package/src/renderer/components/ControlBar.tsx +205 -0
  71. package/src/renderer/components/HookLog.tsx +132 -0
  72. package/src/renderer/components/InteractionLog.tsx +183 -0
  73. package/src/renderer/components/MCPServerModal.tsx +427 -0
  74. package/src/renderer/components/PromptTemplateModal.tsx +254 -0
  75. package/src/renderer/components/ReportView.module.css +413 -0
  76. package/src/renderer/components/ReportView.tsx +429 -0
  77. package/src/renderer/components/RequestDetail.module.css +191 -0
  78. package/src/renderer/components/RequestDetail.tsx +202 -0
  79. package/src/renderer/components/RequestLog.module.css +69 -0
  80. package/src/renderer/components/RequestLog.tsx +208 -0
  81. package/src/renderer/components/SessionList.module.css +245 -0
  82. package/src/renderer/components/SessionList.tsx +247 -0
  83. package/src/renderer/components/SettingsModal.tsx +100 -0
  84. package/src/renderer/components/StatusBar.module.css +44 -0
  85. package/src/renderer/components/StatusBar.tsx +102 -0
  86. package/src/renderer/components/StorageView.module.css +41 -0
  87. package/src/renderer/components/StorageView.tsx +178 -0
  88. package/src/renderer/components/TabBar.module.css +88 -0
  89. package/src/renderer/components/TabBar.tsx +70 -0
  90. package/src/renderer/components/Titlebar.module.css +254 -0
  91. package/src/renderer/components/Titlebar.tsx +169 -0
  92. package/src/renderer/components/settings/FingerprintSection.tsx +198 -0
  93. package/src/renderer/components/settings/GeneralSection.tsx +164 -0
  94. package/src/renderer/components/settings/LLMSection.tsx +148 -0
  95. package/src/renderer/components/settings/MCPServerSection.tsx +136 -0
  96. package/src/renderer/components/settings/MitmProxySection.tsx +320 -0
  97. package/src/renderer/components/settings/ProxySection.tsx +110 -0
  98. package/src/renderer/css-modules.d.ts +4 -0
  99. package/src/renderer/hooks/useCapture.ts +383 -0
  100. package/src/renderer/hooks/useConfirm.tsx +91 -0
  101. package/src/renderer/hooks/useSession.ts +136 -0
  102. package/src/renderer/hooks/useTabs.ts +103 -0
  103. package/src/renderer/i18n/en.ts +167 -0
  104. package/src/renderer/i18n/index.ts +47 -0
  105. package/src/renderer/i18n/zh.ts +170 -0
  106. package/src/renderer/index.html +12 -0
  107. package/src/renderer/main.tsx +15 -0
  108. package/src/renderer/styles/global.css +144 -0
  109. package/src/renderer/styles/themes/ayu-dark.css +59 -0
  110. package/src/renderer/styles/themes/catppuccin.css +59 -0
  111. package/src/renderer/styles/themes/discord.css +59 -0
  112. package/src/renderer/styles/themes/dracula.css +59 -0
  113. package/src/renderer/styles/themes/github-dark.css +59 -0
  114. package/src/renderer/styles/themes/gruvbox.css +59 -0
  115. package/src/renderer/styles/themes/index.css +11 -0
  116. package/src/renderer/styles/themes/light.css +59 -0
  117. package/src/renderer/styles/themes/nord.css +59 -0
  118. package/src/renderer/styles/themes/one-dark.css +59 -0
  119. package/src/renderer/styles/themes/tokyo-night.css +59 -0
  120. package/src/renderer/styles/tokens.css +137 -0
  121. package/src/renderer/theme.ts +31 -0
  122. package/src/renderer/ui/Badge.module.css +38 -0
  123. package/src/renderer/ui/Badge.tsx +36 -0
  124. package/src/renderer/ui/Button.module.css +142 -0
  125. package/src/renderer/ui/Button.tsx +46 -0
  126. package/src/renderer/ui/Collapse.module.css +49 -0
  127. package/src/renderer/ui/Collapse.tsx +57 -0
  128. package/src/renderer/ui/CopyableBlock.module.css +56 -0
  129. package/src/renderer/ui/CopyableBlock.tsx +42 -0
  130. package/src/renderer/ui/Empty.module.css +19 -0
  131. package/src/renderer/ui/Empty.tsx +34 -0
  132. package/src/renderer/ui/Icons.tsx +346 -0
  133. package/src/renderer/ui/Input.module.css +103 -0
  134. package/src/renderer/ui/Input.tsx +94 -0
  135. package/src/renderer/ui/InputNumber.module.css +68 -0
  136. package/src/renderer/ui/InputNumber.tsx +104 -0
  137. package/src/renderer/ui/Modal.module.css +83 -0
  138. package/src/renderer/ui/Modal.tsx +67 -0
  139. package/src/renderer/ui/Popconfirm.module.css +73 -0
  140. package/src/renderer/ui/Popconfirm.tsx +74 -0
  141. package/src/renderer/ui/Progress.module.css +35 -0
  142. package/src/renderer/ui/Progress.tsx +30 -0
  143. package/src/renderer/ui/Select.module.css +91 -0
  144. package/src/renderer/ui/Select.tsx +100 -0
  145. package/src/renderer/ui/Spinner.module.css +44 -0
  146. package/src/renderer/ui/Spinner.tsx +27 -0
  147. package/src/renderer/ui/Switch.module.css +39 -0
  148. package/src/renderer/ui/Switch.tsx +43 -0
  149. package/src/renderer/ui/Tabs.module.css +76 -0
  150. package/src/renderer/ui/Tabs.tsx +53 -0
  151. package/src/renderer/ui/Tag.module.css +66 -0
  152. package/src/renderer/ui/Tag.tsx +47 -0
  153. package/src/renderer/ui/Timeline.module.css +42 -0
  154. package/src/renderer/ui/Timeline.tsx +29 -0
  155. package/src/renderer/ui/Toast.module.css +99 -0
  156. package/src/renderer/ui/Toast.tsx +90 -0
  157. package/src/renderer/ui/Tooltip.module.css +26 -0
  158. package/src/renderer/ui/Tooltip.tsx +23 -0
  159. package/src/renderer/ui/VirtualTable.module.css +230 -0
  160. package/src/renderer/ui/VirtualTable.tsx +416 -0
  161. package/src/renderer/ui/index.ts +55 -0
  162. package/src/shared/types.ts +695 -0
  163. package/tests/main/ai/crypto-script-extractor.test.ts +281 -0
  164. package/tests/main/ai/llm-router.test.ts +1537 -0
  165. package/tests/main/ai/prompt-builder.test.ts +178 -0
  166. package/tests/main/ai/scene-detector.test.ts +212 -0
  167. package/tests/main/db/migrations.test.ts +134 -0
  168. package/tests/main/release-workflow.test.ts +59 -0
  169. package/tsconfig.json +7 -0
  170. package/tsconfig.node.json +23 -0
  171. package/tsconfig.web.json +24 -0
  172. package/vitest.config.ts +13 -0
@@ -0,0 +1,173 @@
1
+ import React, { useEffect, useState } from 'react'
2
+ import { useLocale } from '../i18n'
3
+ import type { AiRequestLog } from '@shared/types'
4
+ import styles from './AiLogView.module.css'
5
+
6
+ interface AiLogDetailProps {
7
+ logId: number | null
8
+ }
9
+
10
+ type DetailTab = 'request' | 'response' | 'headers' | 'meta'
11
+
12
+ function formatTokenCount(prompt: number, completion: number): string {
13
+ const fmt = (n: number) => n >= 1000 ? `${(n / 1000).toFixed(1)}k` : String(n)
14
+ return `${fmt(prompt)} + ${fmt(completion)} tokens`
15
+ }
16
+
17
+ function formatDuration(ms: number | null): string {
18
+ if (ms === null) return '--'
19
+ if (ms < 1000) return `${ms}ms`
20
+ return `${(ms / 1000).toFixed(1)}s`
21
+ }
22
+
23
+ function tryFormatJson(text: string): string {
24
+ try {
25
+ return JSON.stringify(JSON.parse(text), null, 2)
26
+ } catch {
27
+ return text
28
+ }
29
+ }
30
+
31
+ function formatHeaders(headersJson: string | null): Array<[string, string]> {
32
+ if (!headersJson) return []
33
+ try {
34
+ const obj = JSON.parse(headersJson)
35
+ return Object.entries(obj) as Array<[string, string]>
36
+ } catch {
37
+ return []
38
+ }
39
+ }
40
+
41
+ export const AiLogDetail: React.FC<AiLogDetailProps> = ({ logId }) => {
42
+ const { t } = useLocale()
43
+ const [tab, setTab] = useState<DetailTab>('request')
44
+ const [detail, setDetail] = useState<AiRequestLog | null>(null)
45
+ const [copied, setCopied] = useState(false)
46
+
47
+ useEffect(() => {
48
+ if (logId === null) {
49
+ setDetail(null)
50
+ return
51
+ }
52
+ window.electronAPI.getAiRequestLogDetail(logId).then(setDetail)
53
+ }, [logId])
54
+
55
+ if (!detail) {
56
+ return <div className={styles.detailEmpty}>← Select a log entry</div>
57
+ }
58
+
59
+ const statusText = detail.status_code
60
+ ? `${detail.status_code} ${detail.status_code >= 200 && detail.status_code < 300 ? 'OK' : 'ERR'}`
61
+ : 'N/A'
62
+
63
+ const urlPath = (() => {
64
+ try { return new URL(detail.request_url).pathname }
65
+ catch { return detail.request_url }
66
+ })()
67
+
68
+ const handleCopy = () => {
69
+ let content = ''
70
+ switch (tab) {
71
+ case 'request': content = detail.request_body; break
72
+ case 'response': content = detail.response_body ?? ''; break
73
+ case 'headers': content = JSON.stringify({
74
+ request: formatHeaders(detail.request_headers),
75
+ response: formatHeaders(detail.response_headers ?? null),
76
+ }, null, 2); break
77
+ case 'meta': content = JSON.stringify({
78
+ provider: detail.provider, model: detail.model, type: detail.type,
79
+ session_id: detail.session_id, report_id: detail.report_id,
80
+ created_at: new Date(detail.created_at).toISOString(),
81
+ }, null, 2); break
82
+ }
83
+ navigator.clipboard.writeText(content).then(() => {
84
+ setCopied(true)
85
+ setTimeout(() => setCopied(false), 1500)
86
+ })
87
+ }
88
+
89
+ const tabs: { key: DetailTab; label: string }[] = [
90
+ { key: 'request', label: t('aiLog.tabRequest') },
91
+ { key: 'response', label: t('aiLog.tabResponse') },
92
+ { key: 'headers', label: t('aiLog.tabHeaders') },
93
+ { key: 'meta', label: t('aiLog.tabMeta') },
94
+ ]
95
+
96
+ return (
97
+ <div className={styles.detailPanel}>
98
+ {/* Tab bar */}
99
+ <div className={styles.detailTabBar}>
100
+ {tabs.map(tb => (
101
+ <button
102
+ key={tb.key}
103
+ className={`${styles.detailTab} ${tab === tb.key ? styles.detailTabActive : ''}`}
104
+ onClick={() => setTab(tb.key)}
105
+ >
106
+ {tb.label}
107
+ </button>
108
+ ))}
109
+ <div className={styles.detailTabSpacer} />
110
+ <button className={styles.copyBtn} onClick={handleCopy}>
111
+ {copied ? t('aiLog.copied') : t('aiLog.copy')}
112
+ </button>
113
+ </div>
114
+
115
+ {/* Meta info line */}
116
+ <div className={styles.metaLine}>
117
+ <span>{detail.request_method} {urlPath}</span>
118
+ <span className={detail.error ? styles.statusError : styles.statusOk}>{statusText}</span>
119
+ <span>{formatDuration(detail.duration_ms)}</span>
120
+ <span>{formatTokenCount(detail.prompt_tokens, detail.completion_tokens)}</span>
121
+ </div>
122
+
123
+ {/* Error banner */}
124
+ {detail.error && (
125
+ <div className={styles.errorBanner}>{detail.error}</div>
126
+ )}
127
+
128
+ {/* Content area */}
129
+ <div className={styles.detailContent}>
130
+ {tab === 'request' && (
131
+ <pre className={styles.jsonBlock}>{tryFormatJson(detail.request_body)}</pre>
132
+ )}
133
+ {tab === 'response' && (
134
+ <pre className={styles.jsonBlock}>{detail.response_body ? tryFormatJson(detail.response_body) : '(empty)'}</pre>
135
+ )}
136
+ {tab === 'headers' && (
137
+ <div className={styles.headersBlock}>
138
+ <h4>{t('aiLog.requestHeaders')}</h4>
139
+ <table className={styles.headersTable}>
140
+ <tbody>
141
+ {formatHeaders(detail.request_headers).map(([k, v]) => (
142
+ <tr key={`req-${k}`}><td className={styles.headerKey}>{k}</td><td>{v}</td></tr>
143
+ ))}
144
+ </tbody>
145
+ </table>
146
+ <h4>{t('aiLog.responseHeaders')}</h4>
147
+ <table className={styles.headersTable}>
148
+ <tbody>
149
+ {formatHeaders(detail.response_headers ?? null).map(([k, v]) => (
150
+ <tr key={`res-${k}`}><td className={styles.headerKey}>{k}</td><td>{v}</td></tr>
151
+ ))}
152
+ </tbody>
153
+ </table>
154
+ </div>
155
+ )}
156
+ {tab === 'meta' && (
157
+ <table className={styles.metaTable}>
158
+ <tbody>
159
+ <tr><td>Provider</td><td>{detail.provider}</td></tr>
160
+ <tr><td>Model</td><td>{detail.model}</td></tr>
161
+ <tr><td>Type</td><td>{detail.type}</td></tr>
162
+ <tr><td>Session ID</td><td>{detail.session_id ?? '--'}</td></tr>
163
+ <tr><td>Report ID</td><td>{detail.report_id ?? '--'}</td></tr>
164
+ <tr><td>Prompt Tokens</td><td>{detail.prompt_tokens}</td></tr>
165
+ <tr><td>Completion Tokens</td><td>{detail.completion_tokens}</td></tr>
166
+ <tr><td>Created</td><td>{new Date(detail.created_at).toLocaleString()}</td></tr>
167
+ </tbody>
168
+ </table>
169
+ )}
170
+ </div>
171
+ </div>
172
+ )
173
+ }
@@ -0,0 +1,101 @@
1
+ import React, { useMemo, useState } from 'react'
2
+ import { useLocale } from '../i18n'
3
+ import type { AiRequestLog } from '@shared/types'
4
+ import styles from './AiLogView.module.css'
5
+
6
+ interface AiLogListProps {
7
+ logs: AiRequestLog[]
8
+ selectedId: number | null
9
+ onSelect: (id: number) => void
10
+ }
11
+
12
+ type FilterType = 'all' | 'analyze' | 'chat' | 'filter'
13
+
14
+ function formatDuration(ms: number | null): string {
15
+ if (ms === null) return '--'
16
+ if (ms < 1000) return `${ms}ms`
17
+ return `${(ms / 1000).toFixed(1)}s`
18
+ }
19
+
20
+ function formatTokenCount(prompt: number, completion: number): string {
21
+ const total = prompt + completion
22
+ if (total === 0) return '--'
23
+ if (total >= 1000) return `${(total / 1000).toFixed(1)}k`
24
+ return String(total)
25
+ }
26
+
27
+ export const AiLogList: React.FC<AiLogListProps> = ({ logs, selectedId, onSelect }) => {
28
+ const { t } = useLocale()
29
+ const [filterType, setFilterType] = useState<FilterType>('all')
30
+ const [search, setSearch] = useState('')
31
+
32
+ const filteredLogs = useMemo(() => {
33
+ let result = logs
34
+ if (filterType !== 'all') {
35
+ result = result.filter(l => l.type === filterType)
36
+ }
37
+ if (search.trim()) {
38
+ const q = search.toLowerCase()
39
+ result = result.filter(l =>
40
+ l.model.toLowerCase().includes(q) ||
41
+ l.request_url.toLowerCase().includes(q) ||
42
+ l.provider.toLowerCase().includes(q)
43
+ )
44
+ }
45
+ return result
46
+ }, [logs, filterType, search])
47
+
48
+ const filterOptions: { key: FilterType; label: string }[] = [
49
+ { key: 'all', label: `${t('aiLog.filterAll')} (${logs.length})` },
50
+ { key: 'analyze', label: t('aiLog.filterAnalyze') },
51
+ { key: 'chat', label: t('aiLog.filterChat') },
52
+ { key: 'filter', label: t('aiLog.filterFilter') },
53
+ ]
54
+
55
+ return (
56
+ <div className={styles.listPanel}>
57
+ {/* Filter tags */}
58
+ <div className={styles.filterRow}>
59
+ {filterOptions.map(opt => (
60
+ <button
61
+ key={opt.key}
62
+ className={`${styles.filterTag} ${filterType === opt.key ? styles.filterTagActive : ''}`}
63
+ onClick={() => setFilterType(opt.key)}
64
+ >
65
+ {opt.label}
66
+ </button>
67
+ ))}
68
+ </div>
69
+
70
+ {/* Search */}
71
+ <input
72
+ className={styles.searchInput}
73
+ placeholder={t('aiLog.searchPlaceholder')}
74
+ value={search}
75
+ onChange={e => setSearch(e.target.value)}
76
+ />
77
+
78
+ {/* List */}
79
+ <div className={styles.listScroll}>
80
+ {filteredLogs.map(log => (
81
+ <div
82
+ key={log.id}
83
+ className={`${styles.listItem} ${log.id === selectedId ? styles.listItemActive : ''} ${log.error ? styles.listItemError : ''}`}
84
+ onClick={() => onSelect(log.id)}
85
+ >
86
+ <span className={styles.listItemLeft}>
87
+ #{log.id} {log.type} — {log.model}
88
+ {log.error && log.status_code ? ` ✗ ${log.status_code}` : ''}
89
+ </span>
90
+ <span className={styles.listItemRight}>
91
+ {formatDuration(log.duration_ms)} · {formatTokenCount(log.prompt_tokens, log.completion_tokens)}
92
+ </span>
93
+ </div>
94
+ ))}
95
+ {filteredLogs.length === 0 && (
96
+ <div className={styles.emptyList}>{t('aiLog.noData')}</div>
97
+ )}
98
+ </div>
99
+ </div>
100
+ )
101
+ }
@@ -0,0 +1,364 @@
1
+ /* ==============================
2
+ AI Request Log View
3
+ Follows tokens.css design system
4
+ ============================== */
5
+
6
+ .container {
7
+ display: flex;
8
+ flex-direction: column;
9
+ height: 100%;
10
+ overflow: hidden;
11
+ color: var(--text-primary);
12
+ background: var(--color-content);
13
+ }
14
+
15
+ /* Toolbar */
16
+ .toolbar {
17
+ display: flex;
18
+ align-items: center;
19
+ gap: var(--space-lg);
20
+ padding: 0 var(--space-xl);
21
+ height: 38px;
22
+ min-height: 38px;
23
+ border-bottom: 1px solid var(--color-border);
24
+ background: var(--color-bar);
25
+ font-size: var(--font-size-2xs);
26
+ }
27
+
28
+ .backBtn {
29
+ background: var(--color-accent-bg);
30
+ color: var(--color-accent);
31
+ border: 1px solid var(--color-accent-border);
32
+ border-radius: var(--radius-button);
33
+ padding: var(--space-2xs) var(--space-lg);
34
+ font-size: var(--font-size-3xs);
35
+ font-family: var(--font-sans);
36
+ cursor: pointer;
37
+ white-space: nowrap;
38
+ transition: border-color var(--transition-fast), color var(--transition-fast);
39
+ }
40
+ .backBtn:hover {
41
+ border-color: var(--color-accent);
42
+ color: var(--text-primary);
43
+ }
44
+
45
+ .toolbarSpacer { flex: 1; }
46
+
47
+ .sessionLabel {
48
+ font-size: var(--font-size-3xs);
49
+ color: var(--text-muted);
50
+ }
51
+
52
+ .globalLabel {
53
+ font-size: var(--font-size-3xs);
54
+ color: var(--color-accent);
55
+ font-weight: 600;
56
+ }
57
+
58
+ .toggleBtn {
59
+ background: none;
60
+ border: none;
61
+ color: var(--color-accent);
62
+ font-size: var(--font-size-3xs);
63
+ font-family: var(--font-sans);
64
+ cursor: pointer;
65
+ padding: var(--space-2xs) var(--space-sm);
66
+ transition: color var(--transition-fast);
67
+ }
68
+ .toggleBtn:hover {
69
+ color: var(--text-primary);
70
+ text-decoration: underline;
71
+ }
72
+
73
+ /* Main content split */
74
+ .mainContent {
75
+ display: flex;
76
+ flex: 1;
77
+ overflow: hidden;
78
+ }
79
+
80
+ /* ---- List Panel (left 42%) ---- */
81
+ .listPanel {
82
+ width: 42%;
83
+ min-width: 240px;
84
+ border-right: 1px solid var(--color-border);
85
+ background: var(--color-sidebar);
86
+ display: flex;
87
+ flex-direction: column;
88
+ overflow: hidden;
89
+ }
90
+
91
+ .filterRow {
92
+ display: flex;
93
+ gap: var(--space-sm);
94
+ padding: var(--space-md) var(--space-md) var(--space-xs);
95
+ flex-wrap: wrap;
96
+ }
97
+
98
+ .filterTag {
99
+ background: var(--color-surface);
100
+ border: 1px solid var(--color-border-subtle);
101
+ border-radius: var(--radius-badge);
102
+ padding: var(--space-2xs) var(--space-md);
103
+ font-size: var(--font-size-3xs);
104
+ font-family: var(--font-sans);
105
+ cursor: pointer;
106
+ color: var(--text-secondary);
107
+ transition: border-color var(--transition-fast), color var(--transition-fast);
108
+ }
109
+ .filterTag:hover {
110
+ border-color: var(--color-border-hover);
111
+ color: var(--text-primary);
112
+ }
113
+ .filterTagActive {
114
+ background: var(--color-accent-bg);
115
+ color: var(--color-accent);
116
+ border-color: var(--color-accent-border);
117
+ }
118
+
119
+ .searchInput {
120
+ margin: var(--space-xs) var(--space-md) var(--space-md);
121
+ background: var(--color-surface);
122
+ border: 1px solid var(--color-border-subtle);
123
+ border-radius: var(--radius-badge);
124
+ padding: var(--space-xs) var(--space-md);
125
+ font-size: var(--font-size-3xs);
126
+ font-family: var(--font-sans);
127
+ color: var(--text-secondary);
128
+ outline: none;
129
+ transition: border-color var(--transition-fast);
130
+ }
131
+ .searchInput:focus {
132
+ border-color: var(--color-accent);
133
+ }
134
+ .searchInput::placeholder {
135
+ color: var(--text-disabled);
136
+ }
137
+
138
+ .listScroll {
139
+ flex: 1;
140
+ overflow-y: auto;
141
+ padding: 0 var(--space-xs) var(--space-xs);
142
+ }
143
+
144
+ .listItem {
145
+ display: flex;
146
+ justify-content: space-between;
147
+ align-items: center;
148
+ padding: var(--space-sm) var(--space-md);
149
+ margin-bottom: var(--space-2xs);
150
+ border-radius: var(--radius-badge);
151
+ font-size: var(--font-size-3xs);
152
+ cursor: pointer;
153
+ color: var(--text-secondary);
154
+ gap: var(--space-md);
155
+ transition: background var(--transition-fast), color var(--transition-fast);
156
+ }
157
+ .listItem:hover {
158
+ background: var(--color-hover);
159
+ color: var(--text-primary);
160
+ }
161
+
162
+ .listItemActive {
163
+ background: var(--color-accent-bg) !important;
164
+ color: var(--color-accent) !important;
165
+ border: 1px solid var(--color-accent-border);
166
+ }
167
+
168
+ .listItemError {
169
+ color: var(--color-error);
170
+ }
171
+ .listItemActive.listItemError {
172
+ color: var(--color-error);
173
+ }
174
+
175
+ .listItemLeft {
176
+ overflow: hidden;
177
+ text-overflow: ellipsis;
178
+ white-space: nowrap;
179
+ flex: 1;
180
+ }
181
+
182
+ .listItemRight {
183
+ white-space: nowrap;
184
+ font-size: var(--font-size-3xs);
185
+ color: var(--text-muted);
186
+ }
187
+ .listItemActive .listItemRight {
188
+ color: var(--color-accent);
189
+ opacity: 0.8;
190
+ }
191
+
192
+ .emptyList {
193
+ padding: var(--space-3xl) var(--space-lg);
194
+ text-align: center;
195
+ font-size: var(--font-size-2xs);
196
+ color: var(--text-disabled);
197
+ }
198
+
199
+ /* ---- Detail Panel (right 58%) ---- */
200
+ .detailPanel {
201
+ flex: 1;
202
+ display: flex;
203
+ flex-direction: column;
204
+ overflow: hidden;
205
+ background: var(--color-content);
206
+ color: var(--text-secondary);
207
+ }
208
+
209
+ .detailEmpty {
210
+ flex: 1;
211
+ display: flex;
212
+ align-items: center;
213
+ justify-content: center;
214
+ font-size: var(--font-size-xs);
215
+ color: var(--text-disabled);
216
+ }
217
+
218
+ .detailTabBar {
219
+ display: flex;
220
+ align-items: center;
221
+ gap: var(--space-xs);
222
+ padding: var(--space-sm) var(--space-md);
223
+ border-bottom: 1px solid var(--color-border);
224
+ background: var(--color-bar);
225
+ }
226
+
227
+ .detailTab {
228
+ background: var(--color-surface);
229
+ border: 1px solid var(--color-border-subtle);
230
+ border-radius: var(--radius-badge);
231
+ padding: var(--space-2xs) var(--space-lg);
232
+ font-size: var(--font-size-3xs);
233
+ font-family: var(--font-sans);
234
+ cursor: pointer;
235
+ color: var(--text-secondary);
236
+ transition: border-color var(--transition-fast), color var(--transition-fast);
237
+ }
238
+ .detailTab:hover {
239
+ border-color: var(--color-border-hover);
240
+ color: var(--text-primary);
241
+ }
242
+ .detailTabActive {
243
+ background: var(--color-accent-bg);
244
+ color: var(--color-accent);
245
+ border-color: var(--color-accent-border);
246
+ }
247
+
248
+ .detailTabSpacer { flex: 1; }
249
+
250
+ .copyBtn {
251
+ background: var(--color-surface);
252
+ border: 1px solid var(--color-border-subtle);
253
+ border-radius: var(--radius-badge);
254
+ padding: var(--space-2xs) var(--space-lg);
255
+ font-size: var(--font-size-3xs);
256
+ font-family: var(--font-sans);
257
+ cursor: pointer;
258
+ color: var(--text-secondary);
259
+ transition: border-color var(--transition-fast), color var(--transition-fast);
260
+ }
261
+ .copyBtn:hover {
262
+ border-color: var(--color-border-hover);
263
+ color: var(--text-primary);
264
+ }
265
+
266
+ /* Meta info line */
267
+ .metaLine {
268
+ display: flex;
269
+ gap: var(--space-xl);
270
+ padding: var(--space-sm) var(--space-lg);
271
+ font-size: var(--font-size-3xs);
272
+ font-family: var(--font-mono);
273
+ color: var(--text-muted);
274
+ border-bottom: 1px solid var(--color-border);
275
+ background: var(--color-surface);
276
+ }
277
+
278
+ .statusOk { color: var(--color-success); }
279
+ .statusError { color: var(--color-error); }
280
+
281
+ .errorBanner {
282
+ padding: var(--space-sm) var(--space-lg);
283
+ background: var(--color-error-bg);
284
+ color: var(--color-error);
285
+ font-size: var(--font-size-3xs);
286
+ border-bottom: 1px solid var(--color-error-border);
287
+ }
288
+
289
+ /* Content area */
290
+ .detailContent {
291
+ flex: 1;
292
+ overflow-y: auto;
293
+ padding: var(--space-md);
294
+ }
295
+
296
+ .jsonBlock {
297
+ background: var(--color-surface);
298
+ border: 1px solid var(--color-border-subtle);
299
+ border-radius: var(--radius-button);
300
+ padding: var(--space-lg);
301
+ font-size: var(--font-size-3xs);
302
+ font-family: var(--font-mono);
303
+ line-height: var(--line-height-normal);
304
+ color: var(--text-secondary);
305
+ white-space: pre-wrap;
306
+ word-break: break-all;
307
+ margin: 0;
308
+ overflow-x: auto;
309
+ }
310
+
311
+ /* Headers table */
312
+ .headersBlock h4 {
313
+ font-size: var(--font-size-3xs);
314
+ color: var(--text-label);
315
+ letter-spacing: var(--letter-spacing-label);
316
+ text-transform: uppercase;
317
+ margin: var(--space-lg) 0 var(--space-sm);
318
+ }
319
+ .headersBlock h4:first-child { margin-top: 0; }
320
+
321
+ .headersTable {
322
+ width: 100%;
323
+ font-size: var(--font-size-3xs);
324
+ border-collapse: collapse;
325
+ }
326
+ .headersTable td {
327
+ padding: var(--space-2xs) var(--space-md);
328
+ border-bottom: 1px solid var(--color-border-subtle);
329
+ vertical-align: top;
330
+ word-break: break-all;
331
+ color: var(--text-secondary);
332
+ }
333
+ .headerKey {
334
+ font-weight: 600;
335
+ white-space: nowrap;
336
+ width: 180px;
337
+ color: var(--color-accent);
338
+ font-family: var(--font-mono);
339
+ }
340
+
341
+ /* Meta table */
342
+ .metaTable {
343
+ width: 100%;
344
+ font-size: var(--font-size-3xs);
345
+ border-collapse: collapse;
346
+ }
347
+ .metaTable td {
348
+ padding: var(--space-xs) var(--space-md);
349
+ border-bottom: 1px solid var(--color-border-subtle);
350
+ color: var(--text-secondary);
351
+ }
352
+ .metaTable td:first-child {
353
+ font-weight: 600;
354
+ width: 140px;
355
+ color: var(--text-label);
356
+ }
357
+
358
+ /* Empty container */
359
+ .emptyContainer {
360
+ flex: 1;
361
+ display: flex;
362
+ align-items: center;
363
+ justify-content: center;
364
+ }