@ateam-ai-mobile/ui 0.1.5 → 0.1.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.mts +325 -21
- package/dist/index.d.ts +325 -21
- package/dist/index.js +1127 -176
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1120 -177
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -3
- package/src/WorkspaceBanner.tsx +71 -0
- package/src/auth/HandoffScreen.tsx +94 -0
- package/src/auth/JoinScreen.tsx +145 -0
- package/src/auth/LoginScreen.tsx +181 -0
- package/src/auth/OpenJoinScreen.tsx +131 -0
- package/src/auth/handoffExchange.ts +49 -0
- package/src/auth/inviteJoin.ts +44 -0
- package/src/auth/login.ts +57 -0
- package/src/auth/openJoin.ts +56 -0
- package/src/chat/MessageBubble.tsx +3 -0
- package/src/index.ts +38 -1
- package/src/plugin/Plugin.tsx +14 -9
- package/src/providers.ts +44 -0
- package/src/voice/VoiceCallSheet.tsx +4 -32
- package/src/voice/VoiceMicIndicator.tsx +99 -0
- package/src/voice/VoiceSessionHost.tsx +77 -0
- package/src/voice/useVoiceCall.ts +40 -231
- package/src/voice/voiceSession.ts +628 -0
package/dist/index.js
CHANGED
|
@@ -34,9 +34,13 @@ __export(index_exports, {
|
|
|
34
34
|
ChatInput: () => ChatInput,
|
|
35
35
|
DaySeparator: () => DaySeparator,
|
|
36
36
|
EmbeddedPluginWebView: () => EmbeddedPluginWebView,
|
|
37
|
+
HandoffScreen: () => HandoffScreen,
|
|
37
38
|
JobProgressBar: () => JobProgressBar,
|
|
39
|
+
JoinScreen: () => JoinScreen,
|
|
38
40
|
Lightbox: () => Lightbox,
|
|
41
|
+
LoginScreen: () => LoginScreen,
|
|
39
42
|
MessageBubble: () => MessageBubble,
|
|
43
|
+
OpenJoinScreen: () => OpenJoinScreen,
|
|
40
44
|
Plugin: () => Plugin,
|
|
41
45
|
PluginSurface: () => PluginSurface,
|
|
42
46
|
TableCardBlock: () => TableCardBlock,
|
|
@@ -44,17 +48,31 @@ __export(index_exports, {
|
|
|
44
48
|
TypingIndicator: () => TypingIndicator,
|
|
45
49
|
UIActionsRow: () => UIActionsRow,
|
|
46
50
|
VoiceCallSheet: () => VoiceCallSheet,
|
|
51
|
+
VoiceMicIndicator: () => VoiceMicIndicator,
|
|
52
|
+
VoiceSessionHost: () => VoiceSessionHost,
|
|
47
53
|
WidgetHost: () => WidgetHost,
|
|
48
54
|
WidgetWebView: () => WidgetWebView,
|
|
55
|
+
WorkspaceBanner: () => WorkspaceBanner,
|
|
49
56
|
buildPluginBridgeJS: () => buildPluginBridgeJS,
|
|
57
|
+
completeInviteJoin: () => completeInviteJoin,
|
|
58
|
+
completeOpenJoin: () => completeOpenJoin,
|
|
59
|
+
completeProviderSignIn: () => completeProviderSignIn,
|
|
60
|
+
googleClientIds: () => googleClientIds,
|
|
61
|
+
handoffErrorMessage: () => handoffErrorMessage,
|
|
62
|
+
inviteHeadline: () => inviteHeadline,
|
|
63
|
+
openJoinErrorMessage: () => openJoinErrorMessage,
|
|
50
64
|
openWidget: () => openWidget,
|
|
65
|
+
runHandoffExchange: () => runHandoffExchange,
|
|
51
66
|
setClipboardProvider: () => setClipboardProvider,
|
|
67
|
+
setOAuthProvider: () => setOAuthProvider,
|
|
52
68
|
setPluginWebViewExtraHosts: () => setPluginWebViewExtraHosts,
|
|
53
69
|
setUIActionNavigator: () => setUIActionNavigator,
|
|
70
|
+
shouldRecoverToDefaultTenant: () => shouldRecoverToDefaultTenant,
|
|
54
71
|
useLightboxStore: () => useLightboxStore,
|
|
55
72
|
usePluginBridge: () => usePluginBridge,
|
|
56
73
|
useVoiceCall: () => useVoiceCall,
|
|
57
|
-
useWidgetHostStore: () => useWidgetHostStore
|
|
74
|
+
useWidgetHostStore: () => useWidgetHostStore,
|
|
75
|
+
voiceSession: () => voiceSession
|
|
58
76
|
});
|
|
59
77
|
module.exports = __toCommonJS(index_exports);
|
|
60
78
|
|
|
@@ -261,6 +279,13 @@ var import_core7 = require("@ateam-ai-mobile/core");
|
|
|
261
279
|
var import_plugin_runtime5 = require("@ateam-ai-mobile/plugin-runtime");
|
|
262
280
|
|
|
263
281
|
// src/providers.ts
|
|
282
|
+
var _oauth = null;
|
|
283
|
+
function setOAuthProvider(oauth) {
|
|
284
|
+
_oauth = oauth;
|
|
285
|
+
}
|
|
286
|
+
function getOAuthProvider() {
|
|
287
|
+
return _oauth;
|
|
288
|
+
}
|
|
264
289
|
var _clipboard = null;
|
|
265
290
|
function setClipboardProvider(clipboard) {
|
|
266
291
|
_clipboard = clipboard;
|
|
@@ -785,8 +810,10 @@ function Plugin({ id, manifest, commandBufferRef, props: engineProps }) {
|
|
|
785
810
|
const { plugin, loading, error, bundleMissing } = (0, import_plugin_runtime2.usePlugin)(id, manifest);
|
|
786
811
|
const connectorId = id.startsWith("mcp:") ? id.split(":")[1] : manifest._connectorId;
|
|
787
812
|
const bridge = (0, import_plugin_runtime2.useNativeBridge)(id, connectorId, commandBufferRef);
|
|
788
|
-
const
|
|
789
|
-
|
|
813
|
+
const { device, native, deviceState, voice } = (0, import_react4.useMemo)(
|
|
814
|
+
() => (0, import_plugin_runtime2.resolvePluginRuntimeProps)({ pluginId: id, manifest, plugin }),
|
|
815
|
+
[id, manifest, plugin]
|
|
816
|
+
);
|
|
790
817
|
const theme = (0, import_plugin_runtime2.usePluginTheme)();
|
|
791
818
|
const user = (0, import_core4.getUser)();
|
|
792
819
|
const context = (0, import_react4.useMemo)(() => ({
|
|
@@ -809,7 +836,7 @@ function Plugin({ id, manifest, commandBufferRef, props: engineProps }) {
|
|
|
809
836
|
return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native3.View, { style: styles3.center, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native3.Text, { style: styles3.errorMessage, children: error || "Plugin not found" }) });
|
|
810
837
|
}
|
|
811
838
|
const { Component } = plugin;
|
|
812
|
-
return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(PluginErrorBoundary, { pluginId: id, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Component, { bridge, native, theme, context, props: engineProps }) });
|
|
839
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(PluginErrorBoundary, { pluginId: id, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Component, { bridge, device, native, deviceState, voice, theme, context, props: engineProps }) });
|
|
813
840
|
}
|
|
814
841
|
var styles3 = import_react_native3.StyleSheet.create({
|
|
815
842
|
center: {
|
|
@@ -1306,7 +1333,10 @@ function MessageBubble({ message }) {
|
|
|
1306
1333
|
message.toolCalls && message.toolCalls.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(ToolCallsBadge, { calls: message.toolCalls }),
|
|
1307
1334
|
message.jobProgress && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(JobProgressBar, { progress: message.jobProgress, standalone: isProgressOnly }),
|
|
1308
1335
|
!isUser && message.actions && message.actions.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(UIActionsRow, { actions: message.actions }),
|
|
1309
|
-
pq &&
|
|
1336
|
+
pq && // Per-chain: this bubble's OWN chain has a pending question. Parallel
|
|
1337
|
+
// pending chains each render their own chips (jobProgress is per
|
|
1338
|
+
// bubble); answered questions have no jobProgress → no chips.
|
|
1339
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)(QuestionChips, { inputType: pq.inputType, options: pq.options }),
|
|
1310
1340
|
!isProgressOnly && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_react_native8.Text, { style: [styles7.timeText, isUser && styles7.timeTextUser], children: formatTime(message.timestamp) })
|
|
1311
1341
|
]
|
|
1312
1342
|
}
|
|
@@ -2144,17 +2174,522 @@ var styles13 = import_react_native14.StyleSheet.create({
|
|
|
2144
2174
|
}
|
|
2145
2175
|
});
|
|
2146
2176
|
|
|
2147
|
-
// src/
|
|
2148
|
-
var import_react13 = require("react");
|
|
2177
|
+
// src/WorkspaceBanner.tsx
|
|
2149
2178
|
var import_react_native15 = require("react-native");
|
|
2150
|
-
var
|
|
2151
|
-
var
|
|
2179
|
+
var import_core17 = require("@ateam-ai-mobile/core");
|
|
2180
|
+
var import_jsx_runtime16 = require("react/jsx-runtime");
|
|
2181
|
+
var WARM_AMBER2 = "#D49A4E";
|
|
2182
|
+
var WARM_AMBER_SOFT2 = "rgba(212, 154, 78, 0.16)";
|
|
2183
|
+
var WARM_AMBER_BORDER = "rgba(212, 154, 78, 0.4)";
|
|
2184
|
+
function WorkspaceBanner({ topInset = 0 }) {
|
|
2185
|
+
const snapshot = (0, import_core17.useWorkspaceStore)((s) => s.snapshot);
|
|
2186
|
+
if (!snapshot) return null;
|
|
2187
|
+
if (snapshot.state === "active" || snapshot.state === "unknown") return null;
|
|
2188
|
+
if (!snapshot.message) return null;
|
|
2189
|
+
return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(
|
|
2190
|
+
import_react_native15.View,
|
|
2191
|
+
{
|
|
2192
|
+
style: [styles14.container, { paddingTop: import_core17.spacing.sm + topInset }],
|
|
2193
|
+
accessibilityRole: "alert",
|
|
2194
|
+
accessibilityLabel: snapshot.message,
|
|
2195
|
+
children: [
|
|
2196
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_react_native15.View, { style: styles14.dot }),
|
|
2197
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_react_native15.Text, { style: styles14.message, children: snapshot.message })
|
|
2198
|
+
]
|
|
2199
|
+
}
|
|
2200
|
+
);
|
|
2201
|
+
}
|
|
2202
|
+
var styles14 = import_react_native15.StyleSheet.create({
|
|
2203
|
+
container: {
|
|
2204
|
+
flexDirection: "row",
|
|
2205
|
+
alignItems: "flex-start",
|
|
2206
|
+
gap: import_core17.spacing.sm,
|
|
2207
|
+
paddingHorizontal: import_core17.spacing.md,
|
|
2208
|
+
paddingBottom: import_core17.spacing.sm,
|
|
2209
|
+
backgroundColor: WARM_AMBER_SOFT2,
|
|
2210
|
+
borderBottomWidth: import_react_native15.StyleSheet.hairlineWidth,
|
|
2211
|
+
borderBottomColor: WARM_AMBER_BORDER
|
|
2212
|
+
},
|
|
2213
|
+
dot: {
|
|
2214
|
+
width: 8,
|
|
2215
|
+
height: 8,
|
|
2216
|
+
borderRadius: 4,
|
|
2217
|
+
marginTop: 5,
|
|
2218
|
+
backgroundColor: WARM_AMBER2
|
|
2219
|
+
},
|
|
2220
|
+
message: {
|
|
2221
|
+
flex: 1,
|
|
2222
|
+
color: import_core17.ada.ink,
|
|
2223
|
+
fontSize: import_core17.fontSize.sm,
|
|
2224
|
+
lineHeight: import_core17.fontSize.sm * 1.35
|
|
2225
|
+
}
|
|
2226
|
+
});
|
|
2227
|
+
|
|
2228
|
+
// src/auth/HandoffScreen.tsx
|
|
2229
|
+
var import_react12 = require("react");
|
|
2230
|
+
var import_react_native16 = require("react-native");
|
|
2152
2231
|
var import_core18 = require("@ateam-ai-mobile/core");
|
|
2153
2232
|
var import_plugin_runtime9 = require("@ateam-ai-mobile/plugin-runtime");
|
|
2154
2233
|
|
|
2234
|
+
// src/auth/handoffExchange.ts
|
|
2235
|
+
async function runHandoffExchange(code, deps) {
|
|
2236
|
+
const res = await deps.exchange(code);
|
|
2237
|
+
if (res.tenant) await deps.onAdoptTenant(res.tenant);
|
|
2238
|
+
await deps.onSignedIn(res.jwt);
|
|
2239
|
+
deps.onDone();
|
|
2240
|
+
}
|
|
2241
|
+
function handoffErrorMessage(msg) {
|
|
2242
|
+
const m = (msg || "").toLowerCase();
|
|
2243
|
+
if (m.includes("429") || m.includes("rate") || m.includes("too many")) {
|
|
2244
|
+
return "Too many attempts \u2014 wait a moment, then get a new sign-in link on the web.";
|
|
2245
|
+
}
|
|
2246
|
+
return "This sign-in link is no longer valid. Get a new one on the web and try again.";
|
|
2247
|
+
}
|
|
2248
|
+
|
|
2249
|
+
// src/auth/HandoffScreen.tsx
|
|
2250
|
+
var import_jsx_runtime17 = require("react/jsx-runtime");
|
|
2251
|
+
var fontFam7 = import_react_native16.Platform.OS === "ios" ? "SF Pro Rounded" : void 0;
|
|
2252
|
+
function HandoffScreen({ code, onAdoptTenant, onSignedIn, onDone, onCancel }) {
|
|
2253
|
+
const [err, setErr] = (0, import_react12.useState)(null);
|
|
2254
|
+
const startedForRef = (0, import_react12.useRef)(null);
|
|
2255
|
+
(0, import_react12.useEffect)(() => {
|
|
2256
|
+
if (!code || startedForRef.current === code) return;
|
|
2257
|
+
startedForRef.current = code;
|
|
2258
|
+
runHandoffExchange(code, { exchange: import_core18.exchangeHandoff, onAdoptTenant, onSignedIn, onDone }).catch((e) => setErr(handoffErrorMessage(e?.message)));
|
|
2259
|
+
}, [code, onAdoptTenant, onSignedIn, onDone]);
|
|
2260
|
+
return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_react_native16.View, { style: styles15.container, children: /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(import_react_native16.View, { style: styles15.center, children: [
|
|
2261
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_plugin_runtime9.AdaOrb, { size: 72, state: err ? "idle" : "thinking" }),
|
|
2262
|
+
!err ? /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(import_jsx_runtime17.Fragment, { children: [
|
|
2263
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_react_native16.Text, { style: styles15.title, children: "Signing you in\u2026" }),
|
|
2264
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_react_native16.ActivityIndicator, { color: import_core18.ada.ink, style: { marginTop: 6 } })
|
|
2265
|
+
] }) : /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(import_jsx_runtime17.Fragment, { children: [
|
|
2266
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_react_native16.Text, { style: styles15.title, children: "Couldn\u2019t finish sign-in" }),
|
|
2267
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_react_native16.Text, { style: styles15.sub, children: err }),
|
|
2268
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_react_native16.Pressable, { onPress: onCancel, hitSlop: 8, style: styles15.back, children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_react_native16.Text, { style: styles15.backText, children: "Back to sign in" }) })
|
|
2269
|
+
] })
|
|
2270
|
+
] }) });
|
|
2271
|
+
}
|
|
2272
|
+
var styles15 = import_react_native16.StyleSheet.create({
|
|
2273
|
+
container: { flex: 1, backgroundColor: import_core18.ada.paper },
|
|
2274
|
+
center: { flex: 1, alignItems: "center", justifyContent: "center", gap: 14, paddingHorizontal: 32 },
|
|
2275
|
+
title: { fontSize: 20, fontWeight: "600", color: import_core18.ada.ink, fontFamily: fontFam7, marginTop: 8 },
|
|
2276
|
+
sub: { fontSize: 14, lineHeight: 20, color: import_core18.ada.inkSoft, textAlign: "center", fontFamily: fontFam7 },
|
|
2277
|
+
back: { marginTop: 10, paddingVertical: 10 },
|
|
2278
|
+
backText: { fontSize: 15, fontWeight: "600", color: import_core18.ada.orangeDeep, fontFamily: fontFam7 }
|
|
2279
|
+
});
|
|
2280
|
+
|
|
2281
|
+
// src/auth/OpenJoinScreen.tsx
|
|
2282
|
+
var import_react13 = require("react");
|
|
2283
|
+
var import_react_native17 = require("react-native");
|
|
2284
|
+
var import_core19 = require("@ateam-ai-mobile/core");
|
|
2285
|
+
var import_plugin_runtime10 = require("@ateam-ai-mobile/plugin-runtime");
|
|
2286
|
+
|
|
2287
|
+
// src/auth/openJoin.ts
|
|
2288
|
+
async function completeOpenJoin(slug, idToken, deps) {
|
|
2289
|
+
if (!idToken) return false;
|
|
2290
|
+
const consumer = await deps.join(slug, idToken);
|
|
2291
|
+
await deps.onAdoptTenant(slug);
|
|
2292
|
+
await deps.onSignedIn(consumer.jwt);
|
|
2293
|
+
return true;
|
|
2294
|
+
}
|
|
2295
|
+
function openJoinErrorMessage(msg) {
|
|
2296
|
+
const m = (msg || "").toLowerCase();
|
|
2297
|
+
if (m.includes("invite-only") || m.includes("invite only")) {
|
|
2298
|
+
return "This team is invite-only \u2014 ask an admin for an invite link.";
|
|
2299
|
+
}
|
|
2300
|
+
if (m.includes("not found") || m.includes("unknown tenant")) {
|
|
2301
|
+
return "No team with that handle.";
|
|
2302
|
+
}
|
|
2303
|
+
return msg || "Could not join";
|
|
2304
|
+
}
|
|
2305
|
+
|
|
2306
|
+
// src/auth/OpenJoinScreen.tsx
|
|
2307
|
+
var import_jsx_runtime18 = require("react/jsx-runtime");
|
|
2308
|
+
var fontFam8 = import_react_native17.Platform.OS === "ios" ? "SF Pro Rounded" : void 0;
|
|
2309
|
+
function OpenJoinScreen({ onAdoptTenant, onSignedIn, onBack }) {
|
|
2310
|
+
const [team, setTeam] = (0, import_react13.useState)("");
|
|
2311
|
+
const [busy, setBusy] = (0, import_react13.useState)(false);
|
|
2312
|
+
const [err, setErr] = (0, import_react13.useState)(null);
|
|
2313
|
+
const [appleAvailable, setAppleAvailable] = (0, import_react13.useState)(false);
|
|
2314
|
+
(0, import_react13.useEffect)(() => {
|
|
2315
|
+
getOAuthProvider()?.isAppleAvailable().then(setAppleAvailable).catch(() => setAppleAvailable(false));
|
|
2316
|
+
}, []);
|
|
2317
|
+
const slug = team.trim().toLowerCase();
|
|
2318
|
+
const canSubmit = slug.length > 0 && !busy;
|
|
2319
|
+
async function run(acquire, join) {
|
|
2320
|
+
if (!slug) return;
|
|
2321
|
+
const oauth = getOAuthProvider();
|
|
2322
|
+
if (!oauth) {
|
|
2323
|
+
setErr("Sign-in is unavailable on this device.");
|
|
2324
|
+
return;
|
|
2325
|
+
}
|
|
2326
|
+
setErr(null);
|
|
2327
|
+
setBusy(true);
|
|
2328
|
+
try {
|
|
2329
|
+
const idToken = await acquire(oauth);
|
|
2330
|
+
await completeOpenJoin(slug, idToken, { join, onAdoptTenant, onSignedIn });
|
|
2331
|
+
} catch (e) {
|
|
2332
|
+
setErr(openJoinErrorMessage(e?.message));
|
|
2333
|
+
} finally {
|
|
2334
|
+
setBusy(false);
|
|
2335
|
+
}
|
|
2336
|
+
}
|
|
2337
|
+
const handleGoogle = () => run((oauth) => oauth.signInWithGoogle(), import_core19.openJoinWithGoogle);
|
|
2338
|
+
const handleApple = () => run((oauth) => oauth.signInWithApple(), import_core19.openJoinWithApple);
|
|
2339
|
+
return /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(import_react_native17.View, { style: styles16.container, children: [
|
|
2340
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(import_react_native17.View, { style: styles16.hero, children: [
|
|
2341
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_plugin_runtime10.AdaOrb, { size: 110 }),
|
|
2342
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_react_native17.Text, { style: styles16.wordmark, children: "Join a team" }),
|
|
2343
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_react_native17.Text, { style: styles16.tagline, children: "Enter the team's handle. If it's open to join, you're in." })
|
|
2344
|
+
] }),
|
|
2345
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(import_react_native17.View, { style: styles16.form, children: [
|
|
2346
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
|
|
2347
|
+
import_react_native17.TextInput,
|
|
2348
|
+
{
|
|
2349
|
+
style: styles16.input,
|
|
2350
|
+
value: team,
|
|
2351
|
+
onChangeText: setTeam,
|
|
2352
|
+
placeholder: "team handle (e.g. acme)",
|
|
2353
|
+
placeholderTextColor: import_core19.ada.inkMuted,
|
|
2354
|
+
autoCapitalize: "none",
|
|
2355
|
+
autoCorrect: false,
|
|
2356
|
+
returnKeyType: "done"
|
|
2357
|
+
}
|
|
2358
|
+
),
|
|
2359
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(
|
|
2360
|
+
import_react_native17.Pressable,
|
|
2361
|
+
{
|
|
2362
|
+
style: [styles16.googleBtn, !canSubmit && styles16.btnDisabled],
|
|
2363
|
+
onPress: handleGoogle,
|
|
2364
|
+
disabled: !canSubmit,
|
|
2365
|
+
children: [
|
|
2366
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_react_native17.Text, { style: styles16.googleGlyph, children: "G" }),
|
|
2367
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_react_native17.Text, { style: styles16.googleText, children: "Continue with Google" })
|
|
2368
|
+
]
|
|
2369
|
+
}
|
|
2370
|
+
),
|
|
2371
|
+
appleAvailable && /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_react_native17.View, { style: { opacity: canSubmit ? 1 : 0.5 }, pointerEvents: canSubmit ? "auto" : "none", children: getOAuthProvider()?.renderAppleButton({ onPress: handleApple, disabled: !canSubmit, style: styles16.appleBtn }) }),
|
|
2372
|
+
busy && /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_react_native17.ActivityIndicator, { color: import_core19.ada.ink, style: { marginTop: 10 } }),
|
|
2373
|
+
!!err && /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_react_native17.Text, { style: styles16.err, children: err }),
|
|
2374
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_react_native17.Pressable, { onPress: onBack, hitSlop: 8, style: styles16.back, children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_react_native17.Text, { style: styles16.backText, children: "Back to sign in" }) })
|
|
2375
|
+
] })
|
|
2376
|
+
] });
|
|
2377
|
+
}
|
|
2378
|
+
var styles16 = import_react_native17.StyleSheet.create({
|
|
2379
|
+
container: { flex: 1, backgroundColor: import_core19.ada.paper, justifyContent: "space-between" },
|
|
2380
|
+
hero: { alignItems: "center", marginTop: import_core19.spacing.xxl },
|
|
2381
|
+
wordmark: { fontSize: 26, fontWeight: "800", color: import_core19.ada.ink, marginTop: 14, fontFamily: fontFam8 },
|
|
2382
|
+
tagline: { fontSize: 15, color: import_core19.ada.inkSoft, marginTop: 8, textAlign: "center", paddingHorizontal: 28, fontFamily: fontFam8 },
|
|
2383
|
+
form: { paddingHorizontal: 24, paddingBottom: 40, gap: 12 },
|
|
2384
|
+
input: {
|
|
2385
|
+
backgroundColor: import_core19.ada.paperSoft,
|
|
2386
|
+
borderWidth: 1,
|
|
2387
|
+
borderColor: import_core19.ada.border,
|
|
2388
|
+
borderRadius: 14,
|
|
2389
|
+
paddingHorizontal: 16,
|
|
2390
|
+
paddingVertical: 14,
|
|
2391
|
+
fontSize: 16,
|
|
2392
|
+
color: import_core19.ada.ink
|
|
2393
|
+
},
|
|
2394
|
+
googleBtn: {
|
|
2395
|
+
flexDirection: "row",
|
|
2396
|
+
alignItems: "center",
|
|
2397
|
+
justifyContent: "center",
|
|
2398
|
+
gap: 10,
|
|
2399
|
+
backgroundColor: import_core19.ada.ink,
|
|
2400
|
+
paddingVertical: 15,
|
|
2401
|
+
borderRadius: 999
|
|
2402
|
+
},
|
|
2403
|
+
btnDisabled: { opacity: 0.5 },
|
|
2404
|
+
googleGlyph: { color: "#fff", fontSize: 18, fontWeight: "800" },
|
|
2405
|
+
googleText: { color: "#fff", fontSize: 16, fontWeight: "600", fontFamily: fontFam8 },
|
|
2406
|
+
appleBtn: { height: 52, width: "100%" },
|
|
2407
|
+
err: { color: "#C15545", fontSize: 13, textAlign: "center", paddingHorizontal: 16, marginTop: 4 },
|
|
2408
|
+
back: { alignItems: "center", paddingVertical: 10 },
|
|
2409
|
+
backText: { color: import_core19.ada.inkMuted, fontSize: 15, fontWeight: "500" }
|
|
2410
|
+
});
|
|
2411
|
+
|
|
2412
|
+
// src/auth/JoinScreen.tsx
|
|
2413
|
+
var import_react14 = require("react");
|
|
2414
|
+
var import_react_native18 = require("react-native");
|
|
2415
|
+
var import_core20 = require("@ateam-ai-mobile/core");
|
|
2416
|
+
var import_plugin_runtime11 = require("@ateam-ai-mobile/plugin-runtime");
|
|
2417
|
+
|
|
2418
|
+
// src/auth/inviteJoin.ts
|
|
2419
|
+
async function completeInviteJoin(code, idToken, deps) {
|
|
2420
|
+
if (!idToken) return false;
|
|
2421
|
+
const consumer = await deps.join(code, idToken);
|
|
2422
|
+
deps.onDone();
|
|
2423
|
+
await deps.onSignedIn(consumer.jwt);
|
|
2424
|
+
return true;
|
|
2425
|
+
}
|
|
2426
|
+
function inviteHeadline(preview) {
|
|
2427
|
+
if (!preview) return "";
|
|
2428
|
+
return preview.role ? `Join ${preview.org} as ${preview.role}?` : `Join ${preview.org}?`;
|
|
2429
|
+
}
|
|
2430
|
+
|
|
2431
|
+
// src/auth/JoinScreen.tsx
|
|
2432
|
+
var import_jsx_runtime19 = require("react/jsx-runtime");
|
|
2433
|
+
var fontFam9 = import_react_native18.Platform.OS === "ios" ? "SF Pro Rounded" : void 0;
|
|
2434
|
+
function JoinScreen({ code, onSignedIn, onDone, onCancel }) {
|
|
2435
|
+
const [preview, setPreview] = (0, import_react14.useState)(null);
|
|
2436
|
+
const [loadErr, setLoadErr] = (0, import_react14.useState)(null);
|
|
2437
|
+
const [busy, setBusy] = (0, import_react14.useState)(false);
|
|
2438
|
+
const [err, setErr] = (0, import_react14.useState)(null);
|
|
2439
|
+
const [appleAvailable, setAppleAvailable] = (0, import_react14.useState)(false);
|
|
2440
|
+
(0, import_react14.useEffect)(() => {
|
|
2441
|
+
if (!code) return;
|
|
2442
|
+
setPreview(null);
|
|
2443
|
+
setLoadErr(null);
|
|
2444
|
+
(0, import_core20.previewInvite)(code).then(setPreview).catch((e) => setLoadErr(e?.message || "Could not load invite"));
|
|
2445
|
+
}, [code]);
|
|
2446
|
+
(0, import_react14.useEffect)(() => {
|
|
2447
|
+
getOAuthProvider()?.isAppleAvailable().then(setAppleAvailable).catch(() => setAppleAvailable(false));
|
|
2448
|
+
}, []);
|
|
2449
|
+
const orgLine = (0, import_react14.useMemo)(() => inviteHeadline(preview), [preview]);
|
|
2450
|
+
async function run(acquire, join) {
|
|
2451
|
+
if (!code) return;
|
|
2452
|
+
const oauth = getOAuthProvider();
|
|
2453
|
+
if (!oauth) {
|
|
2454
|
+
setErr("Sign-in is unavailable on this device.");
|
|
2455
|
+
return;
|
|
2456
|
+
}
|
|
2457
|
+
setErr(null);
|
|
2458
|
+
setBusy(true);
|
|
2459
|
+
try {
|
|
2460
|
+
const idToken = await acquire(oauth);
|
|
2461
|
+
await completeInviteJoin(code, idToken, { join, onDone, onSignedIn });
|
|
2462
|
+
} catch (e) {
|
|
2463
|
+
setErr(e?.message || "Sign-in failed");
|
|
2464
|
+
} finally {
|
|
2465
|
+
setBusy(false);
|
|
2466
|
+
}
|
|
2467
|
+
}
|
|
2468
|
+
const handleGoogle = () => run((oauth) => oauth.signInWithGoogle(), import_core20.joinWithGoogle);
|
|
2469
|
+
const handleApple = () => run((oauth) => oauth.signInWithApple(), import_core20.joinWithApple);
|
|
2470
|
+
return /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(import_react_native18.View, { style: styles17.container, children: [
|
|
2471
|
+
/* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(import_react_native18.View, { style: styles17.hero, children: [
|
|
2472
|
+
/* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_plugin_runtime11.AdaOrb, { size: 120 }),
|
|
2473
|
+
/* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_react_native18.Text, { style: styles17.wordmark, children: "ada" }),
|
|
2474
|
+
loadErr ? /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_react_native18.Text, { style: styles17.err, children: loadErr }) : preview ? /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_react_native18.Text, { style: styles17.tagline, children: orgLine }) : /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_react_native18.ActivityIndicator, { color: import_core20.ada.ink, style: { marginTop: 12 } })
|
|
2475
|
+
] }),
|
|
2476
|
+
/* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(import_react_native18.View, { style: styles17.buttons, children: [
|
|
2477
|
+
preview && !loadErr && /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(import_jsx_runtime19.Fragment, { children: [
|
|
2478
|
+
/* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(
|
|
2479
|
+
import_react_native18.Pressable,
|
|
2480
|
+
{
|
|
2481
|
+
style: [styles17.googleBtn, busy && styles17.btnDisabled],
|
|
2482
|
+
onPress: handleGoogle,
|
|
2483
|
+
disabled: busy,
|
|
2484
|
+
children: [
|
|
2485
|
+
/* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_react_native18.Text, { style: styles17.googleGlyph, children: "G" }),
|
|
2486
|
+
/* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_react_native18.Text, { style: styles17.googleText, children: "Continue with Google" })
|
|
2487
|
+
]
|
|
2488
|
+
}
|
|
2489
|
+
),
|
|
2490
|
+
appleAvailable && getOAuthProvider()?.renderAppleButton({ onPress: handleApple, disabled: busy, style: styles17.appleBtn })
|
|
2491
|
+
] }),
|
|
2492
|
+
loadErr && /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_react_native18.Pressable, { style: styles17.dismiss, onPress: onCancel, hitSlop: 8, children: /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_react_native18.Text, { style: styles17.dismissText, children: "Back to sign in" }) }),
|
|
2493
|
+
!!err && /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_react_native18.Text, { style: styles17.err, children: err }),
|
|
2494
|
+
busy && /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_react_native18.ActivityIndicator, { color: import_core20.ada.ink, style: { marginTop: 12 } }),
|
|
2495
|
+
/* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_react_native18.Text, { style: styles17.disclaimer, children: "By continuing you agree to Ada's Terms and Privacy Policy." })
|
|
2496
|
+
] })
|
|
2497
|
+
] });
|
|
2498
|
+
}
|
|
2499
|
+
var styles17 = import_react_native18.StyleSheet.create({
|
|
2500
|
+
container: { flex: 1, backgroundColor: import_core20.ada.paper, justifyContent: "space-between" },
|
|
2501
|
+
hero: { alignItems: "center", marginTop: import_core20.spacing.xxl * 1.4 },
|
|
2502
|
+
wordmark: { fontSize: 34, fontWeight: "800", color: import_core20.ada.ink, letterSpacing: 4, marginTop: 12, fontFamily: fontFam9 },
|
|
2503
|
+
tagline: { fontSize: 17, color: import_core20.ada.inkSoft, marginTop: 10, textAlign: "center", paddingHorizontal: 24, fontFamily: fontFam9 },
|
|
2504
|
+
buttons: { paddingHorizontal: 24, paddingBottom: 40, gap: 12 },
|
|
2505
|
+
googleBtn: {
|
|
2506
|
+
flexDirection: "row",
|
|
2507
|
+
alignItems: "center",
|
|
2508
|
+
justifyContent: "center",
|
|
2509
|
+
gap: 10,
|
|
2510
|
+
backgroundColor: import_core20.ada.ink,
|
|
2511
|
+
paddingVertical: 15,
|
|
2512
|
+
borderRadius: 999
|
|
2513
|
+
},
|
|
2514
|
+
btnDisabled: { opacity: 0.5 },
|
|
2515
|
+
googleGlyph: { color: "#fff", fontSize: 18, fontWeight: "800" },
|
|
2516
|
+
googleText: { color: "#fff", fontSize: 16, fontWeight: "600", fontFamily: fontFam9 },
|
|
2517
|
+
appleBtn: { height: 52, width: "100%" },
|
|
2518
|
+
dismiss: { alignItems: "center", paddingVertical: 10 },
|
|
2519
|
+
dismissText: { color: import_core20.ada.inkMuted, fontSize: 15, fontWeight: "500" },
|
|
2520
|
+
err: { color: "#C15545", fontSize: 13, textAlign: "center", paddingHorizontal: 16, marginTop: 8 },
|
|
2521
|
+
disclaimer: { fontSize: 12, color: import_core20.ada.inkMuted, textAlign: "center", marginTop: 8 }
|
|
2522
|
+
});
|
|
2523
|
+
|
|
2524
|
+
// src/auth/LoginScreen.tsx
|
|
2525
|
+
var import_react15 = require("react");
|
|
2526
|
+
var import_react_native19 = require("react-native");
|
|
2527
|
+
var import_core21 = require("@ateam-ai-mobile/core");
|
|
2528
|
+
var import_plugin_runtime12 = require("@ateam-ai-mobile/plugin-runtime");
|
|
2529
|
+
|
|
2530
|
+
// src/auth/login.ts
|
|
2531
|
+
function googleClientIds(config) {
|
|
2532
|
+
const g = config.providers.google || {};
|
|
2533
|
+
return {
|
|
2534
|
+
webClientId: g.web_client_id || g.client_id || "",
|
|
2535
|
+
iosClientId: g.ios_client_id || ""
|
|
2536
|
+
};
|
|
2537
|
+
}
|
|
2538
|
+
function shouldRecoverToDefaultTenant(errorMsg, currentTenant, defaultTenant) {
|
|
2539
|
+
return /capability not enabled/i.test(errorMsg || "") && currentTenant !== defaultTenant;
|
|
2540
|
+
}
|
|
2541
|
+
async function completeProviderSignIn(idToken, deps) {
|
|
2542
|
+
if (!idToken) return false;
|
|
2543
|
+
const consumer = await deps.exchange(idToken);
|
|
2544
|
+
await deps.onSignedIn(consumer.jwt);
|
|
2545
|
+
return true;
|
|
2546
|
+
}
|
|
2547
|
+
|
|
2548
|
+
// src/auth/LoginScreen.tsx
|
|
2549
|
+
var import_jsx_runtime20 = require("react/jsx-runtime");
|
|
2550
|
+
var fontFam10 = import_react_native19.Platform.OS === "ios" ? "SF Pro Rounded" : void 0;
|
|
2551
|
+
function LoginScreen({ activeTenant, defaultTenant, onConsumerDisabled, onSignedIn, onJoinTeam }) {
|
|
2552
|
+
const [config, setConfig] = (0, import_react15.useState)(null);
|
|
2553
|
+
const [error, setError] = (0, import_react15.useState)(null);
|
|
2554
|
+
(0, import_react15.useEffect)(() => {
|
|
2555
|
+
setConfig(null);
|
|
2556
|
+
setError(null);
|
|
2557
|
+
(0, import_core21.fetchConsumerConfig)().then(setConfig).catch(async (err) => {
|
|
2558
|
+
const msg = err?.message || "Failed to load sign-in config";
|
|
2559
|
+
if (shouldRecoverToDefaultTenant(msg, activeTenant, defaultTenant)) {
|
|
2560
|
+
await onConsumerDisabled();
|
|
2561
|
+
return;
|
|
2562
|
+
}
|
|
2563
|
+
setError(msg);
|
|
2564
|
+
});
|
|
2565
|
+
}, [activeTenant, defaultTenant, onConsumerDisabled]);
|
|
2566
|
+
return /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(import_react_native19.View, { style: styles18.container, children: [
|
|
2567
|
+
/* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(import_react_native19.View, { style: styles18.hero, children: [
|
|
2568
|
+
/* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_plugin_runtime12.AdaOrb, { size: 140 }),
|
|
2569
|
+
/* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_react_native19.Text, { style: styles18.wordmark, children: "ada" }),
|
|
2570
|
+
/* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_react_native19.Text, { style: styles18.tagline, children: "Your private personal AI-Team" })
|
|
2571
|
+
] }),
|
|
2572
|
+
/* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(import_react_native19.View, { style: styles18.buttonSection, children: [
|
|
2573
|
+
!config && !error && /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_react_native19.ActivityIndicator, { color: import_core21.ada.ink }),
|
|
2574
|
+
!!error && /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_react_native19.Text, { style: styles18.errorText, children: error }),
|
|
2575
|
+
!!config && /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(SignInButtons, { config, onSignedIn }),
|
|
2576
|
+
onJoinTeam && /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_react_native19.Pressable, { onPress: onJoinTeam, hitSlop: 8, style: { paddingVertical: 8 }, children: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_react_native19.Text, { style: styles18.joinLink, children: "Join a team by name" }) }),
|
|
2577
|
+
/* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_react_native19.Text, { style: styles18.disclaimer, children: "By continuing you agree to Ada's Terms and Privacy Policy." })
|
|
2578
|
+
] })
|
|
2579
|
+
] });
|
|
2580
|
+
}
|
|
2581
|
+
function SignInButtons({ config, onSignedIn }) {
|
|
2582
|
+
const [busy, setBusy] = (0, import_react15.useState)(false);
|
|
2583
|
+
const [error, setError] = (0, import_react15.useState)(null);
|
|
2584
|
+
const [appleAvailable, setAppleAvailable] = (0, import_react15.useState)(false);
|
|
2585
|
+
const { webClientId, iosClientId } = (0, import_react15.useMemo)(() => googleClientIds(config), [config]);
|
|
2586
|
+
(0, import_react15.useEffect)(() => {
|
|
2587
|
+
getOAuthProvider()?.isAppleAvailable().then(setAppleAvailable).catch(() => setAppleAvailable(false));
|
|
2588
|
+
}, []);
|
|
2589
|
+
const handleGoogleLogin = async () => {
|
|
2590
|
+
setError(null);
|
|
2591
|
+
if (!webClientId) {
|
|
2592
|
+
setError("Google sign-in is not configured for this tenant");
|
|
2593
|
+
return;
|
|
2594
|
+
}
|
|
2595
|
+
const oauth = getOAuthProvider();
|
|
2596
|
+
if (!oauth) {
|
|
2597
|
+
setError("Sign-in is unavailable on this device.");
|
|
2598
|
+
return;
|
|
2599
|
+
}
|
|
2600
|
+
setBusy(true);
|
|
2601
|
+
try {
|
|
2602
|
+
const idToken = await oauth.signInWithGoogle({ webClientId, iosClientId });
|
|
2603
|
+
await completeProviderSignIn(idToken, { exchange: import_core21.signInWithGoogle, onSignedIn });
|
|
2604
|
+
} catch (err) {
|
|
2605
|
+
setError(err?.message || "Sign-in failed");
|
|
2606
|
+
} finally {
|
|
2607
|
+
setBusy(false);
|
|
2608
|
+
}
|
|
2609
|
+
};
|
|
2610
|
+
const handleAppleLogin = async () => {
|
|
2611
|
+
setError(null);
|
|
2612
|
+
const oauth = getOAuthProvider();
|
|
2613
|
+
if (!oauth) {
|
|
2614
|
+
setError("Sign-in is unavailable on this device.");
|
|
2615
|
+
return;
|
|
2616
|
+
}
|
|
2617
|
+
setBusy(true);
|
|
2618
|
+
try {
|
|
2619
|
+
const idToken = await oauth.signInWithApple();
|
|
2620
|
+
await completeProviderSignIn(idToken, { exchange: import_core21.signInWithApple, onSignedIn });
|
|
2621
|
+
} catch (err) {
|
|
2622
|
+
setError(err?.message || "Apple sign-in failed");
|
|
2623
|
+
} finally {
|
|
2624
|
+
setBusy(false);
|
|
2625
|
+
}
|
|
2626
|
+
};
|
|
2627
|
+
const googleEnabled = !!webClientId && !busy;
|
|
2628
|
+
const appleEnabled = appleAvailable && !!config.providers.apple?.service_id && !busy;
|
|
2629
|
+
return /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(import_jsx_runtime20.Fragment, { children: [
|
|
2630
|
+
!!config.providers.google && /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(
|
|
2631
|
+
import_react_native19.Pressable,
|
|
2632
|
+
{
|
|
2633
|
+
style: [styles18.signInBtn, !googleEnabled && styles18.signInBtnDisabled],
|
|
2634
|
+
onPress: handleGoogleLogin,
|
|
2635
|
+
disabled: !googleEnabled,
|
|
2636
|
+
children: [
|
|
2637
|
+
/* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_react_native19.Text, { style: styles18.signInGlyph, children: "G" }),
|
|
2638
|
+
/* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_react_native19.Text, { style: styles18.signInText, children: "Continue with Google" })
|
|
2639
|
+
]
|
|
2640
|
+
}
|
|
2641
|
+
),
|
|
2642
|
+
appleEnabled && getOAuthProvider()?.renderAppleButton({ onPress: handleAppleLogin, disabled: busy, style: styles18.appleBtn }),
|
|
2643
|
+
!!error && /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_react_native19.Text, { style: styles18.errorText, children: error }),
|
|
2644
|
+
busy && /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_react_native19.ActivityIndicator, { color: import_core21.ada.ink })
|
|
2645
|
+
] });
|
|
2646
|
+
}
|
|
2647
|
+
var styles18 = import_react_native19.StyleSheet.create({
|
|
2648
|
+
container: {
|
|
2649
|
+
flex: 1,
|
|
2650
|
+
backgroundColor: import_core21.ada.paper,
|
|
2651
|
+
justifyContent: "space-between",
|
|
2652
|
+
paddingHorizontal: 32,
|
|
2653
|
+
paddingBottom: 24
|
|
2654
|
+
},
|
|
2655
|
+
hero: { flex: 1, alignItems: "center", justifyContent: "center", gap: 18 },
|
|
2656
|
+
wordmark: { fontSize: 32, fontWeight: "600", color: import_core21.ada.ink, letterSpacing: 4, fontFamily: fontFam10, marginTop: 8 },
|
|
2657
|
+
tagline: { fontSize: 16, fontWeight: "500", color: import_core21.ada.inkSoft, letterSpacing: -0.1, fontFamily: fontFam10 },
|
|
2658
|
+
buttonSection: { alignItems: "center", gap: 12 },
|
|
2659
|
+
signInBtn: {
|
|
2660
|
+
flexDirection: "row",
|
|
2661
|
+
alignItems: "center",
|
|
2662
|
+
justifyContent: "center",
|
|
2663
|
+
backgroundColor: import_core21.ada.ink,
|
|
2664
|
+
paddingVertical: 16,
|
|
2665
|
+
paddingHorizontal: 24,
|
|
2666
|
+
borderRadius: 999,
|
|
2667
|
+
width: "100%",
|
|
2668
|
+
gap: 12
|
|
2669
|
+
},
|
|
2670
|
+
signInBtnDisabled: { opacity: 0.5 },
|
|
2671
|
+
signInGlyph: { fontSize: 18, fontWeight: "700", color: import_core21.ada.paper, fontFamily: fontFam10 },
|
|
2672
|
+
signInText: { fontSize: 16, fontWeight: "600", color: import_core21.ada.paper, letterSpacing: -0.2, fontFamily: fontFam10 },
|
|
2673
|
+
appleBtn: { width: "100%", height: 52 },
|
|
2674
|
+
errorText: { fontSize: 13, color: "#c43d3d", textAlign: "center", paddingHorizontal: 16 },
|
|
2675
|
+
disclaimer: { fontSize: 12, color: import_core21.ada.inkMuted, textAlign: "center", marginTop: 8, lineHeight: 18, fontFamily: fontFam10 },
|
|
2676
|
+
joinLink: { fontSize: 14, fontWeight: "600", color: import_core21.ada.orangeDeep, textAlign: "center", fontFamily: fontFam10 }
|
|
2677
|
+
});
|
|
2678
|
+
|
|
2679
|
+
// src/voice/VoiceCallSheet.tsx
|
|
2680
|
+
var import_react17 = require("react");
|
|
2681
|
+
var import_react_native20 = require("react-native");
|
|
2682
|
+
var import_react_native_svg2 = __toESM(require("react-native-svg"));
|
|
2683
|
+
var import_core23 = require("@ateam-ai-mobile/core");
|
|
2684
|
+
var import_plugin_runtime14 = require("@ateam-ai-mobile/plugin-runtime");
|
|
2685
|
+
|
|
2155
2686
|
// src/voice/useVoiceCall.ts
|
|
2156
|
-
var
|
|
2157
|
-
|
|
2687
|
+
var import_react16 = require("react");
|
|
2688
|
+
|
|
2689
|
+
// src/voice/voiceSession.ts
|
|
2690
|
+
var import_core22 = require("@ateam-ai-mobile/core");
|
|
2691
|
+
var import_plugin_runtime13 = require("@ateam-ai-mobile/plugin-runtime");
|
|
2692
|
+
var CONNECT_TIMEOUT_MS = 3e4;
|
|
2158
2693
|
var TOKEN_REFRESH_SKEW_MS = 9e4;
|
|
2159
2694
|
function tokenNeedsRefresh(token) {
|
|
2160
2695
|
if (!token) return true;
|
|
@@ -2182,7 +2717,6 @@ var INJECT_TRACK_HOOK = `
|
|
|
2182
2717
|
}
|
|
2183
2718
|
}
|
|
2184
2719
|
|
|
2185
|
-
// 1) Hook getUserMedia \u2014 captures mic streams at request time.
|
|
2186
2720
|
try {
|
|
2187
2721
|
if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) {
|
|
2188
2722
|
const origGUM = navigator.mediaDevices.getUserMedia.bind(navigator.mediaDevices);
|
|
@@ -2196,7 +2730,6 @@ var INJECT_TRACK_HOOK = `
|
|
|
2196
2730
|
}
|
|
2197
2731
|
} catch (e) {}
|
|
2198
2732
|
|
|
2199
|
-
// 2) Hook RTCPeerConnection.addTrack \u2014 catches whatever the app sends.
|
|
2200
2733
|
try {
|
|
2201
2734
|
if (window.RTCPeerConnection) {
|
|
2202
2735
|
const origPC = window.RTCPeerConnection;
|
|
@@ -2216,9 +2749,7 @@ var INJECT_TRACK_HOOK = `
|
|
|
2216
2749
|
}
|
|
2217
2750
|
if (origAddTransceiver) {
|
|
2218
2751
|
origPC.prototype.addTransceiver = function(arg) {
|
|
2219
|
-
if (arg && arg.kind === 'audio') {
|
|
2220
|
-
// Transceiver created with kind 'audio' \u2014 sender track set later.
|
|
2221
|
-
} else if (arg && arg.kind === undefined && arg.track && arg.track.kind === 'audio') {
|
|
2752
|
+
if (arg && arg.kind === undefined && arg.track && arg.track.kind === 'audio') {
|
|
2222
2753
|
track(arg.track);
|
|
2223
2754
|
}
|
|
2224
2755
|
const tx = origAddTransceiver.apply(this, arguments);
|
|
@@ -2231,7 +2762,6 @@ var INJECT_TRACK_HOOK = `
|
|
|
2231
2762
|
}
|
|
2232
2763
|
} catch (e) {}
|
|
2233
2764
|
|
|
2234
|
-
// 3) Helper exposed for direct calls.
|
|
2235
2765
|
window._adaSetMuted = function(muted) {
|
|
2236
2766
|
let n = 0;
|
|
2237
2767
|
if (Array.isArray(window._allAudioTracks)) {
|
|
@@ -2250,7 +2780,6 @@ var buildMuteScript = (muted) => `
|
|
|
2250
2780
|
if (typeof window._adaSetMuted === 'function') {
|
|
2251
2781
|
n = window._adaSetMuted(muted);
|
|
2252
2782
|
}
|
|
2253
|
-
// Last-resort scan if hook somehow missed.
|
|
2254
2783
|
if (n === 0 && Array.isArray(window._allAudioTracks)) {
|
|
2255
2784
|
window._allAudioTracks.forEach(function(t) {
|
|
2256
2785
|
try { t.enabled = !muted; n++; } catch (e) {}
|
|
@@ -2259,104 +2788,413 @@ var buildMuteScript = (muted) => `
|
|
|
2259
2788
|
console.log('[ada-mute]', muted ? 'MUTED' : 'UNMUTED', 'tracks=', n);
|
|
2260
2789
|
})(${muted}); true;
|
|
2261
2790
|
`;
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
2791
|
+
var buildAgentTurnScript = (input) => `
|
|
2792
|
+
(function(input) {
|
|
2793
|
+
if (typeof window.__adasAgentTurn === 'function') {
|
|
2794
|
+
try { window.__adasAgentTurn(input); }
|
|
2795
|
+
catch (e) {
|
|
2796
|
+
window.ReactNativeWebView && window.ReactNativeWebView.postMessage(JSON.stringify({
|
|
2797
|
+
type: 'agentTurnFailed', message: String(e && e.message || e),
|
|
2798
|
+
}));
|
|
2799
|
+
}
|
|
2800
|
+
} else if (window.ReactNativeWebView) {
|
|
2801
|
+
window.ReactNativeWebView.postMessage(JSON.stringify({
|
|
2802
|
+
type: 'agentTurnUnsupported',
|
|
2803
|
+
message: 'voice app does not support agent turns (__adasAgentTurn missing) \u2014 update the voice page',
|
|
2804
|
+
}));
|
|
2805
|
+
}
|
|
2806
|
+
})(${JSON.stringify(input)}); true;
|
|
2807
|
+
`;
|
|
2808
|
+
var buildObserveScript = (text) => `
|
|
2809
|
+
(function(text) {
|
|
2810
|
+
if (typeof window.__adasObserve === 'function') {
|
|
2811
|
+
try { window.__adasObserve(text); }
|
|
2812
|
+
catch (e) {
|
|
2813
|
+
window.ReactNativeWebView && window.ReactNativeWebView.postMessage(JSON.stringify({
|
|
2814
|
+
type: 'observeFailed', message: String(e && e.message || e),
|
|
2815
|
+
}));
|
|
2816
|
+
}
|
|
2817
|
+
} else if (window.ReactNativeWebView) {
|
|
2818
|
+
window.ReactNativeWebView.postMessage(JSON.stringify({
|
|
2819
|
+
type: 'observeUnsupported',
|
|
2820
|
+
message: 'voice app does not support observe (__adasObserve missing) \u2014 update the voice page',
|
|
2821
|
+
}));
|
|
2822
|
+
}
|
|
2823
|
+
})(${JSON.stringify(text)}); true;
|
|
2824
|
+
`;
|
|
2825
|
+
function originOf(url) {
|
|
2826
|
+
if (!url) return null;
|
|
2827
|
+
const m = /^([a-z][a-z0-9+.-]*:\/\/[^/?#]+)/i.exec(url);
|
|
2828
|
+
return m ? m[1].toLowerCase() : null;
|
|
2829
|
+
}
|
|
2830
|
+
var IDLE = {
|
|
2831
|
+
callState: "idle",
|
|
2832
|
+
sessionId: null,
|
|
2833
|
+
error: null,
|
|
2834
|
+
transcript: [],
|
|
2835
|
+
aiPartial: "",
|
|
2836
|
+
userPartial: "",
|
|
2837
|
+
muted: false,
|
|
2838
|
+
userSpeaking: false,
|
|
2839
|
+
lastUserSpeechEndedAt: null,
|
|
2840
|
+
mounted: false,
|
|
2841
|
+
webViewSource: null,
|
|
2842
|
+
skillSlug: void 0
|
|
2843
|
+
};
|
|
2844
|
+
var VoiceSessionManager = class {
|
|
2845
|
+
constructor() {
|
|
2846
|
+
this.state = { ...IDLE };
|
|
2847
|
+
this.listeners = /* @__PURE__ */ new Set();
|
|
2848
|
+
this.webView = null;
|
|
2849
|
+
this.pending = [];
|
|
2850
|
+
/**
|
|
2851
|
+
* WHO owns the one physical session. Set to the caller that started it from
|
|
2852
|
+
* idle (a plugin's pluginId, or the PLATFORM symbol for the built-in sheet),
|
|
2853
|
+
* and kept after stop until the next start reassigns it — so a plugin can read
|
|
2854
|
+
* its own just-ended transcript but another plugin never can.
|
|
2855
|
+
*
|
|
2856
|
+
* ISOLATION (GPT blocker on 8881225): every access is scoped through this. A
|
|
2857
|
+
* different plugin must not read, control, or attach to another plugin's live
|
|
2858
|
+
* conversation just because it declared voice.
|
|
2859
|
+
*/
|
|
2860
|
+
this.owner = null;
|
|
2861
|
+
this.injectedTrackHook = INJECT_TRACK_HOOK;
|
|
2862
|
+
}
|
|
2863
|
+
/** The platform (built-in sheet, mic indicator, session host) sees and
|
|
2864
|
+
* controls any session — it is the user's own surface. A plugin caller may
|
|
2865
|
+
* only touch a session it owns. */
|
|
2866
|
+
canAccess(caller) {
|
|
2867
|
+
return caller === import_plugin_runtime13.PLATFORM_VOICE_CALLER || this.owner === caller;
|
|
2868
|
+
}
|
|
2869
|
+
foreignError() {
|
|
2870
|
+
const err = new Error(
|
|
2871
|
+
"voice: another solution is using the microphone \u2014 one live session per device."
|
|
2872
|
+
);
|
|
2873
|
+
err.code = "RESTRICTED";
|
|
2874
|
+
return err;
|
|
2875
|
+
}
|
|
2876
|
+
// ── Host wiring (used only by VoiceSessionHost) ──────────────
|
|
2877
|
+
/** The offscreen WebView sets its ref here so the session can inject into it. */
|
|
2878
|
+
setWebView(ref) {
|
|
2879
|
+
this.webView = ref;
|
|
2880
|
+
}
|
|
2881
|
+
/** Whole state — the host reads `mounted` + `webViewSource` to decide what to
|
|
2882
|
+
* render, and every reader derives its snapshot from this. */
|
|
2883
|
+
getInternal() {
|
|
2884
|
+
return this.state;
|
|
2885
|
+
}
|
|
2886
|
+
// ── VoiceAPI (what props.voice and useVoiceCall call) ────────
|
|
2887
|
+
async start(opts, caller = import_plugin_runtime13.PLATFORM_VOICE_CALLER) {
|
|
2888
|
+
if (this.state.callState === "connected") {
|
|
2889
|
+
if (!this.canAccess(caller)) throw this.foreignError();
|
|
2890
|
+
return this.realSnapshot();
|
|
2891
|
+
}
|
|
2892
|
+
if (this.state.callState === "connecting") {
|
|
2893
|
+
if (!this.canAccess(caller)) throw this.foreignError();
|
|
2894
|
+
return this.waitForConnect();
|
|
2895
|
+
}
|
|
2272
2896
|
try {
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
|
|
2285
|
-
|
|
2286
|
-
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
|
|
2296
|
-
|
|
2297
|
-
|
|
2897
|
+
await (0, import_plugin_runtime13.ensureDeviceToolsLoaded)();
|
|
2898
|
+
} catch (e) {
|
|
2899
|
+
console.warn("[voiceSession] device tool preload failed:", e?.message || String(e));
|
|
2900
|
+
}
|
|
2901
|
+
try {
|
|
2902
|
+
if (tokenNeedsRefresh((0, import_core22.getToken)())) await (0, import_core22.refreshToken)();
|
|
2903
|
+
} catch {
|
|
2904
|
+
}
|
|
2905
|
+
this.owner = caller;
|
|
2906
|
+
const skillSlug = opts?.skillSlug;
|
|
2907
|
+
this.setState({
|
|
2908
|
+
callState: "connecting",
|
|
2909
|
+
error: null,
|
|
2910
|
+
transcript: [],
|
|
2911
|
+
aiPartial: "",
|
|
2912
|
+
userPartial: "",
|
|
2913
|
+
muted: false,
|
|
2914
|
+
mounted: true,
|
|
2915
|
+
skillSlug,
|
|
2916
|
+
webViewSource: { uri: this.buildUri(skillSlug) }
|
|
2917
|
+
});
|
|
2918
|
+
return this.waitForConnect();
|
|
2919
|
+
}
|
|
2920
|
+
stop(caller = import_plugin_runtime13.PLATFORM_VOICE_CALLER) {
|
|
2921
|
+
if (this.state.callState !== "idle" && !this.canAccess(caller)) throw this.foreignError();
|
|
2922
|
+
this.webView?.injectJavaScript?.("window.hangUp && window.hangUp(); true;");
|
|
2923
|
+
this.rejectPending(new Error("voice: session stopped before it connected"));
|
|
2924
|
+
this.setState({
|
|
2925
|
+
...IDLE,
|
|
2926
|
+
// keep the transcript of the call that just ended for any reader still up
|
|
2927
|
+
transcript: this.state.transcript
|
|
2928
|
+
});
|
|
2929
|
+
}
|
|
2930
|
+
setMuted(muted, caller = import_plugin_runtime13.PLATFORM_VOICE_CALLER) {
|
|
2931
|
+
if (this.state.callState !== "idle" && !this.canAccess(caller)) throw this.foreignError();
|
|
2932
|
+
this.webView?.injectJavaScript?.(buildMuteScript(muted));
|
|
2933
|
+
this.setState({ muted });
|
|
2934
|
+
}
|
|
2935
|
+
prompt(input, caller = import_plugin_runtime13.PLATFORM_VOICE_CALLER) {
|
|
2936
|
+
if (this.state.callState !== "connected") {
|
|
2937
|
+
throw new Error("voice: session is not connected \u2014 call start() and await it before prompt().");
|
|
2938
|
+
}
|
|
2939
|
+
if (!this.canAccess(caller)) throw this.foreignError();
|
|
2940
|
+
this.webView?.injectJavaScript?.(buildAgentTurnScript(input));
|
|
2941
|
+
}
|
|
2942
|
+
/**
|
|
2943
|
+
* Stream a silent observation into the live session — tell the model without
|
|
2944
|
+
* asking it to speak. The cheap, high-frequency channel (safe once or twice a
|
|
2945
|
+
* second) behind a future props.voice.observe(); today the host uses it to run
|
|
2946
|
+
* the real-device stream proof (VOICE_RT_STREAM_DEVICE_PROOF) before the gated
|
|
2947
|
+
* plugin API is added. Owner-scoped like every other control.
|
|
2948
|
+
*/
|
|
2949
|
+
observe(text, caller = import_plugin_runtime13.PLATFORM_VOICE_CALLER) {
|
|
2950
|
+
if (this.state.callState !== "connected") {
|
|
2951
|
+
throw new Error("voice: session is not connected \u2014 observe() needs a live session.");
|
|
2952
|
+
}
|
|
2953
|
+
if (!this.canAccess(caller)) throw this.foreignError();
|
|
2954
|
+
this.webView?.injectJavaScript?.(buildObserveScript(text));
|
|
2955
|
+
}
|
|
2956
|
+
getSnapshot(caller = import_plugin_runtime13.PLATFORM_VOICE_CALLER) {
|
|
2957
|
+
return this.canAccess(caller) ? this.realSnapshot() : this.foreignSnapshot();
|
|
2958
|
+
}
|
|
2959
|
+
realSnapshot() {
|
|
2960
|
+
const s = this.state;
|
|
2961
|
+
return {
|
|
2962
|
+
state: s.callState,
|
|
2963
|
+
sessionId: s.sessionId,
|
|
2964
|
+
muted: s.muted,
|
|
2965
|
+
speaking: !!s.aiPartial,
|
|
2966
|
+
listening: !!s.userPartial,
|
|
2967
|
+
userSpeaking: s.userSpeaking,
|
|
2968
|
+
lastUserSpeechEndedAt: s.lastUserSpeechEndedAt,
|
|
2969
|
+
lastError: s.error
|
|
2970
|
+
};
|
|
2971
|
+
}
|
|
2972
|
+
foreignSnapshot() {
|
|
2973
|
+
return {
|
|
2974
|
+
state: "idle",
|
|
2975
|
+
sessionId: null,
|
|
2976
|
+
muted: false,
|
|
2977
|
+
speaking: false,
|
|
2978
|
+
listening: false,
|
|
2979
|
+
userSpeaking: false,
|
|
2980
|
+
lastUserSpeechEndedAt: null,
|
|
2981
|
+
lastError: null
|
|
2982
|
+
};
|
|
2983
|
+
}
|
|
2984
|
+
subscribe(cb, caller = import_plugin_runtime13.PLATFORM_VOICE_CALLER) {
|
|
2985
|
+
const wrapped = (event) => {
|
|
2986
|
+
if (this.canAccess(caller)) cb(event);
|
|
2987
|
+
};
|
|
2988
|
+
this.listeners.add(wrapped);
|
|
2989
|
+
return { unsubscribe: () => {
|
|
2990
|
+
this.listeners.delete(wrapped);
|
|
2991
|
+
} };
|
|
2992
|
+
}
|
|
2993
|
+
transcript(caller = import_plugin_runtime13.PLATFORM_VOICE_CALLER) {
|
|
2994
|
+
return this.canAccess(caller) ? this.state.transcript.slice() : [];
|
|
2995
|
+
}
|
|
2996
|
+
// ── WebView plumbing ─────────────────────────────────────────
|
|
2997
|
+
buildUri(skillSlug) {
|
|
2998
|
+
const token = (0, import_core22.getToken)();
|
|
2999
|
+
const tenant = (0, import_core22.getTenant)();
|
|
3000
|
+
const actorId = (0, import_core22.getUser)()?.actorId || "";
|
|
3001
|
+
const skillParam = skillSlug ? `&skillSlug=${encodeURIComponent(skillSlug)}` : "";
|
|
3002
|
+
return `${(0, import_core22.getVoiceApiUrl)()}/app?mode=mini&embedded=rn&authToken=${encodeURIComponent(token || "")}&tenant=${encodeURIComponent(tenant)}&actorId=${encodeURIComponent(actorId)}${skillParam}`;
|
|
3003
|
+
}
|
|
3004
|
+
/** Handle a postMessage from the WebView. `event` is the RN WebView event. */
|
|
3005
|
+
handleMessage(event) {
|
|
3006
|
+
let data;
|
|
3007
|
+
try {
|
|
3008
|
+
data = JSON.parse(event.nativeEvent.data);
|
|
3009
|
+
} catch {
|
|
3010
|
+
return;
|
|
3011
|
+
}
|
|
3012
|
+
switch (data.type) {
|
|
3013
|
+
case "callState":
|
|
3014
|
+
case "call-state":
|
|
3015
|
+
if (data.state) this.setState({ callState: data.state });
|
|
3016
|
+
if (data.sessionId) this.setState({ sessionId: data.sessionId });
|
|
3017
|
+
if (data.error) this.setState({ error: data.error });
|
|
3018
|
+
if (data.state === "connected") this.resolvePending();
|
|
3019
|
+
if (data.state === "error") this.rejectPending(new Error(data.error || "voice: connection error"));
|
|
3020
|
+
break;
|
|
3021
|
+
case "transcript":
|
|
3022
|
+
this.appendTranscript({ role: data.role, text: data.text, ts: Date.now() });
|
|
3023
|
+
break;
|
|
3024
|
+
case "aiPartial":
|
|
3025
|
+
this.setState({ aiPartial: data.text });
|
|
3026
|
+
break;
|
|
3027
|
+
case "userPartial":
|
|
3028
|
+
this.setState({ userPartial: data.text });
|
|
3029
|
+
break;
|
|
3030
|
+
case "clearPartials":
|
|
3031
|
+
this.setState({ aiPartial: "", userPartial: "" });
|
|
3032
|
+
break;
|
|
3033
|
+
// User VAD from the mini-app. `at` is when it happened; on speaking:false
|
|
3034
|
+
// we stamp lastUserSpeechEndedAt so a solution can gate "speak after N s of
|
|
3035
|
+
// silence" on the first real stop, never before the user has spoken.
|
|
3036
|
+
case "speechActivity": {
|
|
3037
|
+
const speaking = !!data.speaking;
|
|
3038
|
+
const at = typeof data.at === "number" ? data.at : Date.now();
|
|
3039
|
+
this.setState(
|
|
3040
|
+
speaking ? { userSpeaking: true } : { userSpeaking: false, lastUserSpeechEndedAt: at }
|
|
3041
|
+
);
|
|
3042
|
+
break;
|
|
3043
|
+
}
|
|
3044
|
+
case "error":
|
|
3045
|
+
this.setState({ error: data.message, callState: "error" });
|
|
3046
|
+
this.rejectPending(new Error(data.message || "voice: error"));
|
|
3047
|
+
break;
|
|
3048
|
+
// A proactive turn couldn't run (older voice page / hook threw). NON-FATAL:
|
|
3049
|
+
// the session stays live; we only surface why the guide didn't speak.
|
|
3050
|
+
case "agentTurnUnsupported":
|
|
3051
|
+
case "agentTurnFailed":
|
|
3052
|
+
case "observeUnsupported":
|
|
3053
|
+
case "observeFailed":
|
|
3054
|
+
this.setState({ error: data.message || "voice: channel notice" });
|
|
3055
|
+
break;
|
|
3056
|
+
// Device tools — executed locally, origin-checked. The model's function
|
|
3057
|
+
// call already arrived on this phone; only the voice app's own origin may
|
|
3058
|
+
// drive the camera/GPS.
|
|
3059
|
+
case "device-tool-list": {
|
|
3060
|
+
const listSender = originOf(event?.nativeEvent?.url);
|
|
3061
|
+
const listExpected = originOf((0, import_core22.getVoiceApiUrl)());
|
|
3062
|
+
if (!listSender || !listExpected || listSender !== listExpected) break;
|
|
3063
|
+
this.sendDeviceToolList(data.correlationId);
|
|
3064
|
+
break;
|
|
3065
|
+
}
|
|
3066
|
+
case "device-tool-call": {
|
|
3067
|
+
const sender = originOf(event?.nativeEvent?.url);
|
|
3068
|
+
const expected = originOf((0, import_core22.getVoiceApiUrl)());
|
|
3069
|
+
if (!sender || !expected || sender !== expected) {
|
|
3070
|
+
console.warn(`[voiceSession] REFUSED device tool call from "${sender || "unknown origin"}" (expected "${expected}")`);
|
|
2298
3071
|
break;
|
|
3072
|
+
}
|
|
3073
|
+
void this.runDeviceTool(data);
|
|
3074
|
+
break;
|
|
2299
3075
|
}
|
|
2300
|
-
} catch {
|
|
2301
3076
|
}
|
|
2302
|
-
}
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
|
|
3077
|
+
}
|
|
3078
|
+
/** Answer "what device tools do you have?" — the client is the authority. */
|
|
3079
|
+
sendDeviceToolList(correlationId) {
|
|
3080
|
+
const tools = (0, import_plugin_runtime13.listDeviceTools)().map((t) => ({
|
|
3081
|
+
name: t.name,
|
|
3082
|
+
description: t.description,
|
|
3083
|
+
input_schema: t.input_schema
|
|
3084
|
+
}));
|
|
3085
|
+
this.webView?.injectJavaScript?.(
|
|
3086
|
+
`window.__adasDeviceToolResult && window.__adasDeviceToolResult(${JSON.stringify(
|
|
3087
|
+
JSON.stringify({ correlationId, ok: true, tools })
|
|
3088
|
+
)}); true;`
|
|
3089
|
+
);
|
|
3090
|
+
}
|
|
3091
|
+
/** Run a device tool and post the outcome back into the WebView. */
|
|
3092
|
+
async runDeviceTool(data) {
|
|
3093
|
+
const { correlationId, name, args, timeoutMs } = data || {};
|
|
3094
|
+
if (!correlationId || typeof name !== "string") return;
|
|
3095
|
+
let payload;
|
|
2308
3096
|
try {
|
|
2309
|
-
|
|
2310
|
-
|
|
3097
|
+
const outcome = await (0, import_plugin_runtime13.callDeviceTool)(name, args || {}, { timeoutMs });
|
|
3098
|
+
payload = outcome.ok ? { correlationId, ok: true, result: outcome.result } : { correlationId, ok: false, error: outcome.error, reason: outcome.reason };
|
|
3099
|
+
} catch (err) {
|
|
3100
|
+
payload = { correlationId, ok: false, reason: "failed", error: err?.message || String(err) };
|
|
3101
|
+
}
|
|
3102
|
+
this.webView?.injectJavaScript?.(
|
|
3103
|
+
`window.__adasDeviceToolResult && window.__adasDeviceToolResult(${JSON.stringify(
|
|
3104
|
+
JSON.stringify(payload)
|
|
3105
|
+
)}); true;`
|
|
3106
|
+
);
|
|
3107
|
+
}
|
|
3108
|
+
// ── internals ────────────────────────────────────────────────
|
|
3109
|
+
setState(partial) {
|
|
3110
|
+
this.state = { ...this.state, ...partial };
|
|
3111
|
+
this.emit({ type: "state" });
|
|
3112
|
+
}
|
|
3113
|
+
appendTranscript(entry) {
|
|
3114
|
+
this.state = { ...this.state, transcript: [...this.state.transcript, entry] };
|
|
3115
|
+
this.emit({ type: "transcript" });
|
|
3116
|
+
}
|
|
3117
|
+
emit(event) {
|
|
3118
|
+
for (const cb of this.listeners) {
|
|
3119
|
+
try {
|
|
3120
|
+
cb(event);
|
|
3121
|
+
} catch (e) {
|
|
3122
|
+
console.warn("[voiceSession] listener threw", e);
|
|
2311
3123
|
}
|
|
2312
|
-
} catch {
|
|
2313
3124
|
}
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
|
|
2319
|
-
|
|
2320
|
-
|
|
2321
|
-
|
|
2322
|
-
}, []);
|
|
2323
|
-
const toggleMute = (0, import_react12.useCallback)(() => {
|
|
2324
|
-
setMuted((prev) => {
|
|
2325
|
-
const next = !prev;
|
|
2326
|
-
webViewRef.current?.injectJavaScript?.(buildMuteScript(next));
|
|
2327
|
-
return next;
|
|
3125
|
+
}
|
|
3126
|
+
waitForConnect() {
|
|
3127
|
+
return new Promise((resolve, reject) => {
|
|
3128
|
+
const timer = setTimeout(() => {
|
|
3129
|
+
this.pending = this.pending.filter((p) => p.timer !== timer);
|
|
3130
|
+
reject(new Error("voice: timed out waiting to connect (is VoiceSessionHost mounted?)"));
|
|
3131
|
+
}, CONNECT_TIMEOUT_MS);
|
|
3132
|
+
this.pending.push({ resolve, reject, timer });
|
|
2328
3133
|
});
|
|
3134
|
+
}
|
|
3135
|
+
resolvePending() {
|
|
3136
|
+
const snap = this.realSnapshot();
|
|
3137
|
+
const waiting = this.pending;
|
|
3138
|
+
this.pending = [];
|
|
3139
|
+
for (const p of waiting) {
|
|
3140
|
+
clearTimeout(p.timer);
|
|
3141
|
+
p.resolve(snap);
|
|
3142
|
+
}
|
|
3143
|
+
}
|
|
3144
|
+
rejectPending(err) {
|
|
3145
|
+
const waiting = this.pending;
|
|
3146
|
+
this.pending = [];
|
|
3147
|
+
for (const p of waiting) {
|
|
3148
|
+
clearTimeout(p.timer);
|
|
3149
|
+
p.reject(err);
|
|
3150
|
+
}
|
|
3151
|
+
}
|
|
3152
|
+
/** TEST ONLY: reset the singleton to a clean idle state (mirrors the
|
|
3153
|
+
* __resetSessionSyncForTest precedent). Not part of any surface. */
|
|
3154
|
+
__resetForTest() {
|
|
3155
|
+
this.rejectPending(new Error("voice: reset"));
|
|
3156
|
+
this.state = { ...IDLE };
|
|
3157
|
+
this.owner = null;
|
|
3158
|
+
this.listeners.clear();
|
|
3159
|
+
this.webView = null;
|
|
3160
|
+
}
|
|
3161
|
+
};
|
|
3162
|
+
var voiceSession = new VoiceSessionManager();
|
|
3163
|
+
|
|
3164
|
+
// src/voice/useVoiceCall.ts
|
|
3165
|
+
function useVoiceCall({ skillSlug } = {}) {
|
|
3166
|
+
const [, force] = (0, import_react16.useReducer)((x) => x + 1, 0);
|
|
3167
|
+
(0, import_react16.useEffect)(() => {
|
|
3168
|
+
const sub = voiceSession.subscribe(() => force());
|
|
3169
|
+
return () => sub.unsubscribe();
|
|
2329
3170
|
}, []);
|
|
2330
|
-
const
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
|
|
3171
|
+
const s = voiceSession.getInternal();
|
|
3172
|
+
const startCall = (0, import_react16.useCallback)(
|
|
3173
|
+
(opts) => voiceSession.start(opts ?? { skillSlug }),
|
|
3174
|
+
[skillSlug]
|
|
3175
|
+
);
|
|
3176
|
+
const stopCall = (0, import_react16.useCallback)(() => voiceSession.stop(), []);
|
|
3177
|
+
const toggleMute = (0, import_react16.useCallback)(
|
|
3178
|
+
() => voiceSession.setMuted(!voiceSession.getSnapshot().muted),
|
|
3179
|
+
[]
|
|
3180
|
+
);
|
|
2339
3181
|
return {
|
|
2340
|
-
callState,
|
|
2341
|
-
sessionId,
|
|
2342
|
-
error,
|
|
2343
|
-
transcript,
|
|
2344
|
-
aiPartial,
|
|
2345
|
-
userPartial,
|
|
2346
|
-
muted,
|
|
3182
|
+
callState: s.callState,
|
|
3183
|
+
sessionId: s.sessionId,
|
|
3184
|
+
error: s.error,
|
|
3185
|
+
transcript: s.transcript,
|
|
3186
|
+
aiPartial: s.aiPartial,
|
|
3187
|
+
userPartial: s.userPartial,
|
|
3188
|
+
muted: s.muted,
|
|
2347
3189
|
startCall,
|
|
2348
3190
|
stopCall,
|
|
2349
|
-
toggleMute
|
|
2350
|
-
handleWebViewMessage,
|
|
2351
|
-
getWebViewSource,
|
|
2352
|
-
webViewRef,
|
|
2353
|
-
injectedTrackHook: INJECT_TRACK_HOOK
|
|
3191
|
+
toggleMute
|
|
2354
3192
|
};
|
|
2355
3193
|
}
|
|
2356
3194
|
|
|
2357
3195
|
// src/voice/VoiceCallSheet.tsx
|
|
2358
|
-
var
|
|
2359
|
-
var
|
|
3196
|
+
var import_jsx_runtime21 = require("react/jsx-runtime");
|
|
3197
|
+
var fontFam11 = import_react_native20.Platform.OS === "ios" ? "SF Pro Rounded" : void 0;
|
|
2360
3198
|
var HIGHLIGHT_PATTERNS = [
|
|
2361
3199
|
/[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}/g,
|
|
2362
3200
|
/(?:\+?\d[\s\-().]?){7,}\d/g,
|
|
@@ -2399,8 +3237,8 @@ function HighlightedText({
|
|
|
2399
3237
|
baseStyle,
|
|
2400
3238
|
importantStyle
|
|
2401
3239
|
}) {
|
|
2402
|
-
const segments = (0,
|
|
2403
|
-
return /* @__PURE__ */ (0,
|
|
3240
|
+
const segments = (0, import_react17.useMemo)(() => highlightSegments(text), [text]);
|
|
3241
|
+
return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_react_native20.Text, { style: baseStyle, children: segments.map((seg, i) => /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_react_native20.Text, { style: seg.important ? importantStyle : void 0, children: seg.text }, i)) });
|
|
2404
3242
|
}
|
|
2405
3243
|
function VoiceCallSheet({ visible, onClose, skillSlug }) {
|
|
2406
3244
|
const {
|
|
@@ -2412,31 +3250,27 @@ function VoiceCallSheet({ visible, onClose, skillSlug }) {
|
|
|
2412
3250
|
muted,
|
|
2413
3251
|
startCall,
|
|
2414
3252
|
stopCall,
|
|
2415
|
-
toggleMute
|
|
2416
|
-
handleWebViewMessage,
|
|
2417
|
-
getWebViewSource,
|
|
2418
|
-
webViewRef,
|
|
2419
|
-
injectedTrackHook
|
|
3253
|
+
toggleMute
|
|
2420
3254
|
} = useVoiceCall({ skillSlug });
|
|
2421
|
-
const scrollRef = (0,
|
|
2422
|
-
(0,
|
|
3255
|
+
const scrollRef = (0, import_react17.useRef)(null);
|
|
3256
|
+
(0, import_react17.useEffect)(() => {
|
|
2423
3257
|
if (visible && callState === "idle") startCall();
|
|
2424
3258
|
}, [visible]);
|
|
2425
|
-
(0,
|
|
3259
|
+
(0, import_react17.useEffect)(() => {
|
|
2426
3260
|
scrollRef.current?.scrollToEnd?.({ animated: true });
|
|
2427
3261
|
}, [transcript.length, aiPartial, userPartial]);
|
|
2428
3262
|
const handleHangUp = () => {
|
|
2429
3263
|
stopCall();
|
|
2430
3264
|
onClose();
|
|
2431
3265
|
};
|
|
2432
|
-
const orbState = (0,
|
|
3266
|
+
const orbState = (0, import_react17.useMemo)(() => {
|
|
2433
3267
|
if (callState === "connecting") return "thinking";
|
|
2434
3268
|
if (callState === "error") return "idle";
|
|
2435
3269
|
if (aiPartial) return "speaking";
|
|
2436
3270
|
if (userPartial) return "listening";
|
|
2437
3271
|
return "idle";
|
|
2438
3272
|
}, [callState, aiPartial, userPartial]);
|
|
2439
|
-
const statusLabel = (0,
|
|
3273
|
+
const statusLabel = (0, import_react17.useMemo)(() => {
|
|
2440
3274
|
if (callState === "connecting") return "Connecting\u2026";
|
|
2441
3275
|
if (callState === "error") return error || "Couldn\u2019t connect";
|
|
2442
3276
|
if (aiPartial) return "Ada is speaking";
|
|
@@ -2446,92 +3280,76 @@ function VoiceCallSheet({ visible, onClose, skillSlug }) {
|
|
|
2446
3280
|
}, [callState, aiPartial, userPartial, error]);
|
|
2447
3281
|
if (!visible) return null;
|
|
2448
3282
|
const visibleEntries = transcript.filter((e) => e.role === "user" || e.role === "ai");
|
|
2449
|
-
return /* @__PURE__ */ (0,
|
|
2450
|
-
|
|
3283
|
+
return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(
|
|
3284
|
+
import_plugin_runtime14.SurfaceFullscreen,
|
|
2451
3285
|
{
|
|
2452
3286
|
visible,
|
|
2453
3287
|
onClose: handleHangUp,
|
|
2454
3288
|
status: statusLabel,
|
|
2455
|
-
hero: /* @__PURE__ */ (0,
|
|
3289
|
+
hero: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_plugin_runtime14.AdaOrb, { size: 180, state: orbState }),
|
|
2456
3290
|
closeIcon: "hangup",
|
|
2457
3291
|
closeLabel: "End",
|
|
2458
3292
|
children: [
|
|
2459
|
-
/* @__PURE__ */ (0,
|
|
2460
|
-
|
|
3293
|
+
/* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(
|
|
3294
|
+
import_react_native20.ScrollView,
|
|
2461
3295
|
{
|
|
2462
3296
|
ref: scrollRef,
|
|
2463
|
-
style:
|
|
2464
|
-
contentContainerStyle:
|
|
3297
|
+
style: styles19.transcript,
|
|
3298
|
+
contentContainerStyle: styles19.transcriptContent,
|
|
2465
3299
|
showsVerticalScrollIndicator: false,
|
|
2466
3300
|
children: [
|
|
2467
|
-
callState === "error" && error ? /* @__PURE__ */ (0,
|
|
2468
|
-
visibleEntries.map((entry, i) => /* @__PURE__ */ (0,
|
|
2469
|
-
/* @__PURE__ */ (0,
|
|
2470
|
-
/* @__PURE__ */ (0,
|
|
3301
|
+
callState === "error" && error ? /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_react_native20.View, { style: styles19.errorBox, children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_react_native20.Text, { style: styles19.errorText, children: error }) }) : null,
|
|
3302
|
+
visibleEntries.map((entry, i) => /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(import_react_native20.View, { style: styles19.entry, children: [
|
|
3303
|
+
/* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_react_native20.Text, { style: styles19.role, children: entry.role === "user" ? "YOU" : "ADA" }),
|
|
3304
|
+
/* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
|
|
2471
3305
|
HighlightedText,
|
|
2472
3306
|
{
|
|
2473
3307
|
text: entry.text,
|
|
2474
|
-
baseStyle:
|
|
2475
|
-
importantStyle:
|
|
3308
|
+
baseStyle: styles19.entryText,
|
|
3309
|
+
importantStyle: styles19.important
|
|
2476
3310
|
}
|
|
2477
3311
|
)
|
|
2478
3312
|
] }, `${entry.ts}-${i}`)),
|
|
2479
|
-
userPartial || aiPartial ? /* @__PURE__ */ (0,
|
|
2480
|
-
/* @__PURE__ */ (0,
|
|
2481
|
-
/* @__PURE__ */ (0,
|
|
3313
|
+
userPartial || aiPartial ? /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(import_react_native20.View, { style: styles19.entry, children: [
|
|
3314
|
+
/* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_react_native20.Text, { style: styles19.role, children: userPartial ? "YOU" : "ADA" }),
|
|
3315
|
+
/* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
|
|
2482
3316
|
HighlightedText,
|
|
2483
3317
|
{
|
|
2484
3318
|
text: userPartial || aiPartial,
|
|
2485
|
-
baseStyle:
|
|
2486
|
-
importantStyle:
|
|
3319
|
+
baseStyle: styles19.entryTextLive,
|
|
3320
|
+
importantStyle: styles19.important
|
|
2487
3321
|
}
|
|
2488
3322
|
)
|
|
2489
3323
|
] }) : null
|
|
2490
3324
|
]
|
|
2491
3325
|
}
|
|
2492
3326
|
),
|
|
2493
|
-
callState === "connected" || callState === "connecting" ? /* @__PURE__ */ (0,
|
|
2494
|
-
/* @__PURE__ */ (0,
|
|
2495
|
-
|
|
3327
|
+
callState === "connected" || callState === "connecting" ? /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(import_react_native20.View, { style: styles19.muteWrap, pointerEvents: "box-none", children: [
|
|
3328
|
+
/* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
|
|
3329
|
+
import_react_native20.Pressable,
|
|
2496
3330
|
{
|
|
2497
3331
|
onPress: toggleMute,
|
|
2498
3332
|
accessibilityRole: "button",
|
|
2499
3333
|
accessibilityLabel: muted ? "Unmute microphone" : "Mute microphone",
|
|
2500
3334
|
accessibilityState: { selected: muted },
|
|
2501
|
-
style: [
|
|
3335
|
+
style: [styles19.muteBtn, muted && styles19.muteBtnActive],
|
|
2502
3336
|
hitSlop: 12,
|
|
2503
|
-
children: /* @__PURE__ */ (0,
|
|
2504
|
-
/* @__PURE__ */ (0,
|
|
2505
|
-
/* @__PURE__ */ (0,
|
|
2506
|
-
/* @__PURE__ */ (0,
|
|
2507
|
-
/* @__PURE__ */ (0,
|
|
2508
|
-
muted ? /* @__PURE__ */ (0,
|
|
3337
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(import_react_native_svg2.default, { width: 22, height: 22, viewBox: "0 0 24 24", fill: "none", stroke: muted ? import_core23.ada.paper : import_core23.ada.ink, strokeWidth: 2, strokeLinecap: "round", strokeLinejoin: "round", children: [
|
|
3338
|
+
/* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_react_native_svg2.Path, { d: "M12 1a3 3 0 0 0-3 3v8a3 3 0 0 0 6 0V4a3 3 0 0 0-3-3z" }),
|
|
3339
|
+
/* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_react_native_svg2.Path, { d: "M19 10v2a7 7 0 0 1-14 0v-2" }),
|
|
3340
|
+
/* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_react_native_svg2.Line, { x1: "12", y1: "19", x2: "12", y2: "23" }),
|
|
3341
|
+
/* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_react_native_svg2.Line, { x1: "8", y1: "23", x2: "16", y2: "23" }),
|
|
3342
|
+
muted ? /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_react_native_svg2.Line, { x1: "3", y1: "3", x2: "21", y2: "21", stroke: import_core23.ada.paper, strokeWidth: 2.4 }) : null
|
|
2509
3343
|
] })
|
|
2510
3344
|
}
|
|
2511
3345
|
),
|
|
2512
|
-
/* @__PURE__ */ (0,
|
|
2513
|
-
] }) : null
|
|
2514
|
-
callState !== "idle" && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_react_native15.View, { style: styles14.hiddenWebView, pointerEvents: "none", children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
|
|
2515
|
-
import_react_native_webview3.WebView,
|
|
2516
|
-
{
|
|
2517
|
-
ref: webViewRef,
|
|
2518
|
-
source: getWebViewSource(),
|
|
2519
|
-
style: { flex: 1, backgroundColor: "transparent" },
|
|
2520
|
-
onMessage: handleWebViewMessage,
|
|
2521
|
-
onError: (e) => console.log("[VoiceWebView] error:", e.nativeEvent?.description),
|
|
2522
|
-
injectedJavaScriptBeforeContentLoaded: injectedTrackHook,
|
|
2523
|
-
mediaPlaybackRequiresUserAction: false,
|
|
2524
|
-
allowsInlineMediaPlayback: true,
|
|
2525
|
-
mediaCapturePermissionGrantType: "grant",
|
|
2526
|
-
javaScriptEnabled: true,
|
|
2527
|
-
originWhitelist: ["*"]
|
|
2528
|
-
}
|
|
2529
|
-
) })
|
|
3346
|
+
/* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_react_native20.Text, { style: styles19.muteLabel, children: muted ? "Muted" : "Mute" })
|
|
3347
|
+
] }) : null
|
|
2530
3348
|
]
|
|
2531
3349
|
}
|
|
2532
3350
|
);
|
|
2533
3351
|
}
|
|
2534
|
-
var
|
|
3352
|
+
var styles19 = import_react_native20.StyleSheet.create({
|
|
2535
3353
|
transcript: {
|
|
2536
3354
|
flex: 1,
|
|
2537
3355
|
paddingHorizontal: 28
|
|
@@ -2548,9 +3366,9 @@ var styles14 = import_react_native15.StyleSheet.create({
|
|
|
2548
3366
|
},
|
|
2549
3367
|
role: {
|
|
2550
3368
|
fontSize: 10,
|
|
2551
|
-
fontFamily:
|
|
3369
|
+
fontFamily: fontFam11,
|
|
2552
3370
|
fontWeight: "700",
|
|
2553
|
-
color:
|
|
3371
|
+
color: import_core23.ada.inkMuted,
|
|
2554
3372
|
letterSpacing: 1.4,
|
|
2555
3373
|
minWidth: 32,
|
|
2556
3374
|
paddingTop: 3,
|
|
@@ -2559,9 +3377,9 @@ var styles14 = import_react_native15.StyleSheet.create({
|
|
|
2559
3377
|
entryText: {
|
|
2560
3378
|
flex: 1,
|
|
2561
3379
|
fontSize: 14,
|
|
2562
|
-
fontFamily:
|
|
3380
|
+
fontFamily: fontFam11,
|
|
2563
3381
|
fontWeight: "400",
|
|
2564
|
-
color:
|
|
3382
|
+
color: import_core23.ada.inkSoft,
|
|
2565
3383
|
lineHeight: 20,
|
|
2566
3384
|
letterSpacing: -0.1,
|
|
2567
3385
|
opacity: 0.42
|
|
@@ -2569,18 +3387,18 @@ var styles14 = import_react_native15.StyleSheet.create({
|
|
|
2569
3387
|
entryTextLive: {
|
|
2570
3388
|
flex: 1,
|
|
2571
3389
|
fontSize: 14,
|
|
2572
|
-
fontFamily:
|
|
3390
|
+
fontFamily: fontFam11,
|
|
2573
3391
|
fontWeight: "400",
|
|
2574
|
-
color:
|
|
3392
|
+
color: import_core23.ada.inkSoft,
|
|
2575
3393
|
lineHeight: 20,
|
|
2576
3394
|
letterSpacing: -0.1,
|
|
2577
3395
|
opacity: 0.7
|
|
2578
3396
|
},
|
|
2579
3397
|
important: {
|
|
2580
3398
|
fontSize: 17,
|
|
2581
|
-
fontFamily:
|
|
3399
|
+
fontFamily: fontFam11,
|
|
2582
3400
|
fontWeight: "700",
|
|
2583
|
-
color:
|
|
3401
|
+
color: import_core23.ada.ink,
|
|
2584
3402
|
letterSpacing: -0.1,
|
|
2585
3403
|
opacity: 1
|
|
2586
3404
|
},
|
|
@@ -2595,14 +3413,14 @@ var styles14 = import_react_native15.StyleSheet.create({
|
|
|
2595
3413
|
},
|
|
2596
3414
|
errorText: {
|
|
2597
3415
|
fontSize: 13,
|
|
2598
|
-
fontFamily:
|
|
3416
|
+
fontFamily: fontFam11,
|
|
2599
3417
|
fontWeight: "500",
|
|
2600
3418
|
color: "#fca5a5",
|
|
2601
3419
|
lineHeight: 18
|
|
2602
3420
|
},
|
|
2603
3421
|
muteWrap: {
|
|
2604
3422
|
position: "absolute",
|
|
2605
|
-
bottom:
|
|
3423
|
+
bottom: import_react_native20.Platform.OS === "ios" ? 56 : 40,
|
|
2606
3424
|
left: 28,
|
|
2607
3425
|
alignItems: "center",
|
|
2608
3426
|
gap: 6
|
|
@@ -2611,9 +3429,9 @@ var styles14 = import_react_native15.StyleSheet.create({
|
|
|
2611
3429
|
width: 56,
|
|
2612
3430
|
height: 56,
|
|
2613
3431
|
borderRadius: 28,
|
|
2614
|
-
backgroundColor:
|
|
3432
|
+
backgroundColor: import_core23.ada.paperSoft,
|
|
2615
3433
|
borderWidth: 1,
|
|
2616
|
-
borderColor:
|
|
3434
|
+
borderColor: import_core23.ada.border,
|
|
2617
3435
|
justifyContent: "center",
|
|
2618
3436
|
alignItems: "center"
|
|
2619
3437
|
},
|
|
@@ -2623,18 +3441,133 @@ var styles14 = import_react_native15.StyleSheet.create({
|
|
|
2623
3441
|
},
|
|
2624
3442
|
muteLabel: {
|
|
2625
3443
|
fontSize: 12,
|
|
2626
|
-
fontFamily:
|
|
3444
|
+
fontFamily: fontFam11,
|
|
2627
3445
|
fontWeight: "500",
|
|
2628
|
-
color:
|
|
3446
|
+
color: import_core23.ada.inkMuted,
|
|
2629
3447
|
letterSpacing: 0.3
|
|
2630
|
-
}
|
|
2631
|
-
|
|
3448
|
+
}
|
|
3449
|
+
});
|
|
3450
|
+
|
|
3451
|
+
// src/voice/VoiceSessionHost.tsx
|
|
3452
|
+
var import_react18 = require("react");
|
|
3453
|
+
var import_react_native21 = require("react-native");
|
|
3454
|
+
var import_react_native_webview3 = require("react-native-webview");
|
|
3455
|
+
var import_jsx_runtime22 = require("react/jsx-runtime");
|
|
3456
|
+
function VoiceSessionHost() {
|
|
3457
|
+
const [, force] = (0, import_react18.useReducer)((x) => x + 1, 0);
|
|
3458
|
+
const lastSig = (0, import_react18.useRef)("");
|
|
3459
|
+
(0, import_react18.useEffect)(() => {
|
|
3460
|
+
const sub = voiceSession.subscribe(() => {
|
|
3461
|
+
const s2 = voiceSession.getInternal();
|
|
3462
|
+
const sig = `${s2.mounted}|${s2.webViewSource?.uri || ""}`;
|
|
3463
|
+
if (sig !== lastSig.current) {
|
|
3464
|
+
lastSig.current = sig;
|
|
3465
|
+
force();
|
|
3466
|
+
}
|
|
3467
|
+
});
|
|
3468
|
+
return () => sub.unsubscribe();
|
|
3469
|
+
}, []);
|
|
3470
|
+
const setRef = (0, import_react18.useCallback)((r) => {
|
|
3471
|
+
voiceSession.setWebView(r);
|
|
3472
|
+
}, []);
|
|
3473
|
+
const s = voiceSession.getInternal();
|
|
3474
|
+
if (!s.mounted || !s.webViewSource) return null;
|
|
3475
|
+
return /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_react_native21.View, { style: styles20.hidden, pointerEvents: "none", children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
|
|
3476
|
+
import_react_native_webview3.WebView,
|
|
3477
|
+
{
|
|
3478
|
+
ref: setRef,
|
|
3479
|
+
source: s.webViewSource,
|
|
3480
|
+
style: styles20.web,
|
|
3481
|
+
onMessage: (e) => voiceSession.handleMessage(e),
|
|
3482
|
+
onError: (e) => console.log("[VoiceSessionHost] error:", e.nativeEvent?.description),
|
|
3483
|
+
injectedJavaScriptBeforeContentLoaded: voiceSession.injectedTrackHook,
|
|
3484
|
+
mediaPlaybackRequiresUserAction: false,
|
|
3485
|
+
allowsInlineMediaPlayback: true,
|
|
3486
|
+
mediaCapturePermissionGrantType: "grant",
|
|
3487
|
+
javaScriptEnabled: true,
|
|
3488
|
+
originWhitelist: ["*"]
|
|
3489
|
+
}
|
|
3490
|
+
) });
|
|
3491
|
+
}
|
|
3492
|
+
var styles20 = import_react_native21.StyleSheet.create({
|
|
3493
|
+
hidden: {
|
|
2632
3494
|
position: "absolute",
|
|
2633
3495
|
width: 1,
|
|
2634
3496
|
height: 1,
|
|
2635
3497
|
top: -100,
|
|
2636
3498
|
left: -100,
|
|
2637
3499
|
opacity: 0
|
|
3500
|
+
},
|
|
3501
|
+
web: { flex: 1, backgroundColor: "transparent" }
|
|
3502
|
+
});
|
|
3503
|
+
|
|
3504
|
+
// src/voice/VoiceMicIndicator.tsx
|
|
3505
|
+
var import_react19 = require("react");
|
|
3506
|
+
var import_react_native22 = require("react-native");
|
|
3507
|
+
var import_core24 = require("@ateam-ai-mobile/core");
|
|
3508
|
+
var import_jsx_runtime23 = require("react/jsx-runtime");
|
|
3509
|
+
var fontFam12 = import_react_native22.Platform.OS === "ios" ? "SF Pro Rounded" : void 0;
|
|
3510
|
+
function VoiceMicIndicator({ topInset = 8 }) {
|
|
3511
|
+
const [, force] = (0, import_react19.useReducer)((x) => x + 1, 0);
|
|
3512
|
+
const pulse = (0, import_react19.useRef)(new import_react_native22.Animated.Value(0.4)).current;
|
|
3513
|
+
(0, import_react19.useEffect)(() => {
|
|
3514
|
+
const sub = voiceSession.subscribe(() => force());
|
|
3515
|
+
return () => sub.unsubscribe();
|
|
3516
|
+
}, []);
|
|
3517
|
+
const snap = voiceSession.getSnapshot();
|
|
3518
|
+
const live = snap.state === "connecting" || snap.state === "connected";
|
|
3519
|
+
(0, import_react19.useEffect)(() => {
|
|
3520
|
+
if (!live) return;
|
|
3521
|
+
const loop = import_react_native22.Animated.loop(
|
|
3522
|
+
import_react_native22.Animated.sequence([
|
|
3523
|
+
import_react_native22.Animated.timing(pulse, { toValue: 1, duration: 700, useNativeDriver: true }),
|
|
3524
|
+
import_react_native22.Animated.timing(pulse, { toValue: 0.4, duration: 700, useNativeDriver: true })
|
|
3525
|
+
])
|
|
3526
|
+
);
|
|
3527
|
+
loop.start();
|
|
3528
|
+
return () => loop.stop();
|
|
3529
|
+
}, [live, pulse]);
|
|
3530
|
+
if (!live) return null;
|
|
3531
|
+
const label = snap.state === "connecting" ? "Connecting\u2026" : snap.muted ? "Mic muted" : "Ada is listening";
|
|
3532
|
+
const dotColor = snap.muted ? import_core24.ada.inkMuted : "#E5484D";
|
|
3533
|
+
return /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(import_react_native22.View, { style: [styles21.wrap, { top: topInset }], pointerEvents: "none", children: /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(import_react_native22.View, { style: styles21.pill, children: [
|
|
3534
|
+
/* @__PURE__ */ (0, import_jsx_runtime23.jsx)(import_react_native22.Animated.View, { style: [styles21.dot, { backgroundColor: dotColor, opacity: snap.muted ? 1 : pulse }] }),
|
|
3535
|
+
/* @__PURE__ */ (0, import_jsx_runtime23.jsx)(import_react_native22.Text, { style: styles21.label, children: label })
|
|
3536
|
+
] }) });
|
|
3537
|
+
}
|
|
3538
|
+
var styles21 = import_react_native22.StyleSheet.create({
|
|
3539
|
+
wrap: {
|
|
3540
|
+
position: "absolute",
|
|
3541
|
+
left: 0,
|
|
3542
|
+
right: 0,
|
|
3543
|
+
alignItems: "center",
|
|
3544
|
+
zIndex: 9999,
|
|
3545
|
+
elevation: 9999
|
|
3546
|
+
},
|
|
3547
|
+
pill: {
|
|
3548
|
+
flexDirection: "row",
|
|
3549
|
+
alignItems: "center",
|
|
3550
|
+
gap: 7,
|
|
3551
|
+
paddingHorizontal: 12,
|
|
3552
|
+
paddingVertical: 6,
|
|
3553
|
+
borderRadius: 999,
|
|
3554
|
+
backgroundColor: import_core24.ada.ink,
|
|
3555
|
+
shadowColor: "#000",
|
|
3556
|
+
shadowOpacity: 0.2,
|
|
3557
|
+
shadowRadius: 8,
|
|
3558
|
+
shadowOffset: { width: 0, height: 2 }
|
|
3559
|
+
},
|
|
3560
|
+
dot: {
|
|
3561
|
+
width: 8,
|
|
3562
|
+
height: 8,
|
|
3563
|
+
borderRadius: 4
|
|
3564
|
+
},
|
|
3565
|
+
label: {
|
|
3566
|
+
fontSize: 12,
|
|
3567
|
+
fontFamily: fontFam12,
|
|
3568
|
+
fontWeight: "600",
|
|
3569
|
+
color: import_core24.ada.paper,
|
|
3570
|
+
letterSpacing: 0.2
|
|
2638
3571
|
}
|
|
2639
3572
|
});
|
|
2640
3573
|
// Annotate the CommonJS export names for ESM import in node:
|
|
@@ -2643,9 +3576,13 @@ var styles14 = import_react_native15.StyleSheet.create({
|
|
|
2643
3576
|
ChatInput,
|
|
2644
3577
|
DaySeparator,
|
|
2645
3578
|
EmbeddedPluginWebView,
|
|
3579
|
+
HandoffScreen,
|
|
2646
3580
|
JobProgressBar,
|
|
3581
|
+
JoinScreen,
|
|
2647
3582
|
Lightbox,
|
|
3583
|
+
LoginScreen,
|
|
2648
3584
|
MessageBubble,
|
|
3585
|
+
OpenJoinScreen,
|
|
2649
3586
|
Plugin,
|
|
2650
3587
|
PluginSurface,
|
|
2651
3588
|
TableCardBlock,
|
|
@@ -2653,16 +3590,30 @@ var styles14 = import_react_native15.StyleSheet.create({
|
|
|
2653
3590
|
TypingIndicator,
|
|
2654
3591
|
UIActionsRow,
|
|
2655
3592
|
VoiceCallSheet,
|
|
3593
|
+
VoiceMicIndicator,
|
|
3594
|
+
VoiceSessionHost,
|
|
2656
3595
|
WidgetHost,
|
|
2657
3596
|
WidgetWebView,
|
|
3597
|
+
WorkspaceBanner,
|
|
2658
3598
|
buildPluginBridgeJS,
|
|
3599
|
+
completeInviteJoin,
|
|
3600
|
+
completeOpenJoin,
|
|
3601
|
+
completeProviderSignIn,
|
|
3602
|
+
googleClientIds,
|
|
3603
|
+
handoffErrorMessage,
|
|
3604
|
+
inviteHeadline,
|
|
3605
|
+
openJoinErrorMessage,
|
|
2659
3606
|
openWidget,
|
|
3607
|
+
runHandoffExchange,
|
|
2660
3608
|
setClipboardProvider,
|
|
3609
|
+
setOAuthProvider,
|
|
2661
3610
|
setPluginWebViewExtraHosts,
|
|
2662
3611
|
setUIActionNavigator,
|
|
3612
|
+
shouldRecoverToDefaultTenant,
|
|
2663
3613
|
useLightboxStore,
|
|
2664
3614
|
usePluginBridge,
|
|
2665
3615
|
useVoiceCall,
|
|
2666
|
-
useWidgetHostStore
|
|
3616
|
+
useWidgetHostStore,
|
|
3617
|
+
voiceSession
|
|
2667
3618
|
});
|
|
2668
3619
|
//# sourceMappingURL=index.js.map
|