free-coding-models 0.5.85 → 0.5.87

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