@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
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
import { Marked, type RendererObject } from "marked";
|
|
2
|
+
import type { AgentWidgetMarkdownConfig, AgentWidgetMarkdownRendererOverrides, AgentWidgetMarkdownOptions } from "./types";
|
|
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
|
+
};
|
|
26
|
+
|
|
27
|
+
/**
|
|
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()`.
|
|
95
|
+
*/
|
|
96
|
+
export const markdownPostprocessor = (text: string): string => {
|
|
97
|
+
return defaultMarkdownProcessor(text);
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Escapes HTML entities. Used as the default safe renderer.
|
|
102
|
+
*/
|
|
103
|
+
export const escapeHtml = (text: string): string =>
|
|
104
|
+
text
|
|
105
|
+
.replace(/&/g, "&")
|
|
106
|
+
.replace(/</g, "<")
|
|
107
|
+
.replace(/>/g, ">")
|
|
108
|
+
.replace(/"/g, """)
|
|
109
|
+
.replace(/'/g, "'");
|
|
110
|
+
|
|
111
|
+
const escapeAttribute = (value: string) =>
|
|
112
|
+
value.replace(/"/g, """).replace(/</g, "<").replace(/>/g, ">");
|
|
113
|
+
|
|
114
|
+
const makeToken = (idx: number) => `%%FORM_PLACEHOLDER_${idx}%%`;
|
|
115
|
+
|
|
116
|
+
const directiveReplacer = (source: string, placeholders: Array<{ token: string; type: string }>) => {
|
|
117
|
+
let working = source;
|
|
118
|
+
|
|
119
|
+
// JSON directive pattern e.g. <Directive>{"component":"form","type":"init"}</Directive>
|
|
120
|
+
working = working.replace(/<Directive>([\s\S]*?)<\/Directive>/gi, (match, jsonText) => {
|
|
121
|
+
try {
|
|
122
|
+
const parsed = JSON.parse(jsonText.trim());
|
|
123
|
+
if (parsed && typeof parsed === "object" && parsed.component === "form" && parsed.type) {
|
|
124
|
+
const token = makeToken(placeholders.length);
|
|
125
|
+
placeholders.push({ token, type: String(parsed.type) });
|
|
126
|
+
return token;
|
|
127
|
+
}
|
|
128
|
+
} catch (error) {
|
|
129
|
+
return match;
|
|
130
|
+
}
|
|
131
|
+
return match;
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
// XML-style directive e.g. <Form type="init" />
|
|
135
|
+
working = working.replace(/<Form\s+type="([^"]+)"\s*\/>/gi, (_, type) => {
|
|
136
|
+
const token = makeToken(placeholders.length);
|
|
137
|
+
placeholders.push({ token, type });
|
|
138
|
+
return token;
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
return working;
|
|
142
|
+
};
|
|
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
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Converts special directives (either `<Form type="init" />` or
|
|
175
|
+
* `<Directive>{"component":"form","type":"init"}</Directive>`) into placeholder
|
|
176
|
+
* elements that the widget upgrades after render. Remaining text is rendered as
|
|
177
|
+
* Markdown using default settings.
|
|
178
|
+
*
|
|
179
|
+
* For custom markdown configuration, use `createDirectivePostprocessor()`.
|
|
180
|
+
*/
|
|
181
|
+
export const directivePostprocessor = (text: string): string => {
|
|
182
|
+
const placeholders: Array<{ token: string; type: string }> = [];
|
|
183
|
+
const withTokens = directiveReplacer(text, placeholders);
|
|
184
|
+
let html = markdownPostprocessor(withTokens);
|
|
185
|
+
|
|
186
|
+
placeholders.forEach(({ token, type }) => {
|
|
187
|
+
const tokenRegex = new RegExp(token.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), "g");
|
|
188
|
+
const safeType = escapeAttribute(type);
|
|
189
|
+
const replacement = `<div class="tvw-form-directive" data-tv-form="${safeType}"></div>`;
|
|
190
|
+
html = html.replace(tokenRegex, replacement);
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
return html;
|
|
194
|
+
};
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import { createAgentExperience, AgentWidgetController } from "../ui";
|
|
2
|
+
import { AgentWidgetConfig, AgentWidgetInitOptions, AgentWidgetEvent } from "../types";
|
|
3
|
+
|
|
4
|
+
const ensureTarget = (target: string | HTMLElement): HTMLElement => {
|
|
5
|
+
if (typeof window === "undefined" || typeof document === "undefined") {
|
|
6
|
+
throw new Error("Chat widget can only be mounted in a browser environment");
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
if (typeof target === "string") {
|
|
10
|
+
const element = document.querySelector<HTMLElement>(target);
|
|
11
|
+
if (!element) {
|
|
12
|
+
throw new Error(`Chat widget target "${target}" was not found`);
|
|
13
|
+
}
|
|
14
|
+
return element;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
return target;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
const widgetCssHref = (): string | null => {
|
|
21
|
+
try {
|
|
22
|
+
// This works in ESM builds but not in IIFE builds
|
|
23
|
+
if (typeof import.meta !== "undefined" && import.meta.url) {
|
|
24
|
+
return new URL("../widget.css", import.meta.url).href;
|
|
25
|
+
}
|
|
26
|
+
} catch {
|
|
27
|
+
// Fallback for IIFE builds where CSS should be loaded separately
|
|
28
|
+
}
|
|
29
|
+
return null;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const mountStyles = (root: ShadowRoot | HTMLElement) => {
|
|
33
|
+
const href = widgetCssHref();
|
|
34
|
+
|
|
35
|
+
const adoptExistingStylesheet = () => {
|
|
36
|
+
if (!(root instanceof ShadowRoot)) {
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (root.querySelector('link[data-persona]')) {
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const globalLink = document.head.querySelector<HTMLLinkElement>(
|
|
45
|
+
'link[data-persona]'
|
|
46
|
+
);
|
|
47
|
+
if (!globalLink) {
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const clonedLink = globalLink.cloneNode(true) as HTMLLinkElement;
|
|
52
|
+
root.insertBefore(clonedLink, root.firstChild);
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
if (root instanceof ShadowRoot) {
|
|
56
|
+
// For shadow DOM, we need to load CSS into the shadow root
|
|
57
|
+
if (href) {
|
|
58
|
+
const link = document.createElement("link");
|
|
59
|
+
link.rel = "stylesheet";
|
|
60
|
+
link.href = href;
|
|
61
|
+
link.setAttribute("data-persona", "true");
|
|
62
|
+
root.insertBefore(link, root.firstChild);
|
|
63
|
+
} else {
|
|
64
|
+
adoptExistingStylesheet();
|
|
65
|
+
}
|
|
66
|
+
// If href is null (IIFE build), CSS should already be loaded globally
|
|
67
|
+
} else {
|
|
68
|
+
// For non-shadow DOM, check if CSS is already loaded
|
|
69
|
+
const existing = document.head.querySelector<HTMLLinkElement>(
|
|
70
|
+
"link[data-persona]"
|
|
71
|
+
);
|
|
72
|
+
if (!existing) {
|
|
73
|
+
if (href) {
|
|
74
|
+
// ESM build - load CSS dynamically
|
|
75
|
+
const link = document.createElement("link");
|
|
76
|
+
link.rel = "stylesheet";
|
|
77
|
+
link.href = href;
|
|
78
|
+
link.setAttribute("data-persona", "true");
|
|
79
|
+
document.head.appendChild(link);
|
|
80
|
+
}
|
|
81
|
+
// IIFE build - CSS should be loaded via <link> tag before script
|
|
82
|
+
// If not found, we'll assume it's loaded globally or warn in dev
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
export type AgentWidgetInitHandle = AgentWidgetController & { host: HTMLElement };
|
|
88
|
+
|
|
89
|
+
export const initAgentWidget = (
|
|
90
|
+
options: AgentWidgetInitOptions
|
|
91
|
+
): AgentWidgetInitHandle => {
|
|
92
|
+
const target = ensureTarget(options.target);
|
|
93
|
+
const host = document.createElement("div");
|
|
94
|
+
host.className = "persona-host";
|
|
95
|
+
|
|
96
|
+
// When launcher is disabled (inline embed mode), ensure the host fills its container
|
|
97
|
+
const launcherEnabled = options.config?.launcher?.enabled ?? true;
|
|
98
|
+
if (!launcherEnabled) {
|
|
99
|
+
host.style.height = "100%";
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
target.appendChild(host);
|
|
103
|
+
|
|
104
|
+
const useShadow = options.useShadowDom === true;
|
|
105
|
+
let mount: HTMLElement;
|
|
106
|
+
let root: ShadowRoot | HTMLElement;
|
|
107
|
+
|
|
108
|
+
if (useShadow) {
|
|
109
|
+
const shadowRoot = host.attachShadow({ mode: "open" });
|
|
110
|
+
root = shadowRoot;
|
|
111
|
+
mount = document.createElement("div");
|
|
112
|
+
mount.id = "persona-root";
|
|
113
|
+
// When launcher is disabled, ensure mount fills the host
|
|
114
|
+
if (!launcherEnabled) {
|
|
115
|
+
mount.style.height = "100%";
|
|
116
|
+
mount.style.display = "flex";
|
|
117
|
+
mount.style.flexDirection = "column";
|
|
118
|
+
mount.style.flex = "1";
|
|
119
|
+
mount.style.minHeight = "0";
|
|
120
|
+
}
|
|
121
|
+
shadowRoot.appendChild(mount);
|
|
122
|
+
mountStyles(shadowRoot);
|
|
123
|
+
} else {
|
|
124
|
+
root = host;
|
|
125
|
+
mount = document.createElement("div");
|
|
126
|
+
mount.id = "persona-root";
|
|
127
|
+
// When launcher is disabled, ensure mount fills the host
|
|
128
|
+
if (!launcherEnabled) {
|
|
129
|
+
mount.style.height = "100%";
|
|
130
|
+
mount.style.display = "flex";
|
|
131
|
+
mount.style.flexDirection = "column";
|
|
132
|
+
mount.style.flex = "1";
|
|
133
|
+
mount.style.minHeight = "0";
|
|
134
|
+
}
|
|
135
|
+
host.appendChild(mount);
|
|
136
|
+
mountStyles(host);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
let controller = createAgentExperience(mount, options.config, {
|
|
140
|
+
debugTools: options.debugTools
|
|
141
|
+
});
|
|
142
|
+
options.onReady?.();
|
|
143
|
+
|
|
144
|
+
const handle: AgentWidgetInitHandle = {
|
|
145
|
+
...controller,
|
|
146
|
+
host,
|
|
147
|
+
destroy() {
|
|
148
|
+
controller.destroy();
|
|
149
|
+
host.remove();
|
|
150
|
+
if (options.windowKey && typeof window !== "undefined") {
|
|
151
|
+
delete (window as any)[options.windowKey];
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
// Store on window if windowKey is provided
|
|
157
|
+
if (options.windowKey && typeof window !== 'undefined') {
|
|
158
|
+
(window as any)[options.windowKey] = handle;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
return handle;
|
|
162
|
+
};
|