openaxies 1.0.2 → 1.0.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.
- package/bin/openaxis.js +1 -50
- package/package.json +1 -1
- package/src/App.js +133 -167
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
|
|
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
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
|
|
10
|
+
const LOGO = [
|
|
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',
|
|
@@ -26,8 +26,9 @@ const COMMANDS = [
|
|
|
26
26
|
|
|
27
27
|
export default function AppRoot() {
|
|
28
28
|
const { stdout } = useStdout();
|
|
29
|
-
const
|
|
29
|
+
const { exit } = useApp();
|
|
30
30
|
const cols = stdout.columns || 80;
|
|
31
|
+
const rows = stdout.rows || 24;
|
|
31
32
|
|
|
32
33
|
const [messages, setMessages] = React.useState([]);
|
|
33
34
|
const [activeModel, setActiveModel] = React.useState(DEFAULT_MODEL_ID);
|
|
@@ -36,27 +37,28 @@ export default function AppRoot() {
|
|
|
36
37
|
const [streamText, setStreamText] = React.useState('');
|
|
37
38
|
const [thinkingActive, setThinkingActive] = React.useState(false);
|
|
38
39
|
const [thinkingText, setThinkingText] = React.useState('');
|
|
39
|
-
const [
|
|
40
|
+
const [thinkTime, setThinkTime] = React.useState(0);
|
|
40
41
|
const [startupDone, setStartupDone] = React.useState(false);
|
|
41
42
|
const [showOverlay, setShowOverlay] = React.useState(false);
|
|
42
43
|
const [overlayIdx, setOverlayIdx] = React.useState(0);
|
|
43
44
|
const [showThought, setShowThought] = React.useState(true);
|
|
44
|
-
const [
|
|
45
|
+
const [toolEv, setToolEv] = React.useState(null);
|
|
45
46
|
|
|
46
47
|
const abortRef = React.useRef(null);
|
|
47
|
-
const
|
|
48
|
-
const
|
|
48
|
+
const accumRef = React.useRef('');
|
|
49
|
+
const lastQRef = React.useRef('');
|
|
49
50
|
|
|
50
51
|
React.useEffect(() => {
|
|
51
52
|
if (!thinkingActive) return;
|
|
52
|
-
const t = setInterval(() =>
|
|
53
|
+
const t = setInterval(() => setThinkTime(p => p + 0.1), 100);
|
|
53
54
|
return () => clearInterval(t);
|
|
54
55
|
}, [thinkingActive]);
|
|
55
56
|
|
|
56
57
|
const model = getModelById(activeModel);
|
|
57
|
-
const
|
|
58
|
+
const label = model ? model.label.replace('OpenAxies ', '') : 'llama';
|
|
59
|
+
const W = Math.min(cols - 4, 56);
|
|
58
60
|
|
|
59
|
-
function
|
|
61
|
+
function strip(t) {
|
|
60
62
|
return showThought ? t : t.split('<think>').join('').split('</think>').join('');
|
|
61
63
|
}
|
|
62
64
|
|
|
@@ -64,52 +66,47 @@ export default function AppRoot() {
|
|
|
64
66
|
const s = typeof text === 'string' ? text.trim() : '';
|
|
65
67
|
if (!s || streaming) return;
|
|
66
68
|
setStartupDone(true);
|
|
67
|
-
|
|
68
|
-
|
|
69
|
+
lastQRef.current = s;
|
|
70
|
+
accumRef.current = '';
|
|
69
71
|
setStreaming(true);
|
|
70
72
|
setStreamText('');
|
|
71
73
|
setThinkingText('');
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
setMessages(p => [...p, {
|
|
74
|
+
setThinkTime(0);
|
|
75
|
+
setToolEv(null);
|
|
76
|
+
setMessages(p => [...p, { role: 'user', content: s }]);
|
|
75
77
|
setInput('');
|
|
76
78
|
|
|
77
79
|
const m = getModelById(activeModel);
|
|
78
|
-
if (!m) { setMessages(p => [...p, {
|
|
80
|
+
if (!m) { setMessages(p => [...p, { role: 'assistant', content: 'No model loaded.' }]); setStreaming(false); return; }
|
|
79
81
|
|
|
80
82
|
const ac = new AbortController();
|
|
81
83
|
abortRef.current = ac;
|
|
84
|
+
let thinkOpen = false;
|
|
82
85
|
|
|
83
86
|
try {
|
|
84
|
-
const history =
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
}
|
|
87
|
+
const history = messages.map(msg => ({
|
|
88
|
+
role: msg.role === 'user' ? 'user' : 'assistant',
|
|
89
|
+
content: msg.content || '',
|
|
90
|
+
}));
|
|
89
91
|
history.push({ role: 'user', content: s });
|
|
90
92
|
|
|
91
|
-
let thinkOpen = false;
|
|
92
|
-
|
|
93
93
|
for await (const ev of callModel({ id: activeModel }, history, ac.signal)) {
|
|
94
94
|
if (ac.signal.aborted) break;
|
|
95
95
|
|
|
96
96
|
if (ev.type === 'tool') {
|
|
97
|
-
|
|
97
|
+
setToolEv({ tool: ev.tool, query: ev.query, sites: ev.sites });
|
|
98
98
|
continue;
|
|
99
99
|
}
|
|
100
100
|
|
|
101
101
|
if (ev.type === 'token' || ev.type === 'thinking') {
|
|
102
102
|
const c = typeof ev.content === 'string' ? ev.content : '';
|
|
103
103
|
if (!c) continue;
|
|
104
|
-
|
|
105
|
-
// Track think state
|
|
106
104
|
if (c.includes('<think>')) thinkOpen = true;
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
if (stillOpen) {
|
|
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) {
|
|
113
110
|
setThinkingActive(true);
|
|
114
111
|
setThinkingText(clean);
|
|
115
112
|
setStreamText('');
|
|
@@ -123,26 +120,19 @@ export default function AppRoot() {
|
|
|
123
120
|
setStreamText(clean);
|
|
124
121
|
}
|
|
125
122
|
}
|
|
126
|
-
|
|
127
123
|
if (ev.type === 'done' || ev.type === 'timeout') {
|
|
128
|
-
if (ev.type === 'timeout' &&
|
|
129
|
-
accumulatedRef.current += '\n\u2014 timed out \u2014';
|
|
130
|
-
}
|
|
124
|
+
if (ev.type === 'timeout' && accumRef.current) accumRef.current += '\n\u2014timed out\u2014';
|
|
131
125
|
break;
|
|
132
126
|
}
|
|
133
127
|
}
|
|
134
128
|
} catch (err) {
|
|
135
|
-
if (!ac.signal.aborted) {
|
|
136
|
-
setMessages(p => [...p, { type: 'assistant', content: 'Error: ' + (err.message || 'Connection failed') }]);
|
|
137
|
-
}
|
|
129
|
+
if (!ac.signal.aborted) setMessages(p => [...p, { role: 'assistant', content: 'Error: ' + (err.message || 'connection failed') }]);
|
|
138
130
|
} finally {
|
|
139
131
|
setStreaming(false);
|
|
140
132
|
setThinkingActive(false);
|
|
141
133
|
abortRef.current = null;
|
|
142
|
-
const final =
|
|
143
|
-
if (final) {
|
|
144
|
-
setMessages(p => [...p, { type: 'assistant', content: stripThink(final) }]);
|
|
145
|
-
}
|
|
134
|
+
const final = accumRef.current;
|
|
135
|
+
if (final) setMessages(p => [...p, { role: 'assistant', content: strip(final) }]);
|
|
146
136
|
setStreamText('');
|
|
147
137
|
setThinkingText('');
|
|
148
138
|
}
|
|
@@ -151,27 +141,22 @@ export default function AppRoot() {
|
|
|
151
141
|
function cycleModel() {
|
|
152
142
|
const models = getModels();
|
|
153
143
|
setActiveModel(p => {
|
|
154
|
-
let
|
|
155
|
-
for (const m of models) {
|
|
156
|
-
if (found) return m.id;
|
|
157
|
-
if (m.id === p) found = true;
|
|
158
|
-
}
|
|
144
|
+
let f = false;
|
|
145
|
+
for (const m of models) { if (f) return m.id; if (m.id === p) f = true; }
|
|
159
146
|
return models[0].id;
|
|
160
147
|
});
|
|
161
148
|
}
|
|
162
149
|
|
|
163
|
-
function
|
|
150
|
+
function execCmd(item) {
|
|
164
151
|
if (!item) return;
|
|
165
152
|
const t = item.trigger;
|
|
166
|
-
if (t === '/exit')
|
|
153
|
+
if (t === '/exit') { exit(); return; }
|
|
167
154
|
if (t === '/clear') setMessages([]);
|
|
168
155
|
if (t === '/model') cycleModel();
|
|
169
156
|
if (t === '/thoughts') setShowThought(p => !p);
|
|
170
|
-
if (t === '/resume') { const q =
|
|
171
|
-
if (t === '/help') setMessages(p => [...p, {
|
|
172
|
-
setInput('');
|
|
173
|
-
setShowOverlay(false);
|
|
174
|
-
setOverlayIdx(0);
|
|
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);
|
|
175
160
|
}
|
|
176
161
|
|
|
177
162
|
useInput((inp, key) => {
|
|
@@ -179,187 +164,168 @@ export default function AppRoot() {
|
|
|
179
164
|
if (key.escape) { setShowOverlay(false); setInput(''); setOverlayIdx(0); return; }
|
|
180
165
|
if (key.upArrow) { setOverlayIdx(p => p > 0 ? p - 1 : COMMANDS.length - 1); return; }
|
|
181
166
|
if (key.downArrow) { setOverlayIdx(p => p < COMMANDS.length - 1 ? p + 1 : 0); return; }
|
|
182
|
-
if (key.return) {
|
|
167
|
+
if (key.return) { execCmd(COMMANDS[overlayIdx]); return; }
|
|
183
168
|
return;
|
|
184
169
|
}
|
|
185
170
|
if (!startupDone && !streaming) {
|
|
186
171
|
if (key.return) { setStartupDone(true); setInput(''); return; }
|
|
187
172
|
return;
|
|
188
173
|
}
|
|
189
|
-
if (key.escape) {
|
|
190
|
-
if (
|
|
174
|
+
if (key.escape && streaming) {
|
|
175
|
+
if (abortRef.current) abortRef.current.abort();
|
|
176
|
+
setStreaming(false); setStreamText(''); setThinkingActive(false);
|
|
191
177
|
return;
|
|
192
178
|
}
|
|
193
179
|
if (key.ctrl) {
|
|
194
|
-
if (inp === 'c' || inp === 'C') {
|
|
180
|
+
if (inp === 'c' || inp === 'C') { exit(); return; }
|
|
195
181
|
if (inp === 't' || inp === 'T') { setShowThought(p => !p); return; }
|
|
196
182
|
if (inp === 'p' || inp === 'P') { cycleModel(); return; }
|
|
197
|
-
if (inp === 'r' || inp === 'R') { const q =
|
|
183
|
+
if (inp === 'r' || inp === 'R') { const q = lastQRef.current; if (q && !streaming) handleSubmit(q); return; }
|
|
198
184
|
if (inp === 'l' || inp === 'L') { setMessages([]); return; }
|
|
199
185
|
if (inp === 'k' || inp === 'K') { setInput('/'); setShowOverlay(true); setOverlayIdx(0); return; }
|
|
200
186
|
}
|
|
201
187
|
});
|
|
202
188
|
|
|
203
|
-
// Build
|
|
204
|
-
const
|
|
189
|
+
// Build entries
|
|
190
|
+
const entries = [];
|
|
205
191
|
for (const msg of messages) {
|
|
206
|
-
if (msg.
|
|
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' });
|
|
192
|
+
if (msg.role === 'user' || msg.role === 'assistant') entries.push(msg);
|
|
219
193
|
}
|
|
220
194
|
|
|
221
|
-
|
|
222
|
-
if (
|
|
223
|
-
|
|
224
|
-
}
|
|
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 });
|
|
225
198
|
|
|
226
|
-
const
|
|
199
|
+
const MAX_CHAT = Math.max(3, rows - 10);
|
|
200
|
+
const vis = entries.slice(-MAX_CHAT);
|
|
227
201
|
|
|
228
202
|
// Startup screen
|
|
229
203
|
if (!startupDone && messages.length === 0) {
|
|
230
204
|
return h(Box, { flexDirection: 'column', width: '100%', height: rows, overflow: 'hidden' },
|
|
231
|
-
|
|
205
|
+
h(Box, { height: 2 }),
|
|
206
|
+
...LOGO.map((l, i) => h(Box, { key: 'l' + i, height: 1, paddingLeft: 2 },
|
|
232
207
|
h(Text, { color: '#00BBFF', bold: true }, l)
|
|
233
208
|
)),
|
|
234
|
-
h(Box, {
|
|
235
|
-
h(Box, {
|
|
236
|
-
h(Box, {
|
|
237
|
-
h(Box, {
|
|
238
|
-
h(Box, {
|
|
209
|
+
h(Box, { height: 1 }),
|
|
210
|
+
h(Box, { height: 1, paddingLeft: 2 }, h(Text, { color: '#FFFFFF', bold: true }, 'OpenAxies')),
|
|
211
|
+
h(Box, { height: 1, paddingLeft: 2 }, h(Text, { color: '#777788' }, 'local agent')),
|
|
212
|
+
h(Box, { height: 2 }),
|
|
213
|
+
h(Box, { height: 1, paddingLeft: 2 }, h(Text, { color: '#555577' }, 'Press Enter to begin')),
|
|
239
214
|
h(Box, { flexGrow: 1 }),
|
|
240
|
-
h(Box, {
|
|
215
|
+
h(Box, { height: 1, paddingLeft: 2 }, h(Text, { color: '#333344' }, 'esc interrupt ctrl+p models /help')),
|
|
241
216
|
);
|
|
242
217
|
}
|
|
243
218
|
|
|
244
|
-
//
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
}
|
|
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';
|
|
249
223
|
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
visible.push({ type: '_sep' });
|
|
253
|
-
visible.push({ type: '_ready' });
|
|
254
|
-
visible.push({ type: '_sep' });
|
|
224
|
+
function brdr(line) {
|
|
225
|
+
return '\u2502 ' + line + ' '.repeat(Math.max(0, W + 1 - line.length)) + '\u2502';
|
|
255
226
|
}
|
|
256
227
|
|
|
257
|
-
//
|
|
258
|
-
const
|
|
259
|
-
const
|
|
260
|
-
const
|
|
261
|
-
const
|
|
262
|
-
const
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
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 });
|
|
271
250
|
}
|
|
272
251
|
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
if (totalEst + l > maxLines) break;
|
|
278
|
-
totalEst += l;
|
|
279
|
-
showN = visible.length - i;
|
|
252
|
+
// Empty state
|
|
253
|
+
if (vis.length === 0) {
|
|
254
|
+
vis.push({ role: '_sep' });
|
|
255
|
+
vis.push({ role: '_idle' });
|
|
280
256
|
}
|
|
281
|
-
const show = visible.slice(visible.length - showN);
|
|
282
257
|
|
|
258
|
+
// Render entries
|
|
283
259
|
const els = [];
|
|
284
|
-
for (let i = 0; i <
|
|
285
|
-
const
|
|
260
|
+
for (let i = 0; i < vis.length; i++) {
|
|
261
|
+
const e = vis[i];
|
|
286
262
|
const k = 'e' + i;
|
|
287
263
|
|
|
288
|
-
if (
|
|
289
|
-
els.push(h(Box, { key: k, height: 1 }, h(Text, { color: '#
|
|
290
|
-
} else if (
|
|
291
|
-
els.push(h(Box, { key: k, height: 1, paddingLeft:
|
|
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')));
|
|
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:')));
|
|
294
268
|
for (let ci = 0; ci < COMMANDS.length; ci++) {
|
|
295
269
|
const c = COMMANDS[ci];
|
|
296
|
-
const sel = ci ===
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
els.push(h(Box, { key: k + 'c' + ci, height: 1, paddingLeft: 2 },
|
|
300
|
-
h(Text, { color: col }, prefix + c.trigger),
|
|
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),
|
|
301
273
|
h(Text, { color: '#444466' }, ' ' + (c.desc || ''))
|
|
302
274
|
));
|
|
303
275
|
}
|
|
304
|
-
} else if (
|
|
305
|
-
els.push(h(Box, { key: k
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
} else if (
|
|
310
|
-
els.push(h(Box, { key: k
|
|
311
|
-
h(Text, { color: '#
|
|
276
|
+
} else if (e.role === 'user') {
|
|
277
|
+
els.push(h(Box, { key: k, height: 1, paddingLeft: 2 },
|
|
278
|
+
h(Text, { color: '#888899' }, '\u203A '),
|
|
279
|
+
h(Text, { color: hex.neonBlue, wrap: 'wrap' }, e.content),
|
|
280
|
+
));
|
|
281
|
+
} else if (e.role === 'assistant' || e.role === 'ongoing') {
|
|
282
|
+
els.push(h(Box, { key: k, height: 1, paddingLeft: 2 },
|
|
283
|
+
h(Text, { color: '#88FF88' }, '\u2022 '),
|
|
284
|
+
h(Text, { color: '#FFFFFF', wrap: 'wrap' }, e.content),
|
|
312
285
|
));
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
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),
|
|
289
|
+
));
|
|
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)' : '')),
|
|
317
298
|
));
|
|
318
299
|
}
|
|
319
300
|
}
|
|
320
301
|
|
|
321
|
-
|
|
322
|
-
while (els.length < maxLines) {
|
|
323
|
-
els.push(h(Box, { key: 'f' + els.length, height: 1 }));
|
|
324
|
-
}
|
|
325
|
-
|
|
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)));
|
|
302
|
+
const chat = h(Box, { flexGrow: 1, flexDirection: 'column', overflow: 'hidden' }, ...els);
|
|
337
303
|
|
|
338
304
|
// Input
|
|
339
|
-
const inputLine = h(Box, { height:
|
|
305
|
+
const inputLine = h(Box, { height: 1, flexShrink: 0, paddingLeft: 2, paddingRight: 2 },
|
|
306
|
+
h(Text, { color: '#888899' }, '\u203A '),
|
|
340
307
|
h(Text, { color: hex.greenOnline }, '\u258C'),
|
|
341
308
|
h(Box, { width: 1 }),
|
|
342
309
|
h(TextInput, {
|
|
343
310
|
value: input,
|
|
344
311
|
onChange: setInput,
|
|
345
|
-
onSubmit:
|
|
312
|
+
onSubmit: v => { if (typeof v === 'string' && v.trim() && !streaming) handleSubmit(v); },
|
|
346
313
|
placeholder: '',
|
|
347
314
|
focus: !showOverlay,
|
|
348
315
|
showCursor: true,
|
|
349
316
|
})
|
|
350
317
|
);
|
|
351
318
|
|
|
352
|
-
// Footer
|
|
353
|
-
const footer = h(Box, { height:
|
|
319
|
+
// Footer bar
|
|
320
|
+
const footer = h(Box, { height: 1, flexShrink: 0, paddingLeft: 2, paddingRight: 2 },
|
|
354
321
|
h(Text, { color: '#444466' }, 'esc \u2248 ctrl+t ctrl+p ctrl+k'),
|
|
355
322
|
h(Box, { flexGrow: 1 }),
|
|
356
|
-
h(Text, { color: '#555577' },
|
|
323
|
+
h(Text, { color: '#555577' }, label.toLowerCase() + ' \u2022 local'),
|
|
357
324
|
);
|
|
358
325
|
|
|
359
326
|
return h(Box, { flexDirection: 'column', width: '100%', height: rows, overflow: 'hidden' },
|
|
360
327
|
header,
|
|
361
|
-
|
|
362
|
-
h(Box, { flexGrow: 1, flexDirection: 'column', overflow: 'hidden' }, ...els),
|
|
328
|
+
chat,
|
|
363
329
|
inputLine,
|
|
364
330
|
footer,
|
|
365
331
|
);
|