@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,137 @@
1
+ /* ============================================
2
+ * Anything Analyzer — Design Tokens
3
+ * Minimal Pro / Tool Aesthetic
4
+ * ============================================ */
5
+
6
+ :root {
7
+ /* ---- Surfaces ---- */
8
+ --color-base: #0a0a0a;
9
+ --color-frame: #111111;
10
+ --color-bar: #0e0e0e;
11
+ --color-content: #141414;
12
+ --color-sidebar: #0c0c0c;
13
+ --color-surface: #181818;
14
+ --color-elevated: #1f1f1f;
15
+
16
+ /* ---- Borders ---- */
17
+ --color-border: #1a1a1a;
18
+ --color-border-subtle: #222222;
19
+ --color-border-hover: #333333;
20
+
21
+ /* ---- Interactive States ---- */
22
+ --color-hover: rgba(255, 255, 255, 0.04);
23
+ --color-active: #252525;
24
+ --color-selected: rgba(255, 255, 255, 0.08);
25
+ --color-focus-ring: rgba(96, 165, 250, 0.4);
26
+
27
+ /* ---- Text ---- */
28
+ --text-primary: #ffffff;
29
+ --text-secondary: #a0a0a0;
30
+ --text-muted: #8a8a8a;
31
+ --text-label: #787878;
32
+ --text-disabled: #505050;
33
+
34
+ /* ---- Semantic Colors ---- */
35
+ --color-success: #4ade80;
36
+ --color-success-bg: rgba(74, 222, 128, 0.08);
37
+ --color-success-border: rgba(74, 222, 128, 0.15);
38
+
39
+ --color-info: #60a5fa;
40
+ --color-info-bg: rgba(96, 165, 250, 0.08);
41
+ --color-info-border: rgba(96, 165, 250, 0.15);
42
+
43
+ --color-warning: #facc15;
44
+ --color-warning-bg: rgba(250, 204, 21, 0.08);
45
+ --color-warning-border: rgba(250, 204, 21, 0.15);
46
+
47
+ --color-error: #f87171;
48
+ --color-error-bg: rgba(248, 113, 113, 0.08);
49
+ --color-error-border: rgba(248, 113, 113, 0.15);
50
+
51
+ /* ---- Accent ---- */
52
+ --color-accent: #60a5fa;
53
+ --color-accent-bg: rgba(96, 165, 250, 0.1);
54
+ --color-accent-border: rgba(96, 165, 250, 0.2);
55
+
56
+ /* ---- Primary Button ---- */
57
+ --color-primary: #ffffff;
58
+ --color-primary-hover: #e0e0e0;
59
+ --color-primary-text: #0a0a0a;
60
+
61
+ --color-purple: #a855f7;
62
+ --color-purple-bg: rgba(168, 85, 247, 0.1);
63
+
64
+ --color-orange: #f97316;
65
+ --color-orange-bg: rgba(249, 115, 22, 0.1);
66
+
67
+ /* ---- Border Radius ---- */
68
+ --radius-frame: 10px;
69
+ --radius-lg: 8px;
70
+ --radius-input: 8px;
71
+ --radius-button: 6px;
72
+ --radius-badge: 4px;
73
+ --radius-bar: 2px;
74
+ --radius-chip: 16px;
75
+ --radius-full: 9999px;
76
+
77
+ /* ---- Typography ---- */
78
+ --font-sans: 'Segoe UI', system-ui, -apple-system, sans-serif;
79
+ --font-mono: 'Consolas', 'Cascadia Code', 'Fira Code', monospace;
80
+
81
+ --font-size-3xs: 10px;
82
+ --font-size-2xs: 12px;
83
+ --font-size-xs: 13px;
84
+ --font-size-sm: 14px;
85
+ --font-size-base: 14px;
86
+ --font-size-md: 15px;
87
+ --font-size-lg: 17px;
88
+ --font-size-xl: 18px;
89
+ --font-size-2xl: 22px;
90
+
91
+ --line-height-tight: 1.3;
92
+ --line-height-normal: 1.5;
93
+ --line-height-relaxed: 1.8;
94
+
95
+ --letter-spacing-label: 1px;
96
+ --letter-spacing-section: 2px;
97
+
98
+ /* ---- Spacing ---- */
99
+ --space-2xs: 2px;
100
+ --space-xs: 4px;
101
+ --space-sm: 6px;
102
+ --space-md: 8px;
103
+ --space-lg: 12px;
104
+ --space-xl: 16px;
105
+ --space-2xl: 20px;
106
+ --space-3xl: 24px;
107
+ --space-4xl: 32px;
108
+
109
+ /* ---- Fixed Dimensions ---- */
110
+ --titlebar-height: 44px;
111
+ --navbar-height: 34px;
112
+ --statusbar-height: 26px;
113
+ --sidebar-width: 220px;
114
+ --browser-bar-height: 38px;
115
+ --browser-tabs-height: 30px;
116
+ --inspector-list-width: 420px;
117
+ --report-max-width: 720px;
118
+ --report-context-width: 260px;
119
+
120
+ /* ---- Shadows ---- */
121
+ --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.3);
122
+ --shadow-md: 0 4px 12px rgba(0, 0, 0, 0.4);
123
+ --shadow-lg: 0 8px 24px rgba(0, 0, 0, 0.5);
124
+ --shadow-overlay: 0 16px 48px rgba(0, 0, 0, 0.6);
125
+
126
+ /* ---- Transitions ---- */
127
+ --transition-fast: 0.1s ease;
128
+ --transition-normal: 0.15s ease;
129
+ --transition-slow: 0.3s ease;
130
+
131
+ /* ---- Z-Index ---- */
132
+ --z-dropdown: 100;
133
+ --z-modal-backdrop: 200;
134
+ --z-modal: 210;
135
+ --z-toast: 300;
136
+ --z-tooltip: 400;
137
+ }
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Theme metadata — all available application themes.
3
+ */
4
+
5
+ export interface ThemeMeta {
6
+ id: string
7
+ name: string // 中文显示名
8
+ nameEn: string // 英文显示名
9
+ type: 'dark' | 'light'
10
+ accent: string // 代表色(预览色块)
11
+ }
12
+
13
+ export const THEMES: ThemeMeta[] = [
14
+ { id: 'dark', name: '默认深色', nameEn: 'Default Dark', type: 'dark', accent: '#60a5fa' },
15
+ { id: 'light', name: '亮色清爽', nameEn: 'Light', type: 'light', accent: '#3b82f6' },
16
+ { id: 'discord', name: 'Discord 暖灰', nameEn: 'Discord', type: 'dark', accent: '#5865f2' },
17
+ { id: 'github-dark', name: 'GitHub Dark', nameEn: 'GitHub Dark', type: 'dark', accent: '#58a6ff' },
18
+ { id: 'catppuccin', name: 'Catppuccin', nameEn: 'Catppuccin', type: 'dark', accent: '#cba6f7' },
19
+ { id: 'dracula', name: 'Dracula', nameEn: 'Dracula', type: 'dark', accent: '#ff79c6' },
20
+ { id: 'nord', name: 'Nord 极光蓝', nameEn: 'Nord', type: 'dark', accent: '#88c0d0' },
21
+ { id: 'tokyo-night', name: 'Tokyo Night', nameEn: 'Tokyo Night', type: 'dark', accent: '#7aa2f7' },
22
+ { id: 'gruvbox', name: 'Gruvbox 暖调', nameEn: 'Gruvbox', type: 'dark', accent: '#fe8019' },
23
+ { id: 'ayu-dark', name: 'Ayu Dark', nameEn: 'Ayu Dark', type: 'dark', accent: '#e6b450' },
24
+ { id: 'one-dark', name: 'One Dark', nameEn: 'One Dark', type: 'dark', accent: '#61afef' },
25
+ ]
26
+
27
+ export const DEFAULT_THEME = 'dark'
28
+
29
+ export function getThemeById(id: string): ThemeMeta | undefined {
30
+ return THEMES.find(t => t.id === id)
31
+ }
@@ -0,0 +1,38 @@
1
+ .badge {
2
+ display: inline-flex;
3
+ align-items: center;
4
+ gap: var(--space-sm);
5
+ }
6
+
7
+ .dot {
8
+ width: 6px;
9
+ height: 6px;
10
+ border-radius: var(--radius-full);
11
+ flex-shrink: 0;
12
+ }
13
+
14
+ .label {
15
+ font-size: var(--font-size-xs);
16
+ color: var(--text-secondary);
17
+ }
18
+
19
+ /* Sizes */
20
+ .sm .dot {
21
+ width: 5px;
22
+ height: 5px;
23
+ }
24
+
25
+ .lg .dot {
26
+ width: 8px;
27
+ height: 8px;
28
+ }
29
+
30
+ /* Pulse animation for active states */
31
+ .pulse .dot {
32
+ animation: pulse 2s ease-in-out infinite;
33
+ }
34
+
35
+ @keyframes pulse {
36
+ 0%, 100% { opacity: 1; }
37
+ 50% { opacity: 0.4; }
38
+ }
@@ -0,0 +1,36 @@
1
+ import React from 'react'
2
+ import styles from './Badge.module.css'
3
+
4
+ export interface BadgeProps {
5
+ color?: string
6
+ label?: string
7
+ size?: 'sm' | 'md' | 'lg'
8
+ pulse?: boolean
9
+ className?: string
10
+ style?: React.CSSProperties
11
+ }
12
+
13
+ export const Badge: React.FC<BadgeProps> = ({
14
+ color = 'var(--text-muted)',
15
+ label,
16
+ size = 'md',
17
+ pulse = false,
18
+ className,
19
+ style,
20
+ }) => {
21
+ const cls = [
22
+ styles.badge,
23
+ size !== 'md' && styles[size],
24
+ pulse && styles.pulse,
25
+ className,
26
+ ]
27
+ .filter(Boolean)
28
+ .join(' ')
29
+
30
+ return (
31
+ <span className={cls} style={style}>
32
+ <span className={styles.dot} style={{ background: color }} />
33
+ {label && <span className={styles.label}>{label}</span>}
34
+ </span>
35
+ )
36
+ }
@@ -0,0 +1,142 @@
1
+ /* Button variants */
2
+ .button {
3
+ display: inline-flex;
4
+ align-items: center;
5
+ justify-content: center;
6
+ gap: var(--space-sm);
7
+ padding: 0 var(--space-lg);
8
+ height: 28px;
9
+ font-family: var(--font-sans);
10
+ font-size: var(--font-size-base);
11
+ line-height: 1;
12
+ border-radius: var(--radius-button);
13
+ border: 1px solid var(--color-border);
14
+ background: var(--color-surface);
15
+ color: var(--text-secondary);
16
+ cursor: pointer;
17
+ transition: all var(--transition-normal);
18
+ white-space: nowrap;
19
+ user-select: none;
20
+ outline: none;
21
+ }
22
+
23
+ .button:hover {
24
+ border-color: var(--color-border-hover);
25
+ color: var(--text-primary);
26
+ }
27
+
28
+ .button:active {
29
+ background: var(--color-active);
30
+ }
31
+
32
+ .button:focus-visible {
33
+ box-shadow: 0 0 0 2px var(--color-focus-ring);
34
+ }
35
+
36
+ .button:disabled {
37
+ opacity: 0.4;
38
+ cursor: not-allowed;
39
+ pointer-events: none;
40
+ }
41
+
42
+ /* Size: small */
43
+ .sm {
44
+ height: 24px;
45
+ padding: 0 var(--space-md);
46
+ font-size: var(--font-size-sm);
47
+ }
48
+
49
+ /* Variant: primary */
50
+ .primary {
51
+ background: var(--color-primary);
52
+ color: var(--color-primary-text);
53
+ border-color: var(--color-primary);
54
+ }
55
+
56
+ .primary:hover {
57
+ background: var(--color-primary-hover);
58
+ border-color: var(--color-primary-hover);
59
+ }
60
+
61
+ .primary:active {
62
+ opacity: 0.85;
63
+ }
64
+
65
+ /* Variant: ghost */
66
+ .ghost {
67
+ background: transparent;
68
+ border-color: transparent;
69
+ }
70
+
71
+ .ghost:hover {
72
+ background: var(--color-hover);
73
+ border-color: transparent;
74
+ }
75
+
76
+ /* Variant: danger */
77
+ .danger {
78
+ color: var(--color-error);
79
+ border-color: var(--color-error-border);
80
+ background: var(--color-error-bg);
81
+ }
82
+
83
+ .danger:hover {
84
+ border-color: var(--color-error);
85
+ color: var(--color-error);
86
+ }
87
+
88
+ /* Variant: dashed */
89
+ .dashed {
90
+ border-style: dashed;
91
+ border-color: var(--color-border-hover);
92
+ background: var(--color-active);
93
+ color: var(--text-muted);
94
+ }
95
+
96
+ .dashed:hover {
97
+ border-color: var(--color-border-hover);
98
+ color: var(--text-secondary);
99
+ }
100
+
101
+ /* Variant: success */
102
+ .success {
103
+ color: var(--color-success);
104
+ border-color: var(--color-success-border);
105
+ background: var(--color-success-bg);
106
+ }
107
+
108
+ .success:hover {
109
+ border-color: var(--color-success);
110
+ }
111
+
112
+ /* Variant: info */
113
+ .info {
114
+ color: var(--color-info);
115
+ border-color: var(--color-info-border);
116
+ background: var(--color-info-bg);
117
+ }
118
+
119
+ .info:hover {
120
+ border-color: var(--color-info);
121
+ }
122
+
123
+ /* Icon-only button */
124
+ .iconOnly {
125
+ padding: 0;
126
+ width: 28px;
127
+ }
128
+
129
+ .iconOnly.sm {
130
+ width: 24px;
131
+ }
132
+
133
+ /* Block button */
134
+ .block {
135
+ width: 100%;
136
+ }
137
+
138
+ /* Loading state */
139
+ .loading {
140
+ pointer-events: none;
141
+ opacity: 0.7;
142
+ }
@@ -0,0 +1,46 @@
1
+ import React from 'react'
2
+ import styles from './Button.module.css'
3
+
4
+ export type ButtonVariant = 'default' | 'primary' | 'ghost' | 'danger' | 'dashed' | 'success' | 'info'
5
+ export type ButtonSize = 'sm' | 'md'
6
+
7
+ export interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
8
+ variant?: ButtonVariant
9
+ size?: ButtonSize
10
+ icon?: React.ReactNode
11
+ iconOnly?: boolean
12
+ block?: boolean
13
+ loading?: boolean
14
+ }
15
+
16
+ export const Button: React.FC<ButtonProps> = ({
17
+ variant = 'default',
18
+ size = 'md',
19
+ icon,
20
+ iconOnly = false,
21
+ block = false,
22
+ loading = false,
23
+ className,
24
+ children,
25
+ disabled,
26
+ ...rest
27
+ }) => {
28
+ const cls = [
29
+ styles.button,
30
+ variant !== 'default' && styles[variant],
31
+ size === 'sm' && styles.sm,
32
+ iconOnly && styles.iconOnly,
33
+ block && styles.block,
34
+ loading && styles.loading,
35
+ className,
36
+ ]
37
+ .filter(Boolean)
38
+ .join(' ')
39
+
40
+ return (
41
+ <button className={cls} disabled={disabled || loading} {...rest}>
42
+ {icon && <span style={{ display: 'flex', alignItems: 'center' }}>{icon}</span>}
43
+ {!iconOnly && children}
44
+ </button>
45
+ )
46
+ }
@@ -0,0 +1,49 @@
1
+ .collapse {
2
+ border: 1px solid var(--color-border);
3
+ border-radius: var(--radius-button);
4
+ overflow: hidden;
5
+ }
6
+
7
+ .panel {
8
+ border-bottom: 1px solid var(--color-border);
9
+ }
10
+
11
+ .panel:last-child {
12
+ border-bottom: none;
13
+ }
14
+
15
+ .header {
16
+ display: flex;
17
+ align-items: center;
18
+ gap: var(--space-sm);
19
+ padding: 10px var(--space-md);
20
+ cursor: pointer;
21
+ background: var(--color-surface);
22
+ transition: background var(--transition-fast);
23
+ user-select: none;
24
+ }
25
+
26
+ .header:hover {
27
+ background: var(--color-surface-hover);
28
+ }
29
+
30
+ .arrow {
31
+ flex-shrink: 0;
32
+ color: var(--text-muted);
33
+ transition: transform 0.2s ease;
34
+ }
35
+
36
+ .arrowOpen {
37
+ transform: rotate(90deg);
38
+ }
39
+
40
+ .label {
41
+ flex: 1;
42
+ font-size: var(--font-size-sm);
43
+ color: var(--text-primary);
44
+ }
45
+
46
+ .content {
47
+ padding: var(--space-md);
48
+ background: var(--color-content);
49
+ }
@@ -0,0 +1,57 @@
1
+ import React, { useState } from 'react'
2
+ import styles from './Collapse.module.css'
3
+
4
+ interface CollapseItem {
5
+ key: string
6
+ label: React.ReactNode
7
+ children: React.ReactNode
8
+ }
9
+
10
+ interface CollapseProps {
11
+ items: CollapseItem[]
12
+ defaultActiveKey?: string[]
13
+ className?: string
14
+ }
15
+
16
+ export const Collapse: React.FC<CollapseProps> = ({ items, defaultActiveKey = [], className }) => {
17
+ const [activeKeys, setActiveKeys] = useState<Set<string>>(new Set(defaultActiveKey))
18
+
19
+ const toggle = (key: string) => {
20
+ setActiveKeys((prev) => {
21
+ const next = new Set(prev)
22
+ if (next.has(key)) {
23
+ next.delete(key)
24
+ } else {
25
+ next.add(key)
26
+ }
27
+ return next
28
+ })
29
+ }
30
+
31
+ return (
32
+ <div className={`${styles.collapse} ${className ?? ''}`}>
33
+ {items.map((item) => {
34
+ const isOpen = activeKeys.has(item.key)
35
+ return (
36
+ <div key={item.key} className={styles.panel}>
37
+ <div className={styles.header} onClick={() => toggle(item.key)}>
38
+ <svg
39
+ className={`${styles.arrow} ${isOpen ? styles.arrowOpen : ''}`}
40
+ width="12"
41
+ height="12"
42
+ viewBox="0 0 24 24"
43
+ fill="none"
44
+ stroke="currentColor"
45
+ strokeWidth="2"
46
+ >
47
+ <polyline points="9 18 15 12 9 6" />
48
+ </svg>
49
+ <span className={styles.label}>{item.label}</span>
50
+ </div>
51
+ {isOpen && <div className={styles.content}>{item.children}</div>}
52
+ </div>
53
+ )
54
+ })}
55
+ </div>
56
+ )
57
+ }
@@ -0,0 +1,56 @@
1
+ .container {
2
+ margin-bottom: var(--space-lg);
3
+ }
4
+
5
+ .label {
6
+ font-size: var(--font-size-sm);
7
+ font-weight: 600;
8
+ color: var(--text-primary);
9
+ margin-bottom: var(--space-sm);
10
+ }
11
+
12
+ .block {
13
+ position: relative;
14
+ background: var(--color-surface);
15
+ border: 1px solid var(--color-border-subtle);
16
+ border-radius: var(--radius-button);
17
+ overflow: auto;
18
+ }
19
+
20
+ .content {
21
+ margin: 0;
22
+ padding: 10px 14px;
23
+ padding-right: 40px;
24
+ white-space: pre-wrap;
25
+ word-break: break-all;
26
+ font-family: var(--font-mono);
27
+ font-size: var(--font-size-xs);
28
+ color: var(--text-secondary);
29
+ line-height: 1.6;
30
+ }
31
+
32
+ .copyBtn {
33
+ position: absolute;
34
+ top: 6px;
35
+ right: 6px;
36
+ display: flex;
37
+ align-items: center;
38
+ justify-content: center;
39
+ width: 24px;
40
+ height: 24px;
41
+ border: none;
42
+ border-radius: var(--radius-badge);
43
+ background: var(--color-surface-hover);
44
+ color: var(--text-muted);
45
+ cursor: pointer;
46
+ opacity: 0;
47
+ transition: opacity var(--transition-fast), color var(--transition-fast);
48
+ }
49
+
50
+ .block:hover .copyBtn {
51
+ opacity: 1;
52
+ }
53
+
54
+ .copyBtn:hover {
55
+ color: var(--text-primary);
56
+ }
@@ -0,0 +1,42 @@
1
+ import React, { useState, useCallback } from 'react'
2
+ import styles from './CopyableBlock.module.css'
3
+ import { IconCheck } from './Icons'
4
+
5
+ interface CopyableBlockProps {
6
+ content: string
7
+ label?: string
8
+ maxHeight?: number
9
+ }
10
+
11
+ export const CopyableBlock: React.FC<CopyableBlockProps> = ({ content, label, maxHeight = 400 }) => {
12
+ const [copied, setCopied] = useState(false)
13
+
14
+ const handleCopy = useCallback(async () => {
15
+ try {
16
+ await navigator.clipboard.writeText(content)
17
+ setCopied(true)
18
+ setTimeout(() => setCopied(false), 2000)
19
+ } catch {
20
+ // fallback
21
+ }
22
+ }, [content])
23
+
24
+ return (
25
+ <div className={styles.container}>
26
+ {label && <div className={styles.label}>{label}</div>}
27
+ <div className={styles.block} style={{ maxHeight }}>
28
+ <pre className={styles.content}>{content}</pre>
29
+ <button className={styles.copyBtn} onClick={handleCopy} title="Copy">
30
+ {copied ? (
31
+ <IconCheck size={12} />
32
+ ) : (
33
+ <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
34
+ <rect x="9" y="9" width="13" height="13" rx="2" ry="2" />
35
+ <path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1" />
36
+ </svg>
37
+ )}
38
+ </button>
39
+ </div>
40
+ </div>
41
+ )
42
+ }
@@ -0,0 +1,19 @@
1
+ .empty {
2
+ display: flex;
3
+ flex-direction: column;
4
+ align-items: center;
5
+ justify-content: center;
6
+ padding: var(--space-4xl) var(--space-xl);
7
+ color: var(--text-muted);
8
+ }
9
+
10
+ .icon {
11
+ font-size: 32px;
12
+ margin-bottom: var(--space-lg);
13
+ opacity: 0.3;
14
+ }
15
+
16
+ .text {
17
+ font-size: var(--font-size-base);
18
+ color: var(--text-label);
19
+ }
@@ -0,0 +1,34 @@
1
+ import React from 'react'
2
+ import styles from './Empty.module.css'
3
+
4
+ export interface EmptyProps {
5
+ description?: string
6
+ icon?: React.ReactNode
7
+ children?: React.ReactNode
8
+ className?: string
9
+ style?: React.CSSProperties
10
+ }
11
+
12
+ export const Empty: React.FC<EmptyProps> = ({
13
+ description = 'No data',
14
+ icon,
15
+ children,
16
+ className,
17
+ style,
18
+ }) => {
19
+ return (
20
+ <div className={`${styles.empty} ${className ?? ''}`} style={style}>
21
+ <div className={styles.icon}>
22
+ {icon || (
23
+ <svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1">
24
+ <rect x="3" y="3" width="18" height="18" rx="2" />
25
+ <line x1="9" y1="9" x2="15" y2="15" />
26
+ <line x1="15" y1="9" x2="9" y2="15" />
27
+ </svg>
28
+ )}
29
+ </div>
30
+ <div className={styles.text}>{description}</div>
31
+ {children}
32
+ </div>
33
+ )
34
+ }