@applica-software-guru/persona-sdk 0.1.84 → 0.1.86
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 +92 -0
- package/dist/bundle.cjs.js +18 -18
- package/dist/bundle.cjs.js.map +1 -1
- package/dist/bundle.es.js +3345 -2891
- package/dist/bundle.es.js.map +1 -1
- package/dist/bundle.iife.js +17 -17
- package/dist/bundle.iife.js.map +1 -1
- package/dist/bundle.umd.js +17 -17
- package/dist/bundle.umd.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/messages.d.ts +2 -0
- package/dist/messages.d.ts.map +1 -1
- package/dist/protocol/webrtc.d.ts.map +1 -1
- package/dist/protocol/websocket.d.ts.map +1 -1
- package/dist/runtime/context.d.ts +34 -0
- package/dist/runtime/context.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 +35 -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 +4 -22
- package/dist/runtime.d.ts.map +1 -1
- package/dist/storage/base.d.ts +19 -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 +30 -0
- package/dist/storage/persona.d.ts.map +1 -0
- package/dist/types.d.ts +51 -1
- package/dist/types.d.ts.map +1 -1
- package/package.json +4 -3
- package/playground/src/chat.tsx +58 -65
- package/playground/src/components/assistant-ui/thread-list.tsx +45 -12
- package/playground/src/components/assistant-ui/thread.tsx +34 -96
- package/playground/src/components/assistant-ui/threadlist-sidebar.tsx +59 -0
- package/playground/src/components/chat/logging.tsx +53 -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 +0 -2
- package/playground/src/hooks/theme.ts +70 -0
- package/playground/src/hooks/use-mobile.ts +19 -0
- package/src/index.ts +1 -0
- package/src/messages.ts +98 -8
- package/src/protocol/webrtc.ts +1 -0
- package/src/protocol/websocket.ts +5 -2
- package/src/runtime/context.ts +88 -0
- package/src/runtime/handlers.ts +276 -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 +120 -0
- package/src/runtime/utils.ts +46 -0
- package/src/runtime.tsx +226 -326
- package/src/storage/base.ts +21 -0
- package/src/storage/index.ts +2 -0
- package/src/storage/persona.ts +132 -0
- package/src/types.ts +64 -2
package/src/runtime.tsx
CHANGED
|
@@ -1,80 +1,28 @@
|
|
|
1
|
-
import { useState, useEffect, useCallback, PropsWithChildren,
|
|
1
|
+
import { useState, useEffect, useCallback, PropsWithChildren, useMemo, useRef } from 'react';
|
|
2
2
|
import {
|
|
3
3
|
useExternalStoreRuntime,
|
|
4
|
-
AppendMessage,
|
|
5
4
|
AssistantRuntimeProvider,
|
|
6
5
|
CompositeAttachmentAdapter,
|
|
7
6
|
SimpleImageAttachmentAdapter,
|
|
8
7
|
} from '@assistant-ui/react';
|
|
9
|
-
import {
|
|
10
|
-
PersonaConfig,
|
|
11
|
-
PersonaMessage,
|
|
12
|
-
PersonaPacket,
|
|
13
|
-
PersonaProtocol,
|
|
14
|
-
PersonaProtocolBaseConfig,
|
|
15
|
-
PersonaReasoning,
|
|
16
|
-
PersonaResponse,
|
|
17
|
-
PersonaTransaction,
|
|
18
|
-
ProtocolStatus,
|
|
19
|
-
Session,
|
|
20
|
-
} from './types';
|
|
8
|
+
import { PersonaConfig, PersonaMessage, PersonaResponse, ProtocolStatus, Session, ThreadData } from './types';
|
|
21
9
|
import { parseMessages, convertMessage } from './messages';
|
|
22
|
-
import {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
PersonaWebSocketProtocol,
|
|
29
|
-
PersonaWebSocketProtocolConfig,
|
|
30
|
-
} from './protocol';
|
|
31
|
-
|
|
32
|
-
type PersonaRuntimeContextType = {
|
|
33
|
-
protocols: PersonaProtocol[];
|
|
34
|
-
protocolsStatus: Map<string, ProtocolStatus>;
|
|
35
|
-
getMessages: () => PersonaMessage[];
|
|
36
|
-
};
|
|
37
|
-
|
|
38
|
-
const PersonaRuntimeContext = createContext<PersonaRuntimeContextType | undefined>(undefined);
|
|
39
|
-
|
|
40
|
-
function fileToBase64(file: File): Promise<string> {
|
|
41
|
-
return new Promise((resolve, reject) => {
|
|
42
|
-
const reader = new FileReader();
|
|
43
|
-
reader.readAsDataURL(file); // Converte il file in Data URL (base64)
|
|
44
|
-
|
|
45
|
-
reader.onload = () => {
|
|
46
|
-
//remove data url using ;base64, to split
|
|
47
|
-
const base64 = reader.result as string;
|
|
48
|
-
const base64WithoutPrefix = base64.split(';base64,')[1];
|
|
49
|
-
resolve(base64WithoutPrefix);
|
|
50
|
-
};
|
|
10
|
+
import { initializeProtocols } from './runtime/protocols';
|
|
11
|
+
import { createPacketListener } from './runtime/listeners';
|
|
12
|
+
import { createOnNewHandler, createOnEditHandler, createOnReloadHandler, createOnCancelHandler } from './runtime/handlers';
|
|
13
|
+
import { PersonaRuntimeContext } from './runtime/context';
|
|
14
|
+
import { createThreadListAdapter } from './runtime/threads';
|
|
15
|
+
import { PersonaSessionStorage } from './storage';
|
|
51
16
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
17
|
+
export {
|
|
18
|
+
usePersonaRuntime,
|
|
19
|
+
usePersonaRuntimeProtocol,
|
|
20
|
+
usePersonaRuntimeEndpoint,
|
|
21
|
+
usePersonaRuntimeWebRTCProtocol,
|
|
22
|
+
usePersonaRuntimeMessages,
|
|
23
|
+
} from './runtime/context';
|
|
57
24
|
|
|
58
|
-
|
|
59
|
-
const extractors: { [key: string]: (protocol: PersonaProtocol) => string } = {
|
|
60
|
-
rest: (protocol) => (protocol as PersonaRESTProtocol).config.apiUrl,
|
|
61
|
-
webrtc: (protocol) =>
|
|
62
|
-
(protocol as PersonaWebRTCProtocol).config.webrtcUrl.replace('/webrtc', '').replace('ws://', 'http://').replace('wss://', 'https://'),
|
|
63
|
-
websocket: (protocol) =>
|
|
64
|
-
(protocol as PersonaWebSocketProtocol).config.webSocketUrl
|
|
65
|
-
.replace('/websocket', '')
|
|
66
|
-
.replace('ws://', 'http://')
|
|
67
|
-
.replace('wss://', 'https://'),
|
|
68
|
-
};
|
|
69
|
-
|
|
70
|
-
for (const protocol of protocols) {
|
|
71
|
-
const extractor = extractors[protocol.getName()];
|
|
72
|
-
if (extractor) {
|
|
73
|
-
return extractor(protocol);
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
return null;
|
|
77
|
-
}
|
|
25
|
+
export type { PersonaMessage, PersonaResponse, ThreadData };
|
|
78
26
|
|
|
79
27
|
function PersonaRuntimeProviderInner({
|
|
80
28
|
dev = false,
|
|
@@ -82,239 +30,264 @@ function PersonaRuntimeProviderInner({
|
|
|
82
30
|
protocols: _protocols,
|
|
83
31
|
logger,
|
|
84
32
|
children,
|
|
85
|
-
session:
|
|
33
|
+
session: initialSession = 'new',
|
|
86
34
|
transformMessages,
|
|
35
|
+
threads = false,
|
|
36
|
+
sessionStorage: _sessionStorage,
|
|
37
|
+
onThreadCreate,
|
|
38
|
+
onThreadSwitch,
|
|
39
|
+
onThreadRename,
|
|
40
|
+
onThreadArchive,
|
|
41
|
+
onThreadUnarchive,
|
|
42
|
+
onThreadDelete,
|
|
87
43
|
...config
|
|
88
44
|
}: Readonly<PersonaConfig>) {
|
|
89
45
|
const [isRunning, setIsRunning] = useState(false);
|
|
90
46
|
const [messages, setMessages] = useState<PersonaMessage[]>([]);
|
|
91
|
-
const [session, setSession] = useState<Session>(defaultSession);
|
|
92
47
|
const [protocolsStatus, setProtocolsStatus] = useState<Map<string, ProtocolStatus>>(new Map());
|
|
93
48
|
const didMount = useRef(false);
|
|
49
|
+
const isSwitchingThread = useRef(false);
|
|
50
|
+
|
|
51
|
+
// Initialize message storage
|
|
52
|
+
const sessionStorage = useMemo(
|
|
53
|
+
() =>
|
|
54
|
+
_sessionStorage ||
|
|
55
|
+
new PersonaSessionStorage({
|
|
56
|
+
apiKey: config.apiKey!,
|
|
57
|
+
endpoint: baseUrl || 'https://persona.applica.guru/api',
|
|
58
|
+
}),
|
|
59
|
+
[_sessionStorage, baseUrl, config.apiKey],
|
|
60
|
+
);
|
|
94
61
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
}
|
|
62
|
+
// Thread management state
|
|
63
|
+
const [threadList, setThreadList] = useState<ThreadData[]>([]);
|
|
64
|
+
const [currentThreadId, setCurrentThreadId] = useState<string>('');
|
|
99
65
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
const baseWebSocketProtocol = dev ? 'ws' : 'wss';
|
|
104
|
-
let availableProtocols = Object.keys(_protocols)
|
|
105
|
-
.map((key) => {
|
|
106
|
-
switch (key) {
|
|
107
|
-
case 'rest':
|
|
108
|
-
const restConfig: PersonaProtocolBaseConfig | boolean | undefined = _protocols[key];
|
|
109
|
-
if (restConfig === true) {
|
|
110
|
-
return new PersonaRESTProtocol({
|
|
111
|
-
apiUrl: `${baseEndpointProtocol}://${baseEndpoint}`,
|
|
112
|
-
apiKey: config.apiKey,
|
|
113
|
-
agentId: config.agentId,
|
|
114
|
-
logger,
|
|
115
|
-
});
|
|
116
|
-
} else if (typeof restConfig === 'object' && restConfig !== null) {
|
|
117
|
-
return new PersonaRESTProtocol(restConfig as PersonaRESTProtocolConfig);
|
|
118
|
-
} else {
|
|
119
|
-
return null;
|
|
120
|
-
}
|
|
121
|
-
case 'webrtc':
|
|
122
|
-
const webrtcConfig: PersonaProtocolBaseConfig | boolean | undefined = _protocols[key];
|
|
123
|
-
if (webrtcConfig === true) {
|
|
124
|
-
return new PersonaWebRTCProtocol({
|
|
125
|
-
webrtcUrl: `${baseWebSocketProtocol}://${baseEndpoint}/webrtc`,
|
|
126
|
-
apiKey: config.apiKey,
|
|
127
|
-
agentId: config.agentId,
|
|
128
|
-
logger,
|
|
129
|
-
});
|
|
130
|
-
} else if (typeof webrtcConfig === 'object' && webrtcConfig !== null) {
|
|
131
|
-
return new PersonaWebRTCProtocol(webrtcConfig as PersonaWebRTCProtocolConfig);
|
|
132
|
-
} else {
|
|
133
|
-
return null;
|
|
134
|
-
}
|
|
135
|
-
case 'websocket':
|
|
136
|
-
const websocketConfig: PersonaProtocolBaseConfig | boolean | undefined = _protocols[key];
|
|
137
|
-
if (websocketConfig === true) {
|
|
138
|
-
return new PersonaWebSocketProtocol({
|
|
139
|
-
webSocketUrl: `${baseWebSocketProtocol}://${baseEndpoint}/websocket`,
|
|
140
|
-
apiKey: config.apiKey,
|
|
141
|
-
agentId: config.agentId,
|
|
142
|
-
logger,
|
|
143
|
-
});
|
|
144
|
-
} else if (typeof websocketConfig === 'object' && websocketConfig !== null) {
|
|
145
|
-
return new PersonaWebSocketProtocol(websocketConfig as PersonaWebSocketProtocolConfig);
|
|
146
|
-
} else {
|
|
147
|
-
return null;
|
|
148
|
-
}
|
|
149
|
-
default:
|
|
150
|
-
throw new Error(`Unknown protocol: ${key}`);
|
|
151
|
-
}
|
|
152
|
-
})
|
|
153
|
-
.filter((protocol) => protocol !== null) as PersonaProtocol[];
|
|
154
|
-
|
|
155
|
-
if (config.tools) {
|
|
156
|
-
availableProtocols.push(
|
|
157
|
-
new PersonaTransactionProtocol({
|
|
158
|
-
apiUrl: `${baseEndpointProtocol}://${baseEndpoint}`,
|
|
159
|
-
apiKey: config.apiKey,
|
|
160
|
-
agentId: config.agentId,
|
|
161
|
-
tools: config.tools, // Pass raw tools
|
|
162
|
-
logger,
|
|
163
|
-
}),
|
|
164
|
-
);
|
|
165
|
-
}
|
|
166
|
-
return availableProtocols;
|
|
167
|
-
}
|
|
168
|
-
throw new Error('Invalid protocols configuration');
|
|
169
|
-
}, []);
|
|
66
|
+
// Use refs to avoid re-renders and effect loops
|
|
67
|
+
const currentThreadRef = useRef<Session>(typeof initialSession === 'string' ? initialSession : 'new');
|
|
68
|
+
const hasInitialized = useRef(false);
|
|
170
69
|
|
|
70
|
+
// Load threads on mount (only once)
|
|
171
71
|
useEffect(() => {
|
|
172
|
-
if (
|
|
72
|
+
if (!threads || hasInitialized.current) {
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
hasInitialized.current = true;
|
|
76
|
+
|
|
77
|
+
sessionStorage
|
|
78
|
+
.list(config.agentId, config.userId)
|
|
79
|
+
.then((threads) => {
|
|
80
|
+
if (threads?.length > 0) {
|
|
81
|
+
setThreadList(threads);
|
|
82
|
+
setCurrentThreadId(threads[0].threadId);
|
|
83
|
+
}
|
|
84
|
+
})
|
|
85
|
+
.catch((error) => logger?.error('Failed to load threads:', error));
|
|
86
|
+
}, [threads, sessionStorage, config.agentId, config.userId, logger]);
|
|
87
|
+
|
|
88
|
+
// Initialize protocols
|
|
89
|
+
const protocols = useMemo(
|
|
90
|
+
() =>
|
|
91
|
+
initializeProtocols(_protocols, {
|
|
92
|
+
dev: dev || false,
|
|
93
|
+
baseUrl,
|
|
94
|
+
apiKey: config.apiKey,
|
|
95
|
+
agentId: config.agentId,
|
|
96
|
+
userId: config.userId,
|
|
97
|
+
tools: config.tools,
|
|
98
|
+
logger,
|
|
99
|
+
}),
|
|
100
|
+
[_protocols, dev, baseUrl, config.apiKey, config.agentId, config.userId, config.tools, logger],
|
|
101
|
+
);
|
|
173
102
|
|
|
103
|
+
// Setup protocol listeners (only once on mount)
|
|
104
|
+
useEffect(() => {
|
|
105
|
+
if (didMount.current) {
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
174
108
|
didMount.current = true;
|
|
175
109
|
logger?.debug(
|
|
176
|
-
'
|
|
177
|
-
protocols.map((
|
|
110
|
+
'Setting up protocols:',
|
|
111
|
+
protocols.map((p) => p.getName()),
|
|
178
112
|
);
|
|
179
113
|
protocols.forEach((protocol) => {
|
|
180
|
-
protocol.setSession(
|
|
114
|
+
protocol.setSession(currentThreadRef.current);
|
|
181
115
|
protocol.clearListeners();
|
|
116
|
+
|
|
117
|
+
// Status change listener
|
|
182
118
|
protocol.addStatusChangeListener((status: ProtocolStatus) => {
|
|
183
|
-
logger?.debug(`${protocol.getName()}
|
|
184
|
-
|
|
185
|
-
|
|
119
|
+
logger?.debug(`${protocol.getName()} status: ${status}`);
|
|
120
|
+
setProtocolsStatus((prev) => {
|
|
121
|
+
const next = new Map(prev);
|
|
122
|
+
next.set(protocol.getName(), status);
|
|
123
|
+
return next;
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
// Only send initial packets on first connection (not during thread switches)
|
|
127
|
+
if (status === 'connected' && !isSwitchingThread.current) {
|
|
186
128
|
if (config.context) {
|
|
187
|
-
protocol
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
command: 'set_initial_context',
|
|
191
|
-
|
|
192
|
-
}
|
|
193
|
-
});
|
|
129
|
+
protocol
|
|
130
|
+
.sendPacket({
|
|
131
|
+
type: 'command',
|
|
132
|
+
payload: { command: 'set_initial_context', arguments: config.context },
|
|
133
|
+
})
|
|
134
|
+
.catch(() => {});
|
|
194
135
|
}
|
|
136
|
+
|
|
195
137
|
if (config.tools && Array.isArray(config.tools)) {
|
|
196
|
-
protocol
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
command: 'set_local_tools',
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
},
|
|
203
|
-
},
|
|
204
|
-
});
|
|
138
|
+
protocol
|
|
139
|
+
.sendPacket({
|
|
140
|
+
type: 'command',
|
|
141
|
+
payload: { command: 'set_local_tools', arguments: { tools: config.tools.map((t) => t.schema) } },
|
|
142
|
+
})
|
|
143
|
+
.catch(() => {});
|
|
205
144
|
}
|
|
206
145
|
}
|
|
207
|
-
setProtocolsStatus(new Map(protocolsStatus));
|
|
208
146
|
});
|
|
209
|
-
protocol.addPacketListener((message: PersonaPacket) => {
|
|
210
|
-
if (message.type === 'message') {
|
|
211
|
-
const personaMessage = message.payload as PersonaMessage;
|
|
212
|
-
if (personaMessage?.finishReason === 'stop' && !personaMessage?.functionResponse && !personaMessage?.thought) {
|
|
213
|
-
setIsRunning(false);
|
|
214
|
-
}
|
|
215
147
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
}
|
|
148
|
+
// Packet listener
|
|
149
|
+
protocol.addPacketListener(createPacketListener(protocol, setMessages, setIsRunning, protocols, transformMessages));
|
|
219
150
|
|
|
220
|
-
|
|
221
|
-
const newMessages = parseMessages([...currentConversation, ...[{ ...personaMessage, protocol: protocol.getName() }]]);
|
|
222
|
-
return transformMessages ? transformMessages(newMessages) : newMessages;
|
|
223
|
-
});
|
|
224
|
-
} else if (message.type === 'reasoning') {
|
|
225
|
-
const reasoning = message.payload as PersonaReasoning;
|
|
226
|
-
const personaMessage: PersonaMessage = { type: 'reasoning', text: reasoning.thought, role: 'assistant', finishReason: 'stop' };
|
|
227
|
-
|
|
228
|
-
setMessages((currentConversation) => {
|
|
229
|
-
const newMessages = parseMessages([...currentConversation, ...[{ ...personaMessage, protocol: protocol.getName() }]]);
|
|
230
|
-
return transformMessages ? transformMessages(newMessages) : newMessages;
|
|
231
|
-
});
|
|
232
|
-
} else if (message.type === 'transaction') {
|
|
233
|
-
protocols.filter((p) => p !== protocol).forEach((p) => p.onTransaction(message.payload as PersonaTransaction));
|
|
234
|
-
}
|
|
235
|
-
});
|
|
151
|
+
// Auto-connect if configured
|
|
236
152
|
if (protocol.autostart && protocol.status === 'disconnected') {
|
|
237
|
-
logger?.debug(`
|
|
238
|
-
protocol.connect(
|
|
153
|
+
logger?.debug(`Auto-connecting protocol: ${protocol.getName()}`);
|
|
154
|
+
protocol.connect(currentThreadRef.current).catch(() => {});
|
|
239
155
|
setIsRunning(false);
|
|
240
156
|
}
|
|
241
157
|
});
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
if (!
|
|
248
|
-
setMessages(
|
|
249
|
-
const newMessages = [
|
|
250
|
-
...currentConversation,
|
|
251
|
-
{ role: 'assistant' as const, type: 'text' as const, text: 'No protocol is connected.' },
|
|
252
|
-
];
|
|
253
|
-
return transformMessages ? transformMessages(newMessages) : newMessages;
|
|
254
|
-
});
|
|
158
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
159
|
+
}, []); // Run only once
|
|
160
|
+
|
|
161
|
+
// Handle thread switching
|
|
162
|
+
useEffect(() => {
|
|
163
|
+
if (!threads || !currentThreadId) {
|
|
164
|
+
setMessages([]);
|
|
255
165
|
return;
|
|
256
166
|
}
|
|
257
167
|
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
const
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
168
|
+
if (isSwitchingThread.current || currentThreadId === currentThreadRef.current) {
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// Mark as switching to prevent concurrent switches
|
|
173
|
+
isSwitchingThread.current = true;
|
|
174
|
+
logger?.info('Switching to thread:', currentThreadId);
|
|
175
|
+
|
|
176
|
+
const switchThread = async () => {
|
|
177
|
+
try {
|
|
178
|
+
// Disconnect active protocols in parallel
|
|
179
|
+
const connectedProtocols = protocols.filter((p) => p.status === 'connected');
|
|
180
|
+
if (connectedProtocols.length > 0) {
|
|
181
|
+
await Promise.allSettled(connectedProtocols.map((p) => p.disconnect()));
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// Update thread reference
|
|
185
|
+
currentThreadRef.current = currentThreadId;
|
|
186
|
+
|
|
187
|
+
// Set new session on all protocols
|
|
188
|
+
await Promise.allSettled(protocols.map((p) => p.setSession(currentThreadId)));
|
|
189
|
+
|
|
190
|
+
// Load messages from storage
|
|
191
|
+
const loadedMessages = await sessionStorage.sync(currentThreadId);
|
|
192
|
+
const processed = parseMessages(loadedMessages);
|
|
193
|
+
setMessages(transformMessages ? transformMessages(processed) : processed);
|
|
194
|
+
logger?.debug(`Loaded ${loadedMessages.length} messages for thread ${currentThreadId}`);
|
|
195
|
+
|
|
196
|
+
// Reconnect autostart protocols
|
|
197
|
+
const autostartProtocols = protocols.filter((p) => p.autostart);
|
|
198
|
+
if (autostartProtocols.length > 0) {
|
|
199
|
+
await Promise.allSettled(autostartProtocols.map((p) => p.connect(currentThreadId)));
|
|
279
200
|
}
|
|
201
|
+
} catch (error) {
|
|
202
|
+
logger?.error('Failed to switch thread:', error);
|
|
203
|
+
setMessages([]);
|
|
204
|
+
} finally {
|
|
205
|
+
isSwitchingThread.current = false;
|
|
280
206
|
}
|
|
281
|
-
}
|
|
207
|
+
};
|
|
282
208
|
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
role: 'user',
|
|
286
|
-
text: message.content[0].text,
|
|
287
|
-
type: 'text',
|
|
288
|
-
});
|
|
289
|
-
}
|
|
290
|
-
logger?.debug('Sending message:', content);
|
|
291
|
-
await protocol?.sendPacket({ type: 'request', payload: content });
|
|
292
|
-
};
|
|
209
|
+
switchThread();
|
|
210
|
+
}, [currentThreadId, threads, protocols, sessionStorage, transformMessages, logger]);
|
|
293
211
|
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
212
|
+
// Message handlers
|
|
213
|
+
const onNew = useCallback(createOnNewHandler(protocols, setMessages, setIsRunning, transformMessages, logger), [
|
|
214
|
+
protocols,
|
|
215
|
+
transformMessages,
|
|
216
|
+
logger,
|
|
217
|
+
]);
|
|
300
218
|
|
|
301
|
-
const
|
|
302
|
-
|
|
303
|
-
|
|
219
|
+
const onEdit = useCallback(createOnEditHandler(protocols, messages, setMessages, setIsRunning, transformMessages, logger), [
|
|
220
|
+
protocols,
|
|
221
|
+
messages,
|
|
222
|
+
transformMessages,
|
|
223
|
+
logger,
|
|
224
|
+
]);
|
|
304
225
|
|
|
305
|
-
const
|
|
306
|
-
|
|
307
|
-
|
|
226
|
+
const onReload = useCallback(createOnReloadHandler(protocols, messages, setMessages, setIsRunning, transformMessages, logger), [
|
|
227
|
+
protocols,
|
|
228
|
+
messages,
|
|
229
|
+
transformMessages,
|
|
230
|
+
logger,
|
|
231
|
+
]);
|
|
232
|
+
|
|
233
|
+
const onCancel = useCallback(createOnCancelHandler(protocols, setMessages, setIsRunning, transformMessages), [
|
|
234
|
+
protocols,
|
|
235
|
+
transformMessages,
|
|
236
|
+
]);
|
|
237
|
+
|
|
238
|
+
const handleSetMessages = useCallback(
|
|
239
|
+
(newMessages: readonly PersonaMessage[]) => {
|
|
240
|
+
const processed = parseMessages([...newMessages]);
|
|
241
|
+
setMessages(transformMessages ? transformMessages(processed) : processed);
|
|
242
|
+
},
|
|
243
|
+
[transformMessages],
|
|
244
|
+
);
|
|
308
245
|
|
|
246
|
+
const getMessages = useCallback(() => messages, [messages]);
|
|
247
|
+
|
|
248
|
+
// Thread list adapter
|
|
249
|
+
const threadListAdapter = useMemo(() => {
|
|
250
|
+
if (!threads) return undefined;
|
|
251
|
+
|
|
252
|
+
return createThreadListAdapter(
|
|
253
|
+
currentThreadId,
|
|
254
|
+
threadList,
|
|
255
|
+
setCurrentThreadId,
|
|
256
|
+
setThreadList,
|
|
257
|
+
sessionStorage,
|
|
258
|
+
onThreadCreate,
|
|
259
|
+
onThreadSwitch,
|
|
260
|
+
onThreadArchive,
|
|
261
|
+
onThreadUnarchive,
|
|
262
|
+
onThreadDelete,
|
|
263
|
+
logger,
|
|
264
|
+
);
|
|
265
|
+
}, [
|
|
266
|
+
threads,
|
|
267
|
+
currentThreadId,
|
|
268
|
+
threadList,
|
|
269
|
+
sessionStorage,
|
|
270
|
+
onThreadCreate,
|
|
271
|
+
onThreadSwitch,
|
|
272
|
+
onThreadArchive,
|
|
273
|
+
onThreadUnarchive,
|
|
274
|
+
onThreadDelete,
|
|
275
|
+
logger,
|
|
276
|
+
]);
|
|
277
|
+
|
|
278
|
+
// Create runtime
|
|
309
279
|
const runtime = useExternalStoreRuntime({
|
|
310
280
|
isRunning,
|
|
311
281
|
messages,
|
|
312
282
|
convertMessage,
|
|
283
|
+
setMessages: handleSetMessages,
|
|
313
284
|
onNew,
|
|
285
|
+
onEdit,
|
|
314
286
|
onCancel,
|
|
315
287
|
onReload,
|
|
316
288
|
adapters: {
|
|
317
289
|
attachments: new CompositeAttachmentAdapter([new SimpleImageAttachmentAdapter()]),
|
|
290
|
+
...(threadListAdapter ? { threadList: threadListAdapter } : {}),
|
|
318
291
|
},
|
|
319
292
|
});
|
|
320
293
|
|
|
@@ -325,79 +298,6 @@ function PersonaRuntimeProviderInner({
|
|
|
325
298
|
);
|
|
326
299
|
}
|
|
327
300
|
|
|
328
|
-
function PersonaRuntimeProvider({ children, ...config }: PropsWithChildren<PersonaConfig>) {
|
|
301
|
+
export function PersonaRuntimeProvider({ children, ...config }: PropsWithChildren<PersonaConfig>) {
|
|
329
302
|
return <PersonaRuntimeProviderInner {...config}>{children}</PersonaRuntimeProviderInner>;
|
|
330
303
|
}
|
|
331
|
-
|
|
332
|
-
function usePersonaRuntime(): PersonaRuntimeContextType {
|
|
333
|
-
const context = useContext(PersonaRuntimeContext);
|
|
334
|
-
if (!context) {
|
|
335
|
-
throw new Error('usePersonaRuntime must be used within a PersonaRuntimeProvider');
|
|
336
|
-
}
|
|
337
|
-
return context;
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
/**
|
|
341
|
-
* Retrieves a specific protocol instance from the PersonaRuntimeContext.
|
|
342
|
-
*
|
|
343
|
-
* @param protocol - The name of the protocol to use.
|
|
344
|
-
* @returns {PersonaProtocol | null} - The protocol instance or null if not found.
|
|
345
|
-
* @throws {Error} - If the hook is used outside of a PersonaRuntimeProvider.
|
|
346
|
-
*/
|
|
347
|
-
function usePersonaRuntimeProtocol(protocol: string): PersonaProtocol | null {
|
|
348
|
-
const context = useContext(PersonaRuntimeContext);
|
|
349
|
-
if (!context) {
|
|
350
|
-
throw new Error('usePersonaRuntimeProtocol must be used within a PersonaRuntimeProvider');
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
const protocolInstance = context.protocols.find((p) => p.getName() === protocol);
|
|
354
|
-
if (!protocolInstance) {
|
|
355
|
-
return null;
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
const status = context.protocolsStatus.get(protocolInstance.getName());
|
|
359
|
-
|
|
360
|
-
return {
|
|
361
|
-
...protocolInstance,
|
|
362
|
-
connect: protocolInstance.connect.bind(protocolInstance),
|
|
363
|
-
disconnect: protocolInstance.disconnect.bind(protocolInstance),
|
|
364
|
-
sendPacket: protocolInstance.sendPacket.bind(protocolInstance),
|
|
365
|
-
setSession: protocolInstance.setSession.bind(protocolInstance),
|
|
366
|
-
addStatusChangeListener: protocolInstance.addStatusChangeListener.bind(protocolInstance),
|
|
367
|
-
addPacketListener: protocolInstance.addPacketListener.bind(protocolInstance),
|
|
368
|
-
getName: protocolInstance.getName.bind(protocolInstance),
|
|
369
|
-
getPriority: protocolInstance.getPriority.bind(protocolInstance),
|
|
370
|
-
status: status || protocolInstance.status,
|
|
371
|
-
};
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
function usePersonaRuntimeEndpoint(): string | null {
|
|
375
|
-
const context = useContext(PersonaRuntimeContext);
|
|
376
|
-
if (!context) {
|
|
377
|
-
throw new Error('usePersonaRuntimeEndpoint must be used within a PersonaRuntimeProvider');
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
return getFirstAvailableProtocolEndpoint(context.protocols);
|
|
381
|
-
}
|
|
382
|
-
|
|
383
|
-
function usePersonaRuntimeWebRTCProtocol(): PersonaWebRTCProtocol | null {
|
|
384
|
-
return usePersonaRuntimeProtocol('webrtc') as PersonaWebRTCProtocol;
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
function usePersonaRuntimeMessages(): PersonaMessage[] {
|
|
388
|
-
const context = useContext(PersonaRuntimeContext);
|
|
389
|
-
if (!context) {
|
|
390
|
-
throw new Error('usePersonaRuntimeMessages must be used within a PersonaRuntimeProvider');
|
|
391
|
-
}
|
|
392
|
-
return context.getMessages();
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
export {
|
|
396
|
-
PersonaRuntimeProvider,
|
|
397
|
-
usePersonaRuntimeEndpoint,
|
|
398
|
-
usePersonaRuntime,
|
|
399
|
-
usePersonaRuntimeProtocol,
|
|
400
|
-
usePersonaRuntimeWebRTCProtocol,
|
|
401
|
-
usePersonaRuntimeMessages,
|
|
402
|
-
};
|
|
403
|
-
export type { PersonaMessage, PersonaResponse };
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { PersonaMessage, Session, ThreadData } from '../types';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Storage interface for messages with thread management support.
|
|
5
|
+
*/
|
|
6
|
+
export interface SessionStorage {
|
|
7
|
+
/**
|
|
8
|
+
* Sync messages for a specific session (load existing messages)
|
|
9
|
+
*/
|
|
10
|
+
sync(sessionId: Session): Promise<PersonaMessage[]>;
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Remove messages for a specific session (if supported)
|
|
14
|
+
*/
|
|
15
|
+
remove(sessionId: Session): Promise<void>;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* List all threads for a specific user
|
|
19
|
+
*/
|
|
20
|
+
list(agentId: string, userId?: string): Promise<ThreadData[]>;
|
|
21
|
+
}
|