openaxies 0.5.4 → 0.7.0

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openaxies",
3
- "version": "0.5.4",
3
+ "version": "0.7.0",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "bin": {
package/src/App.js CHANGED
@@ -1,25 +1,22 @@
1
1
  import React from 'react';
2
2
  import { Box, Text, useInput, useStdout } from 'ink';
3
3
  import { hex } from './config/theme.js';
4
- import { getCommands } from './config/commands.js';
5
4
  import { getModels, getModelById, DEFAULT_MODEL_ID } from './config/models.js';
6
5
  import { callModel } from './providers/index.js';
7
- import { createComposerDock, DOCK_HEIGHT } from './components/ComposerDock.js';
6
+ import { createHeaderBar, HEADER_HEIGHT } from './components/BrandHeader.js';
7
+ import TextInput from 'ink-text-input';
8
8
 
9
9
  const h = React.createElement;
10
-
11
10
  export default AppRoot;
12
11
 
13
- const LOGO = [
14
- ' \u2588\u2588\u2588 \u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588 \u2588 \u2588 \u2588\u2588 \u2588 \u2588 \u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588',
15
- '\u2588 \u2588 \u2588 \u2588 \u2588 \u2588\u2588 \u2588 \u2588 \u2588 \u2588 \u2588 \u2588 \u2588 \u2588 ',
16
- '\u2588 \u2588 \u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588 \u2588 \u2588 \u2588 \u2588\u2588\u2588\u2588 \u2588 \u2588 \u2588\u2588\u2588\u2588 \u2588\u2588\u2588',
17
- '\u2588 \u2588 \u2588 \u2588 \u2588 \u2588\u2588 \u2588 \u2588 \u2588 \u2588 \u2588 \u2588 \u2588',
18
- ' \u2588\u2588\u2588 \u2588 \u2588\u2588\u2588\u2588\u2588 \u2588 \u2588 \u2588 \u2588 \u2588 \u2588 \u2588 \u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588 ',
12
+ const STARTUP_LOGO = [
13
+ ' \u2588\u2588\u2588\u2588\u2588\u2588\u2557',
14
+ ' \u2588\u2588\u2594\u2594\u2594\u2594\u2588\u2588\u2557',
15
+ ' \u2588\u2588\u2591 \u2588\u2588\u2591',
16
+ ' \u2588\u2588\u2591 \u2588\u2588\u2591',
17
+ ' \u255a\u2588\u2588\u2588\u2588\u2588\u2588\u2594\u259d',
19
18
  ];
20
19
 
21
- const LOGO_COLORS = ['#00F0FF', '#00DDFF', '#00CCFF', '#00BBFF', '#00AAFF'];
22
-
23
20
  function buildHistory(messages, newText) {
24
21
  const h = [];
25
22
  for (let i = 0; i < messages.length; i++) {
@@ -36,20 +33,28 @@ function buildHistory(messages, newText) {
36
33
  return h;
37
34
  }
38
35
 
39
- function formatTimer(s) {
36
+ function fmtTime(s) {
40
37
  if (typeof s !== 'number' || s < 0) return '0.0s';
41
38
  return s.toFixed(1) + 's';
42
39
  }
43
40
 
44
- function hr(cols, color) {
41
+ function hr(cols) {
45
42
  const n = typeof cols === 'number' && cols > 0 ? cols : 80;
46
- const c = typeof color === 'string' ? color : '#1A1A28';
47
43
  let s = '';
48
44
  for (let i = 0; i < n; i++) s = s + '\u2500';
49
- return h(Text, { color: c }, s);
45
+ return h(Text, { color: '#1A1A28' }, s);
50
46
  }
51
47
 
52
- const SPINNER = ['\u25CB', '\u25D4', '\u25D0', '\u25D5', '\u25CF', '\u25D5', '\u25D0', '\u25D4'];
48
+ const SPINNER = ['\u25CB', '\u25D4', '\u25D0', '\u25D5', '\u25CF', '\u25D5', '\u25D0', '\u25D4', '\u25D1', '\u25D2', '\u25D3'];
49
+
50
+ const COMMANDS = [
51
+ { id: 'model', trigger: '/model', desc: 'Switch model' },
52
+ { id: 'thoughts', trigger: '/thoughts', desc: 'Toggle thinking visibility' },
53
+ { id: 'resume', trigger: '/resume', desc: 'Re-run last query' },
54
+ { id: 'clear', trigger: '/clear', desc: 'Clear conversation' },
55
+ { id: 'help', trigger: '/help', desc: 'Show commands' },
56
+ { id: 'exit', trigger: '/exit', desc: 'Quit' },
57
+ ];
53
58
 
54
59
  function AppRoot() {
55
60
  const { stdout } = useStdout();
@@ -67,51 +72,33 @@ function AppRoot() {
67
72
  const [showOverlay, setShowOverlay] = React.useState(false);
68
73
  const [overlayIndex, setOverlayIndex] = React.useState(0);
69
74
  const [toolInfo, setToolInfo] = React.useState(null);
75
+ const [showThoughts, setShowThoughts] = React.useState(true);
76
+ const [startupDone, setStartupDone] = React.useState(false);
70
77
 
71
- const commands = getCommands();
72
- const LOGO_H = 5;
73
- const overlayH = showOverlay ? 6 : 0;
74
- const fixedH = LOGO_H + overlayH + DOCK_HEIGHT;
75
- const availLines = Math.max(1, rows - fixedH);
78
+ const SEP_H = 1;
79
+ const FIXED_H = startupDone ? (HEADER_HEIGHT + SEP_H + 1 + 1) : 0;
80
+ const DOCK_H = 1;
81
+ const STATUS_H = 1;
82
+ const totalFixed = startupDone ? (HEADER_HEIGHT + SEP_H + DOCK_H + STATUS_H) : 11;
83
+ const availLines = Math.max(10, rows - totalFixed);
76
84
 
77
85
  const abortRef = React.useRef(null);
78
86
  const connRef = React.useRef(null);
79
87
  const spinnerRef = React.useRef(null);
80
88
  const timerRef = React.useRef(null);
81
89
  const thinkStartRef = React.useRef(null);
90
+ const lastQueryRef = React.useRef('');
82
91
 
83
92
  React.useEffect(function () {
84
- if (isThinking === false) {
85
- if (spinnerRef.current !== null) {
86
- clearInterval(spinnerRef.current);
87
- spinnerRef.current = null;
88
- }
89
- if (timerRef.current !== null) {
90
- clearInterval(timerRef.current);
91
- timerRef.current = null;
92
- }
93
- return;
94
- }
93
+ if (isThinking === false) return;
95
94
  let idx = 0;
96
- spinnerRef.current = setInterval(function () {
97
- idx = (idx + 1) % SPINNER.length;
98
- setSpinnerIdx(idx);
95
+ const si = setInterval(function () { idx = (idx + 1) % SPINNER.length; setSpinnerIdx(idx); }, 120);
96
+ const ti = setInterval(function () {
97
+ if (thinkStartRef.current !== null) setThinkingElapsed((Date.now() - thinkStartRef.current) / 1000);
99
98
  }, 100);
100
- timerRef.current = setInterval(function () {
101
- if (thinkStartRef.current !== null) {
102
- setThinkingElapsed((Date.now() - thinkStartRef.current) / 1000);
103
- }
104
- }, 100);
105
- return function () {
106
- if (spinnerRef.current !== null) {
107
- clearInterval(spinnerRef.current);
108
- spinnerRef.current = null;
109
- }
110
- if (timerRef.current !== null) {
111
- clearInterval(timerRef.current);
112
- timerRef.current = null;
113
- }
114
- };
99
+ spinnerRef.current = si;
100
+ timerRef.current = ti;
101
+ return function () { clearInterval(si); clearInterval(ti); };
115
102
  }, [isThinking]);
116
103
 
117
104
  function cycleModel() {
@@ -133,9 +120,16 @@ function AppRoot() {
133
120
  return o > c;
134
121
  }
135
122
 
123
+ function filterText(t) {
124
+ if (showThoughts === true) return t;
125
+ return t.split('<think>').join('').split('</think>').join('');
126
+ }
127
+
136
128
  async function handleSubmit(text) {
137
129
  const safe = typeof text === 'string' ? text : '';
138
130
  if (safe.length === 0 || streamingActive === true) return;
131
+ if (startupDone === false) setStartupDone(true);
132
+ lastQueryRef.current = safe;
139
133
  setStreamingActive(true);
140
134
  setToolInfo(null);
141
135
  setStreamText(' connecting');
@@ -148,16 +142,13 @@ function AppRoot() {
148
142
  return ' connecting';
149
143
  });
150
144
  }, 400);
151
- setMessages(function (p) {
152
- return p.concat([{ role: 'user', content: safe, id: 'u-' + Date.now() }]);
153
- });
145
+ setMessages(function (p) { return p.concat([{ role: 'user', content: safe, id: 'u-' + Date.now() }]); });
154
146
  setInputBuffer('');
155
147
  const modelInfo = getModelById(activeModel);
156
148
  if (modelInfo === null) {
157
149
  if (connRef.current !== null) clearInterval(connRef.current), connRef.current = null;
158
- setStreamingActive(false);
159
- setStreamText('');
160
- setMessages(function (p) { return p.concat([{ role: 'assistant', content: 'No model selected. Use /model.', id: 'e-' + Date.now() }]); });
150
+ setStreamingActive(false); setStreamText('');
151
+ setMessages(function (p) { return p.concat([{ role: 'assistant', content: 'No model selected.', id: 'e-' + Date.now() }]); });
161
152
  return;
162
153
  }
163
154
  const history = buildHistory(messages, safe);
@@ -170,90 +161,72 @@ function AppRoot() {
170
161
  let thinkStarted = false;
171
162
  for await (const event of gen) {
172
163
  if (abortController.signal.aborted === true) break;
173
- if (firstEvent === true) {
174
- firstEvent = false;
175
- if (connRef.current !== null) clearInterval(connRef.current), connRef.current = null;
176
- }
177
- if (event.type === 'tool') {
178
- setToolInfo({ tool: event.tool, used: event.used, sites: event.sites, query: event.query });
179
- continue;
180
- }
164
+ if (firstEvent === true) { firstEvent = false; if (connRef.current !== null) clearInterval(connRef.current), connRef.current = null; }
165
+ if (event.type === 'tool') { setToolInfo({ tool: event.tool, used: event.used, sites: event.sites, query: event.query }); continue; }
181
166
  if (event.type === 'thinking' || event.type === 'token') {
182
- const content = typeof event.content === 'string' ? event.content : '';
183
- accumulated = accumulated + content;
167
+ const c = typeof event.content === 'string' ? event.content : '';
168
+ accumulated = accumulated + c;
184
169
  const inside = checkThink(accumulated);
185
170
  setStreamText(accumulated);
186
171
  setIsThinking(inside);
187
- if (inside === true && thinkStarted === false) {
188
- thinkStarted = true;
189
- thinkStartRef.current = Date.now();
190
- setThinkingElapsed(0);
191
- }
192
- if (inside === false && thinkStarted === true) {
193
- thinkStarted = false;
194
- thinkStartRef.current = null;
195
- setThinkingElapsed(0);
196
- }
197
- await new Promise(function (r) { setTimeout(r, 0); });
172
+ if (inside === true && thinkStarted === false) { thinkStarted = true; thinkStartRef.current = Date.now(); setThinkingElapsed(0); }
173
+ if (inside === false && thinkStarted === true) { thinkStarted = false; thinkStartRef.current = null; setThinkingElapsed(0); }
198
174
  }
199
175
  if (event.type === 'done') break;
200
176
  if (event.type === 'timeout') {
201
177
  if (accumulated.length > 0) accumulated = accumulated + '\n\n\u2014 stream timed out \u2014';
202
- setStreamText(accumulated);
203
- break;
178
+ setStreamText(accumulated); break;
204
179
  }
205
180
  }
206
181
  if (connRef.current !== null) clearInterval(connRef.current), connRef.current = null;
207
- setIsThinking(false);
208
- thinkStartRef.current = null;
209
- setThinkingElapsed(0);
210
- setStreamingActive(false);
182
+ setIsThinking(false); thinkStartRef.current = null; setThinkingElapsed(0); setStreamingActive(false);
211
183
  setStreamText('');
212
184
  if (accumulated.length > 0) {
213
185
  setMessages(function (p) { return p.concat([{ role: 'assistant', content: accumulated, id: 'a-' + Date.now() }]); });
214
186
  }
215
187
  } catch (err) {
216
188
  if (connRef.current !== null) clearInterval(connRef.current), connRef.current = null;
217
- setIsThinking(false);
218
- thinkStartRef.current = null;
219
- setThinkingElapsed(0);
220
- setStreamingActive(false);
221
- setStreamText('');
189
+ setIsThinking(false); thinkStartRef.current = null; setThinkingElapsed(0); setStreamingActive(false); setStreamText('');
222
190
  const errMsg = err !== null && err !== undefined ? (err.message || String(err)) : 'Unknown error';
223
- setMessages(function (p) {
224
- return p.concat([{ role: 'assistant', content: 'Error: ' + errMsg + '\n\nSpace cold-starting? Try again.', id: 'er-' + Date.now() }]);
225
- });
191
+ setMessages(function (p) { return p.concat([{ role: 'assistant', content: 'Error: ' + errMsg, id: 'er-' + Date.now() }]); });
226
192
  }
227
193
  }
228
194
 
229
195
  function handleChange(v) {
230
- const s = typeof v === 'string' ? v : '';
231
- setInputBuffer(s);
232
- if (s.length === 1 && s[0] === '/' && showOverlay === false) { setShowOverlay(true); setOverlayIndex(0); }
233
- if (showOverlay === true && (s.length === 0 || s[0] !== '/')) { setShowOverlay(false); }
196
+ setInputBuffer(v);
197
+ if (v.length === 1 && v[0] === '/') { setShowOverlay(true); setOverlayIndex(0); }
198
+ if (showOverlay === true && (v.length === 0 || v[0] !== '/')) { setShowOverlay(false); }
234
199
  }
235
200
 
236
201
  function handleCmd(item) {
237
202
  if (item === null || item === undefined) return;
238
- const t = item.value;
203
+ const t = item.trigger;
239
204
  if (typeof t !== 'string') return;
240
205
  if (t === '/exit') process.exit(0);
241
- if (t === '/clear') setMessages([]);
206
+ if (t === '/clear') { setMessages([]); }
242
207
  if (t === '/model') cycleModel();
243
- if (t === '/help') {
244
- setMessages(function (p) { return p.concat([{ role: 'assistant', content: 'Commands: /help /clear /model /exit', id: 'h-' + Date.now() }]); });
208
+ if (t === '/thoughts') { setShowThoughts(function (p) { return !p; }); }
209
+ if (t === '/resume') {
210
+ const q = lastQueryRef.current;
211
+ if (q.length > 0 && streamingActive === false) handleSubmit(q);
245
212
  }
246
- setInputBuffer('');
247
- setShowOverlay(false);
248
- setOverlayIndex(0);
213
+ if (t === '/help') { setMessages(function (p) { return p.concat([{ role: 'assistant', content: 'Commands: /help /clear /model /thoughts /resume /exit', id: 'h-' + Date.now() }]); }); }
214
+ setInputBuffer(''); setShowOverlay(false); setOverlayIndex(0);
249
215
  }
250
216
 
251
217
  useInput(function handleInput(input, key) {
252
218
  if (showOverlay === true) {
253
219
  if (key.escape === true) { setShowOverlay(false); setInputBuffer(''); setOverlayIndex(0); return; }
254
- if (key.upArrow === true) { setOverlayIndex(function (p) { return p > 0 ? p - 1 : commands.length - 1; }); return; }
255
- if (key.downArrow === true) { setOverlayIndex(function (p) { return p < commands.length - 1 ? p + 1 : 0; }); return; }
256
- if (key.return === true) { const cmd = commands[overlayIndex]; if (cmd !== null && cmd !== undefined) handleCmd({ value: cmd.trigger }); return; }
220
+ if (key.upArrow === true) { setOverlayIndex(function (p) { return p > 0 ? p - 1 : COMMANDS.length - 1; }); return; }
221
+ if (key.downArrow === true) { setOverlayIndex(function (p) { return p < COMMANDS.length - 1 ? p + 1 : 0; }); return; }
222
+ if (key.return === true) { const cmd = COMMANDS[overlayIndex]; if (cmd !== null && cmd !== undefined) handleCmd(cmd); return; }
223
+ return;
224
+ }
225
+ if (key.escape === true) {
226
+ if (streamingActive === true) {
227
+ if (abortRef.current !== null) { try { abortRef.current.abort(); } catch (_) {} abortRef.current = null; }
228
+ setStreamingActive(false); setStreamText(''); setIsThinking(false);
229
+ }
257
230
  return;
258
231
  }
259
232
  if (key.return === true) {
@@ -261,144 +234,238 @@ function AppRoot() {
261
234
  if (s.length > 0 && streamingActive === false) handleSubmit(s);
262
235
  return;
263
236
  }
264
- if (key.ctrl === true && (input === 'c' || input === 'C')) {
265
- if (abortRef.current !== null) { try { abortRef.current.abort(); } catch (_) {} abortRef.current = null; setStreamingActive(false); setStreamText(''); }
266
- process.exit(0);
237
+ if (key.ctrl === true) {
238
+ if (input === 'c' || input === 'C') {
239
+ if (abortRef.current !== null) { try { abortRef.current.abort(); } catch (_) {} abortRef.current = null; }
240
+ setStreamingActive(false); setStreamText(''); setIsThinking(false);
241
+ process.exit(0);
242
+ }
243
+ if (input === 't' || input === 'T') { setShowThoughts(function (p) { return !p; }); return; }
244
+ if (input === 'p' || input === 'P') { cycleModel(); return; }
245
+ if (input === 'r' || input === 'R') {
246
+ const q = lastQueryRef.current;
247
+ if (q.length > 0 && streamingActive === false) handleSubmit(q);
248
+ return;
249
+ }
250
+ if (input === 'l' || input === 'L') { setMessages([]); return; }
251
+ if (input === 'k' || input === 'K') { setInputBuffer('/'); setShowOverlay(true); setOverlayIndex(0); return; }
267
252
  }
268
253
  });
269
254
 
270
- const activeInfo = getModelById(activeModel);
271
- const activeLabel = activeInfo !== null ? activeInfo.label : 'OpenAxies';
255
+ const modelInfo = getModelById(activeModel);
256
+ const modelLabel = modelInfo !== null ? modelInfo.label : 'OpenAxies Llama';
257
+
258
+ // Build viewport sections
259
+ const sections = [];
272
260
 
273
- const logoEls = [];
274
- for (let r = 0; r < LOGO.length; r++) {
275
- logoEls.push(
276
- h(Text, { key: 'l' + r, color: LOGO_COLORS[r % LOGO_COLORS.length], bold: true }, LOGO[r])
277
- );
261
+ // Overlay items
262
+ if (showOverlay === true) {
263
+ sections.push({ t: 'sep' });
264
+ sections.push({ t: 'overlay_header' });
265
+ for (let i = 0; i < COMMANDS.length; i++) {
266
+ const c = COMMANDS[i];
267
+ sections.push({ t: 'cmd', trigger: c.trigger, desc: c.desc, sel: i === overlayIndex });
268
+ }
269
+ sections.push({ t: 'sep' });
278
270
  }
279
- const logo = h(Box, { flexDirection: 'column', width: '100%', flexShrink: 0, paddingLeft: 0 }, ...logoEls);
280
271
 
281
- const viewLines = [];
272
+ // Messages
282
273
  for (let i = 0; i < messages.length; i++) {
283
274
  const msg = messages[i];
284
275
  if (msg === null || typeof msg !== 'object') continue;
285
276
  const content = typeof msg.content === 'string' ? msg.content : '';
286
277
  if (content.length === 0) continue;
287
278
  if (msg.role === 'user') {
288
- viewLines.push({ t: 'user', text: '\u25B7 ' + content });
279
+ sections.push({ t: 'sep' });
280
+ sections.push({ t: 'user', text: content });
289
281
  } else {
290
- viewLines.push({ t: 'text', text: content });
282
+ if (sections.length > 0 && sections[sections.length - 1].t !== 'sep') sections.push({ t: 'sep' });
283
+ sections.push({ t: 'assistant', text: filterText(content) });
291
284
  }
292
285
  }
293
286
 
294
- const hasStream = typeof streamText === 'string' && streamText.length > 0;
295
- if (hasStream === true) {
287
+ // Streaming
288
+ if (typeof streamText === 'string' && streamText.length > 0) {
289
+ const clean = filterText(streamText);
296
290
  if (toolInfo !== null && toolInfo.used === true && toolInfo.query.length > 0) {
297
- viewLines.push({ t: 'tool', text: '\u2500\u2500 ' + toolInfo.tool + ': "' + toolInfo.query + '" (' + toolInfo.sites + ' sites)' });
291
+ sections.push({ t: 'tool', tool: toolInfo.tool, query: toolInfo.query, sites: toolInfo.sites });
298
292
  }
299
293
  if (isThinking === true) {
300
- viewLines.push({ t: 'think', spin: SPINNER[spinnerIdx], elapsed: formatTimer(thinkingElapsed) });
301
- const clean = streamText.split('<think>').join('').split('</think>').join('');
302
- viewLines.push({ t: 'think-text', text: clean });
294
+ sections.push({ t: 'thinking', spin: SPINNER[spinnerIdx], elapsed: fmtTime(thinkingElapsed), content: clean });
303
295
  } else {
304
- const clean = streamText.split('<think>').join('').split('</think>').join('');
305
- viewLines.push({ t: 'text', text: clean });
296
+ sections.push({ t: 'stream', text: clean });
306
297
  }
307
298
  }
308
299
 
309
- if (viewLines.length === 0) {
310
- viewLines.push({ t: 'idle', text: ' \u25B8 type a message to begin | /model to switch models | /help for commands' });
311
- viewLines.push({ t: 'idle', text: '' });
312
- viewLines.push({ t: 'idle-sm', text: ' Models: OpenAxies Llama | OpenAxies GPT | OpenAxies DeepSeek' });
313
- viewLines.push({ t: 'idle-sm', text: ' Current: ' + activeLabel });
300
+ // Startup screen
301
+ if (startupDone === false && messages.length === 0) {
302
+ sections.push({ t: 'startup' });
314
303
  }
315
304
 
316
- const visible = Math.max(1, availLines - 1);
317
- const display = viewLines.slice(-visible);
318
- const viewEls = [];
305
+ // Ready state
306
+ if (sections.length === 0) {
307
+ sections.push({ t: 'sep' });
308
+ sections.push({ t: 'ready' });
309
+ sections.push({ t: 'sep' });
310
+ }
311
+
312
+ // Estimate and limit sections
313
+ function estLines(s) {
314
+ if (s.t === 'sep' || s.t === 'assistant' || s.t === 'stream' || s.t === 'tool' || s.t === 'ready' || s.t === 'overlay_header') return 1;
315
+ if (s.t === 'cmd') return 1;
316
+ if (s.t === 'user') return 2;
317
+ if (s.t === 'thinking') return 2;
318
+ if (s.t === 'startup') return 11;
319
+ return 1;
320
+ }
319
321
 
320
- for (let i = 0; i < display.length; i++) {
321
- const l = display[i];
322
- if (l.t === 'think') {
323
- viewEls.push(
324
- h(Box, { key: 'k-' + i, height: 1, paddingLeft: 2 },
325
- h(Text, { color: '#FF9F43', bold: true }, l.spin + ' Thinking \u2022 ' + l.elapsed)
322
+ let totalEst = 0;
323
+ let lastN = 0;
324
+ for (let i = sections.length - 1; i >= 0; i--) {
325
+ const l = estLines(sections[i]);
326
+ if (totalEst + l > availLines) break;
327
+ totalEst += l;
328
+ lastN = sections.length - i;
329
+ }
330
+ const visible = sections.slice(sections.length - lastN);
331
+
332
+ // Render sections
333
+ const sectionEls = [];
334
+ for (let i = 0; i < visible.length; i++) {
335
+ const s = visible[i];
336
+ const ki = 's-' + i;
337
+
338
+ if (s.t === 'sep') {
339
+ sectionEls.push(h(Box, { key: ki, height: 1 }, hr(cols)));
340
+ } else if (s.t === 'user') {
341
+ sectionEls.push(
342
+ h(Box, { key: ki + '-h', height: 1, paddingLeft: 1 },
343
+ h(Text, { color: '#888899', bold: true }, 'You')
344
+ )
345
+ );
346
+ sectionEls.push(
347
+ h(Box, { key: ki + '-t', height: 1, paddingLeft: 2 },
348
+ h(Text, { color: hex.neonBlue, wrap: 'wrap' }, s.text)
349
+ )
350
+ );
351
+ } else if (s.t === 'assistant' || s.t === 'stream') {
352
+ sectionEls.push(
353
+ h(Box, { key: ki, height: 1, paddingLeft: 1 },
354
+ h(Text, { color: hex.primary, wrap: 'wrap' }, s.text)
355
+ )
356
+ );
357
+ } else if (s.t === 'thinking') {
358
+ sectionEls.push(
359
+ h(Box, { key: ki + '-h', height: 1, paddingLeft: 1 },
360
+ h(Text, { color: '#FF9F43', bold: true }, s.spin + ' Thinking \u2022 ' + s.elapsed)
361
+ )
362
+ );
363
+ sectionEls.push(
364
+ h(Box, { key: ki + '-c', height: 1, paddingLeft: 2 },
365
+ h(Text, { color: '#FF9F43', wrap: 'wrap' }, s.content)
326
366
  )
327
367
  );
328
- } else if (l.t === 'think-text') {
329
- viewEls.push(
330
- h(Box, { key: 'kt-' + i, height: 1, paddingLeft: 3 },
331
- h(Text, { color: '#FF9F43' }, l.text)
368
+ } else if (s.t === 'tool') {
369
+ sectionEls.push(
370
+ h(Box, { key: ki, height: 1, paddingLeft: 1 },
371
+ h(Text, { color: '#5B5B8A' }, '\u2500\u2500 ' + s.tool + ' \u2014 ' + (s.query || '') + ' (' + (s.sites || 0) + ' sites)')
332
372
  )
333
373
  );
334
- } else if (l.t === 'tool') {
335
- viewEls.push(
336
- h(Box, { key: 'tl-' + i, height: 1, paddingLeft: 2 },
337
- h(Text, { color: '#5B5B8A' }, l.text)
374
+ } else if (s.t === 'overlay_header') {
375
+ sectionEls.push(
376
+ h(Box, { key: ki, height: 1, paddingLeft: 1 },
377
+ h(Text, { color: '#888899', bold: true }, 'Commands')
338
378
  )
339
379
  );
340
- } else if (l.t === 'user') {
341
- viewEls.push(
342
- h(Box, { key: 'u-' + i, height: 1, paddingLeft: 1 },
343
- h(Text, { color: hex.neonBlue }, l.text)
380
+ } else if (s.t === 'cmd') {
381
+ const prefix = s.sel === true ? '\u276F ' : ' ';
382
+ const col = s.sel === true ? hex.neonBlue : '#666688';
383
+ sectionEls.push(
384
+ h(Box, { key: ki, height: 1, paddingLeft: 2 },
385
+ h(Text, { color: col }, prefix + s.trigger),
386
+ h(Text, { color: '#444466' }, ' ' + (s.desc || ''))
344
387
  )
345
388
  );
346
- } else if (l.t === 'text') {
347
- viewEls.push(
348
- h(Box, { key: 'a-' + i, height: 1, paddingLeft: 2 },
349
- h(Text, { color: hex.primary }, l.text)
389
+ } else if (s.t === 'ready') {
390
+ sectionEls.push(
391
+ h(Box, { key: ki, height: 1, paddingLeft: 1 },
392
+ h(Text, { color: '#00FF88', bold: true }, 'Ready')
350
393
  )
351
394
  );
352
- } else if (l.t === 'idle') {
353
- viewEls.push(
354
- h(Box, { key: 'i-' + i, height: 1, paddingLeft: 1 },
355
- h(Text, { color: '#555577' }, l.text)
395
+ } else if (s.t === 'startup') {
396
+ for (let li = 0; li < STARTUP_LOGO.length; li++) {
397
+ sectionEls.push(
398
+ h(Box, { key: ki + '-l' + li, height: 1, paddingLeft: 1 },
399
+ h(Text, { color: '#00BBFF', bold: true }, STARTUP_LOGO[li])
400
+ )
401
+ );
402
+ }
403
+ sectionEls.push(h(Box, { key: ki + '-e1', height: 1 }, null));
404
+ sectionEls.push(
405
+ h(Box, { key: ki + '-t1', height: 1, paddingLeft: 1 },
406
+ h(Text, { color: '#FFFFFF', bold: true }, 'OpenAxies')
407
+ )
408
+ );
409
+ sectionEls.push(
410
+ h(Box, { key: ki + '-t2', height: 1, paddingLeft: 1 },
411
+ h(Text, { color: '#777788' }, 'local agent runtime')
356
412
  )
357
413
  );
358
- } else if (l.t === 'idle-sm') {
359
- viewEls.push(
360
- h(Box, { key: 'is-' + i, height: 1, paddingLeft: 1 },
361
- h(Text, { color: '#333355' }, l.text)
414
+ sectionEls.push(h(Box, { key: ki + '-e2', height: 1 }, null));
415
+ sectionEls.push(
416
+ h(Box, { key: ki + '-t3', height: 1, paddingLeft: 1 },
417
+ h(Text, { color: '#555577' }, 'Press Enter to begin')
362
418
  )
363
419
  );
364
420
  }
365
421
  }
366
422
 
423
+ // Fill remaining
424
+ while (sectionEls.length < availLines) {
425
+ sectionEls.push(h(Box, { key: 'fl-' + sectionEls.length, height: 1 }, h(Text, {}, '')));
426
+ }
427
+
367
428
  const viewport = h(Box, {
368
429
  flexGrow: 1,
369
430
  height: availLines,
370
431
  flexDirection: 'column',
371
432
  overflow: 'hidden',
372
- paddingTop: 0,
373
- paddingBottom: 0,
374
- }, ...viewEls);
375
-
376
- const slashOverlay = showOverlay === true ? createSlashOverlay(commands, overlayIndex) : null;
377
-
378
- const dock = createComposerDock({
379
- value: inputBuffer,
380
- onChange: handleChange,
381
- onSubmit: function () {},
382
- inputActive: showOverlay === false,
383
- isStreaming: streamingActive,
384
- terminalWidth: cols,
385
- modelLabel: activeLabel + ' \u25CF',
386
- modelStatus: true,
387
- });
433
+ }, ...sectionEls);
388
434
 
389
- return h(Box, {
390
- flexDirection: 'column',
391
- width: '100%',
392
- height: rows,
393
- backgroundColor: hex.black,
394
- overflow: 'hidden',
395
- },
396
- logo,
397
- h(Box, { height: 1 }, hr(cols, '#1A1A2E')),
435
+ // Main layout
436
+ const headerBar = startupDone === true ? createHeaderBar(modelLabel) : null;
437
+ const headerSep = startupDone === true ? h(Box, { height: 1 }, hr(cols)) : null;
438
+
439
+ // Input line
440
+ const inputLine = h(Box, { height: DOCK_H, flexShrink: 0, paddingLeft: 2, paddingRight: 2 },
441
+ h(Text, { color: hex.greenOnline }, '\u258C'),
442
+ h(Text, { color: '#555577' }, ' Ask OpenAxies'),
443
+ h(TextInput, {
444
+ value: inputBuffer,
445
+ onChange: handleChange,
446
+ onSubmit: function () {},
447
+ placeholder: '',
448
+ focus: showOverlay === false,
449
+ showCursor: true,
450
+ })
451
+ );
452
+
453
+ // Shortcuts footer
454
+ const modelShort = modelLabel.replace('OpenAxies ', '');
455
+ const footerLine = h(Box, { height: STATUS_H, flexShrink: 0, paddingLeft: 2, paddingRight: 2 },
456
+ h(Text, { color: '#444466' }, 'esc \u2248 '),
457
+ h(Text, { color: '#444466' }, 'ctrl+t '),
458
+ h(Text, { color: '#444466' }, 'ctrl+p '),
459
+ h(Text, { color: '#444466' }, 'ctrl+k'),
460
+ h(Box, { flexGrow: 1 }),
461
+ h(Text, { color: '#555577' }, modelShort.toLowerCase() + ' \u2022 local'),
462
+ );
463
+
464
+ return h(Box, { flexDirection: 'column', width: '100%', height: rows, overflow: 'hidden' },
465
+ headerBar,
466
+ headerSep,
398
467
  viewport,
399
- slashOverlay,
400
- dock
468
+ inputLine,
469
+ footerLine,
401
470
  );
402
471
  }
403
-
404
- import { createSlashOverlay } from './components/SlashOverlay.js';
@@ -0,0 +1,17 @@
1
+ import React from 'react';
2
+ import { Box, Text } from 'ink';
3
+
4
+ const h = React.createElement;
5
+
6
+ export function createHeaderBar(modelLabel) {
7
+ const label = typeof modelLabel === 'string' && modelLabel.length > 0 ? modelLabel : 'OpenAxies Llama';
8
+ return h(Box, { height: 1, paddingLeft: 1, paddingTop: 0, paddingBottom: 0 },
9
+ h(Text, { color: '#00BBFF', bold: true }, 'OpenAxies'),
10
+ h(Text, { color: '#555577' }, ' \u2022 '),
11
+ h(Text, { color: '#FFFFFF' }, label),
12
+ h(Text, { color: '#555577' }, ' \u2022 '),
13
+ h(Text, { color: '#777788' }, 'local'),
14
+ );
15
+ }
16
+
17
+ export const HEADER_HEIGHT = 1;
@@ -4,15 +4,12 @@ import { runWebSearchGraph, shouldUseWebSearch } from './websearch.js';
4
4
  const MODEL_ROUTES = Object.freeze({
5
5
  'openaxis/openaxis-llama': Object.freeze([
6
6
  'https://universal-618-clarity-main.hf.space/v1/chat/completions',
7
- 'https://universal-618-clarity-4.hf.space/v1/chat/completions',
8
7
  ]),
9
8
  'openaxis/openaxis-gpt': Object.freeze([
10
9
  'https://universal-618-clarity-2.hf.space/v1/chat/completions',
11
- 'https://universal-618-clarity-5.hf.space/v1/chat/completions',
12
10
  ]),
13
11
  'openaxis/openaxis-deepseek': Object.freeze([
14
12
  'https://universal-618-clarity-3.hf.space/v1/chat/completions',
15
- 'https://universal-618-clarity-6.hf.space/v1/chat/completions',
16
13
  ]),
17
14
  });
18
15
 
@@ -1,5 +1,5 @@
1
- const CONNECT_TIMEOUT = 120000;
2
- const READ_TIMEOUT = 60000;
1
+ const CONNECT_TIMEOUT = 10000;
2
+ const READ_TIMEOUT = 30000;
3
3
 
4
4
  function checkEndpoint(endpoint) {
5
5
  if (typeof endpoint !== 'string') {
@@ -44,6 +44,9 @@ export async function* streamResponse(endpoint, body, signal) {
44
44
  headers: {
45
45
  'Content-Type': 'application/json',
46
46
  'Accept': 'text/event-stream',
47
+ 'Authorization': typeof process !== 'undefined' && process.env && process.env.OPENAXIES_HF_TOKEN
48
+ ? 'Bearer ' + process.env.OPENAXIES_HF_TOKEN
49
+ : undefined,
47
50
  },
48
51
  body: JSON.stringify(body),
49
52
  signal: combinedSignal,