@adminide-stack/yantra-mobile 12.0.28-alpha.8 → 12.0.28-alpha.81
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/lib/api/stt.js +54 -0
- package/lib/api/stt.js.map +1 -0
- package/lib/assets/icon.png +0 -0
- package/lib/components/CustomDrawer.js +479 -0
- package/lib/components/CustomDrawer.js.map +1 -0
- package/lib/components/GatewayConnector/GatewayConnector.js +18 -0
- package/lib/components/GatewayConnector/GatewayConnector.js.map +1 -0
- package/lib/components/GatewayToolbarButtonMobile.js +84 -0
- package/lib/components/GatewayToolbarButtonMobile.js.map +1 -0
- package/lib/components/NavigationHeader/NavigationHeader.js +214 -0
- package/lib/components/NavigationHeader/NavigationHeader.js.map +1 -0
- package/lib/components/ThinkingIndicator.js +55 -0
- package/lib/components/ThinkingIndicator.js.map +1 -0
- package/lib/components/YantraBrandLoader.js +94 -0
- package/lib/components/YantraBrandLoader.js.map +1 -0
- package/lib/compute.js +114 -5
- package/lib/compute.js.map +1 -1
- package/lib/config/constants.js +18 -0
- package/lib/config/constants.js.map +1 -0
- package/lib/config/env-config.js +75 -19
- package/lib/config/env-config.js.map +1 -1
- package/lib/contexts/CdecliConnectionContext.js +47 -0
- package/lib/contexts/CdecliConnectionContext.js.map +1 -0
- package/lib/contexts/GatewayContext.js +77 -0
- package/lib/contexts/GatewayContext.js.map +1 -0
- package/lib/features/audio-input/AudioRecorderPanel.js +220 -0
- package/lib/features/audio-input/AudioRecorderPanel.js.map +1 -0
- package/lib/features/audio-input/MicErrorBoundary.js +34 -0
- package/lib/features/audio-input/MicErrorBoundary.js.map +1 -0
- package/lib/features/audio-input/useAudioPermission.js +24 -0
- package/lib/features/audio-input/useAudioPermission.js.map +1 -0
- package/lib/graphql/agentGatewayDocuments.js +53 -0
- package/lib/graphql/agentGatewayDocuments.js.map +1 -0
- package/lib/hooks/useAccountDefaultSettings.js +38 -0
- package/lib/hooks/useAccountDefaultSettings.js.map +1 -0
- package/lib/hooks/useCdecliAutoConnect.js +244 -0
- package/lib/hooks/useCdecliAutoConnect.js.map +1 -0
- package/lib/hooks/useCdecliChannel.js +161 -0
- package/lib/hooks/useCdecliChannel.js.map +1 -0
- package/lib/hooks/useChatApi.js +390 -171
- package/lib/hooks/useChatApi.js.map +1 -1
- package/lib/hooks/useChatStream.js +179 -137
- package/lib/hooks/useChatStream.js.map +1 -1
- package/lib/hooks/useGatewayConnection.js +123 -0
- package/lib/hooks/useGatewayConnection.js.map +1 -0
- package/lib/hooks/useGatewayRegistry.js +28 -0
- package/lib/hooks/useGatewayRegistry.js.map +1 -0
- package/lib/hooks/usePrerequisiteIds.js +209 -0
- package/lib/hooks/usePrerequisiteIds.js.map +1 -0
- package/lib/hooks/useWorkspaceProvisioner.js +236 -0
- package/lib/hooks/useWorkspaceProvisioner.js.map +1 -0
- package/lib/index.js +1 -1
- package/lib/index.js.map +1 -1
- package/lib/routes.json +120 -5
- package/lib/screens/Chat/index.js +409 -0
- package/lib/screens/Chat/index.js.map +1 -0
- package/lib/screens/ChatHistory/index.js +56 -0
- package/lib/screens/ChatHistory/index.js.map +1 -0
- package/lib/screens/Home/HomeScreen.js +364 -144
- package/lib/screens/Home/HomeScreen.js.map +1 -1
- package/lib/screens/Home/components/ChatHistoryLanding.js +487 -0
- package/lib/screens/Home/components/ChatHistoryLanding.js.map +1 -0
- package/lib/screens/Home/components/DeepSearchModal.js +349 -0
- package/lib/screens/Home/components/DeepSearchModal.js.map +1 -0
- package/lib/screens/Home/deepSearchUtils.js +41 -0
- package/lib/screens/Home/deepSearchUtils.js.map +1 -0
- package/lib/screens/NewChat/index.js +43 -0
- package/lib/screens/NewChat/index.js.map +1 -0
- package/lib/services/agentSessionManager.js +451 -0
- package/lib/services/agentSessionManager.js.map +1 -0
- package/lib/services/gatewayApiKeyBridge.js +4 -0
- package/lib/services/gatewayApiKeyBridge.js.map +1 -0
- package/lib/services/gatewayClient.js +470 -0
- package/lib/services/gatewayClient.js.map +1 -0
- package/lib/theme/mobileTokens.js +18 -0
- package/lib/theme/mobileTokens.js.map +1 -0
- package/lib/utils/cdecodeUri.js +68 -0
- package/lib/utils/cdecodeUri.js.map +1 -0
- package/lib/utils/gatewaySelectionStorage.js +21 -0
- package/lib/utils/gatewaySelectionStorage.js.map +1 -0
- package/lib/utils/syncMobileOrgRouteContext.js +61 -0
- package/lib/utils/syncMobileOrgRouteContext.js.map +1 -0
- package/package.json +7 -3
- package/lib/api/chatApi.js +0 -102
- package/lib/api/chatApi.js.map +0 -1
|
@@ -0,0 +1,220 @@
|
|
|
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 recorder.prepareToRecordAsync();
|
|
79
|
+
if (cancelled || !isMountedRef.current) return;
|
|
80
|
+
recorder.record();
|
|
81
|
+
} catch (err) {
|
|
82
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
83
|
+
onError == null ? void 0 : onError(msg);
|
|
84
|
+
onCancel();
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
void start();
|
|
88
|
+
return () => {
|
|
89
|
+
cancelled = true;
|
|
90
|
+
};
|
|
91
|
+
}, []);
|
|
92
|
+
useEffect(() => {
|
|
93
|
+
return () => {
|
|
94
|
+
isMountedRef.current = false;
|
|
95
|
+
void stopAndCleanup();
|
|
96
|
+
};
|
|
97
|
+
}, []);
|
|
98
|
+
useEffect(() => {
|
|
99
|
+
var _a;
|
|
100
|
+
if (!recorderState.isRecording) return;
|
|
101
|
+
const elapsedMs = (_a = recorderState.durationMillis) != null ? _a : 0;
|
|
102
|
+
if (elapsedMs >= MAX_DURATION_MS && !sendInFlightRef.current) {
|
|
103
|
+
void finalize();
|
|
104
|
+
}
|
|
105
|
+
}, [recorderState.isRecording, recorderState.durationMillis, finalize]);
|
|
106
|
+
const handleCancelPress = useCallback(async () => {
|
|
107
|
+
if (isTranscribing) return;
|
|
108
|
+
await stopAndCleanup();
|
|
109
|
+
onCancel();
|
|
110
|
+
}, [isTranscribing, stopAndCleanup, onCancel]);
|
|
111
|
+
const handleSendPress = useCallback(() => {
|
|
112
|
+
void finalize();
|
|
113
|
+
}, [finalize]);
|
|
114
|
+
const formatted = useMemo(() => {
|
|
115
|
+
var _a;
|
|
116
|
+
const totalSeconds = Math.floor(((_a = recorderState.durationMillis) != null ? _a : 0) / 1e3);
|
|
117
|
+
const m = Math.floor(totalSeconds / 60);
|
|
118
|
+
const s = totalSeconds % 60;
|
|
119
|
+
return `${m}:${s.toString().padStart(2, "0")}`;
|
|
120
|
+
}, [recorderState.durationMillis]);
|
|
121
|
+
const bg = isDark ? "#0f172a" : "#ffffff";
|
|
122
|
+
const border = isDark ? "rgba(148,163,184,0.18)" : "#e4e4e7";
|
|
123
|
+
const fg = isDark ? "#e5e7eb" : "#18181b";
|
|
124
|
+
const muted = isDark ? "#94a3b8" : "#71717a";
|
|
125
|
+
const cancelBg = isDark ? "rgba(148,163,184,0.08)" : "#f4f4f5";
|
|
126
|
+
const showSpinner = isTranscribing;
|
|
127
|
+
const isRecording = recorderState.isRecording && !showSpinner;
|
|
128
|
+
const statusLabel = showSpinner ? "Transcribing\u2026" : isRecording ? formatted : "Initializing\u2026";
|
|
129
|
+
return /* @__PURE__ */ jsxs(View, { accessibilityRole: "summary", accessibilityLabel: "Voice recorder", style: [styles.container, {
|
|
130
|
+
backgroundColor: bg,
|
|
131
|
+
borderColor: border
|
|
132
|
+
}], children: [
|
|
133
|
+
/* @__PURE__ */ jsxs(View, { style: styles.statusRow, children: [
|
|
134
|
+
showSpinner ? /* @__PURE__ */ jsx(ActivityIndicator, { size: "small", color: fg }) : /* @__PURE__ */ jsx(Animated.View, { style: [styles.recDot, {
|
|
135
|
+
opacity: isRecording ? pulse : 0.35
|
|
136
|
+
}] }),
|
|
137
|
+
/* @__PURE__ */ jsx(Animated.Text, { style: [styles.timer, {
|
|
138
|
+
color: fg
|
|
139
|
+
}], accessibilityLiveRegion: "polite", children: statusLabel }),
|
|
140
|
+
isRecording && /* @__PURE__ */ jsx(Animated.Text, { style: [styles.maxLabel, {
|
|
141
|
+
color: muted
|
|
142
|
+
}], children: "/ 3:00" })
|
|
143
|
+
] }),
|
|
144
|
+
/* @__PURE__ */ jsxs(View, { style: styles.actionsRow, children: [
|
|
145
|
+
/* @__PURE__ */ jsx(Pressable, { onPress: handleCancelPress, disabled: isTranscribing, accessibilityLabel: "Cancel recording", accessibilityRole: "button", style: ({
|
|
146
|
+
pressed
|
|
147
|
+
}) => [styles.iconBtn, {
|
|
148
|
+
backgroundColor: cancelBg,
|
|
149
|
+
borderColor: border,
|
|
150
|
+
opacity: isTranscribing ? 0.4 : pressed ? 0.7 : 1
|
|
151
|
+
}], children: /* @__PURE__ */ jsx(Ionicons, { name: "close", size: 16, color: fg }) }),
|
|
152
|
+
/* @__PURE__ */ jsx(Pressable, { onPress: handleSendPress, disabled: isTranscribing || !isRecording, accessibilityLabel: "Stop and transcribe", accessibilityRole: "button", style: ({
|
|
153
|
+
pressed
|
|
154
|
+
}) => [styles.sendBtn, {
|
|
155
|
+
opacity: isTranscribing ? 0.7 : !isRecording ? 0.5 : pressed ? 0.85 : 1
|
|
156
|
+
}], children: isTranscribing ? /* @__PURE__ */ jsx(ActivityIndicator, { size: "small", color: "#ffffff" }) : /* @__PURE__ */ jsx(Feather, { name: "arrow-up", size: 16, color: "#ffffff" }) })
|
|
157
|
+
] })
|
|
158
|
+
] });
|
|
159
|
+
}
|
|
160
|
+
const styles = StyleSheet.create({
|
|
161
|
+
container: {
|
|
162
|
+
flexDirection: "row",
|
|
163
|
+
alignItems: "center",
|
|
164
|
+
justifyContent: "space-between",
|
|
165
|
+
borderRadius: 24,
|
|
166
|
+
borderWidth: 1,
|
|
167
|
+
paddingVertical: 10,
|
|
168
|
+
paddingHorizontal: 14,
|
|
169
|
+
gap: 12,
|
|
170
|
+
shadowColor: "#0f172a",
|
|
171
|
+
shadowOpacity: 0.08,
|
|
172
|
+
shadowRadius: 12,
|
|
173
|
+
shadowOffset: {
|
|
174
|
+
width: 0,
|
|
175
|
+
height: 4
|
|
176
|
+
},
|
|
177
|
+
elevation: 4
|
|
178
|
+
},
|
|
179
|
+
statusRow: {
|
|
180
|
+
flexDirection: "row",
|
|
181
|
+
alignItems: "center",
|
|
182
|
+
gap: 8,
|
|
183
|
+
flexShrink: 1
|
|
184
|
+
},
|
|
185
|
+
recDot: {
|
|
186
|
+
width: 8,
|
|
187
|
+
height: 8,
|
|
188
|
+
borderRadius: 4,
|
|
189
|
+
backgroundColor: "#ef4444"
|
|
190
|
+
},
|
|
191
|
+
timer: {
|
|
192
|
+
fontSize: 13,
|
|
193
|
+
fontWeight: "600",
|
|
194
|
+
fontVariant: ["tabular-nums"]
|
|
195
|
+
},
|
|
196
|
+
maxLabel: {
|
|
197
|
+
fontSize: 11
|
|
198
|
+
},
|
|
199
|
+
actionsRow: {
|
|
200
|
+
flexDirection: "row",
|
|
201
|
+
alignItems: "center",
|
|
202
|
+
gap: 8
|
|
203
|
+
},
|
|
204
|
+
iconBtn: {
|
|
205
|
+
width: 32,
|
|
206
|
+
height: 32,
|
|
207
|
+
borderRadius: 16,
|
|
208
|
+
borderWidth: 1,
|
|
209
|
+
alignItems: "center",
|
|
210
|
+
justifyContent: "center"
|
|
211
|
+
},
|
|
212
|
+
sendBtn: {
|
|
213
|
+
width: 32,
|
|
214
|
+
height: 32,
|
|
215
|
+
borderRadius: 16,
|
|
216
|
+
alignItems: "center",
|
|
217
|
+
justifyContent: "center",
|
|
218
|
+
backgroundColor: "#18181b"
|
|
219
|
+
}
|
|
220
|
+
});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 { AudioModule, 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 * Host screen already called `requestMicPermission()` (expo-audio\n * docs flow) before mounting this panel — go straight to\n * prepare + record.\n */\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;AAMF,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;AAlMlB,IAAA,IAAA,EAAA;AAmMI,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;AAjNlC,IAAA,IAAA,EAAA;AAkNI,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,24 @@
|
|
|
1
|
+
import {AudioModule,setAudioModeAsync}from'expo-audio';async function requestMicPermission() {
|
|
2
|
+
try {
|
|
3
|
+
const status = await AudioModule.requestRecordingPermissionsAsync();
|
|
4
|
+
if (!status.granted) {
|
|
5
|
+
return {
|
|
6
|
+
granted: false,
|
|
7
|
+
error: "Microphone permission is required for voice input."
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
await setAudioModeAsync({
|
|
11
|
+
playsInSilentMode: true,
|
|
12
|
+
allowsRecording: true
|
|
13
|
+
});
|
|
14
|
+
return {
|
|
15
|
+
granted: true
|
|
16
|
+
};
|
|
17
|
+
} catch (err) {
|
|
18
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
19
|
+
return {
|
|
20
|
+
granted: false,
|
|
21
|
+
error: msg
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
}export{requestMicPermission};//# sourceMappingURL=useAudioPermission.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useAudioPermission.js","sources":["../../../src/features/audio-input/useAudioPermission.ts"],"sourcesContent":["import { AudioModule, setAudioModeAsync } from 'expo-audio';\n\nexport type MicPermissionResult = {\n granted: boolean;\n error?: string;\n};\n\n/**\n * Official expo-audio v53 recording flow — called when the user taps the mic\n * (user-initiated), not at screen mount:\n *\n * 1. AudioModule.requestRecordingPermissionsAsync()\n * 2. setAudioModeAsync({ playsInSilentMode: true, allowsRecording: true })\n *\n * `expo-audio`'s native `record()` checks permission state set by step 1.\n * iOS `setAudioModeAsync` alone can show the system dialog but does NOT update\n * expo-audio's internal grant flag → `AudioPermissionsException` on `record()`.\n *\n * Requires `NSMicrophoneUsageDescription` in the built Info.plist (expo-audio\n * plugin + ios.infoPlist). Without it, step 1 can native-crash in older builds.\n */\nexport async function requestMicPermission(): Promise<MicPermissionResult> {\n try {\n const status = await AudioModule.requestRecordingPermissionsAsync();\n if (!status.granted) {\n return {\n granted: false,\n error: 'Microphone permission is required for voice input.',\n };\n }\n await setAudioModeAsync({\n playsInSilentMode: true,\n allowsRecording: true,\n });\n return { granted: true };\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n return { granted: false, error: msg };\n }\n}\n"],"names":[],"mappings":"uDAoBA,eAAsB,oBAAqD,GAAA;AACzE,EAAI,IAAA;AACF,IAAM,MAAA,MAAA,GAAS,MAAM,WAAA,CAAY,gCAAiC,EAAA;AAClE,IAAI,IAAA,CAAC,OAAO,OAAS,EAAA;AACnB,MAAO,OAAA;AAAA,QACL,OAAS,EAAA,KAAA;AAAA,QACT,KAAO,EAAA;AAAA,OACT;AAAA;AAEF,IAAA,MAAM,iBAAkB,CAAA;AAAA,MACtB,iBAAmB,EAAA,IAAA;AAAA,MACnB,eAAiB,EAAA;AAAA,KAClB,CAAA;AACD,IAAO,OAAA;AAAA,MACL,OAAS,EAAA;AAAA,KACX;AAAA,WACO,GAAK,EAAA;AACZ,IAAA,MAAM,MAAM,GAAe,YAAA,KAAA,GAAQ,GAAI,CAAA,OAAA,GAAU,OAAO,GAAG,CAAA;AAC3D,IAAO,OAAA;AAAA,MACL,OAAS,EAAA,KAAA;AAAA,MACT,KAAO,EAAA;AAAA,KACT;AAAA;AAEJ"}
|
|
@@ -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,38 @@
|
|
|
1
|
+
import {useSettings}from'@adminide-stack/platform-client';import {ContributionSchemaId}from'common';const ACCOUNT_DEFAULT_OPTIONS = {
|
|
2
|
+
schemaId: ContributionSchemaId.Configuration,
|
|
3
|
+
configKey: "account.default",
|
|
4
|
+
includeMarketplace: true
|
|
5
|
+
};
|
|
6
|
+
function readStringField(value) {
|
|
7
|
+
return typeof value === "string" ? value.trim() : "";
|
|
8
|
+
}
|
|
9
|
+
function readProjectField(value) {
|
|
10
|
+
if (!value) return "";
|
|
11
|
+
if (typeof value.project === "string") return value.project.trim();
|
|
12
|
+
if (typeof value.projectId === "string") return value.projectId.trim();
|
|
13
|
+
return "";
|
|
14
|
+
}
|
|
15
|
+
function useAccountDefaultSettings(params) {
|
|
16
|
+
var _a;
|
|
17
|
+
const query = useSettings({
|
|
18
|
+
resourceUri: params.resourceUri,
|
|
19
|
+
skip: (_a = params.skip) != null ? _a : !params.resourceUri,
|
|
20
|
+
options: ACCOUNT_DEFAULT_OPTIONS
|
|
21
|
+
});
|
|
22
|
+
const {
|
|
23
|
+
data,
|
|
24
|
+
loading,
|
|
25
|
+
error
|
|
26
|
+
} = query;
|
|
27
|
+
const {
|
|
28
|
+
refetch
|
|
29
|
+
} = query;
|
|
30
|
+
return {
|
|
31
|
+
organization: readStringField(data == null ? void 0 : data.organization),
|
|
32
|
+
project: readProjectField(data),
|
|
33
|
+
projectId: readStringField(data == null ? void 0 : data.projectId),
|
|
34
|
+
loading,
|
|
35
|
+
error,
|
|
36
|
+
refetch
|
|
37
|
+
};
|
|
38
|
+
}export{useAccountDefaultSettings};//# sourceMappingURL=useAccountDefaultSettings.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useAccountDefaultSettings.js","sources":["../../src/hooks/useAccountDefaultSettings.ts"],"sourcesContent":["import { useSettings } from '@adminide-stack/platform-client';\nimport { ContributionSchemaId } from 'common';\nimport type { SettingsUriInput } from '../utils/cdecodeUri';\n\n/**\n * Browser reference (`packages-modules/account/browser/src/hooks/usePrerequisiteId.ts`):\n *\n * ```ts\n * useSettingsLoader({\n * configKey: 'account.default',\n * schemaId: ContributionSchemaId.Configuration,\n * resourceUri: undefined,\n * includeMarketplace: true,\n * });\n * ```\n *\n * On mobile there is no Remix loader, so we use `useSettings` — the same GraphQL\n * `pageSettings` query that `useSettingsLoader` runs when `immediate: true`.\n */\nconst ACCOUNT_DEFAULT_OPTIONS = {\n schemaId: ContributionSchemaId.Configuration,\n configKey: 'account.default',\n includeMarketplace: true,\n} as const;\n\nexport type AccountDefaultSettings = {\n organization: string;\n project: string;\n projectId: string;\n};\n\nfunction readStringField(value: unknown): string {\n return typeof value === 'string' ? value.trim() : '';\n}\n\nfunction readProjectField(value: { project?: unknown; projectId?: unknown } | undefined): string {\n if (!value) return '';\n if (typeof value.project === 'string') return value.project.trim();\n if (typeof value.projectId === 'string') return value.projectId.trim();\n return '';\n}\n\n/**\n * Loads `account.default` for a resource URI (org context, account bootstrap, etc.).\n */\nexport function useAccountDefaultSettings(params: { resourceUri?: SettingsUriInput; skip?: boolean }) {\n const query = useSettings<{\n organization?: string;\n project?: string;\n projectId?: string;\n }>({\n resourceUri: params.resourceUri as never,\n skip: params.skip ?? !params.resourceUri,\n options: ACCOUNT_DEFAULT_OPTIONS,\n });\n\n const { data, loading, error } = query;\n const { refetch } = query as typeof query & { refetch?: () => Promise<unknown> };\n\n return {\n organization: readStringField(data?.organization),\n project: readProjectField(data),\n projectId: readStringField(data?.projectId),\n loading,\n error,\n refetch,\n };\n}\n"],"names":[],"mappings":"oGAmBA,MAAM,uBAA0B,GAAA;AAAA,EAC9B,UAAU,oBAAqB,CAAA,aAAA;AAAA,EAC/B,SAAW,EAAA,iBAAA;AAAA,EACX,kBAAoB,EAAA;AACtB,CAAA;AAMA,SAAS,gBAAgB,KAAwB,EAAA;AAC/C,EAAA,OAAO,OAAO,KAAA,KAAU,QAAW,GAAA,KAAA,CAAM,MAAS,GAAA,EAAA;AACpD;AACA,SAAS,iBAAiB,KAGH,EAAA;AACrB,EAAI,IAAA,CAAC,OAAc,OAAA,EAAA;AACnB,EAAA,IAAI,OAAO,KAAM,CAAA,OAAA,KAAY,UAAiB,OAAA,KAAA,CAAM,QAAQ,IAAK,EAAA;AACjE,EAAA,IAAI,OAAO,KAAM,CAAA,SAAA,KAAc,UAAiB,OAAA,KAAA,CAAM,UAAU,IAAK,EAAA;AACrE,EAAO,OAAA,EAAA;AACT;AAKO,SAAS,0BAA0B,MAGvC,EAAA;AAhDH,EAAA,IAAA,EAAA;AAiDE,EAAA,MAAM,QAAQ,WAIX,CAAA;AAAA,IACD,aAAa,MAAO,CAAA,WAAA;AAAA,IACpB,IAAM,EAAA,CAAA,EAAA,GAAA,MAAA,CAAO,IAAP,KAAA,IAAA,GAAA,EAAA,GAAe,CAAC,MAAO,CAAA,WAAA;AAAA,IAC7B,OAAS,EAAA;AAAA,GACV,CAAA;AACD,EAAM,MAAA;AAAA,IACJ,IAAA;AAAA,IACA,OAAA;AAAA,IACA;AAAA,GACE,GAAA,KAAA;AACJ,EAAM,MAAA;AAAA,IACJ;AAAA,GACE,GAAA,KAAA;AAGJ,EAAO,OAAA;AAAA,IACL,YAAA,EAAc,eAAgB,CAAA,IAAA,IAAA,IAAA,GAAA,MAAA,GAAA,IAAA,CAAM,YAAY,CAAA;AAAA,IAChD,OAAA,EAAS,iBAAiB,IAAI,CAAA;AAAA,IAC9B,SAAA,EAAW,eAAgB,CAAA,IAAA,IAAA,IAAA,GAAA,MAAA,GAAA,IAAA,CAAM,SAAS,CAAA;AAAA,IAC1C,OAAA;AAAA,IACA,KAAA;AAAA,IACA;AAAA,GACF;AACF"}
|