Skip to content

Commit ebf2a04

Browse files
committed
fix: overlay box rendering — proper sizing, bottom border, cursor positioning
1 parent ed1407e commit ebf2a04

5 files changed

Lines changed: 125 additions & 97 deletions

File tree

‎dist/tui/dashboard.js‎

Lines changed: 57 additions & 44 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎dist/tui/dashboard.js.map‎

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎package-lock.json‎

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎package.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "adaptive-memory-multi-model-router",
3-
"version": "2.13.10",
3+
"version": "2.13.11",
44
"shortName": "A3M Router",
55
"displayName": "A3M Router - Adaptive Memory Multi-Model Router",
66
"description": "🔥 Fastest-growing npm LLM router — 0 to 10,024 downloads in 14 days. Parallel multi-LLM execution with independent benchmark validation (138ms baseline, +96ms proxy overhead), 47+ providers, 99.5% routing accuracy, 62% cost savings. Open-source AI gateway. Parallel ensemble, confidence scoring, query-type presets, persistent memory. Zero ML, 19.5KB. MIT.",

‎src/tui/dashboard.ts‎

Lines changed: 64 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -47,34 +47,11 @@ function clearLine() { process.stdout.write('\x1b[2K'); }
4747
function hideCursor() { process.stdout.write('\x1b[?25l'); }
4848
function showCursor() { process.stdout.write('\x1b[?25h'); }
4949

50-
const B = { tl: '╭', tr: '╮', bl: '╰', br: '╯', h: '─', v: '│' };
51-
52-
function drawOverlay(rows: number, cols: number, top: number, left: number, lines: string[]) {
53-
const w = cols - 2;
54-
55-
// Top border
56-
moveTo(top, left);
57-
process.stdout.write(C.bg + C.purple + B.tl + B.h.repeat(w) + B.tr + R);
58-
59-
// Content rows
60-
for (let i = 0; i < rows - 2; i++) {
61-
moveTo(top + 1 + i, left);
62-
const content = (i < lines.length ? lines[i] : '').padEnd(w, ' ');
63-
process.stdout.write(C.bg + C.purple + B.v + R + C.bg + content + C.purple + B.v + R);
64-
}
65-
66-
// Bottom border
67-
moveTo(top + rows - 1, left);
68-
process.stdout.write(C.bg + C.purple + B.bl + B.h.repeat(w) + B.br + R);
69-
}
70-
71-
function drawPrompt(top: number, left: number, w: number, text: string) {
72-
moveTo(top, left);
73-
clearLine();
74-
process.stdout.write(C.surface + C.text + BOLD + ' ▸ ' + R + C.surface + C.text + text + R);
75-
moveTo(top, left + 3); // cursor after " ▸ "
50+
function stripAnsi(s: string): string {
51+
return s.replace(/\x1b\[[0-9;]*m/g, '');
7652
}
7753

54+
const B = { tl: '╭', tr: '╮', bl: '╰', br: '╯', h: '─', v: '│' };
7855

7956
let inputBuf = '';
8057
let cursorPos = 3;
@@ -160,59 +137,98 @@ function processCommand(cmd: string) {
160137
}
161138

162139

163-
function buildOverlayLines(): string[] {
140+
function buildOverlayLines(boxW: number): string[] {
141+
const contentW = boxW - 4; // inside padding
164142
const lines: string[] = [];
165143

166-
// Header
167-
lines.push(`${C.purple + BOLD}⚡ A3M Router${R} ${C.dim}·${R} ${C.green}${activeModel}${R} ${C.dim}·${R} ${C.dim}${reqCount} req${R} ${C.dim}·${R} ${C.dim}$${totalCost.toFixed(6)}${R}`);
168-
lines.push(`${C.dim}${'─'.repeat(78)}${R}`);
144+
// Header row
145+
const hdr = `${C.purple + BOLD}⚡ A3M Router${R} ${C.dim}·${R} ${C.green}${activeModel}${R} ${C.dim}·${R} ${C.dim}${reqCount} req${R} ${C.dim}·${R} ${C.dim}$${totalCost.toFixed(6)}${R}`;
146+
lines.push(hdr.padEnd(contentW));
147+
148+
// Separator
149+
lines.push(`${C.dim}${'─'.repeat(Math.max(0, contentW))}${R}`);
169150
lines.push('');
170151

171152
// Log lines
172153
for (const l of log) {
173-
lines.push(l);
154+
lines.push(l.padEnd(contentW));
174155
}
175156

176-
// If no log, show welcome
157+
// Welcome message if empty
177158
if (log.length === 0) {
178-
lines.push(` ${C.dim}Type a query — auto-routed to cheapest model.${R}`);
159+
lines.push(` ${C.dim}Type a query — auto-routed to cheapest model.${R}`.padEnd(contentW));
179160
lines.push('');
180-
lines.push(` ${C.dim}Commands:${R}`);
181-
lines.push(` ${C.blue}/route${R} ${C.dim}<query>${R} ${C.blue}/cost${R} ${C.blue}/model nvidia${R}`);
182-
lines.push(` ${C.blue}/health${R} ${C.blue}/models${R} ${C.blue}/clear${R}`);
183-
lines.push(` ${C.blue}/exit${R} ${C.blue}/help${R}`);
161+
lines.push(` ${C.dim}Commands:${R}`.padEnd(contentW));
162+
lines.push(` ${C.blue}/route${R} ${C.dim}<query>${R} ${C.blue}/cost${R} ${C.blue}/model nvidia${R}`.padEnd(contentW));
163+
lines.push(` ${C.blue}/health${R} ${C.blue}/models${R} ${C.blue}/clear${R}`.padEnd(contentW));
164+
lines.push(` ${C.blue}/exit${R} ${C.blue}/help${R}`.padEnd(contentW));
184165
}
185166

186-
// Fill remaining with empty
187-
while (lines.length < 16) lines.push('');
188-
189167
return lines;
190168
}
191169

192170
function render() {
193171
const [w, h] = getSize();
194-
const BOX_W = 82;
172+
const BOX_W = Math.min(82, w - 4);
195173
const BOX_H = 18;
196174
const left = Math.max(0, Math.floor((w - BOX_W) / 2));
197175
const top = Math.max(0, Math.floor((h - BOX_H) / 2));
198176

199-
const overlayLines = buildOverlayLines();
177+
const overlayLines = buildOverlayLines(BOX_W);
200178

201179
hideCursor();
202-
drawOverlay(BOX_H, BOX_W, top, left, overlayLines);
203180

204-
// Prompt line at bottom of box
205-
drawPrompt(top + BOX_H - 1, left, BOX_W, inputBuf);
181+
// Draw the box
182+
const innerW = BOX_W - 2;
183+
184+
// Top border
185+
moveTo(top, left);
186+
clearLine();
187+
process.stdout.write(C.bg + C.purple + B.tl + B.h.repeat(innerW) + B.tr + R);
188+
189+
// Content rows (BOX_H - 3 for borders + prompt)
190+
const contentRows = BOX_H - 3;
191+
for (let i = 0; i < contentRows; i++) {
192+
moveTo(top + 1 + i, left);
193+
clearLine();
194+
const content = (overlayLines[i] || '').slice(0, innerW);
195+
const padded = content + ' '.repeat(Math.max(0, innerW - stripAnsi(content).length));
196+
process.stdout.write(C.bg + C.purple + B.v + R + C.bg + padded + C.purple + B.v + R);
197+
}
198+
199+
// Prompt row (second-to-last)
200+
const promptRow = top + 1 + contentRows;
201+
moveTo(promptRow, left);
202+
clearLine();
203+
const promptText = `${BOLD} ▸ ${R}${inputBuf}`;
204+
const promptPadded = promptText + ' '.repeat(Math.max(0, innerW - stripAnsi(promptText).length));
205+
process.stdout.write(C.bg + C.purple + B.v + R + C.surface + promptPadded + C.purple + B.v + R);
206+
// Cursor position
207+
moveTo(promptRow, left + 2 + stripAnsi(BOLD + ' ▸ ' + R).length + inputBuf.length);
208+
209+
// Bottom border
210+
moveTo(promptRow + 1, left);
211+
clearLine();
212+
process.stdout.write(C.bg + C.purple + B.bl + B.h.repeat(innerW) + B.br + R);
213+
206214
showCursor();
207215
}
208216

209217
function renderPromptLine() {
210218
const [w, h] = getSize();
211-
const BOX_W = 82;
219+
const BOX_W = Math.min(82, w - 4);
212220
const BOX_H = 18;
213221
const left = Math.max(0, Math.floor((w - BOX_W) / 2));
214222
const top = Math.max(0, Math.floor((h - BOX_H) / 2));
215-
drawPrompt(top + BOX_H - 1, left, BOX_W, inputBuf);
223+
const innerW = BOX_W - 2;
224+
const promptRow = top + 1 + (BOX_H - 3);
225+
226+
moveTo(promptRow, left);
227+
clearLine();
228+
const promptText = `${BOLD} ▸ ${R}${inputBuf}`;
229+
const promptPadded = promptText + ' '.repeat(Math.max(0, innerW - stripAnsi(promptText).length));
230+
process.stdout.write(C.bg + C.purple + B.v + R + C.surface + promptPadded + C.purple + B.v + R);
231+
moveTo(promptRow, left + 2 + stripAnsi(BOLD + ' ▸ ' + R).length + inputBuf.length);
216232
showCursor();
217233
}
218234

@@ -223,8 +239,7 @@ function cleanup() {
223239
cleanedUp = true;
224240

225241
const [w, h] = getSize();
226-
// Clear overlay area
227-
const BOX_W = 82;
242+
const BOX_W = Math.min(82, w - 4);
228243
const BOX_H = 18;
229244
const left = Math.max(0, Math.floor((w - BOX_W) / 2));
230245
const top = Math.max(0, Math.floor((h - BOX_H) / 2));

0 commit comments

Comments
 (0)