@astra-code/astra-ai 0.1.3 → 0.1.5
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/README.md +8 -0
- package/dist/app/App.js +256 -37
- package/dist/app/App.js.map +1 -1
- package/dist/lib/backendClient.js +5 -1
- package/dist/lib/backendClient.js.map +1 -1
- package/dist/lib/voice.js +19 -2
- package/dist/lib/voice.js.map +1 -1
- package/package.json +1 -1
- package/src/app/App.tsx +338 -111
- package/src/lib/backendClient.ts +5 -1
- package/src/lib/voice.ts +19 -2
- package/src/types/events.ts +11 -0
package/README.md
CHANGED
|
@@ -2,6 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
`astra-code-term` is the npm-first Astra terminal client built with TypeScript, React, and Ink.
|
|
4
4
|
|
|
5
|
+
## Repository structure
|
|
6
|
+
|
|
7
|
+
- `.env.example` — sample configuration
|
|
8
|
+
- `docs/` — supplemental documentation
|
|
9
|
+
- `install.sh` — helper script for setup
|
|
10
|
+
- `src/` — Ink CLI source code (entrypoint in `src/index.ts`)
|
|
11
|
+
- `tsconfig.json` — shared TypeScript configuration
|
|
12
|
+
|
|
5
13
|
## Install
|
|
6
14
|
|
|
7
15
|
### Global (recommended)
|
package/dist/app/App.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import React, { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
3
3
|
import { Box, Text, useApp, useInput } from "ink";
|
|
4
4
|
import Spinner from "ink-spinner";
|
|
@@ -32,6 +32,7 @@ const ASTRA_ASCII = `
|
|
|
32
32
|
### ### ######## ### ### ### ### ### ######## ######## ######### ##########
|
|
33
33
|
by Sean Donovan
|
|
34
34
|
`;
|
|
35
|
+
const ASTRA_COMPACT = "ASTRA CODE";
|
|
35
36
|
const WELCOME_WIDTH = 96;
|
|
36
37
|
const centerLine = (text, width = WELCOME_WIDTH) => {
|
|
37
38
|
const trimmed = text.trim();
|
|
@@ -44,6 +45,9 @@ const centerLine = (text, width = WELCOME_WIDTH) => {
|
|
|
44
45
|
const FOUNDER_WELCOME = centerLine("Welcome to Astra from Astra CEO & Founder, Sean Donovan");
|
|
45
46
|
const HISTORY_SETTINGS_URL = "https://astra-web-builder.vercel.app/settings";
|
|
46
47
|
const VOICE_SILENCE_MS = Number(process.env.ASTRA_VOICE_SILENCE_MS ?? "3000");
|
|
48
|
+
const VOICE_MIN_CHARS = Number(process.env.ASTRA_VOICE_MIN_CHARS ?? "4");
|
|
49
|
+
const VOICE_DUPLICATE_WINDOW_MS = Number(process.env.ASTRA_VOICE_DUPLICATE_WINDOW_MS ?? "10000");
|
|
50
|
+
const VOICE_NOISE_WORDS = new Set(["you", "yes", "yeah", "yep", "ok", "okay", "uh", "um", "hmm"]);
|
|
47
51
|
const TOOL_SNIPPET_LINES = 6;
|
|
48
52
|
const NOISY_EVENT_TYPES = new Set([
|
|
49
53
|
"timing",
|
|
@@ -145,8 +149,10 @@ const extractAssistantText = (event) => {
|
|
|
145
149
|
}
|
|
146
150
|
return null;
|
|
147
151
|
};
|
|
148
|
-
const DIVIDER = "────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────";
|
|
149
152
|
const LABEL_WIDTH = 10;
|
|
153
|
+
const HEADER_PATH_MAX = Number(process.env.ASTRA_HEADER_PATH_MAX ?? "42");
|
|
154
|
+
const MIN_DIVIDER = 64;
|
|
155
|
+
const MAX_DIVIDER = 120;
|
|
150
156
|
const styleForKind = (kind) => {
|
|
151
157
|
switch (kind) {
|
|
152
158
|
case "assistant":
|
|
@@ -183,14 +189,83 @@ const normalizeAssistantText = (input) => {
|
|
|
183
189
|
if (!input) {
|
|
184
190
|
return "";
|
|
185
191
|
}
|
|
186
|
-
|
|
192
|
+
let out = input
|
|
187
193
|
// Remove control chars but preserve newlines/tabs.
|
|
188
194
|
.replace(/[^\x09\x0A\x0D\x20-\x7E]/g, "")
|
|
195
|
+
.replace(/([a-z0-9/])([A-Z])/g, "$1 $2")
|
|
196
|
+
.replace(/([.!?])([A-Za-z])/g, "$1 $2")
|
|
197
|
+
.replace(/([a-z])(\u2022)/g, "$1\n$2")
|
|
198
|
+
.replace(/([^\n])(Summary:|Next:|Tests:)/g, "$1\n\n$2")
|
|
199
|
+
.replace(/([A-Za-z0-9_/-]+)\.\s+md\b/g, "$1.md")
|
|
200
|
+
.replace(/(\bnpm audit)(All tasks complete\.)/gi, "$1\n\n$2")
|
|
201
|
+
.replace(/([.!?])\s*(Summary:|Next:|Tests:)/g, "$1\n\n$2")
|
|
202
|
+
.replace(/([^\n])(\u2022\s)/g, "$1\n$2")
|
|
189
203
|
// Trim trailing spaces line-by-line.
|
|
190
204
|
.replace(/[ \t]+$/gm, "")
|
|
191
205
|
// Normalize excessive blank lines.
|
|
192
|
-
.replace(/\n{3,}/g, "\n\n")
|
|
193
|
-
|
|
206
|
+
.replace(/\n{3,}/g, "\n\n");
|
|
207
|
+
const paragraphs = out
|
|
208
|
+
.split(/\n{2,}/)
|
|
209
|
+
.map((p) => p.trim())
|
|
210
|
+
.filter(Boolean);
|
|
211
|
+
const seen = new Set();
|
|
212
|
+
const deduped = [];
|
|
213
|
+
for (const para of paragraphs) {
|
|
214
|
+
const key = para.toLowerCase().replace(/\s+/g, " ");
|
|
215
|
+
if (para.length > 50 && seen.has(key)) {
|
|
216
|
+
continue;
|
|
217
|
+
}
|
|
218
|
+
seen.add(key);
|
|
219
|
+
deduped.push(para);
|
|
220
|
+
}
|
|
221
|
+
return deduped.join("\n\n").trim();
|
|
222
|
+
};
|
|
223
|
+
const summarizeToolResult = (toolName, data, success) => {
|
|
224
|
+
if (!success) {
|
|
225
|
+
return `${toolName} failed`;
|
|
226
|
+
}
|
|
227
|
+
const path = typeof data.path === "string" ? data.path : "";
|
|
228
|
+
const totalLines = typeof data.total_lines === "number" ? data.total_lines : null;
|
|
229
|
+
if (toolName === "view_file") {
|
|
230
|
+
if (totalLines !== null && path) {
|
|
231
|
+
return `Read ${totalLines} lines of <${path}>`;
|
|
232
|
+
}
|
|
233
|
+
if (path) {
|
|
234
|
+
return `Read <${path}>`;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
if (toolName === "list_directory") {
|
|
238
|
+
const dir = path || ".";
|
|
239
|
+
if (totalLines !== null) {
|
|
240
|
+
return `Listed ${totalLines} entries in <${dir}>`;
|
|
241
|
+
}
|
|
242
|
+
return `Listed <${dir}>`;
|
|
243
|
+
}
|
|
244
|
+
if (toolName === "semantic_search") {
|
|
245
|
+
return "Searched codebase context";
|
|
246
|
+
}
|
|
247
|
+
if (toolName === "search_files") {
|
|
248
|
+
return totalLines !== null ? `Found ${totalLines} matching paths` : "Searched file paths";
|
|
249
|
+
}
|
|
250
|
+
return `${toolName} completed`;
|
|
251
|
+
};
|
|
252
|
+
const isLikelyVoiceNoise = (text) => {
|
|
253
|
+
const normalized = text.trim().toLowerCase();
|
|
254
|
+
if (!normalized) {
|
|
255
|
+
return true;
|
|
256
|
+
}
|
|
257
|
+
if (normalized.length < VOICE_MIN_CHARS) {
|
|
258
|
+
return true;
|
|
259
|
+
}
|
|
260
|
+
const tokens = normalized.split(/\s+/).filter(Boolean);
|
|
261
|
+
if (tokens.length === 0) {
|
|
262
|
+
return true;
|
|
263
|
+
}
|
|
264
|
+
const nonNoise = tokens.filter((t) => !VOICE_NOISE_WORDS.has(t));
|
|
265
|
+
if (nonNoise.length === 0) {
|
|
266
|
+
return true;
|
|
267
|
+
}
|
|
268
|
+
return false;
|
|
194
269
|
};
|
|
195
270
|
const parseInline = (line) => {
|
|
196
271
|
const tokens = [];
|
|
@@ -351,6 +426,20 @@ const renderMarkdownContent = (text, baseColor, keyPrefix) => {
|
|
|
351
426
|
};
|
|
352
427
|
export const AstraApp = () => {
|
|
353
428
|
const workspaceRoot = useMemo(() => process.cwd(), []);
|
|
429
|
+
const [terminalWidth, setTerminalWidth] = useState(() => process.stdout.columns || MAX_DIVIDER);
|
|
430
|
+
useEffect(() => {
|
|
431
|
+
const updateWidth = () => {
|
|
432
|
+
setTerminalWidth(process.stdout.columns || MAX_DIVIDER);
|
|
433
|
+
};
|
|
434
|
+
process.stdout.on("resize", updateWidth);
|
|
435
|
+
return () => {
|
|
436
|
+
process.stdout.off("resize", updateWidth);
|
|
437
|
+
};
|
|
438
|
+
}, []);
|
|
439
|
+
const dividerWidth = Math.max(MIN_DIVIDER, Math.min(MAX_DIVIDER, (terminalWidth ?? MAX_DIVIDER) - 2));
|
|
440
|
+
const divider = "─".repeat(dividerWidth);
|
|
441
|
+
const brand = dividerWidth < 96 ? ASTRA_COMPACT : ASTRA_ASCII;
|
|
442
|
+
const welcomeLine = dividerWidth < 96 ? "Welcome to Astra" : FOUNDER_WELCOME;
|
|
354
443
|
const backend = useMemo(() => new BackendClient(), []);
|
|
355
444
|
const { exit } = useApp();
|
|
356
445
|
// In-session file cache: tracks files created/edited so subsequent requests
|
|
@@ -400,8 +489,10 @@ export const AstraApp = () => {
|
|
|
400
489
|
const [streamingText, setStreamingText] = useState("");
|
|
401
490
|
const [voiceEnabled, setVoiceEnabled] = useState(false);
|
|
402
491
|
const [voiceListening, setVoiceListening] = useState(false);
|
|
492
|
+
const [voicePreparing, setVoicePreparing] = useState(false);
|
|
403
493
|
const [voiceWaitingForSilence, setVoiceWaitingForSilence] = useState(false);
|
|
404
494
|
const [voiceQueuedPrompt, setVoiceQueuedPrompt] = useState(null);
|
|
495
|
+
const [toolFeedMode, setToolFeedMode] = useState("compact");
|
|
405
496
|
const [historyOpen, setHistoryOpen] = useState(false);
|
|
406
497
|
const [historyMode, setHistoryMode] = useState("picker");
|
|
407
498
|
const [historyPickerIndex, setHistoryPickerIndex] = useState(0);
|
|
@@ -411,13 +502,34 @@ export const AstraApp = () => {
|
|
|
411
502
|
const [historyIndex, setHistoryIndex] = useState(0);
|
|
412
503
|
const liveVoiceRef = useRef(null);
|
|
413
504
|
const voiceSilenceTimerRef = useRef(null);
|
|
505
|
+
const lastVoicePromptRef = useRef(null);
|
|
506
|
+
const lastIgnoredVoiceRef = useRef(null);
|
|
414
507
|
const fileEditBuffersRef = useRef(new Map());
|
|
415
508
|
const isSuperAdmin = user?.role === "super_admin";
|
|
416
509
|
const pushMessage = useCallback((kind, text) => {
|
|
417
510
|
setMessages((prev) => [...prev, { kind, text }].slice(-300));
|
|
418
511
|
}, []);
|
|
419
512
|
const pushToolCard = useCallback((card) => {
|
|
420
|
-
setMessages((prev) =>
|
|
513
|
+
setMessages((prev) => {
|
|
514
|
+
const nextEntry = { kind: "tool", text: card.summary, card };
|
|
515
|
+
const last = prev[prev.length - 1];
|
|
516
|
+
if (last &&
|
|
517
|
+
last.kind === "tool" &&
|
|
518
|
+
last.card &&
|
|
519
|
+
last.card.toolName === card.toolName &&
|
|
520
|
+
last.card.kind === card.kind &&
|
|
521
|
+
last.card.summary === card.summary &&
|
|
522
|
+
last.card.locality === card.locality) {
|
|
523
|
+
const updated = [...prev];
|
|
524
|
+
const existingCount = Math.max(1, Number(last.card.count ?? 1));
|
|
525
|
+
updated[updated.length - 1] = {
|
|
526
|
+
...last,
|
|
527
|
+
card: { ...last.card, count: existingCount + 1 }
|
|
528
|
+
};
|
|
529
|
+
return updated.slice(-300);
|
|
530
|
+
}
|
|
531
|
+
return [...prev, nextEntry].slice(-300);
|
|
532
|
+
});
|
|
421
533
|
}, []);
|
|
422
534
|
const filteredHistory = useMemo(() => {
|
|
423
535
|
const q = historyQuery.trim().toLowerCase();
|
|
@@ -488,11 +600,13 @@ export const AstraApp = () => {
|
|
|
488
600
|
}
|
|
489
601
|
const controller = liveVoiceRef.current;
|
|
490
602
|
if (!controller) {
|
|
603
|
+
setVoicePreparing(false);
|
|
491
604
|
setVoiceWaitingForSilence(false);
|
|
492
605
|
return;
|
|
493
606
|
}
|
|
494
607
|
liveVoiceRef.current = null;
|
|
495
608
|
await controller.stop();
|
|
609
|
+
setVoicePreparing(false);
|
|
496
610
|
setVoiceListening(false);
|
|
497
611
|
setVoiceWaitingForSilence(false);
|
|
498
612
|
}, []);
|
|
@@ -500,24 +614,45 @@ export const AstraApp = () => {
|
|
|
500
614
|
if (liveVoiceRef.current) {
|
|
501
615
|
return;
|
|
502
616
|
}
|
|
617
|
+
// #region agent log
|
|
618
|
+
fetch('http://127.0.0.1:7573/ingest/fdd4f018-1ba3-4303-b1bb-375443267476', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-Debug-Session-Id': '17f1ea' }, body: JSON.stringify({ sessionId: '17f1ea', runId: 'voice_run_1', hypothesisId: 'H5', location: 'App.tsx:startLiveVoice', message: 'startLiveVoice called', data: { announce, voiceEnabled, thinking, hasController: Boolean(liveVoiceRef.current) }, timestamp: Date.now() }) }).catch(() => { });
|
|
619
|
+
// #endregion
|
|
503
620
|
setVoiceEnabled(true);
|
|
504
|
-
|
|
621
|
+
setVoicePreparing(true);
|
|
622
|
+
setVoiceListening(false);
|
|
505
623
|
setVoiceWaitingForSilence(false);
|
|
506
624
|
if (announce) {
|
|
507
|
-
pushMessage("system", "Voice input
|
|
625
|
+
pushMessage("system", "Voice input armed. Preparing microphone...");
|
|
508
626
|
}
|
|
509
627
|
liveVoiceRef.current = startLiveTranscription({
|
|
510
628
|
onPartial: (text) => {
|
|
511
|
-
|
|
629
|
+
setVoicePreparing(false);
|
|
630
|
+
setVoiceListening(true);
|
|
512
631
|
if (voiceSilenceTimerRef.current) {
|
|
513
632
|
clearTimeout(voiceSilenceTimerRef.current);
|
|
514
633
|
}
|
|
515
634
|
const candidate = text.trim();
|
|
516
635
|
if (!candidate) {
|
|
636
|
+
setPrompt("");
|
|
637
|
+
setVoiceWaitingForSilence(false);
|
|
517
638
|
return;
|
|
518
639
|
}
|
|
640
|
+
const normalized = candidate.toLowerCase();
|
|
641
|
+
const isLikelyNoise = isLikelyVoiceNoise(normalized);
|
|
642
|
+
// #region agent log
|
|
643
|
+
fetch('http://127.0.0.1:7573/ingest/fdd4f018-1ba3-4303-b1bb-375443267476', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-Debug-Session-Id': '17f1ea' }, body: JSON.stringify({ sessionId: '17f1ea', runId: 'voice_run_1', hypothesisId: 'H1', location: 'App.tsx:startLiveVoice.onPartial', message: 'partial transcript observed', data: { textLen: text.length, candidateLen: candidate.length, normalized, isLikelyNoise, silenceMs: VOICE_SILENCE_MS }, timestamp: Date.now() }) }).catch(() => { });
|
|
644
|
+
// #endregion
|
|
645
|
+
if (isLikelyNoise) {
|
|
646
|
+
setPrompt("");
|
|
647
|
+
setVoiceWaitingForSilence(false);
|
|
648
|
+
return;
|
|
649
|
+
}
|
|
650
|
+
setPrompt(text);
|
|
519
651
|
setVoiceWaitingForSilence(true);
|
|
520
652
|
voiceSilenceTimerRef.current = setTimeout(() => {
|
|
653
|
+
// #region agent log
|
|
654
|
+
fetch('http://127.0.0.1:7573/ingest/fdd4f018-1ba3-4303-b1bb-375443267476', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-Debug-Session-Id': '17f1ea' }, body: JSON.stringify({ sessionId: '17f1ea', runId: 'voice_run_1', hypothesisId: 'H2', location: 'App.tsx:startLiveVoice.silenceTimeout', message: 'silence timeout fired and queueing prompt', data: { candidate, voiceWaitingForSilence: true }, timestamp: Date.now() }) }).catch(() => { });
|
|
655
|
+
// #endregion
|
|
521
656
|
setVoiceQueuedPrompt(candidate);
|
|
522
657
|
void stopLiveVoice();
|
|
523
658
|
}, VOICE_SILENCE_MS);
|
|
@@ -529,12 +664,21 @@ export const AstraApp = () => {
|
|
|
529
664
|
}
|
|
530
665
|
setPrompt(text);
|
|
531
666
|
liveVoiceRef.current = null;
|
|
667
|
+
setVoicePreparing(false);
|
|
532
668
|
setVoiceListening(false);
|
|
533
669
|
setVoiceWaitingForSilence(false);
|
|
670
|
+
// #region agent log
|
|
671
|
+
fetch('http://127.0.0.1:7573/ingest/fdd4f018-1ba3-4303-b1bb-375443267476', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-Debug-Session-Id': '17f1ea' }, body: JSON.stringify({ sessionId: '17f1ea', runId: 'voice_run_1', hypothesisId: 'H4', location: 'App.tsx:startLiveVoice.onFinal', message: 'final transcript emitted', data: { finalLen: text.length, finalText: text.slice(0, 80) }, timestamp: Date.now() }) }).catch(() => { });
|
|
672
|
+
// #endregion
|
|
534
673
|
},
|
|
535
674
|
onError: (error) => {
|
|
675
|
+
setVoicePreparing(false);
|
|
676
|
+
setVoiceListening(false);
|
|
536
677
|
setVoiceWaitingForSilence(false);
|
|
537
678
|
pushMessage("error", `Voice transcription error: ${error.message}`);
|
|
679
|
+
// #region agent log
|
|
680
|
+
fetch('http://127.0.0.1:7573/ingest/fdd4f018-1ba3-4303-b1bb-375443267476', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-Debug-Session-Id': '17f1ea' }, body: JSON.stringify({ sessionId: '17f1ea', runId: 'voice_run_1', hypothesisId: 'H4', location: 'App.tsx:startLiveVoice.onError', message: 'voice transcription error', data: { error: error.message }, timestamp: Date.now() }) }).catch(() => { });
|
|
681
|
+
// #endregion
|
|
538
682
|
}
|
|
539
683
|
});
|
|
540
684
|
}, [pushMessage, stopLiveVoice]);
|
|
@@ -995,19 +1139,26 @@ export const AstraApp = () => {
|
|
|
995
1139
|
}
|
|
996
1140
|
if (event.type === "credits_exhausted") {
|
|
997
1141
|
setCreditsRemaining(0);
|
|
1142
|
+
if (voiceEnabled) {
|
|
1143
|
+
await stopLiveVoice();
|
|
1144
|
+
setVoiceEnabled(false);
|
|
1145
|
+
pushMessage("system", "Voice input paused: credits exhausted. Recharge, then run /voice on.");
|
|
1146
|
+
pushMessage("system", "");
|
|
1147
|
+
}
|
|
1148
|
+
}
|
|
1149
|
+
if (event.type === "continuation_check") {
|
|
1150
|
+
const recommendation = typeof event.recommendation === "string" && event.recommendation
|
|
1151
|
+
? event.recommendation
|
|
1152
|
+
: "Please narrow the scope and continue with a specific target.";
|
|
1153
|
+
const streak = Number(event.consecutive_read_only_iterations ?? 0);
|
|
1154
|
+
const threshold = Number(event.threshold ?? 0);
|
|
1155
|
+
pushMessage("system", `Exploration paused (${streak}/${threshold} read-only turns). ${recommendation}`);
|
|
1156
|
+
return null;
|
|
998
1157
|
}
|
|
999
1158
|
if (!isSuperAdmin && NOISY_EVENT_TYPES.has(event.type)) {
|
|
1000
1159
|
return null;
|
|
1001
1160
|
}
|
|
1002
1161
|
if (!isSuperAdmin && event.type === "tool_start") {
|
|
1003
|
-
const tool = event.tool;
|
|
1004
|
-
const name = tool?.name ?? "tool";
|
|
1005
|
-
pushToolCard({
|
|
1006
|
-
kind: "start",
|
|
1007
|
-
toolName: name,
|
|
1008
|
-
locality: "REMOTE",
|
|
1009
|
-
summary: `${name} is running...`
|
|
1010
|
-
});
|
|
1011
1162
|
return null;
|
|
1012
1163
|
}
|
|
1013
1164
|
const toolLine = eventToToolLine(event);
|
|
@@ -1022,11 +1173,22 @@ export const AstraApp = () => {
|
|
|
1022
1173
|
else if (event.type === "tool_result") {
|
|
1023
1174
|
const mark = event.success ? "completed" : "failed";
|
|
1024
1175
|
const toolName = typeof event.tool_name === "string" ? event.tool_name : "tool";
|
|
1176
|
+
const payload = (event.data ?? {});
|
|
1177
|
+
const resultType = typeof event.result_type === "string" ? event.result_type : "";
|
|
1178
|
+
const alreadyRepresented = resultType === "file_create" ||
|
|
1179
|
+
resultType === "file_edit" ||
|
|
1180
|
+
resultType === "file_delete" ||
|
|
1181
|
+
toolName === "start_preview" ||
|
|
1182
|
+
toolName === "capture_screenshot";
|
|
1183
|
+
if (alreadyRepresented) {
|
|
1184
|
+
return null;
|
|
1185
|
+
}
|
|
1186
|
+
const summary = summarizeToolResult(toolName, payload, Boolean(event.success));
|
|
1025
1187
|
pushToolCard({
|
|
1026
1188
|
kind: event.success ? "success" : "error",
|
|
1027
1189
|
toolName,
|
|
1028
|
-
locality:
|
|
1029
|
-
summary: `${toolName} ${mark}`
|
|
1190
|
+
locality: payload.local === true ? "LOCAL" : "REMOTE",
|
|
1191
|
+
summary: event.success ? summary : `${toolName} ${mark}`
|
|
1030
1192
|
});
|
|
1031
1193
|
}
|
|
1032
1194
|
}
|
|
@@ -1038,19 +1200,31 @@ export const AstraApp = () => {
|
|
|
1038
1200
|
}
|
|
1039
1201
|
}
|
|
1040
1202
|
return null;
|
|
1041
|
-
}, [backend, deleteLocalFile, isSuperAdmin, pushMessage, pushToolCard, writeLocalFile]);
|
|
1203
|
+
}, [backend, deleteLocalFile, isSuperAdmin, pushMessage, pushToolCard, stopLiveVoice, voiceEnabled, writeLocalFile]);
|
|
1042
1204
|
const sendPrompt = useCallback(async (rawPrompt) => {
|
|
1043
1205
|
const text = rawPrompt.trim();
|
|
1044
1206
|
if (!text || !user || thinking) {
|
|
1045
1207
|
return;
|
|
1046
1208
|
}
|
|
1047
1209
|
if (text === "/help") {
|
|
1048
|
-
pushMessage("system", "/new /history /voice /
|
|
1210
|
+
pushMessage("system", "/new /history /voice /dictate on|off|status /tools compact|expanded /settings /settings model <id> /logout /exit");
|
|
1211
|
+
pushMessage("system", "");
|
|
1212
|
+
return;
|
|
1213
|
+
}
|
|
1214
|
+
if (text === "/tools compact") {
|
|
1215
|
+
setToolFeedMode("compact");
|
|
1216
|
+
pushMessage("system", "Tool feed set to compact.");
|
|
1217
|
+
pushMessage("system", "");
|
|
1218
|
+
return;
|
|
1219
|
+
}
|
|
1220
|
+
if (text === "/tools expanded") {
|
|
1221
|
+
setToolFeedMode("expanded");
|
|
1222
|
+
pushMessage("system", "Tool feed set to expanded.");
|
|
1049
1223
|
pushMessage("system", "");
|
|
1050
1224
|
return;
|
|
1051
1225
|
}
|
|
1052
1226
|
if (text === "/settings") {
|
|
1053
|
-
pushMessage("system", `Settings: mode=${runtimeMode} scope=${workspaceRoot} model=${activeModel} provider=${getProviderForModel(activeModel)} voice=${voiceEnabled ? "on" : "off"} listening=${voiceListening ? "yes" : "no"} silence_ms=${VOICE_SILENCE_MS} role=${user.role ?? "user"} client_id=${getDefaultClientId()} backend=${getBackendUrl()}`);
|
|
1227
|
+
pushMessage("system", `Settings: mode=${runtimeMode} scope=${workspaceRoot} model=${activeModel} provider=${getProviderForModel(activeModel)} voice=${voiceEnabled ? "on" : "off"} listening=${voiceListening ? "yes" : "no"} tool_feed=${toolFeedMode} silence_ms=${VOICE_SILENCE_MS} role=${user.role ?? "user"} client_id=${getDefaultClientId()} backend=${getBackendUrl()}`);
|
|
1054
1228
|
pushMessage("system", "");
|
|
1055
1229
|
return;
|
|
1056
1230
|
}
|
|
@@ -1081,29 +1255,29 @@ export const AstraApp = () => {
|
|
|
1081
1255
|
await openHistory();
|
|
1082
1256
|
return;
|
|
1083
1257
|
}
|
|
1084
|
-
if (text === "/
|
|
1085
|
-
pushMessage("system", `Voice input is ${voiceEnabled ? "on" : "off"}${
|
|
1258
|
+
if (text === "/dictate status") {
|
|
1259
|
+
pushMessage("system", `Voice input is ${voiceEnabled ? "on" : "off"}${voicePreparing ? " (preparing mic)" : ""}${voiceListening ? " (listening)" : ""}${voiceWaitingForSilence ? " (waiting for silence to auto-send)" : ""}.`);
|
|
1086
1260
|
pushMessage("system", "");
|
|
1087
1261
|
return;
|
|
1088
1262
|
}
|
|
1089
|
-
if (text === "/
|
|
1263
|
+
if (text === "/dictate on") {
|
|
1090
1264
|
setVoiceEnabled(true);
|
|
1091
1265
|
startLiveVoice(true);
|
|
1092
1266
|
pushMessage("system", `Voice input enabled. Auto-send after ${Math.round(VOICE_SILENCE_MS / 1000)}s silence.`);
|
|
1093
1267
|
pushMessage("system", "");
|
|
1094
1268
|
return;
|
|
1095
1269
|
}
|
|
1096
|
-
if (text === "/
|
|
1270
|
+
if (text === "/dictate off") {
|
|
1097
1271
|
await stopLiveVoice();
|
|
1098
1272
|
setVoiceEnabled(false);
|
|
1099
1273
|
pushMessage("system", "Voice input disabled.");
|
|
1100
1274
|
pushMessage("system", "");
|
|
1101
1275
|
return;
|
|
1102
1276
|
}
|
|
1103
|
-
if (text === "/
|
|
1277
|
+
if (text === "/dictate input") {
|
|
1104
1278
|
const transcribed = await transcribeOnce();
|
|
1105
1279
|
if (!transcribed) {
|
|
1106
|
-
pushMessage("error", "No speech transcribed. Ensure you're signed in and
|
|
1280
|
+
pushMessage("error", "No speech transcribed. Ensure you're signed in and your microphone capture works (optional ASTRA_STT_CAPTURE_COMMAND).");
|
|
1107
1281
|
return;
|
|
1108
1282
|
}
|
|
1109
1283
|
setVoiceQueuedPrompt(transcribed.trim());
|
|
@@ -1170,6 +1344,9 @@ export const AstraApp = () => {
|
|
|
1170
1344
|
localActionConfirmed = true;
|
|
1171
1345
|
}
|
|
1172
1346
|
}
|
|
1347
|
+
if (event.type === "done") {
|
|
1348
|
+
break;
|
|
1349
|
+
}
|
|
1173
1350
|
const piece = await handleEvent(event, activeSessionId);
|
|
1174
1351
|
if (piece) {
|
|
1175
1352
|
assistant += piece;
|
|
@@ -1209,9 +1386,11 @@ export const AstraApp = () => {
|
|
|
1209
1386
|
startLiveVoice,
|
|
1210
1387
|
stopLiveVoice,
|
|
1211
1388
|
thinking,
|
|
1389
|
+
toolFeedMode,
|
|
1212
1390
|
user,
|
|
1213
1391
|
voiceEnabled,
|
|
1214
1392
|
voiceListening,
|
|
1393
|
+
voicePreparing,
|
|
1215
1394
|
voiceWaitingForSilence,
|
|
1216
1395
|
workspaceRoot
|
|
1217
1396
|
]);
|
|
@@ -1224,21 +1403,46 @@ export const AstraApp = () => {
|
|
|
1224
1403
|
if (!queued) {
|
|
1225
1404
|
return;
|
|
1226
1405
|
}
|
|
1406
|
+
const normalizedQueued = queued.toLowerCase();
|
|
1407
|
+
const last = lastVoicePromptRef.current;
|
|
1408
|
+
const isLikelyNoise = isLikelyVoiceNoise(normalizedQueued);
|
|
1409
|
+
const isFastDuplicate = last !== null &&
|
|
1410
|
+
last.text === normalizedQueued &&
|
|
1411
|
+
Date.now() - last.at <= VOICE_DUPLICATE_WINDOW_MS;
|
|
1412
|
+
// #region agent log
|
|
1413
|
+
fetch('http://127.0.0.1:7573/ingest/fdd4f018-1ba3-4303-b1bb-375443267476', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-Debug-Session-Id': '17f1ea' }, body: JSON.stringify({ sessionId: '17f1ea', runId: 'voice_run_1', hypothesisId: 'H3', location: 'App.tsx:voiceQueuedPromptEffect', message: 'queued prompt evaluated', data: { queued, normalizedQueued, isLikelyNoise, isFastDuplicate, thinking }, timestamp: Date.now() }) }).catch(() => { });
|
|
1414
|
+
// #endregion
|
|
1415
|
+
if (isLikelyNoise || isFastDuplicate) {
|
|
1416
|
+
const now = Date.now();
|
|
1417
|
+
const lastIgnored = lastIgnoredVoiceRef.current;
|
|
1418
|
+
const shouldLogIgnored = !lastIgnored ||
|
|
1419
|
+
lastIgnored.text !== normalizedQueued ||
|
|
1420
|
+
now - lastIgnored.at > VOICE_DUPLICATE_WINDOW_MS;
|
|
1421
|
+
if (shouldLogIgnored) {
|
|
1422
|
+
pushMessage("system", `Ignored likely-noise dictation input: "${queued}"`);
|
|
1423
|
+
lastIgnoredVoiceRef.current = { text: normalizedQueued, at: now };
|
|
1424
|
+
}
|
|
1425
|
+
return;
|
|
1426
|
+
}
|
|
1427
|
+
lastVoicePromptRef.current = { text: normalizedQueued, at: Date.now() };
|
|
1227
1428
|
pushMessage("system", `Voice input: ${queued}`);
|
|
1228
1429
|
setPrompt("");
|
|
1430
|
+
// #region agent log
|
|
1431
|
+
fetch('http://127.0.0.1:7573/ingest/fdd4f018-1ba3-4303-b1bb-375443267476', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-Debug-Session-Id': '17f1ea' }, body: JSON.stringify({ sessionId: '17f1ea', runId: 'voice_run_1', hypothesisId: 'H3', location: 'App.tsx:voiceQueuedPromptEffect', message: 'queued prompt forwarded to sendPrompt', data: { queuedLen: queued.length }, timestamp: Date.now() }) }).catch(() => { });
|
|
1432
|
+
// #endregion
|
|
1229
1433
|
void sendPrompt(queued);
|
|
1230
1434
|
}, [pushMessage, sendPrompt, thinking, user, voiceQueuedPrompt]);
|
|
1231
1435
|
if (!trustedWorkspace) {
|
|
1232
1436
|
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { color: "#c0c9db", children: "claude" }), _jsx(Text, { color: "#8ea1bd", children: "\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500" }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: "#f0f4ff", children: "Do you trust the files in this folder?" }) }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: "#c8d5f0", children: workspaceRoot }) }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: "#8ea1bd", children: "Astra Code may read, write, or execute files contained in this directory. This can pose security risks, so only use files from trusted sources." }) }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: "#7aa2ff", children: "Learn more" }) }), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsxs(Text, { color: trustSelection === 0 ? "#f0f4ff" : "#8ea1bd", children: [trustSelection === 0 ? "❯ " : " ", "1. Yes, proceed"] }), _jsxs(Text, { color: trustSelection === 1 ? "#f0f4ff" : "#8ea1bd", children: [trustSelection === 1 ? "❯ " : " ", "2. No, exit"] })] }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: "#8ea1bd", children: "Enter to confirm \u00B7 Esc to cancel" }) })] }));
|
|
1233
1437
|
}
|
|
1234
1438
|
if (booting) {
|
|
1235
|
-
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { color: "#7aa2ff", children:
|
|
1439
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { color: "#7aa2ff", children: brand }), _jsx(Text, { color: "#8ea1bd", children: welcomeLine }), _jsxs(Text, { color: "#8aa2c9", children: [_jsx(Spinner, { type: "dots12" }), " Booting Astra terminal shell..."] })] }));
|
|
1236
1440
|
}
|
|
1237
1441
|
if (bootError) {
|
|
1238
|
-
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { color: "#7aa2ff", children:
|
|
1442
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { color: "#7aa2ff", children: brand }), _jsx(Text, { color: "#8ea1bd", children: welcomeLine }), _jsx(Text, { color: "red", children: bootError })] }));
|
|
1239
1443
|
}
|
|
1240
1444
|
if (!user) {
|
|
1241
|
-
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { color: "#7aa2ff", children:
|
|
1445
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { color: "#7aa2ff", children: brand }), _jsx(Text, { color: "#8ea1bd", children: welcomeLine }), _jsxs(Text, { color: "#b8c8ff", children: ["Astra terminal AI pair programmer (", loginMode === "login" ? "Sign in" : "Create account", ")"] }), _jsx(Text, { color: "#7c8ea8", children: "Press Ctrl+T to toggle Sign in / Create account" }), _jsxs(Box, { marginTop: 1, children: [_jsx(Text, { color: "#93a3b8", children: "Email: " }), _jsx(TextInput, { value: email, onChange: setEmail, focus: loginField === "email", onSubmit: () => {
|
|
1242
1446
|
setLoginField("password");
|
|
1243
1447
|
} })] }), _jsxs(Box, { children: [_jsx(Text, { color: "#93a3b8", children: "Password: " }), _jsx(TextInput, { value: password, onChange: setPassword, mask: "*", focus: loginField === "password", onSubmit: () => {
|
|
1244
1448
|
void doAuth();
|
|
@@ -1246,21 +1450,36 @@ export const AstraApp = () => {
|
|
|
1246
1450
|
}
|
|
1247
1451
|
if (historyOpen) {
|
|
1248
1452
|
const selected = filteredHistory[historyIndex];
|
|
1249
|
-
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { color: "#7aa2ff", children:
|
|
1453
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { color: "#7aa2ff", children: brand }), _jsx(Text, { color: "#8ea1bd", children: welcomeLine }), _jsx(Text, { color: "#2a3a50", children: divider }), historyMode === "picker" ? (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { flexDirection: "row", justifyContent: "space-between", children: [_jsx(Text, { color: "#dce9ff", children: "History Picker" }), _jsx(Text, { color: "#5a7a9a", children: "Esc close \u00B7 Enter select" })] }), _jsx(Text, { color: "#2a3a50", children: divider }), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsx(Box, { flexDirection: "row", children: _jsxs(Text, { color: historyPickerIndex === 0 ? "#dce9ff" : "#7a9bba", children: [historyPickerIndex === 0 ? "❯ " : " ", "View chat history"] }) }), _jsx(Box, { marginTop: 1, flexDirection: "row", children: _jsxs(Text, { color: historyPickerIndex === 1 ? "#dce9ff" : "#7a9bba", children: [historyPickerIndex === 1 ? "❯ " : " ", "View credit usage history"] }) })] }), _jsx(Text, { color: "#2a3a50", children: divider }), _jsxs(Text, { color: "#5a7a9a", children: ["Credit usage history opens: ", HISTORY_SETTINGS_URL] })] })) : (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { flexDirection: "row", justifyContent: "space-between", children: [_jsx(Text, { color: "#dce9ff", children: "Chat History" }), _jsx(Text, { color: "#5a7a9a", children: "Esc back \u00B7 Enter open \u00B7 D delete \u00B7 \u2190/\u2192 page" })] }), _jsxs(Box, { marginTop: 1, flexDirection: "row", children: [_jsx(Text, { color: "#4a6070", children: "search " }), _jsx(TextInput, { value: historyQuery, onChange: setHistoryQuery, placeholder: "Filter chats..." })] }), _jsx(Text, { color: "#2a3a50", children: divider }), historyLoading ? (_jsxs(Text, { color: "#8aa2c9", children: [_jsx(Spinner, { type: "dots12" }), " Loading chat history..."] })) : filteredHistory.length === 0 ? (_jsx(Text, { color: "#8ea1bd", children: "No sessions found." })) : (_jsx(Box, { flexDirection: "column", marginTop: 1, children: pageRows.map((row, localIdx) => {
|
|
1250
1454
|
const idx = pageStart + localIdx;
|
|
1251
1455
|
const active = idx === historyIndex;
|
|
1252
1456
|
return (_jsxs(Box, { flexDirection: "row", justifyContent: "space-between", children: [_jsxs(Text, { color: active ? "#dce9ff" : "#7a9bba", children: [active ? "❯ " : " ", (row.title || "Untitled").slice(0, 58).padEnd(60, " ")] }), _jsxs(Text, { color: "#5a7a9a", children: [String(row.total_messages ?? 0).padStart(3, " "), " msgs \u00B7 ", formatSessionDate(row.updated_at)] })] }, row.id));
|
|
1253
|
-
}) })), _jsx(Text, { color: "#2a3a50", children:
|
|
1457
|
+
}) })), _jsx(Text, { color: "#2a3a50", children: divider }), _jsxs(Box, { flexDirection: "row", justifyContent: "space-between", children: [_jsxs(Text, { color: "#5a7a9a", children: ["Page ", historyPage + 1, " / ", historyPageCount] }), _jsxs(Text, { color: "#5a7a9a", children: ["Selected: ", selected ? selected.id : "--"] })] })] }))] }));
|
|
1254
1458
|
}
|
|
1255
|
-
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { color: "#7aa2ff", children:
|
|
1459
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { color: "#7aa2ff", children: brand }), _jsx(Text, { color: "#8ea1bd", children: welcomeLine }), _jsx(Text, { color: "#2a3a50", children: divider }), _jsx(Text, { color: "#7a9bba", children: `mode ${runtimeMode} · provider ${getProviderForModel(activeModel)} · model ${activeModel} · credits ${creditsRemaining ?? "--"}${lastCreditCost !== null ? ` (-${lastCreditCost})` : ""}` }), _jsx(Text, { color: "#6c88a8", children: `scope ${workspaceRoot.length > HEADER_PATH_MAX ? `…${workspaceRoot.slice(-(HEADER_PATH_MAX - 1))}` : workspaceRoot} · voice ${voiceEnabled
|
|
1460
|
+
? voicePreparing
|
|
1461
|
+
? "on/preparing"
|
|
1462
|
+
: voiceListening
|
|
1463
|
+
? voiceWaitingForSilence
|
|
1464
|
+
? "on/waiting"
|
|
1465
|
+
: "on/listening"
|
|
1466
|
+
: "on/standby"
|
|
1467
|
+
: "off"}` }), _jsx(Text, { color: "#2a3a50", children: divider }), _jsx(Text, { color: "#3a5068", children: "/help /new /history /voice /dictate on|off|status" }), _jsx(Text, { color: "#3a5068", children: "/tools compact|expanded /settings /logout /exit" }), _jsx(Text, { color: "#2a3a50", children: divider }), _jsxs(Box, { flexDirection: "column", marginTop: 1, children: [messages.map((message, index) => {
|
|
1468
|
+
const prev = index > 0 ? messages[index - 1] : null;
|
|
1256
1469
|
const style = styleForKind(message.kind);
|
|
1257
1470
|
const paddedLabel = style.label.padEnd(LABEL_WIDTH, " ");
|
|
1258
1471
|
const isSpacing = message.text === "" && message.kind === "system";
|
|
1472
|
+
const needsGroupGap = Boolean(prev) &&
|
|
1473
|
+
prev?.kind !== "system" &&
|
|
1474
|
+
message.kind !== "system" &&
|
|
1475
|
+
prev?.kind !== message.kind &&
|
|
1476
|
+
(message.kind === "assistant" || message.kind === "tool");
|
|
1259
1477
|
if (isSpacing) {
|
|
1260
1478
|
return _jsx(Box, { marginTop: 0, children: _jsx(Text, { children: " " }) }, `${index}-spacer`);
|
|
1261
1479
|
}
|
|
1262
1480
|
if (message.kind === "tool" && message.card && !isSuperAdmin) {
|
|
1263
1481
|
const card = message.card;
|
|
1482
|
+
const localityLabel = card.locality === "LOCAL" ? "local" : "cloud";
|
|
1264
1483
|
const icon = card.kind === "error"
|
|
1265
1484
|
? "✕"
|
|
1266
1485
|
: card.kind === "fileDelete"
|
|
@@ -1287,10 +1506,10 @@ export const AstraApp = () => {
|
|
|
1287
1506
|
: card.kind === "preview"
|
|
1288
1507
|
? "#9ad5ff"
|
|
1289
1508
|
: "#9bc5ff";
|
|
1290
|
-
return (_jsxs(Box, { flexDirection: "row", children: [_jsx(Text, { color: style.labelColor, children: paddedLabel }), _jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { color: accent, children: [icon, " ", card.summary,
|
|
1509
|
+
return (_jsxs(React.Fragment, { children: [needsGroupGap ? _jsx(Text, { children: " " }) : null, _jsxs(Box, { flexDirection: "row", children: [_jsx(Text, { color: style.labelColor, children: paddedLabel }), _jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { color: accent, children: [icon, " ", card.summary, card.count && card.count > 1 ? _jsxs(Text, { color: "#9cb8d8", children: [" (x", card.count, ")"] }) : null, _jsxs(Text, { color: "#5a7a9a", children: [" \u00B7 ", localityLabel] })] }), toolFeedMode === "expanded" ? (_jsxs(_Fragment, { children: [card.path ? _jsxs(Text, { color: "#6c88a8", children: ["path: ", card.path] }) : null, (card.snippetLines ?? []).slice(0, TOOL_SNIPPET_LINES).map((line, idx) => (_jsx(Text, { color: "#8ea1bd", children: line }, `${index}-snippet-${idx}`)))] })) : null] })] })] }, `${index}-${message.kind}`));
|
|
1291
1510
|
}
|
|
1292
|
-
return (_jsxs(Box, { flexDirection: "row", children: [_jsx(Text, { color: style.labelColor, bold: style.bold, children: paddedLabel }), message.kind === "assistant" ? (_jsx(Box, { flexDirection: "column", children: renderMarkdownContent(message.text, style.textColor, `assistant-${index}`) })) : (_jsx(Text, { color: style.textColor, bold: style.bold && message.kind === "error", children: message.text }))] }, `${index}-${message.kind}`));
|
|
1293
|
-
}), streamingText ? (_jsxs(Box, { flexDirection: "row", children: [_jsx(Text, { color: "#7aa2ff", children: styleForKind("assistant").label.padEnd(LABEL_WIDTH, " ") }), _jsx(Box, { flexDirection: "column", children: renderMarkdownContent(streamingText, "#dce9ff", "streaming") })] })) : null] }), _jsx(Text, { color: "#2a3a50", children:
|
|
1511
|
+
return (_jsxs(React.Fragment, { children: [needsGroupGap ? _jsx(Text, { children: " " }) : null, _jsxs(Box, { flexDirection: "row", children: [_jsx(Text, { color: style.labelColor, bold: style.bold, children: paddedLabel }), message.kind === "assistant" ? (_jsx(Box, { flexDirection: "column", children: renderMarkdownContent(message.text, style.textColor, `assistant-${index}`) })) : (_jsx(Text, { color: style.textColor, bold: style.bold && message.kind === "error", children: message.text }))] })] }, `${index}-${message.kind}`));
|
|
1512
|
+
}), streamingText ? (_jsxs(Box, { flexDirection: "row", children: [_jsx(Text, { color: "#7aa2ff", children: styleForKind("assistant").label.padEnd(LABEL_WIDTH, " ") }), _jsx(Box, { flexDirection: "column", children: renderMarkdownContent(streamingText, "#dce9ff", "streaming") })] })) : null] }), _jsx(Text, { color: "#2a3a50", children: divider }), thinking ? (_jsxs(Box, { flexDirection: "row", marginTop: 1, children: [_jsx(Text, { color: "#7aa2ff", children: "◆ astra".padEnd(LABEL_WIDTH, " ") }), _jsxs(Text, { color: "#6080a0", children: [_jsx(Spinner, { type: "dots2" }), _jsx(Text, { color: "#8aa2c9", children: " thinking..." })] })] })) : null, voiceEnabled && !thinking ? (_jsxs(Box, { flexDirection: "row", marginTop: 1, children: [_jsx(Text, { color: "#9ad5ff", children: "🎤 voice".padEnd(LABEL_WIDTH, " ") }), voicePreparing ? (_jsx(Text, { color: "#f4d58a", children: "\uD83D\uDFE1 preparing microphone..." })) : voiceListening && !voiceWaitingForSilence ? (_jsx(Text, { color: "#9de3b4", children: "\uD83D\uDFE2 listening now - speak clearly" })) : voiceWaitingForSilence ? (_jsx(Text, { color: "#b7c4d8", children: "\u23F3 speech detected - waiting for silence to send" })) : (_jsx(Text, { color: "#6f8199", children: "\u26AA voice armed - preparing next listen window" }))] })) : null, _jsxs(Box, { marginTop: 1, flexDirection: "row", children: [_jsx(Text, { color: "#7aa2ff", children: "\u276F " }), _jsx(TextInput, { value: prompt, onSubmit: (value) => {
|
|
1294
1513
|
setPrompt("");
|
|
1295
1514
|
void sendPrompt(value);
|
|
1296
1515
|
}, onChange: (value) => {
|