openaxies 1.0.2 → 1.0.4

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/bin/openaxis.js CHANGED
@@ -1,58 +1,9 @@
1
1
  #!/usr/bin/env node
2
2
  import React from 'react';
3
3
  import { render } from 'ink';
4
- import fs from 'fs';
5
- import path from 'path';
6
4
  import App from '../src/App.js';
7
5
 
8
6
  const h = React.createElement;
9
7
 
10
- const debugBuffer = [];
11
-
12
- function sandboxWrite(chunk) {
13
- const str = typeof chunk === 'string' ? chunk : String(chunk);
14
- const trimmed = str.trim();
15
- if (trimmed.length > 0) {
16
- debugBuffer.push(trimmed);
17
- }
18
- return true;
19
- }
20
-
21
- const consoleKeys = ['log', 'error', 'warn'];
22
- for (let i = 0; i < consoleKeys.length; i++) {
23
- const key = consoleKeys[i];
24
- const original = console[key];
25
- console[key] = function sandboxedConsole() {
26
- const args = [];
27
- for (let j = 0; j < arguments.length; j++) {
28
- args.push(arguments[j]);
29
- }
30
- sandboxWrite(args.join(' '));
31
- return original.apply(console, args);
32
- };
33
- }
34
-
35
- const { waitUntilExit } = render(h(App, {}));
36
- // Keep the process alive until the Ink UI signals exit (e.g., via SIGINT).
8
+ const { waitUntilExit } = render(h(App, {}), { exitOnCtrlC: false });
37
9
  waitUntilExit();
38
-
39
- function cleanup() {
40
- if (debugBuffer.length > 0) {
41
- const logPath = path.join(process.cwd(), 'openaxies-debug.log');
42
- try {
43
- fs.writeFileSync(logPath, debugBuffer.join('\n') + '\n');
44
- } catch (_) {}
45
- }
46
- process.exit(0);
47
- }
48
-
49
- process.on('SIGINT', cleanup);
50
- process.on('SIGTERM', cleanup);
51
- process.on('exit', function () {
52
- if (debugBuffer.length > 0) {
53
- const logPath = path.join(process.cwd(), 'openaxies-debug.log');
54
- try {
55
- fs.writeFileSync(logPath, debugBuffer.join('\n') + '\n');
56
- } catch (_) {}
57
- }
58
- });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openaxies",
3
- "version": "1.0.2",
3
+ "version": "1.0.4",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "bin": {
package/src/App.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import React from 'react';
2
- import { Box, Text, useInput, useStdout } from 'ink';
2
+ import { Box, Text, useInput, useStdout, useApp } from 'ink';
3
3
  import TextInput from 'ink-text-input';
4
4
  import { hex } from './config/theme.js';
5
5
  import { getModels, getModelById, DEFAULT_MODEL_ID } from './config/models.js';
@@ -7,7 +7,7 @@ import { callModel } from './providers/index.js';
7
7
 
8
8
  const h = React.createElement;
9
9
 
10
- const STARTUP_LOGO = [
10
+ const STARTUP = [
11
11
  ' \u2588\u2588\u2588\u2588\u2588\u2588\u2557',
12
12
  ' \u2588\u2588\u2554\u2550\u2550\u2550\u2588\u2588\u2557',
13
13
  ' \u2588\u2588\u2551 \u2588\u2588\u2551',
@@ -15,352 +15,248 @@ const STARTUP_LOGO = [
15
15
  ' \u255a\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255d',
16
16
  ];
17
17
 
18
- const COMMANDS = [
19
- { id: 'model', trigger: '/model', desc: 'Switch model' },
20
- { id: 'thoughts', trigger: '/thoughts', desc: 'Toggle thinking' },
21
- { id: 'resume', trigger: '/resume', desc: 'Re-run last query' },
22
- { id: 'clear', trigger: '/clear', desc: 'Clear conversation' },
23
- { id: 'help', trigger: '/help', desc: 'Show commands' },
24
- { id: 'exit', trigger: '/exit', desc: 'Quit' },
18
+ const CMDS = [
19
+ { id: 'model', t: '/model', d: 'Switch model' },
20
+ { id: 'thoughts', t: '/thoughts', d: 'Toggle thinking' },
21
+ { id: 'resume', t: '/resume', d: 'Re-run last query' },
22
+ { id: 'clear', t: '/clear', d: 'Clear conversation' },
23
+ { id: 'help', t: '/help', d: 'Show commands' },
24
+ { id: 'exit', t: '/exit', d: 'Quit' },
25
25
  ];
26
26
 
27
27
  export default function AppRoot() {
28
28
  const { stdout } = useStdout();
29
- const rows = stdout.rows || 24;
29
+ const { exit } = useApp();
30
30
  const cols = stdout.columns || 80;
31
-
32
- const [messages, setMessages] = React.useState([]);
33
- const [activeModel, setActiveModel] = React.useState(DEFAULT_MODEL_ID);
34
- const [input, setInput] = React.useState('');
35
- const [streaming, setStreaming] = React.useState(false);
36
- const [streamText, setStreamText] = React.useState('');
37
- const [thinkingActive, setThinkingActive] = React.useState(false);
38
- const [thinkingText, setThinkingText] = React.useState('');
39
- const [thinkElapsed, setThinkElapsed] = React.useState(0);
40
- const [startupDone, setStartupDone] = React.useState(false);
41
- const [showOverlay, setShowOverlay] = React.useState(false);
42
- const [overlayIdx, setOverlayIdx] = React.useState(0);
43
- const [showThought, setShowThought] = React.useState(true);
44
- const [toolEvent, setToolEvent] = React.useState(null);
45
-
46
- const abortRef = React.useRef(null);
47
- const accumulatedRef = React.useRef('');
48
- const lastQueryRef = React.useRef('');
31
+ const rows = stdout.rows || 24;
32
+ const WID = Math.min(cols - 4, 56);
33
+
34
+ const [msgs, setMsgs] = React.useState([]);
35
+ const [model, setModel] = React.useState(DEFAULT_MODEL_ID);
36
+ const [inp, setInp] = React.useState('');
37
+ const [busy, setBusy] = React.useState(false);
38
+ const [stream, setStream] = React.useState('');
39
+ const [think, setThink] = React.useState(false);
40
+ const [thinkTxt, setThinkTxt] = React.useState('');
41
+ const [thinkSec, setThinkSec] = React.useState(0);
42
+ const [ready, setReady] = React.useState(false);
43
+ const [overlay, setOverlay] = React.useState(false);
44
+ const [ovIdx, setOvIdx] = React.useState(0);
45
+ const [showT, setShowT] = React.useState(true);
46
+ const [tool, setTool] = React.useState(null);
47
+
48
+ const acRef = React.useRef(null);
49
+ const accRef = React.useRef('');
50
+ const lastRef = React.useRef('');
49
51
 
50
52
  React.useEffect(() => {
51
- if (!thinkingActive) return;
52
- const t = setInterval(() => setThinkElapsed(p => p + 0.1), 100);
53
+ if (!think) return;
54
+ const t = setInterval(() => setThinkSec(p => p + 0.1), 100);
53
55
  return () => clearInterval(t);
54
- }, [thinkingActive]);
55
-
56
- const model = getModelById(activeModel);
57
- const modelLabel = model ? model.label : 'Unknown';
58
-
59
- function stripThink(t) {
60
- return showThought ? t : t.split('<think>').join('').split('</think>').join('');
56
+ }, [think]);
57
+
58
+ const mi = getModelById(model);
59
+ const label = mi ? mi.label.replace('OpenAxies ', '') : 'llama';
60
+
61
+ function strip(s) { return showT ? s : s.split('<think>').join('').split('</think>').join(''); }
62
+
63
+ function headBox() {
64
+ const R = '\u2502';
65
+ const D = '\u2500';
66
+ const W = WID;
67
+ const hd = function (s) { return R + ' ' + s + ' '.repeat(Math.max(0, W - s.length)) + ' ' + R; };
68
+ return h(Box, { flexShrink: 0, flexDirection: 'column', paddingLeft: 1, paddingRight: 1 },
69
+ h(Text, { color: '#555577' }, '\u250C' + D.repeat(W + 2) + '\u2510'),
70
+ h(Text, { color: '#555577' }, hd('\u203A_ OpenAxies (v1.0.3)')),
71
+ h(Text, { color: '#555577' }, hd('')),
72
+ h(Text, { color: '#555577' }, hd('model: ' + label + ' /model to change')),
73
+ h(Text, { color: '#555577' }, hd('directory: ~')),
74
+ h(Text, { color: '#555577' }, '\u2514' + D.repeat(W + 2) + '\u2518'),
75
+ );
61
76
  }
62
77
 
63
- async function handleSubmit(text) {
64
- const s = typeof text === 'string' ? text.trim() : '';
65
- if (!s || streaming) return;
66
- setStartupDone(true);
67
- lastQueryRef.current = s;
68
- accumulatedRef.current = '';
69
- setStreaming(true);
70
- setStreamText('');
71
- setThinkingText('');
72
- setThinkElapsed(0);
73
- setToolEvent(null);
74
- setMessages(p => [...p, { type: 'user', content: s }]);
75
- setInput('');
76
-
77
- const m = getModelById(activeModel);
78
- if (!m) { setMessages(p => [...p, { type: 'assistant', content: 'No model loaded.' }]); setStreaming(false); return; }
78
+ async function submit(s) {
79
+ const t = typeof s === 'string' ? s.trim() : '';
80
+ if (!t || busy) return;
81
+ setReady(true); lastRef.current = t; accRef.current = '';
82
+ setBusy(true); setStream(''); setThinkTxt(''); setThinkSec(0); setTool(null);
83
+ setMsgs(p => [...p, { role: 'user', c: t }]);
84
+ setInp('');
79
85
 
86
+ const m = getModelById(model);
87
+ if (!m) { setMsgs(p => [...p, { role: 'asst', c: 'No model loaded.' }]); setBusy(false); return; }
80
88
  const ac = new AbortController();
81
- abortRef.current = ac;
89
+ acRef.current = ac;
90
+ let op = false;
82
91
 
83
92
  try {
84
- const history = [];
85
- for (const msg of messages) {
86
- if (msg.type === 'user') history.push({ role: 'user', content: msg.content });
87
- else if (msg.type === 'assistant' || msg.type === 'thought') history.push({ role: 'assistant', content: msg.content });
88
- }
89
- history.push({ role: 'user', content: s });
93
+ const hist = msgs.map(x => ({ role: x.role === 'user' ? 'user' : 'assistant', content: x.c || '' }));
94
+ hist.push({ role: 'user', content: t });
90
95
 
91
- let thinkOpen = false;
92
-
93
- for await (const ev of callModel({ id: activeModel }, history, ac.signal)) {
96
+ for await (const ev of callModel({ id: model }, hist, ac.signal)) {
94
97
  if (ac.signal.aborted) break;
95
-
96
- if (ev.type === 'tool') {
97
- setToolEvent({ tool: ev.tool, query: ev.query, sites: ev.sites });
98
- continue;
99
- }
100
-
98
+ if (ev.type === 'tool') { setTool({ tool: ev.tool, q: ev.query, sites: ev.sites }); continue; }
101
99
  if (ev.type === 'token' || ev.type === 'thinking') {
102
100
  const c = typeof ev.content === 'string' ? ev.content : '';
103
101
  if (!c) continue;
104
-
105
- // Track think state
106
- if (c.includes('<think>')) thinkOpen = true;
107
- accumulatedRef.current += c;
108
-
109
- const clean = stripThink(accumulatedRef.current);
110
- if (thinkOpen || accumulatedRef.current.includes('<think>')) {
111
- const stillOpen = thinkOpen || !accumulatedRef.current.includes('</think>');
112
- if (stillOpen) {
113
- setThinkingActive(true);
114
- setThinkingText(clean);
115
- setStreamText('');
116
- } else {
117
- thinkOpen = false;
118
- setThinkingActive(false);
119
- setStreamText(clean);
120
- setThinkingText('');
121
- }
122
- } else {
123
- setStreamText(clean);
124
- }
102
+ if (c.includes('<think>')) op = true;
103
+ accRef.current += c;
104
+ const cl = strip(accRef.current);
105
+ if (op || accRef.current.includes('<think>')) {
106
+ const st = op || !accRef.current.includes('</think>');
107
+ if (st) { setThink(true); setThinkTxt(cl); setStream(''); }
108
+ else { op = false; setThink(false); setStream(cl); setThinkTxt(''); }
109
+ } else setStream(cl);
125
110
  }
126
-
127
111
  if (ev.type === 'done' || ev.type === 'timeout') {
128
- if (ev.type === 'timeout' && accumulatedRef.current) {
129
- accumulatedRef.current += '\n\u2014 timed out \u2014';
130
- }
112
+ if (ev.type === 'timeout' && accRef.current) accRef.current += '\n\u2014timed out\u2014';
131
113
  break;
132
114
  }
133
115
  }
134
- } catch (err) {
135
- if (!ac.signal.aborted) {
136
- setMessages(p => [...p, { type: 'assistant', content: 'Error: ' + (err.message || 'Connection failed') }]);
137
- }
116
+ } catch (e) {
117
+ if (!ac.signal.aborted) setMsgs(p => [...p, { role: 'asst', c: 'Error: ' + (e.message || 'connection') }]);
138
118
  } finally {
139
- setStreaming(false);
140
- setThinkingActive(false);
141
- abortRef.current = null;
142
- const final = accumulatedRef.current;
143
- if (final) {
144
- setMessages(p => [...p, { type: 'assistant', content: stripThink(final) }]);
145
- }
146
- setStreamText('');
147
- setThinkingText('');
119
+ setBusy(false); setThink(false); acRef.current = null;
120
+ const f = accRef.current;
121
+ if (f) setMsgs(p => [...p, { role: 'asst', c: strip(f) }]);
122
+ setStream(''); setThinkTxt('');
148
123
  }
149
124
  }
150
125
 
151
- function cycleModel() {
152
- const models = getModels();
153
- setActiveModel(p => {
154
- let found = false;
155
- for (const m of models) {
156
- if (found) return m.id;
157
- if (m.id === p) found = true;
158
- }
159
- return models[0].id;
160
- });
126
+ function cycle() {
127
+ const ms = getModels();
128
+ setModel(p => { let f = false; for (const m of ms) { if (f) return m.id; if (m.id === p) f = true; } return ms[0].id; });
161
129
  }
162
130
 
163
- function handleCmd(item) {
131
+ function cmd(item) {
164
132
  if (!item) return;
165
- const t = item.trigger;
166
- if (t === '/exit') process.exit(0);
167
- if (t === '/clear') setMessages([]);
168
- if (t === '/model') cycleModel();
169
- if (t === '/thoughts') setShowThought(p => !p);
170
- if (t === '/resume') { const q = lastQueryRef.current; if (q && !streaming) handleSubmit(q); }
171
- if (t === '/help') setMessages(p => [...p, { type: 'assistant', content: 'Commands: /help /clear /model /thoughts /resume /exit' }]);
172
- setInput('');
173
- setShowOverlay(false);
174
- setOverlayIdx(0);
133
+ if (item.t === '/exit') { exit(); return; }
134
+ if (item.t === '/clear') setMsgs([]);
135
+ if (item.t === '/model') cycle();
136
+ if (item.t === '/thoughts') setShowT(p => !p);
137
+ if (item.t === '/resume') { const q = lastRef.current; if (q && !busy) submit(q); return; }
138
+ if (item.t === '/help') setMsgs(p => [...p, { role: 'asst', c: 'Commands: /help /clear /model /thoughts /resume /exit' }]);
139
+ setInp(''); setOverlay(false); setOvIdx(0);
175
140
  }
176
141
 
177
- useInput((inp, key) => {
178
- if (showOverlay) {
179
- if (key.escape) { setShowOverlay(false); setInput(''); setOverlayIdx(0); return; }
180
- if (key.upArrow) { setOverlayIdx(p => p > 0 ? p - 1 : COMMANDS.length - 1); return; }
181
- if (key.downArrow) { setOverlayIdx(p => p < COMMANDS.length - 1 ? p + 1 : 0); return; }
182
- if (key.return) { handleCmd(COMMANDS[overlayIdx]); return; }
183
- return;
184
- }
185
- if (!startupDone && !streaming) {
186
- if (key.return) { setStartupDone(true); setInput(''); return; }
187
- return;
188
- }
189
- if (key.escape) {
190
- if (streaming && abortRef.current) { abortRef.current.abort(); setStreaming(false); setStreamText(''); setThinkingActive(false); }
142
+ useInput((i, k) => {
143
+ if (overlay) {
144
+ if (k.escape) { setOverlay(false); setInp(''); setOvIdx(0); return; }
145
+ if (k.upArrow) setOvIdx(p => p > 0 ? p - 1 : CMDS.length - 1);
146
+ if (k.downArrow) setOvIdx(p => p < CMDS.length - 1 ? p + 1 : 0);
147
+ if (k.return) cmd(CMDS[ovIdx]);
191
148
  return;
192
149
  }
193
- if (key.ctrl) {
194
- if (inp === 'c' || inp === 'C') { if (abortRef.current) abortRef.current.abort(); process.exit(0); }
195
- if (inp === 't' || inp === 'T') { setShowThought(p => !p); return; }
196
- if (inp === 'p' || inp === 'P') { cycleModel(); return; }
197
- if (inp === 'r' || inp === 'R') { const q = lastQueryRef.current; if (q && !streaming) handleSubmit(q); return; }
198
- if (inp === 'l' || inp === 'L') { setMessages([]); return; }
199
- if (inp === 'k' || inp === 'K') { setInput('/'); setShowOverlay(true); setOverlayIdx(0); return; }
150
+ if (!ready && !busy) { if (k.return) { setReady(true); setInp(''); } return; }
151
+ if (k.escape && busy) { if (acRef.current) acRef.current.abort(); setBusy(false); setStream(''); setThink(false); return; }
152
+ if (k.ctrl) {
153
+ if (i === 'c' || i === 'C') { exit(); return; }
154
+ if (i === 't' || i === 'T') { setShowT(p => !p); return; }
155
+ if (i === 'p' || i === 'P') { cycle(); return; }
156
+ if (i === 'r' || i === 'R') { const q = lastRef.current; if (q && !busy) submit(q); return; }
157
+ if (i === 'l' || i === 'L') { setMsgs([]); return; }
158
+ if (i === 'k' || i === 'K') { setInp('/'); setOverlay(true); setOvIdx(0); return; }
200
159
  }
201
160
  });
202
161
 
203
- // Build timeline
204
- const events = [];
205
- for (const msg of messages) {
206
- if (msg.type === 'user' || msg.type === 'assistant' || msg.type === 'thought') {
207
- events.push(msg);
208
- }
209
- }
210
-
211
- // Tool event
212
- if (toolEvent && streaming) {
213
- events.push({ type: 'tool', tool: toolEvent.tool, query: toolEvent.query });
214
- }
215
-
216
- // Live thinking
217
- if (thinkingActive && thinkingText) {
218
- events.push({ type: 'thought', content: thinkingText, elapsed: thinkElapsed.toFixed(1) + 's' });
219
- }
220
-
221
- // Live stream
222
- if (streaming && streamText && !thinkingActive) {
223
- events.push({ type: 'assistant', content: streamText });
224
- }
225
-
226
- const visible = events.slice(-20);
227
-
228
- // Startup screen
229
- if (!startupDone && messages.length === 0) {
162
+ // Startup
163
+ if (!ready && msgs.length === 0) {
230
164
  return h(Box, { flexDirection: 'column', width: '100%', height: rows, overflow: 'hidden' },
231
- ...STARTUP_LOGO.map((l, i) => h(Box, { key: 'sl' + i, height: 1, paddingLeft: 1 },
232
- h(Text, { color: '#00BBFF', bold: true }, l)
233
- )),
234
- h(Box, { key: 'sp1', height: 1 }),
235
- h(Box, { key: 'st1', height: 1, paddingLeft: 1 }, h(Text, { color: '#FFFFFF', bold: true }, 'OpenAxies')),
236
- h(Box, { key: 'st2', height: 1, paddingLeft: 1 }, h(Text, { color: '#777788' }, 'local ai agent runtime')),
237
- h(Box, { key: 'sp2', height: 1 }),
238
- h(Box, { key: 'st3', height: 1, paddingLeft: 1 }, h(Text, { color: '#555577' }, 'Press Enter to begin')),
165
+ h(Box, { height: 3 }),
166
+ ...STARTUP.map((l, i) => h(Box, { key: 'l' + i, height: 1, paddingLeft: 2 }, h(Text, { color: '#00BBFF', bold: true }, l))),
167
+ h(Box, { height: 1 }),
168
+ h(Box, { height: 1, paddingLeft: 2 }, h(Text, { color: '#FFFFFF', bold: true }, 'OpenAxies')),
169
+ h(Box, { height: 1, paddingLeft: 2 }, h(Text, { color: '#777788' }, 'local agent')),
170
+ h(Box, { height: 2 }),
171
+ h(Box, { height: 1, paddingLeft: 2 }, h(Text, { color: '#555577' }, 'Press Enter to begin')),
239
172
  h(Box, { flexGrow: 1 }),
240
- h(Box, { key: 'foot', height: 1, paddingLeft: 1 }, h(Text, { color: '#333344' }, 'esc quit enter begin')),
173
+ h(Box, { height: 1, paddingLeft: 2 }, h(Text, { color: '#333344' }, 'esc quit ctrl+p models /help')),
241
174
  );
242
175
  }
243
176
 
244
- // Overlay
245
- if (showOverlay) {
246
- visible.unshift({ type: '_sep' });
247
- visible.unshift({ type: '_overlay', idx: overlayIdx });
177
+ // Build entries
178
+ const entries = [];
179
+ for (const m of msgs) {
180
+ if (m.role === 'user') entries.push({ t: 'user', c: m.c });
181
+ else if (m.role === 'asst') entries.push({ t: 'asst', c: m.c });
248
182
  }
183
+ if (tool && busy) entries.push({ t: 'tool', tool: tool.tool, q: tool.q, sites: tool.sites });
184
+ if (think && thinkTxt) entries.push({ t: 'thought', c: thinkTxt, sec: thinkSec.toFixed(1) + 's' });
185
+ if (busy && stream && !think) entries.push({ t: 'asst', c: stream });
249
186
 
250
- // No content yet after startup
251
- if (visible.length === 0) {
252
- visible.push({ type: '_sep' });
253
- visible.push({ type: '_ready' });
254
- visible.push({ type: '_sep' });
255
- }
256
-
257
- // Estimate lines
258
- const HEADER_H = 1;
259
- const SEP_H = 1;
260
- const INPUT_H = 1;
261
- const FOOTER_H = 1;
262
- const fixed = HEADER_H + SEP_H + INPUT_H + FOOTER_H;
263
- const maxLines = Math.max(5, rows - fixed);
264
-
265
- function estLines(ev) {
266
- if (ev.type === '_sep' || ev.type === '_ready' || ev.type === '_overlay') return 1;
267
- if (ev.type === 'tool') return 1;
268
- if (ev.type === 'user') return 2;
269
- if (ev.type === 'thought') return 2;
270
- return 1;
271
- }
187
+ if (overlay) entries.push({ t: '_ov', idx: ovIdx });
272
188
 
273
- let totalEst = 0;
274
- let showN = 0;
275
- for (let i = visible.length - 1; i >= 0; i--) {
276
- const l = estLines(visible[i]);
277
- if (totalEst + l > maxLines) break;
278
- totalEst += l;
279
- showN = visible.length - i;
280
- }
281
- const show = visible.slice(visible.length - showN);
189
+ const maxChat = Math.max(3, rows - 7);
190
+ const vis = entries.slice(-maxChat);
282
191
 
283
- const els = [];
284
- for (let i = 0; i < show.length; i++) {
285
- const ev = show[i];
192
+ // Render chat
193
+ const chEls = [];
194
+ for (let i = 0; i < vis.length; i++) {
195
+ const e = vis[i];
286
196
  const k = 'e' + i;
287
-
288
- if (ev.type === '_sep') {
289
- els.push(h(Box, { key: k, height: 1 }, h(Text, { color: '#1A1A28' }, '\u2500'.repeat(cols))));
290
- } else if (ev.type === '_ready') {
291
- els.push(h(Box, { key: k, height: 1, paddingLeft: 1 }, h(Text, { color: '#00FF88', bold: true }, 'Ready')));
292
- } else if (ev.type === '_overlay') {
293
- els.push(h(Box, { key: k + 'h', height: 1, paddingLeft: 1 }, h(Text, { color: '#888899', bold: true }, 'Commands')));
294
- for (let ci = 0; ci < COMMANDS.length; ci++) {
295
- const c = COMMANDS[ci];
296
- const sel = ci === ev.idx;
297
- const prefix = sel ? '\u276F ' : ' ';
298
- const col = sel ? hex.neonBlue : '#666688';
299
- els.push(h(Box, { key: k + 'c' + ci, height: 1, paddingLeft: 2 },
300
- h(Text, { color: col }, prefix + c.trigger),
301
- h(Text, { color: '#444466' }, ' ' + (c.desc || ''))
197
+ if (e.t === '_ov') {
198
+ chEls.push(h(Box, { key: k + 'h', height: 1, paddingLeft: 2 }, h(Text, { color: '#888899', bold: true }, 'Commands:')));
199
+ for (let ci = 0; ci < CMDS.length; ci++) {
200
+ const c = CMDS[ci]; const sel = ci === e.idx;
201
+ chEls.push(h(Box, { key: k + 'c' + ci, height: 1, paddingLeft: 3 },
202
+ h(Text, { color: sel ? hex.neonBlue : '#666688' }, (sel ? '\u276F ' : ' ') + c.t),
203
+ h(Text, { color: '#444466' }, ' ' + (c.d || ''))
302
204
  ));
303
205
  }
304
- } else if (ev.type === 'user') {
305
- els.push(h(Box, { key: k + 'h', height: 1, paddingLeft: 1 }, h(Text, { color: '#888899', bold: true }, 'You')));
306
- els.push(h(Box, { key: k + 'c', height: 1, paddingLeft: 2 }, h(Text, { color: hex.neonBlue, wrap: 'wrap' }, ev.content)));
307
- } else if (ev.type === 'assistant') {
308
- els.push(h(Box, { key: k, height: 1, paddingLeft: 1 }, h(Text, { color: '#FFFFFF', wrap: 'wrap' }, ev.content)));
309
- } else if (ev.type === 'thought') {
310
- els.push(h(Box, { key: k + 'h', height: 1, paddingLeft: 1 },
311
- h(Text, { color: '#FF9F43', bold: true }, '\u25CB Thinking \u2022 ' + (ev.elapsed || '0.0s'))
206
+ } else if (e.t === 'user') {
207
+ chEls.push(h(Box, { key: k, height: 1, paddingLeft: 2 },
208
+ h(Text, { color: '#888899' }, '\u203A '),
209
+ h(Text, { color: hex.neonBlue, wrap: 'wrap' }, e.c),
312
210
  ));
313
- els.push(h(Box, { key: k + 'c', height: 1, paddingLeft: 2 }, h(Text, { color: '#FF9F43', wrap: 'wrap' }, ev.content)));
314
- } else if (ev.type === 'tool') {
315
- els.push(h(Box, { key: k, height: 1, paddingLeft: 1 },
316
- h(Text, { color: '#5B5B8A' }, '\u2500\u2500 ' + (ev.tool || 'tool') + (ev.query ? ' \u2014 ' + ev.query : ''))
211
+ } else if (e.t === 'asst') {
212
+ chEls.push(h(Box, { key: k, height: 1, paddingLeft: 2 },
213
+ h(Text, { color: '#88FF88' }, '\u2022 '),
214
+ h(Text, { color: '#FFFFFF', wrap: 'wrap' }, e.c),
215
+ ));
216
+ } else if (e.t === 'thought') {
217
+ chEls.push(h(Box, { key: k, height: 1, paddingLeft: 2 },
218
+ h(Text, { color: '#FF9F43', bold: true }, '\u2022 Thinking \u2022 ' + e.sec),
219
+ ));
220
+ if (e.c) chEls.push(h(Box, { key: k + 'c', height: 1, paddingLeft: 4 }, h(Text, { color: '#FF9F43', wrap: 'wrap' }, e.c)));
221
+ } else if (e.t === 'tool') {
222
+ chEls.push(h(Box, { key: k, height: 1, paddingLeft: 2 },
223
+ h(Text, { color: '#5B5B8A' }, '\u2022 ' + (e.tool || 'tool') + (e.q ? ' "' + e.q + '"' : '') + (e.sites ? ' (' + e.sites + ' sites)' : '')),
317
224
  ));
318
225
  }
319
226
  }
320
227
 
321
- // Fill
322
- while (els.length < maxLines) {
323
- els.push(h(Box, { key: 'f' + els.length, height: 1 }));
228
+ if (chEls.length === 0) {
229
+ chEls.push(h(Box, { key: 'idle', height: 1, paddingLeft: 2 }, h(Text, { color: '#666688' }, '\u203A Ask me anything')));
324
230
  }
325
231
 
326
- // Header
327
- const header = h(Box, { height: HEADER_H, flexShrink: 0, paddingLeft: 1, paddingRight: 1 },
328
- h(Text, { color: '#00BBFF', bold: true }, 'OpenAxies'),
329
- h(Text, { color: '#555577' }, ' \u2022 '),
330
- h(Text, { color: '#FFFFFF' }, modelLabel),
331
- h(Box, { flexGrow: 1 }),
332
- h(Text, { color: '#666688' }, 'local'),
333
- );
334
-
335
- // Separator
336
- const sepLine = h(Box, { height: SEP_H, flexShrink: 0 }, h(Text, { color: '#1A1A28' }, '\u2500'.repeat(cols)));
232
+ const chatArea = h(Box, { flexGrow: 1, flexDirection: 'column', overflow: 'hidden' }, ...chEls);
337
233
 
338
- // Input
339
- const inputLine = h(Box, { height: INPUT_H, flexShrink: 0, paddingLeft: 1, paddingRight: 1 },
234
+ // Input line
235
+ const inputBar = h(Box, { height: 1, flexShrink: 0, paddingLeft: 2, paddingRight: 2 },
236
+ h(Text, { color: '#888899' }, '\u203A '),
340
237
  h(Text, { color: hex.greenOnline }, '\u258C'),
341
238
  h(Box, { width: 1 }),
342
239
  h(TextInput, {
343
- value: input,
344
- onChange: setInput,
345
- onSubmit: function (v) { if (typeof v === 'string' && v.trim() && !streaming) handleSubmit(v); },
240
+ value: inp,
241
+ onChange: setInp,
242
+ onSubmit: v => { if (typeof v === 'string' && v.trim() && !busy) submit(v); },
346
243
  placeholder: '',
347
- focus: !showOverlay,
244
+ focus: !overlay,
348
245
  showCursor: true,
349
- })
246
+ }),
350
247
  );
351
248
 
352
249
  // Footer
353
- const footer = h(Box, { height: FOOTER_H, flexShrink: 0, paddingLeft: 1, paddingRight: 1 },
250
+ const footer = h(Box, { height: 1, flexShrink: 0, paddingLeft: 2, paddingRight: 2 },
354
251
  h(Text, { color: '#444466' }, 'esc \u2248 ctrl+t ctrl+p ctrl+k'),
355
252
  h(Box, { flexGrow: 1 }),
356
- h(Text, { color: '#555577' }, (modelLabel.replace('OpenAxies ', '') || 'model').toLowerCase() + ' \u2022 local'),
253
+ h(Text, { color: '#555577' }, label.toLowerCase() + ' \u2022 ~'),
357
254
  );
358
255
 
359
256
  return h(Box, { flexDirection: 'column', width: '100%', height: rows, overflow: 'hidden' },
360
- header,
361
- sepLine,
362
- h(Box, { flexGrow: 1, flexDirection: 'column', overflow: 'hidden' }, ...els),
363
- inputLine,
257
+ headBox(),
258
+ inputBar,
259
+ chatArea,
364
260
  footer,
365
261
  );
366
262
  }
@@ -4,12 +4,15 @@ 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',
7
8
  ]),
8
9
  'openaxis/openaxis-gpt': Object.freeze([
9
10
  'https://universal-618-clarity-2.hf.space/v1/chat/completions',
11
+ 'https://universal-618-clarity-5.hf.space/v1/chat/completions',
10
12
  ]),
11
13
  'openaxis/openaxis-deepseek': Object.freeze([
12
14
  'https://universal-618-clarity-3.hf.space/v1/chat/completions',
15
+ 'https://universal-618-clarity-6.hf.space/v1/chat/completions',
13
16
  ]),
14
17
  });
15
18
 
@@ -1,5 +1,5 @@
1
- const CONNECT_TIMEOUT = 10000;
2
- const READ_TIMEOUT = 30000;
1
+ const CONNECT_TIMEOUT = 120000;
2
+ const READ_TIMEOUT = 60000;
3
3
 
4
4
  function checkEndpoint(endpoint) {
5
5
  if (typeof endpoint !== 'string') {