@vinaverse/react-native 0.1.0-alpha.2 → 0.1.0-alpha.3
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/README.md +25 -0
- package/dist/index.cjs +116 -18
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +51 -1
- package/dist/index.d.ts +51 -1
- package/dist/index.js +114 -19
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -76,8 +76,33 @@ kit share one copy of `@vinaverse/client`.
|
|
|
76
76
|
| `onOpenLink(link, card)` | a card's link (it already passed the deep-link rule): route by the page it names |
|
|
77
77
|
| `onCardAction(card, action)` | a card action without a link (also sent to the session) |
|
|
78
78
|
| `onPickPhoto(question, card)` | a photo question: pick, upload to your backend, return `{ upload_ref }` |
|
|
79
|
+
| `failureAction` | `{ label?, onPress(state) }`: a way out on a failed turn and on "couldn't connect" ([When the agent fails](#when-the-agent-fails)) |
|
|
79
80
|
| `onWarning(message)` | development warnings: a hard-to-read theme, copy that says "assistant" |
|
|
80
81
|
|
|
82
|
+
## When the agent fails
|
|
83
|
+
|
|
84
|
+
Two failures need a way out for the person: a turn that failed before any reply ("Something went wrong.") and no
|
|
85
|
+
session to talk to ("Couldn't connect.", with "Try again"). Give one action for both, drawn as a button by the kit's
|
|
86
|
+
own parts, so a person is always the way out:
|
|
87
|
+
|
|
88
|
+
```tsx
|
|
89
|
+
<VinaverseChat
|
|
90
|
+
conversation={conversation}
|
|
91
|
+
failureAction={{ onPress: (state) => openSupport(state.conversationId) }} // "Talk to a person", in the person's language
|
|
92
|
+
/>
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
`label` replaces "Talk to a person" (the copy's `talkToPerson`, which `copy` can also change). Or replace either part:
|
|
96
|
+
`components.Error` (the failed turn) and `components.Unavailable` (the banner) each get the `state` and the kit's own
|
|
97
|
+
part as `Default`. With neither, the kit draws both as before.
|
|
98
|
+
|
|
99
|
+
| `state` | |
|
|
100
|
+
|---|---|
|
|
101
|
+
| `kind` | `turn_failed` or `unavailable` |
|
|
102
|
+
| `reason` | the turn's error code (`internal`, `timeout`, …), or `unavailable` / `unauthorized` |
|
|
103
|
+
| `retry` | try again, when it can be: the person's text once more, for a turn the server allows to retry (once); a new session for `unavailable`. Undefined otherwise |
|
|
104
|
+
| `conversationId` | the conversation, once a session named it: for your support records |
|
|
105
|
+
|
|
81
106
|
## The keyboard
|
|
82
107
|
|
|
83
108
|
`keyboardBehavior` keeps the composer above the keyboard: `padding` on iOS by default, nothing on Android (the window
|
package/dist/index.cjs
CHANGED
|
@@ -1473,6 +1473,23 @@ var DisclosureBar = react.memo(function DisclosureBar2() {
|
|
|
1473
1473
|
}
|
|
1474
1474
|
);
|
|
1475
1475
|
});
|
|
1476
|
+
function FailureActionButton(props) {
|
|
1477
|
+
const { handlers, failureAction } = useChat();
|
|
1478
|
+
const { t } = react$1.useCopy();
|
|
1479
|
+
if (!failureAction) return null;
|
|
1480
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
1481
|
+
Button,
|
|
1482
|
+
{
|
|
1483
|
+
label: failureAction.label ?? t("talkToPerson"),
|
|
1484
|
+
variant: "text",
|
|
1485
|
+
onPress: () => handlers.current.failureAction?.onPress(props.state)
|
|
1486
|
+
}
|
|
1487
|
+
);
|
|
1488
|
+
}
|
|
1489
|
+
function FailureSlot(props) {
|
|
1490
|
+
const { slot: Slot, state, Default } = props;
|
|
1491
|
+
return Slot ? /* @__PURE__ */ jsxRuntime.jsx(Slot, { state, Default }) : /* @__PURE__ */ jsxRuntime.jsx(Default, { state });
|
|
1492
|
+
}
|
|
1476
1493
|
function DefaultBubble(props) {
|
|
1477
1494
|
const { colors: colors2, radius: radius2, space: space2, font: font2 } = useTheme();
|
|
1478
1495
|
const role = props.item.role;
|
|
@@ -1628,9 +1645,29 @@ function announce(text) {
|
|
|
1628
1645
|
}
|
|
1629
1646
|
var SystemRow = react.memo(function SystemRow2(props) {
|
|
1630
1647
|
const { t } = react$1.useCopy();
|
|
1631
|
-
const { store } = useChat();
|
|
1632
|
-
const
|
|
1648
|
+
const { store, slots } = useChat();
|
|
1649
|
+
const conversationId = react$1.useConversationSelector(
|
|
1650
|
+
store,
|
|
1651
|
+
(v) => v.conversationId
|
|
1652
|
+
);
|
|
1633
1653
|
const { event, data } = props.item;
|
|
1654
|
+
if (event === "turn_failed") {
|
|
1655
|
+
const key = props.item.key;
|
|
1656
|
+
const state = {
|
|
1657
|
+
kind: "turn_failed",
|
|
1658
|
+
reason: typeof data.code === "string" ? data.code : null,
|
|
1659
|
+
retry: data.retry === true ? () => void store.retryTurn(key) : void 0,
|
|
1660
|
+
conversationId
|
|
1661
|
+
};
|
|
1662
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
1663
|
+
FailureSlot,
|
|
1664
|
+
{
|
|
1665
|
+
slot: slots.Error,
|
|
1666
|
+
state,
|
|
1667
|
+
Default: DefaultTurnFailed
|
|
1668
|
+
}
|
|
1669
|
+
);
|
|
1670
|
+
}
|
|
1634
1671
|
const agent = store.getSnapshot().agent.name ?? t("agentFallbackName");
|
|
1635
1672
|
const text = (() => {
|
|
1636
1673
|
switch (event) {
|
|
@@ -1644,18 +1681,30 @@ var SystemRow = react.memo(function SystemRow2(props) {
|
|
|
1644
1681
|
return data.expected_response ? t("handoffCreated", { expected: data.expected_response }) : t("handoffCreatedNoTime");
|
|
1645
1682
|
case "flow_completed":
|
|
1646
1683
|
return data.result === "ok" ? t("flowSent") : data.result === "failed" ? t("flowNotSent") : null;
|
|
1647
|
-
case "turn_failed":
|
|
1648
|
-
return t("turnFailed");
|
|
1649
1684
|
default:
|
|
1650
1685
|
return null;
|
|
1651
1686
|
}
|
|
1652
1687
|
})();
|
|
1653
1688
|
if (!text) return null;
|
|
1689
|
+
return /* @__PURE__ */ jsxRuntime.jsx(Line, { text });
|
|
1690
|
+
});
|
|
1691
|
+
function DefaultTurnFailed(props) {
|
|
1692
|
+
const { t } = react$1.useCopy();
|
|
1693
|
+
const { failureAction } = useChat();
|
|
1694
|
+
const text = t("turnFailed");
|
|
1695
|
+
if (!failureAction) return /* @__PURE__ */ jsxRuntime.jsx(Line, { text, danger: true });
|
|
1696
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: { alignItems: "center" }, children: [
|
|
1697
|
+
/* @__PURE__ */ jsxRuntime.jsx(Line, { text, danger: true }),
|
|
1698
|
+
/* @__PURE__ */ jsxRuntime.jsx(FailureActionButton, { state: props.state })
|
|
1699
|
+
] });
|
|
1700
|
+
}
|
|
1701
|
+
function Line(props) {
|
|
1702
|
+
const { colors: colors2, space: space2, font: font2 } = useTheme();
|
|
1654
1703
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
1655
1704
|
reactNative.View,
|
|
1656
1705
|
{
|
|
1657
1706
|
accessible: true,
|
|
1658
|
-
accessibilityLabel: text,
|
|
1707
|
+
accessibilityLabel: props.text,
|
|
1659
1708
|
style: {
|
|
1660
1709
|
alignItems: "center",
|
|
1661
1710
|
paddingHorizontal: space2.xl,
|
|
@@ -1665,16 +1714,16 @@ var SystemRow = react.memo(function SystemRow2(props) {
|
|
|
1665
1714
|
reactNative.Text,
|
|
1666
1715
|
{
|
|
1667
1716
|
style: {
|
|
1668
|
-
color:
|
|
1717
|
+
color: props.danger ? colors2.danger : colors2.textMuted,
|
|
1669
1718
|
fontSize: font2.size.sm,
|
|
1670
1719
|
textAlign: "center"
|
|
1671
1720
|
},
|
|
1672
|
-
children: text
|
|
1721
|
+
children: props.text
|
|
1673
1722
|
}
|
|
1674
1723
|
)
|
|
1675
1724
|
}
|
|
1676
1725
|
);
|
|
1677
|
-
}
|
|
1726
|
+
}
|
|
1678
1727
|
var keyOf = (item) => item.key;
|
|
1679
1728
|
function itemType(item) {
|
|
1680
1729
|
if (item.type === "card") return `card:${item.card.kind}`;
|
|
@@ -1759,10 +1808,23 @@ function useAnnounceReplies(items) {
|
|
|
1759
1808
|
}
|
|
1760
1809
|
var CONNECTING_DELAY = tokens.motion.bannerDelay;
|
|
1761
1810
|
var StatusBanner = react.memo(function StatusBanner2() {
|
|
1762
|
-
const { store } = useChat();
|
|
1811
|
+
const { store, slots } = useChat();
|
|
1763
1812
|
const { t } = react$1.useCopy();
|
|
1764
|
-
const { colors: colors2, space: space2, font: font2 } = useTheme();
|
|
1765
1813
|
const banner = react$1.useConversationSelector(store, (v) => v.banner);
|
|
1814
|
+
const status = react$1.useConversationSelector(store, (v) => v.status);
|
|
1815
|
+
const conversationId = react$1.useConversationSelector(
|
|
1816
|
+
store,
|
|
1817
|
+
(v) => v.conversationId
|
|
1818
|
+
);
|
|
1819
|
+
const unavailable = react.useMemo(
|
|
1820
|
+
() => ({
|
|
1821
|
+
kind: "unavailable",
|
|
1822
|
+
reason: status,
|
|
1823
|
+
retry: () => store.start(),
|
|
1824
|
+
conversationId
|
|
1825
|
+
}),
|
|
1826
|
+
[store, status, conversationId]
|
|
1827
|
+
);
|
|
1766
1828
|
const [late, setLate] = react.useState(false);
|
|
1767
1829
|
react.useEffect(() => {
|
|
1768
1830
|
setLate(false);
|
|
@@ -1771,12 +1833,20 @@ var StatusBanner = react.memo(function StatusBanner2() {
|
|
|
1771
1833
|
return () => clearTimeout(timer);
|
|
1772
1834
|
}, [banner]);
|
|
1773
1835
|
if (banner === null || banner === "connecting" && !late) return null;
|
|
1836
|
+
if (banner === "unavailable")
|
|
1837
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
1838
|
+
FailureSlot,
|
|
1839
|
+
{
|
|
1840
|
+
slot: slots.Unavailable,
|
|
1841
|
+
state: unavailable,
|
|
1842
|
+
Default: DefaultUnavailable
|
|
1843
|
+
}
|
|
1844
|
+
);
|
|
1774
1845
|
const text = {
|
|
1775
1846
|
connecting: t("bannerConnecting"),
|
|
1776
1847
|
reconnecting: t("bannerReconnecting"),
|
|
1777
1848
|
offline: t("bannerOffline"),
|
|
1778
|
-
continued_elsewhere: t("bannerContinuedElsewhere")
|
|
1779
|
-
unavailable: t("bannerUnavailable")
|
|
1849
|
+
continued_elsewhere: t("bannerContinuedElsewhere")
|
|
1780
1850
|
}[banner];
|
|
1781
1851
|
const action = banner === "continued_elsewhere" ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
1782
1852
|
Button,
|
|
@@ -1785,7 +1855,7 @@ var StatusBanner = react.memo(function StatusBanner2() {
|
|
|
1785
1855
|
variant: "text",
|
|
1786
1856
|
onPress: () => void store.continueHere()
|
|
1787
1857
|
}
|
|
1788
|
-
) : banner === "offline"
|
|
1858
|
+
) : banner === "offline" ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
1789
1859
|
Button,
|
|
1790
1860
|
{
|
|
1791
1861
|
label: t("tryAgain"),
|
|
@@ -1793,6 +1863,18 @@ var StatusBanner = react.memo(function StatusBanner2() {
|
|
|
1793
1863
|
onPress: () => store.start()
|
|
1794
1864
|
}
|
|
1795
1865
|
) : null;
|
|
1866
|
+
return /* @__PURE__ */ jsxRuntime.jsx(Banner, { text, children: action });
|
|
1867
|
+
});
|
|
1868
|
+
function DefaultUnavailable(props) {
|
|
1869
|
+
const { t } = react$1.useCopy();
|
|
1870
|
+
const { retry } = props.state;
|
|
1871
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(Banner, { text: t("bannerUnavailable"), children: [
|
|
1872
|
+
retry ? /* @__PURE__ */ jsxRuntime.jsx(Button, { label: t("tryAgain"), variant: "text", onPress: retry }) : null,
|
|
1873
|
+
/* @__PURE__ */ jsxRuntime.jsx(FailureActionButton, { state: props.state })
|
|
1874
|
+
] });
|
|
1875
|
+
}
|
|
1876
|
+
function Banner(props) {
|
|
1877
|
+
const { colors: colors2, space: space2, font: font2 } = useTheme();
|
|
1796
1878
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1797
1879
|
reactNative.View,
|
|
1798
1880
|
{
|
|
@@ -1814,22 +1896,35 @@ var StatusBanner = react.memo(function StatusBanner2() {
|
|
|
1814
1896
|
reactNative.Text,
|
|
1815
1897
|
{
|
|
1816
1898
|
style: { color: colors2.text, fontSize: font2.size.sm, flexShrink: 1 },
|
|
1817
|
-
children: text
|
|
1899
|
+
children: props.text
|
|
1818
1900
|
}
|
|
1819
1901
|
),
|
|
1820
|
-
|
|
1902
|
+
props.children
|
|
1821
1903
|
]
|
|
1822
1904
|
}
|
|
1823
1905
|
);
|
|
1824
|
-
}
|
|
1906
|
+
}
|
|
1825
1907
|
function VinaverseChatProvider(props) {
|
|
1826
1908
|
const { conversation, components, agentAvatar, timeZone } = props;
|
|
1827
1909
|
const handlers = react.useRef(props);
|
|
1828
1910
|
handlers.current = props;
|
|
1829
1911
|
const slots = useStableSlots(components);
|
|
1912
|
+
const hasAction = props.failureAction !== void 0;
|
|
1913
|
+
const actionLabel = props.failureAction?.label;
|
|
1914
|
+
const failureAction = react.useMemo(
|
|
1915
|
+
() => hasAction ? { label: actionLabel } : null,
|
|
1916
|
+
[hasAction, actionLabel]
|
|
1917
|
+
);
|
|
1830
1918
|
const value = react.useMemo(
|
|
1831
|
-
() => ({
|
|
1832
|
-
|
|
1919
|
+
() => ({
|
|
1920
|
+
store: conversation,
|
|
1921
|
+
handlers,
|
|
1922
|
+
slots,
|
|
1923
|
+
agentAvatar,
|
|
1924
|
+
timeZone,
|
|
1925
|
+
failureAction
|
|
1926
|
+
}),
|
|
1927
|
+
[conversation, slots, agentAvatar, timeZone, failureAction]
|
|
1833
1928
|
);
|
|
1834
1929
|
useNativeLifecycle(conversation);
|
|
1835
1930
|
react.useEffect(() => {
|
|
@@ -1916,7 +2011,10 @@ exports.Composer = Composer;
|
|
|
1916
2011
|
exports.DefaultBubble = DefaultBubble;
|
|
1917
2012
|
exports.DefaultComposer = DefaultComposer;
|
|
1918
2013
|
exports.DefaultHeader = DefaultHeader;
|
|
2014
|
+
exports.DefaultTurnFailed = DefaultTurnFailed;
|
|
2015
|
+
exports.DefaultUnavailable = DefaultUnavailable;
|
|
1919
2016
|
exports.DisclosureBar = DisclosureBar;
|
|
2017
|
+
exports.FailureActionButton = FailureActionButton;
|
|
1920
2018
|
exports.InfoCard = InfoCard;
|
|
1921
2019
|
exports.MessageBubble = MessageBubble;
|
|
1922
2020
|
exports.MessageList = MessageList;
|