@heroui/agent 0.2.0-beta.7 → 0.2.0-beta.9

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.
Files changed (49) hide show
  1. package/CHANGELOG.md +20 -2
  2. package/README.md +15 -10
  3. package/dist/chart-content-BBHQVMP4.js +1 -0
  4. package/dist/chunk-2XBFFSW2.js +1 -0
  5. package/dist/chunk-64RI74L5.js +1 -0
  6. package/dist/chunk-6ZJJN322.js +1 -0
  7. package/dist/chunk-7KCGPNEN.js +1 -0
  8. package/dist/chunk-BBB5EHDN.js +5 -0
  9. package/dist/chunk-CR3H7AVI.js +1 -0
  10. package/dist/chunk-HCLXS4HQ.js +1 -0
  11. package/dist/chunk-MXLGM4WV.js +1 -0
  12. package/dist/chunk-OA3TG5CS.js +1 -0
  13. package/dist/chunk-PZJ4NC3J.js +1 -0
  14. package/dist/chunk-RNNGE7KM.js +1 -0
  15. package/dist/chunk-S6OIAJSC.js +1 -0
  16. package/dist/chunk-SUDHJNTJ.js +2 -0
  17. package/dist/chunk-WUAFMD7Z.js +2 -0
  18. package/dist/component-renderer-GG6JVRUG.js +1 -0
  19. package/dist/composer-draft-NSKK65F3.js +1 -0
  20. package/dist/composer-image-draft-KAGTNQR7.js +1 -0
  21. package/dist/contracts.d.ts +50 -19
  22. package/dist/contracts.js +2 -2545
  23. package/dist/css/index.css +1 -1
  24. package/dist/embed-runtime-GYXK7V6N.js +5 -0
  25. package/dist/identity-3P8-17km.d.ts +95 -0
  26. package/dist/index.d.ts +46 -10
  27. package/dist/index.js +1 -28
  28. package/dist/interactive-map-surface-WC2LPCR7.js +1 -0
  29. package/dist/internal/client-tools.js +1 -0
  30. package/dist/internal/models.js +1 -0
  31. package/dist/internal/runtime.js +1 -0
  32. package/dist/internal/theme.js +1 -0
  33. package/dist/next.d.ts +2 -1
  34. package/dist/next.js +1 -22
  35. package/dist/server.d.ts +1 -1
  36. package/dist/server.js +1 -260
  37. package/package.json +6 -6
  38. package/dist/chart-content-E4NPTUPR.js +0 -2696
  39. package/dist/chunk-3FG5NRTX.js +0 -9
  40. package/dist/chunk-6O3SFXJK.js +0 -325
  41. package/dist/chunk-FNGGMHVR.js +0 -625
  42. package/dist/chunk-OQGXZY5L.js +0 -1091
  43. package/dist/chunk-TOOT6SZ2.js +0 -74
  44. package/dist/chunk-V5S5FRGV.js +0 -984
  45. package/dist/chunk-VJA52U5P.js +0 -137
  46. package/dist/component-renderer-46EJDPTB.js +0 -10627
  47. package/dist/embed-runtime-UF7XJIFA.js +0 -7650
  48. package/dist/identity-Z6i6pL0F.d.ts +0 -166
  49. package/dist/interactive-map-surface-67KHT3VB.js +0 -362
@@ -1,984 +0,0 @@
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-OQGXZY5L.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
- newsSearch: optionalBoolean,
247
- webSearch: optionalBoolean
248
- })
249
- );
250
- const composer = group(parseComposer(value["composer"]));
251
- const markdown = group(parseMarkdown(value["markdown"]));
252
- const permissions = group(
253
- shape(value["permissions"], {
254
- defaultMode: optionalEnum(["ask", "auto", "full"]),
255
- showPicker: optionalBoolean
256
- })
257
- );
258
- const responseActions = group(parseResponseActions(value["responseActions"]));
259
- const startScreen = group(
260
- shape(value["startScreen"], {
261
- greeting: optionalString,
262
- promptShortcuts: optionalBoolean,
263
- prompts: optionalStringArray
264
- })
265
- );
266
- const webfont = group(
267
- shape(value["webfont"], {
268
- familyName: (familyName) => typeof familyName === "string" && familyName ? familyName : INVALID,
269
- fontFaceUrl: optionalString,
270
- stylesheetUrl: optionalString
271
- })
272
- );
273
- return {
274
- ...appearance ? { appearance } : {},
275
- ...capabilities ? { capabilities } : {},
276
- ...composer ? { composer } : {},
277
- ...markdown ? { markdown } : {},
278
- ...permissions ? { permissions } : {},
279
- ...responseActions === void 0 ? {} : { responseActions },
280
- ...startScreen ? { startScreen } : {},
281
- ...webfont ? { webfont } : {},
282
- revision,
283
- version: HEROUI_AGENT_REMOTE_CONFIG_VERSION
284
- };
285
- }
286
-
287
- // src/embed/remote-config.ts
288
- function cacheKey(agentId) {
289
- return `heroui-agent:config:${agentId}`;
290
- }
291
- function readCachedRemoteConfig(agentId) {
292
- try {
293
- const raw = window.localStorage.getItem(cacheKey(agentId));
294
- return raw ? parseAgentRemoteConfig(JSON.parse(raw)) : null;
295
- } catch {
296
- return null;
297
- }
298
- }
299
- function writeCachedRemoteConfig(agentId, config) {
300
- try {
301
- window.localStorage.setItem(cacheKey(agentId), JSON.stringify(config));
302
- } catch {
303
- }
304
- }
305
- function clearCachedRemoteConfig(agentId) {
306
- try {
307
- window.localStorage.removeItem(cacheKey(agentId));
308
- } catch {
309
- }
310
- }
311
- async function fetchRemoteConfig(apiBaseUrl, agentId, options = {}) {
312
- try {
313
- const response = await fetch(
314
- `${apiBaseUrl}/v1/agents/${encodeURIComponent(agentId)}/appearance`,
315
- {
316
- headers: options.revision ? { "If-None-Match": `"${options.revision}"` } : {},
317
- ...options.signal ? { signal: options.signal } : {}
318
- }
319
- );
320
- if (response.status === 304) return { status: "not-modified" };
321
- if (response.status === 404 || response.status === 410) return { status: "missing" };
322
- if (!response.ok) return { status: "unavailable" };
323
- const config = parseAgentRemoteConfig(await response.json());
324
- return config ? { config, status: "ok" } : { status: "unavailable" };
325
- } catch {
326
- return { status: "unavailable" };
327
- }
328
- }
329
-
330
- // src/embed/shell-conversation.tsx
331
- import { useEffect, useRef, useState } from "react";
332
- import { Fragment, jsx as jsx3, jsxs } from "react/jsx-runtime";
333
- function ShellConversation({
334
- agentId,
335
- greeting,
336
- onIntent,
337
- placeholder,
338
- suggestedPrompts
339
- }) {
340
- const [input, setInput] = useState(() => readAgentShellHandoff(agentId)?.prompt ?? "");
341
- const [submitted, setSubmitted] = useState(false);
342
- const textareaRef = useRef(null);
343
- useEffect(() => {
344
- if (submitted) return;
345
- writeAgentShellHandoff(agentId, { prompt: input, submitted: false });
346
- }, [input, agentId, submitted]);
347
- const submit = (prompt) => {
348
- const trimmed = prompt.trim();
349
- if (!trimmed) return;
350
- setSubmitted(true);
351
- setInput(trimmed);
352
- writeAgentShellHandoff(agentId, { prompt: trimmed, submitted: true });
353
- onIntent();
354
- };
355
- return /* @__PURE__ */ jsxs(Fragment, { children: [
356
- /* @__PURE__ */ jsx3("div", { className: "ha-conversation", children: /* @__PURE__ */ jsxs("div", { "aria-live": "polite", className: "ha-messages", role: "log", children: [
357
- /* @__PURE__ */ jsxs("div", { className: "ha-empty", children: [
358
- /* @__PURE__ */ jsx3("h2", { children: greeting }),
359
- /* @__PURE__ */ jsx3("p", { children: "Live answers with charts, metrics, and tables." })
360
- ] }),
361
- submitted ? /* @__PURE__ */ jsx3("div", { className: "ha-message", "data-role": "user", children: /* @__PURE__ */ jsx3("div", { className: "ha-message-body", children: input }) }) : null
362
- ] }) }),
363
- /* @__PURE__ */ jsxs(
364
- "form",
365
- {
366
- className: "ha-composer-wrap",
367
- onSubmit: (event) => {
368
- event.preventDefault();
369
- submit(input);
370
- },
371
- children: [
372
- !submitted && suggestedPrompts.length > 0 ? /* @__PURE__ */ jsx3("div", { "aria-label": "Suggested prompts", className: "ha-suggestions", role: "group", children: suggestedPrompts.map((prompt) => /* @__PURE__ */ jsxs(
373
- "button",
374
- {
375
- className: "ha-suggestion",
376
- type: "button",
377
- onClick: () => submit(prompt),
378
- children: [
379
- /* @__PURE__ */ jsx3("span", { children: prompt }),
380
- /* @__PURE__ */ jsx3(ArrowUpRightGlyph, {})
381
- ]
382
- },
383
- prompt
384
- )) }) : null,
385
- /* @__PURE__ */ jsxs("div", { className: "@container ha-composer", children: [
386
- /* @__PURE__ */ jsx3(
387
- "textarea",
388
- {
389
- ref: textareaRef,
390
- "aria-label": "Message the assistant",
391
- placeholder,
392
- rows: 1,
393
- value: input,
394
- onFocus: onIntent,
395
- onChange: (event) => {
396
- setInput(event.currentTarget.value);
397
- resizeComposerTextarea(event.currentTarget);
398
- onIntent();
399
- },
400
- onKeyDown: (event) => {
401
- if (event.key !== "Enter" || event.shiftKey) return;
402
- event.preventDefault();
403
- submit(input);
404
- }
405
- }
406
- ),
407
- /* @__PURE__ */ jsxs(
408
- "button",
409
- {
410
- "aria-label": "Send message",
411
- className: "ha-send",
412
- "data-shortcut": "\u21B5",
413
- "data-tooltip": "Send message",
414
- disabled: !input.trim() || submitted,
415
- type: "submit",
416
- children: [
417
- /* @__PURE__ */ jsx3(ArrowUpGlyph, {}),
418
- /* @__PURE__ */ jsx3(ComposerControlTooltip, { shortcut: "\u21B5", children: "Send message" })
419
- ]
420
- }
421
- )
422
- ] }),
423
- /* @__PURE__ */ jsx3(PoweredByHero, {})
424
- ]
425
- }
426
- )
427
- ] });
428
- }
429
- function ArrowUpGlyph() {
430
- return /* @__PURE__ */ jsx3("svg", { "aria-hidden": "true", fill: "none", height: "18", viewBox: "0 0 18 18", width: "18", children: /* @__PURE__ */ jsx3(
431
- "path",
432
- {
433
- d: "M9 14.5V3.5m0 0L4.5 8M9 3.5 13.5 8",
434
- stroke: "currentColor",
435
- strokeLinecap: "round",
436
- strokeLinejoin: "round",
437
- strokeWidth: "1.6"
438
- }
439
- ) });
440
- }
441
- function ArrowUpRightGlyph() {
442
- return /* @__PURE__ */ jsx3("svg", { "aria-hidden": "true", fill: "none", height: "16", viewBox: "0 0 16 16", width: "16", children: /* @__PURE__ */ jsx3(
443
- "path",
444
- {
445
- d: "M5 11 11 5m0 0H6m5 0v5",
446
- stroke: "currentColor",
447
- strokeLinecap: "round",
448
- strokeLinejoin: "round",
449
- strokeWidth: "1.5"
450
- }
451
- ) });
452
- }
453
-
454
- // src/embed/webfont.ts
455
- var FONT_FILE_EXTENSIONS = [".woff2", ".woff", ".ttf", ".otf", ".eot"];
456
- function elementId(font) {
457
- return `heroui-agent-font-${font.familyName.toLowerCase().replace(/[^a-z0-9]+/g, "-")}`;
458
- }
459
- function fontFormat(url) {
460
- const isVariable = url.includes(":vf@") || url.includes("-wght-") || url.includes("-opsz-");
461
- if (url.endsWith(".woff")) return "woff";
462
- if (url.endsWith(".ttf")) return "truetype";
463
- if (url.endsWith(".otf")) return "opentype";
464
- return isVariable ? "woff2-variations" : "woff2";
465
- }
466
- function isSafeFontUrl(url, requireFontFile) {
467
- try {
468
- const parsed = new URL(url);
469
- if (parsed.protocol !== "https:") return false;
470
- if (!requireFontFile) return true;
471
- const pathname = parsed.pathname.toLowerCase();
472
- return FONT_FILE_EXTENSIONS.some((extension) => pathname.endsWith(extension));
473
- } catch {
474
- return false;
475
- }
476
- }
477
- function loadAgentWebfont(font) {
478
- if (!font || typeof document === "undefined") return () => void 0;
479
- const id = elementId(font);
480
- if (document.getElementById(id)) return () => void 0;
481
- if (font.stylesheetUrl && isSafeFontUrl(font.stylesheetUrl, false)) {
482
- const link = document.createElement("link");
483
- link.id = id;
484
- link.rel = "stylesheet";
485
- link.href = font.stylesheetUrl;
486
- document.head.append(link);
487
- return () => link.remove();
488
- }
489
- if (font.fontFaceUrl && isSafeFontUrl(font.fontFaceUrl, true)) {
490
- const style = document.createElement("style");
491
- const isVariable = font.fontFaceUrl.includes(":vf@") || font.fontFaceUrl.includes("-wght-") || font.fontFaceUrl.includes("-opsz-");
492
- style.id = id;
493
- style.textContent = [
494
- "@font-face {",
495
- ` font-family: '${font.familyName.replaceAll("'", "")}';`,
496
- " font-style: normal;",
497
- " font-display: swap;",
498
- ` font-weight: ${isVariable ? "100 900" : "400"};`,
499
- ` src: url(${font.fontFaceUrl}) format('${fontFormat(font.fontFaceUrl)}');`,
500
- "}"
501
- ].join("\n");
502
- document.head.append(style);
503
- return () => style.remove();
504
- }
505
- return () => void 0;
506
- }
507
-
508
- // src/embed/provider.tsx
509
- import { Fragment as Fragment2, jsx as jsx4, jsxs as jsxs2 } from "react/jsx-runtime";
510
- var REMOTE_CONFIG_PAINT_BUDGET_MS = 600;
511
- var RUNTIME_PREFETCH_IDLE_TIMEOUT_MS = 1200;
512
- var OPEN_STATE_STORAGE_PREFIX = "heroui-agent:open:";
513
- function openStateStorageKey(agentId) {
514
- return `${OPEN_STATE_STORAGE_PREFIX}${agentId}`;
515
- }
516
- function readOpenState(agentId) {
517
- if (typeof window === "undefined") return false;
518
- try {
519
- return window.sessionStorage.getItem(openStateStorageKey(agentId)) === "true";
520
- } catch {
521
- return false;
522
- }
523
- }
524
- function writeOpenState(agentId, open) {
525
- try {
526
- if (open) window.sessionStorage.setItem(openStateStorageKey(agentId), "true");
527
- else window.sessionStorage.removeItem(openStateStorageKey(agentId));
528
- } catch {
529
- }
530
- }
531
- var embedRuntimePromise;
532
- function loadEmbedRuntime() {
533
- embedRuntimePromise ??= import(
534
- /* webpackPrefetch: true */
535
- "./embed-runtime-UF7XJIFA.js"
536
- ).then(
537
- (module) => module.default
538
- );
539
- return embedRuntimePromise;
540
- }
541
- var controllers = /* @__PURE__ */ new Map();
542
- function subscribeToColorScheme(onChange) {
543
- const media = window.matchMedia?.("(prefers-color-scheme: dark)");
544
- media?.addEventListener?.("change", onChange);
545
- return () => media?.removeEventListener?.("change", onChange);
546
- }
547
- function prefersDarkColorScheme() {
548
- return window.matchMedia?.("(prefers-color-scheme: dark)").matches === true;
549
- }
550
- function dispatch(agentId, method) {
551
- const controller = agentId ? controllers.get(agentId) : [...controllers.values()][controllers.size - 1];
552
- if (!controller) {
553
- console.warn("HeroUI Agent is not mounted yet. Add <HeroUIAgent /> to your layout.");
554
- return;
555
- }
556
- controller[method]();
557
- }
558
- function useRemoteConfig(agentId, apiBaseUrl, enabled) {
559
- const [state, setState] = useState2(() => {
560
- if (!enabled || typeof window === "undefined") return { config: null, resolved: false };
561
- const cached = readCachedRemoteConfig(agentId);
562
- return { config: cached, resolved: Boolean(cached) };
563
- });
564
- useEffect2(() => {
565
- if (!enabled) return;
566
- const cached = readCachedRemoteConfig(agentId);
567
- const controller = new AbortController();
568
- const budget = window.setTimeout(
569
- () => setState((current) => ({ ...current, resolved: true })),
570
- REMOTE_CONFIG_PAINT_BUDGET_MS
571
- );
572
- void fetchRemoteConfig(apiBaseUrl, agentId, {
573
- ...cached?.revision ? { revision: cached.revision } : {},
574
- signal: controller.signal
575
- }).then((result) => {
576
- if (controller.signal.aborted) return;
577
- if (result.status === "ok") {
578
- setState({ config: result.config, resolved: true });
579
- writeCachedRemoteConfig(agentId, result.config);
580
- }
581
- if (result.status === "missing") {
582
- setState({ config: null, resolved: true });
583
- clearCachedRemoteConfig(agentId);
584
- }
585
- }).finally(() => {
586
- if (controller.signal.aborted) return;
587
- window.clearTimeout(budget);
588
- setState((current) => ({ ...current, resolved: true }));
589
- });
590
- return () => {
591
- controller.abort();
592
- window.clearTimeout(budget);
593
- };
594
- }, [apiBaseUrl, enabled, agentId]);
595
- return enabled ? state : { config: null, resolved: true };
596
- }
597
- function useAgent(agentId) {
598
- return useMemo(
599
- () => ({
600
- hide: () => dispatch(agentId, "hide"),
601
- newConversation: () => dispatch(agentId, "newConversation"),
602
- preload: () => dispatch(agentId, "preload"),
603
- refreshAuth: () => dispatch(agentId, "refreshAuth"),
604
- show: () => dispatch(agentId, "show"),
605
- shutdown: () => dispatch(agentId, "shutdown"),
606
- toggle: () => dispatch(agentId, "toggle")
607
- }),
608
- [agentId]
609
- );
610
- }
611
- function HeroUIAgent(props) {
612
- const {
613
- _api,
614
- agentId,
615
- appearance,
616
- capabilities,
617
- componentExports,
618
- composer,
619
- context,
620
- getAuthToken,
621
- markdown,
622
- onFeedback,
623
- permissions,
624
- preload: preloadEnabled,
625
- remoteConfig: remoteConfigEnabled = true,
626
- reopenOnRefresh,
627
- responseActions,
628
- showLauncher,
629
- startNewConversationOnOpen,
630
- startScreen,
631
- tools
632
- } = props;
633
- const markdownPlugins = markdown?.plugins;
634
- const apiBaseUrl = (_api?.baseUrl ?? "https://api.heroui.com").replace(/\/$/, "");
635
- const { config: remoteConfig, resolved: remoteConfigResolved } = useRemoteConfig(
636
- agentId,
637
- apiBaseUrl,
638
- remoteConfigEnabled
639
- );
640
- const serializedConfig = JSON.stringify({
641
- _api,
642
- appearance,
643
- capabilities,
644
- componentExports,
645
- composer,
646
- markdown: markdown ? { ...markdown, plugins: void 0 } : void 0,
647
- permissions,
648
- preload: preloadEnabled,
649
- remote: remoteConfig,
650
- reopenOnRefresh,
651
- responseActions,
652
- showLauncher,
653
- startNewConversationOnOpen,
654
- startScreen
655
- });
656
- const options = useMemo(() => {
657
- const { remote, ...config } = JSON.parse(serializedConfig);
658
- const merged = mergeRemoteConfig(remote, {
659
- ...config,
660
- agentId,
661
- getAuthToken
662
- });
663
- return resolveOptions({
664
- ...merged,
665
- context,
666
- markdown: merged.markdown ? { ...merged.markdown, plugins: markdownPlugins } : markdownPlugins ? { plugins: markdownPlugins } : void 0,
667
- onFeedback,
668
- tools
669
- });
670
- }, [context, getAuthToken, markdownPlugins, onFeedback, agentId, serializedConfig, tools]);
671
- const hostFont = hostSuppliesFont(props);
672
- const remoteWebfont = remoteConfig?.webfont;
673
- useEffect2(() => {
674
- if (hostFont) return;
675
- return loadAgentWebfont(remoteWebfont);
676
- }, [hostFont, remoteWebfont]);
677
- const [restoredOpenState] = useState2(() => reopenOnRefresh === true && readOpenState(agentId));
678
- const [isOpen, setIsOpen] = useState2(restoredOpenState);
679
- const [conversationEpoch, setConversationEpoch] = useState2(1);
680
- const [identityEpoch, setIdentityEpoch] = useState2(1);
681
- const [identityReset, setIdentityReset] = useState2(false);
682
- const [requestedConversationId, setRequestedConversationId] = useState2(
683
- restoredOpenState || options.startNewConversationOnOpen ? null : void 0
684
- );
685
- const [portalRoot, setPortalRoot] = useState2(null);
686
- const [EmbedRuntime, setEmbedRuntime] = useState2(null);
687
- const conversationEpochRef = useRef2(1);
688
- const launcherRef = useRef2(null);
689
- const portalRootRef = useRef2(null);
690
- const readyEpochRef = useRef2(null);
691
- const shouldOpenRef = useRef2(restoredOpenState);
692
- const hostRef = useRef2(null);
693
- const embedReactRootRef = useRef2(null);
694
- const rootPropertyNamesRef = useRef2([]);
695
- const prefersDark = useSyncExternalStore(
696
- subscribeToColorScheme,
697
- prefersDarkColorScheme,
698
- () => false
699
- );
700
- const designThemeClass = options.designTheme === "base" ? null : `${options.designTheme}-${options.colorScheme === "system" ? prefersDark ? "dark" : "light" : options.colorScheme}`;
701
- const [shouldLoadRuntime, setShouldLoadRuntime] = useState2(restoredOpenState);
702
- const requestRuntime = useCallback(() => setShouldLoadRuntime(true), []);
703
- const [preloadRequested, setPreloadRequested] = useState2(false);
704
- const shouldWarmSession = options.preload || preloadRequested;
705
- useEffect2(() => {
706
- if (options.reopenOnRefresh) writeOpenState(options.agentId, isOpen);
707
- else writeOpenState(options.agentId, false);
708
- }, [isOpen, options.agentId, options.reopenOnRefresh]);
709
- useEffect2(() => {
710
- if (!options.preload || shouldLoadRuntime) return;
711
- return scheduleIdleTask(requestRuntime, RUNTIME_PREFETCH_IDLE_TIMEOUT_MS);
712
- }, [options.preload, requestRuntime, shouldLoadRuntime]);
713
- useEffect2(() => {
714
- if (!shouldLoadRuntime) return;
715
- let active = true;
716
- void loadEmbedRuntime().then((runtime) => {
717
- if (active) setEmbedRuntime(() => runtime);
718
- }).catch(() => void 0);
719
- return () => {
720
- active = false;
721
- };
722
- }, [shouldLoadRuntime]);
723
- useEffect2(() => {
724
- if (!isOpen || options.viewMode !== "sidebar") return;
725
- if (window.innerWidth < 640) return;
726
- const target = document.documentElement;
727
- const previousMargin = target.style.marginRight;
728
- const previousTransition = target.style.transition;
729
- const reducedMotion = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
730
- const width = Math.min(readSidebarWidth(options.agentId, window.innerWidth), window.innerWidth);
731
- if (!reducedMotion) {
732
- target.style.transition = [previousTransition, "margin-right .28s cubic-bezier(.2,.8,.2,1)"].filter(Boolean).join(", ");
733
- }
734
- target.style.marginRight = `${width}px`;
735
- return () => {
736
- target.style.marginRight = previousMargin;
737
- window.setTimeout(() => {
738
- target.style.transition = previousTransition;
739
- }, 300);
740
- };
741
- }, [isOpen, options.agentId, options.viewMode]);
742
- useEffect2(() => {
743
- const host = document.createElement("div");
744
- const root = document.createElement("div");
745
- host.dataset["herouiAgent"] = options.agentId;
746
- root.className = "ha-root";
747
- hostRef.current = host;
748
- portalRootRef.current = root;
749
- embedReactRootRef.current = createRoot(root);
750
- host.append(root);
751
- document.body.append(host);
752
- let active = true;
753
- queueMicrotask(() => {
754
- if (active) setPortalRoot(root);
755
- });
756
- return () => {
757
- active = false;
758
- const reactRoot = embedReactRootRef.current;
759
- portalRootRef.current = null;
760
- hostRef.current = null;
761
- embedReactRootRef.current = null;
762
- rootPropertyNamesRef.current = [];
763
- queueMicrotask(() => {
764
- reactRoot?.unmount();
765
- host.remove();
766
- });
767
- };
768
- }, [options.agentId]);
769
- useLayoutEffect(() => {
770
- const root = portalRootRef.current;
771
- const host = hostRef.current;
772
- if (!root || !host) return;
773
- root.className = designThemeClass ? `ha-root ${designThemeClass}` : "ha-root";
774
- root.dataset["theme"] = options.colorScheme;
775
- root.dataset["viewMode"] = options.viewMode;
776
- root.dataset["position"] = options.launcherPosition;
777
- host.dataset["open"] = isOpen ? "true" : "false";
778
- host.dataset["viewMode"] = options.viewMode;
779
- host.dataset["position"] = options.launcherPosition;
780
- if (options.launcherOffset) {
781
- host.style.setProperty("--ha-launcher-x", `${options.launcherOffset.x}px`);
782
- host.style.setProperty("--ha-launcher-y", `${options.launcherOffset.y}px`);
783
- } else {
784
- host.style.removeProperty("--ha-launcher-x");
785
- host.style.removeProperty("--ha-launcher-y");
786
- }
787
- if (options.viewMode === "sidebar") {
788
- host.style.setProperty(
789
- "--ha-sidebar-width",
790
- `${readSidebarWidth(options.agentId, window.innerWidth)}px`
791
- );
792
- }
793
- const rootProperties = { ...options.themeStyle, ...options.panelStyle };
794
- for (const property of rootPropertyNamesRef.current) {
795
- if (!(property in rootProperties)) root.style.removeProperty(property);
796
- }
797
- for (const [property, value] of Object.entries(rootProperties)) {
798
- root.style.setProperty(property, value);
799
- }
800
- rootPropertyNamesRef.current = Object.keys(rootProperties);
801
- }, [
802
- designThemeClass,
803
- isOpen,
804
- options.colorScheme,
805
- options.launcherOffset,
806
- options.launcherPosition,
807
- options.panelStyle,
808
- options.themeStyle,
809
- options.viewMode,
810
- portalRoot
811
- ]);
812
- const prepareNextConversation = useCallback((conversationId) => {
813
- readyEpochRef.current = null;
814
- setRequestedConversationId(conversationId);
815
- setConversationEpoch((epoch) => {
816
- const nextEpoch = epoch + 1;
817
- conversationEpochRef.current = nextEpoch;
818
- return nextEpoch;
819
- });
820
- }, []);
821
- const handleRuntimeReady = useCallback((readyEpoch) => {
822
- if (conversationEpochRef.current !== readyEpoch) return;
823
- readyEpochRef.current = readyEpoch;
824
- if (shouldOpenRef.current) setIsOpen(true);
825
- }, []);
826
- const hide = useCallback(() => {
827
- shouldOpenRef.current = false;
828
- setIsOpen(false);
829
- prepareNextConversation(options.startNewConversationOnOpen ? null : void 0);
830
- requestAnimationFrame(() => launcherRef.current?.focus());
831
- }, [options.startNewConversationOnOpen, prepareNextConversation]);
832
- const show = useCallback(() => {
833
- shouldOpenRef.current = true;
834
- requestRuntime();
835
- setIsOpen(true);
836
- }, [requestRuntime]);
837
- const preload = useCallback(() => {
838
- setPreloadRequested(true);
839
- requestRuntime();
840
- }, [requestRuntime]);
841
- const newConversation = useCallback(() => {
842
- shouldOpenRef.current = true;
843
- requestRuntime();
844
- prepareNextConversation(null);
845
- }, [prepareNextConversation, requestRuntime]);
846
- const selectConversation = useCallback(
847
- (conversationId) => {
848
- shouldOpenRef.current = true;
849
- requestRuntime();
850
- prepareNextConversation(conversationId);
851
- },
852
- [prepareNextConversation, requestRuntime]
853
- );
854
- const toggle = useCallback(() => {
855
- if (isOpen || shouldOpenRef.current) hide();
856
- else show();
857
- }, [hide, isOpen, show]);
858
- const refreshAuth = useCallback(() => {
859
- setIdentityReset(false);
860
- setIdentityEpoch((epoch) => epoch + 1);
861
- }, []);
862
- const shutdown = useCallback(() => {
863
- const prefixes = [
864
- `heroui-agent:anonymous:${options.agentId}`,
865
- `heroui-agent:conversation:${options.agentId}:`,
866
- `heroui-agent:history:${options.agentId}:`,
867
- `heroui-agent:session:${options.agentId}:`
868
- ];
869
- for (let index = localStorage.length - 1; index >= 0; index -= 1) {
870
- const key = localStorage.key(index);
871
- if (key && prefixes.some((prefix) => key.startsWith(prefix))) localStorage.removeItem(key);
872
- }
873
- shouldOpenRef.current = false;
874
- setIsOpen(false);
875
- writeOpenState(options.agentId, false);
876
- setIdentityReset(true);
877
- setIdentityEpoch((epoch) => epoch + 1);
878
- prepareNextConversation();
879
- window.setTimeout(() => {
880
- void clearAgentComposerDraftsForProject(options.agentId);
881
- }, 0);
882
- }, [options.agentId, prepareNextConversation]);
883
- useEffect2(() => {
884
- const controller = {
885
- hide,
886
- newConversation,
887
- preload,
888
- refreshAuth,
889
- show,
890
- shutdown,
891
- toggle
892
- };
893
- controllers.set(options.agentId, controller);
894
- return () => {
895
- if (controllers.get(options.agentId) === controller) {
896
- controllers.delete(options.agentId);
897
- }
898
- };
899
- }, [hide, newConversation, options.agentId, preload, refreshAuth, show, shutdown, toggle]);
900
- useLayoutEffect(() => {
901
- const reactRoot = embedReactRootRef.current;
902
- if (!portalRoot || !reactRoot) return;
903
- reactRoot.render(
904
- /* @__PURE__ */ jsxs2(Fragment2, { children: [
905
- options.showLauncher && remoteConfigResolved && !isOpen ? /* @__PURE__ */ jsx4(
906
- "button",
907
- {
908
- ref: launcherRef,
909
- "aria-expanded": false,
910
- "aria-haspopup": "dialog",
911
- "aria-label": "Open data assistant",
912
- className: "ha-launcher",
913
- style: options.launcherStyle,
914
- type: "button",
915
- onClick: show,
916
- onFocus: options.preload ? requestRuntime : void 0,
917
- onPointerEnter: options.preload ? requestRuntime : void 0,
918
- children: /* @__PURE__ */ jsx4(LauncherIcon, { icon: options.launcherIcon })
919
- }
920
- ) : null,
921
- EmbedRuntime ? /* @__PURE__ */ jsx4(
922
- EmbedRuntime,
923
- {
924
- conversationEpoch,
925
- identityEpoch,
926
- identityReset,
927
- open: isOpen,
928
- options,
929
- requestedConversationId,
930
- warmSession: shouldWarmSession,
931
- onClose: hide,
932
- onNewConversation: newConversation,
933
- onReady: handleRuntimeReady,
934
- onSelectConversation: selectConversation
935
- }
936
- ) : /* @__PURE__ */ jsx4(
937
- PanelShell,
938
- {
939
- agentId: options.agentId,
940
- defaultExpanded: options.panelExpanded,
941
- expandable: options.panelExpandable,
942
- modal: options.viewMode !== "sidebar",
943
- name: "Data assistant",
944
- open: isOpen,
945
- onClose: hide,
946
- children: /* @__PURE__ */ jsx4(
947
- ShellConversation,
948
- {
949
- agentId: options.agentId,
950
- greeting: options.greeting,
951
- placeholder: options.composerPlaceholder,
952
- suggestedPrompts: options.suggestedPrompts ?? [],
953
- onIntent: requestRuntime
954
- }
955
- )
956
- }
957
- )
958
- ] })
959
- );
960
- }, [
961
- EmbedRuntime,
962
- conversationEpoch,
963
- handleRuntimeReady,
964
- hide,
965
- identityEpoch,
966
- identityReset,
967
- isOpen,
968
- newConversation,
969
- options,
970
- portalRoot,
971
- remoteConfigResolved,
972
- requestRuntime,
973
- requestedConversationId,
974
- selectConversation,
975
- shouldWarmSession,
976
- show
977
- ]);
978
- return null;
979
- }
980
-
981
- export {
982
- useAgent,
983
- HeroUIAgent
984
- };