@appilots/sdk 0.2.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,2658 @@
1
+ export { AppilotsNavigationContainer } from './chunk-KCDNL4HV.mjs';
2
+ import { useAppilotsChat, useAppilotsActions, shouldShowSuggestedPrompts, useSuggestedPrompts } from './chunk-GEM6TPAK.mjs';
3
+ export { captureSnapshot, executeAction, useAppilots, useAppilotsActions, useAppilotsChat, useAppilotsField, useAppilotsNavigation, useAppilotsSlider, useAppilotsTarget, useAppilotsToggle, useSuggestedPrompts } from './chunk-GEM6TPAK.mjs';
4
+ import { useAppilotsContext, componentRegistry } from './chunk-HDLZGXNF.mjs';
5
+ export { AppilotsClient, AppilotsProvider, AppilotsRegistryProvider, SDK_VERSION, _patchJsxRuntimes, clearAppilotsDebugTraces, componentRegistry, createComponentRegistry, createElementRegistry, createListRegistry, elementRegistry, enableAppilotsAutoTracking, getAppilotsDebugTraces, getGlobalConfig, initAppilots, isAutoTrackingEnabled, listRegistry, recordAppilotsDebugTrace, registerScreen, subscribeAppilotsDebugTraces, useAppilotsContext, useResolvedRegistry } from './chunk-HDLZGXNF.mjs';
6
+ import * as React7 from 'react';
7
+ import React7__default, { createContext, useRef, useEffect, useMemo, useContext, useState, useCallback } from 'react';
8
+ import { StyleSheet, Dimensions, Platform, View, Animated, Text, TouchableOpacity, ActivityIndicator, useColorScheme, NativeModules, FlatList, Image, TextInput, Modal, KeyboardAvoidingView, Linking } from 'react-native';
9
+ import { useSafeAreaInsets } from 'react-native-safe-area-context';
10
+ import Svg, { Path } from 'react-native-svg';
11
+
12
+ // ../shared/dist/chunk-43YQ52Q7.mjs
13
+ var SENSITIVE_FIELD_NAME_RE = /(\b|_|-)(password|senha|contrase[ñn]a|mot[\s_-]*de[\s_-]*passe|passwd|pwd|secret|token|api[\s_-]*key|access[\s_-]*key|private[\s_-]*key|cvv|cvc|cvn|cvc2|cid|pin(?:[\s_-]*code)?|otp|one[\s_-]*time[\s_-]*code|2fa|mfa|security[\s_-]*code|c[óo]digo[\s_-]*seguran[çc]a|c[óo]digo[\s_-]*verifica[çc][ãa]o|verification[\s_-]*code)(\b|_|-)/i;
14
+ function isSensitiveFieldName(id, label, placeholder, type) {
15
+ if (type === "password") return true;
16
+ for (const candidate of [id, label, placeholder]) {
17
+ if (typeof candidate === "string" && SENSITIVE_FIELD_NAME_RE.test(candidate)) {
18
+ return true;
19
+ }
20
+ }
21
+ return false;
22
+ }
23
+
24
+ // src/components/humanizeAction.ts
25
+ function describeAction(action) {
26
+ const state = mapStatus(action.status);
27
+ const label = labelFor(action, state);
28
+ return { label, state };
29
+ }
30
+ function mapStatus(status) {
31
+ switch (status) {
32
+ case "pending":
33
+ return "pending";
34
+ case "executing":
35
+ return "running";
36
+ case "completed":
37
+ return "success";
38
+ case "failed":
39
+ return "failed";
40
+ default:
41
+ return "pending";
42
+ }
43
+ }
44
+ function labelFor(action, state) {
45
+ if (state === "failed") {
46
+ return failureLabel(action);
47
+ }
48
+ switch (action.type) {
49
+ case "navigate":
50
+ return navigateLabel(action, state);
51
+ case "form_fill":
52
+ return formFillLabel(action, state);
53
+ case "ui_interaction":
54
+ return uiInteractionLabel(action, state);
55
+ case "scroll_list":
56
+ return scrollListLabel(action, state);
57
+ case "confirm":
58
+ return confirmLabel(state);
59
+ default:
60
+ return genericLabel(action.type, state);
61
+ }
62
+ }
63
+ function navigateLabel(action, state) {
64
+ const screen = readString(action.payload, "screenName") || "a outra tela";
65
+ switch (state) {
66
+ case "pending":
67
+ return `Vou navegar para ${screen}`;
68
+ case "running":
69
+ return `Navegando para ${screen}...`;
70
+ case "success":
71
+ return `Naveguei para ${screen}`;
72
+ default:
73
+ return `Navega\xE7\xE3o para ${screen}`;
74
+ }
75
+ }
76
+ function formFillLabel(action, state) {
77
+ const fields = readArray(action.payload, "fields");
78
+ const summary = summariseFields(fields);
79
+ switch (state) {
80
+ case "pending":
81
+ return summary ? `Vou preencher ${summary}` : "Vou preencher o formul\xE1rio";
82
+ case "running":
83
+ return summary ? `Preenchendo ${summary}...` : "Preenchendo formul\xE1rio...";
84
+ case "success":
85
+ return summary ? `Preenchi ${summary}` : "Preenchi o formul\xE1rio";
86
+ default:
87
+ return "Preenchimento do formul\xE1rio";
88
+ }
89
+ }
90
+ function uiInteractionLabel(action, state) {
91
+ const verbBase = uiVerb(readString(action.payload, "action"));
92
+ const target = humanActionTarget(action.payload);
93
+ const where = target ? ` em ${target}` : "";
94
+ switch (state) {
95
+ case "pending":
96
+ return `Vou ${verbBase.infinitive}${where}`;
97
+ case "running":
98
+ return `${verbBase.gerund}${where}...`;
99
+ case "success":
100
+ return `${verbBase.past}${where}`;
101
+ default:
102
+ return `Intera\xE7\xE3o${where}`;
103
+ }
104
+ }
105
+ function scrollListLabel(action, state) {
106
+ const payload = action.payload;
107
+ const toIndex = typeof payload?.toIndex === "number" ? payload.toIndex : void 0;
108
+ const direction = payload?.direction === "up" ? "para cima" : "para baixo";
109
+ const target = toIndex !== void 0 ? `at\xE9 o item ${toIndex}` : direction;
110
+ switch (state) {
111
+ case "pending":
112
+ return `Vou rolar a lista ${target}`;
113
+ case "running":
114
+ return `Rolando a lista ${target}...`;
115
+ case "success":
116
+ return `Rolei a lista ${target}`;
117
+ default:
118
+ return "Rolagem da lista";
119
+ }
120
+ }
121
+ function confirmLabel(state) {
122
+ switch (state) {
123
+ case "pending":
124
+ case "running":
125
+ return "Aguardando confirma\xE7\xE3o";
126
+ case "success":
127
+ return "Voc\xEA confirmou";
128
+ case "failed":
129
+ return "Confirma\xE7\xE3o cancelada";
130
+ default:
131
+ return "Confirma\xE7\xE3o";
132
+ }
133
+ }
134
+ function genericLabel(type, state) {
135
+ switch (state) {
136
+ case "pending":
137
+ return `Vou executar ${type}`;
138
+ case "running":
139
+ return `Executando ${type}...`;
140
+ case "success":
141
+ return `Executei ${type}`;
142
+ default:
143
+ return type;
144
+ }
145
+ }
146
+ function failureLabel(action) {
147
+ const cause = humanizeError(action.error);
148
+ const head = failureHead(action);
149
+ return cause ? `${head} (${cause})` : head;
150
+ }
151
+ function failureHead(action) {
152
+ switch (action.type) {
153
+ case "navigate": {
154
+ const screen = readString(action.payload, "screenName") || "a tela";
155
+ return `N\xE3o consegui navegar para ${screen}`;
156
+ }
157
+ case "form_fill":
158
+ return "N\xE3o consegui preencher o formul\xE1rio";
159
+ case "ui_interaction": {
160
+ const verb = uiVerb(readString(action.payload, "action"));
161
+ const target = humanComponentId(
162
+ readString(action.payload, "componentId") || readString(action.payload, "targetId")
163
+ );
164
+ return target ? `N\xE3o consegui ${verb.infinitive} em ${target}` : `N\xE3o consegui ${verb.infinitive}`;
165
+ }
166
+ case "scroll_list":
167
+ return "N\xE3o consegui rolar a lista";
168
+ case "confirm":
169
+ return "Voc\xEA cancelou";
170
+ default:
171
+ return `N\xE3o consegui executar ${action.type}`;
172
+ }
173
+ }
174
+ var TECHNICAL_PATTERNS = [
175
+ /\bat\s+\S+\s*\(/,
176
+ // stack trace frame
177
+ /TypeError|ReferenceError|SyntaxError|RangeError/i,
178
+ /HTTP\s*[45]\d{2}/i,
179
+ /^\s*\{[\s\S]*\}\s*$/,
180
+ // JSON dump
181
+ /undefined is not a function/i,
182
+ /Cannot read propert/i
183
+ ];
184
+ var FRIENDLY_REWRITES = [
185
+ [/rejected by user/i, "cancelado pelo usu\xE1rio"],
186
+ [/component '([^']+)' not found/i, "componente $1 n\xE3o encontrado"],
187
+ [/field '([^']+)' not found/i, "campo $1 n\xE3o encontrado"],
188
+ [/screen '([^']+)' not registered/i, "tela $1 n\xE3o registrada"],
189
+ [/permission denied/i, "sem permiss\xE3o"],
190
+ [/timeout|timed out/i, "tempo esgotado"],
191
+ [/network/i, "falha de rede"]
192
+ ];
193
+ var MAX_REASON_LEN = 80;
194
+ function humanizeError(raw, _action) {
195
+ if (!raw) return void 0;
196
+ const trimmed = String(raw).trim();
197
+ if (!trimmed) return void 0;
198
+ for (const [pattern, replacement] of FRIENDLY_REWRITES) {
199
+ if (pattern.test(trimmed)) {
200
+ return finalize(trimmed.replace(pattern, replacement));
201
+ }
202
+ }
203
+ if (TECHNICAL_PATTERNS.some((p) => p.test(trimmed))) {
204
+ return "algo deu errado";
205
+ }
206
+ if (trimmed.length > MAX_REASON_LEN) {
207
+ return "algo deu errado";
208
+ }
209
+ return finalize(trimmed);
210
+ }
211
+ function finalize(text) {
212
+ let out = text.trim();
213
+ if (out.length > MAX_REASON_LEN) out = out.slice(0, MAX_REASON_LEN - 1).trimEnd() + "\u2026";
214
+ if (out.length > 0) out = out[0].toLowerCase() + out.slice(1);
215
+ return out;
216
+ }
217
+ function uiVerb(raw) {
218
+ switch ((raw ?? "press").toLowerCase()) {
219
+ case "press":
220
+ case "tap":
221
+ case "click":
222
+ return { infinitive: "clicar", gerund: "Clicando", past: "Cliquei" };
223
+ case "longpress":
224
+ case "long_press":
225
+ return { infinitive: "pressionar", gerund: "Pressionando", past: "Pressionei" };
226
+ case "scroll":
227
+ case "scroll_to":
228
+ return { infinitive: "rolar", gerund: "Rolando", past: "Rolei" };
229
+ case "focus":
230
+ return { infinitive: "focar", gerund: "Focando", past: "Foquei" };
231
+ default:
232
+ return { infinitive: "interagir", gerund: "Interagindo", past: "Interagi" };
233
+ }
234
+ }
235
+ function summariseFields(fields) {
236
+ if (!Array.isArray(fields) || fields.length === 0) return void 0;
237
+ const parts = [];
238
+ const visible = fields.slice(0, 3);
239
+ for (const f of visible) {
240
+ if (!f || typeof f !== "object") continue;
241
+ const obj = f;
242
+ const name = humanFieldName(obj.label, obj.fieldId);
243
+ if (!name) continue;
244
+ const fieldIdStr = typeof obj.fieldId === "string" ? obj.fieldId : void 0;
245
+ const labelStr = typeof obj.label === "string" ? obj.label : void 0;
246
+ const fieldTypeStr = typeof obj.fieldType === "string" ? obj.fieldType : void 0;
247
+ const sensitive = isSensitiveFieldName(fieldIdStr, labelStr, void 0, fieldTypeStr);
248
+ if (sensitive) {
249
+ parts.push(`${name}=\u2022\u2022\u2022\u2022`);
250
+ continue;
251
+ }
252
+ const value = formatFieldValue(obj.value);
253
+ parts.push(value !== void 0 ? `${name}=${value}` : name);
254
+ }
255
+ if (parts.length === 0) return void 0;
256
+ const extra = fields.length - parts.length;
257
+ return extra > 0 ? `${parts.join(", ")} +${extra}` : parts.join(", ");
258
+ }
259
+ function formatFieldValue(value) {
260
+ if (value === void 0 || value === null || value === "") return void 0;
261
+ if (typeof value === "string") {
262
+ const v = value.length > 18 ? `${value.slice(0, 17)}\u2026` : value;
263
+ return `"${v}"`;
264
+ }
265
+ if (typeof value === "number" || typeof value === "boolean") return String(value);
266
+ return void 0;
267
+ }
268
+ function humanActionTarget(payload) {
269
+ if (!payload || typeof payload !== "object") return "";
270
+ const record = payload;
271
+ const friendly = readFriendlyString(record, "label") || readFriendlyString(record, "actionDescription") || readFriendlyString(record, "title") || readFriendlyString(record, "name");
272
+ if (friendly) return friendly;
273
+ return humanComponentId(
274
+ readString(payload, "componentId") || readString(payload, "targetId")
275
+ );
276
+ }
277
+ function humanFieldName(label, fieldId) {
278
+ if (typeof label === "string" && label.trim()) return label.trim();
279
+ if (typeof fieldId !== "string" || isTechnicalIdentifier(fieldId)) return void 0;
280
+ return humanComponentId(fieldId) || void 0;
281
+ }
282
+ function readFriendlyString(record, key) {
283
+ const value = record[key];
284
+ if (typeof value !== "string") return "";
285
+ const trimmed = value.trim();
286
+ if (!trimmed || isTechnicalIdentifier(trimmed)) return "";
287
+ return trimmed;
288
+ }
289
+ function humanComponentId(id) {
290
+ if (!id) return "";
291
+ if (isTechnicalIdentifier(id)) return "";
292
+ const cleaned = id.replace(/^(btn-|btn_|button-|component-)/i, "").replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/[-_]+/g, " ").trim();
293
+ if (!cleaned) return "";
294
+ return cleaned[0].toUpperCase() + cleaned.slice(1);
295
+ }
296
+ function isTechnicalIdentifier(value) {
297
+ const raw = value.trim();
298
+ if (!raw) return false;
299
+ if (/^el:/i.test(raw)) return true;
300
+ if (/^list-\d+-item-\d+$/i.test(raw)) return true;
301
+ if (/(testid|test-id|accessibilityid|accessibility-id|automationid|automation-id)/i.test(raw)) {
302
+ return true;
303
+ }
304
+ if (/^[a-z]+:[^ ]+:[^ ]+/i.test(raw)) return true;
305
+ if (/^[A-Za-z][\w-]*:[^\s]+$/i.test(raw)) return true;
306
+ if (/^[a-z]+(?:[-_][a-z]+)*[-_][a-z0-9]{5,}$/i.test(raw)) return true;
307
+ return false;
308
+ }
309
+ function readString(payload, key) {
310
+ if (!payload || typeof payload !== "object") return "";
311
+ const v = payload[key];
312
+ return typeof v === "string" ? v : "";
313
+ }
314
+ function readArray(payload, key) {
315
+ if (!payload || typeof payload !== "object") return [];
316
+ const v = payload[key];
317
+ return Array.isArray(v) ? v : [];
318
+ }
319
+
320
+ // src/theme/tokens.ts
321
+ var defaultLightTheme = {
322
+ colors: {
323
+ primary: "#6366f1",
324
+ secondary: "#8b5cf6",
325
+ background: "#ffffff",
326
+ surface: "#f3f4f6",
327
+ text: "#1f2937",
328
+ textSecondary: "#6b7280",
329
+ border: "#e5e7eb",
330
+ // NOTE (BACKLOG 4.2): success/warning/error used to have ZERO visual
331
+ // consumers — nothing read them, so ActionBreadcrumb's tones and the
332
+ // confirm card's destructive accents were hardcoded separately with
333
+ // their OWN hex values (#10B981/#EF4444/#9CA3AF-ish grays). Since no
334
+ // other UI in the SDK reads these three tokens, we picked these
335
+ // defaults to exactly match those pre-existing hardcoded values —
336
+ // wiring the tokens up to ActionBreadcrumb (see breadcrumbPresentation.ts)
337
+ // is now a no-op for anyone on the bundled default theme, while a
338
+ // custom theme's success/warning/error now actually do something.
339
+ success: "#10b981",
340
+ warning: "#9ca3af",
341
+ error: "#ef4444",
342
+ // Same value as `text` — split out so an app can diverge them, but
343
+ // any app that doesn't set these renders pixel-identical to before.
344
+ buttonText: "#1f2937",
345
+ bubbleText: "#1f2937",
346
+ userBubbleText: "#ffffff"
347
+ },
348
+ typography: {
349
+ fontFamily: "System",
350
+ fontSizeXs: 11,
351
+ fontSizeSm: 13,
352
+ fontSizeBase: 15,
353
+ fontSizeLg: 18
354
+ },
355
+ radii: {
356
+ sm: 8,
357
+ md: 12,
358
+ lg: 16
359
+ },
360
+ shadows: {
361
+ appearance: "light"
362
+ }
363
+ };
364
+ var defaultDarkTheme = {
365
+ colors: {
366
+ primary: "#818cf8",
367
+ secondary: "#a78bfa",
368
+ background: "#0f172a",
369
+ surface: "#1e293b",
370
+ text: "#f1f5f9",
371
+ textSecondary: "#94a3b8",
372
+ border: "#334155",
373
+ // Kept IDENTICAL to the light theme's success/warning/error (not the
374
+ // usual "lighter for dark bg" adjustment other tokens get) — the
375
+ // breadcrumb's tones were a fixed, theme-agnostic hardcoded palette
376
+ // before this token existed, rendering the SAME hex in light and dark
377
+ // mode. Diverging them here would change dark-mode breadcrumbs even
378
+ // though nothing in the token tree asked for that.
379
+ success: "#10b981",
380
+ warning: "#9ca3af",
381
+ error: "#ef4444",
382
+ buttonText: "#f1f5f9",
383
+ bubbleText: "#f1f5f9",
384
+ userBubbleText: "#ffffff"
385
+ },
386
+ typography: { ...defaultLightTheme.typography },
387
+ radii: { ...defaultLightTheme.radii },
388
+ shadows: { appearance: "dark" }
389
+ };
390
+ function mergeThemeTokens(base, partial) {
391
+ if (!partial) return base;
392
+ return {
393
+ colors: { ...base.colors, ...partial.colors ?? {} },
394
+ typography: { ...base.typography, ...partial.typography ?? {} },
395
+ radii: { ...base.radii, ...partial.radii ?? {} },
396
+ shadows: { ...base.shadows, ...partial.shadows ?? {} }
397
+ };
398
+ }
399
+ function resolveBaseTheme(mode, systemScheme) {
400
+ if (mode === "dark") return defaultDarkTheme;
401
+ if (mode === "light") return defaultLightTheme;
402
+ return systemScheme === "dark" ? defaultDarkTheme : defaultLightTheme;
403
+ }
404
+
405
+ // src/components/breadcrumbPresentation.ts
406
+ var DEFAULT_BREADCRUMB_PALETTE = {
407
+ primary: defaultLightTheme.colors.primary,
408
+ text: defaultLightTheme.colors.text,
409
+ textSecondary: defaultLightTheme.colors.textSecondary,
410
+ surface: defaultLightTheme.colors.surface,
411
+ border: defaultLightTheme.colors.border,
412
+ success: defaultLightTheme.colors.success,
413
+ warning: defaultLightTheme.colors.warning,
414
+ error: defaultLightTheme.colors.error
415
+ };
416
+ var KNOWN_TINTS = {
417
+ [DEFAULT_BREADCRUMB_PALETTE.primary.toUpperCase()]: "#EEF2FF",
418
+ [DEFAULT_BREADCRUMB_PALETTE.success.toUpperCase()]: "#ECFDF5",
419
+ [DEFAULT_BREADCRUMB_PALETTE.warning.toUpperCase()]: "#F3F4F6",
420
+ [DEFAULT_BREADCRUMB_PALETTE.error.toUpperCase()]: "#FEF2F2"
421
+ };
422
+ function tint(hex) {
423
+ const known = KNOWN_TINTS[hex.toUpperCase()];
424
+ if (known) return known;
425
+ const base = hex.length === 9 ? hex.slice(0, 7) : hex;
426
+ return `${base}1a`;
427
+ }
428
+ function deriveStateTone(palette = DEFAULT_BREADCRUMB_PALETTE) {
429
+ return {
430
+ pending: { color: palette.warning, iconBg: tint(palette.warning) },
431
+ running: { color: palette.primary, iconBg: tint(palette.primary) },
432
+ success: { color: palette.success, iconBg: tint(palette.success) },
433
+ failed: { color: palette.error, iconBg: tint(palette.error) }
434
+ };
435
+ }
436
+ deriveStateTone();
437
+ function iconGlyphFor(state) {
438
+ switch (state) {
439
+ case "pending":
440
+ return "\u25CB";
441
+ case "success":
442
+ return "\u2713";
443
+ case "failed":
444
+ return "\u2717";
445
+ case "running":
446
+ default:
447
+ return "\xB7";
448
+ }
449
+ }
450
+ function isDestructiveAction(action) {
451
+ return action.payload?.destructive === true || action.destructive === true;
452
+ }
453
+ DEFAULT_BREADCRUMB_PALETTE.error;
454
+ function deriveConfirmCardContent(action, palette = DEFAULT_BREADCRUMB_PALETTE) {
455
+ const payload = action.payload ?? {};
456
+ const isDestructive = isDestructiveAction(action);
457
+ const message = typeof payload.message === "string" && payload.message || typeof payload.actionDescription === "string" && payload.actionDescription || "Confirme para prosseguir";
458
+ const title = typeof payload.title === "string" && payload.title || (isDestructive ? "A\xE7\xE3o destrutiva" : "Confirma\xE7\xE3o necess\xE1ria");
459
+ const confirmLabel2 = typeof payload.confirmLabel === "string" && payload.confirmLabel || (isDestructive ? "Sim, executar" : "Confirmar");
460
+ const cancelLabel = typeof payload.cancelLabel === "string" && payload.cancelLabel || "Cancelar";
461
+ return {
462
+ isDestructive,
463
+ title,
464
+ message,
465
+ confirmLabel: confirmLabel2,
466
+ cancelLabel,
467
+ glyph: isDestructive ? "\u26A0" : "?",
468
+ actionColor: isDestructive ? palette.error : palette.primary,
469
+ cardBackground: palette.surface,
470
+ cardBorderColor: isDestructive ? palette.error : palette.border,
471
+ messageColor: palette.textSecondary,
472
+ cancelBackground: palette.border,
473
+ cancelTextColor: palette.textSecondary
474
+ };
475
+ }
476
+
477
+ // src/components/ActionBreadcrumb.tsx
478
+ function ActionBreadcrumb({
479
+ actions,
480
+ primaryColor,
481
+ textColor,
482
+ fontFamily,
483
+ palette,
484
+ requireApprovalFor,
485
+ onApprove,
486
+ onReject
487
+ }) {
488
+ if (actions.length === 0) return null;
489
+ const resolvedPalette = {
490
+ ...DEFAULT_BREADCRUMB_PALETTE,
491
+ ...palette,
492
+ primary: primaryColor,
493
+ text: textColor
494
+ };
495
+ return /* @__PURE__ */ React7__default.createElement(View, { style: styles.container }, actions.map((action) => /* @__PURE__ */ React7__default.createElement(
496
+ BreadcrumbRow,
497
+ {
498
+ key: action.id,
499
+ action,
500
+ primaryColor,
501
+ textColor,
502
+ fontFamily,
503
+ palette: resolvedPalette,
504
+ needsApproval: requireApprovalFor?.(action) ?? false,
505
+ onApprove,
506
+ onReject
507
+ }
508
+ )));
509
+ }
510
+ function BreadcrumbRow(props) {
511
+ const { action, needsApproval } = props;
512
+ if (action.type === "confirm" && action.status === "pending" && needsApproval) {
513
+ return /* @__PURE__ */ React7__default.createElement(
514
+ ConfirmCard,
515
+ {
516
+ action,
517
+ palette: props.palette,
518
+ fontFamily: props.fontFamily,
519
+ onApprove: props.onApprove,
520
+ onReject: props.onReject
521
+ }
522
+ );
523
+ }
524
+ return /* @__PURE__ */ React7__default.createElement(RegularBreadcrumbRow, { ...props });
525
+ }
526
+ function RegularBreadcrumbRow({
527
+ action,
528
+ primaryColor,
529
+ textColor,
530
+ fontFamily,
531
+ palette,
532
+ needsApproval,
533
+ onApprove,
534
+ onReject
535
+ }) {
536
+ const { label, state } = describeAction(action);
537
+ const tone = deriveStateTone(palette)[state];
538
+ const fade = useRef(new Animated.Value(0)).current;
539
+ const pulse = useRef(new Animated.Value(1)).current;
540
+ const lastState = useRef(state);
541
+ useEffect(() => {
542
+ Animated.timing(fade, {
543
+ toValue: 1,
544
+ duration: 180,
545
+ useNativeDriver: true
546
+ }).start();
547
+ }, [fade]);
548
+ useEffect(() => {
549
+ if (lastState.current === state) return;
550
+ lastState.current = state;
551
+ pulse.setValue(0.4);
552
+ Animated.timing(pulse, {
553
+ toValue: 1,
554
+ duration: 220,
555
+ useNativeDriver: true
556
+ }).start();
557
+ }, [state, pulse]);
558
+ const isDestructive = isDestructiveAction(action);
559
+ return /* @__PURE__ */ React7__default.createElement(Animated.View, { style: [styles.row, { opacity: fade }] }, /* @__PURE__ */ React7__default.createElement(Animated.View, { style: { opacity: pulse, flexDirection: "row", alignItems: "flex-start", flex: 1 } }, /* @__PURE__ */ React7__default.createElement(StateIcon, { state, tone, primaryColor }), /* @__PURE__ */ React7__default.createElement(
560
+ Text,
561
+ {
562
+ style: [
563
+ styles.label,
564
+ { color: state === "failed" ? tone.color : textColor, fontFamily }
565
+ ],
566
+ accessibilityLabel: `${state}: ${label}`
567
+ },
568
+ label,
569
+ isDestructive && /* @__PURE__ */ React7__default.createElement(Text, { style: [styles.destructiveTag, { color: palette.error }] }, " \u26A0 destrutiva")
570
+ )), needsApproval && action.status === "pending" && onApprove && onReject && /* @__PURE__ */ React7__default.createElement(View, { style: styles.approvalButtons }, /* @__PURE__ */ React7__default.createElement(
571
+ TouchableOpacity,
572
+ {
573
+ onPress: () => onReject(action.id),
574
+ style: [
575
+ styles.btn,
576
+ { backgroundColor: deriveStateTone(palette).failed.iconBg }
577
+ ],
578
+ activeOpacity: 0.7,
579
+ accessibilityLabel: "Rejeitar a\xE7\xE3o"
580
+ },
581
+ /* @__PURE__ */ React7__default.createElement(Text, { style: [styles.rejectBtnText, { color: palette.error }] }, "Rejeitar")
582
+ ), /* @__PURE__ */ React7__default.createElement(
583
+ TouchableOpacity,
584
+ {
585
+ onPress: () => onApprove(action.id),
586
+ style: [styles.btn, styles.approveBtn, { backgroundColor: primaryColor }],
587
+ activeOpacity: 0.7,
588
+ accessibilityLabel: "Aprovar a\xE7\xE3o"
589
+ },
590
+ /* @__PURE__ */ React7__default.createElement(Text, { style: styles.approveBtnText }, "Aprovar")
591
+ )));
592
+ }
593
+ function ConfirmCard({ action, palette, fontFamily, onApprove, onReject }) {
594
+ const {
595
+ isDestructive,
596
+ title,
597
+ message,
598
+ confirmLabel: confirmLabel2,
599
+ cancelLabel,
600
+ glyph,
601
+ actionColor,
602
+ cardBackground,
603
+ cardBorderColor,
604
+ messageColor,
605
+ cancelBackground,
606
+ cancelTextColor
607
+ } = deriveConfirmCardContent(action, palette);
608
+ return /* @__PURE__ */ React7__default.createElement(
609
+ View,
610
+ {
611
+ style: [
612
+ confirmCardStyles.card,
613
+ { backgroundColor: cardBackground, borderColor: cardBorderColor }
614
+ ],
615
+ testID: "assistant-confirm-card"
616
+ },
617
+ /* @__PURE__ */ React7__default.createElement(View, { style: confirmCardStyles.headerRow }, /* @__PURE__ */ React7__default.createElement(Text, { style: [confirmCardStyles.glyph, { color: actionColor }] }, glyph), /* @__PURE__ */ React7__default.createElement(
618
+ Text,
619
+ {
620
+ style: [
621
+ confirmCardStyles.title,
622
+ { color: isDestructive ? palette.error : palette.text, fontFamily }
623
+ ]
624
+ },
625
+ title
626
+ )),
627
+ /* @__PURE__ */ React7__default.createElement(Text, { style: [confirmCardStyles.message, { color: messageColor, fontFamily }] }, message),
628
+ /* @__PURE__ */ React7__default.createElement(View, { style: confirmCardStyles.buttons }, /* @__PURE__ */ React7__default.createElement(
629
+ TouchableOpacity,
630
+ {
631
+ onPress: () => onReject?.(action.id),
632
+ style: [confirmCardStyles.btn, { backgroundColor: cancelBackground }],
633
+ activeOpacity: 0.7,
634
+ accessibilityLabel: "Cancelar",
635
+ testID: "assistant-confirm-reject"
636
+ },
637
+ /* @__PURE__ */ React7__default.createElement(Text, { style: [confirmCardStyles.cancelText, { color: cancelTextColor, fontFamily }] }, cancelLabel)
638
+ ), /* @__PURE__ */ React7__default.createElement(
639
+ TouchableOpacity,
640
+ {
641
+ onPress: () => onApprove?.(action.id),
642
+ style: [
643
+ confirmCardStyles.btn,
644
+ confirmCardStyles.confirmBtn,
645
+ { backgroundColor: actionColor }
646
+ ],
647
+ activeOpacity: 0.7,
648
+ accessibilityLabel: "Confirmar",
649
+ testID: "assistant-confirm-approve"
650
+ },
651
+ /* @__PURE__ */ React7__default.createElement(Text, { style: [confirmCardStyles.confirmText, { fontFamily }] }, confirmLabel2)
652
+ ))
653
+ );
654
+ }
655
+ var confirmCardStyles = StyleSheet.create({
656
+ card: {
657
+ alignSelf: "stretch",
658
+ marginVertical: 6,
659
+ padding: 14,
660
+ borderRadius: 14,
661
+ borderWidth: 1
662
+ // backgroundColor / borderColor set inline — theme-driven (surface / border / error).
663
+ },
664
+ headerRow: {
665
+ flexDirection: "row",
666
+ alignItems: "center",
667
+ gap: 8,
668
+ marginBottom: 6
669
+ },
670
+ glyph: {
671
+ fontSize: 16,
672
+ fontWeight: "700"
673
+ },
674
+ title: {
675
+ fontSize: 14,
676
+ fontWeight: "700",
677
+ flexShrink: 1
678
+ },
679
+ message: {
680
+ fontSize: 14,
681
+ lineHeight: 20,
682
+ // color set inline — theme-driven (textSecondary).
683
+ marginBottom: 12
684
+ },
685
+ buttons: {
686
+ flexDirection: "row",
687
+ gap: 8,
688
+ justifyContent: "flex-end"
689
+ },
690
+ btn: {
691
+ paddingHorizontal: 14,
692
+ paddingVertical: 8,
693
+ borderRadius: 10,
694
+ minWidth: 96,
695
+ alignItems: "center"
696
+ },
697
+ cancelText: {
698
+ fontSize: 13,
699
+ fontWeight: "600"
700
+ // color set inline — theme-driven (textSecondary).
701
+ },
702
+ confirmBtn: {
703
+ // backgroundColor set inline
704
+ },
705
+ confirmText: {
706
+ fontSize: 13,
707
+ fontWeight: "600",
708
+ color: "#FFFFFF"
709
+ }
710
+ });
711
+ function StateIcon({
712
+ state,
713
+ tone,
714
+ primaryColor
715
+ }) {
716
+ return /* @__PURE__ */ React7__default.createElement(View, { style: [styles.iconCircle, { backgroundColor: tone.iconBg }] }, state === "running" ? /* @__PURE__ */ React7__default.createElement(ActivityIndicator, { size: "small", color: primaryColor }) : /* @__PURE__ */ React7__default.createElement(Text, { style: [styles.iconGlyph, { color: tone.color }] }, iconGlyphFor(state)));
717
+ }
718
+ var styles = StyleSheet.create({
719
+ container: {
720
+ alignSelf: "stretch",
721
+ marginBottom: 8,
722
+ paddingVertical: 4
723
+ },
724
+ row: {
725
+ flexDirection: "row",
726
+ alignItems: "center",
727
+ paddingVertical: 6,
728
+ paddingHorizontal: 4
729
+ },
730
+ iconCircle: {
731
+ width: 22,
732
+ height: 22,
733
+ borderRadius: 11,
734
+ alignItems: "center",
735
+ justifyContent: "center",
736
+ marginRight: 10,
737
+ marginTop: 1
738
+ },
739
+ iconGlyph: {
740
+ fontSize: 13,
741
+ fontWeight: "700",
742
+ lineHeight: 16
743
+ },
744
+ label: {
745
+ flex: 1,
746
+ fontSize: 14,
747
+ lineHeight: 19
748
+ },
749
+ destructiveTag: {
750
+ // color set inline — theme-driven (error).
751
+ fontSize: 12,
752
+ fontWeight: "700"
753
+ },
754
+ approvalButtons: {
755
+ flexDirection: "row",
756
+ gap: 6,
757
+ marginLeft: 8
758
+ },
759
+ btn: {
760
+ paddingHorizontal: 10,
761
+ paddingVertical: 5,
762
+ borderRadius: 8
763
+ },
764
+ rejectBtnText: {
765
+ fontSize: 12,
766
+ fontWeight: "600"
767
+ // color set inline — theme-driven (error).
768
+ },
769
+ approveBtn: {
770
+ // backgroundColor set inline
771
+ },
772
+ approveBtnText: {
773
+ fontSize: 12,
774
+ fontWeight: "600",
775
+ color: "#FFFFFF"
776
+ }
777
+ });
778
+
779
+ // src/components/personalization.ts
780
+ function str(value) {
781
+ const trimmed = value?.trim();
782
+ return trimmed ? trimmed : void 0;
783
+ }
784
+ function resolveTheme(themeProp, remote) {
785
+ return themeProp ?? remote?.theme ?? void 0;
786
+ }
787
+ function resolveThemeMode(themeModeProp, remote) {
788
+ return themeModeProp ?? remote?.theme?.mode ?? "auto";
789
+ }
790
+ function resolveLocaleProp(localeProp, remote) {
791
+ return localeProp ?? remote?.defaultLocale ?? null;
792
+ }
793
+ function resolvePersonalization(props, remote) {
794
+ return {
795
+ headerTitle: props.headerTitle ?? props.assistantName ?? str(remote?.chatTitle) ?? str(remote?.assistantName),
796
+ assistantAvatar: props.assistantAvatar ?? str(remote?.assistantAvatar),
797
+ emptyStateIcon: props.emptyStateIcon ?? str(remote?.emptyStateIcon),
798
+ welcomeMessage: props.welcomeMessage ?? str(remote?.welcomeMessage),
799
+ // Remote is always a boolean when present (server coerces legacy
800
+ // nulls to true), so `??` chains cleanly to the SDK default.
801
+ poweredByVisible: props.poweredByVisible ?? remote?.poweredByVisible ?? true,
802
+ triggerButtonColor: props.triggerButtonColor ?? str(remote?.triggerButtonColor),
803
+ triggerButtonImage: props.triggerButtonImage ?? str(remote?.triggerButtonImageUrl)
804
+ };
805
+ }
806
+ function ChatIcon({ size = 24, color = "#ffffff" }) {
807
+ return /* @__PURE__ */ React7.createElement(Svg, { width: size, height: size, viewBox: "0 0 24 24", fill: "none" }, /* @__PURE__ */ React7.createElement(
808
+ Path,
809
+ {
810
+ d: "M5 3.5h14a3 3 0 0 1 3 3v9a3 3 0 0 1-3 3h-7.4l-4.6 4v-4H5a3 3 0 0 1-3-3v-9a3 3 0 0 1 3-3z",
811
+ fill: color
812
+ }
813
+ ));
814
+ }
815
+ function HeadsetIcon({ size = 18, color = "#ffffff" }) {
816
+ return /* @__PURE__ */ React7.createElement(Svg, { width: size, height: size, viewBox: "0 0 24 24", fill: "none" }, /* @__PURE__ */ React7.createElement(
817
+ Path,
818
+ {
819
+ d: "M12 3a8 8 0 0 0-8 8v5.5A2.5 2.5 0 0 0 6.5 19H8a1 1 0 0 0 1-1v-5a1 1 0 0 0-1-1H6v-1a6 6 0 0 1 12 0v1h-2a1 1 0 0 0-1 1v5a1 1 0 0 0 1 1h1.5a2.5 2.5 0 0 0 2.5-2.5V11a8 8 0 0 0-8-8z",
820
+ fill: color
821
+ }
822
+ ));
823
+ }
824
+
825
+ // src/components/chatTimeline.ts
826
+ function buildChatTimeline(messages, actions) {
827
+ const visibleMessageIds = new Set(messages.map((message) => message.id));
828
+ const visibleActionPrompts = actions.filter(
829
+ (action) => action.type === "confirm" && action.status === "pending"
830
+ );
831
+ const actionsByMessageId = /* @__PURE__ */ new Map();
832
+ const orphanActions = [];
833
+ for (const action of visibleActionPrompts) {
834
+ if (action.messageId && visibleMessageIds.has(action.messageId)) {
835
+ const list = actionsByMessageId.get(action.messageId) ?? [];
836
+ list.push(action);
837
+ actionsByMessageId.set(action.messageId, list);
838
+ } else {
839
+ orphanActions.push(action);
840
+ }
841
+ }
842
+ const timeline = [];
843
+ for (const msg of messages) {
844
+ timeline.push({ type: "message", data: msg });
845
+ if (msg.role === "assistant") {
846
+ const msgActions = actionsByMessageId.get(msg.id);
847
+ if (msgActions && msgActions.length > 0) {
848
+ timeline.push({ type: "breadcrumb", key: `bc-${msg.id}`, actions: msgActions });
849
+ }
850
+ }
851
+ }
852
+ if (orphanActions.length > 0) {
853
+ timeline.push({ type: "breadcrumb", key: "bc-orphan", actions: orphanActions });
854
+ }
855
+ return timeline;
856
+ }
857
+
858
+ // src/components/markdownContent.ts
859
+ var ORDERED_LINE = /^\s*(\d+)\.\s+(.+)$/;
860
+ var BULLET_LINE = /^\s*[-*•]\s+(.+)$/;
861
+ var HEADING_LINE = /^\s*(#{1,6})\s+(.+)$/;
862
+ var FENCE_LINE = /^\s*```\s*([A-Za-z0-9+#_-]*)\s*$/;
863
+ var INLINE_PATTERN = /\[([^\]\n]+)\]\((https?:\/\/[^\s)]+)\)|\*\*(.+?)\*\*|__(.+?)__|`([^`]+)`|\*(.+?)\*|_(.+?)_/g;
864
+ var MIN_ENHANCE_LENGTH = 80;
865
+ var SENTENCE_SPLIT = /[^.!?]+[.!?]+(?=\s+[A-ZÁÉÍÓÚÀÈÌÒÙÂÊÎÔÛÃÕÇÑ¿¡"“]|$)|[^.!?]+$/g;
866
+ var CAPABILITY_SENTENCE = /^((?:No aplicativo,?|In (?:this|the) app,?|En la aplicaci[oó]n,?|Dans l['’]application,?)?(?:\s+(?:voc[eê]|voce|you|tu|usted|puedes|puede|peux))?\s*(?:pode(?:m|r)?|can|could|puedes|puede|peux|peut))\s+(.+?)\.?\s*$/i;
867
+ var INCLUDE_LIST_SENTENCE = /^(.+?)\s+(?:incluem|inclui|include|includes|incluye|incluyen|such as|for example|par exemple|por exemplo)\s+(.+?)\.?\s*$/i;
868
+ function looksLikeMarkdown(content) {
869
+ if (!content) return false;
870
+ return /\*\*.+?\*\*/.test(content) || /__.+?__/.test(content) || /`[^`\n]+`/.test(content) || /^\s*[-*•]\s+/m.test(content) || /^\s*\d+\.\s+/m.test(content) || /^\s*#{1,6}\s+/m.test(content) || /^\s*```/m.test(content) || /\[[^\]\n]+\]\(https?:\/\/[^\s)]+\)/.test(content);
871
+ }
872
+ function splitEnumeration(text) {
873
+ const trimmed = text.trim().replace(/\.\s*$/, "");
874
+ if (!trimmed) return [];
875
+ const commaParts = trimmed.split(",").map((part) => part.trim()).filter(Boolean);
876
+ if (commaParts.length <= 1) {
877
+ const conjParts = trimmed.split(/\s+(?:e|and|y|ou|or)\s+/i).map((part) => part.trim());
878
+ return conjParts.filter(Boolean);
879
+ }
880
+ const last = commaParts[commaParts.length - 1];
881
+ const conjMatch = last.match(/^(.+?)\s+(?:e|and|y|ou|or)\s+(.+)$/i);
882
+ if (conjMatch) {
883
+ commaParts[commaParts.length - 1] = conjMatch[1].trim();
884
+ commaParts.push(conjMatch[2].trim());
885
+ }
886
+ return commaParts.filter(Boolean);
887
+ }
888
+ function splitSentences(text) {
889
+ const matches = text.match(SENTENCE_SPLIT);
890
+ if (!matches) return [text.trim()];
891
+ return matches.map((part) => part.trim()).filter(Boolean);
892
+ }
893
+ function formatCapabilitySentence(sentence) {
894
+ const match = CAPABILITY_SENTENCE.exec(sentence.trim());
895
+ if (!match) return null;
896
+ const items = splitEnumeration(match[2] ?? "");
897
+ if (items.length < 3) return null;
898
+ const intro = `${match[1].trim()}:`;
899
+ return `${intro}
900
+ ${items.map((item) => `- ${item}`).join("\n")}`;
901
+ }
902
+ function formatIncludeListSentence(sentence) {
903
+ const match = INCLUDE_LIST_SENTENCE.exec(sentence.trim());
904
+ if (!match) return null;
905
+ const intro = match[1].trim().replace(/[,:]\s*$/, "");
906
+ const items = splitEnumeration(match[2] ?? "");
907
+ if (items.length < 2) return null;
908
+ return `${intro}:
909
+ ${items.map((item) => `- ${item}`).join("\n")}`;
910
+ }
911
+ function formatReadableSentence(sentence) {
912
+ return formatCapabilitySentence(sentence) ?? formatIncludeListSentence(sentence) ?? sentence.trim();
913
+ }
914
+ function enhancePlainAssistantContent(content) {
915
+ if (!content?.trim() || looksLikeMarkdown(content)) return content;
916
+ const trimmed = content.trim();
917
+ if (trimmed.length < MIN_ENHANCE_LENGTH) return content;
918
+ const sentences = splitSentences(trimmed);
919
+ if (sentences.length === 0) return content;
920
+ const blocks = sentences.map(formatReadableSentence);
921
+ const enhanced = blocks.join("\n\n");
922
+ return enhanced || content;
923
+ }
924
+ function needsWideChatLayout(content) {
925
+ if (!content?.trim()) return false;
926
+ const prepared = enhancePlainAssistantContent(content);
927
+ return looksLikeMarkdown(prepared) || /\n\n+/.test(prepared);
928
+ }
929
+ function parseMarkdownBlocks(content) {
930
+ const lines = content.replace(/\r\n/g, "\n").split("\n");
931
+ const blocks = [];
932
+ let paragraph = [];
933
+ let bulletItems = [];
934
+ let orderedItems = [];
935
+ let codeLines = null;
936
+ let codeLanguage;
937
+ const flushParagraph = () => {
938
+ if (paragraph.length === 0) return;
939
+ blocks.push({ kind: "paragraph", text: paragraph.join("\n") });
940
+ paragraph = [];
941
+ };
942
+ const flushBullets = () => {
943
+ if (bulletItems.length === 0) return;
944
+ blocks.push({ kind: "bullet", items: bulletItems });
945
+ bulletItems = [];
946
+ };
947
+ const flushOrdered = () => {
948
+ if (orderedItems.length === 0) return;
949
+ blocks.push({ kind: "ordered", items: orderedItems });
950
+ orderedItems = [];
951
+ };
952
+ const flushLists = () => {
953
+ flushBullets();
954
+ flushOrdered();
955
+ };
956
+ const flushCode = () => {
957
+ if (codeLines === null) return;
958
+ blocks.push({
959
+ kind: "codeblock",
960
+ code: codeLines.join("\n"),
961
+ ...codeLanguage ? { language: codeLanguage } : {}
962
+ });
963
+ codeLines = null;
964
+ codeLanguage = void 0;
965
+ };
966
+ for (const line of lines) {
967
+ if (codeLines !== null) {
968
+ if (/^\s*```\s*$/.test(line)) {
969
+ flushCode();
970
+ } else {
971
+ codeLines.push(line);
972
+ }
973
+ continue;
974
+ }
975
+ const fence = FENCE_LINE.exec(line);
976
+ if (fence) {
977
+ flushParagraph();
978
+ flushLists();
979
+ codeLines = [];
980
+ codeLanguage = fence[1] || void 0;
981
+ continue;
982
+ }
983
+ const trimmed = line.trim();
984
+ if (!trimmed) {
985
+ flushParagraph();
986
+ flushLists();
987
+ continue;
988
+ }
989
+ const heading = HEADING_LINE.exec(line);
990
+ if (heading) {
991
+ flushParagraph();
992
+ flushLists();
993
+ const level = Math.min(heading[1].length, 3);
994
+ blocks.push({ kind: "heading", level, text: heading[2].trim() });
995
+ continue;
996
+ }
997
+ const ordered = ORDERED_LINE.exec(line);
998
+ if (ordered) {
999
+ flushParagraph();
1000
+ flushBullets();
1001
+ orderedItems.push(ordered[2].trim());
1002
+ continue;
1003
+ }
1004
+ const bullet = BULLET_LINE.exec(line);
1005
+ if (bullet) {
1006
+ flushParagraph();
1007
+ flushOrdered();
1008
+ bulletItems.push(bullet[1].trim());
1009
+ continue;
1010
+ }
1011
+ flushLists();
1012
+ paragraph.push(line);
1013
+ }
1014
+ flushParagraph();
1015
+ flushLists();
1016
+ flushCode();
1017
+ return blocks.length > 0 ? blocks : [{ kind: "paragraph", text: content }];
1018
+ }
1019
+ function parseInlineMarkdown(text) {
1020
+ if (!text) return [{ kind: "text", value: "" }];
1021
+ const segments = [];
1022
+ let lastIndex = 0;
1023
+ INLINE_PATTERN.lastIndex = 0;
1024
+ for (let match = INLINE_PATTERN.exec(text); match; match = INLINE_PATTERN.exec(text)) {
1025
+ if (match.index > lastIndex) {
1026
+ segments.push({ kind: "text", value: text.slice(lastIndex, match.index) });
1027
+ }
1028
+ if (match[1] && match[2]) {
1029
+ segments.push({ kind: "link", value: match[1].trim(), href: match[2] });
1030
+ } else if (match[3] || match[4]) {
1031
+ segments.push({ kind: "bold", value: (match[3] ?? match[4]).trim() });
1032
+ } else if (match[5]) {
1033
+ segments.push({ kind: "code", value: match[5] });
1034
+ } else if (match[6] || match[7]) {
1035
+ segments.push({ kind: "italic", value: (match[6] ?? match[7]).trim() });
1036
+ }
1037
+ lastIndex = INLINE_PATTERN.lastIndex;
1038
+ }
1039
+ if (lastIndex < text.length) {
1040
+ segments.push({ kind: "text", value: text.slice(lastIndex) });
1041
+ }
1042
+ return segments.length > 0 ? segments : [{ kind: "text", value: text }];
1043
+ }
1044
+
1045
+ // src/components/ChatMarkdown.tsx
1046
+ function segmentKey(segment, index) {
1047
+ return `${segment.kind}-${index}-${segment.value.slice(0, 12)}`;
1048
+ }
1049
+ function renderInlineSegments(text, theme, baseStyle) {
1050
+ const segments = parseInlineMarkdown(text);
1051
+ return segments.map((segment, index) => {
1052
+ const key = segmentKey(segment, index);
1053
+ switch (segment.kind) {
1054
+ case "bold":
1055
+ return /* @__PURE__ */ React7__default.createElement(Text, { key, style: [baseStyle, styles2.bold] }, segment.value);
1056
+ case "italic":
1057
+ return /* @__PURE__ */ React7__default.createElement(Text, { key, style: [baseStyle, styles2.italic] }, segment.value);
1058
+ case "code":
1059
+ return /* @__PURE__ */ React7__default.createElement(
1060
+ Text,
1061
+ {
1062
+ key,
1063
+ style: [
1064
+ baseStyle,
1065
+ styles2.code,
1066
+ {
1067
+ backgroundColor: theme.codeBackground ?? (theme.variant === "user" ? "rgba(255,255,255,0.22)" : "rgba(0,0,0,0.06)"),
1068
+ color: theme.codeColor ?? theme.color
1069
+ }
1070
+ ]
1071
+ },
1072
+ segment.value
1073
+ );
1074
+ case "link":
1075
+ return /* @__PURE__ */ React7__default.createElement(
1076
+ Text,
1077
+ {
1078
+ key,
1079
+ accessibilityRole: "link",
1080
+ style: [
1081
+ baseStyle,
1082
+ styles2.link,
1083
+ {
1084
+ color: theme.linkColor ?? (theme.variant === "user" ? theme.color : "#2563EB")
1085
+ }
1086
+ ],
1087
+ onPress: () => {
1088
+ Linking.openURL(segment.href).catch(() => {
1089
+ });
1090
+ }
1091
+ },
1092
+ segment.value
1093
+ );
1094
+ default:
1095
+ return segment.value;
1096
+ }
1097
+ });
1098
+ }
1099
+ function InlineMarkdown({
1100
+ text,
1101
+ theme
1102
+ }) {
1103
+ const baseStyle = {
1104
+ color: theme.color,
1105
+ fontFamily: theme.fontFamily,
1106
+ fontSize: theme.fontSize,
1107
+ lineHeight: theme.lineHeight
1108
+ };
1109
+ return /* @__PURE__ */ React7__default.createElement(Text, { style: baseStyle }, renderInlineSegments(text, theme, baseStyle));
1110
+ }
1111
+ function ChatMarkdown({ content, theme, contentWidth }) {
1112
+ const prepared = enhancePlainAssistantContent(content);
1113
+ const baseTextStyle = {
1114
+ color: theme.color,
1115
+ fontFamily: theme.fontFamily,
1116
+ fontSize: theme.fontSize,
1117
+ lineHeight: theme.lineHeight
1118
+ };
1119
+ const rootStyle = contentWidth ? [styles2.root, { width: contentWidth }] : styles2.root;
1120
+ if (!looksLikeMarkdown(prepared)) {
1121
+ const paragraphs = prepared.split(/\n\n+/).map((part) => part.trim()).filter(Boolean);
1122
+ if (paragraphs.length > 1) {
1123
+ return /* @__PURE__ */ React7__default.createElement(View, { style: rootStyle }, paragraphs.map((paragraph, index) => /* @__PURE__ */ React7__default.createElement(View, { key: `plain-p-${index}`, style: index > 0 ? styles2.blockGap : void 0 }, /* @__PURE__ */ React7__default.createElement(Text, { style: baseTextStyle }, paragraph))));
1124
+ }
1125
+ return /* @__PURE__ */ React7__default.createElement(Text, { style: baseTextStyle }, prepared);
1126
+ }
1127
+ const blocks = parseMarkdownBlocks(prepared);
1128
+ return /* @__PURE__ */ React7__default.createElement(View, { style: rootStyle }, blocks.map((block, blockIndex) => {
1129
+ if (block.kind === "paragraph") {
1130
+ return /* @__PURE__ */ React7__default.createElement(View, { key: `p-${blockIndex}`, style: blockIndex > 0 ? styles2.blockGap : void 0 }, /* @__PURE__ */ React7__default.createElement(InlineMarkdown, { text: block.text, theme }));
1131
+ }
1132
+ if (block.kind === "heading") {
1133
+ const scale = block.level === 1 ? 1.25 : block.level === 2 ? 1.15 : 1.05;
1134
+ const headingSize = Math.round((theme.fontSize ?? 15) * scale);
1135
+ return /* @__PURE__ */ React7__default.createElement(View, { key: `h-${blockIndex}`, style: blockIndex > 0 ? styles2.blockGap : void 0 }, /* @__PURE__ */ React7__default.createElement(
1136
+ Text,
1137
+ {
1138
+ accessibilityRole: "header",
1139
+ style: [
1140
+ baseTextStyle,
1141
+ styles2.bold,
1142
+ { fontSize: headingSize, lineHeight: Math.round(headingSize * 1.35) }
1143
+ ]
1144
+ },
1145
+ renderInlineSegments(block.text, theme, {
1146
+ ...baseTextStyle,
1147
+ fontSize: headingSize,
1148
+ lineHeight: Math.round(headingSize * 1.35)
1149
+ })
1150
+ ));
1151
+ }
1152
+ if (block.kind === "codeblock") {
1153
+ return /* @__PURE__ */ React7__default.createElement(
1154
+ View,
1155
+ {
1156
+ key: `code-${blockIndex}`,
1157
+ style: [
1158
+ styles2.codeBlock,
1159
+ blockIndex > 0 ? styles2.blockGap : void 0,
1160
+ {
1161
+ backgroundColor: theme.codeBackground ?? (theme.variant === "user" ? "rgba(255,255,255,0.18)" : "rgba(0,0,0,0.055)")
1162
+ }
1163
+ ]
1164
+ },
1165
+ /* @__PURE__ */ React7__default.createElement(
1166
+ Text,
1167
+ {
1168
+ style: [
1169
+ styles2.code,
1170
+ {
1171
+ color: theme.codeColor ?? theme.color,
1172
+ fontSize: (theme.fontSize ?? 15) - 1,
1173
+ lineHeight: Math.round(((theme.fontSize ?? 15) - 1) * 1.45)
1174
+ }
1175
+ ]
1176
+ },
1177
+ block.code
1178
+ )
1179
+ );
1180
+ }
1181
+ if (block.kind === "bullet") {
1182
+ return /* @__PURE__ */ React7__default.createElement(View, { key: `ul-${blockIndex}`, style: blockIndex > 0 ? styles2.blockGap : void 0 }, block.items.map((item, itemIndex) => /* @__PURE__ */ React7__default.createElement(
1183
+ Text,
1184
+ {
1185
+ key: `ul-${blockIndex}-${itemIndex}`,
1186
+ style: [baseTextStyle, itemIndex > 0 ? styles2.listItemGap : void 0]
1187
+ },
1188
+ "\u2022 ",
1189
+ renderInlineSegments(item, theme, baseTextStyle)
1190
+ )));
1191
+ }
1192
+ return /* @__PURE__ */ React7__default.createElement(View, { key: `ol-${blockIndex}`, style: blockIndex > 0 ? styles2.blockGap : void 0 }, block.items.map((item, itemIndex) => /* @__PURE__ */ React7__default.createElement(
1193
+ Text,
1194
+ {
1195
+ key: `ol-${blockIndex}-${itemIndex}`,
1196
+ style: [baseTextStyle, itemIndex > 0 ? styles2.listItemGap : void 0]
1197
+ },
1198
+ `${itemIndex + 1}. `,
1199
+ renderInlineSegments(item, theme, baseTextStyle)
1200
+ )));
1201
+ }));
1202
+ }
1203
+ var styles2 = StyleSheet.create({
1204
+ root: {
1205
+ flexShrink: 1,
1206
+ alignSelf: "stretch"
1207
+ },
1208
+ blockGap: {
1209
+ marginTop: 8
1210
+ },
1211
+ listItemGap: {
1212
+ marginTop: 4
1213
+ },
1214
+ bold: {
1215
+ fontWeight: "700"
1216
+ },
1217
+ italic: {
1218
+ fontStyle: "italic"
1219
+ },
1220
+ code: {
1221
+ fontFamily: "Menlo",
1222
+ borderRadius: 4,
1223
+ paddingHorizontal: 4,
1224
+ paddingVertical: 1
1225
+ },
1226
+ codeBlock: {
1227
+ borderRadius: 8,
1228
+ paddingHorizontal: 10,
1229
+ paddingVertical: 8,
1230
+ alignSelf: "stretch"
1231
+ },
1232
+ link: {
1233
+ textDecorationLine: "underline"
1234
+ }
1235
+ });
1236
+ var ThemeContext = createContext(defaultLightTheme);
1237
+ function AppilotsThemeProvider({
1238
+ theme,
1239
+ mode = "auto",
1240
+ children
1241
+ }) {
1242
+ const systemScheme = useColorScheme();
1243
+ const tokens = useMemo(
1244
+ () => mergeThemeTokens(resolveBaseTheme(mode, systemScheme), theme ?? void 0),
1245
+ [mode, systemScheme, theme]
1246
+ );
1247
+ return /* @__PURE__ */ React7__default.createElement(ThemeContext.Provider, { value: tokens }, children);
1248
+ }
1249
+ function useAppilotsTheme() {
1250
+ return useContext(ThemeContext);
1251
+ }
1252
+
1253
+ // src/theme/tailwindBridge.ts
1254
+ var PREFERRED_SHADES = ["500", "600", "400", "700", "300", "800", "200", "900"];
1255
+ function pickColor(value) {
1256
+ if (!value) return void 0;
1257
+ if (typeof value === "string") return value;
1258
+ for (const shade of PREFERRED_SHADES) {
1259
+ if (typeof value[shade] === "string") return value[shade];
1260
+ }
1261
+ for (const v of Object.values(value)) {
1262
+ if (typeof v === "string") return v;
1263
+ }
1264
+ return void 0;
1265
+ }
1266
+ function parseSize(value) {
1267
+ if (typeof value === "number") return value;
1268
+ if (Array.isArray(value)) return parseSize(value[0]);
1269
+ if (typeof value !== "string") return void 0;
1270
+ const trimmed = value.trim();
1271
+ if (trimmed.endsWith("rem")) {
1272
+ const n2 = parseFloat(trimmed);
1273
+ if (!Number.isNaN(n2)) return Math.round(n2 * 16);
1274
+ }
1275
+ if (trimmed.endsWith("px")) {
1276
+ const n2 = parseFloat(trimmed);
1277
+ if (!Number.isNaN(n2)) return Math.round(n2);
1278
+ }
1279
+ const n = parseFloat(trimmed);
1280
+ return Number.isNaN(n) ? void 0 : Math.round(n);
1281
+ }
1282
+ function pickFontFamily(value) {
1283
+ if (!value) return void 0;
1284
+ if (typeof value === "string") return value;
1285
+ return value[0];
1286
+ }
1287
+ function appilotsThemeFromTailwind(config) {
1288
+ const colors = config.theme?.colors ?? {};
1289
+ const out = {};
1290
+ const c = {};
1291
+ const primary = pickColor(colors.primary);
1292
+ if (primary) c.primary = primary;
1293
+ const secondary = pickColor(colors.secondary ?? colors.accent);
1294
+ if (secondary) c.secondary = secondary;
1295
+ const bg = pickColor(colors.background ?? colors.white);
1296
+ if (bg) c.background = bg;
1297
+ const surface = pickColor(colors.surface ?? colors.gray ?? colors.slate);
1298
+ if (surface) c.surface = surface;
1299
+ const textColor = pickColor(colors.text ?? colors.foreground ?? colors.black);
1300
+ if (textColor) c.text = textColor;
1301
+ const muted = pickColor(colors.muted ?? colors.gray);
1302
+ if (muted && muted !== c.text) c.textSecondary = muted;
1303
+ const border = pickColor(colors.border ?? colors.gray);
1304
+ if (border) c.border = border;
1305
+ const success = pickColor(colors.success ?? colors.green ?? colors.emerald);
1306
+ if (success) c.success = success;
1307
+ const warning = pickColor(colors.warning ?? colors.yellow ?? colors.amber);
1308
+ if (warning) c.warning = warning;
1309
+ const error = pickColor(colors.error ?? colors.danger ?? colors.red);
1310
+ if (error) c.error = error;
1311
+ if (Object.keys(c).length > 0) out.colors = c;
1312
+ const fontFamily = pickFontFamily(
1313
+ config.theme?.fontFamily?.sans ?? config.theme?.fontFamily?.body
1314
+ );
1315
+ const sizeXs = parseSize(config.theme?.fontSize?.xs);
1316
+ const sizeSm = parseSize(config.theme?.fontSize?.sm);
1317
+ const sizeBase = parseSize(config.theme?.fontSize?.base);
1318
+ const sizeLg = parseSize(config.theme?.fontSize?.lg);
1319
+ const typography = {};
1320
+ if (fontFamily) typography.fontFamily = fontFamily;
1321
+ if (sizeXs) typography.fontSizeXs = sizeXs;
1322
+ if (sizeSm) typography.fontSizeSm = sizeSm;
1323
+ if (sizeBase) typography.fontSizeBase = sizeBase;
1324
+ if (sizeLg) typography.fontSizeLg = sizeLg;
1325
+ if (Object.keys(typography).length > 0) out.typography = typography;
1326
+ const radiiSm = parseSize(config.theme?.borderRadius?.sm);
1327
+ const radiiMd = parseSize(config.theme?.borderRadius?.md);
1328
+ const radiiLg = parseSize(config.theme?.borderRadius?.lg);
1329
+ const radii = {};
1330
+ if (radiiSm) radii.sm = radiiSm;
1331
+ if (radiiMd) radii.md = radiiMd;
1332
+ if (radiiLg) radii.lg = radiiLg;
1333
+ if (Object.keys(radii).length > 0) out.radii = radii;
1334
+ return out;
1335
+ }
1336
+ var appilotsTheme = {
1337
+ fromTailwind: appilotsThemeFromTailwind
1338
+ };
1339
+
1340
+ // src/i18n/bundles/pt-BR.ts
1341
+ var ptBR = {
1342
+ chatTitle: "Assistente Appilots",
1343
+ clearButton: "Limpar",
1344
+ inputPlaceholder: "Digite uma mensagem...",
1345
+ emptyTitle: "Como posso ajudar?",
1346
+ emptySubtitle: "Navego, preencho formul\xE1rios e executo a\xE7\xF5es neste app.",
1347
+ thinking: "Pensando...",
1348
+ statusAnalyzing: "Analisando a tela...",
1349
+ statusWaitingApp: "Aguardando o app responder...",
1350
+ statusAdjusting: "Ajustando o plano...",
1351
+ poweredBy: "Powered by Appilots",
1352
+ approve: "Aprovar",
1353
+ reject: "Rejeitar",
1354
+ cancelledByUser: "Cancelado pelo usu\xE1rio",
1355
+ unknownAction: "A\xE7\xE3o desconhecida",
1356
+ somethingWentWrong: "Algo deu errado",
1357
+ openChatA11y: "Abrir chat do assistente",
1358
+ closeChatA11y: "Fechar chat do assistente",
1359
+ stopGeneratingA11y: "Parar gera\xE7\xE3o",
1360
+ talkToHumanA11y: "Falar com um atendente humano",
1361
+ humanAgentLabel: "Atendente",
1362
+ escalationRequested: "Certo! Chamei um atendente humano. Voc\xEA pode continuar usando o app normalmente enquanto isso.",
1363
+ escalationConnected: "Um atendente entrou na conversa.",
1364
+ escalationResolved: "O atendimento humano foi encerrado. Voltei a te ajudar por aqui.",
1365
+ escalationOffer: "Se preferir, posso chamar uma pessoa para ajudar.",
1366
+ escalationOfferChip: "Falar com um atendente",
1367
+ escalationPendingBanner: "Aguardando um atendente...",
1368
+ escalationActiveBanner: "Voc\xEA est\xE1 falando com um atendente"
1369
+ };
1370
+
1371
+ // src/i18n/bundles/en.ts
1372
+ var en = {
1373
+ chatTitle: "Appilots Assistant",
1374
+ clearButton: "Clear",
1375
+ inputPlaceholder: "Type a message...",
1376
+ emptyTitle: "How can I help?",
1377
+ emptySubtitle: "I navigate, fill forms, and run actions in this app.",
1378
+ thinking: "Thinking...",
1379
+ statusAnalyzing: "Reading the screen...",
1380
+ statusWaitingApp: "Waiting for the app...",
1381
+ statusAdjusting: "Adjusting the plan...",
1382
+ poweredBy: "Powered by Appilots",
1383
+ approve: "Approve",
1384
+ reject: "Reject",
1385
+ cancelledByUser: "Cancelled by user",
1386
+ unknownAction: "Unknown action",
1387
+ somethingWentWrong: "Something went wrong",
1388
+ openChatA11y: "Open assistant chat",
1389
+ closeChatA11y: "Close assistant chat",
1390
+ stopGeneratingA11y: "Stop generating",
1391
+ talkToHumanA11y: "Talk to a human agent",
1392
+ humanAgentLabel: "Agent",
1393
+ escalationRequested: "Done! I've called a human agent. You can keep using the app normally in the meantime.",
1394
+ escalationConnected: "A human agent joined the conversation.",
1395
+ escalationResolved: "The human handoff has ended. I'm back to help you here.",
1396
+ escalationOffer: "If you prefer, I can bring in a person to help.",
1397
+ escalationOfferChip: "Talk to a human",
1398
+ escalationPendingBanner: "Waiting for a human agent...",
1399
+ escalationActiveBanner: "You're talking to a human agent"
1400
+ };
1401
+
1402
+ // src/i18n/bundles/es.ts
1403
+ var es = {
1404
+ chatTitle: "Asistente Appilots",
1405
+ clearButton: "Limpiar",
1406
+ inputPlaceholder: "Escribe un mensaje...",
1407
+ emptyTitle: "\xBFEn qu\xE9 puedo ayudarte?",
1408
+ emptySubtitle: "Navego, relleno formularios y ejecuto acciones en esta app.",
1409
+ thinking: "Pensando...",
1410
+ statusAnalyzing: "Analizando la pantalla...",
1411
+ statusWaitingApp: "Esperando a la app...",
1412
+ statusAdjusting: "Ajustando el plan...",
1413
+ poweredBy: "Hecho con Appilots",
1414
+ approve: "Aprobar",
1415
+ reject: "Rechazar",
1416
+ cancelledByUser: "Cancelado por el usuario",
1417
+ unknownAction: "Acci\xF3n desconocida",
1418
+ somethingWentWrong: "Algo sali\xF3 mal",
1419
+ openChatA11y: "Abrir chat del asistente",
1420
+ closeChatA11y: "Cerrar chat del asistente",
1421
+ stopGeneratingA11y: "Detener generaci\xF3n",
1422
+ talkToHumanA11y: "Hablar con un agente humano",
1423
+ humanAgentLabel: "Agente",
1424
+ escalationRequested: "\xA1Listo! Llam\xE9 a un agente humano. Mientras tanto puedes seguir usando la app con normalidad.",
1425
+ escalationConnected: "Un agente humano se uni\xF3 a la conversaci\xF3n.",
1426
+ escalationResolved: "La atenci\xF3n humana termin\xF3. Vuelvo a ayudarte por aqu\xED.",
1427
+ escalationOffer: "Si prefieres, puedo llamar a una persona para ayudarte.",
1428
+ escalationOfferChip: "Hablar con un agente",
1429
+ escalationPendingBanner: "Esperando a un agente...",
1430
+ escalationActiveBanner: "Est\xE1s hablando con un agente humano"
1431
+ };
1432
+ var BUNDLES = {
1433
+ "pt-BR": ptBR,
1434
+ en,
1435
+ es
1436
+ };
1437
+ var DEFAULT_LOCALE = "pt-BR";
1438
+ function detectDeviceLocale() {
1439
+ try {
1440
+ let raw;
1441
+ if (Platform.OS === "ios") {
1442
+ const settings = NativeModules?.SettingsManager?.settings;
1443
+ raw = settings?.AppleLocale || (Array.isArray(settings?.AppleLanguages) ? settings?.AppleLanguages?.[0] : void 0);
1444
+ } else if (Platform.OS === "android") {
1445
+ raw = NativeModules?.I18nManager?.localeIdentifier;
1446
+ }
1447
+ if (!raw || typeof raw !== "string") return null;
1448
+ const lower = raw.toLowerCase().replace("_", "-");
1449
+ if (lower.startsWith("pt")) return "pt-BR";
1450
+ if (lower.startsWith("es")) return "es";
1451
+ if (lower.startsWith("en")) return "en";
1452
+ return null;
1453
+ } catch {
1454
+ return null;
1455
+ }
1456
+ }
1457
+ function resolveLocale(prop) {
1458
+ if (prop && BUNDLES[prop]) return prop;
1459
+ const detected = detectDeviceLocale();
1460
+ if (detected && BUNDLES[detected]) return detected;
1461
+ return DEFAULT_LOCALE;
1462
+ }
1463
+ var I18nContext = createContext(null);
1464
+ function AppilotsI18nProvider({
1465
+ locale,
1466
+ overrides,
1467
+ children
1468
+ }) {
1469
+ const value = useMemo(() => {
1470
+ const resolved = resolveLocale(locale ?? null);
1471
+ const merged = overrides ? { ...BUNDLES[resolved], ...overrides } : BUNDLES[resolved];
1472
+ return {
1473
+ locale: resolved,
1474
+ strings: merged,
1475
+ t: (key) => merged[key]
1476
+ };
1477
+ }, [locale, overrides]);
1478
+ return /* @__PURE__ */ React7__default.createElement(I18nContext.Provider, { value }, children);
1479
+ }
1480
+ function useAppilotsI18n() {
1481
+ const ctx = useContext(I18nContext);
1482
+ if (ctx) return ctx;
1483
+ return {
1484
+ locale: DEFAULT_LOCALE,
1485
+ strings: BUNDLES[DEFAULT_LOCALE],
1486
+ t: (key) => BUNDLES[DEFAULT_LOCALE][key]
1487
+ };
1488
+ }
1489
+
1490
+ // src/components/AppilotsChat.tsx
1491
+ function translateLegacyTheme(legacy) {
1492
+ const out = {};
1493
+ if (legacy.primaryColor || legacy.backgroundColor || legacy.textColor || legacy.inputBackgroundColor) {
1494
+ out.colors = {};
1495
+ if (legacy.primaryColor) out.colors.primary = legacy.primaryColor;
1496
+ if (legacy.backgroundColor) out.colors.background = legacy.backgroundColor;
1497
+ if (legacy.textColor) out.colors.text = legacy.textColor;
1498
+ if (legacy.inputBackgroundColor) out.colors.surface = legacy.inputBackgroundColor;
1499
+ }
1500
+ if (legacy.borderRadius !== void 0) {
1501
+ out.radii = { lg: legacy.borderRadius, md: legacy.borderRadius };
1502
+ }
1503
+ if (legacy.fontFamily) {
1504
+ out.typography = { fontFamily: legacy.fontFamily };
1505
+ }
1506
+ return out;
1507
+ }
1508
+ function isLegacyTheme(theme) {
1509
+ if (!theme || typeof theme !== "object") return false;
1510
+ const t = theme;
1511
+ return "primaryColor" in t || "backgroundColor" in t || "textColor" in t || "inputBackgroundColor" in t || typeof t.borderRadius === "number" || typeof t.fontFamily === "string" && !("typography" in t);
1512
+ }
1513
+ function isImageSource(value) {
1514
+ return typeof value === "number" || /^https?:\/\//i.test(value.trim());
1515
+ }
1516
+ var { height: SCREEN_HEIGHT, width: SCREEN_WIDTH } = Dimensions.get("window");
1517
+ var CHAT_HORIZONTAL_PADDING = 16;
1518
+ var BUBBLE_HORIZONTAL_PADDING = 14;
1519
+ var ASSISTANT_CONTENT_WIDTH = SCREEN_WIDTH - CHAT_HORIZONTAL_PADDING * 2 - BUBBLE_HORIZONTAL_PADDING * 2;
1520
+ var APPILOTS_SKIP_PROPS = {
1521
+ __appilotsSkip: true,
1522
+ __appilotsInternal: true
1523
+ };
1524
+ function AppilotsChat(props) {
1525
+ const { remotePersonalization } = useAppilotsContext();
1526
+ const themeProp = useMemo(() => {
1527
+ if (!props.theme) return void 0;
1528
+ return isLegacyTheme(props.theme) ? translateLegacyTheme(props.theme) : props.theme;
1529
+ }, [props.theme]);
1530
+ return /* @__PURE__ */ React7__default.createElement(
1531
+ AppilotsThemeProvider,
1532
+ {
1533
+ theme: resolveTheme(themeProp, remotePersonalization),
1534
+ mode: resolveThemeMode(props.themeMode, remotePersonalization)
1535
+ },
1536
+ /* @__PURE__ */ React7__default.createElement(AppilotsI18nProvider, { locale: resolveLocaleProp(props.locale, remotePersonalization) }, /* @__PURE__ */ React7__default.createElement(AppilotsChatInner, { ...props }))
1537
+ );
1538
+ }
1539
+ function AppilotsChatInner({
1540
+ visible: controlledVisible,
1541
+ onClose,
1542
+ placeholder,
1543
+ headerTitle: headerTitleProp,
1544
+ autoExecute = true,
1545
+ assistantName: assistantNameProp,
1546
+ assistantAvatar: assistantAvatarProp,
1547
+ emptyStateIcon: emptyStateIconProp,
1548
+ welcomeMessage: welcomeMessageProp,
1549
+ poweredByVisible: poweredByVisibleProp,
1550
+ mode = "bubble",
1551
+ position = "bottom-right",
1552
+ triggerButton = "default",
1553
+ triggerIcon,
1554
+ triggerButtonColor: triggerButtonColorProp,
1555
+ triggerButtonImage: triggerButtonImageProp
1556
+ }) {
1557
+ const theme = useAppilotsTheme();
1558
+ const { strings, t } = useAppilotsI18n();
1559
+ const {
1560
+ messages,
1561
+ isLoading,
1562
+ loadingStatusKey,
1563
+ sendMessage,
1564
+ cancelMessage,
1565
+ clearMessages,
1566
+ escalation,
1567
+ requestHuman
1568
+ } = useAppilotsChat({
1569
+ errorPrefix: t("somethingWentWrong"),
1570
+ escalationStrings: {
1571
+ requested: t("escalationRequested"),
1572
+ connected: t("escalationConnected"),
1573
+ resolved: t("escalationResolved"),
1574
+ offer: t("escalationOffer")
1575
+ }
1576
+ });
1577
+ const {
1578
+ pendingActions,
1579
+ executingActions,
1580
+ completedActions,
1581
+ approveAction,
1582
+ rejectAction
1583
+ } = useAppilotsActions({ autoExecute });
1584
+ const { emit, remotePersonalization } = useAppilotsContext();
1585
+ const {
1586
+ headerTitle: resolvedTitle,
1587
+ assistantAvatar,
1588
+ emptyStateIcon,
1589
+ welcomeMessage,
1590
+ poweredByVisible,
1591
+ triggerButtonColor,
1592
+ triggerButtonImage
1593
+ } = resolvePersonalization(
1594
+ {
1595
+ headerTitle: headerTitleProp,
1596
+ assistantName: assistantNameProp,
1597
+ assistantAvatar: assistantAvatarProp,
1598
+ emptyStateIcon: emptyStateIconProp,
1599
+ welcomeMessage: welcomeMessageProp,
1600
+ poweredByVisible: poweredByVisibleProp,
1601
+ triggerButtonColor: triggerButtonColorProp,
1602
+ triggerButtonImage: triggerButtonImageProp
1603
+ },
1604
+ remotePersonalization
1605
+ );
1606
+ const allActions = [...pendingActions, ...executingActions, ...completedActions];
1607
+ const timeline = buildChatTimeline(messages, allActions);
1608
+ const lastAssistantMessageId = React7__default.useMemo(() => {
1609
+ for (let i = messages.length - 1; i >= 0; i--) {
1610
+ if (messages[i].role === "assistant") return messages[i].id;
1611
+ }
1612
+ return void 0;
1613
+ }, [messages]);
1614
+ const insets = useSafeAreaInsets();
1615
+ const [internalVisible, setInternalVisible] = useState(mode === "inline");
1616
+ const [inputText, setInputText] = useState("");
1617
+ const flatListRef = useRef(null);
1618
+ const slideAnim = useRef(new Animated.Value(SCREEN_HEIGHT)).current;
1619
+ const isVisible = mode === "inline" ? true : controlledVisible !== void 0 ? controlledVisible : internalVisible;
1620
+ const showSuggestedPrompts = shouldShowSuggestedPrompts({
1621
+ isVisible,
1622
+ messageCount: messages.length,
1623
+ inputText
1624
+ });
1625
+ const { prompts: suggestedPrompts } = useSuggestedPrompts({
1626
+ enabled: showSuggestedPrompts,
1627
+ limit: 3
1628
+ });
1629
+ const openChat = useCallback(() => {
1630
+ setInternalVisible(true);
1631
+ emit({ type: "chat:open", timestamp: Date.now(), data: {} });
1632
+ }, [emit]);
1633
+ const closeChat = useCallback(() => {
1634
+ setInternalVisible(false);
1635
+ onClose?.();
1636
+ emit({ type: "chat:close", timestamp: Date.now(), data: {} });
1637
+ }, [onClose, emit]);
1638
+ useEffect(() => {
1639
+ if (mode === "inline") return;
1640
+ Animated.spring(slideAnim, {
1641
+ toValue: isVisible ? 0 : SCREEN_HEIGHT,
1642
+ useNativeDriver: true,
1643
+ tension: 65,
1644
+ friction: 11
1645
+ }).start();
1646
+ }, [isVisible, slideAnim, mode]);
1647
+ useEffect(() => {
1648
+ if (messages.length > 0) {
1649
+ setTimeout(() => flatListRef.current?.scrollToEnd({ animated: true }), 100);
1650
+ }
1651
+ }, [messages.length]);
1652
+ const handleSend = useCallback(() => {
1653
+ if (!inputText.trim()) return;
1654
+ sendMessage(inputText.trim());
1655
+ setInputText("");
1656
+ }, [inputText, sendMessage]);
1657
+ const renderMessage = useCallback(
1658
+ ({ item }) => {
1659
+ if (item.role === "system") {
1660
+ const isOffer = item.metadata?.escalationOffer === true;
1661
+ return /* @__PURE__ */ React7__default.createElement(View, { style: styles3.systemMessageContainer }, /* @__PURE__ */ React7__default.createElement(
1662
+ Text,
1663
+ {
1664
+ style: [
1665
+ styles3.systemMessageText,
1666
+ {
1667
+ color: theme.colors.textSecondary,
1668
+ fontFamily: theme.typography.fontFamily,
1669
+ fontSize: theme.typography.fontSizeSm
1670
+ }
1671
+ ]
1672
+ },
1673
+ item.content
1674
+ ), isOffer && !escalation && /* @__PURE__ */ React7__default.createElement(
1675
+ TouchableOpacity,
1676
+ {
1677
+ ...APPILOTS_SKIP_PROPS,
1678
+ style: [
1679
+ styles3.chip,
1680
+ styles3.escalationOfferChip,
1681
+ {
1682
+ backgroundColor: theme.colors.surface,
1683
+ borderColor: theme.colors.border,
1684
+ borderRadius: 999
1685
+ }
1686
+ ],
1687
+ onPress: () => requestHuman("agent_gave_up"),
1688
+ activeOpacity: 0.7,
1689
+ accessibilityRole: "button",
1690
+ accessibilityLabel: strings.talkToHumanA11y,
1691
+ testID: "escalation-offer-chip"
1692
+ },
1693
+ /* @__PURE__ */ React7__default.createElement(HeadsetIcon, { size: 14, color: theme.colors.textSecondary }),
1694
+ /* @__PURE__ */ React7__default.createElement(
1695
+ Text,
1696
+ {
1697
+ style: [
1698
+ styles3.chipText,
1699
+ {
1700
+ color: theme.colors.text,
1701
+ fontFamily: theme.typography.fontFamily,
1702
+ fontSize: theme.typography.fontSizeSm
1703
+ }
1704
+ ]
1705
+ },
1706
+ strings.escalationOfferChip
1707
+ )
1708
+ ));
1709
+ }
1710
+ const isUser = item.role === "user";
1711
+ const isHumanAgent = item.role === "human_agent";
1712
+ const useWideAssistantLayout = !isUser && needsWideChatLayout(item.content);
1713
+ if (isHumanAgent) {
1714
+ return /* @__PURE__ */ React7__default.createElement(View, { style: styles3.humanAgentContainer, testID: "human-agent-message" }, /* @__PURE__ */ React7__default.createElement(View, { style: styles3.humanAgentLabelRow }, /* @__PURE__ */ React7__default.createElement(HeadsetIcon, { size: 11, color: theme.colors.secondary }), /* @__PURE__ */ React7__default.createElement(
1715
+ Text,
1716
+ {
1717
+ style: [
1718
+ styles3.humanAgentLabel,
1719
+ {
1720
+ color: theme.colors.textSecondary,
1721
+ fontFamily: theme.typography.fontFamily
1722
+ }
1723
+ ]
1724
+ },
1725
+ escalation?.operatorName ?? strings.humanAgentLabel
1726
+ )), /* @__PURE__ */ React7__default.createElement(
1727
+ View,
1728
+ {
1729
+ accessibilityLabel: item.content,
1730
+ style: [
1731
+ styles3.messageBubble,
1732
+ styles3.assistantBubble,
1733
+ { backgroundColor: theme.colors.surface }
1734
+ ]
1735
+ },
1736
+ /* @__PURE__ */ React7__default.createElement(
1737
+ ChatMarkdown,
1738
+ {
1739
+ content: item.content,
1740
+ theme: {
1741
+ color: theme.colors.text,
1742
+ fontFamily: theme.typography.fontFamily,
1743
+ fontSize: theme.typography.fontSizeBase,
1744
+ lineHeight: 20,
1745
+ variant: "assistant"
1746
+ }
1747
+ }
1748
+ )
1749
+ ));
1750
+ }
1751
+ return /* @__PURE__ */ React7__default.createElement(
1752
+ View,
1753
+ {
1754
+ testID: isUser ? void 0 : item.id === lastAssistantMessageId ? "assistant-last-message" : "assistant-message",
1755
+ accessibilityLabel: isUser ? void 0 : item.content,
1756
+ style: [
1757
+ styles3.messageBubble,
1758
+ isUser ? [styles3.userBubble, { backgroundColor: theme.colors.primary }] : [
1759
+ styles3.assistantBubble,
1760
+ useWideAssistantLayout && styles3.assistantBubbleWide,
1761
+ { backgroundColor: theme.colors.surface }
1762
+ ]
1763
+ ]
1764
+ },
1765
+ /* @__PURE__ */ React7__default.createElement(
1766
+ ChatMarkdown,
1767
+ {
1768
+ content: item.content,
1769
+ contentWidth: useWideAssistantLayout ? ASSISTANT_CONTENT_WIDTH : void 0,
1770
+ theme: {
1771
+ color: isUser ? theme.colors.userBubbleText : theme.colors.bubbleText,
1772
+ fontFamily: theme.typography.fontFamily,
1773
+ fontSize: theme.typography.fontSizeBase,
1774
+ lineHeight: 20,
1775
+ variant: isUser ? "user" : "assistant"
1776
+ }
1777
+ }
1778
+ )
1779
+ );
1780
+ },
1781
+ [theme, lastAssistantMessageId, escalation, requestHuman, strings]
1782
+ );
1783
+ const requireApproval = useCallback(
1784
+ (action) => {
1785
+ if (action.status !== "pending") return false;
1786
+ if (action.type === "confirm") return true;
1787
+ return !autoExecute;
1788
+ },
1789
+ [autoExecute]
1790
+ );
1791
+ const renderBreadcrumb = useCallback(
1792
+ (actions) => /* @__PURE__ */ React7__default.createElement(
1793
+ ActionBreadcrumb,
1794
+ {
1795
+ actions,
1796
+ primaryColor: theme.colors.primary,
1797
+ textColor: theme.colors.text,
1798
+ fontFamily: theme.typography.fontFamily,
1799
+ palette: {
1800
+ textSecondary: theme.colors.textSecondary,
1801
+ surface: theme.colors.surface,
1802
+ border: theme.colors.border,
1803
+ success: theme.colors.success,
1804
+ warning: theme.colors.warning,
1805
+ error: theme.colors.error
1806
+ },
1807
+ requireApprovalFor: requireApproval,
1808
+ onApprove: approveAction,
1809
+ onReject: rejectAction
1810
+ }
1811
+ ),
1812
+ [
1813
+ theme.colors.primary,
1814
+ theme.colors.text,
1815
+ theme.colors.textSecondary,
1816
+ theme.colors.surface,
1817
+ theme.colors.border,
1818
+ theme.colors.success,
1819
+ theme.colors.warning,
1820
+ theme.colors.error,
1821
+ theme.typography.fontFamily,
1822
+ requireApproval,
1823
+ approveAction,
1824
+ rejectAction
1825
+ ]
1826
+ );
1827
+ const resolvedHeaderTitle = resolvedTitle ?? t("chatTitle");
1828
+ const resolvedPlaceholder = placeholder ?? t("inputPlaceholder");
1829
+ const resolvedEmptyTitle = t("emptyTitle");
1830
+ const resolvedEmptySubtitle = welcomeMessage ?? t("emptySubtitle");
1831
+ const showEmptySubtitle = resolvedEmptySubtitle.length > 0 && !(showSuggestedPrompts && suggestedPrompts.length > 0);
1832
+ const renderAvatar = () => {
1833
+ if (!assistantAvatar) {
1834
+ return /* @__PURE__ */ React7__default.createElement(View, { style: [styles3.headerDot, { backgroundColor: theme.colors.primary }] });
1835
+ }
1836
+ if (typeof assistantAvatar === "string") {
1837
+ return /* @__PURE__ */ React7__default.createElement(Image, { source: { uri: assistantAvatar }, style: styles3.headerAvatar });
1838
+ }
1839
+ if (typeof assistantAvatar === "number") {
1840
+ return /* @__PURE__ */ React7__default.createElement(Image, { source: assistantAvatar, style: styles3.headerAvatar });
1841
+ }
1842
+ return /* @__PURE__ */ React7__default.createElement(View, { style: styles3.headerAvatarSlot }, assistantAvatar);
1843
+ };
1844
+ const showTrigger = mode === "bubble" && !isVisible && controlledVisible === void 0 && triggerButton !== "none";
1845
+ const fabPositionStyle = (() => {
1846
+ switch (position) {
1847
+ case "bottom-left":
1848
+ return { bottom: 24, left: 24 };
1849
+ case "top-right":
1850
+ return { top: 24, right: 24 };
1851
+ case "top-left":
1852
+ return { top: 24, left: 24 };
1853
+ case "bottom-right":
1854
+ default:
1855
+ return { bottom: 24, right: 24 };
1856
+ }
1857
+ })();
1858
+ const chatSurface = /* @__PURE__ */ React7__default.createElement(
1859
+ View,
1860
+ {
1861
+ ...APPILOTS_SKIP_PROPS,
1862
+ style: [
1863
+ styles3.chatContainer,
1864
+ mode === "fullscreen" && styles3.chatContainerFullscreen,
1865
+ mode === "sidebar" && styles3.chatContainerSidebar,
1866
+ mode === "inline" && styles3.chatContainerInline,
1867
+ {
1868
+ backgroundColor: theme.colors.background,
1869
+ borderTopLeftRadius: theme.radii.lg,
1870
+ borderTopRightRadius: theme.radii.lg
1871
+ }
1872
+ ]
1873
+ },
1874
+ /* @__PURE__ */ React7__default.createElement(View, { style: [styles3.header, { borderBottomColor: theme.colors.border }] }, /* @__PURE__ */ React7__default.createElement(View, { style: styles3.headerLeft }, renderAvatar(), /* @__PURE__ */ React7__default.createElement(
1875
+ Text,
1876
+ {
1877
+ style: [
1878
+ styles3.headerTitle,
1879
+ {
1880
+ color: theme.colors.text,
1881
+ fontFamily: theme.typography.fontFamily,
1882
+ fontSize: theme.typography.fontSizeLg
1883
+ }
1884
+ ]
1885
+ },
1886
+ resolvedHeaderTitle
1887
+ )), /* @__PURE__ */ React7__default.createElement(View, { style: styles3.headerActions }, /* @__PURE__ */ React7__default.createElement(
1888
+ TouchableOpacity,
1889
+ {
1890
+ ...APPILOTS_SKIP_PROPS,
1891
+ onPress: () => requestHuman("user_requested"),
1892
+ style: styles3.headerButton,
1893
+ accessibilityRole: "button",
1894
+ accessibilityLabel: strings.talkToHumanA11y,
1895
+ testID: "escalation-header-button"
1896
+ },
1897
+ /* @__PURE__ */ React7__default.createElement(
1898
+ HeadsetIcon,
1899
+ {
1900
+ size: 16,
1901
+ color: escalation ? theme.colors.primary : theme.colors.textSecondary
1902
+ }
1903
+ )
1904
+ ), /* @__PURE__ */ React7__default.createElement(TouchableOpacity, { ...APPILOTS_SKIP_PROPS, onPress: clearMessages, style: styles3.headerButton }, /* @__PURE__ */ React7__default.createElement(Text, { style: [styles3.headerButtonText, { color: theme.colors.textSecondary }] }, strings.clearButton)), mode !== "inline" && /* @__PURE__ */ React7__default.createElement(
1905
+ TouchableOpacity,
1906
+ {
1907
+ ...APPILOTS_SKIP_PROPS,
1908
+ onPress: closeChat,
1909
+ style: styles3.closeButton,
1910
+ accessibilityLabel: strings.closeChatA11y
1911
+ },
1912
+ /* @__PURE__ */ React7__default.createElement(Text, { style: [styles3.closeButtonText, { color: theme.colors.text }] }, "\u2715")
1913
+ ))),
1914
+ escalation && /* @__PURE__ */ React7__default.createElement(
1915
+ View,
1916
+ {
1917
+ style: [
1918
+ styles3.escalationBanner,
1919
+ {
1920
+ borderBottomColor: theme.colors.border,
1921
+ backgroundColor: theme.colors.primary + "0d"
1922
+ }
1923
+ ],
1924
+ testID: "escalation-banner"
1925
+ },
1926
+ /* @__PURE__ */ React7__default.createElement(HeadsetIcon, { size: 12, color: theme.colors.primary }),
1927
+ /* @__PURE__ */ React7__default.createElement(
1928
+ Text,
1929
+ {
1930
+ style: [
1931
+ styles3.escalationBannerText,
1932
+ {
1933
+ color: theme.colors.textSecondary,
1934
+ fontFamily: theme.typography.fontFamily,
1935
+ fontSize: theme.typography.fontSizeXs
1936
+ }
1937
+ ]
1938
+ },
1939
+ escalation.status === "pending" ? strings.escalationPendingBanner : strings.escalationActiveBanner
1940
+ )
1941
+ ),
1942
+ /* @__PURE__ */ React7__default.createElement(
1943
+ FlatList,
1944
+ {
1945
+ ...APPILOTS_SKIP_PROPS,
1946
+ ref: flatListRef,
1947
+ data: timeline,
1948
+ keyExtractor: (item) => item.type === "message" ? item.data.id : item.key,
1949
+ renderItem: ({ item }) => {
1950
+ if (item.type === "message") {
1951
+ return renderMessage({ item: item.data });
1952
+ }
1953
+ if (item.type === "breadcrumb") {
1954
+ return renderBreadcrumb(item.actions);
1955
+ }
1956
+ return null;
1957
+ },
1958
+ contentContainerStyle: styles3.messageList,
1959
+ ListEmptyComponent: /* @__PURE__ */ React7__default.createElement(View, { style: styles3.emptyState }, emptyStateIcon && isImageSource(emptyStateIcon) ? /* @__PURE__ */ React7__default.createElement(
1960
+ Image,
1961
+ {
1962
+ source: typeof emptyStateIcon === "string" ? { uri: emptyStateIcon } : emptyStateIcon,
1963
+ style: styles3.emptyIconImage,
1964
+ resizeMode: "contain"
1965
+ }
1966
+ ) : /* @__PURE__ */ React7__default.createElement(
1967
+ View,
1968
+ {
1969
+ style: [
1970
+ styles3.emptyIconBadge,
1971
+ { backgroundColor: theme.colors.primary + "14" }
1972
+ ]
1973
+ },
1974
+ emptyStateIcon ? /* @__PURE__ */ React7__default.createElement(Text, { style: styles3.emptyIconEmoji }, emptyStateIcon) : /* @__PURE__ */ React7__default.createElement(ChatIcon, { size: 30, color: theme.colors.primary })
1975
+ ), /* @__PURE__ */ React7__default.createElement(
1976
+ Text,
1977
+ {
1978
+ style: [
1979
+ styles3.emptyTitle,
1980
+ { color: theme.colors.text, fontFamily: theme.typography.fontFamily }
1981
+ ]
1982
+ },
1983
+ resolvedEmptyTitle
1984
+ ), showEmptySubtitle && /* @__PURE__ */ React7__default.createElement(
1985
+ Text,
1986
+ {
1987
+ style: [
1988
+ styles3.emptySubtitle,
1989
+ {
1990
+ color: theme.colors.textSecondary,
1991
+ fontFamily: theme.typography.fontFamily
1992
+ }
1993
+ ],
1994
+ numberOfLines: 2
1995
+ },
1996
+ resolvedEmptySubtitle
1997
+ ), showSuggestedPrompts && suggestedPrompts.length > 0 && /* @__PURE__ */ React7__default.createElement(View, { style: styles3.chipsContainer }, suggestedPrompts.map((prompt) => /* @__PURE__ */ React7__default.createElement(
1998
+ TouchableOpacity,
1999
+ {
2000
+ key: `${prompt.source}:${prompt.text}`,
2001
+ style: [
2002
+ styles3.chip,
2003
+ {
2004
+ backgroundColor: theme.colors.surface,
2005
+ borderColor: theme.colors.border,
2006
+ borderRadius: 999
2007
+ }
2008
+ ],
2009
+ onPress: () => setInputText(prompt.text),
2010
+ activeOpacity: 0.7,
2011
+ accessibilityLabel: `Suggested prompt: ${prompt.text}`
2012
+ },
2013
+ /* @__PURE__ */ React7__default.createElement(
2014
+ Text,
2015
+ {
2016
+ style: [
2017
+ styles3.chipText,
2018
+ {
2019
+ color: theme.colors.buttonText,
2020
+ fontFamily: theme.typography.fontFamily,
2021
+ fontSize: theme.typography.fontSizeSm
2022
+ }
2023
+ ],
2024
+ numberOfLines: 1
2025
+ },
2026
+ prompt.text
2027
+ )
2028
+ )))),
2029
+ ListFooterComponent: isLoading ? /* @__PURE__ */ React7__default.createElement(
2030
+ View,
2031
+ {
2032
+ style: [
2033
+ styles3.messageBubble,
2034
+ styles3.assistantBubble,
2035
+ { backgroundColor: theme.colors.surface }
2036
+ ]
2037
+ },
2038
+ /* @__PURE__ */ React7__default.createElement(
2039
+ Text,
2040
+ {
2041
+ style: [
2042
+ styles3.messageText,
2043
+ { color: theme.colors.textSecondary, fontFamily: theme.typography.fontFamily }
2044
+ ]
2045
+ },
2046
+ strings[loadingStatusKey] ?? strings.thinking
2047
+ )
2048
+ ) : null
2049
+ }
2050
+ ),
2051
+ /* @__PURE__ */ React7__default.createElement(
2052
+ View,
2053
+ {
2054
+ style: [
2055
+ styles3.inputContainer,
2056
+ {
2057
+ borderTopColor: theme.colors.border,
2058
+ paddingBottom: Math.max(insets.bottom, 10)
2059
+ }
2060
+ ]
2061
+ },
2062
+ /* @__PURE__ */ React7__default.createElement(
2063
+ TextInput,
2064
+ {
2065
+ style: [
2066
+ styles3.input,
2067
+ {
2068
+ backgroundColor: theme.colors.surface,
2069
+ color: theme.colors.text,
2070
+ borderRadius: theme.radii.md,
2071
+ fontFamily: theme.typography.fontFamily,
2072
+ fontSize: theme.typography.fontSizeBase,
2073
+ lineHeight: theme.typography.fontSizeBase * 1.35
2074
+ }
2075
+ ],
2076
+ placeholder: resolvedPlaceholder,
2077
+ placeholderTextColor: theme.colors.textSecondary,
2078
+ value: inputText,
2079
+ onChangeText: setInputText,
2080
+ multiline: true,
2081
+ blurOnSubmit: false,
2082
+ scrollEnabled: true,
2083
+ editable: !isLoading,
2084
+ testID: "assistant-input",
2085
+ __appilotsInternal: true
2086
+ }
2087
+ ),
2088
+ isLoading ? /* @__PURE__ */ React7__default.createElement(
2089
+ TouchableOpacity,
2090
+ {
2091
+ style: [
2092
+ styles3.sendButton,
2093
+ {
2094
+ backgroundColor: theme.colors.primary,
2095
+ borderRadius: theme.radii.md
2096
+ }
2097
+ ],
2098
+ onPress: cancelMessage,
2099
+ accessibilityRole: "button",
2100
+ accessibilityLabel: strings.stopGeneratingA11y,
2101
+ testID: "appilots-stop-button",
2102
+ activeOpacity: 0.7
2103
+ },
2104
+ /* @__PURE__ */ React7__default.createElement(Text, { style: styles3.sendButtonText }, "\u25A0")
2105
+ ) : /* @__PURE__ */ React7__default.createElement(
2106
+ TouchableOpacity,
2107
+ {
2108
+ style: [
2109
+ styles3.sendButton,
2110
+ {
2111
+ backgroundColor: inputText.trim() ? theme.colors.primary : theme.colors.primary + "40",
2112
+ borderRadius: theme.radii.md
2113
+ }
2114
+ ],
2115
+ onPress: handleSend,
2116
+ disabled: !inputText.trim(),
2117
+ activeOpacity: 0.7,
2118
+ accessibilityRole: "button",
2119
+ testID: "assistant-send"
2120
+ },
2121
+ /* @__PURE__ */ React7__default.createElement(Text, { style: styles3.sendButtonText }, "\u2191")
2122
+ )
2123
+ ),
2124
+ poweredByVisible && /* @__PURE__ */ React7__default.createElement(View, { style: styles3.poweredByContainer }, /* @__PURE__ */ React7__default.createElement(
2125
+ Text,
2126
+ {
2127
+ style: [
2128
+ styles3.poweredByText,
2129
+ {
2130
+ color: theme.colors.textSecondary,
2131
+ fontFamily: theme.typography.fontFamily,
2132
+ fontSize: theme.typography.fontSizeXs
2133
+ }
2134
+ ]
2135
+ },
2136
+ strings.poweredBy
2137
+ ))
2138
+ );
2139
+ if (mode === "inline") {
2140
+ return chatSurface;
2141
+ }
2142
+ const renderTrigger = () => {
2143
+ if (triggerButton === "none") return null;
2144
+ if (triggerButton !== "default" && React7__default.isValidElement(triggerButton)) {
2145
+ return /* @__PURE__ */ React7__default.createElement(View, { ...APPILOTS_SKIP_PROPS, style: [styles3.fabWrapper, fabPositionStyle] }, /* @__PURE__ */ React7__default.createElement(
2146
+ TouchableOpacity,
2147
+ {
2148
+ ...APPILOTS_SKIP_PROPS,
2149
+ onPress: openChat,
2150
+ activeOpacity: 0.8,
2151
+ accessibilityRole: "button",
2152
+ accessibilityLabel: strings.openChatA11y,
2153
+ testID: "assistant-launcher"
2154
+ },
2155
+ triggerButton
2156
+ ));
2157
+ }
2158
+ return /* @__PURE__ */ React7__default.createElement(
2159
+ TouchableOpacity,
2160
+ {
2161
+ ...APPILOTS_SKIP_PROPS,
2162
+ style: [
2163
+ styles3.fab,
2164
+ fabPositionStyle,
2165
+ { backgroundColor: triggerButtonColor ?? theme.colors.primary }
2166
+ ],
2167
+ onPress: openChat,
2168
+ activeOpacity: 0.8,
2169
+ accessibilityRole: "button",
2170
+ accessibilityLabel: strings.openChatA11y,
2171
+ testID: "assistant-launcher"
2172
+ },
2173
+ triggerIcon ?? (triggerButtonImage ? /* @__PURE__ */ React7__default.createElement(
2174
+ Image,
2175
+ {
2176
+ source: typeof triggerButtonImage === "string" ? { uri: triggerButtonImage } : triggerButtonImage,
2177
+ style: styles3.fabImage,
2178
+ resizeMode: "contain"
2179
+ }
2180
+ ) : /* @__PURE__ */ React7__default.createElement(ChatIcon, { size: 26, color: "#ffffff" }))
2181
+ );
2182
+ };
2183
+ return /* @__PURE__ */ React7__default.createElement(React7__default.Fragment, null, showTrigger && renderTrigger(), /* @__PURE__ */ React7__default.createElement(
2184
+ Modal,
2185
+ {
2186
+ ...APPILOTS_SKIP_PROPS,
2187
+ visible: isVisible,
2188
+ transparent: true,
2189
+ animationType: "none",
2190
+ onRequestClose: closeChat
2191
+ },
2192
+ /* @__PURE__ */ React7__default.createElement(
2193
+ Animated.View,
2194
+ {
2195
+ ...APPILOTS_SKIP_PROPS,
2196
+ style: [
2197
+ mode === "fullscreen" ? styles3.modalContainerFullscreen : styles3.modalContainer,
2198
+ mode === "sidebar" && styles3.modalContainerSidebar,
2199
+ { transform: [{ translateY: slideAnim }] }
2200
+ ]
2201
+ },
2202
+ /* @__PURE__ */ React7__default.createElement(
2203
+ KeyboardAvoidingView,
2204
+ {
2205
+ style: styles3.keyboardView,
2206
+ behavior: Platform.OS === "ios" ? "padding" : "height",
2207
+ keyboardVerticalOffset: 0
2208
+ },
2209
+ chatSurface
2210
+ )
2211
+ )
2212
+ ));
2213
+ }
2214
+ var styles3 = StyleSheet.create({
2215
+ fab: {
2216
+ position: "absolute",
2217
+ width: 56,
2218
+ height: 56,
2219
+ borderRadius: 28,
2220
+ alignItems: "center",
2221
+ justifyContent: "center",
2222
+ elevation: 6,
2223
+ shadowColor: "#000",
2224
+ shadowOffset: { width: 0, height: 3 },
2225
+ shadowOpacity: 0.27,
2226
+ shadowRadius: 4.65,
2227
+ zIndex: 999
2228
+ },
2229
+ fabWrapper: {
2230
+ position: "absolute",
2231
+ zIndex: 999
2232
+ },
2233
+ fabIcon: {
2234
+ fontSize: 24
2235
+ },
2236
+ fabImage: {
2237
+ width: 32,
2238
+ height: 32,
2239
+ borderRadius: 16
2240
+ },
2241
+ modalContainer: {
2242
+ flex: 1,
2243
+ justifyContent: "flex-end",
2244
+ backgroundColor: "rgba(0,0,0,0.4)"
2245
+ },
2246
+ modalContainerFullscreen: {
2247
+ flex: 1,
2248
+ backgroundColor: "rgba(0,0,0,0.4)"
2249
+ },
2250
+ modalContainerSidebar: {
2251
+ // Right-pinned half-width on tablet/web. Stays as bottom-sheet on
2252
+ // narrow phones so we don't hand the user a 50% sliver.
2253
+ flex: 1,
2254
+ flexDirection: "row",
2255
+ justifyContent: "flex-end"
2256
+ },
2257
+ keyboardView: {
2258
+ flex: 1,
2259
+ justifyContent: "flex-end"
2260
+ },
2261
+ chatContainer: {
2262
+ height: SCREEN_HEIGHT * 0.75,
2263
+ shadowColor: "#000",
2264
+ shadowOffset: { width: 0, height: -3 },
2265
+ shadowOpacity: 0.1,
2266
+ shadowRadius: 12,
2267
+ elevation: 10
2268
+ },
2269
+ chatContainerFullscreen: {
2270
+ height: SCREEN_HEIGHT
2271
+ },
2272
+ chatContainerSidebar: {
2273
+ height: SCREEN_HEIGHT,
2274
+ width: SCREEN_WIDTH > 600 ? SCREEN_WIDTH * 0.4 : SCREEN_WIDTH
2275
+ },
2276
+ chatContainerInline: {
2277
+ height: "100%",
2278
+ width: "100%",
2279
+ flex: 1
2280
+ },
2281
+ header: {
2282
+ flexDirection: "row",
2283
+ alignItems: "center",
2284
+ justifyContent: "space-between",
2285
+ paddingHorizontal: 16,
2286
+ paddingVertical: 14,
2287
+ borderBottomWidth: 1
2288
+ },
2289
+ headerLeft: {
2290
+ flexDirection: "row",
2291
+ alignItems: "center",
2292
+ gap: 8
2293
+ },
2294
+ headerDot: {
2295
+ width: 8,
2296
+ height: 8,
2297
+ borderRadius: 4
2298
+ },
2299
+ headerAvatar: {
2300
+ width: 24,
2301
+ height: 24,
2302
+ borderRadius: 12
2303
+ },
2304
+ headerAvatarSlot: {
2305
+ width: 24,
2306
+ height: 24,
2307
+ alignItems: "center",
2308
+ justifyContent: "center"
2309
+ },
2310
+ headerTitle: {
2311
+ fontWeight: "600"
2312
+ },
2313
+ headerActions: {
2314
+ flexDirection: "row",
2315
+ alignItems: "center",
2316
+ gap: 12
2317
+ },
2318
+ headerButton: {
2319
+ paddingHorizontal: 8,
2320
+ paddingVertical: 4
2321
+ },
2322
+ headerButtonText: {
2323
+ fontSize: 13
2324
+ },
2325
+ closeButton: {
2326
+ width: 28,
2327
+ height: 28,
2328
+ borderRadius: 14,
2329
+ alignItems: "center",
2330
+ justifyContent: "center"
2331
+ },
2332
+ closeButtonText: {
2333
+ fontSize: 18,
2334
+ fontWeight: "300"
2335
+ },
2336
+ messageList: {
2337
+ paddingHorizontal: 16,
2338
+ paddingTop: 12,
2339
+ paddingBottom: 8,
2340
+ flexGrow: 1
2341
+ },
2342
+ messageBubble: {
2343
+ paddingHorizontal: BUBBLE_HORIZONTAL_PADDING,
2344
+ paddingVertical: 10,
2345
+ borderRadius: 18,
2346
+ marginBottom: 8
2347
+ },
2348
+ userBubble: {
2349
+ alignSelf: "flex-end",
2350
+ maxWidth: "82%",
2351
+ borderBottomRightRadius: 4
2352
+ },
2353
+ assistantBubble: {
2354
+ alignSelf: "flex-start",
2355
+ maxWidth: "94%",
2356
+ borderBottomLeftRadius: 4
2357
+ },
2358
+ assistantBubbleWide: {
2359
+ width: "100%",
2360
+ maxWidth: "100%"
2361
+ },
2362
+ messageText: {
2363
+ lineHeight: 20
2364
+ },
2365
+ emptyState: {
2366
+ flex: 1,
2367
+ alignItems: "center",
2368
+ justifyContent: "center",
2369
+ paddingVertical: 48,
2370
+ paddingHorizontal: 28
2371
+ },
2372
+ emptyIconBadge: {
2373
+ width: 64,
2374
+ height: 64,
2375
+ borderRadius: 32,
2376
+ alignItems: "center",
2377
+ justifyContent: "center",
2378
+ marginBottom: 20
2379
+ },
2380
+ emptyIconImage: {
2381
+ width: 64,
2382
+ height: 64,
2383
+ marginBottom: 20
2384
+ },
2385
+ emptyIconEmoji: {
2386
+ fontSize: 30,
2387
+ textAlign: "center"
2388
+ },
2389
+ emptyTitle: {
2390
+ fontSize: 20,
2391
+ fontWeight: "600",
2392
+ marginBottom: 6,
2393
+ textAlign: "center"
2394
+ },
2395
+ emptySubtitle: {
2396
+ fontSize: 14,
2397
+ textAlign: "center",
2398
+ lineHeight: 20,
2399
+ maxWidth: 280
2400
+ },
2401
+ chipsContainer: {
2402
+ flexDirection: "row",
2403
+ flexWrap: "wrap",
2404
+ justifyContent: "center",
2405
+ gap: 8,
2406
+ marginTop: 20,
2407
+ maxWidth: 320
2408
+ },
2409
+ chip: {
2410
+ paddingHorizontal: 16,
2411
+ paddingVertical: 10,
2412
+ borderWidth: 1,
2413
+ maxWidth: "100%"
2414
+ },
2415
+ chipText: {
2416
+ fontWeight: "500"
2417
+ },
2418
+ escalationBanner: {
2419
+ flexDirection: "row",
2420
+ alignItems: "center",
2421
+ justifyContent: "center",
2422
+ gap: 6,
2423
+ paddingVertical: 6,
2424
+ paddingHorizontal: 16,
2425
+ borderBottomWidth: 1
2426
+ },
2427
+ escalationBannerText: {
2428
+ fontWeight: "500"
2429
+ },
2430
+ systemMessageContainer: {
2431
+ alignItems: "center",
2432
+ marginVertical: 8,
2433
+ paddingHorizontal: 12,
2434
+ gap: 8
2435
+ },
2436
+ systemMessageText: {
2437
+ textAlign: "center",
2438
+ lineHeight: 18
2439
+ },
2440
+ escalationOfferChip: {
2441
+ flexDirection: "row",
2442
+ alignItems: "center",
2443
+ gap: 6
2444
+ },
2445
+ humanAgentContainer: {
2446
+ alignSelf: "flex-start",
2447
+ maxWidth: "94%"
2448
+ },
2449
+ humanAgentLabelRow: {
2450
+ flexDirection: "row",
2451
+ alignItems: "center",
2452
+ gap: 4,
2453
+ marginBottom: 2,
2454
+ marginLeft: 4
2455
+ },
2456
+ humanAgentLabel: {
2457
+ fontSize: 11,
2458
+ fontWeight: "600",
2459
+ textTransform: "uppercase",
2460
+ letterSpacing: 0.4
2461
+ },
2462
+ inputContainer: {
2463
+ flexDirection: "row",
2464
+ alignItems: "flex-end",
2465
+ paddingHorizontal: 12,
2466
+ paddingVertical: 10,
2467
+ borderTopWidth: 1,
2468
+ gap: 8
2469
+ },
2470
+ input: {
2471
+ flex: 1,
2472
+ minHeight: 38,
2473
+ maxHeight: 120,
2474
+ paddingHorizontal: 16,
2475
+ paddingTop: Platform.OS === "ios" ? 9 : 8,
2476
+ paddingBottom: Platform.OS === "ios" ? 9 : 8,
2477
+ textAlignVertical: "top"
2478
+ },
2479
+ sendButton: {
2480
+ width: 38,
2481
+ height: 38,
2482
+ alignItems: "center",
2483
+ justifyContent: "center"
2484
+ },
2485
+ sendButtonText: {
2486
+ color: "#ffffff",
2487
+ fontSize: 18,
2488
+ fontWeight: "700"
2489
+ },
2490
+ poweredByContainer: {
2491
+ alignItems: "center",
2492
+ paddingVertical: 4
2493
+ },
2494
+ poweredByText: {
2495
+ fontWeight: "500"
2496
+ }
2497
+ });
2498
+
2499
+ // src/components/ConfirmDialog.tsx
2500
+ function ConfirmDialog(_props = {}) {
2501
+ return null;
2502
+ }
2503
+ function normalizeId(label) {
2504
+ return label.toLowerCase().normalize("NFD").replace(/[\u0300-\u036f]/g, "").replace(/[^a-z0-9\s]/g, "").trim().replace(/\s+(.)/g, (_, c) => c.toUpperCase());
2505
+ }
2506
+ function createAppilotsInput(WrappedComponent) {
2507
+ const AppilotsInput = React7__default.forwardRef(
2508
+ (props, ref) => {
2509
+ const {
2510
+ appilotsId,
2511
+ appilotsScreen,
2512
+ appilotsFieldType = "text",
2513
+ appilotsEnabled = true,
2514
+ label,
2515
+ value,
2516
+ onChangeText,
2517
+ ...rest
2518
+ } = props;
2519
+ const fieldId = appilotsId || (label ? normalizeId(label) : null);
2520
+ const valueRef = useRef(value ?? "");
2521
+ const onChangeRef = useRef(onChangeText);
2522
+ const innerRef = useRef(null);
2523
+ valueRef.current = value ?? "";
2524
+ onChangeRef.current = onChangeText;
2525
+ const getValue = useCallback(() => valueRef.current, []);
2526
+ const setValue = useCallback((v) => onChangeRef.current?.(v), []);
2527
+ const focus = useCallback(() => {
2528
+ innerRef.current?.focus?.();
2529
+ }, []);
2530
+ useEffect(() => {
2531
+ if (!fieldId || !appilotsEnabled) return;
2532
+ const entry = {
2533
+ kind: "field",
2534
+ getValue,
2535
+ setValue,
2536
+ focus,
2537
+ fieldType: appilotsFieldType,
2538
+ label: label ?? fieldId,
2539
+ screen: appilotsScreen
2540
+ };
2541
+ componentRegistry.register(fieldId, entry);
2542
+ return () => {
2543
+ componentRegistry.unregister(fieldId);
2544
+ };
2545
+ }, [fieldId, appilotsEnabled, getValue, setValue, focus, appilotsFieldType, label, appilotsScreen]);
2546
+ const mergedRef = useCallback(
2547
+ (instance) => {
2548
+ innerRef.current = instance;
2549
+ if (typeof ref === "function") ref(instance);
2550
+ else if (ref) ref.current = instance;
2551
+ },
2552
+ [ref]
2553
+ );
2554
+ return /* @__PURE__ */ React7__default.createElement(
2555
+ WrappedComponent,
2556
+ {
2557
+ ...{ label, value, onChangeText, ...rest },
2558
+ ref: mergedRef
2559
+ }
2560
+ );
2561
+ }
2562
+ );
2563
+ AppilotsInput.displayName = `AppilotsInput(${WrappedComponent.displayName || WrappedComponent.name || "Component"})`;
2564
+ return AppilotsInput;
2565
+ }
2566
+ function normalizeId2(label) {
2567
+ return label.toLowerCase().normalize("NFD").replace(/[\u0300-\u036f]/g, "").replace(/[^a-z0-9\s]/g, "").trim().replace(/\s+(.)/g, (_, c) => c.toUpperCase());
2568
+ }
2569
+ function createAppilotsButton(WrappedComponent) {
2570
+ const AppilotsButton = React7__default.forwardRef(
2571
+ (props, ref) => {
2572
+ const {
2573
+ appilotsId,
2574
+ appilotsScreen,
2575
+ appilotsEnabled = true,
2576
+ title,
2577
+ onPress,
2578
+ ...rest
2579
+ } = props;
2580
+ const targetId = appilotsId || (title ? normalizeId2(title) : null);
2581
+ const onPressRef = useRef(onPress);
2582
+ onPressRef.current = onPress;
2583
+ const press = useCallback(() => onPressRef.current?.(), []);
2584
+ useEffect(() => {
2585
+ if (!targetId || !appilotsEnabled) return;
2586
+ const entry = {
2587
+ kind: "target",
2588
+ press,
2589
+ label: title ?? targetId,
2590
+ screen: appilotsScreen
2591
+ };
2592
+ componentRegistry.register(targetId, entry);
2593
+ return () => {
2594
+ componentRegistry.unregister(targetId);
2595
+ };
2596
+ }, [targetId, appilotsEnabled, press, title, appilotsScreen]);
2597
+ return /* @__PURE__ */ React7__default.createElement(
2598
+ WrappedComponent,
2599
+ {
2600
+ ...{ title, onPress, ...rest },
2601
+ ref
2602
+ }
2603
+ );
2604
+ }
2605
+ );
2606
+ AppilotsButton.displayName = `AppilotsButton(${WrappedComponent.displayName || WrappedComponent.name || "Component"})`;
2607
+ return AppilotsButton;
2608
+ }
2609
+ function normalizeId3(label) {
2610
+ return label.toLowerCase().normalize("NFD").replace(/[\u0300-\u036f]/g, "").replace(/[^a-z0-9\s]/g, "").trim().replace(/\s+(.)/g, (_, c) => c.toUpperCase());
2611
+ }
2612
+ function createAppilotsSwitch(WrappedComponent) {
2613
+ const AppilotsSwitch = React7__default.forwardRef(
2614
+ (props, ref) => {
2615
+ const {
2616
+ appilotsId,
2617
+ appilotsScreen,
2618
+ appilotsEnabled = true,
2619
+ label,
2620
+ value,
2621
+ onValueChange,
2622
+ ...rest
2623
+ } = props;
2624
+ const toggleId = appilotsId || (label ? normalizeId3(label) : null);
2625
+ const valueRef = useRef(value ?? false);
2626
+ const onChangeRef = useRef(onValueChange);
2627
+ valueRef.current = value ?? false;
2628
+ onChangeRef.current = onValueChange;
2629
+ const getValue = useCallback(() => valueRef.current, []);
2630
+ const setValue = useCallback((v) => onChangeRef.current?.(v), []);
2631
+ useEffect(() => {
2632
+ if (!toggleId || !appilotsEnabled) return;
2633
+ const entry = {
2634
+ kind: "toggle",
2635
+ getValue,
2636
+ setValue,
2637
+ label: label ?? toggleId,
2638
+ screen: appilotsScreen
2639
+ };
2640
+ componentRegistry.register(toggleId, entry);
2641
+ return () => {
2642
+ componentRegistry.unregister(toggleId);
2643
+ };
2644
+ }, [toggleId, appilotsEnabled, getValue, setValue, label, appilotsScreen]);
2645
+ return /* @__PURE__ */ React7__default.createElement(
2646
+ WrappedComponent,
2647
+ {
2648
+ ...{ label, value, onValueChange, ...rest },
2649
+ ref
2650
+ }
2651
+ );
2652
+ }
2653
+ );
2654
+ AppilotsSwitch.displayName = `AppilotsSwitch(${WrappedComponent.displayName || WrappedComponent.name || "Component"})`;
2655
+ return AppilotsSwitch;
2656
+ }
2657
+
2658
+ export { ActionBreadcrumb, AppilotsChat, AppilotsI18nProvider, AppilotsThemeProvider, ChatIcon, ConfirmDialog, HeadsetIcon, appilotsTheme, appilotsThemeFromTailwind, createAppilotsButton, createAppilotsInput, createAppilotsSwitch, defaultDarkTheme, defaultLightTheme, describeAction, detectDeviceLocale, humanizeError, mergeThemeTokens, resolveLocale, useAppilotsI18n, useAppilotsTheme };