openaxies 1.0.3 → 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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openaxies",
3
- "version": "1.0.3",
3
+ "version": "1.0.4",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "bin": {
package/src/App.js CHANGED
@@ -7,7 +7,7 @@ import { callModel } from './providers/index.js';
7
7
 
8
8
  const h = React.createElement;
9
9
 
10
- const 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,13 +15,13 @@ const 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() {
@@ -29,304 +29,234 @@ export default function AppRoot() {
29
29
  const { exit } = useApp();
30
30
  const cols = stdout.columns || 80;
31
31
  const rows = stdout.rows || 24;
32
-
33
- const [messages, setMessages] = React.useState([]);
34
- const [activeModel, setActiveModel] = React.useState(DEFAULT_MODEL_ID);
35
- const [input, setInput] = React.useState('');
36
- const [streaming, setStreaming] = React.useState(false);
37
- const [streamText, setStreamText] = React.useState('');
38
- const [thinkingActive, setThinkingActive] = React.useState(false);
39
- const [thinkingText, setThinkingText] = React.useState('');
40
- const [thinkTime, setThinkTime] = React.useState(0);
41
- const [startupDone, setStartupDone] = React.useState(false);
42
- const [showOverlay, setShowOverlay] = React.useState(false);
43
- const [overlayIdx, setOverlayIdx] = React.useState(0);
44
- const [showThought, setShowThought] = React.useState(true);
45
- const [toolEv, setToolEv] = React.useState(null);
46
-
47
- const abortRef = React.useRef(null);
48
- const accumRef = React.useRef('');
49
- const lastQRef = React.useRef('');
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('');
50
51
 
51
52
  React.useEffect(() => {
52
- if (!thinkingActive) return;
53
- const t = setInterval(() => setThinkTime(p => p + 0.1), 100);
53
+ if (!think) return;
54
+ const t = setInterval(() => setThinkSec(p => p + 0.1), 100);
54
55
  return () => clearInterval(t);
55
- }, [thinkingActive]);
56
-
57
- const model = getModelById(activeModel);
58
- const label = model ? model.label.replace('OpenAxies ', '') : 'llama';
59
- const W = Math.min(cols - 4, 56);
60
-
61
- function strip(t) {
62
- 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
+ );
63
76
  }
64
77
 
65
- async function handleSubmit(text) {
66
- const s = typeof text === 'string' ? text.trim() : '';
67
- if (!s || streaming) return;
68
- setStartupDone(true);
69
- lastQRef.current = s;
70
- accumRef.current = '';
71
- setStreaming(true);
72
- setStreamText('');
73
- setThinkingText('');
74
- setThinkTime(0);
75
- setToolEv(null);
76
- setMessages(p => [...p, { role: 'user', content: s }]);
77
- setInput('');
78
-
79
- const m = getModelById(activeModel);
80
- if (!m) { setMessages(p => [...p, { role: '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('');
81
85
 
86
+ const m = getModelById(model);
87
+ if (!m) { setMsgs(p => [...p, { role: 'asst', c: 'No model loaded.' }]); setBusy(false); return; }
82
88
  const ac = new AbortController();
83
- abortRef.current = ac;
84
- let thinkOpen = false;
89
+ acRef.current = ac;
90
+ let op = false;
85
91
 
86
92
  try {
87
- const history = messages.map(msg => ({
88
- role: msg.role === 'user' ? 'user' : 'assistant',
89
- content: msg.content || '',
90
- }));
91
- 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 });
92
95
 
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
- setToolEv({ 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
- if (c.includes('<think>')) thinkOpen = true;
105
- accumRef.current += c;
106
- const clean = strip(accumRef.current);
107
- if (thinkOpen || accumRef.current.includes('<think>')) {
108
- const still = thinkOpen || !accumRef.current.includes('</think>');
109
- if (still) {
110
- setThinkingActive(true);
111
- setThinkingText(clean);
112
- setStreamText('');
113
- } else {
114
- thinkOpen = false;
115
- setThinkingActive(false);
116
- setStreamText(clean);
117
- setThinkingText('');
118
- }
119
- } else {
120
- setStreamText(clean);
121
- }
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);
122
110
  }
123
111
  if (ev.type === 'done' || ev.type === 'timeout') {
124
- if (ev.type === 'timeout' && accumRef.current) accumRef.current += '\n\u2014timed out\u2014';
112
+ if (ev.type === 'timeout' && accRef.current) accRef.current += '\n\u2014timed out\u2014';
125
113
  break;
126
114
  }
127
115
  }
128
- } catch (err) {
129
- if (!ac.signal.aborted) setMessages(p => [...p, { role: 'assistant', content: 'Error: ' + (err.message || 'connection failed') }]);
116
+ } catch (e) {
117
+ if (!ac.signal.aborted) setMsgs(p => [...p, { role: 'asst', c: 'Error: ' + (e.message || 'connection') }]);
130
118
  } finally {
131
- setStreaming(false);
132
- setThinkingActive(false);
133
- abortRef.current = null;
134
- const final = accumRef.current;
135
- if (final) setMessages(p => [...p, { role: 'assistant', content: strip(final) }]);
136
- setStreamText('');
137
- 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('');
138
123
  }
139
124
  }
140
125
 
141
- function cycleModel() {
142
- const models = getModels();
143
- setActiveModel(p => {
144
- let f = false;
145
- for (const m of models) { if (f) return m.id; if (m.id === p) f = true; }
146
- return models[0].id;
147
- });
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; });
148
129
  }
149
130
 
150
- function execCmd(item) {
131
+ function cmd(item) {
151
132
  if (!item) return;
152
- const t = item.trigger;
153
- if (t === '/exit') { exit(); return; }
154
- if (t === '/clear') setMessages([]);
155
- if (t === '/model') cycleModel();
156
- if (t === '/thoughts') setShowThought(p => !p);
157
- if (t === '/resume') { const q = lastQRef.current; if (q && !streaming) handleSubmit(q); return; }
158
- if (t === '/help') setMessages(p => [...p, { role: 'assistant', content: 'Commands: /help /clear /model /thoughts /resume /exit' }]);
159
- setInput(''); setShowOverlay(false); 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);
160
140
  }
161
141
 
162
- useInput((inp, key) => {
163
- if (showOverlay) {
164
- if (key.escape) { setShowOverlay(false); setInput(''); setOverlayIdx(0); return; }
165
- if (key.upArrow) { setOverlayIdx(p => p > 0 ? p - 1 : COMMANDS.length - 1); return; }
166
- if (key.downArrow) { setOverlayIdx(p => p < COMMANDS.length - 1 ? p + 1 : 0); return; }
167
- if (key.return) { execCmd(COMMANDS[overlayIdx]); return; }
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]);
168
148
  return;
169
149
  }
170
- if (!startupDone && !streaming) {
171
- if (key.return) { setStartupDone(true); setInput(''); return; }
172
- return;
173
- }
174
- if (key.escape && streaming) {
175
- if (abortRef.current) abortRef.current.abort();
176
- setStreaming(false); setStreamText(''); setThinkingActive(false);
177
- return;
178
- }
179
- if (key.ctrl) {
180
- if (inp === 'c' || inp === 'C') { exit(); return; }
181
- if (inp === 't' || inp === 'T') { setShowThought(p => !p); return; }
182
- if (inp === 'p' || inp === 'P') { cycleModel(); return; }
183
- if (inp === 'r' || inp === 'R') { const q = lastQRef.current; if (q && !streaming) handleSubmit(q); return; }
184
- if (inp === 'l' || inp === 'L') { setMessages([]); return; }
185
- 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; }
186
159
  }
187
160
  });
188
161
 
189
- // Build entries
190
- const entries = [];
191
- for (const msg of messages) {
192
- if (msg.role === 'user' || msg.role === 'assistant') entries.push(msg);
193
- }
194
-
195
- if (toolEv && streaming) entries.push({ role: 'tool', tool: toolEv.tool, query: toolEv.query, sites: toolEv.sites });
196
- if (thinkingActive && thinkingText) entries.push({ role: 'thought', content: thinkingText, time: thinkTime.toFixed(1) + 's' });
197
- if (streaming && streamText && !thinkingActive) entries.push({ role: 'ongoing', content: streamText });
198
-
199
- const MAX_CHAT = Math.max(3, rows - 10);
200
- const vis = entries.slice(-MAX_CHAT);
201
-
202
- // Startup screen
203
- if (!startupDone && messages.length === 0) {
162
+ // Startup
163
+ if (!ready && msgs.length === 0) {
204
164
  return h(Box, { flexDirection: 'column', width: '100%', height: rows, overflow: 'hidden' },
205
- h(Box, { height: 2 }),
206
- ...LOGO.map((l, i) => h(Box, { key: 'l' + i, height: 1, paddingLeft: 2 },
207
- h(Text, { color: '#00BBFF', bold: true }, l)
208
- )),
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))),
209
167
  h(Box, { height: 1 }),
210
168
  h(Box, { height: 1, paddingLeft: 2 }, h(Text, { color: '#FFFFFF', bold: true }, 'OpenAxies')),
211
169
  h(Box, { height: 1, paddingLeft: 2 }, h(Text, { color: '#777788' }, 'local agent')),
212
170
  h(Box, { height: 2 }),
213
171
  h(Box, { height: 1, paddingLeft: 2 }, h(Text, { color: '#555577' }, 'Press Enter to begin')),
214
172
  h(Box, { flexGrow: 1 }),
215
- h(Box, { height: 1, paddingLeft: 2 }, h(Text, { color: '#333344' }, 'esc interrupt ctrl+p models /help')),
173
+ h(Box, { height: 1, paddingLeft: 2 }, h(Text, { color: '#333344' }, 'esc quit ctrl+p models /help')),
216
174
  );
217
175
  }
218
176
 
219
- // Bordered header
220
- const borderT = '\u250C' + '\u2500'.repeat(W + 2) + '\u2510';
221
- const borderB = '\u2514' + '\u2500'.repeat(W + 2) + '\u2518';
222
- const pad = '\u2502' + ' '.repeat(W + 2) + '\u2502';
223
-
224
- function brdr(line) {
225
- return '\u2502 ' + line + ' '.repeat(Math.max(0, W + 1 - line.length)) + '\u2502';
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 });
226
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 });
227
186
 
228
- // Logo combined with info text
229
- const line1 = brdr(' ' + LOGO[0] + ' OpenAxies');
230
- const line2 = brdr(' ' + LOGO[1] + ' model: ' + label);
231
- const line3 = brdr(' ' + LOGO[2] + ' dir: ~');
232
- const line4 = brdr(' ' + LOGO[3] + ' cmds: /model /help /exit');
233
- const line5 = brdr(' ' + LOGO[4]);
234
-
235
- const header = h(Box, { flexShrink: 0, flexDirection: 'column', paddingLeft: 1, paddingRight: 1 },
236
- h(Text, { color: '#555577' }, borderT),
237
- h(Text, { color: '#555577' }, pad),
238
- h(Text, { color: '#555577' }, line1),
239
- h(Text, { color: '#555577' }, line2),
240
- h(Text, { color: '#555577' }, line3),
241
- h(Text, { color: '#555577' }, line4),
242
- h(Text, { color: '#555577' }, line5),
243
- h(Text, { color: '#555577' }, pad),
244
- h(Text, { color: '#555577' }, borderB),
245
- );
246
-
247
- // Overlay
248
- if (showOverlay) {
249
- vis.unshift({ role: '_ocmd', idx: overlayIdx });
250
- }
187
+ if (overlay) entries.push({ t: '_ov', idx: ovIdx });
251
188
 
252
- // Empty state
253
- if (vis.length === 0) {
254
- vis.push({ role: '_sep' });
255
- vis.push({ role: '_idle' });
256
- }
189
+ const maxChat = Math.max(3, rows - 7);
190
+ const vis = entries.slice(-maxChat);
257
191
 
258
- // Render entries
259
- const els = [];
192
+ // Render chat
193
+ const chEls = [];
260
194
  for (let i = 0; i < vis.length; i++) {
261
195
  const e = vis[i];
262
196
  const k = 'e' + i;
263
-
264
- if (e.role === '_idle') {
265
- els.push(h(Box, { key: k, height: 1, paddingLeft: 2 }, h(Text, { color: '#00FF88', bold: true }, '\u25CB Ready')));
266
- } else if (e.role === '_ocmd') {
267
- els.push(h(Box, { key: k + 'h', height: 1, paddingLeft: 2 }, h(Text, { color: '#888899', bold: true }, 'Commands:')));
268
- for (let ci = 0; ci < COMMANDS.length; ci++) {
269
- const c = COMMANDS[ci];
270
- const sel = ci === e.idx;
271
- els.push(h(Box, { key: k + 'c' + ci, height: 1, paddingLeft: 3 },
272
- h(Text, { color: sel ? hex.neonBlue : '#666688' }, (sel ? '\u276F ' : ' ') + c.trigger),
273
- 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 || ''))
274
204
  ));
275
205
  }
276
- } else if (e.role === 'user') {
277
- els.push(h(Box, { key: k, height: 1, paddingLeft: 2 },
206
+ } else if (e.t === 'user') {
207
+ chEls.push(h(Box, { key: k, height: 1, paddingLeft: 2 },
278
208
  h(Text, { color: '#888899' }, '\u203A '),
279
- h(Text, { color: hex.neonBlue, wrap: 'wrap' }, e.content),
209
+ h(Text, { color: hex.neonBlue, wrap: 'wrap' }, e.c),
280
210
  ));
281
- } else if (e.role === 'assistant' || e.role === 'ongoing') {
282
- els.push(h(Box, { key: k, height: 1, paddingLeft: 2 },
211
+ } else if (e.t === 'asst') {
212
+ chEls.push(h(Box, { key: k, height: 1, paddingLeft: 2 },
283
213
  h(Text, { color: '#88FF88' }, '\u2022 '),
284
- h(Text, { color: '#FFFFFF', wrap: 'wrap' }, e.content),
214
+ h(Text, { color: '#FFFFFF', wrap: 'wrap' }, e.c),
285
215
  ));
286
- } else if (e.role === 'thought') {
287
- els.push(h(Box, { key: k, height: 1, paddingLeft: 2 },
288
- h(Text, { color: '#FF9F43', bold: true }, '\u2022 Thinking \u2022 ' + e.time),
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),
289
219
  ));
290
- if (e.content) {
291
- els.push(h(Box, { key: k + 'c', height: 1, paddingLeft: 4 },
292
- h(Text, { color: '#FF9F43', wrap: 'wrap' }, e.content),
293
- ));
294
- }
295
- } else if (e.role === 'tool') {
296
- els.push(h(Box, { key: k, height: 1, paddingLeft: 2 },
297
- h(Text, { color: '#5B5B8A' }, '\u2022 ' + (e.tool || 'tool') + (e.query ? ' "' + e.query + '"' : '') + (e.sites ? ' (' + e.sites + ' sites)' : '')),
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)' : '')),
298
224
  ));
299
225
  }
300
226
  }
301
227
 
302
- const chat = h(Box, { flexGrow: 1, flexDirection: 'column', overflow: 'hidden' }, ...els);
228
+ if (chEls.length === 0) {
229
+ chEls.push(h(Box, { key: 'idle', height: 1, paddingLeft: 2 }, h(Text, { color: '#666688' }, '\u203A Ask me anything')));
230
+ }
231
+
232
+ const chatArea = h(Box, { flexGrow: 1, flexDirection: 'column', overflow: 'hidden' }, ...chEls);
303
233
 
304
- // Input
305
- const inputLine = h(Box, { height: 1, flexShrink: 0, paddingLeft: 2, paddingRight: 2 },
234
+ // Input line
235
+ const inputBar = h(Box, { height: 1, flexShrink: 0, paddingLeft: 2, paddingRight: 2 },
306
236
  h(Text, { color: '#888899' }, '\u203A '),
307
237
  h(Text, { color: hex.greenOnline }, '\u258C'),
308
238
  h(Box, { width: 1 }),
309
239
  h(TextInput, {
310
- value: input,
311
- onChange: setInput,
312
- onSubmit: 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); },
313
243
  placeholder: '',
314
- focus: !showOverlay,
244
+ focus: !overlay,
315
245
  showCursor: true,
316
- })
246
+ }),
317
247
  );
318
248
 
319
- // Footer bar
249
+ // Footer
320
250
  const footer = h(Box, { height: 1, flexShrink: 0, paddingLeft: 2, paddingRight: 2 },
321
251
  h(Text, { color: '#444466' }, 'esc \u2248 ctrl+t ctrl+p ctrl+k'),
322
252
  h(Box, { flexGrow: 1 }),
323
- h(Text, { color: '#555577' }, label.toLowerCase() + ' \u2022 local'),
253
+ h(Text, { color: '#555577' }, label.toLowerCase() + ' \u2022 ~'),
324
254
  );
325
255
 
326
256
  return h(Box, { flexDirection: 'column', width: '100%', height: rows, overflow: 'hidden' },
327
- header,
328
- chat,
329
- inputLine,
257
+ headBox(),
258
+ inputBar,
259
+ chatArea,
330
260
  footer,
331
261
  );
332
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') {