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
Binary file
@@ -1,12 +0,0 @@
1
- <?xml version="1.0" encoding="utf-8"?>
2
- <browserconfig>
3
- <msapplication>
4
- <tile>
5
- <square70x70logo src="/favicons/mstile-70x70.png"/>
6
- <square150x150logo src="/favicons/mstile-150x150.png"/>
7
- <wide310x150logo src="/favicons/mstile-310x150.png"/>
8
- <square310x310logo src="/favicons/mstile-310x310.png"/>
9
- <TileColor>#0a0a0a</TileColor>
10
- </tile>
11
- </msapplication>
12
- </browserconfig>
Binary file
@@ -1,25 +0,0 @@
1
- {
2
- "name": "free-coding-models — Live Dashboard",
3
- "short_name": "FCM",
4
- "description": "Find the fastest free coding LLM model in seconds.",
5
- "start_url": "/",
6
- "scope": "/",
7
- "display": "standalone",
8
- "orientation": "any",
9
- "background_color": "#0a0a0a",
10
- "theme_color": "#0a0a0a",
11
- "icons": [
12
- {
13
- "src": "/favicons/favicon-192x192.png",
14
- "sizes": "192x192",
15
- "type": "image/png",
16
- "purpose": "any maskable"
17
- },
18
- {
19
- "src": "/favicons/favicon-512x512.png",
20
- "sizes": "512x512",
21
- "type": "image/png",
22
- "purpose": "any maskable"
23
- }
24
- ]
25
- }
package/web/src/App.jsx DELETED
@@ -1,521 +0,0 @@
1
- /**
2
- * @file web/src/App.jsx
3
- * @description Root application component — orchestrates all views, header nav, Socket.IO
4
- * connection, toast notifications, and global state. M2 layout: no sidebar, header
5
- * menu + ⌘K palette, full Settings parity, Help + Changelog modals, UpdateChip,
6
- * URL write-back.
7
- *
8
- * 📖 M2 additions on top of M1:
9
- * 📖 - Full command palette (TUI registry via `buildCommandPaletteEntries`)
10
- * 📖 - HelpView modal (TUI parity help)
11
- * 📖 - ChangelogView modal (2-phase: index + details)
12
- * 📖 - UpdateChip in header (polls /api/version, popover with "Update now" + "What's new")
13
- * 📖 - URL write-back (every filter / sort / view / palette / toolMode change
14
- * 📖 updates the URL via history.replaceState, debounced at 80ms)
15
- * 📖 - M3 Web endpoint install flow: writes tool config only; never starts CLIs.
16
- * 📖 - New Settings rows: theme dropdown, favorites mode toggle, startup AI scan
17
- * 📖 toggle, shell-env toggle, legacy proxy cleanup button, open Changelog link,
18
- * 📖 update status row, per-provider test key button
19
- *
20
- * @functions App → root component with all state and layout composition
21
- */
22
- import { useState, useCallback, useEffect, useRef, useMemo } from 'react'
23
- import { useSocket } from './hooks/useSocket.js'
24
- import { useFilter } from './hooks/useFilter.js'
25
- import { useTheme } from './hooks/useTheme.js'
26
- import { useFavorites } from './hooks/useFavorites.js'
27
- import { useToolMode } from './hooks/useToolMode.js'
28
- import { useUrlState } from './hooks/useUrlState.js'
29
- import { useUpdateChecker } from './hooks/useUpdateChecker.js'
30
- import Header from './components/layout/Header.jsx'
31
- import Footer from './components/layout/Footer.jsx'
32
- import FilterBar from './components/dashboard/FilterBar.jsx'
33
- import ModelTable from './components/dashboard/ModelTable.jsx'
34
- import DetailPanel from './components/dashboard/DetailPanel.jsx'
35
- import ExportModal from './components/dashboard/ExportModal.jsx'
36
- import SettingsView from './components/settings/SettingsView.jsx'
37
- import AnalyticsView from './components/analytics/AnalyticsView.jsx'
38
- import CommandPalette from './components/palette/CommandPalette.jsx'
39
- import HelpView from './components/help/HelpView.jsx'
40
- import ChangelogView from './components/changelog/ChangelogView.jsx'
41
- import UpdateChip from './components/update/UpdateChip.jsx'
42
- import RecommendView from './components/recommend/RecommendView.jsx'
43
- import IncompatibleFallbackModal from './components/launch/IncompatibleFallbackModal.jsx'
44
- import RouterView from './components/router/RouterView.jsx'
45
- import PlaygroundView from './components/playground/PlaygroundView.jsx'
46
- import InstalledModelsView from './components/installed/InstalledModelsView.jsx'
47
- import InstallEndpointsView from './components/install/InstallEndpointsView.jsx'
48
- import ToastContainer from './components/atoms/ToastContainer.jsx'
49
- import { isModelCompatibleWithTool } from '../../src/core/tool-metadata.js'
50
-
51
- let toastIdCounter = 0
52
-
53
- const VIEW_TO_NAV = {
54
- dashboard: 'dashboard',
55
- settings: 'settings',
56
- analytics: 'analytics',
57
- recommend: 'recommend',
58
- router: 'router',
59
- }
60
-
61
- export default function App() {
62
- // 📖 Compatibility sentinel for M4 unit tests:
63
- // setRouterOpen setInstalledModelsOpen setInstallEndpointsOpen
64
- const { models, connected, nextPingAt, isPinging, pingMode, globalBenchmarkRunning, globalBenchmarkTotal, globalBenchmarkCompleted } = useSocket()
65
- const { theme, cycle: cycleTheme } = useTheme()
66
- const [currentView, setCurrentView] = useState('dashboard')
67
- const [selectedModel, setSelectedModel] = useState(null)
68
- const [exportOpen, setExportOpen] = useState(false)
69
- const [paletteOpen, setPaletteOpen] = useState(false)
70
- const [changelogDefaultVersion, setChangelogDefaultVersion] = useState(null)
71
- const [incompatibleRequest, setIncompatibleRequest] = useState(null)
72
- const [toasts, setToasts] = useState([])
73
- const lastActivityRef = useRef(Date.now())
74
-
75
- // 📖 PostHog: track app_web_start on mount
76
- useEffect(() => {
77
- try {
78
- if (typeof window !== 'undefined' && window.posthog?.capture) {
79
- window.posthog.capture('app_web_start', {
80
- version: __APP_VERSION__ || 'unknown',
81
- timestamp: new Date().toISOString(),
82
- })
83
- }
84
- } catch {}
85
- }, [])
86
-
87
- // 📖 PostHog: track app_router_start when router view is active
88
- useEffect(() => {
89
- if (currentView === 'router') {
90
- try {
91
- if (typeof window !== 'undefined' && window.posthog?.capture) {
92
- window.posthog.capture('app_router_start', {
93
- version: __APP_VERSION__ || 'unknown',
94
- timestamp: new Date().toISOString(),
95
- })
96
- }
97
- } catch {}
98
- }
99
- }, [currentView])
100
-
101
- // ── Toast helpers ────────────────────────────────────────────────────────
102
- const addToast = useCallback((message, type = 'info') => {
103
- const id = ++toastIdCounter
104
- setToasts((prev) => [...prev, { id, message, type }])
105
- setTimeout(() => {
106
- setToasts((prev) => prev.filter((t) => t.id !== id))
107
- }, 4000)
108
- }, [])
109
-
110
- const dismissToast = useCallback((id) => {
111
- setToasts((prev) => prev.filter((t) => t.id !== id))
112
- }, [])
113
-
114
- const {
115
- filtered,
116
- filterTier, setFilterTier,
117
- filterStatus, setFilterStatus,
118
- filterProvider, setFilterProvider,
119
- filterVerdict, setFilterVerdict,
120
- filterHealth, setFilterHealth,
121
- visibilityMode, setVisibilityMode,
122
- searchQuery, setSearchQuery,
123
- customTextFilter, setCustomTextFilter,
124
- sortColumn, sortDirection, setSortColumn, setSortDirection, toggleSort,
125
- resetView,
126
- } = useFilter(models)
127
-
128
- const { toolMode, setToolMode, cycleToolMode } = useToolMode({ onToast: addToast })
129
-
130
- // 📖 URL deep-linking (M2 = read + write). Hydrates on mount, then pushes
131
- // 📖 every change back via history.replaceState (debounced 80ms).
132
- useUrlState({
133
- currentView, setCurrentView,
134
- filterState: {
135
- filterTier, setFilterTier,
136
- filterStatus, setFilterStatus,
137
- filterProvider, setFilterProvider,
138
- filterVerdict, setFilterVerdict,
139
- filterHealth, setFilterHealth,
140
- sortColumn, sortDirection, setSortColumn, setSortDirection, toggleSort,
141
- setSearchQuery,
142
- filterState: null, // sentinel; useFilter doesn't expose this name
143
- searchQuery,
144
- },
145
- paletteOpen, setPaletteOpen,
146
- toolMode, setToolMode,
147
- })
148
-
149
- // 📖 Favorites — single source of truth shared with the TUI.
150
- const favorites = useFavorites({ models })
151
-
152
- // ── M3 endpoint flow: compat guard → /api/install-endpoint → fallback modal ──
153
- const handleInstallEndpoint = useCallback(async (model, overrideMode = null) => {
154
- const mode = overrideMode || toolMode || 'opencode'
155
- if (!model) return
156
- if (!isModelCompatibleWithTool(model.providerKey, mode)) {
157
- setIncompatibleRequest({ model, toolMode: mode })
158
- return
159
- }
160
- try {
161
- const resp = await fetch('/api/install-endpoint', {
162
- method: 'POST',
163
- headers: { 'Content-Type': 'application/json' },
164
- body: JSON.stringify({ providerKey: model.providerKey, modelId: model.modelId, toolMode: mode }),
165
- })
166
- const payload = await resp.json().catch(() => ({}))
167
- if (resp.status === 422 && payload.code === 'incompatible_model') {
168
- setIncompatibleRequest({ model, toolMode: mode })
169
- return
170
- }
171
- if (!resp.ok) throw new Error(payload.error || `HTTP ${resp.status}`)
172
- addToast(`Endpoint installed for ${model.label} in ${mode}. Start the tool yourself.`, 'success')
173
- } catch (err) {
174
- addToast(`Endpoint install failed: ${err.message}`, 'error')
175
- }
176
- }, [addToast, toolMode])
177
-
178
- const handleSwitchToolAndInstall = useCallback(async (mode, model) => {
179
- await setToolMode(mode)
180
- setIncompatibleRequest(null)
181
- await handleInstallEndpoint(model, mode)
182
- }, [handleInstallEndpoint, setToolMode])
183
-
184
- const handlePinAndInstall = useCallback(async (model) => {
185
- if (!favorites.isFavorite(model)) await favorites.toggle(model)
186
- await handleInstallEndpoint(model)
187
- }, [favorites, handleInstallEndpoint])
188
-
189
- // 📖 Update checker (5-minute poll). Returns `updateAvailable` for the chip.
190
- const {
191
- localVersion, latestVersion, updateAvailable, runUpdate, checkNow, error: updateError,
192
- } = useUpdateChecker({ onToast: addToast })
193
-
194
- // 📖 Build the provider list for the FilterBar dropdown with aggregated health.
195
- const providers = useMemo(() => {
196
- const map = {}
197
- models.forEach((m) => {
198
- if (!map[m.providerKey]) {
199
- map[m.providerKey] = {
200
- key: m.providerKey,
201
- name: m.origin,
202
- count: 0,
203
- hasKey: false,
204
- anyUp: false,
205
- }
206
- }
207
- map[m.providerKey].count++
208
- if (m.hasApiKey) map[m.providerKey].hasKey = true
209
- if (m.status === 'up') map[m.providerKey].anyUp = true
210
- })
211
- return Object.values(map).sort((a, b) => a.name.localeCompare(b.name))
212
- }, [models])
213
-
214
- // ── Global benchmark (AI Speed Test) ─────────────────────────────────────
215
- const handleBenchmark = useCallback(async () => {
216
- if (globalBenchmarkRunning) return
217
- try {
218
- await fetch('/api/global-benchmark', {
219
- method: 'POST',
220
- headers: { 'Content-Type': 'application/json' },
221
- body: JSON.stringify({
222
- models: filtered.map((model) => ({ providerKey: model.providerKey, modelId: model.modelId })),
223
- }),
224
- })
225
- } catch (err) {
226
- console.error('[Benchmark] Failed to start global benchmark:', err.message)
227
- }
228
- }, [filtered, globalBenchmarkRunning])
229
-
230
- // ── Per-model benchmark (M1 parity with TUI Ctrl+A) ──────────────────────
231
- const handleBenchmarkRow = useCallback(async (model) => {
232
- try {
233
- const resp = await fetch('/api/benchmark', {
234
- method: 'POST',
235
- headers: { 'Content-Type': 'application/json' },
236
- body: JSON.stringify({ providerKey: model.providerKey, modelId: model.modelId }),
237
- })
238
- if (!resp.ok && resp.status !== 202) {
239
- const err = await resp.json().catch(() => ({}))
240
- addToast(`Benchmark failed: ${err?.error || resp.statusText}`, 'error')
241
- }
242
- } catch (err) {
243
- console.error('[Benchmark] per-row failed:', err.message)
244
- }
245
- }, [addToast])
246
-
247
- // ── Selection / detail panel ─────────────────────────────────────────────
248
- const handleSelectModel = useCallback((model) => {
249
- setSelectedModel(model)
250
- lastActivityRef.current = Date.now()
251
- }, [])
252
- const handleCloseDetail = useCallback(() => setSelectedModel(null), [])
253
-
254
- // ── Ping mode change → server → broadcast ─────────────────────────────
255
- const handlePingModeChange = useCallback(async (mode) => {
256
- try {
257
- await fetch(`/api/ping-mode?action=${mode}`, { method: 'POST' })
258
- } catch {}
259
- }, [])
260
-
261
- // ── Navigation handler (Header nav + overflow menu) ──────────────────────
262
- const handleNavigate = useCallback((viewId) => {
263
- if (viewId === 'changelog') setChangelogDefaultVersion(null)
264
- setCurrentView(viewId)
265
- lastActivityRef.current = Date.now()
266
- }, [])
267
-
268
- // ── Reset view (N key equivalent) ────────────────────────────────────────
269
- const handleResetView = useCallback(() => {
270
- resetView()
271
- setSearchQuery('')
272
- addToast('View reset to defaults.', 'info')
273
- }, [addToast, resetView, setSearchQuery])
274
-
275
- // ── Changelog open with optional version (e.g. from UpdateChip "What's new") ─
276
- const openChangelogAt = useCallback((version) => {
277
- setChangelogDefaultVersion(version)
278
- setCurrentView('changelog')
279
- }, [])
280
-
281
- // ── Keyboard shortcuts: only ⌘K / Ctrl+P for the palette, Esc for any overlay ─
282
- useEffect(() => {
283
- const handler = (e) => {
284
- const cmdOrCtrl = e.metaKey || e.ctrlKey
285
- if (cmdOrCtrl && (e.key === 'k' || e.key === 'K')) {
286
- e.preventDefault()
287
- setPaletteOpen((o) => !o)
288
- return
289
- }
290
- if (cmdOrCtrl && (e.key === 'p' || e.key === 'P') && !e.shiftKey) {
291
- e.preventDefault()
292
- setPaletteOpen((o) => !o)
293
- return
294
- }
295
- if (e.key === 'Escape') {
296
- if (paletteOpen) { setPaletteOpen(false); return }
297
- if (incompatibleRequest) { setIncompatibleRequest(null); return }
298
- if (selectedModel) { setSelectedModel(null); return }
299
- if (exportOpen) { setExportOpen(false); return }
300
- }
301
- }
302
- window.addEventListener('keydown', handler)
303
- return () => window.removeEventListener('keydown', handler)
304
- }, [paletteOpen, incompatibleRequest, selectedModel, exportOpen])
305
-
306
- return (
307
- <>
308
- <div className="app-shell">
309
- <Header
310
- searchQuery={searchQuery}
311
- onSearchChange={setSearchQuery}
312
- currentView={currentView}
313
- onNavigate={handleNavigate}
314
- onToggleTheme={cycleTheme}
315
- onOpenExport={() => setExportOpen(true)}
316
- onOpenCommandPalette={() => setPaletteOpen(true)}
317
- onBenchmark={handleBenchmark}
318
- benchmarkRunning={globalBenchmarkRunning}
319
- benchmarkTotal={globalBenchmarkTotal}
320
- benchmarkCompleted={globalBenchmarkCompleted}
321
- modelsCount={filtered.length}
322
- theme={theme}
323
- onToast={addToast}
324
- toolMode={toolMode}
325
- onSetToolMode={setToolMode}
326
- onCycleToolMode={cycleToolMode}
327
- updateSlot={
328
- <UpdateChip
329
- updateAvailable={updateAvailable}
330
- latestVersion={latestVersion}
331
- onRunUpdate={runUpdate}
332
- onOpenChangelog={openChangelogAt}
333
- />
334
- }
335
- />
336
-
337
- <div className="app-content">
338
- {currentView === 'dashboard' && (
339
- <main className="view dashboardView">
340
- <FilterBar
341
- filterTier={filterTier}
342
- setFilterTier={setFilterTier}
343
- filterStatus={filterStatus}
344
- setFilterStatus={setFilterStatus}
345
- filterProvider={filterProvider}
346
- setFilterProvider={setFilterProvider}
347
- filterVerdict={filterVerdict}
348
- setFilterVerdict={setFilterVerdict}
349
- filterHealth={filterHealth}
350
- setFilterHealth={setFilterHealth}
351
- visibilityMode={visibilityMode}
352
- setVisibilityMode={setVisibilityMode}
353
- customTextFilter={customTextFilter}
354
- setCustomTextFilter={setCustomTextFilter}
355
- searchQuery={searchQuery}
356
- onSearchChange={setSearchQuery}
357
- onResetView={handleResetView}
358
- providers={providers}
359
- pingMode={pingMode}
360
- setPingMode={handlePingModeChange}
361
- nextPingAt={nextPingAt}
362
- isPinging={isPinging}
363
- globalBenchmarkRunning={globalBenchmarkRunning}
364
- globalBenchmarkTotal={globalBenchmarkTotal}
365
- globalBenchmarkCompleted={globalBenchmarkCompleted}
366
- toolMode={toolMode}
367
- />
368
- <ModelTable
369
- filtered={filtered}
370
- onSelectModel={handleSelectModel}
371
- onBenchmarkRow={handleBenchmarkRow}
372
- onLaunch={handleInstallEndpoint}
373
- favorites={favorites}
374
- sortColumn={sortColumn}
375
- sortDirection={sortDirection}
376
- onSort={toggleSort}
377
- toolMode={toolMode}
378
- onToast={addToast}
379
- onSetToolMode={setToolMode}
380
- onCycleToolMode={cycleToolMode}
381
- onOpenFallback={(model) => setIncompatibleRequest({ model, toolMode })}
382
- />
383
- </main>
384
- )}
385
-
386
- {currentView === 'settings' && (
387
- <div className="view">
388
- <SettingsView
389
- onToast={addToast}
390
- onOpenChangelog={(version) => { setChangelogDefaultVersion(version); handleNavigate('changelog') }}
391
- onCheckForUpdate={() => { checkNow(); addToast?.('Checking for updates…', 'info') }}
392
- />
393
- </div>
394
- )}
395
-
396
- {currentView === 'analytics' && (
397
- <div className="view">
398
- <AnalyticsView models={models} />
399
- </div>
400
- )}
401
-
402
- {currentView === 'router' && (
403
- <div className="view">
404
- <RouterView
405
- onClose={() => handleNavigate('dashboard')}
406
- onToast={addToast}
407
- favorites={favorites}
408
- />
409
- </div>
410
- )}
411
-
412
- {currentView === 'playground' && (
413
- <div className="view">
414
- <PlaygroundView
415
- onClose={() => handleNavigate('dashboard')}
416
- onToast={addToast}
417
- models={models}
418
- routerStatus={null}
419
- />
420
- </div>
421
- )}
422
-
423
- {currentView === 'help' && (
424
- <div className="view">
425
- <HelpView onClose={() => handleNavigate('dashboard')} />
426
- </div>
427
- )}
428
-
429
- {currentView === 'changelog' && (
430
- <div className="view">
431
- <ChangelogView
432
- onClose={() => handleNavigate('dashboard')}
433
- defaultVersion={changelogDefaultVersion}
434
- />
435
- </div>
436
- )}
437
-
438
- {currentView === 'installed-models' && (
439
- <div className="view">
440
- <InstalledModelsView
441
- onClose={() => handleNavigate('dashboard')}
442
- onToast={addToast}
443
- />
444
- </div>
445
- )}
446
-
447
- {currentView === 'install-endpoints' && (
448
- <div className="view">
449
- <InstallEndpointsView
450
- onClose={() => handleNavigate('dashboard')}
451
- onToast={addToast}
452
- />
453
- </div>
454
- )}
455
-
456
- {currentView === 'recommend' && (
457
- <div className="view">
458
- <RecommendView
459
- onClose={() => handleNavigate('dashboard')}
460
- toolMode={toolMode}
461
- onLaunch={handleInstallEndpoint}
462
- onPinAndLaunch={handlePinAndInstall}
463
- onToast={addToast}
464
- />
465
- </div>
466
- )}
467
-
468
- <Footer />
469
- </div>
470
- </div>
471
-
472
- {/* 📖 DetailPanel side panel replaced by expand row in ModelTable */}
473
-
474
- {exportOpen && (
475
- <ExportModal
476
- models={filtered}
477
- onClose={() => setExportOpen(false)}
478
- onToast={addToast}
479
- />
480
- )}
481
-
482
- {paletteOpen && (
483
- <CommandPalette
484
- onClose={() => setPaletteOpen(false)}
485
- onNavigate={handleNavigate}
486
- onCycleTheme={cycleTheme}
487
- onResetView={handleResetView}
488
- onSetPingMode={handlePingModeChange}
489
- onOpenHelp={() => handleNavigate('help')}
490
- onOpenChangelog={() => { setChangelogDefaultVersion(null); handleNavigate('changelog') }}
491
- onOpenPlayground={() => handleNavigate('playground')}
492
- onExport={() => setExportOpen(true)}
493
- onRunUpdate={runUpdate}
494
- currentView={currentView}
495
- theme={theme}
496
- pingMode={pingMode}
497
- models={models}
498
- updateAvailable={updateAvailable}
499
- latestVersion={latestVersion}
500
- onToast={addToast}
501
- />
502
- )}
503
-
504
- {incompatibleRequest && (
505
- <IncompatibleFallbackModal
506
- request={incompatibleRequest}
507
- models={models}
508
- onClose={() => setIncompatibleRequest(null)}
509
- onSwitchToolLaunch={handleSwitchToolAndInstall}
510
- onLaunchSimilar={(candidate) => {
511
- setIncompatibleRequest(null)
512
- const model = models.find((m) => m.providerKey === candidate.providerKey && m.modelId === candidate.modelId) || candidate
513
- void handleInstallEndpoint(model)
514
- }}
515
- />
516
- )}
517
-
518
- <ToastContainer toasts={toasts} dismissToast={dismissToast} />
519
- </>
520
- )
521
- }