free-coding-models 0.5.26 โ 0.5.28
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.
- package/README.md +14 -9
- package/changelog/v0.5.27.md +17 -0
- package/changelog/v0.5.28.md +5 -0
- package/package.json +1 -1
- package/sources.js +98 -1
- package/src/core/config.js +29 -4
- package/src/core/provider-metadata.js +72 -1
- package/src/core/quota-capabilities.js +5 -0
- package/src/core/router-daemon.js +84 -2
- package/src/core/setup.js +5 -4
- package/src/tui/app.js +21 -4
- package/src/tui/command-palette.js +2 -1
- package/src/tui/key-handler.js +157 -3
- package/src/tui/overlays.js +25 -6
- package/src/tui/render-table.js +18 -2
- package/src/tui/theme.js +10 -0
- package/src/tui/tui-filters.js +1 -1
- package/src/tui/tui-state.js +6 -0
- package/web/dist/assets/index-BRFowJv5.css +1 -0
- package/web/dist/assets/index-Pr3waI0-.js +41 -0
- package/web/dist/index.html +2 -2
- package/web/server.js +9 -0
- package/web/src/components/atoms/HealthCell.jsx +12 -7
- package/web/src/components/atoms/HealthCell.module.css +4 -0
- package/web/src/components/atoms/StatusDot.jsx +5 -2
- package/web/src/components/atoms/StatusDot.module.css +12 -7
- package/web/src/components/dashboard/ModelTable.jsx +5 -2
- package/web/src/components/dashboard/ModelTable.module.css +9 -0
- package/web/src/components/settings/SettingsView.jsx +4 -4
- package/web/dist/assets/index-BDV-Qeuc.js +0 -41
- package/web/dist/assets/index-avN2GM3H.css +0 -1
package/web/dist/index.html
CHANGED
|
@@ -48,8 +48,8 @@
|
|
|
48
48
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
49
49
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
50
50
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800;900&family=JetBrains+Mono:wght@400;500;600&display=swap" rel="stylesheet">
|
|
51
|
-
<script type="module" crossorigin src="/assets/index-
|
|
52
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
51
|
+
<script type="module" crossorigin src="/assets/index-Pr3waI0-.js"></script>
|
|
52
|
+
<link rel="stylesheet" crossorigin href="/assets/index-BRFowJv5.css">
|
|
53
53
|
</head>
|
|
54
54
|
<body>
|
|
55
55
|
<div id="root"></div>
|
package/web/server.js
CHANGED
|
@@ -35,6 +35,7 @@ const { version: LOCAL_VERSION } = require('../package.json')
|
|
|
35
35
|
|
|
36
36
|
import { sources, MODELS } from '../sources.js'
|
|
37
37
|
import { loadConfig, getApiKey, saveConfig, isProviderEnabled } from '../src/core/config.js'
|
|
38
|
+
import { getProviderBillingNote, getProviderLabelWithBilling, PROVIDER_METADATA } from '../src/core/provider-metadata.js'
|
|
38
39
|
import { ensureFavoritesConfig } from '../src/core/favorites.js'
|
|
39
40
|
import { ping } from '../src/core/ping.js'
|
|
40
41
|
import { loadChangelog } from '../src/core/changelog-loader.js'
|
|
@@ -263,6 +264,10 @@ function serializeModel(result) {
|
|
|
263
264
|
const jitter = getJitter(result)
|
|
264
265
|
const stability = getStabilityScore(result)
|
|
265
266
|
const latest = result.pings.length > 0 ? result.pings[result.pings.length - 1] : null
|
|
267
|
+
const routerConfig = config.router || {}
|
|
268
|
+
const activeSetName = routerConfig.activeSet || 'fast-coding'
|
|
269
|
+
const activeSetModels = routerConfig.sets?.[activeSetName]?.models || []
|
|
270
|
+
const inRouterSet = activeSetModels.some((m) => m.provider === result.providerKey && m.model === result.modelId)
|
|
266
271
|
|
|
267
272
|
return {
|
|
268
273
|
idx: result.idx,
|
|
@@ -289,6 +294,7 @@ function serializeModel(result) {
|
|
|
289
294
|
pingHistory: result.pings.slice(-20).map((p) => ({ ms: p.ms, code: p.code })),
|
|
290
295
|
pingCount: result.pings.length,
|
|
291
296
|
hasApiKey: !!getApiKey(config, result.providerKey),
|
|
297
|
+
inRouterSet,
|
|
292
298
|
benchmarkKey: key,
|
|
293
299
|
isBenchmarking: benchmarkRunning.has(key),
|
|
294
300
|
benchmark: benchmarkResults.get(key) || null,
|
|
@@ -318,6 +324,9 @@ function getConfigPayload() {
|
|
|
318
324
|
const rawKey = getApiKey(config, key)
|
|
319
325
|
providers[key] = {
|
|
320
326
|
name: src.name,
|
|
327
|
+
displayName: getProviderLabelWithBilling(key, src.name),
|
|
328
|
+
billingNote: getProviderBillingNote(key),
|
|
329
|
+
paidProviderNote: PROVIDER_METADATA[key]?.paidProviderNote || null,
|
|
321
330
|
hasKey: !!rawKey,
|
|
322
331
|
maskedKey: rawKey ? maskApiKey(rawKey) : null,
|
|
323
332
|
enabled: isProviderEnabled(config, key),
|
|
@@ -23,6 +23,7 @@ import {
|
|
|
23
23
|
IconChecks,
|
|
24
24
|
IconHourglass,
|
|
25
25
|
IconHourglassOff,
|
|
26
|
+
IconCircleDotted,
|
|
26
27
|
IconLock,
|
|
27
28
|
IconFlame,
|
|
28
29
|
IconError404,
|
|
@@ -69,10 +70,10 @@ const ERROR_ICON = {
|
|
|
69
70
|
'504': { Icon: IconClock, color: HEALTH_ICON_COLOR.danger },
|
|
70
71
|
}
|
|
71
72
|
|
|
72
|
-
function statusLabel(status, httpCode) {
|
|
73
|
+
function statusLabel(status, httpCode, inRouterSet) {
|
|
73
74
|
if (status === 'noauth') return 'NO KEY'
|
|
74
75
|
if (status === 'auth_error') return 'AUTH FAIL'
|
|
75
|
-
if (status === 'pending') return 'wait'
|
|
76
|
+
if (status === 'pending') return inRouterSet ? 'wait' : 'NOT IN SET'
|
|
76
77
|
if (status === 'timeout') return 'TIMEOUT'
|
|
77
78
|
if (status === 'down') {
|
|
78
79
|
return ERROR_LABELS[httpCode] || (httpCode || 'ERROR')
|
|
@@ -81,20 +82,21 @@ function statusLabel(status, httpCode) {
|
|
|
81
82
|
return '?'
|
|
82
83
|
}
|
|
83
84
|
|
|
84
|
-
function statusClass(status) {
|
|
85
|
+
function statusClass(status, inRouterSet) {
|
|
85
86
|
if (status === 'noauth') return styles.noKey
|
|
86
87
|
if (status === 'up') return styles.up
|
|
87
88
|
if (status === 'down') return styles.error
|
|
88
89
|
if (status === 'timeout') return styles.warning
|
|
89
90
|
if (status === 'auth_error') return styles.errorBold
|
|
90
|
-
if (status === 'pending') return styles.warning
|
|
91
|
+
if (status === 'pending') return inRouterSet ? styles.warning : styles.notInSet
|
|
91
92
|
return styles.dim
|
|
92
93
|
}
|
|
93
94
|
|
|
94
|
-
export default function HealthCell({ status, httpCode }) {
|
|
95
|
-
const text = statusLabel(status, httpCode)
|
|
96
|
-
const cls = statusClass(status)
|
|
95
|
+
export default function HealthCell({ status, httpCode, inRouterSet = true }) {
|
|
96
|
+
const text = statusLabel(status, httpCode, inRouterSet)
|
|
97
|
+
const cls = statusClass(status, inRouterSet)
|
|
97
98
|
const isNoKey = status === 'noauth'
|
|
99
|
+
const isNotInSet = status === 'pending' && !inRouterSet
|
|
98
100
|
|
|
99
101
|
// ๐ Pick the right icon + color combo for this state.
|
|
100
102
|
let Icon = null
|
|
@@ -105,6 +107,9 @@ export default function HealthCell({ status, httpCode }) {
|
|
|
105
107
|
} else if (status === 'timeout') {
|
|
106
108
|
Icon = IconHourglassOff
|
|
107
109
|
iconColor = HEALTH_ICON_COLOR.warning
|
|
110
|
+
} else if (isNotInSet) {
|
|
111
|
+
Icon = IconCircleDotted
|
|
112
|
+
iconColor = HEALTH_ICON_COLOR.dim
|
|
108
113
|
} else if (status === 'pending') {
|
|
109
114
|
Icon = IconHourglass
|
|
110
115
|
iconColor = HEALTH_ICON_COLOR.warning
|
|
@@ -21,6 +21,9 @@
|
|
|
21
21
|
.error { color: #ff4444; }
|
|
22
22
|
.errorBold { color: #ff4444; font-weight: 800; }
|
|
23
23
|
.dim { color: var(--color-text-dim); }
|
|
24
|
+
/* ๐ Models not in the active router set โ muted text so they stand out from
|
|
25
|
+
๐ genuinely 'pending' models that are still waiting for their first probe. */
|
|
26
|
+
.notInSet { color: #4a4a5a; opacity: 0.7; }
|
|
24
27
|
|
|
25
28
|
/* ๐ The "NO KEY" treatment is intentionally louder than the other states:
|
|
26
29
|
๐ - yellow chip background instead of plain text
|
|
@@ -54,3 +57,4 @@
|
|
|
54
57
|
:global([data-theme="light"]) .error,
|
|
55
58
|
:global([data-theme="light"]) .errorBold { color: #c02323; }
|
|
56
59
|
:global([data-theme="light"]) .dim { color: #888; }
|
|
60
|
+
:global([data-theme="light"]) .notInSet { color: #a0a0a8; }
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @file web/src/components/atoms/StatusDot.jsx
|
|
3
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*.
|
|
4
7
|
*/
|
|
5
8
|
import styles from './StatusDot.module.css'
|
|
6
9
|
|
|
7
|
-
export default function StatusDot({ status }) {
|
|
8
|
-
const cls = status === 'up' ? styles.up : status === 'timeout' ? styles.timeout : status === 'down' ? styles.down : styles.pending
|
|
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
|
|
9
12
|
return <span className={`${styles.dot} ${cls}`} />
|
|
10
13
|
}
|
|
@@ -6,10 +6,14 @@
|
|
|
6
6
|
margin-right: 6px;
|
|
7
7
|
vertical-align: middle;
|
|
8
8
|
}
|
|
9
|
-
.up
|
|
10
|
-
.down
|
|
11
|
-
.timeout
|
|
12
|
-
.pending
|
|
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; }
|
|
13
17
|
|
|
14
18
|
@keyframes pulse {
|
|
15
19
|
0%, 100% { box-shadow: 0 0 0 0 rgba(118,185,0,0.25); }
|
|
@@ -18,7 +22,8 @@
|
|
|
18
22
|
|
|
19
23
|
/* ๐ Light theme: deep green/red for the dot so it still reads as "up"/"down"
|
|
20
24
|
๐ on a white card; the glow halo is dimmed so it doesn't blow out. */
|
|
21
|
-
:global([data-theme="light"]) .up
|
|
25
|
+
:global([data-theme="light"]) .up { background: #008f4d; box-shadow: 0 0 4px rgba(0,143,77,0.35); }
|
|
22
26
|
:global([data-theme="light"]) .down,
|
|
23
|
-
:global([data-theme="light"]) .timeout
|
|
24
|
-
:global([data-theme="light"]) .pending
|
|
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; }
|
|
@@ -67,7 +67,7 @@ function ModelCellRenderer({ row }) {
|
|
|
67
67
|
const showNoKey = !m.hasApiKey && !m.cliOnly
|
|
68
68
|
return (
|
|
69
69
|
<div className={styles.modelCell}>
|
|
70
|
-
<StatusDot status={m.status} />
|
|
70
|
+
<StatusDot status={m.status} inRouterSet={m.inRouterSet} />
|
|
71
71
|
<div className={styles.modelMeta}>
|
|
72
72
|
<div className={styles.modelHeader}>
|
|
73
73
|
<span className={styles.modelName}>{m.label}</span>
|
|
@@ -120,7 +120,7 @@ function AvgPingCellRenderer({ row }) {
|
|
|
120
120
|
|
|
121
121
|
function HealthCellRenderer({ row }) {
|
|
122
122
|
const m = row.original
|
|
123
|
-
return <HealthCell status={m.status} httpCode={m.httpCode} />
|
|
123
|
+
return <HealthCell status={m.status} httpCode={m.httpCode} inRouterSet={m.inRouterSet} />
|
|
124
124
|
}
|
|
125
125
|
|
|
126
126
|
function VerdictCellRenderer({ row }) {
|
|
@@ -526,6 +526,9 @@ export default function ModelTable({
|
|
|
526
526
|
if (m.status === 'noauth' || m.status === 'auth_error') {
|
|
527
527
|
rowClasses.push(styles.unusable)
|
|
528
528
|
}
|
|
529
|
+
if (m.status === 'pending' && m.inRouterSet === false) {
|
|
530
|
+
rowClasses.push(styles.notInSetRow)
|
|
531
|
+
}
|
|
529
532
|
return (
|
|
530
533
|
<Fragment key={row.id}>
|
|
531
534
|
<tr
|
|
@@ -345,6 +345,15 @@
|
|
|
345
345
|
opacity: 0.8;
|
|
346
346
|
}
|
|
347
347
|
|
|
348
|
+
/* ๐ Models not in the active router set โ slightly faded so users can
|
|
349
|
+
๐ instantly see which models are being tested vs just cataloged. */
|
|
350
|
+
.notInSetRow {
|
|
351
|
+
opacity: 0.6;
|
|
352
|
+
}
|
|
353
|
+
.notInSetRow:hover {
|
|
354
|
+
opacity: 0.85;
|
|
355
|
+
}
|
|
356
|
+
|
|
348
357
|
/* โโโ M3: per-row endpoint install plug โโโ */
|
|
349
358
|
.launchCell {
|
|
350
359
|
display: flex;
|
|
@@ -267,7 +267,7 @@ export default function SettingsView({ onToast, onOpenChangelog, onCheckForUpdat
|
|
|
267
267
|
.filter(([, p]) => {
|
|
268
268
|
if (!searchQuery) return true
|
|
269
269
|
const q = searchQuery.toLowerCase()
|
|
270
|
-
return p.name.toLowerCase().includes(q)
|
|
270
|
+
return `${p.name} ${p.displayName || ''} ${p.billingNote || ''}`.toLowerCase().includes(q)
|
|
271
271
|
})
|
|
272
272
|
.sort((a, b) => a[1].name.localeCompare(b[1].name))
|
|
273
273
|
|
|
@@ -451,8 +451,8 @@ export default function SettingsView({ onToast, onOpenChangelog, onCheckForUpdat
|
|
|
451
451
|
<IconPlug size={20} stroke={1.5} />
|
|
452
452
|
</div>
|
|
453
453
|
<div className={styles.cardInfo}>
|
|
454
|
-
<div className={styles.cardName}>{p.name}</div>
|
|
455
|
-
<div className={styles.cardMeta}>{p.modelCount} models ยท {key}</div>
|
|
454
|
+
<div className={styles.cardName}>{p.displayName || p.name}</div>
|
|
455
|
+
<div className={styles.cardMeta}>{p.modelCount} models ยท {key}{p.billingNote ? ` ยท ${p.billingNote}` : ''}</div>
|
|
456
456
|
</div>
|
|
457
457
|
<span className={`${styles.cardStatus} ${p.hasKey ? styles.statusConfigured : styles.statusMissing}`}>
|
|
458
458
|
{p.hasKey ? (
|
|
@@ -517,7 +517,7 @@ export default function SettingsView({ onToast, onOpenChangelog, onCheckForUpdat
|
|
|
517
517
|
)}
|
|
518
518
|
|
|
519
519
|
<div className={styles.keyGroup}>
|
|
520
|
-
<label className={styles.keyLabel}>{p.hasKey ? 'Update API Key' : 'Add API Key'}</label>
|
|
520
|
+
<label className={styles.keyLabel}>{p.hasKey ? 'Update API Key' : 'Add API Key'}{p.billingNote ? ` ๐ฐ ${p.billingNote}` : ''}</label>
|
|
521
521
|
<div className={styles.keyInputRow}>
|
|
522
522
|
<input
|
|
523
523
|
type="password"
|