openaxies 0.7.0 → 1.0.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.
@@ -0,0 +1,6 @@
1
+ The above error occurred in the <AppRoot> component:
2
+
3
+ at AppRoot (file:///storage/emulated/0/agent-open/openaxis/src/App.js:60:22)
4
+ at App (file:///storage/emulated/0/agent-open/openaxis/node_modules/ink/build/components/App.js:17:16)
5
+
6
+ React will try to recreate this component tree from scratch using the error boundary you provided, InternalApp.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openaxies",
3
- "version": "0.7.0",
3
+ "version": "1.0.0",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "bin": {
package/src/App.js CHANGED
@@ -1,471 +1,191 @@
1
1
  import React from 'react';
2
- import { Box, Text, useInput, useStdout } from 'ink';
2
+ import { Box, Text, useStdout } from 'ink';
3
+ import TextInput from 'ink-text-input';
3
4
  import { hex } from './config/theme.js';
4
5
  import { getModels, getModelById, DEFAULT_MODEL_ID } from './config/models.js';
5
6
  import { callModel } from './providers/index.js';
6
- import { createHeaderBar, HEADER_HEIGHT } from './components/BrandHeader.js';
7
- import TextInput from 'ink-text-input';
7
+ import { Typography } from './components/ui/Typography.js';
8
+ import { Separator } from './components/ui/Separator.js';
9
+ import { ActivityItem } from './components/timeline/ActivityItem.js';
10
+ import { TodoTracker } from './components/timeline/TodoTracker.js';
8
11
 
9
12
  const h = React.createElement;
10
- export default AppRoot;
11
13
 
12
14
  const STARTUP_LOGO = [
13
15
  ' \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',
16
+ ' \u2588\u2588\u2554\u2550\u2550\u2550\u2588\u2588\u2557',
17
+ ' \u2588\u2588\u2551 \u2588\u2588\u2551',
18
+ ' \u2588\u2588\u2551 \u2588\u2588\u2551',
19
+ ' \u255a\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255d',
18
20
  ];
19
21
 
20
- function buildHistory(messages, newText) {
21
- const h = [];
22
- for (let i = 0; i < messages.length; i++) {
23
- const m = messages[i];
24
- if (m === null || m === undefined || typeof m !== 'object') continue;
25
- if (m.role === 'user' || m.role === 'assistant') {
26
- const c = typeof m.content === 'string' ? m.content : '';
27
- h.push({ role: m.role, content: c });
28
- }
29
- }
30
- if (typeof newText === 'string' && newText.length > 0) {
31
- h.push({ role: 'user', content: newText });
32
- }
33
- return h;
34
- }
35
-
36
- function fmtTime(s) {
37
- if (typeof s !== 'number' || s < 0) return '0.0s';
38
- return s.toFixed(1) + 's';
39
- }
40
-
41
- function hr(cols) {
42
- const n = typeof cols === 'number' && cols > 0 ? cols : 80;
43
- let s = '';
44
- for (let i = 0; i < n; i++) s = s + '\u2500';
45
- return h(Text, { color: '#1A1A28' }, s);
46
- }
47
-
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
- ];
58
-
59
- function AppRoot() {
22
+ export default function AppRoot() {
60
23
  const { stdout } = useStdout();
61
24
  const rows = stdout.rows || 24;
62
25
  const cols = stdout.columns || 80;
63
26
 
64
27
  const [messages, setMessages] = React.useState([]);
65
28
  const [activeModel, setActiveModel] = React.useState(DEFAULT_MODEL_ID);
66
- const [streamText, setStreamText] = React.useState('');
29
+ const [inputBuffer, setInputBuffer] = React.useState('');
67
30
  const [streamingActive, setStreamingActive] = React.useState(false);
68
- const [isThinking, setIsThinking] = React.useState(false);
69
- const [spinnerIdx, setSpinnerIdx] = React.useState(0);
31
+ const [streamContent, setStreamContent] = React.useState('');
32
+ const [thoughtContent, setThoughtContent] = React.useState('');
70
33
  const [thinkingElapsed, setThinkingElapsed] = React.useState(0);
71
- const [inputBuffer, setInputBuffer] = React.useState('');
72
- const [showOverlay, setShowOverlay] = React.useState(false);
73
- const [overlayIndex, setOverlayIndex] = React.useState(0);
74
- const [toolInfo, setToolInfo] = React.useState(null);
75
- const [showThoughts, setShowThoughts] = React.useState(true);
34
+ const [isThinking, setIsThinking] = React.useState(false);
76
35
  const [startupDone, setStartupDone] = React.useState(false);
36
+ const [todos, setTodos] = React.useState([]);
77
37
 
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);
84
-
85
- const abortRef = React.useRef(null);
86
- const connRef = React.useRef(null);
87
- const spinnerRef = React.useRef(null);
88
- const timerRef = React.useRef(null);
89
- const thinkStartRef = React.useRef(null);
90
- const lastQueryRef = React.useRef('');
38
+ const abortControllerRef = React.useRef(null);
91
39
 
92
- React.useEffect(function () {
93
- if (isThinking === false) return;
94
- let idx = 0;
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);
98
- }, 100);
99
- spinnerRef.current = si;
100
- timerRef.current = ti;
101
- return function () { clearInterval(si); clearInterval(ti); };
40
+ React.useEffect(() => {
41
+ let timer;
42
+ if (isThinking) {
43
+ timer = setInterval(() => {
44
+ setThinkingElapsed(prev => prev + 0.1);
45
+ }, 100);
46
+ }
47
+ return () => clearInterval(timer);
102
48
  }, [isThinking]);
103
49
 
104
- function cycleModel() {
105
- const models = getModels();
106
- setActiveModel(function (prev) {
107
- let found = false;
108
- for (let i = 0; i < models.length; i++) {
109
- if (found) return models[i].id;
110
- if (models[i].id === prev) found = true;
111
- }
112
- return models[0].id;
113
- });
114
- }
115
-
116
- function checkThink(text) {
117
- const o = text.lastIndexOf('<think>');
118
- const c = text.lastIndexOf('</think>');
119
- if (o === -1 && c === -1) return false;
120
- return o > c;
121
- }
122
-
123
- function filterText(t) {
124
- if (showThoughts === true) return t;
125
- return t.split('<think>').join('').split('</think>').join('');
126
- }
50
+ const model = getModelById(activeModel);
51
+ const modelLabel = model ? model.label : 'Unknown Model';
127
52
 
128
53
  async function handleSubmit(text) {
129
- const safe = typeof text === 'string' ? text : '';
130
- if (safe.length === 0 || streamingActive === true) return;
131
- if (startupDone === false) setStartupDone(true);
132
- lastQueryRef.current = safe;
54
+ if (!text || streamingActive) return;
55
+ if (!startupDone) setStartupDone(true);
56
+
57
+ const userMsg = { role: 'user', content: text };
58
+ setMessages(prev => [...prev, userMsg]);
59
+ setInputBuffer('');
133
60
  setStreamingActive(true);
134
- setToolInfo(null);
135
- setStreamText(' connecting');
61
+ setStreamContent('');
62
+ setThoughtContent('');
136
63
  setThinkingElapsed(0);
137
- connRef.current = setInterval(function () {
138
- setStreamText(function (p) {
139
- if (p === ' connecting') return ' connecting.';
140
- if (p === ' connecting.') return ' connecting..';
141
- if (p === ' connecting..') return ' connecting...';
142
- return ' connecting';
143
- });
144
- }, 400);
145
- setMessages(function (p) { return p.concat([{ role: 'user', content: safe, id: 'u-' + Date.now() }]); });
146
- setInputBuffer('');
147
- const modelInfo = getModelById(activeModel);
148
- if (modelInfo === null) {
149
- if (connRef.current !== null) clearInterval(connRef.current), connRef.current = null;
150
- setStreamingActive(false); setStreamText('');
151
- setMessages(function (p) { return p.concat([{ role: 'assistant', content: 'No model selected.', id: 'e-' + Date.now() }]); });
152
- return;
153
- }
154
- const history = buildHistory(messages, safe);
155
- const abortController = new AbortController();
156
- abortRef.current = abortController;
64
+
65
+ abortControllerRef.current = new AbortController();
66
+
157
67
  try {
158
- const gen = callModel(modelInfo, history, abortController.signal);
159
- let accumulated = '';
160
- let firstEvent = true;
161
- let thinkStarted = false;
162
- for await (const event of gen) {
163
- if (abortController.signal.aborted === true) break;
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; }
166
- if (event.type === 'thinking' || event.type === 'token') {
167
- const c = typeof event.content === 'string' ? event.content : '';
168
- accumulated = accumulated + c;
169
- const inside = checkThink(accumulated);
170
- setStreamText(accumulated);
171
- setIsThinking(inside);
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); }
174
- }
175
- if (event.type === 'done') break;
176
- if (event.type === 'timeout') {
177
- if (accumulated.length > 0) accumulated = accumulated + '\n\n\u2014 stream timed out \u2014';
178
- setStreamText(accumulated); break;
68
+ const history = [...messages, userMsg];
69
+ const stream = callModel({ id: activeModel }, history, abortControllerRef.current.signal);
70
+
71
+ for await (const event of stream) {
72
+ if (event.type === 'tool') {
73
+ setMessages(prev => [...prev, { type: 'tool', tool: event.tool, query: event.query }]);
74
+ } else if (event.type === 'token') {
75
+ const content = event.content;
76
+ if (content.includes('<think>')) {
77
+ setIsThinking(true);
78
+ setThoughtContent(prev => prev + content.replace('<think>', ''));
79
+ } else if (content.includes('</think>')) {
80
+ setIsThinking(false);
81
+ setThoughtContent(prev => prev + content.replace('</think>', ''));
82
+ setMessages(prev => [...prev, { type: 'thought', content: thoughtContent, elapsed: thinkingElapsed.toFixed(1) + 's' }]);
83
+ } else if (isThinking) {
84
+ setThoughtContent(prev => prev + content);
85
+ } else {
86
+ setStreamContent(prev => prev + content);
87
+ }
179
88
  }
180
89
  }
181
- if (connRef.current !== null) clearInterval(connRef.current), connRef.current = null;
182
- setIsThinking(false); thinkStartRef.current = null; setThinkingElapsed(0); setStreamingActive(false);
183
- setStreamText('');
184
- if (accumulated.length > 0) {
185
- setMessages(function (p) { return p.concat([{ role: 'assistant', content: accumulated, id: 'a-' + Date.now() }]); });
186
- }
187
- } catch (err) {
188
- if (connRef.current !== null) clearInterval(connRef.current), connRef.current = null;
189
- setIsThinking(false); thinkStartRef.current = null; setThinkingElapsed(0); setStreamingActive(false); setStreamText('');
190
- const errMsg = err !== null && err !== undefined ? (err.message || String(err)) : 'Unknown error';
191
- setMessages(function (p) { return p.concat([{ role: 'assistant', content: 'Error: ' + errMsg, id: 'er-' + Date.now() }]); });
192
- }
193
- }
194
-
195
- function handleChange(v) {
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); }
199
- }
200
-
201
- function handleCmd(item) {
202
- if (item === null || item === undefined) return;
203
- const t = item.trigger;
204
- if (typeof t !== 'string') return;
205
- if (t === '/exit') process.exit(0);
206
- if (t === '/clear') { setMessages([]); }
207
- if (t === '/model') cycleModel();
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);
212
- }
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);
215
- }
216
-
217
- useInput(function handleInput(input, key) {
218
- if (showOverlay === true) {
219
- if (key.escape === true) { setShowOverlay(false); setInputBuffer(''); setOverlayIndex(0); 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
- }
230
- return;
231
- }
232
- if (key.return === true) {
233
- const s = typeof inputBuffer === 'string' ? inputBuffer : '';
234
- if (s.length > 0 && streamingActive === false) handleSubmit(s);
235
- return;
236
- }
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;
90
+ } catch (e) {
91
+ setMessages(prev => [...prev, { type: 'assistant', content: 'Error connecting to model.' }]);
92
+ } finally {
93
+ setStreamingActive(false);
94
+ setIsThinking(false);
95
+ if (streamContent) {
96
+ setMessages(prev => [...prev, { type: 'assistant', content: streamContent }]);
249
97
  }
250
- if (input === 'l' || input === 'L') { setMessages([]); return; }
251
- if (input === 'k' || input === 'K') { setInputBuffer('/'); setShowOverlay(true); setOverlayIndex(0); return; }
252
- }
253
- });
254
-
255
- const modelInfo = getModelById(activeModel);
256
- const modelLabel = modelInfo !== null ? modelInfo.label : 'OpenAxies Llama';
257
-
258
- // Build viewport sections
259
- const sections = [];
260
-
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' });
270
- }
271
-
272
- // Messages
273
- for (let i = 0; i < messages.length; i++) {
274
- const msg = messages[i];
275
- if (msg === null || typeof msg !== 'object') continue;
276
- const content = typeof msg.content === 'string' ? msg.content : '';
277
- if (content.length === 0) continue;
278
- if (msg.role === 'user') {
279
- sections.push({ t: 'sep' });
280
- sections.push({ t: 'user', text: content });
281
- } else {
282
- if (sections.length > 0 && sections[sections.length - 1].t !== 'sep') sections.push({ t: 'sep' });
283
- sections.push({ t: 'assistant', text: filterText(content) });
284
98
  }
285
99
  }
286
100
 
287
- // Streaming
288
- if (typeof streamText === 'string' && streamText.length > 0) {
289
- const clean = filterText(streamText);
290
- if (toolInfo !== null && toolInfo.used === true && toolInfo.query.length > 0) {
291
- sections.push({ t: 'tool', tool: toolInfo.tool, query: toolInfo.query, sites: toolInfo.sites });
101
+ const renderHeader = () => {
102
+ if (!startupDone) return null;
103
+ return h(Box, { height: 1, paddingLeft: 1, paddingRight: 1 },
104
+ h(Typography.Header, 'OpenAxies'),
105
+ h(Typography.Muted, ` \u2022 ${modelLabel}`),
106
+ h(Box, { flexGrow: 1 }),
107
+ h(Typography.Dim, 'local')
108
+ );
109
+ };
110
+
111
+ const renderFooter = () => {
112
+ return h(Box, {
113
+ height: 1,
114
+ paddingLeft: 1,
115
+ paddingRight: 1,
116
+ flexDirection: 'row',
117
+ alignItems: 'center'
118
+ },
119
+ h(Typography.Dim, 'esc interrupt \u2022 ctrl+t thoughts \u2022 ctrl+p models \u2022 ctrl+k commands'),
120
+ h(Box, { flexGrow: 1 }),
121
+ h(Typography.Muted, `${modelLabel.toLowerCase()} \u2022 local`)
122
+ );
123
+ };
124
+
125
+ const renderStartup = () => {
126
+ return h(Box, {
127
+ flexDirection: 'column',
128
+ paddingLeft: 1,
129
+ paddingTop: 2,
130
+ backgroundColor: '#000000'
131
+ },
132
+ ...STARTUP_LOGO.map(line => h(Typography.Accent, line)),
133
+ h(Box, { height: 1 }),
134
+ h(Typography.Header, 'OpenAxies'),
135
+ h(Typography.Muted, 'local ai agent runtime'),
136
+ h(Box, { height: 2 }),
137
+ h(Typography.Dim, 'Press Enter to begin')
138
+ );
139
+ };
140
+
141
+ const renderTimeline = () => {
142
+ const events = [...messages];
143
+ if (isThinking) {
144
+ events.push({ type: 'thought', content: thoughtContent, elapsed: thinkingElapsed.toFixed(1) + 's' });
292
145
  }
293
- if (isThinking === true) {
294
- sections.push({ t: 'thinking', spin: SPINNER[spinnerIdx], elapsed: fmtTime(thinkingElapsed), content: clean });
295
- } else {
296
- sections.push({ t: 'stream', text: clean });
146
+ if (streamingActive && streamContent) {
147
+ events.push({ type: 'assistant', content: streamContent });
297
148
  }
298
- }
299
-
300
- // Startup screen
301
- if (startupDone === false && messages.length === 0) {
302
- sections.push({ t: 'startup' });
303
- }
304
-
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
- }
321
-
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)
366
- )
367
- );
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)')
372
- )
373
- );
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')
378
- )
379
- );
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 || ''))
387
- )
388
- );
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')
393
- )
394
- );
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')
412
- )
413
- );
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')
418
- )
419
- );
420
- }
421
- }
422
-
423
- // Fill remaining
424
- while (sectionEls.length < availLines) {
425
- sectionEls.push(h(Box, { key: 'fl-' + sectionEls.length, height: 1 }, h(Text, {}, '')));
426
- }
427
-
428
- const viewport = h(Box, {
429
- flexGrow: 1,
430
- height: availLines,
431
- flexDirection: 'column',
432
- overflow: 'hidden',
433
- }, ...sectionEls);
434
-
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
149
 
464
- return h(Box, { flexDirection: 'column', width: '100%', height: rows, overflow: 'hidden' },
465
- headerBar,
466
- headerSep,
467
- viewport,
468
- inputLine,
469
- footerLine,
150
+ return h(Box, {
151
+ flexGrow: 1,
152
+ flexDirection: 'column',
153
+ overflow: 'hidden',
154
+ paddingLeft: 0,
155
+ paddingRight: 0,
156
+ },
157
+ ...events.slice(-15).map((event, i) => h(ActivityItem, { key: i, event }))
158
+ );
159
+ };
160
+
161
+ return h(Box, {
162
+ flexDirection: 'column',
163
+ width: '100%',
164
+ height: rows,
165
+ overflow: 'hidden'
166
+ },
167
+ renderStartup() && !startupDone ? renderStartup() : null,
168
+ startupDone && renderHeader(),
169
+ startupDone && h(Separator),
170
+ startupDone && renderTimeline(),
171
+ startupDone && h(Box, { height: 1 }, h(Separator)),
172
+ startupDone && h(Box, {
173
+ height: 1,
174
+ paddingLeft: 1,
175
+ paddingRight: 1,
176
+ flexDirection: 'row',
177
+ alignItems: 'center'
178
+ },
179
+ h(Typography.Accent, '▌ Ask OpenAxies'),
180
+ h(Box, { flexGrow: 1 }),
181
+ h(TextInput, {
182
+ value: inputBuffer,
183
+ onChange: setInputBuffer,
184
+ onSubmit: handleSubmit,
185
+ focus: true,
186
+ showCursor: true
187
+ })
188
+ ),
189
+ startupDone && renderFooter()
470
190
  );
471
191
  }
@@ -0,0 +1,46 @@
1
+ import React from 'react';
2
+ import { Box, Text } from 'ink';
3
+ import { Typography } from '../ui/Typography.js';
4
+ import { hex } from '../../config/theme.js';
5
+
6
+ const h = React.createElement;
7
+
8
+ export function ActivityItem({ event }) {
9
+ switch (event.type) {
10
+ case 'user':
11
+ return h(Box, { flexDirection: 'column', paddingLeft: 1, paddingRight: 1, marginBottom: 1 },
12
+ h(Typography.Subheader, 'You'),
13
+ h(Box, { paddingLeft: 1 }, h(Typography.Body, { color: hex.neonBlue }, event.content))
14
+ );
15
+ case 'assistant':
16
+ return h(Box, { flexDirection: 'column', paddingLeft: 1, paddingRight: 1, marginBottom: 1 },
17
+ h(Typography.Body, event.content)
18
+ );
19
+ case 'thought':
20
+ return h(Box, {
21
+ flexDirection: 'column',
22
+ paddingLeft: 1,
23
+ paddingRight: 1,
24
+ marginBottom: 1,
25
+ backgroundColor: '#12121A'
26
+ },
27
+ h(Box, { flexDirection: 'row', alignItems: 'center' },
28
+ h(Typography.Thinking, `\u25CB Thinking \u2022 ${event.elapsed || '0.0s'}`)
29
+ ),
30
+ h(Box, { paddingLeft: 1 }, h(Typography.Thinking, event.content))
31
+ );
32
+ case 'tool':
33
+ return h(Box, { paddingLeft: 1, paddingRight: 1, marginBottom: 0.5 },
34
+ h(Typography.Muted, `\u2500\u2500 ${event.tool} ${event.query ? ' \u2014 ' + event.query : ''}`)
35
+ );
36
+ case 'todo':
37
+ const icon = event.status === 'completed' ? '\u2713' : (event.status === 'running' ? '\u25B2' : '\u25CB');
38
+ const color = event.status === 'completed' ? '#00FF88' : (event.status === 'running' ? hex.neonBlue : '#666688');
39
+ return h(Box, { paddingLeft: 1, paddingRight: 1 },
40
+ h(Text, { color }, `${icon} `),
41
+ h(Typography.Muted, event.text)
42
+ );
43
+ default:
44
+ return null;
45
+ }
46
+ }
@@ -0,0 +1,23 @@
1
+ import React from 'react';
2
+ import { Box } from 'ink';
3
+ import { ActivityItem } from './ActivityItem.js';
4
+
5
+ const h = React.createElement;
6
+
7
+ export function TodoTracker({ todos }) {
8
+ if (!todos || todos.length === 0) return null;
9
+
10
+ return h(Box, {
11
+ flexDirection: 'column',
12
+ paddingLeft: 1,
13
+ paddingRight: 1,
14
+ marginBottom: 1,
15
+ borderStyle: 'single',
16
+ borderColor: '#1A1A28'
17
+ },
18
+ ...todos.map((todo, i) => h(ActivityItem, {
19
+ key: i,
20
+ event: { type: 'todo', text: todo.text, status: todo.status }
21
+ }))
22
+ );
23
+ }
@@ -0,0 +1,10 @@
1
+ import React from 'react';
2
+ import { Box, Text } from 'ink';
3
+
4
+ const h = React.createElement;
5
+
6
+ export function Separator() {
7
+ return h(Box, { height: 1, width: '100%' },
8
+ h(Text, { color: '#1A1A28' }, '\u2500'.repeat(80))
9
+ );
10
+ }
@@ -0,0 +1,15 @@
1
+ import React from 'react';
2
+ import { Text } from 'ink';
3
+ import { hex } from '../../config/theme.js';
4
+
5
+ const h = React.createElement;
6
+
7
+ export const Typography = {
8
+ Header: ({ children }) => h(Text, { color: '#FFFFFF', bold: true }, children),
9
+ Subheader: ({ children }) => h(Text, { color: '#AAAAAA', bold: true }, children),
10
+ Body: ({ children, color = '#FFFFFF' }) => h(Text, { color, wrap: 'wrap' }, children),
11
+ Muted: ({ children }) => h(Text, { color: '#666688' }, children),
12
+ Dim: ({ children }) => h(Text, { color: '#444466' }, children),
13
+ Accent: ({ children }) => h(Text, { color: hex.neonBlue, bold: true }, children),
14
+ Thinking: ({ children }) => h(Text, { color: '#FF9F43' }, children),
15
+ };
@@ -1,17 +0,0 @@
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;
@@ -1,66 +0,0 @@
1
- import React from 'react';
2
- import { Box, Text, useInput, useStdout } from 'ink';
3
- import TextInput from 'ink-text-input';
4
- import { hex } from '../config/theme.js';
5
-
6
- const h = React.createElement;
7
-
8
- export const DOCK_HEIGHT = 4;
9
-
10
- const PROMPT = '> ';
11
-
12
- export function createComposerDock(config) {
13
- if (config === null || config === undefined || typeof config !== 'object') {
14
- throw new Error('createComposerDock requires a config object');
15
- }
16
-
17
- const value = typeof config.value === 'string' ? config.value : '';
18
- const onChange = config.onChange;
19
- const onSubmit = config.onSubmit;
20
- const inputActive = config.inputActive !== false;
21
- const isStreaming = config.isStreaming === true;
22
- const modelLabel = typeof config.modelLabel === 'string' ? config.modelLabel : '';
23
- const modelStatus = config.modelStatus === true;
24
- const safeCols = typeof config.terminalWidth === 'number' && config.terminalWidth > 0 ? config.terminalWidth : 80;
25
-
26
- const placeholder = isStreaming ? 'Ctrl+C to interrupt...' : 'type a message...';
27
- const innerW = safeCols - 2;
28
-
29
- return h(Box, {
30
- width: '100%',
31
- height: DOCK_HEIGHT,
32
- flexDirection: 'column',
33
- flexShrink: 0,
34
- },
35
- h(Box, { height: 1, paddingLeft: 1 },
36
- h(Text, { color: '#1A1A28' }, '\u2500'.repeat(innerW))
37
- ),
38
- h(Box, { height: 1, paddingLeft: 1, paddingRight: 1 },
39
- h(Text, { color: hex.border }, '\u2502'),
40
- h(Text, { color: inputActive ? hex.neonBlue : '#444466', bold: true }, ' ' + PROMPT),
41
- h(Box, { flexGrow: 1, height: 1, overflow: 'hidden' },
42
- h(TextInput, {
43
- value: value,
44
- onChange: onChange,
45
- onSubmit: onSubmit,
46
- placeholder: placeholder,
47
- focus: inputActive,
48
- showCursor: true,
49
- })
50
- ),
51
- h(Text, { color: '#444466' }, ' '),
52
- h(Text, { color: hex.border }, '\u2502')
53
- ),
54
- h(Box, { height: 1, paddingLeft: 1, paddingRight: 1 },
55
- h(Text, { color: hex.border }, '\u2514'),
56
- h(Box, { flexGrow: 1, height: 1, overflow: 'hidden' },
57
- h(Text, { color: '#333344' }, '\u2500'.repeat(innerW - 2))
58
- ),
59
- h(Text, { color: hex.border }, '\u2518')
60
- ),
61
- h(Box, { height: 1, paddingRight: 1 },
62
- h(Box, { flexGrow: 1 }),
63
- h(Text, { color: '#444466', bold: modelStatus }, modelLabel || '')
64
- )
65
- );
66
- }