@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,164 @@
1
+ import { useEffect, useState, useCallback } from 'react'
2
+ import { Button, Progress, Tag } from '../../ui'
3
+ import {
4
+ IconSync,
5
+ IconLoading,
6
+ IconCheckCircle,
7
+ IconCloseCircle,
8
+ IconCloudDownload,
9
+ IconEdit,
10
+ IconApi,
11
+ IconGitHub,
12
+ IconFileText,
13
+ IconExport,
14
+ } from '../../ui/Icons'
15
+ import type { UpdateStatus } from '@shared/types'
16
+ import PromptTemplateModal from '../PromptTemplateModal'
17
+ import MCPServerModal from '../MCPServerModal'
18
+
19
+ export default function GeneralSection() {
20
+ const [appVersion, setAppVersion] = useState('')
21
+ const [updateStatus, setUpdateStatus] = useState<UpdateStatus>({ state: 'idle' })
22
+ const [templateModalOpen, setTemplateModalOpen] = useState(false)
23
+ const [mcpModalOpen, setMcpModalOpen] = useState(false)
24
+
25
+ useEffect(() => {
26
+ window.electronAPI.getAppVersion().then(setAppVersion)
27
+ }, [])
28
+
29
+ // Subscribe to update status events
30
+ useEffect(() => {
31
+ window.electronAPI.onUpdateStatus((status: UpdateStatus) => {
32
+ setUpdateStatus(status)
33
+ })
34
+ return () => {
35
+ window.electronAPI.removeAllListeners('update:status')
36
+ }
37
+ }, [])
38
+
39
+ const handleCheckUpdate = useCallback(() => {
40
+ setUpdateStatus({ state: 'checking' })
41
+ window.electronAPI.checkForUpdate()
42
+ }, [])
43
+
44
+ const handleInstallUpdate = useCallback(() => {
45
+ window.electronAPI.installUpdate()
46
+ }, [])
47
+
48
+ return (
49
+ <>
50
+ {/* About & Version */}
51
+ <div style={{ marginBottom: 16, display: 'flex', alignItems: 'center', gap: 10 }}>
52
+ <span style={{ fontSize: 'var(--font-size-xl)', fontWeight: 600, color: 'var(--text-primary)' }}>
53
+ Anything Analyzer
54
+ </span>
55
+ <span style={{ color: 'var(--text-secondary)' }}>v{appVersion}</span>
56
+ <a
57
+ href="https://github.com/Mouseww/anything-analyzer"
58
+ target="_blank"
59
+ rel="noopener noreferrer"
60
+ style={{
61
+ display: 'inline-flex',
62
+ alignItems: 'center',
63
+ color: 'var(--text-muted)',
64
+ transition: 'color 0.15s',
65
+ }}
66
+ title="GitHub"
67
+ onMouseEnter={e => (e.currentTarget.style.color = 'var(--text-primary)')}
68
+ onMouseLeave={e => (e.currentTarget.style.color = 'var(--text-muted)')}
69
+ onClick={e => {
70
+ e.preventDefault()
71
+ window.electronAPI.openExternal('https://github.com/Mouseww/anything-analyzer')
72
+ }}
73
+ >
74
+ <IconGitHub size={18} />
75
+ </a>
76
+ </div>
77
+
78
+ {/* Update */}
79
+ <div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 4, flexWrap: 'wrap' }}>
80
+ {updateStatus.state === 'idle' && (
81
+ <Button size="sm" icon={<IconSync size={14} />} onClick={handleCheckUpdate}>
82
+ 检查更新
83
+ </Button>
84
+ )}
85
+ {updateStatus.state === 'checking' && (
86
+ <Button size="sm" icon={<IconLoading size={14} />} disabled>
87
+ 正在检查...
88
+ </Button>
89
+ )}
90
+ {updateStatus.state === 'not-available' && (
91
+ <>
92
+ <IconCheckCircle size={14} style={{ color: 'var(--color-success)' }} />
93
+ <span style={{ fontSize: 'var(--font-size-base)' }}>已是最新版本</span>
94
+ <Button size="sm" onClick={handleCheckUpdate}>重新检查</Button>
95
+ </>
96
+ )}
97
+ {updateStatus.state === 'available' && (
98
+ <>
99
+ <Tag color="info">v{updateStatus.info?.version} 可用</Tag>
100
+ <span style={{ color: 'var(--text-secondary)', fontSize: 'var(--font-size-base)' }}>正在下载...</span>
101
+ </>
102
+ )}
103
+ {updateStatus.state === 'downloaded' && (
104
+ <>
105
+ <IconCloudDownload size={14} style={{ color: 'var(--color-accent)' }} />
106
+ <span style={{ fontSize: 'var(--font-size-base)' }}>v{updateStatus.info?.version} 已就绪</span>
107
+ <Button variant="primary" size="sm" onClick={handleInstallUpdate}>
108
+ 立即重启更新
109
+ </Button>
110
+ </>
111
+ )}
112
+ {updateStatus.state === 'error' && (
113
+ <>
114
+ <IconCloseCircle size={14} style={{ color: 'var(--color-error)' }} />
115
+ <span style={{ color: 'var(--color-error)', fontSize: 'var(--font-size-sm)' }}>{updateStatus.error}</span>
116
+ <Button size="sm" onClick={handleCheckUpdate}>重试</Button>
117
+ </>
118
+ )}
119
+ </div>
120
+
121
+ {updateStatus.state === 'downloading' && (
122
+ <Progress
123
+ percent={Math.round(updateStatus.progress?.percent ?? 0)}
124
+ status="normal"
125
+ />
126
+ )}
127
+
128
+ <div style={{ marginTop: 24 }}>
129
+ <div style={{ fontSize: 'var(--font-size-base)', fontWeight: 600, marginBottom: 12, color: 'var(--text-primary)' }}>
130
+ 管理工具
131
+ </div>
132
+ <div style={{ display: 'flex', flexDirection: 'column', gap: 8, width: '100%' }}>
133
+ <Button icon={<IconEdit size={14} />} block onClick={() => setTemplateModalOpen(true)}>
134
+ 管理提示词模板
135
+ </Button>
136
+ <Button icon={<IconApi size={14} />} block onClick={() => setMcpModalOpen(true)}>
137
+ 管理 MCP 服务器
138
+ </Button>
139
+ </div>
140
+ </div>
141
+
142
+ {/* Error Logs */}
143
+ <div style={{ marginTop: 24 }}>
144
+ <div style={{ fontSize: 'var(--font-size-base)', fontWeight: 600, marginBottom: 12, color: 'var(--text-primary)' }}>
145
+ 错误日志
146
+ </div>
147
+ <div style={{ fontSize: 'var(--font-size-sm)', color: 'var(--text-secondary)', marginBottom: 8 }}>
148
+ 遇到问题时可导出日志文件发送给开发者以便排查
149
+ </div>
150
+ <div style={{ display: 'flex', gap: 8 }}>
151
+ <Button icon={<IconFileText size={14} />} onClick={() => window.electronAPI.openLogFolder()}>
152
+ 打开日志目录
153
+ </Button>
154
+ <Button icon={<IconExport size={14} />} onClick={() => window.electronAPI.exportLogs()}>
155
+ 导出日志
156
+ </Button>
157
+ </div>
158
+ </div>
159
+
160
+ <PromptTemplateModal open={templateModalOpen} onClose={() => setTemplateModalOpen(false)} />
161
+ <MCPServerModal open={mcpModalOpen} onClose={() => setMcpModalOpen(false)} />
162
+ </>
163
+ )
164
+ }
@@ -0,0 +1,148 @@
1
+ import { useEffect, useState } from 'react'
2
+ import { Input, PasswordInput, Select, InputNumber, Button, useToast } from '../../ui'
3
+ import type { LLMProviderConfig, LLMProviderType, OpenAIApiType } from '@shared/types'
4
+
5
+ const defaultUrls: Record<LLMProviderType, string> = {
6
+ openai: 'https://api.openai.com/v1',
7
+ anthropic: 'https://api.anthropic.com/v1',
8
+ minimax: 'https://api.minimax.io/anthropic/v1',
9
+ custom: '',
10
+ }
11
+
12
+ const labelStyle: React.CSSProperties = {
13
+ display: 'block',
14
+ marginBottom: 4,
15
+ fontSize: 'var(--font-size-sm)',
16
+ color: 'var(--text-secondary)',
17
+ }
18
+
19
+ const fieldStyle: React.CSSProperties = {
20
+ marginBottom: 16,
21
+ }
22
+
23
+ export default function LLMSection() {
24
+ const toast = useToast()
25
+ const [name, setName] = useState<LLMProviderType>('openai')
26
+ const [apiType, setApiType] = useState<OpenAIApiType | undefined>('completions')
27
+ const [baseUrl, setBaseUrl] = useState(defaultUrls.openai)
28
+ const [apiKey, setApiKey] = useState('')
29
+ const [model, setModel] = useState('')
30
+ const [maxTokens, setMaxTokens] = useState<number>(4096)
31
+
32
+ const showApiType = name === 'openai' || name === 'custom'
33
+
34
+ useEffect(() => {
35
+ window.electronAPI.getLLMConfig().then(config => {
36
+ if (config) {
37
+ setName(config.name)
38
+ setApiType(config.apiType ?? 'completions')
39
+ setBaseUrl(config.baseUrl)
40
+ setApiKey(config.apiKey)
41
+ setModel(config.model)
42
+ setMaxTokens(config.maxTokens ?? 4096)
43
+ }
44
+ })
45
+ }, [])
46
+
47
+ const handleProviderChange = (value: string) => {
48
+ const provider = value as LLMProviderType
49
+ setName(provider)
50
+ setBaseUrl(defaultUrls[provider])
51
+ if (provider === 'anthropic' || provider === 'minimax') {
52
+ setApiType(undefined)
53
+ } else if (!apiType) {
54
+ setApiType('completions')
55
+ }
56
+ }
57
+
58
+ const handleSave = async () => {
59
+ if (!baseUrl || !apiKey || !model) {
60
+ toast.warning('请填写必填项(Base URL、API Key、Model)')
61
+ return
62
+ }
63
+ const config: LLMProviderConfig = {
64
+ name,
65
+ baseUrl,
66
+ apiKey,
67
+ model,
68
+ maxTokens,
69
+ ...(showApiType && apiType ? { apiType } : {}),
70
+ }
71
+ await window.electronAPI.saveLLMConfig(config)
72
+ toast.success('LLM 配置已保存')
73
+ }
74
+
75
+ return (
76
+ <div>
77
+ <div style={fieldStyle}>
78
+ <label style={labelStyle}>Provider *</label>
79
+ <Select
80
+ value={name}
81
+ onChange={handleProviderChange}
82
+ options={[
83
+ { label: 'OpenAI', value: 'openai' },
84
+ { label: 'Anthropic', value: 'anthropic' },
85
+ { label: 'MiniMax', value: 'minimax' },
86
+ { label: 'Custom (OpenAI Compatible)', value: 'custom' },
87
+ ]}
88
+ />
89
+ </div>
90
+
91
+ {showApiType && (
92
+ <div style={fieldStyle}>
93
+ <label style={labelStyle}>API Type</label>
94
+ <Select
95
+ value={apiType ?? 'completions'}
96
+ onChange={(v) => setApiType(v as OpenAIApiType)}
97
+ options={[
98
+ { label: 'Chat Completions (/chat/completions)', value: 'completions' },
99
+ { label: 'Responses (/responses)', value: 'responses' },
100
+ ]}
101
+ />
102
+ </div>
103
+ )}
104
+
105
+ <div style={fieldStyle}>
106
+ <label style={labelStyle}>Base URL *</label>
107
+ <Input
108
+ value={baseUrl}
109
+ onChange={e => setBaseUrl(e.target.value)}
110
+ placeholder="https://api.openai.com/v1"
111
+ />
112
+ </div>
113
+
114
+ <div style={fieldStyle}>
115
+ <label style={labelStyle}>API Key *</label>
116
+ <PasswordInput
117
+ value={apiKey}
118
+ onChange={e => setApiKey(e.target.value)}
119
+ placeholder="sk-..."
120
+ />
121
+ </div>
122
+
123
+ <div style={fieldStyle}>
124
+ <label style={labelStyle}>Model *</label>
125
+ <Input
126
+ value={model}
127
+ onChange={e => setModel(e.target.value)}
128
+ placeholder="gpt-4o / claude-sonnet-4-20250514 / MiniMax-M2.7 / ..."
129
+ />
130
+ </div>
131
+
132
+ <div style={fieldStyle}>
133
+ <label style={labelStyle}>Max Tokens</label>
134
+ <InputNumber
135
+ value={maxTokens}
136
+ onChange={v => v !== null && setMaxTokens(v)}
137
+ min={256}
138
+ max={128000}
139
+ style={{ width: '100%' }}
140
+ />
141
+ </div>
142
+
143
+ <Button variant="primary" block onClick={handleSave}>
144
+ 保存 LLM 配置
145
+ </Button>
146
+ </div>
147
+ )
148
+ }
@@ -0,0 +1,136 @@
1
+ import { useEffect, useState } from 'react'
2
+ import { InputNumber, Button, Input, Switch, Badge, useToast } from '../../ui'
3
+ import type { MCPServerSettings } from '@shared/types'
4
+ import { useLocale } from '../../i18n'
5
+
6
+ export default function MCPServerSection() {
7
+ const toast = useToast()
8
+ const { t } = useLocale()
9
+ const [enabled, setEnabled] = useState(false)
10
+ const [port, setPort] = useState(23816)
11
+ const [authEnabled, setAuthEnabled] = useState(true)
12
+ const [authToken, setAuthToken] = useState('')
13
+ const [running, setRunning] = useState(false)
14
+ const [tokenVisible, setTokenVisible] = useState(false)
15
+
16
+ useEffect(() => {
17
+ window.electronAPI.getMCPServerConfig().then(config => {
18
+ setEnabled(config.enabled)
19
+ setPort(config.port)
20
+ setAuthEnabled(config.authEnabled ?? true)
21
+ setAuthToken(config.authToken ?? '')
22
+ })
23
+ window.electronAPI.getMCPServerStatus().then(status => {
24
+ setRunning(status.running)
25
+ })
26
+ }, [])
27
+
28
+ const regenerateToken = () => {
29
+ setAuthToken(crypto.randomUUID())
30
+ }
31
+
32
+ const copyToken = async () => {
33
+ try {
34
+ await navigator.clipboard.writeText(authToken)
35
+ toast.success('Token copied')
36
+ } catch {
37
+ toast.error('Copy failed')
38
+ }
39
+ }
40
+
41
+ const maskedToken = authToken
42
+ ? authToken.slice(0, 8) + '••••••••' + authToken.slice(-4)
43
+ : ''
44
+
45
+ const btnStyle: React.CSSProperties = {
46
+ padding: '4px 10px',
47
+ fontSize: 'var(--font-size-2xs)',
48
+ minWidth: 'auto',
49
+ }
50
+
51
+ return (
52
+ <div style={{ display: 'flex', flexDirection: 'column', gap: 12, width: '100%' }}>
53
+ <div>
54
+ <Badge
55
+ color={running ? 'var(--color-success)' : 'var(--text-muted)'}
56
+ label={running ? '运行中' : '已停止'}
57
+ style={{ fontSize: 'var(--font-size-sm)' }}
58
+ />
59
+ </div>
60
+
61
+ <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
62
+ <span style={{ fontSize: 'var(--font-size-base)' }}>启用 MCP Server</span>
63
+ <Switch checked={enabled} onChange={setEnabled} />
64
+ </div>
65
+
66
+ {enabled && (
67
+ <>
68
+ <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
69
+ <span style={{ fontSize: 'var(--font-size-base)' }}>端口</span>
70
+ <InputNumber
71
+ min={1024}
72
+ max={65535}
73
+ value={port}
74
+ onChange={v => v !== null && setPort(v)}
75
+ style={{ width: 120 }}
76
+ />
77
+ </div>
78
+ <div style={{ fontSize: 'var(--font-size-sm)', color: 'var(--text-secondary)' }}>
79
+ 外部工具配置 URL:{' '}
80
+ <code style={{
81
+ background: 'var(--color-surface)',
82
+ padding: '2px 6px',
83
+ borderRadius: 4,
84
+ fontSize: 'var(--font-size-sm)',
85
+ fontFamily: 'var(--font-mono)',
86
+ }}>
87
+ http://localhost:{port}/mcp
88
+ </code>
89
+ </div>
90
+
91
+ {/* Auth section */}
92
+ <div style={{ borderTop: '1px solid var(--color-border)', paddingTop: 12, marginTop: 4 }}>
93
+ <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
94
+ <span style={{ fontSize: 'var(--font-size-base)' }}>鉴权</span>
95
+ <Switch checked={authEnabled} onChange={setAuthEnabled} />
96
+ </div>
97
+
98
+ {authEnabled && (
99
+ <div style={{ marginTop: 10, display: 'flex', flexDirection: 'column', gap: 8 }}>
100
+ <div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
101
+ <Input
102
+ value={tokenVisible ? authToken : maskedToken}
103
+ onChange={e => { setAuthToken(e.target.value); setTokenVisible(true) }}
104
+ onFocus={() => setTokenVisible(true)}
105
+ onBlur={() => setTokenVisible(false)}
106
+ style={{ flex: 1, fontFamily: 'var(--font-mono)', fontSize: 'var(--font-size-2xs)' }}
107
+ />
108
+ <Button onClick={copyToken} style={btnStyle}>复制</Button>
109
+ <Button onClick={regenerateToken} style={btnStyle}>重新生成</Button>
110
+ </div>
111
+ <div style={{ fontSize: 'var(--font-size-2xs)', color: 'var(--text-muted)', lineHeight: 1.5 }}>
112
+ 外部工具需在请求头中设置: <code style={{
113
+ background: 'var(--color-surface)',
114
+ padding: '1px 4px',
115
+ borderRadius: 3,
116
+ fontFamily: 'var(--font-mono)',
117
+ }}>Authorization: Bearer {'<token>'}</code>
118
+ </div>
119
+ </div>
120
+ )}
121
+ </div>
122
+ </>
123
+ )}
124
+
125
+ <Button variant="primary" block onClick={async () => {
126
+ const config: MCPServerSettings = { enabled, port, authEnabled, authToken }
127
+ await window.electronAPI.saveMCPServerConfig(config)
128
+ toast.success('MCP Server 配置已保存')
129
+ const status = await window.electronAPI.getMCPServerStatus()
130
+ setRunning(status.running)
131
+ }}>
132
+ 保存 MCP Server 设置
133
+ </Button>
134
+ </div>
135
+ )
136
+ }