@applica-software-guru/persona-chat-sdk 0.1.102
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/.eslintrc.cjs +11 -0
- package/.github/copilot-instructions.md +3 -0
- package/.nvmrc +1 -0
- package/.prettierignore +5 -0
- package/.prettierrc +8 -0
- package/CLAUDE.md +3 -0
- package/GEMINI.md +3 -0
- package/README.md +33 -0
- package/bitbucket-pipelines.yml +19 -0
- package/components.json +24 -0
- package/dist/bundle.cjs.js +28 -0
- package/dist/bundle.cjs.js.map +1 -0
- package/dist/bundle.es.js +5173 -0
- package/dist/bundle.es.js.map +1 -0
- package/dist/bundle.iife.js +28 -0
- package/dist/bundle.iife.js.map +1 -0
- package/dist/bundle.umd.js +28 -0
- package/dist/bundle.umd.js.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/logging.d.ts +18 -0
- package/dist/logging.d.ts.map +1 -0
- package/dist/messages.d.ts +9 -0
- package/dist/messages.d.ts.map +1 -0
- package/dist/projects.d.ts +17 -0
- package/dist/projects.d.ts.map +1 -0
- package/dist/protocol/base.d.ts +25 -0
- package/dist/protocol/base.d.ts.map +1 -0
- package/dist/protocol/index.d.ts +6 -0
- package/dist/protocol/index.d.ts.map +1 -0
- package/dist/protocol/rest.d.ts +25 -0
- package/dist/protocol/rest.d.ts.map +1 -0
- package/dist/protocol/transaction.d.ts +56 -0
- package/dist/protocol/transaction.d.ts.map +1 -0
- package/dist/protocol/webrtc.d.ts +60 -0
- package/dist/protocol/webrtc.d.ts.map +1 -0
- package/dist/protocol/websocket.d.ts +22 -0
- package/dist/protocol/websocket.d.ts.map +1 -0
- package/dist/runtime/context.d.ts +34 -0
- package/dist/runtime/context.d.ts.map +1 -0
- package/dist/runtime/file-attachment-adapter.d.ts +15 -0
- package/dist/runtime/file-attachment-adapter.d.ts.map +1 -0
- package/dist/runtime/handlers.d.ts +21 -0
- package/dist/runtime/handlers.d.ts.map +1 -0
- package/dist/runtime/listeners.d.ts +6 -0
- package/dist/runtime/listeners.d.ts.map +1 -0
- package/dist/runtime/protocols.d.ts +17 -0
- package/dist/runtime/protocols.d.ts.map +1 -0
- package/dist/runtime/threads.d.ts +34 -0
- package/dist/runtime/threads.d.ts.map +1 -0
- package/dist/runtime/utils.d.ts +10 -0
- package/dist/runtime/utils.d.ts.map +1 -0
- package/dist/runtime.d.ts +6 -0
- package/dist/runtime.d.ts.map +1 -0
- package/dist/storage/base.d.ts +9 -0
- package/dist/storage/base.d.ts.map +1 -0
- package/dist/storage/index.d.ts +3 -0
- package/dist/storage/index.d.ts.map +1 -0
- package/dist/storage/persona.d.ts +29 -0
- package/dist/storage/persona.d.ts.map +1 -0
- package/dist/tools.d.ts +72 -0
- package/dist/tools.d.ts.map +1 -0
- package/dist/types.d.ts +237 -0
- package/dist/types.d.ts.map +1 -0
- package/docs/README.md +13 -0
- package/docs/api-reference.md +16 -0
- package/docs/contributing.md +21 -0
- package/docs/customization.md +74 -0
- package/docs/features.md +7 -0
- package/docs/installation.md +9 -0
- package/docs/messages.md +214 -0
- package/docs/protocols.md +39 -0
- package/docs/transactions.md +121 -0
- package/docs/usage.md +40 -0
- package/jsconfig.node.json +10 -0
- package/package.json +82 -0
- package/playground/index.html +14 -0
- package/playground/src/app.tsx +10 -0
- package/playground/src/chat.tsx +117 -0
- package/playground/src/components/assistant-ui/assistant-modal.tsx +57 -0
- package/playground/src/components/assistant-ui/attachment.tsx +197 -0
- package/playground/src/components/assistant-ui/markdown-text.tsx +228 -0
- package/playground/src/components/assistant-ui/thread-list.tsx +91 -0
- package/playground/src/components/assistant-ui/thread.tsx +302 -0
- package/playground/src/components/assistant-ui/threadlist-sidebar.tsx +59 -0
- package/playground/src/components/assistant-ui/tool-fallback.tsx +93 -0
- package/playground/src/components/assistant-ui/tooltip-icon-button.tsx +42 -0
- package/playground/src/components/chat/logging.tsx +53 -0
- package/playground/src/components/ui/avatar.tsx +51 -0
- package/playground/src/components/ui/button.tsx +60 -0
- package/playground/src/components/ui/dialog.tsx +141 -0
- package/playground/src/components/ui/input.tsx +21 -0
- package/playground/src/components/ui/separator.tsx +26 -0
- package/playground/src/components/ui/sheet.tsx +139 -0
- package/playground/src/components/ui/sidebar.tsx +619 -0
- package/playground/src/components/ui/skeleton.tsx +13 -0
- package/playground/src/components/ui/tooltip.tsx +59 -0
- package/playground/src/hooks/theme.ts +70 -0
- package/playground/src/hooks/use-mobile.ts +19 -0
- package/playground/src/lib/utils.ts +6 -0
- package/playground/src/main.tsx +10 -0
- package/playground/src/styles.css +120 -0
- package/playground/src/tools.ts +149 -0
- package/playground/src/vite-env.d.ts +1 -0
- package/preview-build.sh +23 -0
- package/src/index.ts +7 -0
- package/src/logging.ts +34 -0
- package/src/messages.ts +202 -0
- package/src/projects.ts +57 -0
- package/src/protocol/base.ts +73 -0
- package/src/protocol/index.ts +5 -0
- package/src/protocol/rest.ts +107 -0
- package/src/protocol/transaction.ts +182 -0
- package/src/protocol/webrtc.ts +379 -0
- package/src/protocol/websocket.ts +111 -0
- package/src/runtime/context.ts +88 -0
- package/src/runtime/file-attachment-adapter.ts +48 -0
- package/src/runtime/handlers.ts +322 -0
- package/src/runtime/index.ts +6 -0
- package/src/runtime/listeners.ts +79 -0
- package/src/runtime/protocols.ts +169 -0
- package/src/runtime/threads.ts +105 -0
- package/src/runtime/utils.ts +46 -0
- package/src/runtime.tsx +334 -0
- package/src/storage/base.ts +13 -0
- package/src/storage/index.ts +2 -0
- package/src/storage/persona.ts +138 -0
- package/src/tools.ts +211 -0
- package/src/types.ts +284 -0
- package/tsconfig.json +36 -0
- package/tsconfig.node.json +15 -0
- package/vite.config.ts +74 -0
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { PersonaProtocol } from '../types';
|
|
2
|
+
import { PersonaRESTProtocol, PersonaWebRTCProtocol, PersonaWebSocketProtocol } from '../protocol';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Converts a File object to a base64 string
|
|
6
|
+
*/
|
|
7
|
+
export function fileToBase64(file: File): Promise<string> {
|
|
8
|
+
return new Promise((resolve, reject) => {
|
|
9
|
+
const reader = new FileReader();
|
|
10
|
+
reader.readAsDataURL(file);
|
|
11
|
+
|
|
12
|
+
reader.onload = () => {
|
|
13
|
+
const base64 = reader.result as string;
|
|
14
|
+
const base64WithoutPrefix = base64.split(';base64,')[1];
|
|
15
|
+
resolve(base64WithoutPrefix);
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
reader.onerror = (error) => {
|
|
19
|
+
reject(error);
|
|
20
|
+
};
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Extracts the first available protocol endpoint URL
|
|
26
|
+
*/
|
|
27
|
+
export function getFirstAvailableProtocolEndpoint(protocols: PersonaProtocol[]): string | null {
|
|
28
|
+
const extractors: { [key: string]: (protocol: PersonaProtocol) => string } = {
|
|
29
|
+
rest: (protocol) => (protocol as PersonaRESTProtocol).config.apiUrl,
|
|
30
|
+
webrtc: (protocol) =>
|
|
31
|
+
(protocol as PersonaWebRTCProtocol).config.webrtcUrl.replace('/webrtc', '').replace('ws://', 'http://').replace('wss://', 'https://'),
|
|
32
|
+
websocket: (protocol) =>
|
|
33
|
+
(protocol as PersonaWebSocketProtocol).config.webSocketUrl
|
|
34
|
+
.replace('/websocket', '')
|
|
35
|
+
.replace('ws://', 'http://')
|
|
36
|
+
.replace('wss://', 'https://'),
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
for (const protocol of protocols) {
|
|
40
|
+
const extractor = extractors[protocol.getName()];
|
|
41
|
+
if (extractor) {
|
|
42
|
+
return extractor(protocol);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return null;
|
|
46
|
+
}
|
package/src/runtime.tsx
ADDED
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
import { useState, useEffect, useCallback, PropsWithChildren, useMemo, useRef } from 'react';
|
|
2
|
+
import {
|
|
3
|
+
useExternalStoreRuntime,
|
|
4
|
+
AssistantRuntimeProvider,
|
|
5
|
+
CompositeAttachmentAdapter,
|
|
6
|
+
SimpleImageAttachmentAdapter,
|
|
7
|
+
SimpleTextAttachmentAdapter,
|
|
8
|
+
} from '@assistant-ui/react';
|
|
9
|
+
import { SimpleFileAttachmentAdapter } from './runtime/file-attachment-adapter';
|
|
10
|
+
import { PersonaConfig, PersonaMessage, PersonaResponse, ProtocolStatus, Session, ThreadData } from './types';
|
|
11
|
+
import { parseMessages, convertMessage } from './messages';
|
|
12
|
+
import { initializeProtocols } from './runtime/protocols';
|
|
13
|
+
import { createPacketListener } from './runtime/listeners';
|
|
14
|
+
import { createOnNewHandler, createOnEditHandler, createOnReloadHandler, createOnCancelHandler } from './runtime/handlers';
|
|
15
|
+
import { PersonaRuntimeContext } from './runtime/context';
|
|
16
|
+
import { createThreadListAdapter } from './runtime/threads';
|
|
17
|
+
import { PersonaSessionStorage } from './storage';
|
|
18
|
+
|
|
19
|
+
export {
|
|
20
|
+
usePersonaRuntime,
|
|
21
|
+
usePersonaRuntimeProtocol,
|
|
22
|
+
usePersonaRuntimeEndpoint,
|
|
23
|
+
usePersonaRuntimeWebRTCProtocol,
|
|
24
|
+
usePersonaRuntimeMessages,
|
|
25
|
+
} from './runtime/context';
|
|
26
|
+
|
|
27
|
+
export type { PersonaMessage, PersonaResponse, ThreadData };
|
|
28
|
+
|
|
29
|
+
function PersonaRuntimeProviderInner({
|
|
30
|
+
dev = false,
|
|
31
|
+
baseUrl,
|
|
32
|
+
protocols: _protocols,
|
|
33
|
+
logger,
|
|
34
|
+
children,
|
|
35
|
+
session: initialSession = 'new',
|
|
36
|
+
transformMessages,
|
|
37
|
+
threads = false,
|
|
38
|
+
sessionStorage: _sessionStorage,
|
|
39
|
+
onThreadCreate,
|
|
40
|
+
onThreadSwitch,
|
|
41
|
+
onThreadRename,
|
|
42
|
+
onThreadArchive,
|
|
43
|
+
onThreadUnarchive,
|
|
44
|
+
onThreadDelete,
|
|
45
|
+
enableAttachments = false,
|
|
46
|
+
...config
|
|
47
|
+
}: Readonly<PersonaConfig>) {
|
|
48
|
+
const [isRunning, setIsRunning] = useState(false);
|
|
49
|
+
const [messages, setMessages] = useState<PersonaMessage[]>([]);
|
|
50
|
+
const [protocolsStatus, setProtocolsStatus] = useState<Map<string, ProtocolStatus>>(new Map());
|
|
51
|
+
const didMount = useRef(false);
|
|
52
|
+
const isSwitchingThread = useRef(false);
|
|
53
|
+
|
|
54
|
+
// Initialize message storage
|
|
55
|
+
const sessionStorage = useMemo(
|
|
56
|
+
() =>
|
|
57
|
+
_sessionStorage ||
|
|
58
|
+
new PersonaSessionStorage({
|
|
59
|
+
apiKey: config.apiKey!,
|
|
60
|
+
endpoint: baseUrl || 'https://persona.applica.guru/api',
|
|
61
|
+
}),
|
|
62
|
+
[_sessionStorage, baseUrl, config.apiKey],
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
// Thread management state
|
|
66
|
+
const [threadList, setThreadList] = useState<ThreadData[]>([]);
|
|
67
|
+
const [currentThreadId, setCurrentThreadId] = useState<string>('');
|
|
68
|
+
|
|
69
|
+
// Use refs to avoid re-renders and effect loops
|
|
70
|
+
const currentThreadRef = useRef<Session>(typeof initialSession === 'string' ? initialSession : 'new');
|
|
71
|
+
const hasInitialized = useRef(false);
|
|
72
|
+
|
|
73
|
+
// Load threads on mount (only once)
|
|
74
|
+
useEffect(() => {
|
|
75
|
+
if (!threads || hasInitialized.current) {
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
hasInitialized.current = true;
|
|
79
|
+
|
|
80
|
+
sessionStorage
|
|
81
|
+
.list(config.agentId, config.userId)
|
|
82
|
+
.then((threads) => {
|
|
83
|
+
if (threads?.length > 0) {
|
|
84
|
+
setThreadList(threads);
|
|
85
|
+
setCurrentThreadId(threads[0].threadId);
|
|
86
|
+
}
|
|
87
|
+
})
|
|
88
|
+
.catch((error) => logger?.error('Failed to load threads:', error));
|
|
89
|
+
}, [threads, sessionStorage, config.agentId, config.userId, logger]);
|
|
90
|
+
|
|
91
|
+
// Initialize protocols
|
|
92
|
+
const protocols = useMemo(
|
|
93
|
+
() =>
|
|
94
|
+
initializeProtocols(_protocols, {
|
|
95
|
+
dev: dev || false,
|
|
96
|
+
baseUrl,
|
|
97
|
+
apiKey: config.apiKey,
|
|
98
|
+
agentId: config.agentId,
|
|
99
|
+
userId: config.userId,
|
|
100
|
+
tools: config.tools,
|
|
101
|
+
logger,
|
|
102
|
+
}),
|
|
103
|
+
[_protocols, dev, baseUrl, config.apiKey, config.agentId, config.userId, config.tools, logger],
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
// Setup protocol listeners (only once on mount)
|
|
107
|
+
useEffect(() => {
|
|
108
|
+
if (didMount.current) {
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
didMount.current = true;
|
|
112
|
+
logger?.debug(
|
|
113
|
+
'Setting up protocols:',
|
|
114
|
+
protocols.map((p) => p.getName()),
|
|
115
|
+
);
|
|
116
|
+
protocols.forEach((protocol) => {
|
|
117
|
+
protocol.setSession(currentThreadRef.current);
|
|
118
|
+
protocol.clearListeners();
|
|
119
|
+
|
|
120
|
+
// Status change listener
|
|
121
|
+
protocol.addStatusChangeListener((status: ProtocolStatus) => {
|
|
122
|
+
logger?.debug(`${protocol.getName()} status: ${status}`);
|
|
123
|
+
setProtocolsStatus((prev) => {
|
|
124
|
+
const next = new Map(prev);
|
|
125
|
+
next.set(protocol.getName(), status);
|
|
126
|
+
return next;
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
// Only send initial packets on first connection (not during thread switches)
|
|
130
|
+
if (status === 'connected' && !isSwitchingThread.current) {
|
|
131
|
+
if (config.context) {
|
|
132
|
+
protocol
|
|
133
|
+
.sendPacket({
|
|
134
|
+
type: 'command',
|
|
135
|
+
payload: { command: 'set_initial_context', arguments: config.context },
|
|
136
|
+
})
|
|
137
|
+
.catch(() => {});
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if (config.tools && Array.isArray(config.tools)) {
|
|
141
|
+
protocol
|
|
142
|
+
.sendPacket({
|
|
143
|
+
type: 'command',
|
|
144
|
+
payload: { command: 'set_local_tools', arguments: { tools: config.tools.map((t) => t.schema) } },
|
|
145
|
+
})
|
|
146
|
+
.catch(() => {});
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
// Packet listener
|
|
152
|
+
protocol.addPacketListener(createPacketListener(protocol, setMessages, setIsRunning, protocols, transformMessages));
|
|
153
|
+
|
|
154
|
+
// Auto-connect if configured
|
|
155
|
+
if (protocol.autostart && protocol.status === 'disconnected') {
|
|
156
|
+
logger?.debug(`Auto-connecting protocol: ${protocol.getName()}`);
|
|
157
|
+
protocol.connect(currentThreadRef.current).catch(() => {});
|
|
158
|
+
setIsRunning(false);
|
|
159
|
+
}
|
|
160
|
+
});
|
|
161
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
162
|
+
}, []); // Run only once
|
|
163
|
+
|
|
164
|
+
// Load messages for an existing session on mount (without threads)
|
|
165
|
+
useEffect(() => {
|
|
166
|
+
if (threads || !initialSession || initialSession === 'new') {
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
sessionStorage
|
|
171
|
+
.sync(initialSession)
|
|
172
|
+
.then((loadedMessages) => {
|
|
173
|
+
if (loadedMessages.length > 0) {
|
|
174
|
+
const processed = parseMessages(loadedMessages);
|
|
175
|
+
setMessages(transformMessages ? transformMessages(processed) : processed);
|
|
176
|
+
logger?.debug(`Loaded ${loadedMessages.length} messages for session ${initialSession}`);
|
|
177
|
+
}
|
|
178
|
+
})
|
|
179
|
+
.catch((error) => {
|
|
180
|
+
logger?.error('Failed to load session messages:', error);
|
|
181
|
+
});
|
|
182
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
183
|
+
}, []); // Run only once on mount
|
|
184
|
+
|
|
185
|
+
// Handle thread switching
|
|
186
|
+
useEffect(() => {
|
|
187
|
+
if (!threads || !currentThreadId) {
|
|
188
|
+
setMessages([]);
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
if (isSwitchingThread.current || currentThreadId === currentThreadRef.current) {
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Mark as switching to prevent concurrent switches
|
|
197
|
+
isSwitchingThread.current = true;
|
|
198
|
+
logger?.info('Switching to thread:', currentThreadId);
|
|
199
|
+
|
|
200
|
+
const switchThread = async () => {
|
|
201
|
+
try {
|
|
202
|
+
// Disconnect active protocols in parallel
|
|
203
|
+
const connectedProtocols = protocols.filter((p) => p.status === 'connected');
|
|
204
|
+
if (connectedProtocols.length > 0) {
|
|
205
|
+
await Promise.allSettled(connectedProtocols.map((p) => p.disconnect()));
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// Update thread reference
|
|
209
|
+
currentThreadRef.current = currentThreadId;
|
|
210
|
+
|
|
211
|
+
// Set new session on all protocols
|
|
212
|
+
await Promise.allSettled(protocols.map((p) => p.setSession(currentThreadId)));
|
|
213
|
+
|
|
214
|
+
// Load messages from storage
|
|
215
|
+
const loadedMessages = await sessionStorage.sync(currentThreadId);
|
|
216
|
+
const processed = parseMessages(loadedMessages);
|
|
217
|
+
setMessages(transformMessages ? transformMessages(processed) : processed);
|
|
218
|
+
logger?.debug(`Loaded ${loadedMessages.length} messages for thread ${currentThreadId}`);
|
|
219
|
+
|
|
220
|
+
// Reconnect autostart protocols
|
|
221
|
+
const autostartProtocols = protocols.filter((p) => p.autostart);
|
|
222
|
+
if (autostartProtocols.length > 0) {
|
|
223
|
+
await Promise.allSettled(autostartProtocols.map((p) => p.connect(currentThreadId)));
|
|
224
|
+
}
|
|
225
|
+
} catch (error) {
|
|
226
|
+
logger?.error('Failed to switch thread:', error);
|
|
227
|
+
setMessages([]);
|
|
228
|
+
} finally {
|
|
229
|
+
isSwitchingThread.current = false;
|
|
230
|
+
}
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
switchThread();
|
|
234
|
+
}, [currentThreadId, threads, protocols, sessionStorage, transformMessages, logger]);
|
|
235
|
+
|
|
236
|
+
// Message handlers
|
|
237
|
+
const onNew = useCallback(createOnNewHandler(protocols, setMessages, setIsRunning, transformMessages, logger), [
|
|
238
|
+
protocols,
|
|
239
|
+
transformMessages,
|
|
240
|
+
logger,
|
|
241
|
+
]);
|
|
242
|
+
|
|
243
|
+
const onEdit = useCallback(createOnEditHandler(protocols, messages, setMessages, setIsRunning, transformMessages, logger), [
|
|
244
|
+
protocols,
|
|
245
|
+
messages,
|
|
246
|
+
transformMessages,
|
|
247
|
+
logger,
|
|
248
|
+
]);
|
|
249
|
+
|
|
250
|
+
const onReload = useCallback(createOnReloadHandler(protocols, messages, setMessages, setIsRunning, transformMessages, logger), [
|
|
251
|
+
protocols,
|
|
252
|
+
messages,
|
|
253
|
+
transformMessages,
|
|
254
|
+
logger,
|
|
255
|
+
]);
|
|
256
|
+
|
|
257
|
+
const onCancel = useCallback(createOnCancelHandler(protocols, setMessages, setIsRunning, transformMessages), [
|
|
258
|
+
protocols,
|
|
259
|
+
transformMessages,
|
|
260
|
+
]);
|
|
261
|
+
|
|
262
|
+
const handleSetMessages = useCallback(
|
|
263
|
+
(newMessages: readonly PersonaMessage[]) => {
|
|
264
|
+
const processed = parseMessages([...newMessages]);
|
|
265
|
+
setMessages(transformMessages ? transformMessages(processed) : processed);
|
|
266
|
+
},
|
|
267
|
+
[transformMessages],
|
|
268
|
+
);
|
|
269
|
+
|
|
270
|
+
const getMessages = useCallback(() => messages, [messages]);
|
|
271
|
+
|
|
272
|
+
// Thread list adapter
|
|
273
|
+
const threadListAdapter = useMemo(() => {
|
|
274
|
+
if (!threads) return undefined;
|
|
275
|
+
|
|
276
|
+
return createThreadListAdapter(
|
|
277
|
+
currentThreadId,
|
|
278
|
+
threadList,
|
|
279
|
+
setCurrentThreadId,
|
|
280
|
+
setThreadList,
|
|
281
|
+
sessionStorage,
|
|
282
|
+
onThreadCreate,
|
|
283
|
+
onThreadSwitch,
|
|
284
|
+
onThreadArchive,
|
|
285
|
+
onThreadUnarchive,
|
|
286
|
+
onThreadDelete,
|
|
287
|
+
);
|
|
288
|
+
}, [
|
|
289
|
+
threads,
|
|
290
|
+
currentThreadId,
|
|
291
|
+
threadList,
|
|
292
|
+
sessionStorage,
|
|
293
|
+
onThreadCreate,
|
|
294
|
+
onThreadSwitch,
|
|
295
|
+
onThreadArchive,
|
|
296
|
+
onThreadUnarchive,
|
|
297
|
+
onThreadDelete,
|
|
298
|
+
logger,
|
|
299
|
+
]);
|
|
300
|
+
|
|
301
|
+
// Create runtime
|
|
302
|
+
const runtime = useExternalStoreRuntime({
|
|
303
|
+
isRunning,
|
|
304
|
+
messages,
|
|
305
|
+
convertMessage,
|
|
306
|
+
setMessages: handleSetMessages,
|
|
307
|
+
onNew,
|
|
308
|
+
onEdit,
|
|
309
|
+
onCancel,
|
|
310
|
+
onReload,
|
|
311
|
+
adapters: {
|
|
312
|
+
...(enableAttachments
|
|
313
|
+
? {
|
|
314
|
+
attachments: new CompositeAttachmentAdapter([
|
|
315
|
+
new SimpleImageAttachmentAdapter(),
|
|
316
|
+
new SimpleTextAttachmentAdapter(),
|
|
317
|
+
new SimpleFileAttachmentAdapter(),
|
|
318
|
+
]),
|
|
319
|
+
}
|
|
320
|
+
: {}),
|
|
321
|
+
...(threadListAdapter ? { threadList: threadListAdapter } : {}),
|
|
322
|
+
},
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
return (
|
|
326
|
+
<PersonaRuntimeContext.Provider value={{ protocols, protocolsStatus, getMessages }}>
|
|
327
|
+
<AssistantRuntimeProvider runtime={runtime}>{children}</AssistantRuntimeProvider>
|
|
328
|
+
</PersonaRuntimeContext.Provider>
|
|
329
|
+
);
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
export function PersonaRuntimeProvider({ children, ...config }: PropsWithChildren<PersonaConfig>) {
|
|
333
|
+
return <PersonaRuntimeProviderInner {...config}>{children}</PersonaRuntimeProviderInner>;
|
|
334
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { PersonaMessage, Session, ThreadData } from '../types';
|
|
2
|
+
|
|
3
|
+
export interface SessionStorage {
|
|
4
|
+
sync(sessionId: Session): Promise<PersonaMessage[]>;
|
|
5
|
+
|
|
6
|
+
archive(sessionId: Session): Promise<void>;
|
|
7
|
+
|
|
8
|
+
unarchive(sessionId: Session): Promise<void>;
|
|
9
|
+
|
|
10
|
+
delete(sessionId: Session): Promise<void>;
|
|
11
|
+
|
|
12
|
+
list(agentId: string, userId?: string): Promise<ThreadData[]>;
|
|
13
|
+
}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import type { PersonaMessage, Session, ThreadData } from '../types';
|
|
2
|
+
import type { SessionStorage } from './base';
|
|
3
|
+
import type { PersonaLogger } from '../logging';
|
|
4
|
+
|
|
5
|
+
type PersonaSessionStorageOptions = {
|
|
6
|
+
endpoint: string;
|
|
7
|
+
apiKey?: string;
|
|
8
|
+
pageSize?: number;
|
|
9
|
+
logger?: PersonaLogger;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export class PersonaSessionStorage implements SessionStorage {
|
|
13
|
+
private endpoint: string;
|
|
14
|
+
private apiKey?: string;
|
|
15
|
+
private pageSize: number;
|
|
16
|
+
private logger?: PersonaLogger;
|
|
17
|
+
|
|
18
|
+
constructor(opts: PersonaSessionStorageOptions) {
|
|
19
|
+
this.endpoint = opts.endpoint.replace(/\/$/, '');
|
|
20
|
+
this.apiKey = opts.apiKey;
|
|
21
|
+
this.pageSize = opts.pageSize ?? 200;
|
|
22
|
+
this.logger = opts.logger;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
private buildHeaders() {
|
|
26
|
+
const headers: Record<string, string> = { 'Content-Type': 'application/json' };
|
|
27
|
+
if (this.apiKey) headers['x-persona-apikey'] = this.apiKey;
|
|
28
|
+
return headers;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Fetch messages for a session from the Persona REST API
|
|
33
|
+
*/
|
|
34
|
+
async sync(sessionId: Session): Promise<PersonaMessage[]> {
|
|
35
|
+
if (!sessionId) return [];
|
|
36
|
+
const url = `${this.endpoint}/sessions/${encodeURIComponent(String(sessionId))}/messages?page=1&size=${this.pageSize}`;
|
|
37
|
+
|
|
38
|
+
try {
|
|
39
|
+
const res = await fetch(url, { method: 'GET', headers: this.buildHeaders() });
|
|
40
|
+
if (!res.ok) {
|
|
41
|
+
// Log and return empty list on error
|
|
42
|
+
this.logger?.warn('PersonaSessionStorage: failed to fetch messages', res.status, await res.text());
|
|
43
|
+
return [];
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const data = await res.json();
|
|
47
|
+
if (!Array.isArray(data.items)) return [];
|
|
48
|
+
|
|
49
|
+
// Try to normalize createdAt strings to Date objects
|
|
50
|
+
const messages: PersonaMessage[] = data.items.map((m: any) => {
|
|
51
|
+
const msg: PersonaMessage = {
|
|
52
|
+
id: m.id ?? m.messageId ?? undefined,
|
|
53
|
+
protocol: m.protocol,
|
|
54
|
+
thought: m.thought,
|
|
55
|
+
text: m.text ?? '',
|
|
56
|
+
image: m.image,
|
|
57
|
+
type: m.type ?? 'text',
|
|
58
|
+
role: m.role ?? 'assistant',
|
|
59
|
+
file: m.file,
|
|
60
|
+
sources: m.sources,
|
|
61
|
+
sessionId: m.sessionId ?? sessionId,
|
|
62
|
+
finishReason: m.finishReason,
|
|
63
|
+
functionCalls: m.functionCalls,
|
|
64
|
+
functionResponse: m.functionResponse,
|
|
65
|
+
status: m.status,
|
|
66
|
+
createdAt: m.createdAt ? new Date(m.createdAt) : undefined,
|
|
67
|
+
metadata: m.metadata,
|
|
68
|
+
};
|
|
69
|
+
return msg;
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
return messages;
|
|
73
|
+
} catch (err) {
|
|
74
|
+
// Network or parsing error
|
|
75
|
+
this.logger?.warn('PersonaSessionStorage: error fetching messages', err);
|
|
76
|
+
return [];
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
async archive(sessionId: Session): Promise<void> {
|
|
81
|
+
return this._visiblility(sessionId, 'archived');
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
async unarchive(sessionId: Session): Promise<void> {
|
|
85
|
+
return this._visiblility(sessionId, 'active');
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
async delete(sessionId: Session): Promise<void> {
|
|
89
|
+
return this._visiblility(sessionId, 'deleted');
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
async _visiblility(sessionId: Session, visibility: 'active' | 'deleted' | 'archived'): Promise<void> {
|
|
93
|
+
if (!sessionId) return;
|
|
94
|
+
const url = `${this.endpoint}/sessions/${encodeURIComponent(String(sessionId))}/visibility`;
|
|
95
|
+
try {
|
|
96
|
+
const body = JSON.stringify({ visibility });
|
|
97
|
+
const res = await fetch(url, { method: 'PUT', headers: this.buildHeaders(), body });
|
|
98
|
+
if (!res.ok) {
|
|
99
|
+
const text = await res.text();
|
|
100
|
+
this.logger?.warn('PersonaSessionStorage: failed to set session visibility', res.status, text);
|
|
101
|
+
throw new Error(`Failed to set session visibility: ${res.status}`);
|
|
102
|
+
}
|
|
103
|
+
return;
|
|
104
|
+
} catch (err) {
|
|
105
|
+
throw err;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
async clearAll(): Promise<void> {
|
|
110
|
+
return Promise.reject(new Error('PersonaAPIMessageStorage: clearAll not supported'));
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
async list(agentId: string, userId?: string): Promise<ThreadData[]> {
|
|
114
|
+
const params = new URLSearchParams({ page: '1', size: '100', visibility: 'active' });
|
|
115
|
+
if (userId) params.set('userId', userId);
|
|
116
|
+
if (agentId) params.set('agentId', agentId);
|
|
117
|
+
|
|
118
|
+
const url = `${this.endpoint}/sessions?${params.toString()}`;
|
|
119
|
+
try {
|
|
120
|
+
const res = await fetch(url, { method: 'GET', headers: this.buildHeaders() });
|
|
121
|
+
if (!res.ok) {
|
|
122
|
+
return [];
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const data = await res.json();
|
|
126
|
+
if (!Array.isArray(data.items)) return [];
|
|
127
|
+
const threads: ThreadData[] = data.items.map((item: any) => ({
|
|
128
|
+
threadId: item.code || item.id,
|
|
129
|
+
title: item.title || `Session ${item.code || item.id}`,
|
|
130
|
+
status: item.status === 'archived' ? 'archived' : 'regular',
|
|
131
|
+
}));
|
|
132
|
+
|
|
133
|
+
return threads;
|
|
134
|
+
} catch (err) {
|
|
135
|
+
return [];
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|