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