@astra-code/astra-ai 0.1.4 → 0.1.6
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/dist/app/App.js +154 -60
- package/dist/app/App.js.map +1 -1
- package/dist/lib/backendClient.js +18 -0
- package/dist/lib/backendClient.js.map +1 -1
- package/dist/lib/voice.js +111 -2
- package/dist/lib/voice.js.map +1 -1
- package/package.json +1 -1
- package/src/app/App.tsx +122 -26
- package/src/lib/backendClient.ts +18 -0
- package/src/lib/voice.ts +115 -2
package/dist/app/App.js
CHANGED
|
@@ -4,7 +4,7 @@ import { Box, Text, useApp, useInput } from "ink";
|
|
|
4
4
|
import Spinner from "ink-spinner";
|
|
5
5
|
import TextInput from "ink-text-input";
|
|
6
6
|
import { spawn } from "child_process";
|
|
7
|
-
import { mkdirSync, unlinkSync, writeFileSync } from "fs";
|
|
7
|
+
import { existsSync, mkdirSync, unlinkSync, writeFileSync } from "fs";
|
|
8
8
|
import { dirname, join } from "path";
|
|
9
9
|
import { BackendClient } from "../lib/backendClient.js";
|
|
10
10
|
import { clearSession, loadSession, saveSession } from "../lib/sessionStore.js";
|
|
@@ -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 { startLiveTranscription, transcribeOnce } from "../lib/voice.js";
|
|
15
|
+
import { startLiveTranscription, transcribeOnce, resolveAudioDevice, setAudioDevice, listAvfAudioDevices, writeAstraKey } from "../lib/voice.js";
|
|
16
16
|
// const ASTRA_ASCII = `
|
|
17
17
|
// █████╗ ███████╗████████╗██████╗ █████╗ ██████╗ ██████╗ ██████╗ ███████╗
|
|
18
18
|
// ██╔══██╗██╔════╝╚══██╔══╝██╔══██╗██╔══██╗ ██╔════╝██╔═══██╗██╔══██╗██╔════╝
|
|
@@ -249,6 +249,24 @@ const summarizeToolResult = (toolName, data, success) => {
|
|
|
249
249
|
}
|
|
250
250
|
return `${toolName} completed`;
|
|
251
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;
|
|
269
|
+
};
|
|
252
270
|
const parseInline = (line) => {
|
|
253
271
|
const tokens = [];
|
|
254
272
|
const re = /(`[^`]+`|\*\*[^*]+\*\*|__[^_]+__|\*[^*\n]+\*|_[^_\n]+_)/g;
|
|
@@ -474,6 +492,7 @@ export const AstraApp = () => {
|
|
|
474
492
|
const [voicePreparing, setVoicePreparing] = useState(false);
|
|
475
493
|
const [voiceWaitingForSilence, setVoiceWaitingForSilence] = useState(false);
|
|
476
494
|
const [voiceQueuedPrompt, setVoiceQueuedPrompt] = useState(null);
|
|
495
|
+
const [micSetupDevices, setMicSetupDevices] = useState(null);
|
|
477
496
|
const [toolFeedMode, setToolFeedMode] = useState("compact");
|
|
478
497
|
const [historyOpen, setHistoryOpen] = useState(false);
|
|
479
498
|
const [historyMode, setHistoryMode] = useState("picker");
|
|
@@ -596,57 +615,96 @@ export const AstraApp = () => {
|
|
|
596
615
|
if (liveVoiceRef.current) {
|
|
597
616
|
return;
|
|
598
617
|
}
|
|
618
|
+
// #region agent log
|
|
619
|
+
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(() => { });
|
|
620
|
+
// #endregion
|
|
599
621
|
setVoiceEnabled(true);
|
|
600
622
|
setVoicePreparing(true);
|
|
601
623
|
setVoiceListening(false);
|
|
602
624
|
setVoiceWaitingForSilence(false);
|
|
603
625
|
if (announce) {
|
|
604
|
-
pushMessage("system", "
|
|
626
|
+
pushMessage("system", "Voice input armed. Preparing microphone...");
|
|
605
627
|
}
|
|
606
|
-
|
|
607
|
-
|
|
628
|
+
// Resolve mic device before starting — triggers onboarding if not configured.
|
|
629
|
+
void resolveAudioDevice(workspaceRoot).then((device) => {
|
|
630
|
+
if (device === null) {
|
|
631
|
+
// No device configured — run onboarding inline.
|
|
608
632
|
setVoicePreparing(false);
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
}
|
|
614
|
-
const candidate = text.trim();
|
|
615
|
-
if (!candidate) {
|
|
616
|
-
setVoiceWaitingForSilence(false);
|
|
633
|
+
const devices = listAvfAudioDevices();
|
|
634
|
+
if (!devices.length) {
|
|
635
|
+
pushMessage("error", "No audio devices found. Install ffmpeg: brew install ffmpeg");
|
|
636
|
+
setVoiceEnabled(false);
|
|
617
637
|
return;
|
|
618
638
|
}
|
|
619
|
-
|
|
620
|
-
const
|
|
621
|
-
|
|
639
|
+
setMicSetupDevices(devices);
|
|
640
|
+
const lines = [
|
|
641
|
+
"Let's set up your microphone first.",
|
|
642
|
+
...devices.map(d => ` [${d.index}] ${d.name}`),
|
|
643
|
+
"Type the number for your mic and press Enter:"
|
|
644
|
+
];
|
|
645
|
+
pushMessage("system", lines.join("\n"));
|
|
646
|
+
return;
|
|
647
|
+
}
|
|
648
|
+
// Device resolved — start transcription.
|
|
649
|
+
liveVoiceRef.current = startLiveTranscription({
|
|
650
|
+
onPartial: (text) => {
|
|
651
|
+
setVoicePreparing(false);
|
|
652
|
+
setVoiceListening(true);
|
|
653
|
+
if (voiceSilenceTimerRef.current) {
|
|
654
|
+
clearTimeout(voiceSilenceTimerRef.current);
|
|
655
|
+
}
|
|
656
|
+
const candidate = text.trim();
|
|
657
|
+
if (!candidate) {
|
|
658
|
+
setPrompt("");
|
|
659
|
+
setVoiceWaitingForSilence(false);
|
|
660
|
+
return;
|
|
661
|
+
}
|
|
662
|
+
const normalized = candidate.toLowerCase();
|
|
663
|
+
const isLikelyNoise = isLikelyVoiceNoise(normalized);
|
|
664
|
+
// #region agent log
|
|
665
|
+
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(() => { });
|
|
666
|
+
// #endregion
|
|
667
|
+
if (isLikelyNoise) {
|
|
668
|
+
setPrompt("");
|
|
669
|
+
setVoiceWaitingForSilence(false);
|
|
670
|
+
return;
|
|
671
|
+
}
|
|
672
|
+
setPrompt(text);
|
|
673
|
+
setVoiceWaitingForSilence(true);
|
|
674
|
+
voiceSilenceTimerRef.current = setTimeout(() => {
|
|
675
|
+
// #region agent log
|
|
676
|
+
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(() => { });
|
|
677
|
+
// #endregion
|
|
678
|
+
setVoiceQueuedPrompt(candidate);
|
|
679
|
+
void stopLiveVoice();
|
|
680
|
+
}, VOICE_SILENCE_MS);
|
|
681
|
+
},
|
|
682
|
+
onFinal: (text) => {
|
|
683
|
+
if (voiceSilenceTimerRef.current) {
|
|
684
|
+
clearTimeout(voiceSilenceTimerRef.current);
|
|
685
|
+
voiceSilenceTimerRef.current = null;
|
|
686
|
+
}
|
|
687
|
+
setPrompt(text);
|
|
688
|
+
liveVoiceRef.current = null;
|
|
689
|
+
setVoicePreparing(false);
|
|
690
|
+
setVoiceListening(false);
|
|
622
691
|
setVoiceWaitingForSilence(false);
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
692
|
+
// #region agent log
|
|
693
|
+
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(() => { });
|
|
694
|
+
// #endregion
|
|
695
|
+
},
|
|
696
|
+
onError: (error) => {
|
|
697
|
+
setVoicePreparing(false);
|
|
698
|
+
setVoiceListening(false);
|
|
699
|
+
setVoiceWaitingForSilence(false);
|
|
700
|
+
pushMessage("error", `Voice transcription error: ${error.message}`);
|
|
701
|
+
// #region agent log
|
|
702
|
+
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(() => { });
|
|
703
|
+
// #endregion
|
|
635
704
|
}
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
setVoiceListening(false);
|
|
640
|
-
setVoiceWaitingForSilence(false);
|
|
641
|
-
},
|
|
642
|
-
onError: (error) => {
|
|
643
|
-
setVoicePreparing(false);
|
|
644
|
-
setVoiceListening(false);
|
|
645
|
-
setVoiceWaitingForSilence(false);
|
|
646
|
-
pushMessage("error", `Voice transcription error: ${error.message}`);
|
|
647
|
-
}
|
|
648
|
-
});
|
|
649
|
-
}, [pushMessage, stopLiveVoice]);
|
|
705
|
+
});
|
|
706
|
+
}); // end resolveAudioDevice.then
|
|
707
|
+
}, [pushMessage, stopLiveVoice, workspaceRoot]);
|
|
650
708
|
useEffect(() => {
|
|
651
709
|
return () => {
|
|
652
710
|
if (voiceSilenceTimerRef.current) {
|
|
@@ -728,6 +786,13 @@ export const AstraApp = () => {
|
|
|
728
786
|
if (key.return) {
|
|
729
787
|
if (trustSelection === 0) {
|
|
730
788
|
trustWorkspace(workspaceRoot);
|
|
789
|
+
// Create .astra settings file at workspace root if it doesn't exist yet.
|
|
790
|
+
try {
|
|
791
|
+
const astraPath = join(workspaceRoot, ".astra");
|
|
792
|
+
if (!existsSync(astraPath))
|
|
793
|
+
writeFileSync(astraPath, "");
|
|
794
|
+
}
|
|
795
|
+
catch { /* non-fatal */ }
|
|
731
796
|
setTrustedWorkspace(true);
|
|
732
797
|
setBooting(true);
|
|
733
798
|
return;
|
|
@@ -1107,7 +1172,7 @@ export const AstraApp = () => {
|
|
|
1107
1172
|
if (voiceEnabled) {
|
|
1108
1173
|
await stopLiveVoice();
|
|
1109
1174
|
setVoiceEnabled(false);
|
|
1110
|
-
pushMessage("system", "
|
|
1175
|
+
pushMessage("system", "Voice input paused: credits exhausted. Recharge, then run /voice on.");
|
|
1111
1176
|
pushMessage("system", "");
|
|
1112
1177
|
}
|
|
1113
1178
|
}
|
|
@@ -1171,8 +1236,29 @@ export const AstraApp = () => {
|
|
|
1171
1236
|
if (!text || !user || thinking) {
|
|
1172
1237
|
return;
|
|
1173
1238
|
}
|
|
1239
|
+
// Mic onboarding: intercept when waiting for device selection.
|
|
1240
|
+
if (micSetupDevices !== null) {
|
|
1241
|
+
const idx = parseInt(text, 10);
|
|
1242
|
+
const valid = !isNaN(idx) && idx >= 0 && micSetupDevices.some(d => d.index === idx);
|
|
1243
|
+
if (!valid) {
|
|
1244
|
+
pushMessage("error", `Please type one of: ${micSetupDevices.map(d => d.index).join(", ")}`);
|
|
1245
|
+
return;
|
|
1246
|
+
}
|
|
1247
|
+
const device = `:${idx}`;
|
|
1248
|
+
// Write to .astra local cache
|
|
1249
|
+
writeAstraKey(workspaceRoot, "ASTRA_STT_DEVICE", device);
|
|
1250
|
+
// Persist to backend
|
|
1251
|
+
void backend.updateCliSettings({ audio_device_index: idx });
|
|
1252
|
+
// Update in-process cache
|
|
1253
|
+
setAudioDevice(device);
|
|
1254
|
+
setMicSetupDevices(null);
|
|
1255
|
+
pushMessage("system", `Mic set to [${idx}] ${micSetupDevices.find(d => d.index === idx)?.name ?? ""}. Starting voice...`);
|
|
1256
|
+
pushMessage("system", "");
|
|
1257
|
+
startLiveVoice(false);
|
|
1258
|
+
return;
|
|
1259
|
+
}
|
|
1174
1260
|
if (text === "/help") {
|
|
1175
|
-
pushMessage("system", "/new /history /
|
|
1261
|
+
pushMessage("system", "/new /history /voice /dictate on|off|status /tools compact|expanded /settings /settings model <id> /logout /exit");
|
|
1176
1262
|
pushMessage("system", "");
|
|
1177
1263
|
return;
|
|
1178
1264
|
}
|
|
@@ -1189,17 +1275,17 @@ export const AstraApp = () => {
|
|
|
1189
1275
|
return;
|
|
1190
1276
|
}
|
|
1191
1277
|
if (text === "/settings") {
|
|
1192
|
-
pushMessage("system", `Settings: mode=${runtimeMode} scope=${workspaceRoot} model=${activeModel} provider=${getProviderForModel(activeModel)}
|
|
1278
|
+
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()}`);
|
|
1193
1279
|
pushMessage("system", "");
|
|
1194
1280
|
return;
|
|
1195
1281
|
}
|
|
1196
|
-
if (text === "/
|
|
1282
|
+
if (text === "/voice") {
|
|
1197
1283
|
if (!voiceEnabled) {
|
|
1198
1284
|
setVoiceEnabled(true);
|
|
1199
1285
|
startLiveVoice(true);
|
|
1200
1286
|
return;
|
|
1201
1287
|
}
|
|
1202
|
-
pushMessage("system", `
|
|
1288
|
+
pushMessage("system", `Voice input is on${voiceListening ? " (currently listening)" : ""}. Use /voice off to disable.`);
|
|
1203
1289
|
pushMessage("system", "");
|
|
1204
1290
|
return;
|
|
1205
1291
|
}
|
|
@@ -1220,26 +1306,26 @@ export const AstraApp = () => {
|
|
|
1220
1306
|
await openHistory();
|
|
1221
1307
|
return;
|
|
1222
1308
|
}
|
|
1223
|
-
if (text === "/dictate status"
|
|
1224
|
-
pushMessage("system", `
|
|
1309
|
+
if (text === "/dictate status") {
|
|
1310
|
+
pushMessage("system", `Voice input is ${voiceEnabled ? "on" : "off"}${voicePreparing ? " (preparing mic)" : ""}${voiceListening ? " (listening)" : ""}${voiceWaitingForSilence ? " (waiting for silence to auto-send)" : ""}.`);
|
|
1225
1311
|
pushMessage("system", "");
|
|
1226
1312
|
return;
|
|
1227
1313
|
}
|
|
1228
|
-
if (text === "/dictate on"
|
|
1314
|
+
if (text === "/dictate on") {
|
|
1229
1315
|
setVoiceEnabled(true);
|
|
1230
1316
|
startLiveVoice(true);
|
|
1231
|
-
pushMessage("system", `
|
|
1317
|
+
pushMessage("system", `Voice input enabled. Auto-send after ${Math.round(VOICE_SILENCE_MS / 1000)}s silence.`);
|
|
1232
1318
|
pushMessage("system", "");
|
|
1233
1319
|
return;
|
|
1234
1320
|
}
|
|
1235
|
-
if (text === "/dictate off"
|
|
1321
|
+
if (text === "/dictate off") {
|
|
1236
1322
|
await stopLiveVoice();
|
|
1237
1323
|
setVoiceEnabled(false);
|
|
1238
|
-
pushMessage("system", "
|
|
1324
|
+
pushMessage("system", "Voice input disabled.");
|
|
1239
1325
|
pushMessage("system", "");
|
|
1240
1326
|
return;
|
|
1241
1327
|
}
|
|
1242
|
-
if (text === "/dictate input"
|
|
1328
|
+
if (text === "/dictate input") {
|
|
1243
1329
|
const transcribed = await transcribeOnce();
|
|
1244
1330
|
if (!transcribed) {
|
|
1245
1331
|
pushMessage("error", "No speech transcribed. Ensure you're signed in and your microphone capture works (optional ASTRA_STT_CAPTURE_COMMAND).");
|
|
@@ -1345,9 +1431,11 @@ export const AstraApp = () => {
|
|
|
1345
1431
|
exit,
|
|
1346
1432
|
handleEvent,
|
|
1347
1433
|
localFileCache,
|
|
1434
|
+
micSetupDevices,
|
|
1348
1435
|
openHistory,
|
|
1349
1436
|
pushMessage,
|
|
1350
1437
|
sessionId,
|
|
1438
|
+
setMicSetupDevices,
|
|
1351
1439
|
startLiveVoice,
|
|
1352
1440
|
stopLiveVoice,
|
|
1353
1441
|
thinking,
|
|
@@ -1370,10 +1458,13 @@ export const AstraApp = () => {
|
|
|
1370
1458
|
}
|
|
1371
1459
|
const normalizedQueued = queued.toLowerCase();
|
|
1372
1460
|
const last = lastVoicePromptRef.current;
|
|
1373
|
-
const isLikelyNoise =
|
|
1461
|
+
const isLikelyNoise = isLikelyVoiceNoise(normalizedQueued);
|
|
1374
1462
|
const isFastDuplicate = last !== null &&
|
|
1375
1463
|
last.text === normalizedQueued &&
|
|
1376
1464
|
Date.now() - last.at <= VOICE_DUPLICATE_WINDOW_MS;
|
|
1465
|
+
// #region agent log
|
|
1466
|
+
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(() => { });
|
|
1467
|
+
// #endregion
|
|
1377
1468
|
if (isLikelyNoise || isFastDuplicate) {
|
|
1378
1469
|
const now = Date.now();
|
|
1379
1470
|
const lastIgnored = lastIgnoredVoiceRef.current;
|
|
@@ -1387,8 +1478,11 @@ export const AstraApp = () => {
|
|
|
1387
1478
|
return;
|
|
1388
1479
|
}
|
|
1389
1480
|
lastVoicePromptRef.current = { text: normalizedQueued, at: Date.now() };
|
|
1390
|
-
pushMessage("system", `
|
|
1481
|
+
pushMessage("system", `Voice input: ${queued}`);
|
|
1391
1482
|
setPrompt("");
|
|
1483
|
+
// #region agent log
|
|
1484
|
+
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(() => { });
|
|
1485
|
+
// #endregion
|
|
1392
1486
|
void sendPrompt(queued);
|
|
1393
1487
|
}, [pushMessage, sendPrompt, thinking, user, voiceQueuedPrompt]);
|
|
1394
1488
|
if (!trustedWorkspace) {
|
|
@@ -1415,7 +1509,7 @@ export const AstraApp = () => {
|
|
|
1415
1509
|
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));
|
|
1416
1510
|
}) })), _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 : "--"] })] })] }))] }));
|
|
1417
1511
|
}
|
|
1418
|
-
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} ·
|
|
1512
|
+
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
|
|
1419
1513
|
? voicePreparing
|
|
1420
1514
|
? "on/preparing"
|
|
1421
1515
|
: voiceListening
|
|
@@ -1423,7 +1517,7 @@ export const AstraApp = () => {
|
|
|
1423
1517
|
? "on/waiting"
|
|
1424
1518
|
: "on/listening"
|
|
1425
1519
|
: "on/standby"
|
|
1426
|
-
: "off"}` }), _jsx(Text, { color: "#2a3a50", children: divider }), _jsx(Text, { color: "#3a5068", children: "/help /new /history /
|
|
1520
|
+
: "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) => {
|
|
1427
1521
|
const prev = index > 0 ? messages[index - 1] : null;
|
|
1428
1522
|
const style = styleForKind(message.kind);
|
|
1429
1523
|
const paddedLabel = style.label.padEnd(LABEL_WIDTH, " ");
|
|
@@ -1468,13 +1562,13 @@ export const AstraApp = () => {
|
|
|
1468
1562
|
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}`));
|
|
1469
1563
|
}
|
|
1470
1564
|
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}`));
|
|
1471
|
-
}), 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: "🎤
|
|
1565
|
+
}), 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) => {
|
|
1472
1566
|
setPrompt("");
|
|
1473
1567
|
void sendPrompt(value);
|
|
1474
1568
|
}, onChange: (value) => {
|
|
1475
1569
|
if (!voiceListening) {
|
|
1476
1570
|
setPrompt(value);
|
|
1477
1571
|
}
|
|
1478
|
-
}, placeholder: voiceEnabled ? "Ask Astra... (
|
|
1572
|
+
}, placeholder: voiceEnabled ? "Ask Astra... (voice on: auto listen + send on silence)" : "Ask Astra..." })] })] }));
|
|
1479
1573
|
};
|
|
1480
1574
|
//# sourceMappingURL=App.js.map
|