free-coding-models 0.5.1 โ 0.5.4
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 +31 -5
- package/changelog/v0.5.4.md +24 -0
- package/package.json +5 -2
- package/sources.js +2 -0
- package/src/tui/key-handler.js +1 -1
- package/web/assets/providers/aistudio/aistudio-text.svg +1 -0
- package/web/assets/providers/aistudio/aistudio.svg +1 -0
- package/web/assets/providers/cerebras/cerebras-brand-color.svg +1 -0
- package/web/assets/providers/cerebras/cerebras-brand.svg +1 -0
- package/web/assets/providers/cerebras/cerebras-color.svg +1 -0
- package/web/assets/providers/cerebras/cerebras-text.svg +1 -0
- package/web/assets/providers/cerebras/cerebras.svg +1 -0
- package/web/assets/providers/cloudflare/cloudflare-color.svg +1 -0
- package/web/assets/providers/cloudflare/cloudflare-text.svg +1 -0
- package/web/assets/providers/cloudflare/cloudflare.svg +1 -0
- package/web/assets/providers/gemini/gemini-color.svg +1 -0
- package/web/assets/providers/gemini/gemini-text.svg +1 -0
- package/web/assets/providers/gemini/gemini.svg +1 -0
- package/web/assets/providers/github/github-text.svg +1 -0
- package/web/assets/providers/github/github.svg +1 -0
- package/web/assets/providers/groq/groq-text.svg +1 -0
- package/web/assets/providers/groq/groq.svg +1 -0
- package/web/assets/providers/mistral/mistral-color.svg +1 -0
- package/web/assets/providers/mistral/mistral-text.svg +1 -0
- package/web/assets/providers/mistral/mistral.svg +1 -0
- package/web/assets/providers/nvidia/nvidia-color.svg +1 -0
- package/web/assets/providers/nvidia/nvidia-text.svg +1 -0
- package/web/assets/providers/nvidia/nvidia.svg +1 -0
- package/web/assets/providers/opencode/opencode-text.svg +1 -0
- package/web/assets/providers/opencode/opencode.svg +1 -0
- package/web/assets/providers/openrouter/openrouter (1).svg +1 -0
- package/web/assets/providers/openrouter/openrouter-text.svg +1 -0
- package/web/assets/providers/openrouter/openrouter.svg +1 -0
- package/web/assets/providers/ovhcloud/ovhcloud.svg +12 -0
- package/web/assets/providers/qwen/qwen-color.svg +1 -0
- package/web/assets/providers/qwen/qwen-text.svg +1 -0
- package/web/assets/providers/qwen/qwen.svg +1 -0
- package/web/assets/providers/sambanova/sambanova-color.svg +1 -0
- package/web/assets/providers/sambanova/sambanova-text.svg +1 -0
- package/web/assets/providers/sambanova/sambanova.svg +1 -0
- package/web/assets/providers/scalewaylogo/ScalewayLogo.svg +13 -0
- package/web/assets/providers/zai/zai-text.svg +1 -0
- package/web/assets/providers/zai/zai.svg +1 -0
- package/web/dist/assets/index-BoWmUveV.js +39 -0
- package/web/dist/assets/index-BrpHevg4.css +1 -0
- package/web/dist/favicon.ico +0 -0
- package/web/dist/favicons/apple-touch-icon.png +0 -0
- package/web/dist/favicons/browserconfig.xml +12 -0
- package/web/dist/favicons/favicon-16x16.png +0 -0
- package/web/dist/favicons/favicon-192x192.png +0 -0
- package/web/dist/favicons/favicon-32x32.png +0 -0
- package/web/dist/favicons/favicon-48x48.png +0 -0
- package/web/dist/favicons/favicon-512x512.png +0 -0
- package/web/dist/favicons/favicon-96x96.png +0 -0
- package/web/dist/favicons/favicon.ico +0 -0
- package/web/dist/favicons/mstile-150x150.png +0 -0
- package/web/dist/favicons/mstile-310x150.png +0 -0
- package/web/dist/favicons/mstile-310x310.png +0 -0
- package/web/dist/favicons/mstile-512x512.png +0 -0
- package/web/dist/favicons/mstile-70x70.png +0 -0
- package/web/dist/favicons/site.webmanifest +25 -0
- package/web/dist/index.html +42 -3
- package/web/index.html +40 -1
- package/web/public/favicon.ico +0 -0
- package/web/public/favicons/apple-touch-icon.png +0 -0
- package/web/public/favicons/browserconfig.xml +12 -0
- 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 +25 -0
- package/web/server.js +71 -1
- package/web/src/App.jsx +209 -89
- package/web/src/components/analytics/AnalyticsView.jsx +30 -2
- package/web/src/components/analytics/AnalyticsView.module.css +4 -0
- package/web/src/components/atoms/AILatencyCell.module.css +9 -1
- package/web/src/components/atoms/HealthCell.jsx +113 -28
- package/web/src/components/atoms/HealthCell.module.css +43 -2
- package/web/src/components/atoms/LastPingCell.module.css +10 -0
- package/web/src/components/atoms/NoKeyIcon.jsx +65 -0
- package/web/src/components/atoms/NoKeyIcon.module.css +30 -0
- package/web/src/components/atoms/ProviderLogo.jsx +188 -0
- package/web/src/components/atoms/ProviderLogo.module.css +103 -0
- package/web/src/components/atoms/Sparkline.jsx +21 -2
- package/web/src/components/atoms/StabilityCell.module.css +5 -0
- package/web/src/components/atoms/StatusDot.module.css +7 -0
- package/web/src/components/atoms/TPSCell.module.css +7 -1
- package/web/src/components/atoms/TierBadge.module.css +11 -0
- package/web/src/components/atoms/Toast.module.css +6 -0
- package/web/src/components/atoms/VerdictBadge.module.css +12 -0
- package/web/src/components/dashboard/DetailPanel.jsx +63 -2
- package/web/src/components/dashboard/DetailPanel.module.css +130 -0
- package/web/src/components/dashboard/FilterBar.jsx +146 -53
- package/web/src/components/dashboard/FilterBar.module.css +138 -58
- package/web/src/components/dashboard/ModelTable.jsx +235 -30
- package/web/src/components/dashboard/ModelTable.module.css +205 -16
- package/web/src/components/layout/Header.jsx +140 -12
- package/web/src/components/layout/Header.module.css +191 -27
- package/web/src/components/palette/CommandPalette.jsx +146 -0
- package/web/src/components/palette/CommandPalette.module.css +121 -0
- package/web/src/global.css +52 -15
- package/web/src/hooks/useColumnSizing.js +211 -0
- package/web/src/hooks/useFavorites.js +150 -0
- package/web/src/hooks/useFilter.js +157 -24
- package/web/src/hooks/useTheme.js +72 -16
- package/web/src/hooks/useUrlState.js +93 -0
- package/web/dist/assets/index-ByGf4Kq-.js +0 -14
- package/web/dist/assets/index-Ds7wmHBv.css +0 -1
- package/web/src/components/dashboard/StatsBar.jsx +0 -42
- package/web/src/components/dashboard/StatsBar.module.css +0 -28
- package/web/src/components/layout/Sidebar.jsx +0 -50
- package/web/src/components/layout/Sidebar.module.css +0 -86
- package/web/src/components/map/MapView.jsx +0 -17
- package/web/src/components/map/MapView.module.css +0 -25
|
@@ -1,27 +1,83 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @file web/src/hooks/useTheme.js
|
|
3
|
-
* @description React hook for dark/light
|
|
4
|
-
* ๐
|
|
5
|
-
* โ
|
|
3
|
+
* @description React hook for tri-state theme cycle (auto / dark / light) โ M1 parity.
|
|
4
|
+
* ๐ Mirrors the TUI's `G` key behavior:
|
|
5
|
+
* ๐ auto โ system preference (resolved once at mount)
|
|
6
|
+
* ๐ dark โ explicit dark
|
|
7
|
+
* ๐ light โ explicit light
|
|
8
|
+
* ๐ Persists the choice on <html data-theme="auto|dark|light">. When set to
|
|
9
|
+
* ๐ `auto`, the hook resolves the actual rendered theme and writes it back to
|
|
10
|
+
* ๐ <html data-theme="..."> via resolveSystemTheme() (light/dark) so the
|
|
11
|
+
* ๐ rest of the CSS keeps working without any "auto" branch in stylesheets.
|
|
12
|
+
*
|
|
13
|
+
* @functions
|
|
14
|
+
* โ useTheme() โ { theme, resolvedTheme, setTheme, cycle }
|
|
6
15
|
*/
|
|
7
16
|
import { useState, useCallback, useEffect } from 'react'
|
|
8
17
|
|
|
18
|
+
export const THEME_CYCLE = ['auto', 'dark', 'light']
|
|
19
|
+
|
|
20
|
+
// ๐ Read the OS-level dark/light preference. Falls back to dark on browsers
|
|
21
|
+
// ๐ that don't support matchMedia (very rare; SSR snapshots, etc.).
|
|
22
|
+
function resolveSystemTheme() {
|
|
23
|
+
if (typeof window === 'undefined' || typeof window.matchMedia !== 'function') return 'dark'
|
|
24
|
+
try {
|
|
25
|
+
return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'
|
|
26
|
+
} catch {
|
|
27
|
+
return 'dark'
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function readInitialTheme() {
|
|
32
|
+
if (typeof document === 'undefined') return 'auto'
|
|
33
|
+
const attr = document.documentElement.getAttribute('data-theme')
|
|
34
|
+
if (attr === 'auto' || attr === 'dark' || attr === 'light') return attr
|
|
35
|
+
return 'auto'
|
|
36
|
+
}
|
|
37
|
+
|
|
9
38
|
export function useTheme() {
|
|
10
|
-
const [theme,
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
39
|
+
const [theme, setThemeState] = useState(readInitialTheme)
|
|
40
|
+
const [systemTheme, setSystemTheme] = useState(resolveSystemTheme)
|
|
41
|
+
|
|
42
|
+
// ๐ Listen for OS theme changes while the user is in "auto" mode.
|
|
43
|
+
useEffect(() => {
|
|
44
|
+
if (typeof window === 'undefined' || typeof window.matchMedia !== 'function') return
|
|
45
|
+
const mq = window.matchMedia('(prefers-color-scheme: dark)')
|
|
46
|
+
const handler = (e) => setSystemTheme(e.matches ? 'dark' : 'light')
|
|
47
|
+
if (typeof mq.addEventListener === 'function') {
|
|
48
|
+
mq.addEventListener('change', handler)
|
|
49
|
+
return () => mq.removeEventListener('change', handler)
|
|
50
|
+
}
|
|
51
|
+
// ๐ Safari < 14 fallback
|
|
52
|
+
if (typeof mq.addListener === 'function') {
|
|
53
|
+
mq.addListener(handler)
|
|
54
|
+
return () => mq.removeListener(handler)
|
|
55
|
+
}
|
|
20
56
|
}, [])
|
|
21
57
|
|
|
58
|
+
// ๐ Apply the resolved theme to <html data-theme="..."> on every change.
|
|
59
|
+
// ๐ We write the *resolved* value (light / dark) so existing CSS keeps working.
|
|
22
60
|
useEffect(() => {
|
|
23
|
-
|
|
24
|
-
|
|
61
|
+
const resolved = theme === 'auto' ? systemTheme : theme
|
|
62
|
+
document.documentElement.setAttribute('data-theme', resolved)
|
|
63
|
+
// ๐ Also stamp the user-chosen mode so other parts of the app can read it
|
|
64
|
+
// ๐ (e.g. to render the right "auto" badge in the theme button).
|
|
65
|
+
document.documentElement.setAttribute('data-theme-preference', theme)
|
|
66
|
+
}, [theme, systemTheme])
|
|
67
|
+
|
|
68
|
+
// ๐ Cycle through auto โ dark โ light โ auto, matching the TUI's `G` key.
|
|
69
|
+
const cycle = useCallback(() => {
|
|
70
|
+
setThemeState((prev) => {
|
|
71
|
+
const idx = THEME_CYCLE.indexOf(prev)
|
|
72
|
+
return THEME_CYCLE[(idx + 1) % THEME_CYCLE.length]
|
|
73
|
+
})
|
|
74
|
+
}, [])
|
|
75
|
+
|
|
76
|
+
const setTheme = useCallback((next) => {
|
|
77
|
+
if (THEME_CYCLE.includes(next)) setThemeState(next)
|
|
78
|
+
}, [])
|
|
79
|
+
|
|
80
|
+
const resolvedTheme = theme === 'auto' ? systemTheme : theme
|
|
25
81
|
|
|
26
|
-
return { theme,
|
|
82
|
+
return { theme, resolvedTheme, setTheme, cycle }
|
|
27
83
|
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file web/src/hooks/useUrlState.js
|
|
3
|
+
* @description URL deep-linking hook โ the Web's answer to TUI CLI flags (--tier, --sort, etc.).
|
|
4
|
+
* ๐ M1 ships read-only hydration: on mount, parses the query string and hydrates the Web view.
|
|
5
|
+
* ๐ M2 will add write-back (push filter/sort/view changes back to the URL via history.replaceState).
|
|
6
|
+
*
|
|
7
|
+
* ๐ Supported query params (M1 = read-only):
|
|
8
|
+
* ๐ ?tier=S+|S|A+|A|A-|B+|B|C|all โ sets the tier filter
|
|
9
|
+
* ๐ ?status=up|down|pending|all โ sets the health/status filter
|
|
10
|
+
* ๐ ?provider=<providerKey>|all โ sets the provider filter
|
|
11
|
+
* ๐ ?verdict=<verdict>|all โ sets the verdict filter
|
|
12
|
+
* ๐ ?health=<health>|all โ sets the health filter
|
|
13
|
+
* ๐ ?sort=<col>&dir=asc|desc โ sets the sort column + direction
|
|
14
|
+
* ๐ ?view=dashboard|settings|analytics โ sets the active view
|
|
15
|
+
* ๐ ?q=<text> โ sets the search query
|
|
16
|
+
* ๐ ?tier=S&sort=verdict&dir=asc โ compose freely; the same URL is shareable
|
|
17
|
+
*
|
|
18
|
+
* @functions
|
|
19
|
+
* โ useUrlState({ currentView, setCurrentView, filterState }) โ hydrates Web state from URL
|
|
20
|
+
*
|
|
21
|
+
* @see ideas/tui-web-feature-parity.md ยง5.4 โ full CLI-flag โ URL-param mapping
|
|
22
|
+
*/
|
|
23
|
+
import { useEffect } from 'react'
|
|
24
|
+
|
|
25
|
+
// ๐ Valid enum values for cycle-style params. Keys here determine which params
|
|
26
|
+
// ๐ get accepted silently vs ignored. Anything not in the list is dropped.
|
|
27
|
+
const VALID_TIERS = new Set(['S+', 'S', 'A+', 'A', 'A-', 'B+', 'B', 'C', 'all'])
|
|
28
|
+
const VALID_STATUS = new Set(['up', 'down', 'pending', 'all'])
|
|
29
|
+
const VALID_SORTS = new Set([
|
|
30
|
+
'mood', 'idx', 'tier', 'sweScore', 'ctx', 'label', 'origin',
|
|
31
|
+
'latestPing', 'avg', 'condition', 'verdict', 'stability', 'uptime',
|
|
32
|
+
'aiLatency', 'tps', 'trend',
|
|
33
|
+
])
|
|
34
|
+
const VALID_VIEWS = new Set(['dashboard', 'settings', 'analytics'])
|
|
35
|
+
const VALID_DIRS = new Set(['asc', 'desc'])
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* ๐ parseUrlParams: extract a normalized set of params from the current URL.
|
|
39
|
+
* ๐ Returns a flat object with camelCase keys, only including recognized params.
|
|
40
|
+
* @returns {{
|
|
41
|
+
* tier: string|null, status: string|null, provider: string|null,
|
|
42
|
+
* verdict: string|null, health: string|null,
|
|
43
|
+
* sort: string|null, dir: string|null, view: string|null, q: string,
|
|
44
|
+
* }}
|
|
45
|
+
*/
|
|
46
|
+
function parseUrlParams() {
|
|
47
|
+
if (typeof window === 'undefined') return null
|
|
48
|
+
const params = new URLSearchParams(window.location.search)
|
|
49
|
+
const out = {}
|
|
50
|
+
if (params.has('tier') && VALID_TIERS.has(params.get('tier'))) out.tier = params.get('tier')
|
|
51
|
+
if (params.has('status') && VALID_STATUS.has(params.get('status'))) out.status = params.get('status')
|
|
52
|
+
if (params.has('provider')) out.provider = params.get('provider')
|
|
53
|
+
if (params.has('verdict')) out.verdict = params.get('verdict')
|
|
54
|
+
if (params.has('health')) out.health = params.get('health')
|
|
55
|
+
if (params.has('sort') && VALID_SORTS.has(params.get('sort'))) out.sort = params.get('sort')
|
|
56
|
+
if (params.has('dir') && VALID_DIRS.has(params.get('dir'))) out.dir = params.get('dir')
|
|
57
|
+
if (params.has('view') && VALID_VIEWS.has(params.get('view'))) out.view = params.get('view')
|
|
58
|
+
if (params.has('q')) out.q = params.get('q')
|
|
59
|
+
return out
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* ๐ useUrlState: hydrate the Web view from the current URL on mount.
|
|
64
|
+
* ๐ M1 = read-only. Pass setters; the hook calls them once on mount with
|
|
65
|
+
* ๐ whatever the URL declares, then leaves the URL alone.
|
|
66
|
+
*
|
|
67
|
+
* ๐ The hook is intentionally non-intrusive: if the URL has no params, it does
|
|
68
|
+
* ๐ nothing. The caller is free to call the setters independently โ the URL
|
|
69
|
+
* ๐ simply becomes the *initial* state.
|
|
70
|
+
*
|
|
71
|
+
* @param {{
|
|
72
|
+
* currentView: string,
|
|
73
|
+
* setCurrentView: (view: string) => void,
|
|
74
|
+
* filterState: object | null, // wired in M2
|
|
75
|
+
* }} opts
|
|
76
|
+
*/
|
|
77
|
+
export function useUrlState({ currentView, setCurrentView, filterState }) {
|
|
78
|
+
useEffect(() => {
|
|
79
|
+
const params = parseUrlParams()
|
|
80
|
+
if (!params) return
|
|
81
|
+
|
|
82
|
+
if (params.view && params.view !== currentView) {
|
|
83
|
+
setCurrentView(params.view)
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// ๐ Filter hydration is wired through the filterState prop in M2. M1 just
|
|
87
|
+
// ๐ sets the view; the rest of the URL params act as documentation for
|
|
88
|
+
// ๐ the user and get the write-back loop fully closed in M2.
|
|
89
|
+
}, [currentView, setCurrentView, filterState])
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// ๐ Re-export the parser so M2's write-back path can use the same validator.
|
|
93
|
+
export { parseUrlParams, VALID_TIERS, VALID_STATUS, VALID_SORTS, VALID_VIEWS, VALID_DIRS }
|