free-coding-models 0.5.49 → 0.5.50
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 +29 -3
- package/changelog/v0.5.48.md +25 -0
- package/changelog/v0.5.50.md +21 -0
- package/package.json +1 -1
- package/sources.js +33 -44
- package/src/core/ping.js +3 -0
- package/src/core/quota-capabilities.js +14 -12
- package/src/tui/app.js +2 -0
- package/src/tui/render-table.js +41 -13
- package/web/dist/assets/index-BkK1gdJN.css +1 -0
- package/web/dist/assets/index-DBlJl2Hs.js +40 -0
- package/web/dist/index.html +2 -2
- package/web/server.js +15 -1
- package/web/src/components/dashboard/ModelTable.jsx +21 -1
- package/web/src/components/dashboard/ModelTable.module.css +11 -0
- package/web/src/hooks/useFilter.js +2 -0
- package/web/dist/assets/index-Cv5m3BUX.js +0 -40
- package/web/dist/assets/index-r8niexgJ.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-DBlJl2Hs.js"></script>
|
|
52
|
+
<link rel="stylesheet" crossorigin href="/assets/index-BkK1gdJN.css">
|
|
53
53
|
</head>
|
|
54
54
|
<body>
|
|
55
55
|
<div id="root"></div>
|
package/web/server.js
CHANGED
|
@@ -37,7 +37,7 @@ import { sources, MODELS } from '../sources.js'
|
|
|
37
37
|
import { loadConfig, getApiKey, saveConfig, isProviderEnabled } from '../src/core/config.js'
|
|
38
38
|
import { getProviderBillingNote, getProviderLabelWithBilling, PROVIDER_METADATA } from '../src/core/provider-metadata.js'
|
|
39
39
|
import { ensureFavoritesConfig } from '../src/core/favorites.js'
|
|
40
|
-
import { ping } from '../src/core/ping.js'
|
|
40
|
+
import { ping, getProviderQuotaPercentCached } from '../src/core/ping.js'
|
|
41
41
|
import { runProviderKeyTest } from '../src/core/provider-key-tester.js'
|
|
42
42
|
import { loadChangelog } from '../src/core/changelog-loader.js'
|
|
43
43
|
import { checkForUpdateDetailed, checkForUpdate, runUpdate, fetchLastReleaseDate } from '../src/core/updater.js'
|
|
@@ -231,6 +231,19 @@ async function pingModel(result) {
|
|
|
231
231
|
const apiKey = getApiKey(config, result.providerKey) ?? null
|
|
232
232
|
try {
|
|
233
233
|
const pingResult = await ping(apiKey, result.modelId, result.providerKey, result.url)
|
|
234
|
+
let quotaPercent = pingResult.quotaPercent
|
|
235
|
+
if ((quotaPercent === null || quotaPercent === undefined) && apiKey) {
|
|
236
|
+
const providerQuota = await getProviderQuotaPercentCached(result.providerKey, apiKey)
|
|
237
|
+
if (typeof providerQuota === 'number' && Number.isFinite(providerQuota)) quotaPercent = providerQuota
|
|
238
|
+
}
|
|
239
|
+
if (typeof quotaPercent === 'number' && Number.isFinite(quotaPercent)) {
|
|
240
|
+
result.usagePercent = quotaPercent
|
|
241
|
+
for (const sibling of results) {
|
|
242
|
+
if (sibling.providerKey === result.providerKey && (sibling.usagePercent === undefined || sibling.usagePercent === null)) {
|
|
243
|
+
sibling.usagePercent = quotaPercent
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
234
247
|
updateHealthFromPing(result, pingResult, !!apiKey)
|
|
235
248
|
} catch (err) {
|
|
236
249
|
updateHealthFromPing(result, { code: '000', ms: null, error: err?.message || 'Ping failed' }, !!apiKey)
|
|
@@ -295,6 +308,7 @@ function serializeModel(result) {
|
|
|
295
308
|
pingHistory: result.pings.slice(-20).map((p) => ({ ms: p.ms, code: p.code })),
|
|
296
309
|
pingCount: result.pings.length,
|
|
297
310
|
hasApiKey: !!getApiKey(config, result.providerKey),
|
|
311
|
+
usagePercent: typeof result.usagePercent === 'number' && Number.isFinite(result.usagePercent) ? result.usagePercent : null,
|
|
298
312
|
inRouterSet,
|
|
299
313
|
benchmarkKey: key,
|
|
300
314
|
isBenchmarking: benchmarkRunning.has(key),
|
|
@@ -44,6 +44,7 @@ import { sweClass } from '../../utils/ranks.js'
|
|
|
44
44
|
// 📖 file the TUI uses; both surfaces share the source of truth for which
|
|
45
45
|
// 📖 providers are compatible with which tools.
|
|
46
46
|
import { getCompatibleTools } from '../../../../src/core/tool-metadata.js'
|
|
47
|
+
import { supportsUsagePercent } from '../../../../src/core/quota-capabilities.js'
|
|
47
48
|
import ExpandedDetailRow from './ExpandedDetailRow.jsx'
|
|
48
49
|
import styles from './ModelTable.module.css'
|
|
49
50
|
|
|
@@ -51,7 +52,7 @@ const colHelper = createColumnHelper()
|
|
|
51
52
|
|
|
52
53
|
const SORTABLE_COLUMN_IDS = new Set([
|
|
53
54
|
'mood', 'idx', 'tier', 'sweScore', 'ctx', 'label', 'origin', 'latestPing',
|
|
54
|
-
'avg', 'condition', 'verdict', 'stability', 'uptime', 'aiLatency', 'tps', 'trend',
|
|
55
|
+
'avg', 'condition', 'verdict', 'stability', 'uptime', 'aiLatency', 'tps', 'quota', 'trend',
|
|
55
56
|
])
|
|
56
57
|
|
|
57
58
|
// ─── Cell renderers ───────────────────────────────────────────────────────────
|
|
@@ -167,6 +168,18 @@ function TPSCellRenderer({ row }) {
|
|
|
167
168
|
return <TPSCell result={m.benchmark || null} isRunning={Boolean(m.isBenchmarking)} />
|
|
168
169
|
}
|
|
169
170
|
|
|
171
|
+
function QuotaCellRenderer({ row }) {
|
|
172
|
+
const m = row.original
|
|
173
|
+
if (!m.hasApiKey) return <span className={`${styles.quota} ${styles.quotaNone}`}>-</span>
|
|
174
|
+
if (!supportsUsagePercent(m.providerKey)) return <span className={`${styles.quota} ${styles.quotaNA}`}>N/A</span>
|
|
175
|
+
if (typeof m.usagePercent !== 'number' || !Number.isFinite(m.usagePercent)) {
|
|
176
|
+
return <span className={`${styles.quota} ${styles.quotaNone}`}>…</span>
|
|
177
|
+
}
|
|
178
|
+
const pct = Math.round(m.usagePercent)
|
|
179
|
+
const cls = pct >= 50 ? styles.quotaGood : pct >= 20 ? styles.quotaWarn : styles.quotaBad
|
|
180
|
+
return <span className={`${styles.quota} ${cls}`}>{pct}%</span>
|
|
181
|
+
}
|
|
182
|
+
|
|
170
183
|
function TrendCellRenderer({ row }) {
|
|
171
184
|
return <Sparkline history={row.original.pingHistory} />
|
|
172
185
|
}
|
|
@@ -301,6 +314,13 @@ const buildColumns = ({ favorites, onBenchmarkRow, onSelectModel, onLaunch, tool
|
|
|
301
314
|
cell: TPSCellRenderer,
|
|
302
315
|
enableSorting: true,
|
|
303
316
|
}),
|
|
317
|
+
colHelper.display({
|
|
318
|
+
id: 'quota',
|
|
319
|
+
header: 'Quota',
|
|
320
|
+
size: 64,
|
|
321
|
+
cell: QuotaCellRenderer,
|
|
322
|
+
enableSorting: true,
|
|
323
|
+
}),
|
|
304
324
|
colHelper.display({
|
|
305
325
|
id: 'trend',
|
|
306
326
|
header: 'Trend',
|
|
@@ -259,6 +259,17 @@
|
|
|
259
259
|
|
|
260
260
|
.uptime { font-family: var(--font-mono); font-size: 13px; }
|
|
261
261
|
|
|
262
|
+
.quota {
|
|
263
|
+
font-family: var(--font-mono);
|
|
264
|
+
font-size: 13px;
|
|
265
|
+
font-weight: 800;
|
|
266
|
+
}
|
|
267
|
+
.quotaGood { color: #00ff88; }
|
|
268
|
+
.quotaWarn { color: #ffaa00; }
|
|
269
|
+
.quotaBad { color: #ff4444; }
|
|
270
|
+
.quotaNA,
|
|
271
|
+
.quotaNone { color: var(--color-text-dim); }
|
|
272
|
+
|
|
262
273
|
/* ─── Model cell ─── */
|
|
263
274
|
.modelCell { display: flex; align-items: center; gap: 6px; overflow: hidden; }
|
|
264
275
|
.modelMeta { flex: 1; min-width: 0; }
|
|
@@ -232,6 +232,8 @@ export function useFilter(models) {
|
|
|
232
232
|
cmp = compareNullableNumber(a.benchmark?.ok ? a.benchmark.totalMs : null, b.benchmark?.ok ? b.benchmark.totalMs : null, direction)
|
|
233
233
|
} else if (sortColumn === 'tps') {
|
|
234
234
|
cmp = compareNullableNumber(a.benchmark?.ok ? a.benchmark.tokensPerSecond ?? 0 : null, b.benchmark?.ok ? b.benchmark.tokensPerSecond ?? 0 : null, direction)
|
|
235
|
+
} else if (sortColumn === 'quota') {
|
|
236
|
+
cmp = compareNullableNumber(numericOrNull(a.usagePercent), numericOrNull(b.usagePercent), direction)
|
|
235
237
|
} else if (sortColumn === 'trend') {
|
|
236
238
|
cmp = compareNullableNumber(trendDelta(a), trendDelta(b), direction)
|
|
237
239
|
}
|