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,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; }