free-coding-models 0.5.86 → 0.5.88

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 (165) hide show
  1. package/README.md +109 -888
  2. package/bin/free-coding-models.js +4 -1
  3. package/changelog/v0.5.87.md +48 -0
  4. package/changelog/v0.5.88.md +16 -0
  5. package/package.json +13 -5
  6. package/sources.js +4 -4
  7. package/src/core/cache.js +3 -1
  8. package/src/core/config.js +43 -5
  9. package/src/core/endpoint-installer.js +23 -12
  10. package/src/core/installed-models-manager.js +43 -20
  11. package/src/core/kilo-config.js +51 -5
  12. package/src/core/kilo.js +5 -3
  13. package/src/core/legacy-proxy-cleanup.js +22 -5
  14. package/src/core/model-merger.js +4 -0
  15. package/src/core/models-drift.js +9 -0
  16. package/src/core/opencode-config.js +57 -5
  17. package/src/core/opencode.js +67 -41
  18. package/src/core/playground.js +7 -0
  19. package/src/core/probe-cache.js +35 -4
  20. package/src/core/provider-key-tester.js +10 -2
  21. package/src/core/provider-metadata.js +10 -0
  22. package/src/core/provider-quota-fetchers.js +14 -7
  23. package/src/core/router-daemon.js +208 -26
  24. package/src/core/runtime-telemetry.js +27 -1
  25. package/src/core/security.js +170 -24
  26. package/src/core/shared-helpers.js +12 -1
  27. package/src/core/shell-env.js +3 -1
  28. package/src/core/telemetry.js +18 -5
  29. package/src/core/tool-launchers.js +9 -6
  30. package/src/core/updater.js +28 -2
  31. package/src/core/utils.js +173 -71
  32. package/src/tui/app.js +38 -14
  33. package/src/tui/command-palette.js +22 -2
  34. package/src/tui/key-handler.js +123 -179
  35. package/src/tui/overlays.js +21 -71
  36. package/src/tui/render-helpers.js +62 -4
  37. package/src/tui/render-table.js +34 -3
  38. package/src/tui/theme.js +36 -5
  39. package/src/tui/tui-filters.js +28 -8
  40. package/src/tui/tui-state.js +22 -6
  41. package/web/dist/assets/index-CAzFIt8P.css +1 -0
  42. package/web/dist/assets/index-DFg1h0Nd.js +44 -0
  43. package/web/dist/index.html +5 -9
  44. package/web/index.html +3 -7
  45. package/web/server.js +97 -23
  46. package/web/vite.config.js +8 -0
  47. package/web/app.legacy.js +0 -900
  48. package/web/dist/assets/index-C5-Ywvv3.css +0 -1
  49. package/web/dist/assets/index-Dm-Xfby9.js +0 -40
  50. package/web/public/favicon.ico +0 -0
  51. package/web/public/favicons/apple-touch-icon.png +0 -0
  52. package/web/public/favicons/browserconfig.xml +0 -12
  53. package/web/public/favicons/favicon-16x16.png +0 -0
  54. package/web/public/favicons/favicon-192x192.png +0 -0
  55. package/web/public/favicons/favicon-32x32.png +0 -0
  56. package/web/public/favicons/favicon-48x48.png +0 -0
  57. package/web/public/favicons/favicon-512x512.png +0 -0
  58. package/web/public/favicons/favicon-96x96.png +0 -0
  59. package/web/public/favicons/favicon.ico +0 -0
  60. package/web/public/favicons/mstile-150x150.png +0 -0
  61. package/web/public/favicons/mstile-310x150.png +0 -0
  62. package/web/public/favicons/mstile-310x310.png +0 -0
  63. package/web/public/favicons/mstile-512x512.png +0 -0
  64. package/web/public/favicons/mstile-70x70.png +0 -0
  65. package/web/public/favicons/site.webmanifest +0 -25
  66. package/web/src/App.jsx +0 -521
  67. package/web/src/components/analytics/AnalyticsView.jsx +0 -147
  68. package/web/src/components/analytics/AnalyticsView.module.css +0 -190
  69. package/web/src/components/analytics/TokenUsagePanel.jsx +0 -105
  70. package/web/src/components/analytics/TokenUsagePanel.module.css +0 -126
  71. package/web/src/components/atoms/AILatencyCell.jsx +0 -38
  72. package/web/src/components/atoms/AILatencyCell.module.css +0 -51
  73. package/web/src/components/atoms/HealthCell.jsx +0 -143
  74. package/web/src/components/atoms/HealthCell.module.css +0 -60
  75. package/web/src/components/atoms/LastPingCell.jsx +0 -35
  76. package/web/src/components/atoms/LastPingCell.module.css +0 -45
  77. package/web/src/components/atoms/MoodCell.jsx +0 -25
  78. package/web/src/components/atoms/MoodCell.module.css +0 -6
  79. package/web/src/components/atoms/NoKeyIcon.jsx +0 -65
  80. package/web/src/components/atoms/NoKeyIcon.module.css +0 -30
  81. package/web/src/components/atoms/ProviderLogo.jsx +0 -187
  82. package/web/src/components/atoms/ProviderLogo.module.css +0 -103
  83. package/web/src/components/atoms/RankCell.jsx +0 -9
  84. package/web/src/components/atoms/RankCell.module.css +0 -9
  85. package/web/src/components/atoms/Sparkline.jsx +0 -63
  86. package/web/src/components/atoms/StabilityCell.jsx +0 -18
  87. package/web/src/components/atoms/StabilityCell.module.css +0 -13
  88. package/web/src/components/atoms/StatusDot.jsx +0 -13
  89. package/web/src/components/atoms/StatusDot.module.css +0 -29
  90. package/web/src/components/atoms/TPSCell.jsx +0 -36
  91. package/web/src/components/atoms/TPSCell.module.css +0 -44
  92. package/web/src/components/atoms/TierBadge.jsx +0 -10
  93. package/web/src/components/atoms/TierBadge.module.css +0 -29
  94. package/web/src/components/atoms/Toast.jsx +0 -25
  95. package/web/src/components/atoms/Toast.module.css +0 -41
  96. package/web/src/components/atoms/ToastContainer.jsx +0 -16
  97. package/web/src/components/atoms/ToastContainer.module.css +0 -10
  98. package/web/src/components/atoms/VerdictBadge.jsx +0 -36
  99. package/web/src/components/atoms/VerdictBadge.module.css +0 -40
  100. package/web/src/components/changelog/ChangelogView.jsx +0 -135
  101. package/web/src/components/changelog/ChangelogView.module.css +0 -159
  102. package/web/src/components/dashboard/DetailPanel.jsx +0 -217
  103. package/web/src/components/dashboard/DetailPanel.module.css +0 -260
  104. package/web/src/components/dashboard/ExpandedDetailRow.jsx +0 -361
  105. package/web/src/components/dashboard/ExpandedDetailRow.module.css +0 -356
  106. package/web/src/components/dashboard/ExportModal.jsx +0 -84
  107. package/web/src/components/dashboard/ExportModal.module.css +0 -99
  108. package/web/src/components/dashboard/FilterBar.jsx +0 -338
  109. package/web/src/components/dashboard/FilterBar.module.css +0 -342
  110. package/web/src/components/dashboard/ModelTable.jsx +0 -672
  111. package/web/src/components/dashboard/ModelTable.module.css +0 -446
  112. package/web/src/components/dashboard/ProviderDropdown.jsx +0 -156
  113. package/web/src/components/dashboard/ProviderDropdown.module.css +0 -248
  114. package/web/src/components/help/HelpView.jsx +0 -201
  115. package/web/src/components/help/HelpView.module.css +0 -156
  116. package/web/src/components/install/InstallEndpointsView.jsx +0 -309
  117. package/web/src/components/install/InstallEndpointsView.module.css +0 -427
  118. package/web/src/components/installed/InstalledModelsView.jsx +0 -89
  119. package/web/src/components/installed/InstalledModelsView.module.css +0 -200
  120. package/web/src/components/launch/IncompatibleFallbackModal.jsx +0 -69
  121. package/web/src/components/launch/LaunchButton.jsx +0 -30
  122. package/web/src/components/launch/LaunchButton.module.css +0 -35
  123. package/web/src/components/launch/LaunchModal.module.css +0 -125
  124. package/web/src/components/layout/Footer.jsx +0 -19
  125. package/web/src/components/layout/Footer.module.css +0 -10
  126. package/web/src/components/layout/Header.jsx +0 -265
  127. package/web/src/components/layout/Header.module.css +0 -357
  128. package/web/src/components/palette/CommandPalette.jsx +0 -301
  129. package/web/src/components/palette/CommandPalette.module.css +0 -121
  130. package/web/src/components/playground/PlaygroundChat.jsx +0 -527
  131. package/web/src/components/playground/PlaygroundChat.module.css +0 -382
  132. package/web/src/components/playground/PlaygroundView.jsx +0 -371
  133. package/web/src/components/playground/PlaygroundView.module.css +0 -504
  134. package/web/src/components/recommend/RecommendView.jsx +0 -121
  135. package/web/src/components/recommend/RecommendView.module.css +0 -57
  136. package/web/src/components/router/RouterView.jsx +0 -1212
  137. package/web/src/components/router/RouterView.module.css +0 -1289
  138. package/web/src/components/settings/SettingsView.jsx +0 -555
  139. package/web/src/components/settings/SettingsView.module.css +0 -550
  140. package/web/src/components/tools/ToolPicker.jsx +0 -86
  141. package/web/src/components/tools/ToolPicker.module.css +0 -97
  142. package/web/src/components/update/UpdateChip.jsx +0 -104
  143. package/web/src/components/update/UpdateChip.module.css +0 -146
  144. package/web/src/global.css +0 -298
  145. package/web/src/hooks/urlState.constants.js +0 -28
  146. package/web/src/hooks/useChangelog.js +0 -51
  147. package/web/src/hooks/useColumnSizing.js +0 -211
  148. package/web/src/hooks/useFavorites.js +0 -150
  149. package/web/src/hooks/useFilter.js +0 -310
  150. package/web/src/hooks/useInstalledModels.js +0 -42
  151. package/web/src/hooks/useRecommend.js +0 -67
  152. package/web/src/hooks/useRouterDashboard.js +0 -133
  153. package/web/src/hooks/useSSE.js +0 -73
  154. package/web/src/hooks/useSocket.js +0 -203
  155. package/web/src/hooks/useTheme.js +0 -83
  156. package/web/src/hooks/useTokenUsage.js +0 -103
  157. package/web/src/hooks/useToolMode.js +0 -77
  158. package/web/src/hooks/useUpdateChecker.js +0 -91
  159. package/web/src/hooks/useUrlState.js +0 -170
  160. package/web/src/main.jsx +0 -15
  161. package/web/src/utils/download.js +0 -15
  162. package/web/src/utils/format.js +0 -42
  163. package/web/src/utils/m3.js +0 -57
  164. package/web/src/utils/ranks.js +0 -37
  165. package/web/styles.legacy.css +0 -963
@@ -1,356 +0,0 @@
1
- /**
2
- * @file web/src/components/dashboard/ExpandedDetailRow.module.css
3
- * @description CSS module for the 3-column expandable detail row shown beneath a table row.
4
- * 📖 Columns: Info (left), Mini Playground (center), AI Latency Test (right).
5
- * 📖 Supports dark/light themes via CSS variables and [data-theme="light"] overrides.
6
- * 📖 Responsive: collapses to single column on screens < 900px.
7
- */
8
-
9
- /* ─── Root container ─── */
10
- .row {
11
- grid-column: 1 / -1;
12
- padding: 16px 20px;
13
- background: var(--color-bg-elevated);
14
- border-bottom: 2px solid var(--color-accent);
15
- animation: slideDown 180ms ease-out;
16
- }
17
-
18
- @keyframes slideDown {
19
- from {
20
- opacity: 0;
21
- max-height: 0;
22
- }
23
- to {
24
- opacity: 1;
25
- max-height: 600px;
26
- }
27
- }
28
-
29
- .grid {
30
- display: grid;
31
- grid-template-columns: 280px 1fr 300px;
32
- gap: 20px;
33
- min-height: 220px;
34
- }
35
-
36
- /* ─── Column shared ─── */
37
- .col {
38
- display: flex;
39
- flex-direction: column;
40
- gap: 10px;
41
- }
42
-
43
- .colTitle {
44
- font-size: 10px;
45
- font-weight: 700;
46
- text-transform: uppercase;
47
- letter-spacing: 0.8px;
48
- color: var(--color-text-muted);
49
- margin-bottom: 2px;
50
- }
51
-
52
- /* ─── Column 1: Info ─── */
53
- .statGrid {
54
- display: grid;
55
- grid-template-columns: 1fr 1fr;
56
- gap: 8px 12px;
57
- }
58
-
59
- .statItem {
60
- display: flex;
61
- flex-direction: column;
62
- gap: 2px;
63
- }
64
-
65
- .statLabel {
66
- font-size: 9px;
67
- font-weight: 700;
68
- text-transform: uppercase;
69
- letter-spacing: 0.5px;
70
- color: var(--color-text-dim);
71
- }
72
-
73
- .statValue {
74
- font-family: var(--font-mono);
75
- font-size: 12px;
76
- font-weight: 600;
77
- display: flex;
78
- align-items: center;
79
- gap: 5px;
80
- }
81
-
82
- .infoActions {
83
- display: flex;
84
- align-items: center;
85
- gap: 8px;
86
- margin-top: auto;
87
- padding-top: 8px;
88
- }
89
-
90
- .favBtn {
91
- display: inline-flex;
92
- align-items: center;
93
- gap: 4px;
94
- padding: 6px 10px;
95
- background: var(--color-bg-card);
96
- color: var(--color-text-muted);
97
- border: 1px solid var(--color-border);
98
- border-radius: 6px;
99
- font-size: 11px;
100
- font-weight: 600;
101
- cursor: pointer;
102
- transition: all 150ms;
103
- }
104
- .favBtn:hover {
105
- border-color: #ffd700;
106
- color: #ffd700;
107
- background: rgba(255, 215, 0, 0.08);
108
- }
109
- .favBtnActive {
110
- border-color: #ffd700;
111
- color: #ffd700;
112
- background: rgba(255, 215, 0, 0.12);
113
- }
114
- :global([data-theme="light"]) .favBtn { color: #555; }
115
- :global([data-theme="light"]) .favBtn:hover { color: #b08800; border-color: #b08800; background: rgba(176, 136, 0, 0.06); }
116
- :global([data-theme="light"]) .favBtnActive { color: #b08800; border-color: #b08800; background: rgba(176, 136, 0, 0.10); }
117
-
118
- /* ─── Column 2: Mini Playground ─── */
119
- .playgroundHeader {
120
- font-size: 12px;
121
- font-weight: 700;
122
- color: var(--color-text);
123
- margin-bottom: 2px;
124
- }
125
-
126
- /* 📖 Wrapper that lets the shared PlaygroundChat fill the column height so its
127
- transcript scrolls and the input stays pinned to the bottom. */
128
- .playgroundWrap {
129
- flex: 1;
130
- display: flex;
131
- flex-direction: column;
132
- min-height: 140px;
133
- }
134
-
135
- .inputRow {
136
- display: flex;
137
- gap: 6px;
138
- }
139
-
140
- .inputField {
141
- flex: 1;
142
- padding: 8px 10px;
143
- font-size: 12px;
144
- font-family: var(--font-mono);
145
- background: var(--color-bg-card);
146
- color: var(--color-text);
147
- border: 1px solid var(--color-border);
148
- border-radius: 6px;
149
- outline: none;
150
- transition: border-color 150ms;
151
- }
152
- .inputField:focus {
153
- border-color: var(--color-accent);
154
- }
155
- .inputField::placeholder {
156
- color: var(--color-text-dim);
157
- }
158
- .inputField:disabled {
159
- opacity: 0.5;
160
- cursor: not-allowed;
161
- }
162
-
163
- .sendBtn {
164
- display: inline-flex;
165
- align-items: center;
166
- justify-content: center;
167
- gap: 4px;
168
- padding: 8px 12px;
169
- font-size: 12px;
170
- font-weight: 700;
171
- font-family: var(--font-mono);
172
- background: var(--color-accent);
173
- color: var(--color-bg);
174
- border: 1px solid transparent;
175
- border-radius: 6px;
176
- cursor: pointer;
177
- transition: all 150ms;
178
- white-space: nowrap;
179
- }
180
- .sendBtn:hover:not(:disabled) {
181
- opacity: 0.9;
182
- }
183
- .sendBtn:disabled {
184
- opacity: 0.4;
185
- cursor: not-allowed;
186
- }
187
-
188
- .responseArea {
189
- flex: 1;
190
- min-height: 60px;
191
- max-height: 160px;
192
- overflow-y: auto;
193
- padding: 10px 12px;
194
- font-family: var(--font-mono);
195
- font-size: 11px;
196
- line-height: 1.5;
197
- color: var(--color-text);
198
- background: var(--color-bg-card);
199
- border: 1px solid var(--color-border);
200
- border-radius: 6px;
201
- white-space: pre-wrap;
202
- word-break: break-word;
203
- }
204
-
205
- .responsePlaceholder {
206
- color: var(--color-text-dim);
207
- font-style: italic;
208
- }
209
-
210
- /* ─── Column 3: AI Latency ─── */
211
- .benchBtn {
212
- display: inline-flex;
213
- align-items: center;
214
- justify-content: center;
215
- gap: 6px;
216
- padding: 8px 14px;
217
- font-size: 12px;
218
- font-weight: 700;
219
- font-family: var(--font-mono);
220
- background: rgba(180, 0, 255, 0.12);
221
- color: #b400ff;
222
- border: 1px solid rgba(180, 0, 255, 0.4);
223
- border-radius: 6px;
224
- cursor: pointer;
225
- transition: all 150ms;
226
- }
227
- .benchBtn:hover:not(:disabled) {
228
- background: rgba(180, 0, 255, 0.20);
229
- border-color: rgba(180, 0, 255, 0.7);
230
- }
231
- .benchBtn:disabled {
232
- opacity: 0.4;
233
- cursor: not-allowed;
234
- }
235
- :global([data-theme="light"]) .benchBtn {
236
- background: rgba(140, 0, 200, 0.06);
237
- color: #6a00a0;
238
- border-color: rgba(140, 0, 200, 0.3);
239
- }
240
- :global([data-theme="light"]) .benchBtn:hover:not(:disabled) {
241
- background: rgba(140, 0, 200, 0.12);
242
- }
243
-
244
- .metricsGrid {
245
- display: grid;
246
- grid-template-columns: 1fr 1fr;
247
- gap: 6px;
248
- }
249
-
250
- .metricCard {
251
- display: flex;
252
- flex-direction: column;
253
- gap: 2px;
254
- padding: 8px 10px;
255
- background: var(--color-bg-card);
256
- border: 1px solid var(--color-border);
257
- border-radius: 6px;
258
- }
259
-
260
- .metricLabel {
261
- font-size: 9px;
262
- font-weight: 700;
263
- text-transform: uppercase;
264
- letter-spacing: 0.5px;
265
- color: var(--color-text-dim);
266
- }
267
-
268
- .metricValue {
269
- font-family: var(--font-mono);
270
- font-size: 14px;
271
- font-weight: 700;
272
- color: var(--color-accent);
273
- }
274
-
275
- .streamText {
276
- min-height: 40px;
277
- max-height: 80px;
278
- overflow-y: auto;
279
- padding: 8px 10px;
280
- font-family: var(--font-mono);
281
- font-size: 11px;
282
- line-height: 1.4;
283
- color: var(--color-text);
284
- background: var(--color-bg-card);
285
- border: 1px solid var(--color-border);
286
- border-radius: 6px;
287
- white-space: pre-wrap;
288
- word-break: break-word;
289
- }
290
-
291
- .progressBar {
292
- width: 100%;
293
- height: 4px;
294
- background: var(--color-bg-card);
295
- border-radius: 2px;
296
- overflow: hidden;
297
- }
298
-
299
- .progressFill {
300
- height: 100%;
301
- background: var(--color-accent);
302
- border-radius: 2px;
303
- transition: width 200ms ease-out;
304
- }
305
-
306
- /* ─── SWE score classes ─── */
307
- .sweHigh { color: #ffd700; font-weight: 700; }
308
- .sweMid { color: #3ddc84; font-weight: 600; }
309
- .sweLow { color: var(--color-text-dim); }
310
- :global([data-theme="light"]) .sweHigh { color: #b08800; }
311
- :global([data-theme="light"]) .sweMid { color: #1a8f4e; }
312
-
313
- /* ─── Ping classes ─── */
314
- .pingFast { color: #00ff88; }
315
- .pingMedium { color: #ffaa00; }
316
- .pingSlow { color: #ff4444; }
317
- .pingNone { color: var(--color-text-dim); }
318
- :global([data-theme="light"]) .pingFast { color: #008f4d; }
319
- :global([data-theme="light"]) .pingMedium { color: #b86b00; }
320
- :global([data-theme="light"]) .pingSlow { color: #c8143a; }
321
-
322
- /* ─── No API key banner ─── */
323
- .noKeyBanner {
324
- grid-column: 1 / -1;
325
- display: flex;
326
- align-items: center;
327
- justify-content: center;
328
- gap: 10px;
329
- padding: 32px 20px;
330
- color: var(--color-text-dim);
331
- font-size: 13px;
332
- font-weight: 600;
333
- text-align: center;
334
- }
335
-
336
- .noKeyIcon {
337
- font-size: 20px;
338
- opacity: 0.5;
339
- }
340
-
341
- /* ─── Spinning animation ─── */
342
- .spinning {
343
- animation: spin 1s linear infinite;
344
- }
345
-
346
- @keyframes spin {
347
- from { transform: rotate(0deg); }
348
- to { transform: rotate(360deg); }
349
- }
350
-
351
- /* ─── Responsive: single column below 900px ─── */
352
- @media (max-width: 900px) {
353
- .grid {
354
- grid-template-columns: 1fr;
355
- }
356
- }
@@ -1,84 +0,0 @@
1
- /**
2
- * @file web/src/components/dashboard/ExportModal.jsx
3
- * @description Modal dialog for exporting model data as JSON, CSV, or clipboard text.
4
- * 📖 Triggered by the export button in the header. Accepts `models` (filtered list),
5
- * `onClose` callback, and `onToast` for feedback notifications.
6
- * @functions ExportModal → renders the modal with three export option buttons
7
- */
8
- import { IconUpload, IconBraces, IconFileSpreadsheet, IconClipboard } from '@tabler/icons-react'
9
- import { downloadFile } from '../../utils/download.js'
10
- import styles from './ExportModal.module.css'
11
-
12
- export default function ExportModal({ models, onClose, onToast }) {
13
- if (!models) return null
14
-
15
- const handleJson = () => {
16
- const data = JSON.stringify(models, null, 2)
17
- downloadFile(data, 'free-coding-models-export.json', 'application/json')
18
- onToast?.('Exported as JSON', 'success')
19
- onClose()
20
- }
21
-
22
- const handleCsv = () => {
23
- const headers = ['Rank', 'Tier', 'Model', 'Provider', 'SWE%', 'Context', 'LatestPing', 'AvgPing', 'Stability', 'Verdict', 'Uptime']
24
- const rows = models.map((m, i) =>
25
- [i + 1, m.tier, m.label, m.origin, m.sweScore || '', m.ctx || '', m.latestPing || '', m.avg === Infinity ? '' : m.avg, m.stability || '', m.verdict || '', m.uptime || ''].join(',')
26
- )
27
- const csv = [headers.join(','), ...rows].join('\n')
28
- downloadFile(csv, 'free-coding-models-export.csv', 'text/csv')
29
- onToast?.('Exported as CSV', 'success')
30
- onClose()
31
- }
32
-
33
- const handleClipboard = async () => {
34
- const online = models.filter(m => m.status === 'up')
35
- const text = `free-coding-models Dashboard Export\n` +
36
- `Total: ${models.length} | Online: ${online.length}\n\n` +
37
- online.slice(0, 20).map((m, i) =>
38
- `${i + 1}. ${m.label} [${m.tier}] — ${m.avg !== Infinity ? m.avg + 'ms' : 'N/A'} (${m.origin})`
39
- ).join('\n')
40
- await navigator.clipboard.writeText(text)
41
- onToast?.('Copied to clipboard', 'success')
42
- onClose()
43
- }
44
-
45
- return (
46
- <div className={styles.overlay} onClick={(e) => e.target === e.currentTarget && onClose()}>
47
- <div className={styles.modal}>
48
- <div className={styles.modalHeader}>
49
- <h2 className={styles.modalTitle}>
50
- <IconUpload size={20} stroke={1.5} style={{ marginRight: 8, verticalAlign: 'middle' }} />
51
- Export Data
52
- </h2>
53
- <button className={styles.modalClose} onClick={onClose}>&times;</button>
54
- </div>
55
- <div className={styles.modalBody}>
56
- <div className={styles.options}>
57
- <button className={styles.option} onClick={handleJson}>
58
- <span className={styles.optionIcon}><IconBraces size={20} stroke={1.5} /></span>
59
- <div>
60
- <span className={styles.optionLabel}>Export as JSON</span>
61
- <span className={styles.optionDesc}>Full model data with all metrics</span>
62
- </div>
63
- </button>
64
- <button className={styles.option} onClick={handleCsv}>
65
- <span className={styles.optionIcon}><IconFileSpreadsheet size={20} stroke={1.5} /></span>
66
- <div>
67
- <span className={styles.optionLabel}>Export as CSV</span>
68
- <span className={styles.optionDesc}>Spreadsheet-compatible format</span>
69
- </div>
70
- </button>
71
- <button className={styles.option} onClick={handleClipboard}>
72
- <span className={styles.optionIcon}><IconClipboard size={20} stroke={1.5} /></span>
73
- <div>
74
- <span className={styles.optionLabel}>Copy to Clipboard</span>
75
- <span className={styles.optionDesc}>Copy model summary as text</span>
76
- </div>
77
- </button>
78
- </div>
79
- </div>
80
- </div>
81
- </div>
82
- )
83
- }
84
-
@@ -1,99 +0,0 @@
1
- .overlay {
2
- position: fixed;
3
- inset: 0;
4
- z-index: 1000;
5
- background: rgba(0, 0, 0, 0.6);
6
- backdrop-filter: blur(4px);
7
- display: flex;
8
- align-items: center;
9
- justify-content: center;
10
- animation: fadeIn 200ms ease-out;
11
- }
12
-
13
- .overlay[hidden] { display: none; }
14
-
15
- @keyframes fadeIn {
16
- from { opacity: 0; } to { opacity: 1; } }
17
-
18
- .modal {
19
- background: var(--color-bg-elevated);
20
- border: 1px solid var(--color-border);
21
- border-radius: var(--radius-xl);
22
- width: min(500px, 90vw);
23
- max-height: 80vh;
24
- overflow-y: auto;
25
- box-shadow: var(--shadow-lg);
26
- animation: modalSlideIn 300ms var(--ease-out);
27
- }
28
- @keyframes modalSlideIn {
29
- from { transform: translateY(20px) scale(0.96); opacity: 0; }
30
- to { transform: translateY(0) scale(1); opacity: 1; }
31
- }
32
-
33
- .modalHeader {
34
- display: flex;
35
- align-items: center;
36
- justify-content: space-between;
37
- padding: 20px 24px;
38
- border-bottom: 1px solid var(--color-border);
39
- }
40
- .modalTitle {
41
- font-size: 18px;
42
- font-weight: 700;
43
- margin: 0;
44
- }
45
- .modalClose {
46
- background: none;
47
- border: none;
48
- color: var(--color-text-muted);
49
- font-size: 24px;
50
- cursor: pointer;
51
- padding: 0;
52
- line-height: 1;
53
- }
54
- .modalClose:hover {
55
- color: var(--color-text);
56
- }
57
- .modalBody {
58
- padding: 24px;
59
- }
60
- .options {
61
- display: flex;
62
- flex-direction: column;
63
- gap: 10px;
64
- }
65
- .option {
66
- display: flex;
67
- align-items: center;
68
- gap: 14px;
69
- padding: 14px 18px;
70
- border: 1px solid var(--color-border);
71
- border-radius: var(--radius-md);
72
- background: var(--color-surface);
73
- cursor: pointer;
74
- transition: all var(--transition-fast);
75
- text-align: left;
76
- font-family: var(--font-sans);
77
- color: var(--color-text);
78
- width: 100%;
79
- }
80
- .option:hover {
81
- border-color: var(--color-accent);
82
- background: var(--color-accent-dim);
83
- }
84
- .optionIcon {
85
- font-size: 24px;
86
- flex-shrink: 0;
87
- }
88
- .optionText {}
89
- .optionLabel {
90
- display: block;
91
- font-weight: 600;
92
- font-size: 14px;
93
- }
94
- .optionDesc {
95
- display: block;
96
- font-size: 11px;
97
- color: var(--color-text-muted);
98
- margin-top: 2px;
99
- }