@runtypelabs/persona 1.36.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 +1080 -0
- package/dist/index.cjs +140 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +2626 -0
- package/dist/index.d.ts +2626 -0
- package/dist/index.global.js +1843 -0
- package/dist/index.global.js.map +1 -0
- package/dist/index.js +140 -0
- package/dist/index.js.map +1 -0
- package/dist/install.global.js +2 -0
- package/dist/install.global.js.map +1 -0
- package/dist/widget.css +1627 -0
- package/package.json +79 -0
- package/src/@types/idiomorph.d.ts +37 -0
- package/src/client.test.ts +387 -0
- package/src/client.ts +1589 -0
- package/src/components/composer-builder.ts +530 -0
- package/src/components/feedback.ts +379 -0
- package/src/components/forms.ts +170 -0
- package/src/components/header-builder.ts +455 -0
- package/src/components/header-layouts.ts +303 -0
- package/src/components/launcher.ts +193 -0
- package/src/components/message-bubble.ts +528 -0
- package/src/components/messages.ts +54 -0
- package/src/components/panel.ts +204 -0
- package/src/components/reasoning-bubble.ts +144 -0
- package/src/components/registry.ts +87 -0
- package/src/components/suggestions.ts +97 -0
- package/src/components/tool-bubble.ts +288 -0
- package/src/defaults.ts +321 -0
- package/src/index.ts +175 -0
- package/src/install.ts +284 -0
- package/src/plugins/registry.ts +77 -0
- package/src/plugins/types.ts +95 -0
- package/src/postprocessors.ts +194 -0
- package/src/runtime/init.ts +162 -0
- package/src/session.ts +376 -0
- package/src/styles/tailwind.css +20 -0
- package/src/styles/widget.css +1627 -0
- package/src/types.ts +1635 -0
- package/src/ui.ts +3341 -0
- package/src/utils/actions.ts +227 -0
- package/src/utils/attachment-manager.ts +384 -0
- package/src/utils/code-generators.test.ts +500 -0
- package/src/utils/code-generators.ts +1806 -0
- package/src/utils/component-middleware.ts +137 -0
- package/src/utils/component-parser.ts +119 -0
- package/src/utils/constants.ts +16 -0
- package/src/utils/content.ts +306 -0
- package/src/utils/dom.ts +25 -0
- package/src/utils/events.ts +41 -0
- package/src/utils/formatting.test.ts +166 -0
- package/src/utils/formatting.ts +470 -0
- package/src/utils/icons.ts +92 -0
- package/src/utils/message-id.ts +37 -0
- package/src/utils/morph.ts +36 -0
- package/src/utils/positioning.ts +17 -0
- package/src/utils/storage.ts +72 -0
- package/src/utils/theme.ts +105 -0
- package/src/widget.css +1 -0
- package/widget.css +1 -0
package/src/session.ts
ADDED
|
@@ -0,0 +1,376 @@
|
|
|
1
|
+
import { AgentWidgetClient } from "./client";
|
|
2
|
+
import {
|
|
3
|
+
AgentWidgetConfig,
|
|
4
|
+
AgentWidgetEvent,
|
|
5
|
+
AgentWidgetMessage,
|
|
6
|
+
ClientSession,
|
|
7
|
+
ContentPart
|
|
8
|
+
} from "./types";
|
|
9
|
+
import {
|
|
10
|
+
generateUserMessageId,
|
|
11
|
+
generateAssistantMessageId
|
|
12
|
+
} from "./utils/message-id";
|
|
13
|
+
|
|
14
|
+
export type AgentWidgetSessionStatus =
|
|
15
|
+
| "idle"
|
|
16
|
+
| "connecting"
|
|
17
|
+
| "connected"
|
|
18
|
+
| "error";
|
|
19
|
+
|
|
20
|
+
type SessionCallbacks = {
|
|
21
|
+
onMessagesChanged: (messages: AgentWidgetMessage[]) => void;
|
|
22
|
+
onStatusChanged: (status: AgentWidgetSessionStatus) => void;
|
|
23
|
+
onStreamingChanged: (streaming: boolean) => void;
|
|
24
|
+
onError?: (error: Error) => void;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export class AgentWidgetSession {
|
|
28
|
+
private client: AgentWidgetClient;
|
|
29
|
+
private messages: AgentWidgetMessage[];
|
|
30
|
+
private status: AgentWidgetSessionStatus = "idle";
|
|
31
|
+
private streaming = false;
|
|
32
|
+
private abortController: AbortController | null = null;
|
|
33
|
+
private sequenceCounter = Date.now();
|
|
34
|
+
|
|
35
|
+
// Client token session management
|
|
36
|
+
private clientSession: ClientSession | null = null;
|
|
37
|
+
|
|
38
|
+
constructor(
|
|
39
|
+
private config: AgentWidgetConfig = {},
|
|
40
|
+
private callbacks: SessionCallbacks
|
|
41
|
+
) {
|
|
42
|
+
this.messages = [...(config.initialMessages ?? [])].map((message) => ({
|
|
43
|
+
...message,
|
|
44
|
+
sequence: message.sequence ?? this.nextSequence()
|
|
45
|
+
}));
|
|
46
|
+
this.messages = this.sortMessages(this.messages);
|
|
47
|
+
this.client = new AgentWidgetClient(config);
|
|
48
|
+
|
|
49
|
+
if (this.messages.length) {
|
|
50
|
+
this.callbacks.onMessagesChanged([...this.messages]);
|
|
51
|
+
}
|
|
52
|
+
this.callbacks.onStatusChanged(this.status);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Check if running in client token mode
|
|
57
|
+
*/
|
|
58
|
+
public isClientTokenMode(): boolean {
|
|
59
|
+
return this.client.isClientTokenMode();
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Initialize the client session (for client token mode).
|
|
64
|
+
* This is called automatically on first message, but can be called
|
|
65
|
+
* explicitly to pre-initialize the session and get config from server.
|
|
66
|
+
*/
|
|
67
|
+
public async initClientSession(): Promise<ClientSession | null> {
|
|
68
|
+
if (!this.isClientTokenMode()) {
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
try {
|
|
73
|
+
const session = await this.client.initSession();
|
|
74
|
+
this.setClientSession(session);
|
|
75
|
+
return session;
|
|
76
|
+
} catch (error) {
|
|
77
|
+
this.callbacks.onError?.(
|
|
78
|
+
error instanceof Error ? error : new Error(String(error))
|
|
79
|
+
);
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Set the client session after initialization
|
|
86
|
+
*/
|
|
87
|
+
public setClientSession(session: ClientSession): void {
|
|
88
|
+
this.clientSession = session;
|
|
89
|
+
|
|
90
|
+
// Optionally add welcome message from session config
|
|
91
|
+
if (session.config.welcomeMessage && this.messages.length === 0) {
|
|
92
|
+
const welcomeMessage: AgentWidgetMessage = {
|
|
93
|
+
id: `welcome-${Date.now()}`,
|
|
94
|
+
role: "assistant",
|
|
95
|
+
content: session.config.welcomeMessage,
|
|
96
|
+
createdAt: new Date().toISOString(),
|
|
97
|
+
sequence: this.nextSequence()
|
|
98
|
+
};
|
|
99
|
+
this.appendMessage(welcomeMessage);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Get current client session
|
|
105
|
+
*/
|
|
106
|
+
public getClientSession(): ClientSession | null {
|
|
107
|
+
return this.clientSession ?? this.client.getClientSession();
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Check if session is valid and not expired
|
|
112
|
+
*/
|
|
113
|
+
public isSessionValid(): boolean {
|
|
114
|
+
const session = this.getClientSession();
|
|
115
|
+
if (!session) return false;
|
|
116
|
+
return new Date() < session.expiresAt;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Clear session (on expiry or error)
|
|
121
|
+
*/
|
|
122
|
+
public clearClientSession(): void {
|
|
123
|
+
this.clientSession = null;
|
|
124
|
+
this.client.clearClientSession();
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Get the underlying client instance (for advanced use cases like feedback)
|
|
129
|
+
*/
|
|
130
|
+
public getClient(): AgentWidgetClient {
|
|
131
|
+
return this.client;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Submit message feedback (upvote, downvote, or copy) to the API.
|
|
136
|
+
* Only available in client token mode.
|
|
137
|
+
*
|
|
138
|
+
* @param messageId - The ID of the message to provide feedback for
|
|
139
|
+
* @param type - The feedback type: 'upvote', 'downvote', or 'copy'
|
|
140
|
+
*/
|
|
141
|
+
public async submitMessageFeedback(
|
|
142
|
+
messageId: string,
|
|
143
|
+
type: 'upvote' | 'downvote' | 'copy'
|
|
144
|
+
): Promise<void> {
|
|
145
|
+
return this.client.submitMessageFeedback(messageId, type);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Submit CSAT (Customer Satisfaction) feedback to the API.
|
|
150
|
+
* Only available in client token mode.
|
|
151
|
+
*
|
|
152
|
+
* @param rating - Rating from 1 to 5
|
|
153
|
+
* @param comment - Optional comment
|
|
154
|
+
*/
|
|
155
|
+
public async submitCSATFeedback(rating: number, comment?: string): Promise<void> {
|
|
156
|
+
return this.client.submitCSATFeedback(rating, comment);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Submit NPS (Net Promoter Score) feedback to the API.
|
|
161
|
+
* Only available in client token mode.
|
|
162
|
+
*
|
|
163
|
+
* @param rating - Rating from 0 to 10
|
|
164
|
+
* @param comment - Optional comment
|
|
165
|
+
*/
|
|
166
|
+
public async submitNPSFeedback(rating: number, comment?: string): Promise<void> {
|
|
167
|
+
return this.client.submitNPSFeedback(rating, comment);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
public updateConfig(next: AgentWidgetConfig) {
|
|
171
|
+
this.config = { ...this.config, ...next };
|
|
172
|
+
this.client = new AgentWidgetClient(this.config);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
public getMessages() {
|
|
176
|
+
return [...this.messages];
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
public getStatus() {
|
|
180
|
+
return this.status;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
public isStreaming() {
|
|
184
|
+
return this.streaming;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
public injectTestEvent(event: AgentWidgetEvent) {
|
|
188
|
+
this.handleEvent(event);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
public async sendMessage(
|
|
192
|
+
rawInput: string,
|
|
193
|
+
options?: {
|
|
194
|
+
viaVoice?: boolean;
|
|
195
|
+
/** Multi-modal content parts (e.g., images) to include with the message */
|
|
196
|
+
contentParts?: ContentPart[];
|
|
197
|
+
}
|
|
198
|
+
) {
|
|
199
|
+
const input = rawInput.trim();
|
|
200
|
+
// Allow sending if there's text OR attachments
|
|
201
|
+
if (!input && (!options?.contentParts || options.contentParts.length === 0)) return;
|
|
202
|
+
|
|
203
|
+
this.abortController?.abort();
|
|
204
|
+
|
|
205
|
+
// Generate IDs for both user message and expected assistant response
|
|
206
|
+
const userMessageId = generateUserMessageId();
|
|
207
|
+
const assistantMessageId = generateAssistantMessageId();
|
|
208
|
+
|
|
209
|
+
const userMessage: AgentWidgetMessage = {
|
|
210
|
+
id: userMessageId,
|
|
211
|
+
role: "user",
|
|
212
|
+
content: input || "[Image]", // Display text (fallback if only images)
|
|
213
|
+
createdAt: new Date().toISOString(),
|
|
214
|
+
sequence: this.nextSequence(),
|
|
215
|
+
viaVoice: options?.viaVoice || false,
|
|
216
|
+
// Include contentParts if provided (for multi-modal messages)
|
|
217
|
+
...(options?.contentParts && options.contentParts.length > 0 && {
|
|
218
|
+
contentParts: options.contentParts
|
|
219
|
+
})
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
this.appendMessage(userMessage);
|
|
223
|
+
this.setStreaming(true);
|
|
224
|
+
|
|
225
|
+
const controller = new AbortController();
|
|
226
|
+
this.abortController = controller;
|
|
227
|
+
|
|
228
|
+
const snapshot = [...this.messages];
|
|
229
|
+
|
|
230
|
+
try {
|
|
231
|
+
await this.client.dispatch(
|
|
232
|
+
{
|
|
233
|
+
messages: snapshot,
|
|
234
|
+
signal: controller.signal,
|
|
235
|
+
assistantMessageId // Pass expected assistant message ID for tracking
|
|
236
|
+
},
|
|
237
|
+
this.handleEvent
|
|
238
|
+
);
|
|
239
|
+
} catch (error) {
|
|
240
|
+
const fallback: AgentWidgetMessage = {
|
|
241
|
+
id: assistantMessageId, // Use the pre-generated ID for fallback too
|
|
242
|
+
role: "assistant",
|
|
243
|
+
createdAt: new Date().toISOString(),
|
|
244
|
+
content:
|
|
245
|
+
"It looks like the proxy isn't returning a real response yet. Here's a sample message so you can continue testing locally.",
|
|
246
|
+
sequence: this.nextSequence()
|
|
247
|
+
};
|
|
248
|
+
|
|
249
|
+
this.appendMessage(fallback);
|
|
250
|
+
this.setStatus("idle");
|
|
251
|
+
this.setStreaming(false);
|
|
252
|
+
this.abortController = null;
|
|
253
|
+
if (error instanceof Error) {
|
|
254
|
+
this.callbacks.onError?.(error);
|
|
255
|
+
} else {
|
|
256
|
+
this.callbacks.onError?.(new Error(String(error)));
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
public cancel() {
|
|
262
|
+
this.abortController?.abort();
|
|
263
|
+
this.abortController = null;
|
|
264
|
+
this.setStreaming(false);
|
|
265
|
+
this.setStatus("idle");
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
public clearMessages() {
|
|
269
|
+
this.abortController?.abort();
|
|
270
|
+
this.abortController = null;
|
|
271
|
+
this.messages = [];
|
|
272
|
+
this.setStreaming(false);
|
|
273
|
+
this.setStatus("idle");
|
|
274
|
+
this.callbacks.onMessagesChanged([...this.messages]);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
public hydrateMessages(messages: AgentWidgetMessage[]) {
|
|
278
|
+
this.abortController?.abort();
|
|
279
|
+
this.abortController = null;
|
|
280
|
+
this.messages = this.sortMessages(
|
|
281
|
+
messages.map((message) => ({
|
|
282
|
+
...message,
|
|
283
|
+
streaming: false,
|
|
284
|
+
sequence: message.sequence ?? this.nextSequence()
|
|
285
|
+
}))
|
|
286
|
+
);
|
|
287
|
+
this.setStreaming(false);
|
|
288
|
+
this.setStatus("idle");
|
|
289
|
+
this.callbacks.onMessagesChanged([...this.messages]);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
private handleEvent = (event: AgentWidgetEvent) => {
|
|
293
|
+
if (event.type === "message") {
|
|
294
|
+
this.upsertMessage(event.message);
|
|
295
|
+
} else if (event.type === "status") {
|
|
296
|
+
this.setStatus(event.status);
|
|
297
|
+
if (event.status === "connecting") {
|
|
298
|
+
this.setStreaming(true);
|
|
299
|
+
} else if (event.status === "idle" || event.status === "error") {
|
|
300
|
+
this.setStreaming(false);
|
|
301
|
+
this.abortController = null;
|
|
302
|
+
}
|
|
303
|
+
} else if (event.type === "error") {
|
|
304
|
+
this.setStatus("error");
|
|
305
|
+
this.setStreaming(false);
|
|
306
|
+
this.abortController = null;
|
|
307
|
+
this.callbacks.onError?.(event.error);
|
|
308
|
+
}
|
|
309
|
+
};
|
|
310
|
+
|
|
311
|
+
private setStatus(status: AgentWidgetSessionStatus) {
|
|
312
|
+
if (this.status === status) return;
|
|
313
|
+
this.status = status;
|
|
314
|
+
this.callbacks.onStatusChanged(status);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
private setStreaming(streaming: boolean) {
|
|
318
|
+
if (this.streaming === streaming) return;
|
|
319
|
+
this.streaming = streaming;
|
|
320
|
+
this.callbacks.onStreamingChanged(streaming);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
private appendMessage(message: AgentWidgetMessage) {
|
|
324
|
+
const withSequence = this.ensureSequence(message);
|
|
325
|
+
this.messages = this.sortMessages([...this.messages, withSequence]);
|
|
326
|
+
this.callbacks.onMessagesChanged([...this.messages]);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
private upsertMessage(message: AgentWidgetMessage) {
|
|
330
|
+
const withSequence = this.ensureSequence(message);
|
|
331
|
+
const index = this.messages.findIndex((m) => m.id === withSequence.id);
|
|
332
|
+
if (index === -1) {
|
|
333
|
+
this.appendMessage(withSequence);
|
|
334
|
+
return;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
this.messages = this.messages.map((existing, idx) =>
|
|
338
|
+
idx === index ? { ...existing, ...withSequence } : existing
|
|
339
|
+
);
|
|
340
|
+
this.messages = this.sortMessages(this.messages);
|
|
341
|
+
this.callbacks.onMessagesChanged([...this.messages]);
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
private ensureSequence(message: AgentWidgetMessage): AgentWidgetMessage {
|
|
345
|
+
if (message.sequence !== undefined) {
|
|
346
|
+
return { ...message };
|
|
347
|
+
}
|
|
348
|
+
return {
|
|
349
|
+
...message,
|
|
350
|
+
sequence: this.nextSequence()
|
|
351
|
+
};
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
private nextSequence() {
|
|
355
|
+
return this.sequenceCounter++;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
private sortMessages(messages: AgentWidgetMessage[]) {
|
|
359
|
+
return [...messages].sort((a, b) => {
|
|
360
|
+
// Sort by createdAt timestamp first (chronological order)
|
|
361
|
+
const timeA = new Date(a.createdAt).getTime();
|
|
362
|
+
const timeB = new Date(b.createdAt).getTime();
|
|
363
|
+
if (!Number.isNaN(timeA) && !Number.isNaN(timeB) && timeA !== timeB) {
|
|
364
|
+
return timeA - timeB;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
// Fall back to sequence if timestamps are equal or invalid
|
|
368
|
+
const seqA = a.sequence ?? 0;
|
|
369
|
+
const seqB = b.sequence ?? 0;
|
|
370
|
+
if (seqA !== seqB) return seqA - seqB;
|
|
371
|
+
|
|
372
|
+
// Final fallback to ID
|
|
373
|
+
return a.id.localeCompare(b.id);
|
|
374
|
+
});
|
|
375
|
+
}
|
|
376
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
@tailwind base;
|
|
2
|
+
@tailwind components;
|
|
3
|
+
@tailwind utilities;
|
|
4
|
+
|
|
5
|
+
@layer base {
|
|
6
|
+
/* Widget CSS Variables - scoped to widget root to avoid polluting global namespace */
|
|
7
|
+
#persona-root {
|
|
8
|
+
--travrse-primary: #111827;
|
|
9
|
+
--travrse-secondary: #4338ca;
|
|
10
|
+
--travrse-surface: #ffffff;
|
|
11
|
+
--travrse-muted: #6b7280;
|
|
12
|
+
--travrse-accent: #1d4ed8;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
@layer components {
|
|
17
|
+
.tvw-widget-shadow {
|
|
18
|
+
box-shadow: 0 25px 50px -12px rgba(15, 23, 42, 0.35);
|
|
19
|
+
}
|
|
20
|
+
}
|