@vinaverse/react-native 0.1.0-alpha.5 → 0.1.0-alpha.6

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.
Files changed (48) hide show
  1. package/README.md +57 -1
  2. package/dist/ask.cjs +58 -0
  3. package/dist/ask.cjs.map +1 -0
  4. package/dist/ask.d.cts +25 -0
  5. package/dist/ask.d.ts +25 -0
  6. package/dist/ask.js +5 -0
  7. package/dist/ask.js.map +1 -0
  8. package/dist/chat.cjs +266 -0
  9. package/dist/chat.cjs.map +1 -0
  10. package/dist/chat.d.cts +449 -0
  11. package/dist/chat.d.ts +449 -0
  12. package/dist/chat.js +5 -0
  13. package/dist/chat.js.map +1 -0
  14. package/dist/chunk-643GOUTG.cjs +75 -0
  15. package/dist/chunk-643GOUTG.cjs.map +1 -0
  16. package/dist/chunk-H2Y2YW2Q.cjs +2651 -0
  17. package/dist/chunk-H2Y2YW2Q.cjs.map +1 -0
  18. package/dist/chunk-NJWRZ55G.js +254 -0
  19. package/dist/chunk-NJWRZ55G.js.map +1 -0
  20. package/dist/chunk-OGPMGGYS.cjs +261 -0
  21. package/dist/chunk-OGPMGGYS.cjs.map +1 -0
  22. package/dist/chunk-OWUKWVEZ.cjs +53 -0
  23. package/dist/chunk-OWUKWVEZ.cjs.map +1 -0
  24. package/dist/chunk-PRXHS57U.js +40 -0
  25. package/dist/chunk-PRXHS57U.js.map +1 -0
  26. package/dist/chunk-PYPO6F7L.cjs +44 -0
  27. package/dist/chunk-PYPO6F7L.cjs.map +1 -0
  28. package/dist/chunk-SF6LJTVJ.js +2567 -0
  29. package/dist/chunk-SF6LJTVJ.js.map +1 -0
  30. package/dist/chunk-WKRCCPSN.js +48 -0
  31. package/dist/chunk-WKRCCPSN.js.map +1 -0
  32. package/dist/chunk-Y7P7DDHD.js +73 -0
  33. package/dist/chunk-Y7P7DDHD.js.map +1 -0
  34. package/dist/index.cjs +264 -2998
  35. package/dist/index.cjs.map +1 -1
  36. package/dist/index.d.cts +10 -606
  37. package/dist/index.d.ts +10 -606
  38. package/dist/index.js +5 -2930
  39. package/dist/index.js.map +1 -1
  40. package/dist/launcher.cjs +79 -0
  41. package/dist/launcher.cjs.map +1 -0
  42. package/dist/launcher.d.cts +121 -0
  43. package/dist/launcher.d.ts +121 -0
  44. package/dist/launcher.js +6 -0
  45. package/dist/launcher.js.map +1 -0
  46. package/dist/theme-BhiEGv64.d.cts +35 -0
  47. package/dist/theme-BhiEGv64.d.ts +35 -0
  48. package/package.json +54 -9
@@ -0,0 +1,2651 @@
1
+ 'use strict';
2
+
3
+ var chunkPYPO6F7L_cjs = require('./chunk-PYPO6F7L.cjs');
4
+ var chunkOWUKWVEZ_cjs = require('./chunk-OWUKWVEZ.cjs');
5
+ var agent = require('@vinaverse/react/agent');
6
+ var flashList = require('@shopify/flash-list');
7
+ var react$1 = require('@vinaverse/react');
8
+ var reactNative = require('react-native');
9
+ var agent$1 = require('@vinaverse/client/agent');
10
+ var react = require('react');
11
+ var jsxRuntime = require('react/jsx-runtime');
12
+ var conversation = require('@vinaverse/client/conversation');
13
+ var tokens = require('@vinaverse/tokens');
14
+
15
+ var ChatContext = /* @__PURE__ */ react.createContext(
16
+ null
17
+ );
18
+ function ChatContextProvider(props) {
19
+ return /* @__PURE__ */ jsxRuntime.jsx(ChatContext.Provider, { value: props.value, children: props.children });
20
+ }
21
+ function useChat() {
22
+ const value = react.useContext(ChatContext);
23
+ if (!value)
24
+ throw new Error(
25
+ "Vinaverse parts render inside <VinaverseChat> or <VinaverseChatProvider>."
26
+ );
27
+ return value;
28
+ }
29
+ var Button = /* @__PURE__ */ react.memo(function Button2(props) {
30
+ const { colors, radius, space, size: size2, font } = chunkOWUKWVEZ_cjs.useTheme();
31
+ const variant = props.variant ?? "secondary";
32
+ const inactive = props.disabled === true || props.busy === true;
33
+ const filled = variant === "primary" || props.selected === true;
34
+ const background = filled ? colors.primary : variant === "secondary" ? colors.surface : "transparent";
35
+ const foreground = filled ? colors.onPrimary : colors.primary;
36
+ const checkable = props.role === "radio" || props.role === "checkbox";
37
+ return /* @__PURE__ */ jsxRuntime.jsxs(
38
+ reactNative.Pressable,
39
+ {
40
+ accessibilityRole: props.role ?? "button",
41
+ accessibilityLabel: props.label,
42
+ ...props.hint ? { accessibilityHint: props.hint } : {},
43
+ accessibilityState: {
44
+ disabled: inactive,
45
+ busy: props.busy === true,
46
+ ...checkable ? props.role === "radio" ? { selected: props.selected === true } : { checked: props.selected === true } : {}
47
+ },
48
+ disabled: inactive,
49
+ onPress: props.onPress,
50
+ ...props.testID ? { testID: props.testID } : {},
51
+ style: ({ pressed }) => ({
52
+ minHeight: size2.touchTarget,
53
+ minWidth: size2.touchTarget,
54
+ paddingHorizontal: space.md,
55
+ paddingVertical: space.sm,
56
+ borderRadius: radius.md,
57
+ borderWidth: variant === "text" ? 0 : 1,
58
+ borderColor: filled ? colors.primary : colors.border,
59
+ backgroundColor: background,
60
+ opacity: inactive ? 0.55 : pressed ? 0.8 : 1,
61
+ alignItems: "center",
62
+ justifyContent: "center",
63
+ flexDirection: "row",
64
+ gap: space.xs
65
+ }),
66
+ children: [
67
+ props.busy ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.ActivityIndicator, { color: foreground }) : null,
68
+ props.children ?? /* @__PURE__ */ jsxRuntime.jsx(
69
+ reactNative.Text,
70
+ {
71
+ style: {
72
+ color: foreground,
73
+ fontSize: font.size.md,
74
+ fontWeight: "500",
75
+ ...font.family ? { fontFamily: font.family } : {}
76
+ },
77
+ children: props.label
78
+ }
79
+ )
80
+ ]
81
+ }
82
+ );
83
+ });
84
+ function cardOf(held) {
85
+ return held.card;
86
+ }
87
+ function CardFrame(props) {
88
+ const { colors, radius, space } = chunkOWUKWVEZ_cjs.useTheme();
89
+ return /* @__PURE__ */ jsxRuntime.jsx(
90
+ reactNative.View,
91
+ {
92
+ accessibilityLabel: props.label,
93
+ ...props.restored ? {
94
+ pointerEvents: "none",
95
+ accessibilityState: { disabled: true }
96
+ } : {},
97
+ ...props.testID ? { testID: props.testID } : {},
98
+ style: {
99
+ marginHorizontal: 16,
100
+ marginTop: space.sm,
101
+ padding: space.md,
102
+ gap: space.sm,
103
+ borderRadius: radius.lg,
104
+ borderWidth: 1,
105
+ borderColor: colors.divider,
106
+ backgroundColor: colors.background,
107
+ opacity: props.restored ? 0.7 : 1,
108
+ maxWidth: 520,
109
+ alignSelf: "stretch"
110
+ },
111
+ children: props.children
112
+ }
113
+ );
114
+ }
115
+ function Title(props) {
116
+ const { colors, font } = chunkOWUKWVEZ_cjs.useTheme();
117
+ return /* @__PURE__ */ jsxRuntime.jsx(
118
+ reactNative.Text,
119
+ {
120
+ accessibilityRole: "header",
121
+ numberOfLines: props.numberOfLines,
122
+ style: {
123
+ color: colors.text,
124
+ fontSize: font.size.md,
125
+ fontWeight: "600",
126
+ ...font.family ? { fontFamily: font.family } : {}
127
+ },
128
+ children: props.children
129
+ }
130
+ );
131
+ }
132
+ function Body(props) {
133
+ const { colors, font } = chunkOWUKWVEZ_cjs.useTheme();
134
+ return /* @__PURE__ */ jsxRuntime.jsx(
135
+ reactNative.Text,
136
+ {
137
+ style: {
138
+ color: props.danger ? colors.danger : props.muted ? colors.textMuted : colors.text,
139
+ fontSize: props.muted ? font.size.sm : font.size.md,
140
+ lineHeight: (props.muted ? font.size.sm : font.size.md) * font.lineHeight,
141
+ ...font.family ? { fontFamily: font.family } : {}
142
+ },
143
+ children: props.children
144
+ }
145
+ );
146
+ }
147
+ function useLink(link, held) {
148
+ const { store, handlers } = useChat();
149
+ if (!link || !store.checkLink(link)) return null;
150
+ return () => handlers.current?.onOpenLink?.(link, held);
151
+ }
152
+ function ratingText(valuePpm, locale) {
153
+ const value = valuePpm / 1e6;
154
+ try {
155
+ return new Intl.NumberFormat(locale, {
156
+ minimumFractionDigits: 1,
157
+ maximumFractionDigits: 1
158
+ }).format(value);
159
+ } catch {
160
+ return value.toFixed(1);
161
+ }
162
+ }
163
+ function allowedImage(url, hosts) {
164
+ const safe = safeImage(url);
165
+ if (safe === null || !hosts) return safe;
166
+ const host = /^https:\/\/([^/:?#]+)/i.exec(safe)?.[1]?.toLowerCase();
167
+ return host && hosts.some((h) => h.toLowerCase() === host) ? safe : null;
168
+ }
169
+ function safeImage(url) {
170
+ return url && /^https:\/\//i.test(url) ? url : null;
171
+ }
172
+ function InfoCard(props) {
173
+ const card = cardOf(props.card);
174
+ const { locale, t } = react$1.useCopy();
175
+ const { slots, timeZone } = useChat();
176
+ const { colors, radius, space, font } = chunkOWUKWVEZ_cjs.useTheme();
177
+ const image = safeImage(card.image_url);
178
+ const Img = slots.Image ?? reactNative.Image;
179
+ const money = card.price ?? card.price_from;
180
+ const rating = card.rating ? ratingText(card.rating.value_ppm, locale) : null;
181
+ const amount = money ? react$1.formatMoney(money.amount, money.currency, locale) : null;
182
+ const price = amount && !card.price ? t("priceFrom", { price: amount }) : amount;
183
+ const when = card.starts_at ? react$1.formatTimeRange(card.starts_at, void 0, locale, timeZone) : null;
184
+ const label = [
185
+ card.title,
186
+ card.subtitle,
187
+ rating && `\u2605 ${rating}`,
188
+ price,
189
+ when
190
+ ].filter(Boolean).join(", ");
191
+ return /* @__PURE__ */ jsxRuntime.jsxs(
192
+ CardFrame,
193
+ {
194
+ label,
195
+ restored: props.restored,
196
+ testID: `vinaverse-card-${card.id}`,
197
+ children: [
198
+ /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: { flexDirection: "row", gap: space.md }, children: [
199
+ image ? /* @__PURE__ */ jsxRuntime.jsx(
200
+ Img,
201
+ {
202
+ source: { uri: image },
203
+ style: {
204
+ width: 64,
205
+ height: 64,
206
+ borderRadius: radius.md,
207
+ backgroundColor: colors.surface
208
+ },
209
+ accessible: false
210
+ }
211
+ ) : null,
212
+ /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: { flex: 1, gap: space.xxs }, children: [
213
+ /* @__PURE__ */ jsxRuntime.jsx(Title, { numberOfLines: 2, children: card.title }),
214
+ card.subtitle ? /* @__PURE__ */ jsxRuntime.jsx(Body, { muted: true, children: card.subtitle }) : null,
215
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: { color: colors.textMuted, fontSize: font.size.sm }, children: [
216
+ rating && `\u2605 ${rating}${card.rating ? ` (${card.rating.count})` : ""}`,
217
+ price,
218
+ when
219
+ ].filter(Boolean).join(" \xB7 ") })
220
+ ] })
221
+ ] }),
222
+ card.body ? /* @__PURE__ */ jsxRuntime.jsx(Body, { children: card.body }) : null,
223
+ /* @__PURE__ */ jsxRuntime.jsx(Badges, { badges: card.badges }),
224
+ props.restored ? null : /* @__PURE__ */ jsxRuntime.jsx(CardActions, { ...props })
225
+ ]
226
+ }
227
+ );
228
+ }
229
+ function Badges(props) {
230
+ const { colors, radius, space, font } = chunkOWUKWVEZ_cjs.useTheme();
231
+ if (!props.badges?.length) return null;
232
+ return /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: { flexDirection: "row", flexWrap: "wrap", gap: space.xs }, children: props.badges.map((b) => /* @__PURE__ */ jsxRuntime.jsx(
233
+ reactNative.Text,
234
+ {
235
+ style: {
236
+ color: colors.text,
237
+ backgroundColor: colors.surface,
238
+ borderRadius: radius.pill,
239
+ paddingHorizontal: space.sm,
240
+ paddingVertical: space.xxs,
241
+ fontSize: font.size.xs
242
+ },
243
+ children: b
244
+ },
245
+ b
246
+ )) });
247
+ }
248
+ function CardActions(props) {
249
+ const card = cardOf(props.card);
250
+ const { space } = chunkOWUKWVEZ_cjs.useTheme();
251
+ if (!card.actions?.length) return null;
252
+ return /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: { flexDirection: "row", flexWrap: "wrap", gap: space.sm }, children: card.actions.map((a) => /* @__PURE__ */ jsxRuntime.jsx(
253
+ Action,
254
+ {
255
+ held: props.card,
256
+ action: a.action,
257
+ label: a.label,
258
+ link: a.deep_link
259
+ },
260
+ `${a.action}:${a.label}`
261
+ )) });
262
+ }
263
+ function Action(props) {
264
+ const { store, handlers } = useChat();
265
+ const open = useLink(props.link, props.held);
266
+ if (props.link && !open) return null;
267
+ return /* @__PURE__ */ jsxRuntime.jsx(
268
+ Button,
269
+ {
270
+ label: props.label,
271
+ role: open ? "link" : "button",
272
+ onPress: () => {
273
+ if (open) return open();
274
+ store.cardAction(props.held.id, props.action);
275
+ handlers.current?.onCardAction?.(props.held, props.action);
276
+ }
277
+ }
278
+ );
279
+ }
280
+ function LinksCard(props) {
281
+ const card = cardOf(props.card);
282
+ const { store, handlers } = useChat();
283
+ const links = (card.links ?? []).filter(
284
+ (l) => /^https:\/\//i.test(l.url) || store.checkLink(l.url)
285
+ );
286
+ return /* @__PURE__ */ jsxRuntime.jsxs(
287
+ CardFrame,
288
+ {
289
+ label: [card.title, ...links.map((l) => l.label)].join(", "),
290
+ restored: props.restored,
291
+ testID: `vinaverse-card-${card.id}`,
292
+ children: [
293
+ /* @__PURE__ */ jsxRuntime.jsx(Title, { children: card.title }),
294
+ links.map((l) => /* @__PURE__ */ jsxRuntime.jsx(
295
+ Button,
296
+ {
297
+ label: l.label,
298
+ role: "link",
299
+ variant: "secondary",
300
+ disabled: props.restored,
301
+ onPress: () => {
302
+ const open = handlers.current?.onOpenLink;
303
+ if (open) open(l.url, props.card);
304
+ else void reactNative.Linking.openURL(l.url);
305
+ }
306
+ },
307
+ l.url
308
+ ))
309
+ ]
310
+ }
311
+ );
312
+ }
313
+ function ListingCard(props) {
314
+ const card = cardOf(props.card);
315
+ const { locale, t } = react$1.useCopy();
316
+ const { slots, timeZone, handlers, imagePolicy } = useChat();
317
+ const { colors, radius, space, font } = chunkOWUKWVEZ_cjs.useTheme();
318
+ const [failed, setFailed] = react.useState(null);
319
+ const photoUrl = allowedImage(
320
+ card.images?.[0]?.url ?? card.image_url,
321
+ imagePolicy?.hosts
322
+ );
323
+ const hero = photoUrl === failed ? null : photoUrl;
324
+ const Img = slots.Image ?? reactNative.Image;
325
+ const rating = card.rating ? `\u2605 ${ratingText(card.rating.value_ppm, locale)} (${card.rating.count})` : null;
326
+ const price = card.price_from ? t("priceFrom", {
327
+ price: react$1.formatMoney(
328
+ card.price_from.amount,
329
+ card.price_from.currency,
330
+ locale
331
+ )
332
+ }) : null;
333
+ const next = card.next_available ? t("nextAvailable", {
334
+ time: react$1.formatTimeRange(card.next_available, void 0, locale, timeZone)
335
+ }) : null;
336
+ const ref = card.ref;
337
+ const onOpenTarget = handlers.current?.onOpenTarget;
338
+ const open = ref && onOpenTarget && !props.restored ? () => onOpenTarget({ type: ref.type, id: ref.id }, props.card) : null;
339
+ const photo = {
340
+ width: "100%",
341
+ aspectRatio: imagePolicy?.aspectRatio ?? 1.6,
342
+ borderRadius: radius.md
343
+ };
344
+ const top = /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
345
+ hero ? /* @__PURE__ */ jsxRuntime.jsx(
346
+ Img,
347
+ {
348
+ source: { uri: hero },
349
+ onError: () => setFailed(hero),
350
+ style: { ...photo, backgroundColor: colors.surface },
351
+ accessible: false,
352
+ testID: `vinaverse-photo-${card.id}`
353
+ }
354
+ ) : imagePolicy?.Placeholder ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: photo, testID: `vinaverse-placeholder-${card.id}`, children: /* @__PURE__ */ jsxRuntime.jsx(imagePolicy.Placeholder, { title: card.title }) }) : /* @__PURE__ */ jsxRuntime.jsx(
355
+ reactNative.View,
356
+ {
357
+ accessible: false,
358
+ importantForAccessibility: "no-hide-descendants",
359
+ testID: `vinaverse-monogram-${card.id}`,
360
+ style: {
361
+ ...photo,
362
+ backgroundColor: colors.surface,
363
+ alignItems: "center",
364
+ justifyContent: "center"
365
+ },
366
+ children: /* @__PURE__ */ jsxRuntime.jsx(
367
+ reactNative.Text,
368
+ {
369
+ style: {
370
+ color: colors.textMuted,
371
+ fontSize: font.size.xl,
372
+ fontWeight: "600"
373
+ },
374
+ children: monogram(card.title)
375
+ }
376
+ )
377
+ }
378
+ ),
379
+ /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: { gap: space.xxs }, children: [
380
+ /* @__PURE__ */ jsxRuntime.jsx(Title, { numberOfLines: 2, children: card.title }),
381
+ card.subtitle ? /* @__PURE__ */ jsxRuntime.jsx(Body, { muted: true, children: card.subtitle }) : null,
382
+ rating || price ? /* @__PURE__ */ jsxRuntime.jsx(Body, { muted: true, children: [rating, price].filter(Boolean).join(" \xB7 ") }) : null
383
+ ] })
384
+ ] });
385
+ return /* @__PURE__ */ jsxRuntime.jsxs(
386
+ CardFrame,
387
+ {
388
+ label: [card.title, card.subtitle, rating, price, next].filter(Boolean).join(", "),
389
+ restored: props.restored,
390
+ testID: `vinaverse-card-${card.id}`,
391
+ children: [
392
+ open ? /* @__PURE__ */ jsxRuntime.jsx(
393
+ reactNative.Pressable,
394
+ {
395
+ accessibilityRole: "link",
396
+ accessibilityLabel: card.title,
397
+ onPress: open,
398
+ style: { gap: space.sm },
399
+ testID: `vinaverse-open-${card.id}`,
400
+ children: top
401
+ }
402
+ ) : top,
403
+ card.body ? /* @__PURE__ */ jsxRuntime.jsx(Body, { children: card.body }) : null,
404
+ /* @__PURE__ */ jsxRuntime.jsx(Badges, { badges: next ? [next, ...card.badges ?? []] : card.badges }),
405
+ props.restored ? null : /* @__PURE__ */ jsxRuntime.jsx(CardActions, { ...props })
406
+ ]
407
+ }
408
+ );
409
+ }
410
+ function monogram(title) {
411
+ return title.split(/\s+/).filter(Boolean).slice(0, 2).map((w) => [...w][0]?.toUpperCase() ?? "").join("");
412
+ }
413
+ function NextStepCard(props) {
414
+ const card = cardOf(props.card);
415
+ const action = card.actions?.[0];
416
+ const open = useLink(action?.deep_link, props.card);
417
+ return /* @__PURE__ */ jsxRuntime.jsxs(
418
+ CardFrame,
419
+ {
420
+ label: [card.title, card.body].filter(Boolean).join(". "),
421
+ restored: props.restored,
422
+ testID: `vinaverse-card-${card.id}`,
423
+ children: [
424
+ /* @__PURE__ */ jsxRuntime.jsx(Title, { children: card.title }),
425
+ card.body ? /* @__PURE__ */ jsxRuntime.jsx(Body, { muted: true, children: card.body }) : null,
426
+ action && open && !props.restored ? /* @__PURE__ */ jsxRuntime.jsx(
427
+ Button,
428
+ {
429
+ label: action.label,
430
+ variant: "primary",
431
+ role: "link",
432
+ onPress: open
433
+ }
434
+ ) : null
435
+ ]
436
+ }
437
+ );
438
+ }
439
+ function fieldText(type, value, locale, timeZone) {
440
+ if (value === null || value === void 0) return "";
441
+ const v = value;
442
+ switch (type) {
443
+ case "list":
444
+ return Array.isArray(value) ? value.join(", ") : String(value);
445
+ case "time_range":
446
+ return react$1.formatTimeRange(
447
+ String(v.start ?? ""),
448
+ typeof v.end === "string" ? v.end : void 0,
449
+ locale,
450
+ timeZone
451
+ );
452
+ case "money_cents":
453
+ return react$1.formatMoney(
454
+ Number(v.amount ?? 0),
455
+ String(v.currency ?? "USD"),
456
+ locale
457
+ );
458
+ case "address":
459
+ return ["line1", "line2", "city", "region", "postal_code", "country"].map((k) => v[k]).filter((p) => typeof p === "string" && p).join(", ");
460
+ case "weekly_hours": {
461
+ const entries = Array.isArray(v.entries) ? v.entries : [];
462
+ return entries.map((e) => `${weekday(e.day, locale)} ${e.start}\u2013${e.end}`).join(", ");
463
+ }
464
+ default:
465
+ return typeof value === "object" ? JSON.stringify(value) : String(value);
466
+ }
467
+ }
468
+ var DAYS = ["mon", "tue", "wed", "thu", "fri", "sat", "sun"];
469
+ function weekday(day, locale) {
470
+ const i = DAYS.indexOf(day);
471
+ if (i < 0) return day;
472
+ try {
473
+ return new Intl.DateTimeFormat(locale, {
474
+ weekday: "short",
475
+ timeZone: "UTC"
476
+ }).format(new Date(Date.UTC(2024, 0, 1 + i)));
477
+ } catch {
478
+ return day;
479
+ }
480
+ }
481
+ function outcomeText(t, outcome, status) {
482
+ if (outcome === "confirmed") return t("proposalConfirmed");
483
+ if (outcome === "declined") return t("proposalDeclined");
484
+ if (outcome === "failed") return t("proposalFailed");
485
+ if (outcome === "expired") return t("proposalExpired");
486
+ if (status === "confirmed") return t("proposalConfirmed");
487
+ if (status === "declined") return t("proposalDeclined");
488
+ if (status === "expired") return t("proposalExpired");
489
+ if (status !== "pending") return t("proposalSettled");
490
+ return null;
491
+ }
492
+ function ProposalCard(props) {
493
+ const card = cardOf(props.card);
494
+ const proposal = card.proposal;
495
+ const { t, locale } = react$1.useCopy();
496
+ const { store, handlers, timeZone } = useChat();
497
+ const { colors, space, font } = chunkOWUKWVEZ_cjs.useTheme();
498
+ const edit = useLink(proposal?.edit_deep_link, props.card);
499
+ if (!proposal) return null;
500
+ const settled = outcomeText(t, props.card.outcome, proposal.status);
501
+ const actionable = settled === null && !props.restored;
502
+ const decide = (decision) => {
503
+ const onProposal = handlers.current?.onProposal;
504
+ if (!onProposal) return;
505
+ store.decideProposal(
506
+ props.card.id,
507
+ () => onProposal(props.card, decision)
508
+ ).catch(
509
+ (error) => handlers.current?.onWarning?.(`Vinaverse proposal: ${String(error)}`)
510
+ );
511
+ };
512
+ const fields = proposal.fields.map((f) => {
513
+ const after = fieldText(f.type, f.after, locale, timeZone);
514
+ const before = f.before === void 0 || f.before === null ? null : fieldText(f.type, f.before, locale, timeZone);
515
+ return {
516
+ key: f.key,
517
+ label: f.label,
518
+ value: before !== null && before !== after ? `${before} \u2192 ${after}` : after
519
+ };
520
+ });
521
+ return /* @__PURE__ */ jsxRuntime.jsxs(
522
+ CardFrame,
523
+ {
524
+ label: [
525
+ proposal.title,
526
+ proposal.summary,
527
+ ...fields.map((f) => `${f.label}: ${f.value}`),
528
+ settled
529
+ ].filter(Boolean).join(". "),
530
+ restored: props.restored,
531
+ testID: `vinaverse-proposal-${card.id}`,
532
+ children: [
533
+ /* @__PURE__ */ jsxRuntime.jsx(Title, { children: proposal.title }),
534
+ /* @__PURE__ */ jsxRuntime.jsx(Body, { muted: true, children: proposal.summary }),
535
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: { gap: space.xs }, children: fields.map((f) => /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: { flexDirection: "row", gap: space.sm }, children: [
536
+ /* @__PURE__ */ jsxRuntime.jsx(
537
+ reactNative.Text,
538
+ {
539
+ style: {
540
+ color: colors.textMuted,
541
+ fontSize: font.size.sm,
542
+ minWidth: 72
543
+ },
544
+ children: f.label
545
+ }
546
+ ),
547
+ /* @__PURE__ */ jsxRuntime.jsx(
548
+ reactNative.Text,
549
+ {
550
+ style: { color: colors.text, fontSize: font.size.sm, flex: 1 },
551
+ children: f.value
552
+ }
553
+ )
554
+ ] }, f.key)) }),
555
+ settled ? /* @__PURE__ */ jsxRuntime.jsx(
556
+ reactNative.Text,
557
+ {
558
+ style: {
559
+ color: props.card.outcome === "confirmed" || proposal.status === "confirmed" ? colors.success : colors.textMuted,
560
+ fontSize: font.size.sm,
561
+ fontWeight: "600"
562
+ },
563
+ children: settled
564
+ }
565
+ ) : null,
566
+ actionable ? /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: { flexDirection: "row", flexWrap: "wrap", gap: space.sm }, children: [
567
+ /* @__PURE__ */ jsxRuntime.jsx(
568
+ Button,
569
+ {
570
+ label: t("proposalConfirm"),
571
+ variant: "primary",
572
+ busy: props.busy,
573
+ disabled: props.busy,
574
+ onPress: () => decide("confirm"),
575
+ testID: "vinaverse-proposal-confirm"
576
+ }
577
+ ),
578
+ /* @__PURE__ */ jsxRuntime.jsx(
579
+ Button,
580
+ {
581
+ label: t("proposalDecline"),
582
+ disabled: props.busy,
583
+ onPress: () => decide("decline"),
584
+ testID: "vinaverse-proposal-decline"
585
+ }
586
+ ),
587
+ edit ? /* @__PURE__ */ jsxRuntime.jsx(
588
+ Button,
589
+ {
590
+ label: t("proposalChange"),
591
+ variant: "text",
592
+ role: "link",
593
+ onPress: edit
594
+ }
595
+ ) : null
596
+ ] }) : null
597
+ ]
598
+ }
599
+ );
600
+ }
601
+ var KNOWN = /* @__PURE__ */ new Set([
602
+ "single_choice",
603
+ "multi_choice",
604
+ "image_choice",
605
+ "yes_no",
606
+ "list_choice",
607
+ "rate_each",
608
+ "short_text",
609
+ "long_text",
610
+ "date",
611
+ "number",
612
+ "scale",
613
+ "photo"
614
+ ]);
615
+ var ONE_TAP = /* @__PURE__ */ new Set(["single_choice", "yes_no", "list_choice", "scale"]);
616
+ function QuestionCard(props) {
617
+ const held = props.card;
618
+ const question = held.question;
619
+ const { store, handlers } = useChat();
620
+ const { t, locale } = react$1.useCopy();
621
+ const { colors, font, space } = chunkOWUKWVEZ_cjs.useTheme();
622
+ const needsSocket = react$1.useConversationSelector(
623
+ store,
624
+ (v) => v.answersNeedWebSocket
625
+ );
626
+ if (!question) return null;
627
+ const send = (answer, questionId = question.question_id) => {
628
+ try {
629
+ store.answer(held.id, questionId, answer);
630
+ } catch (error) {
631
+ handlers.current?.onWarning?.(`Vinaverse answer: ${String(error)}`);
632
+ }
633
+ };
634
+ const locked = held.locked.includes(question.question_id);
635
+ const state = question.state;
636
+ const open = state === "open" && !locked && !props.restored;
637
+ const summary = state === "answered" ? answerText(question, locale, t("questionOther")) : null;
638
+ return /* @__PURE__ */ jsxRuntime.jsxs(
639
+ CardFrame,
640
+ {
641
+ label: [question.prompt, summary].filter(Boolean).join(". "),
642
+ restored: props.restored,
643
+ testID: `vinaverse-question-${held.id}`,
644
+ children: [
645
+ question.progress ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: { color: colors.textMuted, fontSize: font.size.xs }, children: t("questionStep", {
646
+ step: question.progress.step,
647
+ total: question.progress.total
648
+ }) }) : null,
649
+ /* @__PURE__ */ jsxRuntime.jsx(Title, { children: question.prompt }),
650
+ question.help ? /* @__PURE__ */ jsxRuntime.jsx(Body, { muted: true, children: question.help }) : null,
651
+ state === "answered" ? /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: { gap: space.xxs }, children: [
652
+ /* @__PURE__ */ jsxRuntime.jsx(Body, { muted: true, children: t("questionAnswered") }),
653
+ summary ? /* @__PURE__ */ jsxRuntime.jsx(Body, { children: summary }) : null
654
+ ] }) : state === "superseded" ? /* @__PURE__ */ jsxRuntime.jsx(Body, { muted: true, children: t("questionReplaced") }) : !KNOWN.has(question.kind) || needsSocket ? /* @__PURE__ */ jsxRuntime.jsx(Body, { muted: true, children: t("typeAnswerBelow") }) : question.kind === "rate_each" ? /* @__PURE__ */ jsxRuntime.jsx(
655
+ RateEach,
656
+ {
657
+ question,
658
+ locked: held.locked,
659
+ enabled: !props.restored,
660
+ send
661
+ }
662
+ ) : /* @__PURE__ */ jsxRuntime.jsx(
663
+ Answer,
664
+ {
665
+ question,
666
+ enabled: open,
667
+ send,
668
+ held
669
+ },
670
+ question.question_id
671
+ )
672
+ ]
673
+ }
674
+ );
675
+ }
676
+ function Answer(props) {
677
+ const { question, send, enabled } = props;
678
+ const { t } = react$1.useCopy();
679
+ const { space } = chunkOWUKWVEZ_cjs.useTheme();
680
+ const { handlers } = useChat();
681
+ const [draft, setDraft] = react.useState({});
682
+ const kind = question.kind;
683
+ const oneTap = ONE_TAP.has(kind) && !question.allows_other;
684
+ const problem = conversation.answerProblem(question, draft);
685
+ const submit = (answer = draft) => {
686
+ if (conversation.answerProblem(question, answer) === null)
687
+ send(answer);
688
+ };
689
+ const input = (() => {
690
+ switch (kind) {
691
+ case "short_text":
692
+ case "long_text":
693
+ return /* @__PURE__ */ jsxRuntime.jsx(
694
+ TextAnswer,
695
+ {
696
+ question,
697
+ enabled,
698
+ value: draft.text ?? "",
699
+ onChange: (text) => setDraft({ text })
700
+ }
701
+ );
702
+ case "number":
703
+ return /* @__PURE__ */ jsxRuntime.jsx(
704
+ NumberAnswer,
705
+ {
706
+ question,
707
+ enabled,
708
+ value: draft.number,
709
+ onChange: (n) => setDraft({ number: n })
710
+ }
711
+ );
712
+ case "scale":
713
+ return /* @__PURE__ */ jsxRuntime.jsx(
714
+ ScaleAnswer,
715
+ {
716
+ question,
717
+ enabled,
718
+ onPick: (n) => submit({ number: n })
719
+ }
720
+ );
721
+ case "date":
722
+ return /* @__PURE__ */ jsxRuntime.jsx(
723
+ DateAnswer,
724
+ {
725
+ question,
726
+ enabled,
727
+ value: draft.date,
728
+ onChange: (date) => setDraft({ date })
729
+ }
730
+ );
731
+ case "photo":
732
+ return /* @__PURE__ */ jsxRuntime.jsx(
733
+ Button,
734
+ {
735
+ label: t("questionAddPhoto"),
736
+ disabled: !enabled || !handlers.current?.onPickPhoto,
737
+ onPress: () => {
738
+ handlers.current?.onPickPhoto?.(question, props.held).then((picked) => {
739
+ if (picked) send({ upload_ref: picked.upload_ref });
740
+ }).catch(
741
+ (error) => handlers.current?.onWarning?.(
742
+ `Vinaverse photo: ${String(error)}`
743
+ )
744
+ );
745
+ }
746
+ }
747
+ );
748
+ default:
749
+ return /* @__PURE__ */ jsxRuntime.jsx(
750
+ ChoiceAnswer,
751
+ {
752
+ question,
753
+ enabled,
754
+ draft,
755
+ onChange: (next) => oneTap ? submit(next) : setDraft(next)
756
+ }
757
+ );
758
+ }
759
+ })();
760
+ const tapToAnswer = oneTap || kind === "scale" || kind === "photo";
761
+ return /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: { gap: space.sm }, children: [
762
+ input,
763
+ /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: { flexDirection: "row", flexWrap: "wrap", gap: space.sm }, children: [
764
+ tapToAnswer ? null : /* @__PURE__ */ jsxRuntime.jsx(
765
+ Button,
766
+ {
767
+ label: t("questionSubmit"),
768
+ variant: "primary",
769
+ disabled: !enabled || problem !== null,
770
+ onPress: () => submit(),
771
+ testID: "vinaverse-question-submit"
772
+ }
773
+ ),
774
+ question.required ? null : /* @__PURE__ */ jsxRuntime.jsx(
775
+ Button,
776
+ {
777
+ label: t("questionSkip"),
778
+ variant: "text",
779
+ disabled: !enabled,
780
+ onPress: () => send({ skipped: true })
781
+ }
782
+ )
783
+ ] })
784
+ ] });
785
+ }
786
+ function ChoiceAnswer(props) {
787
+ const { question, draft, onChange, enabled } = props;
788
+ const { t } = react$1.useCopy();
789
+ const { slots } = useChat();
790
+ const { colors, radius, space, font } = chunkOWUKWVEZ_cjs.useTheme();
791
+ const [search, setSearch] = react.useState("");
792
+ const multi = question.kind === "multi_choice" || question.kind === "image_choice";
793
+ const values = draft.values ?? [];
794
+ const other = draft.other_text;
795
+ const none = question.none_of_the_above;
796
+ const list = question.kind === "list_choice";
797
+ const options = react.useMemo(() => {
798
+ const all = question.options ?? [];
799
+ const q = search.trim().toLowerCase();
800
+ return list && q ? all.filter((o) => o.label.toLowerCase().includes(q)) : all;
801
+ }, [question.options, search, list]);
802
+ const shown = list ? options.slice(0, 50) : options;
803
+ const pick = (value) => {
804
+ if (none && value === none.value) return onChange({ values: [value] });
805
+ const rest = values.filter((v) => v !== none?.value);
806
+ const next = multi ? rest.includes(value) ? rest.filter((v) => v !== value) : [...rest, value] : [value];
807
+ const keepOther = other !== void 0 && (multi || question.kind === "single_choice");
808
+ onChange({
809
+ ...next.length ? { values: next } : {},
810
+ ...keepOther ? { other_text: other } : {}
811
+ });
812
+ };
813
+ const Img = slots.Image ?? reactNative.Image;
814
+ return /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: { gap: space.xs }, children: [
815
+ list ? /* @__PURE__ */ jsxRuntime.jsx(
816
+ reactNative.TextInput,
817
+ {
818
+ value: search,
819
+ onChangeText: setSearch,
820
+ placeholder: t("questionSearch"),
821
+ placeholderTextColor: colors.textMuted,
822
+ accessibilityLabel: t("questionSearch"),
823
+ editable: enabled,
824
+ style: {
825
+ borderWidth: 1,
826
+ borderColor: colors.border,
827
+ borderRadius: radius.md,
828
+ paddingHorizontal: space.md,
829
+ minHeight: 44,
830
+ color: colors.text,
831
+ fontSize: font.size.md
832
+ }
833
+ }
834
+ ) : null,
835
+ shown.map((o) => {
836
+ const image = question.kind === "image_choice" ? safeImage(o.image_url) : null;
837
+ return /* @__PURE__ */ jsxRuntime.jsx(
838
+ Button,
839
+ {
840
+ label: o.label,
841
+ role: multi ? "checkbox" : "radio",
842
+ selected: values.includes(o.value),
843
+ disabled: !enabled,
844
+ ...o.description ? { hint: o.description } : {},
845
+ onPress: () => pick(o.value),
846
+ children: image ? /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: { alignItems: "center", gap: space.xs }, children: [
847
+ /* @__PURE__ */ jsxRuntime.jsx(
848
+ Img,
849
+ {
850
+ source: { uri: image },
851
+ style: { width: 96, height: 96, borderRadius: radius.md },
852
+ accessible: false
853
+ }
854
+ ),
855
+ /* @__PURE__ */ jsxRuntime.jsx(
856
+ ChoiceLabel,
857
+ {
858
+ selected: values.includes(o.value),
859
+ text: o.label
860
+ }
861
+ )
862
+ ] }) : /* @__PURE__ */ jsxRuntime.jsx(ChoiceLabel, { selected: values.includes(o.value), text: o.label })
863
+ },
864
+ o.value
865
+ );
866
+ }),
867
+ none ? /* @__PURE__ */ jsxRuntime.jsx(
868
+ Button,
869
+ {
870
+ label: none.label,
871
+ role: multi ? "checkbox" : "radio",
872
+ selected: values.includes(none.value),
873
+ disabled: !enabled,
874
+ onPress: () => pick(none.value)
875
+ }
876
+ ) : null,
877
+ question.allows_other ? /* @__PURE__ */ jsxRuntime.jsx(
878
+ OtherText,
879
+ {
880
+ max: question.other_max_length ?? 500,
881
+ enabled,
882
+ value: other,
883
+ onChange: (text) => {
884
+ const kept = values.filter((v) => v !== none?.value);
885
+ const withValues = question.kind === "single_choice" || multi;
886
+ onChange({
887
+ ...withValues && kept.length ? { values: question.kind === "single_choice" ? [] : kept } : {},
888
+ ...text === void 0 ? {} : { other_text: text }
889
+ });
890
+ }
891
+ }
892
+ ) : null
893
+ ] });
894
+ }
895
+ function ChoiceLabel(props) {
896
+ const { colors, font } = chunkOWUKWVEZ_cjs.useTheme();
897
+ return /* @__PURE__ */ jsxRuntime.jsx(
898
+ reactNative.Text,
899
+ {
900
+ style: {
901
+ color: props.selected ? colors.onPrimary : colors.text,
902
+ fontSize: font.size.md,
903
+ textAlign: "center"
904
+ },
905
+ children: props.text
906
+ }
907
+ );
908
+ }
909
+ function OtherText(props) {
910
+ const { t } = react$1.useCopy();
911
+ const { colors, radius, space, font } = chunkOWUKWVEZ_cjs.useTheme();
912
+ const on = props.value !== void 0;
913
+ return /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: { gap: space.xs }, children: [
914
+ /* @__PURE__ */ jsxRuntime.jsx(
915
+ Button,
916
+ {
917
+ label: t("questionOther"),
918
+ role: "checkbox",
919
+ selected: on,
920
+ disabled: !props.enabled,
921
+ onPress: () => props.onChange(on ? void 0 : "")
922
+ }
923
+ ),
924
+ on ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
925
+ /* @__PURE__ */ jsxRuntime.jsx(
926
+ reactNative.TextInput,
927
+ {
928
+ value: props.value,
929
+ onChangeText: (text) => props.onChange(text),
930
+ accessibilityLabel: t("questionOther"),
931
+ testID: "vinaverse-question-other",
932
+ editable: props.enabled,
933
+ style: {
934
+ borderWidth: 1,
935
+ borderColor: colors.border,
936
+ borderRadius: radius.md,
937
+ paddingHorizontal: space.md,
938
+ minHeight: 44,
939
+ color: colors.text,
940
+ fontSize: font.size.md
941
+ }
942
+ }
943
+ ),
944
+ /* @__PURE__ */ jsxRuntime.jsx(CharactersLeft, { max: props.max, text: props.value ?? "" })
945
+ ] }) : null
946
+ ] });
947
+ }
948
+ function CharactersLeft(props) {
949
+ const { t } = react$1.useCopy();
950
+ const { colors, font } = chunkOWUKWVEZ_cjs.useTheme();
951
+ const left = props.max - conversation.codePoints(props.text);
952
+ return /* @__PURE__ */ jsxRuntime.jsx(
953
+ reactNative.Text,
954
+ {
955
+ accessibilityLiveRegion: left < 20 ? "polite" : "none",
956
+ style: {
957
+ color: left < 0 ? colors.danger : colors.textMuted,
958
+ fontSize: font.size.xs
959
+ },
960
+ children: t("questionCharactersLeft", { count: left })
961
+ }
962
+ );
963
+ }
964
+ function TextAnswer(props) {
965
+ const { colors, radius, space, font } = chunkOWUKWVEZ_cjs.useTheme();
966
+ const long = props.question.kind === "long_text";
967
+ const max = props.question.text?.max_length ?? (long ? 2e3 : 200);
968
+ return /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: { gap: space.xs }, children: [
969
+ /* @__PURE__ */ jsxRuntime.jsx(
970
+ reactNative.TextInput,
971
+ {
972
+ value: props.value,
973
+ onChangeText: props.onChange,
974
+ accessibilityLabel: props.question.prompt,
975
+ editable: props.enabled,
976
+ multiline: long,
977
+ testID: "vinaverse-question-text",
978
+ style: {
979
+ borderWidth: 1,
980
+ borderColor: colors.border,
981
+ borderRadius: radius.md,
982
+ paddingHorizontal: space.md,
983
+ paddingVertical: space.sm,
984
+ minHeight: long ? 96 : 44,
985
+ color: colors.text,
986
+ fontSize: font.size.md,
987
+ textAlignVertical: long ? "top" : "center"
988
+ }
989
+ }
990
+ ),
991
+ /* @__PURE__ */ jsxRuntime.jsx(CharactersLeft, { max, text: props.value })
992
+ ] });
993
+ }
994
+ function NumberAnswer(props) {
995
+ const range = props.question.number ?? { min: 0, max: 100, step: 1 };
996
+ const { colors, space, font } = chunkOWUKWVEZ_cjs.useTheme();
997
+ const value = props.value ?? range.min;
998
+ const clamp = (n) => Math.min(range.max, Math.max(range.min, Number(n.toFixed(6))));
999
+ const shown = `${value}${props.question.number?.unit ? ` ${props.question.number.unit}` : ""}`;
1000
+ return /* @__PURE__ */ jsxRuntime.jsxs(
1001
+ reactNative.View,
1002
+ {
1003
+ accessible: true,
1004
+ accessibilityRole: "adjustable",
1005
+ accessibilityLabel: props.question.prompt,
1006
+ accessibilityValue: {
1007
+ min: range.min,
1008
+ max: range.max,
1009
+ now: value,
1010
+ text: shown
1011
+ },
1012
+ accessibilityActions: [{ name: "increment" }, { name: "decrement" }],
1013
+ testID: "vinaverse-question-number",
1014
+ onAccessibilityAction: (e) => props.onChange(
1015
+ clamp(
1016
+ value + (e.nativeEvent.actionName === "increment" ? range.step : -range.step)
1017
+ )
1018
+ ),
1019
+ style: { flexDirection: "row", alignItems: "center", gap: space.md },
1020
+ children: [
1021
+ /* @__PURE__ */ jsxRuntime.jsx(
1022
+ Button,
1023
+ {
1024
+ label: "\u2212",
1025
+ disabled: !props.enabled || value <= range.min,
1026
+ onPress: () => props.onChange(clamp(value - range.step))
1027
+ }
1028
+ ),
1029
+ /* @__PURE__ */ jsxRuntime.jsx(
1030
+ reactNative.Text,
1031
+ {
1032
+ style: {
1033
+ color: colors.text,
1034
+ fontSize: font.size.lg,
1035
+ minWidth: 64,
1036
+ textAlign: "center"
1037
+ },
1038
+ children: shown
1039
+ }
1040
+ ),
1041
+ /* @__PURE__ */ jsxRuntime.jsx(
1042
+ Button,
1043
+ {
1044
+ label: "+",
1045
+ disabled: !props.enabled || value >= range.max,
1046
+ onPress: () => props.onChange(clamp(value + range.step))
1047
+ }
1048
+ )
1049
+ ]
1050
+ }
1051
+ );
1052
+ }
1053
+ function ScaleAnswer(props) {
1054
+ const scale = props.question.scale;
1055
+ const { colors, space, font } = chunkOWUKWVEZ_cjs.useTheme();
1056
+ if (!scale) return null;
1057
+ const points = [];
1058
+ for (let n = scale.min; n <= scale.max && points.length < 21; n += scale.step)
1059
+ points.push(n);
1060
+ return /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: { gap: space.xs }, children: [
1061
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: { flexDirection: "row", flexWrap: "wrap", gap: space.xs }, children: points.map((n) => /* @__PURE__ */ jsxRuntime.jsx(
1062
+ Button,
1063
+ {
1064
+ label: String(n),
1065
+ role: "radio",
1066
+ disabled: !props.enabled,
1067
+ onPress: () => props.onPick(n)
1068
+ },
1069
+ n
1070
+ )) }),
1071
+ /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: { flexDirection: "row", justifyContent: "space-between" }, children: [
1072
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: { color: colors.textMuted, fontSize: font.size.xs }, children: scale.min_label }),
1073
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: { color: colors.textMuted, fontSize: font.size.xs }, children: scale.max_label })
1074
+ ] })
1075
+ ] });
1076
+ }
1077
+ var iso = (y, m, d) => `${String(y).padStart(4, "0")}-${String(m + 1).padStart(2, "0")}-${String(d).padStart(2, "0")}`;
1078
+ function DateAnswer(props) {
1079
+ const { locale } = react$1.useCopy();
1080
+ const { colors, space, font } = chunkOWUKWVEZ_cjs.useTheme();
1081
+ const bounds = props.question.date;
1082
+ const start = props.value ?? bounds?.min ?? (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
1083
+ const [month, setMonth] = react.useState(() => ({
1084
+ y: Number(start.slice(0, 4)),
1085
+ m: Number(start.slice(5, 7)) - 1
1086
+ }));
1087
+ const days = new Date(Date.UTC(month.y, month.m + 1, 0)).getUTCDate();
1088
+ const title = (() => {
1089
+ try {
1090
+ return new Intl.DateTimeFormat(locale, {
1091
+ month: "long",
1092
+ year: "numeric",
1093
+ timeZone: "UTC"
1094
+ }).format(new Date(Date.UTC(month.y, month.m, 1)));
1095
+ } catch {
1096
+ return `${month.y}-${month.m + 1}`;
1097
+ }
1098
+ })();
1099
+ const move = (by) => {
1100
+ const d = new Date(Date.UTC(month.y, month.m + by, 1));
1101
+ setMonth({ y: d.getUTCFullYear(), m: d.getUTCMonth() });
1102
+ };
1103
+ const outside = (date) => bounds?.min !== void 0 && date < bounds.min || bounds?.max !== void 0 && date > bounds.max;
1104
+ return /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: { gap: space.xs }, children: [
1105
+ /* @__PURE__ */ jsxRuntime.jsxs(
1106
+ reactNative.View,
1107
+ {
1108
+ style: {
1109
+ flexDirection: "row",
1110
+ alignItems: "center",
1111
+ justifyContent: "space-between"
1112
+ },
1113
+ children: [
1114
+ /* @__PURE__ */ jsxRuntime.jsx(
1115
+ Button,
1116
+ {
1117
+ label: "\u2039",
1118
+ variant: "text",
1119
+ disabled: !props.enabled,
1120
+ onPress: () => move(-1)
1121
+ }
1122
+ ),
1123
+ /* @__PURE__ */ jsxRuntime.jsx(
1124
+ reactNative.Text,
1125
+ {
1126
+ accessibilityRole: "header",
1127
+ style: { color: colors.text, fontSize: font.size.md },
1128
+ children: title
1129
+ }
1130
+ ),
1131
+ /* @__PURE__ */ jsxRuntime.jsx(
1132
+ Button,
1133
+ {
1134
+ label: "\u203A",
1135
+ variant: "text",
1136
+ disabled: !props.enabled,
1137
+ onPress: () => move(1)
1138
+ }
1139
+ )
1140
+ ]
1141
+ }
1142
+ ),
1143
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: { flexDirection: "row", flexWrap: "wrap", gap: space.xxs }, children: Array.from({ length: days }, (_, i) => {
1144
+ const date = iso(month.y, month.m, i + 1);
1145
+ return /* @__PURE__ */ jsxRuntime.jsx(
1146
+ Button,
1147
+ {
1148
+ label: String(i + 1),
1149
+ hint: react$1.formatDate(date, locale),
1150
+ role: "radio",
1151
+ selected: props.value === date,
1152
+ disabled: !props.enabled || outside(date),
1153
+ onPress: () => props.onChange(date)
1154
+ },
1155
+ date
1156
+ );
1157
+ }) }),
1158
+ props.value ? /* @__PURE__ */ jsxRuntime.jsx(Body, { muted: true, children: react$1.formatDate(props.value, locale) }) : null
1159
+ ] });
1160
+ }
1161
+ function RateEach(props) {
1162
+ const { question } = props;
1163
+ const { t } = react$1.useCopy();
1164
+ const { colors, space, font } = chunkOWUKWVEZ_cjs.useTheme();
1165
+ const answered = new Map(
1166
+ (question.row_answers ?? []).map((r) => [
1167
+ r.question_id,
1168
+ (r.answer.values ?? [])[0]
1169
+ ])
1170
+ );
1171
+ const open = (question.rows ?? []).filter(
1172
+ (r) => !props.locked.includes(r.question_id)
1173
+ );
1174
+ return /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: { gap: space.md }, children: [
1175
+ (question.rows ?? []).map((row) => {
1176
+ const done = props.locked.includes(row.question_id);
1177
+ return /* @__PURE__ */ jsxRuntime.jsxs(
1178
+ reactNative.View,
1179
+ {
1180
+ accessibilityLabel: row.label,
1181
+ style: { gap: space.xs },
1182
+ children: [
1183
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: { color: colors.text, fontSize: font.size.sm }, children: row.label }),
1184
+ /* @__PURE__ */ jsxRuntime.jsx(
1185
+ reactNative.View,
1186
+ {
1187
+ style: { flexDirection: "row", flexWrap: "wrap", gap: space.xs },
1188
+ children: (question.options ?? []).map((o) => /* @__PURE__ */ jsxRuntime.jsx(
1189
+ Button,
1190
+ {
1191
+ label: o.label,
1192
+ role: "radio",
1193
+ selected: answered.get(row.question_id) === o.value,
1194
+ disabled: !props.enabled || done,
1195
+ onPress: () => props.send({ values: [o.value] }, row.question_id)
1196
+ },
1197
+ o.value
1198
+ ))
1199
+ }
1200
+ )
1201
+ ]
1202
+ },
1203
+ row.question_id
1204
+ );
1205
+ }),
1206
+ question.required || open.length === 0 ? null : /* @__PURE__ */ jsxRuntime.jsx(
1207
+ Button,
1208
+ {
1209
+ label: t("questionSkip"),
1210
+ variant: "text",
1211
+ disabled: !props.enabled,
1212
+ onPress: () => {
1213
+ for (const row of open)
1214
+ props.send({ skipped: true }, row.question_id);
1215
+ }
1216
+ }
1217
+ )
1218
+ ] });
1219
+ }
1220
+ function answerText(question, locale, otherLabel) {
1221
+ const answer = question.answer;
1222
+ if (!answer || answer.skipped) return null;
1223
+ const labels = new Map(
1224
+ (question.options ?? []).map((o) => [o.value, o.label])
1225
+ );
1226
+ if (question.none_of_the_above)
1227
+ labels.set(
1228
+ question.none_of_the_above.value,
1229
+ question.none_of_the_above.label
1230
+ );
1231
+ const parts = [
1232
+ ...(answer.values ?? []).map((v) => labels.get(v) ?? v),
1233
+ ...answer.other_text ? [`${otherLabel}: ${answer.other_text}`] : [],
1234
+ ...answer.text ? [answer.text] : [],
1235
+ ...answer.number !== void 0 ? [String(answer.number)] : [],
1236
+ ...answer.date ? [react$1.formatDate(answer.date, locale)] : [],
1237
+ ...answer.upload_ref ? ["\u{1F4F7}"] : []
1238
+ ];
1239
+ return parts.length ? parts.join(", ") : null;
1240
+ }
1241
+ function TimeSlotsCard(props) {
1242
+ const card = cardOf(props.card);
1243
+ const { locale } = react$1.useCopy();
1244
+ const { timeZone } = useChat();
1245
+ const { colors, radius, space, font } = chunkOWUKWVEZ_cjs.useTheme();
1246
+ const slots = (card.slots ?? []).map(
1247
+ (s) => react$1.formatTimeRange(s.start, s.end, locale, timeZone)
1248
+ );
1249
+ return /* @__PURE__ */ jsxRuntime.jsxs(
1250
+ CardFrame,
1251
+ {
1252
+ label: [card.title, ...slots].join(", "),
1253
+ restored: props.restored,
1254
+ testID: `vinaverse-card-${card.id}`,
1255
+ children: [
1256
+ /* @__PURE__ */ jsxRuntime.jsx(Title, { children: card.title }),
1257
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: { flexDirection: "row", flexWrap: "wrap", gap: space.xs }, children: slots.map((s) => /* @__PURE__ */ jsxRuntime.jsx(
1258
+ reactNative.Text,
1259
+ {
1260
+ style: {
1261
+ color: colors.text,
1262
+ borderWidth: 1,
1263
+ borderColor: colors.border,
1264
+ borderRadius: radius.pill,
1265
+ paddingHorizontal: space.md,
1266
+ paddingVertical: space.xs,
1267
+ fontSize: font.size.sm
1268
+ },
1269
+ children: s
1270
+ },
1271
+ s
1272
+ )) })
1273
+ ]
1274
+ }
1275
+ );
1276
+ }
1277
+ var DEFAULTS = {
1278
+ proposal: ProposalCard,
1279
+ question: QuestionCard,
1280
+ next_step: NextStepCard,
1281
+ time_slots: TimeSlotsCard,
1282
+ professional: ListingCard,
1283
+ workspace: ListingCard,
1284
+ links: LinksCard
1285
+ };
1286
+ function defaultCard(kind) {
1287
+ return DEFAULTS[kind] ?? InfoCard;
1288
+ }
1289
+ var CardView = /* @__PURE__ */ react.memo(function CardView2(props) {
1290
+ const { slots } = useChat();
1291
+ const { card, busy } = props.item;
1292
+ const Default = defaultCard(card.kind);
1293
+ const Custom = slots.cards?.[card.kind];
1294
+ const read = react.useMemo(() => agent$1.readCard(card.card), [card]);
1295
+ const own = { card, read, busy, restored: props.item.restored === true };
1296
+ return Custom ? /* @__PURE__ */ jsxRuntime.jsx(Custom, { ...own, Default }) : /* @__PURE__ */ jsxRuntime.jsx(Default, { ...own });
1297
+ });
1298
+ var keyOf = (item) => item.key;
1299
+ var kindOf = (item) => item.card.kind;
1300
+ function CardCarousel(props) {
1301
+ const { width } = reactNative.useWindowDimensions();
1302
+ const { t } = react$1.useCopy();
1303
+ const size2 = Math.min(Math.round(width * 0.8), 360);
1304
+ return /* @__PURE__ */ jsxRuntime.jsx(
1305
+ reactNative.View,
1306
+ {
1307
+ accessibilityRole: "list",
1308
+ accessibilityLabel: t("cardsInRow", {
1309
+ count: String(props.items.length)
1310
+ }),
1311
+ children: /* @__PURE__ */ jsxRuntime.jsx(
1312
+ flashList.FlashList,
1313
+ {
1314
+ horizontal: true,
1315
+ data: props.items,
1316
+ keyExtractor: keyOf,
1317
+ getItemType: kindOf,
1318
+ renderItem: ({ item }) => /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: { width: size2 }, children: /* @__PURE__ */ jsxRuntime.jsx(CardView, { item }) }),
1319
+ snapToInterval: size2,
1320
+ decelerationRate: "fast",
1321
+ showsHorizontalScrollIndicator: false
1322
+ }
1323
+ )
1324
+ }
1325
+ );
1326
+ }
1327
+ var ActivityLine = /* @__PURE__ */ react.memo(function ActivityLine2() {
1328
+ const { store } = useChat();
1329
+ const { t } = react$1.useCopy();
1330
+ const { colors, space, font } = chunkOWUKWVEZ_cjs.useTheme();
1331
+ const activity = react$1.useConversationSelector(store, (v) => v.activity);
1332
+ const waiting = react$1.useConversationSelector(
1333
+ store,
1334
+ (v) => v.responding && v.items.at(-1)?.type === "message" && v.items.at(-1).role === "person"
1335
+ );
1336
+ const name = react$1.useConversationSelector(store, (v) => v.agent.name);
1337
+ const running = activity.filter((a) => a.state === "running").at(-1);
1338
+ if (!running && !waiting) return null;
1339
+ const text = running?.label ?? t("working", { agent: name ?? t("agentFallbackName") });
1340
+ return /* @__PURE__ */ jsxRuntime.jsx(
1341
+ reactNative.View,
1342
+ {
1343
+ accessibilityLiveRegion: "polite",
1344
+ style: { paddingHorizontal: space.lg, paddingVertical: space.sm },
1345
+ children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: { color: colors.textMuted, fontSize: font.size.sm }, children: text })
1346
+ }
1347
+ );
1348
+ });
1349
+ var Avatar = /* @__PURE__ */ react.memo(function Avatar2(props) {
1350
+ const { colors, size: size2, font } = chunkOWUKWVEZ_cjs.useTheme();
1351
+ const { slots } = useChat();
1352
+ const box = {
1353
+ width: size2.avatar,
1354
+ height: size2.avatar,
1355
+ borderRadius: size2.avatar / 2
1356
+ };
1357
+ if (props.uri && !props.person) {
1358
+ const Img = slots.Image ?? reactNative.Image;
1359
+ return /* @__PURE__ */ jsxRuntime.jsx(
1360
+ Img,
1361
+ {
1362
+ source: { uri: props.uri },
1363
+ style: box,
1364
+ accessibilityIgnoresInvertColors: true,
1365
+ accessible: false,
1366
+ importantForAccessibility: "no"
1367
+ }
1368
+ );
1369
+ }
1370
+ return /* @__PURE__ */ jsxRuntime.jsx(
1371
+ reactNative.View,
1372
+ {
1373
+ accessible: false,
1374
+ importantForAccessibility: "no-hide-descendants",
1375
+ style: {
1376
+ ...box,
1377
+ backgroundColor: props.person ? colors.staffBubble : colors.primary,
1378
+ alignItems: "center",
1379
+ justifyContent: "center"
1380
+ },
1381
+ children: /* @__PURE__ */ jsxRuntime.jsx(
1382
+ reactNative.Text,
1383
+ {
1384
+ style: {
1385
+ color: props.person ? colors.staffText : colors.onPrimary,
1386
+ fontSize: font.size.sm,
1387
+ fontWeight: "600"
1388
+ },
1389
+ children: props.person ? "\u25C9" : (props.name.trim()[0] ?? "?").toUpperCase()
1390
+ }
1391
+ )
1392
+ }
1393
+ );
1394
+ });
1395
+ function DefaultHeader(props) {
1396
+ const { colors, space, font } = chunkOWUKWVEZ_cjs.useTheme();
1397
+ const { t } = react$1.useCopy();
1398
+ const person = props.takeoverBy !== null;
1399
+ const subtitle = person ? t("staffLabel", { by: props.takeoverBy || t("staffFallbackRole") }) : t("agentLabel");
1400
+ return /* @__PURE__ */ jsxRuntime.jsxs(
1401
+ reactNative.View,
1402
+ {
1403
+ accessibilityRole: "header",
1404
+ accessible: true,
1405
+ accessibilityLabel: `${props.name}, ${subtitle}`,
1406
+ style: {
1407
+ flexDirection: "row",
1408
+ alignItems: "center",
1409
+ gap: space.sm,
1410
+ paddingHorizontal: space.lg,
1411
+ paddingVertical: space.sm,
1412
+ backgroundColor: colors.background,
1413
+ borderBottomWidth: 1,
1414
+ borderBottomColor: colors.divider
1415
+ },
1416
+ children: [
1417
+ /* @__PURE__ */ jsxRuntime.jsx(Avatar, { name: props.name, uri: props.avatarUrl, person }),
1418
+ /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: { flexShrink: 1 }, children: [
1419
+ /* @__PURE__ */ jsxRuntime.jsx(
1420
+ reactNative.Text,
1421
+ {
1422
+ numberOfLines: 1,
1423
+ style: {
1424
+ color: colors.text,
1425
+ fontSize: font.size.md,
1426
+ fontWeight: "600",
1427
+ ...font.family ? { fontFamily: font.family } : {}
1428
+ },
1429
+ children: props.name
1430
+ }
1431
+ ),
1432
+ /* @__PURE__ */ jsxRuntime.jsx(
1433
+ reactNative.Text,
1434
+ {
1435
+ numberOfLines: 1,
1436
+ style: {
1437
+ color: person ? colors.staffAccent : colors.textMuted,
1438
+ fontSize: font.size.xs
1439
+ },
1440
+ children: subtitle
1441
+ }
1442
+ )
1443
+ ] })
1444
+ ]
1445
+ }
1446
+ );
1447
+ }
1448
+ var ChatHeader = /* @__PURE__ */ react.memo(function ChatHeader2() {
1449
+ const { store, slots, agentAvatar } = useChat();
1450
+ const { t } = react$1.useCopy();
1451
+ const agent = react$1.useConversationSelector(store, (v) => v.agent);
1452
+ const takeover = react$1.useConversationSelector(store, (v) => v.takeover);
1453
+ const Header = slots.Header;
1454
+ const props = {
1455
+ name: agent.name ?? t("agentFallbackName"),
1456
+ avatarUrl: agentAvatar ?? agent.avatarUrl,
1457
+ takeoverBy: takeover ? takeover.by ?? "" : null
1458
+ };
1459
+ return Header ? /* @__PURE__ */ jsxRuntime.jsx(Header, { ...props, Default: DefaultHeader }) : /* @__PURE__ */ jsxRuntime.jsx(DefaultHeader, { ...props });
1460
+ });
1461
+ function DefaultComposer(props) {
1462
+ const { t, direction } = react$1.useCopy();
1463
+ const { colors, radius, space, font, size: size2 } = chunkOWUKWVEZ_cjs.useTheme();
1464
+ const [text, setText] = react.useState("");
1465
+ const length = conversation.codePoints(text);
1466
+ const canSend = text.trim().length > 0 && length <= conversation.MAX_TEXT;
1467
+ const send = () => {
1468
+ if (!canSend) return;
1469
+ props.onSend(text);
1470
+ setText("");
1471
+ };
1472
+ return /* @__PURE__ */ jsxRuntime.jsxs(
1473
+ reactNative.View,
1474
+ {
1475
+ style: {
1476
+ flexDirection: "row",
1477
+ alignItems: "flex-end",
1478
+ gap: space.sm,
1479
+ paddingHorizontal: space.lg,
1480
+ paddingVertical: space.sm,
1481
+ borderTopWidth: 1,
1482
+ borderTopColor: colors.divider,
1483
+ backgroundColor: colors.background
1484
+ },
1485
+ children: [
1486
+ /* @__PURE__ */ jsxRuntime.jsx(
1487
+ reactNative.TextInput,
1488
+ {
1489
+ value: text,
1490
+ onChangeText: setText,
1491
+ placeholder: props.placeholder,
1492
+ placeholderTextColor: colors.textMuted,
1493
+ accessibilityLabel: props.placeholder,
1494
+ multiline: true,
1495
+ maxLength: conversation.MAX_TEXT * 2,
1496
+ testID: "vinaverse-composer-input",
1497
+ style: {
1498
+ flex: 1,
1499
+ minHeight: size2.touchTarget,
1500
+ maxHeight: 140,
1501
+ paddingHorizontal: space.md,
1502
+ paddingVertical: space.sm,
1503
+ borderRadius: radius.lg,
1504
+ borderWidth: 1,
1505
+ borderColor: colors.border,
1506
+ color: colors.text,
1507
+ backgroundColor: colors.surface,
1508
+ fontSize: font.size.md,
1509
+ textAlign: direction === "rtl" ? "right" : "left",
1510
+ writingDirection: direction,
1511
+ ...font.family ? { fontFamily: font.family } : {}
1512
+ }
1513
+ }
1514
+ ),
1515
+ props.responding && !text ? /* @__PURE__ */ jsxRuntime.jsx(
1516
+ Button,
1517
+ {
1518
+ label: t("stop"),
1519
+ onPress: props.onStop,
1520
+ testID: "vinaverse-stop"
1521
+ }
1522
+ ) : /* @__PURE__ */ jsxRuntime.jsx(
1523
+ Button,
1524
+ {
1525
+ label: t("send"),
1526
+ variant: "primary",
1527
+ disabled: !canSend,
1528
+ onPress: send,
1529
+ testID: "vinaverse-send"
1530
+ }
1531
+ )
1532
+ ]
1533
+ }
1534
+ );
1535
+ }
1536
+ var Composer = /* @__PURE__ */ react.memo(function Composer2(props) {
1537
+ const { store, slots } = useChat();
1538
+ const { t } = react$1.useCopy();
1539
+ const takeover = react$1.useConversationSelector(store, (v) => v.takeover);
1540
+ const name = react$1.useConversationSelector(store, (v) => v.agent.name);
1541
+ const responding = react$1.useConversationSelector(store, (v) => v.responding);
1542
+ const { onError } = props;
1543
+ const onSend = react.useCallback(
1544
+ (text) => {
1545
+ try {
1546
+ store.send(text);
1547
+ } catch (error) {
1548
+ onError?.(error);
1549
+ }
1550
+ },
1551
+ [store, onError]
1552
+ );
1553
+ const onStop = react.useCallback(() => store.cancel(), [store]);
1554
+ const placeholder = takeover ? t("composerPlaceholderTakeover", {
1555
+ by: takeover.by || t("staffFallbackRole")
1556
+ }) : t("composerPlaceholder", { agent: name ?? t("agentFallbackName") });
1557
+ const Slot = slots.Composer;
1558
+ const own = { placeholder, responding, onSend, onStop };
1559
+ return Slot ? /* @__PURE__ */ jsxRuntime.jsx(Slot, { ...own, Default: DefaultComposer }) : /* @__PURE__ */ jsxRuntime.jsx(DefaultComposer, { ...own });
1560
+ });
1561
+ var DisclosureBar = /* @__PURE__ */ react.memo(function DisclosureBar2() {
1562
+ const { store } = useChat();
1563
+ const { t } = react$1.useCopy();
1564
+ const theme = chunkOWUKWVEZ_cjs.useTheme();
1565
+ const { space, font } = theme;
1566
+ const disclosure = react$1.useConversationSelector(store, (v) => v.disclosure);
1567
+ const [open, setOpen] = react.useState(false);
1568
+ const legible = tokens.contrastRatio(
1569
+ theme.colors.disclosureText,
1570
+ theme.colors.disclosureBackground
1571
+ ) >= 4.5;
1572
+ const pair = legible ? theme.colors : tokens.colors(theme.scheme);
1573
+ const text = disclosure ?? t("disclosureFallback");
1574
+ return /* @__PURE__ */ jsxRuntime.jsx(
1575
+ reactNative.Pressable,
1576
+ {
1577
+ accessibilityRole: "text",
1578
+ accessibilityLabel: text,
1579
+ onPress: () => setOpen((o) => !o),
1580
+ testID: "vinaverse-disclosure",
1581
+ style: {
1582
+ backgroundColor: pair.disclosureBackground,
1583
+ paddingHorizontal: space.lg,
1584
+ paddingVertical: space.xs
1585
+ },
1586
+ children: /* @__PURE__ */ jsxRuntime.jsx(
1587
+ reactNative.Text,
1588
+ {
1589
+ numberOfLines: open ? void 0 : 2,
1590
+ style: {
1591
+ color: pair.disclosureText,
1592
+ // never below the disclosure's minimum, whatever the tenant's type scale
1593
+ fontSize: Math.max(font.size.xs, tokens.size.disclosureFontMin),
1594
+ lineHeight: Math.max(font.size.xs, tokens.size.disclosureFontMin) * font.lineHeight
1595
+ },
1596
+ children: text
1597
+ }
1598
+ )
1599
+ }
1600
+ );
1601
+ });
1602
+ var ChatSkeleton = /* @__PURE__ */ react.memo(function ChatSkeleton2() {
1603
+ const { colors, radius, space } = chunkOWUKWVEZ_cjs.useTheme();
1604
+ const row = (width, end) => /* @__PURE__ */ jsxRuntime.jsx(
1605
+ reactNative.View,
1606
+ {
1607
+ style: {
1608
+ alignSelf: end ? "flex-end" : "flex-start",
1609
+ width,
1610
+ height: 40,
1611
+ borderRadius: radius.lg,
1612
+ backgroundColor: colors.surface,
1613
+ marginHorizontal: space.lg,
1614
+ marginTop: space.md
1615
+ }
1616
+ }
1617
+ );
1618
+ return /* @__PURE__ */ jsxRuntime.jsxs(
1619
+ reactNative.View,
1620
+ {
1621
+ accessible: true,
1622
+ accessibilityRole: "progressbar",
1623
+ accessibilityState: { busy: true },
1624
+ testID: "vinaverse-skeleton",
1625
+ children: [
1626
+ row("62%", false),
1627
+ row("45%", true),
1628
+ row("70%", false)
1629
+ ]
1630
+ }
1631
+ );
1632
+ });
1633
+ function DefaultBubble(props) {
1634
+ const { colors, radius, space, font } = chunkOWUKWVEZ_cjs.useTheme();
1635
+ const role = props.item.role;
1636
+ const [background, foreground] = role === "person" ? [colors.personBubble, colors.personText] : role === "staff" ? [colors.staffBubble, colors.staffText] : [colors.agentBubble, colors.agentText];
1637
+ return /* @__PURE__ */ jsxRuntime.jsx(
1638
+ reactNative.View,
1639
+ {
1640
+ style: {
1641
+ backgroundColor: background,
1642
+ borderRadius: radius.lg,
1643
+ paddingHorizontal: space.md,
1644
+ paddingVertical: space.sm
1645
+ },
1646
+ children: /* @__PURE__ */ jsxRuntime.jsx(
1647
+ reactNative.Text,
1648
+ {
1649
+ selectable: true,
1650
+ style: {
1651
+ color: foreground,
1652
+ fontSize: font.size.md,
1653
+ lineHeight: font.size.md * font.lineHeight,
1654
+ ...font.family ? { fontFamily: font.family } : {}
1655
+ },
1656
+ children: props.text
1657
+ }
1658
+ )
1659
+ }
1660
+ );
1661
+ }
1662
+ function useAuthor(item) {
1663
+ const { t } = react$1.useCopy();
1664
+ const { store } = useChat();
1665
+ const name = store.getSnapshot().agent.name ?? t("agentFallbackName");
1666
+ const by = store.getSnapshot().takeover?.by;
1667
+ if (item.role === "person") return t("you");
1668
+ if (item.role === "staff")
1669
+ return t("staffLabel", { by: by || t("staffFallbackRole") });
1670
+ return `${name} \xB7 ${t("agentLabel")}`;
1671
+ }
1672
+ var MessageBubble = /* @__PURE__ */ react.memo(
1673
+ function MessageBubble2(props) {
1674
+ const { item } = props;
1675
+ return /* @__PURE__ */ jsxRuntime.jsx(Frame, { item, text: item.text, showAuthor: props.showAuthor });
1676
+ }
1677
+ );
1678
+ var StreamingBubble = /* @__PURE__ */ react.memo(
1679
+ function StreamingBubble2(props) {
1680
+ const { store } = useChat();
1681
+ const text = react$1.useStreamingText(store, props.item.key) ?? "";
1682
+ return /* @__PURE__ */ jsxRuntime.jsx(Frame, { item: props.item, text, showAuthor: props.showAuthor });
1683
+ }
1684
+ );
1685
+ function Frame(props) {
1686
+ const { item, text } = props;
1687
+ const { store, slots } = useChat();
1688
+ const { t } = react$1.useCopy();
1689
+ const { colors, space, font } = chunkOWUKWVEZ_cjs.useTheme();
1690
+ const author = useAuthor(item);
1691
+ const mine = item.role === "person";
1692
+ const status = item.state === "sending" ? t("messageSending") : item.state === "held" ? t("messageHeld") : item.state === "not_sent" ? item.rejection?.message || t("messageNotSent") : item.state === "delivered" ? t("messageDelivered") : item.partial ? t("messageCutOff") : null;
1693
+ const Bubble = slots.MessageBubble;
1694
+ const bubble = Bubble ? /* @__PURE__ */ jsxRuntime.jsx(Bubble, { item, text, Default: DefaultBubble }) : /* @__PURE__ */ jsxRuntime.jsx(DefaultBubble, { item, text });
1695
+ return /* @__PURE__ */ jsxRuntime.jsxs(
1696
+ reactNative.View,
1697
+ {
1698
+ accessible: true,
1699
+ accessibilityLabel: [author, text, status].filter(Boolean).join(". "),
1700
+ style: {
1701
+ alignSelf: mine ? "flex-end" : "flex-start",
1702
+ maxWidth: "85%",
1703
+ marginHorizontal: space.lg,
1704
+ marginTop: props.showAuthor ? space.md : space.xs,
1705
+ opacity: item.state === "sending" || item.state === "held" ? 0.75 : 1
1706
+ },
1707
+ children: [
1708
+ props.showAuthor && !mine ? /* @__PURE__ */ jsxRuntime.jsx(
1709
+ reactNative.Text,
1710
+ {
1711
+ style: {
1712
+ color: item.role === "staff" ? colors.staffAccent : colors.textMuted,
1713
+ fontSize: font.size.xs,
1714
+ marginBottom: space.xxs
1715
+ },
1716
+ children: author
1717
+ }
1718
+ ) : null,
1719
+ bubble,
1720
+ status ? /* @__PURE__ */ jsxRuntime.jsxs(
1721
+ reactNative.View,
1722
+ {
1723
+ style: {
1724
+ flexDirection: "row",
1725
+ alignItems: "center",
1726
+ justifyContent: mine ? "flex-end" : "flex-start",
1727
+ gap: space.xs,
1728
+ marginTop: space.xxs
1729
+ },
1730
+ children: [
1731
+ /* @__PURE__ */ jsxRuntime.jsx(
1732
+ reactNative.Text,
1733
+ {
1734
+ style: {
1735
+ color: item.state === "not_sent" ? colors.danger : colors.textMuted,
1736
+ fontSize: font.size.xs
1737
+ },
1738
+ children: status
1739
+ }
1740
+ ),
1741
+ item.state === "not_sent" ? /* @__PURE__ */ jsxRuntime.jsx(
1742
+ Button,
1743
+ {
1744
+ label: t("tryAgain"),
1745
+ variant: "text",
1746
+ onPress: () => {
1747
+ store.retry(item.key);
1748
+ }
1749
+ }
1750
+ ) : null
1751
+ ]
1752
+ }
1753
+ ) : null
1754
+ ]
1755
+ }
1756
+ );
1757
+ }
1758
+ function FailureActionButton(props) {
1759
+ const { handlers, failureAction } = useChat();
1760
+ const { t } = react$1.useCopy();
1761
+ if (!failureAction) return null;
1762
+ return /* @__PURE__ */ jsxRuntime.jsx(
1763
+ Button,
1764
+ {
1765
+ label: failureAction.label ?? t("talkToPerson"),
1766
+ variant: "text",
1767
+ onPress: () => handlers.current.failureAction?.onPress(props.state)
1768
+ }
1769
+ );
1770
+ }
1771
+ function FailureSlot(props) {
1772
+ const { slot: Slot, state, Default } = props;
1773
+ return Slot ? /* @__PURE__ */ jsxRuntime.jsx(Slot, { state, Default }) : /* @__PURE__ */ jsxRuntime.jsx(Default, { state });
1774
+ }
1775
+ var SystemRow = /* @__PURE__ */ react.memo(function SystemRow2(props) {
1776
+ const { t } = react$1.useCopy();
1777
+ const { store, slots } = useChat();
1778
+ const conversationId = react$1.useConversationSelector(
1779
+ store,
1780
+ (v) => v.conversationId
1781
+ );
1782
+ const { event, data } = props.item;
1783
+ if (event === "turn_failed") {
1784
+ const key = props.item.key;
1785
+ const state = {
1786
+ kind: "turn_failed",
1787
+ reason: typeof data.code === "string" ? data.code : null,
1788
+ retry: data.retry === true ? () => void store.retryTurn(key) : void 0,
1789
+ conversationId
1790
+ };
1791
+ return /* @__PURE__ */ jsxRuntime.jsx(
1792
+ FailureSlot,
1793
+ {
1794
+ slot: slots.Error,
1795
+ state,
1796
+ Default: DefaultTurnFailed
1797
+ }
1798
+ );
1799
+ }
1800
+ const agent = store.getSnapshot().agent.name ?? t("agentFallbackName");
1801
+ const text = (() => {
1802
+ switch (event) {
1803
+ case "takeover_started":
1804
+ return t("takeoverStarted", {
1805
+ by: data.by || t("staffFallbackRole")
1806
+ });
1807
+ case "takeover_ended":
1808
+ return t("takeoverEnded", { agent });
1809
+ case "handoff_created":
1810
+ return data.expected_response ? t("handoffCreated", { expected: data.expected_response }) : t("handoffCreatedNoTime");
1811
+ case "flow_completed":
1812
+ return data.result === "ok" ? t("flowSent") : data.result === "failed" ? t("flowNotSent") : null;
1813
+ default:
1814
+ return null;
1815
+ }
1816
+ })();
1817
+ if (!text) return null;
1818
+ return /* @__PURE__ */ jsxRuntime.jsx(Line, { text });
1819
+ });
1820
+ function DefaultTurnFailed(props) {
1821
+ const { t } = react$1.useCopy();
1822
+ const { failureAction } = useChat();
1823
+ const text = t("turnFailed");
1824
+ if (!failureAction) return /* @__PURE__ */ jsxRuntime.jsx(Line, { text, danger: true });
1825
+ return /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: { alignItems: "center" }, children: [
1826
+ /* @__PURE__ */ jsxRuntime.jsx(Line, { text, danger: true }),
1827
+ /* @__PURE__ */ jsxRuntime.jsx(FailureActionButton, { state: props.state })
1828
+ ] });
1829
+ }
1830
+ function Line(props) {
1831
+ const { colors, space, font } = chunkOWUKWVEZ_cjs.useTheme();
1832
+ return /* @__PURE__ */ jsxRuntime.jsx(
1833
+ reactNative.View,
1834
+ {
1835
+ accessible: true,
1836
+ accessibilityLabel: props.text,
1837
+ style: {
1838
+ alignItems: "center",
1839
+ paddingHorizontal: space.xl,
1840
+ paddingVertical: space.sm
1841
+ },
1842
+ children: /* @__PURE__ */ jsxRuntime.jsx(
1843
+ reactNative.Text,
1844
+ {
1845
+ style: {
1846
+ color: props.danger ? colors.danger : colors.textMuted,
1847
+ fontSize: font.size.sm,
1848
+ textAlign: "center"
1849
+ },
1850
+ children: props.text
1851
+ }
1852
+ )
1853
+ }
1854
+ );
1855
+ }
1856
+ var Welcome = /* @__PURE__ */ react.memo(function Welcome2(props) {
1857
+ const { colors, radius, space, font } = chunkOWUKWVEZ_cjs.useTheme();
1858
+ return /* @__PURE__ */ jsxRuntime.jsxs(
1859
+ reactNative.View,
1860
+ {
1861
+ style: { padding: space.lg, gap: space.md },
1862
+ testID: "vinaverse-welcome",
1863
+ children: [
1864
+ /* @__PURE__ */ jsxRuntime.jsx(
1865
+ reactNative.Text,
1866
+ {
1867
+ style: {
1868
+ alignSelf: "flex-start",
1869
+ maxWidth: "85%",
1870
+ color: colors.agentText,
1871
+ backgroundColor: colors.agentBubble,
1872
+ borderRadius: radius.lg,
1873
+ paddingHorizontal: space.md,
1874
+ paddingVertical: space.sm,
1875
+ fontSize: font.size.md,
1876
+ lineHeight: font.size.md * font.lineHeight
1877
+ },
1878
+ children: props.welcome.text
1879
+ }
1880
+ ),
1881
+ props.welcome.chips.length ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: { flexDirection: "row", flexWrap: "wrap", gap: space.sm }, children: props.welcome.chips.map((chip) => /* @__PURE__ */ jsxRuntime.jsx(
1882
+ Button,
1883
+ {
1884
+ label: chip.label,
1885
+ onPress: () => props.onChip(chip.message)
1886
+ },
1887
+ chip.label
1888
+ )) }) : null
1889
+ ]
1890
+ }
1891
+ );
1892
+ });
1893
+ var keyOf2 = (item) => item.key;
1894
+ function itemType(item) {
1895
+ if (item.type === "card") return `card:${item.card.kind}`;
1896
+ if (item.type === "message")
1897
+ return item.state === "streaming" ? "streaming" : `message:${item.role}`;
1898
+ return "system";
1899
+ }
1900
+ function authorOf(item) {
1901
+ return item?.type === "message" ? item.role : null;
1902
+ }
1903
+ function carousels(items, previous) {
1904
+ const runs = /* @__PURE__ */ new Map();
1905
+ const inside = /* @__PURE__ */ new Set();
1906
+ const groupOf = (item) => {
1907
+ if (item?.type !== "card") return null;
1908
+ const group = item.card.card.group;
1909
+ return group?.layout === "carousel" && typeof group.id === "string" ? group.id : null;
1910
+ };
1911
+ for (let i = 0; i < items.length; i++) {
1912
+ const id = groupOf(items[i]);
1913
+ if (id === null) continue;
1914
+ let end = i + 1;
1915
+ while (end < items.length && groupOf(items[end]) === id) end++;
1916
+ if (end - i > 1) {
1917
+ const run = items.slice(i, end);
1918
+ const key = run[0]?.key;
1919
+ const before = previous.get(key);
1920
+ runs.set(
1921
+ key,
1922
+ before && before.length === run.length && before.every((c, n) => c === run[n]) ? before : run
1923
+ );
1924
+ for (const card of run.slice(1)) inside.add(card.key);
1925
+ }
1926
+ i = end - 1;
1927
+ }
1928
+ return { runs, inside };
1929
+ }
1930
+ var Row = /* @__PURE__ */ react.memo(function Row2(props) {
1931
+ const { item } = props;
1932
+ const { slots } = useChat();
1933
+ const Group = slots.CardGroup;
1934
+ if (props.inside) return null;
1935
+ if (props.run && Group) return /* @__PURE__ */ jsxRuntime.jsx(Group, { items: props.run });
1936
+ if (item.type === "card") return /* @__PURE__ */ jsxRuntime.jsx(CardView, { item });
1937
+ if (item.type === "system") return /* @__PURE__ */ jsxRuntime.jsx(SystemRow, { item });
1938
+ return item.state === "streaming" ? /* @__PURE__ */ jsxRuntime.jsx(StreamingBubble, { item, showAuthor: props.showAuthor }) : /* @__PURE__ */ jsxRuntime.jsx(MessageBubble, { item, showAuthor: props.showAuthor });
1939
+ });
1940
+ var MessageList = /* @__PURE__ */ react.memo(function MessageList2() {
1941
+ const { store, slots, starters, handlers, imagePolicy } = useChat();
1942
+ const { t } = react$1.useCopy();
1943
+ const { space } = chunkOWUKWVEZ_cjs.useTheme();
1944
+ const reducedMotion = chunkPYPO6F7L_cjs.useReducedMotion();
1945
+ const items = react$1.useConversationSelector(store, (v) => v.items);
1946
+ usePrefetchPhotos(items, handlers.current?.prefetchImage, imagePolicy?.hosts);
1947
+ const opening = react$1.useConversationSelector(
1948
+ store,
1949
+ (v) => v.status === "starting" || v.status === "connecting"
1950
+ );
1951
+ const agentName = react$1.useConversationSelector(store, (v) => v.agent.name);
1952
+ const served = react$1.useConversationSelector(store, (v) => v.welcome);
1953
+ const welcome = react.useMemo(
1954
+ () => served ?? (starters?.length ? {
1955
+ text: t("welcomeFallback", {
1956
+ agent: agentName ?? t("agentFallbackName")
1957
+ }),
1958
+ chips: [...starters]
1959
+ } : null),
1960
+ [served, starters, t, agentName]
1961
+ );
1962
+ useAnnounceReplies(items);
1963
+ const Group = slots.CardGroup;
1964
+ const runsRef = react.useRef(/* @__PURE__ */ new Map());
1965
+ const grouping = react.useMemo(
1966
+ () => Group ? carousels(items, runsRef.current) : null,
1967
+ [items, Group]
1968
+ );
1969
+ runsRef.current = grouping?.runs ?? /* @__PURE__ */ new Map();
1970
+ const itemsRef = react.useRef(items);
1971
+ itemsRef.current = items;
1972
+ const groupingRef = react.useRef(grouping);
1973
+ groupingRef.current = grouping;
1974
+ const renderItem = react.useCallback(
1975
+ ({ item, index }) => /* @__PURE__ */ jsxRuntime.jsx(
1976
+ Row,
1977
+ {
1978
+ item,
1979
+ showAuthor: authorOf(item) !== authorOf(itemsRef.current[index - 1]),
1980
+ run: groupingRef.current?.runs.get(item.key),
1981
+ inside: groupingRef.current?.inside.has(item.key) === true
1982
+ }
1983
+ ),
1984
+ []
1985
+ );
1986
+ const onChip = react.useCallback(
1987
+ (message) => {
1988
+ store.send(message);
1989
+ const chip = welcome?.chips.find((c) => c.message === message);
1990
+ if (chip) handlers.current?.onChip?.(chip);
1991
+ },
1992
+ [store, welcome, handlers]
1993
+ );
1994
+ const Empty = slots.EmptyState;
1995
+ const empty = opening ? /* @__PURE__ */ jsxRuntime.jsx(ChatSkeleton, {}) : Empty ? /* @__PURE__ */ jsxRuntime.jsx(
1996
+ Empty,
1997
+ {
1998
+ agentName: agentName ?? t("agentFallbackName"),
1999
+ welcome,
2000
+ onChip
2001
+ }
2002
+ ) : welcome ? /* @__PURE__ */ jsxRuntime.jsx(Welcome, { welcome, onChip }) : null;
2003
+ const props = react.useMemo(
2004
+ () => ({
2005
+ data: items,
2006
+ renderItem,
2007
+ keyExtractor: keyOf2,
2008
+ getItemType: itemType,
2009
+ maintainVisibleContentPosition: {
2010
+ startRenderingFromBottom: true,
2011
+ autoscrollToBottomThreshold: 0.2,
2012
+ animateAutoScrollToBottom: !reducedMotion
2013
+ },
2014
+ ListFooterComponent: /* @__PURE__ */ jsxRuntime.jsx(ActivityLine, {}),
2015
+ ListEmptyComponent: empty,
2016
+ keyboardShouldPersistTaps: "handled",
2017
+ contentContainerStyle: { paddingVertical: space.sm }
2018
+ }),
2019
+ [items, renderItem, reducedMotion, empty, space.sm]
2020
+ );
2021
+ const List = slots.ListComponent;
2022
+ return List ? /* @__PURE__ */ jsxRuntime.jsx(List, { ...props }) : /* @__PURE__ */ jsxRuntime.jsx(flashList.FlashList, { ...props });
2023
+ });
2024
+ function usePrefetchPhotos(items, prefetch, hosts) {
2025
+ const done = react.useRef(/* @__PURE__ */ new Set());
2026
+ react.useEffect(() => {
2027
+ if (!prefetch) return;
2028
+ for (const item of items) {
2029
+ if (item.type !== "card") continue;
2030
+ const card = item.card.card;
2031
+ const url = allowedImage(card.images?.[0]?.url ?? card.image_url, hosts);
2032
+ if (url && !done.current.has(url)) {
2033
+ done.current.add(url);
2034
+ prefetch(url);
2035
+ }
2036
+ }
2037
+ }, [items, prefetch, hosts]);
2038
+ }
2039
+ function useAnnounceReplies(items) {
2040
+ const seen = react.useRef(void 0);
2041
+ const { t } = react$1.useCopy();
2042
+ const { store, handlers } = useChat();
2043
+ react.useEffect(() => {
2044
+ const last = items.at(-1);
2045
+ if (seen.current === void 0) {
2046
+ seen.current = last?.key ?? "";
2047
+ return;
2048
+ }
2049
+ if (last?.type !== "message" || last.role === "person") return;
2050
+ if (last.state !== "sent" || last.key === seen.current) return;
2051
+ seen.current = last.key;
2052
+ const author = last.role === "staff" ? t("staffLabel", {
2053
+ by: store.getSnapshot().takeover?.by || t("staffFallbackRole")
2054
+ }) : store.getSnapshot().agent.name ?? t("agentFallbackName");
2055
+ (handlers.current?.announce ?? chunkPYPO6F7L_cjs.announce)(`${author}: ${last.text}`);
2056
+ }, [items, t, store, handlers]);
2057
+ }
2058
+ var CONNECTING_DELAY = tokens.motion.bannerDelay;
2059
+ var StatusBanner = /* @__PURE__ */ react.memo(function StatusBanner2() {
2060
+ const { store, slots } = useChat();
2061
+ const { t } = react$1.useCopy();
2062
+ const banner = react$1.useConversationSelector(store, (v) => v.banner);
2063
+ const status = react$1.useConversationSelector(store, (v) => v.status);
2064
+ const conversationId = react$1.useConversationSelector(
2065
+ store,
2066
+ (v) => v.conversationId
2067
+ );
2068
+ const unavailable = react.useMemo(
2069
+ () => ({
2070
+ kind: "unavailable",
2071
+ reason: status,
2072
+ retry: () => store.start(),
2073
+ conversationId
2074
+ }),
2075
+ [store, status, conversationId]
2076
+ );
2077
+ const [late, setLate] = react.useState(false);
2078
+ react.useEffect(() => {
2079
+ setLate(false);
2080
+ if (banner !== "connecting") return;
2081
+ const timer = setTimeout(() => setLate(true), CONNECTING_DELAY);
2082
+ return () => clearTimeout(timer);
2083
+ }, [banner]);
2084
+ if (banner === null || banner === "connecting" && !late) return null;
2085
+ if (banner === "unavailable")
2086
+ return /* @__PURE__ */ jsxRuntime.jsx(
2087
+ FailureSlot,
2088
+ {
2089
+ slot: slots.Unavailable,
2090
+ state: unavailable,
2091
+ Default: DefaultUnavailable
2092
+ }
2093
+ );
2094
+ const text = {
2095
+ connecting: t("bannerConnecting"),
2096
+ reconnecting: t("bannerReconnecting"),
2097
+ offline: t("bannerOffline"),
2098
+ continued_elsewhere: t("bannerContinuedElsewhere")
2099
+ }[banner];
2100
+ const action = banner === "continued_elsewhere" ? /* @__PURE__ */ jsxRuntime.jsx(
2101
+ Button,
2102
+ {
2103
+ label: t("continueHere"),
2104
+ variant: "text",
2105
+ onPress: () => void store.continueHere()
2106
+ }
2107
+ ) : banner === "offline" ? /* @__PURE__ */ jsxRuntime.jsx(
2108
+ Button,
2109
+ {
2110
+ label: t("tryAgain"),
2111
+ variant: "text",
2112
+ onPress: () => store.start()
2113
+ }
2114
+ ) : null;
2115
+ return /* @__PURE__ */ jsxRuntime.jsx(Banner, { text, children: action });
2116
+ });
2117
+ function DefaultUnavailable(props) {
2118
+ const { t } = react$1.useCopy();
2119
+ const { retry } = props.state;
2120
+ return /* @__PURE__ */ jsxRuntime.jsxs(Banner, { text: t("bannerUnavailable"), children: [
2121
+ retry ? /* @__PURE__ */ jsxRuntime.jsx(Button, { label: t("tryAgain"), variant: "text", onPress: retry }) : null,
2122
+ /* @__PURE__ */ jsxRuntime.jsx(FailureActionButton, { state: props.state })
2123
+ ] });
2124
+ }
2125
+ function Banner(props) {
2126
+ const { colors, space, font } = chunkOWUKWVEZ_cjs.useTheme();
2127
+ return /* @__PURE__ */ jsxRuntime.jsxs(
2128
+ reactNative.View,
2129
+ {
2130
+ accessibilityLiveRegion: "polite",
2131
+ accessibilityRole: "alert",
2132
+ style: {
2133
+ flexDirection: "row",
2134
+ alignItems: "center",
2135
+ justifyContent: "space-between",
2136
+ gap: space.sm,
2137
+ paddingHorizontal: space.lg,
2138
+ paddingVertical: space.xs,
2139
+ backgroundColor: colors.surface,
2140
+ borderBottomWidth: 1,
2141
+ borderBottomColor: colors.divider
2142
+ },
2143
+ children: [
2144
+ /* @__PURE__ */ jsxRuntime.jsx(
2145
+ reactNative.Text,
2146
+ {
2147
+ style: { color: colors.text, fontSize: font.size.sm, flexShrink: 1 },
2148
+ children: props.text
2149
+ }
2150
+ ),
2151
+ props.children
2152
+ ]
2153
+ }
2154
+ );
2155
+ }
2156
+ function VinaverseChatProvider(props) {
2157
+ const {
2158
+ conversation,
2159
+ components,
2160
+ agentAvatar,
2161
+ timeZone,
2162
+ starters,
2163
+ imagePolicy
2164
+ } = props;
2165
+ const handlers = react.useRef(props);
2166
+ handlers.current = props;
2167
+ const slots = useStableSlots(components);
2168
+ const hasAction = props.failureAction !== void 0;
2169
+ const actionLabel = props.failureAction?.label;
2170
+ const failureAction = react.useMemo(
2171
+ () => hasAction ? { label: actionLabel } : null,
2172
+ [hasAction, actionLabel]
2173
+ );
2174
+ const value = react.useMemo(
2175
+ () => ({
2176
+ store: conversation,
2177
+ handlers,
2178
+ slots,
2179
+ agentAvatar,
2180
+ timeZone,
2181
+ failureAction,
2182
+ starters,
2183
+ imagePolicy
2184
+ }),
2185
+ [
2186
+ conversation,
2187
+ slots,
2188
+ agentAvatar,
2189
+ timeZone,
2190
+ failureAction,
2191
+ starters,
2192
+ imagePolicy
2193
+ ]
2194
+ );
2195
+ chunkPYPO6F7L_cjs.useNativeLifecycle(conversation);
2196
+ react.useEffect(() => {
2197
+ conversation.start();
2198
+ }, [conversation]);
2199
+ const { onWarning, theme, copy } = props;
2200
+ react.useEffect(() => {
2201
+ if (!onWarning) return;
2202
+ for (const problem of chunkOWUKWVEZ_cjs.themeProblems(theme))
2203
+ onWarning(`Vinaverse theme: ${problem}`);
2204
+ for (const key of react$1.bannedWordsIn(copy ?? {}))
2205
+ onWarning(
2206
+ `Vinaverse copy: "${key}" says assistant, bot or crew; the copy says agent.`
2207
+ );
2208
+ }, [onWarning, theme, copy]);
2209
+ return /* @__PURE__ */ jsxRuntime.jsx(react$1.CopyProvider, { locale: props.locale, copy: props.copy, children: /* @__PURE__ */ jsxRuntime.jsx(chunkOWUKWVEZ_cjs.ThemeProvider, { theme: props.theme, colorScheme: props.colorScheme, children: /* @__PURE__ */ jsxRuntime.jsx(ChatContextProvider, { value, children: props.children }) }) });
2210
+ }
2211
+ function VinaverseChat(props) {
2212
+ return /* @__PURE__ */ jsxRuntime.jsx(VinaverseChatProvider, { ...props, children: /* @__PURE__ */ jsxRuntime.jsx(
2213
+ Layout,
2214
+ {
2215
+ hideHeader: props.hideHeader === true,
2216
+ keyboardVerticalOffset: props.keyboardVerticalOffset ?? 0,
2217
+ keyboardBehavior: props.keyboardBehavior ?? (reactNative.Platform.OS === "ios" ? "padding" : "none"),
2218
+ style: props.style,
2219
+ onError: props.onWarning,
2220
+ suggestion: props.suggestion ?? null,
2221
+ footer: props.footer
2222
+ }
2223
+ ) });
2224
+ }
2225
+ function Layout(props) {
2226
+ const { colors } = chunkOWUKWVEZ_cjs.useTheme();
2227
+ const { direction } = react$1.useCopy();
2228
+ const { onError } = props;
2229
+ const reportError = react.useMemo(
2230
+ () => onError ? (e) => onError(String(e)) : void 0,
2231
+ [onError]
2232
+ );
2233
+ return /* @__PURE__ */ jsxRuntime.jsxs(
2234
+ reactNative.KeyboardAvoidingView,
2235
+ {
2236
+ ...props.keyboardBehavior === "none" ? { enabled: false } : { behavior: props.keyboardBehavior },
2237
+ keyboardVerticalOffset: props.keyboardVerticalOffset,
2238
+ style: [
2239
+ { flex: 1, backgroundColor: colors.background, direction },
2240
+ props.style
2241
+ ],
2242
+ children: [
2243
+ props.hideHeader ? null : /* @__PURE__ */ jsxRuntime.jsx(ChatHeader, {}),
2244
+ /* @__PURE__ */ jsxRuntime.jsx(DisclosureBar, {}),
2245
+ /* @__PURE__ */ jsxRuntime.jsx(StatusBanner, {}),
2246
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: { flex: 1 }, children: /* @__PURE__ */ jsxRuntime.jsx(MessageList, {}) }),
2247
+ props.suggestion ? /* @__PURE__ */ jsxRuntime.jsx(Suggestion, { chip: props.suggestion }) : null,
2248
+ /* @__PURE__ */ jsxRuntime.jsx(Composer, { ...reportError ? { onError: reportError } : {} }),
2249
+ props.footer
2250
+ ]
2251
+ }
2252
+ );
2253
+ }
2254
+ function Suggestion(props) {
2255
+ const { store, handlers } = useChat();
2256
+ const { space } = chunkOWUKWVEZ_cjs.useTheme();
2257
+ return /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: { paddingHorizontal: space.lg, alignItems: "flex-start" }, children: /* @__PURE__ */ jsxRuntime.jsx(
2258
+ Button,
2259
+ {
2260
+ label: props.chip.label,
2261
+ onPress: () => {
2262
+ store.send(props.chip.message);
2263
+ handlers.current?.onChip?.(props.chip);
2264
+ }
2265
+ }
2266
+ ) });
2267
+ }
2268
+ function useStableSlots(slots) {
2269
+ const held = react.useRef({});
2270
+ const next = slots ?? {};
2271
+ if (!sameSlots(held.current, next)) held.current = next;
2272
+ return held.current;
2273
+ }
2274
+ function sameSlots(a, b) {
2275
+ const keys = /* @__PURE__ */ new Set([...Object.keys(a), ...Object.keys(b)]);
2276
+ for (const key of keys) {
2277
+ if (key === "cards") {
2278
+ const ca = a.cards ?? {};
2279
+ const cb = b.cards ?? {};
2280
+ const kinds = /* @__PURE__ */ new Set([...Object.keys(ca), ...Object.keys(cb)]);
2281
+ for (const kind of kinds) if (ca[kind] !== cb[kind]) return false;
2282
+ } else if (a[key] !== b[key]) return false;
2283
+ }
2284
+ return true;
2285
+ }
2286
+ function AgentChat(props) {
2287
+ const {
2288
+ agentKey,
2289
+ place,
2290
+ entryPoint,
2291
+ askAbout,
2292
+ onContact,
2293
+ onOpenPrivacy,
2294
+ components,
2295
+ agentName,
2296
+ opener,
2297
+ ...chat
2298
+ } = props;
2299
+ const runtime = agent.useAgentRuntime();
2300
+ const { conversation, state, startNew } = agent.useAgentConversation(agentKey, {
2301
+ place: place ?? null,
2302
+ entryPoint: entryPoint ?? null
2303
+ });
2304
+ const store = conversation.store;
2305
+ const spoke = react$1.useConversationSelector(
2306
+ store,
2307
+ (v) => v.items.some((i) => i.type === "message" && i.role === "person")
2308
+ );
2309
+ const { Ended, Unreachable, UnavailableScreen, Footer, ...slots } = components ?? {};
2310
+ const copy = react.useMemo(
2311
+ () => agentName ? { ...props.copy, agentFallbackName: agentName } : props.copy,
2312
+ [agentName, props.copy]
2313
+ );
2314
+ const events = {
2315
+ onOpenTarget: chat.onOpenTarget ? (target, card) => {
2316
+ runtime.emit({
2317
+ type: "target_opened",
2318
+ key: conversation.key,
2319
+ cardId: card.id,
2320
+ kind: target.type
2321
+ });
2322
+ chat.onOpenTarget?.(target, card);
2323
+ } : void 0,
2324
+ onOpenLink: chat.onOpenLink ? (link, card) => {
2325
+ runtime.emit({
2326
+ type: "link_opened",
2327
+ key: conversation.key,
2328
+ cardId: card.id,
2329
+ kind: card.kind
2330
+ });
2331
+ chat.onOpenLink?.(link, card);
2332
+ } : void 0,
2333
+ onChip: (chip) => {
2334
+ runtime.emit({ type: "chip_tapped", key: conversation.key });
2335
+ chat.onChip?.(chip);
2336
+ }
2337
+ };
2338
+ const shared = {
2339
+ ...chat,
2340
+ ...events.onOpenTarget ? { onOpenTarget: events.onOpenTarget } : {},
2341
+ ...events.onOpenLink ? { onOpenLink: events.onOpenLink } : {},
2342
+ onChip: events.onChip,
2343
+ ...copy ? { copy } : {},
2344
+ ...onContact && !chat.failureAction ? { failureAction: { onPress: () => onContact() } } : {},
2345
+ conversation: store,
2346
+ components: slots
2347
+ };
2348
+ if (state.failure === "ended")
2349
+ return /* @__PURE__ */ jsxRuntime.jsx(VinaverseChatProvider, { ...shared, children: /* @__PURE__ */ jsxRuntime.jsx(Centered, { children: Ended ? /* @__PURE__ */ jsxRuntime.jsx(
2350
+ Ended,
2351
+ {
2352
+ onStartNew: startNew,
2353
+ code: state.code,
2354
+ Default: DefaultEnded
2355
+ }
2356
+ ) : /* @__PURE__ */ jsxRuntime.jsx(DefaultEnded, { onStartNew: startNew, code: state.code }) }) });
2357
+ if (state.failure === "unreachable")
2358
+ return /* @__PURE__ */ jsxRuntime.jsx(VinaverseChatProvider, { ...shared, children: /* @__PURE__ */ jsxRuntime.jsx(Centered, { children: Unreachable ? /* @__PURE__ */ jsxRuntime.jsx(
2359
+ Unreachable,
2360
+ {
2361
+ onRetry: conversation.retry,
2362
+ reason: state.reason,
2363
+ Default: DefaultUnreachable
2364
+ }
2365
+ ) : /* @__PURE__ */ jsxRuntime.jsx(
2366
+ DefaultUnreachable,
2367
+ {
2368
+ onRetry: conversation.retry,
2369
+ reason: state.reason
2370
+ }
2371
+ ) }) });
2372
+ if (state.failure === "unavailable") {
2373
+ const own = {
2374
+ reason: state.reason,
2375
+ code: state.code,
2376
+ onContact,
2377
+ onRetry: conversation.retry
2378
+ };
2379
+ return /* @__PURE__ */ jsxRuntime.jsx(VinaverseChatProvider, { ...shared, children: /* @__PURE__ */ jsxRuntime.jsx(Centered, { children: UnavailableScreen ? /* @__PURE__ */ jsxRuntime.jsx(UnavailableScreen, { ...own, Default: DefaultUnavailableScreen }) : /* @__PURE__ */ jsxRuntime.jsx(DefaultUnavailableScreen, { ...own }) }) });
2380
+ }
2381
+ return /* @__PURE__ */ jsxRuntime.jsx(
2382
+ VinaverseChat,
2383
+ {
2384
+ ...shared,
2385
+ suggestion: opener && !spoke ? opener : state.moved && askAbout ? { label: askAbout, message: askAbout } : null,
2386
+ footer: Footer === null ? null : /* @__PURE__ */ jsxRuntime.jsx(
2387
+ FooterFor,
2388
+ {
2389
+ store,
2390
+ state,
2391
+ rate: conversation.rate,
2392
+ Footer,
2393
+ onOpenPrivacy
2394
+ }
2395
+ )
2396
+ }
2397
+ );
2398
+ }
2399
+ function FooterFor(props) {
2400
+ const turnId = react$1.useConversationSelector(props.store, newestWholeReply);
2401
+ const own = {
2402
+ turnId,
2403
+ rating: turnId ? props.state.ratings.get(turnId) : void 0,
2404
+ onRate: (rating) => {
2405
+ if (turnId) props.rate(turnId, rating);
2406
+ },
2407
+ onOpenPrivacy: props.onOpenPrivacy
2408
+ };
2409
+ const { Footer } = props;
2410
+ return Footer ? /* @__PURE__ */ jsxRuntime.jsx(Footer, { ...own, Default: AgentFooter }) : /* @__PURE__ */ jsxRuntime.jsx(AgentFooter, { ...own });
2411
+ }
2412
+ function newestWholeReply(view) {
2413
+ for (let i = view.items.length - 1; i >= 0; i--) {
2414
+ const item = view.items[i];
2415
+ if (item?.type !== "message" || item.role !== "agent") continue;
2416
+ const whole = item.state === "sent" && !item.partial && !item.refusal && item.key.startsWith("a:");
2417
+ return whole ? item.key.slice(2) : null;
2418
+ }
2419
+ return null;
2420
+ }
2421
+ function Centered(props) {
2422
+ const { colors, space } = chunkOWUKWVEZ_cjs.useTheme();
2423
+ return /* @__PURE__ */ jsxRuntime.jsx(
2424
+ reactNative.View,
2425
+ {
2426
+ style: {
2427
+ flex: 1,
2428
+ alignItems: "center",
2429
+ justifyContent: "center",
2430
+ padding: space.xl,
2431
+ backgroundColor: colors.background
2432
+ },
2433
+ children: props.children
2434
+ }
2435
+ );
2436
+ }
2437
+ function Notice(props) {
2438
+ const { colors, space, font } = chunkOWUKWVEZ_cjs.useTheme();
2439
+ return /* @__PURE__ */ jsxRuntime.jsxs(
2440
+ reactNative.View,
2441
+ {
2442
+ accessibilityRole: "alert",
2443
+ style: { alignItems: "center", gap: space.md },
2444
+ children: [
2445
+ /* @__PURE__ */ jsxRuntime.jsx(
2446
+ reactNative.Text,
2447
+ {
2448
+ style: {
2449
+ color: colors.text,
2450
+ fontSize: font.size.lg,
2451
+ fontWeight: "600",
2452
+ textAlign: "center"
2453
+ },
2454
+ children: props.title
2455
+ }
2456
+ ),
2457
+ /* @__PURE__ */ jsxRuntime.jsx(
2458
+ reactNative.Text,
2459
+ {
2460
+ style: {
2461
+ color: colors.textMuted,
2462
+ fontSize: font.size.md,
2463
+ textAlign: "center"
2464
+ },
2465
+ children: props.body
2466
+ }
2467
+ ),
2468
+ props.action && props.onPress ? /* @__PURE__ */ jsxRuntime.jsx(
2469
+ Button,
2470
+ {
2471
+ label: props.action,
2472
+ variant: "primary",
2473
+ onPress: props.onPress
2474
+ }
2475
+ ) : null
2476
+ ]
2477
+ }
2478
+ );
2479
+ }
2480
+ function DefaultEnded(props) {
2481
+ const { t } = react$1.useCopy();
2482
+ return /* @__PURE__ */ jsxRuntime.jsx(
2483
+ Notice,
2484
+ {
2485
+ title: t("agentEnded"),
2486
+ body: t("agentEndedBody"),
2487
+ action: t("agentStartOver"),
2488
+ onPress: props.onStartNew
2489
+ }
2490
+ );
2491
+ }
2492
+ function DefaultUnavailableScreen(props) {
2493
+ const { t } = react$1.useCopy();
2494
+ const name = agent.useAgentName(t("agentFallbackName"));
2495
+ const title = t("agentOff", { agent: name });
2496
+ return /* @__PURE__ */ jsxRuntime.jsx(
2497
+ Notice,
2498
+ {
2499
+ title: title.charAt(0).toUpperCase() + title.slice(1),
2500
+ body: t("agentOffBody"),
2501
+ ...props.onContact ? { action: t("talkToPerson"), onPress: props.onContact } : {}
2502
+ }
2503
+ );
2504
+ }
2505
+ function DefaultUnreachable(props) {
2506
+ const { t } = react$1.useCopy();
2507
+ const name = agent.useAgentName(t("agentFallbackName"));
2508
+ return /* @__PURE__ */ jsxRuntime.jsx(
2509
+ Notice,
2510
+ {
2511
+ title: t("agentUnreachable", { agent: name }),
2512
+ body: t("agentUnreachableBody"),
2513
+ action: t("tryAgain"),
2514
+ onPress: props.onRetry
2515
+ }
2516
+ );
2517
+ }
2518
+ function AgentFooter(props) {
2519
+ const { t } = react$1.useCopy();
2520
+ const { colors, space, font } = chunkOWUKWVEZ_cjs.useTheme();
2521
+ const muted = { color: colors.textMuted, fontSize: font.size.sm };
2522
+ return /* @__PURE__ */ jsxRuntime.jsxs(
2523
+ reactNative.View,
2524
+ {
2525
+ style: {
2526
+ flexDirection: "row",
2527
+ flexWrap: "wrap",
2528
+ alignItems: "center",
2529
+ gap: space.xs,
2530
+ paddingHorizontal: space.lg
2531
+ },
2532
+ children: [
2533
+ props.turnId === null ? null : props.rating ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: muted, children: t("helpfulThanks") }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
2534
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: muted, children: t("helpful") }),
2535
+ /* @__PURE__ */ jsxRuntime.jsx(
2536
+ Button,
2537
+ {
2538
+ label: t("helpfulYes"),
2539
+ variant: "text",
2540
+ onPress: () => props.onRate("up")
2541
+ }
2542
+ ),
2543
+ /* @__PURE__ */ jsxRuntime.jsx(
2544
+ Button,
2545
+ {
2546
+ label: t("helpfulNo"),
2547
+ variant: "text",
2548
+ onPress: () => props.onRate("down")
2549
+ }
2550
+ )
2551
+ ] }),
2552
+ props.onOpenPrivacy ? /* @__PURE__ */ jsxRuntime.jsx(
2553
+ Button,
2554
+ {
2555
+ label: t("privacy"),
2556
+ variant: "text",
2557
+ role: "link",
2558
+ onPress: props.onOpenPrivacy
2559
+ }
2560
+ ) : null
2561
+ ]
2562
+ }
2563
+ );
2564
+ }
2565
+
2566
+ Object.defineProperty(exports, "AgentRuntimeProvider", {
2567
+ enumerable: true,
2568
+ get: function () { return agent.AgentRuntimeProvider; }
2569
+ });
2570
+ Object.defineProperty(exports, "greetedKey", {
2571
+ enumerable: true,
2572
+ get: function () { return agent.greetedKey; }
2573
+ });
2574
+ Object.defineProperty(exports, "useAgentConversation", {
2575
+ enumerable: true,
2576
+ get: function () { return agent.useAgentConversation; }
2577
+ });
2578
+ Object.defineProperty(exports, "useAgentFeatures", {
2579
+ enumerable: true,
2580
+ get: function () { return agent.useAgentFeatures; }
2581
+ });
2582
+ Object.defineProperty(exports, "useAgentLauncher", {
2583
+ enumerable: true,
2584
+ get: function () { return agent.useAgentLauncher; }
2585
+ });
2586
+ Object.defineProperty(exports, "useAgentName", {
2587
+ enumerable: true,
2588
+ get: function () { return agent.useAgentName; }
2589
+ });
2590
+ Object.defineProperty(exports, "useAgentProfile", {
2591
+ enumerable: true,
2592
+ get: function () { return agent.useAgentProfile; }
2593
+ });
2594
+ Object.defineProperty(exports, "useAgentRuntime", {
2595
+ enumerable: true,
2596
+ get: function () { return agent.useAgentRuntime; }
2597
+ });
2598
+ Object.defineProperty(exports, "useAgentStatus", {
2599
+ enumerable: true,
2600
+ get: function () { return agent.useAgentStatus; }
2601
+ });
2602
+ exports.ActivityLine = ActivityLine;
2603
+ exports.AgentChat = AgentChat;
2604
+ exports.AgentFooter = AgentFooter;
2605
+ exports.Avatar = Avatar;
2606
+ exports.Button = Button;
2607
+ exports.CardActions = CardActions;
2608
+ exports.CardCarousel = CardCarousel;
2609
+ exports.CardFrame = CardFrame;
2610
+ exports.CardView = CardView;
2611
+ exports.ChatHeader = ChatHeader;
2612
+ exports.ChatSkeleton = ChatSkeleton;
2613
+ exports.Composer = Composer;
2614
+ exports.DefaultBubble = DefaultBubble;
2615
+ exports.DefaultComposer = DefaultComposer;
2616
+ exports.DefaultEnded = DefaultEnded;
2617
+ exports.DefaultHeader = DefaultHeader;
2618
+ exports.DefaultTurnFailed = DefaultTurnFailed;
2619
+ exports.DefaultUnavailable = DefaultUnavailable;
2620
+ exports.DefaultUnavailableScreen = DefaultUnavailableScreen;
2621
+ exports.DefaultUnreachable = DefaultUnreachable;
2622
+ exports.DisclosureBar = DisclosureBar;
2623
+ exports.FailureActionButton = FailureActionButton;
2624
+ exports.InfoCard = InfoCard;
2625
+ exports.LinksCard = LinksCard;
2626
+ exports.ListingCard = ListingCard;
2627
+ exports.MessageBubble = MessageBubble;
2628
+ exports.MessageList = MessageList;
2629
+ exports.NextStepCard = NextStepCard;
2630
+ exports.ProposalCard = ProposalCard;
2631
+ exports.QuestionCard = QuestionCard;
2632
+ exports.Row = Row;
2633
+ exports.StatusBanner = StatusBanner;
2634
+ exports.StreamingBubble = StreamingBubble;
2635
+ exports.SystemRow = SystemRow;
2636
+ exports.TimeSlotsCard = TimeSlotsCard;
2637
+ exports.VinaverseChat = VinaverseChat;
2638
+ exports.VinaverseChatProvider = VinaverseChatProvider;
2639
+ exports.Welcome = Welcome;
2640
+ exports.allowedImage = allowedImage;
2641
+ exports.answerText = answerText;
2642
+ exports.carousels = carousels;
2643
+ exports.defaultCard = defaultCard;
2644
+ exports.fieldText = fieldText;
2645
+ exports.itemType = itemType;
2646
+ exports.monogram = monogram;
2647
+ exports.newestWholeReply = newestWholeReply;
2648
+ exports.ratingText = ratingText;
2649
+ exports.useChat = useChat;
2650
+ //# sourceMappingURL=chunk-H2Y2YW2Q.cjs.map
2651
+ //# sourceMappingURL=chunk-H2Y2YW2Q.cjs.map