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,103 +0,0 @@
1
- /**
2
- * @file web/src/components/atoms/ProviderLogo.module.css
3
- * @description Styles for the inline [icon + wordmark] provider cell.
4
- *
5
- * - Single horizontal row, max-height pinned so the cell never exceeds the
6
- * table's row height (matches the 7px vertical padding on .td).
7
- * - `color: var(--color-text)` propagates the active theme's text color into
8
- * inlined SVGs that use `fill="currentColor"` (mono icons + all wordmarks).
9
- * - Sized to fit within a ~150px column at default row height. Cloudflare
10
- * is the widest wordmark (~145px at 12px high) — it fits but is tight.
11
- */
12
-
13
- .row {
14
- display: inline-flex;
15
- align-items: center;
16
- gap: 6px;
17
- max-width: 100%;
18
- max-height: var(--pl-max-h, 22px);
19
- overflow: hidden;
20
- /* 📖 Inherited by the inlined SVGs that use `fill="currentColor"`. Set on
21
- 📖 the row so both the mono icon and the wordmark pick up the theme. */
22
- color: var(--color-text);
23
- line-height: 1;
24
- vertical-align: middle;
25
- }
26
-
27
- .icon {
28
- display: inline-flex;
29
- align-items: center;
30
- justify-content: center;
31
- height: var(--pl-icon-h, 16px);
32
- width: auto;
33
- flex-shrink: 0;
34
- }
35
- /* 📖 The inlined <svg> from lobe-icons ships with `width="1em" height="1em"`.
36
- 📖 We override both so the wrapper's height controls the rendered size, and
37
- 📖 let the viewBox dictate the width (preserves each icon's aspect ratio). */
38
- .icon svg {
39
- height: 100%;
40
- width: auto;
41
- display: block;
42
- }
43
-
44
- .textLogo {
45
- display: inline-flex;
46
- align-items: center;
47
- justify-content: center;
48
- height: var(--pl-text-h, 12px);
49
- width: auto;
50
- flex-shrink: 0;
51
- }
52
- .textLogo svg {
53
- height: 100%;
54
- width: auto;
55
- display: block;
56
- }
57
-
58
- /* 📖 Wordmark-only variant used by OVH (and any future provider whose "icon"
59
- 📖 asset is itself a full horizontal wordmark). Sized to a slightly tighter
60
- 📖 max height than the square icons so the wordmark doesn't visually crowd
61
- 📖 the row. The viewBox aspect ratio of the OVH wordmark is ~6.3:1, so at
62
- 📖 14px high it renders ~88px wide — fits comfortably in 150px. */
63
- .iconLegacyWordmark {
64
- display: inline-flex;
65
- align-items: center;
66
- justify-content: center;
67
- height: var(--pl-icon-h, 14px);
68
- width: auto;
69
- flex-shrink: 0;
70
- /* 📖 Default text color is inherited from the row. Wordmarks that use
71
- 📖 `fill="currentColor"` (OVH) follow the theme automatically. Wordmarks
72
- 📖 with hardcoded fills (Scaleway) need a per-theme override — see
73
- 📖 `.scalewayLogo` below. */
74
- color: var(--color-text);
75
- }
76
- .iconLegacyWordmark svg {
77
- height: 100%;
78
- width: auto;
79
- display: block;
80
- }
81
-
82
- /* 📖 Scaleway: brand purple #521094 is the official color. It looks great on
83
- 📖 white but is barely visible on the dark background — the contrast is
84
- 📖 too low (dark purple on near-black). Bump to a lighter, more saturated
85
- 📖 shade in dark mode so the wordmark reads cleanly without losing brand
86
- 📖 identity. Paths were rewritten to use `fill="currentColor"` so this
87
- 📖 single CSS color property controls the whole wordmark. */
88
- :global(.scalewayLogo) {
89
- color: #521094;
90
- transition: color 200ms var(--ease-out, ease);
91
- }
92
- :global([data-theme="dark"]) :global(.scalewayLogo) {
93
- color: #a78bfa;
94
- }
95
-
96
- .fallback {
97
- font-size: 11px;
98
- font-weight: 500;
99
- color: var(--color-text);
100
- white-space: nowrap;
101
- /* 📖 Slight tracking so the origin name reads cleanly next to the icon. */
102
- letter-spacing: 0.2px;
103
- }
@@ -1,9 +0,0 @@
1
- /**
2
- * @file web/src/components/atoms/RankCell.jsx
3
- * @description Row index number — triable column, matches CLI Rank (idx).
4
- */
5
- import styles from './RankCell.module.css'
6
-
7
- export default function RankCell({ index }) {
8
- return <span className={styles.rank}>{index}</span>
9
- }
@@ -1,9 +0,0 @@
1
- .rank {
2
- display: inline-block;
3
- font-family: var(--font-mono);
4
- font-size: 12px;
5
- font-weight: 600;
6
- color: var(--color-text-dim);
7
- text-align: center;
8
- min-width: 28px;
9
- }
@@ -1,63 +0,0 @@
1
- /**
2
- * @file web/src/components/atoms/Sparkline.jsx
3
- * @description SVG sparkline chart for ping history trend visualization.
4
- * 📖 Renders a polyline with gradient area fill and endpoint dot.
5
- * 📖 Colors are theme-aware: reads CSS custom properties from :root so the
6
- * 📖 stroke/dot swap to the AA-contrast palette in light mode automatically.
7
- */
8
- import { useMemo } from 'react'
9
-
10
- // 📖 Read CSS custom properties at module scope; falls back to dark-mode neon
11
- // 📖 values when running outside the browser (e.g. during server rendering).
12
- function readCssVar(name, fallback) {
13
- if (typeof window === 'undefined' || !window.document?.documentElement) return fallback
14
- const v = getComputedStyle(document.documentElement).getPropertyValue(name).trim()
15
- return v || fallback
16
- }
17
-
18
- // 📖 Threshold-based color picker that returns the current theme's variant of
19
- // 📖 each color. Reading the variables on every render is fine — they only
20
- // 📖 change when the user toggles the theme, and the memo cache key catches it.
21
- function pickSparkColor(lastVal) {
22
- if (lastVal < 500) return readCssVar('--color-success', '#00ff88')
23
- if (lastVal < 1500) return readCssVar('--color-warning', '#ffaa00')
24
- return readCssVar('--color-danger', '#ff4444')
25
- }
26
-
27
- export default function Sparkline({ history }) {
28
- const svg = useMemo(() => {
29
- if (!history || history.length < 2) return null
30
- const valid = history.filter((p) => p.code === '200' || p.code === '401')
31
- if (valid.length < 2) return null
32
-
33
- const values = valid.map((p) => p.ms)
34
- const max = Math.max(...values, 1)
35
- const min = Math.min(...values, 0)
36
- const range = max - min || 1
37
- const w = 80,
38
- h = 22
39
- const step = w / (values.length - 1)
40
-
41
- const points = values
42
- .map((v, i) => {
43
- const x = i * step
44
- const y = h - ((v - min) / range) * (h - 4) - 2
45
- return `${x.toFixed(1)},${y.toFixed(1)}`
46
- })
47
- .join(' ')
48
-
49
- const lastVal = values[values.length - 1]
50
- const color = pickSparkColor(lastVal)
51
- const lastX = ((values.length - 1) * step).toFixed(1)
52
- const lastY = (h - ((lastVal - min) / range) * (h - 4) - 2).toFixed(1)
53
-
54
- return (
55
- <svg className="sparkline-svg" width={w} height={h} viewBox={`0 0 ${w} ${h}`}>
56
- <polyline fill="none" stroke={color} strokeWidth="1.5" strokeLinejoin="round" strokeLinecap="round" points={points} opacity="0.85" />
57
- <circle cx={lastX} cy={lastY} r="2.5" fill={color} />
58
- </svg>
59
- )
60
- }, [history])
61
-
62
- return svg || null
63
- }
@@ -1,18 +0,0 @@
1
- /**
2
- * @file web/src/components/atoms/StabilityCell.jsx
3
- * @description Renders stability score with a progress bar and numeric value.
4
- */
5
- import styles from './StabilityCell.module.css'
6
-
7
- export default function StabilityCell({ score }) {
8
- if (score == null || score < 0) return <span className={styles.none}>—</span>
9
- const cls = score >= 70 ? 'high' : score >= 40 ? 'mid' : 'low'
10
- return (
11
- <div className={styles.cell}>
12
- <div className={styles.bar}>
13
- <div className={`${styles.fill} ${styles[cls]}`} style={{ width: `${score}%` }} />
14
- </div>
15
- <span className={styles.value}>{score}</span>
16
- </div>
17
- )
18
- }
@@ -1,13 +0,0 @@
1
- .cell { display: flex; align-items: center; gap: 6px; }
2
- .bar { width: 48px; height: 6px; border-radius: 3px; background: var(--color-surface); overflow: hidden; }
3
- .fill { height: 100%; border-radius: 3px; transition: width 250ms cubic-bezier(0.16, 1, 0.3, 1); }
4
- .high { background: linear-gradient(90deg, #00ff88, #76b900); }
5
- .mid { background: linear-gradient(90deg, #ffaa00, #ff8c00); }
6
- .low { background: linear-gradient(90deg, #ff4444, #cc0000); }
7
- .value { font-family: 'JetBrains Mono', monospace; font-size: 12px; font-weight: 600; }
8
- .none { color: var(--color-text-dim); }
9
-
10
- /* 📖 Light theme: drop the neon gradients to deep, saturated versions. */
11
- :global([data-theme="light"]) .high { background: linear-gradient(90deg, #008f4d, #4a7a00); }
12
- :global([data-theme="light"]) .mid { background: linear-gradient(90deg, #b86b00, #c45a00); }
13
- :global([data-theme="light"]) .low { background: linear-gradient(90deg, #c8143a, #990000); }
@@ -1,13 +0,0 @@
1
- /**
2
- * @file web/src/components/atoms/StatusDot.jsx
3
- * @description Renders a colored status indicator dot (green=up, red=down, gray=pending).
4
- * 📖 When a model is not in the active router set and still pending, we show
5
- * 📖 a muted gray dot instead of the animated yellow — this makes it visually
6
- * 📖 clear that the model is *not being tested* rather than *still loading*.
7
- */
8
- import styles from './StatusDot.module.css'
9
-
10
- export default function StatusDot({ status, inRouterSet = true }) {
11
- const cls = status === 'up' ? styles.up : status === 'timeout' ? styles.timeout : status === 'down' ? styles.down : (status === 'pending' && !inRouterSet) ? styles.notInSet : styles.pending
12
- return <span className={`${styles.dot} ${cls}`} />
13
- }
@@ -1,29 +0,0 @@
1
- .dot {
2
- display: inline-block;
3
- width: 8px;
4
- height: 8px;
5
- border-radius: 50%;
6
- margin-right: 6px;
7
- vertical-align: middle;
8
- }
9
- .up { background: #00ff88; box-shadow: 0 0 6px rgba(0,255,136,0.4); }
10
- .down { background: #ff4444; }
11
- .timeout { background: #ff4444; }
12
- .pending { background: #555570; animation: pulse 1.5s ease-in-out infinite; }
13
- /* 📖 Models not in the active router set — static dim dot, no pulse animation.
14
- 📖 Visually distinct from 'pending' (yellow animated) so users know this
15
- 📖 model is simply not being tested, not still loading. */
16
- .notInSet { background: #3a3a4a; opacity: 0.5; }
17
-
18
- @keyframes pulse {
19
- 0%, 100% { box-shadow: 0 0 0 0 rgba(118,185,0,0.25); }
20
- 50% { box-shadow: 0 0 0 6px transparent; }
21
- }
22
-
23
- /* 📖 Light theme: deep green/red for the dot so it still reads as "up"/"down"
24
- 📖 on a white card; the glow halo is dimmed so it doesn't blow out. */
25
- :global([data-theme="light"]) .up { background: #008f4d; box-shadow: 0 0 4px rgba(0,143,77,0.35); }
26
- :global([data-theme="light"]) .down,
27
- :global([data-theme="light"]) .timeout { background: #c8143a; }
28
- :global([data-theme="light"]) .pending { background: #888899; }
29
- :global([data-theme="light"]) .notInSet { background: #b0b0b8; opacity: 0.6; }
@@ -1,36 +0,0 @@
1
- /**
2
- * @file web/src/components/atoms/TPSCell.jsx
3
- * @description Benchmark tokens-per-second column — animated spinner during benchmarks.
4
- * 📖 Shows: 13, 45, —. Retry badge ↻N in blue.
5
- * 📖 When isRunning: animated CSS spinner matches AILatencyCell.
6
- */
7
- import styles from './TPSCell.module.css'
8
-
9
- function formatTps(result, isRunning) {
10
- if (isRunning) return { text: '…', badge: '' }
11
- if (!result || !result.ok) return { text: '—', badge: '' }
12
- const badge = result.retries > 0 ? `↻${result.retries}` : ''
13
- return { text: String(Math.round(result.tokensPerSecond ?? 0)), badge }
14
- }
15
-
16
- export default function TPSCell({ result, isRunning }) {
17
- const { text, badge } = formatTps(result, isRunning)
18
- const ok = result?.ok
19
- const colorCls = ok ? styles.fast : (result ? styles.slow : styles.dim)
20
-
21
- return (
22
- <span className={`${styles.cell} ${isRunning ? styles.runningCell : ''}`}>
23
- {isRunning ? (
24
- <>
25
- <span className={styles.miniSpinner} />
26
- <span className={`${styles.value} ${styles.running}`}>{text}</span>
27
- </>
28
- ) : (
29
- <>
30
- <span className={`${styles.value} ${colorCls}`}>{text}</span>
31
- {badge && <span className={styles.badge}>{badge}</span>}
32
- </>
33
- )}
34
- </span>
35
- )
36
- }
@@ -1,44 +0,0 @@
1
- .cell {
2
- font-family: var(--font-mono);
3
- font-size: 12px;
4
- font-weight: 600;
5
- display: inline-flex;
6
- align-items: center;
7
- gap: 4px;
8
- min-width: 40px;
9
- transition: background 200ms;
10
- border-radius: 4px;
11
- padding: 1px 3px;
12
- }
13
- .value { }
14
- .fast { color: #00ff88; }
15
- .slow { color: #ff4444; }
16
- .dim { color: var(--color-text-dim); }
17
- .running { color: #b400ff; font-weight: 700; }
18
- .badge { color: #06b6d4; font-size: 10px; }
19
-
20
- /* ── Running row highlight ── */
21
- .runningCell {
22
- background: rgba(180, 0, 255, 0.12);
23
- }
24
-
25
- /* ── Mini spinner matching AILatencyCell ── */
26
- @keyframes miniSpin {
27
- to { transform: rotate(360deg); }
28
- }
29
- .miniSpinner {
30
- display: inline-block;
31
- width: 10px;
32
- height: 10px;
33
- border: 1.5px solid rgba(180, 0, 255, 0.3);
34
- border-top-color: #b400ff;
35
- border-radius: 50%;
36
- animation: miniSpin 0.6s linear infinite;
37
- flex-shrink: 0;
38
- }
39
-
40
- /* 📖 Light theme overrides — match AILatencyCell. */
41
- :global([data-theme="light"]) .fast { color: #008f4d; }
42
- :global([data-theme="light"]) .slow { color: #c8143a; }
43
- :global([data-theme="light"]) .badge { color: #0e7490; }
44
- :global([data-theme="light"]) .runningCell { background: rgba(125, 0, 180, 0.08); }
@@ -1,10 +0,0 @@
1
- /**
2
- * @file web/src/components/atoms/TierBadge.jsx
3
- * @description Renders a tier badge (S+, S, A+, etc.) with color-coded styling.
4
- */
5
- import styles from './TierBadge.module.css'
6
-
7
- export default function TierBadge({ tier }) {
8
- const cls = tier.replace('+', 'plus').replace('-', 'minus').toLowerCase()
9
- return <span className={`${styles.badge} ${styles[`tier_${cls}`]}`}>{tier}</span>
10
- }
@@ -1,29 +0,0 @@
1
- .badge {
2
- display: inline-block;
3
- font-family: 'JetBrains Mono', monospace;
4
- font-weight: 700;
5
- font-size: 11px;
6
- padding: 2px 8px;
7
- border-radius: 4px;
8
- text-align: center;
9
- min-width: 32px;
10
- }
11
- .tier_splus { background: rgba(255,215,0,0.15); color: #ffd700; border: 1px solid rgba(255,215,0,0.3); }
12
- .tier_s { background: rgba(255,140,0,0.12); color: #ff8c00; border: 1px solid rgba(255,140,0,0.25); }
13
- .tier_aplus { background: rgba(0,200,255,0.1); color: #00c8ff; border: 1px solid rgba(0,200,255,0.2); }
14
- .tier_a { background: rgba(61,220,132,0.1); color: #3ddc84; border: 1px solid rgba(61,220,132,0.2); }
15
- .tier_aminus{ background: rgba(126,207,126,0.1); color: #7ecf7e; border: 1px solid rgba(126,207,126,0.2); }
16
- .tier_bplus { background: rgba(168,168,200,0.1); color: #b8b8d8; border: 1px solid rgba(168,168,200,0.15); }
17
- .tier_b { background: rgba(128,128,152,0.1); color: #a0a0b8; border: 1px solid rgba(128,128,152,0.15); }
18
- .tier_c { background: rgba(96,96,120,0.1); color: #909098; border: 1px solid rgba(96,96,120,0.15); }
19
-
20
- /* 📖 Light theme: darker, AA-contrast tier text colors. Borders stay the same
21
- 📖 hue so the badge still feels "tier-colored", just more legible. */
22
- :global([data-theme="light"]) .tier_splus { color: #7a5e00; border-color: rgba(176,136,0,0.4); }
23
- :global([data-theme="light"]) .tier_s { color: #a14a00; border-color: rgba(196,90,0,0.4); }
24
- :global([data-theme="light"]) .tier_aplus { color: #005d8a; border-color: rgba(0,115,168,0.4); }
25
- :global([data-theme="light"]) .tier_a { color: #1f7a3e; border-color: rgba(31,122,62,0.4); }
26
- :global([data-theme="light"]) .tier_aminus { color: #2f7a2f; border-color: rgba(47,122,47,0.4); }
27
- :global([data-theme="light"]) .tier_bplus { color: #4a4a72; border-color: rgba(90,90,138,0.4); }
28
- :global([data-theme="light"]) .tier_b { color: #555566; border-color: rgba(85,85,102,0.4); }
29
- :global([data-theme="light"]) .tier_c { color: #5a5a6a; border-color: rgba(106,106,122,0.4); }
@@ -1,25 +0,0 @@
1
- /**
2
- * @file web/src/components/atoms/Toast.jsx
3
- * @description Toast notification component with auto-dismiss and animated entrance/exit.
4
- */
5
- import { useEffect, useRef } from 'react'
6
- import styles from './Toast.module.css'
7
-
8
- const ICONS = { success: '✅', error: '❌', warning: '⚠️', info: 'ℹ️' }
9
-
10
- export default function Toast({ message, type = 'info', duration = 3500, onDismiss }) {
11
- const timerRef = useRef(null)
12
-
13
- useEffect(() => {
14
- timerRef.current = setTimeout(onDismiss, duration)
15
- return () => clearTimeout(timerRef.current)
16
- }, [duration, onDismiss])
17
-
18
- return (
19
- <div className={`${styles.toast} ${styles[type]}`}>
20
- <span className={styles.icon}>{ICONS[type] || '📌'}</span>
21
- <span className={styles.message}>{message}</span>
22
- <button className={styles.close} onClick={onDismiss}>×</button>
23
- </div>
24
- )
25
- }
@@ -1,41 +0,0 @@
1
- .toast {
2
- pointer-events: auto;
3
- display: flex;
4
- align-items: center;
5
- gap: 10px;
6
- padding: 12px 18px;
7
- border-radius: var(--radius-md, 10px);
8
- background: var(--color-bg-elevated);
9
- border: 1px solid var(--color-border);
10
- box-shadow: var(--shadow-lg);
11
- font-size: 13px;
12
- font-weight: 500;
13
- animation: slideIn 300ms cubic-bezier(0.16, 1, 0.3, 1);
14
- min-width: 280px;
15
- max-width: 420px;
16
- }
17
- .success { border-left: 4px solid #00ff88; }
18
- .error { border-left: 4px solid #ff4444; }
19
- .warning { border-left: 4px solid #ffaa00; }
20
- .info { border-left: 4px solid #06b6d4; }
21
-
22
- /* 📖 Light theme: deeper accent bars so they pop on the white toast card. */
23
- :global([data-theme="light"]) .success { border-left-color: #008f4d; }
24
- :global([data-theme="light"]) .error { border-left-color: #c8143a; }
25
- :global([data-theme="light"]) .warning { border-left-color: #b86b00; }
26
- :global([data-theme="light"]) .info { border-left-color: #0e7490; }
27
- .icon { font-size: 18px; flex-shrink: 0; }
28
- .message { flex: 1; }
29
- .close {
30
- background: none;
31
- border: none;
32
- color: var(--color-text-dim);
33
- cursor: pointer;
34
- font-size: 16px;
35
- padding: 0;
36
- line-height: 1;
37
- }
38
- .exiting { animation: slideOut 300ms cubic-bezier(0.16, 1, 0.3, 1) forwards; }
39
-
40
- @keyframes slideIn { from { transform: translateX(100%); opacity: 0; } to { transform: translateX(0); opacity: 1; } }
41
- @keyframes slideOut { from { transform: translateX(0); opacity: 1; } to { transform: translateX(100%); opacity: 0; } }
@@ -1,16 +0,0 @@
1
- /**
2
- * @file web/src/components/atoms/ToastContainer.jsx
3
- * @description Fixed-position container that renders all active toasts.
4
- */
5
- import styles from './ToastContainer.module.css'
6
- import Toast from './Toast.jsx'
7
-
8
- export default function ToastContainer({ toasts, dismissToast }) {
9
- return (
10
- <div className={styles.container}>
11
- {toasts.map((t) => (
12
- <Toast key={t.id} message={t.message} type={t.type} onDismiss={() => dismissToast(t.id)} />
13
- ))}
14
- </div>
15
- )
16
- }
@@ -1,10 +0,0 @@
1
- .container {
2
- position: fixed;
3
- top: 20px;
4
- right: 20px;
5
- z-index: 10000;
6
- display: flex;
7
- flex-direction: column;
8
- gap: 8px;
9
- pointer-events: none;
10
- }
@@ -1,36 +0,0 @@
1
- /**
2
- * @file web/src/components/atoms/VerdictBadge.jsx
3
- * @description Renders a verdict badge matching exactly the TUI format.
4
- * 📖 Shows emoji + text: 🟩 Perfect, 🟢 Normal, 🟡 Spiky, 🟠 Slow, 🔴 Very Slow, 🔥 Overloaded, 🟥 Unstable, ⚫ Not Active, ⏳ Pending.
5
- */
6
- import styles from './VerdictBadge.module.css'
7
-
8
- // ─── Emoji map matching TUI render-table.js verdictIcon ──────────────────────────
9
- const VERDICT_WITH_EMOJI = {
10
- Perfect: { emoji: '🟩', text: 'Perfect', cls: 'perfect' },
11
- Normal: { emoji: '🟢', text: 'Normal', cls: 'normal' },
12
- Spiky: { emoji: '🟡', text: 'Spiky', cls: 'spiky' },
13
- Slow: { emoji: '🟠', text: 'Slow', cls: 'slow' },
14
- 'Very Slow': { emoji: '🔴', text: 'Very Slow', cls: 'veryslow' },
15
- Overloaded: { emoji: '🔥', text: 'Overloaded', cls: 'overloaded' },
16
- Unstable: { emoji: '🟥', text: 'Unstable', cls: 'unstable' },
17
- 'Not Active':{ emoji: '⚫', text: 'Not Active', cls: 'notactive' },
18
- Pending: { emoji: '⏳', text: 'Pending', cls: 'pending' },
19
- }
20
-
21
- const DEFAULT_ENTRY = { emoji: '❔', text: 'Pending', cls: 'pending' }
22
-
23
- export default function VerdictBadge({ verdict, httpCode }) {
24
- // Handle 429 rate limit from HTTP code (TUI shows 🔥 429 TRY LATER in Health column)
25
- // In Verdict column, TUI shows 'Overloaded' for 429 — keep same behavior
26
- const entry = verdict
27
- ? (VERDICT_WITH_EMOJI[verdict] || { emoji: '❔', text: verdict, cls: 'pending' })
28
- : DEFAULT_ENTRY
29
-
30
- return (
31
- <span className={`${styles.badge} ${styles[entry.cls]}`}>
32
- <span className={styles.emoji}>{entry.emoji}</span>
33
- <span className={styles.text}>{entry.text}</span>
34
- </span>
35
- )
36
- }
@@ -1,40 +0,0 @@
1
- /**
2
- * @file web/src/components/atoms/VerdictBadge.module.css
3
- * @description Styles matching TUI verdict colors (emoji + text).
4
- */
5
- .badge {
6
- display: inline-flex;
7
- align-items: center;
8
- gap: 3px;
9
- font-size: 11px;
10
- font-weight: 700;
11
- padding: 2px 7px;
12
- border-radius: 999px;
13
- white-space: nowrap;
14
- }
15
-
16
- .emoji { font-size: 12px; line-height: 1; }
17
- .text { line-height: 1; }
18
-
19
- /* ─── Color variants matching TUI theme ─── */
20
- .perfect { background: rgba(0,255,136,0.12); color: #00ff88; }
21
- .normal { background: rgba(118,185,0,0.12); color: #76b900; }
22
- .slow { background: rgba(255,170,0,0.12); color: #ffaa00; }
23
- .spiky { background: rgba(255,102,0,0.12); color: #ff6600; }
24
- .veryslow { background: rgba(255,68,68,0.12); color: #ff4444; }
25
- .overloaded { background: rgba(255,34,34,0.12); color: #ff2222; }
26
- .unstable { background: rgba(204,0,0,0.12); color: #cc0000; }
27
- .notactive { background: rgba(85,85,112,0.12); color: #555570; }
28
- .pending { background: rgba(68,68,96,0.1); color: #444460; }
29
-
30
- /* 📖 Light theme: deeper, more saturated text colors. Backgrounds stay
31
- 📖 the same since they are translucent and adapt to the white card. */
32
- :global([data-theme="light"]) .perfect { color: #008f4d; background: rgba(0,143,77,0.10); }
33
- :global([data-theme="light"]) .normal { color: #4a7a00; background: rgba(74,122,0,0.10); }
34
- :global([data-theme="light"]) .slow { color: #b86b00; background: rgba(184,107,0,0.10); }
35
- :global([data-theme="light"]) .spiky { color: #c45000; background: rgba(196,80,0,0.10); }
36
- :global([data-theme="light"]) .veryslow { color: #c8143a; background: rgba(200,20,58,0.10); }
37
- :global([data-theme="light"]) .overloaded { color: #b8101f; background: rgba(184,16,31,0.10); }
38
- :global([data-theme="light"]) .unstable { color: #990000; background: rgba(153,0,0,0.10); }
39
- :global([data-theme="light"]) .notactive { color: #5a5a72; }
40
- :global([data-theme="light"]) .pending { color: #5a5a8a; }