@webless/agent 0.2.0 → 0.2.7

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.
@@ -1,524 +1,127 @@
1
- // src/react/components/AgentRail/AgentRail.tsx
2
- import { useEffect, useRef as useRef2 } from "react";
1
+ // src/react/hooks/useAgentChat.ts
2
+ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
3
3
 
4
- // src/react/components/ToolTimeline/ToolTimeline.tsx
5
- import { jsx, jsxs } from "react/jsx-runtime";
6
- function SpinnerIcon() {
7
- return /* @__PURE__ */ jsxs("svg", { className: "agent-status__spinner", viewBox: "0 0 16 16", fill: "none", "aria-hidden": "true", children: [
8
- /* @__PURE__ */ jsx("circle", { cx: "8", cy: "8", r: "6", stroke: "currentColor", strokeWidth: "1.5", strokeOpacity: "0.25" }),
9
- /* @__PURE__ */ jsx("path", { d: "M14 8a6 6 0 0 0-6-6", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round" })
10
- ] });
11
- }
12
- function getCurrentStep(steps) {
13
- const active = steps.find((step) => step.state === "active");
14
- if (active) return active;
15
- const error = steps.find((step) => step.state === "error");
16
- if (error) return error;
17
- const pending = steps.find((step) => step.state === "pending");
18
- if (pending) return pending;
19
- return steps.at(-1) ?? null;
20
- }
21
- function ToolTimeline({
22
- steps,
23
- streamingText,
24
- showStreaming = false,
25
- inline = false
26
- }) {
27
- if (steps.length === 0 && !(showStreaming && streamingText)) return null;
28
- const currentStep = getCurrentStep(steps);
29
- const showStatus = currentStep && currentStep.state !== "completed" && !(showStreaming && streamingText);
30
- return /* @__PURE__ */ jsxs(
31
- "div",
32
- {
33
- className: `tool-timeline${inline ? " tool-timeline--inline" : ""}`,
34
- role: "status",
35
- "aria-live": "polite",
36
- "aria-label": "Agent progress",
37
- children: [
38
- showStatus ? /* @__PURE__ */ jsxs("div", { className: "agent-status", children: [
39
- currentStep.state === "error" ? /* @__PURE__ */ jsx("span", { className: "agent-status__icon agent-status__icon--error", "aria-hidden": "true", children: "!" }) : /* @__PURE__ */ jsx("span", { className: "agent-status__icon", "aria-hidden": "true", children: /* @__PURE__ */ jsx(SpinnerIcon, {}) }),
40
- /* @__PURE__ */ jsxs("div", { className: "agent-status__copy", children: [
41
- /* @__PURE__ */ jsx("span", { className: "agent-status__label", children: currentStep.label }),
42
- currentStep.detail ? /* @__PURE__ */ jsx("span", { className: "agent-status__detail", children: currentStep.detail }) : null
43
- ] })
44
- ] }, currentStep.id) : null,
45
- showStreaming && streamingText ? /* @__PURE__ */ jsxs("p", { className: "tool-timeline__streaming-text", children: [
46
- streamingText,
47
- /* @__PURE__ */ jsx("span", { className: "tool-timeline__cursor", "aria-hidden": "true" })
48
- ] }) : null
49
- ]
50
- }
51
- );
52
- }
4
+ // src/runtime/client.ts
5
+ import {
6
+ Client,
7
+ ClientError
8
+ } from "eve/client";
53
9
 
54
- // src/react/components/AgentActivityBubble/AgentActivityBubble.tsx
55
- import { jsx as jsx2 } from "react/jsx-runtime";
56
- function AgentActivityBubble({
57
- steps,
58
- streamingText,
59
- showStreaming = false
60
- }) {
61
- return /* @__PURE__ */ jsx2("article", { className: "agent-activity-bubble", children: /* @__PURE__ */ jsx2("div", { className: "agent-activity-bubble__shell", children: /* @__PURE__ */ jsx2(
62
- ToolTimeline,
63
- {
64
- steps,
65
- streamingText,
66
- showStreaming,
67
- inline: true
68
- }
69
- ) }) });
10
+ // src/runtime/config.ts
11
+ var DEFAULT_RUNTIME_ORIGIN = "https://runtime.staging.webless.ai";
12
+ var trimTrailingSlash = (value) => value.replace(/\/+$/, "");
13
+ function buildEveHost(origin, indexId, version = "published") {
14
+ const base = trimTrailingSlash(origin);
15
+ const searchParams = new URLSearchParams({ indexId: indexId.trim() });
16
+ if (version === "unpublished") {
17
+ searchParams.set("version", version);
18
+ }
19
+ return `${base}?${searchParams.toString()}`;
20
+ }
21
+ function resolveAgentRuntimeConfig(input) {
22
+ const origin = trimTrailingSlash(input.runtimeOrigin?.trim() || DEFAULT_RUNTIME_ORIGIN);
23
+ const resolvedIndexId = input.indexId.trim();
24
+ const version = input.version ?? "published";
25
+ return {
26
+ origin,
27
+ indexId: resolvedIndexId,
28
+ version,
29
+ host: resolvedIndexId ? buildEveHost(origin, resolvedIndexId, version) : origin
30
+ };
70
31
  }
71
32
 
72
- // src/react/components/Composer/Composer.tsx
73
- import { useRef, useState } from "react";
74
- import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
75
- function SendIcon() {
76
- return /* @__PURE__ */ jsx3("svg", { viewBox: "0 0 16 16", fill: "none", "aria-hidden": "true", children: /* @__PURE__ */ jsx3("path", { d: "M8 12V4M8 4l-3 3M8 4l3 3", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round" }) });
33
+ // src/runtime/session-store.ts
34
+ var DEFAULT_STORAGE_KEY_PREFIX = "webless:agent";
35
+ function normalizeRuntimeOrigin(runtimeOrigin) {
36
+ const trimmed = runtimeOrigin?.trim();
37
+ if (!trimmed) return "_";
38
+ return trimmed.replace(/\/+$/, "");
77
39
  }
78
- function Composer({
79
- disabled = false,
80
- placeholder = "Ask anything\u2026",
81
- variant = "default",
82
- onSubmit
83
- }) {
84
- const [value, setValue] = useState("");
85
- const inputRef = useRef(null);
86
- function submitCurrent() {
87
- const trimmed = value.trim();
88
- if (!trimmed || disabled) return;
89
- onSubmit?.(trimmed);
90
- setValue("");
91
- inputRef.current?.focus();
92
- }
93
- function handleSubmit(event) {
94
- event.preventDefault();
95
- submitCurrent();
96
- }
97
- function handleKeyDown(event) {
98
- if (event.key === "Enter" && !event.shiftKey) {
99
- event.preventDefault();
100
- submitCurrent();
101
- }
40
+ function buildAgentStorageKeyPrefix(input) {
41
+ const customerId = input.customerId?.trim() || "_";
42
+ const indexId = input.indexId.trim();
43
+ if (!indexId) {
44
+ throw new Error("indexId is required to build agent storage keys.");
102
45
  }
103
- return /* @__PURE__ */ jsx3("form", { className: `composer${variant === "dock" ? " composer--dock" : ""}`, onSubmit: handleSubmit, children: /* @__PURE__ */ jsxs2("div", { className: "composer__field", children: [
104
- /* @__PURE__ */ jsx3(
105
- "textarea",
106
- {
107
- ref: inputRef,
108
- className: "composer__input",
109
- rows: 1,
110
- value,
111
- placeholder,
112
- disabled,
113
- "aria-label": "Message",
114
- onChange: (event) => setValue(event.target.value),
115
- onKeyDown: handleKeyDown
116
- }
117
- ),
118
- /* @__PURE__ */ jsx3(
119
- "button",
120
- {
121
- type: "submit",
122
- className: "composer__send",
123
- disabled: disabled || !value.trim(),
124
- "aria-label": "Send message",
125
- children: /* @__PURE__ */ jsx3(SendIcon, {})
126
- }
127
- )
128
- ] }) });
46
+ const versionSuffix = input.version === "unpublished" ? ":unpublished" : "";
47
+ return `${DEFAULT_STORAGE_KEY_PREFIX}:${customerId}:${indexId}${versionSuffix}:${normalizeRuntimeOrigin(input.runtimeOrigin)}`;
129
48
  }
130
-
131
- // src/react/components/FollowUpChips/FollowUpChips.tsx
132
- import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
133
- function FollowUpChips({
134
- suggestions,
135
- disabled = false,
136
- label = "Follow up",
137
- variant = "default",
138
- onSelect
139
- }) {
140
- if (suggestions.length === 0) return null;
141
- if (variant === "dock") {
142
- return /* @__PURE__ */ jsx4("div", { className: "followups followups--dock", children: /* @__PURE__ */ jsx4("div", { className: "followups__scroll", children: suggestions.map((suggestion) => /* @__PURE__ */ jsx4(
143
- "button",
144
- {
145
- type: "button",
146
- className: "followups__chip followups__chip--dock",
147
- disabled,
148
- onClick: () => onSelect?.(suggestion),
149
- children: suggestion.label
150
- },
151
- suggestion.id
152
- )) }) });
49
+ function createSessionId() {
50
+ if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
51
+ return crypto.randomUUID();
153
52
  }
154
- return /* @__PURE__ */ jsxs3("div", { className: "followups", children: [
155
- /* @__PURE__ */ jsx4("span", { className: "followups__label", children: label }),
156
- /* @__PURE__ */ jsx4("div", { className: "followups__list", children: suggestions.map((suggestion) => /* @__PURE__ */ jsx4(
157
- "button",
158
- {
159
- type: "button",
160
- className: "followups__chip",
161
- disabled,
162
- onClick: () => onSelect?.(suggestion),
163
- children: suggestion.label
164
- },
165
- suggestion.id
166
- )) })
167
- ] });
53
+ return `sess_${Math.random().toString(36).slice(2)}${Date.now().toString(36)}`;
168
54
  }
169
-
170
- // src/react/components/MessageBubble/MessageBubble.tsx
171
- import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
172
- function MessageBubble({ message }) {
173
- if (message.role === "visitor") {
174
- return /* @__PURE__ */ jsx5("article", { className: "message-bubble message-bubble--visitor", children: /* @__PURE__ */ jsx5("p", { className: "message-bubble__text", children: message.text }) });
55
+ function resolvePrefix(options) {
56
+ return options?.storageKeyPrefix?.trim() || DEFAULT_STORAGE_KEY_PREFIX;
57
+ }
58
+ function getOrCreateVisitorSessionId(options) {
59
+ const prefix = resolvePrefix(options);
60
+ const visitorKey = `${prefix}:visitorSessionId`;
61
+ if (typeof sessionStorage === "undefined") {
62
+ return createSessionId();
175
63
  }
176
- return /* @__PURE__ */ jsx5("article", { className: "message-bubble message-bubble--agent", children: /* @__PURE__ */ jsxs4("p", { className: "message-bubble__text", children: [
177
- message.text,
178
- message.streaming ? /* @__PURE__ */ jsx5("span", { className: "tool-timeline__cursor", "aria-hidden": "true" }) : null
179
- ] }) });
64
+ const existing = sessionStorage.getItem(visitorKey)?.trim();
65
+ if (existing) return existing;
66
+ const next = createSessionId();
67
+ sessionStorage.setItem(visitorKey, next);
68
+ return next;
180
69
  }
181
-
182
- // src/react/components/AgentRail/AgentRail.tsx
183
- import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
184
- function MinimizeIcon() {
185
- return /* @__PURE__ */ jsx6("svg", { viewBox: "0 0 16 16", fill: "none", "aria-hidden": "true", children: /* @__PURE__ */ jsx6("path", { d: "M3.5 8h9", stroke: "currentColor", strokeWidth: "1.7", strokeLinecap: "round" }) });
70
+ function runtimeSessionIdKey(visitorSessionId, prefix) {
71
+ return `${prefix}:eve:${visitorSessionId}:sessionId`;
186
72
  }
187
- function ExpandIcon() {
188
- return /* @__PURE__ */ jsx6("svg", { viewBox: "0 0 16 16", fill: "none", "aria-hidden": "true", children: /* @__PURE__ */ jsx6(
189
- "path",
190
- {
191
- d: "M6 3.5H3.5V6M10 3.5h2.5V6M10 12.5h2.5V10M6 12.5H3.5V10",
192
- stroke: "currentColor",
193
- strokeWidth: "1.5",
194
- strokeLinecap: "round",
195
- strokeLinejoin: "round"
196
- }
197
- ) });
73
+ function runtimeStreamIndexKey(visitorSessionId, prefix) {
74
+ return `${prefix}:eve:${visitorSessionId}:streamIndex`;
198
75
  }
199
- function RestoreIcon() {
200
- return /* @__PURE__ */ jsx6("svg", { viewBox: "0 0 16 16", fill: "none", "aria-hidden": "true", children: /* @__PURE__ */ jsx6(
201
- "path",
202
- {
203
- d: "M5.5 5.5H3.5V7.5M10.5 5.5h2V7.5M10.5 10.5h2V8.5M5.5 10.5H3.5V8.5",
204
- stroke: "currentColor",
205
- strokeWidth: "1.5",
206
- strokeLinecap: "round",
207
- strokeLinejoin: "round"
208
- }
209
- ) });
76
+ function loadPersistedAgentSession(visitorSessionId, options) {
77
+ if (typeof sessionStorage === "undefined" || !visitorSessionId.trim()) return null;
78
+ const prefix = resolvePrefix(options);
79
+ const sessionId = sessionStorage.getItem(runtimeSessionIdKey(visitorSessionId, prefix))?.trim();
80
+ if (!sessionId) return null;
81
+ const rawIndex = sessionStorage.getItem(runtimeStreamIndexKey(visitorSessionId, prefix));
82
+ const streamIndex = rawIndex ? Number.parseInt(rawIndex, 10) : 0;
83
+ return {
84
+ sessionId,
85
+ streamIndex: Number.isFinite(streamIndex) && streamIndex >= 0 ? streamIndex : 0
86
+ };
210
87
  }
211
- function AgentRail({
212
- state,
213
- theme,
214
- brandLabel = "Webless Assist",
215
- poweredByLabel = "Powered by Webless",
216
- composerPlaceholder = "Ask anything\u2026",
217
- mobileFullscreen = false,
218
- expanded = false,
219
- onCollapse,
220
- onClose,
221
- onExpandToggle,
222
- onSubmit,
223
- onFollowUpSelect
224
- }) {
225
- const transcriptRef = useRef2(null);
226
- const railStyle = theme?.railMaxWidth ? { ["--rail-width"]: theme.railMaxWidth, ["--as-rail-max-width"]: theme.railMaxWidth } : void 0;
227
- const isBusy = state.phase === "thinking" || state.phase === "running-tools" || state.phase === "streaming";
228
- const showActivity = state.toolSteps.length > 0 && (state.phase === "thinking" || state.phase === "running-tools" || state.phase === "streaming");
229
- const showStreamingInActivity = state.phase === "streaming" && Boolean(state.streamingText);
230
- const hasVisitorMessages2 = state.messages.some((message) => message.role === "visitor");
231
- const showIdleFollowUps = !hasVisitorMessages2 && state.followUps.length > 0;
232
- const showCompleteFollowUps = state.phase === "complete" && state.followUps.length > 0 && hasVisitorMessages2;
233
- const showDockFollowUps = expanded && (showIdleFollowUps || showCompleteFollowUps);
234
- useEffect(() => {
235
- const node = transcriptRef.current;
236
- if (!node) return;
237
- node.scrollTop = node.scrollHeight;
238
- }, [state.messages, state.toolSteps, state.streamingText, state.followUps, state.journey]);
239
- return /* @__PURE__ */ jsxs5(
240
- "aside",
241
- {
242
- className: `agent-rail${mobileFullscreen ? " agent-rail--mobile-fullscreen" : ""}${expanded ? " agent-rail--expanded" : ""}`,
243
- style: railStyle,
244
- "aria-label": "Agent conversation",
245
- children: [
246
- /* @__PURE__ */ jsx6("header", { className: "agent-rail__header", children: /* @__PURE__ */ jsxs5("div", { className: "agent-rail__brand-row", children: [
247
- onCollapse ? /* @__PURE__ */ jsx6(
248
- "button",
249
- {
250
- type: "button",
251
- className: "agent-rail__collapse",
252
- "aria-label": "Collapse assist",
253
- onClick: onCollapse,
254
- children: /* @__PURE__ */ jsx6(MinimizeIcon, {})
255
- }
256
- ) : onClose ? /* @__PURE__ */ jsx6("button", { type: "button", className: "agent-rail__close", "aria-label": "Close agent", onClick: onClose, children: /* @__PURE__ */ jsx6(MinimizeIcon, {}) }) : /* @__PURE__ */ jsx6("span", { className: "agent-rail__brand-spacer", "aria-hidden": "true" }),
257
- /* @__PURE__ */ jsx6("span", { className: "agent-rail__brand-label", children: brandLabel }),
258
- onExpandToggle ? /* @__PURE__ */ jsx6(
259
- "button",
260
- {
261
- type: "button",
262
- className: "agent-rail__expand",
263
- "aria-label": expanded ? "Restore assist panel" : "Expand assist panel",
264
- onClick: onExpandToggle,
265
- children: expanded ? /* @__PURE__ */ jsx6(RestoreIcon, {}) : /* @__PURE__ */ jsx6(ExpandIcon, {})
266
- }
267
- ) : /* @__PURE__ */ jsx6("span", { className: "agent-rail__brand-spacer", "aria-hidden": "true" })
268
- ] }) }),
269
- /* @__PURE__ */ jsxs5("div", { ref: transcriptRef, className: "agent-rail__transcript", children: [
270
- state.messages.map((message) => /* @__PURE__ */ jsx6(MessageBubble, { message }, message.id)),
271
- showActivity ? /* @__PURE__ */ jsx6(
272
- AgentActivityBubble,
273
- {
274
- steps: state.toolSteps,
275
- streamingText: state.streamingText,
276
- showStreaming: showStreamingInActivity
277
- }
278
- ) : null,
279
- !expanded && showIdleFollowUps ? /* @__PURE__ */ jsx6("div", { className: "agent-rail__followups-slot", children: /* @__PURE__ */ jsx6(
280
- FollowUpChips,
281
- {
282
- suggestions: state.followUps,
283
- disabled: isBusy,
284
- label: "Try asking",
285
- onSelect: (suggestion) => onFollowUpSelect?.(suggestion.label)
286
- }
287
- ) }) : null,
288
- !expanded && showCompleteFollowUps ? /* @__PURE__ */ jsx6("div", { className: "agent-rail__followups-slot", children: /* @__PURE__ */ jsx6(
289
- FollowUpChips,
290
- {
291
- suggestions: state.followUps,
292
- disabled: isBusy,
293
- onSelect: (suggestion) => onFollowUpSelect?.(suggestion.label)
294
- }
295
- ) }) : null,
296
- state.error ? /* @__PURE__ */ jsx6("p", { role: "alert", style: { fontSize: 13, color: "var(--as-danger)", margin: 0 }, children: state.error }) : null
297
- ] }),
298
- expanded ? /* @__PURE__ */ jsxs5("div", { className: "agent-rail__dock-wrap", children: [
299
- showDockFollowUps ? /* @__PURE__ */ jsx6("div", { className: "agent-rail__dock-followups", children: /* @__PURE__ */ jsx6(
300
- FollowUpChips,
301
- {
302
- variant: "dock",
303
- suggestions: state.followUps,
304
- disabled: isBusy,
305
- onSelect: (suggestion) => onFollowUpSelect?.(suggestion.label)
306
- }
307
- ) }) : null,
308
- /* @__PURE__ */ jsx6("div", { className: "agent-rail__dock", children: /* @__PURE__ */ jsx6(
309
- Composer,
310
- {
311
- variant: "dock",
312
- disabled: isBusy,
313
- placeholder: composerPlaceholder,
314
- onSubmit
315
- }
316
- ) }),
317
- /* @__PURE__ */ jsxs5("div", { className: "agent-rail__footer", children: [
318
- /* @__PURE__ */ jsx6("p", { className: "agent-rail__footer-disclaimer", children: "AI can make mistakes. Check important info." }),
319
- /* @__PURE__ */ jsx6("p", { className: "agent-rail__footer-note", children: poweredByLabel })
320
- ] })
321
- ] }) : /* @__PURE__ */ jsxs5("div", { className: "agent-rail__composer-wrap", children: [
322
- /* @__PURE__ */ jsx6(
323
- Composer,
324
- {
325
- disabled: isBusy,
326
- placeholder: composerPlaceholder,
327
- onSubmit
328
- }
329
- ),
330
- /* @__PURE__ */ jsxs5("div", { className: "agent-rail__footer", children: [
331
- /* @__PURE__ */ jsx6("p", { className: "agent-rail__footer-disclaimer", children: "AI can make mistakes. Check important info." }),
332
- /* @__PURE__ */ jsx6("p", { className: "agent-rail__footer-note", children: poweredByLabel })
333
- ] })
334
- ] })
335
- ]
336
- }
88
+ function savePersistedAgentSession(visitorSessionId, sessionId, streamIndex, options) {
89
+ if (typeof sessionStorage === "undefined" || !visitorSessionId.trim() || !sessionId.trim()) {
90
+ return;
91
+ }
92
+ const prefix = resolvePrefix(options);
93
+ sessionStorage.setItem(runtimeSessionIdKey(visitorSessionId, prefix), sessionId);
94
+ sessionStorage.setItem(
95
+ runtimeStreamIndexKey(visitorSessionId, prefix),
96
+ String(Math.max(0, streamIndex))
337
97
  );
338
98
  }
339
-
340
- // src/react/components/AssistEdgeTab/AssistEdgeTab.tsx
341
- import { Fragment, jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
342
- function SparklesIcon() {
343
- return /* @__PURE__ */ jsxs6("svg", { className: "assist-edge-tab__sparkles", viewBox: "0 0 18 16", fill: "none", "aria-hidden": "true", children: [
344
- /* @__PURE__ */ jsx7("path", { d: "M8 1.2l.95 2.7 2.85.05-2.25 1.75.8 2.75L8 6.7 5.65 8.45l.8-2.75L4.2 3.95l2.85-.05L8 1.2z", fill: "currentColor" }),
345
- /* @__PURE__ */ jsx7("path", { d: "M14.2 6.4l.55 1.55 1.65.03-1.3 1 .46 1.58-1.36-1-1.36 1 .46-1.58-1.3-1 1.65-.03.55-1.55z", fill: "currentColor" }),
346
- /* @__PURE__ */ jsx7("path", { d: "M3.1 9.1l.4 1.15 1.22.02-.96.74.34 1.17-1-.74-1 .74.34-1.17-.96-.74 1.22-.02.4-1.15z", fill: "currentColor" })
347
- ] });
348
- }
349
- function ChevronLeftIcon() {
350
- return /* @__PURE__ */ jsx7("svg", { viewBox: "0 0 16 16", fill: "none", "aria-hidden": "true", children: /* @__PURE__ */ jsx7("path", { d: "M10 4L6 8l4 4", stroke: "currentColor", strokeWidth: "1.6", strokeLinecap: "round", strokeLinejoin: "round" }) });
351
- }
352
- function ChevronDownIcon() {
353
- return /* @__PURE__ */ jsx7("svg", { viewBox: "0 0 16 16", fill: "none", "aria-hidden": "true", children: /* @__PURE__ */ jsx7("path", { d: "M4 6l4 4 4-4", stroke: "currentColor", strokeWidth: "1.6", strokeLinecap: "round", strokeLinejoin: "round" }) });
354
- }
355
- function DragDots() {
356
- return /* @__PURE__ */ jsx7("span", { className: "assist-edge-tab__dots", "aria-hidden": "true", children: Array.from({ length: 12 }, (_, index) => /* @__PURE__ */ jsx7("i", {}, index)) });
357
- }
358
- var VARIANT_COPY = {
359
- outline: { label: "Assist", aria: "Open Assist" },
360
- ask: { label: "Ask anything", aria: "Ask anything" },
361
- fill: { label: "Assist", aria: "Open Assist" }
362
- };
363
- function AssistEdgeTab({
364
- variant,
365
- side,
366
- along,
367
- inset,
368
- visible,
369
- onOpen
370
- }) {
371
- const copy = VARIANT_COPY[variant];
372
- const style = {
373
- "--tab-along": `${along}%`,
374
- "--tab-inset": `${inset}px`
375
- };
376
- return /* @__PURE__ */ jsxs6(
377
- "button",
378
- {
379
- type: "button",
380
- className: `assist-edge-tab assist-edge-tab--${variant} assist-edge-tab--${side}${visible ? " is-visible" : ""}`,
381
- style,
382
- "aria-label": copy.aria,
383
- "aria-hidden": !visible,
384
- tabIndex: visible ? 0 : -1,
385
- onClick: onOpen,
386
- children: [
387
- variant === "outline" ? /* @__PURE__ */ jsxs6(Fragment, { children: [
388
- /* @__PURE__ */ jsx7(SparklesIcon, {}),
389
- /* @__PURE__ */ jsx7("span", { className: "assist-edge-tab__label", children: copy.label }),
390
- /* @__PURE__ */ jsx7(ChevronDownIcon, {})
391
- ] }) : null,
392
- variant === "ask" ? /* @__PURE__ */ jsxs6(Fragment, { children: [
393
- /* @__PURE__ */ jsx7(ChevronLeftIcon, {}),
394
- /* @__PURE__ */ jsx7("span", { className: "assist-edge-tab__label", children: copy.label }),
395
- /* @__PURE__ */ jsx7(DragDots, {})
396
- ] }) : null,
397
- variant === "fill" ? /* @__PURE__ */ jsxs6(Fragment, { children: [
398
- /* @__PURE__ */ jsx7(SparklesIcon, {}),
399
- /* @__PURE__ */ jsx7("span", { className: "assist-edge-tab__label", children: copy.label }),
400
- /* @__PURE__ */ jsx7(ChevronLeftIcon, {})
401
- ] }) : null
402
- ]
403
- }
404
- );
99
+ function clearPersistedAgentSession(visitorSessionId, options) {
100
+ if (typeof sessionStorage === "undefined" || !visitorSessionId.trim()) return;
101
+ const prefix = resolvePrefix(options);
102
+ sessionStorage.removeItem(runtimeSessionIdKey(visitorSessionId, prefix));
103
+ sessionStorage.removeItem(runtimeStreamIndexKey(visitorSessionId, prefix));
405
104
  }
406
105
 
407
- // src/react/hooks/useAgentChat.ts
408
- import { useCallback, useEffect as useEffect2, useMemo, useRef as useRef3, useState as useState2 } from "react";
409
-
410
106
  // src/runtime/client.ts
411
- import {
412
- Client,
413
- ClientError
414
- } from "eve/client";
415
-
416
- // src/runtime/config.ts
417
- var DEFAULT_RUNTIME_ORIGIN = "https://runtime.staging.webless.ai";
418
- var trimTrailingSlash = (value) => value.replace(/\/+$/, "");
419
- function buildEveHost(origin, indexId) {
420
- const base = trimTrailingSlash(origin);
421
- return `${base}?indexId=${encodeURIComponent(indexId.trim())}`;
422
- }
423
- function resolveAgentRuntimeConfig(input) {
424
- const origin = trimTrailingSlash(input.runtimeOrigin?.trim() || DEFAULT_RUNTIME_ORIGIN);
425
- const resolvedIndexId = input.indexId.trim();
107
+ function mapStepLabel(event) {
108
+ if (event.type !== "step.started") return null;
109
+ const stepIndex = event.data.stepIndex;
426
110
  return {
427
- origin,
428
- indexId: resolvedIndexId,
429
- host: resolvedIndexId ? buildEveHost(origin, resolvedIndexId) : origin
111
+ label: "Model step running",
112
+ detail: typeof stepIndex === "number" ? `Step ${stepIndex + 1}` : void 0
430
113
  };
431
114
  }
432
-
433
- // src/runtime/session-store.ts
434
- var DEFAULT_STORAGE_KEY_PREFIX = "webless:agent";
435
- function normalizeRuntimeOrigin(runtimeOrigin) {
436
- const trimmed = runtimeOrigin?.trim();
437
- if (!trimmed) return "_";
438
- return trimmed.replace(/\/+$/, "");
439
- }
440
- function buildAgentStorageKeyPrefix(input) {
441
- const customerId = input.customerId?.trim() || "_";
442
- const indexId = input.indexId.trim();
443
- if (!indexId) {
444
- throw new Error("indexId is required to build agent storage keys.");
445
- }
446
- return `${DEFAULT_STORAGE_KEY_PREFIX}:${customerId}:${indexId}:${normalizeRuntimeOrigin(input.runtimeOrigin)}`;
447
- }
448
- function createSessionId() {
449
- if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
450
- return crypto.randomUUID();
451
- }
452
- return `sess_${Math.random().toString(36).slice(2)}${Date.now().toString(36)}`;
453
- }
454
- function resolvePrefix(options) {
455
- return options?.storageKeyPrefix?.trim() || DEFAULT_STORAGE_KEY_PREFIX;
456
- }
457
- function getOrCreateVisitorSessionId(options) {
458
- const prefix = resolvePrefix(options);
459
- const visitorKey = `${prefix}:visitorSessionId`;
460
- if (typeof sessionStorage === "undefined") {
461
- return createSessionId();
462
- }
463
- const existing = sessionStorage.getItem(visitorKey)?.trim();
464
- if (existing) return existing;
465
- const next = createSessionId();
466
- sessionStorage.setItem(visitorKey, next);
467
- return next;
468
- }
469
- function runtimeSessionIdKey(visitorSessionId, prefix) {
470
- return `${prefix}:eve:${visitorSessionId}:sessionId`;
471
- }
472
- function runtimeStreamIndexKey(visitorSessionId, prefix) {
473
- return `${prefix}:eve:${visitorSessionId}:streamIndex`;
474
- }
475
- function loadPersistedAgentSession(visitorSessionId, options) {
476
- if (typeof sessionStorage === "undefined" || !visitorSessionId.trim()) return null;
477
- const prefix = resolvePrefix(options);
478
- const sessionId = sessionStorage.getItem(runtimeSessionIdKey(visitorSessionId, prefix))?.trim();
479
- if (!sessionId) return null;
480
- const rawIndex = sessionStorage.getItem(runtimeStreamIndexKey(visitorSessionId, prefix));
481
- const streamIndex = rawIndex ? Number.parseInt(rawIndex, 10) : 0;
482
- return {
483
- sessionId,
484
- streamIndex: Number.isFinite(streamIndex) && streamIndex >= 0 ? streamIndex : 0
485
- };
486
- }
487
- function savePersistedAgentSession(visitorSessionId, sessionId, streamIndex, options) {
488
- if (typeof sessionStorage === "undefined" || !visitorSessionId.trim() || !sessionId.trim()) {
489
- return;
490
- }
491
- const prefix = resolvePrefix(options);
492
- sessionStorage.setItem(runtimeSessionIdKey(visitorSessionId, prefix), sessionId);
493
- sessionStorage.setItem(
494
- runtimeStreamIndexKey(visitorSessionId, prefix),
495
- String(Math.max(0, streamIndex))
496
- );
497
- }
498
- function clearPersistedAgentSession(visitorSessionId, options) {
499
- if (typeof sessionStorage === "undefined" || !visitorSessionId.trim()) return;
500
- const prefix = resolvePrefix(options);
501
- sessionStorage.removeItem(runtimeSessionIdKey(visitorSessionId, prefix));
502
- sessionStorage.removeItem(runtimeStreamIndexKey(visitorSessionId, prefix));
503
- }
504
-
505
- // src/runtime/client.ts
506
- function mapStepLabel(event) {
507
- if (event.type !== "step.started") return null;
508
- const stepIndex = event.data.stepIndex;
509
- return {
510
- label: "Model step running",
511
- detail: typeof stepIndex === "number" ? `Step ${stepIndex + 1}` : void 0
512
- };
513
- }
514
- var AgentSession = class {
515
- constructor(indexId, runtimeOrigin, visitorSessionId, storeOptions) {
516
- this.indexId = indexId;
517
- this.runtimeOrigin = runtimeOrigin;
518
- this.visitorSessionId = visitorSessionId;
519
- this.storeOptions = storeOptions;
115
+ var AgentSession = class {
116
+ constructor(indexId, version, runtimeOrigin, visitorSessionId, storeOptions) {
117
+ this.indexId = indexId;
118
+ this.version = version;
119
+ this.runtimeOrigin = runtimeOrigin;
120
+ this.visitorSessionId = visitorSessionId;
121
+ this.storeOptions = storeOptions;
520
122
  }
521
123
  indexId;
124
+ version;
522
125
  runtimeOrigin;
523
126
  visitorSessionId;
524
127
  storeOptions;
@@ -549,6 +152,7 @@ var AgentSession = class {
549
152
  ensureClient() {
550
153
  const config = resolveAgentRuntimeConfig({
551
154
  indexId: this.indexId,
155
+ version: this.version,
552
156
  runtimeOrigin: this.runtimeOrigin
553
157
  });
554
158
  if (!config.indexId) {
@@ -648,17 +252,20 @@ function createAgentClient(options) {
648
252
  throw new Error("indexId is required.");
649
253
  }
650
254
  const runtimeOrigin = options.runtimeOrigin?.trim() || resolveAgentRuntimeConfig({ indexId }).origin;
255
+ const version = options.version ?? "published";
651
256
  const storeOptions = {
652
257
  storageKeyPrefix: options.storageKeyPrefix?.trim() || buildAgentStorageKeyPrefix({
653
258
  customerId: options.customerId,
654
259
  indexId,
260
+ version,
655
261
  runtimeOrigin
656
262
  })
657
263
  };
658
264
  const visitorSessionId = options.visitorSessionId?.trim() || getOrCreateVisitorSessionId(storeOptions);
659
- const session = new AgentSession(indexId, runtimeOrigin, visitorSessionId, storeOptions);
265
+ const session = new AgentSession(indexId, version, runtimeOrigin, visitorSessionId, storeOptions);
660
266
  return {
661
267
  indexId,
268
+ version,
662
269
  runtimeOrigin,
663
270
  visitorSessionId,
664
271
  sendTurn: (message, sendOptions) => session.sendTurn(
@@ -672,261 +279,817 @@ function createAgentClient(options) {
672
279
  };
673
280
  }
674
281
 
675
- // src/runtime/errors.ts
676
- import { ClientError as ClientError2 } from "eve/client";
677
- function formatAgentError(error) {
678
- if (error instanceof ClientError2) {
679
- if (error.status === 401 && error.code === "index_required") {
680
- return "Missing indexId \u2014 pass a published index id to createAgentClient().";
681
- }
682
- if (error.status === 403 && error.code === "agent_unavailable") {
683
- return "Agent unavailable for this index (disabled or unpublished).";
684
- }
685
- if (error.status === 409 && error.code === "session_not_active") {
686
- return "Session expired \u2014 send a new message to start again.";
687
- }
688
- return error.message || `Runtime error (${error.status})`;
689
- }
690
- if (error instanceof DOMException && error.name === "AbortError") {
691
- return "";
692
- }
693
- if (error instanceof Error) return error.message;
694
- return "Runtime request failed";
282
+ // src/runtime/errors.ts
283
+ import { ClientError as ClientError2 } from "eve/client";
284
+ function formatAgentError(error) {
285
+ if (error instanceof ClientError2) {
286
+ if (error.status === 401 && error.code === "index_required") {
287
+ return "Missing indexId \u2014 pass a published index id to createAgentClient().";
288
+ }
289
+ if (error.status === 403 && error.code === "agent_unavailable") {
290
+ return "Agent unavailable for this index (disabled or unpublished).";
291
+ }
292
+ if (error.status === 409 && error.code === "session_not_active") {
293
+ return "Session expired \u2014 send a new message to start again.";
294
+ }
295
+ return error.message || `Runtime error (${error.status})`;
296
+ }
297
+ if (error instanceof DOMException && error.name === "AbortError") {
298
+ return "";
299
+ }
300
+ if (error instanceof Error) return error.message;
301
+ return "Runtime request failed";
302
+ }
303
+
304
+ // src/react/hooks/useAgentChat.ts
305
+ var GREETING_MESSAGE = {
306
+ id: "greeting",
307
+ role: "agent",
308
+ text: "Hi! I'm connected to the Webless Agent Runtime. Ask anything about your published site index.",
309
+ createdAt: 0
310
+ };
311
+ var INITIAL_STATE = {
312
+ phase: "idle",
313
+ messages: [GREETING_MESSAGE],
314
+ toolSteps: [],
315
+ journey: null,
316
+ followUps: [],
317
+ streamingText: "",
318
+ error: null
319
+ };
320
+ var STATUS_SEQUENCE = [
321
+ { id: "s1", label: "Starting Eve session", ms: 400 },
322
+ { id: "s2", label: "Connecting to runtime", ms: 500 }
323
+ ];
324
+ var DEFAULT_FOLLOW_UPS = [
325
+ { id: "fu-1", label: "What can you help me with?" },
326
+ { id: "fu-2", label: "Summarize your capabilities" },
327
+ { id: "fu-3", label: "What should I ask next?" }
328
+ ];
329
+ function delay(ms, signal) {
330
+ return new Promise((resolve, reject) => {
331
+ const timer = window.setTimeout(resolve, ms);
332
+ signal.addEventListener(
333
+ "abort",
334
+ () => {
335
+ window.clearTimeout(timer);
336
+ reject(new DOMException("Aborted", "AbortError"));
337
+ },
338
+ { once: true }
339
+ );
340
+ });
341
+ }
342
+ async function runStatusSequence(signal, onStep) {
343
+ for (const item of STATUS_SEQUENCE) {
344
+ onStep({ id: item.id, label: item.label, detail: item.detail, state: "active" });
345
+ await delay(item.ms, signal);
346
+ }
347
+ }
348
+ function useAgentChat({
349
+ customerId,
350
+ indexId,
351
+ version,
352
+ runtimeOrigin,
353
+ visitorSessionId,
354
+ storageKeyPrefix
355
+ }) {
356
+ const [state, setState] = useState(INITIAL_STATE);
357
+ const runRef = useRef(null);
358
+ const resolvedStorageKeyPrefix = useMemo(
359
+ () => storageKeyPrefix?.trim() || buildAgentStorageKeyPrefix({ customerId, indexId, version, runtimeOrigin }),
360
+ [customerId, indexId, runtimeOrigin, storageKeyPrefix, version]
361
+ );
362
+ const visitorId = useMemo(
363
+ () => visitorSessionId?.trim() || getOrCreateVisitorSessionId({ storageKeyPrefix: resolvedStorageKeyPrefix }),
364
+ [resolvedStorageKeyPrefix, visitorSessionId]
365
+ );
366
+ const clientRef = useRef(
367
+ createAgentClient({
368
+ customerId,
369
+ indexId,
370
+ version,
371
+ runtimeOrigin,
372
+ visitorSessionId: visitorId,
373
+ storageKeyPrefix: resolvedStorageKeyPrefix
374
+ })
375
+ );
376
+ const identityRef = useRef(null);
377
+ const identityKey = `${customerId ?? ""}|${indexId}|${version ?? "published"}|${runtimeOrigin ?? ""}|${resolvedStorageKeyPrefix}|${visitorId}`;
378
+ useEffect(() => {
379
+ if (identityRef.current === null) {
380
+ identityRef.current = identityKey;
381
+ return;
382
+ }
383
+ if (identityRef.current === identityKey) {
384
+ return;
385
+ }
386
+ identityRef.current = identityKey;
387
+ runRef.current?.abort();
388
+ runRef.current = null;
389
+ clientRef.current.reset();
390
+ clientRef.current = createAgentClient({
391
+ customerId,
392
+ indexId,
393
+ version,
394
+ runtimeOrigin,
395
+ visitorSessionId: visitorId,
396
+ storageKeyPrefix: resolvedStorageKeyPrefix
397
+ });
398
+ setState(INITIAL_STATE);
399
+ }, [customerId, identityKey, indexId, runtimeOrigin, resolvedStorageKeyPrefix, version, visitorId]);
400
+ const reset = useCallback(() => {
401
+ runRef.current?.abort();
402
+ runRef.current = null;
403
+ clientRef.current.reset();
404
+ setState(INITIAL_STATE);
405
+ }, []);
406
+ const submit = useCallback(
407
+ async (visitorText) => {
408
+ runRef.current?.abort();
409
+ clientRef.current.cancelActive();
410
+ const controller = new AbortController();
411
+ runRef.current = controller;
412
+ const { signal } = controller;
413
+ const isActiveRun = () => runRef.current === controller && !signal.aborted;
414
+ const visitorMessage = {
415
+ id: `visitor-${Date.now()}`,
416
+ role: "visitor",
417
+ text: visitorText,
418
+ createdAt: Date.now()
419
+ };
420
+ setState((prev) => ({
421
+ ...prev,
422
+ phase: "thinking",
423
+ messages: [...prev.messages, visitorMessage],
424
+ toolSteps: [{ id: "s1", label: "Starting Eve session", state: "active" }],
425
+ journey: null,
426
+ followUps: [],
427
+ streamingText: "",
428
+ error: null
429
+ }));
430
+ try {
431
+ let streamStarted = false;
432
+ const planningPromise = runStatusSequence(signal, (step) => {
433
+ if (streamStarted || !isActiveRun()) return;
434
+ setState((prev) => ({ ...prev, phase: "running-tools", toolSteps: [step] }));
435
+ });
436
+ let streamed = "";
437
+ const finalText = await clientRef.current.sendTurn(visitorText, {
438
+ signal,
439
+ handlers: {
440
+ onStep: (label, detail) => {
441
+ if (streamStarted || !isActiveRun()) return;
442
+ setState((prev) => ({
443
+ ...prev,
444
+ phase: "running-tools",
445
+ toolSteps: [{ id: `step-${label}`, label, detail, state: "active" }]
446
+ }));
447
+ },
448
+ onDelta: (delta) => {
449
+ if (!isActiveRun()) return;
450
+ void planningPromise.catch(() => {
451
+ });
452
+ if (!streamStarted) {
453
+ streamStarted = true;
454
+ setState((prev) => ({
455
+ ...prev,
456
+ phase: "streaming",
457
+ toolSteps: [],
458
+ streamingText: ""
459
+ }));
460
+ }
461
+ streamed += delta;
462
+ setState((prev) => ({ ...prev, phase: "streaming", streamingText: streamed }));
463
+ },
464
+ onComplete: () => {
465
+ if (!isActiveRun()) return;
466
+ streamStarted = true;
467
+ }
468
+ }
469
+ });
470
+ await planningPromise.catch(() => {
471
+ });
472
+ if (!isActiveRun()) return;
473
+ const agentMessage = {
474
+ id: `agent-${Date.now()}`,
475
+ role: "agent",
476
+ text: finalText,
477
+ createdAt: Date.now()
478
+ };
479
+ setState((prev) => ({
480
+ ...prev,
481
+ phase: "complete",
482
+ messages: [...prev.messages, agentMessage],
483
+ toolSteps: [],
484
+ streamingText: "",
485
+ followUps: DEFAULT_FOLLOW_UPS,
486
+ journey: null
487
+ }));
488
+ } catch (error) {
489
+ if (error instanceof DOMException && error.name === "AbortError") return;
490
+ if (!isActiveRun()) return;
491
+ const message = formatAgentError(error);
492
+ if (!message) return;
493
+ setState((prev) => ({
494
+ ...prev,
495
+ phase: "complete",
496
+ toolSteps: [],
497
+ streamingText: "",
498
+ error: message
499
+ }));
500
+ }
501
+ },
502
+ [customerId, indexId, runtimeOrigin, resolvedStorageKeyPrefix, version, visitorId]
503
+ );
504
+ useEffect(() => {
505
+ return () => {
506
+ runRef.current?.abort();
507
+ clientRef.current.cancelActive();
508
+ };
509
+ }, []);
510
+ return {
511
+ state,
512
+ reset,
513
+ submit,
514
+ visitorSessionId: visitorId,
515
+ sessionId: clientRef.current.getActiveSessionId()
516
+ };
517
+ }
518
+ function hasVisitorMessages(messages) {
519
+ return messages.some((message) => message.role === "visitor");
520
+ }
521
+ function createIdleSuggestions() {
522
+ return [
523
+ { id: "idle-1", label: "Hello \u2014 what can you do?" },
524
+ { id: "idle-2", label: "Help me evaluate this product" },
525
+ { id: "idle-3", label: "What should I ask you?" }
526
+ ];
527
+ }
528
+ function isAgentBusy(phase) {
529
+ return phase === "thinking" || phase === "running-tools" || phase === "streaming";
530
+ }
531
+
532
+ // src/react/placement.ts
533
+ var DEFAULT_AGENT_PLACEMENT = {
534
+ side: "right",
535
+ along: 50,
536
+ inset: 0,
537
+ variant: "outline"
538
+ };
539
+ function normalizeAgentPlacement(placement) {
540
+ const along = placement?.along ?? DEFAULT_AGENT_PLACEMENT.along;
541
+ return {
542
+ side: placement?.side ?? DEFAULT_AGENT_PLACEMENT.side,
543
+ along: Math.min(100, Math.max(0, along)),
544
+ inset: Math.max(0, placement?.inset ?? DEFAULT_AGENT_PLACEMENT.inset),
545
+ variant: placement?.variant ?? DEFAULT_AGENT_PLACEMENT.variant
546
+ };
547
+ }
548
+
549
+ // src/react/components/AgentRail/AgentRail.tsx
550
+ import { useEffect as useEffect2, useRef as useRef3 } from "react";
551
+
552
+ // src/react/components/ToolTimeline/ToolTimeline.tsx
553
+ import { jsx, jsxs } from "react/jsx-runtime";
554
+ function SpinnerIcon() {
555
+ return /* @__PURE__ */ jsxs("svg", { className: "agent-status__spinner", viewBox: "0 0 16 16", fill: "none", "aria-hidden": "true", children: [
556
+ /* @__PURE__ */ jsx("circle", { cx: "8", cy: "8", r: "6", stroke: "currentColor", strokeWidth: "1.5", strokeOpacity: "0.25" }),
557
+ /* @__PURE__ */ jsx("path", { d: "M14 8a6 6 0 0 0-6-6", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round" })
558
+ ] });
559
+ }
560
+ function getCurrentStep(steps) {
561
+ const active = steps.find((step) => step.state === "active");
562
+ if (active) return active;
563
+ const error = steps.find((step) => step.state === "error");
564
+ if (error) return error;
565
+ const pending = steps.find((step) => step.state === "pending");
566
+ if (pending) return pending;
567
+ return steps.at(-1) ?? null;
568
+ }
569
+ function ToolTimeline({
570
+ steps,
571
+ streamingText,
572
+ showStreaming = false,
573
+ inline = false
574
+ }) {
575
+ if (steps.length === 0 && !(showStreaming && streamingText)) return null;
576
+ const currentStep = getCurrentStep(steps);
577
+ const showStatus = currentStep && currentStep.state !== "completed" && !(showStreaming && streamingText);
578
+ return /* @__PURE__ */ jsxs(
579
+ "div",
580
+ {
581
+ className: `tool-timeline${inline ? " tool-timeline--inline" : ""}`,
582
+ role: "status",
583
+ "aria-live": "polite",
584
+ "aria-label": "Agent progress",
585
+ children: [
586
+ showStatus ? /* @__PURE__ */ jsxs("div", { className: "agent-status", children: [
587
+ currentStep.state === "error" ? /* @__PURE__ */ jsx("span", { className: "agent-status__icon agent-status__icon--error", "aria-hidden": "true", children: "!" }) : /* @__PURE__ */ jsx("span", { className: "agent-status__icon", "aria-hidden": "true", children: /* @__PURE__ */ jsx(SpinnerIcon, {}) }),
588
+ /* @__PURE__ */ jsxs("div", { className: "agent-status__copy", children: [
589
+ /* @__PURE__ */ jsx("span", { className: "agent-status__label", children: currentStep.label }),
590
+ currentStep.detail ? /* @__PURE__ */ jsx("span", { className: "agent-status__detail", children: currentStep.detail }) : null
591
+ ] })
592
+ ] }, currentStep.id) : null,
593
+ showStreaming && streamingText ? /* @__PURE__ */ jsxs("p", { className: "tool-timeline__streaming-text", children: [
594
+ streamingText,
595
+ /* @__PURE__ */ jsx("span", { className: "tool-timeline__cursor", "aria-hidden": "true" })
596
+ ] }) : null
597
+ ]
598
+ }
599
+ );
600
+ }
601
+
602
+ // src/react/components/AgentActivityBubble/AgentActivityBubble.tsx
603
+ import { jsx as jsx2 } from "react/jsx-runtime";
604
+ function AgentActivityBubble({
605
+ steps,
606
+ streamingText,
607
+ showStreaming = false
608
+ }) {
609
+ return /* @__PURE__ */ jsx2("article", { className: "agent-activity-bubble", children: /* @__PURE__ */ jsx2("div", { className: "agent-activity-bubble__shell", children: /* @__PURE__ */ jsx2(
610
+ ToolTimeline,
611
+ {
612
+ steps,
613
+ streamingText,
614
+ showStreaming,
615
+ inline: true
616
+ }
617
+ ) }) });
618
+ }
619
+
620
+ // src/react/components/Composer/Composer.tsx
621
+ import { useRef as useRef2, useState as useState2 } from "react";
622
+ import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
623
+ function SendIcon() {
624
+ return /* @__PURE__ */ jsx3("svg", { viewBox: "0 0 16 16", fill: "none", "aria-hidden": "true", children: /* @__PURE__ */ jsx3("path", { d: "M8 12V4M8 4l-3 3M8 4l3 3", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round" }) });
625
+ }
626
+ function Composer({
627
+ disabled = false,
628
+ placeholder = "Ask anything\u2026",
629
+ variant = "default",
630
+ onSubmit
631
+ }) {
632
+ const [value, setValue] = useState2("");
633
+ const inputRef = useRef2(null);
634
+ function submitCurrent() {
635
+ const trimmed = value.trim();
636
+ if (!trimmed || disabled) return;
637
+ onSubmit?.(trimmed);
638
+ setValue("");
639
+ inputRef.current?.focus();
640
+ }
641
+ function handleSubmit(event) {
642
+ event.preventDefault();
643
+ submitCurrent();
644
+ }
645
+ function handleKeyDown(event) {
646
+ if (event.key === "Enter" && !event.shiftKey) {
647
+ event.preventDefault();
648
+ submitCurrent();
649
+ }
650
+ }
651
+ return /* @__PURE__ */ jsx3("form", { className: `composer${variant === "dock" ? " composer--dock" : ""}`, onSubmit: handleSubmit, children: /* @__PURE__ */ jsxs2("div", { className: "composer__field", children: [
652
+ /* @__PURE__ */ jsx3(
653
+ "textarea",
654
+ {
655
+ ref: inputRef,
656
+ className: "composer__input",
657
+ rows: 1,
658
+ value,
659
+ placeholder,
660
+ disabled,
661
+ "aria-label": "Message",
662
+ onChange: (event) => setValue(event.target.value),
663
+ onKeyDown: handleKeyDown
664
+ }
665
+ ),
666
+ /* @__PURE__ */ jsx3(
667
+ "button",
668
+ {
669
+ type: "submit",
670
+ className: "composer__send",
671
+ disabled: disabled || !value.trim(),
672
+ "aria-label": "Send message",
673
+ children: /* @__PURE__ */ jsx3(SendIcon, {})
674
+ }
675
+ )
676
+ ] }) });
677
+ }
678
+
679
+ // src/react/components/FollowUpChips/FollowUpChips.tsx
680
+ import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
681
+ function FollowUpChips({
682
+ suggestions,
683
+ disabled = false,
684
+ label = "Follow up",
685
+ variant = "default",
686
+ onSelect
687
+ }) {
688
+ if (suggestions.length === 0) return null;
689
+ if (variant === "dock") {
690
+ return /* @__PURE__ */ jsx4("div", { className: "followups followups--dock", children: /* @__PURE__ */ jsx4("div", { className: "followups__scroll", children: suggestions.map((suggestion) => /* @__PURE__ */ jsx4(
691
+ "button",
692
+ {
693
+ type: "button",
694
+ className: "followups__chip followups__chip--dock",
695
+ disabled,
696
+ onClick: () => onSelect?.(suggestion),
697
+ children: suggestion.label
698
+ },
699
+ suggestion.id
700
+ )) }) });
701
+ }
702
+ return /* @__PURE__ */ jsxs3("div", { className: "followups", children: [
703
+ /* @__PURE__ */ jsx4("span", { className: "followups__label", children: label }),
704
+ /* @__PURE__ */ jsx4("div", { className: "followups__list", children: suggestions.map((suggestion) => /* @__PURE__ */ jsx4(
705
+ "button",
706
+ {
707
+ type: "button",
708
+ className: "followups__chip",
709
+ disabled,
710
+ onClick: () => onSelect?.(suggestion),
711
+ children: suggestion.label
712
+ },
713
+ suggestion.id
714
+ )) })
715
+ ] });
716
+ }
717
+
718
+ // src/react/components/MessageBubble/MessageBubble.tsx
719
+ import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
720
+ function MessageBubble({ message }) {
721
+ if (message.role === "visitor") {
722
+ return /* @__PURE__ */ jsx5("article", { className: "message-bubble message-bubble--visitor", children: /* @__PURE__ */ jsx5("p", { className: "message-bubble__text", children: message.text }) });
723
+ }
724
+ return /* @__PURE__ */ jsx5("article", { className: "message-bubble message-bubble--agent", children: /* @__PURE__ */ jsxs4("p", { className: "message-bubble__text", children: [
725
+ message.text,
726
+ message.streaming ? /* @__PURE__ */ jsx5("span", { className: "tool-timeline__cursor", "aria-hidden": "true" }) : null
727
+ ] }) });
728
+ }
729
+
730
+ // src/react/components/AgentRail/AgentRail.tsx
731
+ import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
732
+ function MinimizeIcon() {
733
+ return /* @__PURE__ */ jsx6("svg", { viewBox: "0 0 16 16", fill: "none", "aria-hidden": "true", children: /* @__PURE__ */ jsx6("path", { d: "M3.5 8h9", stroke: "currentColor", strokeWidth: "1.7", strokeLinecap: "round" }) });
734
+ }
735
+ function ExpandIcon() {
736
+ return /* @__PURE__ */ jsx6("svg", { viewBox: "0 0 16 16", fill: "none", "aria-hidden": "true", children: /* @__PURE__ */ jsx6(
737
+ "path",
738
+ {
739
+ d: "M6 3.5H3.5V6M10 3.5h2.5V6M10 12.5h2.5V10M6 12.5H3.5V10",
740
+ stroke: "currentColor",
741
+ strokeWidth: "1.5",
742
+ strokeLinecap: "round",
743
+ strokeLinejoin: "round"
744
+ }
745
+ ) });
746
+ }
747
+ function RestoreIcon() {
748
+ return /* @__PURE__ */ jsx6("svg", { viewBox: "0 0 16 16", fill: "none", "aria-hidden": "true", children: /* @__PURE__ */ jsx6(
749
+ "path",
750
+ {
751
+ d: "M5.5 5.5H3.5V7.5M10.5 5.5h2V7.5M10.5 10.5h2V8.5M5.5 10.5H3.5V8.5",
752
+ stroke: "currentColor",
753
+ strokeWidth: "1.5",
754
+ strokeLinecap: "round",
755
+ strokeLinejoin: "round"
756
+ }
757
+ ) });
758
+ }
759
+ function AgentRail({
760
+ state,
761
+ theme,
762
+ brandLabel = "Webless Assist",
763
+ poweredByLabel = "Powered by Webless",
764
+ composerPlaceholder = "Ask anything\u2026",
765
+ mobileFullscreen = false,
766
+ expanded = false,
767
+ onCollapse,
768
+ onClose,
769
+ onExpandToggle,
770
+ onSubmit,
771
+ onFollowUpSelect
772
+ }) {
773
+ const transcriptRef = useRef3(null);
774
+ const railStyle = theme?.railMaxWidth ? { ["--rail-width"]: theme.railMaxWidth, ["--as-rail-max-width"]: theme.railMaxWidth } : void 0;
775
+ const isBusy = state.phase === "thinking" || state.phase === "running-tools" || state.phase === "streaming";
776
+ const showActivity = state.toolSteps.length > 0 && (state.phase === "thinking" || state.phase === "running-tools" || state.phase === "streaming");
777
+ const showStreamingInActivity = state.phase === "streaming" && Boolean(state.streamingText);
778
+ const hasVisitorMessages2 = state.messages.some((message) => message.role === "visitor");
779
+ const showIdleFollowUps = !hasVisitorMessages2 && state.followUps.length > 0;
780
+ const showCompleteFollowUps = state.phase === "complete" && state.followUps.length > 0 && hasVisitorMessages2;
781
+ const showDockFollowUps = expanded && (showIdleFollowUps || showCompleteFollowUps);
782
+ useEffect2(() => {
783
+ const node = transcriptRef.current;
784
+ if (!node) return;
785
+ node.scrollTop = node.scrollHeight;
786
+ }, [state.messages, state.toolSteps, state.streamingText, state.followUps, state.journey]);
787
+ return /* @__PURE__ */ jsxs5(
788
+ "aside",
789
+ {
790
+ className: `agent-rail${mobileFullscreen ? " agent-rail--mobile-fullscreen" : ""}${expanded ? " agent-rail--expanded" : ""}`,
791
+ style: railStyle,
792
+ "aria-label": "Agent conversation",
793
+ children: [
794
+ /* @__PURE__ */ jsx6("header", { className: "agent-rail__header", children: /* @__PURE__ */ jsxs5("div", { className: "agent-rail__brand-row", children: [
795
+ onCollapse ? /* @__PURE__ */ jsx6(
796
+ "button",
797
+ {
798
+ type: "button",
799
+ className: "agent-rail__collapse",
800
+ "aria-label": "Collapse assist",
801
+ onClick: onCollapse,
802
+ children: /* @__PURE__ */ jsx6(MinimizeIcon, {})
803
+ }
804
+ ) : onClose ? /* @__PURE__ */ jsx6("button", { type: "button", className: "agent-rail__close", "aria-label": "Close agent", onClick: onClose, children: /* @__PURE__ */ jsx6(MinimizeIcon, {}) }) : /* @__PURE__ */ jsx6("span", { className: "agent-rail__brand-spacer", "aria-hidden": "true" }),
805
+ /* @__PURE__ */ jsx6("span", { className: "agent-rail__brand-label", children: brandLabel }),
806
+ onExpandToggle ? /* @__PURE__ */ jsx6(
807
+ "button",
808
+ {
809
+ type: "button",
810
+ className: "agent-rail__expand",
811
+ "aria-label": expanded ? "Restore assist panel" : "Expand assist panel",
812
+ onClick: onExpandToggle,
813
+ children: expanded ? /* @__PURE__ */ jsx6(RestoreIcon, {}) : /* @__PURE__ */ jsx6(ExpandIcon, {})
814
+ }
815
+ ) : /* @__PURE__ */ jsx6("span", { className: "agent-rail__brand-spacer", "aria-hidden": "true" })
816
+ ] }) }),
817
+ /* @__PURE__ */ jsxs5("div", { ref: transcriptRef, className: "agent-rail__transcript", children: [
818
+ state.messages.map((message) => /* @__PURE__ */ jsx6(MessageBubble, { message }, message.id)),
819
+ showActivity ? /* @__PURE__ */ jsx6(
820
+ AgentActivityBubble,
821
+ {
822
+ steps: state.toolSteps,
823
+ streamingText: state.streamingText,
824
+ showStreaming: showStreamingInActivity
825
+ }
826
+ ) : null,
827
+ !expanded && showIdleFollowUps ? /* @__PURE__ */ jsx6("div", { className: "agent-rail__followups-slot", children: /* @__PURE__ */ jsx6(
828
+ FollowUpChips,
829
+ {
830
+ suggestions: state.followUps,
831
+ disabled: isBusy,
832
+ label: "Try asking",
833
+ onSelect: (suggestion) => onFollowUpSelect?.(suggestion.label)
834
+ }
835
+ ) }) : null,
836
+ !expanded && showCompleteFollowUps ? /* @__PURE__ */ jsx6("div", { className: "agent-rail__followups-slot", children: /* @__PURE__ */ jsx6(
837
+ FollowUpChips,
838
+ {
839
+ suggestions: state.followUps,
840
+ disabled: isBusy,
841
+ onSelect: (suggestion) => onFollowUpSelect?.(suggestion.label)
842
+ }
843
+ ) }) : null,
844
+ state.error ? /* @__PURE__ */ jsx6("p", { role: "alert", style: { fontSize: 13, color: "var(--as-danger)", margin: 0 }, children: state.error }) : null
845
+ ] }),
846
+ expanded ? /* @__PURE__ */ jsxs5("div", { className: "agent-rail__dock-wrap", children: [
847
+ showDockFollowUps ? /* @__PURE__ */ jsx6("div", { className: "agent-rail__dock-followups", children: /* @__PURE__ */ jsx6(
848
+ FollowUpChips,
849
+ {
850
+ variant: "dock",
851
+ suggestions: state.followUps,
852
+ disabled: isBusy,
853
+ onSelect: (suggestion) => onFollowUpSelect?.(suggestion.label)
854
+ }
855
+ ) }) : null,
856
+ /* @__PURE__ */ jsx6("div", { className: "agent-rail__dock", children: /* @__PURE__ */ jsx6(
857
+ Composer,
858
+ {
859
+ variant: "dock",
860
+ disabled: isBusy,
861
+ placeholder: composerPlaceholder,
862
+ onSubmit
863
+ }
864
+ ) }),
865
+ /* @__PURE__ */ jsxs5("div", { className: "agent-rail__footer", children: [
866
+ /* @__PURE__ */ jsx6("p", { className: "agent-rail__footer-disclaimer", children: "AI can make mistakes. Check important info." }),
867
+ /* @__PURE__ */ jsx6("p", { className: "agent-rail__footer-note", children: poweredByLabel })
868
+ ] })
869
+ ] }) : /* @__PURE__ */ jsxs5("div", { className: "agent-rail__composer-wrap", children: [
870
+ /* @__PURE__ */ jsx6(
871
+ Composer,
872
+ {
873
+ disabled: isBusy,
874
+ placeholder: composerPlaceholder,
875
+ onSubmit
876
+ }
877
+ ),
878
+ /* @__PURE__ */ jsxs5("div", { className: "agent-rail__footer", children: [
879
+ /* @__PURE__ */ jsx6("p", { className: "agent-rail__footer-disclaimer", children: "AI can make mistakes. Check important info." }),
880
+ /* @__PURE__ */ jsx6("p", { className: "agent-rail__footer-note", children: poweredByLabel })
881
+ ] })
882
+ ] })
883
+ ]
884
+ }
885
+ );
886
+ }
887
+
888
+ // src/react/components/AssistEdgeTab/AssistEdgeTab.tsx
889
+ import { Fragment, jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
890
+ function SparklesIcon() {
891
+ return /* @__PURE__ */ jsxs6("svg", { className: "assist-edge-tab__sparkles", viewBox: "0 0 18 16", fill: "none", "aria-hidden": "true", children: [
892
+ /* @__PURE__ */ jsx7("path", { d: "M8 1.2l.95 2.7 2.85.05-2.25 1.75.8 2.75L8 6.7 5.65 8.45l.8-2.75L4.2 3.95l2.85-.05L8 1.2z", fill: "currentColor" }),
893
+ /* @__PURE__ */ jsx7("path", { d: "M14.2 6.4l.55 1.55 1.65.03-1.3 1 .46 1.58-1.36-1-1.36 1 .46-1.58-1.3-1 1.65-.03.55-1.55z", fill: "currentColor" }),
894
+ /* @__PURE__ */ jsx7("path", { d: "M3.1 9.1l.4 1.15 1.22.02-.96.74.34 1.17-1-.74-1 .74.34-1.17-.96-.74 1.22-.02.4-1.15z", fill: "currentColor" })
895
+ ] });
896
+ }
897
+ function ChevronLeftIcon() {
898
+ return /* @__PURE__ */ jsx7("svg", { viewBox: "0 0 16 16", fill: "none", "aria-hidden": "true", children: /* @__PURE__ */ jsx7("path", { d: "M10 4L6 8l4 4", stroke: "currentColor", strokeWidth: "1.6", strokeLinecap: "round", strokeLinejoin: "round" }) });
899
+ }
900
+ function ChevronDownIcon() {
901
+ return /* @__PURE__ */ jsx7("svg", { viewBox: "0 0 16 16", fill: "none", "aria-hidden": "true", children: /* @__PURE__ */ jsx7("path", { d: "M4 6l4 4 4-4", stroke: "currentColor", strokeWidth: "1.6", strokeLinecap: "round", strokeLinejoin: "round" }) });
902
+ }
903
+ function DragDots() {
904
+ return /* @__PURE__ */ jsx7("span", { className: "assist-edge-tab__dots", "aria-hidden": "true", children: Array.from({ length: 12 }, (_, index) => /* @__PURE__ */ jsx7("i", {}, index)) });
905
+ }
906
+ var VARIANT_COPY = {
907
+ outline: { label: "Assist", aria: "Open Assist" },
908
+ ask: { label: "Ask anything", aria: "Ask anything" },
909
+ fill: { label: "Assist", aria: "Open Assist" }
910
+ };
911
+ function AssistEdgeTab({
912
+ variant,
913
+ side,
914
+ along,
915
+ inset,
916
+ visible,
917
+ onOpen
918
+ }) {
919
+ const copy = VARIANT_COPY[variant];
920
+ const style = {
921
+ "--tab-along": `${along}%`,
922
+ "--tab-inset": `${inset}px`
923
+ };
924
+ return /* @__PURE__ */ jsxs6(
925
+ "button",
926
+ {
927
+ type: "button",
928
+ className: `assist-edge-tab assist-edge-tab--${variant} assist-edge-tab--${side}${visible ? " is-visible" : ""}`,
929
+ style,
930
+ "aria-label": copy.aria,
931
+ "aria-hidden": !visible,
932
+ tabIndex: visible ? 0 : -1,
933
+ onClick: onOpen,
934
+ children: [
935
+ variant === "outline" ? /* @__PURE__ */ jsxs6(Fragment, { children: [
936
+ /* @__PURE__ */ jsx7(SparklesIcon, {}),
937
+ /* @__PURE__ */ jsx7("span", { className: "assist-edge-tab__label", children: copy.label }),
938
+ /* @__PURE__ */ jsx7(ChevronDownIcon, {})
939
+ ] }) : null,
940
+ variant === "ask" ? /* @__PURE__ */ jsxs6(Fragment, { children: [
941
+ /* @__PURE__ */ jsx7(ChevronLeftIcon, {}),
942
+ /* @__PURE__ */ jsx7("span", { className: "assist-edge-tab__label", children: copy.label }),
943
+ /* @__PURE__ */ jsx7(DragDots, {})
944
+ ] }) : null,
945
+ variant === "fill" ? /* @__PURE__ */ jsxs6(Fragment, { children: [
946
+ /* @__PURE__ */ jsx7(SparklesIcon, {}),
947
+ /* @__PURE__ */ jsx7("span", { className: "assist-edge-tab__label", children: copy.label }),
948
+ /* @__PURE__ */ jsx7(ChevronLeftIcon, {})
949
+ ] }) : null
950
+ ]
951
+ }
952
+ );
953
+ }
954
+
955
+ // src/react/components/AgentWidget/AgentWidget.tsx
956
+ import { useEffect as useEffect4, useState as useState4 } from "react";
957
+
958
+ // src/react/hooks/useIsMobile.ts
959
+ import { useEffect as useEffect3, useState as useState3 } from "react";
960
+ function useIsMobile(breakpoint = 767) {
961
+ const [isMobile, setIsMobile] = useState3(
962
+ () => typeof window !== "undefined" && window.matchMedia(`(max-width: ${breakpoint}px)`).matches
963
+ );
964
+ useEffect3(() => {
965
+ const media = window.matchMedia(`(max-width: ${breakpoint}px)`);
966
+ const onChange = () => setIsMobile(media.matches);
967
+ onChange();
968
+ media.addEventListener("change", onChange);
969
+ return () => media.removeEventListener("change", onChange);
970
+ }, [breakpoint]);
971
+ return isMobile;
695
972
  }
696
973
 
697
- // src/react/hooks/useAgentChat.ts
698
- var GREETING_MESSAGE = {
699
- id: "greeting",
700
- role: "agent",
701
- text: "Hi! I'm connected to the Webless Agent Runtime. Ask anything about your published site index.",
702
- createdAt: 0
703
- };
704
- var INITIAL_STATE = {
705
- phase: "idle",
706
- messages: [GREETING_MESSAGE],
707
- toolSteps: [],
708
- journey: null,
709
- followUps: [],
710
- streamingText: "",
711
- error: null
712
- };
713
- var STATUS_SEQUENCE = [
714
- { id: "s1", label: "Starting Eve session", ms: 400 },
715
- { id: "s2", label: "Connecting to runtime", ms: 500 }
716
- ];
717
- var DEFAULT_FOLLOW_UPS = [
718
- { id: "fu-1", label: "What can you help me with?" },
719
- { id: "fu-2", label: "Summarize your capabilities" },
720
- { id: "fu-3", label: "What should I ask next?" }
721
- ];
722
- function delay(ms, signal) {
723
- return new Promise((resolve, reject) => {
724
- const timer = window.setTimeout(resolve, ms);
725
- signal.addEventListener(
726
- "abort",
727
- () => {
728
- window.clearTimeout(timer);
729
- reject(new DOMException("Aborted", "AbortError"));
730
- },
731
- { once: true }
732
- );
733
- });
974
+ // src/react/panel-controller.ts
975
+ var controllers = /* @__PURE__ */ new Map();
976
+ function registerAgentPanelController(customerId, controller) {
977
+ controllers.set(customerId, controller);
734
978
  }
735
- async function runStatusSequence(signal, onStep) {
736
- for (const item of STATUS_SEQUENCE) {
737
- onStep({ id: item.id, label: item.label, detail: item.detail, state: "active" });
738
- await delay(item.ms, signal);
739
- }
979
+ function unregisterAgentPanelController(customerId) {
980
+ controllers.delete(customerId);
740
981
  }
741
- function useAgentChat({
742
- customerId,
982
+ function openAgentPanel(customerId) {
983
+ controllers.get(customerId)?.open();
984
+ }
985
+ function closeAgentPanel(customerId) {
986
+ controllers.get(customerId)?.close();
987
+ }
988
+
989
+ // src/react/components/AgentWidget/AgentWidget.tsx
990
+ import { jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
991
+ function AgentWidget({
743
992
  indexId,
993
+ customerId,
994
+ version,
744
995
  runtimeOrigin,
745
- visitorSessionId,
746
- storageKeyPrefix
996
+ placement: placementInput,
997
+ defaultCollapsed = true,
998
+ registerPanelController = false
747
999
  }) {
748
- const [state, setState] = useState2(INITIAL_STATE);
749
- const runRef = useRef3(null);
750
- const resolvedStorageKeyPrefix = useMemo(
751
- () => storageKeyPrefix?.trim() || buildAgentStorageKeyPrefix({ customerId, indexId, runtimeOrigin }),
752
- [customerId, indexId, runtimeOrigin, storageKeyPrefix]
753
- );
754
- const visitorId = useMemo(
755
- () => visitorSessionId?.trim() || getOrCreateVisitorSessionId({ storageKeyPrefix: resolvedStorageKeyPrefix }),
756
- [resolvedStorageKeyPrefix, visitorSessionId]
757
- );
758
- const clientRef = useRef3(
759
- createAgentClient({
760
- customerId,
761
- indexId,
762
- runtimeOrigin,
763
- visitorSessionId: visitorId,
764
- storageKeyPrefix: resolvedStorageKeyPrefix
765
- })
766
- );
767
- const identityRef = useRef3(null);
768
- const identityKey = `${customerId ?? ""}|${indexId}|${runtimeOrigin ?? ""}|${resolvedStorageKeyPrefix}|${visitorId}`;
769
- useEffect2(() => {
770
- if (identityRef.current === null) {
771
- identityRef.current = identityKey;
772
- return;
773
- }
774
- if (identityRef.current === identityKey) {
775
- return;
776
- }
777
- identityRef.current = identityKey;
778
- runRef.current?.abort();
779
- runRef.current = null;
780
- clientRef.current.reset();
781
- clientRef.current = createAgentClient({
782
- customerId,
783
- indexId,
784
- runtimeOrigin,
785
- visitorSessionId: visitorId,
786
- storageKeyPrefix: resolvedStorageKeyPrefix
1000
+ const isMobile = useIsMobile();
1001
+ const placement = normalizeAgentPlacement(placementInput);
1002
+ const [railCollapsed, setRailCollapsed] = useState4(defaultCollapsed);
1003
+ const [railExpanded, setRailExpanded] = useState4(false);
1004
+ const { state, submit } = useAgentChat({
1005
+ customerId,
1006
+ indexId,
1007
+ version,
1008
+ runtimeOrigin
1009
+ });
1010
+ const idle = state.phase === "idle" && !hasVisitorMessages(state.messages);
1011
+ useEffect4(() => {
1012
+ if (!registerPanelController) return;
1013
+ registerAgentPanelController(customerId, {
1014
+ open: () => setRailCollapsed(false),
1015
+ close: () => {
1016
+ setRailCollapsed(true);
1017
+ setRailExpanded(false);
1018
+ }
787
1019
  });
788
- setState(INITIAL_STATE);
789
- }, [customerId, identityKey, indexId, runtimeOrigin, resolvedStorageKeyPrefix, visitorId]);
790
- const reset = useCallback(() => {
791
- runRef.current?.abort();
792
- runRef.current = null;
793
- clientRef.current.reset();
794
- setState(INITIAL_STATE);
795
- }, []);
796
- const submit = useCallback(
797
- async (visitorText) => {
798
- runRef.current?.abort();
799
- clientRef.current.cancelActive();
800
- const controller = new AbortController();
801
- runRef.current = controller;
802
- const { signal } = controller;
803
- const isActiveRun = () => runRef.current === controller && !signal.aborted;
804
- const visitorMessage = {
805
- id: `visitor-${Date.now()}`,
806
- role: "visitor",
807
- text: visitorText,
808
- createdAt: Date.now()
809
- };
810
- setState((prev) => ({
811
- ...prev,
812
- phase: "thinking",
813
- messages: [...prev.messages, visitorMessage],
814
- toolSteps: [{ id: "s1", label: "Starting Eve session", state: "active" }],
815
- journey: null,
816
- followUps: [],
817
- streamingText: "",
818
- error: null
819
- }));
820
- try {
821
- let streamStarted = false;
822
- const planningPromise = runStatusSequence(signal, (step) => {
823
- if (streamStarted || !isActiveRun()) return;
824
- setState((prev) => ({ ...prev, phase: "running-tools", toolSteps: [step] }));
825
- });
826
- let streamed = "";
827
- const finalText = await clientRef.current.sendTurn(visitorText, {
828
- signal,
829
- handlers: {
830
- onStep: (label, detail) => {
831
- if (streamStarted || !isActiveRun()) return;
832
- setState((prev) => ({
833
- ...prev,
834
- phase: "running-tools",
835
- toolSteps: [{ id: `step-${label}`, label, detail, state: "active" }]
836
- }));
837
- },
838
- onDelta: (delta) => {
839
- if (!isActiveRun()) return;
840
- void planningPromise.catch(() => {
841
- });
842
- if (!streamStarted) {
843
- streamStarted = true;
844
- setState((prev) => ({
845
- ...prev,
846
- phase: "streaming",
847
- toolSteps: [],
848
- streamingText: ""
849
- }));
1020
+ return () => unregisterAgentPanelController(customerId);
1021
+ }, [customerId, registerPanelController]);
1022
+ async function handleSubmit(message) {
1023
+ if (isMobile) setRailCollapsed(false);
1024
+ await submit(message);
1025
+ }
1026
+ return /* @__PURE__ */ jsxs7("div", { className: "webless-agent-root", children: [
1027
+ /* @__PURE__ */ jsx8(
1028
+ "div",
1029
+ {
1030
+ className: `webless-agent-root__shell${railCollapsed ? " webless-agent-root__shell--collapsed" : ""}${railExpanded ? " webless-agent-root__shell--expanded" : ""}`,
1031
+ children: /* @__PURE__ */ jsx8(
1032
+ "div",
1033
+ {
1034
+ className: "webless-agent-root__rail-slot",
1035
+ inert: railCollapsed || void 0,
1036
+ "aria-hidden": railCollapsed,
1037
+ children: /* @__PURE__ */ jsx8(
1038
+ AgentRail,
1039
+ {
1040
+ state: idle ? {
1041
+ ...state,
1042
+ followUps: createIdleSuggestions()
1043
+ } : state,
1044
+ mobileFullscreen: isMobile && !railCollapsed,
1045
+ expanded: railExpanded,
1046
+ onCollapse: !isMobile ? () => setRailCollapsed(true) : void 0,
1047
+ onClose: isMobile ? () => setRailCollapsed(true) : void 0,
1048
+ onExpandToggle: !isMobile ? () => setRailExpanded((current) => !current) : void 0,
1049
+ onSubmit: handleSubmit,
1050
+ onFollowUpSelect: (label) => void handleSubmit(label)
850
1051
  }
851
- streamed += delta;
852
- setState((prev) => ({ ...prev, phase: "streaming", streamingText: streamed }));
853
- },
854
- onComplete: () => {
855
- if (!isActiveRun()) return;
856
- streamStarted = true;
857
- }
1052
+ )
858
1053
  }
859
- });
860
- await planningPromise.catch(() => {
861
- });
862
- if (!isActiveRun()) return;
863
- const agentMessage = {
864
- id: `agent-${Date.now()}`,
865
- role: "agent",
866
- text: finalText,
867
- createdAt: Date.now()
868
- };
869
- setState((prev) => ({
870
- ...prev,
871
- phase: "complete",
872
- messages: [...prev.messages, agentMessage],
873
- toolSteps: [],
874
- streamingText: "",
875
- followUps: DEFAULT_FOLLOW_UPS,
876
- journey: null
877
- }));
878
- } catch (error) {
879
- if (error instanceof DOMException && error.name === "AbortError") return;
880
- if (!isActiveRun()) return;
881
- const message = formatAgentError(error);
882
- if (!message) return;
883
- setState((prev) => ({
884
- ...prev,
885
- phase: "complete",
886
- toolSteps: [],
887
- streamingText: "",
888
- error: message
889
- }));
1054
+ )
890
1055
  }
891
- },
892
- [customerId, indexId, runtimeOrigin, resolvedStorageKeyPrefix, visitorId]
893
- );
894
- useEffect2(() => {
895
- return () => {
896
- runRef.current?.abort();
897
- clientRef.current.cancelActive();
898
- };
899
- }, []);
900
- return {
901
- state,
902
- reset,
903
- submit,
904
- visitorSessionId: visitorId,
905
- sessionId: clientRef.current.getActiveSessionId()
906
- };
907
- }
908
- function hasVisitorMessages(messages) {
909
- return messages.some((message) => message.role === "visitor");
910
- }
911
- function createIdleSuggestions() {
912
- return [
913
- { id: "idle-1", label: "Hello \u2014 what can you do?" },
914
- { id: "idle-2", label: "Help me evaluate this product" },
915
- { id: "idle-3", label: "What should I ask you?" }
916
- ];
917
- }
918
- function isAgentBusy(phase) {
919
- return phase === "thinking" || phase === "running-tools" || phase === "streaming";
1056
+ ),
1057
+ !isMobile && railExpanded && !railCollapsed ? /* @__PURE__ */ jsx8(
1058
+ "button",
1059
+ {
1060
+ type: "button",
1061
+ className: "webless-agent-root__backdrop",
1062
+ "aria-label": "Close expanded assist",
1063
+ onClick: () => setRailExpanded(false)
1064
+ }
1065
+ ) : null,
1066
+ railCollapsed ? /* @__PURE__ */ jsx8(
1067
+ AssistEdgeTab,
1068
+ {
1069
+ variant: placement.variant,
1070
+ side: placement.side,
1071
+ along: placement.along,
1072
+ inset: placement.inset,
1073
+ visible: true,
1074
+ onOpen: () => setRailCollapsed(false)
1075
+ }
1076
+ ) : null
1077
+ ] });
920
1078
  }
921
1079
 
922
1080
  export {
923
- AgentRail,
924
- AssistEdgeTab,
925
1081
  DEFAULT_RUNTIME_ORIGIN,
926
1082
  resolveAgentRuntimeConfig,
927
1083
  useAgentChat,
928
1084
  hasVisitorMessages,
929
1085
  createIdleSuggestions,
930
- isAgentBusy
1086
+ isAgentBusy,
1087
+ DEFAULT_AGENT_PLACEMENT,
1088
+ normalizeAgentPlacement,
1089
+ openAgentPanel,
1090
+ closeAgentPanel,
1091
+ AgentRail,
1092
+ AssistEdgeTab,
1093
+ AgentWidget
931
1094
  };
932
- //# sourceMappingURL=chunk-WLQDMWO6.js.map
1095
+ //# sourceMappingURL=chunk-4QGS7UZY.js.map