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

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