@surf-kit/agent 0.2.1 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. package/LICENSE +184 -12
  2. package/README.md +1 -1
  3. package/dist/agent-identity/index.cjs +1 -0
  4. package/dist/agent-identity/index.cjs.map +1 -1
  5. package/dist/agent-identity/index.js +2 -0
  6. package/dist/agent-identity/index.js.map +1 -1
  7. package/dist/chat/index.cjs +626 -204
  8. package/dist/chat/index.cjs.map +1 -1
  9. package/dist/chat/index.d.cts +11 -6
  10. package/dist/chat/index.d.ts +11 -6
  11. package/dist/chat/index.js +608 -185
  12. package/dist/chat/index.js.map +1 -1
  13. package/dist/{chat--OifhIRe.d.ts → chat-BIIDOGrD.d.ts} +10 -1
  14. package/dist/{chat-ChYl2XjV.d.cts → chat-CGamM7Mz.d.cts} +10 -1
  15. package/dist/confidence/index.cjs +1 -0
  16. package/dist/confidence/index.cjs.map +1 -1
  17. package/dist/confidence/index.js +2 -0
  18. package/dist/confidence/index.js.map +1 -1
  19. package/dist/feedback/index.cjs +1 -0
  20. package/dist/feedback/index.cjs.map +1 -1
  21. package/dist/feedback/index.js +2 -0
  22. package/dist/feedback/index.js.map +1 -1
  23. package/dist/{hooks-DLfF18IU.d.cts → hooks-B1NYoLLs.d.cts} +21 -5
  24. package/dist/{hooks-BGs8-4GK.d.ts → hooks-CTeEqnBQ.d.ts} +21 -5
  25. package/dist/hooks.cjs +127 -81
  26. package/dist/hooks.cjs.map +1 -1
  27. package/dist/hooks.d.cts +3 -3
  28. package/dist/hooks.d.ts +3 -3
  29. package/dist/hooks.js +128 -81
  30. package/dist/hooks.js.map +1 -1
  31. package/dist/index.cjs +687 -265
  32. package/dist/index.cjs.map +1 -1
  33. package/dist/index.d.cts +3 -3
  34. package/dist/index.d.ts +3 -3
  35. package/dist/index.js +647 -224
  36. package/dist/index.js.map +1 -1
  37. package/dist/layouts/index.cjs +647 -225
  38. package/dist/layouts/index.cjs.map +1 -1
  39. package/dist/layouts/index.d.cts +1 -1
  40. package/dist/layouts/index.d.ts +1 -1
  41. package/dist/layouts/index.js +624 -201
  42. package/dist/layouts/index.js.map +1 -1
  43. package/dist/mcp/index.cjs +2 -1
  44. package/dist/mcp/index.cjs.map +1 -1
  45. package/dist/mcp/index.js +4 -2
  46. package/dist/mcp/index.js.map +1 -1
  47. package/dist/response/index.cjs +67 -12
  48. package/dist/response/index.cjs.map +1 -1
  49. package/dist/response/index.d.cts +2 -2
  50. package/dist/response/index.d.ts +2 -2
  51. package/dist/response/index.js +66 -10
  52. package/dist/response/index.js.map +1 -1
  53. package/dist/sources/index.cjs +31 -1
  54. package/dist/sources/index.cjs.map +1 -1
  55. package/dist/sources/index.js +32 -1
  56. package/dist/sources/index.js.map +1 -1
  57. package/dist/streaming/index.cjs +203 -93
  58. package/dist/streaming/index.cjs.map +1 -1
  59. package/dist/streaming/index.d.cts +4 -3
  60. package/dist/streaming/index.d.ts +4 -3
  61. package/dist/streaming/index.js +174 -73
  62. package/dist/streaming/index.js.map +1 -1
  63. package/dist/{streaming-DbQxScpi.d.ts → streaming-Bx-ff2tt.d.ts} +1 -1
  64. package/dist/{streaming-DfT22A0z.d.cts → streaming-x7umFHoP.d.cts} +1 -1
  65. package/package.json +17 -6
@@ -1,3 +1,4 @@
1
+ 'use client';
1
2
  "use strict";
2
3
  var __create = Object.create;
3
4
  var __defProp = Object.defineProperty;
@@ -38,11 +39,11 @@ __export(layouts_exports, {
38
39
  module.exports = __toCommonJS(layouts_exports);
39
40
 
40
41
  // src/layouts/AgentFullPage/AgentFullPage.tsx
41
- var import_tailwind_merge10 = require("tailwind-merge");
42
- var import_react7 = require("react");
42
+ var import_tailwind_merge11 = require("tailwind-merge");
43
+ var import_react8 = require("react");
43
44
 
44
45
  // src/chat/AgentChat/AgentChat.tsx
45
- var import_tailwind_merge8 = require("tailwind-merge");
46
+ var import_tailwind_merge9 = require("tailwind-merge");
46
47
 
47
48
  // src/hooks/useAgentChat.ts
48
49
  var import_react = require("react");
@@ -53,7 +54,8 @@ var initialState = {
53
54
  error: null,
54
55
  inputValue: "",
55
56
  streamPhase: "idle",
56
- streamingContent: ""
57
+ streamingContent: "",
58
+ streamingAgent: null
57
59
  };
58
60
  function reducer(state, action) {
59
61
  switch (action.type) {
@@ -67,12 +69,15 @@ function reducer(state, action) {
67
69
  error: null,
68
70
  inputValue: "",
69
71
  streamPhase: "thinking",
70
- streamingContent: ""
72
+ streamingContent: "",
73
+ streamingAgent: null
71
74
  };
72
75
  case "STREAM_PHASE":
73
76
  return { ...state, streamPhase: action.phase };
74
77
  case "STREAM_CONTENT":
75
78
  return { ...state, streamingContent: state.streamingContent + action.content };
79
+ case "STREAM_AGENT":
80
+ return { ...state, streamingAgent: action.agent };
76
81
  case "SEND_SUCCESS":
77
82
  return {
78
83
  ...state,
@@ -88,7 +93,8 @@ function reducer(state, action) {
88
93
  isLoading: false,
89
94
  error: action.error,
90
95
  streamPhase: "idle",
91
- streamingContent: ""
96
+ streamingContent: "",
97
+ streamingAgent: null
92
98
  };
93
99
  case "LOAD_CONVERSATION":
94
100
  return {
@@ -114,107 +120,142 @@ function useAgentChat(config) {
114
120
  const configRef = (0, import_react.useRef)(config);
115
121
  configRef.current = config;
116
122
  const lastUserMessageRef = (0, import_react.useRef)(null);
123
+ const lastUserAttachmentsRef = (0, import_react.useRef)(void 0);
117
124
  const sendMessage = (0, import_react.useCallback)(
118
- async (content) => {
119
- const { apiUrl, streamPath = "/chat/stream", headers = {}, timeout = 3e4 } = configRef.current;
125
+ async (content, attachments) => {
126
+ const { apiUrl, streamPath = "/chat/stream", headers: headersOrFn, timeout = 3e4, bodyExtra } = configRef.current;
127
+ const headers = typeof headersOrFn === "function" ? await headersOrFn() : headersOrFn ?? {};
120
128
  lastUserMessageRef.current = content;
129
+ lastUserAttachmentsRef.current = attachments;
121
130
  const userMessage = {
122
131
  id: generateMessageId(),
123
132
  role: "user",
124
133
  content,
134
+ attachments,
125
135
  timestamp: /* @__PURE__ */ new Date()
126
136
  };
127
137
  dispatch({ type: "SEND_START", message: userMessage });
128
138
  const controller = new AbortController();
129
139
  const timeoutId = setTimeout(() => controller.abort(), timeout);
130
140
  try {
131
- const response = await fetch(`${apiUrl}${streamPath}`, {
132
- method: "POST",
133
- headers: {
134
- "Content-Type": "application/json",
135
- Accept: "text/event-stream",
136
- ...headers
137
- },
138
- body: JSON.stringify({
139
- message: content,
140
- conversation_id: state.conversationId
141
- }),
142
- signal: controller.signal
143
- });
144
- clearTimeout(timeoutId);
145
- if (!response.ok) {
146
- dispatch({
147
- type: "SEND_ERROR",
148
- error: {
149
- code: "API_ERROR",
150
- message: `HTTP ${response.status}: ${response.statusText}`,
151
- retryable: response.status >= 500
152
- }
153
- });
154
- return;
141
+ const url = `${apiUrl}${streamPath}`;
142
+ const mergedHeaders = {
143
+ "Content-Type": "application/json",
144
+ Accept: "text/event-stream",
145
+ ...headers
146
+ };
147
+ const requestBody = {
148
+ message: content,
149
+ conversation_id: state.conversationId,
150
+ ...bodyExtra
151
+ };
152
+ if (attachments && attachments.length > 0) {
153
+ requestBody.attachments = attachments.map((a) => ({
154
+ filename: a.filename,
155
+ content_type: a.content_type,
156
+ data: a.data
157
+ }));
155
158
  }
156
- const reader = response.body?.getReader();
157
- if (!reader) {
158
- dispatch({
159
- type: "SEND_ERROR",
160
- error: { code: "STREAM_ERROR", message: "No response body", retryable: true }
159
+ const body = JSON.stringify(requestBody);
160
+ const ctx = {
161
+ accumulatedContent: "",
162
+ agentResponse: null,
163
+ capturedAgent: null,
164
+ capturedConversationId: null,
165
+ hadStreamError: false
166
+ };
167
+ const handleEvent = (event) => {
168
+ switch (event.type) {
169
+ case "agent":
170
+ ctx.capturedAgent = event.agent;
171
+ dispatch({ type: "STREAM_AGENT", agent: ctx.capturedAgent });
172
+ break;
173
+ case "phase":
174
+ dispatch({ type: "STREAM_PHASE", phase: event.phase });
175
+ break;
176
+ case "delta":
177
+ ctx.accumulatedContent += event.content;
178
+ dispatch({ type: "STREAM_CONTENT", content: event.content });
179
+ break;
180
+ case "done":
181
+ ctx.agentResponse = event.response;
182
+ ctx.capturedConversationId = event.conversation_id ?? null;
183
+ break;
184
+ case "error":
185
+ ctx.hadStreamError = true;
186
+ dispatch({ type: "SEND_ERROR", error: event.error });
187
+ break;
188
+ }
189
+ };
190
+ const { streamAdapter } = configRef.current;
191
+ if (streamAdapter) {
192
+ await streamAdapter(
193
+ url,
194
+ { method: "POST", headers: mergedHeaders, body, signal: controller.signal },
195
+ handleEvent
196
+ );
197
+ clearTimeout(timeoutId);
198
+ } else {
199
+ const response = await fetch(url, {
200
+ method: "POST",
201
+ headers: mergedHeaders,
202
+ body,
203
+ signal: controller.signal
161
204
  });
162
- return;
163
- }
164
- const decoder = new TextDecoder();
165
- let buffer = "";
166
- let accumulatedContent = "";
167
- let agentResponse = null;
168
- let capturedAgent = null;
169
- let capturedConversationId = null;
170
- while (true) {
171
- const { done, value } = await reader.read();
172
- if (done) break;
173
- buffer += decoder.decode(value, { stream: true });
174
- const lines = buffer.split("\n");
175
- buffer = lines.pop() ?? "";
176
- for (const line of lines) {
177
- if (!line.startsWith("data: ")) continue;
178
- const data = line.slice(6).trim();
179
- if (data === "[DONE]") continue;
180
- try {
181
- const event = JSON.parse(data);
182
- switch (event.type) {
183
- case "agent":
184
- capturedAgent = event.agent;
185
- break;
186
- case "phase":
187
- dispatch({ type: "STREAM_PHASE", phase: event.phase });
188
- break;
189
- case "delta":
190
- accumulatedContent += event.content;
191
- dispatch({ type: "STREAM_CONTENT", content: event.content });
192
- break;
193
- case "done":
194
- agentResponse = event.response;
195
- capturedConversationId = event.conversation_id ?? null;
196
- break;
197
- case "error":
198
- dispatch({ type: "SEND_ERROR", error: event.error });
199
- return;
205
+ clearTimeout(timeoutId);
206
+ if (!response.ok) {
207
+ dispatch({
208
+ type: "SEND_ERROR",
209
+ error: {
210
+ code: "API_ERROR",
211
+ message: `HTTP ${response.status}: ${response.statusText}`,
212
+ retryable: response.status >= 500
213
+ }
214
+ });
215
+ return;
216
+ }
217
+ const reader = response.body?.getReader();
218
+ if (!reader) {
219
+ dispatch({
220
+ type: "SEND_ERROR",
221
+ error: { code: "STREAM_ERROR", message: "No response body", retryable: true }
222
+ });
223
+ return;
224
+ }
225
+ const decoder = new TextDecoder();
226
+ let buffer = "";
227
+ while (true) {
228
+ const { done, value } = await reader.read();
229
+ if (done) break;
230
+ buffer += decoder.decode(value, { stream: true });
231
+ const lines = buffer.split("\n");
232
+ buffer = lines.pop() ?? "";
233
+ for (const line of lines) {
234
+ if (!line.startsWith("data: ")) continue;
235
+ const data = line.slice(6).trim();
236
+ if (data === "[DONE]") continue;
237
+ try {
238
+ const event = JSON.parse(data);
239
+ handleEvent(event);
240
+ } catch {
200
241
  }
201
- } catch {
202
242
  }
203
243
  }
204
244
  }
245
+ if (ctx.hadStreamError) return;
205
246
  const assistantMessage = {
206
247
  id: generateMessageId(),
207
248
  role: "assistant",
208
- content: agentResponse?.message ?? accumulatedContent,
209
- response: agentResponse ?? void 0,
210
- agent: capturedAgent ?? void 0,
249
+ content: ctx.agentResponse?.message ?? ctx.accumulatedContent,
250
+ response: ctx.agentResponse ?? void 0,
251
+ agent: ctx.capturedAgent ?? void 0,
211
252
  timestamp: /* @__PURE__ */ new Date()
212
253
  };
213
254
  dispatch({
214
255
  type: "SEND_SUCCESS",
215
256
  message: assistantMessage,
216
- streamingContent: accumulatedContent,
217
- conversationId: capturedConversationId
257
+ streamingContent: ctx.accumulatedContent,
258
+ conversationId: ctx.capturedConversationId
218
259
  });
219
260
  } catch (err) {
220
261
  clearTimeout(timeoutId);
@@ -245,7 +286,8 @@ function useAgentChat(config) {
245
286
  }, []);
246
287
  const submitFeedback = (0, import_react.useCallback)(
247
288
  async (messageId, rating, comment) => {
248
- const { apiUrl, feedbackPath = "/feedback", headers = {} } = configRef.current;
289
+ const { apiUrl, feedbackPath = "/feedback", headers: headersOrFn } = configRef.current;
290
+ const headers = typeof headersOrFn === "function" ? await headersOrFn() : headersOrFn ?? {};
249
291
  await fetch(`${apiUrl}${feedbackPath}`, {
250
292
  method: "POST",
251
293
  headers: { "Content-Type": "application/json", ...headers },
@@ -256,12 +298,13 @@ function useAgentChat(config) {
256
298
  );
257
299
  const retry = (0, import_react.useCallback)(async () => {
258
300
  if (lastUserMessageRef.current) {
259
- await sendMessage(lastUserMessageRef.current);
301
+ await sendMessage(lastUserMessageRef.current, lastUserAttachmentsRef.current);
260
302
  }
261
303
  }, [sendMessage]);
262
304
  const reset = (0, import_react.useCallback)(() => {
263
305
  dispatch({ type: "RESET" });
264
306
  lastUserMessageRef.current = null;
307
+ lastUserAttachmentsRef.current = void 0;
265
308
  }, []);
266
309
  const actions = {
267
310
  sendMessage,
@@ -276,7 +319,7 @@ function useAgentChat(config) {
276
319
 
277
320
  // src/chat/MessageThread/MessageThread.tsx
278
321
  var import_tailwind_merge5 = require("tailwind-merge");
279
- var import_react3 = require("react");
322
+ var import_react4 = require("react");
280
323
 
281
324
  // src/chat/MessageBubble/MessageBubble.tsx
282
325
  var import_tailwind_merge4 = require("tailwind-merge");
@@ -285,6 +328,7 @@ var import_tailwind_merge4 = require("tailwind-merge");
285
328
  var import_core2 = require("@surf-kit/core");
286
329
 
287
330
  // src/response/ResponseMessage/ResponseMessage.tsx
331
+ var import_react2 = __toESM(require("react"), 1);
288
332
  var import_react_markdown = __toESM(require("react-markdown"), 1);
289
333
  var import_rehype_sanitize = __toESM(require("rehype-sanitize"), 1);
290
334
  var import_tailwind_merge = require("tailwind-merge");
@@ -309,6 +353,7 @@ function ResponseMessage({ content, className }) {
309
353
  "[&_h3]:text-sm [&_h3]:font-semibold [&_h3]:text-accent [&_h3]:mt-2 [&_h3]:mb-1",
310
354
  "[&_code]:bg-surface-raised [&_code]:text-accent [&_code]:px-1.5 [&_code]:py-0.5 [&_code]:rounded [&_code]:text-xs [&_code]:font-mono",
311
355
  "[&_pre]:bg-surface-raised [&_pre]:border [&_pre]:border-border [&_pre]:rounded-xl [&_pre]:p-4 [&_pre]:overflow-x-auto",
356
+ "[&_hr]:my-3 [&_hr]:border-border",
312
357
  "[&_blockquote]:border-l-2 [&_blockquote]:border-border-strong [&_blockquote]:pl-4 [&_blockquote]:text-text-secondary",
313
358
  "[&_a]:text-accent [&_a]:underline-offset-2 [&_a]:hover:text-accent/80",
314
359
  className
@@ -324,11 +369,24 @@ function ResponseMessage({ content, className }) {
324
369
  p: ({ children }) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "my-2", children }),
325
370
  ul: ({ children }) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("ul", { className: "my-2 list-disc pl-6", children }),
326
371
  ol: ({ children }) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("ol", { className: "my-2 list-decimal pl-6", children }),
327
- li: ({ children }) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("li", { className: "my-1", children }),
372
+ li: ({ children, ...props }) => {
373
+ let content2 = children;
374
+ if (props.ordered) {
375
+ content2 = import_react2.default.Children.map(children, (child, i) => {
376
+ if (i === 0 && typeof child === "string") {
377
+ return child.replace(/^\d+[.)]\s*/, "");
378
+ }
379
+ return child;
380
+ });
381
+ }
382
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("li", { className: "my-1", children: content2 });
383
+ },
328
384
  strong: ({ children }) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("strong", { className: "font-semibold", children }),
385
+ em: ({ children }) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("em", { className: "italic text-text-secondary", children }),
329
386
  h1: ({ children }) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h1", { className: "text-base font-bold mt-4 mb-2", children }),
330
387
  h2: ({ children }) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h2", { className: "text-sm font-bold mt-3 mb-1", children }),
331
388
  h3: ({ children }) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h3", { className: "text-sm font-semibold mt-2 mb-1", children }),
389
+ hr: () => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("hr", { className: "my-3 border-border" }),
332
390
  code: ({ children }) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("code", { className: "bg-surface-sunken rounded px-1 py-0.5 text-xs font-mono", children })
333
391
  },
334
392
  children: normalizeMarkdownLists(content)
@@ -464,7 +522,14 @@ function renderWarning(data) {
464
522
  }
465
523
  );
466
524
  }
467
- function StructuredResponse({ uiHint, data, className }) {
525
+ function StructuredResponse({ uiHint, data: rawData, className }) {
526
+ const data = typeof rawData === "string" ? (() => {
527
+ try {
528
+ return JSON.parse(rawData);
529
+ } catch {
530
+ return null;
531
+ }
532
+ })() : rawData;
468
533
  if (!data) return null;
469
534
  let content;
470
535
  switch (uiHint) {
@@ -494,7 +559,7 @@ function StructuredResponse({ uiHint, data, className }) {
494
559
  }
495
560
 
496
561
  // src/sources/SourceList/SourceList.tsx
497
- var import_react2 = require("react");
562
+ var import_react3 = require("react");
498
563
 
499
564
  // src/sources/SourceCard/SourceCard.tsx
500
565
  var import_core = require("@surf-kit/core");
@@ -544,7 +609,36 @@ function SourceCard({ source, variant = "compact", onNavigate, className }) {
544
609
  children: [
545
610
  /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex items-start justify-between gap-2", children: [
546
611
  /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex-1 min-w-0", children: [
547
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { className: "text-sm font-medium text-text-primary truncate", children: source.title }),
612
+ source.url ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
613
+ "a",
614
+ {
615
+ href: source.url,
616
+ target: "_blank",
617
+ rel: "noopener noreferrer",
618
+ className: "text-sm font-medium text-accent hover:underline truncate block",
619
+ onClick: (e) => e.stopPropagation(),
620
+ children: [
621
+ source.title,
622
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
623
+ "svg",
624
+ {
625
+ className: "inline-block ml-1 w-3 h-3 opacity-60",
626
+ viewBox: "0 0 24 24",
627
+ fill: "none",
628
+ stroke: "currentColor",
629
+ strokeWidth: "2",
630
+ strokeLinecap: "round",
631
+ strokeLinejoin: "round",
632
+ children: [
633
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "M18 13v6a2 2 0 01-2 2H5a2 2 0 01-2-2V8a2 2 0 012-2h6" }),
634
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("polyline", { points: "15 3 21 3 21 9" }),
635
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("line", { x1: "10", y1: "14", x2: "21", y2: "3" })
636
+ ]
637
+ }
638
+ )
639
+ ]
640
+ }
641
+ ) : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { className: "text-sm font-medium text-text-primary truncate", children: source.title }),
548
642
  source.section && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { className: "text-[11px] font-semibold uppercase tracking-wider text-text-secondary truncate mt-0.5", children: source.section })
549
643
  ] }),
550
644
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
@@ -574,7 +668,7 @@ function SourceList({
574
668
  onNavigate,
575
669
  className
576
670
  }) {
577
- const [isExpanded, setIsExpanded] = (0, import_react2.useState)(defaultExpanded);
671
+ const [isExpanded, setIsExpanded] = (0, import_react3.useState)(defaultExpanded);
578
672
  if (sources.length === 0) return null;
579
673
  const content = /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "flex flex-col gap-1.5", "data-testid": "source-list-items", children: sources.map((source) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
580
674
  SourceCard,
@@ -678,13 +772,16 @@ function AgentResponse({
678
772
  }) {
679
773
  return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: `flex flex-col gap-4 ${className ?? ""}`, "data-testid": "agent-response", children: [
680
774
  /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(ResponseMessage, { content: response.message }),
681
- response.ui_hint !== "text" && response.structured_data && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
682
- StructuredResponse,
683
- {
684
- uiHint: response.ui_hint,
685
- data: response.structured_data
686
- }
687
- ),
775
+ response.ui_hint !== "text" && response.structured_data && (() => {
776
+ const parsed = typeof response.structured_data === "string" ? (() => {
777
+ try {
778
+ return JSON.parse(response.structured_data);
779
+ } catch {
780
+ return null;
781
+ }
782
+ })() : response.structured_data;
783
+ return parsed ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(StructuredResponse, { uiHint: response.ui_hint, data: parsed }) : null;
784
+ })(),
688
785
  (showConfidence || showVerification) && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "flex flex-wrap items-center gap-2 mt-1", "data-testid": "response-meta", children: [
689
786
  showConfidence && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
690
787
  import_core2.Badge,
@@ -735,6 +832,31 @@ function AgentResponse({
735
832
 
736
833
  // src/chat/MessageBubble/MessageBubble.tsx
737
834
  var import_jsx_runtime7 = require("react/jsx-runtime");
835
+ function DocumentIcon() {
836
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round", children: [
837
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("path", { d: "M14 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V8z" }),
838
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("polyline", { points: "14 2 14 8 20 8" }),
839
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("line", { x1: "16", y1: "13", x2: "8", y2: "13" }),
840
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("line", { x1: "16", y1: "17", x2: "8", y2: "17" })
841
+ ] });
842
+ }
843
+ function AttachmentThumbnail({ attachment }) {
844
+ const isImage = attachment.content_type.startsWith("image/");
845
+ if (isImage) {
846
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "rounded-lg overflow-hidden border border-black/10 max-w-[240px]", children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
847
+ "img",
848
+ {
849
+ src: attachment.preview_url ?? `data:${attachment.content_type};base64,${attachment.data}`,
850
+ alt: attachment.filename,
851
+ className: "max-w-full max-h-[200px] object-contain"
852
+ }
853
+ ) });
854
+ }
855
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "flex items-center gap-2 px-3 py-2 rounded-lg border border-black/10 bg-black/5", children: [
856
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(DocumentIcon, {}),
857
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "text-xs truncate max-w-[160px]", children: attachment.filename })
858
+ ] });
859
+ }
738
860
  function MessageBubble({
739
861
  message,
740
862
  showAgent,
@@ -742,23 +864,29 @@ function MessageBubble({
742
864
  showConfidence = true,
743
865
  showVerification = true,
744
866
  animated = true,
867
+ userBubbleClassName,
745
868
  className
746
869
  }) {
747
870
  const isUser = message.role === "user";
871
+ const hasAttachments = message.attachments && message.attachments.length > 0;
748
872
  if (isUser) {
749
873
  return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
750
874
  "div",
751
875
  {
752
876
  "data-message-id": message.id,
753
877
  className: (0, import_tailwind_merge4.twMerge)("flex w-full justify-end", className),
754
- children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
878
+ children: /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
755
879
  "div",
756
880
  {
757
881
  className: (0, import_tailwind_merge4.twMerge)(
758
- "max-w-[70%] rounded-[18px] rounded-br-[4px] px-4 py-2.5 bg-accent text-brand-cream break-words whitespace-pre-wrap text-sm leading-relaxed",
759
- animated && "motion-safe:animate-slideFromRight"
882
+ "max-w-[70%] rounded-[18px] rounded-br-[4px] px-4 py-2.5 bg-[#e8e8e8] text-[#1a1a1a] break-words whitespace-pre-wrap text-sm leading-relaxed",
883
+ animated && "motion-safe:animate-slideFromRight",
884
+ userBubbleClassName
760
885
  ),
761
- children: message.content
886
+ children: [
887
+ hasAttachments && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "flex flex-wrap gap-2 mb-2", children: message.attachments.map((att, i) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(AttachmentThumbnail, { attachment: att }, `${att.filename}-${i}`)) }),
888
+ message.content
889
+ ]
762
890
  }
763
891
  )
764
892
  }
@@ -770,7 +898,7 @@ function MessageBubble({
770
898
  "data-message-id": message.id,
771
899
  className: (0, import_tailwind_merge4.twMerge)("flex w-full flex-col items-start gap-1.5", className),
772
900
  children: [
773
- showAgent && message.agent && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "text-[11px] font-semibold uppercase tracking-[0.08em] text-text-muted px-1", children: message.agent.replace("_agent", "").replace("_", " ") }),
901
+ showAgent && message.agent && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "text-[11px] font-display font-semibold uppercase tracking-[0.08em] text-text-muted px-1", children: message.agent.replace("_agent", "").replace("_", " ") }),
774
902
  /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
775
903
  "div",
776
904
  {
@@ -796,34 +924,70 @@ function MessageBubble({
796
924
 
797
925
  // src/chat/MessageThread/MessageThread.tsx
798
926
  var import_jsx_runtime8 = require("react/jsx-runtime");
799
- function MessageThread({ messages, streamingSlot, showSources, showConfidence, showVerification, className }) {
800
- const bottomRef = (0, import_react3.useRef)(null);
801
- (0, import_react3.useEffect)(() => {
802
- bottomRef.current?.scrollIntoView?.({ behavior: "smooth" });
803
- }, [messages.length, streamingSlot]);
927
+ function MessageThread({ messages, streamingSlot, showAgent, showSources, showConfidence, showVerification, hideLastAssistant, userBubbleClassName, className }) {
928
+ const scrollRef = (0, import_react4.useRef)(null);
929
+ const isNearBottom = (0, import_react4.useRef)(true);
930
+ const isProgrammaticScroll = (0, import_react4.useRef)(false);
931
+ const hasStreaming = !!streamingSlot;
932
+ const scrollToBottom = (0, import_react4.useCallback)(() => {
933
+ const el = scrollRef.current;
934
+ if (el && isNearBottom.current) {
935
+ isProgrammaticScroll.current = true;
936
+ el.scrollTop = el.scrollHeight;
937
+ }
938
+ }, []);
939
+ const handleScroll = (0, import_react4.useCallback)(() => {
940
+ if (isProgrammaticScroll.current) {
941
+ isProgrammaticScroll.current = false;
942
+ return;
943
+ }
944
+ const el = scrollRef.current;
945
+ if (!el) return;
946
+ isNearBottom.current = el.scrollHeight - el.scrollTop - el.clientHeight < 80;
947
+ }, []);
948
+ (0, import_react4.useEffect)(scrollToBottom, [messages.length, scrollToBottom]);
949
+ (0, import_react4.useEffect)(() => {
950
+ if (!hasStreaming) return;
951
+ let raf;
952
+ const tick = () => {
953
+ scrollToBottom();
954
+ raf = requestAnimationFrame(tick);
955
+ };
956
+ raf = requestAnimationFrame(tick);
957
+ return () => cancelAnimationFrame(raf);
958
+ }, [hasStreaming, scrollToBottom]);
804
959
  return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
805
960
  "div",
806
961
  {
962
+ ref: scrollRef,
807
963
  role: "log",
808
964
  "aria-live": "polite",
809
965
  "aria-label": "Message thread",
966
+ onScroll: handleScroll,
810
967
  className: (0, import_tailwind_merge5.twMerge)(
811
968
  "flex flex-col gap-4 overflow-y-auto flex-1 px-4 py-6",
812
969
  className
813
970
  ),
814
971
  children: [
815
- messages.map((message) => /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
816
- MessageBubble,
817
- {
818
- message,
819
- showSources,
820
- showConfidence,
821
- showVerification
822
- },
823
- message.id
824
- )),
825
- streamingSlot,
826
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { ref: bottomRef })
972
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "flex-1 shrink-0" }),
973
+ messages.map((message, i) => {
974
+ if (hideLastAssistant && i === messages.length - 1 && message.role === "assistant") {
975
+ return null;
976
+ }
977
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
978
+ MessageBubble,
979
+ {
980
+ message,
981
+ showAgent,
982
+ showSources,
983
+ showConfidence,
984
+ showVerification,
985
+ userBubbleClassName
986
+ },
987
+ message.id
988
+ );
989
+ }),
990
+ streamingSlot
827
991
  ]
828
992
  }
829
993
  );
@@ -831,32 +995,126 @@ function MessageThread({ messages, streamingSlot, showSources, showConfidence, s
831
995
 
832
996
  // src/chat/MessageComposer/MessageComposer.tsx
833
997
  var import_tailwind_merge6 = require("tailwind-merge");
834
- var import_react4 = require("react");
998
+ var import_react5 = require("react");
835
999
  var import_jsx_runtime9 = require("react/jsx-runtime");
1000
+ var ALLOWED_TYPES = /* @__PURE__ */ new Set([
1001
+ "image/png",
1002
+ "image/jpeg",
1003
+ "image/gif",
1004
+ "image/webp",
1005
+ "application/pdf"
1006
+ ]);
1007
+ var MAX_FILE_SIZE = 10 * 1024 * 1024;
1008
+ var MAX_ATTACHMENTS = 5;
1009
+ function ArrowUpIcon() {
1010
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("svg", { width: "20", height: "20", viewBox: "0 0 20 20", fill: "none", stroke: "currentColor", strokeWidth: "2.5", strokeLinecap: "round", strokeLinejoin: "round", children: [
1011
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("path", { d: "M10 16V4" }),
1012
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("path", { d: "M4 10l6-6 6 6" })
1013
+ ] });
1014
+ }
1015
+ function StopIcon() {
1016
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "currentColor", children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("rect", { x: "3", y: "3", width: "10", height: "10", rx: "2" }) });
1017
+ }
1018
+ function PaperclipIcon() {
1019
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("svg", { width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("path", { d: "M21.44 11.05l-9.19 9.19a6 6 0 01-8.49-8.49l9.19-9.19a4 4 0 015.66 5.66l-9.2 9.19a2 2 0 01-2.83-2.83l8.49-8.48" }) });
1020
+ }
1021
+ function XIcon({ size = 14 }) {
1022
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("svg", { width: size, height: size, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.5", strokeLinecap: "round", strokeLinejoin: "round", children: [
1023
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("path", { d: "M18 6L6 18" }),
1024
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("path", { d: "M6 6l12 12" })
1025
+ ] });
1026
+ }
1027
+ function DocumentIcon2() {
1028
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round", children: [
1029
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("path", { d: "M14 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V8z" }),
1030
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("polyline", { points: "14 2 14 8 20 8" }),
1031
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("line", { x1: "16", y1: "13", x2: "8", y2: "13" }),
1032
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("line", { x1: "16", y1: "17", x2: "8", y2: "17" }),
1033
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("polyline", { points: "10 9 9 9 8 9" })
1034
+ ] });
1035
+ }
1036
+ function fileToBase64(file) {
1037
+ return new Promise((resolve, reject) => {
1038
+ const reader = new FileReader();
1039
+ reader.onload = () => {
1040
+ const result = reader.result;
1041
+ const base64 = result.split(",")[1];
1042
+ resolve(base64);
1043
+ };
1044
+ reader.onerror = reject;
1045
+ reader.readAsDataURL(file);
1046
+ });
1047
+ }
1048
+ function AttachmentPreview({
1049
+ attachment,
1050
+ onRemove
1051
+ }) {
1052
+ const isImage = attachment.content_type.startsWith("image/");
1053
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "relative group flex-shrink-0", children: [
1054
+ isImage ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "w-16 h-16 rounded-lg overflow-hidden border border-border/60 bg-surface-alt", children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1055
+ "img",
1056
+ {
1057
+ src: attachment.preview_url ?? `data:${attachment.content_type};base64,${attachment.data}`,
1058
+ alt: attachment.filename,
1059
+ className: "w-full h-full object-cover"
1060
+ }
1061
+ ) }) : /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "h-16 px-3 rounded-lg border border-border/60 bg-surface-alt flex items-center gap-2", children: [
1062
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "text-text-muted", children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(DocumentIcon2, {}) }),
1063
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "flex flex-col min-w-0", children: [
1064
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "text-xs text-text-primary truncate max-w-[120px]", children: attachment.filename }),
1065
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "text-[10px] text-text-muted", children: "PDF" })
1066
+ ] })
1067
+ ] }),
1068
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1069
+ "button",
1070
+ {
1071
+ type: "button",
1072
+ onClick: onRemove,
1073
+ className: (0, import_tailwind_merge6.twMerge)(
1074
+ "absolute -top-1.5 -right-1.5",
1075
+ "w-5 h-5 rounded-full",
1076
+ "bg-text-muted/80 text-white",
1077
+ "flex items-center justify-center",
1078
+ "opacity-0 group-hover:opacity-100",
1079
+ "transition-opacity duration-150",
1080
+ "hover:bg-text-primary"
1081
+ ),
1082
+ "aria-label": `Remove ${attachment.filename}`,
1083
+ children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(XIcon, { size: 10 })
1084
+ }
1085
+ )
1086
+ ] });
1087
+ }
836
1088
  function MessageComposer({
837
1089
  onSend,
838
1090
  isLoading = false,
839
1091
  placeholder = "Type a message...",
840
1092
  className
841
1093
  }) {
842
- const [value, setValue] = (0, import_react4.useState)("");
843
- const textareaRef = (0, import_react4.useRef)(null);
844
- const canSend = value.trim().length > 0 && !isLoading;
845
- const resetHeight = (0, import_react4.useCallback)(() => {
1094
+ const [value, setValue] = (0, import_react5.useState)("");
1095
+ const [attachments, setAttachments] = (0, import_react5.useState)([]);
1096
+ const [dragOver, setDragOver] = (0, import_react5.useState)(false);
1097
+ const textareaRef = (0, import_react5.useRef)(null);
1098
+ const fileInputRef = (0, import_react5.useRef)(null);
1099
+ const canSend = (value.trim().length > 0 || attachments.length > 0) && !isLoading;
1100
+ const resetHeight = (0, import_react5.useCallback)(() => {
846
1101
  const el = textareaRef.current;
847
1102
  if (el) {
848
1103
  el.style.height = "auto";
849
1104
  el.style.overflowY = "hidden";
850
1105
  }
851
1106
  }, []);
852
- const handleSend = (0, import_react4.useCallback)(() => {
1107
+ const handleSend = (0, import_react5.useCallback)(() => {
853
1108
  if (!canSend) return;
854
- onSend(value.trim());
1109
+ const message = value.trim() || (attachments.length > 0 ? "Please analyse the attached file(s)." : "");
1110
+ if (!message && attachments.length === 0) return;
1111
+ onSend(message, attachments.length > 0 ? attachments : void 0);
855
1112
  setValue("");
1113
+ setAttachments([]);
856
1114
  resetHeight();
857
1115
  textareaRef.current?.focus();
858
- }, [canSend, onSend, value, resetHeight]);
859
- const handleKeyDown = (0, import_react4.useCallback)(
1116
+ }, [canSend, onSend, value, attachments, resetHeight]);
1117
+ const handleKeyDown = (0, import_react5.useCallback)(
860
1118
  (e) => {
861
1119
  if (e.key === "Enter" && !e.shiftKey) {
862
1120
  e.preventDefault();
@@ -865,64 +1123,194 @@ function MessageComposer({
865
1123
  },
866
1124
  [handleSend]
867
1125
  );
868
- const handleChange = (0, import_react4.useCallback)(
1126
+ const handleChange = (0, import_react5.useCallback)(
869
1127
  (e) => {
870
1128
  setValue(e.target.value);
871
1129
  const el = e.target;
872
1130
  el.style.height = "auto";
873
- const capped = Math.min(el.scrollHeight, 128);
1131
+ const capped = Math.min(el.scrollHeight, 200);
874
1132
  el.style.height = `${capped}px`;
875
- el.style.overflowY = el.scrollHeight > 128 ? "auto" : "hidden";
1133
+ el.style.overflowY = el.scrollHeight > 200 ? "auto" : "hidden";
876
1134
  },
877
1135
  []
878
1136
  );
1137
+ const addFiles = (0, import_react5.useCallback)(async (files) => {
1138
+ const fileArray = Array.from(files);
1139
+ for (const file of fileArray) {
1140
+ if (attachments.length >= MAX_ATTACHMENTS) break;
1141
+ if (!ALLOWED_TYPES.has(file.type)) continue;
1142
+ if (file.size > MAX_FILE_SIZE) continue;
1143
+ try {
1144
+ const data = await fileToBase64(file);
1145
+ const previewUrl = file.type.startsWith("image/") ? URL.createObjectURL(file) : void 0;
1146
+ const attachment = {
1147
+ filename: file.name,
1148
+ content_type: file.type,
1149
+ data,
1150
+ preview_url: previewUrl
1151
+ };
1152
+ setAttachments((prev) => {
1153
+ if (prev.length >= MAX_ATTACHMENTS) return prev;
1154
+ return [...prev, attachment];
1155
+ });
1156
+ } catch {
1157
+ }
1158
+ }
1159
+ }, [attachments.length]);
1160
+ const handleFileSelect = (0, import_react5.useCallback)(() => {
1161
+ fileInputRef.current?.click();
1162
+ }, []);
1163
+ const handleFileInputChange = (0, import_react5.useCallback)(
1164
+ (e) => {
1165
+ if (e.target.files) {
1166
+ void addFiles(e.target.files);
1167
+ e.target.value = "";
1168
+ }
1169
+ },
1170
+ [addFiles]
1171
+ );
1172
+ const removeAttachment = (0, import_react5.useCallback)((index) => {
1173
+ setAttachments((prev) => {
1174
+ const removed = prev[index];
1175
+ if (removed?.preview_url) URL.revokeObjectURL(removed.preview_url);
1176
+ return prev.filter((_, i) => i !== index);
1177
+ });
1178
+ }, []);
1179
+ const handlePaste = (0, import_react5.useCallback)(
1180
+ (e) => {
1181
+ const items = e.clipboardData.items;
1182
+ const files = [];
1183
+ for (const item of items) {
1184
+ if (item.kind === "file" && ALLOWED_TYPES.has(item.type)) {
1185
+ const file = item.getAsFile();
1186
+ if (file) files.push(file);
1187
+ }
1188
+ }
1189
+ if (files.length > 0) {
1190
+ void addFiles(files);
1191
+ }
1192
+ },
1193
+ [addFiles]
1194
+ );
1195
+ const handleDragOver = (0, import_react5.useCallback)((e) => {
1196
+ e.preventDefault();
1197
+ e.stopPropagation();
1198
+ setDragOver(true);
1199
+ }, []);
1200
+ const handleDragLeave = (0, import_react5.useCallback)((e) => {
1201
+ e.preventDefault();
1202
+ e.stopPropagation();
1203
+ setDragOver(false);
1204
+ }, []);
1205
+ const handleDrop = (0, import_react5.useCallback)(
1206
+ (e) => {
1207
+ e.preventDefault();
1208
+ e.stopPropagation();
1209
+ setDragOver(false);
1210
+ if (e.dataTransfer.files.length > 0) {
1211
+ void addFiles(e.dataTransfer.files);
1212
+ }
1213
+ },
1214
+ [addFiles]
1215
+ );
879
1216
  return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
880
1217
  "div",
881
1218
  {
882
1219
  className: (0, import_tailwind_merge6.twMerge)(
883
- "flex items-end gap-3 shrink-0 border-t border-border px-4 py-3",
1220
+ "relative shrink-0 rounded-3xl border bg-surface",
1221
+ "shadow-lg shadow-black/10",
1222
+ "transition-all duration-200",
1223
+ "focus-within:border-accent/40 focus-within:shadow-accent/5",
1224
+ dragOver ? "border-accent/60 bg-accent/5" : "border-border/60",
884
1225
  className
885
1226
  ),
1227
+ onDragOver: handleDragOver,
1228
+ onDragLeave: handleDragLeave,
1229
+ onDrop: handleDrop,
886
1230
  children: [
887
1231
  /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
888
- "textarea",
1232
+ "input",
889
1233
  {
890
- ref: textareaRef,
891
- value,
892
- onChange: handleChange,
893
- onKeyDown: handleKeyDown,
894
- placeholder,
895
- rows: 1,
896
- disabled: isLoading,
897
- className: (0, import_tailwind_merge6.twMerge)(
898
- "flex-1 resize-none rounded-xl border border-border bg-surface/80",
899
- "px-4 py-2.5 text-sm text-text-primary placeholder:text-text-muted",
900
- "focus:border-transparent focus:ring-2 focus:ring-accent/40 focus:outline-none",
901
- "disabled:opacity-50 disabled:cursor-not-allowed",
902
- "overflow-hidden",
903
- "transition-all duration-200"
904
- ),
905
- style: { colorScheme: "dark" },
906
- "aria-label": "Message input"
1234
+ ref: fileInputRef,
1235
+ type: "file",
1236
+ multiple: true,
1237
+ accept: "image/png,image/jpeg,image/gif,image/webp,application/pdf",
1238
+ onChange: handleFileInputChange,
1239
+ className: "hidden",
1240
+ "aria-hidden": "true"
907
1241
  }
908
1242
  ),
909
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
910
- "button",
1243
+ attachments.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "flex gap-2 px-4 pt-3 pb-1 overflow-x-auto", children: attachments.map((att, i) => /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1244
+ AttachmentPreview,
911
1245
  {
912
- type: "button",
913
- onClick: handleSend,
914
- disabled: !value.trim() || isLoading,
915
- "aria-label": "Send message",
916
- className: (0, import_tailwind_merge6.twMerge)(
917
- "inline-flex items-center justify-center rounded-xl px-5 py-2.5",
918
- "text-sm font-semibold text-white shrink-0",
919
- "transition-all duration-200",
920
- "focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-accent",
921
- value.trim() && !isLoading ? "bg-accent hover:bg-accent-hover hover:scale-[1.02] hover:shadow-glow-cyan active:scale-[0.98]" : "bg-accent/30 text-text-muted cursor-not-allowed"
922
- ),
923
- children: "Send"
924
- }
925
- )
1246
+ attachment: att,
1247
+ onRemove: () => removeAttachment(i)
1248
+ },
1249
+ `${att.filename}-${i}`
1250
+ )) }),
1251
+ dragOver && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "absolute inset-0 rounded-3xl flex items-center justify-center bg-accent/10 border-2 border-dashed border-accent/40 z-10 pointer-events-none", children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "text-sm font-display font-semibold text-accent", children: "Drop files here" }) }),
1252
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "flex items-end", children: [
1253
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1254
+ "button",
1255
+ {
1256
+ type: "button",
1257
+ onClick: handleFileSelect,
1258
+ disabled: isLoading || attachments.length >= MAX_ATTACHMENTS,
1259
+ "aria-label": "Attach file",
1260
+ className: (0, import_tailwind_merge6.twMerge)(
1261
+ "flex-shrink-0 ml-2 mb-3",
1262
+ "inline-flex items-center justify-center",
1263
+ "w-9 h-9 rounded-full",
1264
+ "transition-all duration-200",
1265
+ "text-text-muted/60 hover:text-text-secondary hover:bg-text-muted/10",
1266
+ "focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-accent",
1267
+ "disabled:opacity-30 disabled:cursor-not-allowed disabled:hover:bg-transparent"
1268
+ ),
1269
+ children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(PaperclipIcon, {})
1270
+ }
1271
+ ),
1272
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1273
+ "textarea",
1274
+ {
1275
+ ref: textareaRef,
1276
+ value,
1277
+ onChange: handleChange,
1278
+ onKeyDown: handleKeyDown,
1279
+ onPaste: handlePaste,
1280
+ placeholder,
1281
+ rows: 1,
1282
+ disabled: isLoading,
1283
+ className: (0, import_tailwind_merge6.twMerge)(
1284
+ "flex-1 resize-none bg-transparent",
1285
+ "pl-2 pr-14 pt-4 pb-4 text-[15px] leading-relaxed",
1286
+ "text-text-primary placeholder:text-text-muted/70",
1287
+ "focus:outline-none",
1288
+ "disabled:opacity-50 disabled:cursor-not-allowed",
1289
+ "overflow-hidden"
1290
+ ),
1291
+ style: { colorScheme: "dark" },
1292
+ "aria-label": "Message input"
1293
+ }
1294
+ ),
1295
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1296
+ "button",
1297
+ {
1298
+ type: "button",
1299
+ onClick: handleSend,
1300
+ disabled: !canSend,
1301
+ "aria-label": "Send message",
1302
+ className: (0, import_tailwind_merge6.twMerge)(
1303
+ "absolute bottom-3 right-3",
1304
+ "inline-flex items-center justify-center",
1305
+ "w-9 h-9 rounded-full",
1306
+ "transition-all duration-200",
1307
+ "focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-accent",
1308
+ canSend ? "bg-accent text-white hover:bg-accent-hover active:scale-90 shadow-md shadow-accent/25" : isLoading ? "bg-text-muted/20 text-text-secondary hover:bg-text-muted/30" : "bg-transparent text-text-muted/40 cursor-default"
1309
+ ),
1310
+ children: isLoading ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(StopIcon, {}) : /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(ArrowUpIcon, {})
1311
+ }
1312
+ )
1313
+ ] })
926
1314
  ]
927
1315
  }
928
1316
  );
@@ -935,6 +1323,7 @@ function WelcomeScreen({
935
1323
  title = "Welcome",
936
1324
  message = "How can I help you today?",
937
1325
  icon,
1326
+ iconClassName,
938
1327
  suggestedQuestions = [],
939
1328
  onQuestionSelect,
940
1329
  className
@@ -947,12 +1336,15 @@ function WelcomeScreen({
947
1336
  className
948
1337
  ),
949
1338
  children: [
950
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1339
+ icon ? iconClassName ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: iconClassName, "aria-hidden": "true", children: icon }) : icon : /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
951
1340
  "div",
952
1341
  {
953
- className: "w-14 h-14 rounded-2xl bg-accent/10 border border-border flex items-center justify-center pulse-glow",
1342
+ className: (0, import_tailwind_merge7.twMerge)(
1343
+ "w-14 h-14 rounded-2xl bg-accent/10 border border-border flex items-center justify-center pulse-glow",
1344
+ iconClassName
1345
+ ),
954
1346
  "aria-hidden": "true",
955
- children: icon ?? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "text-2xl", children: "\u2726" })
1347
+ children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "text-2xl", children: "\u2726" })
956
1348
  }
957
1349
  ),
958
1350
  /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "flex flex-col gap-2", children: [
@@ -962,7 +1354,7 @@ function WelcomeScreen({
962
1354
  suggestedQuestions.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
963
1355
  "div",
964
1356
  {
965
- className: "flex flex-wrap justify-center gap-2 max-w-md",
1357
+ className: "flex flex-wrap justify-center gap-2 max-w-xl",
966
1358
  role: "group",
967
1359
  "aria-label": "Suggested questions",
968
1360
  children: suggestedQuestions.map((question) => /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
@@ -971,7 +1363,7 @@ function WelcomeScreen({
971
1363
  type: "button",
972
1364
  onClick: () => onQuestionSelect?.(question),
973
1365
  className: (0, import_tailwind_merge7.twMerge)(
974
- "px-4 py-2 rounded-full text-sm",
1366
+ "px-3.5 py-1.5 rounded-full text-[12px]",
975
1367
  "border border-border bg-transparent text-text-secondary",
976
1368
  "hover:bg-accent/10 hover:border-interactive hover:text-text-primary",
977
1369
  "focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-accent",
@@ -989,25 +1381,26 @@ function WelcomeScreen({
989
1381
  }
990
1382
 
991
1383
  // src/streaming/StreamingMessage/StreamingMessage.tsx
992
- var import_react6 = require("react");
1384
+ var import_react7 = require("react");
1385
+ var import_tailwind_merge8 = require("tailwind-merge");
993
1386
  var import_core3 = require("@surf-kit/core");
994
1387
 
995
1388
  // src/hooks/useCharacterDrain.ts
996
- var import_react5 = require("react");
1389
+ var import_react6 = require("react");
997
1390
  function useCharacterDrain(target, msPerChar = 15) {
998
- const [displayed, setDisplayed] = (0, import_react5.useState)("");
999
- const indexRef = (0, import_react5.useRef)(0);
1000
- const lastTimeRef = (0, import_react5.useRef)(0);
1001
- const rafRef = (0, import_react5.useRef)(null);
1002
- const drainTargetRef = (0, import_react5.useRef)("");
1003
- const msPerCharRef = (0, import_react5.useRef)(msPerChar);
1391
+ const [displayed, setDisplayed] = (0, import_react6.useState)("");
1392
+ const indexRef = (0, import_react6.useRef)(0);
1393
+ const lastTimeRef = (0, import_react6.useRef)(0);
1394
+ const rafRef = (0, import_react6.useRef)(null);
1395
+ const drainTargetRef = (0, import_react6.useRef)("");
1396
+ const msPerCharRef = (0, import_react6.useRef)(msPerChar);
1004
1397
  msPerCharRef.current = msPerChar;
1005
1398
  if (target !== "") {
1006
1399
  drainTargetRef.current = target;
1007
1400
  }
1008
1401
  const drainTarget = drainTargetRef.current;
1009
1402
  const isDraining = displayed.length < drainTarget.length;
1010
- const tickRef = (0, import_react5.useRef)(() => {
1403
+ const tickRef = (0, import_react6.useRef)(() => {
1011
1404
  });
1012
1405
  tickRef.current = (now) => {
1013
1406
  const currentTarget = drainTargetRef.current;
@@ -1019,7 +1412,10 @@ function useCharacterDrain(target, msPerChar = 15) {
1019
1412
  const elapsed = now - lastTimeRef.current;
1020
1413
  const charsToAdvance = Math.floor(elapsed / msPerCharRef.current);
1021
1414
  if (charsToAdvance > 0 && indexRef.current < currentTarget.length) {
1022
- const nextIndex = Math.min(indexRef.current + charsToAdvance, currentTarget.length);
1415
+ let nextIndex = Math.min(indexRef.current + charsToAdvance, currentTarget.length);
1416
+ while (nextIndex < currentTarget.length && currentTarget[nextIndex - 1].trim() === "") {
1417
+ nextIndex++;
1418
+ }
1023
1419
  indexRef.current = nextIndex;
1024
1420
  lastTimeRef.current = now;
1025
1421
  setDisplayed(currentTarget.slice(0, nextIndex));
@@ -1030,12 +1426,12 @@ function useCharacterDrain(target, msPerChar = 15) {
1030
1426
  rafRef.current = null;
1031
1427
  }
1032
1428
  };
1033
- (0, import_react5.useEffect)(() => {
1429
+ (0, import_react6.useEffect)(() => {
1034
1430
  if (drainTargetRef.current !== "" && indexRef.current < drainTargetRef.current.length && rafRef.current === null) {
1035
1431
  rafRef.current = requestAnimationFrame((t) => tickRef.current(t));
1036
1432
  }
1037
1433
  }, [drainTarget]);
1038
- (0, import_react5.useEffect)(() => {
1434
+ (0, import_react6.useEffect)(() => {
1039
1435
  if (target === "" && !isDraining && displayed !== "") {
1040
1436
  indexRef.current = 0;
1041
1437
  lastTimeRef.current = 0;
@@ -1043,7 +1439,7 @@ function useCharacterDrain(target, msPerChar = 15) {
1043
1439
  setDisplayed("");
1044
1440
  }
1045
1441
  }, [target, isDraining, displayed]);
1046
- (0, import_react5.useEffect)(() => {
1442
+ (0, import_react6.useEffect)(() => {
1047
1443
  return () => {
1048
1444
  if (rafRef.current !== null) {
1049
1445
  cancelAnimationFrame(rafRef.current);
@@ -1064,51 +1460,77 @@ var phaseLabels = {
1064
1460
  generating: "Writing...",
1065
1461
  verifying: "Verifying..."
1066
1462
  };
1463
+ var CURSOR_STYLES = `
1464
+ .sk-streaming-cursor > :not(ul,ol,blockquote):last-child::after,
1465
+ .sk-streaming-cursor > :is(ul,ol):last-child > li:last-child::after,
1466
+ .sk-streaming-cursor > blockquote:last-child > p:last-child::after {
1467
+ content: "";
1468
+ display: inline-block;
1469
+ width: 2px;
1470
+ height: 1em;
1471
+ background: var(--color-accent, #38bdf8);
1472
+ animation: sk-cursor-blink 0.8s steps(1) infinite;
1473
+ margin-left: 2px;
1474
+ vertical-align: text-bottom;
1475
+ }
1476
+ @keyframes sk-cursor-blink {
1477
+ 0%, 60% { opacity: 1; }
1478
+ 61%, 100% { opacity: 0; }
1479
+ }
1480
+ `;
1067
1481
  function StreamingMessage({
1068
1482
  stream,
1069
1483
  onComplete,
1484
+ onDraining,
1070
1485
  showPhases = true,
1071
1486
  className
1072
1487
  }) {
1073
- const onCompleteRef = (0, import_react6.useRef)(onComplete);
1488
+ const onCompleteRef = (0, import_react7.useRef)(onComplete);
1074
1489
  onCompleteRef.current = onComplete;
1075
- const wasActiveRef = (0, import_react6.useRef)(stream.active);
1076
- (0, import_react6.useEffect)(() => {
1490
+ const onDrainingRef = (0, import_react7.useRef)(onDraining);
1491
+ onDrainingRef.current = onDraining;
1492
+ const wasActiveRef = (0, import_react7.useRef)(stream.active);
1493
+ (0, import_react7.useEffect)(() => {
1077
1494
  if (wasActiveRef.current && !stream.active) {
1078
1495
  onCompleteRef.current?.();
1079
1496
  }
1080
1497
  wasActiveRef.current = stream.active;
1081
1498
  }, [stream.active]);
1082
1499
  const phaseLabel = phaseLabels[stream.phase];
1083
- const { displayed: displayedContent } = useCharacterDrain(stream.content);
1084
- return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className, "data-testid": "streaming-message", children: [
1500
+ const { displayed: rawDisplayed, isDraining } = useCharacterDrain(stream.content);
1501
+ const displayedContent = stream.active || isDraining ? rawDisplayed.trimEnd() : rawDisplayed;
1502
+ (0, import_react7.useEffect)(() => {
1503
+ onDrainingRef.current?.(isDraining);
1504
+ }, [isDraining]);
1505
+ const agentLabel = stream.agent ? stream.agent.replace("_agent", "").replace("_", " ") : null;
1506
+ const showPhaseIndicator = showPhases && stream.active && stream.phase !== "idle" && !displayedContent;
1507
+ const showCursor = (stream.active || isDraining) && !!displayedContent;
1508
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: (0, import_tailwind_merge8.twMerge)("flex w-full flex-col items-start", className), "data-testid": "streaming-message", children: [
1085
1509
  /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { "aria-live": "assertive", className: "sr-only", children: [
1086
1510
  stream.active && stream.phase !== "idle" && "Response started",
1087
1511
  !stream.active && stream.content && "Response complete"
1088
1512
  ] }),
1513
+ showCursor && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("style", { children: CURSOR_STYLES }),
1514
+ agentLabel && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { className: "text-[11px] font-display font-semibold uppercase tracking-[0.08em] text-text-muted px-1 mb-1.5", children: agentLabel }),
1089
1515
  /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "max-w-[88%] px-4 py-3 rounded-[18px] rounded-tl-[4px] bg-surface border border-border motion-safe:animate-springFromLeft", children: [
1090
- showPhases && stream.active && stream.phase !== "idle" && /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
1516
+ showPhaseIndicator && /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
1091
1517
  "div",
1092
1518
  {
1093
- className: "flex items-center gap-2 mb-2 text-sm text-text-secondary",
1519
+ className: "flex items-center gap-2 text-sm text-text-secondary",
1094
1520
  "data-testid": "phase-indicator",
1095
1521
  children: [
1096
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_core3.Spinner, { size: "sm" }) }),
1522
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_core3.WaveLoader, { size: "sm", color: "#38bdf8" }) }),
1097
1523
  /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { children: phaseLabel })
1098
1524
  ]
1099
1525
  }
1100
1526
  ),
1101
- /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "text-sm leading-relaxed text-text-primary whitespace-pre-wrap", children: [
1102
- displayedContent,
1103
- stream.active && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
1104
- "span",
1105
- {
1106
- className: "inline-block w-0.5 h-4 bg-accent align-text-bottom animate-pulse ml-0.5",
1107
- "aria-hidden": "true",
1108
- "data-testid": "streaming-cursor"
1109
- }
1110
- )
1111
- ] })
1527
+ displayedContent && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
1528
+ ResponseMessage,
1529
+ {
1530
+ content: displayedContent,
1531
+ className: showCursor ? "sk-streaming-cursor" : void 0
1532
+ }
1533
+ )
1112
1534
  ] })
1113
1535
  ] });
1114
1536
  }
@@ -1139,7 +1561,7 @@ function AgentChat({
1139
1561
  return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
1140
1562
  "div",
1141
1563
  {
1142
- className: (0, import_tailwind_merge8.twMerge)(
1564
+ className: (0, import_tailwind_merge9.twMerge)(
1143
1565
  "flex flex-col h-full bg-canvas border border-border rounded-xl overflow-hidden",
1144
1566
  className
1145
1567
  ),
@@ -1182,7 +1604,7 @@ function AgentChat({
1182
1604
  }
1183
1605
 
1184
1606
  // src/chat/ConversationList/ConversationList.tsx
1185
- var import_tailwind_merge9 = require("tailwind-merge");
1607
+ var import_tailwind_merge10 = require("tailwind-merge");
1186
1608
  var import_jsx_runtime13 = require("react/jsx-runtime");
1187
1609
  function ConversationList({
1188
1610
  conversations,
@@ -1196,7 +1618,7 @@ function ConversationList({
1196
1618
  "nav",
1197
1619
  {
1198
1620
  "aria-label": "Conversation list",
1199
- className: (0, import_tailwind_merge9.twMerge)("flex flex-col h-full bg-canvas", className),
1621
+ className: (0, import_tailwind_merge10.twMerge)("flex flex-col h-full bg-canvas", className),
1200
1622
  children: [
1201
1623
  onNew && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { className: "p-3 border-b border-border", children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
1202
1624
  "button",
@@ -1213,7 +1635,7 @@ function ConversationList({
1213
1635
  return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
1214
1636
  "li",
1215
1637
  {
1216
- className: (0, import_tailwind_merge9.twMerge)(
1638
+ className: (0, import_tailwind_merge10.twMerge)(
1217
1639
  "flex items-start border-b border-border transition-colors duration-200",
1218
1640
  "hover:bg-surface",
1219
1641
  isActive && "bg-surface-raised border-l-2 border-l-accent"
@@ -1285,8 +1707,8 @@ function AgentFullPage({
1285
1707
  onNewConversation,
1286
1708
  className
1287
1709
  }) {
1288
- const [sidebarOpen, setSidebarOpen] = (0, import_react7.useState)(false);
1289
- const handleSelect = (0, import_react7.useCallback)(
1710
+ const [sidebarOpen, setSidebarOpen] = (0, import_react8.useState)(false);
1711
+ const handleSelect = (0, import_react8.useCallback)(
1290
1712
  (id) => {
1291
1713
  onConversationSelect?.(id);
1292
1714
  setSidebarOpen(false);
@@ -1296,7 +1718,7 @@ function AgentFullPage({
1296
1718
  return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(
1297
1719
  "div",
1298
1720
  {
1299
- className: (0, import_tailwind_merge10.twMerge)("flex h-screen w-full overflow-hidden bg-brand-dark", className),
1721
+ className: (0, import_tailwind_merge11.twMerge)("flex h-screen w-full overflow-hidden bg-brand-dark", className),
1300
1722
  "data-testid": "agent-full-page",
1301
1723
  children: [
1302
1724
  showConversationList && /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(import_jsx_runtime14.Fragment, { children: [
@@ -1311,7 +1733,7 @@ function AgentFullPage({
1311
1733
  /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
1312
1734
  "aside",
1313
1735
  {
1314
- className: (0, import_tailwind_merge10.twMerge)(
1736
+ className: (0, import_tailwind_merge11.twMerge)(
1315
1737
  "bg-brand-dark border-r border-brand-gold/15 w-72 shrink-0 flex-col z-40",
1316
1738
  // Desktop: always visible
1317
1739
  "hidden md:flex",
@@ -1377,8 +1799,8 @@ function AgentFullPage({
1377
1799
  }
1378
1800
 
1379
1801
  // src/layouts/AgentPanel/AgentPanel.tsx
1380
- var import_tailwind_merge11 = require("tailwind-merge");
1381
- var import_react8 = require("react");
1802
+ var import_tailwind_merge12 = require("tailwind-merge");
1803
+ var import_react9 = require("react");
1382
1804
  var import_jsx_runtime15 = require("react/jsx-runtime");
1383
1805
  function AgentPanel({
1384
1806
  endpoint,
@@ -1389,8 +1811,8 @@ function AgentPanel({
1389
1811
  title = "Chat",
1390
1812
  className
1391
1813
  }) {
1392
- const panelRef = (0, import_react8.useRef)(null);
1393
- (0, import_react8.useEffect)(() => {
1814
+ const panelRef = (0, import_react9.useRef)(null);
1815
+ (0, import_react9.useEffect)(() => {
1394
1816
  if (!isOpen) return;
1395
1817
  const handleKeyDown = (e) => {
1396
1818
  if (e.key === "Escape") onClose();
@@ -1402,13 +1824,13 @@ function AgentPanel({
1402
1824
  return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(
1403
1825
  "div",
1404
1826
  {
1405
- className: (0, import_tailwind_merge11.twMerge)("fixed inset-0 z-50", !isOpen && "pointer-events-none"),
1827
+ className: (0, import_tailwind_merge12.twMerge)("fixed inset-0 z-50", !isOpen && "pointer-events-none"),
1406
1828
  "aria-hidden": !isOpen,
1407
1829
  children: [
1408
1830
  /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
1409
1831
  "div",
1410
1832
  {
1411
- className: (0, import_tailwind_merge11.twMerge)(
1833
+ className: (0, import_tailwind_merge12.twMerge)(
1412
1834
  "fixed inset-0 transition-opacity duration-300",
1413
1835
  isOpen ? "opacity-100 bg-brand-dark/70 backdrop-blur-sm pointer-events-auto" : "opacity-0 pointer-events-none"
1414
1836
  ),
@@ -1424,7 +1846,7 @@ function AgentPanel({
1424
1846
  "aria-label": title,
1425
1847
  "aria-modal": isOpen ? "true" : void 0,
1426
1848
  style: { width: widthStyle, maxWidth: "100vw" },
1427
- className: (0, import_tailwind_merge11.twMerge)(
1849
+ className: (0, import_tailwind_merge12.twMerge)(
1428
1850
  "fixed top-0 h-full flex flex-col z-50 bg-brand-dark shadow-card",
1429
1851
  "transition-transform duration-300 ease-in-out",
1430
1852
  side === "left" ? `left-0 border-r border-brand-gold/15 ${isOpen ? "translate-x-0" : "-translate-x-full"}` : `right-0 border-l border-brand-gold/15 ${isOpen ? "translate-x-0" : "translate-x-full"}`,
@@ -1466,8 +1888,8 @@ function AgentPanel({
1466
1888
  }
1467
1889
 
1468
1890
  // src/layouts/AgentWidget/AgentWidget.tsx
1469
- var import_tailwind_merge12 = require("tailwind-merge");
1470
- var import_react9 = require("react");
1891
+ var import_tailwind_merge13 = require("tailwind-merge");
1892
+ var import_react10 = require("react");
1471
1893
  var import_jsx_runtime16 = require("react/jsx-runtime");
1472
1894
  function AgentWidget({
1473
1895
  endpoint,
@@ -1476,8 +1898,8 @@ function AgentWidget({
1476
1898
  title = "Chat",
1477
1899
  className
1478
1900
  }) {
1479
- const [isOpen, setIsOpen] = (0, import_react9.useState)(false);
1480
- const toggle = (0, import_react9.useCallback)(() => {
1901
+ const [isOpen, setIsOpen] = (0, import_react10.useState)(false);
1902
+ const toggle = (0, import_react10.useCallback)(() => {
1481
1903
  setIsOpen((prev) => !prev);
1482
1904
  }, []);
1483
1905
  const positionClasses = position === "bottom-left" ? "left-4 bottom-4" : "right-4 bottom-4";
@@ -1490,7 +1912,7 @@ function AgentWidget({
1490
1912
  role: "dialog",
1491
1913
  "aria-label": title,
1492
1914
  "aria-hidden": !isOpen,
1493
- className: (0, import_tailwind_merge12.twMerge)(
1915
+ className: (0, import_tailwind_merge13.twMerge)(
1494
1916
  "fixed z-50 flex flex-col",
1495
1917
  "w-[min(400px,calc(100vw-2rem))] h-[min(600px,calc(100vh-6rem))]",
1496
1918
  "rounded-2xl overflow-hidden border border-brand-gold/15",
@@ -1537,7 +1959,7 @@ function AgentWidget({
1537
1959
  onClick: toggle,
1538
1960
  "aria-label": isOpen ? "Close chat" : triggerLabel,
1539
1961
  "aria-expanded": isOpen,
1540
- className: (0, import_tailwind_merge12.twMerge)(
1962
+ className: (0, import_tailwind_merge13.twMerge)(
1541
1963
  "fixed z-50 flex items-center justify-center w-14 h-14 rounded-full",
1542
1964
  "bg-brand-blue text-brand-cream shadow-glow-cyan",
1543
1965
  "hover:bg-brand-cyan hover:shadow-glow-cyan hover:scale-105",
@@ -1555,7 +1977,7 @@ function AgentWidget({
1555
1977
  }
1556
1978
 
1557
1979
  // src/layouts/AgentEmbed/AgentEmbed.tsx
1558
- var import_tailwind_merge13 = require("tailwind-merge");
1980
+ var import_tailwind_merge14 = require("tailwind-merge");
1559
1981
  var import_jsx_runtime17 = require("react/jsx-runtime");
1560
1982
  function AgentEmbed({
1561
1983
  endpoint,
@@ -1565,7 +1987,7 @@ function AgentEmbed({
1565
1987
  return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
1566
1988
  "div",
1567
1989
  {
1568
- className: (0, import_tailwind_merge13.twMerge)("w-full h-full min-h-0", className),
1990
+ className: (0, import_tailwind_merge14.twMerge)("w-full h-full min-h-0", className),
1569
1991
  "data-testid": "agent-embed",
1570
1992
  children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
1571
1993
  AgentChat,