@@ -47,34 +47,11 @@ function clearLine() { process.stdout.write('\x1b[2K'); }
4747function hideCursor ( ) { process . stdout . write ( '\x1b[?25l' ) ; }
4848function 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
7956let inputBuf = '' ;
8057let 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
192170function 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
209217function 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