free-coding-models 0.5.15 → 0.5.16
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/changelog/v0.5.16.md +15 -0
- package/package.json +1 -1
- package/web/dist/assets/index-avN2GM3H.css +1 -0
- package/web/dist/assets/index-vsysCImz.js +41 -0
- package/web/dist/index.html +6 -2
- package/web/index.html +4 -0
- package/web/server.js +166 -1
- package/web/src/App.jsx +31 -14
- package/web/src/components/dashboard/ExpandedDetailRow.jsx +464 -0
- package/web/src/components/dashboard/ExpandedDetailRow.module.css +347 -0
- package/web/src/components/dashboard/ModelTable.jsx +49 -29
- package/web/src/components/dashboard/ModelTable.module.css +12 -0
- package/web/dist/assets/index-BRqzsHVw.css +0 -1
- package/web/dist/assets/index-CUYQh5_t.js +0 -39
package/web/dist/index.html
CHANGED
|
@@ -48,10 +48,14 @@
|
|
|
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-vsysCImz.js"></script>
|
|
52
|
+
<link rel="stylesheet" crossorigin href="/assets/index-avN2GM3H.css">
|
|
53
53
|
</head>
|
|
54
54
|
<body>
|
|
55
55
|
<div id="root"></div>
|
|
56
|
+
<!-- 📖 PostHog analytics — privacy-first, self-hosted option available -->
|
|
57
|
+
<script>
|
|
58
|
+
!function(t,e){var o,n,p,r;e.__SV||(window.posthog=e,e._i=[],e.init=function(i,s,a){function g(t,e){var o=e.split(".");2==o.length&&(t=t[o[0]],e=o[1]),t[e]=function(){t.push([e].concat(Array.prototype.slice.call(arguments,0)))}}(p=t.createElement("script")).type="text/javascript",p.crossOrigin="anonymous",p.async=!0,p.src=s.api_host.replace(".i.",".js."),function(t,e,o,n){var i,s;for(n=function(t,e){return t[e]},s=e.split("."),i=0;i<s.length-1;i++)n=n(s[i],t);(a=n[s.pop()])&&a.apply(t,[].slice.call(arguments,0))}(e,"5.0.0")},e.init('phc_PLACEHOLDER_REPLACE_WITH_YOUR_KEY',{api_host:'https://us.i.posthog.com'}))}(document,window.posthog||[]);
|
|
59
|
+
</script>
|
|
56
60
|
</body>
|
|
57
61
|
</html>
|
package/web/index.html
CHANGED
|
@@ -52,5 +52,9 @@
|
|
|
52
52
|
<body>
|
|
53
53
|
<div id="root"></div>
|
|
54
54
|
<script type="module" src="/src/main.jsx"></script>
|
|
55
|
+
<!-- 📖 PostHog analytics — privacy-first, self-hosted option available -->
|
|
56
|
+
<script>
|
|
57
|
+
!function(t,e){var o,n,p,r;e.__SV||(window.posthog=e,e._i=[],e.init=function(i,s,a){function g(t,e){var o=e.split(".");2==o.length&&(t=t[o[0]],e=o[1]),t[e]=function(){t.push([e].concat(Array.prototype.slice.call(arguments,0)))}}(p=t.createElement("script")).type="text/javascript",p.crossOrigin="anonymous",p.async=!0,p.src=s.api_host.replace(".i.",".js."),function(t,e,o,n){var i,s;for(n=function(t,e){return t[e]},s=e.split("."),i=0;i<s.length-1;i++)n=n(s[i],t);(a=n[s.pop()])&&a.apply(t,[].slice.call(arguments,0))}(e,"5.0.0")},e.init('phc_PLACEHOLDER_REPLACE_WITH_YOUR_KEY',{api_host:'https://us.i.posthog.com'}))}(document,window.posthog||[]);
|
|
58
|
+
</script>
|
|
55
59
|
</body>
|
|
56
60
|
</html>
|
package/web/server.js
CHANGED
|
@@ -45,7 +45,7 @@ import {
|
|
|
45
45
|
getAvg, getVerdict, getUptime, getP95, getJitter,
|
|
46
46
|
getStabilityScore,
|
|
47
47
|
} from '../src/core/utils.js'
|
|
48
|
-
import { benchmarkModel, BENCHMARK_TIMEOUT_MS } from '../src/core/benchmark.js'
|
|
48
|
+
import { benchmarkModel, BENCHMARK_TIMEOUT_MS, BENCHMARK_PROMPT } from '../src/core/benchmark.js'
|
|
49
49
|
import { getInstallTargetModes, installProviderEndpoints, getConfiguredInstallableProviders, getProviderCatalogModels } from '../src/core/endpoint-installer.js'
|
|
50
50
|
import { isModelCompatibleWithTool } from '../src/core/tool-metadata.js'
|
|
51
51
|
import { sendUsageTelemetry } from '../src/core/telemetry.js'
|
|
@@ -1030,6 +1030,171 @@ async function handleRequest(req, res) {
|
|
|
1030
1030
|
return
|
|
1031
1031
|
}
|
|
1032
1032
|
|
|
1033
|
+
case '/api/benchmark-stream': {
|
|
1034
|
+
if (req.method !== 'POST') {
|
|
1035
|
+
res.writeHead(405)
|
|
1036
|
+
res.end('Method Not Allowed')
|
|
1037
|
+
return
|
|
1038
|
+
}
|
|
1039
|
+
|
|
1040
|
+
let body
|
|
1041
|
+
try {
|
|
1042
|
+
body = await readJsonBody(req)
|
|
1043
|
+
} catch {
|
|
1044
|
+
res.writeHead(400)
|
|
1045
|
+
res.end('Invalid JSON')
|
|
1046
|
+
return
|
|
1047
|
+
}
|
|
1048
|
+
|
|
1049
|
+
const result = getResult(body.providerKey, body.modelId)
|
|
1050
|
+
if (!result) {
|
|
1051
|
+
sendJson(res, 404, { error: 'Model not found' })
|
|
1052
|
+
return
|
|
1053
|
+
}
|
|
1054
|
+
|
|
1055
|
+
const source = sources[result.providerKey]
|
|
1056
|
+
const apiKey = getApiKey(config, result.providerKey)
|
|
1057
|
+
if (!apiKey) {
|
|
1058
|
+
sendJson(res, 401, { error: 'No API key configured' })
|
|
1059
|
+
return
|
|
1060
|
+
}
|
|
1061
|
+
|
|
1062
|
+
const key = getResultKey(result)
|
|
1063
|
+
benchmarkRunning.add(key)
|
|
1064
|
+
broadcastUpdate({ immediate: true })
|
|
1065
|
+
|
|
1066
|
+
// 📖 Set SSE headers for streaming response to the browser
|
|
1067
|
+
res.writeHead(200, {
|
|
1068
|
+
'Content-Type': 'text/event-stream',
|
|
1069
|
+
'Cache-Control': 'no-cache',
|
|
1070
|
+
'Connection': 'keep-alive',
|
|
1071
|
+
'X-Accel-Buffering': 'no',
|
|
1072
|
+
})
|
|
1073
|
+
|
|
1074
|
+
try {
|
|
1075
|
+
// Build the upstream URL — append /v1/chat/completions if needed
|
|
1076
|
+
let upstreamUrl = source?.url || result.url
|
|
1077
|
+
if (!upstreamUrl.includes('/chat/completions')) {
|
|
1078
|
+
upstreamUrl = upstreamUrl.replace(/\/+$/, '') + '/v1/chat/completions'
|
|
1079
|
+
}
|
|
1080
|
+
|
|
1081
|
+
// 📖 ZAI provider: strip the "zai/" prefix from modelId for the API
|
|
1082
|
+
let apiModelId = result.modelId
|
|
1083
|
+
if (result.providerKey === 'zai' && apiModelId.startsWith('zai/')) {
|
|
1084
|
+
apiModelId = apiModelId.slice(4)
|
|
1085
|
+
}
|
|
1086
|
+
|
|
1087
|
+
const upstreamHeaders = {
|
|
1088
|
+
'Content-Type': 'application/json',
|
|
1089
|
+
'Authorization': `Bearer ${apiKey}`,
|
|
1090
|
+
}
|
|
1091
|
+
|
|
1092
|
+
// 📖 OpenRouter requires HTTP-Referer and X-Title headers
|
|
1093
|
+
if (result.providerKey === 'openrouter') {
|
|
1094
|
+
upstreamHeaders['HTTP-Referer'] = 'https://github.com/vava-nessa/free-coding-models'
|
|
1095
|
+
upstreamHeaders['X-Title'] = 'free-coding-models'
|
|
1096
|
+
}
|
|
1097
|
+
|
|
1098
|
+
const reqBody = {
|
|
1099
|
+
model: apiModelId,
|
|
1100
|
+
messages: [{ role: 'user', content: BENCHMARK_PROMPT }],
|
|
1101
|
+
max_tokens: 140,
|
|
1102
|
+
temperature: 0,
|
|
1103
|
+
stream: true,
|
|
1104
|
+
}
|
|
1105
|
+
|
|
1106
|
+
const resp = await fetch(upstreamUrl, {
|
|
1107
|
+
method: 'POST',
|
|
1108
|
+
headers: upstreamHeaders,
|
|
1109
|
+
body: JSON.stringify(reqBody),
|
|
1110
|
+
signal: AbortSignal.timeout(20000),
|
|
1111
|
+
})
|
|
1112
|
+
|
|
1113
|
+
if (!resp.ok) {
|
|
1114
|
+
let message = `HTTP ${resp.status}`
|
|
1115
|
+
try {
|
|
1116
|
+
const errJson = await resp.json()
|
|
1117
|
+
message = errJson?.error?.message || errJson?.error || errJson?.message || message
|
|
1118
|
+
} catch { /* keep default */ }
|
|
1119
|
+
res.write('event: error\ndata: ' + JSON.stringify({ error: message }) + '\n\n')
|
|
1120
|
+
res.end()
|
|
1121
|
+
benchmarkRunning.delete(key)
|
|
1122
|
+
broadcastUpdate({ immediate: true })
|
|
1123
|
+
return
|
|
1124
|
+
}
|
|
1125
|
+
|
|
1126
|
+
// 📖 Parse SSE stream from the provider — extract tokens and measure TPS
|
|
1127
|
+
const reader = resp.body.getReader()
|
|
1128
|
+
const decoder = new TextDecoder()
|
|
1129
|
+
const t0 = performance.now()
|
|
1130
|
+
let tokenCount = 0
|
|
1131
|
+
let fullText = ''
|
|
1132
|
+
let buffer = ''
|
|
1133
|
+
|
|
1134
|
+
while (true) {
|
|
1135
|
+
const { done, value } = await reader.read()
|
|
1136
|
+
if (done) break
|
|
1137
|
+
|
|
1138
|
+
buffer += decoder.decode(value, { stream: true })
|
|
1139
|
+
const lines = buffer.split('\n')
|
|
1140
|
+
buffer = lines.pop() || '' // keep incomplete line in buffer
|
|
1141
|
+
|
|
1142
|
+
for (const line of lines) {
|
|
1143
|
+
const trimmed = line.trim()
|
|
1144
|
+
if (!trimmed.startsWith('data: ')) continue
|
|
1145
|
+
const payload = trimmed.slice(6)
|
|
1146
|
+
if (payload === '[DONE]') continue
|
|
1147
|
+
|
|
1148
|
+
let parsed
|
|
1149
|
+
try { parsed = JSON.parse(payload) } catch { continue }
|
|
1150
|
+
|
|
1151
|
+
const delta = parsed?.choices?.[0]?.delta?.content
|
|
1152
|
+
if (!delta) continue
|
|
1153
|
+
|
|
1154
|
+
fullText += delta
|
|
1155
|
+
tokenCount++
|
|
1156
|
+
res.write('event: token\ndata: ' + JSON.stringify({
|
|
1157
|
+
token: delta,
|
|
1158
|
+
totalMs: Math.round(performance.now() - t0),
|
|
1159
|
+
tokens: tokenCount,
|
|
1160
|
+
tps: Math.round(tokenCount / ((performance.now() - t0) / 1000) * 10) / 10,
|
|
1161
|
+
text: fullText,
|
|
1162
|
+
}) + '\n\n')
|
|
1163
|
+
}
|
|
1164
|
+
}
|
|
1165
|
+
|
|
1166
|
+
const totalMs = Math.round(performance.now() - t0)
|
|
1167
|
+
const tps = tokenCount / (totalMs / 1000)
|
|
1168
|
+
|
|
1169
|
+
res.write('event: done\ndata: ' + JSON.stringify({
|
|
1170
|
+
totalMs,
|
|
1171
|
+
outputTokens: tokenCount,
|
|
1172
|
+
tokensPerSecond: Math.round(tps * 10) / 10,
|
|
1173
|
+
answerPreview: fullText.slice(0, 100),
|
|
1174
|
+
}) + '\n\n')
|
|
1175
|
+
res.end()
|
|
1176
|
+
|
|
1177
|
+
// 📖 Update shared benchmark state so the dashboard reflects the result
|
|
1178
|
+
const benchmarkResult = {
|
|
1179
|
+
ok: true,
|
|
1180
|
+
totalMs,
|
|
1181
|
+
outputTokens: tokenCount,
|
|
1182
|
+
tokensPerSecond: tps,
|
|
1183
|
+
answerPreview: fullText.slice(0, 60),
|
|
1184
|
+
}
|
|
1185
|
+
benchmarkResults.set(key, benchmarkResult)
|
|
1186
|
+
benchmarkRunning.delete(key)
|
|
1187
|
+
updateHealthFromBenchmark(result, benchmarkResult)
|
|
1188
|
+
broadcastUpdate({ immediate: true })
|
|
1189
|
+
} catch (err) {
|
|
1190
|
+
res.write('event: error\ndata: ' + JSON.stringify({ error: err?.message || 'Stream failed' }) + '\n\n')
|
|
1191
|
+
res.end()
|
|
1192
|
+
benchmarkRunning.delete(key)
|
|
1193
|
+
broadcastUpdate({ immediate: true })
|
|
1194
|
+
}
|
|
1195
|
+
return
|
|
1196
|
+
}
|
|
1197
|
+
|
|
1033
1198
|
case '/api/global-benchmark': {
|
|
1034
1199
|
if (req.method === 'GET') {
|
|
1035
1200
|
sendJson(res, 200, {
|
package/web/src/App.jsx
CHANGED
|
@@ -77,6 +77,31 @@ export default function App() {
|
|
|
77
77
|
const [toasts, setToasts] = useState([])
|
|
78
78
|
const lastActivityRef = useRef(Date.now())
|
|
79
79
|
|
|
80
|
+
// 📖 PostHog: track app_web_start on mount
|
|
81
|
+
useEffect(() => {
|
|
82
|
+
try {
|
|
83
|
+
if (typeof window !== 'undefined' && window.posthog?.capture) {
|
|
84
|
+
window.posthog.capture('app_web_start', {
|
|
85
|
+
version: __APP_VERSION__ || 'unknown',
|
|
86
|
+
timestamp: new Date().toISOString(),
|
|
87
|
+
})
|
|
88
|
+
}
|
|
89
|
+
} catch {}
|
|
90
|
+
}, [])
|
|
91
|
+
|
|
92
|
+
// 📖 PostHog: track app_router_start when router opens
|
|
93
|
+
const handleRouterOpen = useCallback(() => {
|
|
94
|
+
setRouterOpen(true)
|
|
95
|
+
try {
|
|
96
|
+
if (typeof window !== 'undefined' && window.posthog?.capture) {
|
|
97
|
+
window.posthog.capture('app_router_start', {
|
|
98
|
+
version: __APP_VERSION__ || 'unknown',
|
|
99
|
+
timestamp: new Date().toISOString(),
|
|
100
|
+
})
|
|
101
|
+
}
|
|
102
|
+
} catch {}
|
|
103
|
+
}, [])
|
|
104
|
+
|
|
80
105
|
// ── Toast helpers ────────────────────────────────────────────────────────
|
|
81
106
|
const addToast = useCallback((message, type = 'info') => {
|
|
82
107
|
const id = ++toastIdCounter
|
|
@@ -248,7 +273,7 @@ export default function App() {
|
|
|
248
273
|
if (viewId === 'help') { setHelpOpen(true); return }
|
|
249
274
|
if (viewId === 'changelog') { setChangelogOpen(true); setChangelogDefaultVersion(null); return }
|
|
250
275
|
if (viewId === 'recommend') { setRecommendOpen(true); return }
|
|
251
|
-
if (viewId === 'router') {
|
|
276
|
+
if (viewId === 'router') { handleRouterOpen(); return }
|
|
252
277
|
if (viewId === 'playground') { setPlaygroundOpen(true); return }
|
|
253
278
|
if (viewId === 'install-endpoints') { setInstallEndpointsOpen(true); return }
|
|
254
279
|
if (viewId === 'installed-models') { setInstalledModelsOpen(true); return }
|
|
@@ -379,6 +404,10 @@ export default function App() {
|
|
|
379
404
|
sortDirection={sortDirection}
|
|
380
405
|
onSort={toggleSort}
|
|
381
406
|
toolMode={toolMode}
|
|
407
|
+
onToast={addToast}
|
|
408
|
+
onSetToolMode={setToolMode}
|
|
409
|
+
onCycleToolMode={cycleToolMode}
|
|
410
|
+
onOpenFallback={(model) => setIncompatibleRequest({ model, toolMode })}
|
|
382
411
|
/>
|
|
383
412
|
</main>
|
|
384
413
|
)}
|
|
@@ -403,19 +432,7 @@ export default function App() {
|
|
|
403
432
|
</div>
|
|
404
433
|
</div>
|
|
405
434
|
|
|
406
|
-
|
|
407
|
-
model={selectedModel}
|
|
408
|
-
onClose={handleCloseDetail}
|
|
409
|
-
favorites={favorites}
|
|
410
|
-
onBenchmark={handleBenchmarkRow}
|
|
411
|
-
onLaunch={handleInstallEndpoint}
|
|
412
|
-
toolMode={toolMode}
|
|
413
|
-
onSetToolMode={setToolMode}
|
|
414
|
-
onCycleToolMode={cycleToolMode}
|
|
415
|
-
models={models}
|
|
416
|
-
onOpenFallback={(model) => setIncompatibleRequest({ model, toolMode })}
|
|
417
|
-
onToast={addToast}
|
|
418
|
-
/>
|
|
435
|
+
{/* 📖 DetailPanel side panel replaced by expand row in ModelTable */}
|
|
419
436
|
|
|
420
437
|
{exportOpen && (
|
|
421
438
|
<ExportModal
|