free-coding-models 0.5.86 → 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 (163) hide show
  1. package/README.md +110 -881
  2. package/bin/free-coding-models.js +4 -1
  3. package/changelog/v0.5.87.md +48 -0
  4. package/package.json +8 -5
  5. package/sources.js +4 -4
  6. package/src/core/cache.js +3 -1
  7. package/src/core/config.js +43 -5
  8. package/src/core/endpoint-installer.js +23 -12
  9. package/src/core/installed-models-manager.js +43 -20
  10. package/src/core/kilo-config.js +51 -5
  11. package/src/core/kilo.js +5 -3
  12. package/src/core/legacy-proxy-cleanup.js +22 -5
  13. package/src/core/model-merger.js +4 -0
  14. package/src/core/models-drift.js +9 -0
  15. package/src/core/opencode-config.js +57 -5
  16. package/src/core/opencode.js +67 -41
  17. package/src/core/playground.js +7 -0
  18. package/src/core/probe-cache.js +35 -4
  19. package/src/core/provider-key-tester.js +10 -2
  20. package/src/core/provider-metadata.js +10 -0
  21. package/src/core/provider-quota-fetchers.js +14 -7
  22. package/src/core/router-daemon.js +208 -26
  23. package/src/core/runtime-telemetry.js +27 -1
  24. package/src/core/shared-helpers.js +12 -1
  25. package/src/core/shell-env.js +3 -1
  26. package/src/core/telemetry.js +18 -5
  27. package/src/core/tool-launchers.js +9 -6
  28. package/src/core/updater.js +28 -2
  29. package/src/core/utils.js +76 -71
  30. package/src/tui/app.js +38 -14
  31. package/src/tui/command-palette.js +22 -2
  32. package/src/tui/key-handler.js +123 -179
  33. package/src/tui/overlays.js +21 -71
  34. package/src/tui/render-helpers.js +62 -4
  35. package/src/tui/render-table.js +34 -3
  36. package/src/tui/theme.js +36 -5
  37. package/src/tui/tui-filters.js +28 -8
  38. package/src/tui/tui-state.js +22 -6
  39. package/web/dist/assets/index-BpTpPS0g.js +44 -0
  40. package/web/dist/assets/index-CAzFIt8P.css +1 -0
  41. package/web/dist/index.html +5 -9
  42. package/web/index.html +3 -7
  43. package/web/server.js +97 -23
  44. package/web/vite.config.js +8 -0
  45. package/web/app.legacy.js +0 -900
  46. package/web/dist/assets/index-C5-Ywvv3.css +0 -1
  47. package/web/dist/assets/index-Dm-Xfby9.js +0 -40
  48. package/web/public/favicon.ico +0 -0
  49. package/web/public/favicons/apple-touch-icon.png +0 -0
  50. package/web/public/favicons/browserconfig.xml +0 -12
  51. package/web/public/favicons/favicon-16x16.png +0 -0
  52. package/web/public/favicons/favicon-192x192.png +0 -0
  53. package/web/public/favicons/favicon-32x32.png +0 -0
  54. package/web/public/favicons/favicon-48x48.png +0 -0
  55. package/web/public/favicons/favicon-512x512.png +0 -0
  56. package/web/public/favicons/favicon-96x96.png +0 -0
  57. package/web/public/favicons/favicon.ico +0 -0
  58. package/web/public/favicons/mstile-150x150.png +0 -0
  59. package/web/public/favicons/mstile-310x150.png +0 -0
  60. package/web/public/favicons/mstile-310x310.png +0 -0
  61. package/web/public/favicons/mstile-512x512.png +0 -0
  62. package/web/public/favicons/mstile-70x70.png +0 -0
  63. package/web/public/favicons/site.webmanifest +0 -25
  64. package/web/src/App.jsx +0 -521
  65. package/web/src/components/analytics/AnalyticsView.jsx +0 -147
  66. package/web/src/components/analytics/AnalyticsView.module.css +0 -190
  67. package/web/src/components/analytics/TokenUsagePanel.jsx +0 -105
  68. package/web/src/components/analytics/TokenUsagePanel.module.css +0 -126
  69. package/web/src/components/atoms/AILatencyCell.jsx +0 -38
  70. package/web/src/components/atoms/AILatencyCell.module.css +0 -51
  71. package/web/src/components/atoms/HealthCell.jsx +0 -143
  72. package/web/src/components/atoms/HealthCell.module.css +0 -60
  73. package/web/src/components/atoms/LastPingCell.jsx +0 -35
  74. package/web/src/components/atoms/LastPingCell.module.css +0 -45
  75. package/web/src/components/atoms/MoodCell.jsx +0 -25
  76. package/web/src/components/atoms/MoodCell.module.css +0 -6
  77. package/web/src/components/atoms/NoKeyIcon.jsx +0 -65
  78. package/web/src/components/atoms/NoKeyIcon.module.css +0 -30
  79. package/web/src/components/atoms/ProviderLogo.jsx +0 -187
  80. package/web/src/components/atoms/ProviderLogo.module.css +0 -103
  81. package/web/src/components/atoms/RankCell.jsx +0 -9
  82. package/web/src/components/atoms/RankCell.module.css +0 -9
  83. package/web/src/components/atoms/Sparkline.jsx +0 -63
  84. package/web/src/components/atoms/StabilityCell.jsx +0 -18
  85. package/web/src/components/atoms/StabilityCell.module.css +0 -13
  86. package/web/src/components/atoms/StatusDot.jsx +0 -13
  87. package/web/src/components/atoms/StatusDot.module.css +0 -29
  88. package/web/src/components/atoms/TPSCell.jsx +0 -36
  89. package/web/src/components/atoms/TPSCell.module.css +0 -44
  90. package/web/src/components/atoms/TierBadge.jsx +0 -10
  91. package/web/src/components/atoms/TierBadge.module.css +0 -29
  92. package/web/src/components/atoms/Toast.jsx +0 -25
  93. package/web/src/components/atoms/Toast.module.css +0 -41
  94. package/web/src/components/atoms/ToastContainer.jsx +0 -16
  95. package/web/src/components/atoms/ToastContainer.module.css +0 -10
  96. package/web/src/components/atoms/VerdictBadge.jsx +0 -36
  97. package/web/src/components/atoms/VerdictBadge.module.css +0 -40
  98. package/web/src/components/changelog/ChangelogView.jsx +0 -135
  99. package/web/src/components/changelog/ChangelogView.module.css +0 -159
  100. package/web/src/components/dashboard/DetailPanel.jsx +0 -217
  101. package/web/src/components/dashboard/DetailPanel.module.css +0 -260
  102. package/web/src/components/dashboard/ExpandedDetailRow.jsx +0 -361
  103. package/web/src/components/dashboard/ExpandedDetailRow.module.css +0 -356
  104. package/web/src/components/dashboard/ExportModal.jsx +0 -84
  105. package/web/src/components/dashboard/ExportModal.module.css +0 -99
  106. package/web/src/components/dashboard/FilterBar.jsx +0 -338
  107. package/web/src/components/dashboard/FilterBar.module.css +0 -342
  108. package/web/src/components/dashboard/ModelTable.jsx +0 -672
  109. package/web/src/components/dashboard/ModelTable.module.css +0 -446
  110. package/web/src/components/dashboard/ProviderDropdown.jsx +0 -156
  111. package/web/src/components/dashboard/ProviderDropdown.module.css +0 -248
  112. package/web/src/components/help/HelpView.jsx +0 -201
  113. package/web/src/components/help/HelpView.module.css +0 -156
  114. package/web/src/components/install/InstallEndpointsView.jsx +0 -309
  115. package/web/src/components/install/InstallEndpointsView.module.css +0 -427
  116. package/web/src/components/installed/InstalledModelsView.jsx +0 -89
  117. package/web/src/components/installed/InstalledModelsView.module.css +0 -200
  118. package/web/src/components/launch/IncompatibleFallbackModal.jsx +0 -69
  119. package/web/src/components/launch/LaunchButton.jsx +0 -30
  120. package/web/src/components/launch/LaunchButton.module.css +0 -35
  121. package/web/src/components/launch/LaunchModal.module.css +0 -125
  122. package/web/src/components/layout/Footer.jsx +0 -19
  123. package/web/src/components/layout/Footer.module.css +0 -10
  124. package/web/src/components/layout/Header.jsx +0 -265
  125. package/web/src/components/layout/Header.module.css +0 -357
  126. package/web/src/components/palette/CommandPalette.jsx +0 -301
  127. package/web/src/components/palette/CommandPalette.module.css +0 -121
  128. package/web/src/components/playground/PlaygroundChat.jsx +0 -527
  129. package/web/src/components/playground/PlaygroundChat.module.css +0 -382
  130. package/web/src/components/playground/PlaygroundView.jsx +0 -371
  131. package/web/src/components/playground/PlaygroundView.module.css +0 -504
  132. package/web/src/components/recommend/RecommendView.jsx +0 -121
  133. package/web/src/components/recommend/RecommendView.module.css +0 -57
  134. package/web/src/components/router/RouterView.jsx +0 -1212
  135. package/web/src/components/router/RouterView.module.css +0 -1289
  136. package/web/src/components/settings/SettingsView.jsx +0 -555
  137. package/web/src/components/settings/SettingsView.module.css +0 -550
  138. package/web/src/components/tools/ToolPicker.jsx +0 -86
  139. package/web/src/components/tools/ToolPicker.module.css +0 -97
  140. package/web/src/components/update/UpdateChip.jsx +0 -104
  141. package/web/src/components/update/UpdateChip.module.css +0 -146
  142. package/web/src/global.css +0 -298
  143. package/web/src/hooks/urlState.constants.js +0 -28
  144. package/web/src/hooks/useChangelog.js +0 -51
  145. package/web/src/hooks/useColumnSizing.js +0 -211
  146. package/web/src/hooks/useFavorites.js +0 -150
  147. package/web/src/hooks/useFilter.js +0 -310
  148. package/web/src/hooks/useInstalledModels.js +0 -42
  149. package/web/src/hooks/useRecommend.js +0 -67
  150. package/web/src/hooks/useRouterDashboard.js +0 -133
  151. package/web/src/hooks/useSSE.js +0 -73
  152. package/web/src/hooks/useSocket.js +0 -203
  153. package/web/src/hooks/useTheme.js +0 -83
  154. package/web/src/hooks/useTokenUsage.js +0 -103
  155. package/web/src/hooks/useToolMode.js +0 -77
  156. package/web/src/hooks/useUpdateChecker.js +0 -91
  157. package/web/src/hooks/useUrlState.js +0 -170
  158. package/web/src/main.jsx +0 -15
  159. package/web/src/utils/download.js +0 -15
  160. package/web/src/utils/format.js +0 -42
  161. package/web/src/utils/m3.js +0 -57
  162. package/web/src/utils/ranks.js +0 -37
  163. 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
- }