@astra-code/astra-ai 0.1.0 → 0.1.2
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/.env.example +6 -1
- package/README.md +13 -2
- package/dist/app/App.js +366 -51
- package/dist/app/App.js.map +1 -1
- package/dist/lib/backendClient.js +39 -2
- package/dist/lib/backendClient.js.map +1 -1
- package/dist/lib/config.js +10 -1
- package/dist/lib/config.js.map +1 -1
- package/dist/lib/voice.js +48 -11
- package/dist/lib/voice.js.map +1 -1
- package/package.json +1 -1
- package/src/app/App.tsx +429 -53
- package/src/lib/backendClient.ts +41 -2
- package/src/lib/config.ts +12 -1
- package/src/lib/voice.ts +51 -15
- package/src/types/events.ts +11 -2
package/dist/app/App.js
CHANGED
|
@@ -12,7 +12,7 @@ import { getBackendUrl, getDefaultClientId, getDefaultModel, getProviderForModel
|
|
|
12
12
|
import { runTerminalCommand } from "../lib/terminalBridge.js";
|
|
13
13
|
import { isWorkspaceTrusted, trustWorkspace } from "../lib/trustStore.js";
|
|
14
14
|
import { scanWorkspace } from "../lib/workspaceScanner.js";
|
|
15
|
-
import {
|
|
15
|
+
import { startLiveTranscription, transcribeOnce } from "../lib/voice.js";
|
|
16
16
|
// const ASTRA_ASCII = `
|
|
17
17
|
// █████╗ ███████╗████████╗██████╗ █████╗ ██████╗ ██████╗ ██████╗ ███████╗
|
|
18
18
|
// ██╔══██╗██╔════╝╚══██╔══╝██╔══██╗██╔══██╗ ██╔════╝██╔═══██╗██╔══██╗██╔════╝
|
|
@@ -43,9 +43,21 @@ const centerLine = (text, width = WELCOME_WIDTH) => {
|
|
|
43
43
|
};
|
|
44
44
|
const FOUNDER_WELCOME = centerLine("Welcome to Astra from Astra CEO & Founder, Sean Donovan");
|
|
45
45
|
const HISTORY_SETTINGS_URL = "https://astra-web-builder.vercel.app/settings";
|
|
46
|
+
const VOICE_SILENCE_MS = Number(process.env.ASTRA_VOICE_SILENCE_MS ?? "3000");
|
|
47
|
+
const TOOL_SNIPPET_LINES = 6;
|
|
48
|
+
const NOISY_EVENT_TYPES = new Set([
|
|
49
|
+
"timing",
|
|
50
|
+
"session_info",
|
|
51
|
+
"axon_status",
|
|
52
|
+
"orchestration_plan",
|
|
53
|
+
"task_plan",
|
|
54
|
+
"task_update",
|
|
55
|
+
"preset_status"
|
|
56
|
+
]);
|
|
46
57
|
const eventToToolLine = (event) => {
|
|
47
58
|
if (event.type === "tool_start") {
|
|
48
|
-
const
|
|
59
|
+
const tool = event.tool;
|
|
60
|
+
const name = tool?.name ?? "tool";
|
|
49
61
|
return `↳ ${name} executing...`;
|
|
50
62
|
}
|
|
51
63
|
if (event.type === "tool_result") {
|
|
@@ -53,8 +65,11 @@ const eventToToolLine = (event) => {
|
|
|
53
65
|
const mark = success ? "✓" : "✗";
|
|
54
66
|
const toolName = event.tool_name ?? "tool";
|
|
55
67
|
const payload = (event.data ?? {});
|
|
68
|
+
const screenshotPath = typeof payload.path === "string" ? payload.path : "";
|
|
56
69
|
const output = String(payload.output ??
|
|
57
70
|
payload.content ??
|
|
71
|
+
payload.summary ??
|
|
72
|
+
(toolName === "capture_screenshot" && screenshotPath ? `Saved screenshot: ${screenshotPath}` : undefined) ??
|
|
58
73
|
event.error ??
|
|
59
74
|
"");
|
|
60
75
|
const locality = payload.local === true ? "LOCAL" : "REMOTE";
|
|
@@ -70,6 +85,26 @@ const eventToToolLine = (event) => {
|
|
|
70
85
|
}
|
|
71
86
|
return null;
|
|
72
87
|
};
|
|
88
|
+
const extractSnippetLines = (content, maxLines = TOOL_SNIPPET_LINES) => {
|
|
89
|
+
if (!content) {
|
|
90
|
+
return [];
|
|
91
|
+
}
|
|
92
|
+
const lines = content.split("\n").map((line) => line.trimEnd());
|
|
93
|
+
if (lines.length <= maxLines) {
|
|
94
|
+
return lines;
|
|
95
|
+
}
|
|
96
|
+
return lines.slice(0, maxLines);
|
|
97
|
+
};
|
|
98
|
+
const extractDiffSnippet = (diff, maxLines = TOOL_SNIPPET_LINES) => {
|
|
99
|
+
if (!Array.isArray(diff)) {
|
|
100
|
+
return [];
|
|
101
|
+
}
|
|
102
|
+
const interesting = diff
|
|
103
|
+
.map((line) => String(line))
|
|
104
|
+
.filter((line) => line.startsWith("+") || line.startsWith("-"))
|
|
105
|
+
.map((line) => line.slice(0, 220));
|
|
106
|
+
return interesting.slice(0, maxLines);
|
|
107
|
+
};
|
|
73
108
|
const looksLikeLocalFilesystemClaim = (text) => {
|
|
74
109
|
const lower = text.toLowerCase();
|
|
75
110
|
const changeWord = lower.includes("created") ||
|
|
@@ -365,6 +400,8 @@ export const AstraApp = () => {
|
|
|
365
400
|
const [streamingText, setStreamingText] = useState("");
|
|
366
401
|
const [voiceEnabled, setVoiceEnabled] = useState(false);
|
|
367
402
|
const [voiceListening, setVoiceListening] = useState(false);
|
|
403
|
+
const [voiceWaitingForSilence, setVoiceWaitingForSilence] = useState(false);
|
|
404
|
+
const [voiceQueuedPrompt, setVoiceQueuedPrompt] = useState(null);
|
|
368
405
|
const [historyOpen, setHistoryOpen] = useState(false);
|
|
369
406
|
const [historyMode, setHistoryMode] = useState("picker");
|
|
370
407
|
const [historyPickerIndex, setHistoryPickerIndex] = useState(0);
|
|
@@ -373,9 +410,15 @@ export const AstraApp = () => {
|
|
|
373
410
|
const [historyRows, setHistoryRows] = useState([]);
|
|
374
411
|
const [historyIndex, setHistoryIndex] = useState(0);
|
|
375
412
|
const liveVoiceRef = useRef(null);
|
|
413
|
+
const voiceSilenceTimerRef = useRef(null);
|
|
414
|
+
const fileEditBuffersRef = useRef(new Map());
|
|
415
|
+
const isSuperAdmin = user?.role === "super_admin";
|
|
376
416
|
const pushMessage = useCallback((kind, text) => {
|
|
377
417
|
setMessages((prev) => [...prev, { kind, text }].slice(-300));
|
|
378
418
|
}, []);
|
|
419
|
+
const pushToolCard = useCallback((card) => {
|
|
420
|
+
setMessages((prev) => [...prev, { kind: "tool", text: card.summary, card }].slice(-300));
|
|
421
|
+
}, []);
|
|
379
422
|
const filteredHistory = useMemo(() => {
|
|
380
423
|
const q = historyQuery.trim().toLowerCase();
|
|
381
424
|
if (!q) {
|
|
@@ -439,41 +482,80 @@ export const AstraApp = () => {
|
|
|
439
482
|
}
|
|
440
483
|
}, [backend, pushMessage, user]);
|
|
441
484
|
const stopLiveVoice = useCallback(async () => {
|
|
485
|
+
if (voiceSilenceTimerRef.current) {
|
|
486
|
+
clearTimeout(voiceSilenceTimerRef.current);
|
|
487
|
+
voiceSilenceTimerRef.current = null;
|
|
488
|
+
}
|
|
442
489
|
const controller = liveVoiceRef.current;
|
|
443
490
|
if (!controller) {
|
|
491
|
+
setVoiceWaitingForSilence(false);
|
|
444
492
|
return;
|
|
445
493
|
}
|
|
446
494
|
liveVoiceRef.current = null;
|
|
447
495
|
await controller.stop();
|
|
448
496
|
setVoiceListening(false);
|
|
497
|
+
setVoiceWaitingForSilence(false);
|
|
449
498
|
}, []);
|
|
450
|
-
const startLiveVoice = useCallback(() => {
|
|
499
|
+
const startLiveVoice = useCallback((announce = true) => {
|
|
451
500
|
if (liveVoiceRef.current) {
|
|
452
501
|
return;
|
|
453
502
|
}
|
|
454
503
|
setVoiceEnabled(true);
|
|
455
504
|
setVoiceListening(true);
|
|
456
|
-
|
|
505
|
+
setVoiceWaitingForSilence(false);
|
|
506
|
+
if (announce) {
|
|
507
|
+
pushMessage("system", "Voice input started. Speak now…");
|
|
508
|
+
}
|
|
457
509
|
liveVoiceRef.current = startLiveTranscription({
|
|
458
510
|
onPartial: (text) => {
|
|
459
511
|
setPrompt(text);
|
|
512
|
+
if (voiceSilenceTimerRef.current) {
|
|
513
|
+
clearTimeout(voiceSilenceTimerRef.current);
|
|
514
|
+
}
|
|
515
|
+
const candidate = text.trim();
|
|
516
|
+
if (!candidate) {
|
|
517
|
+
return;
|
|
518
|
+
}
|
|
519
|
+
setVoiceWaitingForSilence(true);
|
|
520
|
+
voiceSilenceTimerRef.current = setTimeout(() => {
|
|
521
|
+
setVoiceQueuedPrompt(candidate);
|
|
522
|
+
void stopLiveVoice();
|
|
523
|
+
}, VOICE_SILENCE_MS);
|
|
460
524
|
},
|
|
461
525
|
onFinal: (text) => {
|
|
526
|
+
if (voiceSilenceTimerRef.current) {
|
|
527
|
+
clearTimeout(voiceSilenceTimerRef.current);
|
|
528
|
+
voiceSilenceTimerRef.current = null;
|
|
529
|
+
}
|
|
462
530
|
setPrompt(text);
|
|
531
|
+
liveVoiceRef.current = null;
|
|
532
|
+
setVoiceListening(false);
|
|
533
|
+
setVoiceWaitingForSilence(false);
|
|
463
534
|
},
|
|
464
535
|
onError: (error) => {
|
|
536
|
+
setVoiceWaitingForSilence(false);
|
|
465
537
|
pushMessage("error", `Voice transcription error: ${error.message}`);
|
|
466
538
|
}
|
|
467
539
|
});
|
|
468
|
-
}, [pushMessage]);
|
|
540
|
+
}, [pushMessage, stopLiveVoice]);
|
|
469
541
|
useEffect(() => {
|
|
470
542
|
return () => {
|
|
543
|
+
if (voiceSilenceTimerRef.current) {
|
|
544
|
+
clearTimeout(voiceSilenceTimerRef.current);
|
|
545
|
+
voiceSilenceTimerRef.current = null;
|
|
546
|
+
}
|
|
471
547
|
const controller = liveVoiceRef.current;
|
|
472
548
|
if (controller) {
|
|
473
549
|
void controller.stop();
|
|
474
550
|
}
|
|
475
551
|
};
|
|
476
552
|
}, []);
|
|
553
|
+
useEffect(() => {
|
|
554
|
+
if (!voiceEnabled || !user || thinking || voiceListening || liveVoiceRef.current) {
|
|
555
|
+
return;
|
|
556
|
+
}
|
|
557
|
+
startLiveVoice(false);
|
|
558
|
+
}, [startLiveVoice, thinking, user, voiceEnabled, voiceListening]);
|
|
477
559
|
useEffect(() => {
|
|
478
560
|
if (!trustedWorkspace) {
|
|
479
561
|
return;
|
|
@@ -490,6 +572,7 @@ export const AstraApp = () => {
|
|
|
490
572
|
}
|
|
491
573
|
const cached = loadSession();
|
|
492
574
|
if (!cached) {
|
|
575
|
+
backend.setAuthSession(null);
|
|
493
576
|
if (!cancelled) {
|
|
494
577
|
setBooting(false);
|
|
495
578
|
}
|
|
@@ -498,9 +581,13 @@ export const AstraApp = () => {
|
|
|
498
581
|
const valid = await backend.validateSession(cached);
|
|
499
582
|
if (!valid) {
|
|
500
583
|
clearSession();
|
|
584
|
+
backend.setAuthSession(null);
|
|
501
585
|
}
|
|
502
586
|
else if (!cancelled) {
|
|
503
|
-
|
|
587
|
+
const hydrated = await backend.getUserProfile(cached).catch(() => cached);
|
|
588
|
+
saveSession(hydrated);
|
|
589
|
+
backend.setAuthSession(hydrated);
|
|
590
|
+
setUser(hydrated);
|
|
504
591
|
setMessages([
|
|
505
592
|
{ kind: "system", text: "Welcome back. Type a message or /help." },
|
|
506
593
|
{ kind: "system", text: "" }
|
|
@@ -629,7 +716,7 @@ export const AstraApp = () => {
|
|
|
629
716
|
try {
|
|
630
717
|
await backend.deleteSession(selected.id);
|
|
631
718
|
setHistoryRows((prev) => prev.filter((row) => row.id !== selected.id));
|
|
632
|
-
pushMessage("
|
|
719
|
+
pushMessage("system", `Archived session: ${selected.title}`);
|
|
633
720
|
}
|
|
634
721
|
catch (error) {
|
|
635
722
|
pushMessage("error", `Failed to delete session: ${error instanceof Error ? error.message : String(error)}`);
|
|
@@ -662,8 +749,10 @@ export const AstraApp = () => {
|
|
|
662
749
|
throw new Error(data.error);
|
|
663
750
|
}
|
|
664
751
|
const authSession = data;
|
|
665
|
-
|
|
666
|
-
|
|
752
|
+
const hydrated = await backend.getUserProfile(authSession).catch(() => authSession);
|
|
753
|
+
saveSession(hydrated);
|
|
754
|
+
backend.setAuthSession(hydrated);
|
|
755
|
+
setUser(hydrated);
|
|
667
756
|
setEmail("");
|
|
668
757
|
setPassword("");
|
|
669
758
|
setMessages([
|
|
@@ -679,8 +768,80 @@ export const AstraApp = () => {
|
|
|
679
768
|
}
|
|
680
769
|
}, [backend, email, loginMode, password]);
|
|
681
770
|
const handleEvent = useCallback(async (event, activeSessionId) => {
|
|
771
|
+
if (event.type === "file_edit_start") {
|
|
772
|
+
const data = (event.data ?? {});
|
|
773
|
+
const toolId = typeof event.tool_id === "string"
|
|
774
|
+
? event.tool_id
|
|
775
|
+
: typeof data.tool_id === "string"
|
|
776
|
+
? data.tool_id
|
|
777
|
+
: "";
|
|
778
|
+
const toolName = typeof event.tool_name === "string"
|
|
779
|
+
? event.tool_name
|
|
780
|
+
: typeof data.tool_name === "string"
|
|
781
|
+
? data.tool_name
|
|
782
|
+
: undefined;
|
|
783
|
+
if (toolId) {
|
|
784
|
+
const current = fileEditBuffersRef.current.get(toolId) ?? { path: "(unknown)", chunks: [], toolName: undefined };
|
|
785
|
+
fileEditBuffersRef.current.set(toolId, { ...current, toolName });
|
|
786
|
+
}
|
|
787
|
+
return null;
|
|
788
|
+
}
|
|
789
|
+
if (event.type === "file_edit_path") {
|
|
790
|
+
const data = (event.data ?? {});
|
|
791
|
+
const toolId = typeof event.tool_id === "string"
|
|
792
|
+
? event.tool_id
|
|
793
|
+
: typeof data.tool_id === "string"
|
|
794
|
+
? data.tool_id
|
|
795
|
+
: "";
|
|
796
|
+
const path = typeof event.path === "string"
|
|
797
|
+
? event.path
|
|
798
|
+
: typeof data.path === "string"
|
|
799
|
+
? data.path
|
|
800
|
+
: "(unknown)";
|
|
801
|
+
const toolName = typeof event.tool_name === "string"
|
|
802
|
+
? event.tool_name
|
|
803
|
+
: typeof data.tool_name === "string"
|
|
804
|
+
? data.tool_name
|
|
805
|
+
: undefined;
|
|
806
|
+
if (toolId) {
|
|
807
|
+
const current = fileEditBuffersRef.current.get(toolId);
|
|
808
|
+
fileEditBuffersRef.current.set(toolId, { path, chunks: [], toolName: toolName ?? current?.toolName });
|
|
809
|
+
}
|
|
810
|
+
if (!isSuperAdmin) {
|
|
811
|
+
const resolvedToolName = toolName ?? fileEditBuffersRef.current.get(toolId)?.toolName;
|
|
812
|
+
const usingBulkWriter = resolvedToolName === "bulk_file_writer";
|
|
813
|
+
pushToolCard({
|
|
814
|
+
kind: "fileEdit",
|
|
815
|
+
toolName: resolvedToolName ?? "edit_file",
|
|
816
|
+
locality: "LOCAL",
|
|
817
|
+
summary: usingBulkWriter ? `Writing file ${path}` : `Editing ${path}`,
|
|
818
|
+
path
|
|
819
|
+
});
|
|
820
|
+
}
|
|
821
|
+
return null;
|
|
822
|
+
}
|
|
823
|
+
if (event.type === "file_edit_delta") {
|
|
824
|
+
const data = (event.data ?? {});
|
|
825
|
+
const toolId = typeof event.tool_id === "string"
|
|
826
|
+
? event.tool_id
|
|
827
|
+
: typeof data.tool_id === "string"
|
|
828
|
+
? data.tool_id
|
|
829
|
+
: "";
|
|
830
|
+
const chunk = typeof event.content === "string" ? event.content : "";
|
|
831
|
+
if (toolId && chunk) {
|
|
832
|
+
const current = fileEditBuffersRef.current.get(toolId) ?? { path: "(unknown)", chunks: [], toolName: undefined };
|
|
833
|
+
current.chunks.push(chunk);
|
|
834
|
+
fileEditBuffersRef.current.set(toolId, current);
|
|
835
|
+
}
|
|
836
|
+
if (isSuperAdmin) {
|
|
837
|
+
const raw = JSON.stringify(event);
|
|
838
|
+
pushMessage("tool", `event ${event.type}: ${raw.slice(0, 220)}`);
|
|
839
|
+
}
|
|
840
|
+
return null;
|
|
841
|
+
}
|
|
682
842
|
const assistantPiece = extractAssistantText(event);
|
|
683
|
-
if (assistantPiece &&
|
|
843
|
+
if (assistantPiece &&
|
|
844
|
+
!["tool_result", "error", "credits_update", "credits_exhausted", "file_edit_delta", "file_edit_path"].includes(event.type)) {
|
|
684
845
|
return assistantPiece;
|
|
685
846
|
}
|
|
686
847
|
if (event.type === "run_in_terminal") {
|
|
@@ -698,20 +859,45 @@ export const AstraApp = () => {
|
|
|
698
859
|
cancelled: result.cancelled
|
|
699
860
|
});
|
|
700
861
|
}
|
|
701
|
-
|
|
862
|
+
if (isSuperAdmin) {
|
|
863
|
+
pushMessage("tool", `[LOCAL] ▸ ran: ${command.slice(0, 60)}...`);
|
|
864
|
+
}
|
|
865
|
+
else {
|
|
866
|
+
pushToolCard({
|
|
867
|
+
kind: "terminal",
|
|
868
|
+
toolName: "run_in_terminal",
|
|
869
|
+
locality: "LOCAL",
|
|
870
|
+
summary: `Running command: ${command.slice(0, 80)}`
|
|
871
|
+
});
|
|
872
|
+
}
|
|
702
873
|
return null;
|
|
703
874
|
}
|
|
704
875
|
// Apply file operations to the local workspace immediately.
|
|
705
876
|
if (event.type === "tool_result" && event.success) {
|
|
706
877
|
const d = (event.data ?? {});
|
|
707
878
|
const resultType = event.result_type;
|
|
879
|
+
const toolResultId = typeof event.tool_call_id === "string" ? event.tool_call_id : "";
|
|
708
880
|
const relPath = typeof d.path === "string" ? d.path : null;
|
|
709
881
|
const lang = typeof d.language === "string" ? d.language : "plaintext";
|
|
882
|
+
const locality = d.local === true ? "LOCAL" : "REMOTE";
|
|
710
883
|
if (relPath) {
|
|
711
884
|
if (resultType === "file_create") {
|
|
712
885
|
const content = typeof d.content === "string" ? d.content : "";
|
|
713
886
|
writeLocalFile(relPath, content, lang);
|
|
714
|
-
|
|
887
|
+
const snippetLines = extractSnippetLines(content);
|
|
888
|
+
if (isSuperAdmin) {
|
|
889
|
+
pushMessage("tool", `[LOCAL] ✓ wrote ${relPath}`);
|
|
890
|
+
}
|
|
891
|
+
else {
|
|
892
|
+
pushToolCard({
|
|
893
|
+
kind: "fileCreate",
|
|
894
|
+
toolName: "create_file",
|
|
895
|
+
locality,
|
|
896
|
+
summary: `Created ${relPath}`,
|
|
897
|
+
path: relPath,
|
|
898
|
+
snippetLines
|
|
899
|
+
});
|
|
900
|
+
}
|
|
715
901
|
}
|
|
716
902
|
else if (resultType === "file_edit") {
|
|
717
903
|
const content = typeof d.full_new_content === "string"
|
|
@@ -721,14 +907,78 @@ export const AstraApp = () => {
|
|
|
721
907
|
: null;
|
|
722
908
|
if (content !== null) {
|
|
723
909
|
writeLocalFile(relPath, content, lang);
|
|
724
|
-
|
|
910
|
+
const snippetLines = extractDiffSnippet(d.diff) ?? extractSnippetLines(content);
|
|
911
|
+
if (isSuperAdmin) {
|
|
912
|
+
pushMessage("tool", `[LOCAL] ✓ edited ${relPath}`);
|
|
913
|
+
}
|
|
914
|
+
else {
|
|
915
|
+
const toolId = toolResultId;
|
|
916
|
+
const streamed = toolId ? fileEditBuffersRef.current.get(toolId) : undefined;
|
|
917
|
+
const streamedSnippet = streamed ? extractSnippetLines(streamed.chunks.join("")) : [];
|
|
918
|
+
pushToolCard({
|
|
919
|
+
kind: "fileEdit",
|
|
920
|
+
toolName: "edit_file",
|
|
921
|
+
locality,
|
|
922
|
+
summary: `Updated ${relPath}`,
|
|
923
|
+
path: relPath,
|
|
924
|
+
snippetLines: streamedSnippet.length > 0 ? streamedSnippet : snippetLines
|
|
925
|
+
});
|
|
926
|
+
if (toolId) {
|
|
927
|
+
fileEditBuffersRef.current.delete(toolId);
|
|
928
|
+
}
|
|
929
|
+
}
|
|
725
930
|
}
|
|
726
931
|
}
|
|
727
932
|
else if (resultType === "file_delete") {
|
|
728
933
|
deleteLocalFile(relPath);
|
|
729
|
-
|
|
934
|
+
if (isSuperAdmin) {
|
|
935
|
+
pushMessage("tool", `[LOCAL] ✓ deleted ${relPath}`);
|
|
936
|
+
}
|
|
937
|
+
else {
|
|
938
|
+
pushToolCard({
|
|
939
|
+
kind: "fileDelete",
|
|
940
|
+
toolName: "delete_file",
|
|
941
|
+
locality,
|
|
942
|
+
summary: `Deleted ${relPath}`,
|
|
943
|
+
path: relPath
|
|
944
|
+
});
|
|
945
|
+
}
|
|
730
946
|
}
|
|
731
947
|
}
|
|
948
|
+
if (!isSuperAdmin && event.tool_name === "start_preview") {
|
|
949
|
+
const message = typeof d.message === "string"
|
|
950
|
+
? d.message
|
|
951
|
+
: typeof d.preview_url === "string"
|
|
952
|
+
? `Preview: ${d.preview_url}`
|
|
953
|
+
: "Local preview started.";
|
|
954
|
+
pushToolCard({
|
|
955
|
+
kind: "preview",
|
|
956
|
+
toolName: "start_preview",
|
|
957
|
+
locality,
|
|
958
|
+
summary: message
|
|
959
|
+
});
|
|
960
|
+
return null;
|
|
961
|
+
}
|
|
962
|
+
if (!isSuperAdmin && event.tool_name === "capture_screenshot") {
|
|
963
|
+
const screenshotPath = typeof d.path === "string" ? d.path : "";
|
|
964
|
+
const targetUrl = typeof d.url === "string" ? d.url : "";
|
|
965
|
+
const message = screenshotPath
|
|
966
|
+
? `Screenshot saved: ${screenshotPath}`
|
|
967
|
+
: targetUrl
|
|
968
|
+
? `Captured screenshot: ${targetUrl}`
|
|
969
|
+
: "Screenshot captured.";
|
|
970
|
+
pushToolCard({
|
|
971
|
+
kind: "preview",
|
|
972
|
+
toolName: "capture_screenshot",
|
|
973
|
+
locality,
|
|
974
|
+
summary: message,
|
|
975
|
+
...(screenshotPath ? { path: screenshotPath } : {})
|
|
976
|
+
});
|
|
977
|
+
return null;
|
|
978
|
+
}
|
|
979
|
+
if (toolResultId) {
|
|
980
|
+
fileEditBuffersRef.current.delete(toolResultId);
|
|
981
|
+
}
|
|
732
982
|
}
|
|
733
983
|
if (event.type === "credits_update") {
|
|
734
984
|
const remaining = Number(event.remaining ?? 0);
|
|
@@ -744,33 +994,71 @@ export const AstraApp = () => {
|
|
|
744
994
|
if (event.type === "credits_exhausted") {
|
|
745
995
|
setCreditsRemaining(0);
|
|
746
996
|
}
|
|
997
|
+
if (!isSuperAdmin && NOISY_EVENT_TYPES.has(event.type)) {
|
|
998
|
+
return null;
|
|
999
|
+
}
|
|
1000
|
+
if (!isSuperAdmin && event.type === "tool_start") {
|
|
1001
|
+
const tool = event.tool;
|
|
1002
|
+
const name = tool?.name ?? "tool";
|
|
1003
|
+
pushToolCard({
|
|
1004
|
+
kind: "start",
|
|
1005
|
+
toolName: name,
|
|
1006
|
+
locality: "REMOTE",
|
|
1007
|
+
summary: `${name} is running...`
|
|
1008
|
+
});
|
|
1009
|
+
return null;
|
|
1010
|
+
}
|
|
747
1011
|
const toolLine = eventToToolLine(event);
|
|
748
1012
|
if (toolLine) {
|
|
749
1013
|
if (event.type === "error" || event.type === "credits_exhausted") {
|
|
750
1014
|
pushMessage("error", toolLine);
|
|
751
1015
|
}
|
|
752
1016
|
else {
|
|
753
|
-
|
|
1017
|
+
if (isSuperAdmin) {
|
|
1018
|
+
pushMessage("tool", toolLine);
|
|
1019
|
+
}
|
|
1020
|
+
else if (event.type === "tool_result") {
|
|
1021
|
+
const mark = event.success ? "completed" : "failed";
|
|
1022
|
+
const toolName = typeof event.tool_name === "string" ? event.tool_name : "tool";
|
|
1023
|
+
pushToolCard({
|
|
1024
|
+
kind: event.success ? "success" : "error",
|
|
1025
|
+
toolName,
|
|
1026
|
+
locality: (event.data ?? {}).local === true ? "LOCAL" : "REMOTE",
|
|
1027
|
+
summary: `${toolName} ${mark}`
|
|
1028
|
+
});
|
|
1029
|
+
}
|
|
754
1030
|
}
|
|
755
1031
|
}
|
|
756
1032
|
else if (event.type !== "thinking") {
|
|
757
|
-
|
|
758
|
-
|
|
1033
|
+
if (isSuperAdmin) {
|
|
1034
|
+
const raw = JSON.stringify(event);
|
|
1035
|
+
pushMessage("tool", `event ${event.type}: ${raw.slice(0, 220)}`);
|
|
1036
|
+
}
|
|
759
1037
|
}
|
|
760
1038
|
return null;
|
|
761
|
-
}, [backend, deleteLocalFile, pushMessage, writeLocalFile]);
|
|
1039
|
+
}, [backend, deleteLocalFile, isSuperAdmin, pushMessage, pushToolCard, writeLocalFile]);
|
|
762
1040
|
const sendPrompt = useCallback(async (rawPrompt) => {
|
|
763
1041
|
const text = rawPrompt.trim();
|
|
764
1042
|
if (!text || !user || thinking) {
|
|
765
1043
|
return;
|
|
766
1044
|
}
|
|
767
1045
|
if (text === "/help") {
|
|
768
|
-
pushMessage("system", "/new /history /voice on|off|status
|
|
1046
|
+
pushMessage("system", "/new /history /voice /voice on|off|status /settings /settings model <id> /logout /exit");
|
|
769
1047
|
pushMessage("system", "");
|
|
770
1048
|
return;
|
|
771
1049
|
}
|
|
772
1050
|
if (text === "/settings") {
|
|
773
|
-
pushMessage("system", `Settings: mode=${runtimeMode} scope=${workspaceRoot} model=${activeModel} provider=${getProviderForModel(activeModel)} voice=${voiceEnabled ? "on" : "off"} listening=${voiceListening ? "yes" : "no"} client_id=${getDefaultClientId()} backend=${getBackendUrl()}`);
|
|
1051
|
+
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()}`);
|
|
1052
|
+
pushMessage("system", "");
|
|
1053
|
+
return;
|
|
1054
|
+
}
|
|
1055
|
+
if (text === "/voice") {
|
|
1056
|
+
if (!voiceEnabled) {
|
|
1057
|
+
setVoiceEnabled(true);
|
|
1058
|
+
startLiveVoice(true);
|
|
1059
|
+
return;
|
|
1060
|
+
}
|
|
1061
|
+
pushMessage("system", `Voice input is on${voiceListening ? " (currently listening)" : ""}. Use /voice off to disable.`);
|
|
774
1062
|
pushMessage("system", "");
|
|
775
1063
|
return;
|
|
776
1064
|
}
|
|
@@ -792,50 +1080,31 @@ export const AstraApp = () => {
|
|
|
792
1080
|
return;
|
|
793
1081
|
}
|
|
794
1082
|
if (text === "/voice status") {
|
|
795
|
-
pushMessage("system", `Voice
|
|
1083
|
+
pushMessage("system", `Voice input is ${voiceEnabled ? "on" : "off"}${voiceListening ? " (live transcription active)" : ""}${voiceWaitingForSilence ? " (waiting for silence to auto-send)" : ""}.`);
|
|
796
1084
|
pushMessage("system", "");
|
|
797
1085
|
return;
|
|
798
1086
|
}
|
|
799
1087
|
if (text === "/voice on") {
|
|
800
1088
|
setVoiceEnabled(true);
|
|
801
|
-
|
|
1089
|
+
startLiveVoice(true);
|
|
1090
|
+
pushMessage("system", `Voice input enabled. Auto-send after ${Math.round(VOICE_SILENCE_MS / 1000)}s silence.`);
|
|
802
1091
|
pushMessage("system", "");
|
|
803
1092
|
return;
|
|
804
1093
|
}
|
|
805
1094
|
if (text === "/voice off") {
|
|
806
1095
|
await stopLiveVoice();
|
|
807
1096
|
setVoiceEnabled(false);
|
|
808
|
-
pushMessage("system", "Voice
|
|
809
|
-
pushMessage("system", "");
|
|
810
|
-
return;
|
|
811
|
-
}
|
|
812
|
-
if (text === "/voice start") {
|
|
813
|
-
if (voiceListening) {
|
|
814
|
-
pushMessage("system", "Live transcription is already running.");
|
|
815
|
-
return;
|
|
816
|
-
}
|
|
817
|
-
startLiveVoice();
|
|
818
|
-
return;
|
|
819
|
-
}
|
|
820
|
-
if (text === "/voice stop") {
|
|
821
|
-
if (!voiceListening) {
|
|
822
|
-
pushMessage("system", "Live transcription is not running.");
|
|
823
|
-
return;
|
|
824
|
-
}
|
|
825
|
-
await stopLiveVoice();
|
|
826
|
-
pushMessage("system", "Live transcription stopped. Press Enter to send transcript.");
|
|
1097
|
+
pushMessage("system", "Voice input disabled.");
|
|
827
1098
|
pushMessage("system", "");
|
|
828
1099
|
return;
|
|
829
1100
|
}
|
|
830
1101
|
if (text === "/voice input") {
|
|
831
1102
|
const transcribed = await transcribeOnce();
|
|
832
1103
|
if (!transcribed) {
|
|
833
|
-
pushMessage("error", "No speech transcribed.
|
|
1104
|
+
pushMessage("error", "No speech transcribed. Ensure you're signed in and that mic capture works (optional ASTRA_STT_CAPTURE_COMMAND).");
|
|
834
1105
|
return;
|
|
835
1106
|
}
|
|
836
|
-
|
|
837
|
-
setPrompt(transcribed);
|
|
838
|
-
pushMessage("system", "Transcribed input ready. Press Enter to send.");
|
|
1107
|
+
setVoiceQueuedPrompt(transcribed.trim());
|
|
839
1108
|
return;
|
|
840
1109
|
}
|
|
841
1110
|
if (text === "/new") {
|
|
@@ -849,6 +1118,7 @@ export const AstraApp = () => {
|
|
|
849
1118
|
if (text === "/logout") {
|
|
850
1119
|
await stopLiveVoice();
|
|
851
1120
|
clearSession();
|
|
1121
|
+
backend.setAuthSession(null);
|
|
852
1122
|
setUser(null);
|
|
853
1123
|
setMessages([]);
|
|
854
1124
|
setChatMessages([]);
|
|
@@ -911,9 +1181,6 @@ export const AstraApp = () => {
|
|
|
911
1181
|
? `Remote result (not yet confirmed as local filesystem change): ${cleanedAssistant}`
|
|
912
1182
|
: cleanedAssistant;
|
|
913
1183
|
pushMessage("assistant", guardedAssistant);
|
|
914
|
-
if (voiceEnabled) {
|
|
915
|
-
speakText(guardedAssistant);
|
|
916
|
-
}
|
|
917
1184
|
setChatMessages((prev) => [...prev, { role: "assistant", content: cleanedAssistant }]);
|
|
918
1185
|
}
|
|
919
1186
|
else {
|
|
@@ -943,8 +1210,22 @@ export const AstraApp = () => {
|
|
|
943
1210
|
user,
|
|
944
1211
|
voiceEnabled,
|
|
945
1212
|
voiceListening,
|
|
1213
|
+
voiceWaitingForSilence,
|
|
946
1214
|
workspaceRoot
|
|
947
1215
|
]);
|
|
1216
|
+
useEffect(() => {
|
|
1217
|
+
if (!voiceQueuedPrompt || !user || thinking) {
|
|
1218
|
+
return;
|
|
1219
|
+
}
|
|
1220
|
+
const queued = voiceQueuedPrompt.trim();
|
|
1221
|
+
setVoiceQueuedPrompt(null);
|
|
1222
|
+
if (!queued) {
|
|
1223
|
+
return;
|
|
1224
|
+
}
|
|
1225
|
+
pushMessage("system", `Voice input: ${queued}`);
|
|
1226
|
+
setPrompt("");
|
|
1227
|
+
void sendPrompt(queued);
|
|
1228
|
+
}, [pushMessage, sendPrompt, thinking, user, voiceQueuedPrompt]);
|
|
948
1229
|
if (!trustedWorkspace) {
|
|
949
1230
|
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" }) })] }));
|
|
950
1231
|
}
|
|
@@ -969,17 +1250,51 @@ export const AstraApp = () => {
|
|
|
969
1250
|
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));
|
|
970
1251
|
}) })), _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 : "--"] })] })] }))] }));
|
|
971
1252
|
}
|
|
972
|
-
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { color: "#7aa2ff", children: ASTRA_ASCII }), _jsx(Text, { color: "#8ea1bd", children: FOUNDER_WELCOME }), _jsx(Text, { color: "#2a3a50", children: DIVIDER }), _jsxs(Box, { flexDirection: "row", gap: 2, children: [_jsxs(Box, { flexDirection: "row", children: [_jsx(Text, { color: "#4a6070", children: "mode " }), _jsx(Text, { color: "#9ad5ff", children: runtimeMode })] }), _jsxs(Box, { flexDirection: "row", children: [_jsx(Text, { color: "#4a6070", children: "scope " }), _jsx(Text, { color: "#7a9bba", children: workspaceRoot })] }), _jsxs(Box, { flexDirection: "row", children: [_jsx(Text, { color: "#4a6070", children: "provider " }), _jsx(Text, { color: "#9ad5ff", children: getProviderForModel(activeModel) })] }), _jsxs(Box, { flexDirection: "row", children: [_jsx(Text, { color: "#4a6070", children: "credits " }), _jsxs(Text, { color: creditsRemaining !== null && creditsRemaining < 50 ? "#ffaa55" : "#9ad5ff", children: [creditsRemaining ?? "--", lastCreditCost !== null ? (_jsxs(Text, { color: "#5a7a9a", children: [" (-", lastCreditCost, ")"] })) : null] })] }), _jsxs(Box, { flexDirection: "row", children: [_jsx(Text, { color: "#4a6070", children: "model " }), _jsx(Text, { color: "#9ad5ff", children: activeModel })] }), _jsxs(Box, { flexDirection: "row", children: [_jsx(Text, { color: "#4a6070", children: "voice " }), _jsx(Text, { color: voiceEnabled ? "#9ad5ff" : "#5a7a9a", children: voiceEnabled ? (voiceListening ? "on/listening" : "on") : "off" })] })] }), _jsx(Text, { color: "#2a3a50", children: DIVIDER }), _jsx(Text, { color: "#3a5068", children: "/help /new /history /voice on|off|status
|
|
1253
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { color: "#7aa2ff", children: ASTRA_ASCII }), _jsx(Text, { color: "#8ea1bd", children: FOUNDER_WELCOME }), _jsx(Text, { color: "#2a3a50", children: DIVIDER }), _jsxs(Box, { flexDirection: "row", gap: 2, children: [_jsxs(Box, { flexDirection: "row", children: [_jsx(Text, { color: "#4a6070", children: "mode " }), _jsx(Text, { color: "#9ad5ff", children: runtimeMode })] }), _jsxs(Box, { flexDirection: "row", children: [_jsx(Text, { color: "#4a6070", children: "scope " }), _jsx(Text, { color: "#7a9bba", children: workspaceRoot })] }), _jsxs(Box, { flexDirection: "row", children: [_jsx(Text, { color: "#4a6070", children: "provider " }), _jsx(Text, { color: "#9ad5ff", children: getProviderForModel(activeModel) })] }), _jsxs(Box, { flexDirection: "row", children: [_jsx(Text, { color: "#4a6070", children: "credits " }), _jsxs(Text, { color: creditsRemaining !== null && creditsRemaining < 50 ? "#ffaa55" : "#9ad5ff", children: [creditsRemaining ?? "--", lastCreditCost !== null ? (_jsxs(Text, { color: "#5a7a9a", children: [" (-", lastCreditCost, ")"] })) : null] })] }), _jsxs(Box, { flexDirection: "row", children: [_jsx(Text, { color: "#4a6070", children: "model " }), _jsx(Text, { color: "#9ad5ff", children: activeModel })] }), _jsxs(Box, { flexDirection: "row", children: [_jsx(Text, { color: "#4a6070", children: "voice " }), _jsx(Text, { color: voiceEnabled ? "#9ad5ff" : "#5a7a9a", children: voiceEnabled ? (voiceListening ? (voiceWaitingForSilence ? "on/waiting" : "on/listening") : "on") : "off" })] })] }), _jsx(Text, { color: "#2a3a50", children: DIVIDER }), _jsx(Text, { color: "#3a5068", children: "/help /new /history /voice /voice on|off|status /settings /logout /exit" }), _jsx(Text, { color: "#2a3a50", children: DIVIDER }), _jsxs(Box, { flexDirection: "column", marginTop: 1, children: [messages.map((message, index) => {
|
|
973
1254
|
const style = styleForKind(message.kind);
|
|
974
1255
|
const paddedLabel = style.label.padEnd(LABEL_WIDTH, " ");
|
|
975
1256
|
const isSpacing = message.text === "" && message.kind === "system";
|
|
976
1257
|
if (isSpacing) {
|
|
977
1258
|
return _jsx(Box, { marginTop: 0, children: _jsx(Text, { children: " " }) }, `${index}-spacer`);
|
|
978
1259
|
}
|
|
1260
|
+
if (message.kind === "tool" && message.card && !isSuperAdmin) {
|
|
1261
|
+
const card = message.card;
|
|
1262
|
+
const icon = card.kind === "error"
|
|
1263
|
+
? "✕"
|
|
1264
|
+
: card.kind === "fileDelete"
|
|
1265
|
+
? "🗑"
|
|
1266
|
+
: card.kind === "fileCreate"
|
|
1267
|
+
? "+"
|
|
1268
|
+
: card.kind === "fileEdit"
|
|
1269
|
+
? "✎"
|
|
1270
|
+
: card.kind === "preview"
|
|
1271
|
+
? "◉"
|
|
1272
|
+
: card.kind === "terminal"
|
|
1273
|
+
? "⌘"
|
|
1274
|
+
: card.kind === "start"
|
|
1275
|
+
? "…"
|
|
1276
|
+
: "✓";
|
|
1277
|
+
const accent = card.kind === "error"
|
|
1278
|
+
? "#ff8d8d"
|
|
1279
|
+
: card.kind === "fileDelete"
|
|
1280
|
+
? "#8a96a6"
|
|
1281
|
+
: card.kind === "fileEdit"
|
|
1282
|
+
? "#b7d4ff"
|
|
1283
|
+
: card.kind === "fileCreate"
|
|
1284
|
+
? "#a5e9c5"
|
|
1285
|
+
: card.kind === "preview"
|
|
1286
|
+
? "#9ad5ff"
|
|
1287
|
+
: "#9bc5ff";
|
|
1288
|
+
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, " ", _jsxs(Text, { color: "#5a7a9a", children: ["[", card.locality, "]"] })] }), 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}`)))] })] }, `${index}-${message.kind}`));
|
|
1289
|
+
}
|
|
979
1290
|
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}`));
|
|
980
|
-
}), 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, _jsxs(Box, { marginTop: 1, flexDirection: "row", children: [_jsx(Text, { color: "#7aa2ff", children: "\u276F " }), _jsx(TextInput, { value: prompt,
|
|
1291
|
+
}), 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, " ") }), voiceListening && !voiceWaitingForSilence ? (_jsx(Text, { color: "#a6d9ff", children: "\uD83C\uDF99 listening... goblin ears activated \uD83D\uDC42" })) : voiceWaitingForSilence ? (_jsx(Text, { color: "#b7c4d8", children: "\u23F8 waiting for silence... dramatic pause loading..." })) : (_jsx(Text, { color: "#6f8199", children: "voice armed... say something when ready" }))] })) : null, _jsxs(Box, { marginTop: 1, flexDirection: "row", children: [_jsx(Text, { color: "#7aa2ff", children: "\u276F " }), _jsx(TextInput, { value: prompt, onSubmit: (value) => {
|
|
981
1292
|
setPrompt("");
|
|
982
1293
|
void sendPrompt(value);
|
|
983
|
-
},
|
|
1294
|
+
}, onChange: (value) => {
|
|
1295
|
+
if (!voiceListening) {
|
|
1296
|
+
setPrompt(value);
|
|
1297
|
+
}
|
|
1298
|
+
}, placeholder: voiceEnabled ? "Ask Astra... (voice on: auto listen + send on silence)" : "Ask Astra..." })] })] }));
|
|
984
1299
|
};
|
|
985
1300
|
//# sourceMappingURL=App.js.map
|