vanilla-agent 1.20.0 → 1.22.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 +87 -0
- package/dist/index.cjs +24 -24
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +608 -6
- package/dist/index.d.ts +608 -6
- package/dist/index.global.js +44 -44
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +23 -23
- package/dist/index.js.map +1 -1
- package/dist/install.global.js +1 -1
- package/dist/install.global.js.map +1 -1
- package/dist/widget.css +467 -5
- package/package.json +1 -1
- package/src/client.ts +215 -1
- package/src/components/launcher.ts +10 -1
- package/src/components/message-bubble.ts +208 -4
- package/src/components/messages.ts +10 -3
- package/src/defaults.ts +32 -0
- package/src/index.ts +20 -4
- package/src/install.ts +69 -7
- package/src/postprocessors.ts +124 -6
- package/src/session.ts +77 -1
- package/src/styles/widget.css +467 -5
- package/src/types.ts +487 -0
- package/src/ui.ts +40 -5
package/src/install.ts
CHANGED
|
@@ -12,6 +12,10 @@ interface SiteAgentInstallConfig {
|
|
|
12
12
|
target?: string | HTMLElement;
|
|
13
13
|
config?: any;
|
|
14
14
|
autoInit?: boolean;
|
|
15
|
+
// Client token mode options (can also be set via data attributes)
|
|
16
|
+
clientToken?: string;
|
|
17
|
+
flowId?: string;
|
|
18
|
+
apiUrl?: string;
|
|
15
19
|
}
|
|
16
20
|
|
|
17
21
|
declare global {
|
|
@@ -30,7 +34,45 @@ declare global {
|
|
|
30
34
|
}
|
|
31
35
|
(window as any).__siteAgentInstallerLoaded = true;
|
|
32
36
|
|
|
33
|
-
|
|
37
|
+
/**
|
|
38
|
+
* Read configuration from data attributes on the current script tag.
|
|
39
|
+
* Supports: data-travrse-token, data-flow-id, data-api-url
|
|
40
|
+
*/
|
|
41
|
+
const getConfigFromScript = (): Partial<SiteAgentInstallConfig> => {
|
|
42
|
+
// Try to get the current script element
|
|
43
|
+
const script = document.currentScript as HTMLScriptElement | null;
|
|
44
|
+
if (!script) return {};
|
|
45
|
+
|
|
46
|
+
const scriptConfig: Partial<SiteAgentInstallConfig> = {};
|
|
47
|
+
|
|
48
|
+
// Client token from data attribute (primary method for client token mode)
|
|
49
|
+
const token = script.getAttribute('data-travrse-token');
|
|
50
|
+
if (token) {
|
|
51
|
+
scriptConfig.clientToken = token;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Optional flow ID
|
|
55
|
+
const flowId = script.getAttribute('data-flow-id');
|
|
56
|
+
if (flowId) {
|
|
57
|
+
scriptConfig.flowId = flowId;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Optional API URL override
|
|
61
|
+
const apiUrl = script.getAttribute('data-api-url');
|
|
62
|
+
if (apiUrl) {
|
|
63
|
+
scriptConfig.apiUrl = apiUrl;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return scriptConfig;
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
// Get config from script attributes (must be called synchronously during script execution)
|
|
70
|
+
const scriptConfig = getConfigFromScript();
|
|
71
|
+
|
|
72
|
+
// Merge script attributes with window config (script attributes take precedence)
|
|
73
|
+
const windowConfig: SiteAgentInstallConfig = window.siteAgentConfig || {};
|
|
74
|
+
const config: SiteAgentInstallConfig = { ...windowConfig, ...scriptConfig };
|
|
75
|
+
|
|
34
76
|
const version = config.version || "latest";
|
|
35
77
|
const cdn = config.cdn || "jsdelivr";
|
|
36
78
|
const autoInit = config.autoInit !== false; // Default to true
|
|
@@ -113,14 +155,27 @@ declare global {
|
|
|
113
155
|
}
|
|
114
156
|
|
|
115
157
|
const target = config.target || "body";
|
|
116
|
-
// Merge
|
|
158
|
+
// Merge top-level config options into widget config
|
|
117
159
|
const widgetConfig = { ...config.config };
|
|
118
|
-
|
|
119
|
-
|
|
160
|
+
|
|
161
|
+
// Merge apiUrl from top-level config into widget config if present
|
|
162
|
+
if (config.apiUrl && !widgetConfig.apiUrl) {
|
|
163
|
+
widgetConfig.apiUrl = config.apiUrl;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Merge clientToken from top-level config into widget config if present
|
|
167
|
+
if (config.clientToken && !widgetConfig.clientToken) {
|
|
168
|
+
widgetConfig.clientToken = config.clientToken;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Merge flowId from top-level config into widget config if present
|
|
172
|
+
if (config.flowId && !widgetConfig.flowId) {
|
|
173
|
+
widgetConfig.flowId = config.flowId;
|
|
120
174
|
}
|
|
121
175
|
|
|
122
|
-
// Only initialize if
|
|
123
|
-
|
|
176
|
+
// Only initialize if we have either apiUrl OR clientToken (or other config)
|
|
177
|
+
const hasApiConfig = widgetConfig.apiUrl || widgetConfig.clientToken;
|
|
178
|
+
if (!hasApiConfig && Object.keys(widgetConfig).length === 0) {
|
|
124
179
|
return;
|
|
125
180
|
}
|
|
126
181
|
|
|
@@ -140,7 +195,14 @@ declare global {
|
|
|
140
195
|
await loadCSS();
|
|
141
196
|
await loadJS();
|
|
142
197
|
|
|
143
|
-
|
|
198
|
+
// Auto-init if we have config OR apiUrl OR clientToken
|
|
199
|
+
const shouldAutoInit = autoInit && (
|
|
200
|
+
config.config ||
|
|
201
|
+
config.apiUrl ||
|
|
202
|
+
config.clientToken
|
|
203
|
+
);
|
|
204
|
+
|
|
205
|
+
if (shouldAutoInit) {
|
|
144
206
|
// Wait a tick to ensure AgentWidget is fully initialized
|
|
145
207
|
setTimeout(initWidget, 0);
|
|
146
208
|
}
|
package/src/postprocessors.ts
CHANGED
|
@@ -1,13 +1,100 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Marked, type RendererObject } from "marked";
|
|
2
|
+
import type { AgentWidgetMarkdownConfig, AgentWidgetMarkdownRendererOverrides, AgentWidgetMarkdownOptions } from "./types";
|
|
2
3
|
|
|
3
|
-
|
|
4
|
+
/**
|
|
5
|
+
* Options for creating a markdown processor
|
|
6
|
+
*/
|
|
7
|
+
export type MarkdownProcessorOptions = {
|
|
8
|
+
/** Marked parsing options */
|
|
9
|
+
markedOptions?: AgentWidgetMarkdownOptions;
|
|
10
|
+
/** Custom renderer overrides */
|
|
11
|
+
renderer?: AgentWidgetMarkdownRendererOverrides;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Converts AgentWidgetMarkdownRendererOverrides to marked's RendererObject format
|
|
16
|
+
*/
|
|
17
|
+
const convertRendererOverrides = (
|
|
18
|
+
overrides?: AgentWidgetMarkdownRendererOverrides
|
|
19
|
+
): Partial<RendererObject> | undefined => {
|
|
20
|
+
if (!overrides) return undefined;
|
|
21
|
+
|
|
22
|
+
// The token-based API in marked v12+ matches our type definitions
|
|
23
|
+
// We can pass through the overrides directly
|
|
24
|
+
return overrides as Partial<RendererObject>;
|
|
25
|
+
};
|
|
4
26
|
|
|
5
27
|
/**
|
|
6
|
-
*
|
|
7
|
-
*
|
|
28
|
+
* Creates a configured markdown processor with custom options and renderers.
|
|
29
|
+
*
|
|
30
|
+
* @param options - Configuration options for the markdown processor
|
|
31
|
+
* @returns A function that converts markdown text to HTML
|
|
32
|
+
*
|
|
33
|
+
* @example
|
|
34
|
+
* ```typescript
|
|
35
|
+
* // Basic usage with defaults
|
|
36
|
+
* const processor = createMarkdownProcessor();
|
|
37
|
+
* const html = processor("# Hello World");
|
|
38
|
+
*
|
|
39
|
+
* // With custom options
|
|
40
|
+
* const processor = createMarkdownProcessor({
|
|
41
|
+
* markedOptions: { gfm: true, breaks: true },
|
|
42
|
+
* renderer: {
|
|
43
|
+
* link(token) {
|
|
44
|
+
* return `<a href="${token.href}" target="_blank">${token.text}</a>`;
|
|
45
|
+
* }
|
|
46
|
+
* }
|
|
47
|
+
* });
|
|
48
|
+
* ```
|
|
49
|
+
*/
|
|
50
|
+
export const createMarkdownProcessor = (options?: MarkdownProcessorOptions) => {
|
|
51
|
+
const opts = options?.markedOptions;
|
|
52
|
+
const markedInstance = new Marked({
|
|
53
|
+
gfm: opts?.gfm ?? true,
|
|
54
|
+
breaks: opts?.breaks ?? true,
|
|
55
|
+
pedantic: opts?.pedantic,
|
|
56
|
+
silent: opts?.silent,
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
const rendererOverrides = convertRendererOverrides(options?.renderer);
|
|
60
|
+
if (rendererOverrides) {
|
|
61
|
+
markedInstance.use({ renderer: rendererOverrides });
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return (text: string): string => {
|
|
65
|
+
return markedInstance.parse(text) as string;
|
|
66
|
+
};
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Creates a markdown processor from AgentWidgetMarkdownConfig.
|
|
71
|
+
* This is a convenience function that maps the widget config to processor options.
|
|
72
|
+
*
|
|
73
|
+
* @param config - The markdown configuration from widget config
|
|
74
|
+
* @returns A function that converts markdown text to HTML
|
|
75
|
+
*/
|
|
76
|
+
export const createMarkdownProcessorFromConfig = (config?: AgentWidgetMarkdownConfig) => {
|
|
77
|
+
if (!config) {
|
|
78
|
+
return createMarkdownProcessor();
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return createMarkdownProcessor({
|
|
82
|
+
markedOptions: config.options,
|
|
83
|
+
renderer: config.renderer,
|
|
84
|
+
});
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
// Create default markdown processor instance
|
|
88
|
+
const defaultMarkdownProcessor = createMarkdownProcessor();
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Basic markdown renderer using default settings.
|
|
92
|
+
* Remember to sanitize the returned HTML if you render untrusted content in your host page.
|
|
93
|
+
*
|
|
94
|
+
* For custom configuration, use `createMarkdownProcessor()` or `createMarkdownProcessorFromConfig()`.
|
|
8
95
|
*/
|
|
9
96
|
export const markdownPostprocessor = (text: string): string => {
|
|
10
|
-
return
|
|
97
|
+
return defaultMarkdownProcessor(text);
|
|
11
98
|
};
|
|
12
99
|
|
|
13
100
|
/**
|
|
@@ -54,11 +141,42 @@ const directiveReplacer = (source: string, placeholders: Array<{ token: string;
|
|
|
54
141
|
return working;
|
|
55
142
|
};
|
|
56
143
|
|
|
144
|
+
/**
|
|
145
|
+
* Creates a directive postprocessor with custom markdown configuration.
|
|
146
|
+
* Converts special directives (either `<Form type="init" />` or
|
|
147
|
+
* `<Directive>{"component":"form","type":"init"}</Directive>`) into placeholder
|
|
148
|
+
* elements that the widget upgrades after render. Remaining text is rendered as
|
|
149
|
+
* Markdown with the provided configuration.
|
|
150
|
+
*
|
|
151
|
+
* @param markdownConfig - Optional markdown configuration
|
|
152
|
+
* @returns A function that processes text with directives and markdown
|
|
153
|
+
*/
|
|
154
|
+
export const createDirectivePostprocessor = (markdownConfig?: AgentWidgetMarkdownConfig) => {
|
|
155
|
+
const processor = createMarkdownProcessorFromConfig(markdownConfig);
|
|
156
|
+
|
|
157
|
+
return (text: string): string => {
|
|
158
|
+
const placeholders: Array<{ token: string; type: string }> = [];
|
|
159
|
+
const withTokens = directiveReplacer(text, placeholders);
|
|
160
|
+
let html = processor(withTokens);
|
|
161
|
+
|
|
162
|
+
placeholders.forEach(({ token, type }) => {
|
|
163
|
+
const tokenRegex = new RegExp(token.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), "g");
|
|
164
|
+
const safeType = escapeAttribute(type);
|
|
165
|
+
const replacement = `<div class="tvw-form-directive" data-tv-form="${safeType}"></div>`;
|
|
166
|
+
html = html.replace(tokenRegex, replacement);
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
return html;
|
|
170
|
+
};
|
|
171
|
+
};
|
|
172
|
+
|
|
57
173
|
/**
|
|
58
174
|
* Converts special directives (either `<Form type="init" />` or
|
|
59
175
|
* `<Directive>{"component":"form","type":"init"}</Directive>`) into placeholder
|
|
60
176
|
* elements that the widget upgrades after render. Remaining text is rendered as
|
|
61
|
-
* Markdown.
|
|
177
|
+
* Markdown using default settings.
|
|
178
|
+
*
|
|
179
|
+
* For custom markdown configuration, use `createDirectivePostprocessor()`.
|
|
62
180
|
*/
|
|
63
181
|
export const directivePostprocessor = (text: string): string => {
|
|
64
182
|
const placeholders: Array<{ token: string; type: string }> = [];
|
package/src/session.ts
CHANGED
|
@@ -2,7 +2,8 @@ import { AgentWidgetClient } from "./client";
|
|
|
2
2
|
import {
|
|
3
3
|
AgentWidgetConfig,
|
|
4
4
|
AgentWidgetEvent,
|
|
5
|
-
AgentWidgetMessage
|
|
5
|
+
AgentWidgetMessage,
|
|
6
|
+
ClientSession
|
|
6
7
|
} from "./types";
|
|
7
8
|
|
|
8
9
|
export type AgentWidgetSessionStatus =
|
|
@@ -25,6 +26,9 @@ export class AgentWidgetSession {
|
|
|
25
26
|
private streaming = false;
|
|
26
27
|
private abortController: AbortController | null = null;
|
|
27
28
|
private sequenceCounter = Date.now();
|
|
29
|
+
|
|
30
|
+
// Client token session management
|
|
31
|
+
private clientSession: ClientSession | null = null;
|
|
28
32
|
|
|
29
33
|
constructor(
|
|
30
34
|
private config: AgentWidgetConfig = {},
|
|
@@ -43,6 +47,78 @@ export class AgentWidgetSession {
|
|
|
43
47
|
this.callbacks.onStatusChanged(this.status);
|
|
44
48
|
}
|
|
45
49
|
|
|
50
|
+
/**
|
|
51
|
+
* Check if running in client token mode
|
|
52
|
+
*/
|
|
53
|
+
public isClientTokenMode(): boolean {
|
|
54
|
+
return this.client.isClientTokenMode();
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Initialize the client session (for client token mode).
|
|
59
|
+
* This is called automatically on first message, but can be called
|
|
60
|
+
* explicitly to pre-initialize the session and get config from server.
|
|
61
|
+
*/
|
|
62
|
+
public async initClientSession(): Promise<ClientSession | null> {
|
|
63
|
+
if (!this.isClientTokenMode()) {
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
try {
|
|
68
|
+
const session = await this.client.initSession();
|
|
69
|
+
this.setClientSession(session);
|
|
70
|
+
return session;
|
|
71
|
+
} catch (error) {
|
|
72
|
+
this.callbacks.onError?.(
|
|
73
|
+
error instanceof Error ? error : new Error(String(error))
|
|
74
|
+
);
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Set the client session after initialization
|
|
81
|
+
*/
|
|
82
|
+
public setClientSession(session: ClientSession): void {
|
|
83
|
+
this.clientSession = session;
|
|
84
|
+
|
|
85
|
+
// Optionally add welcome message from session config
|
|
86
|
+
if (session.config.welcomeMessage && this.messages.length === 0) {
|
|
87
|
+
const welcomeMessage: AgentWidgetMessage = {
|
|
88
|
+
id: `welcome-${Date.now()}`,
|
|
89
|
+
role: "assistant",
|
|
90
|
+
content: session.config.welcomeMessage,
|
|
91
|
+
createdAt: new Date().toISOString(),
|
|
92
|
+
sequence: this.nextSequence()
|
|
93
|
+
};
|
|
94
|
+
this.appendMessage(welcomeMessage);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Get current client session
|
|
100
|
+
*/
|
|
101
|
+
public getClientSession(): ClientSession | null {
|
|
102
|
+
return this.clientSession ?? this.client.getClientSession();
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Check if session is valid and not expired
|
|
107
|
+
*/
|
|
108
|
+
public isSessionValid(): boolean {
|
|
109
|
+
const session = this.getClientSession();
|
|
110
|
+
if (!session) return false;
|
|
111
|
+
return new Date() < session.expiresAt;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Clear session (on expiry or error)
|
|
116
|
+
*/
|
|
117
|
+
public clearClientSession(): void {
|
|
118
|
+
this.clientSession = null;
|
|
119
|
+
this.client.clearClientSession();
|
|
120
|
+
}
|
|
121
|
+
|
|
46
122
|
public updateConfig(next: AgentWidgetConfig) {
|
|
47
123
|
this.config = { ...this.config, ...next };
|
|
48
124
|
this.client = new AgentWidgetClient(this.config);
|