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,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
- }