@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 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), held and unsent messages, a
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