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.
- package/README.md +110 -881
- package/bin/free-coding-models.js +4 -1
- package/changelog/v0.5.87.md +48 -0
- package/package.json +8 -5
- package/sources.js +4 -4
- package/src/core/cache.js +3 -1
- package/src/core/config.js +43 -5
- package/src/core/endpoint-installer.js +23 -12
- package/src/core/installed-models-manager.js +43 -20
- package/src/core/kilo-config.js +51 -5
- package/src/core/kilo.js +5 -3
- package/src/core/legacy-proxy-cleanup.js +22 -5
- package/src/core/model-merger.js +4 -0
- package/src/core/models-drift.js +9 -0
- package/src/core/opencode-config.js +57 -5
- package/src/core/opencode.js +67 -41
- package/src/core/playground.js +7 -0
- package/src/core/probe-cache.js +35 -4
- package/src/core/provider-key-tester.js +10 -2
- package/src/core/provider-metadata.js +10 -0
- package/src/core/provider-quota-fetchers.js +14 -7
- package/src/core/router-daemon.js +208 -26
- package/src/core/runtime-telemetry.js +27 -1
- package/src/core/shared-helpers.js +12 -1
- package/src/core/shell-env.js +3 -1
- package/src/core/telemetry.js +18 -5
- package/src/core/tool-launchers.js +9 -6
- package/src/core/updater.js +28 -2
- package/src/core/utils.js +76 -71
- package/src/tui/app.js +38 -14
- package/src/tui/command-palette.js +22 -2
- package/src/tui/key-handler.js +123 -179
- package/src/tui/overlays.js +21 -71
- package/src/tui/render-helpers.js +62 -4
- package/src/tui/render-table.js +34 -3
- package/src/tui/theme.js +36 -5
- package/src/tui/tui-filters.js +28 -8
- package/src/tui/tui-state.js +22 -6
- package/web/dist/assets/index-BpTpPS0g.js +44 -0
- package/web/dist/assets/index-CAzFIt8P.css +1 -0
- package/web/dist/index.html +5 -9
- package/web/index.html +3 -7
- package/web/server.js +97 -23
- package/web/vite.config.js +8 -0
- package/web/app.legacy.js +0 -900
- package/web/dist/assets/index-C5-Ywvv3.css +0 -1
- package/web/dist/assets/index-Dm-Xfby9.js +0 -40
- package/web/public/favicon.ico +0 -0
- package/web/public/favicons/apple-touch-icon.png +0 -0
- package/web/public/favicons/browserconfig.xml +0 -12
- package/web/public/favicons/favicon-16x16.png +0 -0
- package/web/public/favicons/favicon-192x192.png +0 -0
- package/web/public/favicons/favicon-32x32.png +0 -0
- package/web/public/favicons/favicon-48x48.png +0 -0
- package/web/public/favicons/favicon-512x512.png +0 -0
- package/web/public/favicons/favicon-96x96.png +0 -0
- package/web/public/favicons/favicon.ico +0 -0
- package/web/public/favicons/mstile-150x150.png +0 -0
- package/web/public/favicons/mstile-310x150.png +0 -0
- package/web/public/favicons/mstile-310x310.png +0 -0
- package/web/public/favicons/mstile-512x512.png +0 -0
- package/web/public/favicons/mstile-70x70.png +0 -0
- package/web/public/favicons/site.webmanifest +0 -25
- package/web/src/App.jsx +0 -521
- package/web/src/components/analytics/AnalyticsView.jsx +0 -147
- package/web/src/components/analytics/AnalyticsView.module.css +0 -190
- package/web/src/components/analytics/TokenUsagePanel.jsx +0 -105
- package/web/src/components/analytics/TokenUsagePanel.module.css +0 -126
- package/web/src/components/atoms/AILatencyCell.jsx +0 -38
- package/web/src/components/atoms/AILatencyCell.module.css +0 -51
- package/web/src/components/atoms/HealthCell.jsx +0 -143
- package/web/src/components/atoms/HealthCell.module.css +0 -60
- package/web/src/components/atoms/LastPingCell.jsx +0 -35
- package/web/src/components/atoms/LastPingCell.module.css +0 -45
- package/web/src/components/atoms/MoodCell.jsx +0 -25
- package/web/src/components/atoms/MoodCell.module.css +0 -6
- package/web/src/components/atoms/NoKeyIcon.jsx +0 -65
- package/web/src/components/atoms/NoKeyIcon.module.css +0 -30
- package/web/src/components/atoms/ProviderLogo.jsx +0 -187
- package/web/src/components/atoms/ProviderLogo.module.css +0 -103
- package/web/src/components/atoms/RankCell.jsx +0 -9
- package/web/src/components/atoms/RankCell.module.css +0 -9
- package/web/src/components/atoms/Sparkline.jsx +0 -63
- package/web/src/components/atoms/StabilityCell.jsx +0 -18
- package/web/src/components/atoms/StabilityCell.module.css +0 -13
- package/web/src/components/atoms/StatusDot.jsx +0 -13
- package/web/src/components/atoms/StatusDot.module.css +0 -29
- package/web/src/components/atoms/TPSCell.jsx +0 -36
- package/web/src/components/atoms/TPSCell.module.css +0 -44
- package/web/src/components/atoms/TierBadge.jsx +0 -10
- package/web/src/components/atoms/TierBadge.module.css +0 -29
- package/web/src/components/atoms/Toast.jsx +0 -25
- package/web/src/components/atoms/Toast.module.css +0 -41
- package/web/src/components/atoms/ToastContainer.jsx +0 -16
- package/web/src/components/atoms/ToastContainer.module.css +0 -10
- package/web/src/components/atoms/VerdictBadge.jsx +0 -36
- package/web/src/components/atoms/VerdictBadge.module.css +0 -40
- package/web/src/components/changelog/ChangelogView.jsx +0 -135
- package/web/src/components/changelog/ChangelogView.module.css +0 -159
- package/web/src/components/dashboard/DetailPanel.jsx +0 -217
- package/web/src/components/dashboard/DetailPanel.module.css +0 -260
- package/web/src/components/dashboard/ExpandedDetailRow.jsx +0 -361
- package/web/src/components/dashboard/ExpandedDetailRow.module.css +0 -356
- package/web/src/components/dashboard/ExportModal.jsx +0 -84
- package/web/src/components/dashboard/ExportModal.module.css +0 -99
- package/web/src/components/dashboard/FilterBar.jsx +0 -338
- package/web/src/components/dashboard/FilterBar.module.css +0 -342
- package/web/src/components/dashboard/ModelTable.jsx +0 -672
- package/web/src/components/dashboard/ModelTable.module.css +0 -446
- package/web/src/components/dashboard/ProviderDropdown.jsx +0 -156
- package/web/src/components/dashboard/ProviderDropdown.module.css +0 -248
- package/web/src/components/help/HelpView.jsx +0 -201
- package/web/src/components/help/HelpView.module.css +0 -156
- package/web/src/components/install/InstallEndpointsView.jsx +0 -309
- package/web/src/components/install/InstallEndpointsView.module.css +0 -427
- package/web/src/components/installed/InstalledModelsView.jsx +0 -89
- package/web/src/components/installed/InstalledModelsView.module.css +0 -200
- package/web/src/components/launch/IncompatibleFallbackModal.jsx +0 -69
- package/web/src/components/launch/LaunchButton.jsx +0 -30
- package/web/src/components/launch/LaunchButton.module.css +0 -35
- package/web/src/components/launch/LaunchModal.module.css +0 -125
- package/web/src/components/layout/Footer.jsx +0 -19
- package/web/src/components/layout/Footer.module.css +0 -10
- package/web/src/components/layout/Header.jsx +0 -265
- package/web/src/components/layout/Header.module.css +0 -357
- package/web/src/components/palette/CommandPalette.jsx +0 -301
- package/web/src/components/palette/CommandPalette.module.css +0 -121
- package/web/src/components/playground/PlaygroundChat.jsx +0 -527
- package/web/src/components/playground/PlaygroundChat.module.css +0 -382
- package/web/src/components/playground/PlaygroundView.jsx +0 -371
- package/web/src/components/playground/PlaygroundView.module.css +0 -504
- package/web/src/components/recommend/RecommendView.jsx +0 -121
- package/web/src/components/recommend/RecommendView.module.css +0 -57
- package/web/src/components/router/RouterView.jsx +0 -1212
- package/web/src/components/router/RouterView.module.css +0 -1289
- package/web/src/components/settings/SettingsView.jsx +0 -555
- package/web/src/components/settings/SettingsView.module.css +0 -550
- package/web/src/components/tools/ToolPicker.jsx +0 -86
- package/web/src/components/tools/ToolPicker.module.css +0 -97
- package/web/src/components/update/UpdateChip.jsx +0 -104
- package/web/src/components/update/UpdateChip.module.css +0 -146
- package/web/src/global.css +0 -298
- package/web/src/hooks/urlState.constants.js +0 -28
- package/web/src/hooks/useChangelog.js +0 -51
- package/web/src/hooks/useColumnSizing.js +0 -211
- package/web/src/hooks/useFavorites.js +0 -150
- package/web/src/hooks/useFilter.js +0 -310
- package/web/src/hooks/useInstalledModels.js +0 -42
- package/web/src/hooks/useRecommend.js +0 -67
- package/web/src/hooks/useRouterDashboard.js +0 -133
- package/web/src/hooks/useSSE.js +0 -73
- package/web/src/hooks/useSocket.js +0 -203
- package/web/src/hooks/useTheme.js +0 -83
- package/web/src/hooks/useTokenUsage.js +0 -103
- package/web/src/hooks/useToolMode.js +0 -77
- package/web/src/hooks/useUpdateChecker.js +0 -91
- package/web/src/hooks/useUrlState.js +0 -170
- package/web/src/main.jsx +0 -15
- package/web/src/utils/download.js +0 -15
- package/web/src/utils/format.js +0 -42
- package/web/src/utils/m3.js +0 -57
- package/web/src/utils/ranks.js +0 -37
- package/web/styles.legacy.css +0 -963
|
@@ -1,248 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @file web/src/components/dashboard/ProviderDropdown.module.css
|
|
3
|
-
* @description Styles for the custom provider dropdown — SVG logos + health dots.
|
|
4
|
-
*
|
|
5
|
-
* 📖 Theme-aware: uses CSS variables for all colors. Dark + light mode both
|
|
6
|
-
* 📖 fully supported. Health dot colors are defined per-theme with explicit
|
|
7
|
-
* 📖 overrides for light mode to maintain contrast.
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
.dropdown {
|
|
11
|
-
position: relative;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
/* ── Trigger button ── */
|
|
15
|
-
.trigger {
|
|
16
|
-
display: inline-flex;
|
|
17
|
-
align-items: center;
|
|
18
|
-
gap: 6px;
|
|
19
|
-
padding: 4px 10px 4px 8px;
|
|
20
|
-
border: 1px solid var(--color-border);
|
|
21
|
-
border-radius: 5px;
|
|
22
|
-
background: var(--color-surface);
|
|
23
|
-
color: var(--color-text);
|
|
24
|
-
font-size: 11px;
|
|
25
|
-
font-family: var(--font-sans);
|
|
26
|
-
cursor: pointer;
|
|
27
|
-
transition: all 150ms;
|
|
28
|
-
min-width: 130px;
|
|
29
|
-
max-width: 240px;
|
|
30
|
-
user-select: none;
|
|
31
|
-
outline: none;
|
|
32
|
-
}
|
|
33
|
-
.trigger:hover {
|
|
34
|
-
background: var(--color-bg-hover);
|
|
35
|
-
border-color: var(--color-text-muted);
|
|
36
|
-
}
|
|
37
|
-
.trigger:focus {
|
|
38
|
-
border-color: var(--color-accent);
|
|
39
|
-
}
|
|
40
|
-
.triggerActive {
|
|
41
|
-
border-color: var(--color-accent);
|
|
42
|
-
color: var(--color-text);
|
|
43
|
-
}
|
|
44
|
-
.triggerOpen {
|
|
45
|
-
border-color: var(--color-accent);
|
|
46
|
-
border-bottom-left-radius: 0;
|
|
47
|
-
border-bottom-right-radius: 0;
|
|
48
|
-
z-index: 101;
|
|
49
|
-
background: var(--color-bg-hover);
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
.triggerContent {
|
|
53
|
-
display: inline-flex;
|
|
54
|
-
align-items: center;
|
|
55
|
-
gap: 8px;
|
|
56
|
-
flex: 1;
|
|
57
|
-
min-width: 0;
|
|
58
|
-
overflow: hidden;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
.triggerLogo {
|
|
62
|
-
display: inline-flex;
|
|
63
|
-
align-items: center;
|
|
64
|
-
min-width: 0;
|
|
65
|
-
overflow: hidden;
|
|
66
|
-
/* 📖 Inherit max-height from ProviderLogo's row. */
|
|
67
|
-
--pl-max-h: 20px;
|
|
68
|
-
--pl-icon-h: 14px;
|
|
69
|
-
--pl-text-h: 10px;
|
|
70
|
-
}
|
|
71
|
-
/* 📖 Clamp ProviderLogo width inside trigger so long wordmarks don't overflow. */
|
|
72
|
-
.triggerLogo > span {
|
|
73
|
-
max-width: 160px;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
.triggerLabel {
|
|
77
|
-
font-size: 11px;
|
|
78
|
-
font-weight: 600;
|
|
79
|
-
color: var(--color-text-muted);
|
|
80
|
-
white-space: nowrap;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
.triggerCount {
|
|
84
|
-
font-size: 10px;
|
|
85
|
-
font-weight: 700;
|
|
86
|
-
color: var(--color-text-muted);
|
|
87
|
-
font-family: var(--font-mono);
|
|
88
|
-
margin-left: auto;
|
|
89
|
-
flex-shrink: 0;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
.chevron {
|
|
93
|
-
flex-shrink: 0;
|
|
94
|
-
opacity: 0.5;
|
|
95
|
-
transition: transform 200ms ease;
|
|
96
|
-
}
|
|
97
|
-
.chevronOpen {
|
|
98
|
-
transform: rotate(180deg);
|
|
99
|
-
opacity: 0.8;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
/* ── Dropdown menu ── */
|
|
103
|
-
.menu {
|
|
104
|
-
position: absolute;
|
|
105
|
-
top: 100%;
|
|
106
|
-
left: 0;
|
|
107
|
-
z-index: 100;
|
|
108
|
-
min-width: 100%;
|
|
109
|
-
max-width: 300px;
|
|
110
|
-
max-height: 320px;
|
|
111
|
-
overflow-y: auto;
|
|
112
|
-
background: var(--color-surface);
|
|
113
|
-
border: 1px solid var(--color-accent);
|
|
114
|
-
border-top: none;
|
|
115
|
-
border-radius: 0 0 6px 6px;
|
|
116
|
-
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.3);
|
|
117
|
-
padding: 4px 0;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
/* 📖 Custom scrollbar for the dropdown. */
|
|
121
|
-
.menu::-webkit-scrollbar {
|
|
122
|
-
width: 5px;
|
|
123
|
-
}
|
|
124
|
-
.menu::-webkit-scrollbar-track {
|
|
125
|
-
background: transparent;
|
|
126
|
-
}
|
|
127
|
-
.menu::-webkit-scrollbar-thumb {
|
|
128
|
-
background: var(--color-border);
|
|
129
|
-
border-radius: 3px;
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
/* ── Menu options ── */
|
|
133
|
-
.option {
|
|
134
|
-
display: flex;
|
|
135
|
-
align-items: center;
|
|
136
|
-
gap: 8px;
|
|
137
|
-
width: 100%;
|
|
138
|
-
padding: 6px 10px;
|
|
139
|
-
border: none;
|
|
140
|
-
background: transparent;
|
|
141
|
-
color: var(--color-text-muted);
|
|
142
|
-
font-size: 11px;
|
|
143
|
-
font-family: var(--font-sans);
|
|
144
|
-
cursor: pointer;
|
|
145
|
-
transition: all 100ms;
|
|
146
|
-
text-align: left;
|
|
147
|
-
outline: none;
|
|
148
|
-
}
|
|
149
|
-
.option:hover {
|
|
150
|
-
background: var(--color-bg-hover);
|
|
151
|
-
color: var(--color-text);
|
|
152
|
-
}
|
|
153
|
-
.option:focus-visible {
|
|
154
|
-
background: var(--color-bg-hover);
|
|
155
|
-
color: var(--color-text);
|
|
156
|
-
box-shadow: inset 0 0 0 1px var(--color-accent);
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
.optionActive {
|
|
160
|
-
background: var(--color-accent-dim);
|
|
161
|
-
color: var(--color-text);
|
|
162
|
-
font-weight: 600;
|
|
163
|
-
}
|
|
164
|
-
.optionActive:hover {
|
|
165
|
-
background: var(--color-bg-hover);
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
.optionLabel {
|
|
169
|
-
font-weight: 600;
|
|
170
|
-
white-space: nowrap;
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
.optionLogo {
|
|
174
|
-
display: inline-flex;
|
|
175
|
-
align-items: center;
|
|
176
|
-
min-width: 0;
|
|
177
|
-
flex: 1;
|
|
178
|
-
overflow: hidden;
|
|
179
|
-
/* 📖 Slightly larger than trigger for readability in the expanded menu. */
|
|
180
|
-
--pl-max-h: 20px;
|
|
181
|
-
--pl-icon-h: 15px;
|
|
182
|
-
--pl-text-h: 11px;
|
|
183
|
-
}
|
|
184
|
-
.optionLogo > span {
|
|
185
|
-
max-width: 180px;
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
.optionCount {
|
|
189
|
-
font-size: 10px;
|
|
190
|
-
font-weight: 700;
|
|
191
|
-
font-family: var(--font-mono);
|
|
192
|
-
color: var(--color-text-dim, #666);
|
|
193
|
-
flex-shrink: 0;
|
|
194
|
-
margin-left: auto;
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
/* ── Separator between "All" and per-provider options ── */
|
|
198
|
-
.separator {
|
|
199
|
-
height: 1px;
|
|
200
|
-
background: var(--color-border);
|
|
201
|
-
margin: 4px 8px;
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
/* ── Health indicator dots ── */
|
|
205
|
-
.healthDot {
|
|
206
|
-
display: inline-block;
|
|
207
|
-
width: 7px;
|
|
208
|
-
height: 7px;
|
|
209
|
-
border-radius: 50%;
|
|
210
|
-
flex-shrink: 0;
|
|
211
|
-
transition: background 200ms ease, box-shadow 200ms ease;
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
/* 📖 Active: green glow — key works and models are UP. */
|
|
215
|
-
.dot_active {
|
|
216
|
-
background: #00ff88;
|
|
217
|
-
box-shadow: 0 0 5px rgba(0, 255, 136, 0.5);
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
/* 📖 Pending: yellow — key set, health data not yet available. */
|
|
221
|
-
.dot_pending {
|
|
222
|
-
background: #ffaa00;
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
/* 📖 Down: red — key set but models are down / auth errors. */
|
|
226
|
-
.dot_down {
|
|
227
|
-
background: #ff4444;
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
/* 📖 No key: gray — no API key configured for this provider. */
|
|
231
|
-
.dot_nokey {
|
|
232
|
-
background: #555570;
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
/* ── Light theme overrides — deeper colors for white background contrast ── */
|
|
236
|
-
:global([data-theme="light"]) .dot_active {
|
|
237
|
-
background: #008f4d;
|
|
238
|
-
box-shadow: 0 0 4px rgba(0, 143, 77, 0.35);
|
|
239
|
-
}
|
|
240
|
-
:global([data-theme="light"]) .dot_pending {
|
|
241
|
-
background: #b38600;
|
|
242
|
-
}
|
|
243
|
-
:global([data-theme="light"]) .dot_down {
|
|
244
|
-
background: #c8143a;
|
|
245
|
-
}
|
|
246
|
-
:global([data-theme="light"]) .dot_nokey {
|
|
247
|
-
background: #aaa;
|
|
248
|
-
}
|
|
@@ -1,201 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @file web/src/components/help/HelpView.jsx
|
|
3
|
-
* @description Help modal — M2 parity with the TUI's `K` / `I` help overlay.
|
|
4
|
-
* 📖 Renders the same content the TUI help overlay does, as JSX, with a live
|
|
5
|
-
* 📖 search bar at the top. The TUI source-of-truth is `src/tui/cli-help.js`
|
|
6
|
-
* 📖 (CLI flags) and `src/tui/overlays.js` `renderHelp` (key bindings) — we
|
|
7
|
-
* 📖 re-render those lists statically here to avoid a TUI-engine import that
|
|
8
|
-
* 📖 would pull chalk into the Web bundle.
|
|
9
|
-
*
|
|
10
|
-
* 📖 The header's overflow menu and the ⌘K palette expose "Help" as a page;
|
|
11
|
-
* 📖 this component is rendered as a full-screen modal inside the Web.
|
|
12
|
-
*
|
|
13
|
-
* @functions
|
|
14
|
-
* → HelpView → main modal component
|
|
15
|
-
*/
|
|
16
|
-
import { useState, useMemo } from 'react'
|
|
17
|
-
import { IconSearch, IconX } from '@tabler/icons-react'
|
|
18
|
-
import styles from './HelpView.module.css'
|
|
19
|
-
|
|
20
|
-
const SECTIONS = [
|
|
21
|
-
{
|
|
22
|
-
id: 'navigation',
|
|
23
|
-
title: '🧭 Navigation',
|
|
24
|
-
items: [
|
|
25
|
-
{ key: '↑ / ↓', desc: 'Navigate rows in the main table (planned — currently mouse-only)' },
|
|
26
|
-
{ key: 'Enter', desc: 'Open the detail panel for the focused row' },
|
|
27
|
-
{ key: 'Esc', desc: 'Close any open modal or detail panel' },
|
|
28
|
-
],
|
|
29
|
-
},
|
|
30
|
-
{
|
|
31
|
-
id: 'filters',
|
|
32
|
-
title: '🔍 Filters',
|
|
33
|
-
items: [
|
|
34
|
-
{ key: 'Tier chip', desc: 'Click to cycle: All → S+ → S → A+ → A → A- → B+ → B → C → All' },
|
|
35
|
-
{ key: 'Status chip', desc: 'Click to cycle: All → Up → Down → Pending' },
|
|
36
|
-
{ key: 'Verdict chip', desc: 'Click to cycle: All → Perfect → Normal → Spiky → Slow → Overloaded → Down → Unstable → Pending' },
|
|
37
|
-
{ key: 'Health chip', desc: 'Click to cycle: All → Up → Timeout → Down → Pending → No key → Auth err' },
|
|
38
|
-
{ key: 'Visibility', desc: 'Normal (all) / Configured only (hide no-key) / Usable only (UP + good verdict)' },
|
|
39
|
-
{ key: 'Provider dropdown', desc: 'Filter by provider' },
|
|
40
|
-
{ key: 'Custom text filter', desc: 'Apply via ⌘K palette or `Apply text filter` — clear with the X chip' },
|
|
41
|
-
{ key: 'Reset button', desc: 'Clears every active filter + sort back to defaults' },
|
|
42
|
-
],
|
|
43
|
-
},
|
|
44
|
-
{
|
|
45
|
-
id: 'sort',
|
|
46
|
-
title: '📶 Sort',
|
|
47
|
-
items: [
|
|
48
|
-
{ key: 'Click any column header', desc: 'Sort asc → desc → reset (no sort, default order)' },
|
|
49
|
-
{ key: 'Resizable columns', desc: 'Drag the right edge of any header to resize. Double-click resets one column.' },
|
|
50
|
-
],
|
|
51
|
-
},
|
|
52
|
-
{
|
|
53
|
-
id: 'favorites',
|
|
54
|
-
title: '⭐ Favorites',
|
|
55
|
-
items: [
|
|
56
|
-
{ key: 'Star button (per row)', desc: 'Click to favorite / unfavorite the model (TUI: F key)' },
|
|
57
|
-
{ key: 'Detail panel', desc: 'Favorite + Up/Down reorder + current priority rank (TUI: Shift+↑/↓)' },
|
|
58
|
-
{ key: 'Pinned mode', desc: 'In the Settings view, toggle "Pinned + always visible" (TUI: Y key)' },
|
|
59
|
-
{ key: 'Shared with TUI', desc: 'Favorites are persisted in the same ~/.free-coding-models.json the TUI uses' },
|
|
60
|
-
],
|
|
61
|
-
},
|
|
62
|
-
{
|
|
63
|
-
id: 'benchmark',
|
|
64
|
-
title: '🤖 AI Speed Test (benchmark)',
|
|
65
|
-
items: [
|
|
66
|
-
{ key: 'Header button', desc: 'Run a global AI Speed Test on every visible model' },
|
|
67
|
-
{ key: 'AI Lat. cell', desc: 'Click any AI Lat. cell to benchmark just that model (TUI: Ctrl+A)' },
|
|
68
|
-
{ key: 'Detail panel', desc: 'Dedicated "AI Speed Test" button (TUI: Ctrl+A)' },
|
|
69
|
-
{ key: 'TPS column', desc: 'Tokens per second for the model — appears after a benchmark run' },
|
|
70
|
-
{ key: 'Latency column', desc: 'Real completion latency (not just ping) for the model' },
|
|
71
|
-
],
|
|
72
|
-
},
|
|
73
|
-
{
|
|
74
|
-
id: 'tools',
|
|
75
|
-
title: '🧰 Tool mode (M3)',
|
|
76
|
-
items: [
|
|
77
|
-
{ key: 'M3 shipped', desc: 'Endpoint target picker, per-row Install Endpoint button, and incompatible-target fallback modal' },
|
|
78
|
-
],
|
|
79
|
-
},
|
|
80
|
-
{
|
|
81
|
-
id: 'palette',
|
|
82
|
-
title: '⚡ Command palette',
|
|
83
|
-
items: [
|
|
84
|
-
{ key: '⌘K / Ctrl+P', desc: 'The Web\'s only global keyboard shortcut — toggles the command palette' },
|
|
85
|
-
{ key: 'Type to search', desc: 'Fuzzy match across all filters, sorts, tools, pages, and theme / ping / reset actions' },
|
|
86
|
-
{ key: '↑ / ↓ + Enter', desc: 'Navigate the results and execute the highlighted command' },
|
|
87
|
-
{ key: 'Esc', desc: 'Close the palette without running anything' },
|
|
88
|
-
],
|
|
89
|
-
},
|
|
90
|
-
{
|
|
91
|
-
id: 'theme',
|
|
92
|
-
title: '🌗 Theme',
|
|
93
|
-
items: [
|
|
94
|
-
{ key: 'Theme button (header)', desc: 'Click to cycle: auto → dark → light (TUI: G key)' },
|
|
95
|
-
{ key: 'Auto mode', desc: 'Follows the OS prefers-color-scheme preference and updates live' },
|
|
96
|
-
],
|
|
97
|
-
},
|
|
98
|
-
{
|
|
99
|
-
id: 'ping',
|
|
100
|
-
title: '⚡ Ping mode',
|
|
101
|
-
items: [
|
|
102
|
-
{ key: 'Speed / Normal / Slow / Forced', desc: 'Ping cadence (2s / 10s / 30s / 4s) — TUI: W key' },
|
|
103
|
-
{ key: 'next ping in Xs', desc: 'Live countdown shown in the FilterBar (TUI footer style)' },
|
|
104
|
-
],
|
|
105
|
-
},
|
|
106
|
-
{
|
|
107
|
-
id: 'url',
|
|
108
|
-
title: '🔗 URL deep-linking',
|
|
109
|
-
items: [
|
|
110
|
-
{ key: '?tier=S+&sort=verdict&origin=groq&view=dashboard', desc: 'Every filter / sort / view is reflected in the URL — share pre-filtered links' },
|
|
111
|
-
],
|
|
112
|
-
},
|
|
113
|
-
{
|
|
114
|
-
id: 'cli',
|
|
115
|
-
title: '⌨️ CLI parity',
|
|
116
|
-
items: [
|
|
117
|
-
{ key: 'Same storage', desc: 'The Web reads + writes the same ~/.free-coding-models.json as the TUI' },
|
|
118
|
-
{ key: 'Same engine', desc: 'All model parsing, ping, and benchmark code is shared with the TUI' },
|
|
119
|
-
],
|
|
120
|
-
},
|
|
121
|
-
{
|
|
122
|
-
id: 'how-router-works',
|
|
123
|
-
title: '🌐 How the FCM Router works',
|
|
124
|
-
items: [
|
|
125
|
-
{ key: 'Smart router', desc: 'Point any OpenAI client at http://localhost:19280/v1 with model: "fcm". The daemon picks the healthiest model in the active set and forwards the request with automatic failover.' },
|
|
126
|
-
{ key: 'Pre-prompt', desc: 'A first-class system message is injected on every proxied request. The default introduces the assistant as the FCM routing agent. Edit from Settings.' },
|
|
127
|
-
{ key: 'Probes', desc: 'Every 10s/30s/120s (eco/balanced/aggressive) the daemon sends a 1-token chat-completion ping to every model in the active set. The probe measures latency + status code, not just URL reachability — so a wrong API key is caught and the circuit opens.' },
|
|
128
|
-
{ key: 'Circuit breaker', desc: 'Per-model state. Healthy (green) = last probe 2xx, route here. Down (red) = last 3 probes failed, skip until cooldown. Recovering (yellow) = cooldown expired, retrying. Auth error (orange) = 401/403, your key is wrong. Deprecated (gray) = removed from catalog, will be replaced by auto-heal.' },
|
|
129
|
-
{ key: 'Failover order', desc: 'Models are tried in priority order. A model in Recovering/Down/Auth error is skipped — the request goes to the next healthy one. If ALL fail, you get 503 with the "models_tried" list in the error body.' },
|
|
130
|
-
{ key: 'Auto-heal', desc: 'On daemon start, every Auth-error / Deprecated model in the active set is swapped for a working alternative (same provider first, then cross-provider). The first time you add/remove/reorder a model, auto-heal switches off.' },
|
|
131
|
-
{ key: 'Rate limits', desc: 'Each provider has its own quota. Common free-tier limits: Groq 14 400 RPD, Mistral 1 RPS, NVIDIA ~40 RPM, OpenRouter 50 RPD. When a provider returns 429, the router fails over. When daily quota is exhausted, the model goes Auth error and auto-heal swaps it out next start.' },
|
|
132
|
-
],
|
|
133
|
-
},
|
|
134
|
-
]
|
|
135
|
-
|
|
136
|
-
export default function HelpView({ onClose }) {
|
|
137
|
-
const [query, setQuery] = useState('')
|
|
138
|
-
|
|
139
|
-
// 📖 Filter every section's items by the live query. Case-insensitive
|
|
140
|
-
// 📖 substring match on the key or description. Empty results hide the
|
|
141
|
-
// 📖 section entirely.
|
|
142
|
-
const filteredSections = useMemo(() => {
|
|
143
|
-
const q = query.trim().toLowerCase()
|
|
144
|
-
if (!q) return SECTIONS
|
|
145
|
-
return SECTIONS.map((section) => ({
|
|
146
|
-
...section,
|
|
147
|
-
items: section.items.filter(
|
|
148
|
-
(item) => item.key.toLowerCase().includes(q) || item.desc.toLowerCase().includes(q),
|
|
149
|
-
),
|
|
150
|
-
})).filter((section) => section.items.length > 0)
|
|
151
|
-
}, [query])
|
|
152
|
-
|
|
153
|
-
return (
|
|
154
|
-
<div className={styles.backdrop} onClick={onClose}>
|
|
155
|
-
<div className={styles.modal} onClick={(e) => e.stopPropagation()}>
|
|
156
|
-
<div className={styles.header}>
|
|
157
|
-
<div className={styles.titleRow}>
|
|
158
|
-
<h2 className={styles.title}>❓ Help & Keyboard Shortcuts</h2>
|
|
159
|
-
<button className={styles.closeBtn} onClick={onClose} aria-label="Close help">
|
|
160
|
-
<IconX size={18} stroke={1.5} />
|
|
161
|
-
</button>
|
|
162
|
-
</div>
|
|
163
|
-
<div className={styles.searchBar}>
|
|
164
|
-
<IconSearch size={14} stroke={1.5} className={styles.searchIcon} />
|
|
165
|
-
<input
|
|
166
|
-
className={styles.searchInput}
|
|
167
|
-
placeholder="Search help (filters, hotkeys, TUI parity)…"
|
|
168
|
-
value={query}
|
|
169
|
-
onChange={(e) => setQuery(e.target.value)}
|
|
170
|
-
autoFocus
|
|
171
|
-
/>
|
|
172
|
-
</div>
|
|
173
|
-
<p className={styles.note}>
|
|
174
|
-
The Web Dashboard is mouse-first. The only global keyboard shortcut is{' '}
|
|
175
|
-
<kbd>⌘K</kbd> / <kbd>Ctrl+P</kbd> for the command palette — everything else
|
|
176
|
-
lives here, in the header menu, or in modals.
|
|
177
|
-
</p>
|
|
178
|
-
</div>
|
|
179
|
-
<div className={styles.body}>
|
|
180
|
-
{filteredSections.length === 0 ? (
|
|
181
|
-
<div className={styles.empty}>No help matches "{query}".</div>
|
|
182
|
-
) : (
|
|
183
|
-
filteredSections.map((section) => (
|
|
184
|
-
<section key={section.id} className={styles.section}>
|
|
185
|
-
<h3 className={styles.sectionTitle}>{section.title}</h3>
|
|
186
|
-
<ul className={styles.itemList}>
|
|
187
|
-
{section.items.map((item) => (
|
|
188
|
-
<li key={item.key + item.desc} className={styles.item}>
|
|
189
|
-
<span className={styles.itemKey}>{item.key}</span>
|
|
190
|
-
<span className={styles.itemDesc}>{item.desc}</span>
|
|
191
|
-
</li>
|
|
192
|
-
))}
|
|
193
|
-
</ul>
|
|
194
|
-
</section>
|
|
195
|
-
))
|
|
196
|
-
)}
|
|
197
|
-
</div>
|
|
198
|
-
</div>
|
|
199
|
-
</div>
|
|
200
|
-
)
|
|
201
|
-
}
|
|
@@ -1,156 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @file web/src/components/help/HelpView.module.css
|
|
3
|
-
* @description Full-screen help modal styles — TUI parity.
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
.backdrop {
|
|
7
|
-
display: flex;
|
|
8
|
-
flex-direction: column;
|
|
9
|
-
flex: 1;
|
|
10
|
-
min-height: 0;
|
|
11
|
-
width: 100%;
|
|
12
|
-
padding: 24px;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
.modal {
|
|
16
|
-
width: 100%;
|
|
17
|
-
max-width: 820px;
|
|
18
|
-
margin: 0 auto;
|
|
19
|
-
background: var(--color-bg-card, #080908);
|
|
20
|
-
border: 1px solid var(--color-border, #161a16);
|
|
21
|
-
border-radius: 14px;
|
|
22
|
-
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
|
|
23
|
-
display: flex;
|
|
24
|
-
flex-direction: column;
|
|
25
|
-
overflow: hidden;
|
|
26
|
-
flex: 1;
|
|
27
|
-
min-height: 0;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
.header {
|
|
31
|
-
padding: 20px 24px 16px;
|
|
32
|
-
border-bottom: 1px solid var(--color-border);
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
.titleRow {
|
|
36
|
-
display: flex;
|
|
37
|
-
align-items: center;
|
|
38
|
-
justify-content: space-between;
|
|
39
|
-
gap: 12px;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
.title {
|
|
43
|
-
font-size: 17px;
|
|
44
|
-
font-weight: 700;
|
|
45
|
-
margin: 0;
|
|
46
|
-
color: var(--color-text);
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
.closeBtn {
|
|
50
|
-
background: transparent;
|
|
51
|
-
border: none;
|
|
52
|
-
color: var(--color-text-muted);
|
|
53
|
-
cursor: pointer;
|
|
54
|
-
padding: 4px;
|
|
55
|
-
border-radius: 4px;
|
|
56
|
-
display: inline-flex;
|
|
57
|
-
align-items: center;
|
|
58
|
-
justify-content: center;
|
|
59
|
-
}
|
|
60
|
-
.closeBtn:hover { color: var(--color-text); background: var(--color-bg-hover); }
|
|
61
|
-
|
|
62
|
-
.searchBar {
|
|
63
|
-
display: flex;
|
|
64
|
-
align-items: center;
|
|
65
|
-
gap: 8px;
|
|
66
|
-
margin-top: 12px;
|
|
67
|
-
background: var(--color-surface);
|
|
68
|
-
border: 1px solid var(--color-border);
|
|
69
|
-
border-radius: 8px;
|
|
70
|
-
padding: 0 12px;
|
|
71
|
-
height: 34px;
|
|
72
|
-
}
|
|
73
|
-
.searchBar:focus-within {
|
|
74
|
-
border-color: var(--color-accent);
|
|
75
|
-
box-shadow: 0 0 0 3px var(--color-accent-glow);
|
|
76
|
-
}
|
|
77
|
-
.searchIcon { color: var(--color-text-dim); }
|
|
78
|
-
.searchInput {
|
|
79
|
-
flex: 1;
|
|
80
|
-
background: none;
|
|
81
|
-
border: none;
|
|
82
|
-
outline: none;
|
|
83
|
-
color: var(--color-text);
|
|
84
|
-
font-size: 13px;
|
|
85
|
-
font-family: var(--font-sans);
|
|
86
|
-
}
|
|
87
|
-
.searchInput::placeholder { color: var(--color-text-dim); }
|
|
88
|
-
|
|
89
|
-
.note {
|
|
90
|
-
margin: 10px 0 0;
|
|
91
|
-
font-size: 11px;
|
|
92
|
-
color: var(--color-text-muted);
|
|
93
|
-
line-height: 1.5;
|
|
94
|
-
}
|
|
95
|
-
.note kbd {
|
|
96
|
-
display: inline-block;
|
|
97
|
-
font-family: var(--font-mono);
|
|
98
|
-
background: var(--color-surface);
|
|
99
|
-
border: 1px solid var(--color-border);
|
|
100
|
-
padding: 1px 5px;
|
|
101
|
-
border-radius: 3px;
|
|
102
|
-
font-size: 10px;
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
.body {
|
|
106
|
-
padding: 18px 24px 24px;
|
|
107
|
-
overflow-y: auto;
|
|
108
|
-
flex: 1;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
.section { margin-bottom: 18px; }
|
|
112
|
-
.sectionTitle {
|
|
113
|
-
font-size: 12px;
|
|
114
|
-
font-weight: 700;
|
|
115
|
-
text-transform: uppercase;
|
|
116
|
-
letter-spacing: 0.6px;
|
|
117
|
-
color: var(--color-text-muted);
|
|
118
|
-
margin: 0 0 8px;
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
.itemList {
|
|
122
|
-
list-style: none;
|
|
123
|
-
margin: 0;
|
|
124
|
-
padding: 0;
|
|
125
|
-
display: flex;
|
|
126
|
-
flex-direction: column;
|
|
127
|
-
gap: 4px;
|
|
128
|
-
}
|
|
129
|
-
.item {
|
|
130
|
-
display: grid;
|
|
131
|
-
grid-template-columns: minmax(160px, 0.4fr) 1fr;
|
|
132
|
-
gap: 12px;
|
|
133
|
-
align-items: baseline;
|
|
134
|
-
padding: 6px 10px;
|
|
135
|
-
background: var(--color-surface);
|
|
136
|
-
border: 1px solid var(--color-border);
|
|
137
|
-
border-radius: 6px;
|
|
138
|
-
font-size: 12px;
|
|
139
|
-
}
|
|
140
|
-
.itemKey {
|
|
141
|
-
font-weight: 600;
|
|
142
|
-
color: var(--color-text);
|
|
143
|
-
font-family: var(--font-mono);
|
|
144
|
-
font-size: 11px;
|
|
145
|
-
}
|
|
146
|
-
.itemDesc {
|
|
147
|
-
color: var(--color-text-muted);
|
|
148
|
-
line-height: 1.5;
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
.empty {
|
|
152
|
-
text-align: center;
|
|
153
|
-
padding: 40px 20px;
|
|
154
|
-
color: var(--color-text-dim);
|
|
155
|
-
font-size: 13px;
|
|
156
|
-
}
|