free-coding-models 0.5.85 → 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 (164) hide show
  1. package/README.md +110 -881
  2. package/bin/free-coding-models.js +4 -1
  3. package/changelog/v0.5.86.md +10 -0
  4. package/changelog/v0.5.87.md +48 -0
  5. package/package.json +8 -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/shared-helpers.js +12 -1
  26. package/src/core/shell-env.js +3 -1
  27. package/src/core/telemetry.js +18 -5
  28. package/src/core/tool-launchers.js +9 -6
  29. package/src/core/updater.js +28 -2
  30. package/src/core/utils.js +76 -71
  31. package/src/tui/app.js +47 -16
  32. package/src/tui/command-palette.js +22 -2
  33. package/src/tui/key-handler.js +123 -179
  34. package/src/tui/overlays.js +35 -75
  35. package/src/tui/render-helpers.js +62 -4
  36. package/src/tui/render-table.js +46 -5
  37. package/src/tui/theme.js +36 -5
  38. package/src/tui/tui-filters.js +28 -8
  39. package/src/tui/tui-state.js +22 -6
  40. package/web/dist/assets/index-BpTpPS0g.js +44 -0
  41. package/web/dist/assets/index-CAzFIt8P.css +1 -0
  42. package/web/dist/index.html +5 -9
  43. package/web/index.html +3 -7
  44. package/web/server.js +97 -23
  45. package/web/vite.config.js +8 -0
  46. package/web/app.legacy.js +0 -900
  47. package/web/dist/assets/index-C5-Ywvv3.css +0 -1
  48. package/web/dist/assets/index-DWUely84.js +0 -40
  49. package/web/public/favicon.ico +0 -0
  50. package/web/public/favicons/apple-touch-icon.png +0 -0
  51. package/web/public/favicons/browserconfig.xml +0 -12
  52. package/web/public/favicons/favicon-16x16.png +0 -0
  53. package/web/public/favicons/favicon-192x192.png +0 -0
  54. package/web/public/favicons/favicon-32x32.png +0 -0
  55. package/web/public/favicons/favicon-48x48.png +0 -0
  56. package/web/public/favicons/favicon-512x512.png +0 -0
  57. package/web/public/favicons/favicon-96x96.png +0 -0
  58. package/web/public/favicons/favicon.ico +0 -0
  59. package/web/public/favicons/mstile-150x150.png +0 -0
  60. package/web/public/favicons/mstile-310x150.png +0 -0
  61. package/web/public/favicons/mstile-310x310.png +0 -0
  62. package/web/public/favicons/mstile-512x512.png +0 -0
  63. package/web/public/favicons/mstile-70x70.png +0 -0
  64. package/web/public/favicons/site.webmanifest +0 -25
  65. package/web/src/App.jsx +0 -521
  66. package/web/src/components/analytics/AnalyticsView.jsx +0 -147
  67. package/web/src/components/analytics/AnalyticsView.module.css +0 -190
  68. package/web/src/components/analytics/TokenUsagePanel.jsx +0 -105
  69. package/web/src/components/analytics/TokenUsagePanel.module.css +0 -126
  70. package/web/src/components/atoms/AILatencyCell.jsx +0 -38
  71. package/web/src/components/atoms/AILatencyCell.module.css +0 -51
  72. package/web/src/components/atoms/HealthCell.jsx +0 -143
  73. package/web/src/components/atoms/HealthCell.module.css +0 -60
  74. package/web/src/components/atoms/LastPingCell.jsx +0 -35
  75. package/web/src/components/atoms/LastPingCell.module.css +0 -45
  76. package/web/src/components/atoms/MoodCell.jsx +0 -25
  77. package/web/src/components/atoms/MoodCell.module.css +0 -6
  78. package/web/src/components/atoms/NoKeyIcon.jsx +0 -65
  79. package/web/src/components/atoms/NoKeyIcon.module.css +0 -30
  80. package/web/src/components/atoms/ProviderLogo.jsx +0 -187
  81. package/web/src/components/atoms/ProviderLogo.module.css +0 -103
  82. package/web/src/components/atoms/RankCell.jsx +0 -9
  83. package/web/src/components/atoms/RankCell.module.css +0 -9
  84. package/web/src/components/atoms/Sparkline.jsx +0 -63
  85. package/web/src/components/atoms/StabilityCell.jsx +0 -18
  86. package/web/src/components/atoms/StabilityCell.module.css +0 -13
  87. package/web/src/components/atoms/StatusDot.jsx +0 -13
  88. package/web/src/components/atoms/StatusDot.module.css +0 -29
  89. package/web/src/components/atoms/TPSCell.jsx +0 -36
  90. package/web/src/components/atoms/TPSCell.module.css +0 -44
  91. package/web/src/components/atoms/TierBadge.jsx +0 -10
  92. package/web/src/components/atoms/TierBadge.module.css +0 -29
  93. package/web/src/components/atoms/Toast.jsx +0 -25
  94. package/web/src/components/atoms/Toast.module.css +0 -41
  95. package/web/src/components/atoms/ToastContainer.jsx +0 -16
  96. package/web/src/components/atoms/ToastContainer.module.css +0 -10
  97. package/web/src/components/atoms/VerdictBadge.jsx +0 -36
  98. package/web/src/components/atoms/VerdictBadge.module.css +0 -40
  99. package/web/src/components/changelog/ChangelogView.jsx +0 -135
  100. package/web/src/components/changelog/ChangelogView.module.css +0 -159
  101. package/web/src/components/dashboard/DetailPanel.jsx +0 -217
  102. package/web/src/components/dashboard/DetailPanel.module.css +0 -260
  103. package/web/src/components/dashboard/ExpandedDetailRow.jsx +0 -361
  104. package/web/src/components/dashboard/ExpandedDetailRow.module.css +0 -356
  105. package/web/src/components/dashboard/ExportModal.jsx +0 -84
  106. package/web/src/components/dashboard/ExportModal.module.css +0 -99
  107. package/web/src/components/dashboard/FilterBar.jsx +0 -338
  108. package/web/src/components/dashboard/FilterBar.module.css +0 -342
  109. package/web/src/components/dashboard/ModelTable.jsx +0 -672
  110. package/web/src/components/dashboard/ModelTable.module.css +0 -446
  111. package/web/src/components/dashboard/ProviderDropdown.jsx +0 -156
  112. package/web/src/components/dashboard/ProviderDropdown.module.css +0 -248
  113. package/web/src/components/help/HelpView.jsx +0 -201
  114. package/web/src/components/help/HelpView.module.css +0 -156
  115. package/web/src/components/install/InstallEndpointsView.jsx +0 -309
  116. package/web/src/components/install/InstallEndpointsView.module.css +0 -427
  117. package/web/src/components/installed/InstalledModelsView.jsx +0 -89
  118. package/web/src/components/installed/InstalledModelsView.module.css +0 -200
  119. package/web/src/components/launch/IncompatibleFallbackModal.jsx +0 -69
  120. package/web/src/components/launch/LaunchButton.jsx +0 -30
  121. package/web/src/components/launch/LaunchButton.module.css +0 -35
  122. package/web/src/components/launch/LaunchModal.module.css +0 -125
  123. package/web/src/components/layout/Footer.jsx +0 -19
  124. package/web/src/components/layout/Footer.module.css +0 -10
  125. package/web/src/components/layout/Header.jsx +0 -265
  126. package/web/src/components/layout/Header.module.css +0 -357
  127. package/web/src/components/palette/CommandPalette.jsx +0 -301
  128. package/web/src/components/palette/CommandPalette.module.css +0 -121
  129. package/web/src/components/playground/PlaygroundChat.jsx +0 -527
  130. package/web/src/components/playground/PlaygroundChat.module.css +0 -382
  131. package/web/src/components/playground/PlaygroundView.jsx +0 -371
  132. package/web/src/components/playground/PlaygroundView.module.css +0 -504
  133. package/web/src/components/recommend/RecommendView.jsx +0 -121
  134. package/web/src/components/recommend/RecommendView.module.css +0 -57
  135. package/web/src/components/router/RouterView.jsx +0 -1212
  136. package/web/src/components/router/RouterView.module.css +0 -1289
  137. package/web/src/components/settings/SettingsView.jsx +0 -555
  138. package/web/src/components/settings/SettingsView.module.css +0 -550
  139. package/web/src/components/tools/ToolPicker.jsx +0 -86
  140. package/web/src/components/tools/ToolPicker.module.css +0 -97
  141. package/web/src/components/update/UpdateChip.jsx +0 -104
  142. package/web/src/components/update/UpdateChip.module.css +0 -146
  143. package/web/src/global.css +0 -298
  144. package/web/src/hooks/urlState.constants.js +0 -28
  145. package/web/src/hooks/useChangelog.js +0 -51
  146. package/web/src/hooks/useColumnSizing.js +0 -211
  147. package/web/src/hooks/useFavorites.js +0 -150
  148. package/web/src/hooks/useFilter.js +0 -310
  149. package/web/src/hooks/useInstalledModels.js +0 -42
  150. package/web/src/hooks/useRecommend.js +0 -67
  151. package/web/src/hooks/useRouterDashboard.js +0 -133
  152. package/web/src/hooks/useSSE.js +0 -73
  153. package/web/src/hooks/useSocket.js +0 -203
  154. package/web/src/hooks/useTheme.js +0 -83
  155. package/web/src/hooks/useTokenUsage.js +0 -103
  156. package/web/src/hooks/useToolMode.js +0 -77
  157. package/web/src/hooks/useUpdateChecker.js +0 -91
  158. package/web/src/hooks/useUrlState.js +0 -170
  159. package/web/src/main.jsx +0 -15
  160. package/web/src/utils/download.js +0 -15
  161. package/web/src/utils/format.js +0 -42
  162. package/web/src/utils/m3.js +0 -57
  163. package/web/src/utils/ranks.js +0 -37
  164. package/web/styles.legacy.css +0 -963
@@ -1,550 +0,0 @@
1
- .page {
2
- padding: 24px;
3
- }
4
-
5
- .pageHeader {
6
- margin-bottom: 24px;
7
- }
8
-
9
- .pageTitle {
10
- font-size: 24px;
11
- font-weight: 800;
12
- margin: 0 0 6px;
13
- }
14
-
15
- .pageSubtitle {
16
- font-size: 13px;
17
- color: var(--color-text-muted);
18
- line-height: 1.5;
19
- margin: 0;
20
- }
21
-
22
- .pageSubtitle code {
23
- font-family: var(--font-mono);
24
- font-size: 12px;
25
- background: var(--color-surface);
26
- padding: 2px 6px;
27
- border-radius: 4px;
28
- }
29
-
30
- .loading {
31
- text-align: center;
32
- padding: 60px 0;
33
- color: var(--color-text-muted);
34
- }
35
-
36
- /* ─── M2: feature toggles section ─── */
37
- .featureSection {
38
- margin-bottom: 28px;
39
- padding: 18px 20px;
40
- background: var(--color-bg-card);
41
- border: 1px solid var(--color-border);
42
- border-radius: 12px;
43
- }
44
- .sectionHeading {
45
- font-size: 11px;
46
- font-weight: 700;
47
- text-transform: uppercase;
48
- letter-spacing: 0.6px;
49
- color: var(--color-text-muted);
50
- margin: 0 0 14px;
51
- }
52
- .featureGrid {
53
- display: flex;
54
- flex-direction: column;
55
- gap: 4px;
56
- }
57
- .featureRow {
58
- display: flex;
59
- align-items: center;
60
- gap: 14px;
61
- padding: 10px 12px;
62
- border: 1px solid transparent;
63
- border-radius: 8px;
64
- transition: background 120ms;
65
- }
66
- .featureRow:hover { background: var(--color-surface); }
67
- .featureLabel {
68
- display: flex;
69
- align-items: flex-start;
70
- gap: 10px;
71
- flex: 1;
72
- min-width: 0;
73
- }
74
- .featureLabel > svg {
75
- flex-shrink: 0;
76
- color: var(--color-text-muted);
77
- margin-top: 2px;
78
- }
79
- .featureTitle {
80
- font-size: 13px;
81
- font-weight: 600;
82
- color: var(--color-text);
83
- line-height: 1.4;
84
- }
85
- .featureDesc {
86
- font-size: 11px;
87
- color: var(--color-text-muted);
88
- line-height: 1.5;
89
- margin-top: 2px;
90
- }
91
- .featureActions {
92
- display: flex;
93
- gap: 6px;
94
- flex-shrink: 0;
95
- }
96
- .select {
97
- background: var(--color-surface);
98
- color: var(--color-text);
99
- border: 1px solid var(--color-border);
100
- border-radius: 5px;
101
- padding: 5px 8px;
102
- font-size: 12px;
103
- font-family: var(--font-sans);
104
- cursor: pointer;
105
- outline: none;
106
- min-width: 130px;
107
- }
108
- .select:focus { border-color: var(--color-accent); }
109
-
110
- .toggleSwitch {
111
- position: relative;
112
- display: inline-block;
113
- width: 36px;
114
- height: 20px;
115
- flex-shrink: 0;
116
- }
117
- .toggleSwitch input {
118
- opacity: 0;
119
- width: 0;
120
- height: 0;
121
- }
122
- .toggleSlider {
123
- position: absolute;
124
- inset: 0;
125
- background: var(--color-border);
126
- border-radius: 20px;
127
- cursor: pointer;
128
- transition: background 150ms;
129
- }
130
- .toggleSlider::before {
131
- content: '';
132
- position: absolute;
133
- left: 2px;
134
- top: 2px;
135
- width: 16px;
136
- height: 16px;
137
- background: var(--color-text);
138
- border-radius: 50%;
139
- transition: transform 150ms;
140
- }
141
- .toggleSwitch input:checked + .toggleSlider {
142
- background: var(--color-accent);
143
- }
144
- .toggleSwitch input:checked + .toggleSlider::before {
145
- transform: translateX(16px);
146
- }
147
-
148
- .smallBtn {
149
- display: inline-flex;
150
- align-items: center;
151
- gap: 4px;
152
- padding: 5px 10px;
153
- font-size: 11px;
154
- font-weight: 600;
155
- font-family: var(--font-sans);
156
- background: var(--color-surface);
157
- color: var(--color-text);
158
- border: 1px solid var(--color-border);
159
- border-radius: 5px;
160
- cursor: pointer;
161
- white-space: nowrap;
162
- }
163
- .smallBtn:hover { background: var(--color-bg-hover); border-color: var(--color-text-muted); }
164
- .smallBtn:disabled { opacity: 0.5; cursor: not-allowed; }
165
-
166
- .notice {
167
- margin-top: 12px;
168
- padding: 8px 12px;
169
- background: var(--color-info-dim);
170
- color: var(--color-info);
171
- border: 1px solid var(--color-info);
172
- border-radius: 6px;
173
- font-size: 12px;
174
- line-height: 1.5;
175
- }
176
-
177
- /* ─── M2: per-provider test badge ─── */
178
- .testBadge {
179
- display: inline-flex;
180
- align-items: center;
181
- gap: 5px;
182
- padding: 4px 8px;
183
- border-radius: 5px;
184
- font-size: 10px;
185
- font-weight: 700;
186
- font-family: var(--font-mono);
187
- margin-bottom: 8px;
188
- align-self: flex-start;
189
- width: fit-content;
190
- }
191
- .testBadge.test_ok { background: var(--color-success-dim); color: var(--color-success); border: 1px solid var(--color-success); }
192
- .testBadge.test_auth_error { background: var(--color-danger-dim); color: var(--color-danger); border: 1px solid var(--color-danger); }
193
- .testBadge.test_rate_limited { background: var(--color-warning-dim); color: var(--color-warning); border: 1px solid var(--color-warning); }
194
- .testBadge.test_no_callable_model { background: var(--color-warning-dim); color: var(--color-warning); border: 1px solid var(--color-warning); }
195
- .testBadge.test_fail { background: var(--color-danger-dim); color: var(--color-danger); border: 1px solid var(--color-danger); }
196
- .testBadge.test_missing_key { background: var(--color-surface); color: var(--color-text-muted); border: 1px solid var(--color-border); }
197
- .testBadge.test_pending { background: var(--color-surface); color: var(--color-text-muted); border: 1px solid var(--color-border); }
198
-
199
- .testSpinner {
200
- display: inline-block;
201
- width: 10px;
202
- height: 10px;
203
- border: 2px solid rgba(180, 0, 255, 0.3);
204
- border-top-color: #b400ff;
205
- border-radius: 50%;
206
- animation: spin 0.8s linear infinite;
207
- }
208
- @keyframes spin { to { transform: rotate(360deg); } }
209
-
210
- .toolbar {
211
- display: flex;
212
- align-items: center;
213
- gap: 12px;
214
- margin-bottom: 20px;
215
- flex-wrap: wrap;
216
- }
217
-
218
- .toolbarSearch {
219
- display: flex;
220
- align-items: center;
221
- gap: 8px;
222
- background: var(--color-surface);
223
- border: 1px solid var(--color-border);
224
- border-radius: var(--radius-md);
225
- padding: 0 12px;
226
- height: 36px;
227
- flex: 1;
228
- min-width: 200px;
229
- }
230
-
231
- .toolbarSearch svg {
232
- color: var(--color-text-dim);
233
- flex-shrink: 0;
234
- }
235
-
236
- .toolbarSearch input {
237
- flex: 1;
238
- background: none;
239
- border: none;
240
- outline: none;
241
- color: var(--color-text);
242
- font-size: 13px;
243
- font-family: var(--font-sans);
244
- }
245
-
246
- .toolbarSearch input::placeholder {
247
- color: var(--color-text-dim);
248
- }
249
-
250
- .toolbarActions {
251
- display: flex;
252
- gap: 8px;
253
- }
254
-
255
- .toolbarBtn {
256
- display: inline-flex;
257
- align-items: center;
258
- gap: 6px;
259
- border: 1px solid var(--color-border);
260
- border-radius: var(--radius-sm);
261
- padding: 6px 14px;
262
- font-size: 13px;
263
- font-weight: 500;
264
- cursor: pointer;
265
- background: var(--color-surface);
266
- color: var(--color-text);
267
- transition: all var(--transition-fast);
268
- font-family: var(--font-sans);
269
- }
270
-
271
- .toolbarBtn:hover {
272
- background: var(--color-bg-hover);
273
- border-color: var(--color-border-hover);
274
- }
275
-
276
- .providers {
277
- display: flex;
278
- flex-direction: column;
279
- gap: 12px;
280
- }
281
-
282
- .card {
283
- background: var(--color-bg-card);
284
- backdrop-filter: blur(12px);
285
- border: 1px solid var(--color-border);
286
- border-radius: var(--radius-lg);
287
- overflow: hidden;
288
- transition: border-color var(--transition-fast);
289
- }
290
-
291
- .card:hover {
292
- border-color: var(--color-border-hover);
293
- }
294
-
295
- .cardHeader {
296
- display: flex;
297
- align-items: center;
298
- gap: 12px;
299
- padding: 16px 20px;
300
- cursor: pointer;
301
- transition: background var(--transition-fast);
302
- }
303
-
304
- .cardHeader:hover {
305
- background: var(--color-bg-hover);
306
- }
307
-
308
- .cardIcon {
309
- width: 36px;
310
- height: 36px;
311
- display: flex;
312
- align-items: center;
313
- justify-content: center;
314
- border-radius: var(--radius-sm);
315
- font-size: 18px;
316
- background: var(--color-accent-dim);
317
- flex-shrink: 0;
318
- }
319
-
320
- .cardInfo {
321
- flex: 1;
322
- min-width: 0;
323
- }
324
-
325
- .cardName {
326
- font-weight: 700;
327
- font-size: 14px;
328
- }
329
-
330
- .cardMeta {
331
- font-size: 11px;
332
- color: var(--color-text-muted);
333
- margin-top: 2px;
334
- }
335
-
336
- .cardStatus {
337
- font-size: 11px;
338
- font-weight: 700;
339
- padding: 3px 10px;
340
- border-radius: 999px;
341
- flex-shrink: 0;
342
- }
343
-
344
- .statusConfigured {
345
- background: var(--color-success-dim);
346
- color: var(--color-success);
347
- }
348
-
349
- .statusMissing {
350
- background: var(--color-warning-dim);
351
- color: var(--color-warning);
352
- }
353
-
354
- .toggleIcon {
355
- color: var(--color-text-dim);
356
- transition: transform var(--transition-fast);
357
- font-size: 18px;
358
- flex-shrink: 0;
359
- }
360
-
361
- .toggleIconExpanded {
362
- transform: rotate(180deg);
363
- }
364
-
365
- .cardBody {
366
- max-height: 0;
367
- overflow: hidden;
368
- transition: max-height var(--transition-medium);
369
- border-top: 0px solid transparent;
370
- }
371
-
372
- .cardExpanded .cardBody {
373
- max-height: 500px;
374
- border-top: 1px solid var(--color-border);
375
- }
376
-
377
- .cardContent {
378
- padding: 16px 20px;
379
- }
380
-
381
- .keyGroup {
382
- margin-bottom: 12px;
383
- }
384
-
385
- .keyLabel {
386
- display: block;
387
- font-size: 11px;
388
- font-weight: 600;
389
- color: var(--color-text-muted);
390
- text-transform: uppercase;
391
- letter-spacing: 0.5px;
392
- margin-bottom: 6px;
393
- }
394
-
395
- .keyDisplay {
396
- display: flex;
397
- align-items: center;
398
- gap: 8px;
399
- background: var(--color-surface);
400
- border: 1px solid var(--color-border);
401
- border-radius: var(--radius-sm);
402
- padding: 8px 12px;
403
- margin-bottom: 8px;
404
- }
405
-
406
- .keyDisplayValue {
407
- flex: 1;
408
- font-family: var(--font-mono);
409
- font-size: 13px;
410
- color: var(--color-text-muted);
411
- word-break: break-all;
412
- }
413
-
414
- .keyDisplayActions {
415
- display: flex;
416
- gap: 4px;
417
- flex-shrink: 0;
418
- }
419
-
420
- .actionBtn {
421
- display: inline-flex;
422
- align-items: center;
423
- justify-content: center;
424
- padding: 4px 8px;
425
- border: 1px solid var(--color-border);
426
- border-radius: var(--radius-sm);
427
- background: var(--color-surface);
428
- color: var(--color-text);
429
- cursor: pointer;
430
- font-size: 12px;
431
- transition: all var(--transition-fast);
432
- }
433
-
434
- .actionBtn:hover {
435
- background: var(--color-bg-hover);
436
- }
437
-
438
- .actionBtnDanger {
439
- background: var(--color-danger-dim);
440
- color: var(--color-danger);
441
- border-color: rgba(255, 68, 68, 0.2);
442
- }
443
-
444
- .actionBtnDanger:hover {
445
- background: rgba(255, 68, 68, 0.2);
446
- }
447
-
448
- .keyInputRow {
449
- display: flex;
450
- gap: 8px;
451
- align-items: center;
452
- }
453
-
454
- .keyInput {
455
- flex: 1;
456
- background: var(--color-surface);
457
- border: 1px solid var(--color-border);
458
- border-radius: var(--radius-sm);
459
- padding: 8px 12px;
460
- font-size: 13px;
461
- font-family: var(--font-mono);
462
- color: var(--color-text);
463
- outline: none;
464
- transition: border-color var(--transition-fast);
465
- }
466
-
467
- .keyInput:focus {
468
- border-color: var(--color-accent);
469
- }
470
-
471
- .keyInput::placeholder {
472
- color: var(--color-text-dim);
473
- }
474
-
475
- .saveBtn {
476
- display: inline-flex;
477
- align-items: center;
478
- gap: 6px;
479
- padding: 8px 14px;
480
- font-size: 13px;
481
- font-weight: 600;
482
- border: 1px solid rgba(0, 255, 136, 0.2);
483
- border-radius: var(--radius-sm);
484
- background: var(--color-success-dim);
485
- color: var(--color-success);
486
- cursor: pointer;
487
- font-family: var(--font-sans);
488
- transition: all var(--transition-fast);
489
- }
490
-
491
- .saveBtn:hover {
492
- background: rgba(0, 255, 136, 0.15);
493
- }
494
-
495
- .enabledRow {
496
- display: flex;
497
- align-items: center;
498
- justify-content: space-between;
499
- padding-top: 12px;
500
- border-top: 1px solid var(--color-border);
501
- margin-top: 12px;
502
- }
503
-
504
- .enabledLabel {
505
- font-size: 12px;
506
- color: var(--color-text-muted);
507
- font-weight: 500;
508
- }
509
-
510
- .toggleSwitch {
511
- position: relative;
512
- width: 44px;
513
- height: 24px;
514
- cursor: pointer;
515
- }
516
-
517
- .toggleSwitch input {
518
- display: none;
519
- }
520
-
521
- .toggleSlider {
522
- position: absolute;
523
- inset: 0;
524
- background: var(--color-surface);
525
- border: 1px solid var(--color-border);
526
- border-radius: 12px;
527
- transition: all var(--transition-fast);
528
- }
529
-
530
- .toggleSlider::after {
531
- content: '';
532
- position: absolute;
533
- left: 3px;
534
- top: 3px;
535
- width: 16px;
536
- height: 16px;
537
- background: var(--color-text-dim);
538
- border-radius: 50%;
539
- transition: all var(--transition-fast);
540
- }
541
-
542
- .toggleSwitch input:checked + .toggleSlider {
543
- background: var(--color-accent-dim);
544
- border-color: var(--color-accent);
545
- }
546
-
547
- .toggleSwitch input:checked + .toggleSlider::after {
548
- transform: translateX(20px);
549
- background: var(--color-accent);
550
- }
@@ -1,86 +0,0 @@
1
- /**
2
- * @file web/src/components/tools/ToolPicker.jsx
3
- * @description Header/dropdown control for the active Web endpoint-install target.
4
- * Mirrors the TUI install target flow with a mouse-first dropdown and cycle button.
5
- *
6
- * @functions
7
- * → ToolPicker — renders active tool, cycle control, and selectable tool list
8
- * @exports ToolPicker
9
- */
10
- import { useEffect, useRef, useState } from 'react'
11
- import { IconChevronDown, IconRefresh } from '@tabler/icons-react'
12
- import { TOOL_METADATA, getToolMeta } from '../../../../src/core/tool-metadata.js'
13
- import { INSTALL_ENDPOINT_TOOL_MODES } from '../../utils/m3.js'
14
- import styles from './ToolPicker.module.css'
15
-
16
- export default function ToolPicker({ toolMode = 'opencode', onSetToolMode, onCycleToolMode, compact = false, tools = INSTALL_ENDPOINT_TOOL_MODES }) {
17
- const [open, setOpen] = useState(false)
18
- const wrapRef = useRef(null)
19
- const active = getToolMeta(toolMode)
20
-
21
- useEffect(() => {
22
- if (!open) return
23
- const onPointer = (event) => {
24
- if (wrapRef.current && !wrapRef.current.contains(event.target)) setOpen(false)
25
- }
26
- const onKey = (event) => { if (event.key === 'Escape') setOpen(false) }
27
- document.addEventListener('mousedown', onPointer)
28
- document.addEventListener('keydown', onKey)
29
- return () => {
30
- document.removeEventListener('mousedown', onPointer)
31
- document.removeEventListener('keydown', onKey)
32
- }
33
- }, [open])
34
-
35
- return (
36
- <div className={`${styles.wrap} ${compact ? styles.compact : ''}`} ref={wrapRef}>
37
- <button
38
- type="button"
39
- className={styles.trigger}
40
- onClick={() => setOpen((value) => !value)}
41
- aria-haspopup="listbox"
42
- aria-expanded={open}
43
- title={`Install endpoint into: ${active.label}`}
44
- >
45
- <span className={styles.emoji}>{active.emoji}</span>
46
- <span className={styles.label}>{compact ? active.label.replace(/ CLI$/, '') : active.label}</span>
47
- <IconChevronDown size={13} stroke={1.7} />
48
- </button>
49
- <button
50
- type="button"
51
- className={styles.cycle}
52
- onClick={onCycleToolMode}
53
- title="Cycle endpoint target"
54
- aria-label="Cycle endpoint target"
55
- >
56
- <IconRefresh size={13} stroke={1.7} />
57
- </button>
58
-
59
- {open && (
60
- <div className={styles.menu} role="listbox" aria-label="Endpoint install target">
61
- {tools.map((mode) => {
62
- const meta = TOOL_METADATA[mode]
63
- const activeMode = mode === toolMode
64
- return (
65
- <button
66
- type="button"
67
- key={mode}
68
- className={`${styles.item} ${activeMode ? styles.itemActive : ''}`}
69
- onClick={() => { onSetToolMode?.(mode); setOpen(false) }}
70
- role="option"
71
- aria-selected={activeMode}
72
- >
73
- <span className={styles.itemEmoji}>{meta.emoji}</span>
74
- <span className={styles.itemText}>
75
- <strong>{meta.label}</strong>
76
- <small>{meta.flag}</small>
77
- </span>
78
- {activeMode && <span className={styles.activeDot}>●</span>}
79
- </button>
80
- )
81
- })}
82
- </div>
83
- )}
84
- </div>
85
- )
86
- }
@@ -1,97 +0,0 @@
1
- /**
2
- * @file web/src/components/tools/ToolPicker.module.css
3
- * @description Compact header dropdown styles for the M3 tool-mode picker.
4
- */
5
- .wrap {
6
- position: relative;
7
- display: inline-flex;
8
- align-items: center;
9
- gap: 4px;
10
- }
11
- .trigger {
12
- height: 30px;
13
- display: inline-flex;
14
- align-items: center;
15
- justify-content: center;
16
- gap: 6px;
17
- padding: 0 12px;
18
- border-radius: 6px;
19
- font-size: 11px;
20
- font-weight: 800;
21
- font-family: var(--font-mono);
22
- text-transform: uppercase;
23
- letter-spacing: 0.5px;
24
- background: var(--color-accent);
25
- color: var(--color-bg);
26
- border: 1px solid var(--color-accent);
27
- cursor: pointer;
28
- transition: all 150ms var(--ease-out);
29
- }
30
- .cycle {
31
- height: 30px;
32
- width: 30px;
33
- display: inline-flex;
34
- align-items: center;
35
- justify-content: center;
36
- border: 1px solid var(--color-border);
37
- background: var(--color-surface);
38
- color: var(--color-text);
39
- border-radius: 6px;
40
- cursor: pointer;
41
- transition: all 150ms var(--ease-out);
42
- }
43
- .trigger:hover {
44
- background: var(--color-accent-hover);
45
- border-color: var(--color-accent-hover);
46
- color: var(--color-bg);
47
- }
48
- .cycle:hover {
49
- background: var(--color-bg-hover);
50
- border-color: var(--color-border-hover);
51
- color: var(--color-accent);
52
- }
53
- .emoji { font-size: 14px; line-height: 1; }
54
- .label { white-space: nowrap; }
55
- .menu {
56
- position: absolute;
57
- right: 0;
58
- top: calc(100% + 7px);
59
- width: 280px;
60
- max-height: min(520px, calc(100vh - 90px));
61
- overflow: auto;
62
- padding: 5px;
63
- border: 1px solid var(--color-border);
64
- border-radius: 10px;
65
- background: var(--color-bg-elevated);
66
- box-shadow: 0 14px 36px rgba(0, 0, 0, 0.38);
67
- z-index: 250;
68
- }
69
- .item {
70
- width: 100%;
71
- display: flex;
72
- align-items: center;
73
- gap: 9px;
74
- padding: 8px 9px;
75
- border: 0;
76
- border-radius: 7px;
77
- color: var(--color-text);
78
- background: transparent;
79
- cursor: pointer;
80
- text-align: left;
81
- }
82
- .item:hover { background: var(--color-bg-hover); }
83
- .itemActive {
84
- background: var(--color-accent-dim);
85
- color: var(--color-accent);
86
- }
87
- .itemEmoji { width: 22px; text-align: center; font-size: 16px; }
88
- .itemText { display: flex; flex-direction: column; gap: 2px; flex: 1; min-width: 0; }
89
- .itemText strong { font-size: 12px; font-weight: 800; }
90
- .itemText small { color: var(--color-text-dim); font-family: var(--font-mono); font-size: 10px; }
91
- .activeDot { color: var(--color-accent); font-size: 10px; }
92
- .compact .trigger { width: 100%; justify-content: flex-start; }
93
- .compact .menu { left: 0; right: auto; }
94
- @media (max-width: 1024px) {
95
- .label { display: none; }
96
- .trigger { padding: 0 8px; }
97
- }