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,104 +0,0 @@
1
- /**
2
- * @file web/src/components/update/UpdateChip.jsx
3
- * @description Header update chip + popover — M2 parity with TUI's auto-update banner.
4
- * 📖 Sits in the right side of the header (next to AI Latency, theme, export).
5
- * 📖 Hidden when no update is available; shows "⬆ vX.Y.Z" when one is.
6
- * 📖 Click → popover with "Update now" + "What's new" (which opens the
7
- * 📖 Changelog modal pre-focused on the new version).
8
- *
9
- * @functions
10
- * → UpdateChip — small badge with popover
11
- */
12
- import { useState, useRef, useEffect } from 'react'
13
- import { IconDownload, IconHistory, IconX, IconExternalLink } from '@tabler/icons-react'
14
- import styles from './UpdateChip.module.css'
15
-
16
- export default function UpdateChip({ updateAvailable, latestVersion, onRunUpdate, onOpenChangelog, checking }) {
17
- const [open, setOpen] = useState(false)
18
- const ref = useRef(null)
19
-
20
- // 📖 Close on outside click / Esc — same pattern as the header kebab menu.
21
- useEffect(() => {
22
- if (!open) return
23
- const onClick = (e) => { if (ref.current && !ref.current.contains(e.target)) setOpen(false) }
24
- const onKey = (e) => { if (e.key === 'Escape') setOpen(false) }
25
- document.addEventListener('mousedown', onClick)
26
- document.addEventListener('keydown', onKey)
27
- return () => {
28
- document.removeEventListener('mousedown', onClick)
29
- document.removeEventListener('keydown', onKey)
30
- }
31
- }, [open])
32
-
33
- // 📖 No chip when no update. The M1 plan keeps the chip honest — a fresh
34
- // 📖 install should look clean, not noisy.
35
- if (!updateAvailable) {
36
- if (checking) {
37
- return (
38
- <span className={styles.checking} title="Checking for updates…">
39
- <span className={styles.dot} />
40
- </span>
41
- )
42
- }
43
- return null
44
- }
45
-
46
- return (
47
- <div className={styles.wrap} ref={ref}>
48
- <button
49
- className={styles.chip}
50
- onClick={() => setOpen((o) => !o)}
51
- title={`Update available: v${latestVersion}. Click to install.`}
52
- aria-expanded={open}
53
- aria-haspopup="dialog"
54
- >
55
- <IconDownload size={13} stroke={1.5} />
56
- <span>v{latestVersion}</span>
57
- </button>
58
-
59
- {open && (
60
- <div className={styles.popover} role="dialog" aria-label="Update available">
61
- <div className={styles.popoverHeader}>
62
- <div className={styles.popoverTitle}>
63
- <IconDownload size={14} stroke={1.5} />
64
- <span>Update available</span>
65
- </div>
66
- <button
67
- className={styles.popoverClose}
68
- onClick={() => setOpen(false)}
69
- aria-label="Close"
70
- >
71
- <IconX size={14} stroke={1.5} />
72
- </button>
73
- </div>
74
- <p className={styles.popoverBody}>
75
- A newer version (<strong>v{latestVersion}</strong>) is available on npm.
76
- After updating, restart the dashboard to pick up the changes.
77
- </p>
78
- <div className={styles.popoverActions}>
79
- <button
80
- className={styles.primaryAction}
81
- onClick={() => {
82
- setOpen(false)
83
- onRunUpdate?.()
84
- }}
85
- >
86
- <IconDownload size={13} stroke={1.5} />
87
- <span>Update now</span>
88
- </button>
89
- <button
90
- className={styles.secondaryAction}
91
- onClick={() => {
92
- setOpen(false)
93
- onOpenChangelog?.(latestVersion)
94
- }}
95
- >
96
- <IconHistory size={13} stroke={1.5} />
97
- <span>What's new</span>
98
- </button>
99
- </div>
100
- </div>
101
- )}
102
- </div>
103
- )
104
- }
@@ -1,146 +0,0 @@
1
- /**
2
- * @file web/src/components/update/UpdateChip.module.css
3
- * @description Styles for the header update chip + popover.
4
- */
5
-
6
- .wrap {
7
- position: relative;
8
- display: inline-flex;
9
- }
10
-
11
- .chip {
12
- display: inline-flex;
13
- align-items: center;
14
- gap: 5px;
15
- height: 28px;
16
- padding: 0 10px;
17
- font-size: 11px;
18
- font-weight: 700;
19
- font-family: var(--font-mono);
20
- background: var(--color-success-dim);
21
- color: var(--color-success);
22
- border: 1px solid var(--color-success);
23
- border-radius: 6px;
24
- cursor: pointer;
25
- font-family: var(--font-mono);
26
- transition: all 150ms;
27
- white-space: nowrap;
28
- }
29
- .chip:hover {
30
- background: var(--color-success);
31
- color: var(--color-bg);
32
- }
33
-
34
- .checking {
35
- display: inline-flex;
36
- align-items: center;
37
- justify-content: center;
38
- width: 22px;
39
- height: 22px;
40
- opacity: 0.5;
41
- }
42
- .dot {
43
- width: 6px;
44
- height: 6px;
45
- border-radius: 50%;
46
- background: var(--color-text-muted);
47
- animation: pulse 1.5s ease-in-out infinite;
48
- }
49
- @keyframes pulse {
50
- 0%, 100% { opacity: 0.3; }
51
- 50% { opacity: 1; }
52
- }
53
-
54
- .popover {
55
- position: absolute;
56
- top: calc(100% + 6px);
57
- right: 0;
58
- width: 320px;
59
- background: var(--color-bg-elevated);
60
- border: 1px solid var(--color-border-hover);
61
- border-radius: 10px;
62
- box-shadow: 0 12px 32px rgba(0, 0, 0, 0.4);
63
- z-index: 200;
64
- overflow: hidden;
65
- }
66
-
67
- .popoverHeader {
68
- display: flex;
69
- align-items: center;
70
- justify-content: space-between;
71
- padding: 10px 12px;
72
- border-bottom: 1px solid var(--color-border);
73
- }
74
- .popoverTitle {
75
- display: inline-flex;
76
- align-items: center;
77
- gap: 6px;
78
- font-size: 12px;
79
- font-weight: 700;
80
- color: var(--color-success);
81
- }
82
- .popoverClose {
83
- background: transparent;
84
- border: none;
85
- color: var(--color-text-muted);
86
- cursor: pointer;
87
- display: inline-flex;
88
- align-items: center;
89
- justify-content: center;
90
- padding: 2px;
91
- border-radius: 4px;
92
- }
93
- .popoverClose:hover { color: var(--color-text); background: var(--color-bg-hover); }
94
-
95
- .popoverBody {
96
- padding: 10px 12px;
97
- margin: 0;
98
- font-size: 12px;
99
- line-height: 1.5;
100
- color: var(--color-text);
101
- }
102
- .popoverBody strong {
103
- color: var(--color-success);
104
- font-family: var(--font-mono);
105
- }
106
-
107
- .popoverActions {
108
- display: flex;
109
- gap: 6px;
110
- padding: 8px 12px 12px;
111
- }
112
-
113
- .primaryAction,
114
- .secondaryAction {
115
- display: inline-flex;
116
- align-items: center;
117
- gap: 5px;
118
- flex: 1;
119
- height: 32px;
120
- padding: 0 10px;
121
- font-size: 11px;
122
- font-weight: 600;
123
- border-radius: 6px;
124
- cursor: pointer;
125
- justify-content: center;
126
- font-family: var(--font-sans);
127
- transition: all 150ms;
128
- }
129
- .primaryAction {
130
- background: var(--color-success);
131
- color: var(--color-bg);
132
- border: 1px solid var(--color-success);
133
- }
134
- .primaryAction:hover {
135
- background: var(--color-success);
136
- filter: brightness(0.9);
137
- }
138
- .secondaryAction {
139
- background: var(--color-surface);
140
- color: var(--color-text);
141
- border: 1px solid var(--color-border);
142
- }
143
- .secondaryAction:hover {
144
- background: var(--color-bg-hover);
145
- border-color: var(--color-text-muted);
146
- }
@@ -1,298 +0,0 @@
1
- /**
2
- * @file web/src/global.css
3
- * @description Global CSS with design tokens, base reset, background effects, and shared styles.
4
- * 📖 Contains all CSS custom properties (--color-*, --font-*, --radius-*, etc.),
5
- * light theme overrides, reset rules, background grid/glow effects,
6
- * app-content layout, view visibility, scrollbar, and responsive breakpoints.
7
- */
8
-
9
- /* ─── CSS Custom Properties (Design Tokens) ─── */
10
- :root {
11
- --color-brand: #c0f20c;
12
- --color-bg: #050505;
13
- --color-bg-elevated: #090a09;
14
- --color-bg-card: #080908;
15
- --color-bg-hover: #121512;
16
- --color-bg-active: #181d18;
17
- --color-surface: #0d100d;
18
- --color-border: #161a16;
19
- --color-border-hover: #2e3b2e;
20
-
21
- --color-text: #ffffff;
22
- --color-text-muted: #8fa08f;
23
- --color-text-dim: #455245;
24
-
25
- --color-accent: #c0f20c;
26
- --color-accent-hover: #d2ff2b;
27
- --color-accent-glow: rgba(192, 242, 12, 0.15);
28
- --color-accent-dim: rgba(192, 242, 12, 0.08);
29
-
30
- --color-danger: #ff4444;
31
- --color-danger-dim: rgba(255, 68, 68, 0.1);
32
- --color-warning: #ffaa00;
33
- --color-warning-dim: rgba(255, 170, 0, 0.1);
34
- --color-success: #00ff88;
35
- --color-success-dim: rgba(0, 255, 136, 0.1);
36
- --color-info: #06b6d4;
37
- --color-info-dim: rgba(6, 182, 212, 0.1);
38
-
39
- --tier-splus: #ffd700;
40
- --tier-s: #ff8c00;
41
- --tier-aplus: #00c8ff;
42
- --tier-a: #3ddc84;
43
- --tier-aminus: #7ecf7e;
44
- --tier-bplus: #a8a8c8;
45
- --tier-b: #808098;
46
- --tier-c: #606078;
47
-
48
- --verdict-perfect: #00ff88;
49
- --verdict-normal: #76b900;
50
- --verdict-slow: #ffaa00;
51
- --verdict-spiky: #ff6600;
52
- --verdict-veryslow: #ff4444;
53
- --verdict-overloaded: #ff2222;
54
- --verdict-unstable: #cc0000;
55
- --verdict-notactive: #555570;
56
- --verdict-pending: #444460;
57
-
58
- --font-sans: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
59
- --font-mono: 'JetBrains Mono', 'Fira Code', 'Cascadia Code', monospace;
60
-
61
- --header-h: 60px;
62
- --radius-sm: 4px;
63
- --radius-md: 6px;
64
- --radius-lg: 8px;
65
- --radius-xl: 8px;
66
-
67
- --shadow-sm: none;
68
- --shadow-md: none;
69
- --shadow-lg: none;
70
- --shadow-glow: none;
71
-
72
- --ease-out: cubic-bezier(0.16, 1, 0.3, 1);
73
- --transition-fast: 150ms var(--ease-out);
74
- --transition-medium: 250ms var(--ease-out);
75
- --transition-slow: 400ms var(--ease-out);
76
- }
77
-
78
- /* ─── Light Theme ─── */
79
- [data-theme="light"] {
80
- --color-bg: #ffffff;
81
- --color-bg-elevated: #ffffff;
82
- --color-bg-card: #ffffff;
83
- --color-bg-hover: #fafafa;
84
- --color-bg-active: #f0f0f0;
85
- --color-surface: #fafafa;
86
- --color-border: #eaeaea;
87
- --color-border-hover: #d5d5d5;
88
- --color-text: #000000;
89
- --color-text-muted: #666666;
90
- --color-text-dim: #999999;
91
- --color-accent: #000000;
92
- --color-accent-hover: #333333;
93
- --color-accent-glow: rgba(0, 0, 0, 0.05);
94
- --color-accent-dim: rgba(0, 0, 0, 0.02);
95
-
96
- /* 📖 Bright neon colors that look great on black become unreadable on white.
97
- 📖 We swap them for darker, AA-contrast equivalents in light mode. The
98
- 📖 hex values are picked to stay visually similar to the dark variant
99
- 📖 (still "green" / "cyan" / "gold") while passing WCAG AA on #fff. */
100
- --color-success: #008f4d; /* was #00ff88 (neon) */
101
- --color-warning: #b86b00; /* was #ffaa00 (neon amber) */
102
- --color-danger: #c8143a; /* was #ff4444 (neon red) */
103
- --color-info: #0e7490; /* was #06b6d4 (neon cyan) */
104
- --color-danger-dim: rgba(200, 20, 58, 0.08);
105
- --color-warning-dim: rgba(184, 107, 0, 0.08);
106
- --color-success-dim: rgba(0, 143, 77, 0.08);
107
- --color-info-dim: rgba(14, 116, 144, 0.08);
108
-
109
- /* 📖 Tier + verdict palette: shift toward darker, more saturated variants
110
- 📖 so badges stay legible on white. The original "neon" feel is dropped
111
- 📖 in favor of "saturated-but-deep" colors that pass contrast. */
112
- --tier-splus: #b08800; /* was #ffd700 (gold) */
113
- --tier-s: #c45a00; /* was #ff8c00 (dark orange) */
114
- --tier-aplus: #0073a8; /* was #00c8ff (neon cyan) */
115
- --tier-a: #1f7a3e; /* was #3ddc84 (mid green) */
116
- --tier-aminus: #2f7a2f; /* was #7ecf7e (light green) */
117
- --tier-bplus: #5a5a8a; /* was #a8a8c8 (lavender) */
118
- --tier-b: #555566; /* was #808098 (gray-blue) */
119
- --tier-c: #6a6a7a; /* was #606078 (slate) */
120
-
121
- --verdict-perfect: #008f4d; /* was #00ff88 */
122
- --verdict-normal: #4a7a00; /* was #76b900 */
123
- --verdict-slow: #b86b00; /* was #ffaa00 */
124
- --verdict-spiky: #c45000; /* was #ff6600 */
125
- --verdict-veryslow: #c8143a; /* was #ff4444 */
126
- --verdict-overloaded: #b8101f; /* was #ff2222 */
127
- --verdict-unstable: #990000; /* was #cc0000 */
128
- --verdict-notactive: #6a6a7a; /* was #555570 */
129
- --verdict-pending: #6a6a8a; /* was #444460 */
130
-
131
- --shadow-sm: none;
132
- --shadow-md: none;
133
- --shadow-lg: none;
134
- }
135
-
136
- /* ─── Reset & Base ─── */
137
- *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
138
-
139
- /* 📖 M5: Global :focus-visible outline for keyboard navigation (a11y).
140
- 📖 Only shows for keyboard users, not mouse clicks. Uses the accent color
141
- 📖 with a subtle glow ring so focus is always visible but not jarring. */
142
- :focus-visible {
143
- outline: 2px solid var(--color-accent);
144
- outline-offset: 2px;
145
- border-radius: var(--radius-sm);
146
- }
147
-
148
- /* 📖 Remove default browser :focus ring — we only want :focus-visible */
149
- :focus:not(:focus-visible) {
150
- outline: none;
151
- }
152
-
153
- /* 📖 Inputs keep their own focus styling via component CSS, but keyboard
154
- 📖 users still see the global ring as a fallback safety net. */
155
- input:focus-visible,
156
- select:focus-visible,
157
- textarea:focus-visible {
158
- outline: 2px solid var(--color-accent);
159
- outline-offset: 1px;
160
- }
161
-
162
- html {
163
- font-size: 16px;
164
- scroll-behavior: smooth;
165
- -webkit-font-smoothing: antialiased;
166
- -moz-osx-font-smoothing: grayscale;
167
- }
168
-
169
- body {
170
- font-family: var(--font-sans);
171
- background: var(--color-bg);
172
- color: var(--color-text);
173
- min-height: 100vh;
174
- overflow-x: hidden;
175
- position: relative;
176
- display: flex;
177
- flex-direction: column;
178
- }
179
-
180
- a { color: var(--color-accent); text-decoration: none; }
181
- a:hover { color: var(--color-accent-hover); }
182
- code { font-family: var(--font-mono); font-size: 14px; background: var(--color-surface); padding: 2px 6px; border-radius: 4px; }
183
-
184
- /* ─── Background Effects ─── */
185
- [data-theme="light"] .app-shell {
186
- background-image:
187
- linear-gradient(rgba(0,0,0,0.03) 1px, transparent 1px),
188
- linear-gradient(90deg, rgba(0,0,0,0.03) 1px, transparent 1px);
189
- }
190
- [data-theme="light"] .app-shell::before {
191
- background: radial-gradient(circle, rgba(0, 143, 77, 0.03) 0%, transparent 70%);
192
- }
193
-
194
- /* ─── App Layout ─── */
195
- .app-shell {
196
- flex: 1;
197
- display: flex;
198
- flex-direction: column;
199
- min-height: 100vh;
200
- width: 100%;
201
- position: relative;
202
- z-index: 1;
203
- background-color: var(--color-bg);
204
- /* background grid effect */
205
- background-image:
206
- linear-gradient(rgba(192, 242, 12, 0.012) 1px, transparent 1px),
207
- linear-gradient(90deg, rgba(192, 242, 12, 0.012) 1px, transparent 1px);
208
- background-size: 50px 50px;
209
- background-position: center top;
210
- }
211
-
212
- .app-shell::before {
213
- content: "";
214
- position: absolute;
215
- top: 0;
216
- left: 25%;
217
- width: 600px;
218
- height: 600px;
219
- background: radial-gradient(circle, rgba(192, 242, 12, 0.03) 0%, transparent 70%);
220
- z-index: -1;
221
- pointer-events: none;
222
- }
223
-
224
- .app-content {
225
- flex: 1;
226
- position: relative;
227
- z-index: 1;
228
- min-height: calc(100vh - var(--header-h));
229
- display: flex;
230
- flex-direction: column;
231
- }
232
-
233
- .view {
234
- display: flex;
235
- flex-direction: column;
236
- flex: 1;
237
- min-height: 0;
238
- }
239
-
240
- /* 📖 Dashboard-only: bound the main box to the viewport height (minus the
241
- 📖 global header) so the model table scrolls INTERNALLY inside .scrollInner
242
- 📖 instead of growing the whole page. That internal scroll is what lets the
243
- 📖 green <thead> header be position:sticky and stay pinned while you scroll
244
- 📖 190+ model rows. Scoped to the dashboard only (main.dashboardView has
245
- 📖 higher specificity than the shared .view rule, so flex:none + the capped
246
- 📖 height reliably win). Settings / Analytics / Playground keep their layout. */
247
- main.dashboardView {
248
- flex: none;
249
- height: calc(100vh - var(--header-h));
250
- height: calc(100dvh - var(--header-h));
251
- min-height: 0;
252
- overflow: hidden;
253
- }
254
-
255
- .update-warning-banner {
256
- position: sticky;
257
- top: var(--header-h, 60px);
258
- z-index: 90;
259
- padding: 10px 18px;
260
- background: var(--color-danger);
261
- color: #fff;
262
- border-bottom: 1px solid rgba(255, 255, 255, 0.22);
263
- font-weight: 800;
264
- font-size: 15px;
265
- letter-spacing: 0.01em;
266
- text-align: center;
267
- box-shadow: 0 8px 24px rgba(0, 0, 0, 0.22);
268
- }
269
-
270
- /* ─── Scrollbar ─── */
271
- ::-webkit-scrollbar { width: 8px; height: 8px; }
272
- ::-webkit-scrollbar-track { background: var(--color-bg); }
273
- ::-webkit-scrollbar-thumb { background: var(--color-border); border-radius: 4px; }
274
- ::-webkit-scrollbar-thumb:hover { background: var(--color-text-dim); }
275
- ::-webkit-scrollbar-corner { background: var(--color-bg); }
276
-
277
- /* ─── Responsive ─── */
278
- @media (max-width: 768px) {
279
- /* 📖 No sidebar to collapse on small viewports — the header itself collapses
280
- 📖 (see Header.module.css). The model table keeps its 100% width. */
281
- }
282
-
283
- /* ─── Full-page horizontal/vertical scroll layout ─── */
284
- .app-content {
285
- flex: 1;
286
- position: relative;
287
- z-index: 1;
288
- min-height: calc(100vh - var(--header-h));
289
- display: flex;
290
- flex-direction: column;
291
- }
292
-
293
- .view {
294
- display: flex;
295
- flex-direction: column;
296
- flex: 1;
297
- min-height: 0;
298
- }
@@ -1,28 +0,0 @@
1
- /**
2
- * @file web/src/hooks/urlState.constants.js
3
- * @description URL param validation tables — extracted from useUrlState for testability.
4
- * 📖 Keep these in sync with src/tui/tui-state.js TIER_CYCLE / VERDICT_CYCLE /
5
- * 📖 HEALTH_CYCLE constants so the URL reflects the same filter universe
6
- * 📖 the TUI uses.
7
- */
8
- import { TOOL_MODE_ORDER } from '../../../src/core/tool-metadata.js'
9
-
10
- const TIER_VALUES = new Set(['S+', 'S', 'A+', 'A', 'A-', 'B+', 'B', 'C', 'all'])
11
- const STATUS_VALUES = new Set(['up', 'down', 'pending', 'all'])
12
- const SORT_VALUES = new Set([
13
- 'mood', 'idx', 'tier', 'sweScore', 'ctx', 'label', 'origin',
14
- 'latestPing', 'avg', 'condition', 'verdict', 'stability', 'uptime',
15
- 'aiLatency', 'tps', 'trend',
16
- ])
17
- const VIEW_VALUES = new Set(['dashboard', 'settings', 'analytics', 'recommend', 'router', 'help', 'changelog', 'playground', 'install-endpoints', 'installed-models'])
18
- const DIR_VALUES = new Set(['asc', 'desc'])
19
- const TOOL_MODE_VALUES = new Set(TOOL_MODE_ORDER)
20
-
21
- export {
22
- TIER_VALUES as VALID_TIERS,
23
- STATUS_VALUES as VALID_STATUS,
24
- SORT_VALUES as VALID_SORTS,
25
- VIEW_VALUES as VALID_VIEWS,
26
- DIR_VALUES as VALID_DIRS,
27
- TOOL_MODE_VALUES as VALID_TOOL_MODES,
28
- }
@@ -1,51 +0,0 @@
1
- /**
2
- * @file web/src/hooks/useChangelog.js
3
- * @description React hook for the changelog data — M2 parity with the TUI's `N` key overlay.
4
- * 📖 Loads `/api/changelog` once on mount, exposes the parsed { versions } map,
5
- * 📖 and provides helpers for the index/details two-phase modal.
6
- *
7
- * @functions
8
- * → useChangelog() — { versions, sortedVersions, getVersion, loading, error, refresh }
9
- */
10
- import { useEffect, useMemo, useState, useCallback } from 'react'
11
-
12
- export function useChangelog() {
13
- const [versions, setVersions] = useState({})
14
- const [loading, setLoading] = useState(true)
15
- const [error, setError] = useState(null)
16
-
17
- const refresh = useCallback(async () => {
18
- try {
19
- const resp = await fetch('/api/changelog')
20
- if (!resp.ok) throw new Error(`HTTP ${resp.status}`)
21
- const data = await resp.json()
22
- setVersions(data?.versions ?? {})
23
- setError(null)
24
- } catch (err) {
25
- setError(err.message || 'Failed to load changelog')
26
- } finally {
27
- setLoading(false)
28
- }
29
- }, [])
30
-
31
- useEffect(() => { refresh() }, [refresh])
32
-
33
- // 📖 Sort versions in descending semver order. We do a string-based compare
34
- // 📖 on the dotted tuples so '0.10.0' > '0.9.0' works correctly.
35
- const sortedVersions = useMemo(() => {
36
- return Object.keys(versions).sort((a, b) => {
37
- const ap = a.split('.').map(Number)
38
- const bp = b.split('.').map(Number)
39
- for (let i = 0; i < Math.max(ap.length, bp.length); i++) {
40
- const av = ap[i] || 0
41
- const bv = bp[i] || 0
42
- if (bv !== av) return bv - av
43
- }
44
- return 0
45
- })
46
- }, [versions])
47
-
48
- const getVersion = useCallback((v) => versions[v] ?? null, [versions])
49
-
50
- return { versions, sortedVersions, getVersion, loading, error, refresh }
51
- }