vanilla-agent 1.5.0 → 1.7.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 +43 -5
- package/dist/index.cjs +7 -7
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +163 -23
- package/dist/index.d.ts +163 -23
- package/dist/index.global.js +52 -52
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +7 -7
- package/dist/index.js.map +1 -1
- package/dist/widget.css +25 -8
- package/package.json +8 -3
- package/src/client.test.ts +1 -1
- package/src/client.ts +132 -29
- package/src/components/message-bubble.ts +6 -1
- package/src/components/reasoning-bubble.ts +2 -0
- package/src/components/tool-bubble.ts +2 -0
- package/src/index.ts +6 -0
- package/src/runtime/init.ts +5 -33
- package/src/session.ts +15 -0
- package/src/styles/widget.css +25 -8
- package/src/types.ts +140 -1
- package/src/ui.ts +414 -20
- package/src/utils/actions.ts +228 -0
- package/src/utils/events.ts +41 -0
- package/src/utils/formatting.test.ts +10 -4
- package/src/utils/formatting.ts +22 -10
- package/src/utils/storage.ts +72 -0
package/dist/widget.css
CHANGED
|
@@ -797,7 +797,7 @@ form:focus-within textarea {
|
|
|
797
797
|
}
|
|
798
798
|
|
|
799
799
|
/* Markdown content overflow handling */
|
|
800
|
-
|
|
800
|
+
.vanilla-message-bubble pre {
|
|
801
801
|
overflow-x: auto;
|
|
802
802
|
max-width: 100%;
|
|
803
803
|
word-wrap: break-word;
|
|
@@ -812,7 +812,7 @@ form:focus-within textarea {
|
|
|
812
812
|
border: 1px solid #e5e7eb;
|
|
813
813
|
}
|
|
814
814
|
|
|
815
|
-
|
|
815
|
+
.vanilla-message-bubble code {
|
|
816
816
|
word-break: break-word;
|
|
817
817
|
word-wrap: break-word;
|
|
818
818
|
white-space: pre-wrap;
|
|
@@ -821,14 +821,14 @@ form:focus-within textarea {
|
|
|
821
821
|
font-size: 0.875em;
|
|
822
822
|
}
|
|
823
823
|
|
|
824
|
-
|
|
824
|
+
.vanilla-message-bubble pre code {
|
|
825
825
|
font-size: inherit;
|
|
826
826
|
background-color: transparent;
|
|
827
827
|
padding: 0;
|
|
828
828
|
border-radius: 0;
|
|
829
829
|
}
|
|
830
830
|
|
|
831
|
-
|
|
831
|
+
.vanilla-message-bubble img {
|
|
832
832
|
max-width: 100%;
|
|
833
833
|
height: auto;
|
|
834
834
|
display: block;
|
|
@@ -836,11 +836,28 @@ form:focus-within textarea {
|
|
|
836
836
|
border-radius: 0.375rem;
|
|
837
837
|
}
|
|
838
838
|
|
|
839
|
+
/* Ensure all links in chat bubbles have underlines */
|
|
840
|
+
.vanilla-message-bubble a {
|
|
841
|
+
text-decoration: underline;
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
.vanilla-message-bubble a:visited {
|
|
845
|
+
text-decoration: underline;
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
.vanilla-message-bubble a:hover {
|
|
849
|
+
text-decoration: underline;
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
.vanilla-message-bubble a:active {
|
|
853
|
+
text-decoration: underline;
|
|
854
|
+
}
|
|
855
|
+
|
|
839
856
|
/* Ensure links in user messages match the text color */
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
857
|
+
.vanilla-message-user-bubble a,
|
|
858
|
+
.vanilla-message-user-bubble a:visited,
|
|
859
|
+
.vanilla-message-user-bubble a:hover,
|
|
860
|
+
.vanilla-message-user-bubble a:active {
|
|
844
861
|
color: inherit;
|
|
845
862
|
text-decoration: underline;
|
|
846
863
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vanilla-agent",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.7.0",
|
|
4
4
|
"description": "Themeable, plugable streaming agent widget for websites, in plain JS with support for voice input and reasoning / tool output.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.cjs",
|
|
@@ -30,13 +30,15 @@
|
|
|
30
30
|
},
|
|
31
31
|
"devDependencies": {
|
|
32
32
|
"@types/node": "^20.12.7",
|
|
33
|
+
"@vitest/ui": "^4.0.9",
|
|
33
34
|
"eslint": "^8.57.0",
|
|
34
35
|
"eslint-config-prettier": "^9.1.0",
|
|
35
36
|
"postcss": "^8.4.38",
|
|
36
37
|
"rimraf": "^5.0.5",
|
|
37
38
|
"tailwindcss": "^3.4.10",
|
|
38
39
|
"tsup": "^8.0.1",
|
|
39
|
-
"typescript": "^5.4.5"
|
|
40
|
+
"typescript": "^5.4.5",
|
|
41
|
+
"vitest": "^4.0.9"
|
|
40
42
|
},
|
|
41
43
|
"engines": {
|
|
42
44
|
"node": ">=18.17.0"
|
|
@@ -58,6 +60,9 @@
|
|
|
58
60
|
"build:client": "tsup src/index.ts --format esm,cjs,iife --global-name AgentWidget --minify --sourcemap --splitting false --dts --loader \".css=text\"",
|
|
59
61
|
"build:installer": "tsup src/install.ts --format iife --global-name SiteAgentInstaller --out-dir dist --minify --sourcemap --no-splitting",
|
|
60
62
|
"lint": "eslint . --ext .ts",
|
|
61
|
-
"typecheck": "tsc --noEmit"
|
|
63
|
+
"typecheck": "tsc --noEmit",
|
|
64
|
+
"test": "vitest",
|
|
65
|
+
"test:ui": "vitest --ui",
|
|
66
|
+
"test:run": "vitest run"
|
|
62
67
|
}
|
|
63
68
|
}
|
package/src/client.test.ts
CHANGED
|
@@ -22,7 +22,7 @@ describe('AgentWidgetClient - JSON Streaming', () => {
|
|
|
22
22
|
'',
|
|
23
23
|
'data: {"type":"step_start","id":"step_01k9x5db72fzwvmdenryn0qm48","name":"Prompt 1","stepType":"prompt","index":1,"totalSteps":1,"startedAt":"2025-11-12T23:47:39.565Z"}',
|
|
24
24
|
'',
|
|
25
|
-
'data: {"type":"step_chunk","id":"step_01k9x5db72fzwvmdenryn0qm48","name":"Prompt 1","executionType":"prompt","index":2,"text":"{
|
|
25
|
+
'data: {"type":"step_chunk","id":"step_01k9x5db72fzwvmdenryn0qm48","name":"Prompt 1","executionType":"prompt","index":2,"text":"{\\n"}',
|
|
26
26
|
'',
|
|
27
27
|
'data: {"type":"step_chunk","id":"step_01k9x5db72fzwvmdenryn0qm48","name":"Prompt 1","executionType":"prompt","index":2,"text":" "}',
|
|
28
28
|
'',
|
package/src/client.ts
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import {
|
|
2
|
+
AgentWidgetConfig,
|
|
3
|
+
AgentWidgetMessage,
|
|
4
|
+
AgentWidgetEvent,
|
|
5
|
+
AgentWidgetStreamParser,
|
|
6
|
+
AgentWidgetContextProvider,
|
|
7
|
+
AgentWidgetRequestMiddleware,
|
|
8
|
+
AgentWidgetRequestPayload
|
|
9
|
+
} from "./types";
|
|
10
|
+
import {
|
|
11
|
+
extractTextFromJson,
|
|
12
|
+
createPlainTextParser,
|
|
13
|
+
createJsonStreamParser,
|
|
14
|
+
createRegexJsonParser,
|
|
15
|
+
createXmlParser
|
|
16
|
+
} from "./utils/formatting";
|
|
3
17
|
|
|
4
18
|
type DispatchOptions = {
|
|
5
19
|
messages: AgentWidgetMessage[];
|
|
@@ -10,11 +24,30 @@ type SSEHandler = (event: AgentWidgetEvent) => void;
|
|
|
10
24
|
|
|
11
25
|
const DEFAULT_ENDPOINT = "https://api.travrse.ai/v1/dispatch";
|
|
12
26
|
|
|
27
|
+
/**
|
|
28
|
+
* Maps parserType string to the corresponding parser factory function
|
|
29
|
+
*/
|
|
30
|
+
function getParserFromType(parserType?: "plain" | "json" | "regex-json" | "xml"): () => AgentWidgetStreamParser {
|
|
31
|
+
switch (parserType) {
|
|
32
|
+
case "json":
|
|
33
|
+
return createJsonStreamParser;
|
|
34
|
+
case "regex-json":
|
|
35
|
+
return createRegexJsonParser;
|
|
36
|
+
case "xml":
|
|
37
|
+
return createXmlParser;
|
|
38
|
+
case "plain":
|
|
39
|
+
default:
|
|
40
|
+
return createPlainTextParser;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
13
44
|
export class AgentWidgetClient {
|
|
14
45
|
private readonly apiUrl: string;
|
|
15
46
|
private readonly headers: Record<string, string>;
|
|
16
47
|
private readonly debug: boolean;
|
|
17
48
|
private readonly createStreamParser: () => AgentWidgetStreamParser;
|
|
49
|
+
private readonly contextProviders: AgentWidgetContextProvider[];
|
|
50
|
+
private readonly requestMiddleware?: AgentWidgetRequestMiddleware;
|
|
18
51
|
|
|
19
52
|
constructor(private config: AgentWidgetConfig = {}) {
|
|
20
53
|
this.apiUrl = config.apiUrl ?? DEFAULT_ENDPOINT;
|
|
@@ -23,8 +56,10 @@ export class AgentWidgetClient {
|
|
|
23
56
|
...config.headers
|
|
24
57
|
};
|
|
25
58
|
this.debug = Boolean(config.debug);
|
|
26
|
-
// Use custom stream parser
|
|
27
|
-
this.createStreamParser = config.streamParser ??
|
|
59
|
+
// Use custom stream parser if provided, otherwise use parserType, or fall back to plain text parser
|
|
60
|
+
this.createStreamParser = config.streamParser ?? getParserFromType(config.parserType);
|
|
61
|
+
this.contextProviders = config.contextProviders ?? [];
|
|
62
|
+
this.requestMiddleware = config.requestMiddleware;
|
|
28
63
|
}
|
|
29
64
|
|
|
30
65
|
public async dispatch(options: DispatchOptions, onEvent: SSEHandler) {
|
|
@@ -35,23 +70,7 @@ export class AgentWidgetClient {
|
|
|
35
70
|
|
|
36
71
|
onEvent({ type: "status", status: "connecting" });
|
|
37
72
|
|
|
38
|
-
|
|
39
|
-
// Sort by createdAt to ensure chronological order (not local sequence)
|
|
40
|
-
const body = {
|
|
41
|
-
messages: options.messages
|
|
42
|
-
.slice()
|
|
43
|
-
.sort((a, b) => {
|
|
44
|
-
const timeA = new Date(a.createdAt).getTime();
|
|
45
|
-
const timeB = new Date(b.createdAt).getTime();
|
|
46
|
-
return timeA - timeB;
|
|
47
|
-
})
|
|
48
|
-
.map((message) => ({
|
|
49
|
-
role: message.role,
|
|
50
|
-
content: message.content,
|
|
51
|
-
createdAt: message.createdAt
|
|
52
|
-
})),
|
|
53
|
-
...(this.config.flowId && { flowId: this.config.flowId })
|
|
54
|
-
};
|
|
73
|
+
const body = await this.buildPayload(options.messages);
|
|
55
74
|
|
|
56
75
|
if (this.debug) {
|
|
57
76
|
// eslint-disable-next-line no-console
|
|
@@ -81,6 +100,73 @@ export class AgentWidgetClient {
|
|
|
81
100
|
}
|
|
82
101
|
}
|
|
83
102
|
|
|
103
|
+
private async buildPayload(
|
|
104
|
+
messages: AgentWidgetMessage[]
|
|
105
|
+
): Promise<AgentWidgetRequestPayload> {
|
|
106
|
+
const normalizedMessages = messages
|
|
107
|
+
.slice()
|
|
108
|
+
.sort((a, b) => {
|
|
109
|
+
const timeA = new Date(a.createdAt).getTime();
|
|
110
|
+
const timeB = new Date(b.createdAt).getTime();
|
|
111
|
+
return timeA - timeB;
|
|
112
|
+
})
|
|
113
|
+
.map((message) => ({
|
|
114
|
+
role: message.role,
|
|
115
|
+
content: message.content,
|
|
116
|
+
createdAt: message.createdAt
|
|
117
|
+
}));
|
|
118
|
+
|
|
119
|
+
const payload: AgentWidgetRequestPayload = {
|
|
120
|
+
messages: normalizedMessages,
|
|
121
|
+
...(this.config.flowId && { flowId: this.config.flowId })
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
if (this.contextProviders.length) {
|
|
125
|
+
const contextAggregate: Record<string, unknown> = {};
|
|
126
|
+
await Promise.all(
|
|
127
|
+
this.contextProviders.map(async (provider) => {
|
|
128
|
+
try {
|
|
129
|
+
const result = await provider({
|
|
130
|
+
messages,
|
|
131
|
+
config: this.config
|
|
132
|
+
});
|
|
133
|
+
if (result && typeof result === "object") {
|
|
134
|
+
Object.assign(contextAggregate, result);
|
|
135
|
+
}
|
|
136
|
+
} catch (error) {
|
|
137
|
+
if (typeof console !== "undefined") {
|
|
138
|
+
// eslint-disable-next-line no-console
|
|
139
|
+
console.warn("[AgentWidget] Context provider failed:", error);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
})
|
|
143
|
+
);
|
|
144
|
+
|
|
145
|
+
if (Object.keys(contextAggregate).length) {
|
|
146
|
+
payload.context = contextAggregate;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if (this.requestMiddleware) {
|
|
151
|
+
try {
|
|
152
|
+
const result = await this.requestMiddleware({
|
|
153
|
+
payload: { ...payload },
|
|
154
|
+
config: this.config
|
|
155
|
+
});
|
|
156
|
+
if (result && typeof result === "object") {
|
|
157
|
+
return result as AgentWidgetRequestPayload;
|
|
158
|
+
}
|
|
159
|
+
} catch (error) {
|
|
160
|
+
if (typeof console !== "undefined") {
|
|
161
|
+
// eslint-disable-next-line no-console
|
|
162
|
+
console.error("[AgentWidget] Request middleware error:", error);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
return payload;
|
|
168
|
+
}
|
|
169
|
+
|
|
84
170
|
private async streamResponse(
|
|
85
171
|
body: ReadableStream<Uint8Array>,
|
|
86
172
|
onEvent: SSEHandler
|
|
@@ -554,6 +640,8 @@ export class AgentWidgetClient {
|
|
|
554
640
|
// Accumulate raw content for structured format parsing
|
|
555
641
|
const rawBuffer = rawContentBuffers.get(assistant.id) ?? "";
|
|
556
642
|
const accumulatedRaw = rawBuffer + chunk;
|
|
643
|
+
// Store raw content for action parsing, but NEVER set assistant.content to raw JSON
|
|
644
|
+
assistant.rawContent = accumulatedRaw;
|
|
557
645
|
|
|
558
646
|
// Use stream parser to parse
|
|
559
647
|
if (!streamParsers.has(assistant.id)) {
|
|
@@ -578,6 +666,7 @@ export class AgentWidgetClient {
|
|
|
578
666
|
// Clear any raw buffer/parser since we're in plain text mode
|
|
579
667
|
rawContentBuffers.delete(assistant.id);
|
|
580
668
|
streamParsers.delete(assistant.id);
|
|
669
|
+
assistant.rawContent = undefined;
|
|
581
670
|
emitMessage(assistant);
|
|
582
671
|
continue;
|
|
583
672
|
}
|
|
@@ -606,15 +695,18 @@ export class AgentWidgetClient {
|
|
|
606
695
|
currentAssistant.content += chunk;
|
|
607
696
|
rawContentBuffers.delete(currentAssistant.id);
|
|
608
697
|
streamParsers.delete(currentAssistant.id);
|
|
698
|
+
currentAssistant.rawContent = undefined;
|
|
609
699
|
emitMessage(currentAssistant);
|
|
610
700
|
}
|
|
611
701
|
}
|
|
612
702
|
// Otherwise wait for more chunks (incomplete structured format)
|
|
703
|
+
// Don't emit message if parser hasn't extracted text yet
|
|
613
704
|
}).catch(() => {
|
|
614
705
|
// On error, treat as plain text
|
|
615
706
|
assistant.content += chunk;
|
|
616
707
|
rawContentBuffers.delete(assistant.id);
|
|
617
708
|
streamParsers.delete(assistant.id);
|
|
709
|
+
assistant.rawContent = undefined;
|
|
618
710
|
emitMessage(assistant);
|
|
619
711
|
});
|
|
620
712
|
} else {
|
|
@@ -633,17 +725,16 @@ export class AgentWidgetClient {
|
|
|
633
725
|
// Clear any raw buffer/parser if we were in structured format mode
|
|
634
726
|
rawContentBuffers.delete(assistant.id);
|
|
635
727
|
streamParsers.delete(assistant.id);
|
|
728
|
+
assistant.rawContent = undefined;
|
|
636
729
|
emitMessage(assistant);
|
|
637
730
|
}
|
|
638
731
|
// Otherwise wait for more chunks (incomplete structured format)
|
|
732
|
+
// Don't emit message if parser hasn't extracted text yet
|
|
639
733
|
}
|
|
640
734
|
|
|
641
|
-
//
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
assistant.content = currentText;
|
|
645
|
-
emitMessage(assistant);
|
|
646
|
-
}
|
|
735
|
+
// IMPORTANT: Don't call getExtractedText() and emit messages here
|
|
736
|
+
// This was causing raw JSON to be displayed because getExtractedText()
|
|
737
|
+
// wasn't extracting the "text" field correctly during streaming
|
|
647
738
|
}
|
|
648
739
|
if (payload.isComplete) {
|
|
649
740
|
const finalContent = payload.result?.response ?? assistant.content;
|
|
@@ -651,6 +742,7 @@ export class AgentWidgetClient {
|
|
|
651
742
|
// Check if we have raw content buffer that needs final processing
|
|
652
743
|
const rawBuffer = rawContentBuffers.get(assistant.id);
|
|
653
744
|
const contentToProcess = rawBuffer ?? ensureStringContent(finalContent);
|
|
745
|
+
assistant.rawContent = contentToProcess;
|
|
654
746
|
|
|
655
747
|
// Try to extract text from final structured content
|
|
656
748
|
const parser = streamParsers.get(assistant.id);
|
|
@@ -728,14 +820,18 @@ export class AgentWidgetClient {
|
|
|
728
820
|
if (parser) {
|
|
729
821
|
// First check if parser already extracted text during streaming
|
|
730
822
|
const currentExtractedText = parser.getExtractedText();
|
|
823
|
+
const rawBuffer = rawContentBuffers.get(assistant.id);
|
|
824
|
+
const contentToProcess = rawBuffer ?? ensureStringContent(finalContent);
|
|
825
|
+
|
|
826
|
+
// Always set rawContent so action parsers can access the raw JSON
|
|
827
|
+
assistant.rawContent = contentToProcess;
|
|
828
|
+
|
|
731
829
|
if (currentExtractedText !== null && currentExtractedText.trim() !== "") {
|
|
732
830
|
// We already have extracted text from streaming - use it
|
|
733
831
|
assistant.content = currentExtractedText;
|
|
734
832
|
hasExtractedText = true;
|
|
735
833
|
} else {
|
|
736
834
|
// No extracted text yet - try to extract from final content
|
|
737
|
-
const rawBuffer = rawContentBuffers.get(assistant.id);
|
|
738
|
-
const contentToProcess = rawBuffer ?? ensureStringContent(finalContent);
|
|
739
835
|
|
|
740
836
|
// Try fast path first
|
|
741
837
|
const extractedText = extractTextFromJson(contentToProcess);
|
|
@@ -794,6 +890,12 @@ export class AgentWidgetClient {
|
|
|
794
890
|
}
|
|
795
891
|
}
|
|
796
892
|
|
|
893
|
+
// Ensure rawContent is set even if there's no parser (for action parsing)
|
|
894
|
+
if (!assistant.rawContent) {
|
|
895
|
+
const rawBuffer = rawContentBuffers.get(assistant.id);
|
|
896
|
+
assistant.rawContent = rawBuffer ?? ensureStringContent(finalContent);
|
|
897
|
+
}
|
|
898
|
+
|
|
797
899
|
// Only show raw content if we never extracted any text and no buffer was used
|
|
798
900
|
if (!hasExtractedText && !rawContentBuffers.has(assistant.id)) {
|
|
799
901
|
// No extracted text and no streaming happened - show raw content
|
|
@@ -825,6 +927,7 @@ export class AgentWidgetClient {
|
|
|
825
927
|
// Check if we have raw content buffer that needs final processing
|
|
826
928
|
const rawBuffer = rawContentBuffers.get(assistant.id);
|
|
827
929
|
const stringContent = rawBuffer ?? ensureStringContent(finalContent);
|
|
930
|
+
assistant.rawContent = stringContent;
|
|
828
931
|
// Try to extract text from structured content
|
|
829
932
|
let displayContent = ensureStringContent(finalContent);
|
|
830
933
|
const parser = streamParsers.get(assistant.id);
|
|
@@ -5,6 +5,7 @@ export type MessageTransform = (context: {
|
|
|
5
5
|
text: string;
|
|
6
6
|
message: AgentWidgetMessage;
|
|
7
7
|
streaming: boolean;
|
|
8
|
+
raw?: string;
|
|
8
9
|
}) => string;
|
|
9
10
|
|
|
10
11
|
// Create typing indicator element
|
|
@@ -41,6 +42,7 @@ export const createStandardBubble = (
|
|
|
41
42
|
transform: MessageTransform
|
|
42
43
|
): HTMLElement => {
|
|
43
44
|
const classes = [
|
|
45
|
+
"vanilla-message-bubble",
|
|
44
46
|
"tvw-max-w-[85%]",
|
|
45
47
|
"tvw-rounded-2xl",
|
|
46
48
|
"tvw-text-sm",
|
|
@@ -50,6 +52,7 @@ export const createStandardBubble = (
|
|
|
50
52
|
|
|
51
53
|
if (message.role === "user") {
|
|
52
54
|
classes.push(
|
|
55
|
+
"vanilla-message-user-bubble",
|
|
53
56
|
"tvw-ml-auto",
|
|
54
57
|
"tvw-bg-cw-accent",
|
|
55
58
|
"tvw-text-white",
|
|
@@ -58,6 +61,7 @@ export const createStandardBubble = (
|
|
|
58
61
|
);
|
|
59
62
|
} else {
|
|
60
63
|
classes.push(
|
|
64
|
+
"vanilla-message-assistant-bubble",
|
|
61
65
|
"tvw-bg-cw-surface",
|
|
62
66
|
"tvw-border",
|
|
63
67
|
"tvw-border-cw-message-border",
|
|
@@ -74,7 +78,8 @@ export const createStandardBubble = (
|
|
|
74
78
|
contentDiv.innerHTML = transform({
|
|
75
79
|
text: message.content,
|
|
76
80
|
message,
|
|
77
|
-
streaming: Boolean(message.streaming)
|
|
81
|
+
streaming: Boolean(message.streaming),
|
|
82
|
+
raw: message.rawContent
|
|
78
83
|
});
|
|
79
84
|
bubble.appendChild(contentDiv);
|
|
80
85
|
|
package/src/index.ts
CHANGED
|
@@ -25,6 +25,12 @@ export {
|
|
|
25
25
|
type AgentWidgetSessionStatus
|
|
26
26
|
} from "./session";
|
|
27
27
|
export { AgentWidgetClient } from "./client";
|
|
28
|
+
export { createLocalStorageAdapter } from "./utils/storage";
|
|
29
|
+
export {
|
|
30
|
+
createActionManager,
|
|
31
|
+
defaultActionHandlers,
|
|
32
|
+
defaultJsonActionParser
|
|
33
|
+
} from "./utils/actions";
|
|
28
34
|
export {
|
|
29
35
|
markdownPostprocessor,
|
|
30
36
|
escapeHtml,
|
package/src/runtime/init.ts
CHANGED
|
@@ -113,46 +113,18 @@ export const initAgentWidget = (
|
|
|
113
113
|
mountStyles(host);
|
|
114
114
|
}
|
|
115
115
|
|
|
116
|
-
let controller = createAgentExperience(mount, options.config
|
|
116
|
+
let controller = createAgentExperience(mount, options.config, {
|
|
117
|
+
debugTools: options.debugTools
|
|
118
|
+
});
|
|
117
119
|
options.onReady?.();
|
|
118
120
|
|
|
119
121
|
const handle: AgentWidgetInitHandle = {
|
|
122
|
+
...controller,
|
|
120
123
|
host,
|
|
121
|
-
update(nextConfig: AgentWidgetConfig) {
|
|
122
|
-
controller.update(nextConfig);
|
|
123
|
-
},
|
|
124
|
-
open() {
|
|
125
|
-
controller.open();
|
|
126
|
-
},
|
|
127
|
-
close() {
|
|
128
|
-
controller.close();
|
|
129
|
-
},
|
|
130
|
-
toggle() {
|
|
131
|
-
controller.toggle();
|
|
132
|
-
},
|
|
133
|
-
clearChat() {
|
|
134
|
-
controller.clearChat();
|
|
135
|
-
},
|
|
136
|
-
setMessage(message: string) {
|
|
137
|
-
return controller.setMessage(message);
|
|
138
|
-
},
|
|
139
|
-
submitMessage(message?: string) {
|
|
140
|
-
return controller.submitMessage(message);
|
|
141
|
-
},
|
|
142
|
-
startVoiceRecognition() {
|
|
143
|
-
return controller.startVoiceRecognition();
|
|
144
|
-
},
|
|
145
|
-
stopVoiceRecognition() {
|
|
146
|
-
return controller.stopVoiceRecognition();
|
|
147
|
-
},
|
|
148
|
-
injectTestMessage(event: AgentWidgetEvent) {
|
|
149
|
-
controller.injectTestMessage(event);
|
|
150
|
-
},
|
|
151
124
|
destroy() {
|
|
152
125
|
controller.destroy();
|
|
153
126
|
host.remove();
|
|
154
|
-
|
|
155
|
-
if (options.windowKey && typeof window !== 'undefined') {
|
|
127
|
+
if (options.windowKey && typeof window !== "undefined") {
|
|
156
128
|
delete (window as any)[options.windowKey];
|
|
157
129
|
}
|
|
158
130
|
}
|
package/src/session.ts
CHANGED
|
@@ -133,6 +133,21 @@ export class AgentWidgetSession {
|
|
|
133
133
|
this.callbacks.onMessagesChanged([...this.messages]);
|
|
134
134
|
}
|
|
135
135
|
|
|
136
|
+
public hydrateMessages(messages: AgentWidgetMessage[]) {
|
|
137
|
+
this.abortController?.abort();
|
|
138
|
+
this.abortController = null;
|
|
139
|
+
this.messages = this.sortMessages(
|
|
140
|
+
messages.map((message) => ({
|
|
141
|
+
...message,
|
|
142
|
+
streaming: false,
|
|
143
|
+
sequence: message.sequence ?? this.nextSequence()
|
|
144
|
+
}))
|
|
145
|
+
);
|
|
146
|
+
this.setStreaming(false);
|
|
147
|
+
this.setStatus("idle");
|
|
148
|
+
this.callbacks.onMessagesChanged([...this.messages]);
|
|
149
|
+
}
|
|
150
|
+
|
|
136
151
|
private handleEvent = (event: AgentWidgetEvent) => {
|
|
137
152
|
if (event.type === "message") {
|
|
138
153
|
this.upsertMessage(event.message);
|
package/src/styles/widget.css
CHANGED
|
@@ -797,7 +797,7 @@ form:focus-within textarea {
|
|
|
797
797
|
}
|
|
798
798
|
|
|
799
799
|
/* Markdown content overflow handling */
|
|
800
|
-
|
|
800
|
+
.vanilla-message-bubble pre {
|
|
801
801
|
overflow-x: auto;
|
|
802
802
|
max-width: 100%;
|
|
803
803
|
word-wrap: break-word;
|
|
@@ -812,7 +812,7 @@ form:focus-within textarea {
|
|
|
812
812
|
border: 1px solid #e5e7eb;
|
|
813
813
|
}
|
|
814
814
|
|
|
815
|
-
|
|
815
|
+
.vanilla-message-bubble code {
|
|
816
816
|
word-break: break-word;
|
|
817
817
|
word-wrap: break-word;
|
|
818
818
|
white-space: pre-wrap;
|
|
@@ -821,14 +821,14 @@ form:focus-within textarea {
|
|
|
821
821
|
font-size: 0.875em;
|
|
822
822
|
}
|
|
823
823
|
|
|
824
|
-
|
|
824
|
+
.vanilla-message-bubble pre code {
|
|
825
825
|
font-size: inherit;
|
|
826
826
|
background-color: transparent;
|
|
827
827
|
padding: 0;
|
|
828
828
|
border-radius: 0;
|
|
829
829
|
}
|
|
830
830
|
|
|
831
|
-
|
|
831
|
+
.vanilla-message-bubble img {
|
|
832
832
|
max-width: 100%;
|
|
833
833
|
height: auto;
|
|
834
834
|
display: block;
|
|
@@ -836,11 +836,28 @@ form:focus-within textarea {
|
|
|
836
836
|
border-radius: 0.375rem;
|
|
837
837
|
}
|
|
838
838
|
|
|
839
|
+
/* Ensure all links in chat bubbles have underlines */
|
|
840
|
+
.vanilla-message-bubble a {
|
|
841
|
+
text-decoration: underline;
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
.vanilla-message-bubble a:visited {
|
|
845
|
+
text-decoration: underline;
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
.vanilla-message-bubble a:hover {
|
|
849
|
+
text-decoration: underline;
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
.vanilla-message-bubble a:active {
|
|
853
|
+
text-decoration: underline;
|
|
854
|
+
}
|
|
855
|
+
|
|
839
856
|
/* Ensure links in user messages match the text color */
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
857
|
+
.vanilla-message-user-bubble a,
|
|
858
|
+
.vanilla-message-user-bubble a:visited,
|
|
859
|
+
.vanilla-message-user-bubble a:hover,
|
|
860
|
+
.vanilla-message-user-bubble a:active {
|
|
844
861
|
color: inherit;
|
|
845
862
|
text-decoration: underline;
|
|
846
863
|
}
|