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,504 +0,0 @@
1
- /**
2
- * @file web/src/components/playground/PlaygroundView.module.css
3
- * @description Styles for the Playground chat modal — multi-turn chat, model
4
- * selector, streaming response, and routed-via metadata.
5
- */
6
-
7
- .overlay {
8
- display: flex;
9
- flex-direction: column;
10
- flex: 1;
11
- min-height: 0;
12
- width: 100%;
13
- padding: 24px;
14
- }
15
-
16
- .modal {
17
- background: var(--color-bg-card, #080908);
18
- border: 1px solid var(--color-border, #161a16);
19
- border-radius: 12px;
20
- width: 100%;
21
- max-width: 1000px;
22
- margin: 0 auto;
23
- flex: 1;
24
- display: flex;
25
- flex-direction: column;
26
- box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
27
- min-height: 0;
28
- }
29
-
30
- @keyframes fadeIn {
31
- from { opacity: 0; }
32
- to { opacity: 1; }
33
- }
34
-
35
- @keyframes slideUp {
36
- from { transform: translateY(8px); opacity: 0; }
37
- to { transform: translateY(0); opacity: 1; }
38
- }
39
-
40
- .header {
41
- display: flex;
42
- align-items: center;
43
- justify-content: space-between;
44
- padding: 16px 20px;
45
- border-bottom: 1px solid var(--border, #1e1e2e);
46
- flex-shrink: 0;
47
- }
48
-
49
- .headerLeft {
50
- display: flex;
51
- align-items: center;
52
- gap: 12px;
53
- min-width: 0;
54
- }
55
-
56
- .headerTitle {
57
- font-size: 16px;
58
- font-weight: 600;
59
- display: flex;
60
- align-items: center;
61
- gap: 8px;
62
- }
63
-
64
- .headerSubtitle {
65
- font-size: 12px;
66
- color: var(--text-muted, #888);
67
- font-weight: 400;
68
- }
69
-
70
- .headerActions {
71
- display: flex;
72
- align-items: center;
73
- gap: 6px;
74
- }
75
-
76
- .iconBtn {
77
- background: transparent;
78
- border: 1px solid var(--border, #1e1e2e);
79
- border-radius: 6px;
80
- color: var(--text-muted, #888);
81
- cursor: pointer;
82
- padding: 6px;
83
- display: flex;
84
- align-items: center;
85
- justify-content: center;
86
- transition: color 0.12s, border-color 0.12s;
87
- }
88
-
89
- .iconBtn:hover {
90
- color: var(--text, #eee);
91
- border-color: var(--accent, #22c55e);
92
- }
93
-
94
- .modelBar {
95
- display: flex;
96
- align-items: center;
97
- gap: 10px;
98
- padding: 10px 20px;
99
- border-bottom: 1px solid var(--border, #1e1e2e);
100
- background: var(--bg-secondary, #16161e);
101
- font-size: 12px;
102
- flex-shrink: 0;
103
- }
104
-
105
- .modelLabel {
106
- color: var(--text-muted, #888);
107
- font-weight: 500;
108
- }
109
-
110
- .modelSelect {
111
- flex: 1;
112
- background: var(--bg-primary, #0f0f17);
113
- border: 1px solid var(--border, #1e1e2e);
114
- border-radius: 6px;
115
- color: var(--text, #eee);
116
- padding: 6px 10px;
117
- font-size: 12px;
118
- font-family: inherit;
119
- cursor: pointer;
120
- max-width: 360px;
121
- }
122
-
123
- .modelSelect:focus {
124
- outline: none;
125
- border-color: var(--accent, #22c55e);
126
- }
127
-
128
- .presetChip {
129
- background: var(--bg-primary, #0f0f17);
130
- border: 1px solid var(--border, #1e1e2e);
131
- border-radius: 6px;
132
- color: var(--text, #eee);
133
- padding: 4px 8px;
134
- font-size: 11px;
135
- font-family: inherit;
136
- cursor: pointer;
137
- transition: border-color 0.12s, color 0.12s;
138
- }
139
-
140
- .presetChip:hover {
141
- border-color: var(--accent, #22c55e);
142
- color: var(--accent, #22c55e);
143
- }
144
-
145
- .prePromptToggle {
146
- display: flex;
147
- align-items: center;
148
- gap: 6px;
149
- color: var(--text-muted, #888);
150
- cursor: pointer;
151
- user-select: none;
152
- }
153
-
154
- .prePromptToggle input {
155
- cursor: pointer;
156
- }
157
-
158
- .transcript {
159
- flex: 1;
160
- overflow-y: auto;
161
- padding: 18px 20px;
162
- display: flex;
163
- flex-direction: column;
164
- gap: 14px;
165
- min-height: 320px;
166
- }
167
-
168
- /* 📖 Flex container that fills the modal body and hosts either the router
169
- daemon start/starting panels or the shared <PlaygroundChat> (which itself is
170
- a column flex that scrolls internally and pins the input at the bottom). */
171
- .chatBody {
172
- flex: 1;
173
- min-height: 320px;
174
- display: flex;
175
- flex-direction: column;
176
- }
177
-
178
- .empty {
179
- flex: 1;
180
- display: flex;
181
- flex-direction: column;
182
- align-items: center;
183
- justify-content: center;
184
- color: var(--text-muted, #888);
185
- text-align: center;
186
- padding: 40px 20px;
187
- gap: 8px;
188
- }
189
-
190
- .emptyTitle {
191
- font-size: 16px;
192
- color: var(--text, #eee);
193
- font-weight: 600;
194
- }
195
-
196
- .emptyHint {
197
- font-size: 12px;
198
- max-width: 380px;
199
- line-height: 1.5;
200
- }
201
-
202
- .message {
203
- display: flex;
204
- flex-direction: column;
205
- gap: 4px;
206
- max-width: 88%;
207
- animation: msgIn 0.15s ease;
208
- }
209
-
210
- @keyframes msgIn {
211
- from { opacity: 0; transform: translateY(2px); }
212
- to { opacity: 1; transform: translateY(0); }
213
- }
214
-
215
- .message.user {
216
- align-self: flex-end;
217
- }
218
-
219
- .message.assistant {
220
- align-self: flex-start;
221
- }
222
-
223
- .message.system {
224
- align-self: center;
225
- max-width: 100%;
226
- }
227
-
228
- .messageRole {
229
- font-size: 11px;
230
- text-transform: uppercase;
231
- letter-spacing: 0.05em;
232
- color: var(--text-muted, #888);
233
- font-weight: 600;
234
- padding: 0 4px;
235
- }
236
-
237
- .bubble {
238
- padding: 10px 14px;
239
- border-radius: 10px;
240
- font-size: 13px;
241
- line-height: 1.5;
242
- white-space: pre-wrap;
243
- word-wrap: break-word;
244
- overflow-wrap: break-word;
245
- }
246
-
247
- .message.user .bubble {
248
- background: var(--accent, #22c55e);
249
- color: #061a08;
250
- }
251
-
252
- .message.assistant .bubble {
253
- background: var(--bg-secondary, #16161e);
254
- border: 1px solid var(--border, #1e1e2e);
255
- color: var(--text, #eee);
256
- }
257
-
258
- .message.system .bubble {
259
- background: transparent;
260
- border: 1px dashed var(--border, #1e1e2e);
261
- color: var(--text-muted, #888);
262
- font-size: 12px;
263
- font-style: italic;
264
- text-align: center;
265
- max-width: 100%;
266
- }
267
-
268
- .codeBlock {
269
- background: rgba(0, 0, 0, 0.35);
270
- border: 1px solid var(--border, #1e1e2e);
271
- border-radius: 6px;
272
- padding: 8px 10px;
273
- font-family: 'Menlo', 'Monaco', 'Courier New', monospace;
274
- font-size: 12px;
275
- overflow-x: auto;
276
- margin: 6px 0;
277
- white-space: pre;
278
- }
279
-
280
- .meta {
281
- display: flex;
282
- align-items: center;
283
- gap: 10px;
284
- font-size: 11px;
285
- color: var(--text-muted, #888);
286
- padding: 0 4px;
287
- }
288
-
289
- .metaChip {
290
- display: inline-flex;
291
- align-items: center;
292
- gap: 4px;
293
- background: var(--bg-secondary, #16161e);
294
- border: 1px solid var(--border, #1e1e2e);
295
- border-radius: 4px;
296
- padding: 2px 6px;
297
- font-family: 'Menlo', 'Monaco', monospace;
298
- }
299
-
300
- .metaChip.provider {
301
- color: var(--accent, #22c55e);
302
- border-color: rgba(34, 197, 94, 0.3);
303
- }
304
-
305
- .metaChip.error {
306
- color: #f87171;
307
- border-color: rgba(248, 113, 113, 0.3);
308
- }
309
-
310
- .cursor {
311
- display: inline-block;
312
- width: 7px;
313
- height: 14px;
314
- background: var(--accent, #22c55e);
315
- margin-left: 2px;
316
- vertical-align: text-bottom;
317
- animation: blink 0.9s infinite;
318
- }
319
-
320
- @keyframes blink {
321
- 0%, 50% { opacity: 1; }
322
- 51%, 100% { opacity: 0; }
323
- }
324
-
325
- .inputBar {
326
- display: flex;
327
- gap: 8px;
328
- padding: 12px 16px;
329
- border-top: 1px solid var(--border, #1e1e2e);
330
- flex-shrink: 0;
331
- align-items: flex-end;
332
- background: var(--bg-secondary, #16161e);
333
- }
334
-
335
- .textarea {
336
- flex: 1;
337
- resize: none;
338
- min-height: 40px;
339
- max-height: 180px;
340
- background: var(--bg-primary, #0f0f17);
341
- border: 1px solid var(--border, #1e1e2e);
342
- border-radius: 8px;
343
- color: var(--text, #eee);
344
- padding: 9px 12px;
345
- font-size: 13px;
346
- font-family: inherit;
347
- line-height: 1.4;
348
- }
349
-
350
- .textarea:focus {
351
- outline: none;
352
- border-color: var(--accent, #22c55e);
353
- }
354
-
355
- .textarea:disabled {
356
- opacity: 0.5;
357
- cursor: not-allowed;
358
- }
359
-
360
- .sendBtn {
361
- background: var(--accent, #22c55e);
362
- color: #061a08;
363
- border: none;
364
- border-radius: 8px;
365
- padding: 9px 14px;
366
- font-size: 13px;
367
- font-weight: 600;
368
- cursor: pointer;
369
- display: flex;
370
- align-items: center;
371
- gap: 6px;
372
- height: 40px;
373
- font-family: inherit;
374
- }
375
-
376
- .sendBtn:disabled {
377
- opacity: 0.5;
378
- cursor: not-allowed;
379
- }
380
-
381
- .sendBtn:hover:not(:disabled) {
382
- filter: brightness(1.1);
383
- }
384
-
385
- .errorBar {
386
- padding: 8px 16px;
387
- background: rgba(248, 113, 113, 0.12);
388
- border-top: 1px solid rgba(248, 113, 113, 0.3);
389
- color: #f87171;
390
- font-size: 12px;
391
- display: flex;
392
- align-items: center;
393
- gap: 6px;
394
- flex-shrink: 0;
395
- }
396
-
397
- .stopBar {
398
- padding: 6px 16px;
399
- background: var(--bg-secondary, #16161e);
400
- border-top: 1px solid var(--border, #1e1e2e);
401
- display: flex;
402
- align-items: center;
403
- justify-content: space-between;
404
- font-size: 12px;
405
- color: var(--text-muted, #888);
406
- flex-shrink: 0;
407
- }
408
-
409
- .stopBtn {
410
- background: transparent;
411
- border: 1px solid #f87171;
412
- color: #f87171;
413
- border-radius: 6px;
414
- padding: 4px 10px;
415
- font-size: 12px;
416
- cursor: pointer;
417
- font-family: inherit;
418
- }
419
-
420
- .stopBtn:hover {
421
- background: rgba(248, 113, 113, 0.1);
422
- }
423
-
424
- /* ── Daemon start panel ─────────────────────────────────────────────── */
425
-
426
- .daemonStartPanel {
427
- flex: 1;
428
- display: flex;
429
- flex-direction: column;
430
- align-items: center;
431
- justify-content: center;
432
- color: var(--text-muted, #888);
433
- text-align: center;
434
- padding: 40px 24px;
435
- gap: 12px;
436
- }
437
-
438
- .daemonStartTitle {
439
- font-size: 16px;
440
- color: var(--text, #eee);
441
- font-weight: 600;
442
- }
443
-
444
- .daemonStartHint {
445
- font-size: 12px;
446
- max-width: 400px;
447
- line-height: 1.5;
448
- opacity: 0.7;
449
- }
450
-
451
- .daemonStartBtn {
452
- margin-top: 4px;
453
- background: var(--accent, #22c55e);
454
- color: #061a08;
455
- border: none;
456
- border-radius: 8px;
457
- padding: 10px 20px;
458
- font-size: 14px;
459
- font-weight: 600;
460
- cursor: pointer;
461
- display: flex;
462
- align-items: center;
463
- gap: 8px;
464
- font-family: inherit;
465
- transition: opacity 0.2s, transform 0.15s, filter 0.12s;
466
- }
467
-
468
- .daemonStartBtn:disabled {
469
- opacity: 0.45;
470
- cursor: not-allowed;
471
- filter: grayscale(0.5);
472
- }
473
-
474
- .daemonStartBtn:hover:not(:disabled) {
475
- filter: brightness(1.1);
476
- transform: scale(1.02);
477
- }
478
-
479
- .daemonStartBtn:active:not(:disabled) {
480
- transform: scale(0.98);
481
- }
482
-
483
- .daemonStartAlt {
484
- font-size: 11px;
485
- opacity: 0.5;
486
- margin-top: 4px;
487
- }
488
-
489
- .daemonStartAlt code {
490
- background: var(--bg-secondary, #16161e);
491
- padding: 2px 6px;
492
- border-radius: 4px;
493
- font-family: 'Menlo', 'Monaco', 'Courier New', monospace;
494
- font-size: 11px;
495
- }
496
-
497
- .spin {
498
- animation: spinAnim 1s linear infinite;
499
- }
500
-
501
- @keyframes spinAnim {
502
- from { transform: rotate(0deg); }
503
- to { transform: rotate(360deg); }
504
- }
@@ -1,121 +0,0 @@
1
- /**
2
- * @file web/src/components/recommend/RecommendView.jsx
3
- * @description Smart Recommend modal for Web M3 parity: 3-question wizard,
4
- * 10-second analysis phase, then Top 3 results from the shared scoring engine.
5
- *
6
- * @functions RecommendView → full-screen recommendation wizard
7
- * @exports RecommendView
8
- */
9
- import { useMemo, useState } from 'react'
10
- import { IconArrowLeft, IconSparkles, IconX } from '@tabler/icons-react'
11
- import { TASK_TYPES, PRIORITY_TYPES, CONTEXT_BUDGETS } from '../../../../src/core/utils.js'
12
- import LaunchButton from '../launch/LaunchButton.jsx'
13
- import { useRecommend } from '../../hooks/useRecommend.js'
14
- import styles from './RecommendView.module.css'
15
-
16
- const QUESTIONS = [
17
- { key: 'taskType', title: 'What are you working on?', options: TASK_TYPES },
18
- { key: 'priority', title: 'What matters most?', options: PRIORITY_TYPES },
19
- { key: 'contextBudget', title: 'How big is your context?', options: CONTEXT_BUDGETS },
20
- ]
21
-
22
- export default function RecommendView({ onClose, onLaunch, onPinAndLaunch, toolMode, onToast }) {
23
- const [step, setStep] = useState(0)
24
- const [answers, setAnswers] = useState({ taskType: null, priority: null, contextBudget: null })
25
- const { recommend, loading, progress, results, error, reset } = useRecommend({ onToast })
26
- const question = QUESTIONS[step]
27
- const complete = results.length > 0
28
-
29
- const summary = useMemo(() => QUESTIONS
30
- .map((q) => q.options[answers[q.key]]?.label)
31
- .filter(Boolean)
32
- .join(' · '), [answers])
33
-
34
- async function choose(value) {
35
- const nextAnswers = { ...answers, [question.key]: value }
36
- setAnswers(nextAnswers)
37
- if (step < QUESTIONS.length - 1) {
38
- setStep(step + 1)
39
- return
40
- }
41
- await recommend(nextAnswers)
42
- }
43
-
44
- function back() {
45
- if (complete || loading) {
46
- reset()
47
- setStep(0)
48
- setAnswers({ taskType: null, priority: null, contextBudget: null })
49
- return
50
- }
51
- setStep((value) => Math.max(0, value - 1))
52
- }
53
-
54
- return (
55
- <div className={styles.backdrop} onClick={onClose}>
56
- <div className={styles.modal} onClick={(event) => event.stopPropagation()}>
57
- <header className={styles.header}>
58
- <div>
59
- <p className={styles.eyebrow}>Smart Recommend</p>
60
- <h2><IconSparkles size={20} /> Find the best model for this task</h2>
61
- {summary && <p className={styles.summary}>{summary}</p>}
62
- </div>
63
- <button className={styles.close} onClick={onClose} aria-label="Close"><IconX size={18} /></button>
64
- </header>
65
-
66
- {!loading && !complete && (
67
- <main className={styles.body}>
68
- <div className={styles.steps}>Question {step + 1} / {QUESTIONS.length}</div>
69
- <h3>{question.title}</h3>
70
- <div className={styles.options}>
71
- {Object.entries(question.options).map(([key, option]) => (
72
- <button key={key} className={styles.option} onClick={() => choose(key)}>
73
- <strong>{option.label}</strong>
74
- <span>{key}</span>
75
- </button>
76
- ))}
77
- </div>
78
- </main>
79
- )}
80
-
81
- {loading && (
82
- <main className={styles.body}>
83
- <h3>Analyzing live catalog…</h3>
84
- <p className={styles.muted}>10 seconds · roughly 2 checks/sec · same scoring as the TUI.</p>
85
- <div className={styles.progress}><span style={{ width: `${progress}%` }} /></div>
86
- <p className={styles.percent}>{progress}%</p>
87
- </main>
88
- )}
89
-
90
- {complete && (
91
- <main className={styles.body}>
92
- <h3>Top 3 recommendations</h3>
93
- <div className={styles.results}>
94
- {results.map((item, index) => (
95
- <article key={`${item.result.providerKey}/${item.result.modelId}`} className={styles.resultCard}>
96
- <div className={styles.medal}>{['🥇', '🥈', '🥉'][index]}</div>
97
- <div className={styles.resultMain}>
98
- <strong>{item.result.label}</strong>
99
- <span>{item.result.origin} · {item.result.tier} · {item.result.ctx}</span>
100
- <p>{item.reason}</p>
101
- </div>
102
- <div className={styles.score}>{item.score}</div>
103
- <LaunchButton model={item.result} toolMode={toolMode} onLaunch={() => onPinAndLaunch?.(item.result)} />
104
- </article>
105
- ))}
106
- </div>
107
- </main>
108
- )}
109
-
110
- {error && <p className={styles.error}>{error}</p>}
111
-
112
- <footer className={styles.footer}>
113
- <button className={styles.secondary} onClick={back} disabled={step === 0 && !loading && !complete}>
114
- <IconArrowLeft size={14} /> {complete || loading ? 'Restart' : 'Back'}
115
- </button>
116
- <button className={styles.secondary} onClick={onClose}>Close</button>
117
- </footer>
118
- </div>
119
- </div>
120
- )
121
- }
@@ -1,57 +0,0 @@
1
- /** @file web/src/components/recommend/RecommendView.module.css */
2
- .backdrop {
3
- display: flex;
4
- flex-direction: column;
5
- flex: 1;
6
- min-height: 0;
7
- width: 100%;
8
- padding: 24px;
9
- }
10
- .modal {
11
- width: 100%;
12
- max-width: 980px;
13
- margin: 0 auto;
14
- overflow: auto;
15
- border: 1px solid var(--color-border);
16
- border-radius: 16px;
17
- background: var(--color-bg-card, #080908);
18
- color: var(--color-text);
19
- box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
20
- flex: 1;
21
- display: flex;
22
- flex-direction: column;
23
- min-height: 0;
24
- }
25
- .header,
26
- .footer { display: flex; align-items: center; justify-content: space-between; gap: 14px; padding: 20px 24px; border-bottom: 1px solid var(--color-border); }
27
- .footer { border-top: 1px solid var(--color-border); border-bottom: 0; justify-content: flex-end; }
28
- .header h2 { display: flex; align-items: center; gap: 9px; margin: 4px 0; font-size: 21px; }
29
- .eyebrow { margin: 0; color: var(--color-accent); font-size: 11px; font-weight: 900; text-transform: uppercase; letter-spacing: 1px; }
30
- .summary,
31
- .muted { color: var(--color-text-muted); margin: 0; }
32
- .close,
33
- .secondary { display: inline-flex; align-items: center; gap: 6px; border: 1px solid var(--color-border); border-radius: 8px; background: var(--color-surface); color: var(--color-text); cursor: pointer; }
34
- .close { width: 32px; height: 32px; justify-content: center; }
35
- .secondary { padding: 8px 12px; font-weight: 800; }
36
- .secondary:disabled { opacity: .45; cursor: not-allowed; }
37
- .body { padding: 26px 28px; }
38
- .steps { color: var(--color-accent); font-family: var(--font-mono); font-size: 12px; font-weight: 800; margin-bottom: 10px; }
39
- .body h3 { margin: 0 0 18px; font-size: 20px; }
40
- .options { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 14px; }
41
- .option { min-height: 120px; padding: 18px; border: 1px solid var(--color-border); border-radius: 14px; background: var(--color-surface); color: var(--color-text); cursor: pointer; text-align: left; transition: transform 120ms, border-color 120ms, background 120ms; }
42
- .option:hover { transform: translateY(-2px); border-color: var(--color-accent); background: var(--color-bg-hover); }
43
- .option strong { display: block; font-size: 16px; margin-bottom: 8px; }
44
- .option span { color: var(--color-text-dim); font-family: var(--font-mono); font-size: 11px; }
45
- .progress { height: 14px; border: 1px solid var(--color-border); border-radius: 999px; overflow: hidden; background: var(--color-surface); }
46
- .progress span { display: block; height: 100%; background: linear-gradient(90deg, var(--color-accent), #b400ff); transition: width 200ms; }
47
- .percent { font-family: var(--font-mono); color: var(--color-accent); font-weight: 900; }
48
- .results { display: flex; flex-direction: column; gap: 12px; }
49
- .resultCard { display: grid; grid-template-columns: 38px 1fr 60px auto; align-items: center; gap: 14px; padding: 14px; border: 1px solid var(--color-border); border-radius: 14px; background: var(--color-surface); }
50
- .medal { font-size: 26px; }
51
- .resultMain { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
52
- .resultMain strong { font-size: 15px; }
53
- .resultMain span { color: var(--color-text-muted); font-size: 12px; }
54
- .resultMain p { margin: 5px 0 0; color: var(--color-text-dim); font-size: 12px; }
55
- .score { font-family: var(--font-mono); color: var(--color-accent); font-size: 24px; font-weight: 900; text-align: center; }
56
- .error { margin: 0 24px 16px; color: #ff5a7a; font-weight: 800; }
57
- @media (max-width: 760px) { .options { grid-template-columns: 1fr; } .resultCard { grid-template-columns: 32px 1fr; } .score { text-align: left; } }