@adminide-stack/yantra-mobile 12.0.28-alpha.7 → 12.0.28-alpha.72

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.
Files changed (75) hide show
  1. package/lib/api/stt.js +54 -0
  2. package/lib/api/stt.js.map +1 -0
  3. package/lib/assets/icon.png +0 -0
  4. package/lib/components/CustomDrawer.js +479 -0
  5. package/lib/components/CustomDrawer.js.map +1 -0
  6. package/lib/components/GatewayConnector/GatewayConnector.js +18 -0
  7. package/lib/components/GatewayConnector/GatewayConnector.js.map +1 -0
  8. package/lib/components/GatewayToolbarButtonMobile.js +84 -0
  9. package/lib/components/GatewayToolbarButtonMobile.js.map +1 -0
  10. package/lib/components/NavigationHeader/NavigationHeader.js +214 -0
  11. package/lib/components/NavigationHeader/NavigationHeader.js.map +1 -0
  12. package/lib/components/ThinkingIndicator.js +55 -0
  13. package/lib/components/ThinkingIndicator.js.map +1 -0
  14. package/lib/components/YantraBrandLoader.js +94 -0
  15. package/lib/components/YantraBrandLoader.js.map +1 -0
  16. package/lib/compute.js +114 -5
  17. package/lib/compute.js.map +1 -1
  18. package/lib/config/constants.js +16 -0
  19. package/lib/config/constants.js.map +1 -0
  20. package/lib/config/env-config.js +74 -19
  21. package/lib/config/env-config.js.map +1 -1
  22. package/lib/contexts/CdecliConnectionContext.js +47 -0
  23. package/lib/contexts/CdecliConnectionContext.js.map +1 -0
  24. package/lib/contexts/GatewayContext.js +77 -0
  25. package/lib/contexts/GatewayContext.js.map +1 -0
  26. package/lib/features/audio-input/AudioRecorderPanel.js +224 -0
  27. package/lib/features/audio-input/AudioRecorderPanel.js.map +1 -0
  28. package/lib/features/audio-input/MicErrorBoundary.js +34 -0
  29. package/lib/features/audio-input/MicErrorBoundary.js.map +1 -0
  30. package/lib/graphql/agentGatewayDocuments.js +53 -0
  31. package/lib/graphql/agentGatewayDocuments.js.map +1 -0
  32. package/lib/hooks/useCdecliAutoConnect.js +242 -0
  33. package/lib/hooks/useCdecliAutoConnect.js.map +1 -0
  34. package/lib/hooks/useCdecliChannel.js +226 -0
  35. package/lib/hooks/useCdecliChannel.js.map +1 -0
  36. package/lib/hooks/useChatApi.js +338 -171
  37. package/lib/hooks/useChatApi.js.map +1 -1
  38. package/lib/hooks/useChatStream.js +281 -64
  39. package/lib/hooks/useChatStream.js.map +1 -1
  40. package/lib/hooks/useGatewayConnection.js +123 -0
  41. package/lib/hooks/useGatewayConnection.js.map +1 -0
  42. package/lib/hooks/useGatewayRegistry.js +28 -0
  43. package/lib/hooks/useGatewayRegistry.js.map +1 -0
  44. package/lib/hooks/usePrerequisiteIds.js +181 -0
  45. package/lib/hooks/usePrerequisiteIds.js.map +1 -0
  46. package/lib/hooks/useWorkspaceProvisioner.js +236 -0
  47. package/lib/hooks/useWorkspaceProvisioner.js.map +1 -0
  48. package/lib/index.js +1 -1
  49. package/lib/index.js.map +1 -1
  50. package/lib/routes.json +120 -5
  51. package/lib/screens/Chat/index.js +404 -0
  52. package/lib/screens/Chat/index.js.map +1 -0
  53. package/lib/screens/ChatHistory/index.js +56 -0
  54. package/lib/screens/ChatHistory/index.js.map +1 -0
  55. package/lib/screens/Home/HomeScreen.js +397 -143
  56. package/lib/screens/Home/HomeScreen.js.map +1 -1
  57. package/lib/screens/Home/components/ChatHistoryLanding.js +487 -0
  58. package/lib/screens/Home/components/ChatHistoryLanding.js.map +1 -0
  59. package/lib/screens/Home/components/DeepSearchModal.js +349 -0
  60. package/lib/screens/Home/components/DeepSearchModal.js.map +1 -0
  61. package/lib/screens/Home/deepSearchUtils.js +41 -0
  62. package/lib/screens/Home/deepSearchUtils.js.map +1 -0
  63. package/lib/screens/NewChat/index.js +79 -0
  64. package/lib/screens/NewChat/index.js.map +1 -0
  65. package/lib/services/agentSessionManager.js +451 -0
  66. package/lib/services/agentSessionManager.js.map +1 -0
  67. package/lib/services/gatewayApiKeyBridge.js +4 -0
  68. package/lib/services/gatewayApiKeyBridge.js.map +1 -0
  69. package/lib/services/gatewayClient.js +470 -0
  70. package/lib/services/gatewayClient.js.map +1 -0
  71. package/lib/theme/mobileTokens.js +18 -0
  72. package/lib/theme/mobileTokens.js.map +1 -0
  73. package/lib/utils/gatewaySelectionStorage.js +21 -0
  74. package/lib/utils/gatewaySelectionStorage.js.map +1 -0
  75. package/package.json +7 -3
@@ -0,0 +1,224 @@
1
+ import {jsxs,jsx}from'react/jsx-runtime';import {useState,useRef,useEffect,useCallback,useMemo}from'react';import {Animated,Easing,View,StyleSheet,ActivityIndicator,Pressable}from'react-native';import {Ionicons,Feather}from'@expo/vector-icons';import {useAudioRecorder,RecordingPresets,useAudioRecorderState,setAudioModeAsync}from'expo-audio';import {transcribeAudio}from'../../api/stt.js';const MAX_DURATION_MS = 3 * 60 * 1e3;
2
+ function AudioRecorderPanel({
3
+ isDark,
4
+ onTranscriptionComplete,
5
+ onCancel,
6
+ onError
7
+ }) {
8
+ const recorder = useAudioRecorder(RecordingPresets.HIGH_QUALITY);
9
+ const recorderState = useAudioRecorderState(recorder, 200);
10
+ const [isTranscribing, setIsTranscribing] = useState(false);
11
+ const hasStartedRef = useRef(false);
12
+ const isMountedRef = useRef(true);
13
+ const sendInFlightRef = useRef(false);
14
+ const pulse = useRef(new Animated.Value(1)).current;
15
+ useEffect(() => {
16
+ const loop = Animated.loop(Animated.sequence([Animated.timing(pulse, {
17
+ toValue: 0.3,
18
+ duration: 700,
19
+ easing: Easing.inOut(Easing.ease),
20
+ useNativeDriver: true
21
+ }), Animated.timing(pulse, {
22
+ toValue: 1,
23
+ duration: 700,
24
+ easing: Easing.inOut(Easing.ease),
25
+ useNativeDriver: true
26
+ })]));
27
+ loop.start();
28
+ return () => loop.stop();
29
+ }, [pulse]);
30
+ const stopAndCleanup = useCallback(async () => {
31
+ try {
32
+ if (recorderState.isRecording || recorder.isRecording) {
33
+ await recorder.stop().catch(() => void 0);
34
+ }
35
+ } catch (e) {
36
+ }
37
+ try {
38
+ await setAudioModeAsync({
39
+ allowsRecording: false,
40
+ playsInSilentMode: true
41
+ });
42
+ } catch (e) {
43
+ }
44
+ }, [recorder, recorderState.isRecording]);
45
+ const finalize = useCallback(async () => {
46
+ if (sendInFlightRef.current || isTranscribing) return;
47
+ sendInFlightRef.current = true;
48
+ if (isMountedRef.current) setIsTranscribing(true);
49
+ try {
50
+ await stopAndCleanup();
51
+ const uri = recorder.uri;
52
+ if (!uri) {
53
+ throw new Error("No recording produced");
54
+ }
55
+ const text = await transcribeAudio({
56
+ uri,
57
+ mimeType: "audio/m4a",
58
+ filename: "audio.m4a"
59
+ });
60
+ if (isMountedRef.current) {
61
+ onTranscriptionComplete(text);
62
+ }
63
+ } catch (err) {
64
+ const msg = err instanceof Error ? err.message : String(err);
65
+ onError == null ? void 0 : onError(msg);
66
+ onCancel();
67
+ } finally {
68
+ sendInFlightRef.current = false;
69
+ if (isMountedRef.current) setIsTranscribing(false);
70
+ }
71
+ }, [isTranscribing, recorder, stopAndCleanup, onTranscriptionComplete, onError, onCancel]);
72
+ useEffect(() => {
73
+ if (hasStartedRef.current) return;
74
+ hasStartedRef.current = true;
75
+ let cancelled = false;
76
+ const start = async () => {
77
+ try {
78
+ await setAudioModeAsync({
79
+ allowsRecording: true,
80
+ playsInSilentMode: true
81
+ });
82
+ await recorder.prepareToRecordAsync();
83
+ if (cancelled || !isMountedRef.current) return;
84
+ recorder.record();
85
+ } catch (err) {
86
+ const msg = err instanceof Error ? err.message : String(err);
87
+ onError == null ? void 0 : onError(msg);
88
+ onCancel();
89
+ }
90
+ };
91
+ void start();
92
+ return () => {
93
+ cancelled = true;
94
+ };
95
+ }, []);
96
+ useEffect(() => {
97
+ return () => {
98
+ isMountedRef.current = false;
99
+ void stopAndCleanup();
100
+ };
101
+ }, []);
102
+ useEffect(() => {
103
+ var _a;
104
+ if (!recorderState.isRecording) return;
105
+ const elapsedMs = (_a = recorderState.durationMillis) != null ? _a : 0;
106
+ if (elapsedMs >= MAX_DURATION_MS && !sendInFlightRef.current) {
107
+ void finalize();
108
+ }
109
+ }, [recorderState.isRecording, recorderState.durationMillis, finalize]);
110
+ const handleCancelPress = useCallback(async () => {
111
+ if (isTranscribing) return;
112
+ await stopAndCleanup();
113
+ onCancel();
114
+ }, [isTranscribing, stopAndCleanup, onCancel]);
115
+ const handleSendPress = useCallback(() => {
116
+ void finalize();
117
+ }, [finalize]);
118
+ const formatted = useMemo(() => {
119
+ var _a;
120
+ const totalSeconds = Math.floor(((_a = recorderState.durationMillis) != null ? _a : 0) / 1e3);
121
+ const m = Math.floor(totalSeconds / 60);
122
+ const s = totalSeconds % 60;
123
+ return `${m}:${s.toString().padStart(2, "0")}`;
124
+ }, [recorderState.durationMillis]);
125
+ const bg = isDark ? "#0f172a" : "#ffffff";
126
+ const border = isDark ? "rgba(148,163,184,0.18)" : "#e4e4e7";
127
+ const fg = isDark ? "#e5e7eb" : "#18181b";
128
+ const muted = isDark ? "#94a3b8" : "#71717a";
129
+ const cancelBg = isDark ? "rgba(148,163,184,0.08)" : "#f4f4f5";
130
+ const showSpinner = isTranscribing;
131
+ const isRecording = recorderState.isRecording && !showSpinner;
132
+ const statusLabel = showSpinner ? "Transcribing\u2026" : isRecording ? formatted : "Initializing\u2026";
133
+ return /* @__PURE__ */ jsxs(View, { accessibilityRole: "summary", accessibilityLabel: "Voice recorder", style: [styles.container, {
134
+ backgroundColor: bg,
135
+ borderColor: border
136
+ }], children: [
137
+ /* @__PURE__ */ jsxs(View, { style: styles.statusRow, children: [
138
+ showSpinner ? /* @__PURE__ */ jsx(ActivityIndicator, { size: "small", color: fg }) : /* @__PURE__ */ jsx(Animated.View, { style: [styles.recDot, {
139
+ opacity: isRecording ? pulse : 0.35
140
+ }] }),
141
+ /* @__PURE__ */ jsx(Animated.Text, { style: [styles.timer, {
142
+ color: fg
143
+ }], accessibilityLiveRegion: "polite", children: statusLabel }),
144
+ isRecording && /* @__PURE__ */ jsx(Animated.Text, { style: [styles.maxLabel, {
145
+ color: muted
146
+ }], children: "/ 3:00" })
147
+ ] }),
148
+ /* @__PURE__ */ jsxs(View, { style: styles.actionsRow, children: [
149
+ /* @__PURE__ */ jsx(Pressable, { onPress: handleCancelPress, disabled: isTranscribing, accessibilityLabel: "Cancel recording", accessibilityRole: "button", style: ({
150
+ pressed
151
+ }) => [styles.iconBtn, {
152
+ backgroundColor: cancelBg,
153
+ borderColor: border,
154
+ opacity: isTranscribing ? 0.4 : pressed ? 0.7 : 1
155
+ }], children: /* @__PURE__ */ jsx(Ionicons, { name: "close", size: 16, color: fg }) }),
156
+ /* @__PURE__ */ jsx(Pressable, { onPress: handleSendPress, disabled: isTranscribing || !isRecording, accessibilityLabel: "Stop and transcribe", accessibilityRole: "button", style: ({
157
+ pressed
158
+ }) => [styles.sendBtn, {
159
+ opacity: isTranscribing ? 0.7 : !isRecording ? 0.5 : pressed ? 0.85 : 1
160
+ }], children: isTranscribing ? /* @__PURE__ */ jsx(ActivityIndicator, { size: "small", color: "#ffffff" }) : /* @__PURE__ */ jsx(Feather, { name: "arrow-up", size: 16, color: "#ffffff" }) })
161
+ ] })
162
+ ] });
163
+ }
164
+ const styles = StyleSheet.create({
165
+ container: {
166
+ flexDirection: "row",
167
+ alignItems: "center",
168
+ justifyContent: "space-between",
169
+ borderRadius: 24,
170
+ borderWidth: 1,
171
+ paddingVertical: 10,
172
+ paddingHorizontal: 14,
173
+ gap: 12,
174
+ shadowColor: "#0f172a",
175
+ shadowOpacity: 0.08,
176
+ shadowRadius: 12,
177
+ shadowOffset: {
178
+ width: 0,
179
+ height: 4
180
+ },
181
+ elevation: 4
182
+ },
183
+ statusRow: {
184
+ flexDirection: "row",
185
+ alignItems: "center",
186
+ gap: 8,
187
+ flexShrink: 1
188
+ },
189
+ recDot: {
190
+ width: 8,
191
+ height: 8,
192
+ borderRadius: 4,
193
+ backgroundColor: "#ef4444"
194
+ },
195
+ timer: {
196
+ fontSize: 13,
197
+ fontWeight: "600",
198
+ fontVariant: ["tabular-nums"]
199
+ },
200
+ maxLabel: {
201
+ fontSize: 11
202
+ },
203
+ actionsRow: {
204
+ flexDirection: "row",
205
+ alignItems: "center",
206
+ gap: 8
207
+ },
208
+ iconBtn: {
209
+ width: 32,
210
+ height: 32,
211
+ borderRadius: 16,
212
+ borderWidth: 1,
213
+ alignItems: "center",
214
+ justifyContent: "center"
215
+ },
216
+ sendBtn: {
217
+ width: 32,
218
+ height: 32,
219
+ borderRadius: 16,
220
+ alignItems: "center",
221
+ justifyContent: "center",
222
+ backgroundColor: "#18181b"
223
+ }
224
+ });export{AudioRecorderPanel,AudioRecorderPanel as default};//# sourceMappingURL=AudioRecorderPanel.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AudioRecorderPanel.js","sources":["../../../src/features/audio-input/AudioRecorderPanel.tsx"],"sourcesContent":["import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';\nimport { ActivityIndicator, Animated, Easing, Pressable, StyleSheet, View } from 'react-native';\nimport { Feather, Ionicons } from '@expo/vector-icons';\nimport { RecordingPresets, setAudioModeAsync, useAudioRecorder, useAudioRecorderState } from 'expo-audio';\nimport { transcribeAudio } from '../../api/stt';\n\n/**\n * Mirrors the web ceiling in\n * `packages-modules/account/browser/src/features/audio-input/components/AudioRecorder.tsx`.\n * Three minutes is a soft Whisper ceiling — past that, accuracy and upload\n * time both degrade more than the marginal extra context is worth.\n */\nconst MAX_DURATION_MS = 3 * 60 * 1000;\n\nexport interface AudioRecorderPanelProps {\n isDark: boolean;\n /**\n * Called once with the final transcribed string after the user taps \"Send\".\n * The host screen typically appends this to its composer state and re-focuses\n * the text input — mirroring the web `handleTranscriptionComplete` flow.\n */\n onTranscriptionComplete: (text: string) => void;\n /** Called when the user discards the recording (X) or recording cannot start. */\n onCancel: () => void;\n /** Optional error sink — host can surface a toast/banner. */\n onError?: (error: string) => void;\n}\n\n/**\n * Inline voice-capture panel that replaces the composer while recording —\n * the React Native port of the web `AudioRecorder` component\n * (`packages-modules/account/browser/src/features/audio-input/components/AudioRecorder.tsx`).\n *\n * Flow (parity with web):\n * 1. Mount → request mic permission → set audio mode → prepare + start recording.\n * 2. While recording, display a live timer and pulsing record dot.\n * 3. User taps \"Send\" → stop recorder → upload `recorder.uri` to Groq Whisper\n * via `transcribeAudio` → return the transcribed text to the host via\n * `onTranscriptionComplete`.\n * 4. User taps \"Cancel\" (X) → stop + discard, no upload.\n * 5. If recording exceeds `MAX_DURATION_MS`, auto-finalize as if the user\n * tapped Send (matches web safety net).\n *\n * The panel is intentionally NOT rendered inside the `InputToolBar`'s `topContent`\n * slot — web fully swaps the toolbar for the recorder. Keeping the toolbar\n * visible during recording invites accidental sends with empty input and an\n * unfocused recorder state.\n */\nexport function AudioRecorderPanel({ isDark, onTranscriptionComplete, onCancel, onError }: AudioRecorderPanelProps) {\n const recorder = useAudioRecorder(RecordingPresets.HIGH_QUALITY);\n const recorderState = useAudioRecorderState(recorder, 200);\n const [isTranscribing, setIsTranscribing] = useState(false);\n\n /**\n * Guard against re-entry. `useEffect` runs once on mount but a fast double-\n * tap on the mic can produce two panel mounts in dev mode (StrictMode);\n * `hasStartedRef` ensures we don't start a second recorder against the\n * same shared object.\n */\n const hasStartedRef = useRef(false);\n const isMountedRef = useRef(true);\n /**\n * Caches the result of the in-flight transcription/cleanup pipeline so the\n * MAX_DURATION watcher and the explicit Send button can both call\n * `finalize()` without racing each other into a double-upload.\n */\n const sendInFlightRef = useRef(false);\n\n /**\n * Pulsing dot for the \"REC\" indicator. Pure UI sugar; avoids importing\n * reanimated for a 2-keyframe loop.\n */\n const pulse = useRef(new Animated.Value(1)).current;\n useEffect(() => {\n const loop = Animated.loop(\n Animated.sequence([\n Animated.timing(pulse, {\n toValue: 0.3,\n duration: 700,\n easing: Easing.inOut(Easing.ease),\n useNativeDriver: true,\n }),\n Animated.timing(pulse, {\n toValue: 1,\n duration: 700,\n easing: Easing.inOut(Easing.ease),\n useNativeDriver: true,\n }),\n ]),\n );\n loop.start();\n return () => loop.stop();\n }, [pulse]);\n\n const stopAndCleanup = useCallback(async () => {\n try {\n if (recorderState.isRecording || recorder.isRecording) {\n await recorder.stop().catch(() => undefined);\n }\n } catch {\n /* best-effort */\n }\n try {\n /*\n * Releasing the recording audio session matters more on iOS — leaving\n * `allowsRecording: true` will dim playback for the rest of the app\n * lifecycle. We tolerate failures because the AVAudioSession may\n * already be torn down by the time we get here.\n */\n await setAudioModeAsync({ allowsRecording: false, playsInSilentMode: true });\n } catch {\n /* best-effort */\n }\n }, [recorder, recorderState.isRecording]);\n\n /**\n * One-shot finalize: stop → upload → emit transcript. Swallowed errors get\n * surfaced via `onError` so the host can show a toast. We always clear\n * `sendInFlightRef` even on error so a retry path is possible if needed.\n */\n const finalize = useCallback(async () => {\n if (sendInFlightRef.current || isTranscribing) return;\n sendInFlightRef.current = true;\n if (isMountedRef.current) setIsTranscribing(true);\n try {\n await stopAndCleanup();\n const uri = recorder.uri;\n if (!uri) {\n throw new Error('No recording produced');\n }\n const text = await transcribeAudio({ uri, mimeType: 'audio/m4a', filename: 'audio.m4a' });\n if (isMountedRef.current) {\n onTranscriptionComplete(text);\n }\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n onError?.(msg);\n onCancel();\n } finally {\n sendInFlightRef.current = false;\n if (isMountedRef.current) setIsTranscribing(false);\n }\n }, [isTranscribing, recorder, stopAndCleanup, onTranscriptionComplete, onError, onCancel]);\n\n useEffect(() => {\n if (hasStartedRef.current) return;\n hasStartedRef.current = true;\n let cancelled = false;\n const start = async () => {\n try {\n /*\n * IMPORTANT: We intentionally do NOT call\n * `AudioModule.requestRecordingPermissionsAsync()` or\n * `AudioModule.getRecordingPermissionsAsync()` here.\n *\n * expo-audio 0.4.x's `AudioRecordingRequester.getPermissions()`\n * (AudioRecordingRequester.swift:15) crashes with a native\n * SIGABRT via `EXFatal` → `RCTFatal` on a background dispatch\n * queue in TestFlight/release builds (see crash report: Thread\n * 13, EXPermissionsService → AudioRecordingRequester). Because\n * the abort happens on a native thread, no JS try/catch can\n * intercept it.\n *\n * Instead, `setAudioModeAsync({ allowsRecording: true })` sets\n * AVAudioSession's category to PlayAndRecord, which triggers\n * the native iOS microphone permission dialog on first access\n * (same dialog, different code path — bypasses the broken\n * EXPermissionsService requester). If permission is denied or\n * the session can't be configured, `setAudioModeAsync` or\n * `prepareToRecordAsync` will reject their promises normally,\n * and the catch block handles it gracefully.\n */\n await setAudioModeAsync({ allowsRecording: true, playsInSilentMode: true });\n await recorder.prepareToRecordAsync();\n if (cancelled || !isMountedRef.current) return;\n recorder.record();\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n onError?.(msg);\n onCancel();\n }\n };\n void start();\n return () => {\n cancelled = true;\n };\n // Mount-only effect: recorder/start refs are stable for the life of the panel.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n useEffect(() => {\n return () => {\n isMountedRef.current = false;\n /*\n * Unmount cleanup: fire-and-forget. We don't await anything here\n * because React doesn't allow async destructors and we can't\n * guarantee the recorder shared object outlives this tick. The\n * stopAndCleanup util is null-safe.\n */\n void stopAndCleanup();\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n /**\n * Max-duration safety net. `RecorderState.durationMillis` already reports\n * elapsed time in milliseconds (see `expo-audio` `Audio.types.ts`), which\n * lines up directly with our `MAX_DURATION_MS` ceiling (web parity).\n */\n useEffect(() => {\n if (!recorderState.isRecording) return;\n const elapsedMs = recorderState.durationMillis ?? 0;\n if (elapsedMs >= MAX_DURATION_MS && !sendInFlightRef.current) {\n void finalize();\n }\n }, [recorderState.isRecording, recorderState.durationMillis, finalize]);\n\n const handleCancelPress = useCallback(async () => {\n if (isTranscribing) return;\n await stopAndCleanup();\n onCancel();\n }, [isTranscribing, stopAndCleanup, onCancel]);\n\n const handleSendPress = useCallback(() => {\n void finalize();\n }, [finalize]);\n\n const formatted = useMemo(() => {\n const totalSeconds = Math.floor((recorderState.durationMillis ?? 0) / 1000);\n const m = Math.floor(totalSeconds / 60);\n const s = totalSeconds % 60;\n return `${m}:${s.toString().padStart(2, '0')}`;\n }, [recorderState.durationMillis]);\n\n const bg = isDark ? '#0f172a' : '#ffffff';\n const border = isDark ? 'rgba(148,163,184,0.18)' : '#e4e4e7';\n const fg = isDark ? '#e5e7eb' : '#18181b';\n const muted = isDark ? '#94a3b8' : '#71717a';\n const cancelBg = isDark ? 'rgba(148,163,184,0.08)' : '#f4f4f5';\n\n const showSpinner = isTranscribing;\n const isRecording = recorderState.isRecording && !showSpinner;\n const statusLabel = showSpinner ? 'Transcribing…' : isRecording ? formatted : 'Initializing…';\n\n return (\n <View\n accessibilityRole=\"summary\"\n accessibilityLabel=\"Voice recorder\"\n style={[styles.container, { backgroundColor: bg, borderColor: border }]}\n >\n <View style={styles.statusRow}>\n {showSpinner ? (\n <ActivityIndicator size=\"small\" color={fg} />\n ) : (\n <Animated.View style={[styles.recDot, { opacity: isRecording ? pulse : 0.35 }]} />\n )}\n <Animated.Text style={[styles.timer, { color: fg }]} accessibilityLiveRegion=\"polite\">\n {statusLabel}\n </Animated.Text>\n {isRecording && <Animated.Text style={[styles.maxLabel, { color: muted }]}>/ 3:00</Animated.Text>}\n </View>\n <View style={styles.actionsRow}>\n <Pressable\n onPress={handleCancelPress}\n disabled={isTranscribing}\n accessibilityLabel=\"Cancel recording\"\n accessibilityRole=\"button\"\n style={({ pressed }) => [\n styles.iconBtn,\n {\n backgroundColor: cancelBg,\n borderColor: border,\n opacity: isTranscribing ? 0.4 : pressed ? 0.7 : 1,\n },\n ]}\n >\n <Ionicons name=\"close\" size={16} color={fg} />\n </Pressable>\n <Pressable\n onPress={handleSendPress}\n disabled={isTranscribing || !isRecording}\n accessibilityLabel=\"Stop and transcribe\"\n accessibilityRole=\"button\"\n style={({ pressed }) => [\n styles.sendBtn,\n {\n opacity: isTranscribing ? 0.7 : !isRecording ? 0.5 : pressed ? 0.85 : 1,\n },\n ]}\n >\n {isTranscribing ? (\n <ActivityIndicator size=\"small\" color=\"#ffffff\" />\n ) : (\n <Feather name=\"arrow-up\" size={16} color=\"#ffffff\" />\n )}\n </Pressable>\n </View>\n </View>\n );\n}\n\nconst styles = StyleSheet.create({\n container: {\n flexDirection: 'row',\n alignItems: 'center',\n justifyContent: 'space-between',\n borderRadius: 24,\n borderWidth: 1,\n paddingVertical: 10,\n paddingHorizontal: 14,\n gap: 12,\n shadowColor: '#0f172a',\n shadowOpacity: 0.08,\n shadowRadius: 12,\n shadowOffset: { width: 0, height: 4 },\n elevation: 4,\n },\n statusRow: {\n flexDirection: 'row',\n alignItems: 'center',\n gap: 8,\n flexShrink: 1,\n },\n recDot: {\n width: 8,\n height: 8,\n borderRadius: 4,\n backgroundColor: '#ef4444',\n },\n timer: {\n fontSize: 13,\n fontWeight: '600',\n fontVariant: ['tabular-nums'],\n },\n maxLabel: {\n fontSize: 11,\n },\n actionsRow: {\n flexDirection: 'row',\n alignItems: 'center',\n gap: 8,\n },\n iconBtn: {\n width: 32,\n height: 32,\n borderRadius: 16,\n borderWidth: 1,\n alignItems: 'center',\n justifyContent: 'center',\n },\n sendBtn: {\n width: 32,\n height: 32,\n borderRadius: 16,\n alignItems: 'center',\n justifyContent: 'center',\n backgroundColor: '#18181b',\n },\n});\n\nexport default AudioRecorderPanel;\n"],"names":[],"mappings":"sYAYA,MAAM,eAAA,GAAkB,IAAI,EAAK,GAAA,GAAA;AAmC1B,SAAS,kBAAmB,CAAA;AAAA,EACjC,MAAA;AAAA,EACA,uBAAA;AAAA,EACA,QAAA;AAAA,EACA;AACF,CAA4B,EAAA;AAC1B,EAAM,MAAA,QAAA,GAAW,gBAAiB,CAAA,gBAAA,CAAiB,YAAY,CAAA;AAC/D,EAAM,MAAA,aAAA,GAAgB,qBAAsB,CAAA,QAAA,EAAU,GAAG,CAAA;AACzD,EAAA,MAAM,CAAC,cAAA,EAAgB,iBAAiB,CAAA,GAAI,SAAS,KAAK,CAAA;AAQ1D,EAAM,MAAA,aAAA,GAAgB,OAAO,KAAK,CAAA;AAClC,EAAM,MAAA,YAAA,GAAe,OAAO,IAAI,CAAA;AAMhC,EAAM,MAAA,eAAA,GAAkB,OAAO,KAAK,CAAA;AAMpC,EAAA,MAAM,QAAQ,MAAO,CAAA,IAAI,SAAS,KAAM,CAAA,CAAC,CAAC,CAAE,CAAA,OAAA;AAC5C,EAAA,SAAA,CAAU,MAAM;AACd,IAAM,MAAA,IAAA,GAAO,SAAS,IAAK,CAAA,QAAA,CAAS,SAAS,CAAC,QAAA,CAAS,OAAO,KAAO,EAAA;AAAA,MACnE,OAAS,EAAA,GAAA;AAAA,MACT,QAAU,EAAA,GAAA;AAAA,MACV,MAAQ,EAAA,MAAA,CAAO,KAAM,CAAA,MAAA,CAAO,IAAI,CAAA;AAAA,MAChC,eAAiB,EAAA;AAAA,KAClB,CAAA,EAAG,QAAS,CAAA,MAAA,CAAO,KAAO,EAAA;AAAA,MACzB,OAAS,EAAA,CAAA;AAAA,MACT,QAAU,EAAA,GAAA;AAAA,MACV,MAAQ,EAAA,MAAA,CAAO,KAAM,CAAA,MAAA,CAAO,IAAI,CAAA;AAAA,MAChC,eAAiB,EAAA;AAAA,KAClB,CAAC,CAAC,CAAC,CAAA;AACJ,IAAA,IAAA,CAAK,KAAM,EAAA;AACX,IAAO,OAAA,MAAM,KAAK,IAAK,EAAA;AAAA,GACzB,EAAG,CAAC,KAAK,CAAC,CAAA;AACV,EAAM,MAAA,cAAA,GAAiB,YAAY,YAAY;AAC7C,IAAI,IAAA;AACF,MAAI,IAAA,aAAA,CAAc,WAAe,IAAA,QAAA,CAAS,WAAa,EAAA;AACrD,QAAA,MAAM,QAAS,CAAA,IAAA,EAAO,CAAA,KAAA,CAAM,MAAM,KAAS,CAAA,CAAA;AAAA;AAC7C,KACM,CAAA,OAAA,CAAA,EAAA;AAAA;AAGR,IAAI,IAAA;AAOF,MAAA,MAAM,iBAAkB,CAAA;AAAA,QACtB,eAAiB,EAAA,KAAA;AAAA,QACjB,iBAAmB,EAAA;AAAA,OACpB,CAAA;AAAA,KACK,CAAA,OAAA,CAAA,EAAA;AAAA;AAER,GACC,EAAA,CAAC,QAAU,EAAA,aAAA,CAAc,WAAW,CAAC,CAAA;AAOxC,EAAM,MAAA,QAAA,GAAW,YAAY,YAAY;AACvC,IAAI,IAAA,eAAA,CAAgB,WAAW,cAAgB,EAAA;AAC/C,IAAA,eAAA,CAAgB,OAAU,GAAA,IAAA;AAC1B,IAAI,IAAA,YAAA,CAAa,OAAS,EAAA,iBAAA,CAAkB,IAAI,CAAA;AAChD,IAAI,IAAA;AACF,MAAA,MAAM,cAAe,EAAA;AACrB,MAAA,MAAM,MAAM,QAAS,CAAA,GAAA;AACrB,MAAA,IAAI,CAAC,GAAK,EAAA;AACR,QAAM,MAAA,IAAI,MAAM,uBAAuB,CAAA;AAAA;AAEzC,MAAM,MAAA,IAAA,GAAO,MAAM,eAAgB,CAAA;AAAA,QACjC,GAAA;AAAA,QACA,QAAU,EAAA,WAAA;AAAA,QACV,QAAU,EAAA;AAAA,OACX,CAAA;AACD,MAAA,IAAI,aAAa,OAAS,EAAA;AACxB,QAAA,uBAAA,CAAwB,IAAI,CAAA;AAAA;AAC9B,aACO,GAAK,EAAA;AACZ,MAAA,MAAM,MAAM,GAAe,YAAA,KAAA,GAAQ,GAAI,CAAA,OAAA,GAAU,OAAO,GAAG,CAAA;AAC3D,MAAU,OAAA,IAAA,IAAA,GAAA,MAAA,GAAA,OAAA,CAAA,GAAA,CAAA;AACV,MAAS,QAAA,EAAA;AAAA,KACT,SAAA;AACA,MAAA,eAAA,CAAgB,OAAU,GAAA,KAAA;AAC1B,MAAI,IAAA,YAAA,CAAa,OAAS,EAAA,iBAAA,CAAkB,KAAK,CAAA;AAAA;AACnD,GACF,EAAG,CAAC,cAAgB,EAAA,QAAA,EAAU,gBAAgB,uBAAyB,EAAA,OAAA,EAAS,QAAQ,CAAC,CAAA;AACzF,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,IAAI,cAAc,OAAS,EAAA;AAC3B,IAAA,aAAA,CAAc,OAAU,GAAA,IAAA;AACxB,IAAA,IAAI,SAAY,GAAA,KAAA;AAChB,IAAA,MAAM,QAAQ,YAAY;AACxB,MAAI,IAAA;AAuBF,QAAA,MAAM,iBAAkB,CAAA;AAAA,UACtB,eAAiB,EAAA,IAAA;AAAA,UACjB,iBAAmB,EAAA;AAAA,SACpB,CAAA;AACD,QAAA,MAAM,SAAS,oBAAqB,EAAA;AACpC,QAAI,IAAA,SAAA,IAAa,CAAC,YAAA,CAAa,OAAS,EAAA;AACxC,QAAA,QAAA,CAAS,MAAO,EAAA;AAAA,eACT,GAAK,EAAA;AACZ,QAAA,MAAM,MAAM,GAAe,YAAA,KAAA,GAAQ,GAAI,CAAA,OAAA,GAAU,OAAO,GAAG,CAAA;AAC3D,QAAU,OAAA,IAAA,IAAA,GAAA,MAAA,GAAA,OAAA,CAAA,GAAA,CAAA;AACV,QAAS,QAAA,EAAA;AAAA;AACX,KACF;AACA,IAAA,KAAK,KAAM,EAAA;AACX,IAAA,OAAO,MAAM;AACX,MAAY,SAAA,GAAA,IAAA;AAAA,KACd;AAAA,GAGF,EAAG,EAAE,CAAA;AACL,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,OAAO,MAAM;AACX,MAAA,YAAA,CAAa,OAAU,GAAA,KAAA;AAOvB,MAAA,KAAK,cAAe,EAAA;AAAA,KACtB;AAAA,GAEF,EAAG,EAAE,CAAA;AAOL,EAAA,SAAA,CAAU,MAAM;AAvNlB,IAAA,IAAA,EAAA;AAwNI,IAAI,IAAA,CAAC,cAAc,WAAa,EAAA;AAChC,IAAM,MAAA,SAAA,GAAA,CAAY,EAAc,GAAA,aAAA,CAAA,cAAA,KAAd,IAAgC,GAAA,EAAA,GAAA,CAAA;AAClD,IAAA,IAAI,SAAa,IAAA,eAAA,IAAmB,CAAC,eAAA,CAAgB,OAAS,EAAA;AAC5D,MAAA,KAAK,QAAS,EAAA;AAAA;AAChB,KACC,CAAC,aAAA,CAAc,aAAa,aAAc,CAAA,cAAA,EAAgB,QAAQ,CAAC,CAAA;AACtE,EAAM,MAAA,iBAAA,GAAoB,YAAY,YAAY;AAChD,IAAA,IAAI,cAAgB,EAAA;AACpB,IAAA,MAAM,cAAe,EAAA;AACrB,IAAS,QAAA,EAAA;AAAA,GACR,EAAA,CAAC,cAAgB,EAAA,cAAA,EAAgB,QAAQ,CAAC,CAAA;AAC7C,EAAM,MAAA,eAAA,GAAkB,YAAY,MAAM;AACxC,IAAA,KAAK,QAAS,EAAA;AAAA,GAChB,EAAG,CAAC,QAAQ,CAAC,CAAA;AACb,EAAM,MAAA,SAAA,GAAY,QAAQ,MAAM;AAtOlC,IAAA,IAAA,EAAA;AAuOI,IAAA,MAAM,eAAe,IAAK,CAAA,KAAA,CAAA,CAAA,CAAO,mBAAc,cAAd,KAAA,IAAA,GAAA,EAAA,GAAgC,KAAK,GAAI,CAAA;AAC1E,IAAA,MAAM,CAAI,GAAA,IAAA,CAAK,KAAM,CAAA,YAAA,GAAe,EAAE,CAAA;AACtC,IAAA,MAAM,IAAI,YAAe,GAAA,EAAA;AACzB,IAAO,OAAA,CAAA,EAAG,CAAC,CAAI,CAAA,EAAA,CAAA,CAAE,UAAW,CAAA,QAAA,CAAS,CAAG,EAAA,GAAG,CAAC,CAAA,CAAA;AAAA,GAC3C,EAAA,CAAC,aAAc,CAAA,cAAc,CAAC,CAAA;AACjC,EAAM,MAAA,EAAA,GAAK,SAAS,SAAY,GAAA,SAAA;AAChC,EAAM,MAAA,MAAA,GAAS,SAAS,wBAA2B,GAAA,SAAA;AACnD,EAAM,MAAA,EAAA,GAAK,SAAS,SAAY,GAAA,SAAA;AAChC,EAAM,MAAA,KAAA,GAAQ,SAAS,SAAY,GAAA,SAAA;AACnC,EAAM,MAAA,QAAA,GAAW,SAAS,wBAA2B,GAAA,SAAA;AACrD,EAAA,MAAM,WAAc,GAAA,cAAA;AACpB,EAAM,MAAA,WAAA,GAAc,aAAc,CAAA,WAAA,IAAe,CAAC,WAAA;AAClD,EAAA,MAAM,WAAc,GAAA,WAAA,GAAc,oBAAkB,GAAA,WAAA,GAAc,SAAY,GAAA,oBAAA;AAC9E,EAAO,uBAAA,IAAA,CAAC,QAAK,iBAAkB,EAAA,SAAA,EAAU,oBAAmB,gBAAiB,EAAA,KAAA,EAAO,CAAC,MAAA,CAAO,SAAW,EAAA;AAAA,IACrG,eAAiB,EAAA,EAAA;AAAA,IACjB,WAAa,EAAA;AAAA,GACd,CACS,EAAA,QAAA,EAAA;AAAA,oBAAC,IAAA,CAAA,IAAA,EAAA,EAAK,KAAO,EAAA,MAAA,CAAO,SACf,EAAA,QAAA,EAAA;AAAA,MAAA,WAAA,mBAAe,GAAA,CAAA,iBAAA,EAAA,EAAkB,IAAK,EAAA,OAAA,EAAQ,OAAO,EAAI,EAAA,CAAA,mBAAM,GAAA,CAAA,QAAA,CAAS,IAAT,EAAA,EAAc,KAAO,EAAA,CAAC,OAAO,MAAQ,EAAA;AAAA,QAC7G,OAAA,EAAS,cAAc,KAAQ,GAAA;AAAA,OAChC,CAAG,EAAA,CAAA;AAAA,0BACO,QAAS,CAAA,IAAA,EAAT,EAAc,KAAO,EAAA,CAAC,OAAO,KAAO,EAAA;AAAA,QAC7C,KAAO,EAAA;AAAA,OACR,CAAA,EAAG,uBAAwB,EAAA,QAAA,EACb,QACL,EAAA,WAAA,EAAA,CAAA;AAAA,MACC,WAAA,wBAAgB,QAAS,CAAA,IAAA,EAAT,EAAc,KAAO,EAAA,CAAC,OAAO,QAAU,EAAA;AAAA,QAChE,KAAO,EAAA;AAAA,OACR,GAAG,QAAM,EAAA,QAAA,EAAA;AAAA,KACJ,EAAA,CAAA;AAAA,oBACC,IAAA,CAAA,IAAA,EAAA,EAAK,KAAO,EAAA,MAAA,CAAO,UAChB,EAAA,QAAA,EAAA;AAAA,sBAAC,GAAA,CAAA,SAAA,EAAA,EAAU,OAAS,EAAA,iBAAA,EAAmB,QAAU,EAAA,cAAA,EAAgB,oBAAmB,kBAAmB,EAAA,iBAAA,EAAkB,QAAS,EAAA,KAAA,EAAO,CAAC;AAAA,QAClJ;AAAA,OACF,KAAM,CAAC,MAAA,CAAO,OAAS,EAAA;AAAA,QACrB,eAAiB,EAAA,QAAA;AAAA,QACjB,WAAa,EAAA,MAAA;AAAA,QACb,OAAS,EAAA,cAAA,GAAiB,GAAM,GAAA,OAAA,GAAU,GAAM,GAAA;AAAA,OACjD,CACa,EAAA,QAAA,kBAAA,GAAA,CAAC,QAAS,EAAA,EAAA,IAAA,EAAK,SAAQ,IAAM,EAAA,EAAA,EAAI,KAAO,EAAA,EAAA,EAAI,CAChD,EAAA,CAAA;AAAA,sBACC,GAAA,CAAA,SAAA,EAAA,EAAU,OAAS,EAAA,eAAA,EAAiB,QAAU,EAAA,cAAA,IAAkB,CAAC,WAAA,EAAa,kBAAmB,EAAA,qBAAA,EAAsB,iBAAkB,EAAA,QAAA,EAAS,OAAO,CAAC;AAAA,QACnK;AAAA,OACF,KAAM,CAAC,MAAA,CAAO,OAAS,EAAA;AAAA,QACrB,SAAS,cAAiB,GAAA,GAAA,GAAM,CAAC,WAAc,GAAA,GAAA,GAAM,UAAU,IAAO,GAAA;AAAA,OACvE,CACc,EAAA,QAAA,EAAA,cAAA,uBAAkB,iBAAkB,EAAA,EAAA,IAAA,EAAK,SAAQ,KAAM,EAAA,SAAA,EAAU,CAAK,mBAAA,GAAA,CAAC,WAAQ,IAAK,EAAA,UAAA,EAAW,MAAM,EAAI,EAAA,KAAA,EAAM,WAAU,CAC9H,EAAA;AAAA,KACJ,EAAA;AAAA,GACJ,EAAA,CAAA;AACR;AACA,MAAM,MAAA,GAAS,WAAW,MAAO,CAAA;AAAA,EAC/B,SAAW,EAAA;AAAA,IACT,aAAe,EAAA,KAAA;AAAA,IACf,UAAY,EAAA,QAAA;AAAA,IACZ,cAAgB,EAAA,eAAA;AAAA,IAChB,YAAc,EAAA,EAAA;AAAA,IACd,WAAa,EAAA,CAAA;AAAA,IACb,eAAiB,EAAA,EAAA;AAAA,IACjB,iBAAmB,EAAA,EAAA;AAAA,IACnB,GAAK,EAAA,EAAA;AAAA,IACL,WAAa,EAAA,SAAA;AAAA,IACb,aAAe,EAAA,IAAA;AAAA,IACf,YAAc,EAAA,EAAA;AAAA,IACd,YAAc,EAAA;AAAA,MACZ,KAAO,EAAA,CAAA;AAAA,MACP,MAAQ,EAAA;AAAA,KACV;AAAA,IACA,SAAW,EAAA;AAAA,GACb;AAAA,EACA,SAAW,EAAA;AAAA,IACT,aAAe,EAAA,KAAA;AAAA,IACf,UAAY,EAAA,QAAA;AAAA,IACZ,GAAK,EAAA,CAAA;AAAA,IACL,UAAY,EAAA;AAAA,GACd;AAAA,EACA,MAAQ,EAAA;AAAA,IACN,KAAO,EAAA,CAAA;AAAA,IACP,MAAQ,EAAA,CAAA;AAAA,IACR,YAAc,EAAA,CAAA;AAAA,IACd,eAAiB,EAAA;AAAA,GACnB;AAAA,EACA,KAAO,EAAA;AAAA,IACL,QAAU,EAAA,EAAA;AAAA,IACV,UAAY,EAAA,KAAA;AAAA,IACZ,WAAA,EAAa,CAAC,cAAc;AAAA,GAC9B;AAAA,EACA,QAAU,EAAA;AAAA,IACR,QAAU,EAAA;AAAA,GACZ;AAAA,EACA,UAAY,EAAA;AAAA,IACV,aAAe,EAAA,KAAA;AAAA,IACf,UAAY,EAAA,QAAA;AAAA,IACZ,GAAK,EAAA;AAAA,GACP;AAAA,EACA,OAAS,EAAA;AAAA,IACP,KAAO,EAAA,EAAA;AAAA,IACP,MAAQ,EAAA,EAAA;AAAA,IACR,YAAc,EAAA,EAAA;AAAA,IACd,WAAa,EAAA,CAAA;AAAA,IACb,UAAY,EAAA,QAAA;AAAA,IACZ,cAAgB,EAAA;AAAA,GAClB;AAAA,EACA,OAAS,EAAA;AAAA,IACP,KAAO,EAAA,EAAA;AAAA,IACP,MAAQ,EAAA,EAAA;AAAA,IACR,YAAc,EAAA,EAAA;AAAA,IACd,UAAY,EAAA,QAAA;AAAA,IACZ,cAAgB,EAAA,QAAA;AAAA,IAChB,eAAiB,EAAA;AAAA;AAErB,CAAC,CAAA"}
@@ -0,0 +1,34 @@
1
+ import React from'react';var __defProp = Object.defineProperty;
2
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
3
+ var __publicField = (obj, key, value) => __defNormalProp(obj, key + "" , value);
4
+ class MicErrorBoundary extends React.Component {
5
+ constructor() {
6
+ super(...arguments);
7
+ __publicField(this, "state", {
8
+ hasError: false
9
+ });
10
+ }
11
+ static getDerivedStateFromError() {
12
+ return {
13
+ hasError: true
14
+ };
15
+ }
16
+ componentDidCatch(error) {
17
+ var _a, _b;
18
+ const msg = (error == null ? void 0 : error.message) ? error.message : "Voice input failed to start.";
19
+ try {
20
+ (_b = (_a = this.props).onError) == null ? void 0 : _b.call(_a, msg);
21
+ } catch (e) {
22
+ }
23
+ try {
24
+ this.props.onCancel();
25
+ } catch (e) {
26
+ }
27
+ }
28
+ render() {
29
+ if (this.state.hasError) {
30
+ return null;
31
+ }
32
+ return this.props.children;
33
+ }
34
+ }export{MicErrorBoundary,MicErrorBoundary as default};//# sourceMappingURL=MicErrorBoundary.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MicErrorBoundary.js","sources":["../../../src/features/audio-input/MicErrorBoundary.tsx"],"sourcesContent":["import React from 'react';\n\ninterface MicErrorBoundaryProps {\n /**\n * Soft-cancel callback fired when a render-time error is caught.\n * Host screens use this to dismiss the panel so the composer reappears.\n */\n onCancel: () => void;\n /** Optional error sink so the host can surface a banner / toast. */\n onError?: (message: string) => void;\n children: React.ReactNode;\n}\n\ninterface MicErrorBoundaryState {\n hasError: boolean;\n}\n\n/**\n * Catches synchronous render-time errors thrown by the audio-input subtree.\n *\n * Why it exists: `expo-audio`'s `useAudioRecorder(...)` constructs a native\n * `AVAudioRecorder` (iOS) / `MediaRecorder` (Android) during the React render\n * phase. If the native module isn't linked, the new-architecture bridge can't\n * resolve it, or the recorder constructor throws (e.g. permissions revoked\n * mid-session), the throw propagates out of render and — in a release build\n * with Hermes — kills the JS thread and crashes the app.\n *\n * Wrapping the panel in this boundary turns that hard crash into a graceful\n * cancel: the boundary catches the throw, hides the children, fires\n * `onError` (so the host can surface \"Voice input failed to start\"), and\n * fires `onCancel` (so the host can dismiss the panel and restore the\n * composer). The composer remains usable, the user sees a clear message,\n * and the app stays alive.\n *\n * This does NOT catch async/native crashes (e.g. AVAudioSession explosions\n * inside Objective-C). For those, the pre-permission gate in `handleMicPress`\n * is the primary defense — this boundary is the belt-and-suspenders fallback\n * for the JS render path.\n */\nexport class MicErrorBoundary extends React.Component<MicErrorBoundaryProps, MicErrorBoundaryState> {\n state: MicErrorBoundaryState = { hasError: false };\n\n static getDerivedStateFromError(): MicErrorBoundaryState {\n return { hasError: true };\n }\n\n componentDidCatch(error: Error) {\n const msg = error?.message ? error.message : 'Voice input failed to start.';\n try {\n this.props.onError?.(msg);\n } catch {\n /* host error sink should never re-throw, but stay defensive */\n }\n try {\n this.props.onCancel();\n } catch {\n /* same — onCancel is \"soft\", never re-throws */\n }\n }\n\n render() {\n if (this.state.hasError) {\n return null;\n }\n return this.props.children;\n }\n}\n\nexport default MicErrorBoundary;\n"],"names":[],"mappings":";;;AAqCa,MAAA,gBAAA,SAAyB,MAAM,SAAwD,CAAA;AAAA,EAA7F,WAAA,GAAA;AAAA,IAAA,KAAA,CAAA,GAAA,SAAA,CAAA;AACL,IAA+B,aAAA,CAAA,IAAA,EAAA,OAAA,EAAA;AAAA,MAC7B,QAAU,EAAA;AAAA,KACZ,CAAA;AAAA;AAAA,EACA,OAAO,wBAAkD,GAAA;AACvD,IAAO,OAAA;AAAA,MACL,QAAU,EAAA;AAAA,KACZ;AAAA;AACF,EACA,kBAAkB,KAAc,EAAA;AA9ClC,IAAA,IAAA,EAAA,EAAA,EAAA;AA+CI,IAAA,MAAM,GAAM,GAAA,CAAA,KAAA,IAAA,IAAA,GAAA,MAAA,GAAA,KAAA,CAAO,OAAU,IAAA,KAAA,CAAM,OAAU,GAAA,8BAAA;AAC7C,IAAI,IAAA;AACF,MAAK,CAAA,EAAA,GAAA,CAAA,EAAA,GAAA,IAAA,CAAA,KAAA,EAAM,YAAX,IAAqB,GAAA,KAAA,CAAA,GAAA,EAAA,CAAA,IAAA,CAAA,EAAA,EAAA,GAAA,CAAA;AAAA,KACf,CAAA,OAAA,CAAA,EAAA;AAAA;AAGR,IAAI,IAAA;AACF,MAAA,IAAA,CAAK,MAAM,QAAS,EAAA;AAAA,KACd,CAAA,OAAA,CAAA,EAAA;AAAA;AAER;AACF,EACA,MAAS,GAAA;AACP,IAAI,IAAA,IAAA,CAAK,MAAM,QAAU,EAAA;AACvB,MAAO,OAAA,IAAA;AAAA;AAET,IAAA,OAAO,KAAK,KAAM,CAAA,QAAA;AAAA;AAEtB"}
@@ -0,0 +1,53 @@
1
+ import {gql}from'@apollo/client/index.js';const OPENCLAW_INSTANCES_QUERY = gql`
2
+ query GetOpenClawInstances($projectId: String!) {
3
+ openclawInstances(projectId: $projectId) {
4
+ userId
5
+ slug
6
+ url
7
+ status
8
+ projectId
9
+ replicas
10
+ readyReplicas
11
+ }
12
+ }
13
+ `;
14
+ const PROVISION_OPENCLAW_MUTATION = gql`
15
+ mutation ProvisionOpenClaw($input: ProvisionOpenClawInput!) {
16
+ provisionOpenClaw(input: $input) {
17
+ userId
18
+ slug
19
+ url
20
+ status
21
+ projectId
22
+ replicas
23
+ readyReplicas
24
+ }
25
+ }
26
+ `;
27
+ const START_OPENCLAW_MUTATION = gql`
28
+ mutation StartOpenClaw($projectId: String!, $userId: String!) {
29
+ startOpenClaw(projectId: $projectId, userId: $userId) {
30
+ userId
31
+ slug
32
+ url
33
+ status
34
+ projectId
35
+ }
36
+ }
37
+ `;
38
+ const DELETE_OPENCLAW_MUTATION = gql`
39
+ mutation DeleteOpenClaw($projectId: String!, $userId: String!) {
40
+ deleteOpenClaw(projectId: $projectId, userId: $userId)
41
+ }
42
+ `;
43
+ const OPENCLAW_CHAT_CONNECTION_QUERY = gql`
44
+ query OpenClawChatConnection($projectId: String!, $userId: String!) {
45
+ openclawChatConnection(projectId: $projectId, userId: $userId) {
46
+ wsUrl
47
+ token
48
+ url
49
+ proxyToken
50
+ expiresAt
51
+ }
52
+ }
53
+ `;export{DELETE_OPENCLAW_MUTATION,OPENCLAW_CHAT_CONNECTION_QUERY,OPENCLAW_INSTANCES_QUERY,PROVISION_OPENCLAW_MUTATION,START_OPENCLAW_MUTATION};//# sourceMappingURL=agentGatewayDocuments.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agentGatewayDocuments.js","sources":["../../src/graphql/agentGatewayDocuments.ts"],"sourcesContent":["import { gql } from '@apollo/client';\n\nexport const OPENCLAW_INSTANCES_QUERY = gql`\n query GetOpenClawInstances($projectId: String!) {\n openclawInstances(projectId: $projectId) {\n userId\n slug\n url\n status\n projectId\n replicas\n readyReplicas\n }\n }\n`;\n\nexport const PROVISION_OPENCLAW_MUTATION = gql`\n mutation ProvisionOpenClaw($input: ProvisionOpenClawInput!) {\n provisionOpenClaw(input: $input) {\n userId\n slug\n url\n status\n projectId\n replicas\n readyReplicas\n }\n }\n`;\n\nexport const START_OPENCLAW_MUTATION = gql`\n mutation StartOpenClaw($projectId: String!, $userId: String!) {\n startOpenClaw(projectId: $projectId, userId: $userId) {\n userId\n slug\n url\n status\n projectId\n }\n }\n`;\n\nexport const DELETE_OPENCLAW_MUTATION = gql`\n mutation DeleteOpenClaw($projectId: String!, $userId: String!) {\n deleteOpenClaw(projectId: $projectId, userId: $userId)\n }\n`;\n\nexport const OPENCLAW_CHAT_CONNECTION_QUERY = gql`\n query OpenClawChatConnection($projectId: String!, $userId: String!) {\n openclawChatConnection(projectId: $projectId, userId: $userId) {\n wsUrl\n token\n url\n proxyToken\n expiresAt\n }\n }\n`;\n"],"names":[],"mappings":"0CACO,MAAM,wBAA2B,GAAA,GAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAajC,MAAM,2BAA8B,GAAA,GAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAapC,MAAM,uBAA0B,GAAA,GAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAWhC,MAAM,wBAA2B,GAAA,GAAA;AAAA;AAAA;AAAA;AAAA;AAKjC,MAAM,8BAAiC,GAAA,GAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA"}
@@ -0,0 +1,242 @@
1
+ import {useState,useRef,useCallback,useEffect}from'react';import {useGatewayConnectMutation,useGatewayDisconnectMutation,useCreateSecretApiTokenMutation,useRevealSecretApiTokenMutation,useGetUserSystemTokenQuery}from'common/graphql';import {config}from'../config/env-config.js';import {usePrerequisiteIds}from'./usePrerequisiteIds.js';var __defProp = Object.defineProperty;
2
+ var __defProps = Object.defineProperties;
3
+ var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
4
+ var __getOwnPropSymbols = Object.getOwnPropertySymbols;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __propIsEnum = Object.prototype.propertyIsEnumerable;
7
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
8
+ var __spreadValues = (a, b) => {
9
+ for (var prop in b || (b = {}))
10
+ if (__hasOwnProp.call(b, prop))
11
+ __defNormalProp(a, prop, b[prop]);
12
+ if (__getOwnPropSymbols)
13
+ for (var prop of __getOwnPropSymbols(b)) {
14
+ if (__propIsEnum.call(b, prop))
15
+ __defNormalProp(a, prop, b[prop]);
16
+ }
17
+ return a;
18
+ };
19
+ var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
20
+ const CHANNEL_TYPE = "cdecli-serve";
21
+ function useCdecliAutoConnect(isSelected, channelId) {
22
+ const [status, setStatus] = useState("idle");
23
+ const [error, setError] = useState(null);
24
+ const [persistenceMode, setPersistenceMode] = useState(void 0);
25
+ const [activeSkill, setActiveSkill] = useState(null);
26
+ const connectingRef = useRef(false);
27
+ const connectedRef = useRef(false);
28
+ const {
29
+ orgName,
30
+ projectId,
31
+ tagId,
32
+ accountUserId,
33
+ loading: prerequisitesLoading
34
+ } = usePrerequisiteIds();
35
+ const accountId = accountUserId != null ? accountUserId : "default";
36
+ const prevChannelIdRef = useRef(channelId);
37
+ const [connectMutation] = useGatewayConnectMutation();
38
+ const [disconnectMutation] = useGatewayDisconnectMutation();
39
+ const [createTokenMutation] = useCreateSecretApiTokenMutation();
40
+ const [revealTokenMutation] = useRevealSecretApiTokenMutation();
41
+ const {
42
+ data: systemTokenData,
43
+ loading: systemTokenLoading,
44
+ refetch: refetchSystemToken
45
+ } = useGetUserSystemTokenQuery({
46
+ skip: !isSelected,
47
+ fetchPolicy: "network-only"
48
+ });
49
+ const isAlreadyHasSystemTokenError = (err) => {
50
+ const message = err instanceof Error ? err.message : String(err != null ? err : "");
51
+ return /already have a system token/i.test(message);
52
+ };
53
+ const obtainSystemToken = useCallback(async () => {
54
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j;
55
+ try {
56
+ let tokenId = (_b = (_a = systemTokenData == null ? void 0 : systemTokenData.getUserSystemToken) == null ? void 0 : _a.id) != null ? _b : null;
57
+ if (!tokenId) {
58
+ if (!orgName || !projectId) {
59
+ const refetchResult = await refetchSystemToken().catch(() => null);
60
+ tokenId = (_e = (_d = (_c = refetchResult == null ? void 0 : refetchResult.data) == null ? void 0 : _c.getUserSystemToken) == null ? void 0 : _d.id) != null ? _e : null;
61
+ if (!tokenId) return null;
62
+ } else {
63
+ try {
64
+ const {
65
+ data: createData
66
+ } = await createTokenMutation({
67
+ variables: {
68
+ input: {
69
+ name: "System Token",
70
+ expiryInSeconds: 31536e3,
71
+ isSystemToken: true,
72
+ environmentTagId: tagId,
73
+ orgName: orgName || "default",
74
+ projectId: projectId || "default"
75
+ }
76
+ }
77
+ });
78
+ tokenId = (_g = (_f = createData == null ? void 0 : createData.createSecretApiToken) == null ? void 0 : _f.id) != null ? _g : null;
79
+ await refetchSystemToken();
80
+ } catch (createErr) {
81
+ if (!isAlreadyHasSystemTokenError(createErr)) throw createErr;
82
+ const refetchResult = await refetchSystemToken().catch(() => null);
83
+ tokenId = (_j = (_i = (_h = refetchResult == null ? void 0 : refetchResult.data) == null ? void 0 : _h.getUserSystemToken) == null ? void 0 : _i.id) != null ? _j : null;
84
+ if (!tokenId) {
85
+ console.warn("[useCdecliAutoConnect] System token already exists but refetch returned no id; retrying");
86
+ return null;
87
+ }
88
+ }
89
+ }
90
+ }
91
+ if (!tokenId) return null;
92
+ const {
93
+ data: revealData
94
+ } = await revealTokenMutation({
95
+ variables: {
96
+ id: tokenId
97
+ }
98
+ });
99
+ return (revealData == null ? void 0 : revealData.revealSecretApiToken) || null;
100
+ } catch (err) {
101
+ console.error("[useCdecliAutoConnect] obtainSystemToken error:", err);
102
+ return null;
103
+ }
104
+ }, [systemTokenData, createTokenMutation, revealTokenMutation, refetchSystemToken, orgName, projectId, tagId]);
105
+ useEffect(() => {
106
+ if (!isSelected) {
107
+ connectingRef.current = false;
108
+ return;
109
+ }
110
+ if (prerequisitesLoading || !accountUserId) return;
111
+ if (systemTokenLoading) return;
112
+ let isSilentReconnect = false;
113
+ if (connectedRef.current || connectingRef.current) {
114
+ if (channelId && prevChannelIdRef.current !== channelId) {
115
+ isSilentReconnect = connectedRef.current;
116
+ prevChannelIdRef.current = channelId;
117
+ connectedRef.current = false;
118
+ } else {
119
+ return;
120
+ }
121
+ }
122
+ const doConnect = async () => {
123
+ var _a;
124
+ connectingRef.current = true;
125
+ if (!isSilentReconnect) setStatus("connecting");
126
+ setError(null);
127
+ try {
128
+ const endpoint = config.CDECLI_AGENT_ENDPOINT || "https://cdecli-agent.cdebase.dev";
129
+ const token = await obtainSystemToken();
130
+ if (!token) {
131
+ console.warn("[useCdecliAutoConnect] No system token, connecting without auth");
132
+ }
133
+ const {
134
+ data
135
+ } = await connectMutation({
136
+ variables: {
137
+ input: {
138
+ channelType: CHANNEL_TYPE,
139
+ accountId,
140
+ options: __spreadValues(__spreadValues({
141
+ endpoint: endpoint.trim()
142
+ }, token ? {
143
+ token
144
+ } : {}), channelId ? {
145
+ chatId: channelId
146
+ } : {})
147
+ }
148
+ }
149
+ });
150
+ const result = data == null ? void 0 : data.gatewayConnect;
151
+ console.log("[useCdecliAutoConnect] connectMutation result (account=%s):", accountId, JSON.stringify(result));
152
+ if (result == null ? void 0 : result.persistenceMode) {
153
+ setPersistenceMode(result.persistenceMode);
154
+ }
155
+ if ((result == null ? void 0 : result.state) === "CONNECTED" || (result == null ? void 0 : result.connected)) {
156
+ setStatus("connected");
157
+ connectedRef.current = true;
158
+ } else if ((result == null ? void 0 : result.state) === "ERROR") {
159
+ setStatus("error");
160
+ setError((_a = result.lastError) != null ? _a : "Failed to connect to CDeCLI agent");
161
+ connectedRef.current = false;
162
+ } else {
163
+ setStatus("connected");
164
+ connectedRef.current = true;
165
+ }
166
+ } catch (err) {
167
+ const msg = err instanceof Error ? err.message : String(err);
168
+ console.error("[useCdecliAutoConnect] connect error:", msg);
169
+ setStatus("error");
170
+ setError(msg);
171
+ connectedRef.current = false;
172
+ } finally {
173
+ connectingRef.current = false;
174
+ }
175
+ };
176
+ doConnect();
177
+ }, [isSelected, obtainSystemToken, connectMutation, accountId, prerequisitesLoading, accountUserId, channelId, systemTokenLoading]);
178
+ const connectWithSkill = useCallback(async (skill, model, systemPrompt, skillId) => {
179
+ var _a;
180
+ console.log("[useCdecliAutoConnect] connectWithSkill called with skill=%s skillId=%s model=%s systemPrompt=%d chars", skill, skillId != null ? skillId : "", model, (_a = systemPrompt == null ? void 0 : systemPrompt.length) != null ? _a : 0);
181
+ setStatus("connecting");
182
+ setError(null);
183
+ try {
184
+ const endpoint = config.CDECLI_AGENT_ENDPOINT || "https://cdecli-agent.cdebase.dev";
185
+ const token = await obtainSystemToken();
186
+ console.log("[useCdecliAutoConnect] connectWithSkill sending mutation with skill=%s", skill);
187
+ const {
188
+ data
189
+ } = await connectMutation({
190
+ variables: {
191
+ input: {
192
+ channelType: CHANNEL_TYPE,
193
+ accountId,
194
+ options: __spreadValues(__spreadValues(__spreadValues(__spreadValues(__spreadProps(__spreadValues({
195
+ endpoint: endpoint.trim()
196
+ }, token ? {
197
+ token
198
+ } : {}), {
199
+ skill
200
+ }), skillId ? {
201
+ skillId
202
+ } : {}), model ? {
203
+ model
204
+ } : {}), systemPrompt ? {
205
+ systemPrompt
206
+ } : {}), channelId ? {
207
+ chatId: channelId
208
+ } : {})
209
+ }
210
+ }
211
+ });
212
+ console.log("[useCdecliAutoConnect] connectWithSkill result:", JSON.stringify(data == null ? void 0 : data.gatewayConnect));
213
+ const result = data == null ? void 0 : data.gatewayConnect;
214
+ if (result == null ? void 0 : result.persistenceMode) {
215
+ setPersistenceMode(result.persistenceMode);
216
+ }
217
+ if ((result == null ? void 0 : result.state) === "CONNECTED" || (result == null ? void 0 : result.connected)) {
218
+ setStatus("connected");
219
+ connectedRef.current = true;
220
+ setActiveSkill(skill);
221
+ } else {
222
+ setStatus("connected");
223
+ connectedRef.current = true;
224
+ setActiveSkill(skill);
225
+ }
226
+ } catch (err) {
227
+ const msg = err instanceof Error ? err.message : String(err);
228
+ console.error("[useCdecliAutoConnect] connectWithSkill error:", msg);
229
+ setStatus("error");
230
+ setError(msg);
231
+ }
232
+ }, [connectMutation, obtainSystemToken, accountId, channelId]);
233
+ return {
234
+ channelConnected: status === "connected",
235
+ status,
236
+ error,
237
+ persistenceMode,
238
+ activeSkill,
239
+ accountId,
240
+ connectWithSkill
241
+ };
242
+ }export{useCdecliAutoConnect};//# sourceMappingURL=useCdecliAutoConnect.js.map