free-coding-models 0.5.86 → 0.5.87

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 (163) hide show
  1. package/README.md +110 -881
  2. package/bin/free-coding-models.js +4 -1
  3. package/changelog/v0.5.87.md +48 -0
  4. package/package.json +8 -5
  5. package/sources.js +4 -4
  6. package/src/core/cache.js +3 -1
  7. package/src/core/config.js +43 -5
  8. package/src/core/endpoint-installer.js +23 -12
  9. package/src/core/installed-models-manager.js +43 -20
  10. package/src/core/kilo-config.js +51 -5
  11. package/src/core/kilo.js +5 -3
  12. package/src/core/legacy-proxy-cleanup.js +22 -5
  13. package/src/core/model-merger.js +4 -0
  14. package/src/core/models-drift.js +9 -0
  15. package/src/core/opencode-config.js +57 -5
  16. package/src/core/opencode.js +67 -41
  17. package/src/core/playground.js +7 -0
  18. package/src/core/probe-cache.js +35 -4
  19. package/src/core/provider-key-tester.js +10 -2
  20. package/src/core/provider-metadata.js +10 -0
  21. package/src/core/provider-quota-fetchers.js +14 -7
  22. package/src/core/router-daemon.js +208 -26
  23. package/src/core/runtime-telemetry.js +27 -1
  24. package/src/core/shared-helpers.js +12 -1
  25. package/src/core/shell-env.js +3 -1
  26. package/src/core/telemetry.js +18 -5
  27. package/src/core/tool-launchers.js +9 -6
  28. package/src/core/updater.js +28 -2
  29. package/src/core/utils.js +76 -71
  30. package/src/tui/app.js +38 -14
  31. package/src/tui/command-palette.js +22 -2
  32. package/src/tui/key-handler.js +123 -179
  33. package/src/tui/overlays.js +21 -71
  34. package/src/tui/render-helpers.js +62 -4
  35. package/src/tui/render-table.js +34 -3
  36. package/src/tui/theme.js +36 -5
  37. package/src/tui/tui-filters.js +28 -8
  38. package/src/tui/tui-state.js +22 -6
  39. package/web/dist/assets/index-BpTpPS0g.js +44 -0
  40. package/web/dist/assets/index-CAzFIt8P.css +1 -0
  41. package/web/dist/index.html +5 -9
  42. package/web/index.html +3 -7
  43. package/web/server.js +97 -23
  44. package/web/vite.config.js +8 -0
  45. package/web/app.legacy.js +0 -900
  46. package/web/dist/assets/index-C5-Ywvv3.css +0 -1
  47. package/web/dist/assets/index-Dm-Xfby9.js +0 -40
  48. package/web/public/favicon.ico +0 -0
  49. package/web/public/favicons/apple-touch-icon.png +0 -0
  50. package/web/public/favicons/browserconfig.xml +0 -12
  51. package/web/public/favicons/favicon-16x16.png +0 -0
  52. package/web/public/favicons/favicon-192x192.png +0 -0
  53. package/web/public/favicons/favicon-32x32.png +0 -0
  54. package/web/public/favicons/favicon-48x48.png +0 -0
  55. package/web/public/favicons/favicon-512x512.png +0 -0
  56. package/web/public/favicons/favicon-96x96.png +0 -0
  57. package/web/public/favicons/favicon.ico +0 -0
  58. package/web/public/favicons/mstile-150x150.png +0 -0
  59. package/web/public/favicons/mstile-310x150.png +0 -0
  60. package/web/public/favicons/mstile-310x310.png +0 -0
  61. package/web/public/favicons/mstile-512x512.png +0 -0
  62. package/web/public/favicons/mstile-70x70.png +0 -0
  63. package/web/public/favicons/site.webmanifest +0 -25
  64. package/web/src/App.jsx +0 -521
  65. package/web/src/components/analytics/AnalyticsView.jsx +0 -147
  66. package/web/src/components/analytics/AnalyticsView.module.css +0 -190
  67. package/web/src/components/analytics/TokenUsagePanel.jsx +0 -105
  68. package/web/src/components/analytics/TokenUsagePanel.module.css +0 -126
  69. package/web/src/components/atoms/AILatencyCell.jsx +0 -38
  70. package/web/src/components/atoms/AILatencyCell.module.css +0 -51
  71. package/web/src/components/atoms/HealthCell.jsx +0 -143
  72. package/web/src/components/atoms/HealthCell.module.css +0 -60
  73. package/web/src/components/atoms/LastPingCell.jsx +0 -35
  74. package/web/src/components/atoms/LastPingCell.module.css +0 -45
  75. package/web/src/components/atoms/MoodCell.jsx +0 -25
  76. package/web/src/components/atoms/MoodCell.module.css +0 -6
  77. package/web/src/components/atoms/NoKeyIcon.jsx +0 -65
  78. package/web/src/components/atoms/NoKeyIcon.module.css +0 -30
  79. package/web/src/components/atoms/ProviderLogo.jsx +0 -187
  80. package/web/src/components/atoms/ProviderLogo.module.css +0 -103
  81. package/web/src/components/atoms/RankCell.jsx +0 -9
  82. package/web/src/components/atoms/RankCell.module.css +0 -9
  83. package/web/src/components/atoms/Sparkline.jsx +0 -63
  84. package/web/src/components/atoms/StabilityCell.jsx +0 -18
  85. package/web/src/components/atoms/StabilityCell.module.css +0 -13
  86. package/web/src/components/atoms/StatusDot.jsx +0 -13
  87. package/web/src/components/atoms/StatusDot.module.css +0 -29
  88. package/web/src/components/atoms/TPSCell.jsx +0 -36
  89. package/web/src/components/atoms/TPSCell.module.css +0 -44
  90. package/web/src/components/atoms/TierBadge.jsx +0 -10
  91. package/web/src/components/atoms/TierBadge.module.css +0 -29
  92. package/web/src/components/atoms/Toast.jsx +0 -25
  93. package/web/src/components/atoms/Toast.module.css +0 -41
  94. package/web/src/components/atoms/ToastContainer.jsx +0 -16
  95. package/web/src/components/atoms/ToastContainer.module.css +0 -10
  96. package/web/src/components/atoms/VerdictBadge.jsx +0 -36
  97. package/web/src/components/atoms/VerdictBadge.module.css +0 -40
  98. package/web/src/components/changelog/ChangelogView.jsx +0 -135
  99. package/web/src/components/changelog/ChangelogView.module.css +0 -159
  100. package/web/src/components/dashboard/DetailPanel.jsx +0 -217
  101. package/web/src/components/dashboard/DetailPanel.module.css +0 -260
  102. package/web/src/components/dashboard/ExpandedDetailRow.jsx +0 -361
  103. package/web/src/components/dashboard/ExpandedDetailRow.module.css +0 -356
  104. package/web/src/components/dashboard/ExportModal.jsx +0 -84
  105. package/web/src/components/dashboard/ExportModal.module.css +0 -99
  106. package/web/src/components/dashboard/FilterBar.jsx +0 -338
  107. package/web/src/components/dashboard/FilterBar.module.css +0 -342
  108. package/web/src/components/dashboard/ModelTable.jsx +0 -672
  109. package/web/src/components/dashboard/ModelTable.module.css +0 -446
  110. package/web/src/components/dashboard/ProviderDropdown.jsx +0 -156
  111. package/web/src/components/dashboard/ProviderDropdown.module.css +0 -248
  112. package/web/src/components/help/HelpView.jsx +0 -201
  113. package/web/src/components/help/HelpView.module.css +0 -156
  114. package/web/src/components/install/InstallEndpointsView.jsx +0 -309
  115. package/web/src/components/install/InstallEndpointsView.module.css +0 -427
  116. package/web/src/components/installed/InstalledModelsView.jsx +0 -89
  117. package/web/src/components/installed/InstalledModelsView.module.css +0 -200
  118. package/web/src/components/launch/IncompatibleFallbackModal.jsx +0 -69
  119. package/web/src/components/launch/LaunchButton.jsx +0 -30
  120. package/web/src/components/launch/LaunchButton.module.css +0 -35
  121. package/web/src/components/launch/LaunchModal.module.css +0 -125
  122. package/web/src/components/layout/Footer.jsx +0 -19
  123. package/web/src/components/layout/Footer.module.css +0 -10
  124. package/web/src/components/layout/Header.jsx +0 -265
  125. package/web/src/components/layout/Header.module.css +0 -357
  126. package/web/src/components/palette/CommandPalette.jsx +0 -301
  127. package/web/src/components/palette/CommandPalette.module.css +0 -121
  128. package/web/src/components/playground/PlaygroundChat.jsx +0 -527
  129. package/web/src/components/playground/PlaygroundChat.module.css +0 -382
  130. package/web/src/components/playground/PlaygroundView.jsx +0 -371
  131. package/web/src/components/playground/PlaygroundView.module.css +0 -504
  132. package/web/src/components/recommend/RecommendView.jsx +0 -121
  133. package/web/src/components/recommend/RecommendView.module.css +0 -57
  134. package/web/src/components/router/RouterView.jsx +0 -1212
  135. package/web/src/components/router/RouterView.module.css +0 -1289
  136. package/web/src/components/settings/SettingsView.jsx +0 -555
  137. package/web/src/components/settings/SettingsView.module.css +0 -550
  138. package/web/src/components/tools/ToolPicker.jsx +0 -86
  139. package/web/src/components/tools/ToolPicker.module.css +0 -97
  140. package/web/src/components/update/UpdateChip.jsx +0 -104
  141. package/web/src/components/update/UpdateChip.module.css +0 -146
  142. package/web/src/global.css +0 -298
  143. package/web/src/hooks/urlState.constants.js +0 -28
  144. package/web/src/hooks/useChangelog.js +0 -51
  145. package/web/src/hooks/useColumnSizing.js +0 -211
  146. package/web/src/hooks/useFavorites.js +0 -150
  147. package/web/src/hooks/useFilter.js +0 -310
  148. package/web/src/hooks/useInstalledModels.js +0 -42
  149. package/web/src/hooks/useRecommend.js +0 -67
  150. package/web/src/hooks/useRouterDashboard.js +0 -133
  151. package/web/src/hooks/useSSE.js +0 -73
  152. package/web/src/hooks/useSocket.js +0 -203
  153. package/web/src/hooks/useTheme.js +0 -83
  154. package/web/src/hooks/useTokenUsage.js +0 -103
  155. package/web/src/hooks/useToolMode.js +0 -77
  156. package/web/src/hooks/useUpdateChecker.js +0 -91
  157. package/web/src/hooks/useUrlState.js +0 -170
  158. package/web/src/main.jsx +0 -15
  159. package/web/src/utils/download.js +0 -15
  160. package/web/src/utils/format.js +0 -42
  161. package/web/src/utils/m3.js +0 -57
  162. package/web/src/utils/ranks.js +0 -37
  163. 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
- }