@vinaverse/react-native 0.1.0-alpha.2 → 0.1.0-alpha.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -1,14 +1,14 @@
1
1
  'use strict';
2
2
 
3
+ var flashList = require('@shopify/flash-list');
4
+ var react$1 = require('@vinaverse/react');
3
5
  var reactNative = require('react-native');
4
6
  var react = require('react');
5
7
  var jsxRuntime = require('react/jsx-runtime');
6
8
  var tokens = require('@vinaverse/tokens');
7
- var react$1 = require('@vinaverse/react');
8
9
  var conversation = require('@vinaverse/client/conversation');
9
- var flashList = require('@shopify/flash-list');
10
10
 
11
- // src/cards/CardFrame.tsx
11
+ // src/cards/CardCarousel.tsx
12
12
  var ChatContext = react.createContext(null);
13
13
  function ChatContextProvider(props) {
14
14
  return /* @__PURE__ */ jsxRuntime.jsx(ChatContext.Provider, { value: props.value, children: props.children });
@@ -57,6 +57,61 @@ function ThemeProvider(props) {
57
57
  function useTheme() {
58
58
  return react.useContext(ThemeContext);
59
59
  }
60
+ var Button = react.memo(function Button2(props) {
61
+ const { colors: colors2, radius: radius2, space: space2, size: size3, font: font2 } = useTheme();
62
+ const variant = props.variant ?? "secondary";
63
+ const inactive = props.disabled === true || props.busy === true;
64
+ const filled = variant === "primary" || props.selected === true;
65
+ const background = filled ? colors2.primary : variant === "secondary" ? colors2.surface : "transparent";
66
+ const foreground = filled ? colors2.onPrimary : colors2.primary;
67
+ const checkable = props.role === "radio" || props.role === "checkbox";
68
+ return /* @__PURE__ */ jsxRuntime.jsxs(
69
+ reactNative.Pressable,
70
+ {
71
+ accessibilityRole: props.role ?? "button",
72
+ accessibilityLabel: props.label,
73
+ ...props.hint ? { accessibilityHint: props.hint } : {},
74
+ accessibilityState: {
75
+ disabled: inactive,
76
+ busy: props.busy === true,
77
+ ...checkable ? props.role === "radio" ? { selected: props.selected === true } : { checked: props.selected === true } : {}
78
+ },
79
+ disabled: inactive,
80
+ onPress: props.onPress,
81
+ ...props.testID ? { testID: props.testID } : {},
82
+ style: ({ pressed }) => ({
83
+ minHeight: size3.touchTarget,
84
+ minWidth: size3.touchTarget,
85
+ paddingHorizontal: space2.md,
86
+ paddingVertical: space2.sm,
87
+ borderRadius: radius2.md,
88
+ borderWidth: variant === "text" ? 0 : 1,
89
+ borderColor: filled ? colors2.primary : colors2.border,
90
+ backgroundColor: background,
91
+ opacity: inactive ? 0.55 : pressed ? 0.8 : 1,
92
+ alignItems: "center",
93
+ justifyContent: "center",
94
+ flexDirection: "row",
95
+ gap: space2.xs
96
+ }),
97
+ children: [
98
+ props.busy ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.ActivityIndicator, { color: foreground }) : null,
99
+ props.children ?? /* @__PURE__ */ jsxRuntime.jsx(
100
+ reactNative.Text,
101
+ {
102
+ style: {
103
+ color: foreground,
104
+ fontSize: font2.size.md,
105
+ fontWeight: "500",
106
+ ...font2.family ? { fontFamily: font2.family } : {}
107
+ },
108
+ children: props.label
109
+ }
110
+ )
111
+ ]
112
+ }
113
+ );
114
+ });
60
115
  function cardOf(held) {
61
116
  return held.card;
62
117
  }
@@ -125,64 +180,20 @@ function useLink(link, held) {
125
180
  if (!link || !store.checkLink(link)) return null;
126
181
  return () => handlers.current?.onOpenLink?.(link, held);
127
182
  }
183
+ function ratingText(valuePpm, locale) {
184
+ const value = valuePpm / 1e6;
185
+ try {
186
+ return new Intl.NumberFormat(locale, {
187
+ minimumFractionDigits: 1,
188
+ maximumFractionDigits: 1
189
+ }).format(value);
190
+ } catch {
191
+ return value.toFixed(1);
192
+ }
193
+ }
128
194
  function safeImage(url) {
129
195
  return url && /^https:\/\//i.test(url) ? url : null;
130
196
  }
131
- var Button = react.memo(function Button2(props) {
132
- const { colors: colors2, radius: radius2, space: space2, size: size3, font: font2 } = useTheme();
133
- const variant = props.variant ?? "secondary";
134
- const inactive = props.disabled === true || props.busy === true;
135
- const filled = variant === "primary" || props.selected === true;
136
- const background = filled ? colors2.primary : variant === "secondary" ? colors2.surface : "transparent";
137
- const foreground = filled ? colors2.onPrimary : colors2.primary;
138
- const checkable = props.role === "radio" || props.role === "checkbox";
139
- return /* @__PURE__ */ jsxRuntime.jsxs(
140
- reactNative.Pressable,
141
- {
142
- accessibilityRole: props.role ?? "button",
143
- accessibilityLabel: props.label,
144
- ...props.hint ? { accessibilityHint: props.hint } : {},
145
- accessibilityState: {
146
- disabled: inactive,
147
- busy: props.busy === true,
148
- ...checkable ? props.role === "radio" ? { selected: props.selected === true } : { checked: props.selected === true } : {}
149
- },
150
- disabled: inactive,
151
- onPress: props.onPress,
152
- ...props.testID ? { testID: props.testID } : {},
153
- style: ({ pressed }) => ({
154
- minHeight: size3.touchTarget,
155
- minWidth: size3.touchTarget,
156
- paddingHorizontal: space2.md,
157
- paddingVertical: space2.sm,
158
- borderRadius: radius2.md,
159
- borderWidth: variant === "text" ? 0 : 1,
160
- borderColor: filled ? colors2.primary : colors2.border,
161
- backgroundColor: background,
162
- opacity: inactive ? 0.55 : pressed ? 0.8 : 1,
163
- alignItems: "center",
164
- justifyContent: "center",
165
- flexDirection: "row",
166
- gap: space2.xs
167
- }),
168
- children: [
169
- props.busy ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.ActivityIndicator, { color: foreground }) : null,
170
- props.children ?? /* @__PURE__ */ jsxRuntime.jsx(
171
- reactNative.Text,
172
- {
173
- style: {
174
- color: foreground,
175
- fontSize: font2.size.md,
176
- fontWeight: "500",
177
- ...font2.family ? { fontFamily: font2.family } : {}
178
- },
179
- children: props.label
180
- }
181
- )
182
- ]
183
- }
184
- );
185
- });
186
197
  function InfoCard(props) {
187
198
  const card = cardOf(props.card);
188
199
  const { locale, t } = react$1.useCopy();
@@ -191,7 +202,7 @@ function InfoCard(props) {
191
202
  const image = safeImage(card.image_url);
192
203
  const Img = slots.Image ?? reactNative.Image;
193
204
  const money = card.price ?? card.price_from;
194
- const rating = card.rating ? (card.rating.value_ppm / 1e6).toFixed(1) : null;
205
+ const rating = card.rating ? ratingText(card.rating.value_ppm, locale) : null;
195
206
  const amount = money ? react$1.formatMoney(money.amount, money.currency, locale) : null;
196
207
  const price = amount && !card.price ? t("priceFrom", { price: amount }) : amount;
197
208
  const when = card.starts_at ? react$1.formatTimeRange(card.starts_at, void 0, locale, timeZone) : null;
@@ -234,27 +245,32 @@ function InfoCard(props) {
234
245
  ] })
235
246
  ] }),
236
247
  card.body ? /* @__PURE__ */ jsxRuntime.jsx(Body, { children: card.body }) : null,
237
- card.badges?.length ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: { flexDirection: "row", flexWrap: "wrap", gap: space2.xs }, children: card.badges.map((b) => /* @__PURE__ */ jsxRuntime.jsx(
238
- reactNative.Text,
239
- {
240
- style: {
241
- color: colors2.text,
242
- backgroundColor: colors2.surface,
243
- borderRadius: radius2.pill,
244
- paddingHorizontal: space2.sm,
245
- paddingVertical: space2.xxs,
246
- fontSize: font2.size.xs
247
- },
248
- children: b
249
- },
250
- b
251
- )) }) : null,
252
- props.restored ? null : /* @__PURE__ */ jsxRuntime.jsx(Actions, { ...props })
248
+ /* @__PURE__ */ jsxRuntime.jsx(Badges, { badges: card.badges }),
249
+ props.restored ? null : /* @__PURE__ */ jsxRuntime.jsx(CardActions, { ...props })
253
250
  ]
254
251
  }
255
252
  );
256
253
  }
257
- function Actions(props) {
254
+ function Badges(props) {
255
+ const { colors: colors2, radius: radius2, space: space2, font: font2 } = useTheme();
256
+ if (!props.badges?.length) return null;
257
+ return /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: { flexDirection: "row", flexWrap: "wrap", gap: space2.xs }, children: props.badges.map((b) => /* @__PURE__ */ jsxRuntime.jsx(
258
+ reactNative.Text,
259
+ {
260
+ style: {
261
+ color: colors2.text,
262
+ backgroundColor: colors2.surface,
263
+ borderRadius: radius2.pill,
264
+ paddingHorizontal: space2.sm,
265
+ paddingVertical: space2.xxs,
266
+ fontSize: font2.size.xs
267
+ },
268
+ children: b
269
+ },
270
+ b
271
+ )) });
272
+ }
273
+ function CardActions(props) {
258
274
  const card = cardOf(props.card);
259
275
  const { space: space2 } = useTheme();
260
276
  if (!card.actions?.length) return null;
@@ -286,6 +302,120 @@ function Action(props) {
286
302
  }
287
303
  );
288
304
  }
305
+ function LinksCard(props) {
306
+ const card = cardOf(props.card);
307
+ const { store, handlers } = useChat();
308
+ const links = (card.links ?? []).filter(
309
+ (l) => /^https:\/\//i.test(l.url) || store.checkLink(l.url)
310
+ );
311
+ return /* @__PURE__ */ jsxRuntime.jsxs(
312
+ CardFrame,
313
+ {
314
+ label: [card.title, ...links.map((l) => l.label)].join(", "),
315
+ restored: props.restored,
316
+ testID: `vinaverse-card-${card.id}`,
317
+ children: [
318
+ /* @__PURE__ */ jsxRuntime.jsx(Title, { children: card.title }),
319
+ links.map((l) => /* @__PURE__ */ jsxRuntime.jsx(
320
+ Button,
321
+ {
322
+ label: l.label,
323
+ role: "link",
324
+ variant: "secondary",
325
+ disabled: props.restored,
326
+ onPress: () => {
327
+ const open = handlers.current?.onOpenLink;
328
+ if (open) open(l.url, props.card);
329
+ else void reactNative.Linking.openURL(l.url);
330
+ }
331
+ },
332
+ l.url
333
+ ))
334
+ ]
335
+ }
336
+ );
337
+ }
338
+ function ListingCard(props) {
339
+ const card = cardOf(props.card);
340
+ const { locale, t } = react$1.useCopy();
341
+ const { slots, timeZone } = useChat();
342
+ const { colors: colors2, radius: radius2, space: space2, font: font2 } = useTheme();
343
+ const [failed, setFailed] = react.useState(null);
344
+ const photoUrl = safeImage(card.images?.[0]?.url ?? card.image_url);
345
+ const hero = photoUrl === failed ? null : photoUrl;
346
+ const Img = slots.Image ?? reactNative.Image;
347
+ const rating = card.rating ? `\u2605 ${ratingText(card.rating.value_ppm, locale)} (${card.rating.count})` : null;
348
+ const price = card.price_from ? t("priceFrom", {
349
+ price: react$1.formatMoney(
350
+ card.price_from.amount,
351
+ card.price_from.currency,
352
+ locale
353
+ )
354
+ }) : null;
355
+ const next = card.next_available ? t("nextAvailable", {
356
+ time: react$1.formatTimeRange(card.next_available, void 0, locale, timeZone)
357
+ }) : null;
358
+ const photo = {
359
+ width: "100%",
360
+ aspectRatio: 1.6,
361
+ borderRadius: radius2.md
362
+ };
363
+ return /* @__PURE__ */ jsxRuntime.jsxs(
364
+ CardFrame,
365
+ {
366
+ label: [card.title, card.subtitle, rating, price, next].filter(Boolean).join(", "),
367
+ restored: props.restored,
368
+ testID: `vinaverse-card-${card.id}`,
369
+ children: [
370
+ hero ? /* @__PURE__ */ jsxRuntime.jsx(
371
+ Img,
372
+ {
373
+ source: { uri: hero },
374
+ onError: () => setFailed(hero),
375
+ style: { ...photo, backgroundColor: colors2.surface },
376
+ accessible: false,
377
+ testID: `vinaverse-photo-${card.id}`
378
+ }
379
+ ) : /* @__PURE__ */ jsxRuntime.jsx(
380
+ reactNative.View,
381
+ {
382
+ accessible: false,
383
+ importantForAccessibility: "no-hide-descendants",
384
+ testID: `vinaverse-monogram-${card.id}`,
385
+ style: {
386
+ ...photo,
387
+ backgroundColor: colors2.surface,
388
+ alignItems: "center",
389
+ justifyContent: "center"
390
+ },
391
+ children: /* @__PURE__ */ jsxRuntime.jsx(
392
+ reactNative.Text,
393
+ {
394
+ style: {
395
+ color: colors2.textMuted,
396
+ fontSize: font2.size.xl,
397
+ fontWeight: "600"
398
+ },
399
+ children: monogram(card.title)
400
+ }
401
+ )
402
+ }
403
+ ),
404
+ /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: { gap: space2.xxs }, children: [
405
+ /* @__PURE__ */ jsxRuntime.jsx(Title, { numberOfLines: 2, children: card.title }),
406
+ card.subtitle ? /* @__PURE__ */ jsxRuntime.jsx(Body, { muted: true, children: card.subtitle }) : null,
407
+ rating || price ? /* @__PURE__ */ jsxRuntime.jsx(Body, { muted: true, children: [rating, price].filter(Boolean).join(" \xB7 ") }) : null
408
+ ] }),
409
+ card.body ? /* @__PURE__ */ jsxRuntime.jsx(Body, { children: card.body }) : null,
410
+ /* @__PURE__ */ jsxRuntime.jsx(Badges, { badges: next ? [next, ...card.badges ?? []] : card.badges }),
411
+ props.restored ? null : /* @__PURE__ */ jsxRuntime.jsx(CardActions, { ...props })
412
+ ]
413
+ }
414
+ );
415
+ }
416
+ function monogram(title) {
417
+ return title.split(/\s+/).filter(Boolean).slice(0, 2).map((w) => [...w][0]?.toUpperCase() ?? "").join("");
418
+ }
289
419
  function NextStepCard(props) {
290
420
  const card = cardOf(props.card);
291
421
  const action = card.actions?.[0];
@@ -1154,7 +1284,10 @@ var DEFAULTS = {
1154
1284
  proposal: ProposalCard,
1155
1285
  question: QuestionCard,
1156
1286
  next_step: NextStepCard,
1157
- time_slots: TimeSlotsCard
1287
+ time_slots: TimeSlotsCard,
1288
+ professional: ListingCard,
1289
+ workspace: ListingCard,
1290
+ links: LinksCard
1158
1291
  };
1159
1292
  function defaultCard(kind) {
1160
1293
  return DEFAULTS[kind] ?? InfoCard;
@@ -1167,6 +1300,35 @@ var CardView = react.memo(function CardView2(props) {
1167
1300
  const own = { card, busy, restored: props.item.restored === true };
1168
1301
  return Custom ? /* @__PURE__ */ jsxRuntime.jsx(Custom, { ...own, Default }) : /* @__PURE__ */ jsxRuntime.jsx(Default, { ...own });
1169
1302
  });
1303
+ var keyOf = (item) => item.key;
1304
+ var kindOf = (item) => item.card.kind;
1305
+ function CardCarousel(props) {
1306
+ const { width } = reactNative.useWindowDimensions();
1307
+ const { t } = react$1.useCopy();
1308
+ const size3 = Math.min(Math.round(width * 0.8), 360);
1309
+ return /* @__PURE__ */ jsxRuntime.jsx(
1310
+ reactNative.View,
1311
+ {
1312
+ accessibilityRole: "list",
1313
+ accessibilityLabel: t("cardsInRow", {
1314
+ count: String(props.items.length)
1315
+ }),
1316
+ children: /* @__PURE__ */ jsxRuntime.jsx(
1317
+ flashList.FlashList,
1318
+ {
1319
+ horizontal: true,
1320
+ data: props.items,
1321
+ keyExtractor: keyOf,
1322
+ getItemType: kindOf,
1323
+ renderItem: ({ item }) => /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: { width: size3 }, children: /* @__PURE__ */ jsxRuntime.jsx(CardView, { item }) }),
1324
+ snapToInterval: size3,
1325
+ decelerationRate: "fast",
1326
+ showsHorizontalScrollIndicator: false
1327
+ }
1328
+ )
1329
+ }
1330
+ );
1331
+ }
1170
1332
  var ActivityLine = react.memo(function ActivityLine2() {
1171
1333
  const { store } = useChat();
1172
1334
  const { t } = react$1.useCopy();
@@ -1473,6 +1635,23 @@ var DisclosureBar = react.memo(function DisclosureBar2() {
1473
1635
  }
1474
1636
  );
1475
1637
  });
1638
+ function FailureActionButton(props) {
1639
+ const { handlers, failureAction } = useChat();
1640
+ const { t } = react$1.useCopy();
1641
+ if (!failureAction) return null;
1642
+ return /* @__PURE__ */ jsxRuntime.jsx(
1643
+ Button,
1644
+ {
1645
+ label: failureAction.label ?? t("talkToPerson"),
1646
+ variant: "text",
1647
+ onPress: () => handlers.current.failureAction?.onPress(props.state)
1648
+ }
1649
+ );
1650
+ }
1651
+ function FailureSlot(props) {
1652
+ const { slot: Slot, state, Default } = props;
1653
+ return Slot ? /* @__PURE__ */ jsxRuntime.jsx(Slot, { state, Default }) : /* @__PURE__ */ jsxRuntime.jsx(Default, { state });
1654
+ }
1476
1655
  function DefaultBubble(props) {
1477
1656
  const { colors: colors2, radius: radius2, space: space2, font: font2 } = useTheme();
1478
1657
  const role = props.item.role;
@@ -1628,9 +1807,29 @@ function announce(text) {
1628
1807
  }
1629
1808
  var SystemRow = react.memo(function SystemRow2(props) {
1630
1809
  const { t } = react$1.useCopy();
1631
- const { store } = useChat();
1632
- const { colors: colors2, space: space2, font: font2 } = useTheme();
1810
+ const { store, slots } = useChat();
1811
+ const conversationId = react$1.useConversationSelector(
1812
+ store,
1813
+ (v) => v.conversationId
1814
+ );
1633
1815
  const { event, data } = props.item;
1816
+ if (event === "turn_failed") {
1817
+ const key = props.item.key;
1818
+ const state = {
1819
+ kind: "turn_failed",
1820
+ reason: typeof data.code === "string" ? data.code : null,
1821
+ retry: data.retry === true ? () => void store.retryTurn(key) : void 0,
1822
+ conversationId
1823
+ };
1824
+ return /* @__PURE__ */ jsxRuntime.jsx(
1825
+ FailureSlot,
1826
+ {
1827
+ slot: slots.Error,
1828
+ state,
1829
+ Default: DefaultTurnFailed
1830
+ }
1831
+ );
1832
+ }
1634
1833
  const agent = store.getSnapshot().agent.name ?? t("agentFallbackName");
1635
1834
  const text = (() => {
1636
1835
  switch (event) {
@@ -1644,18 +1843,30 @@ var SystemRow = react.memo(function SystemRow2(props) {
1644
1843
  return data.expected_response ? t("handoffCreated", { expected: data.expected_response }) : t("handoffCreatedNoTime");
1645
1844
  case "flow_completed":
1646
1845
  return data.result === "ok" ? t("flowSent") : data.result === "failed" ? t("flowNotSent") : null;
1647
- case "turn_failed":
1648
- return t("turnFailed");
1649
1846
  default:
1650
1847
  return null;
1651
1848
  }
1652
1849
  })();
1653
1850
  if (!text) return null;
1851
+ return /* @__PURE__ */ jsxRuntime.jsx(Line, { text });
1852
+ });
1853
+ function DefaultTurnFailed(props) {
1854
+ const { t } = react$1.useCopy();
1855
+ const { failureAction } = useChat();
1856
+ const text = t("turnFailed");
1857
+ if (!failureAction) return /* @__PURE__ */ jsxRuntime.jsx(Line, { text, danger: true });
1858
+ return /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: { alignItems: "center" }, children: [
1859
+ /* @__PURE__ */ jsxRuntime.jsx(Line, { text, danger: true }),
1860
+ /* @__PURE__ */ jsxRuntime.jsx(FailureActionButton, { state: props.state })
1861
+ ] });
1862
+ }
1863
+ function Line(props) {
1864
+ const { colors: colors2, space: space2, font: font2 } = useTheme();
1654
1865
  return /* @__PURE__ */ jsxRuntime.jsx(
1655
1866
  reactNative.View,
1656
1867
  {
1657
1868
  accessible: true,
1658
- accessibilityLabel: text,
1869
+ accessibilityLabel: props.text,
1659
1870
  style: {
1660
1871
  alignItems: "center",
1661
1872
  paddingHorizontal: space2.xl,
@@ -1665,17 +1876,54 @@ var SystemRow = react.memo(function SystemRow2(props) {
1665
1876
  reactNative.Text,
1666
1877
  {
1667
1878
  style: {
1668
- color: event === "turn_failed" ? colors2.danger : colors2.textMuted,
1879
+ color: props.danger ? colors2.danger : colors2.textMuted,
1669
1880
  fontSize: font2.size.sm,
1670
1881
  textAlign: "center"
1671
1882
  },
1672
- children: text
1883
+ children: props.text
1673
1884
  }
1674
1885
  )
1675
1886
  }
1676
1887
  );
1888
+ }
1889
+ var Welcome = react.memo(function Welcome2(props) {
1890
+ const { colors: colors2, radius: radius2, space: space2, font: font2 } = useTheme();
1891
+ return /* @__PURE__ */ jsxRuntime.jsxs(
1892
+ reactNative.View,
1893
+ {
1894
+ style: { padding: space2.lg, gap: space2.md },
1895
+ testID: "vinaverse-welcome",
1896
+ children: [
1897
+ /* @__PURE__ */ jsxRuntime.jsx(
1898
+ reactNative.Text,
1899
+ {
1900
+ style: {
1901
+ alignSelf: "flex-start",
1902
+ maxWidth: "85%",
1903
+ color: colors2.agentText,
1904
+ backgroundColor: colors2.agentBubble,
1905
+ borderRadius: radius2.lg,
1906
+ paddingHorizontal: space2.md,
1907
+ paddingVertical: space2.sm,
1908
+ fontSize: font2.size.md,
1909
+ lineHeight: font2.size.md * font2.lineHeight
1910
+ },
1911
+ children: props.welcome.text
1912
+ }
1913
+ ),
1914
+ 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(
1915
+ Button,
1916
+ {
1917
+ label: chip.label,
1918
+ onPress: () => props.onChip(chip.message)
1919
+ },
1920
+ chip.label
1921
+ )) }) : null
1922
+ ]
1923
+ }
1924
+ );
1677
1925
  });
1678
- var keyOf = (item) => item.key;
1926
+ var keyOf2 = (item) => item.key;
1679
1927
  function itemType(item) {
1680
1928
  if (item.type === "card") return `card:${item.card.kind}`;
1681
1929
  if (item.type === "message")
@@ -1685,8 +1933,39 @@ function itemType(item) {
1685
1933
  function authorOf(item) {
1686
1934
  return item?.type === "message" ? item.role : null;
1687
1935
  }
1936
+ function carousels(items, previous) {
1937
+ const runs = /* @__PURE__ */ new Map();
1938
+ const inside = /* @__PURE__ */ new Set();
1939
+ const groupOf = (item) => {
1940
+ if (item?.type !== "card") return null;
1941
+ const group = item.card.card.group;
1942
+ return group?.layout === "carousel" && typeof group.id === "string" ? group.id : null;
1943
+ };
1944
+ for (let i = 0; i < items.length; i++) {
1945
+ const id = groupOf(items[i]);
1946
+ if (id === null) continue;
1947
+ let end = i + 1;
1948
+ while (end < items.length && groupOf(items[end]) === id) end++;
1949
+ if (end - i > 1) {
1950
+ const run = items.slice(i, end);
1951
+ const key = run[0]?.key;
1952
+ const before = previous.get(key);
1953
+ runs.set(
1954
+ key,
1955
+ before && before.length === run.length && before.every((c, n) => c === run[n]) ? before : run
1956
+ );
1957
+ for (const card of run.slice(1)) inside.add(card.key);
1958
+ }
1959
+ i = end - 1;
1960
+ }
1961
+ return { runs, inside };
1962
+ }
1688
1963
  var Row = react.memo(function Row2(props) {
1689
1964
  const { item } = props;
1965
+ const { slots } = useChat();
1966
+ const Group = slots.CardGroup;
1967
+ if (props.inside) return null;
1968
+ if (props.run && Group) return /* @__PURE__ */ jsxRuntime.jsx(Group, { items: props.run });
1690
1969
  if (item.type === "card") return /* @__PURE__ */ jsxRuntime.jsx(CardView, { item });
1691
1970
  if (item.type === "system") return /* @__PURE__ */ jsxRuntime.jsx(SystemRow, { item });
1692
1971
  return item.state === "streaming" ? /* @__PURE__ */ jsxRuntime.jsx(StreamingBubble, { item, showAuthor: props.showAuthor }) : /* @__PURE__ */ jsxRuntime.jsx(MessageBubble, { item, showAuthor: props.showAuthor });
@@ -1702,26 +1981,49 @@ var MessageList = react.memo(function MessageList2() {
1702
1981
  (v) => v.status === "starting" || v.status === "connecting"
1703
1982
  );
1704
1983
  const agentName = react$1.useConversationSelector(store, (v) => v.agent.name);
1984
+ const welcome = react$1.useConversationSelector(store, (v) => v.welcome);
1705
1985
  useAnnounceReplies(items);
1986
+ const Group = slots.CardGroup;
1987
+ const runsRef = react.useRef(/* @__PURE__ */ new Map());
1988
+ const grouping = react.useMemo(
1989
+ () => Group ? carousels(items, runsRef.current) : null,
1990
+ [items, Group]
1991
+ );
1992
+ runsRef.current = grouping?.runs ?? /* @__PURE__ */ new Map();
1706
1993
  const itemsRef = react.useRef(items);
1707
1994
  itemsRef.current = items;
1995
+ const groupingRef = react.useRef(grouping);
1996
+ groupingRef.current = grouping;
1708
1997
  const renderItem = react.useCallback(
1709
1998
  ({ item, index }) => /* @__PURE__ */ jsxRuntime.jsx(
1710
1999
  Row,
1711
2000
  {
1712
2001
  item,
1713
- showAuthor: authorOf(item) !== authorOf(itemsRef.current[index - 1])
2002
+ showAuthor: authorOf(item) !== authorOf(itemsRef.current[index - 1]),
2003
+ run: groupingRef.current?.runs.get(item.key),
2004
+ inside: groupingRef.current?.inside.has(item.key) === true
1714
2005
  }
1715
2006
  ),
1716
2007
  []
1717
2008
  );
2009
+ const onChip = react.useCallback(
2010
+ (message) => void store.send(message),
2011
+ [store]
2012
+ );
1718
2013
  const Empty = slots.EmptyState;
1719
- const empty = opening ? /* @__PURE__ */ jsxRuntime.jsx(ChatSkeleton, {}) : Empty ? /* @__PURE__ */ jsxRuntime.jsx(Empty, { agentName: agentName ?? t("agentFallbackName") }) : null;
2014
+ const empty = opening ? /* @__PURE__ */ jsxRuntime.jsx(ChatSkeleton, {}) : Empty ? /* @__PURE__ */ jsxRuntime.jsx(
2015
+ Empty,
2016
+ {
2017
+ agentName: agentName ?? t("agentFallbackName"),
2018
+ welcome,
2019
+ onChip
2020
+ }
2021
+ ) : welcome ? /* @__PURE__ */ jsxRuntime.jsx(Welcome, { welcome, onChip }) : null;
1720
2022
  const props = react.useMemo(
1721
2023
  () => ({
1722
2024
  data: items,
1723
2025
  renderItem,
1724
- keyExtractor: keyOf,
2026
+ keyExtractor: keyOf2,
1725
2027
  getItemType: itemType,
1726
2028
  maintainVisibleContentPosition: {
1727
2029
  startRenderingFromBottom: true,
@@ -1759,10 +2061,23 @@ function useAnnounceReplies(items) {
1759
2061
  }
1760
2062
  var CONNECTING_DELAY = tokens.motion.bannerDelay;
1761
2063
  var StatusBanner = react.memo(function StatusBanner2() {
1762
- const { store } = useChat();
2064
+ const { store, slots } = useChat();
1763
2065
  const { t } = react$1.useCopy();
1764
- const { colors: colors2, space: space2, font: font2 } = useTheme();
1765
2066
  const banner = react$1.useConversationSelector(store, (v) => v.banner);
2067
+ const status = react$1.useConversationSelector(store, (v) => v.status);
2068
+ const conversationId = react$1.useConversationSelector(
2069
+ store,
2070
+ (v) => v.conversationId
2071
+ );
2072
+ const unavailable = react.useMemo(
2073
+ () => ({
2074
+ kind: "unavailable",
2075
+ reason: status,
2076
+ retry: () => store.start(),
2077
+ conversationId
2078
+ }),
2079
+ [store, status, conversationId]
2080
+ );
1766
2081
  const [late, setLate] = react.useState(false);
1767
2082
  react.useEffect(() => {
1768
2083
  setLate(false);
@@ -1771,12 +2086,20 @@ var StatusBanner = react.memo(function StatusBanner2() {
1771
2086
  return () => clearTimeout(timer);
1772
2087
  }, [banner]);
1773
2088
  if (banner === null || banner === "connecting" && !late) return null;
2089
+ if (banner === "unavailable")
2090
+ return /* @__PURE__ */ jsxRuntime.jsx(
2091
+ FailureSlot,
2092
+ {
2093
+ slot: slots.Unavailable,
2094
+ state: unavailable,
2095
+ Default: DefaultUnavailable
2096
+ }
2097
+ );
1774
2098
  const text = {
1775
2099
  connecting: t("bannerConnecting"),
1776
2100
  reconnecting: t("bannerReconnecting"),
1777
2101
  offline: t("bannerOffline"),
1778
- continued_elsewhere: t("bannerContinuedElsewhere"),
1779
- unavailable: t("bannerUnavailable")
2102
+ continued_elsewhere: t("bannerContinuedElsewhere")
1780
2103
  }[banner];
1781
2104
  const action = banner === "continued_elsewhere" ? /* @__PURE__ */ jsxRuntime.jsx(
1782
2105
  Button,
@@ -1785,7 +2108,7 @@ var StatusBanner = react.memo(function StatusBanner2() {
1785
2108
  variant: "text",
1786
2109
  onPress: () => void store.continueHere()
1787
2110
  }
1788
- ) : banner === "offline" || banner === "unavailable" ? /* @__PURE__ */ jsxRuntime.jsx(
2111
+ ) : banner === "offline" ? /* @__PURE__ */ jsxRuntime.jsx(
1789
2112
  Button,
1790
2113
  {
1791
2114
  label: t("tryAgain"),
@@ -1793,6 +2116,18 @@ var StatusBanner = react.memo(function StatusBanner2() {
1793
2116
  onPress: () => store.start()
1794
2117
  }
1795
2118
  ) : null;
2119
+ return /* @__PURE__ */ jsxRuntime.jsx(Banner, { text, children: action });
2120
+ });
2121
+ function DefaultUnavailable(props) {
2122
+ const { t } = react$1.useCopy();
2123
+ const { retry } = props.state;
2124
+ return /* @__PURE__ */ jsxRuntime.jsxs(Banner, { text: t("bannerUnavailable"), children: [
2125
+ retry ? /* @__PURE__ */ jsxRuntime.jsx(Button, { label: t("tryAgain"), variant: "text", onPress: retry }) : null,
2126
+ /* @__PURE__ */ jsxRuntime.jsx(FailureActionButton, { state: props.state })
2127
+ ] });
2128
+ }
2129
+ function Banner(props) {
2130
+ const { colors: colors2, space: space2, font: font2 } = useTheme();
1796
2131
  return /* @__PURE__ */ jsxRuntime.jsxs(
1797
2132
  reactNative.View,
1798
2133
  {
@@ -1814,22 +2149,35 @@ var StatusBanner = react.memo(function StatusBanner2() {
1814
2149
  reactNative.Text,
1815
2150
  {
1816
2151
  style: { color: colors2.text, fontSize: font2.size.sm, flexShrink: 1 },
1817
- children: text
2152
+ children: props.text
1818
2153
  }
1819
2154
  ),
1820
- action
2155
+ props.children
1821
2156
  ]
1822
2157
  }
1823
2158
  );
1824
- });
2159
+ }
1825
2160
  function VinaverseChatProvider(props) {
1826
2161
  const { conversation, components, agentAvatar, timeZone } = props;
1827
2162
  const handlers = react.useRef(props);
1828
2163
  handlers.current = props;
1829
2164
  const slots = useStableSlots(components);
2165
+ const hasAction = props.failureAction !== void 0;
2166
+ const actionLabel = props.failureAction?.label;
2167
+ const failureAction = react.useMemo(
2168
+ () => hasAction ? { label: actionLabel } : null,
2169
+ [hasAction, actionLabel]
2170
+ );
1830
2171
  const value = react.useMemo(
1831
- () => ({ store: conversation, handlers, slots, agentAvatar, timeZone }),
1832
- [conversation, slots, agentAvatar, timeZone]
2172
+ () => ({
2173
+ store: conversation,
2174
+ handlers,
2175
+ slots,
2176
+ agentAvatar,
2177
+ timeZone,
2178
+ failureAction
2179
+ }),
2180
+ [conversation, slots, agentAvatar, timeZone, failureAction]
1833
2181
  );
1834
2182
  useNativeLifecycle(conversation);
1835
2183
  react.useEffect(() => {
@@ -1908,6 +2256,8 @@ function sameSlots(a, b) {
1908
2256
  exports.ActivityLine = ActivityLine;
1909
2257
  exports.Avatar = Avatar;
1910
2258
  exports.Button = Button;
2259
+ exports.CardActions = CardActions;
2260
+ exports.CardCarousel = CardCarousel;
1911
2261
  exports.CardFrame = CardFrame;
1912
2262
  exports.CardView = CardView;
1913
2263
  exports.ChatHeader = ChatHeader;
@@ -1916,8 +2266,13 @@ exports.Composer = Composer;
1916
2266
  exports.DefaultBubble = DefaultBubble;
1917
2267
  exports.DefaultComposer = DefaultComposer;
1918
2268
  exports.DefaultHeader = DefaultHeader;
2269
+ exports.DefaultTurnFailed = DefaultTurnFailed;
2270
+ exports.DefaultUnavailable = DefaultUnavailable;
1919
2271
  exports.DisclosureBar = DisclosureBar;
2272
+ exports.FailureActionButton = FailureActionButton;
1920
2273
  exports.InfoCard = InfoCard;
2274
+ exports.LinksCard = LinksCard;
2275
+ exports.ListingCard = ListingCard;
1921
2276
  exports.MessageBubble = MessageBubble;
1922
2277
  exports.MessageList = MessageList;
1923
2278
  exports.NextStepCard = NextStepCard;
@@ -1931,12 +2286,16 @@ exports.ThemeProvider = ThemeProvider;
1931
2286
  exports.TimeSlotsCard = TimeSlotsCard;
1932
2287
  exports.VinaverseChat = VinaverseChat;
1933
2288
  exports.VinaverseChatProvider = VinaverseChatProvider;
2289
+ exports.Welcome = Welcome;
1934
2290
  exports.announce = announce;
1935
2291
  exports.answerText = answerText;
2292
+ exports.carousels = carousels;
1936
2293
  exports.createTheme = createTheme;
1937
2294
  exports.defaultCard = defaultCard;
1938
2295
  exports.fieldText = fieldText;
1939
2296
  exports.itemType = itemType;
2297
+ exports.monogram = monogram;
2298
+ exports.ratingText = ratingText;
1940
2299
  exports.themeProblems = themeProblems;
1941
2300
  exports.useChat = useChat;
1942
2301
  exports.useNativeLifecycle = useNativeLifecycle;