@personaai/react 0.10.0 → 0.11.0

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
@@ -1,210 +1,210 @@
1
- # @personaai/react
2
-
3
- React SDK for [Persona](https://persona.hasanraiyan.me) — hooks and a context provider for building chat UIs against any Persona backend.
4
-
5
- > **v0.9.0.** Requires React 18+. Client-side only — never holds a credential.
6
-
7
- ## Install
8
-
9
- ```bash
10
- npm install @personaai/react
11
- ```
12
-
13
- ## Quickstart
14
-
15
- ```tsx
16
- import { PersonaProvider, useChat } from "@personaai/react";
17
-
18
- function App() {
19
- return (
20
- <PersonaProvider
21
- baseUrl="http://localhost:3000/api/persona"
22
- getAuthToken={async () => getToken()}
23
- defaultAgentId="my-agent-id"
24
- >
25
- <Chat />
26
- </PersonaProvider>
27
- );
28
- }
29
-
30
- function Chat() {
31
- const { messages, input, setInput, handleSubmit, isStreaming } = useChat();
32
-
33
- return (
34
- <div>
35
- {messages.map((m) => (
36
- <div key={m.id}>{m.content}</div>
37
- ))}
38
- <form onSubmit={handleSubmit}>
39
- <input value={input} onChange={(e) => setInput(e.target.value)} />
40
- <button disabled={isStreaming}>Send</button>
41
- </form>
42
- </div>
43
- );
44
- }
45
- ```
46
-
47
- ## Hooks
48
-
49
- | Hook | Purpose |
50
- | -------------------- | ----------------------------------------------------------------------------------------------- |
51
- | `useChat` | Streaming chat — messages, send, stop, reload, interrupts, workspace files, ephemeral new chat |
52
- | `useArchitectChat` | Streaming chat with the Agent Architect co-pilot — same mechanics as `useChat`, no `agentId` |
53
- | `useWorkflowStream` | Live streaming workflow execution — real-time node state tracking, cancellation, and resume |
54
- | `useWorkflows` | Workflow discovery & creation — list, paginate, filter, and create workflows |
55
- | `useWorkflow` | Single workflow authoring — drafts, publishing, versions, and Mermaid diagrams |
56
- | `useWorkflowRuns` | Workflow run history — list historical runs, get run details, and cancel in-flight runs |
57
- | `useVoice` | Real-time voice calls (Gemini Live) — start/stop/mute, live transcript, tool calls |
58
- | `useThreads` | Thread CRUD — list, create, delete, rename, reset, archive |
59
- | `useFiles` | Upload management — list, upload, delete |
60
- | `useMemory` | Persistent memory — read, write, delete |
61
- | `useAgents` | Agent discovery — list available agents |
62
- | `useConnection` | Health check — backend connectivity status |
63
-
64
- ## Voice
65
-
66
- Real-time voice calls with an Agent, powered by Gemini Live. Requires your backend to be running
67
- `@personaai/runtime@^0.7.0` (or a framework adapter on top of it) so `POST /voice/sessions` exists
68
- to mint tickets — see that package's README for why the actual call bypasses your backend and
69
- goes straight to Persona instead of relaying through it like `useChat` does.
70
-
71
- ```tsx
72
- import { useVoice } from "@personaai/react";
73
-
74
- function VoiceCall() {
75
- const { state, transcript, partial, toolCalls, start, stop, mute, isMuted } = useVoice();
76
-
77
- return (
78
- <div>
79
- <p>State: {state}</p>
80
- {state === "idle" ? (
81
- <button onClick={start}>Start call</button>
82
- ) : (
83
- <>
84
- <button onClick={() => mute(!isMuted)}>{isMuted ? "Unmute" : "Mute"}</button>
85
- <button onClick={stop}>End call</button>
86
- </>
87
- )}
88
- {transcript.map((line) => (
89
- <p key={line.id}>
90
- <b>{line.speaker}:</b> {line.text}
91
- </p>
92
- ))}
93
- {partial && <p style={{ opacity: 0.6 }}>{partial.text}</p>}
94
- </div>
95
- );
96
- }
97
- ```
98
-
99
- Requires a browser with `AudioWorklet` support (every current evergreen browser). Needs an HTTPS
100
- page in production — browsers refuse an insecure `ws://` connection from an `https:` page.
101
-
102
- ### How `transcript` is shaped
103
-
104
- `transcript` is **live-only**: it starts empty on every `start()` and grows purely from the
105
- voice session's events. The server never replays the thread's prior history into it — pass
106
- `threadId` and the session *persists* new turns to that thread (so `useChat`/`useThreads` can read
107
- them back on reload), but `transcript` itself always reflects just what was spoken in this call.
108
-
109
- Gemini Live streams an agent answer as several incremental transcription fragments. The hook merges
110
- them so one utterance is **one transcript line**, mirroring how the same turns read back from the
111
- thread (consecutive assistant messages are coalesced on reload). The merge applies across distinct
112
- spoken segments of one answer too (e.g. split by a mid-answer tool call) — a new agent line is only
113
- started once a committed user line has intervened.
114
-
115
- ### Showing voice turns in a text `useChat` feed
116
-
117
- Share the same `threadId` between `useVoice` and `useChat`, and pass the voice hook's return
118
- value into `useChat`'s `voice` option — that's the whole integration:
119
-
120
- ```tsx
121
- const voice = useVoice({ agentId, threadId });
122
- const { messages, ... } = useChat({ threadId, voice });
123
- ```
124
-
125
- `useChat` merges live voice turns into `messages` for you: one bubble per utterance, deduped
126
- against thread history and against a voice turn that already persisted back into the shared
127
- thread, consecutive same-speaker fragments folded into the line they opened, and the
128
- in-progress agent line updated in place while it's still being spoken (`isStreaming: true`, same
129
- as a text response). Injected messages get a `voice-`-prefixed id. You still call `voice.start()`
130
- / `voice.stop()` yourself — `useChat` only owns the transcript-to-feed sync, not the call
131
- lifecycle.
132
-
133
- ## Ephemeral new chat (0.8.0+)
134
-
135
- Create a new chat instantly without minting an empty thread. The real `threadId` is created lazily on the first `sendMessage`.
136
-
137
- ```tsx
138
- const [threadId, setThreadId] = useState<string | undefined>()
139
- const chat = useChat({
140
- agentId,
141
- threadId,
142
- onThreadCreated: (id) => setThreadId(id), // sync sidebar after mint
143
- })
144
-
145
- const handleNewChat = () => {
146
- chat.startNewChat() // clears messages/files/todos, enters ephemeral mode
147
- setThreadId(undefined)
148
- }
149
-
150
- // isEphemeral is true until first send mints
151
- // chat.currentThreadId reflects the effective id (prop ?? minted)
152
- await chat.sendMessage("Hello") // auto POST /threads if threadId is undefined
153
- ```
154
-
155
- `sendMessage` and `reload` now return `Promise<boolean>` (`true` sent, `false` dropped while streaming). `startNewChat()`, `currentThreadId` and `isEphemeral` are new returns on `useChat`.
156
-
157
- ## Agent Architect chat (0.10.0+)
158
-
159
- `useArchitectChat` runs the Agent Architect co-pilot — a conversational agent that creates/edits
160
- your own Agents via tool calls (`manage_agent`, `manage_skill`, ...). Same shape as `useChat` (same
161
- streaming/interrupt/reload/ephemeral-thread mechanics), minus `agentId` (fixed target) and
162
- `voice`/`sandboxCommands` (the Architect has neither):
163
-
164
- ```tsx
165
- import { useArchitectChat } from "@personaai/react";
166
-
167
- const architect = useArchitectChat({ threadId });
168
-
169
- await architect.sendMessage("Create a support bot that...");
170
- if (architect.interrupt?.kind === "hitl") {
171
- await architect.resumeInterrupt({ decisions: [{ type: "approve" }] }, "Approved");
172
- }
173
- ```
174
-
175
- `threadId` resume only has an effect when the underlying credential asserts an external user
176
- (`x-persona-external-user-id`) — a bare Project credential has no Subject for a Thread to belong to,
177
- so the backend keeps its single deterministic per-Project conversation either way.
178
-
179
- ## Devtools
180
-
181
- Floating panel for local debugging — inspect hooks, messages and threads with zero runtime changes. Dev-only, not bundled to production unless imported.
182
-
183
- ```bash
184
- npm install -D @personaai/devtools
185
- ```
186
-
187
- ```tsx
188
- import { PersonaDevtools } from "@personaai/devtools/react";
189
- import { useChat, useThreads } from "@personaai/react";
190
-
191
- function Devtools() {
192
- const { threads } = useThreads();
193
- const { messages, files, todos } = useChat();
194
- return <PersonaDevtools clientState={{ threads, messages, files, todos }} />;
195
- }
196
-
197
- {
198
- process.env.NODE_ENV === "development" && <Devtools />;
199
- }
200
- ```
201
-
202
- See `@personaai/devtools` `sdk/devtools/README.md` for `core`/`react`/`nextjs` subpaths and `baseUrl` polling option.
203
-
204
- ## Full documentation
205
-
206
- **[persona.hasanraiyan.me/guides/react/quickstart](https://persona.hasanraiyan.me/guides/react/quickstart)** — quickstart, hooks reference, streaming events, and types.
207
-
208
- ## Peer dependencies
209
-
210
- - `react >= 18`
1
+ # @personaai/react
2
+
3
+ React SDK for [Persona](https://persona.hasanraiyan.me) — hooks and a context provider for building chat UIs against any Persona backend.
4
+
5
+ > **v0.9.0.** Requires React 18+. Client-side only — never holds a credential.
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ npm install @personaai/react
11
+ ```
12
+
13
+ ## Quickstart
14
+
15
+ ```tsx
16
+ import { PersonaProvider, useChat } from "@personaai/react";
17
+
18
+ function App() {
19
+ return (
20
+ <PersonaProvider
21
+ baseUrl="http://localhost:3000/api/persona"
22
+ getAuthToken={async () => getToken()}
23
+ defaultAgentId="my-agent-id"
24
+ >
25
+ <Chat />
26
+ </PersonaProvider>
27
+ );
28
+ }
29
+
30
+ function Chat() {
31
+ const { messages, input, setInput, handleSubmit, isStreaming } = useChat();
32
+
33
+ return (
34
+ <div>
35
+ {messages.map((m) => (
36
+ <div key={m.id}>{m.content}</div>
37
+ ))}
38
+ <form onSubmit={handleSubmit}>
39
+ <input value={input} onChange={(e) => setInput(e.target.value)} />
40
+ <button disabled={isStreaming}>Send</button>
41
+ </form>
42
+ </div>
43
+ );
44
+ }
45
+ ```
46
+
47
+ ## Hooks
48
+
49
+ | Hook | Purpose |
50
+ | -------------------- | ----------------------------------------------------------------------------------------------- |
51
+ | `useChat` | Streaming chat — messages, send, stop, reload, interrupts, workspace files, ephemeral new chat |
52
+ | `useArchitectChat` | Streaming chat with the Agent Architect co-pilot — same mechanics as `useChat`, no `agentId` |
53
+ | `useWorkflowStream` | Live streaming workflow execution — real-time node state tracking, cancellation, and resume |
54
+ | `useWorkflows` | Workflow discovery & creation — list, paginate, filter, and create workflows |
55
+ | `useWorkflow` | Single workflow authoring — drafts, publishing, versions, and Mermaid diagrams |
56
+ | `useWorkflowRuns` | Workflow run history — list historical runs, get run details, and cancel in-flight runs |
57
+ | `useVoice` | Real-time voice calls (Gemini Live) — start/stop/mute, live transcript, tool calls |
58
+ | `useThreads` | Thread CRUD — list, create, delete, rename, reset, archive |
59
+ | `useFiles` | Upload management — list, upload, delete |
60
+ | `useMemory` | Persistent memory — read, write, delete |
61
+ | `useAgents` | Agent discovery — list available agents |
62
+ | `useConnection` | Health check — backend connectivity status |
63
+
64
+ ## Voice
65
+
66
+ Real-time voice calls with an Agent, powered by Gemini Live. Requires your backend to be running
67
+ `@personaai/runtime@^0.7.0` (or a framework adapter on top of it) so `POST /voice/sessions` exists
68
+ to mint tickets — see that package's README for why the actual call bypasses your backend and
69
+ goes straight to Persona instead of relaying through it like `useChat` does.
70
+
71
+ ```tsx
72
+ import { useVoice } from "@personaai/react";
73
+
74
+ function VoiceCall() {
75
+ const { state, transcript, partial, toolCalls, start, stop, mute, isMuted } = useVoice();
76
+
77
+ return (
78
+ <div>
79
+ <p>State: {state}</p>
80
+ {state === "idle" ? (
81
+ <button onClick={start}>Start call</button>
82
+ ) : (
83
+ <>
84
+ <button onClick={() => mute(!isMuted)}>{isMuted ? "Unmute" : "Mute"}</button>
85
+ <button onClick={stop}>End call</button>
86
+ </>
87
+ )}
88
+ {transcript.map((line) => (
89
+ <p key={line.id}>
90
+ <b>{line.speaker}:</b> {line.text}
91
+ </p>
92
+ ))}
93
+ {partial && <p style={{ opacity: 0.6 }}>{partial.text}</p>}
94
+ </div>
95
+ );
96
+ }
97
+ ```
98
+
99
+ Requires a browser with `AudioWorklet` support (every current evergreen browser). Needs an HTTPS
100
+ page in production — browsers refuse an insecure `ws://` connection from an `https:` page.
101
+
102
+ ### How `transcript` is shaped
103
+
104
+ `transcript` is **live-only**: it starts empty on every `start()` and grows purely from the
105
+ voice session's events. The server never replays the thread's prior history into it — pass
106
+ `threadId` and the session *persists* new turns to that thread (so `useChat`/`useThreads` can read
107
+ them back on reload), but `transcript` itself always reflects just what was spoken in this call.
108
+
109
+ Gemini Live streams an agent answer as several incremental transcription fragments. The hook merges
110
+ them so one utterance is **one transcript line**, mirroring how the same turns read back from the
111
+ thread (consecutive assistant messages are coalesced on reload). The merge applies across distinct
112
+ spoken segments of one answer too (e.g. split by a mid-answer tool call) — a new agent line is only
113
+ started once a committed user line has intervened.
114
+
115
+ ### Showing voice turns in a text `useChat` feed
116
+
117
+ Share the same `threadId` between `useVoice` and `useChat`, and pass the voice hook's return
118
+ value into `useChat`'s `voice` option — that's the whole integration:
119
+
120
+ ```tsx
121
+ const voice = useVoice({ agentId, threadId });
122
+ const { messages, ... } = useChat({ threadId, voice });
123
+ ```
124
+
125
+ `useChat` merges live voice turns into `messages` for you: one bubble per utterance, deduped
126
+ against thread history and against a voice turn that already persisted back into the shared
127
+ thread, consecutive same-speaker fragments folded into the line they opened, and the
128
+ in-progress agent line updated in place while it's still being spoken (`isStreaming: true`, same
129
+ as a text response). Injected messages get a `voice-`-prefixed id. You still call `voice.start()`
130
+ / `voice.stop()` yourself — `useChat` only owns the transcript-to-feed sync, not the call
131
+ lifecycle.
132
+
133
+ ## Ephemeral new chat (0.8.0+)
134
+
135
+ Create a new chat instantly without minting an empty thread. The real `threadId` is created lazily on the first `sendMessage`.
136
+
137
+ ```tsx
138
+ const [threadId, setThreadId] = useState<string | undefined>()
139
+ const chat = useChat({
140
+ agentId,
141
+ threadId,
142
+ onThreadCreated: (id) => setThreadId(id), // sync sidebar after mint
143
+ })
144
+
145
+ const handleNewChat = () => {
146
+ chat.startNewChat() // clears messages/files/todos, enters ephemeral mode
147
+ setThreadId(undefined)
148
+ }
149
+
150
+ // isEphemeral is true until first send mints
151
+ // chat.currentThreadId reflects the effective id (prop ?? minted)
152
+ await chat.sendMessage("Hello") // auto POST /threads if threadId is undefined
153
+ ```
154
+
155
+ `sendMessage` and `reload` now return `Promise<boolean>` (`true` sent, `false` dropped while streaming). `startNewChat()`, `currentThreadId` and `isEphemeral` are new returns on `useChat`.
156
+
157
+ ## Agent Architect chat (0.10.0+)
158
+
159
+ `useArchitectChat` runs the Agent Architect co-pilot — a conversational agent that creates/edits
160
+ your own Agents via tool calls (`manage_agent`, `manage_skill`, ...). Same shape as `useChat` (same
161
+ streaming/interrupt/reload/ephemeral-thread mechanics), minus `agentId` (fixed target) and
162
+ `voice`/`sandboxCommands` (the Architect has neither):
163
+
164
+ ```tsx
165
+ import { useArchitectChat } from "@personaai/react";
166
+
167
+ const architect = useArchitectChat({ threadId });
168
+
169
+ await architect.sendMessage("Create a support bot that...");
170
+ if (architect.interrupt?.kind === "hitl") {
171
+ await architect.resumeInterrupt({ decisions: [{ type: "approve" }] }, "Approved");
172
+ }
173
+ ```
174
+
175
+ `threadId` resume only has an effect when the underlying credential asserts an external user
176
+ (`x-persona-external-user-id`) — a bare Project credential has no Subject for a Thread to belong to,
177
+ so the backend keeps its single deterministic per-Project conversation either way.
178
+
179
+ ## Devtools
180
+
181
+ Floating panel for local debugging — inspect hooks, messages and threads with zero runtime changes. Dev-only, not bundled to production unless imported.
182
+
183
+ ```bash
184
+ npm install -D @personaai/devtools
185
+ ```
186
+
187
+ ```tsx
188
+ import { PersonaDevtools } from "@personaai/devtools/react";
189
+ import { useChat, useThreads } from "@personaai/react";
190
+
191
+ function Devtools() {
192
+ const { threads } = useThreads();
193
+ const { messages, files, todos } = useChat();
194
+ return <PersonaDevtools clientState={{ threads, messages, files, todos }} />;
195
+ }
196
+
197
+ {
198
+ process.env.NODE_ENV === "development" && <Devtools />;
199
+ }
200
+ ```
201
+
202
+ See `@personaai/devtools` `sdk/devtools/README.md` for `core`/`react`/`nextjs` subpaths and `baseUrl` polling option.
203
+
204
+ ## Full documentation
205
+
206
+ **[persona.hasanraiyan.me/guides/react/quickstart](https://persona.hasanraiyan.me/guides/react/quickstart)** — quickstart, hooks reference, streaming events, and types.
207
+
208
+ ## Peer dependencies
209
+
210
+ - `react >= 18`
@@ -0,0 +1 @@
1
+ TypeDoc added this file to prevent GitHub Pages from using Jekyll. You can turn off this behavior by setting the `githubPages` option to false.