@heroui/agent 0.2.0-beta.1
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/CHANGELOG.md +64 -0
- package/LICENSE +21 -0
- package/README.md +205 -0
- package/dist/chart-content-VZ66GD22.js +1391 -0
- package/dist/chunk-3FG5NRTX.js +9 -0
- package/dist/chunk-CU6MPKAJ.js +359 -0
- package/dist/chunk-DW4AQRM5.js +297 -0
- package/dist/chunk-GDDNN2XY.js +954 -0
- package/dist/chunk-RQCTC4JB.js +1084 -0
- package/dist/chunk-TOOT6SZ2.js +74 -0
- package/dist/chunk-VJA52U5P.js +137 -0
- package/dist/component-renderer-IBJDNXSO.js +9780 -0
- package/dist/contracts.d.ts +1293 -0
- package/dist/contracts.js +2325 -0
- package/dist/css/index.css +2 -0
- package/dist/embed-runtime-XOPQY7Z5.js +7254 -0
- package/dist/identity-NYCXY1mT.d.ts +164 -0
- package/dist/index.d.ts +594 -0
- package/dist/index.js +28 -0
- package/dist/interactive-map-surface-67KHT3VB.js +362 -0
- package/dist/next.d.ts +4 -0
- package/dist/next.js +22 -0
- package/dist/server.d.ts +31 -0
- package/dist/server.js +256 -0
- package/package.json +133 -0
|
@@ -0,0 +1,954 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ComposerControlTooltip,
|
|
3
|
+
PanelShell,
|
|
4
|
+
PoweredByHero,
|
|
5
|
+
clearAgentComposerDraftsForProject,
|
|
6
|
+
isHeroUIAgentAttachmentContentType,
|
|
7
|
+
readAgentShellHandoff,
|
|
8
|
+
readSidebarWidth,
|
|
9
|
+
resizeComposerTextarea,
|
|
10
|
+
resolveOptions,
|
|
11
|
+
scheduleIdleTask,
|
|
12
|
+
writeAgentShellHandoff
|
|
13
|
+
} from "./chunk-RQCTC4JB.js";
|
|
14
|
+
|
|
15
|
+
// src/embed/provider.tsx
|
|
16
|
+
import {
|
|
17
|
+
useCallback,
|
|
18
|
+
useEffect as useEffect2,
|
|
19
|
+
useLayoutEffect,
|
|
20
|
+
useMemo,
|
|
21
|
+
useRef as useRef2,
|
|
22
|
+
useState as useState2,
|
|
23
|
+
useSyncExternalStore
|
|
24
|
+
} from "react";
|
|
25
|
+
import { createRoot } from "react-dom/client";
|
|
26
|
+
|
|
27
|
+
// src/embed/launcher-spark-icon.tsx
|
|
28
|
+
import { SparklesFill } from "@gravity-ui/icons";
|
|
29
|
+
import { jsx } from "react/jsx-runtime";
|
|
30
|
+
function LauncherSparkIcon(props) {
|
|
31
|
+
return /* @__PURE__ */ jsx(SparklesFill, { ...props });
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// src/embed/launcher-icon.tsx
|
|
35
|
+
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
36
|
+
function isSvgIcon(icon) {
|
|
37
|
+
return /\.svg(?:[?#]|$)/i.test(icon) || /^data:image\/svg\+xml[;,]/i.test(icon);
|
|
38
|
+
}
|
|
39
|
+
function cssUrl(value) {
|
|
40
|
+
return `url("${value.replace(/["\\\n\r]/g, encodeURIComponent)}")`;
|
|
41
|
+
}
|
|
42
|
+
function LauncherIcon({ icon }) {
|
|
43
|
+
if (!icon) return /* @__PURE__ */ jsx2(LauncherSparkIcon, {});
|
|
44
|
+
if (isSvgIcon(icon)) {
|
|
45
|
+
return /* @__PURE__ */ jsx2(
|
|
46
|
+
"span",
|
|
47
|
+
{
|
|
48
|
+
"aria-hidden": "true",
|
|
49
|
+
className: "ha-launcher__icon ha-launcher__icon--tinted",
|
|
50
|
+
style: { WebkitMaskImage: cssUrl(icon), maskImage: cssUrl(icon) }
|
|
51
|
+
}
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
return /* @__PURE__ */ jsx2("img", { alt: "", className: "ha-launcher__icon", src: icon });
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// src/embed/merge-remote-config.ts
|
|
58
|
+
function isPlainObject(value) {
|
|
59
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
60
|
+
}
|
|
61
|
+
function deepMerge(base, override) {
|
|
62
|
+
if (override === void 0) return base;
|
|
63
|
+
if (!isPlainObject(base) || !isPlainObject(override)) return override;
|
|
64
|
+
const merged = { ...base };
|
|
65
|
+
for (const [key, value] of Object.entries(override)) {
|
|
66
|
+
if (value === void 0) continue;
|
|
67
|
+
merged[key] = key in base ? deepMerge(base[key], value) : value;
|
|
68
|
+
}
|
|
69
|
+
return merged;
|
|
70
|
+
}
|
|
71
|
+
function mergeRemoteConfig(remote, props) {
|
|
72
|
+
if (!remote) return props;
|
|
73
|
+
const groups = {
|
|
74
|
+
appearance: remote.appearance,
|
|
75
|
+
capabilities: remote.capabilities,
|
|
76
|
+
composer: remote.composer,
|
|
77
|
+
markdown: remote.markdown,
|
|
78
|
+
permissions: remote.permissions,
|
|
79
|
+
responseActions: remote.responseActions,
|
|
80
|
+
startScreen: remote.startScreen
|
|
81
|
+
};
|
|
82
|
+
const merged = deepMerge(groups, {
|
|
83
|
+
appearance: props.appearance,
|
|
84
|
+
capabilities: props.capabilities,
|
|
85
|
+
composer: props.composer,
|
|
86
|
+
// Markdown renderer plugins are functions and never survive a JSON
|
|
87
|
+
// document, so they are merged back by identity in the provider.
|
|
88
|
+
markdown: props.markdown ? { ...props.markdown, plugins: void 0 } : void 0,
|
|
89
|
+
permissions: props.permissions,
|
|
90
|
+
responseActions: props.responseActions,
|
|
91
|
+
startScreen: props.startScreen
|
|
92
|
+
});
|
|
93
|
+
return { ...props, ...merged };
|
|
94
|
+
}
|
|
95
|
+
function hostSuppliesFont(props) {
|
|
96
|
+
return Boolean(props.appearance?.theme?.typography?.fontFamily?.trim());
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// src/contracts/appearance.ts
|
|
100
|
+
var HEROUI_AGENT_REMOTE_CONFIG_VERSION = 1;
|
|
101
|
+
var INVALID = Symbol("invalid");
|
|
102
|
+
function isRecord(value) {
|
|
103
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
104
|
+
}
|
|
105
|
+
function optionalString(value) {
|
|
106
|
+
if (value === void 0) return void 0;
|
|
107
|
+
return typeof value === "string" ? value : INVALID;
|
|
108
|
+
}
|
|
109
|
+
function optionalBoolean(value) {
|
|
110
|
+
if (value === void 0) return void 0;
|
|
111
|
+
return typeof value === "boolean" ? value : INVALID;
|
|
112
|
+
}
|
|
113
|
+
function optionalNumber(value) {
|
|
114
|
+
if (value === void 0) return void 0;
|
|
115
|
+
return typeof value === "number" && Number.isFinite(value) ? value : INVALID;
|
|
116
|
+
}
|
|
117
|
+
function optionalEnum(allowed) {
|
|
118
|
+
return (value) => {
|
|
119
|
+
if (value === void 0) return void 0;
|
|
120
|
+
return typeof value === "string" && allowed.includes(value) ? value : INVALID;
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
function optionalThemeColor(value) {
|
|
124
|
+
if (value === void 0) return void 0;
|
|
125
|
+
if (typeof value === "string") return value;
|
|
126
|
+
if (!isRecord(value)) return INVALID;
|
|
127
|
+
const dark = optionalString(value["dark"]);
|
|
128
|
+
const light = optionalString(value["light"]);
|
|
129
|
+
if (dark === INVALID || light === INVALID) return INVALID;
|
|
130
|
+
return { ...dark === void 0 ? {} : { dark }, ...light === void 0 ? {} : { light } };
|
|
131
|
+
}
|
|
132
|
+
function optionalStringRecord(value) {
|
|
133
|
+
if (value === void 0) return void 0;
|
|
134
|
+
if (!isRecord(value)) return INVALID;
|
|
135
|
+
const record = {};
|
|
136
|
+
for (const [key, entry] of Object.entries(value)) {
|
|
137
|
+
if (typeof entry !== "string") return INVALID;
|
|
138
|
+
record[key] = entry;
|
|
139
|
+
}
|
|
140
|
+
return record;
|
|
141
|
+
}
|
|
142
|
+
function optionalStringArray(value) {
|
|
143
|
+
if (value === void 0) return void 0;
|
|
144
|
+
if (!Array.isArray(value) || value.some((entry) => typeof entry !== "string")) return INVALID;
|
|
145
|
+
return value;
|
|
146
|
+
}
|
|
147
|
+
function shape(value, validators) {
|
|
148
|
+
if (value === void 0) return INVALID;
|
|
149
|
+
if (!isRecord(value)) return INVALID;
|
|
150
|
+
const result = {};
|
|
151
|
+
for (const [key, validate] of Object.entries(validators)) {
|
|
152
|
+
const parsed = validate(value[key]);
|
|
153
|
+
if (parsed === INVALID) return INVALID;
|
|
154
|
+
if (parsed !== void 0) result[key] = parsed;
|
|
155
|
+
}
|
|
156
|
+
return result;
|
|
157
|
+
}
|
|
158
|
+
function group(parsed) {
|
|
159
|
+
return parsed === INVALID ? void 0 : parsed;
|
|
160
|
+
}
|
|
161
|
+
var COLOR_KEYS = [
|
|
162
|
+
"accent",
|
|
163
|
+
"background",
|
|
164
|
+
"foreground",
|
|
165
|
+
"overlay",
|
|
166
|
+
"surface",
|
|
167
|
+
"surfaceSecondary",
|
|
168
|
+
"tooltip"
|
|
169
|
+
];
|
|
170
|
+
function parseAppearance(value) {
|
|
171
|
+
return shape(value, {
|
|
172
|
+
launcher: (launcher) => launcher === void 0 ? void 0 : shape(launcher, {
|
|
173
|
+
background: optionalThemeColor,
|
|
174
|
+
icon: optionalString,
|
|
175
|
+
position: optionalEnum(["bottom-left", "bottom-right"]),
|
|
176
|
+
style: optionalStringRecord
|
|
177
|
+
}),
|
|
178
|
+
panel: (panel) => panel === void 0 ? void 0 : shape(panel, {
|
|
179
|
+
expandable: optionalBoolean,
|
|
180
|
+
expanded: optionalBoolean,
|
|
181
|
+
initialHeight: (entry) => entry === void 0 || typeof entry === "string" ? entry : optionalNumber(entry),
|
|
182
|
+
initialWidth: (entry) => entry === void 0 || typeof entry === "string" ? entry : optionalNumber(entry)
|
|
183
|
+
}),
|
|
184
|
+
theme: (theme) => theme === void 0 ? void 0 : shape(theme, {
|
|
185
|
+
colorScheme: optionalEnum(["dark", "light", "system"]),
|
|
186
|
+
colors: (colors) => colors === void 0 ? void 0 : shape(
|
|
187
|
+
colors,
|
|
188
|
+
Object.fromEntries(COLOR_KEYS.map((key) => [key, optionalThemeColor]))
|
|
189
|
+
),
|
|
190
|
+
designTheme: optionalEnum(["base", "brutalism", "glass", "mouve"]),
|
|
191
|
+
radius: optionalEnum(["pill", "round", "sharp", "soft"]),
|
|
192
|
+
typography: (typography) => typography === void 0 ? void 0 : shape(typography, { baseSize: optionalNumber, fontFamily: optionalString })
|
|
193
|
+
}),
|
|
194
|
+
viewMode: optionalEnum(["floating", "sidebar"])
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
function parseComposer(value) {
|
|
198
|
+
return shape(value, {
|
|
199
|
+
attachments: (attachments) => {
|
|
200
|
+
if (attachments === void 0 || attachments === false) return attachments;
|
|
201
|
+
if (!Array.isArray(attachments)) return INVALID;
|
|
202
|
+
const types = attachments.filter(
|
|
203
|
+
(entry) => typeof entry === "string" && isHeroUIAgentAttachmentContentType(entry)
|
|
204
|
+
);
|
|
205
|
+
return types.length === attachments.length ? types : INVALID;
|
|
206
|
+
},
|
|
207
|
+
defaultModel: optionalString,
|
|
208
|
+
dictation: optionalBoolean,
|
|
209
|
+
disclaimer: (disclaimer) => disclaimer === false ? false : optionalString(disclaimer),
|
|
210
|
+
modelPicker: optionalBoolean,
|
|
211
|
+
placeholder: optionalString
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
function parseMarkdown(value) {
|
|
215
|
+
return shape(value, {
|
|
216
|
+
animated: (animated) => {
|
|
217
|
+
if (animated === void 0 || animated === false) return animated;
|
|
218
|
+
if (!isRecord(animated)) return INVALID;
|
|
219
|
+
const animation = optionalEnum(["blurIn", "fadeIn", "slideUp"])(
|
|
220
|
+
animated["animation"]
|
|
221
|
+
);
|
|
222
|
+
if (animation === INVALID || animation === void 0) return INVALID;
|
|
223
|
+
return { ...animated, animation };
|
|
224
|
+
},
|
|
225
|
+
caret: (caret) => caret === false ? false : optionalEnum(["block", "circle"])(caret)
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
function parseResponseActions(value) {
|
|
229
|
+
if (value === void 0 || value === false) return value;
|
|
230
|
+
if (!Array.isArray(value)) return INVALID;
|
|
231
|
+
const allowed = ["copy", "feedback", "retry"];
|
|
232
|
+
const actions = value.filter(
|
|
233
|
+
(entry) => typeof entry === "string" && allowed.includes(entry)
|
|
234
|
+
);
|
|
235
|
+
return actions.length === value.length ? actions : INVALID;
|
|
236
|
+
}
|
|
237
|
+
function parseAgentRemoteConfig(value) {
|
|
238
|
+
if (!isRecord(value)) return null;
|
|
239
|
+
if (value["version"] !== HEROUI_AGENT_REMOTE_CONFIG_VERSION) return null;
|
|
240
|
+
const revision = value["revision"];
|
|
241
|
+
if (typeof revision !== "string" || !revision) return null;
|
|
242
|
+
const appearance = group(parseAppearance(value["appearance"]));
|
|
243
|
+
const capabilities = group(
|
|
244
|
+
shape(value["capabilities"], {
|
|
245
|
+
imageSearch: optionalBoolean,
|
|
246
|
+
webSearch: optionalBoolean
|
|
247
|
+
})
|
|
248
|
+
);
|
|
249
|
+
const composer = group(parseComposer(value["composer"]));
|
|
250
|
+
const markdown = group(parseMarkdown(value["markdown"]));
|
|
251
|
+
const permissions = group(
|
|
252
|
+
shape(value["permissions"], {
|
|
253
|
+
defaultMode: optionalEnum(["ask", "auto", "full"]),
|
|
254
|
+
showPicker: optionalBoolean
|
|
255
|
+
})
|
|
256
|
+
);
|
|
257
|
+
const responseActions = group(parseResponseActions(value["responseActions"]));
|
|
258
|
+
const startScreen = group(
|
|
259
|
+
shape(value["startScreen"], {
|
|
260
|
+
greeting: optionalString,
|
|
261
|
+
promptShortcuts: optionalBoolean,
|
|
262
|
+
prompts: optionalStringArray
|
|
263
|
+
})
|
|
264
|
+
);
|
|
265
|
+
const webfont = group(
|
|
266
|
+
shape(value["webfont"], {
|
|
267
|
+
familyName: (familyName) => typeof familyName === "string" && familyName ? familyName : INVALID,
|
|
268
|
+
fontFaceUrl: optionalString,
|
|
269
|
+
stylesheetUrl: optionalString
|
|
270
|
+
})
|
|
271
|
+
);
|
|
272
|
+
return {
|
|
273
|
+
...appearance ? { appearance } : {},
|
|
274
|
+
...capabilities ? { capabilities } : {},
|
|
275
|
+
...composer ? { composer } : {},
|
|
276
|
+
...markdown ? { markdown } : {},
|
|
277
|
+
...permissions ? { permissions } : {},
|
|
278
|
+
...responseActions === void 0 ? {} : { responseActions },
|
|
279
|
+
...startScreen ? { startScreen } : {},
|
|
280
|
+
...webfont ? { webfont } : {},
|
|
281
|
+
revision,
|
|
282
|
+
version: HEROUI_AGENT_REMOTE_CONFIG_VERSION
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// src/embed/remote-config.ts
|
|
287
|
+
function cacheKey(agentId) {
|
|
288
|
+
return `heroui-agent:config:${agentId}`;
|
|
289
|
+
}
|
|
290
|
+
function readCachedRemoteConfig(agentId) {
|
|
291
|
+
try {
|
|
292
|
+
const raw = window.localStorage.getItem(cacheKey(agentId));
|
|
293
|
+
return raw ? parseAgentRemoteConfig(JSON.parse(raw)) : null;
|
|
294
|
+
} catch {
|
|
295
|
+
return null;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
function writeCachedRemoteConfig(agentId, config) {
|
|
299
|
+
try {
|
|
300
|
+
window.localStorage.setItem(cacheKey(agentId), JSON.stringify(config));
|
|
301
|
+
} catch {
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
function clearCachedRemoteConfig(agentId) {
|
|
305
|
+
try {
|
|
306
|
+
window.localStorage.removeItem(cacheKey(agentId));
|
|
307
|
+
} catch {
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
async function fetchRemoteConfig(apiBaseUrl, agentId, options = {}) {
|
|
311
|
+
try {
|
|
312
|
+
const response = await fetch(
|
|
313
|
+
`${apiBaseUrl}/v1/agents/${encodeURIComponent(agentId)}/appearance`,
|
|
314
|
+
{
|
|
315
|
+
headers: options.revision ? { "If-None-Match": `"${options.revision}"` } : {},
|
|
316
|
+
...options.signal ? { signal: options.signal } : {}
|
|
317
|
+
}
|
|
318
|
+
);
|
|
319
|
+
if (response.status === 304) return { status: "not-modified" };
|
|
320
|
+
if (response.status === 404 || response.status === 410) return { status: "missing" };
|
|
321
|
+
if (!response.ok) return { status: "unavailable" };
|
|
322
|
+
const config = parseAgentRemoteConfig(await response.json());
|
|
323
|
+
return config ? { config, status: "ok" } : { status: "unavailable" };
|
|
324
|
+
} catch {
|
|
325
|
+
return { status: "unavailable" };
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// src/embed/shell-conversation.tsx
|
|
330
|
+
import { useEffect, useRef, useState } from "react";
|
|
331
|
+
import { Fragment, jsx as jsx3, jsxs } from "react/jsx-runtime";
|
|
332
|
+
function ShellConversation({
|
|
333
|
+
agentId,
|
|
334
|
+
greeting,
|
|
335
|
+
onIntent,
|
|
336
|
+
placeholder,
|
|
337
|
+
suggestedPrompts
|
|
338
|
+
}) {
|
|
339
|
+
const [input, setInput] = useState(() => readAgentShellHandoff(agentId)?.prompt ?? "");
|
|
340
|
+
const [submitted, setSubmitted] = useState(false);
|
|
341
|
+
const textareaRef = useRef(null);
|
|
342
|
+
useEffect(() => {
|
|
343
|
+
if (submitted) return;
|
|
344
|
+
writeAgentShellHandoff(agentId, { prompt: input, submitted: false });
|
|
345
|
+
}, [input, agentId, submitted]);
|
|
346
|
+
const submit = (prompt) => {
|
|
347
|
+
const trimmed = prompt.trim();
|
|
348
|
+
if (!trimmed) return;
|
|
349
|
+
setSubmitted(true);
|
|
350
|
+
setInput(trimmed);
|
|
351
|
+
writeAgentShellHandoff(agentId, { prompt: trimmed, submitted: true });
|
|
352
|
+
onIntent();
|
|
353
|
+
};
|
|
354
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
355
|
+
/* @__PURE__ */ jsx3("div", { className: "ha-conversation", children: /* @__PURE__ */ jsxs("div", { "aria-live": "polite", className: "ha-messages", role: "log", children: [
|
|
356
|
+
/* @__PURE__ */ jsxs("div", { className: "ha-empty", children: [
|
|
357
|
+
/* @__PURE__ */ jsx3("h2", { children: greeting }),
|
|
358
|
+
/* @__PURE__ */ jsx3("p", { children: "Live answers with charts, metrics, and tables." })
|
|
359
|
+
] }),
|
|
360
|
+
submitted ? /* @__PURE__ */ jsx3("div", { className: "ha-message", "data-role": "user", children: /* @__PURE__ */ jsx3("div", { className: "ha-message-body", children: input }) }) : null
|
|
361
|
+
] }) }),
|
|
362
|
+
/* @__PURE__ */ jsxs(
|
|
363
|
+
"form",
|
|
364
|
+
{
|
|
365
|
+
className: "ha-composer-wrap",
|
|
366
|
+
onSubmit: (event) => {
|
|
367
|
+
event.preventDefault();
|
|
368
|
+
submit(input);
|
|
369
|
+
},
|
|
370
|
+
children: [
|
|
371
|
+
!submitted && suggestedPrompts.length > 0 ? /* @__PURE__ */ jsx3("div", { "aria-label": "Suggested prompts", className: "ha-suggestions", role: "group", children: suggestedPrompts.map((prompt) => /* @__PURE__ */ jsxs(
|
|
372
|
+
"button",
|
|
373
|
+
{
|
|
374
|
+
className: "ha-suggestion",
|
|
375
|
+
type: "button",
|
|
376
|
+
onClick: () => submit(prompt),
|
|
377
|
+
children: [
|
|
378
|
+
/* @__PURE__ */ jsx3("span", { children: prompt }),
|
|
379
|
+
/* @__PURE__ */ jsx3(ArrowUpRightGlyph, {})
|
|
380
|
+
]
|
|
381
|
+
},
|
|
382
|
+
prompt
|
|
383
|
+
)) }) : null,
|
|
384
|
+
/* @__PURE__ */ jsxs("div", { className: "@container ha-composer", children: [
|
|
385
|
+
/* @__PURE__ */ jsx3(
|
|
386
|
+
"textarea",
|
|
387
|
+
{
|
|
388
|
+
ref: textareaRef,
|
|
389
|
+
"aria-label": "Message the assistant",
|
|
390
|
+
placeholder,
|
|
391
|
+
rows: 1,
|
|
392
|
+
value: input,
|
|
393
|
+
onFocus: onIntent,
|
|
394
|
+
onChange: (event) => {
|
|
395
|
+
setInput(event.currentTarget.value);
|
|
396
|
+
resizeComposerTextarea(event.currentTarget);
|
|
397
|
+
onIntent();
|
|
398
|
+
},
|
|
399
|
+
onKeyDown: (event) => {
|
|
400
|
+
if (event.key !== "Enter" || event.shiftKey) return;
|
|
401
|
+
event.preventDefault();
|
|
402
|
+
submit(input);
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
),
|
|
406
|
+
/* @__PURE__ */ jsxs(
|
|
407
|
+
"button",
|
|
408
|
+
{
|
|
409
|
+
"aria-label": "Send message",
|
|
410
|
+
className: "ha-send",
|
|
411
|
+
"data-shortcut": "\u21B5",
|
|
412
|
+
"data-tooltip": "Send message",
|
|
413
|
+
disabled: !input.trim() || submitted,
|
|
414
|
+
type: "submit",
|
|
415
|
+
children: [
|
|
416
|
+
/* @__PURE__ */ jsx3(ArrowUpGlyph, {}),
|
|
417
|
+
/* @__PURE__ */ jsx3(ComposerControlTooltip, { shortcut: "\u21B5", children: "Send message" })
|
|
418
|
+
]
|
|
419
|
+
}
|
|
420
|
+
)
|
|
421
|
+
] }),
|
|
422
|
+
/* @__PURE__ */ jsx3(PoweredByHero, {})
|
|
423
|
+
]
|
|
424
|
+
}
|
|
425
|
+
)
|
|
426
|
+
] });
|
|
427
|
+
}
|
|
428
|
+
function ArrowUpGlyph() {
|
|
429
|
+
return /* @__PURE__ */ jsx3("svg", { "aria-hidden": "true", fill: "none", height: "18", viewBox: "0 0 18 18", width: "18", children: /* @__PURE__ */ jsx3(
|
|
430
|
+
"path",
|
|
431
|
+
{
|
|
432
|
+
d: "M9 14.5V3.5m0 0L4.5 8M9 3.5 13.5 8",
|
|
433
|
+
stroke: "currentColor",
|
|
434
|
+
strokeLinecap: "round",
|
|
435
|
+
strokeLinejoin: "round",
|
|
436
|
+
strokeWidth: "1.6"
|
|
437
|
+
}
|
|
438
|
+
) });
|
|
439
|
+
}
|
|
440
|
+
function ArrowUpRightGlyph() {
|
|
441
|
+
return /* @__PURE__ */ jsx3("svg", { "aria-hidden": "true", fill: "none", height: "16", viewBox: "0 0 16 16", width: "16", children: /* @__PURE__ */ jsx3(
|
|
442
|
+
"path",
|
|
443
|
+
{
|
|
444
|
+
d: "M5 11 11 5m0 0H6m5 0v5",
|
|
445
|
+
stroke: "currentColor",
|
|
446
|
+
strokeLinecap: "round",
|
|
447
|
+
strokeLinejoin: "round",
|
|
448
|
+
strokeWidth: "1.5"
|
|
449
|
+
}
|
|
450
|
+
) });
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
// src/embed/webfont.ts
|
|
454
|
+
var FONT_FILE_EXTENSIONS = [".woff2", ".woff", ".ttf", ".otf", ".eot"];
|
|
455
|
+
function elementId(font) {
|
|
456
|
+
return `heroui-agent-font-${font.familyName.toLowerCase().replace(/[^a-z0-9]+/g, "-")}`;
|
|
457
|
+
}
|
|
458
|
+
function fontFormat(url) {
|
|
459
|
+
const isVariable = url.includes(":vf@") || url.includes("-wght-") || url.includes("-opsz-");
|
|
460
|
+
if (url.endsWith(".woff")) return "woff";
|
|
461
|
+
if (url.endsWith(".ttf")) return "truetype";
|
|
462
|
+
if (url.endsWith(".otf")) return "opentype";
|
|
463
|
+
return isVariable ? "woff2-variations" : "woff2";
|
|
464
|
+
}
|
|
465
|
+
function isSafeFontUrl(url, requireFontFile) {
|
|
466
|
+
try {
|
|
467
|
+
const parsed = new URL(url);
|
|
468
|
+
if (parsed.protocol !== "https:") return false;
|
|
469
|
+
if (!requireFontFile) return true;
|
|
470
|
+
const pathname = parsed.pathname.toLowerCase();
|
|
471
|
+
return FONT_FILE_EXTENSIONS.some((extension) => pathname.endsWith(extension));
|
|
472
|
+
} catch {
|
|
473
|
+
return false;
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
function loadAgentWebfont(font) {
|
|
477
|
+
if (!font || typeof document === "undefined") return () => void 0;
|
|
478
|
+
const id = elementId(font);
|
|
479
|
+
if (document.getElementById(id)) return () => void 0;
|
|
480
|
+
if (font.stylesheetUrl && isSafeFontUrl(font.stylesheetUrl, false)) {
|
|
481
|
+
const link = document.createElement("link");
|
|
482
|
+
link.id = id;
|
|
483
|
+
link.rel = "stylesheet";
|
|
484
|
+
link.href = font.stylesheetUrl;
|
|
485
|
+
document.head.append(link);
|
|
486
|
+
return () => link.remove();
|
|
487
|
+
}
|
|
488
|
+
if (font.fontFaceUrl && isSafeFontUrl(font.fontFaceUrl, true)) {
|
|
489
|
+
const style = document.createElement("style");
|
|
490
|
+
const isVariable = font.fontFaceUrl.includes(":vf@") || font.fontFaceUrl.includes("-wght-") || font.fontFaceUrl.includes("-opsz-");
|
|
491
|
+
style.id = id;
|
|
492
|
+
style.textContent = [
|
|
493
|
+
"@font-face {",
|
|
494
|
+
` font-family: '${font.familyName.replaceAll("'", "")}';`,
|
|
495
|
+
" font-style: normal;",
|
|
496
|
+
" font-display: swap;",
|
|
497
|
+
` font-weight: ${isVariable ? "100 900" : "400"};`,
|
|
498
|
+
` src: url(${font.fontFaceUrl}) format('${fontFormat(font.fontFaceUrl)}');`,
|
|
499
|
+
"}"
|
|
500
|
+
].join("\n");
|
|
501
|
+
document.head.append(style);
|
|
502
|
+
return () => style.remove();
|
|
503
|
+
}
|
|
504
|
+
return () => void 0;
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
// src/embed/provider.tsx
|
|
508
|
+
import { Fragment as Fragment2, jsx as jsx4, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
509
|
+
var REMOTE_CONFIG_PAINT_BUDGET_MS = 600;
|
|
510
|
+
var RUNTIME_PREFETCH_IDLE_TIMEOUT_MS = 1200;
|
|
511
|
+
var embedRuntimePromise;
|
|
512
|
+
function loadEmbedRuntime() {
|
|
513
|
+
embedRuntimePromise ??= import(
|
|
514
|
+
/* webpackPrefetch: true */
|
|
515
|
+
"./embed-runtime-XOPQY7Z5.js"
|
|
516
|
+
).then(
|
|
517
|
+
(module) => module.default
|
|
518
|
+
);
|
|
519
|
+
return embedRuntimePromise;
|
|
520
|
+
}
|
|
521
|
+
var controllers = /* @__PURE__ */ new Map();
|
|
522
|
+
function subscribeToColorScheme(onChange) {
|
|
523
|
+
const media = window.matchMedia?.("(prefers-color-scheme: dark)");
|
|
524
|
+
media?.addEventListener?.("change", onChange);
|
|
525
|
+
return () => media?.removeEventListener?.("change", onChange);
|
|
526
|
+
}
|
|
527
|
+
function prefersDarkColorScheme() {
|
|
528
|
+
return window.matchMedia?.("(prefers-color-scheme: dark)").matches === true;
|
|
529
|
+
}
|
|
530
|
+
function dispatch(agentId, method) {
|
|
531
|
+
const controller = agentId ? controllers.get(agentId) : [...controllers.values()][controllers.size - 1];
|
|
532
|
+
if (!controller) {
|
|
533
|
+
console.warn("HeroUI Agent is not mounted yet. Add <HeroUIAgent /> to your layout.");
|
|
534
|
+
return;
|
|
535
|
+
}
|
|
536
|
+
controller[method]();
|
|
537
|
+
}
|
|
538
|
+
function useRemoteConfig(agentId, apiBaseUrl, enabled) {
|
|
539
|
+
const [state, setState] = useState2(() => {
|
|
540
|
+
if (!enabled || typeof window === "undefined") return { config: null, resolved: false };
|
|
541
|
+
const cached = readCachedRemoteConfig(agentId);
|
|
542
|
+
return { config: cached, resolved: Boolean(cached) };
|
|
543
|
+
});
|
|
544
|
+
useEffect2(() => {
|
|
545
|
+
if (!enabled) return;
|
|
546
|
+
const cached = readCachedRemoteConfig(agentId);
|
|
547
|
+
const controller = new AbortController();
|
|
548
|
+
const budget = window.setTimeout(
|
|
549
|
+
() => setState((current) => ({ ...current, resolved: true })),
|
|
550
|
+
REMOTE_CONFIG_PAINT_BUDGET_MS
|
|
551
|
+
);
|
|
552
|
+
void fetchRemoteConfig(apiBaseUrl, agentId, {
|
|
553
|
+
...cached?.revision ? { revision: cached.revision } : {},
|
|
554
|
+
signal: controller.signal
|
|
555
|
+
}).then((result) => {
|
|
556
|
+
if (controller.signal.aborted) return;
|
|
557
|
+
if (result.status === "ok") {
|
|
558
|
+
setState({ config: result.config, resolved: true });
|
|
559
|
+
writeCachedRemoteConfig(agentId, result.config);
|
|
560
|
+
}
|
|
561
|
+
if (result.status === "missing") {
|
|
562
|
+
setState({ config: null, resolved: true });
|
|
563
|
+
clearCachedRemoteConfig(agentId);
|
|
564
|
+
}
|
|
565
|
+
}).finally(() => {
|
|
566
|
+
if (controller.signal.aborted) return;
|
|
567
|
+
window.clearTimeout(budget);
|
|
568
|
+
setState((current) => ({ ...current, resolved: true }));
|
|
569
|
+
});
|
|
570
|
+
return () => {
|
|
571
|
+
controller.abort();
|
|
572
|
+
window.clearTimeout(budget);
|
|
573
|
+
};
|
|
574
|
+
}, [apiBaseUrl, enabled, agentId]);
|
|
575
|
+
return enabled ? state : { config: null, resolved: true };
|
|
576
|
+
}
|
|
577
|
+
function useAgent(agentId) {
|
|
578
|
+
return useMemo(
|
|
579
|
+
() => ({
|
|
580
|
+
hide: () => dispatch(agentId, "hide"),
|
|
581
|
+
newConversation: () => dispatch(agentId, "newConversation"),
|
|
582
|
+
preload: () => dispatch(agentId, "preload"),
|
|
583
|
+
refreshAuth: () => dispatch(agentId, "refreshAuth"),
|
|
584
|
+
show: () => dispatch(agentId, "show"),
|
|
585
|
+
shutdown: () => dispatch(agentId, "shutdown"),
|
|
586
|
+
toggle: () => dispatch(agentId, "toggle")
|
|
587
|
+
}),
|
|
588
|
+
[agentId]
|
|
589
|
+
);
|
|
590
|
+
}
|
|
591
|
+
function HeroUIAgent(props) {
|
|
592
|
+
const {
|
|
593
|
+
_api,
|
|
594
|
+
agentId,
|
|
595
|
+
appearance,
|
|
596
|
+
capabilities,
|
|
597
|
+
componentExports,
|
|
598
|
+
composer,
|
|
599
|
+
context,
|
|
600
|
+
getAuthToken,
|
|
601
|
+
markdown,
|
|
602
|
+
onFeedback,
|
|
603
|
+
permissions,
|
|
604
|
+
preload: preloadEnabled,
|
|
605
|
+
remoteConfig: remoteConfigEnabled = true,
|
|
606
|
+
responseActions,
|
|
607
|
+
showLauncher,
|
|
608
|
+
startScreen,
|
|
609
|
+
tools
|
|
610
|
+
} = props;
|
|
611
|
+
const markdownPlugins = markdown?.plugins;
|
|
612
|
+
const apiBaseUrl = (_api?.baseUrl ?? "https://api.heroui.com").replace(/\/$/, "");
|
|
613
|
+
const { config: remoteConfig, resolved: remoteConfigResolved } = useRemoteConfig(
|
|
614
|
+
agentId,
|
|
615
|
+
apiBaseUrl,
|
|
616
|
+
remoteConfigEnabled
|
|
617
|
+
);
|
|
618
|
+
const serializedConfig = JSON.stringify({
|
|
619
|
+
_api,
|
|
620
|
+
appearance,
|
|
621
|
+
capabilities,
|
|
622
|
+
componentExports,
|
|
623
|
+
composer,
|
|
624
|
+
markdown: markdown ? { ...markdown, plugins: void 0 } : void 0,
|
|
625
|
+
permissions,
|
|
626
|
+
preload: preloadEnabled,
|
|
627
|
+
remote: remoteConfig,
|
|
628
|
+
responseActions,
|
|
629
|
+
showLauncher,
|
|
630
|
+
startScreen
|
|
631
|
+
});
|
|
632
|
+
const options = useMemo(() => {
|
|
633
|
+
const { remote, ...config } = JSON.parse(serializedConfig);
|
|
634
|
+
const merged = mergeRemoteConfig(remote, {
|
|
635
|
+
...config,
|
|
636
|
+
agentId,
|
|
637
|
+
getAuthToken
|
|
638
|
+
});
|
|
639
|
+
return resolveOptions({
|
|
640
|
+
...merged,
|
|
641
|
+
context,
|
|
642
|
+
markdown: merged.markdown ? { ...merged.markdown, plugins: markdownPlugins } : markdownPlugins ? { plugins: markdownPlugins } : void 0,
|
|
643
|
+
onFeedback,
|
|
644
|
+
tools
|
|
645
|
+
});
|
|
646
|
+
}, [context, getAuthToken, markdownPlugins, onFeedback, agentId, serializedConfig, tools]);
|
|
647
|
+
const hostFont = hostSuppliesFont(props);
|
|
648
|
+
const remoteWebfont = remoteConfig?.webfont;
|
|
649
|
+
useEffect2(() => {
|
|
650
|
+
if (hostFont) return;
|
|
651
|
+
return loadAgentWebfont(remoteWebfont);
|
|
652
|
+
}, [hostFont, remoteWebfont]);
|
|
653
|
+
const [isOpen, setIsOpen] = useState2(false);
|
|
654
|
+
const [conversationEpoch, setConversationEpoch] = useState2(1);
|
|
655
|
+
const [identityEpoch, setIdentityEpoch] = useState2(1);
|
|
656
|
+
const [identityReset, setIdentityReset] = useState2(false);
|
|
657
|
+
const [requestedConversationId, setRequestedConversationId] = useState2(
|
|
658
|
+
void 0
|
|
659
|
+
);
|
|
660
|
+
const [portalRoot, setPortalRoot] = useState2(null);
|
|
661
|
+
const [EmbedRuntime, setEmbedRuntime] = useState2(null);
|
|
662
|
+
const conversationEpochRef = useRef2(1);
|
|
663
|
+
const launcherRef = useRef2(null);
|
|
664
|
+
const portalRootRef = useRef2(null);
|
|
665
|
+
const readyEpochRef = useRef2(null);
|
|
666
|
+
const shouldOpenRef = useRef2(false);
|
|
667
|
+
const hostRef = useRef2(null);
|
|
668
|
+
const embedReactRootRef = useRef2(null);
|
|
669
|
+
const rootPropertyNamesRef = useRef2([]);
|
|
670
|
+
const prefersDark = useSyncExternalStore(
|
|
671
|
+
subscribeToColorScheme,
|
|
672
|
+
prefersDarkColorScheme,
|
|
673
|
+
() => false
|
|
674
|
+
);
|
|
675
|
+
const designThemeClass = options.designTheme === "base" ? null : `${options.designTheme}-${options.colorScheme === "system" ? prefersDark ? "dark" : "light" : options.colorScheme}`;
|
|
676
|
+
const [shouldLoadRuntime, setShouldLoadRuntime] = useState2(false);
|
|
677
|
+
const requestRuntime = useCallback(() => setShouldLoadRuntime(true), []);
|
|
678
|
+
const [preloadRequested, setPreloadRequested] = useState2(false);
|
|
679
|
+
const shouldWarmSession = options.preload || preloadRequested;
|
|
680
|
+
useEffect2(() => {
|
|
681
|
+
if (!options.preload || shouldLoadRuntime) return;
|
|
682
|
+
return scheduleIdleTask(requestRuntime, RUNTIME_PREFETCH_IDLE_TIMEOUT_MS);
|
|
683
|
+
}, [options.preload, requestRuntime, shouldLoadRuntime]);
|
|
684
|
+
useEffect2(() => {
|
|
685
|
+
if (!shouldLoadRuntime) return;
|
|
686
|
+
let active = true;
|
|
687
|
+
void loadEmbedRuntime().then((runtime) => {
|
|
688
|
+
if (active) setEmbedRuntime(() => runtime);
|
|
689
|
+
}).catch(() => void 0);
|
|
690
|
+
return () => {
|
|
691
|
+
active = false;
|
|
692
|
+
};
|
|
693
|
+
}, [shouldLoadRuntime]);
|
|
694
|
+
useEffect2(() => {
|
|
695
|
+
if (!isOpen || options.viewMode !== "sidebar") return;
|
|
696
|
+
if (window.innerWidth < 640) return;
|
|
697
|
+
const target = document.documentElement;
|
|
698
|
+
const previousMargin = target.style.marginRight;
|
|
699
|
+
const previousTransition = target.style.transition;
|
|
700
|
+
const reducedMotion = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
701
|
+
const width = Math.min(readSidebarWidth(options.agentId, window.innerWidth), window.innerWidth);
|
|
702
|
+
if (!reducedMotion) {
|
|
703
|
+
target.style.transition = [previousTransition, "margin-right .28s cubic-bezier(.2,.8,.2,1)"].filter(Boolean).join(", ");
|
|
704
|
+
}
|
|
705
|
+
target.style.marginRight = `${width}px`;
|
|
706
|
+
return () => {
|
|
707
|
+
target.style.marginRight = previousMargin;
|
|
708
|
+
window.setTimeout(() => {
|
|
709
|
+
target.style.transition = previousTransition;
|
|
710
|
+
}, 300);
|
|
711
|
+
};
|
|
712
|
+
}, [isOpen, options.agentId, options.viewMode]);
|
|
713
|
+
useEffect2(() => {
|
|
714
|
+
const host = document.createElement("div");
|
|
715
|
+
const root = document.createElement("div");
|
|
716
|
+
host.dataset["herouiAgent"] = options.agentId;
|
|
717
|
+
root.className = "ha-root";
|
|
718
|
+
hostRef.current = host;
|
|
719
|
+
portalRootRef.current = root;
|
|
720
|
+
embedReactRootRef.current = createRoot(root);
|
|
721
|
+
host.append(root);
|
|
722
|
+
document.body.append(host);
|
|
723
|
+
let active = true;
|
|
724
|
+
queueMicrotask(() => {
|
|
725
|
+
if (active) setPortalRoot(root);
|
|
726
|
+
});
|
|
727
|
+
return () => {
|
|
728
|
+
active = false;
|
|
729
|
+
const reactRoot = embedReactRootRef.current;
|
|
730
|
+
portalRootRef.current = null;
|
|
731
|
+
hostRef.current = null;
|
|
732
|
+
embedReactRootRef.current = null;
|
|
733
|
+
rootPropertyNamesRef.current = [];
|
|
734
|
+
queueMicrotask(() => {
|
|
735
|
+
reactRoot?.unmount();
|
|
736
|
+
host.remove();
|
|
737
|
+
});
|
|
738
|
+
};
|
|
739
|
+
}, [options.agentId]);
|
|
740
|
+
useLayoutEffect(() => {
|
|
741
|
+
const root = portalRootRef.current;
|
|
742
|
+
const host = hostRef.current;
|
|
743
|
+
if (!root || !host) return;
|
|
744
|
+
root.className = designThemeClass ? `ha-root ${designThemeClass}` : "ha-root";
|
|
745
|
+
root.dataset["theme"] = options.colorScheme;
|
|
746
|
+
root.dataset["viewMode"] = options.viewMode;
|
|
747
|
+
root.dataset["position"] = options.launcherPosition;
|
|
748
|
+
host.dataset["open"] = isOpen ? "true" : "false";
|
|
749
|
+
host.dataset["viewMode"] = options.viewMode;
|
|
750
|
+
host.dataset["position"] = options.launcherPosition;
|
|
751
|
+
if (options.launcherOffset) {
|
|
752
|
+
host.style.setProperty("--ha-launcher-x", `${options.launcherOffset.x}px`);
|
|
753
|
+
host.style.setProperty("--ha-launcher-y", `${options.launcherOffset.y}px`);
|
|
754
|
+
} else {
|
|
755
|
+
host.style.removeProperty("--ha-launcher-x");
|
|
756
|
+
host.style.removeProperty("--ha-launcher-y");
|
|
757
|
+
}
|
|
758
|
+
if (options.viewMode === "sidebar") {
|
|
759
|
+
host.style.setProperty(
|
|
760
|
+
"--ha-sidebar-width",
|
|
761
|
+
`${readSidebarWidth(options.agentId, window.innerWidth)}px`
|
|
762
|
+
);
|
|
763
|
+
}
|
|
764
|
+
const rootProperties = { ...options.themeStyle, ...options.panelStyle };
|
|
765
|
+
for (const property of rootPropertyNamesRef.current) {
|
|
766
|
+
if (!(property in rootProperties)) root.style.removeProperty(property);
|
|
767
|
+
}
|
|
768
|
+
for (const [property, value] of Object.entries(rootProperties)) {
|
|
769
|
+
root.style.setProperty(property, value);
|
|
770
|
+
}
|
|
771
|
+
rootPropertyNamesRef.current = Object.keys(rootProperties);
|
|
772
|
+
}, [
|
|
773
|
+
designThemeClass,
|
|
774
|
+
isOpen,
|
|
775
|
+
options.colorScheme,
|
|
776
|
+
options.launcherOffset,
|
|
777
|
+
options.launcherPosition,
|
|
778
|
+
options.panelStyle,
|
|
779
|
+
options.themeStyle,
|
|
780
|
+
options.viewMode,
|
|
781
|
+
portalRoot
|
|
782
|
+
]);
|
|
783
|
+
const prepareNextConversation = useCallback((conversationId) => {
|
|
784
|
+
readyEpochRef.current = null;
|
|
785
|
+
setRequestedConversationId(conversationId);
|
|
786
|
+
setConversationEpoch((epoch) => {
|
|
787
|
+
const nextEpoch = epoch + 1;
|
|
788
|
+
conversationEpochRef.current = nextEpoch;
|
|
789
|
+
return nextEpoch;
|
|
790
|
+
});
|
|
791
|
+
}, []);
|
|
792
|
+
const handleRuntimeReady = useCallback((readyEpoch) => {
|
|
793
|
+
if (conversationEpochRef.current !== readyEpoch) return;
|
|
794
|
+
readyEpochRef.current = readyEpoch;
|
|
795
|
+
if (shouldOpenRef.current) setIsOpen(true);
|
|
796
|
+
}, []);
|
|
797
|
+
const hide = useCallback(() => {
|
|
798
|
+
shouldOpenRef.current = false;
|
|
799
|
+
setIsOpen(false);
|
|
800
|
+
prepareNextConversation();
|
|
801
|
+
requestAnimationFrame(() => launcherRef.current?.focus());
|
|
802
|
+
}, [prepareNextConversation]);
|
|
803
|
+
const show = useCallback(() => {
|
|
804
|
+
shouldOpenRef.current = true;
|
|
805
|
+
requestRuntime();
|
|
806
|
+
setIsOpen(true);
|
|
807
|
+
}, [requestRuntime]);
|
|
808
|
+
const preload = useCallback(() => {
|
|
809
|
+
setPreloadRequested(true);
|
|
810
|
+
requestRuntime();
|
|
811
|
+
}, [requestRuntime]);
|
|
812
|
+
const newConversation = useCallback(() => {
|
|
813
|
+
shouldOpenRef.current = true;
|
|
814
|
+
requestRuntime();
|
|
815
|
+
prepareNextConversation(null);
|
|
816
|
+
}, [prepareNextConversation, requestRuntime]);
|
|
817
|
+
const selectConversation = useCallback(
|
|
818
|
+
(conversationId) => {
|
|
819
|
+
shouldOpenRef.current = true;
|
|
820
|
+
requestRuntime();
|
|
821
|
+
prepareNextConversation(conversationId);
|
|
822
|
+
},
|
|
823
|
+
[prepareNextConversation, requestRuntime]
|
|
824
|
+
);
|
|
825
|
+
const toggle = useCallback(() => {
|
|
826
|
+
if (isOpen || shouldOpenRef.current) hide();
|
|
827
|
+
else show();
|
|
828
|
+
}, [hide, isOpen, show]);
|
|
829
|
+
const refreshAuth = useCallback(() => {
|
|
830
|
+
setIdentityReset(false);
|
|
831
|
+
setIdentityEpoch((epoch) => epoch + 1);
|
|
832
|
+
}, []);
|
|
833
|
+
const shutdown = useCallback(() => {
|
|
834
|
+
const prefixes = [
|
|
835
|
+
`heroui-agent:anonymous:${options.agentId}`,
|
|
836
|
+
`heroui-agent:conversation:${options.agentId}:`,
|
|
837
|
+
`heroui-agent:history:${options.agentId}:`,
|
|
838
|
+
`heroui-agent:session:${options.agentId}:`
|
|
839
|
+
];
|
|
840
|
+
for (let index = localStorage.length - 1; index >= 0; index -= 1) {
|
|
841
|
+
const key = localStorage.key(index);
|
|
842
|
+
if (key && prefixes.some((prefix) => key.startsWith(prefix))) localStorage.removeItem(key);
|
|
843
|
+
}
|
|
844
|
+
shouldOpenRef.current = false;
|
|
845
|
+
setIsOpen(false);
|
|
846
|
+
setIdentityReset(true);
|
|
847
|
+
setIdentityEpoch((epoch) => epoch + 1);
|
|
848
|
+
prepareNextConversation();
|
|
849
|
+
window.setTimeout(() => {
|
|
850
|
+
void clearAgentComposerDraftsForProject(options.agentId);
|
|
851
|
+
}, 0);
|
|
852
|
+
}, [options.agentId, prepareNextConversation]);
|
|
853
|
+
useEffect2(() => {
|
|
854
|
+
const controller = {
|
|
855
|
+
hide,
|
|
856
|
+
newConversation,
|
|
857
|
+
preload,
|
|
858
|
+
refreshAuth,
|
|
859
|
+
show,
|
|
860
|
+
shutdown,
|
|
861
|
+
toggle
|
|
862
|
+
};
|
|
863
|
+
controllers.set(options.agentId, controller);
|
|
864
|
+
return () => {
|
|
865
|
+
if (controllers.get(options.agentId) === controller) {
|
|
866
|
+
controllers.delete(options.agentId);
|
|
867
|
+
}
|
|
868
|
+
};
|
|
869
|
+
}, [hide, newConversation, options.agentId, preload, refreshAuth, show, shutdown, toggle]);
|
|
870
|
+
useLayoutEffect(() => {
|
|
871
|
+
const reactRoot = embedReactRootRef.current;
|
|
872
|
+
if (!portalRoot || !reactRoot) return;
|
|
873
|
+
reactRoot.render(
|
|
874
|
+
/* @__PURE__ */ jsxs2(Fragment2, { children: [
|
|
875
|
+
options.showLauncher && remoteConfigResolved && !isOpen ? /* @__PURE__ */ jsx4(
|
|
876
|
+
"button",
|
|
877
|
+
{
|
|
878
|
+
ref: launcherRef,
|
|
879
|
+
"aria-expanded": false,
|
|
880
|
+
"aria-haspopup": "dialog",
|
|
881
|
+
"aria-label": "Open data assistant",
|
|
882
|
+
className: "ha-launcher",
|
|
883
|
+
style: options.launcherStyle,
|
|
884
|
+
type: "button",
|
|
885
|
+
onClick: show,
|
|
886
|
+
onFocus: options.preload ? requestRuntime : void 0,
|
|
887
|
+
onPointerEnter: options.preload ? requestRuntime : void 0,
|
|
888
|
+
children: /* @__PURE__ */ jsx4(LauncherIcon, { icon: options.launcherIcon })
|
|
889
|
+
}
|
|
890
|
+
) : null,
|
|
891
|
+
EmbedRuntime ? /* @__PURE__ */ jsx4(
|
|
892
|
+
EmbedRuntime,
|
|
893
|
+
{
|
|
894
|
+
conversationEpoch,
|
|
895
|
+
identityEpoch,
|
|
896
|
+
identityReset,
|
|
897
|
+
open: isOpen,
|
|
898
|
+
options,
|
|
899
|
+
requestedConversationId,
|
|
900
|
+
warmSession: shouldWarmSession,
|
|
901
|
+
onClose: hide,
|
|
902
|
+
onNewConversation: newConversation,
|
|
903
|
+
onReady: handleRuntimeReady,
|
|
904
|
+
onSelectConversation: selectConversation
|
|
905
|
+
}
|
|
906
|
+
) : /* @__PURE__ */ jsx4(
|
|
907
|
+
PanelShell,
|
|
908
|
+
{
|
|
909
|
+
defaultExpanded: options.panelExpanded,
|
|
910
|
+
expandable: options.panelExpandable,
|
|
911
|
+
modal: options.viewMode !== "sidebar",
|
|
912
|
+
name: "Data assistant",
|
|
913
|
+
open: isOpen,
|
|
914
|
+
agentId: options.agentId,
|
|
915
|
+
onClose: hide,
|
|
916
|
+
children: /* @__PURE__ */ jsx4(
|
|
917
|
+
ShellConversation,
|
|
918
|
+
{
|
|
919
|
+
greeting: options.greeting,
|
|
920
|
+
placeholder: options.composerPlaceholder,
|
|
921
|
+
agentId: options.agentId,
|
|
922
|
+
suggestedPrompts: options.suggestedPrompts ?? [],
|
|
923
|
+
onIntent: requestRuntime
|
|
924
|
+
}
|
|
925
|
+
)
|
|
926
|
+
}
|
|
927
|
+
)
|
|
928
|
+
] })
|
|
929
|
+
);
|
|
930
|
+
}, [
|
|
931
|
+
EmbedRuntime,
|
|
932
|
+
conversationEpoch,
|
|
933
|
+
handleRuntimeReady,
|
|
934
|
+
hide,
|
|
935
|
+
identityEpoch,
|
|
936
|
+
identityReset,
|
|
937
|
+
isOpen,
|
|
938
|
+
newConversation,
|
|
939
|
+
options,
|
|
940
|
+
portalRoot,
|
|
941
|
+
remoteConfigResolved,
|
|
942
|
+
requestRuntime,
|
|
943
|
+
requestedConversationId,
|
|
944
|
+
selectConversation,
|
|
945
|
+
shouldWarmSession,
|
|
946
|
+
show
|
|
947
|
+
]);
|
|
948
|
+
return null;
|
|
949
|
+
}
|
|
950
|
+
|
|
951
|
+
export {
|
|
952
|
+
useAgent,
|
|
953
|
+
HeroUIAgent
|
|
954
|
+
};
|