@vinaverse/react-native 0.1.0-alpha.3 → 0.1.0-alpha.5
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 +98 -2
- package/dist/index.cjs +1152 -166
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +401 -108
- package/dist/index.d.ts +401 -108
- package/dist/index.js +1098 -169
- package/dist/index.js.map +1 -1
- package/package.json +98 -6
package/README.md
CHANGED
|
@@ -2,12 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
A ready, themeable conversation with your Vinaverse agent for React Native and Expo apps. Drop it into a screen,
|
|
4
4
|
give it a conversation whose session comes from your backend, and it draws everything the realtime protocol defines:
|
|
5
|
-
streaming replies, cards (people, services, bookings, questions, proposals, next steps
|
|
5
|
+
streaming replies, cards (photo cards for people and places, services, bookings, questions, proposals, next steps,
|
|
6
|
+
your own help pages), held and unsent messages, a
|
|
6
7
|
person from your business taking over, and every way a session ends. No native module; New Architecture; FlashList 2.3.2 or later.
|
|
7
8
|
|
|
8
9
|
```tsx
|
|
9
10
|
import { createConversation } from '@vinaverse/client/conversation';
|
|
10
|
-
import { VinaverseChat } from '@vinaverse/react-native';
|
|
11
|
+
import { CardCarousel, VinaverseChat } from '@vinaverse/react-native';
|
|
11
12
|
|
|
12
13
|
// Outside the screen: it connects early and survives navigation.
|
|
13
14
|
const conversation = createConversation({
|
|
@@ -15,6 +16,8 @@ const conversation = createConversation({
|
|
|
15
16
|
locale: 'es-US',
|
|
16
17
|
deepLinkPrefixes: ['yourapp://bookings/'],
|
|
17
18
|
});
|
|
19
|
+
// Opt-in: a turn's listing cards in one swipeable row (without it they stack). Module level: a stable object.
|
|
20
|
+
const components = { CardGroup: CardCarousel };
|
|
18
21
|
|
|
19
22
|
export function AgentScreen() {
|
|
20
23
|
return (
|
|
@@ -22,6 +25,7 @@ export function AgentScreen() {
|
|
|
22
25
|
conversation={conversation}
|
|
23
26
|
locale="es-US"
|
|
24
27
|
theme={{ light: { primary: '#7A2E8E', personBubble: '#7A2E8E' } }}
|
|
28
|
+
components={components}
|
|
25
29
|
onProposal={(card, decision) => api.decideProposal(card, decision)} // your backend, the person's token
|
|
26
30
|
onOpenLink={(link) => router.push(link)}
|
|
27
31
|
/>
|
|
@@ -29,6 +33,72 @@ export function AgentScreen() {
|
|
|
29
33
|
}
|
|
30
34
|
```
|
|
31
35
|
|
|
36
|
+
## Hosting the agent (the runtime)
|
|
37
|
+
|
|
38
|
+
An app that hosts the agent writes its backend once (`createAgentRuntime`, `@vinaverse/client/agent`) and draws the
|
|
39
|
+
parts; which conversation, keeping it across screens, waiting the backend out, a conversation that ended, the backend
|
|
40
|
+
out of reach, status, name, ratings and sign-out are the runtime's.
|
|
41
|
+
|
|
42
|
+
```tsx
|
|
43
|
+
import { createAgentRuntime } from '@vinaverse/client/agent';
|
|
44
|
+
import { AgentChat, AgentLauncher, AgentRuntimeProvider, AskAboutRow, CardCarousel } from '@vinaverse/react-native';
|
|
45
|
+
|
|
46
|
+
const agent = createAgentRuntime({ backend: { openSession, refreshSecret, status }, conversation: { locale } });
|
|
47
|
+
const components = { CardGroup: CardCarousel, Image: ExpoImage }; // module level: stable
|
|
48
|
+
const imagePolicy = { hosts: ['images.example.com'], aspectRatio: 1.6, Placeholder: BrandTile }; // stable too
|
|
49
|
+
|
|
50
|
+
<AgentRuntimeProvider runtime={agent}>
|
|
51
|
+
<Tabs />
|
|
52
|
+
<AgentLauncher subject={user?.id ?? null} mode={side} Mark={YourMark}
|
|
53
|
+
route={currentTab} routes={['home', 'explore']} hidden={sheetOpen} // never with the keyboard up
|
|
54
|
+
storage={preferences} onOpen={({ route, mode }) => openChat(route, mode)}
|
|
55
|
+
style={{ position: 'absolute', right: 16, bottom: tabBarHeight + 16 }} />
|
|
56
|
+
</AgentRuntimeProvider>
|
|
57
|
+
|
|
58
|
+
// The chat screen
|
|
59
|
+
<AgentChat
|
|
60
|
+
agentKey={{ subject: user?.id ?? null, mode: side, scope: businessId }}
|
|
61
|
+
place={{ screen: 'professional', entity: { type: 'professional', id } }}
|
|
62
|
+
askAbout={`Tell me about ${name}`} // offered when the session was told another place
|
|
63
|
+
starters={startersFor(screen)} // when the session brings no welcome
|
|
64
|
+
components={components}
|
|
65
|
+
onOpenTarget={(t) => router.push(routeFor(t))} // a card about a professional, a space, a booking
|
|
66
|
+
onOpenLink={openLink}
|
|
67
|
+
onContact={() => router.push('/contact')} // a person, when the agent fails or is unavailable
|
|
68
|
+
onOpenPrivacy={() => router.push('/privacy')}
|
|
69
|
+
prefetchImage={(url) => Image.prefetch(url)}
|
|
70
|
+
imagePolicy={imagePolicy}
|
|
71
|
+
/>
|
|
72
|
+
|
|
73
|
+
// On the app's own page about a professional
|
|
74
|
+
<AskAboutRow title={`Ask ${agentName} about ${name}`} onPress={() => openChat('professional', side)} />
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
- **Parts:** `AgentChat` draws the conversation, a conversation that ended (`components.Ended`, start over), the backend
|
|
78
|
+
out of reach (`components.Unreachable`, try again), no agent at all (`components.UnavailableScreen`, the whole
|
|
79
|
+
screen: off, or the session refused, with a person through `onContact`), and under the composer thumbs on the newest
|
|
80
|
+
whole reply and the privacy link (`components.Footer`, `null` to hide); each slot gets the kit's part as `Default`,
|
|
81
|
+
to wrap it. `opener` is a first question as a chip the person taps (a flow that asks for itself), until they say
|
|
82
|
+
anything; nothing is ever sent in their name. `AgentLauncher` is a round button with
|
|
83
|
+
a greeting bubble once per person and mode (`storage`: your preferences store; `greeting`: words or words by mode,
|
|
84
|
+
read through `announce`; `storageKey` maps the record to one your app already keeps, so nobody is greeted twice),
|
|
85
|
+
your `Mark` inside, floating where `placement` says (`edge`, `bottom` above your tab bar, `inset`; `style` after it),
|
|
86
|
+
with `appearance` (a `ring`, the `shadow`, the bubble's `tail`), shown on your `routes` only, never with the keyboard
|
|
87
|
+
up (`hideWithKeyboard`), stepping aside when you say (`hidden`), and only when the status offers it; its button,
|
|
88
|
+
bubble and entrance are slots (`LauncherButton`, `LauncherGreeting`, `LauncherEntrance`: a short fade and scale,
|
|
89
|
+
none with reduced motion). `AskAboutRow` is "ask the agent about this" on
|
|
90
|
+
your own pages, shown when the agent is available, decided once per visit.
|
|
91
|
+
- **Policies and adapters:** `imagePolicy` (photo hosts, frame, the stand-in for a missing photo) applies to every card
|
|
92
|
+
and every fetch ahead; `announce` reads finished replies and the greeting your way; `onChip` hears a tapped chip.
|
|
93
|
+
Card renderers get the card read once (`read`).
|
|
94
|
+
- **Hooks** for your own parts: `useAgentConversation`, `useAgentStatus`, `useAgentFeatures`, `useAgentProfile`,
|
|
95
|
+
`useAgentName`, `useAgentLauncher`, `useAgentRuntime`.
|
|
96
|
+
- **Events** for analytics through `onEvent`: sessions, ratings, chips, targets and links opened, the launcher shown,
|
|
97
|
+
opened, greeted and its greeting closed.
|
|
98
|
+
- **StrictMode, in development only:** React mounts, unmounts and mounts again, so a conversation still opening is let
|
|
99
|
+
go with the first unmount and the second mount asks your backend for another session: two `openSession` calls per
|
|
100
|
+
opening in development, one in a release build. The first request's answer is dropped, never shown.
|
|
101
|
+
|
|
32
102
|
## Install
|
|
33
103
|
|
|
34
104
|
```sh
|
|
@@ -65,6 +135,12 @@ kit share one copy of `@vinaverse/client`.
|
|
|
65
135
|
- **Background**: the kit tells the conversation when the app goes to the background and comes back (`AppState`).
|
|
66
136
|
By default the connection stays, without pings; `createConversation({ closeInBackground: true, refreshSecret })`
|
|
67
137
|
closes it in the background and opens it again, with a fresh secret, in the foreground. Nothing typed is lost.
|
|
138
|
+
- **A session's end**: pass `expires_at` on from the session your backend created. Past it (the tenant's tool token
|
|
139
|
+
with it), the person's next message gets a new session for the same conversation from `getSession`; the old one is
|
|
140
|
+
never reopened with a fresh secret. A reconnect keeps everything drawn: the session's transcript merges by id.
|
|
141
|
+
- **Modes**: a customer and a professional each get their own conversation, `createConversationsByMode`, and the
|
|
142
|
+
screen draws `conversations.select(mode)`. Each mode keeps its own history on the device, and `getSession` gets the
|
|
143
|
+
`mode` so your backend creates the session for that mode's agent.
|
|
68
144
|
- **Language**: the agent answers in `subject.locale`, which your backend sets when it creates the session; the kit's
|
|
69
145
|
`locale` prop is only for its own words, dates and money (and is passed to `getSession` as a hint you may ignore).
|
|
70
146
|
|
|
@@ -103,6 +179,26 @@ part as `Default`. With neither, the kit draws both as before.
|
|
|
103
179
|
| `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
180
|
| `conversationId` | the conversation, once a session named it: for your support records |
|
|
105
181
|
|
|
182
|
+
## Listings, carousels, links and the welcome
|
|
183
|
+
|
|
184
|
+
- **Professionals and spaces** draw as photo cards: the hero photo (through your `Image` slot, so `expo-image`'s cache
|
|
185
|
+
applies), the name's monogram when there is none or it fails, rating with its count, "From $45" and the next open
|
|
186
|
+
time.
|
|
187
|
+
- **A carousel** for the cards a turn draws together: add the kit's `CardCarousel`. Without it they draw one under
|
|
188
|
+
another; it is opt-in so an app that does not use it does not ship it.
|
|
189
|
+
|
|
190
|
+
```tsx
|
|
191
|
+
import { CardCarousel, VinaverseChat } from '@vinaverse/react-native';
|
|
192
|
+
const components = { Image: ExpoImage, CardGroup: CardCarousel }; // module level: a stable object
|
|
193
|
+
<VinaverseChat conversation={conversation} components={components} onOpenLink={openLink} />;
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
- **Links**: when your scope keeps the agent from answering, it shows your own help, support, policy, contact or
|
|
197
|
+
feedback pages. Each opens through `onOpenLink` (an https page too: your in-app browser), a deep link only when it
|
|
198
|
+
passes your deep-link prefixes.
|
|
199
|
+
- **The welcome**: pass `welcome` on from the session your backend created; the kit greets an empty conversation with
|
|
200
|
+
it and its chips (no model call). A chip sends its message as the person's own. An `EmptyState` slot gets both.
|
|
201
|
+
|
|
106
202
|
## The keyboard
|
|
107
203
|
|
|
108
204
|
`keyboardBehavior` keeps the composer above the keyboard: `padding` on iOS by default, nothing on Android (the window
|