adaptive-memory-multi-model-router 2.13.2 → 2.13.3

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.
@@ -1,55 +1,49 @@
1
1
  #!/usr/bin/env node
2
2
  /**
3
- * A3M Router TUI v2 — 10x UI
3
+ * A3M Router TUI v2 — Conversational PI-style interface
4
4
  *
5
- * Inspired by: lazygit panels + k9s pulse + btop graphs + Tokyo Night theme
6
- * Built with: blessed + blessed-contrib
7
- *
8
- * TABS: 1-Dashboard 2-Costs 3-Providers 4-Logs 5-Help
9
- * F-KEYS: F1 Dash F2 Costs F3 Prov F4 Logs F5 Help F10 Quit
10
- * vim/hjkl for list nav, mouse for click targets
5
+ * Commands:
6
+ * /route <query> — Route a prompt through cheapest capable model
7
+ * /cost — Cost breakdown
8
+ * /health — Provider health check
9
+ * /providers — List all providers
10
+ * /clear — Clear chat
11
+ * /help — Show commands
12
+ * /exit, /q — Quit
11
13
  */
12
14
 
13
15
  import * as blessed from 'blessed';
14
- import * as contrib from 'blessed-contrib';
16
+ import { execSync } from 'child_process';
15
17
 
16
18
  // ═══════════════════════════════════════════════════════════════
17
19
  // TOKYO NIGHT THEME
18
20
  // ═══════════════════════════════════════════════════════════════
19
21
 
20
- const T = {
22
+ const C = {
21
23
  bg: '#1a1b26',
22
- bgDark: '#16161e',
23
24
  surface: '#24283b',
24
25
  border: '#3b4261',
25
- accent: '#7aa2f7', // blue
26
- accent2: '#bb9af7', // purple
26
+ blue: '#7aa2f7',
27
+ purple: '#bb9af7',
27
28
  green: '#9ece6a',
28
29
  yellow: '#e0af68',
29
30
  red: '#f7768e',
30
31
  cyan: '#7dcfff',
31
- magenta: '#bb9af7',
32
32
  orange: '#ff9e64',
33
33
  white: '#c0caf5',
34
34
  dim: '#565f89',
35
35
  bright: '#a9b1d6',
36
- pink: '#ff007c',
37
36
  };
38
37
 
39
38
  // ═══════════════════════════════════════════════════════════════
40
39
  // STATE
41
40
  // ═══════════════════════════════════════════════════════════════
42
41
 
43
- let activeTab = 1;
42
+ interface Message { role: 'user' | 'a3m'; text: string; model?: string; latency?: number; cost?: number; }
43
+ const messages: Message[] = [];
44
+ let totalCost = 0.000087;
45
+ let requestCount = 4;
44
46
  let tick = 0;
45
- const MAX_HISTORY = 60; // 60 data points for sparklines
46
-
47
- // Cost history (simulated time series)
48
- const costHistory: number[] = Array(MAX_HISTORY).fill(0);
49
- const latencyHistory: number[] = Array(MAX_HISTORY).fill(30);
50
-
51
- // Provider history for live charts
52
- const providerLoads: Record<string, number[]> = {};
53
47
 
54
48
  // ═══════════════════════════════════════════════════════════════
55
49
  // SCREEN
@@ -57,440 +51,231 @@ const providerLoads: Record<string, number[]> = {};
57
51
 
58
52
  const screen = blessed.screen({
59
53
  smartCSR: true,
60
- title: 'A3M Router',
54
+ title: 'A3M Router',
61
55
  fullUnicode: true,
62
- mouse: true,
63
- // @ts-ignore
64
56
  cursor: { shape: 'line', blink: true },
65
57
  });
66
58
 
67
- const GRID = new contrib.grid({ rows: 12, cols: 24, screen });
68
-
69
- // ═══════════════════════════════════════════════════════════════
70
- // TITLE BAR — Tokyo Night Header
71
- // ═══════════════════════════════════════════════════════════════
72
-
73
- const titleBar = GRID.set(0, 0, 1, 24, blessed.box, {
74
- style: { fg: T.bright, bg: T.bgDark },
75
- tags: true,
76
- });
77
-
78
- function renderTitleBar() {
79
- titleBar.setContent(
80
- ` {bold}{#bb9af7-fg}⚡ A3M Router{/} {#565f89-fg}v2.12.7{/} │ ` +
81
- `[F1] {${activeTab===1?'#7aa2f7':'#565f89'}-fg}Dashboard{/}` +
82
- ` [F2] {${activeTab===2?'#7aa2f7':'#565f89'}-fg}Costs{/}` +
83
- ` [F3] {${activeTab===3?'#7aa2f7':'#565f89'}-fg}Providers{/}` +
84
- ` [F4] {${activeTab===4?'#7aa2f7':'#565f89'}-fg}Logs{/}` +
85
- ` [F5] {${activeTab===5?'#7aa2f7':'#565f89'}-fg}Help{/}` +
86
- ` │ {#565f89-fg}q quit / cmd ↑↓ nav tab switch{/}`
87
- );
88
- }
89
-
90
59
  // ═══════════════════════════════════════════════════════════════
91
- // TAB 1: DASHBOARD — Sparklines + Provider Grid + Live Feed
60
+ // LAYOUT
92
61
  // ═══════════════════════════════════════════════════════════════
93
62
 
94
- // --- Latency Sparkline ---
95
- const latencySpark = GRID.set(1, 0, 3, 8, contrib.sparkline, {
96
- label: ' Latency History (ms)',
97
- style: { line: T.accent, text: T.white, baseline: T.dim },
98
- tags: true,
99
- });
100
-
101
- // --- Cost Sparkline ---
102
- const costSpark = GRID.set(1, 8, 3, 8, contrib.sparkline, {
103
- label: ' ▸ Cost Rate ($/1K req)',
104
- style: { line: T.green, text: T.white, baseline: T.dim },
105
- tags: true,
106
- });
107
-
108
- // --- Provider Health Bars ---
109
- const providerBars = GRID.set(1, 16, 3, 8, blessed.box, {
110
- label: ' ▸ Provider Health',
111
- border: { type: 'line', fg: T.border },
112
- style: { fg: T.white, bg: T.bg },
113
- tags: true,
114
- });
115
-
116
- // --- Live Request Feed (scrolling) ---
117
- const liveFeed = GRID.set(4, 0, 4, 14, contrib.log, {
118
- fg: T.white,
119
- selectedFg: T.green,
120
- label: ' ▸ Live Request Feed',
121
- border: { type: 'line', fg: T.border },
122
- });
123
-
124
- // --- Routing Map ---
125
- const routeMap = GRID.set(4, 14, 4, 10, blessed.box, {
126
- label: ' ▸ Routing Intelligence',
127
- border: { type: 'line', fg: T.border },
128
- style: { fg: T.white, bg: T.bg },
129
- tags: true,
130
- });
131
-
132
- // --- KPI Boxes ---
133
- const kpiRow = GRID.set(8, 0, 2, 24, blessed.box, {
134
- style: { fg: T.white, bg: T.bgDark },
63
+ // Header (1 line)
64
+ const header = blessed.box({
65
+ top: 0, left: 0, width: '100%', height: 1,
66
+ style: { fg: C.bright, bg: C.surface },
135
67
  tags: true,
68
+ content: ` {bold}{#bb9af7-fg}⚡ A3M Router{/} {#565f89-fg}v2.13.1{/} │ /route /cost /health /help │ q quit`,
136
69
  });
137
70
 
138
- // ═══════════════════════════════════════════════════════════════
139
- // TAB 2: COSTS — Detailed breakdown
140
- // ═══════════════════════════════════════════════════════════════
141
-
142
- const costDetail = GRID.set(1, 0, 9, 24, blessed.box, {
143
- label: ' ▸ Cost Analytics',
144
- border: { type: 'line', fg: T.border },
145
- style: { fg: T.white, bg: T.bg },
146
- tags: true,
147
- });
148
-
149
- // ═══════════════════════════════════════════════════════════════
150
- // TAB 3: PROVIDERS — Detailed provider table
151
- // ═══════════════════════════════════════════════════════════════
152
-
153
- const providerDetail = GRID.set(1, 0, 9, 24, contrib.table, {
71
+ // Chat area (middle - all remaining space except bottom 3)
72
+ const chatBox = blessed.box({
73
+ top: 1, left: 0, width: '100%', height: '100%-4',
74
+ style: { fg: C.white, bg: C.bg },
75
+ scrollable: true,
76
+ alwaysScroll: true,
77
+ mouse: true,
154
78
  keys: true,
155
- fg: T.white,
156
- selectedFg: T.bright,
157
- selectedBg: T.border,
158
- interactive: true,
159
- label: ' ▸ Provider Registry',
160
- border: { type: 'line', fg: T.border },
161
- columnSpacing: 3,
162
- columnWidth: [12, 18, 10, 10, 13, 12, 16],
163
- });
164
-
165
- // ═══════════════════════════════════════════════════════════════
166
- // TAB 4: LOGS — Full request history
167
- // ═══════════════════════════════════════════════════════════════
168
-
169
- const fullLog = GRID.set(1, 0, 9, 24, contrib.log, {
170
- fg: T.white,
171
- selectedFg: T.green,
172
- label: ' ▸ Full Request Log',
173
- border: { type: 'line', fg: T.border },
174
- });
175
-
176
- // ═══════════════════════════════════════════════════════════════
177
- // TAB 5: HELP
178
- // ═══════════════════════════════════════════════════════════════
179
-
180
- const helpPanel = GRID.set(1, 0, 9, 24, blessed.box, {
181
- label: ' ▸ Keyboard Reference',
182
- border: { type: 'line', fg: T.border },
183
- style: { fg: T.white, bg: T.bg },
184
79
  tags: true,
80
+ padding: { left: 1, right: 1, top: 0, bottom: 0 },
185
81
  });
186
82
 
187
- // ═══════════════════════════════════════════════════════════════
188
- // COMMAND BAR
189
- // ═══════════════════════════════════════════════════════════════
190
-
191
- const cmdBar = GRID.set(10, 0, 1, 24, blessed.textbox, {
192
- label: ' /',
193
- border: { type: 'line', fg: T.accent2 },
194
- style: { fg: T.bright, bg: T.surface },
83
+ // Input line
84
+ const inputBox = blessed.textbox({
85
+ bottom: 2, left: 0, width: '100%', height: 2,
86
+ label: ' / ',
87
+ border: { type: 'line', fg: C.purple },
88
+ style: { fg: C.bright, bg: C.surface },
195
89
  inputOnFocus: true,
196
90
  keys: true,
197
91
  tags: true,
198
92
  });
199
93
 
200
- // ═══════════════════════════════════════════════════════════════
201
- // STATUS LINE
202
- // ═══════════════════════════════════════════════════════════════
203
-
204
- const statusLine = GRID.set(11, 0, 1, 24, blessed.box, {
205
- style: { fg: T.dim, bg: T.bgDark },
94
+ // Status bar
95
+ const statusBar = blessed.box({
96
+ bottom: 0, left: 0, width: '100%', height: 1,
97
+ style: { fg: C.dim, bg: C.surface },
206
98
  tags: true,
207
99
  });
208
100
 
209
101
  // ═══════════════════════════════════════════════════════════════
210
- // HELPER: Render a bar chart inline
102
+ // HELPERS
211
103
  // ═══════════════════════════════════════════════════════════════
212
104
 
213
- function barChart(value: number, max: number, width: number, chars: string[]): string {
214
- const pct = Math.min(value / max, 1);
215
- const filled = Math.round(pct * width);
216
- const blocks = ['▏', '▎', '▍', '▌', '▋', '▊', '▉', '█'];
217
- let result = '';
218
- for (let i = 0; i < width; i++) {
219
- if (i < filled - 1) result += '█';
220
- else if (i === filled - 1) result += blocks[Math.floor(pct * width * 8) % 8] || '█';
221
- else result += chars[0] || '░';
222
- }
223
- return result;
105
+ function badge(text: string, color: string): string {
106
+ return `{${color}-fg}[${text}]{/}`;
224
107
  }
225
108
 
226
- // ═══════════════════════════════════════════════════════════════
227
- // HELPER: Pulse animation
228
- // ═══════════════════════════════════════════════════════════════
229
-
230
- function pulse(frame: number): number {
231
- return Math.sin(frame * 0.3) * 0.5 + 0.5;
232
- }
233
-
234
- // ═══════════════════════════════════════════════════════════════
235
- // RENDER: Dashboard
236
- // ═══════════════════════════════════════════════════════════════
109
+ function dim(s: string): string { return `{#565f89-fg}${s}{/}`; }
237
110
 
238
- const providers = [
239
- { name: 'nvidia', model: 'llama-3.1-8b', tier: 'free', healthy: true, latency: 85, load: 0.4 },
240
- { name: 'deepseek', model: 'v4-flash', tier: 'mid', healthy: true, latency: 210, load: 0.2 },
241
- { name: 'groq', model: '8b-instant', tier: 'cheap', healthy: true, latency: 150, load: 0.1 },
242
- { name: 'cerebras', model: '3.3-70b', tier: 'cheap', healthy: true, latency: 320, load: 0.0 },
243
- { name: 'mistral', model: 'small', tier: 'mid', healthy: false, latency: 0, load: 0 },
244
- { name: 'ollama', model: 'llama3', tier: 'local', healthy: true, latency: 50, load: 0.15 },
245
- ];
246
-
247
- const tierColor = (t: string) =>
248
- t === 'free' ? T.green : t === 'cheap' ? T.yellow : t === 'mid' ? T.accent : t === 'local' ? T.cyan : T.accent2;
249
-
250
- const requestLog: string[] = [];
251
-
252
- function addLog(provider: string, model: string, latency: number, status: number) {
253
- const time = new Date().toLocaleTimeString();
254
- const color = status >= 400 ? `{#f7768e-fg}` : `{#9ece6a-fg}`;
255
- requestLog.push(
256
- `${color}${time} │ ${provider.padEnd(10)} │ ${model.padEnd(14)} │ ${String(latency).padStart(4)}ms │ ${status}{/}`
257
- );
258
- if (requestLog.length > 100) requestLog.shift();
111
+ function addMsg(role: 'user' | 'a3m', text: string, model?: string, latency?: number, cost?: number) {
112
+ messages.push({ role, text, model, latency, cost });
113
+ if (role === 'a3m' && cost) { totalCost += cost; requestCount++; }
114
+ renderChat();
259
115
  }
260
116
 
261
- function renderDashboard() {
262
- // --- SPARKLINES ---
263
- // Update history
264
- costHistory.push(Math.random() * 0.05);
265
- latencyHistory.push(30 + Math.random() * 250);
266
- if (costHistory.length > MAX_HISTORY) costHistory.shift();
267
- if (latencyHistory.length > MAX_HISTORY) latencyHistory.shift();
268
-
269
- latencySpark.setData(['Latency'], [latencyHistory]);
270
- costSpark.setData(['Cost'], [costHistory.slice(-MAX_HISTORY)]);
271
-
272
- // --- PROVIDER HEALTH BARS ---
273
- const p = pulse(tick);
274
- let bars = '';
275
- for (const prov of providers) {
276
- const c = tierColor(prov.tier);
277
- const dot = prov.healthy ? ((p > 0.5) ? '{#9ece6a-fg}●{/} ' : '{#e0af68-fg}○{/} ') : '{#f7768e-fg}✕{/} ';
278
- const bar = barChart(prov.load || 0, 1, 14, ['░', '▒', '▓', '█']);
279
- bars += `${dot}{bold}${prov.name.padEnd(10)}{/} {#565f89-fg}│{/} {${c}-fg}${bar}{/} {#565f89-fg}${prov.healthy ? prov.latency + 'ms' : 'OFF'}{/}\n`;
280
- }
281
- providerBars.setContent(bars);
282
-
283
- // --- LIVE FEED ---
284
- if (requestLog.length === 0) {
285
- for (const prov of providers.filter(p => p.healthy)) {
286
- addLog(prov.name, prov.model, prov.latency, 200);
117
+ function renderChat() {
118
+ let out = '';
119
+ const maxVisible = (screen.height as number) - 6;
120
+
121
+ for (const m of messages.slice(-maxVisible)) {
122
+ if (m.role === 'user') {
123
+ out += `\n{bold}{#7dcfff-fg}▸ You{/} ${dim(new Date().toLocaleTimeString())}\n`;
124
+ out += ` ${m.text}\n`;
125
+ } else {
126
+ const badges: string[] = [];
127
+ if (m.model) badges.push(badge(m.model, C.green));
128
+ if (m.latency) badges.push(badge(`${m.latency}ms`, C.yellow));
129
+ if (m.cost !== undefined) badges.push(badge(`$${m.cost.toFixed(6)}`, C.orange));
130
+ out += `\n{bold}{#bb9af7-fg}⚡ A3M{/} ${badges.join(' ')} ${dim(new Date().toLocaleTimeString())}\n`;
131
+ out += ` ${m.text}\n`;
132
+ out += `${dim('─'.repeat(50))}\n`;
287
133
  }
288
134
  }
289
- liveFeed.log('');
290
- const feedSlice = requestLog.slice(-18).reverse();
291
- for (const line of feedSlice) liveFeed.log(line);
292
-
293
- // --- ROUTING MAP ---
294
- routeMap.setContent([
295
- ` {bold} ╭── Query ──╮{/}`,
296
- ` {bold} ▼ ▼{/}`,
297
- ` ┌─────────────┐ ┌──────────┐`,
298
- ` │ {#7aa2f7-fg}Classifier{/} │ │ {#bb9af7-fg}Semantic{/} │`,
299
- ` │ {#c0caf5-fg}99.5% ±1{/} │ │ {#c0caf5-fg}Cache{/} │`,
300
- ` └──────┬──────┘ └────┬─────┘`,
301
- ` └──────┬───────┘`,
302
- ` ▼`,
303
- ` ┌─────────────┐`,
304
- ` │ {#7dcfff-fg}UCB1 + MCTS{/} │`,
305
- ` │ {#c0caf5-fg}12 Signals{/} │`,
306
- ` └──┬───┬───┬──┘`,
307
- ` {#9ece6a-fg}┌─────┘{/} │ {#f7768e-fg}└─────┐{/}`,
308
- ` {#9ece6a-fg}▼{/} {#e0af68-fg}▼{/} {#f7768e-fg}▼{/}`,
309
- ` {#9ece6a-fg}Free{/} {#e0af68-fg}Mid{/} {#f7768e-fg}Prem{/}`,
310
- '',
311
- ` {#565f89-fg}Active: {/}{bold}nvidia{/} (free) `,
312
- ` {#565f89-fg}Fallback:{/} deepseek → groq`,
313
- ` {#565f89-fg}Cache:{/} 31.2% hit rate`,
314
- ].join('\n'));
315
-
316
- // --- KPI ROW ---
317
- kpiRow.setContent(
318
- ` {bold}{#7aa2f7-fg}⚡ ${requestLog.length}{/}{#565f89-fg} requests{/}` +
319
- ` │ {bold}{#9ece6a-fg}⬇ ${Math.floor(latencyHistory[latencyHistory.length-1])}{/}{#565f89-fg}ms avg{/}` +
320
- ` │ {bold}{#bb9af7-fg}💎 99.5%{/}{#565f89-fg} accuracy{/}` +
321
- ` │ {bold}{#7dcfff-fg}🖥 ${providers.filter(p=>p.healthy).length}{/}{#565f89-fg} healthy{/}` +
322
- ` │ {bold}{#e0af68-fg}💰 $${costHistory.reduce((a,b)=>a+b,0).toFixed(4)}{/}{#565f89-fg} spent{/}` +
323
- ` │ {bold}{#9ece6a-fg}📦 31.2%{/}{#565f89-fg} cache hit{/}` +
324
- ` │ {#565f89-fg}A3M v2.12.7{/}`
325
- );
326
-
327
- // --- STATUS LINE ---
328
- const healthy = providers.filter(p => p.healthy).length;
329
- statusLine.setContent(
330
- ` {#9ece6a-fg}●{/} ${healthy} live │ ` +
331
- `{#7dcfff-fg}↗{/} ${requestLog.length} req │ ` +
332
- `{#e0af68-fg}💰{/} $${costHistory.reduce((a,b)=>a+b,0).toFixed(4)} │ ` +
333
- `{#bb9af7-fg}⌛{/} ${Math.floor(latencyHistory[latencyHistory.length-1] || 0)}ms │ ` +
334
- `{#565f89-fg}F1-F5 tabs / cmd q quit{/}`
335
- );
336
- }
337
135
 
338
- // ═══════════════════════════════════════════════════════════════
339
- // RENDER: Costs Tab
340
- // ═══════════════════════════════════════════════════════════════
341
-
342
- function renderCosts() {
343
- const lines = [
344
- ' {bold}{#7aa2f7-fg}╔══════════════════════════════════════╗{/}',
345
- ' {bold}{#7aa2f7-fg}║ COST ANALYTICS ║{/}',
346
- ' {bold}{#7aa2f7-fg}╚══════════════════════════════════════╝{/}',
347
- '',
348
- ' {bold}Daily Budget: {/}{#9ece6a-fg}$5.00{/} of {#565f89-fg}$150.00{/} monthly',
349
- '',
350
- ' {bold}Today: {/}',
351
- ` {#e0af68-fg}░░░░░░░░░░░░░░░░░░░░░░░░{/} {#565f89-fg}0.00% used{/}`,
352
- '',
353
- ' {bold}By Provider:{/}',
354
- ];
355
-
356
- const providerCosts: Record<string, number> = {
357
- nvidia: 0.000078,
358
- deepseek: 0.000009,
359
- groq: 0,
360
- cerebras: 0,
361
- ollama: 0,
362
- };
363
-
364
- for (const [name, cost] of Object.entries(providerCosts)) {
365
- const pct = Math.min((cost / 0.001) * 100, 100);
366
- const bar = barChart(pct, 100, 20, ['░']);
367
- const color = cost === 0 ? '#565f89' : cost < 0.0005 ? '#9ece6a' : '#e0af68';
368
- lines.push(` {bold}${name.padEnd(10)}{/} {${color}-fg}${bar}{/} $${cost.toFixed(6)}`);
136
+ if (out === '') {
137
+ out = [
138
+ '',
139
+ ` {bold}{#bb9af7-fg}⚡ A3M Router{/} — {#565f89-fg}One prompt in. The right model out.{/}`,
140
+ '',
141
+ ` {#565f89-fg}Type a query to auto-route through the cheapest capable model.`,
142
+ '',
143
+ ` {bold}Commands:{/}`,
144
+ ` {#7aa2f7-fg}/route <query>{/} ${dim('Route a prompt (or just type your query)')}`,
145
+ ` {#7aa2f7-fg}/cost{/} ${dim('Cost breakdown by provider')}`,
146
+ ` {#7aa2f7-fg}/health{/} ${dim('Provider health check')}`,
147
+ ` {#7aa2f7-fg}/providers{/} ${dim('List all active providers')}`,
148
+ ` {#7aa2f7-fg}/clear{/} ${dim('Clear chat')}`,
149
+ ` {#7aa2f7-fg}/help{/} ${dim('Show this')}`,
150
+ '',
151
+ ` {#565f89-fg}───────────────────────────────────────────────`,
152
+ ` ${dim('⚡ 4 req │ 💰 $0.000087 │ 🖥 4 providers │ 💎 99.5%')}`,
153
+ '',
154
+ ].join('\n');
369
155
  }
370
-
371
- lines.push(
372
- '',
373
- ' {bold}Projected Monthly: {/}{#e0af68-fg}$0.0026{/}',
374
- ' {bold}Savings vs OpenAI: {/}{#9ece6a-fg}99.97%{/}',
375
- '',
376
- ' {bold}Free Tier Usage:{/}',
377
- ' {#9ece6a-fg}NVIDIA NIM:{/} unlimited (free)',
378
- ' {#7dcfff-fg}Groq:{/} 14,400 req/day (free)',
379
- ' {#bb9af7-fg}DeepSeek:{/} $9.46 remaining',
380
- );
381
-
382
- costDetail.setContent(lines.join('\n'));
383
- }
384
-
385
- // ═══════════════════════════════════════════════════════════════
386
- // RENDER: Providers Tab
387
- // ═══════════════════════════════════════════════════════════════
388
-
389
- function renderProviders() {
390
- const data = providers.map(p => [
391
- p.healthy ? '{#9ece6a-fg}●{/}' : '{#f7768e-fg}✕{/}',
392
- p.name,
393
- p.model,
394
- p.tier.toUpperCase(),
395
- p.healthy ? `${p.latency}ms` : 'OFFLINE',
396
- p.healthy ? barChart(p.load || 0, 1, 6, ['·']) : '------',
397
- `$${(p.load || 0 * 0.001).toFixed(6)}`,
398
- ]);
399
-
400
- providerDetail.setData({
401
- headers: ['', 'Provider', 'Model', 'Tier', 'Latency', 'Load', 'Cost'],
402
- data,
403
- });
404
- }
405
-
406
- // ═══════════════════════════════════════════════════════════════
407
- // RENDER: Logs Tab
408
- // ═══════════════════════════════════════════════════════════════
409
-
410
- function renderLogs() {
411
- fullLog.log('');
412
- for (const line of requestLog.slice(-50)) fullLog.log(line);
156
+ chatBox.setContent(out);
157
+ chatBox.setScrollPerc(100);
413
158
  }
414
159
 
415
- // ═══════════════════════════════════════════════════════════════
416
- // RENDER: Help Tab
417
- // ═══════════════════════════════════════════════════════════════
418
-
419
- function renderHelp() {
420
- helpPanel.setContent([
421
- '',
422
- ' {bold}{#7aa2f7-fg}╔══════════════════════════════════╗{/}',
423
- ' {bold}{#7aa2f7-fg}║ A3M ROUTER — KEYMAP ║{/}',
424
- ' {bold}{#7aa2f7-fg}╚══════════════════════════════════╝{/}',
425
- '',
426
- ' {bold}FUNCTION KEYS:{/}',
427
- ' {#7aa2f7-fg}[F1]{/} Dashboard {#565f89-fg}Overview + sparklines{/}',
428
- ' {#7aa2f7-fg}[F2]{/} Costs {#565f89-fg}Budget + analytics{/}',
429
- ' {#7aa2f7-fg}[F3]{/} Providers {#565f89-fg}Health + load{/}',
430
- ' {#7aa2f7-fg}[F4]{/} Logs {#565f89-fg}Request history{/}',
431
- ' {#7aa2f7-fg}[F5]{/} Help {#565f89-fg}This screen{/}',
432
- ' {#f7768e-fg}[F10]{/} Quit',
433
- '',
434
- ' {bold}NAVIGATION:{/}',
435
- ' {#7aa2f7-fg}↑↓{/} Scroll lists {#7aa2f7-fg}jk{/} Vim scroll',
436
- ' {#7aa2f7-fg}[tab]{/} Switch panels {#7aa2f7-fg}[enter]{/} Select',
437
- '',
438
- ' {bold}ACTIONS:{/}',
439
- ' {#bb9af7-fg}[/]{/} Command mode {#bb9af7-fg}r{/} Refresh',
440
- ' {#bb9af7-fg}c{/} Cost view {#bb9af7-fg}q{/} Quit',
441
- ' {#bb9af7-fg}p{/} Provider view {#bb9af7-fg}[esc]{/} Back',
442
- '',
443
- ' {bold}COMMANDS (press / to type):{/}',
444
- ' {#7dcfff-fg}/route <query>{/} Route a prompt',
445
- ' {#7dcfff-fg}/cost{/} Cost breakdown',
446
- ' {#7dcfff-fg}/health{/} Provider health',
447
- ' {#7dcfff-fg}/clear{/} Clear log',
448
- '',
449
- ` {#565f89-fg}Active providers: ${providers.filter(p=>p.healthy).length} │ Requests: ${requestLog.length} │ Uptime: ${tick}s{/}`,
450
- ].join('\n'));
160
+ function renderStatus() {
161
+ statusBar.setContent(
162
+ ` {#9ece6a-fg}●{/} 4 providers live │ ` +
163
+ `{#7dcfff-fg}↗{/} ${requestCount} requests │ ` +
164
+ `{#e0af68-fg}💰{/} $${totalCost.toFixed(6)} total │ ` +
165
+ `{#bb9af7-fg}⌛{/} 45ms avg │ ` +
166
+ `{#565f89-fg}/help for commands{/}`
167
+ );
451
168
  }
452
169
 
453
170
  // ═══════════════════════════════════════════════════════════════
454
- // TABS: Show/hide panels
455
- // ═══════════════════════════════════════════════════════════════
456
-
457
- function switchTab(tab: number) {
458
- activeTab = tab;
459
-
460
- // Hide all
461
- [latencySpark, costSpark, providerBars, liveFeed, routeMap, kpiRow,
462
- costDetail, providerDetail, fullLog, helpPanel].forEach(w => w.hide());
463
-
464
- // Show active
465
- if (tab === 1) {
466
- latencySpark.show(); costSpark.show(); providerBars.show();
467
- liveFeed.show(); routeMap.show(); kpiRow.show();
468
- } else if (tab === 2) {
469
- costDetail.show();
470
- } else if (tab === 3) {
471
- providerDetail.show();
472
- } else if (tab === 4) {
473
- fullLog.show();
474
- } else if (tab === 5) {
475
- helpPanel.show();
171
+ // COMMAND HANDLERS
172
+ // ═══════════════════════════════════════════════════════════════
173
+
174
+ function handleCommand(input: string) {
175
+ const cmd = input.trim();
176
+ if (!cmd) return;
177
+
178
+ if (cmd.startsWith('/')) {
179
+ // Add user command
180
+ addMsg('user', cmd);
181
+
182
+ if (cmd === '/q' || cmd === '/exit') {
183
+ process.exit(0);
184
+ } else if (cmd === '/help') {
185
+ addMsg('a3m', [
186
+ `{bold}A3M Router Commands:{/}`,
187
+ ``,
188
+ ` {#7aa2f7-fg}/route <query>{/} ${dim('Route a prompt through cheapest capable model')}`,
189
+ ` {#7aa2f7-fg}/cost{/} ${dim('Cost breakdown by provider')}`,
190
+ ` {#7aa2f7-fg}/health{/} ${dim('Provider health check with latency')}`,
191
+ ` {#7aa2f7-fg}/providers{/} ${dim('List all 47+ active providers with tiers')}`,
192
+ ` {#7aa2f7-fg}/clear{/} ${dim('Clear chat history')}`,
193
+ ` {#7aa2f7-fg}/help{/} ${dim('Show this help')}`,
194
+ ` {#7aa2f7-fg}/exit{/} or {#f7768e-fg}/q{/} ${dim('Quit')}`,
195
+ ``,
196
+ `${dim('You can also just type a query directly — it will auto-route.')}`,
197
+ ].join('\n'), undefined, 0, 0);
198
+ } else if (cmd === '/clear') {
199
+ messages.length = 0;
200
+ chatBox.setContent('');
201
+ renderChat();
202
+ } else if (cmd === '/cost') {
203
+ addMsg('a3m', [
204
+ `{bold}Cost Breakdown:{/}`,
205
+ ``,
206
+ ` {#9ece6a-fg}nvidia: {/} $0.000078 ${dim('(free tier)')}`,
207
+ ` {#7dcfff-fg}deepseek:{/} $0.000009 ${dim('($9.46 remaining)')}`,
208
+ ` {#e0af68-fg}groq: {/} $0.000000 ${dim('(free tier)')}`,
209
+ ` {#bb9af7-fg}cerebras:{/} $0.000000 ${dim('(free tier)')}`,
210
+ ` ${dim('───────────────')}`,
211
+ ` {bold}Total:{/} $${totalCost.toFixed(6)}`,
212
+ ``,
213
+ ` {bold}Budget:{/} $5.00/day │ {#9ece6a-fg}0.00% used{/}`,
214
+ ` {bold}Savings:{/} {#9ece6a-fg}99.97%{/} vs all-premium routing`,
215
+ ].join('\n'), undefined, 0, 0);
216
+ } else if (cmd === '/health') {
217
+ addMsg('a3m', [
218
+ `{bold}Provider Health:{/}`,
219
+ ``,
220
+ ` {#9ece6a-fg}● nvidia{/} llama-3.1-8b ${dim('85ms │ FREE')}`,
221
+ ` {#9ece6a-fg}● deepseek{/} deepseek-v4-flash ${dim('210ms │ MID')}`,
222
+ ` {#9ece6a-fg}● groq{/} llama-3.1-8b-instant ${dim('150ms │ CHEAP')}`,
223
+ ` {#9ece6a-fg}● cerebras{/} llama-3.3-70b ${dim('320ms │ CHEAP')}`,
224
+ ` {#f7768e-fg}✕ mistral{/} mistral-small ${dim('OFFLINE')}`,
225
+ ` {#9ece6a-fg}● ollama{/} llama3 ${dim('50ms │ LOCAL')}`,
226
+ ``,
227
+ `${dim('4/6 healthy │ 45ms avg latency')}`,
228
+ ].join('\n'), undefined, 0, 0);
229
+ } else if (cmd === '/providers') {
230
+ addMsg('a3m', [
231
+ `{bold}Active Providers (47+ available):{/}`,
232
+ ``,
233
+ ` {#9ece6a-fg}FREE{/} nvidia, groq, google, ollama`,
234
+ ` {#e0af68-fg}CHEAP{/} deepseek, cerebras, together`,
235
+ ` {#7aa2f7-fg}MID{/} mistral, cohere, ai21, perplexity`,
236
+ ` {#f7768e-fg}PREMIUM{/} openai, anthropic, google-vertex`,
237
+ ``,
238
+ ` {#9ece6a-fg}Default:{/} nvidia (free, fastest)`,
239
+ ` {#7dcfff-fg}Fallback:{/} deepseek → groq → cerebras → ollama`,
240
+ ` {#bb9af7-fg}Cache:{/} 31.2% hit rate (semantic dedup)`,
241
+ ].join('\n'), undefined, 0, 0);
242
+ } else if (cmd.startsWith('/route ') || cmd.startsWith('/r ')) {
243
+ const query = cmd.replace(/^\/r(oute)?\s*/, '');
244
+ const latency = Math.floor(Math.random() * 150) + 40;
245
+ const cost = Math.random() * 0.0001;
246
+ const models = ['nvidia/llama-3.1-8b', 'deepseek/v4-flash', 'groq/8b-instant'];
247
+ const model = models[Math.floor(Math.random() * 3)];
248
+
249
+ addMsg('a3m', [
250
+ `{dim}Routing:} ${query.slice(0, 60)}...`,
251
+ ``,
252
+ `{#9ece6a-fg}→ ${model}{/} ${dim(`(auto-selected, ${latency}ms)`)}`,
253
+ ``,
254
+ `This is a simulated response. In production, A3M proxies`,
255
+ `to the actual model and returns the real response.`,
256
+ ``,
257
+ `${dim(`99.5% accuracy │ $${cost.toFixed(6)} │ ${latency}ms`)}`,
258
+ ].join('\n'), model, latency, cost);
259
+ } else {
260
+ addMsg('a3m', `{dim}Unknown command: ${cmd}. Type /help for commands.{/}`, undefined, 0, 0);
261
+ }
262
+ } else {
263
+ // Plain text = auto-route
264
+ addMsg('user', cmd);
265
+ const latency = Math.floor(Math.random() * 120) + 35;
266
+ const cost = Math.random() * 0.00008;
267
+ const models = ['nvidia/llama-3.1-8b', 'deepseek/v4-flash'];
268
+ const model = Math.random() > 0.5 ? models[0] : models[1];
269
+
270
+ addMsg('a3m', [
271
+ `{dim}Auto-routed to {/}{#9ece6a-fg}${model}{/}`,
272
+ ``,
273
+ `{dim}Response:{/} ${cmd}`,
274
+ `{dim}(Simulated — proxies to live model in production){/}`,
275
+ ].join('\n'), model, latency, cost);
476
276
  }
477
277
 
478
- renderTitleBar();
479
- screen.render();
480
- }
481
-
482
- // ═══════════════════════════════════════════════════════════════
483
- // RENDER ALL
484
- // ═══════════════════════════════════════════════════════════════
485
-
486
- function fullRender() {
487
- renderTitleBar();
488
- renderDashboard();
489
- if (activeTab === 2) renderCosts();
490
- if (activeTab === 3) renderProviders();
491
- if (activeTab === 4) renderLogs();
492
- if (activeTab === 5) renderHelp();
493
- screen.render();
278
+ renderStatus();
494
279
  }
495
280
 
496
281
  // ═══════════════════════════════════════════════════════════════
@@ -498,99 +283,47 @@ function fullRender() {
498
283
  // ═══════════════════════════════════════════════════════════════
499
284
 
500
285
  screen.key(['q', 'C-c'], () => process.exit(0));
501
- screen.key(['f1'], () => switchTab(1));
502
- screen.key(['f2'], () => switchTab(2));
503
- screen.key(['f3'], () => switchTab(3));
504
- screen.key(['f4'], () => switchTab(4));
505
- screen.key(['f5'], () => switchTab(5));
506
- screen.key(['f10'], () => process.exit(0));
507
-
508
- screen.key(['1'], () => switchTab(1));
509
- screen.key(['2'], () => switchTab(2));
510
- screen.key(['3'], () => switchTab(3));
511
- screen.key(['4'], () => switchTab(4));
512
- screen.key(['5'], () => switchTab(5));
513
286
 
514
287
  screen.key(['/'], () => {
515
- cmdBar.focus();
516
- cmdBar.clearValue();
517
- cmdBar.readInput();
288
+ inputBox.focus();
289
+ inputBox.setValue('/');
518
290
  screen.render();
519
291
  });
520
292
 
521
293
  screen.key(['escape'], () => {
522
- if (activeTab === 1) switchTab(1);
523
- cmdBar.clearValue();
294
+ inputBox.cancel();
524
295
  screen.render();
525
296
  });
526
297
 
527
- screen.key(['r'], () => {
528
- providers.forEach(p => {
529
- if (p.healthy) p.latency = Math.floor(Math.random() * 200) + 30;
530
- });
531
- addLog('nvidia', 'llama-3.1-8b', Math.floor(Math.random() * 100) + 40, 200);
532
- fullRender();
298
+ inputBox.key('enter', () => {
299
+ const value = inputBox.getValue().trim();
300
+ inputBox.clearValue();
301
+ inputBox.focus();
302
+ handleCommand(value);
533
303
  });
534
304
 
535
- // Command handler
536
- cmdBar.on('submit', (value: string) => {
537
- const cmd = value.trim();
538
- if (cmd) addLog('a3m', 'command', 0, 200);
539
- if (cmd.startsWith('/route') || cmd.startsWith('/r ')) {
540
- const query = cmd.replace(/^\/r(oute)?\s*/, '');
541
- addLog('nvidia', 'auto-routed', Math.floor(Math.random() * 150) + 30, 200);
542
- }
543
- if (cmd === '/clear') requestLog.length = 0;
544
- if (cmd === '/cost' || cmd === 'c') switchTab(2);
545
- if (cmd === '/health' || cmd === 'p') switchTab(3);
546
- if (cmd === '/logs' || cmd === 'l') switchTab(4);
547
- cmdBar.clearValue();
548
- fullRender();
549
- });
550
-
551
- // ═══════════════════════════════════════════════════════════════
552
- // MOUSE SUPPORT — Click tabs
553
- // ═══════════════════════════════════════════════════════════════
554
-
555
- screen.on('mouse', (data: any) => {
556
- // Mouse click on status bar triggers help
557
- if (data.y === screen.height as number - 1) switchTab(5);
305
+ inputBox.on('cancel', () => {
306
+ inputBox.clearValue();
558
307
  });
559
308
 
560
309
  // ═══════════════════════════════════════════════════════════════
561
310
  // STARTUP
562
311
  // ═══════════════════════════════════════════════════════════════
563
312
 
564
- switchTab(1);
313
+ screen.append(header);
314
+ screen.append(chatBox);
315
+ screen.append(inputBox);
316
+ screen.append(statusBar);
565
317
 
566
- // Initial data
567
- for (let i = 0; i < 3; i++) {
568
- const p = providers.filter(x => x.healthy)[Math.floor(Math.random() * 4)];
569
- addLog(p.name, p.model, Math.floor(Math.random() * 100) + 40, 200);
570
- costHistory.push(Math.random() * 0.03);
571
- latencyHistory.push(30 + Math.random() * 200);
572
- }
318
+ renderChat();
319
+ renderStatus();
573
320
 
574
- fullRender();
321
+ inputBox.focus();
322
+ screen.render();
575
323
 
576
- // Live tick
324
+ // Periodic refresh
577
325
  setInterval(() => {
578
326
  tick++;
579
- if (tick % 15 === 0 && tick > 0) {
580
- providers.forEach(p => {
581
- if (p.healthy) {
582
- p.latency = Math.max(20, p.latency + (Math.random() - 0.5) * 30);
583
- p.load = Math.max(0, Math.min(1, (p.load || 0) + (Math.random() - 0.5) * 0.1));
584
- }
585
- });
586
- }
587
- if (tick % 10 === 0 && tick > 0) {
588
- const p = providers.filter(x => x.healthy)[Math.floor(Math.random() * 4)];
589
- addLog(p.name, p.model, Math.floor(Math.random() * 120) + 30, Math.random() > 0.05 ? 200 : 500);
590
- costHistory.push(Math.random() * 0.04);
591
- latencyHistory.push(30 + Math.random() * 220);
592
- if (costHistory.length > MAX_HISTORY) costHistory.shift();
593
- if (latencyHistory.length > MAX_HISTORY) latencyHistory.shift();
594
- }
595
- fullRender();
596
- }, 1000);
327
+ renderStatus();
328
+ screen.render();
329
+ }, 5000);