@applica-software-guru/persona-sdk 0.1.84 → 0.1.85
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 +104 -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 +18 -18
- 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 +51 -66
- 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
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { useCallback, useEffect, useState } from 'react';
|
|
2
|
+
|
|
3
|
+
type Theme = 'light' | 'dark' | 'auto';
|
|
4
|
+
const STORAGE_KEY = 'persona_theme_preference';
|
|
5
|
+
|
|
6
|
+
function getSystemTheme(): 'light' | 'dark' {
|
|
7
|
+
if (typeof window === 'undefined' || !window.matchMedia) return 'light';
|
|
8
|
+
return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function useColorScheme() {
|
|
12
|
+
const [theme, setTheme] = useState<Theme>(() => {
|
|
13
|
+
try {
|
|
14
|
+
const stored = localStorage.getItem(STORAGE_KEY);
|
|
15
|
+
return (stored as Theme) || 'auto';
|
|
16
|
+
} catch {
|
|
17
|
+
return 'auto';
|
|
18
|
+
}
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
const apply = useCallback((t: Theme) => {
|
|
22
|
+
const root = document.documentElement;
|
|
23
|
+
const effective = t === 'auto' ? getSystemTheme() : t;
|
|
24
|
+
if (effective === 'dark') root.classList.add('dark');
|
|
25
|
+
else root.classList.remove('dark');
|
|
26
|
+
}, []);
|
|
27
|
+
|
|
28
|
+
useEffect(() => {
|
|
29
|
+
// Apply on mount and whenever theme changes
|
|
30
|
+
apply(theme);
|
|
31
|
+
|
|
32
|
+
// Listen to system changes when in 'auto' mode
|
|
33
|
+
let mql: MediaQueryList | null = null;
|
|
34
|
+
const onChange = () => {
|
|
35
|
+
if (theme === 'auto') apply('auto');
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
if (typeof window !== 'undefined' && window.matchMedia) {
|
|
39
|
+
mql = window.matchMedia('(prefers-color-scheme: dark)');
|
|
40
|
+
if (mql.addEventListener) mql.addEventListener('change', onChange);
|
|
41
|
+
else mql.addListener(onChange as any);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return () => {
|
|
45
|
+
if (!mql) return;
|
|
46
|
+
if (mql.removeEventListener) mql.removeEventListener('change', onChange);
|
|
47
|
+
else mql.removeListener(onChange as any);
|
|
48
|
+
};
|
|
49
|
+
}, [theme, apply]);
|
|
50
|
+
|
|
51
|
+
const setThemeAndPersist = useCallback((t: Theme) => {
|
|
52
|
+
try {
|
|
53
|
+
localStorage.setItem(STORAGE_KEY, t);
|
|
54
|
+
} catch {}
|
|
55
|
+
setTheme(t);
|
|
56
|
+
}, []);
|
|
57
|
+
|
|
58
|
+
const toggle = useCallback(() => {
|
|
59
|
+
console.log('Toggling theme from');
|
|
60
|
+
setTheme((prev) => {
|
|
61
|
+
const next = prev === 'dark' ? 'light' : prev === 'light' ? 'auto' : 'dark';
|
|
62
|
+
try {
|
|
63
|
+
localStorage.setItem(STORAGE_KEY, next);
|
|
64
|
+
} catch {}
|
|
65
|
+
return next;
|
|
66
|
+
});
|
|
67
|
+
}, []);
|
|
68
|
+
|
|
69
|
+
return { theme, setTheme: setThemeAndPersist, toggle } as const;
|
|
70
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
|
|
3
|
+
const MOBILE_BREAKPOINT = 768
|
|
4
|
+
|
|
5
|
+
export function useIsMobile() {
|
|
6
|
+
const [isMobile, setIsMobile] = React.useState<boolean | undefined>(undefined)
|
|
7
|
+
|
|
8
|
+
React.useEffect(() => {
|
|
9
|
+
const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`)
|
|
10
|
+
const onChange = () => {
|
|
11
|
+
setIsMobile(window.innerWidth < MOBILE_BREAKPOINT)
|
|
12
|
+
}
|
|
13
|
+
mql.addEventListener("change", onChange)
|
|
14
|
+
setIsMobile(window.innerWidth < MOBILE_BREAKPOINT)
|
|
15
|
+
return () => mql.removeEventListener("change", onChange)
|
|
16
|
+
}, [])
|
|
17
|
+
|
|
18
|
+
return !!isMobile
|
|
19
|
+
}
|
package/src/index.ts
CHANGED
package/src/messages.ts
CHANGED
|
@@ -1,5 +1,30 @@
|
|
|
1
1
|
import { PersonaMessage } from './types';
|
|
2
|
-
import { FileMessagePart, ThreadMessageLike } from '@assistant-ui/react';
|
|
2
|
+
import { FileMessagePart, ThreadMessageLike, MessageStatus } from '@assistant-ui/react';
|
|
3
|
+
|
|
4
|
+
let messageIdCounter = 0;
|
|
5
|
+
|
|
6
|
+
export function generateMessageId(): string {
|
|
7
|
+
return `msg_${Date.now()}_${messageIdCounter++}`;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function ensureMessageId(message: PersonaMessage): PersonaMessage {
|
|
11
|
+
// Convert thought to reasoning type
|
|
12
|
+
if (message.thought) {
|
|
13
|
+
message = { ...message, type: 'reasoning' };
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
if (!message.id) {
|
|
17
|
+
return {
|
|
18
|
+
...message,
|
|
19
|
+
id: generateMessageId(),
|
|
20
|
+
createdAt: message.createdAt || new Date(),
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
if (!message.createdAt) {
|
|
24
|
+
return { ...message, createdAt: new Date() };
|
|
25
|
+
}
|
|
26
|
+
return message;
|
|
27
|
+
}
|
|
3
28
|
|
|
4
29
|
function removeEmptyMessages(messages: PersonaMessage[]): PersonaMessage[] {
|
|
5
30
|
return messages.filter((message) => {
|
|
@@ -13,10 +38,14 @@ function parseMessages(messages: PersonaMessage[]): PersonaMessage[] {
|
|
|
13
38
|
const outputMessages: PersonaMessage[] = [];
|
|
14
39
|
let currentMessage: PersonaMessage | null = null;
|
|
15
40
|
|
|
16
|
-
for (
|
|
41
|
+
for (let message of messages) {
|
|
42
|
+
// Ensure every message has an ID
|
|
43
|
+
message = ensureMessageId(message);
|
|
44
|
+
|
|
17
45
|
if (message.type === 'transaction') {
|
|
18
46
|
continue;
|
|
19
47
|
}
|
|
48
|
+
|
|
20
49
|
if (message.file) {
|
|
21
50
|
if (currentMessage) {
|
|
22
51
|
outputMessages.push(currentMessage);
|
|
@@ -39,11 +68,20 @@ function parseMessages(messages: PersonaMessage[]): PersonaMessage[] {
|
|
|
39
68
|
currentMessage &&
|
|
40
69
|
message.type === currentMessage.type &&
|
|
41
70
|
message.protocol === currentMessage.protocol &&
|
|
42
|
-
currentMessage.role === message.role
|
|
71
|
+
currentMessage.role === message.role &&
|
|
72
|
+
// CRITICAL: Only merge if the new message has the same ID (streaming chunks)
|
|
73
|
+
// OR if the current message doesn't have a finishReason yet (still streaming)
|
|
74
|
+
// This preserves branches while allowing streaming to work
|
|
75
|
+
(message.id === currentMessage.id || !currentMessage.finishReason)
|
|
43
76
|
) {
|
|
77
|
+
// Merge streaming chunks
|
|
44
78
|
currentMessage.text += message.text;
|
|
45
79
|
currentMessage.file = currentMessage.file || message.file;
|
|
80
|
+
currentMessage.finishReason = message.finishReason || currentMessage.finishReason;
|
|
81
|
+
// Keep the ID of the first chunk
|
|
82
|
+
currentMessage.id = currentMessage.id || message.id;
|
|
46
83
|
} else {
|
|
84
|
+
// Different message or current is complete - don't merge
|
|
47
85
|
if (currentMessage) {
|
|
48
86
|
outputMessages.push(currentMessage);
|
|
49
87
|
}
|
|
@@ -73,29 +111,81 @@ function convertMessage(message: PersonaMessage): ThreadMessageLike {
|
|
|
73
111
|
]
|
|
74
112
|
: [];
|
|
75
113
|
|
|
114
|
+
// Determine message status for assistant messages
|
|
115
|
+
let messageStatus: MessageStatus | undefined;
|
|
116
|
+
|
|
117
|
+
if (message.role === 'assistant' || message.role === 'function') {
|
|
118
|
+
if (message.status) {
|
|
119
|
+
// Use existing status if provided
|
|
120
|
+
messageStatus = message.status as any;
|
|
121
|
+
} else if (message.finishReason === 'stop') {
|
|
122
|
+
messageStatus = { type: 'complete', reason: 'stop' } as const;
|
|
123
|
+
} else if (message.finishReason === 'function_call') {
|
|
124
|
+
messageStatus = { type: 'complete', reason: 'stop' } as const;
|
|
125
|
+
} else if (!message.finishReason) {
|
|
126
|
+
// Message is still being generated - this will trigger optimistic updates
|
|
127
|
+
messageStatus = undefined; // Let the runtime handle in-progress state
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Handle function/tool calls
|
|
76
132
|
if (message.role === 'function') {
|
|
77
|
-
|
|
133
|
+
const result: ThreadMessageLike = {
|
|
78
134
|
id: message.id!,
|
|
79
135
|
role: 'assistant',
|
|
80
|
-
|
|
136
|
+
createdAt: message.createdAt,
|
|
81
137
|
content:
|
|
82
138
|
message.functionCalls?.map((call) => ({
|
|
83
139
|
type: 'tool-call',
|
|
84
140
|
toolName: call.name,
|
|
85
|
-
toolCallId: call.id
|
|
141
|
+
toolCallId: call.id || `tool_${Date.now()}`,
|
|
86
142
|
args: call.args,
|
|
87
143
|
result: message.functionResponse?.result,
|
|
88
144
|
})) ?? [],
|
|
145
|
+
metadata: {
|
|
146
|
+
...message.metadata,
|
|
147
|
+
custom: {
|
|
148
|
+
protocol: message.protocol,
|
|
149
|
+
sessionId: message.sessionId,
|
|
150
|
+
sources: message.sources,
|
|
151
|
+
...message.metadata?.custom,
|
|
152
|
+
},
|
|
153
|
+
},
|
|
89
154
|
};
|
|
155
|
+
|
|
156
|
+
if (messageStatus) {
|
|
157
|
+
return { ...result, status: messageStatus };
|
|
158
|
+
}
|
|
159
|
+
return result;
|
|
90
160
|
}
|
|
91
|
-
|
|
161
|
+
|
|
162
|
+
// Handle regular messages
|
|
163
|
+
const baseMessage: ThreadMessageLike = {
|
|
92
164
|
id: message.id!,
|
|
93
165
|
role: message.role,
|
|
166
|
+
createdAt: message.createdAt,
|
|
94
167
|
content:
|
|
95
168
|
message.type === 'reasoning'
|
|
96
|
-
? [{ type: 'reasoning', text: message.text }, ...files]
|
|
169
|
+
? [{ type: 'reasoning' as any, text: message.text }, ...files]
|
|
97
170
|
: [{ type: 'text', text: message.text }, ...files],
|
|
171
|
+
metadata: {
|
|
172
|
+
...message.metadata,
|
|
173
|
+
custom: {
|
|
174
|
+
protocol: message.protocol,
|
|
175
|
+
sessionId: message.sessionId,
|
|
176
|
+
sources: message.sources,
|
|
177
|
+
type: message.type,
|
|
178
|
+
...message.metadata?.custom,
|
|
179
|
+
},
|
|
180
|
+
},
|
|
98
181
|
};
|
|
182
|
+
|
|
183
|
+
// Add status only for assistant messages
|
|
184
|
+
if (message.role === 'assistant' && messageStatus) {
|
|
185
|
+
return { ...baseMessage, status: messageStatus };
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
return baseMessage;
|
|
99
189
|
}
|
|
100
190
|
|
|
101
191
|
export { parseMessages, convertMessage, removeEmptyMessages };
|
package/src/protocol/webrtc.ts
CHANGED
|
@@ -124,6 +124,7 @@ class PersonaWebRTCClient {
|
|
|
124
124
|
const metadata = {
|
|
125
125
|
apiKey: this.config.apiKey,
|
|
126
126
|
agentId: this.config.agentId,
|
|
127
|
+
userId: this.config.userId || 'anonymous',
|
|
127
128
|
sessionCode: session as string,
|
|
128
129
|
};
|
|
129
130
|
this.config.logger?.debug('Opening connection to WebRTC server: ', metadata);
|
|
@@ -49,7 +49,10 @@ class PersonaWebSocketProtocol extends PersonaProtocolBase {
|
|
|
49
49
|
|
|
50
50
|
const apiKey = encodeURIComponent(this.config.apiKey);
|
|
51
51
|
const agentId = this.config.agentId;
|
|
52
|
-
const
|
|
52
|
+
const userId = this.config.userId || 'anonymous';
|
|
53
|
+
const webSocketUrl = `${this.config.webSocketUrl}?sessionCode=${sid}&agentId=${agentId}&apiKey=${apiKey}&userId=${encodeURIComponent(
|
|
54
|
+
userId,
|
|
55
|
+
)}`;
|
|
53
56
|
this.setStatus('connecting');
|
|
54
57
|
this.webSocket = new WebSocket(webSocketUrl);
|
|
55
58
|
this.webSocket.addEventListener('open', () => {
|
|
@@ -87,7 +90,7 @@ class PersonaWebSocketProtocol extends PersonaProtocolBase {
|
|
|
87
90
|
public disconnect(): Promise<void> {
|
|
88
91
|
this.config.logger?.debug('Disconnecting WebSocket');
|
|
89
92
|
if (this.webSocket && this.status === 'connected') {
|
|
90
|
-
this.webSocket.close();
|
|
93
|
+
this.webSocket.close(1000, 'Normal closure');
|
|
91
94
|
this.setStatus('disconnected');
|
|
92
95
|
this.webSocket = null;
|
|
93
96
|
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { createContext, useContext } from 'react';
|
|
2
|
+
import { PersonaProtocol, ProtocolStatus } from '../types';
|
|
3
|
+
import { PersonaMessage } from '../types';
|
|
4
|
+
import { PersonaWebRTCProtocol } from '../protocol';
|
|
5
|
+
import { getFirstAvailableProtocolEndpoint } from './utils';
|
|
6
|
+
|
|
7
|
+
export type PersonaRuntimeContextType = {
|
|
8
|
+
protocols: PersonaProtocol[];
|
|
9
|
+
protocolsStatus: Map<string, ProtocolStatus>;
|
|
10
|
+
getMessages: () => PersonaMessage[];
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export const PersonaRuntimeContext = createContext<PersonaRuntimeContextType | undefined>(undefined);
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Hook to access the PersonaRuntime context
|
|
17
|
+
*/
|
|
18
|
+
export function usePersonaRuntime(): PersonaRuntimeContextType {
|
|
19
|
+
const context = useContext(PersonaRuntimeContext);
|
|
20
|
+
if (!context) {
|
|
21
|
+
throw new Error('usePersonaRuntime must be used within a PersonaRuntimeProvider');
|
|
22
|
+
}
|
|
23
|
+
return context;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Retrieves a specific protocol instance from the PersonaRuntimeContext.
|
|
28
|
+
*
|
|
29
|
+
* @param protocol - The name of the protocol to use.
|
|
30
|
+
* @returns {PersonaProtocol | null} - The protocol instance or null if not found.
|
|
31
|
+
* @throws {Error} - If the hook is used outside of a PersonaRuntimeProvider.
|
|
32
|
+
*/
|
|
33
|
+
export function usePersonaRuntimeProtocol(protocol: string): PersonaProtocol | null {
|
|
34
|
+
const context = useContext(PersonaRuntimeContext);
|
|
35
|
+
if (!context) {
|
|
36
|
+
throw new Error('usePersonaRuntimeProtocol must be used within a PersonaRuntimeProvider');
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const protocolInstance = context.protocols.find((p) => p.getName() === protocol);
|
|
40
|
+
if (!protocolInstance) {
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const status = context.protocolsStatus.get(protocolInstance.getName());
|
|
45
|
+
|
|
46
|
+
return {
|
|
47
|
+
...protocolInstance,
|
|
48
|
+
connect: protocolInstance.connect.bind(protocolInstance),
|
|
49
|
+
disconnect: protocolInstance.disconnect.bind(protocolInstance),
|
|
50
|
+
sendPacket: protocolInstance.sendPacket.bind(protocolInstance),
|
|
51
|
+
setSession: protocolInstance.setSession.bind(protocolInstance),
|
|
52
|
+
addStatusChangeListener: protocolInstance.addStatusChangeListener.bind(protocolInstance),
|
|
53
|
+
addPacketListener: protocolInstance.addPacketListener.bind(protocolInstance),
|
|
54
|
+
getName: protocolInstance.getName.bind(protocolInstance),
|
|
55
|
+
getPriority: protocolInstance.getPriority.bind(protocolInstance),
|
|
56
|
+
status: status || protocolInstance.status,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Gets the endpoint URL of the first available protocol
|
|
62
|
+
*/
|
|
63
|
+
export function usePersonaRuntimeEndpoint(): string | null {
|
|
64
|
+
const context = useContext(PersonaRuntimeContext);
|
|
65
|
+
if (!context) {
|
|
66
|
+
throw new Error('usePersonaRuntimeEndpoint must be used within a PersonaRuntimeProvider');
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return getFirstAvailableProtocolEndpoint(context.protocols);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Gets the WebRTC protocol instance
|
|
74
|
+
*/
|
|
75
|
+
export function usePersonaRuntimeWebRTCProtocol(): PersonaWebRTCProtocol | null {
|
|
76
|
+
return usePersonaRuntimeProtocol('webrtc') as PersonaWebRTCProtocol;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Gets all messages from the current conversation
|
|
81
|
+
*/
|
|
82
|
+
export function usePersonaRuntimeMessages(): PersonaMessage[] {
|
|
83
|
+
const context = useContext(PersonaRuntimeContext);
|
|
84
|
+
if (!context) {
|
|
85
|
+
throw new Error('usePersonaRuntimeMessages must be used within a PersonaRuntimeProvider');
|
|
86
|
+
}
|
|
87
|
+
return context.getMessages();
|
|
88
|
+
}
|
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
import { AppendMessage } from '@assistant-ui/react';
|
|
2
|
+
import { StartRunConfig } from '@assistant-ui/react/dist/legacy-runtime/runtime-cores/core/ThreadRuntimeCore';
|
|
3
|
+
import { PersonaProtocol, PersonaMessage, TransformMessages } from '../types';
|
|
4
|
+
import type { PersonaLogger } from '../logging';
|
|
5
|
+
import { parseMessages } from '../messages';
|
|
6
|
+
import { fileToBase64 } from './utils';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Creates the onNew handler for new messages
|
|
10
|
+
*/
|
|
11
|
+
export function createOnNewHandler(
|
|
12
|
+
protocols: PersonaProtocol[],
|
|
13
|
+
setMessages: React.Dispatch<React.SetStateAction<PersonaMessage[]>>,
|
|
14
|
+
setIsRunning: React.Dispatch<React.SetStateAction<boolean>>,
|
|
15
|
+
transformMessages?: TransformMessages,
|
|
16
|
+
logger?: PersonaLogger,
|
|
17
|
+
) {
|
|
18
|
+
return async (message: AppendMessage) => {
|
|
19
|
+
if (message.content[0]?.type !== 'text') {
|
|
20
|
+
throw new Error('Only text messages are supported');
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const ready = protocols.some((protocol) => protocol.getName() !== 'transaction' && protocol.status === 'connected');
|
|
24
|
+
|
|
25
|
+
if (!ready) {
|
|
26
|
+
setMessages((currentConversation) => {
|
|
27
|
+
const errorMessage: PersonaMessage = {
|
|
28
|
+
role: 'assistant',
|
|
29
|
+
type: 'text',
|
|
30
|
+
text: 'No protocol is connected.',
|
|
31
|
+
createdAt: new Date(),
|
|
32
|
+
finishReason: 'stop',
|
|
33
|
+
};
|
|
34
|
+
const newMessages = [...currentConversation, errorMessage];
|
|
35
|
+
return transformMessages ? transformMessages(newMessages) : newMessages;
|
|
36
|
+
});
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const input = message.content[0].text;
|
|
41
|
+
const userMessage: PersonaMessage = {
|
|
42
|
+
role: 'user',
|
|
43
|
+
type: 'text',
|
|
44
|
+
text: input,
|
|
45
|
+
createdAt: new Date(),
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
setMessages((currentConversation) => {
|
|
49
|
+
const newMessages = parseMessages([...currentConversation, userMessage]);
|
|
50
|
+
return transformMessages ? transformMessages(newMessages) : newMessages;
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
setIsRunning(true);
|
|
54
|
+
|
|
55
|
+
const protocol = protocols.sort((a, b) => b.getPriority() - a.getPriority()).find((protocol) => protocol.status === 'connected');
|
|
56
|
+
|
|
57
|
+
const content: Array<PersonaMessage> = [];
|
|
58
|
+
|
|
59
|
+
if (message.attachments) {
|
|
60
|
+
for (const attachment of message.attachments) {
|
|
61
|
+
if (attachment.contentType.startsWith('image/') && attachment.file) {
|
|
62
|
+
content.push({
|
|
63
|
+
role: 'user',
|
|
64
|
+
image: {
|
|
65
|
+
contentType: attachment.contentType,
|
|
66
|
+
content: await fileToBase64(attachment.file),
|
|
67
|
+
},
|
|
68
|
+
text: '',
|
|
69
|
+
type: 'text',
|
|
70
|
+
createdAt: new Date(),
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (message.content) {
|
|
77
|
+
content.push({
|
|
78
|
+
role: 'user',
|
|
79
|
+
text: message.content[0].text,
|
|
80
|
+
type: 'text',
|
|
81
|
+
createdAt: new Date(),
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
logger?.debug('Sending message:', content);
|
|
86
|
+
await protocol?.sendPacket({ type: 'request', payload: content });
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Creates the onEdit handler for editing messages
|
|
92
|
+
*/
|
|
93
|
+
export function createOnEditHandler(
|
|
94
|
+
protocols: PersonaProtocol[],
|
|
95
|
+
messages: PersonaMessage[],
|
|
96
|
+
setMessages: React.Dispatch<React.SetStateAction<PersonaMessage[]>>,
|
|
97
|
+
setIsRunning: React.Dispatch<React.SetStateAction<boolean>>,
|
|
98
|
+
transformMessages?: TransformMessages,
|
|
99
|
+
logger?: PersonaLogger,
|
|
100
|
+
) {
|
|
101
|
+
return async (message: AppendMessage) => {
|
|
102
|
+
if (message.content[0]?.type !== 'text') {
|
|
103
|
+
throw new Error('Only text messages are supported');
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const protocol = protocols.sort((a, b) => b.getPriority() - a.getPriority()).find((protocol) => protocol.status === 'connected');
|
|
107
|
+
|
|
108
|
+
if (!protocol) {
|
|
109
|
+
logger?.debug('No protocol available for edit');
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const parentId = message.parentId;
|
|
114
|
+
if (!parentId) {
|
|
115
|
+
logger?.debug('No parent ID provided for edit');
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const parentIndex = messages.findIndex((m) => m.id === parentId);
|
|
120
|
+
if (parentIndex === -1) {
|
|
121
|
+
logger?.debug('Parent message not found:', parentId);
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const messagesToKeep = messages.slice(0, parentIndex + 1);
|
|
126
|
+
const editedMessage: PersonaMessage = {
|
|
127
|
+
role: 'user',
|
|
128
|
+
text: message.content[0].text,
|
|
129
|
+
type: 'text',
|
|
130
|
+
createdAt: new Date(),
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
const newMessages = parseMessages([...messagesToKeep, editedMessage]);
|
|
134
|
+
setMessages(transformMessages ? transformMessages(newMessages) : newMessages);
|
|
135
|
+
|
|
136
|
+
setIsRunning(true);
|
|
137
|
+
|
|
138
|
+
const content: Array<PersonaMessage> = [
|
|
139
|
+
{
|
|
140
|
+
role: 'user',
|
|
141
|
+
text: message.content[0].text,
|
|
142
|
+
type: 'text',
|
|
143
|
+
createdAt: new Date(),
|
|
144
|
+
},
|
|
145
|
+
];
|
|
146
|
+
|
|
147
|
+
logger?.debug('Sending edited message:', content);
|
|
148
|
+
await protocol.sendPacket({ type: 'request', payload: content });
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Creates the onReload handler for regenerating messages
|
|
154
|
+
*/
|
|
155
|
+
export function createOnReloadHandler(
|
|
156
|
+
protocols: PersonaProtocol[],
|
|
157
|
+
messages: PersonaMessage[],
|
|
158
|
+
setMessages: React.Dispatch<React.SetStateAction<PersonaMessage[]>>,
|
|
159
|
+
setIsRunning: React.Dispatch<React.SetStateAction<boolean>>,
|
|
160
|
+
transformMessages?: TransformMessages,
|
|
161
|
+
logger?: PersonaLogger,
|
|
162
|
+
) {
|
|
163
|
+
return async (parentId: string | null, config: StartRunConfig) => {
|
|
164
|
+
const protocol = protocols.sort((a, b) => b.getPriority() - a.getPriority()).find((protocol) => protocol.status === 'connected');
|
|
165
|
+
|
|
166
|
+
if (!protocol) {
|
|
167
|
+
logger?.debug('No protocol available for reload');
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const actualParentId = config.parentId ?? parentId;
|
|
172
|
+
if (!actualParentId) {
|
|
173
|
+
logger?.debug('No parent ID provided for reload');
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const parentIndex = messages.findIndex((msg) => msg.id === actualParentId);
|
|
178
|
+
if (parentIndex === -1) {
|
|
179
|
+
logger?.debug('Parent message not found:', actualParentId);
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const parentMessage = messages[parentIndex];
|
|
184
|
+
|
|
185
|
+
setMessages((currentMessages) => {
|
|
186
|
+
const messagesToKeep = currentMessages.slice(0, parentIndex + 1);
|
|
187
|
+
return transformMessages ? transformMessages(messagesToKeep) : messagesToKeep;
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
let userMessage: PersonaMessage | undefined;
|
|
191
|
+
if (parentMessage.role === 'assistant' || parentMessage.role === 'function') {
|
|
192
|
+
for (let i = parentIndex - 1; i >= 0; i--) {
|
|
193
|
+
if (messages[i].role === 'user') {
|
|
194
|
+
userMessage = messages[i];
|
|
195
|
+
break;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
} else if (parentMessage.role === 'user') {
|
|
199
|
+
userMessage = parentMessage;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
if (!userMessage) {
|
|
203
|
+
logger?.debug('No user message found to regenerate from');
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
setIsRunning(true);
|
|
208
|
+
|
|
209
|
+
const content: Array<PersonaMessage> = [];
|
|
210
|
+
|
|
211
|
+
if (userMessage.image) {
|
|
212
|
+
content.push({
|
|
213
|
+
role: 'user',
|
|
214
|
+
image: userMessage.image,
|
|
215
|
+
text: '',
|
|
216
|
+
type: 'text',
|
|
217
|
+
createdAt: new Date(),
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
content.push({
|
|
222
|
+
role: 'user',
|
|
223
|
+
text: userMessage.text,
|
|
224
|
+
type: 'text',
|
|
225
|
+
createdAt: new Date(),
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
logger?.debug('Regenerating message with config:', {
|
|
229
|
+
actualParentId,
|
|
230
|
+
sourceId: config.sourceId,
|
|
231
|
+
custom: config.runConfig.custom,
|
|
232
|
+
});
|
|
233
|
+
await protocol.sendPacket({ type: 'request', payload: content });
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Creates the onCancel handler for cancelling message generation
|
|
239
|
+
*/
|
|
240
|
+
export function createOnCancelHandler(
|
|
241
|
+
protocols: PersonaProtocol[],
|
|
242
|
+
setMessages: React.Dispatch<React.SetStateAction<PersonaMessage[]>>,
|
|
243
|
+
setIsRunning: React.Dispatch<React.SetStateAction<boolean>>,
|
|
244
|
+
transformMessages?: TransformMessages,
|
|
245
|
+
) {
|
|
246
|
+
return () => {
|
|
247
|
+
setIsRunning(false);
|
|
248
|
+
|
|
249
|
+
setMessages((currentMessages) => {
|
|
250
|
+
const lastMessage = currentMessages[currentMessages.length - 1];
|
|
251
|
+
if (lastMessage?.role === 'assistant' && !lastMessage.finishReason) {
|
|
252
|
+
const updatedMessages = [...currentMessages];
|
|
253
|
+
updatedMessages[updatedMessages.length - 1] = {
|
|
254
|
+
...lastMessage,
|
|
255
|
+
finishReason: 'stop',
|
|
256
|
+
status: { type: 'incomplete' },
|
|
257
|
+
};
|
|
258
|
+
return transformMessages ? transformMessages(updatedMessages) : updatedMessages;
|
|
259
|
+
}
|
|
260
|
+
return currentMessages;
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
protocols.forEach((protocol) => {
|
|
264
|
+
if (protocol.status === 'connected') {
|
|
265
|
+
protocol
|
|
266
|
+
.sendPacket({
|
|
267
|
+
type: 'command',
|
|
268
|
+
payload: { command: 'set_initial_context', arguments: { cancel: true } },
|
|
269
|
+
})
|
|
270
|
+
.catch(() => {});
|
|
271
|
+
}
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
return Promise.resolve();
|
|
275
|
+
};
|
|
276
|
+
}
|