free-coding-models 0.3.56 → 0.3.58
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 +4 -47
- package/README.md +10 -32
- package/package.json +1 -1
- package/sources.js +3 -3
- package/src/app.js +28 -92
- package/src/command-palette.js +2 -4
- package/src/config.js +2 -3
- package/src/constants.js +4 -4
- package/src/key-handler.js +13 -105
- package/src/overlays.js +20 -120
- package/src/provider-metadata.js +1 -1
- package/src/render-helpers.js +38 -8
- package/src/render-table.js +116 -278
- package/src/router-dashboard.js +10 -1
- package/web/dist/assets/{index-DNRCaWPi.js → index-Bg2AldyN.js} +1 -1
- package/web/dist/index.html +1 -1
package/src/key-handler.js
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* tool launch actions. It also keeps the live key bindings aligned with the
|
|
9
9
|
* highlighted letters shown in the table headers.
|
|
10
10
|
*
|
|
11
|
-
* 📖 Key I opens the
|
|
11
|
+
* 📖 Key I opens the changelog overlay.
|
|
12
12
|
*
|
|
13
13
|
* It also owns the "test key" model selection used by the Settings overlay.
|
|
14
14
|
* Anonymous telemetry hooks for model launches and a few high-signal settings
|
|
@@ -291,7 +291,6 @@ export function createKeyHandler(ctx) {
|
|
|
291
291
|
sendUsageTelemetry,
|
|
292
292
|
startRecommendAnalysis,
|
|
293
293
|
stopRecommendAnalysis,
|
|
294
|
-
sendBugReport,
|
|
295
294
|
stopUi,
|
|
296
295
|
ping,
|
|
297
296
|
getPingModel,
|
|
@@ -950,12 +949,7 @@ export function createKeyHandler(ctx) {
|
|
|
950
949
|
state.installEndpointsResult = null
|
|
951
950
|
}
|
|
952
951
|
|
|
953
|
-
|
|
954
|
-
state.feedbackOpen = true
|
|
955
|
-
state.bugReportBuffer = ''
|
|
956
|
-
state.bugReportStatus = 'idle'
|
|
957
|
-
state.bugReportError = null
|
|
958
|
-
}
|
|
952
|
+
|
|
959
953
|
|
|
960
954
|
function openChangelogOverlay() {
|
|
961
955
|
state.changelogOpen = true
|
|
@@ -1115,7 +1109,7 @@ export function createKeyHandler(ctx) {
|
|
|
1115
1109
|
|| state.installedModelsOpen
|
|
1116
1110
|
|| state.routerDashboardOpen
|
|
1117
1111
|
|| state.recommendOpen
|
|
1118
|
-
|
|
1112
|
+
|
|
1119
1113
|
|| state.helpVisible
|
|
1120
1114
|
|| state.changelogOpen
|
|
1121
1115
|
}
|
|
@@ -1303,7 +1297,7 @@ export function createKeyHandler(ctx) {
|
|
|
1303
1297
|
state.helpScrollOffset = 0
|
|
1304
1298
|
return
|
|
1305
1299
|
case 'open-changelog': return openChangelogOverlay()
|
|
1306
|
-
|
|
1300
|
+
|
|
1307
1301
|
case 'open-recommend': return openRecommendOverlay()
|
|
1308
1302
|
case 'open-router-dashboard': return openRouterDashboardOverlay(state)
|
|
1309
1303
|
case 'open-token-usage': return openTokenUsageOverlay()
|
|
@@ -1433,7 +1427,7 @@ export function createKeyHandler(ctx) {
|
|
|
1433
1427
|
return
|
|
1434
1428
|
}
|
|
1435
1429
|
|
|
1436
|
-
if (!state.
|
|
1430
|
+
if (!state.settingsEditMode && !state.settingsAddKeyMode && key.name === 'g' && !key.ctrl && !key.meta) {
|
|
1437
1431
|
cycleGlobalTheme()
|
|
1438
1432
|
return
|
|
1439
1433
|
}
|
|
@@ -1889,72 +1883,7 @@ export function createKeyHandler(ctx) {
|
|
|
1889
1883
|
return
|
|
1890
1884
|
}
|
|
1891
1885
|
|
|
1892
|
-
// 📖 Feedback overlay: intercept ALL keys while overlay is active.
|
|
1893
|
-
// 📖 Enter → send to Discord, Esc → cancel, Backspace → delete char, printable → append to buffer.
|
|
1894
|
-
if (state.feedbackOpen) {
|
|
1895
|
-
if (key.ctrl && key.name === 'c') { exit(0); return }
|
|
1896
|
-
|
|
1897
|
-
if (key.name === 'escape') {
|
|
1898
|
-
// 📖 Cancel feedback — close overlay
|
|
1899
|
-
state.feedbackOpen = false
|
|
1900
|
-
state.bugReportBuffer = ''
|
|
1901
|
-
state.bugReportStatus = 'idle'
|
|
1902
|
-
state.bugReportError = null
|
|
1903
|
-
return
|
|
1904
|
-
}
|
|
1905
|
-
|
|
1906
|
-
if (key.name === 'return') {
|
|
1907
|
-
// 📖 Send feedback to Discord webhook
|
|
1908
|
-
const message = state.bugReportBuffer.trim()
|
|
1909
|
-
if (message.length > 0 && state.bugReportStatus !== 'sending') {
|
|
1910
|
-
state.bugReportStatus = 'sending'
|
|
1911
|
-
const result = await sendBugReport(message)
|
|
1912
|
-
if (result.success) {
|
|
1913
|
-
// 📖 Success — show confirmation briefly, then close overlay after 3 seconds
|
|
1914
|
-
state.bugReportStatus = 'success'
|
|
1915
|
-
setTimeout(() => {
|
|
1916
|
-
state.feedbackOpen = false
|
|
1917
|
-
state.bugReportBuffer = ''
|
|
1918
|
-
state.bugReportStatus = 'idle'
|
|
1919
|
-
state.bugReportError = null
|
|
1920
|
-
}, 3000)
|
|
1921
|
-
} else {
|
|
1922
|
-
// 📖 Error — show error message, keep overlay open
|
|
1923
|
-
state.bugReportStatus = 'error'
|
|
1924
|
-
state.bugReportError = result.error || 'Unknown error'
|
|
1925
|
-
}
|
|
1926
|
-
}
|
|
1927
|
-
return
|
|
1928
|
-
}
|
|
1929
1886
|
|
|
1930
|
-
if (key.name === 'backspace') {
|
|
1931
|
-
// 📖 Don't allow editing while sending or after success
|
|
1932
|
-
if (state.bugReportStatus === 'sending' || state.bugReportStatus === 'success') return
|
|
1933
|
-
state.bugReportBuffer = state.bugReportBuffer.slice(0, -1)
|
|
1934
|
-
// 📖 Clear error status when user starts editing again
|
|
1935
|
-
if (state.bugReportStatus === 'error') {
|
|
1936
|
-
state.bugReportStatus = 'idle'
|
|
1937
|
-
state.bugReportError = null
|
|
1938
|
-
}
|
|
1939
|
-
return
|
|
1940
|
-
}
|
|
1941
|
-
|
|
1942
|
-
// 📖 Append printable characters (str is the raw character typed)
|
|
1943
|
-
// 📖 Limit to 500 characters (Discord embed description limit)
|
|
1944
|
-
if (str && str.length === 1 && !key.ctrl && !key.meta) {
|
|
1945
|
-
// 📖 Don't allow editing while sending or after success
|
|
1946
|
-
if (state.bugReportStatus === 'sending' || state.bugReportStatus === 'success') return
|
|
1947
|
-
if (state.bugReportBuffer.length < 500) {
|
|
1948
|
-
state.bugReportBuffer += str
|
|
1949
|
-
// 📖 Clear error status when user starts editing again
|
|
1950
|
-
if (state.bugReportStatus === 'error') {
|
|
1951
|
-
state.bugReportStatus = 'idle'
|
|
1952
|
-
state.bugReportError = null
|
|
1953
|
-
}
|
|
1954
|
-
}
|
|
1955
|
-
}
|
|
1956
|
-
return
|
|
1957
|
-
}
|
|
1958
1887
|
|
|
1959
1888
|
// 📖 Help overlay: full keyboard navigation + key swallowing while overlay is open.
|
|
1960
1889
|
if (state.helpVisible) {
|
|
@@ -2629,7 +2558,8 @@ export function createKeyHandler(ctx) {
|
|
|
2629
2558
|
|
|
2630
2559
|
// 📖 Profile system removed - API keys now persist permanently across all sessions
|
|
2631
2560
|
|
|
2632
|
-
// 📖 Shift+R
|
|
2561
|
+
// 📖 Shift+R intentionally stays unadvertised in the main UI, but remains
|
|
2562
|
+
// 📖 available as a tester entry point for the Router Dashboard.
|
|
2633
2563
|
if (key.name === 'r' && key.shift && !key.ctrl && !key.meta) {
|
|
2634
2564
|
openRouterDashboardOverlay(state)
|
|
2635
2565
|
return
|
|
@@ -2670,11 +2600,7 @@ export function createKeyHandler(ctx) {
|
|
|
2670
2600
|
return
|
|
2671
2601
|
}
|
|
2672
2602
|
|
|
2673
|
-
|
|
2674
|
-
if (key.name === 'i') {
|
|
2675
|
-
openFeedbackOverlay()
|
|
2676
|
-
return
|
|
2677
|
-
}
|
|
2603
|
+
|
|
2678
2604
|
|
|
2679
2605
|
// 📖 W cycles the supported ping modes:
|
|
2680
2606
|
// 📖 speed (2s) → normal (10s) → slow (30s) → forced (4s) → speed.
|
|
@@ -2686,17 +2612,7 @@ export function createKeyHandler(ctx) {
|
|
|
2686
2612
|
return
|
|
2687
2613
|
}
|
|
2688
2614
|
|
|
2689
|
-
// 📖
|
|
2690
|
-
if (key.ctrl && key.name === 'o' && !key.meta) {
|
|
2691
|
-
state.footerHidden = !state.footerHidden
|
|
2692
|
-
if (!state.config.settings || typeof state.config.settings !== 'object') state.config.settings = {}
|
|
2693
|
-
state.config.settings.footerHidden = state.footerHidden
|
|
2694
|
-
saveConfig(state.config)
|
|
2695
|
-
state.frame++ // 📖 Force immediate re-render
|
|
2696
|
-
return
|
|
2697
|
-
}
|
|
2698
|
-
|
|
2699
|
-
// 📖 E toggles hiding models whose provider has no configured API key.
|
|
2615
|
+
// 📖 E toggles "Show only configured & working models": hides models whose provider has no configured API key, or whose health status is noauth/auth_error (but keeps timeout and 429).
|
|
2700
2616
|
// 📖 The preference is saved globally.
|
|
2701
2617
|
if (key.name === 'e') {
|
|
2702
2618
|
state.hideUnconfiguredModels = !state.hideUnconfiguredModels
|
|
@@ -2746,8 +2662,8 @@ export function createKeyHandler(ctx) {
|
|
|
2746
2662
|
return
|
|
2747
2663
|
}
|
|
2748
2664
|
|
|
2749
|
-
// 📖 Help overlay key:
|
|
2750
|
-
if (key.
|
|
2665
|
+
// 📖 Help overlay key: I = toggle help overlay
|
|
2666
|
+
if (key.name === 'i') {
|
|
2751
2667
|
state.helpVisible = !state.helpVisible
|
|
2752
2668
|
if (state.helpVisible) state.helpScrollOffset = 0
|
|
2753
2669
|
return
|
|
@@ -3002,10 +2918,7 @@ export function createMouseEventHandler(ctx) {
|
|
|
3002
2918
|
}
|
|
3003
2919
|
return
|
|
3004
2920
|
}
|
|
3005
|
-
|
|
3006
|
-
// 📖 Feedback overlay doesn't scroll — ignore
|
|
3007
|
-
return
|
|
3008
|
-
}
|
|
2921
|
+
|
|
3009
2922
|
if (state.commandPaletteOpen) {
|
|
3010
2923
|
// 📖 Command palette: scroll the results list
|
|
3011
2924
|
const count = state.commandPaletteResults?.length || 0
|
|
@@ -3147,12 +3060,7 @@ export function createMouseEventHandler(ctx) {
|
|
|
3147
3060
|
return
|
|
3148
3061
|
}
|
|
3149
3062
|
|
|
3150
|
-
|
|
3151
|
-
// 📖 Feedback overlay: click anywhere closes (no scroll, no cursor)
|
|
3152
|
-
state.feedbackOpen = false
|
|
3153
|
-
state.feedbackInput = ''
|
|
3154
|
-
return
|
|
3155
|
-
}
|
|
3063
|
+
|
|
3156
3064
|
|
|
3157
3065
|
if (state.helpVisible) {
|
|
3158
3066
|
// 📖 Help overlay: click anywhere closes (same as K or Escape)
|
package/src/overlays.js
CHANGED
|
@@ -4,15 +4,13 @@
|
|
|
4
4
|
*
|
|
5
5
|
* @details
|
|
6
6
|
* This module centralizes all overlay rendering in one place:
|
|
7
|
-
* - Settings, Install Endpoints, Command Palette, Help, Smart Recommend,
|
|
7
|
+
* - Settings, Install Endpoints, Command Palette, Help, Smart Recommend, Changelog, Router Dashboard
|
|
8
8
|
* - Settings diagnostics for provider key tests, including wrapped retry/error details
|
|
9
9
|
* - Recommend analysis timer orchestration and progress updates
|
|
10
10
|
*
|
|
11
11
|
* The factory pattern keeps stateful UI logic isolated while still
|
|
12
12
|
* allowing the main CLI to control shared state and dependencies.
|
|
13
13
|
*
|
|
14
|
-
* 📖 Feedback overlay (I key) combines feature requests + bug reports in one left-aligned input
|
|
15
|
-
*
|
|
16
14
|
* → Functions:
|
|
17
15
|
* - `createOverlayRenderers` — returns renderer + analysis helpers + overlayLayout
|
|
18
16
|
* - `renderRouterDashboard` — mounts the Smart Model Router dashboard renderer
|
|
@@ -299,12 +297,16 @@ export function createOverlayRenderers(state, deps) {
|
|
|
299
297
|
}
|
|
300
298
|
lines.push('')
|
|
301
299
|
|
|
302
|
-
// 📖 Footer with credits
|
|
300
|
+
// 📖 Footer with credits + community links — Discord and Buy me a coffee
|
|
301
|
+
// 📖 live here (and in the onboarding) instead of the main TUI footer to
|
|
302
|
+
// 📖 keep the table chrome lean.
|
|
303
303
|
lines.push('')
|
|
304
304
|
lines.push(
|
|
305
305
|
themeColors.dim(' ') +
|
|
306
306
|
themeColors.footerLove('Made with 💖 & ☕ by ') +
|
|
307
307
|
themeColors.link('\x1b]8;;https://github.com/vava-nessa\x1b\\vava-nessa\x1b]8;;\x1b\\') +
|
|
308
|
+
themeColors.dim(' • 💬 ') +
|
|
309
|
+
themeColors.footerDiscord('\x1b]8;;https://discord.gg/ZTNFHvvCkU\x1b\\Join the Discord\x1b]8;;\x1b\\') +
|
|
308
310
|
themeColors.dim(' • ☕ ') +
|
|
309
311
|
themeColors.footerCoffee('\x1b]8;;https://buymeacoffee.com/vavanessadev\x1b\\Buy me a coffee\x1b]8;;\x1b\\') +
|
|
310
312
|
themeColors.dim(' • ') +
|
|
@@ -868,8 +870,9 @@ export function createOverlayRenderers(state, deps) {
|
|
|
868
870
|
// 📖 Branding header
|
|
869
871
|
lines.push(` ${themeColors.accent('🚀')} ${themeColors.accentBold('free-coding-models')} ${themeColors.dim(`v${LOCAL_VERSION}`)}`)
|
|
870
872
|
lines.push(` ${heading('❓ Help & Keyboard Shortcuts')}`)
|
|
873
|
+
lines.push(` ${themeColors.successBold('🔑 Yellow = active key')}`)
|
|
871
874
|
lines.push('')
|
|
872
|
-
lines.push(` ${hint('— ↑↓ / PgUp / PgDn / Home / End scroll • K or Esc close')}`)
|
|
875
|
+
lines.push(` ${hint('— ↑↓ / PgUp / PgDn / Home / End scroll • K or ')}${themeColors.successBold('Esc close')}`)
|
|
873
876
|
lines.push(` ${heading('Columns')}`)
|
|
874
877
|
lines.push('')
|
|
875
878
|
lines.push(` ${label('Rank')} SWE-bench rank (1 = best coding score) ${hint('Sort:')} ${key('R')}`)
|
|
@@ -884,7 +887,7 @@ export function createOverlayRenderers(state, deps) {
|
|
|
884
887
|
lines.push(` ${label('CTX')} Context window size (128k, 200k, 256k, 1m, etc.) ${hint('Sort:')} ${key('C')}`)
|
|
885
888
|
lines.push(` ${hint('Bigger context = the model can read more of your codebase at once without forgetting.')}`)
|
|
886
889
|
lines.push('')
|
|
887
|
-
lines.push(` ${label('Model')} Model name (
|
|
890
|
+
lines.push(` ${label('Model')} Model name (1️⃣2️⃣3️⃣ = favorite order) ${hint('Sort:')} ${key('M')} ${hint('Favorite:')} ${key('F')}`)
|
|
888
891
|
lines.push(` ${hint('Star the ones you like. Press Y to switch between pinned mode and normal filter/sort mode.')}`)
|
|
889
892
|
lines.push('')
|
|
890
893
|
lines.push(` ${label('Provider')} Provider source (NIM, Groq, Cerebras, etc.) ${hint('Sort:')} ${key('O')} ${hint('Cycle:')} ${key('D')}`)
|
|
@@ -925,19 +928,18 @@ export function createOverlayRenderers(state, deps) {
|
|
|
925
928
|
lines.push(` ${key('Ctrl+P')} Open ⚡️ command palette ${hint('(search and run actions quickly)')}`)
|
|
926
929
|
lines.push(` ${key('E')} Toggle configured models only ${hint('(enabled by default)')}`)
|
|
927
930
|
lines.push(` ${key('Z')} Cycle tool mode ${hint('(📦 OpenCode → π Pi → 🪼 jcode → 📦 Desktop → 🦞 OpenClaw → 💘 Crush → 🪿 Goose → 🛠 Aider → 🐉 Qwen → 🤲 OpenHands → ⚡ Amp → 🦘 Rovo → ♊ Gemini)')}`)
|
|
928
|
-
lines.push(` ${key('F')} Toggle favorite on selected row ${hint('(
|
|
931
|
+
lines.push(` ${key('F')} Toggle favorite on selected row ${hint('(1️⃣2️⃣3️⃣ = router fallback order, capped at 🔟)')}`)
|
|
929
932
|
lines.push(` ${key('⇧↑/⇧↓')} Reorder selected favorite up/down ${hint('(changes router priority)')}`)
|
|
930
933
|
lines.push(` ${key('Y')} Toggle favorites mode ${hint('(Pinned + always visible ↔ Normal filter/sort behavior)')}`)
|
|
931
934
|
lines.push(` ${key('X')} Clear active text filter ${hint('(remove custom query applied from ⚡️ Command Palette)')}`)
|
|
932
935
|
lines.push(` ${key('Q')} Smart Recommend ${hint('(🎯 find the best model for your task — questionnaire + live analysis)')}`)
|
|
933
|
-
lines.push(` ${key('Shift+R')} Router Dashboard ${hint('(🔀 daemon health, circuit breakers, tokens, request log)')}`)
|
|
934
936
|
lines.push(` ${key('G')} Cycle theme ${hint('(auto → dark → light)')}`)
|
|
935
|
-
|
|
937
|
+
|
|
936
938
|
lines.push(` ${key('P')} Open settings ${hint('(manage API keys, provider toggles, updates, legacy cleanup)')}`)
|
|
937
939
|
// 📖 Profile system removed - API keys now persist permanently across all sessions
|
|
938
940
|
lines.push(` ${key('Ctrl+P')} Reset view settings ${hint('(search "Reset view" in the command palette)')}`)
|
|
939
941
|
lines.push(` ${key('N')} Changelog ${hint('(📋 browse all versions, Enter to view details)')}`)
|
|
940
|
-
lines.push(` ${key('
|
|
942
|
+
lines.push(` ${key('I')} / ${key('Esc')} Show/hide this help`)
|
|
941
943
|
lines.push(` ${key('Ctrl+C')} Exit`)
|
|
942
944
|
lines.push('')
|
|
943
945
|
lines.push(` ${heading('Settings (P)')}`)
|
|
@@ -1170,92 +1172,6 @@ export function createOverlayRenderers(state, deps) {
|
|
|
1170
1172
|
}, PING_RATE)
|
|
1171
1173
|
}
|
|
1172
1174
|
|
|
1173
|
-
// ─── Feedback overlay renderer ────────────────────────────────────────────
|
|
1174
|
-
// 📖 renderFeedback: Draw the overlay for anonymous Discord feedback.
|
|
1175
|
-
// 📖 Shows an input field where users can type feedback, bug reports, or any comments.
|
|
1176
|
-
function renderFeedback() {
|
|
1177
|
-
const EL = '\x1b[K'
|
|
1178
|
-
const lines = []
|
|
1179
|
-
|
|
1180
|
-
// 📖 Calculate available space for multi-line input (dynamic based on terminal width)
|
|
1181
|
-
const maxInputWidth = state.terminalCols - 8 // 8 = padding (4 spaces each side)
|
|
1182
|
-
const maxInputLines = 10 // Show up to 10 lines of input
|
|
1183
|
-
|
|
1184
|
-
// 📖 Split buffer into lines for display (with wrapping)
|
|
1185
|
-
const wrapText = (text, width) => {
|
|
1186
|
-
const words = text.split(' ')
|
|
1187
|
-
const lines = []
|
|
1188
|
-
let currentLine = ''
|
|
1189
|
-
|
|
1190
|
-
for (const word of words) {
|
|
1191
|
-
const testLine = currentLine ? currentLine + ' ' + word : word
|
|
1192
|
-
if (testLine.length <= width) {
|
|
1193
|
-
currentLine = testLine
|
|
1194
|
-
} else {
|
|
1195
|
-
if (currentLine) lines.push(currentLine)
|
|
1196
|
-
currentLine = word
|
|
1197
|
-
}
|
|
1198
|
-
}
|
|
1199
|
-
if (currentLine) lines.push(currentLine)
|
|
1200
|
-
return lines
|
|
1201
|
-
}
|
|
1202
|
-
|
|
1203
|
-
const inputLines = wrapText(state.bugReportBuffer, maxInputWidth)
|
|
1204
|
-
const displayLines = inputLines.slice(0, maxInputLines)
|
|
1205
|
-
|
|
1206
|
-
// 📖 Branding header
|
|
1207
|
-
lines.push('')
|
|
1208
|
-
lines.push(` ${themeColors.accent('🚀')} ${themeColors.accentBold('free-coding-models')} ${themeColors.dim(`v${LOCAL_VERSION}`)}`)
|
|
1209
|
-
lines.push(` ${themeColors.successBold('📝 Feedback, bugs & requests')}`)
|
|
1210
|
-
lines.push('')
|
|
1211
|
-
lines.push(themeColors.dim(" — don't hesitate to send us feedback, bug reports, or just your feeling about the app"))
|
|
1212
|
-
lines.push('')
|
|
1213
|
-
|
|
1214
|
-
// 📖 Status messages (if any)
|
|
1215
|
-
if (state.bugReportStatus === 'sending') {
|
|
1216
|
-
lines.push(` ${themeColors.warning('⏳ Sending...')}`)
|
|
1217
|
-
lines.push('')
|
|
1218
|
-
} else if (state.bugReportStatus === 'success') {
|
|
1219
|
-
lines.push(` ${themeColors.successBold('✅ Successfully sent!')} ${themeColors.dim('Closing overlay in 3 seconds...')}`)
|
|
1220
|
-
lines.push('')
|
|
1221
|
-
lines.push(` ${themeColors.dim('Thank you for your feedback! It has been sent to the project team.')}`)
|
|
1222
|
-
lines.push('')
|
|
1223
|
-
} else if (state.bugReportStatus === 'error') {
|
|
1224
|
-
lines.push(` ${themeColors.error('❌ Error:')} ${themeColors.warning(state.bugReportError || 'Failed to send')}`)
|
|
1225
|
-
lines.push(` ${themeColors.dim('Press Backspace to edit, or Esc to close')}`)
|
|
1226
|
-
lines.push('')
|
|
1227
|
-
} else {
|
|
1228
|
-
lines.push(` ${themeColors.dim('Type your feedback below. Press Enter to send, Esc to cancel.')}`)
|
|
1229
|
-
lines.push(` ${themeColors.dim('Your message will be sent anonymously to the project team.')}`)
|
|
1230
|
-
lines.push('')
|
|
1231
|
-
}
|
|
1232
|
-
|
|
1233
|
-
// 📖 Simple input area – left-aligned, framed by horizontal lines
|
|
1234
|
-
lines.push(` ${themeColors.info('Message')} (${state.bugReportBuffer.length}/500 chars)`)
|
|
1235
|
-
lines.push(` ${themeColors.dim('─'.repeat(maxInputWidth))}`)
|
|
1236
|
-
// 📖 Input lines — left-aligned, or placeholder when empty
|
|
1237
|
-
if (displayLines.length > 0) {
|
|
1238
|
-
for (const line of displayLines) {
|
|
1239
|
-
lines.push(` ${line}`)
|
|
1240
|
-
}
|
|
1241
|
-
// 📖 Show cursor on last line
|
|
1242
|
-
if (state.bugReportStatus === 'idle' || state.bugReportStatus === 'error') {
|
|
1243
|
-
lines[lines.length - 1] += themeColors.accentBold('▏')
|
|
1244
|
-
}
|
|
1245
|
-
} else {
|
|
1246
|
-
const placeholderBR = state.bugReportStatus === 'idle' ? chalk.italic.rgb(...getProviderRgb('googleai'))('Type your message here...') : ''
|
|
1247
|
-
lines.push(` ${placeholderBR}${themeColors.accentBold('▏')}`)
|
|
1248
|
-
}
|
|
1249
|
-
lines.push(` ${themeColors.dim('─'.repeat(maxInputWidth))}`)
|
|
1250
|
-
lines.push('')
|
|
1251
|
-
lines.push(themeColors.dim(' Enter Send • Esc Cancel • Backspace Delete'))
|
|
1252
|
-
|
|
1253
|
-
// 📖 Apply overlay tint and return
|
|
1254
|
-
const tintedLines = tintOverlayLines(lines, themeColors.overlayBgFeedback, state.terminalCols)
|
|
1255
|
-
const cleared = tintedLines.map(l => l + EL)
|
|
1256
|
-
return cleared.join('\n')
|
|
1257
|
-
}
|
|
1258
|
-
|
|
1259
1175
|
// ─── Changelog overlay renderer ───────────────────────────────────────────
|
|
1260
1176
|
// 📖 renderChangelog: Two-phase overlay — index of all versions or details of one version
|
|
1261
1177
|
function renderChangelog() {
|
|
@@ -1606,7 +1522,7 @@ export function createOverlayRenderers(state, deps) {
|
|
|
1606
1522
|
lines.push(` ${totalRow.join(' ')}`)
|
|
1607
1523
|
|
|
1608
1524
|
lines.push('')
|
|
1609
|
-
lines.push(themeColors.dim(' Esc Back to main table
|
|
1525
|
+
lines.push(themeColors.dim(' Esc Back to main table'))
|
|
1610
1526
|
|
|
1611
1527
|
const { visible, offset } = sliceOverlayLines(lines, state.tokenUsageScrollOffset, state.terminalRows)
|
|
1612
1528
|
state.tokenUsageScrollOffset = offset
|
|
@@ -1641,7 +1557,7 @@ export function createOverlayRenderers(state, deps) {
|
|
|
1641
1557
|
lines.push(themeColors.info(' Enabling router, please wait...'))
|
|
1642
1558
|
} else if (state.routerOnboardingPhase === 'success') {
|
|
1643
1559
|
lines.push(themeColors.success(' ✅ Router enabled! Dashboard opening...'))
|
|
1644
|
-
lines.push(themeColors.dim('
|
|
1560
|
+
lines.push(themeColors.dim(' Setup complete. Return to the main table to continue.'))
|
|
1645
1561
|
} else if (state.routerOnboardingPhase === 'error') {
|
|
1646
1562
|
lines.push(themeColors.error(` ❌ ${state.routerOnboardingError || 'Failed to enable router'}`))
|
|
1647
1563
|
lines.push(themeColors.dim(' Press Esc or Enter to continue to the main table'))
|
|
@@ -1657,6 +1573,12 @@ export function createOverlayRenderers(state, deps) {
|
|
|
1657
1573
|
lines.push('')
|
|
1658
1574
|
}
|
|
1659
1575
|
lines.push(themeColors.dim(' ↑↓ Navigate • Enter Select • Esc Skip for now'))
|
|
1576
|
+
lines.push('')
|
|
1577
|
+
lines.push(
|
|
1578
|
+
themeColors.dim(' 💬 ') +
|
|
1579
|
+
themeColors.footerDiscord('\x1b]8;;https://discord.gg/ZTNFHvvCkU\x1b\\Join the Discord community\x1b]8;;\x1b\\') +
|
|
1580
|
+
themeColors.dim(' • Get help, share feedback, follow updates')
|
|
1581
|
+
)
|
|
1660
1582
|
}
|
|
1661
1583
|
|
|
1662
1584
|
const targetLine = cursorLineByRow[state.routerOnboardingCursor] ?? 0
|
|
@@ -1667,26 +1589,6 @@ export function createOverlayRenderers(state, deps) {
|
|
|
1667
1589
|
return tintedLines.map((l) => l + EL).join('\n')
|
|
1668
1590
|
}
|
|
1669
1591
|
|
|
1670
|
-
// ─── Router upgrade banner (inline in main table, not an overlay) ─────────────
|
|
1671
|
-
// 📖 renderRouterUpgradeBanner: non-blocking notification at top of the table
|
|
1672
|
-
// 📖 shown once to existing users who haven't seen router yet. Auto-dismisses after 10s.
|
|
1673
|
-
function renderRouterUpgradeBanner() {
|
|
1674
|
-
const EL = '\x1b[K'
|
|
1675
|
-
const now = Date.now()
|
|
1676
|
-
const BANNER_TTL_MS = 10_000
|
|
1677
|
-
// Dismissed or already seen in this session?
|
|
1678
|
-
if (state.routerUpgradeBannerDismissedAt > 0) return ''
|
|
1679
|
-
if (state.routerUpgradeBannerShownAt === 0) state.routerUpgradeBannerShownAt = now
|
|
1680
|
-
if (now - state.routerUpgradeBannerShownAt > BANNER_TTL_MS) {
|
|
1681
|
-
state.routerUpgradeBannerDismissedAt = now
|
|
1682
|
-
return ''
|
|
1683
|
-
}
|
|
1684
|
-
const remaining = Math.ceil((BANNER_TTL_MS - (now - state.routerUpgradeBannerShownAt)) / 1000)
|
|
1685
|
-
const msg = ` ${themeColors.accentBold('🆕')} ${themeColors.textBold('Smart Router is now available!')} ${themeColors.dim('Press')} ${themeColors.hotkey('Shift+R')} ${themeColors.dim('to set it up.')} ${themeColors.dim(`(dismisses in ${remaining}s)`)}`
|
|
1686
|
-
const pad = state.terminalCols > displayWidth(msg) ? ' '.repeat(Math.max(0, state.terminalCols - displayWidth(msg))) : ''
|
|
1687
|
-
return themeColors.warningBold(msg + pad)
|
|
1688
|
-
}
|
|
1689
|
-
|
|
1690
1592
|
return {
|
|
1691
1593
|
renderSettings,
|
|
1692
1594
|
renderInstallEndpoints,
|
|
@@ -1694,14 +1596,12 @@ export function createOverlayRenderers(state, deps) {
|
|
|
1694
1596
|
renderCommandPalette,
|
|
1695
1597
|
renderHelp,
|
|
1696
1598
|
renderRecommend,
|
|
1697
|
-
renderFeedback,
|
|
1698
1599
|
renderChangelog,
|
|
1699
1600
|
renderInstalledModels,
|
|
1700
1601
|
renderRouterDashboard,
|
|
1701
1602
|
renderIncompatibleFallback,
|
|
1702
1603
|
renderTokenUsage,
|
|
1703
1604
|
renderRouterOnboarding,
|
|
1704
|
-
renderRouterUpgradeBanner,
|
|
1705
1605
|
startRecommendAnalysis,
|
|
1706
1606
|
stopRecommendAnalysis,
|
|
1707
1607
|
overlayLayout,
|
package/src/provider-metadata.js
CHANGED
|
@@ -116,7 +116,7 @@ export const PROVIDER_METADATA = {
|
|
|
116
116
|
rateLimits: 'Quota depends on GitHub/Copilot tier; no separate provider billing',
|
|
117
117
|
},
|
|
118
118
|
mistral: {
|
|
119
|
-
label: 'Mistral
|
|
119
|
+
label: 'Mistral LP',
|
|
120
120
|
color: chalk.rgb(255, 196, 120),
|
|
121
121
|
signupUrl: 'https://console.mistral.ai/api-keys',
|
|
122
122
|
signupHint: 'La Plateforme → API keys (MISTRAL_API_KEY)',
|
package/src/render-helpers.js
CHANGED
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
*/
|
|
48
48
|
|
|
49
49
|
import chalk from 'chalk'
|
|
50
|
-
import { OVERLAY_PANEL_WIDTH, TABLE_FIXED_LINES } from './constants.js'
|
|
50
|
+
import { OVERLAY_PANEL_WIDTH, TABLE_FIXED_LINES, TABLE_HEADER_LINES, TABLE_FOOTER_LINES } from './constants.js'
|
|
51
51
|
import { sortResults } from './utils.js'
|
|
52
52
|
|
|
53
53
|
// 📖 stripAnsi: Remove ANSI color/control sequences to estimate visible text width before padding.
|
|
@@ -65,13 +65,32 @@ export function maskApiKey(key) {
|
|
|
65
65
|
|
|
66
66
|
// 📖 displayWidth: Calculate display width of a string in terminal columns.
|
|
67
67
|
// 📖 Emojis and other wide characters occupy 2 columns, variation selectors (U+FE0F) are zero-width.
|
|
68
|
+
// 📖 Keycap sequences (digit/# + FE0F + 20E3, e.g. 1️⃣) render as a single 2-cell glyph.
|
|
68
69
|
// 📖 This avoids pulling in a full `string-width` dependency for a lightweight CLI tool.
|
|
69
70
|
export function displayWidth(str) {
|
|
70
71
|
const plain = stripAnsi(String(str))
|
|
72
|
+
const codepoints = [...plain]
|
|
71
73
|
let w = 0
|
|
72
|
-
for (
|
|
74
|
+
for (let i = 0; i < codepoints.length; i++) {
|
|
75
|
+
const ch = codepoints[i]
|
|
73
76
|
const cp = ch.codePointAt(0)
|
|
74
|
-
|
|
77
|
+
|
|
78
|
+
// Keycap sequence detection: ASCII digit / # / * followed by optional FE0F then 20E3 → +2 (single emoji glyph)
|
|
79
|
+
const isKeycapBase = (cp >= 0x30 && cp <= 0x39) || cp === 0x23 || cp === 0x2A
|
|
80
|
+
if (isKeycapBase) {
|
|
81
|
+
let j = i + 1
|
|
82
|
+
let sawFe0f = false
|
|
83
|
+
if (j < codepoints.length && codepoints[j].codePointAt(0) === 0xFE0F) { sawFe0f = true; j++ }
|
|
84
|
+
if (j < codepoints.length && codepoints[j].codePointAt(0) === 0x20E3) {
|
|
85
|
+
w += 2
|
|
86
|
+
i = j // 📖 skip the consumed FE0F (if any) and the 20E3
|
|
87
|
+
continue
|
|
88
|
+
}
|
|
89
|
+
// 📖 Not a keycap, fall through to normal handling
|
|
90
|
+
void sawFe0f
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Zero-width: variation selectors (FE00-FE0F), zero-width joiner/non-joiner, lone combining keycap
|
|
75
94
|
if ((cp >= 0xFE00 && cp <= 0xFE0F) || cp === 0x200D || cp === 0x200C || cp === 0x20E3) continue
|
|
76
95
|
// Wide: CJK, emoji (most above U+1F000), fullwidth forms
|
|
77
96
|
if (
|
|
@@ -146,14 +165,24 @@ export function sliceOverlayLines(lines, offset, terminalRows) {
|
|
|
146
165
|
|
|
147
166
|
// ─── Table viewport calculation ────────────────────────────────────────────────
|
|
148
167
|
|
|
168
|
+
// 📖 getTableFixedLines: Resolve the non-model line budget for the main table.
|
|
169
|
+
// 📖 Header and full footer are always visible in the main table, with optional
|
|
170
|
+
// 📖 extra fixed rows for temporary banners.
|
|
171
|
+
export function getTableFixedLines({ extraFixedLines = 0 } = {}) {
|
|
172
|
+
return TABLE_HEADER_LINES + TABLE_FOOTER_LINES + Math.max(0, extraFixedLines)
|
|
173
|
+
}
|
|
174
|
+
|
|
149
175
|
// 📖 calculateViewport: Computes the visible slice of model rows that fits in the terminal.
|
|
150
176
|
// 📖 When scroll indicators are needed, they each consume 1 line from the model budget.
|
|
151
|
-
// 📖 `
|
|
152
|
-
// 📖 viewport permanently for the normal case.
|
|
177
|
+
// 📖 `lineBudget` lets callers reserve temporary footer/header rows without shrinking
|
|
178
|
+
// 📖 the viewport permanently for the normal case.
|
|
153
179
|
// 📖 Returns { startIdx, endIdx, hasAbove, hasBelow } for rendering.
|
|
154
|
-
export function calculateViewport(terminalRows, scrollOffset, totalModels,
|
|
180
|
+
export function calculateViewport(terminalRows, scrollOffset, totalModels, lineBudget = 0) {
|
|
155
181
|
if (terminalRows <= 0) return { startIdx: 0, endIdx: totalModels, hasAbove: false, hasBelow: false }
|
|
156
|
-
|
|
182
|
+
const fixedLines = typeof lineBudget === 'number'
|
|
183
|
+
? TABLE_FIXED_LINES + Math.max(0, lineBudget)
|
|
184
|
+
: getTableFixedLines(lineBudget)
|
|
185
|
+
let maxSlots = terminalRows - fixedLines
|
|
157
186
|
if (maxSlots < 1) maxSlots = 1
|
|
158
187
|
if (totalModels <= maxSlots) return { startIdx: 0, endIdx: totalModels, hasAbove: false, hasBelow: false }
|
|
159
188
|
|
|
@@ -206,7 +235,8 @@ export function sortResultsWithPinnedFavorites(results, sortColumn, sortDirectio
|
|
|
206
235
|
// 📖 Modifies st.scrollOffset in-place, returns undefined.
|
|
207
236
|
export function adjustScrollOffset(st) {
|
|
208
237
|
const total = st.visibleSorted ? st.visibleSorted.length : st.results.filter(r => !r.hidden).length
|
|
209
|
-
|
|
238
|
+
const fixedLines = getTableFixedLines()
|
|
239
|
+
let maxSlots = st.terminalRows - fixedLines
|
|
210
240
|
if (maxSlots < 1) maxSlots = 1
|
|
211
241
|
if (total <= maxSlots) { st.scrollOffset = 0; return }
|
|
212
242
|
// Ensure cursor is not above the visible window
|