@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,245 @@
1
+ .container {
2
+ display: flex;
3
+ flex-direction: column;
4
+ flex: 1;
5
+ min-height: 0;
6
+ }
7
+
8
+ /* Section header: label + count badge */
9
+ .sectionHeader {
10
+ display: flex;
11
+ align-items: center;
12
+ justify-content: space-between;
13
+ padding: 16px 16px 8px;
14
+ }
15
+
16
+ .sectionLabel {
17
+ font-size: var(--font-size-3xs);
18
+ color: var(--text-label);
19
+ text-transform: uppercase;
20
+ letter-spacing: var(--letter-spacing-section);
21
+ font-weight: 600;
22
+ }
23
+
24
+ .sectionCount {
25
+ font-size: var(--font-size-3xs);
26
+ color: var(--text-disabled);
27
+ background: var(--color-active);
28
+ padding: 1px 6px;
29
+ border-radius: var(--radius-full);
30
+ font-variant-numeric: tabular-nums;
31
+ }
32
+
33
+ /* Session list */
34
+ .list {
35
+ flex: 1;
36
+ overflow: auto;
37
+ padding: 0 8px;
38
+ }
39
+
40
+ .list::-webkit-scrollbar {
41
+ width: 4px;
42
+ }
43
+
44
+ .list::-webkit-scrollbar-thumb {
45
+ background: var(--color-border-hover);
46
+ border-radius: var(--radius-full);
47
+ }
48
+
49
+ .list::-webkit-scrollbar-track {
50
+ background: transparent;
51
+ }
52
+
53
+ /* Session item — card style */
54
+ .item {
55
+ display: flex;
56
+ align-items: center;
57
+ padding: 10px 12px;
58
+ margin-bottom: 2px;
59
+ cursor: pointer;
60
+ border-radius: var(--radius-lg);
61
+ transition: background var(--transition-fast);
62
+ position: relative;
63
+ border-left: 3px solid transparent;
64
+ }
65
+
66
+ .item:hover {
67
+ background: var(--color-hover);
68
+ }
69
+
70
+ .itemActive {
71
+ background: var(--color-selected);
72
+ border-left-color: var(--color-accent);
73
+ }
74
+
75
+ .itemActive:hover {
76
+ background: var(--color-selected);
77
+ }
78
+
79
+ .itemActive .sessionName {
80
+ color: var(--text-primary);
81
+ }
82
+
83
+ /* Status dot — replaces old bar */
84
+ .statusDot {
85
+ width: 7px;
86
+ height: 7px;
87
+ border-radius: var(--radius-full);
88
+ flex-shrink: 0;
89
+ margin-right: 10px;
90
+ box-shadow: 0 0 0 2px var(--color-sidebar);
91
+ }
92
+
93
+ /* Running dot pulse animation */
94
+ .statusDotRunning {
95
+ animation: pulse 2s ease-in-out infinite;
96
+ }
97
+
98
+ @keyframes pulse {
99
+ 0%, 100% { opacity: 1; box-shadow: 0 0 0 2px var(--color-sidebar); }
100
+ 50% { opacity: 0.6; box-shadow: 0 0 0 2px var(--color-sidebar), 0 0 6px currentColor; }
101
+ }
102
+
103
+ .sessionInfo {
104
+ flex: 1;
105
+ min-width: 0;
106
+ }
107
+
108
+ .sessionName {
109
+ font-size: var(--font-size-2xs);
110
+ color: var(--text-secondary);
111
+ font-weight: 500;
112
+ overflow: hidden;
113
+ text-overflow: ellipsis;
114
+ white-space: nowrap;
115
+ line-height: var(--line-height-tight);
116
+ }
117
+
118
+ .sessionMeta {
119
+ font-size: var(--font-size-3xs);
120
+ color: var(--text-muted);
121
+ display: flex;
122
+ align-items: center;
123
+ gap: 4px;
124
+ margin-top: 3px;
125
+ line-height: 1;
126
+ }
127
+
128
+ .sessionCount {
129
+ color: var(--text-muted);
130
+ }
131
+
132
+ .sessionUrl {
133
+ font-size: var(--font-size-3xs);
134
+ color: var(--text-disabled);
135
+ overflow: hidden;
136
+ text-overflow: ellipsis;
137
+ white-space: nowrap;
138
+ margin-top: 2px;
139
+ }
140
+
141
+ /* Delete button */
142
+ .deleteBtn {
143
+ flex-shrink: 0;
144
+ margin-left: var(--space-sm);
145
+ padding: 4px;
146
+ border-radius: var(--radius-badge);
147
+ color: var(--text-disabled);
148
+ cursor: pointer;
149
+ transition: color var(--transition-fast), background var(--transition-fast);
150
+ display: flex;
151
+ align-items: center;
152
+ }
153
+
154
+ .deleteBtn:hover {
155
+ color: var(--color-error);
156
+ background: var(--color-error-bg);
157
+ }
158
+
159
+ .deleteBtnDisabled {
160
+ cursor: wait;
161
+ color: var(--text-disabled);
162
+ }
163
+
164
+ /* Footer: New session button */
165
+ .footer {
166
+ padding: 10px 12px;
167
+ flex-shrink: 0;
168
+ }
169
+
170
+ .newBtn {
171
+ border: 1px solid var(--color-accent-border);
172
+ border-radius: var(--radius-lg);
173
+ padding: 9px 0;
174
+ text-align: center;
175
+ font-size: var(--font-size-2xs);
176
+ color: var(--color-accent);
177
+ background: var(--color-accent-bg);
178
+ cursor: pointer;
179
+ font-weight: 600;
180
+ transition: background var(--transition-fast), border-color var(--transition-fast), transform var(--transition-fast);
181
+ }
182
+
183
+ .newBtn:hover {
184
+ background: var(--color-accent-border);
185
+ border-color: var(--color-accent);
186
+ }
187
+
188
+ .newBtn:active {
189
+ transform: scale(0.98);
190
+ }
191
+
192
+ /* Bottom: Settings + Version */
193
+ .sidebarBottom {
194
+ padding: 10px 16px;
195
+ border-top: 1px solid var(--color-border);
196
+ flex-shrink: 0;
197
+ display: flex;
198
+ align-items: center;
199
+ justify-content: space-between;
200
+ }
201
+
202
+ .bottomBtn {
203
+ font-size: var(--font-size-2xs);
204
+ color: var(--text-muted);
205
+ cursor: pointer;
206
+ padding: 2px 6px;
207
+ border-radius: var(--radius-badge);
208
+ transition: color var(--transition-fast), background var(--transition-fast);
209
+ }
210
+
211
+ .bottomBtn:hover {
212
+ color: var(--text-secondary);
213
+ background: var(--color-hover);
214
+ }
215
+
216
+ .versionText {
217
+ font-size: var(--font-size-3xs);
218
+ color: var(--text-disabled);
219
+ font-variant-numeric: tabular-nums;
220
+ }
221
+
222
+ /* Modal form */
223
+ .formGroup {
224
+ margin-bottom: var(--space-lg);
225
+ }
226
+
227
+ .formLabel {
228
+ display: block;
229
+ font-size: var(--font-size-sm);
230
+ color: var(--text-secondary);
231
+ margin-bottom: var(--space-xs);
232
+ font-weight: 500;
233
+ }
234
+
235
+ .formHint {
236
+ font-size: var(--font-size-2xs);
237
+ color: var(--text-muted);
238
+ margin-top: var(--space-xs);
239
+ }
240
+
241
+ .formError {
242
+ font-size: var(--font-size-2xs);
243
+ color: var(--color-error);
244
+ margin-top: var(--space-xs);
245
+ }
@@ -0,0 +1,247 @@
1
+ import React, { useState, useEffect, useCallback } from 'react'
2
+ import { Button, Input, Modal, Empty } from '../ui'
3
+ import { IconPlus, IconDelete } from '../ui/Icons'
4
+ import { useLocale } from '../i18n'
5
+ import type { Session } from '../../shared/types'
6
+ import styles from './SessionList.module.css'
7
+
8
+ interface SessionListProps {
9
+ sessions: Session[]
10
+ currentSessionId: string | null
11
+ onSelect: (id: string) => void
12
+ onCreate: (name: string, url: string) => Promise<void>
13
+ onDelete: (id: string) => Promise<void>
14
+ onOpenSettings: () => void
15
+ activeRequestCount?: number
16
+ /** Incrementing counter to trigger open-create-modal from outside */
17
+ createTrigger?: number
18
+ }
19
+
20
+ /**
21
+ * Session status dot color
22
+ */
23
+ function getDotColor(session: Session): string {
24
+ if (session.status === 'running') return 'var(--color-success)'
25
+ if (session.status === 'paused') return 'var(--color-warning)'
26
+ return 'var(--text-disabled)'
27
+ }
28
+
29
+ function getStatusInfo(session: Session): { symbol: string; color: string; labelKey: string } {
30
+ if (session.status === 'running') return { symbol: '●', color: 'var(--color-success)', labelKey: 'capture.running' }
31
+ if (session.status === 'paused') return { symbol: '⏸', color: 'var(--color-warning)', labelKey: 'capture.paused' }
32
+ return { symbol: '■', color: 'var(--text-muted)', labelKey: 'capture.stopped' }
33
+ }
34
+
35
+ function extractDomain(url: string): string {
36
+ if (!url) return ''
37
+ try { return new URL(url).hostname } catch { return url }
38
+ }
39
+
40
+ const SessionList: React.FC<SessionListProps> = ({
41
+ sessions,
42
+ currentSessionId,
43
+ onSelect,
44
+ onCreate,
45
+ onDelete,
46
+ onOpenSettings,
47
+ activeRequestCount = 0,
48
+ createTrigger = 0,
49
+ }) => {
50
+ const { t } = useLocale()
51
+ const [modalOpen, setModalOpen] = useState(false)
52
+ const [creating, setCreating] = useState(false)
53
+ const [hoveredId, setHoveredId] = useState<string | null>(null)
54
+ const [deletingId, setDeletingId] = useState<string | null>(null)
55
+
56
+ const [formName, setFormName] = useState('')
57
+ const [formUrl, setFormUrl] = useState('')
58
+ const [nameError, setNameError] = useState('')
59
+ const [urlError, setUrlError] = useState('')
60
+ const [appVersion, setAppVersion] = useState('')
61
+
62
+ useEffect(() => {
63
+ window.electronAPI.getAppVersion().then(v => setAppVersion(v))
64
+ }, [])
65
+
66
+ const openModal = () => {
67
+ setModalOpen(true)
68
+ window.electronAPI.setTargetViewVisible(false)
69
+ }
70
+
71
+ // Open create modal when triggered externally
72
+ useEffect(() => {
73
+ if (createTrigger > 0) openModal()
74
+ }, [createTrigger]) // eslint-disable-line react-hooks/exhaustive-deps
75
+
76
+ const closeModal = () => {
77
+ setModalOpen(false)
78
+ setFormName('')
79
+ setFormUrl('')
80
+ setNameError('')
81
+ setUrlError('')
82
+ // Only restore WebContentsView if a session is selected;
83
+ // otherwise the empty guide needs to stay clickable
84
+ if (currentSessionId) {
85
+ window.electronAPI.setTargetViewVisible(true)
86
+ }
87
+ }
88
+
89
+ const validate = (): boolean => {
90
+ let valid = true
91
+ if (!formName.trim()) {
92
+ setNameError('Please enter a session name')
93
+ valid = false
94
+ } else {
95
+ setNameError('')
96
+ }
97
+ if (formUrl.trim()) {
98
+ try {
99
+ new URL(formUrl)
100
+ setUrlError('')
101
+ } catch {
102
+ setUrlError('Please enter a valid URL')
103
+ valid = false
104
+ }
105
+ } else {
106
+ setUrlError('')
107
+ }
108
+ return valid
109
+ }
110
+
111
+ const handleCreate = async () => {
112
+ if (!validate()) return
113
+ setCreating(true)
114
+ try {
115
+ await onCreate(formName.trim(), formUrl.trim())
116
+ closeModal()
117
+ } catch {
118
+ // create failed
119
+ } finally {
120
+ setCreating(false)
121
+ }
122
+ }
123
+
124
+ const handleDelete = async (e: React.MouseEvent, id: string) => {
125
+ e.stopPropagation()
126
+ setDeletingId(id)
127
+ try {
128
+ await onDelete(id)
129
+ } catch {
130
+ // delete failed
131
+ } finally {
132
+ setDeletingId(null)
133
+ }
134
+ }
135
+
136
+ return (
137
+ <div className={styles.container}>
138
+ {/* Section header with count */}
139
+ <div className={styles.sectionHeader}>
140
+ <span className={styles.sectionLabel}>SESSIONS</span>
141
+ {sessions.length > 0 && (
142
+ <span className={styles.sectionCount}>{sessions.length}</span>
143
+ )}
144
+ </div>
145
+
146
+ {/* Session list */}
147
+ <div className={styles.list}>
148
+ {sessions.length === 0 ? (
149
+ <Empty description="No sessions" style={{ marginTop: 40 }} />
150
+ ) : (
151
+ sessions.map((session) => {
152
+ const isActive = session.id === currentSessionId
153
+ const isHovered = session.id === hoveredId
154
+ const dotColor = getDotColor(session)
155
+ const status = getStatusInfo(session)
156
+ const domain = extractDomain(session.target_url)
157
+ return (
158
+ <div
159
+ key={session.id}
160
+ className={`${styles.item} ${isActive ? styles.itemActive : ''}`}
161
+ onClick={() => onSelect(session.id)}
162
+ onMouseEnter={() => setHoveredId(session.id)}
163
+ onMouseLeave={() => setHoveredId(null)}
164
+ >
165
+ <div
166
+ className={`${styles.statusDot} ${session.status === 'running' ? styles.statusDotRunning : ''}`}
167
+ style={{ background: dotColor, color: dotColor }}
168
+ />
169
+ <div className={styles.sessionInfo}>
170
+ <div className={styles.sessionName}>{session.name}</div>
171
+ <div className={styles.sessionMeta}>
172
+ <span style={{ color: status.color }}>{status.symbol} {t(status.labelKey as any)}</span>
173
+ {isActive && activeRequestCount > 0 && (
174
+ <span className={styles.sessionCount}> · {activeRequestCount} reqs</span>
175
+ )}
176
+ </div>
177
+ {domain && <div className={styles.sessionUrl}>{domain}</div>}
178
+ </div>
179
+
180
+ {isHovered && (
181
+ <span
182
+ className={`${styles.deleteBtn} ${deletingId === session.id ? styles.deleteBtnDisabled : ''}`}
183
+ onClick={(e) => handleDelete(e, session.id)}
184
+ >
185
+ <IconDelete size={13} />
186
+ </span>
187
+ )}
188
+ </div>
189
+ )
190
+ })
191
+ )}
192
+ </div>
193
+
194
+ {/* New session button */}
195
+ <div className={styles.footer}>
196
+ <div className={styles.newBtn} onClick={openModal}>
197
+ + {t('session.newSession').replace('+ ', '')}
198
+ </div>
199
+ </div>
200
+
201
+ {/* Bottom: Settings + Version */}
202
+ <div className={styles.sidebarBottom}>
203
+ <div className={styles.bottomBtn} onClick={onOpenSettings}>⚙ {t('settings.title')}</div>
204
+ <div className={styles.versionText}>v{appVersion}</div>
205
+ </div>
206
+
207
+ {/* Create session modal */}
208
+ <Modal
209
+ open={modalOpen}
210
+ onClose={closeModal}
211
+ title={t('session.createTitle')}
212
+ footer={
213
+ <>
214
+ <Button onClick={closeModal}>{t('session.cancel')}</Button>
215
+ <Button variant="primary" onClick={handleCreate} loading={creating}>
216
+ {t('session.create')}
217
+ </Button>
218
+ </>
219
+ }
220
+ >
221
+ <div className={styles.formGroup}>
222
+ <label className={styles.formLabel}>{t('session.name')}</label>
223
+ <Input
224
+ value={formName}
225
+ onChange={(e) => setFormName(e.target.value)}
226
+ placeholder={t('session.namePlaceholder')}
227
+ autoFocus
228
+ onKeyDown={(e) => e.key === 'Enter' && handleCreate()}
229
+ />
230
+ {nameError && <div className={styles.formError}>{nameError}</div>}
231
+ </div>
232
+ <div className={styles.formGroup}>
233
+ <label className={styles.formLabel}>{t('session.targetUrl')}</label>
234
+ <Input
235
+ value={formUrl}
236
+ onChange={(e) => setFormUrl(e.target.value)}
237
+ placeholder={t('session.targetUrlPlaceholder')}
238
+ />
239
+ <div className={styles.formHint}>Leave empty to capture traffic via proxy only</div>
240
+ {urlError && <div className={styles.formError}>{urlError}</div>}
241
+ </div>
242
+ </Modal>
243
+ </div>
244
+ )
245
+ }
246
+
247
+ export default SessionList
@@ -0,0 +1,100 @@
1
+ import React, { useState } from 'react'
2
+ import { Modal } from '../ui'
3
+ import { IconApp, IconRobot, IconGlobe, IconBolt, IconShield, IconCode } from '../ui/Icons'
4
+ import GeneralSection from './settings/GeneralSection'
5
+ import LLMSection from './settings/LLMSection'
6
+ import ProxySection from './settings/ProxySection'
7
+ import MCPServerSection from './settings/MCPServerSection'
8
+ import MitmProxySection from './settings/MitmProxySection'
9
+ import FingerprintSection from './settings/FingerprintSection'
10
+
11
+ type SettingsSection = 'general' | 'llm' | 'proxy' | 'mcp-server' | 'mitm-proxy' | 'fingerprint'
12
+
13
+ const menuItems: { key: SettingsSection; icon: React.FC<{ size?: number | string }>; label: string }[] = [
14
+ { key: 'general', icon: IconApp, label: '通用' },
15
+ { key: 'llm', icon: IconRobot, label: 'LLM' },
16
+ { key: 'proxy', icon: IconGlobe, label: '代理' },
17
+ { key: 'mcp-server', icon: IconBolt, label: 'MCP Server' },
18
+ { key: 'mitm-proxy', icon: IconShield, label: 'MITM 代理' },
19
+ { key: 'fingerprint', icon: IconCode, label: '指纹' },
20
+ ]
21
+
22
+ const sectionComponents: Record<SettingsSection, React.ComponentType> = {
23
+ 'general': GeneralSection,
24
+ 'llm': LLMSection,
25
+ 'proxy': ProxySection,
26
+ 'mcp-server': MCPServerSection,
27
+ 'mitm-proxy': MitmProxySection,
28
+ 'fingerprint': FingerprintSection as React.ComponentType,
29
+ }
30
+
31
+ interface Props { open: boolean; onClose: () => void; currentSessionId?: string | null }
32
+
33
+ export default function SettingsModal({ open, onClose, currentSessionId }: Props) {
34
+ const [activeSection, setActiveSection] = useState<SettingsSection>('general')
35
+
36
+ const ActiveComponent = sectionComponents[activeSection]
37
+
38
+ return (
39
+ <Modal
40
+ title="Settings"
41
+ open={open}
42
+ onClose={onClose}
43
+ width={900}
44
+ >
45
+ {/* Negate modal body padding so sidebar border runs edge-to-edge */}
46
+ <div style={{ margin: '-16px -24px', display: 'flex', height: 560, overflow: 'hidden' }}>
47
+ {/* Left sidebar navigation */}
48
+ <div style={{
49
+ width: 180,
50
+ borderRight: '1px solid var(--color-border)',
51
+ paddingTop: 12,
52
+ paddingBottom: 12,
53
+ flexShrink: 0,
54
+ overflow: 'auto',
55
+ }}>
56
+ {menuItems.map(item => {
57
+ const Icon = item.icon
58
+ const isActive = activeSection === item.key
59
+ return (
60
+ <div
61
+ key={item.key}
62
+ onClick={() => setActiveSection(item.key)}
63
+ style={{
64
+ padding: '8px 16px',
65
+ cursor: 'pointer',
66
+ display: 'flex',
67
+ alignItems: 'center',
68
+ gap: 10,
69
+ borderRadius: 6,
70
+ margin: '2px 8px',
71
+ fontSize: 'var(--font-size-base)',
72
+ background: isActive ? 'var(--color-accent-bg)' : 'transparent',
73
+ color: isActive ? 'var(--color-accent)' : 'var(--text-secondary)',
74
+ transition: 'background 0.2s',
75
+ }}
76
+ onMouseEnter={e => {
77
+ if (!isActive) (e.currentTarget.style.background = 'var(--color-hover)')
78
+ }}
79
+ onMouseLeave={e => {
80
+ if (!isActive) (e.currentTarget.style.background = 'transparent')
81
+ }}
82
+ >
83
+ <Icon size={16} />
84
+ <span style={{ color: 'inherit', fontSize: 'inherit' }}>{item.label}</span>
85
+ </div>
86
+ )
87
+ })}
88
+ </div>
89
+
90
+ {/* Right content area */}
91
+ <div style={{ flex: 1, padding: 24, overflowY: 'auto' }}>
92
+ {activeSection === 'fingerprint'
93
+ ? <FingerprintSection currentSessionId={currentSessionId} />
94
+ : <ActiveComponent />
95
+ }
96
+ </div>
97
+ </div>
98
+ </Modal>
99
+ )
100
+ }
@@ -0,0 +1,44 @@
1
+ .statusBar {
2
+ display: flex;
3
+ align-items: center;
4
+ height: var(--statusbar-height);
5
+ background: var(--color-sidebar);
6
+ border-top: 1px solid var(--color-border);
7
+ padding: 0 var(--space-lg);
8
+ gap: var(--space-xl);
9
+ flex-shrink: 0;
10
+ }
11
+
12
+ .item {
13
+ display: flex;
14
+ align-items: center;
15
+ gap: 5px;
16
+ font-size: var(--font-size-3xs);
17
+ }
18
+
19
+ .dot {
20
+ width: 5px;
21
+ height: 5px;
22
+ border-radius: var(--radius-full);
23
+ }
24
+
25
+ .label {
26
+ color: var(--text-label);
27
+ }
28
+
29
+ .value {
30
+ color: var(--text-secondary);
31
+ }
32
+
33
+ .spacer {
34
+ flex: 1;
35
+ }
36
+
37
+ .pulse {
38
+ animation: pulse 2s ease-in-out infinite;
39
+ }
40
+
41
+ @keyframes pulse {
42
+ 0%, 100% { opacity: 1; }
43
+ 50% { opacity: 0.4; }
44
+ }