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

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