@astra-code/astra-ai 0.1.0 → 0.1.1
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 +3 -1
- package/README.md +1 -1
- package/dist/app/App.js +303 -50
- 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/voice.js +38 -3
- package/dist/lib/voice.js.map +1 -1
- package/package.json +1 -1
- package/src/app/App.tsx +409 -53
- package/src/lib/backendClient.ts +41 -2
- package/src/lib/voice.ts +40 -3
- package/src/types/events.ts +11 -2
package/.env.example
CHANGED
|
@@ -13,7 +13,9 @@ ASTRA_STT_COMMAND=
|
|
|
13
13
|
# Optional: live Whisper transcription tuning
|
|
14
14
|
ASTRA_STT_MODEL=whisper-1
|
|
15
15
|
ASTRA_STT_CHUNK_SECONDS=2.5
|
|
16
|
+
ASTRA_VOICE_SILENCE_MS=3000
|
|
16
17
|
# Optional override for microphone capture.
|
|
17
18
|
# Use placeholders {output} and {seconds}.
|
|
18
|
-
#
|
|
19
|
+
# Default on macOS prefers ffmpeg; custom example:
|
|
20
|
+
# ffmpeg -f avfoundation -i ":0" -ar 16000 -ac 1 -t {seconds} "{output}"
|
|
19
21
|
ASTRA_STT_CAPTURE_COMMAND=
|
package/README.md
CHANGED
|
@@ -57,7 +57,7 @@ No-args mode launches the Ink terminal UI:
|
|
|
57
57
|
- slash commands:
|
|
58
58
|
- `/help`, `/new`, `/settings`, `/logout`, `/exit`
|
|
59
59
|
- `/history` (open searchable session history panel)
|
|
60
|
-
- `/voice on|off|status
|
|
60
|
+
- `/voice`, `/voice on|off|status`, `/voice input` (user voice input; auto-send after silence)
|
|
61
61
|
- tool-event rendering (`tool_start`, `tool_result`, `credits_update`, `error`)
|
|
62
62
|
- terminal bridge auto-run for `run_in_terminal` + callback to `/api/agent/terminal-result`
|
|
63
63
|
|
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") {
|
|
@@ -70,6 +82,26 @@ const eventToToolLine = (event) => {
|
|
|
70
82
|
}
|
|
71
83
|
return null;
|
|
72
84
|
};
|
|
85
|
+
const extractSnippetLines = (content, maxLines = TOOL_SNIPPET_LINES) => {
|
|
86
|
+
if (!content) {
|
|
87
|
+
return [];
|
|
88
|
+
}
|
|
89
|
+
const lines = content.split("\n").map((line) => line.trimEnd());
|
|
90
|
+
if (lines.length <= maxLines) {
|
|
91
|
+
return lines;
|
|
92
|
+
}
|
|
93
|
+
return lines.slice(0, maxLines);
|
|
94
|
+
};
|
|
95
|
+
const extractDiffSnippet = (diff, maxLines = TOOL_SNIPPET_LINES) => {
|
|
96
|
+
if (!Array.isArray(diff)) {
|
|
97
|
+
return [];
|
|
98
|
+
}
|
|
99
|
+
const interesting = diff
|
|
100
|
+
.map((line) => String(line))
|
|
101
|
+
.filter((line) => line.startsWith("+") || line.startsWith("-"))
|
|
102
|
+
.map((line) => line.slice(0, 220));
|
|
103
|
+
return interesting.slice(0, maxLines);
|
|
104
|
+
};
|
|
73
105
|
const looksLikeLocalFilesystemClaim = (text) => {
|
|
74
106
|
const lower = text.toLowerCase();
|
|
75
107
|
const changeWord = lower.includes("created") ||
|
|
@@ -365,6 +397,8 @@ export const AstraApp = () => {
|
|
|
365
397
|
const [streamingText, setStreamingText] = useState("");
|
|
366
398
|
const [voiceEnabled, setVoiceEnabled] = useState(false);
|
|
367
399
|
const [voiceListening, setVoiceListening] = useState(false);
|
|
400
|
+
const [voiceWaitingForSilence, setVoiceWaitingForSilence] = useState(false);
|
|
401
|
+
const [voiceQueuedPrompt, setVoiceQueuedPrompt] = useState(null);
|
|
368
402
|
const [historyOpen, setHistoryOpen] = useState(false);
|
|
369
403
|
const [historyMode, setHistoryMode] = useState("picker");
|
|
370
404
|
const [historyPickerIndex, setHistoryPickerIndex] = useState(0);
|
|
@@ -373,9 +407,15 @@ export const AstraApp = () => {
|
|
|
373
407
|
const [historyRows, setHistoryRows] = useState([]);
|
|
374
408
|
const [historyIndex, setHistoryIndex] = useState(0);
|
|
375
409
|
const liveVoiceRef = useRef(null);
|
|
410
|
+
const voiceSilenceTimerRef = useRef(null);
|
|
411
|
+
const fileEditBuffersRef = useRef(new Map());
|
|
412
|
+
const isSuperAdmin = user?.role === "super_admin";
|
|
376
413
|
const pushMessage = useCallback((kind, text) => {
|
|
377
414
|
setMessages((prev) => [...prev, { kind, text }].slice(-300));
|
|
378
415
|
}, []);
|
|
416
|
+
const pushToolCard = useCallback((card) => {
|
|
417
|
+
setMessages((prev) => [...prev, { kind: "tool", text: card.summary, card }].slice(-300));
|
|
418
|
+
}, []);
|
|
379
419
|
const filteredHistory = useMemo(() => {
|
|
380
420
|
const q = historyQuery.trim().toLowerCase();
|
|
381
421
|
if (!q) {
|
|
@@ -439,41 +479,80 @@ export const AstraApp = () => {
|
|
|
439
479
|
}
|
|
440
480
|
}, [backend, pushMessage, user]);
|
|
441
481
|
const stopLiveVoice = useCallback(async () => {
|
|
482
|
+
if (voiceSilenceTimerRef.current) {
|
|
483
|
+
clearTimeout(voiceSilenceTimerRef.current);
|
|
484
|
+
voiceSilenceTimerRef.current = null;
|
|
485
|
+
}
|
|
442
486
|
const controller = liveVoiceRef.current;
|
|
443
487
|
if (!controller) {
|
|
488
|
+
setVoiceWaitingForSilence(false);
|
|
444
489
|
return;
|
|
445
490
|
}
|
|
446
491
|
liveVoiceRef.current = null;
|
|
447
492
|
await controller.stop();
|
|
448
493
|
setVoiceListening(false);
|
|
494
|
+
setVoiceWaitingForSilence(false);
|
|
449
495
|
}, []);
|
|
450
|
-
const startLiveVoice = useCallback(() => {
|
|
496
|
+
const startLiveVoice = useCallback((announce = true) => {
|
|
451
497
|
if (liveVoiceRef.current) {
|
|
452
498
|
return;
|
|
453
499
|
}
|
|
454
500
|
setVoiceEnabled(true);
|
|
455
501
|
setVoiceListening(true);
|
|
456
|
-
|
|
502
|
+
setVoiceWaitingForSilence(false);
|
|
503
|
+
if (announce) {
|
|
504
|
+
pushMessage("system", "Voice input started. Speak now…");
|
|
505
|
+
}
|
|
457
506
|
liveVoiceRef.current = startLiveTranscription({
|
|
458
507
|
onPartial: (text) => {
|
|
459
508
|
setPrompt(text);
|
|
509
|
+
if (voiceSilenceTimerRef.current) {
|
|
510
|
+
clearTimeout(voiceSilenceTimerRef.current);
|
|
511
|
+
}
|
|
512
|
+
const candidate = text.trim();
|
|
513
|
+
if (!candidate) {
|
|
514
|
+
return;
|
|
515
|
+
}
|
|
516
|
+
setVoiceWaitingForSilence(true);
|
|
517
|
+
voiceSilenceTimerRef.current = setTimeout(() => {
|
|
518
|
+
setVoiceQueuedPrompt(candidate);
|
|
519
|
+
void stopLiveVoice();
|
|
520
|
+
}, VOICE_SILENCE_MS);
|
|
460
521
|
},
|
|
461
522
|
onFinal: (text) => {
|
|
523
|
+
if (voiceSilenceTimerRef.current) {
|
|
524
|
+
clearTimeout(voiceSilenceTimerRef.current);
|
|
525
|
+
voiceSilenceTimerRef.current = null;
|
|
526
|
+
}
|
|
462
527
|
setPrompt(text);
|
|
528
|
+
liveVoiceRef.current = null;
|
|
529
|
+
setVoiceListening(false);
|
|
530
|
+
setVoiceWaitingForSilence(false);
|
|
463
531
|
},
|
|
464
532
|
onError: (error) => {
|
|
533
|
+
setVoiceWaitingForSilence(false);
|
|
465
534
|
pushMessage("error", `Voice transcription error: ${error.message}`);
|
|
466
535
|
}
|
|
467
536
|
});
|
|
468
|
-
}, [pushMessage]);
|
|
537
|
+
}, [pushMessage, stopLiveVoice]);
|
|
469
538
|
useEffect(() => {
|
|
470
539
|
return () => {
|
|
540
|
+
if (voiceSilenceTimerRef.current) {
|
|
541
|
+
clearTimeout(voiceSilenceTimerRef.current);
|
|
542
|
+
voiceSilenceTimerRef.current = null;
|
|
543
|
+
}
|
|
471
544
|
const controller = liveVoiceRef.current;
|
|
472
545
|
if (controller) {
|
|
473
546
|
void controller.stop();
|
|
474
547
|
}
|
|
475
548
|
};
|
|
476
549
|
}, []);
|
|
550
|
+
useEffect(() => {
|
|
551
|
+
if (!voiceEnabled || !user || thinking || voiceListening || liveVoiceRef.current) {
|
|
552
|
+
return;
|
|
553
|
+
}
|
|
554
|
+
startLiveVoice(false);
|
|
555
|
+
}, [startLiveVoice, thinking, user, voiceEnabled, voiceListening]);
|
|
477
556
|
useEffect(() => {
|
|
478
557
|
if (!trustedWorkspace) {
|
|
479
558
|
return;
|
|
@@ -490,6 +569,7 @@ export const AstraApp = () => {
|
|
|
490
569
|
}
|
|
491
570
|
const cached = loadSession();
|
|
492
571
|
if (!cached) {
|
|
572
|
+
backend.setAuthSession(null);
|
|
493
573
|
if (!cancelled) {
|
|
494
574
|
setBooting(false);
|
|
495
575
|
}
|
|
@@ -498,9 +578,13 @@ export const AstraApp = () => {
|
|
|
498
578
|
const valid = await backend.validateSession(cached);
|
|
499
579
|
if (!valid) {
|
|
500
580
|
clearSession();
|
|
581
|
+
backend.setAuthSession(null);
|
|
501
582
|
}
|
|
502
583
|
else if (!cancelled) {
|
|
503
|
-
|
|
584
|
+
const hydrated = await backend.getUserProfile(cached).catch(() => cached);
|
|
585
|
+
saveSession(hydrated);
|
|
586
|
+
backend.setAuthSession(hydrated);
|
|
587
|
+
setUser(hydrated);
|
|
504
588
|
setMessages([
|
|
505
589
|
{ kind: "system", text: "Welcome back. Type a message or /help." },
|
|
506
590
|
{ kind: "system", text: "" }
|
|
@@ -662,8 +746,10 @@ export const AstraApp = () => {
|
|
|
662
746
|
throw new Error(data.error);
|
|
663
747
|
}
|
|
664
748
|
const authSession = data;
|
|
665
|
-
|
|
666
|
-
|
|
749
|
+
const hydrated = await backend.getUserProfile(authSession).catch(() => authSession);
|
|
750
|
+
saveSession(hydrated);
|
|
751
|
+
backend.setAuthSession(hydrated);
|
|
752
|
+
setUser(hydrated);
|
|
667
753
|
setEmail("");
|
|
668
754
|
setPassword("");
|
|
669
755
|
setMessages([
|
|
@@ -679,8 +765,42 @@ export const AstraApp = () => {
|
|
|
679
765
|
}
|
|
680
766
|
}, [backend, email, loginMode, password]);
|
|
681
767
|
const handleEvent = useCallback(async (event, activeSessionId) => {
|
|
768
|
+
if (event.type === "file_edit_path") {
|
|
769
|
+
const data = (event.data ?? {});
|
|
770
|
+
const toolId = typeof data.tool_id === "string" ? data.tool_id : "";
|
|
771
|
+
const path = typeof data.path === "string" ? data.path : "(unknown)";
|
|
772
|
+
if (toolId) {
|
|
773
|
+
fileEditBuffersRef.current.set(toolId, { path, chunks: [] });
|
|
774
|
+
}
|
|
775
|
+
if (!isSuperAdmin) {
|
|
776
|
+
pushToolCard({
|
|
777
|
+
kind: "fileEdit",
|
|
778
|
+
toolName: "edit_file",
|
|
779
|
+
locality: "LOCAL",
|
|
780
|
+
summary: `Editing ${path}`,
|
|
781
|
+
path
|
|
782
|
+
});
|
|
783
|
+
}
|
|
784
|
+
return null;
|
|
785
|
+
}
|
|
786
|
+
if (event.type === "file_edit_delta") {
|
|
787
|
+
const data = (event.data ?? {});
|
|
788
|
+
const toolId = typeof data.tool_id === "string" ? data.tool_id : "";
|
|
789
|
+
const chunk = typeof event.content === "string" ? event.content : "";
|
|
790
|
+
if (toolId && chunk) {
|
|
791
|
+
const current = fileEditBuffersRef.current.get(toolId) ?? { path: "(unknown)", chunks: [] };
|
|
792
|
+
current.chunks.push(chunk);
|
|
793
|
+
fileEditBuffersRef.current.set(toolId, current);
|
|
794
|
+
}
|
|
795
|
+
if (isSuperAdmin) {
|
|
796
|
+
const raw = JSON.stringify(event);
|
|
797
|
+
pushMessage("tool", `event ${event.type}: ${raw.slice(0, 220)}`);
|
|
798
|
+
}
|
|
799
|
+
return null;
|
|
800
|
+
}
|
|
682
801
|
const assistantPiece = extractAssistantText(event);
|
|
683
|
-
if (assistantPiece &&
|
|
802
|
+
if (assistantPiece &&
|
|
803
|
+
!["tool_result", "error", "credits_update", "credits_exhausted", "file_edit_delta", "file_edit_path"].includes(event.type)) {
|
|
684
804
|
return assistantPiece;
|
|
685
805
|
}
|
|
686
806
|
if (event.type === "run_in_terminal") {
|
|
@@ -698,7 +818,17 @@ export const AstraApp = () => {
|
|
|
698
818
|
cancelled: result.cancelled
|
|
699
819
|
});
|
|
700
820
|
}
|
|
701
|
-
|
|
821
|
+
if (isSuperAdmin) {
|
|
822
|
+
pushMessage("tool", `[LOCAL] ▸ ran: ${command.slice(0, 60)}...`);
|
|
823
|
+
}
|
|
824
|
+
else {
|
|
825
|
+
pushToolCard({
|
|
826
|
+
kind: "terminal",
|
|
827
|
+
toolName: "run_in_terminal",
|
|
828
|
+
locality: "LOCAL",
|
|
829
|
+
summary: `Running command: ${command.slice(0, 80)}`
|
|
830
|
+
});
|
|
831
|
+
}
|
|
702
832
|
return null;
|
|
703
833
|
}
|
|
704
834
|
// Apply file operations to the local workspace immediately.
|
|
@@ -707,11 +837,25 @@ export const AstraApp = () => {
|
|
|
707
837
|
const resultType = event.result_type;
|
|
708
838
|
const relPath = typeof d.path === "string" ? d.path : null;
|
|
709
839
|
const lang = typeof d.language === "string" ? d.language : "plaintext";
|
|
840
|
+
const locality = d.local === true ? "LOCAL" : "REMOTE";
|
|
710
841
|
if (relPath) {
|
|
711
842
|
if (resultType === "file_create") {
|
|
712
843
|
const content = typeof d.content === "string" ? d.content : "";
|
|
713
844
|
writeLocalFile(relPath, content, lang);
|
|
714
|
-
|
|
845
|
+
const snippetLines = extractSnippetLines(content);
|
|
846
|
+
if (isSuperAdmin) {
|
|
847
|
+
pushMessage("tool", `[LOCAL] ✓ wrote ${relPath}`);
|
|
848
|
+
}
|
|
849
|
+
else {
|
|
850
|
+
pushToolCard({
|
|
851
|
+
kind: "fileCreate",
|
|
852
|
+
toolName: "create_file",
|
|
853
|
+
locality,
|
|
854
|
+
summary: `Created ${relPath}`,
|
|
855
|
+
path: relPath,
|
|
856
|
+
snippetLines
|
|
857
|
+
});
|
|
858
|
+
}
|
|
715
859
|
}
|
|
716
860
|
else if (resultType === "file_edit") {
|
|
717
861
|
const content = typeof d.full_new_content === "string"
|
|
@@ -721,14 +865,58 @@ export const AstraApp = () => {
|
|
|
721
865
|
: null;
|
|
722
866
|
if (content !== null) {
|
|
723
867
|
writeLocalFile(relPath, content, lang);
|
|
724
|
-
|
|
868
|
+
const snippetLines = extractDiffSnippet(d.diff) ?? extractSnippetLines(content);
|
|
869
|
+
if (isSuperAdmin) {
|
|
870
|
+
pushMessage("tool", `[LOCAL] ✓ edited ${relPath}`);
|
|
871
|
+
}
|
|
872
|
+
else {
|
|
873
|
+
const toolId = typeof event.tool_call_id === "string" ? event.tool_call_id : "";
|
|
874
|
+
const streamed = toolId ? fileEditBuffersRef.current.get(toolId) : undefined;
|
|
875
|
+
const streamedSnippet = streamed ? extractSnippetLines(streamed.chunks.join("")) : [];
|
|
876
|
+
pushToolCard({
|
|
877
|
+
kind: "fileEdit",
|
|
878
|
+
toolName: "edit_file",
|
|
879
|
+
locality,
|
|
880
|
+
summary: `Updated ${relPath}`,
|
|
881
|
+
path: relPath,
|
|
882
|
+
snippetLines: streamedSnippet.length > 0 ? streamedSnippet : snippetLines
|
|
883
|
+
});
|
|
884
|
+
if (toolId) {
|
|
885
|
+
fileEditBuffersRef.current.delete(toolId);
|
|
886
|
+
}
|
|
887
|
+
}
|
|
725
888
|
}
|
|
726
889
|
}
|
|
727
890
|
else if (resultType === "file_delete") {
|
|
728
891
|
deleteLocalFile(relPath);
|
|
729
|
-
|
|
892
|
+
if (isSuperAdmin) {
|
|
893
|
+
pushMessage("tool", `[LOCAL] ✓ deleted ${relPath}`);
|
|
894
|
+
}
|
|
895
|
+
else {
|
|
896
|
+
pushToolCard({
|
|
897
|
+
kind: "fileDelete",
|
|
898
|
+
toolName: "delete_file",
|
|
899
|
+
locality,
|
|
900
|
+
summary: `Deleted ${relPath}`,
|
|
901
|
+
path: relPath
|
|
902
|
+
});
|
|
903
|
+
}
|
|
730
904
|
}
|
|
731
905
|
}
|
|
906
|
+
if (!isSuperAdmin && event.tool_name === "start_preview") {
|
|
907
|
+
const message = typeof d.message === "string"
|
|
908
|
+
? d.message
|
|
909
|
+
: typeof d.preview_url === "string"
|
|
910
|
+
? `Preview: ${d.preview_url}`
|
|
911
|
+
: "Local preview started.";
|
|
912
|
+
pushToolCard({
|
|
913
|
+
kind: "preview",
|
|
914
|
+
toolName: "start_preview",
|
|
915
|
+
locality,
|
|
916
|
+
summary: message
|
|
917
|
+
});
|
|
918
|
+
return null;
|
|
919
|
+
}
|
|
732
920
|
}
|
|
733
921
|
if (event.type === "credits_update") {
|
|
734
922
|
const remaining = Number(event.remaining ?? 0);
|
|
@@ -744,33 +932,71 @@ export const AstraApp = () => {
|
|
|
744
932
|
if (event.type === "credits_exhausted") {
|
|
745
933
|
setCreditsRemaining(0);
|
|
746
934
|
}
|
|
935
|
+
if (!isSuperAdmin && NOISY_EVENT_TYPES.has(event.type)) {
|
|
936
|
+
return null;
|
|
937
|
+
}
|
|
938
|
+
if (!isSuperAdmin && event.type === "tool_start") {
|
|
939
|
+
const tool = event.tool;
|
|
940
|
+
const name = tool?.name ?? "tool";
|
|
941
|
+
pushToolCard({
|
|
942
|
+
kind: "start",
|
|
943
|
+
toolName: name,
|
|
944
|
+
locality: "REMOTE",
|
|
945
|
+
summary: `${name} is running...`
|
|
946
|
+
});
|
|
947
|
+
return null;
|
|
948
|
+
}
|
|
747
949
|
const toolLine = eventToToolLine(event);
|
|
748
950
|
if (toolLine) {
|
|
749
951
|
if (event.type === "error" || event.type === "credits_exhausted") {
|
|
750
952
|
pushMessage("error", toolLine);
|
|
751
953
|
}
|
|
752
954
|
else {
|
|
753
|
-
|
|
955
|
+
if (isSuperAdmin) {
|
|
956
|
+
pushMessage("tool", toolLine);
|
|
957
|
+
}
|
|
958
|
+
else if (event.type === "tool_result") {
|
|
959
|
+
const mark = event.success ? "completed" : "failed";
|
|
960
|
+
const toolName = typeof event.tool_name === "string" ? event.tool_name : "tool";
|
|
961
|
+
pushToolCard({
|
|
962
|
+
kind: event.success ? "success" : "error",
|
|
963
|
+
toolName,
|
|
964
|
+
locality: (event.data ?? {}).local === true ? "LOCAL" : "REMOTE",
|
|
965
|
+
summary: `${toolName} ${mark}`
|
|
966
|
+
});
|
|
967
|
+
}
|
|
754
968
|
}
|
|
755
969
|
}
|
|
756
970
|
else if (event.type !== "thinking") {
|
|
757
|
-
|
|
758
|
-
|
|
971
|
+
if (isSuperAdmin) {
|
|
972
|
+
const raw = JSON.stringify(event);
|
|
973
|
+
pushMessage("tool", `event ${event.type}: ${raw.slice(0, 220)}`);
|
|
974
|
+
}
|
|
759
975
|
}
|
|
760
976
|
return null;
|
|
761
|
-
}, [backend, deleteLocalFile, pushMessage, writeLocalFile]);
|
|
977
|
+
}, [backend, deleteLocalFile, isSuperAdmin, pushMessage, pushToolCard, writeLocalFile]);
|
|
762
978
|
const sendPrompt = useCallback(async (rawPrompt) => {
|
|
763
979
|
const text = rawPrompt.trim();
|
|
764
980
|
if (!text || !user || thinking) {
|
|
765
981
|
return;
|
|
766
982
|
}
|
|
767
983
|
if (text === "/help") {
|
|
768
|
-
pushMessage("system", "/new /history /voice on|off|status
|
|
984
|
+
pushMessage("system", "/new /history /voice /voice on|off|status /settings /settings model <id> /logout /exit");
|
|
769
985
|
pushMessage("system", "");
|
|
770
986
|
return;
|
|
771
987
|
}
|
|
772
988
|
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()}`);
|
|
989
|
+
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()}`);
|
|
990
|
+
pushMessage("system", "");
|
|
991
|
+
return;
|
|
992
|
+
}
|
|
993
|
+
if (text === "/voice") {
|
|
994
|
+
if (!voiceEnabled) {
|
|
995
|
+
setVoiceEnabled(true);
|
|
996
|
+
startLiveVoice(true);
|
|
997
|
+
return;
|
|
998
|
+
}
|
|
999
|
+
pushMessage("system", `Voice input is on${voiceListening ? " (currently listening)" : ""}. Use /voice off to disable.`);
|
|
774
1000
|
pushMessage("system", "");
|
|
775
1001
|
return;
|
|
776
1002
|
}
|
|
@@ -792,50 +1018,31 @@ export const AstraApp = () => {
|
|
|
792
1018
|
return;
|
|
793
1019
|
}
|
|
794
1020
|
if (text === "/voice status") {
|
|
795
|
-
pushMessage("system", `Voice
|
|
1021
|
+
pushMessage("system", `Voice input is ${voiceEnabled ? "on" : "off"}${voiceListening ? " (live transcription active)" : ""}${voiceWaitingForSilence ? " (waiting for silence to auto-send)" : ""}.`);
|
|
796
1022
|
pushMessage("system", "");
|
|
797
1023
|
return;
|
|
798
1024
|
}
|
|
799
1025
|
if (text === "/voice on") {
|
|
800
1026
|
setVoiceEnabled(true);
|
|
801
|
-
|
|
1027
|
+
startLiveVoice(true);
|
|
1028
|
+
pushMessage("system", `Voice input enabled. Auto-send after ${Math.round(VOICE_SILENCE_MS / 1000)}s silence.`);
|
|
802
1029
|
pushMessage("system", "");
|
|
803
1030
|
return;
|
|
804
1031
|
}
|
|
805
1032
|
if (text === "/voice off") {
|
|
806
1033
|
await stopLiveVoice();
|
|
807
1034
|
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.");
|
|
1035
|
+
pushMessage("system", "Voice input disabled.");
|
|
827
1036
|
pushMessage("system", "");
|
|
828
1037
|
return;
|
|
829
1038
|
}
|
|
830
1039
|
if (text === "/voice input") {
|
|
831
1040
|
const transcribed = await transcribeOnce();
|
|
832
1041
|
if (!transcribed) {
|
|
833
|
-
pushMessage("error", "No speech transcribed.
|
|
1042
|
+
pushMessage("error", "No speech transcribed. Check OPENAI_API_KEY and mic capture command (optional ASTRA_STT_CAPTURE_COMMAND).");
|
|
834
1043
|
return;
|
|
835
1044
|
}
|
|
836
|
-
|
|
837
|
-
setPrompt(transcribed);
|
|
838
|
-
pushMessage("system", "Transcribed input ready. Press Enter to send.");
|
|
1045
|
+
setVoiceQueuedPrompt(transcribed.trim());
|
|
839
1046
|
return;
|
|
840
1047
|
}
|
|
841
1048
|
if (text === "/new") {
|
|
@@ -849,6 +1056,7 @@ export const AstraApp = () => {
|
|
|
849
1056
|
if (text === "/logout") {
|
|
850
1057
|
await stopLiveVoice();
|
|
851
1058
|
clearSession();
|
|
1059
|
+
backend.setAuthSession(null);
|
|
852
1060
|
setUser(null);
|
|
853
1061
|
setMessages([]);
|
|
854
1062
|
setChatMessages([]);
|
|
@@ -911,9 +1119,6 @@ export const AstraApp = () => {
|
|
|
911
1119
|
? `Remote result (not yet confirmed as local filesystem change): ${cleanedAssistant}`
|
|
912
1120
|
: cleanedAssistant;
|
|
913
1121
|
pushMessage("assistant", guardedAssistant);
|
|
914
|
-
if (voiceEnabled) {
|
|
915
|
-
speakText(guardedAssistant);
|
|
916
|
-
}
|
|
917
1122
|
setChatMessages((prev) => [...prev, { role: "assistant", content: cleanedAssistant }]);
|
|
918
1123
|
}
|
|
919
1124
|
else {
|
|
@@ -943,8 +1148,22 @@ export const AstraApp = () => {
|
|
|
943
1148
|
user,
|
|
944
1149
|
voiceEnabled,
|
|
945
1150
|
voiceListening,
|
|
1151
|
+
voiceWaitingForSilence,
|
|
946
1152
|
workspaceRoot
|
|
947
1153
|
]);
|
|
1154
|
+
useEffect(() => {
|
|
1155
|
+
if (!voiceQueuedPrompt || !user || thinking) {
|
|
1156
|
+
return;
|
|
1157
|
+
}
|
|
1158
|
+
const queued = voiceQueuedPrompt.trim();
|
|
1159
|
+
setVoiceQueuedPrompt(null);
|
|
1160
|
+
if (!queued) {
|
|
1161
|
+
return;
|
|
1162
|
+
}
|
|
1163
|
+
pushMessage("tool", `[LOCAL] 🎙 ${queued}`);
|
|
1164
|
+
setPrompt("");
|
|
1165
|
+
void sendPrompt(queued);
|
|
1166
|
+
}, [pushMessage, sendPrompt, thinking, user, voiceQueuedPrompt]);
|
|
948
1167
|
if (!trustedWorkspace) {
|
|
949
1168
|
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
1169
|
}
|
|
@@ -969,17 +1188,51 @@ export const AstraApp = () => {
|
|
|
969
1188
|
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
1189
|
}) })), _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
1190
|
}
|
|
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
|
|
1191
|
+
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
1192
|
const style = styleForKind(message.kind);
|
|
974
1193
|
const paddedLabel = style.label.padEnd(LABEL_WIDTH, " ");
|
|
975
1194
|
const isSpacing = message.text === "" && message.kind === "system";
|
|
976
1195
|
if (isSpacing) {
|
|
977
1196
|
return _jsx(Box, { marginTop: 0, children: _jsx(Text, { children: " " }) }, `${index}-spacer`);
|
|
978
1197
|
}
|
|
1198
|
+
if (message.kind === "tool" && message.card && !isSuperAdmin) {
|
|
1199
|
+
const card = message.card;
|
|
1200
|
+
const icon = card.kind === "error"
|
|
1201
|
+
? "✕"
|
|
1202
|
+
: card.kind === "fileDelete"
|
|
1203
|
+
? "🗑"
|
|
1204
|
+
: card.kind === "fileCreate"
|
|
1205
|
+
? "+"
|
|
1206
|
+
: card.kind === "fileEdit"
|
|
1207
|
+
? "✎"
|
|
1208
|
+
: card.kind === "preview"
|
|
1209
|
+
? "◉"
|
|
1210
|
+
: card.kind === "terminal"
|
|
1211
|
+
? "⌘"
|
|
1212
|
+
: card.kind === "start"
|
|
1213
|
+
? "…"
|
|
1214
|
+
: "✓";
|
|
1215
|
+
const accent = card.kind === "error"
|
|
1216
|
+
? "#ff8d8d"
|
|
1217
|
+
: card.kind === "fileDelete"
|
|
1218
|
+
? "#8a96a6"
|
|
1219
|
+
: card.kind === "fileEdit"
|
|
1220
|
+
? "#b7d4ff"
|
|
1221
|
+
: card.kind === "fileCreate"
|
|
1222
|
+
? "#a5e9c5"
|
|
1223
|
+
: card.kind === "preview"
|
|
1224
|
+
? "#9ad5ff"
|
|
1225
|
+
: "#9bc5ff";
|
|
1226
|
+
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}`));
|
|
1227
|
+
}
|
|
979
1228
|
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,
|
|
1229
|
+
}), 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
1230
|
setPrompt("");
|
|
982
1231
|
void sendPrompt(value);
|
|
983
|
-
},
|
|
1232
|
+
}, onChange: (value) => {
|
|
1233
|
+
if (!voiceListening) {
|
|
1234
|
+
setPrompt(value);
|
|
1235
|
+
}
|
|
1236
|
+
}, placeholder: voiceEnabled ? "Ask Astra... (voice on: auto listen + send on silence)" : "Ask Astra..." })] })] }));
|
|
984
1237
|
};
|
|
985
1238
|
//# sourceMappingURL=App.js.map
|