free-coding-models 0.3.62 → 0.3.63
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.md +15 -5
- package/package.json +1 -1
- package/src/app.js +13 -2
- package/src/endpoint-installer.js +17 -0
- package/src/favorites.js +22 -1
- package/src/key-handler.js +108 -23
- package/src/overlays.js +6 -4
- package/src/render-table.js +1 -1
- package/src/router-daemon.js +11 -9
- package/src/router-dashboard.js +99 -22
- package/web/dist/assets/{index-A4ph-qbA.js → index-B_N6UmGP.js} +1 -1
- package/web/dist/index.html +1 -1
package/src/router-dashboard.js
CHANGED
|
@@ -43,9 +43,10 @@ import chalk from 'chalk'
|
|
|
43
43
|
import { existsSync, readFileSync } from 'node:fs'
|
|
44
44
|
import { displayWidth, padEndDisplay, sliceOverlayLines, tintOverlayLines } from './render-helpers.js'
|
|
45
45
|
import { ROUTER_DEFAULT_PORT, ROUTER_MAX_PORT, ROUTER_PID_PATH, ROUTER_PORT_PATH, getRouterPortRange } from './router-daemon.js'
|
|
46
|
-
import { themeColors } from './theme.js'
|
|
46
|
+
import { themeColors, getTierRgb } from './theme.js'
|
|
47
47
|
import { formatTokenTotalCompact } from './token-usage-reader.js'
|
|
48
48
|
import { sendUsageTelemetry } from './telemetry.js'
|
|
49
|
+
import { getAvg, getVerdict } from './utils.js'
|
|
49
50
|
|
|
50
51
|
export const ROUTER_DASHBOARD_POLL_INTERVAL_MS = 2000
|
|
51
52
|
export const ROUTER_DASHBOARD_FETCH_TIMEOUT_MS = 1200
|
|
@@ -859,7 +860,7 @@ export function renderRouterDashboard(state, deps = {}) {
|
|
|
859
860
|
// 📖 Instead of the old "sets" system, show the user's favorites from the main
|
|
860
861
|
// 📖 table as the router fallback chain. #1 = tried first, #2 = next, etc.
|
|
861
862
|
lines.push(` ${themeColors.textBold('Router Models')} ${themeColors.dim('— your favorites, in fallback order')}`)
|
|
862
|
-
lines.push(` ${themeColors.dim('Star models with F in the main table.
|
|
863
|
+
lines.push(` ${themeColors.dim('Star models with F in the main table. Shift+↑↓ to reorder here.')}`)
|
|
863
864
|
lines.push('')
|
|
864
865
|
|
|
865
866
|
// 📖 Build the favorites list — pull from config.favorites + daemon health data
|
|
@@ -879,33 +880,84 @@ export function renderRouterDashboard(state, deps = {}) {
|
|
|
879
880
|
healthByKey.set(`${m.provider}/${m.model}`, m)
|
|
880
881
|
}
|
|
881
882
|
|
|
883
|
+
// 📖 Column headers
|
|
884
|
+
lines.push(` ${themeColors.dim(padEndDisplay('PRI', 4))} ${themeColors.dim(padEndDisplay('MODEL', 42))} ${themeColors.dim(padEndDisplay('DAEMON STATUS', 16))} ${themeColors.dim(padEndDisplay('AVG PING', 8))} ${themeColors.dim('VERDICT')}`)
|
|
885
|
+
|
|
882
886
|
for (let i = 0; i < favorites.length; i++) {
|
|
883
887
|
const favKey = favorites[i]
|
|
884
888
|
const health = healthByKey.get(favKey)
|
|
885
889
|
const isCursorRow = i === cursor
|
|
886
890
|
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
if (
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
else if (
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
else
|
|
891
|
+
let healthLabel = themeColors.dim(padEndDisplay('—', 16))
|
|
892
|
+
|
|
893
|
+
const mainResult = state.results?.find(r => `${r.providerKey}/${r.modelId}` === favKey)
|
|
894
|
+
|
|
895
|
+
if (mainResult) {
|
|
896
|
+
let statusText, statusColor
|
|
897
|
+
if (mainResult.status === 'noauth') {
|
|
898
|
+
statusText = `🔑 NO KEY`
|
|
899
|
+
statusColor = themeColors.dim
|
|
900
|
+
} else if (mainResult.status === 'auth_error') {
|
|
901
|
+
statusText = `🔐 AUTH FAIL`
|
|
902
|
+
statusColor = themeColors.errorBold
|
|
903
|
+
} else if (mainResult.status === 'pending') {
|
|
904
|
+
statusText = `⏳ PENDING`
|
|
905
|
+
statusColor = themeColors.warning
|
|
906
|
+
} else if (mainResult.status === 'up') {
|
|
907
|
+
statusText = `✅ UP`
|
|
908
|
+
statusColor = themeColors.success
|
|
909
|
+
} else if (mainResult.status === 'timeout') {
|
|
910
|
+
statusText = `⏳ TIMEOUT`
|
|
911
|
+
statusColor = themeColors.warning
|
|
912
|
+
} else if (mainResult.status === 'down') {
|
|
913
|
+
const code = mainResult.httpCode ?? 'ERR'
|
|
914
|
+
const errorEmojis = { '429': '🔥', '404': '🚫', '500': '💥', '502': '🔌', '503': '🔒', '504': '⏰' }
|
|
915
|
+
const errorLabels = { '404': '404 NOT FOUND', '410': '410 GONE', '429': '429 TRY LATER', '500': '500 ERROR' }
|
|
916
|
+
const emoji = errorEmojis[code] || '❌'
|
|
917
|
+
statusText = `${emoji} ${errorLabels[code] || code}`
|
|
918
|
+
statusColor = themeColors.error
|
|
919
|
+
} else {
|
|
920
|
+
statusText = '?'
|
|
921
|
+
statusColor = themeColors.dim
|
|
922
|
+
}
|
|
923
|
+
healthLabel = statusColor(padEndDisplay(statusText, 16))
|
|
900
924
|
} else {
|
|
901
|
-
healthLabel = themeColors.dim('⏳ Pending')
|
|
925
|
+
healthLabel = themeColors.dim(padEndDisplay('⏳ Pending', 16))
|
|
902
926
|
}
|
|
903
927
|
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
928
|
+
// 📖 Get global metrics from main table state
|
|
929
|
+
let avgPingDisplay = themeColors.dim('———')
|
|
930
|
+
let verdictDisplay = themeColors.dim('Pending ⏳')
|
|
931
|
+
|
|
932
|
+
if (mainResult) {
|
|
933
|
+
// Avg Ping
|
|
934
|
+
const avg = getAvg(mainResult)
|
|
935
|
+
if (avg !== Infinity) {
|
|
936
|
+
const str = String(avg).padEnd(4)
|
|
937
|
+
avgPingDisplay = avg < 500 ? themeColors.metricGood(str) : avg < 1500 ? themeColors.metricWarn(str) : themeColors.metricBad(str)
|
|
938
|
+
}
|
|
939
|
+
|
|
940
|
+
// Verdict
|
|
941
|
+
const verdict = getVerdict(mainResult)
|
|
942
|
+
let verdictText, verdictColor
|
|
943
|
+
switch (verdict) {
|
|
944
|
+
case 'Perfect': verdictText = 'Perfect 🚀'; verdictColor = themeColors.successBold; break
|
|
945
|
+
case 'Normal': verdictText = 'Normal ✅'; verdictColor = themeColors.metricGood; break
|
|
946
|
+
case 'Spiky': verdictText = 'Spiky 📈'; verdictColor = (t) => chalk.bold.rgb(...getTierRgb('A+'))(t); break
|
|
947
|
+
case 'Slow': verdictText = 'Slow 🐢'; verdictColor = (t) => chalk.bold.rgb(...getTierRgb('A-'))(t); break
|
|
948
|
+
case 'Very Slow': verdictText = 'Very Slow 🐌'; verdictColor = (t) => chalk.bold.rgb(...getTierRgb('B+'))(t); break
|
|
949
|
+
case 'Overloaded': verdictText = 'Overloaded 🔥'; verdictColor = (t) => chalk.bold.rgb(...getTierRgb('B'))(t); break
|
|
950
|
+
case 'Unstable': verdictText = 'Unstable ⚠️'; verdictColor = themeColors.errorBold; break
|
|
951
|
+
case 'Not Active': verdictText = 'Not Active 👻'; verdictColor = themeColors.dim; break
|
|
952
|
+
case 'Pending': verdictText = 'Pending ⏳'; verdictColor = themeColors.dim; break
|
|
953
|
+
default: verdictText = 'Unusable 💀'; verdictColor = (t) => chalk.bold.rgb(...getTierRgb('C'))(t); break
|
|
954
|
+
}
|
|
955
|
+
verdictDisplay = verdictColor(padEndDisplay(verdictText, 14))
|
|
956
|
+
} else {
|
|
957
|
+
verdictDisplay = padEndDisplay(verdictDisplay, 14)
|
|
958
|
+
}
|
|
907
959
|
|
|
908
|
-
const rowText = ` ${padEndDisplay(priorityGlyph(i), 4)} ${padEndDisplay(favKey, 42)} ${padEndDisplay(healthLabel,
|
|
960
|
+
const rowText = ` ${padEndDisplay(priorityGlyph(i), 4)} ${padEndDisplay(favKey, 42)} ${padEndDisplay(healthLabel, 16)} ${padEndDisplay(avgPingDisplay, 8)} ${verdictDisplay}`
|
|
909
961
|
|
|
910
962
|
if (isCursorRow) {
|
|
911
963
|
lines.push(themeColors.bgCursor(rowText + ' '.repeat(Math.max(0, width - displayWidth(rowText) - 3))))
|
|
@@ -915,6 +967,31 @@ export function renderRouterDashboard(state, deps = {}) {
|
|
|
915
967
|
}
|
|
916
968
|
}
|
|
917
969
|
|
|
970
|
+
// 📖 Toggle Daemon Button
|
|
971
|
+
lines.push('')
|
|
972
|
+
const btnCursor = favorites.length
|
|
973
|
+
const isBtnCursor = cursor === btnCursor
|
|
974
|
+
const btnText = isStopped ? '▶ Start Router Daemon' : '⏹ Stop Router Daemon'
|
|
975
|
+
const btnRowText = ` [ ${btnText} ]`
|
|
976
|
+
|
|
977
|
+
if (isBtnCursor) {
|
|
978
|
+
lines.push(themeColors.bgCursor(btnRowText + ' '.repeat(Math.max(0, width - displayWidth(btnRowText) - 3))))
|
|
979
|
+
} else {
|
|
980
|
+
lines.push(btnRowText)
|
|
981
|
+
}
|
|
982
|
+
|
|
983
|
+
// 📖 Install Endpoint Button
|
|
984
|
+
const installBtnCursor = favorites.length + 1
|
|
985
|
+
const isInstallBtnCursor = cursor === installBtnCursor
|
|
986
|
+
const installBtnText = '🔌 Install Router Endpoint to CLI Tool'
|
|
987
|
+
const installBtnRowText = ` [ ${installBtnText} ]`
|
|
988
|
+
|
|
989
|
+
if (isInstallBtnCursor) {
|
|
990
|
+
lines.push(themeColors.bgCursor(installBtnRowText + ' '.repeat(Math.max(0, width - displayWidth(installBtnRowText) - 3))))
|
|
991
|
+
} else {
|
|
992
|
+
lines.push(installBtnRowText)
|
|
993
|
+
}
|
|
994
|
+
|
|
918
995
|
lines.push('')
|
|
919
996
|
lines.push(` ${separator}`)
|
|
920
997
|
lines.push('')
|
|
@@ -958,7 +1035,7 @@ export function renderRouterDashboard(state, deps = {}) {
|
|
|
958
1035
|
// ── Error/Notice display ────────────────────────────────────────────────────
|
|
959
1036
|
if (state.routerDashboardError && isStopped) {
|
|
960
1037
|
lines.push('')
|
|
961
|
-
lines.push(` ${themeColors.dim('
|
|
1038
|
+
lines.push(` ${themeColors.dim('Press')} ${themeColors.hotkey('S')} ${themeColors.dim('to start it now.')}`)
|
|
962
1039
|
} else if (state.routerDashboardError) {
|
|
963
1040
|
lines.push('')
|
|
964
1041
|
lines.push(` ${themeColors.warning(state.routerDashboardError)}`)
|
|
@@ -971,7 +1048,7 @@ export function renderRouterDashboard(state, deps = {}) {
|
|
|
971
1048
|
|
|
972
1049
|
// ── Footer ──────────────────────────────────────────────────────────────────
|
|
973
1050
|
lines.push('')
|
|
974
|
-
lines.push(` ${themeColors.hotkey('↑↓')} ${themeColors.dim('Navigate')} ${themeColors.dim('•')} ${themeColors.hotkey('
|
|
1051
|
+
lines.push(` ${themeColors.hotkey('↑↓')} ${themeColors.dim('Navigate')} ${themeColors.dim('•')} ${themeColors.hotkey('Shift+↑↓')} ${themeColors.dim('Reorder')} ${themeColors.dim('•')} ${themeColors.hotkey('S')} ${themeColors.dim(isStopped ? 'Start daemon' : 'Stop daemon')} ${themeColors.dim('•')} ${themeColors.hotkey('I')} ${themeColors.dim(`Health check: ${probeLabel}`)} ${themeColors.dim('•')} ${themeColors.hotkey('C')} ${themeColors.dim('Clear log')} ${themeColors.dim('•')} ${themeColors.hotkey('Esc')} ${themeColors.dim('Back')}`)
|
|
975
1052
|
|
|
976
1053
|
const { visible, offset } = sliceOverlayLines(lines, state.routerDashboardScrollOffset || 0, state.terminalRows || 24)
|
|
977
1054
|
state.routerDashboardScrollOffset = offset
|