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