@surf-kit/agent 0.2.2 → 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 (51) hide show
  1. package/dist/chat/index.cjs +625 -204
  2. package/dist/chat/index.cjs.map +1 -1
  3. package/dist/chat/index.d.cts +11 -6
  4. package/dist/chat/index.d.ts +11 -6
  5. package/dist/chat/index.js +606 -185
  6. package/dist/chat/index.js.map +1 -1
  7. package/dist/{chat--OifhIRe.d.ts → chat-BIIDOGrD.d.ts} +10 -1
  8. package/dist/{chat-ChYl2XjV.d.cts → chat-CGamM7Mz.d.cts} +10 -1
  9. package/dist/{hooks-DLfF18IU.d.cts → hooks-B1NYoLLs.d.cts} +21 -5
  10. package/dist/{hooks-BGs8-4GK.d.ts → hooks-CTeEqnBQ.d.ts} +21 -5
  11. package/dist/hooks.cjs +126 -81
  12. package/dist/hooks.cjs.map +1 -1
  13. package/dist/hooks.d.cts +3 -3
  14. package/dist/hooks.d.ts +3 -3
  15. package/dist/hooks.js +126 -81
  16. package/dist/hooks.js.map +1 -1
  17. package/dist/index.cjs +686 -265
  18. package/dist/index.cjs.map +1 -1
  19. package/dist/index.d.cts +3 -3
  20. package/dist/index.d.ts +3 -3
  21. package/dist/index.js +645 -224
  22. package/dist/index.js.map +1 -1
  23. package/dist/layouts/index.cjs +646 -225
  24. package/dist/layouts/index.cjs.map +1 -1
  25. package/dist/layouts/index.d.cts +1 -1
  26. package/dist/layouts/index.d.ts +1 -1
  27. package/dist/layouts/index.js +622 -201
  28. package/dist/layouts/index.js.map +1 -1
  29. package/dist/mcp/index.cjs +1 -1
  30. package/dist/mcp/index.cjs.map +1 -1
  31. package/dist/mcp/index.js +2 -2
  32. package/dist/mcp/index.js.map +1 -1
  33. package/dist/response/index.cjs +66 -12
  34. package/dist/response/index.cjs.map +1 -1
  35. package/dist/response/index.d.cts +2 -2
  36. package/dist/response/index.d.ts +2 -2
  37. package/dist/response/index.js +64 -10
  38. package/dist/response/index.js.map +1 -1
  39. package/dist/sources/index.cjs +30 -1
  40. package/dist/sources/index.cjs.map +1 -1
  41. package/dist/sources/index.js +30 -1
  42. package/dist/sources/index.js.map +1 -1
  43. package/dist/streaming/index.cjs +202 -93
  44. package/dist/streaming/index.cjs.map +1 -1
  45. package/dist/streaming/index.d.cts +4 -3
  46. package/dist/streaming/index.d.ts +4 -3
  47. package/dist/streaming/index.js +172 -73
  48. package/dist/streaming/index.js.map +1 -1
  49. package/dist/{streaming-DbQxScpi.d.ts → streaming-Bx-ff2tt.d.ts} +1 -1
  50. package/dist/{streaming-DfT22A0z.d.cts → streaming-x7umFHoP.d.cts} +1 -1
  51. package/package.json +15 -4
@@ -39,11 +39,11 @@ __export(layouts_exports, {
39
39
  module.exports = __toCommonJS(layouts_exports);
40
40
 
41
41
  // src/layouts/AgentFullPage/AgentFullPage.tsx
42
- var import_tailwind_merge10 = require("tailwind-merge");
43
- var import_react7 = require("react");
42
+ var import_tailwind_merge11 = require("tailwind-merge");
43
+ var import_react8 = require("react");
44
44
 
45
45
  // src/chat/AgentChat/AgentChat.tsx
46
- var import_tailwind_merge8 = require("tailwind-merge");
46
+ var import_tailwind_merge9 = require("tailwind-merge");
47
47
 
48
48
  // src/hooks/useAgentChat.ts
49
49
  var import_react = require("react");
@@ -54,7 +54,8 @@ var initialState = {
54
54
  error: null,
55
55
  inputValue: "",
56
56
  streamPhase: "idle",
57
- streamingContent: ""
57
+ streamingContent: "",
58
+ streamingAgent: null
58
59
  };
59
60
  function reducer(state, action) {
60
61
  switch (action.type) {
@@ -68,12 +69,15 @@ function reducer(state, action) {
68
69
  error: null,
69
70
  inputValue: "",
70
71
  streamPhase: "thinking",
71
- streamingContent: ""
72
+ streamingContent: "",
73
+ streamingAgent: null
72
74
  };
73
75
  case "STREAM_PHASE":
74
76
  return { ...state, streamPhase: action.phase };
75
77
  case "STREAM_CONTENT":
76
78
  return { ...state, streamingContent: state.streamingContent + action.content };
79
+ case "STREAM_AGENT":
80
+ return { ...state, streamingAgent: action.agent };
77
81
  case "SEND_SUCCESS":
78
82
  return {
79
83
  ...state,
@@ -89,7 +93,8 @@ function reducer(state, action) {
89
93
  isLoading: false,
90
94
  error: action.error,
91
95
  streamPhase: "idle",
92
- streamingContent: ""
96
+ streamingContent: "",
97
+ streamingAgent: null
93
98
  };
94
99
  case "LOAD_CONVERSATION":
95
100
  return {
@@ -115,107 +120,142 @@ function useAgentChat(config) {
115
120
  const configRef = (0, import_react.useRef)(config);
116
121
  configRef.current = config;
117
122
  const lastUserMessageRef = (0, import_react.useRef)(null);
123
+ const lastUserAttachmentsRef = (0, import_react.useRef)(void 0);
118
124
  const sendMessage = (0, import_react.useCallback)(
119
- async (content) => {
120
- 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 ?? {};
121
128
  lastUserMessageRef.current = content;
129
+ lastUserAttachmentsRef.current = attachments;
122
130
  const userMessage = {
123
131
  id: generateMessageId(),
124
132
  role: "user",
125
133
  content,
134
+ attachments,
126
135
  timestamp: /* @__PURE__ */ new Date()
127
136
  };
128
137
  dispatch({ type: "SEND_START", message: userMessage });
129
138
  const controller = new AbortController();
130
139
  const timeoutId = setTimeout(() => controller.abort(), timeout);
131
140
  try {
132
- const response = await fetch(`${apiUrl}${streamPath}`, {
133
- method: "POST",
134
- headers: {
135
- "Content-Type": "application/json",
136
- Accept: "text/event-stream",
137
- ...headers
138
- },
139
- body: JSON.stringify({
140
- message: content,
141
- conversation_id: state.conversationId
142
- }),
143
- signal: controller.signal
144
- });
145
- clearTimeout(timeoutId);
146
- if (!response.ok) {
147
- dispatch({
148
- type: "SEND_ERROR",
149
- error: {
150
- code: "API_ERROR",
151
- message: `HTTP ${response.status}: ${response.statusText}`,
152
- retryable: response.status >= 500
153
- }
154
- });
155
- 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
+ }));
156
158
  }
157
- const reader = response.body?.getReader();
158
- if (!reader) {
159
- dispatch({
160
- type: "SEND_ERROR",
161
- 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
162
204
  });
163
- return;
164
- }
165
- const decoder = new TextDecoder();
166
- let buffer = "";
167
- let accumulatedContent = "";
168
- let agentResponse = null;
169
- let capturedAgent = null;
170
- let capturedConversationId = null;
171
- while (true) {
172
- const { done, value } = await reader.read();
173
- if (done) break;
174
- buffer += decoder.decode(value, { stream: true });
175
- const lines = buffer.split("\n");
176
- buffer = lines.pop() ?? "";
177
- for (const line of lines) {
178
- if (!line.startsWith("data: ")) continue;
179
- const data = line.slice(6).trim();
180
- if (data === "[DONE]") continue;
181
- try {
182
- const event = JSON.parse(data);
183
- switch (event.type) {
184
- case "agent":
185
- capturedAgent = event.agent;
186
- break;
187
- case "phase":
188
- dispatch({ type: "STREAM_PHASE", phase: event.phase });
189
- break;
190
- case "delta":
191
- accumulatedContent += event.content;
192
- dispatch({ type: "STREAM_CONTENT", content: event.content });
193
- break;
194
- case "done":
195
- agentResponse = event.response;
196
- capturedConversationId = event.conversation_id ?? null;
197
- break;
198
- case "error":
199
- dispatch({ type: "SEND_ERROR", error: event.error });
200
- 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 {
201
241
  }
202
- } catch {
203
242
  }
204
243
  }
205
244
  }
245
+ if (ctx.hadStreamError) return;
206
246
  const assistantMessage = {
207
247
  id: generateMessageId(),
208
248
  role: "assistant",
209
- content: agentResponse?.message ?? accumulatedContent,
210
- response: agentResponse ?? void 0,
211
- agent: capturedAgent ?? void 0,
249
+ content: ctx.agentResponse?.message ?? ctx.accumulatedContent,
250
+ response: ctx.agentResponse ?? void 0,
251
+ agent: ctx.capturedAgent ?? void 0,
212
252
  timestamp: /* @__PURE__ */ new Date()
213
253
  };
214
254
  dispatch({
215
255
  type: "SEND_SUCCESS",
216
256
  message: assistantMessage,
217
- streamingContent: accumulatedContent,
218
- conversationId: capturedConversationId
257
+ streamingContent: ctx.accumulatedContent,
258
+ conversationId: ctx.capturedConversationId
219
259
  });
220
260
  } catch (err) {
221
261
  clearTimeout(timeoutId);
@@ -246,7 +286,8 @@ function useAgentChat(config) {
246
286
  }, []);
247
287
  const submitFeedback = (0, import_react.useCallback)(
248
288
  async (messageId, rating, comment) => {
249
- 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 ?? {};
250
291
  await fetch(`${apiUrl}${feedbackPath}`, {
251
292
  method: "POST",
252
293
  headers: { "Content-Type": "application/json", ...headers },
@@ -257,12 +298,13 @@ function useAgentChat(config) {
257
298
  );
258
299
  const retry = (0, import_react.useCallback)(async () => {
259
300
  if (lastUserMessageRef.current) {
260
- await sendMessage(lastUserMessageRef.current);
301
+ await sendMessage(lastUserMessageRef.current, lastUserAttachmentsRef.current);
261
302
  }
262
303
  }, [sendMessage]);
263
304
  const reset = (0, import_react.useCallback)(() => {
264
305
  dispatch({ type: "RESET" });
265
306
  lastUserMessageRef.current = null;
307
+ lastUserAttachmentsRef.current = void 0;
266
308
  }, []);
267
309
  const actions = {
268
310
  sendMessage,
@@ -277,7 +319,7 @@ function useAgentChat(config) {
277
319
 
278
320
  // src/chat/MessageThread/MessageThread.tsx
279
321
  var import_tailwind_merge5 = require("tailwind-merge");
280
- var import_react3 = require("react");
322
+ var import_react4 = require("react");
281
323
 
282
324
  // src/chat/MessageBubble/MessageBubble.tsx
283
325
  var import_tailwind_merge4 = require("tailwind-merge");
@@ -286,6 +328,7 @@ var import_tailwind_merge4 = require("tailwind-merge");
286
328
  var import_core2 = require("@surf-kit/core");
287
329
 
288
330
  // src/response/ResponseMessage/ResponseMessage.tsx
331
+ var import_react2 = __toESM(require("react"), 1);
289
332
  var import_react_markdown = __toESM(require("react-markdown"), 1);
290
333
  var import_rehype_sanitize = __toESM(require("rehype-sanitize"), 1);
291
334
  var import_tailwind_merge = require("tailwind-merge");
@@ -310,6 +353,7 @@ function ResponseMessage({ content, className }) {
310
353
  "[&_h3]:text-sm [&_h3]:font-semibold [&_h3]:text-accent [&_h3]:mt-2 [&_h3]:mb-1",
311
354
  "[&_code]:bg-surface-raised [&_code]:text-accent [&_code]:px-1.5 [&_code]:py-0.5 [&_code]:rounded [&_code]:text-xs [&_code]:font-mono",
312
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",
313
357
  "[&_blockquote]:border-l-2 [&_blockquote]:border-border-strong [&_blockquote]:pl-4 [&_blockquote]:text-text-secondary",
314
358
  "[&_a]:text-accent [&_a]:underline-offset-2 [&_a]:hover:text-accent/80",
315
359
  className
@@ -325,11 +369,24 @@ function ResponseMessage({ content, className }) {
325
369
  p: ({ children }) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "my-2", children }),
326
370
  ul: ({ children }) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("ul", { className: "my-2 list-disc pl-6", children }),
327
371
  ol: ({ children }) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("ol", { className: "my-2 list-decimal pl-6", children }),
328
- 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
+ },
329
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 }),
330
386
  h1: ({ children }) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h1", { className: "text-base font-bold mt-4 mb-2", children }),
331
387
  h2: ({ children }) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h2", { className: "text-sm font-bold mt-3 mb-1", children }),
332
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" }),
333
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 })
334
391
  },
335
392
  children: normalizeMarkdownLists(content)
@@ -465,7 +522,14 @@ function renderWarning(data) {
465
522
  }
466
523
  );
467
524
  }
468
- 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;
469
533
  if (!data) return null;
470
534
  let content;
471
535
  switch (uiHint) {
@@ -495,7 +559,7 @@ function StructuredResponse({ uiHint, data, className }) {
495
559
  }
496
560
 
497
561
  // src/sources/SourceList/SourceList.tsx
498
- var import_react2 = require("react");
562
+ var import_react3 = require("react");
499
563
 
500
564
  // src/sources/SourceCard/SourceCard.tsx
501
565
  var import_core = require("@surf-kit/core");
@@ -545,7 +609,36 @@ function SourceCard({ source, variant = "compact", onNavigate, className }) {
545
609
  children: [
546
610
  /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex items-start justify-between gap-2", children: [
547
611
  /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex-1 min-w-0", children: [
548
- /* @__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 }),
549
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 })
550
643
  ] }),
551
644
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
@@ -575,7 +668,7 @@ function SourceList({
575
668
  onNavigate,
576
669
  className
577
670
  }) {
578
- const [isExpanded, setIsExpanded] = (0, import_react2.useState)(defaultExpanded);
671
+ const [isExpanded, setIsExpanded] = (0, import_react3.useState)(defaultExpanded);
579
672
  if (sources.length === 0) return null;
580
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)(
581
674
  SourceCard,
@@ -679,13 +772,16 @@ function AgentResponse({
679
772
  }) {
680
773
  return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: `flex flex-col gap-4 ${className ?? ""}`, "data-testid": "agent-response", children: [
681
774
  /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(ResponseMessage, { content: response.message }),
682
- response.ui_hint !== "text" && response.structured_data && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
683
- StructuredResponse,
684
- {
685
- uiHint: response.ui_hint,
686
- data: response.structured_data
687
- }
688
- ),
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
+ })(),
689
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: [
690
786
  showConfidence && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
691
787
  import_core2.Badge,
@@ -736,6 +832,31 @@ function AgentResponse({
736
832
 
737
833
  // src/chat/MessageBubble/MessageBubble.tsx
738
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
+ }
739
860
  function MessageBubble({
740
861
  message,
741
862
  showAgent,
@@ -743,23 +864,29 @@ function MessageBubble({
743
864
  showConfidence = true,
744
865
  showVerification = true,
745
866
  animated = true,
867
+ userBubbleClassName,
746
868
  className
747
869
  }) {
748
870
  const isUser = message.role === "user";
871
+ const hasAttachments = message.attachments && message.attachments.length > 0;
749
872
  if (isUser) {
750
873
  return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
751
874
  "div",
752
875
  {
753
876
  "data-message-id": message.id,
754
877
  className: (0, import_tailwind_merge4.twMerge)("flex w-full justify-end", className),
755
- children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
878
+ children: /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
756
879
  "div",
757
880
  {
758
881
  className: (0, import_tailwind_merge4.twMerge)(
759
- "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",
760
- 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
761
885
  ),
762
- 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
+ ]
763
890
  }
764
891
  )
765
892
  }
@@ -771,7 +898,7 @@ function MessageBubble({
771
898
  "data-message-id": message.id,
772
899
  className: (0, import_tailwind_merge4.twMerge)("flex w-full flex-col items-start gap-1.5", className),
773
900
  children: [
774
- 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("_", " ") }),
775
902
  /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
776
903
  "div",
777
904
  {
@@ -797,34 +924,70 @@ function MessageBubble({
797
924
 
798
925
  // src/chat/MessageThread/MessageThread.tsx
799
926
  var import_jsx_runtime8 = require("react/jsx-runtime");
800
- function MessageThread({ messages, streamingSlot, showSources, showConfidence, showVerification, className }) {
801
- const bottomRef = (0, import_react3.useRef)(null);
802
- (0, import_react3.useEffect)(() => {
803
- bottomRef.current?.scrollIntoView?.({ behavior: "smooth" });
804
- }, [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]);
805
959
  return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
806
960
  "div",
807
961
  {
962
+ ref: scrollRef,
808
963
  role: "log",
809
964
  "aria-live": "polite",
810
965
  "aria-label": "Message thread",
966
+ onScroll: handleScroll,
811
967
  className: (0, import_tailwind_merge5.twMerge)(
812
968
  "flex flex-col gap-4 overflow-y-auto flex-1 px-4 py-6",
813
969
  className
814
970
  ),
815
971
  children: [
816
- messages.map((message) => /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
817
- MessageBubble,
818
- {
819
- message,
820
- showSources,
821
- showConfidence,
822
- showVerification
823
- },
824
- message.id
825
- )),
826
- streamingSlot,
827
- /* @__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
828
991
  ]
829
992
  }
830
993
  );
@@ -832,32 +995,126 @@ function MessageThread({ messages, streamingSlot, showSources, showConfidence, s
832
995
 
833
996
  // src/chat/MessageComposer/MessageComposer.tsx
834
997
  var import_tailwind_merge6 = require("tailwind-merge");
835
- var import_react4 = require("react");
998
+ var import_react5 = require("react");
836
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
+ }
837
1088
  function MessageComposer({
838
1089
  onSend,
839
1090
  isLoading = false,
840
1091
  placeholder = "Type a message...",
841
1092
  className
842
1093
  }) {
843
- const [value, setValue] = (0, import_react4.useState)("");
844
- const textareaRef = (0, import_react4.useRef)(null);
845
- const canSend = value.trim().length > 0 && !isLoading;
846
- 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)(() => {
847
1101
  const el = textareaRef.current;
848
1102
  if (el) {
849
1103
  el.style.height = "auto";
850
1104
  el.style.overflowY = "hidden";
851
1105
  }
852
1106
  }, []);
853
- const handleSend = (0, import_react4.useCallback)(() => {
1107
+ const handleSend = (0, import_react5.useCallback)(() => {
854
1108
  if (!canSend) return;
855
- 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);
856
1112
  setValue("");
1113
+ setAttachments([]);
857
1114
  resetHeight();
858
1115
  textareaRef.current?.focus();
859
- }, [canSend, onSend, value, resetHeight]);
860
- const handleKeyDown = (0, import_react4.useCallback)(
1116
+ }, [canSend, onSend, value, attachments, resetHeight]);
1117
+ const handleKeyDown = (0, import_react5.useCallback)(
861
1118
  (e) => {
862
1119
  if (e.key === "Enter" && !e.shiftKey) {
863
1120
  e.preventDefault();
@@ -866,64 +1123,194 @@ function MessageComposer({
866
1123
  },
867
1124
  [handleSend]
868
1125
  );
869
- const handleChange = (0, import_react4.useCallback)(
1126
+ const handleChange = (0, import_react5.useCallback)(
870
1127
  (e) => {
871
1128
  setValue(e.target.value);
872
1129
  const el = e.target;
873
1130
  el.style.height = "auto";
874
- const capped = Math.min(el.scrollHeight, 128);
1131
+ const capped = Math.min(el.scrollHeight, 200);
875
1132
  el.style.height = `${capped}px`;
876
- el.style.overflowY = el.scrollHeight > 128 ? "auto" : "hidden";
1133
+ el.style.overflowY = el.scrollHeight > 200 ? "auto" : "hidden";
877
1134
  },
878
1135
  []
879
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
+ );
880
1216
  return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
881
1217
  "div",
882
1218
  {
883
1219
  className: (0, import_tailwind_merge6.twMerge)(
884
- "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",
885
1225
  className
886
1226
  ),
1227
+ onDragOver: handleDragOver,
1228
+ onDragLeave: handleDragLeave,
1229
+ onDrop: handleDrop,
887
1230
  children: [
888
1231
  /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
889
- "textarea",
1232
+ "input",
890
1233
  {
891
- ref: textareaRef,
892
- value,
893
- onChange: handleChange,
894
- onKeyDown: handleKeyDown,
895
- placeholder,
896
- rows: 1,
897
- disabled: isLoading,
898
- className: (0, import_tailwind_merge6.twMerge)(
899
- "flex-1 resize-none rounded-xl border border-border bg-surface/80",
900
- "px-4 py-2.5 text-sm text-text-primary placeholder:text-text-muted",
901
- "focus:border-transparent focus:ring-2 focus:ring-accent/40 focus:outline-none",
902
- "disabled:opacity-50 disabled:cursor-not-allowed",
903
- "overflow-hidden",
904
- "transition-all duration-200"
905
- ),
906
- style: { colorScheme: "dark" },
907
- "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"
908
1241
  }
909
1242
  ),
910
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
911
- "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,
912
1245
  {
913
- type: "button",
914
- onClick: handleSend,
915
- disabled: !value.trim() || isLoading,
916
- "aria-label": "Send message",
917
- className: (0, import_tailwind_merge6.twMerge)(
918
- "inline-flex items-center justify-center rounded-xl px-5 py-2.5",
919
- "text-sm font-semibold text-white shrink-0",
920
- "transition-all duration-200",
921
- "focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-accent",
922
- 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"
923
- ),
924
- children: "Send"
925
- }
926
- )
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
+ ] })
927
1314
  ]
928
1315
  }
929
1316
  );
@@ -936,6 +1323,7 @@ function WelcomeScreen({
936
1323
  title = "Welcome",
937
1324
  message = "How can I help you today?",
938
1325
  icon,
1326
+ iconClassName,
939
1327
  suggestedQuestions = [],
940
1328
  onQuestionSelect,
941
1329
  className
@@ -948,12 +1336,15 @@ function WelcomeScreen({
948
1336
  className
949
1337
  ),
950
1338
  children: [
951
- /* @__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)(
952
1340
  "div",
953
1341
  {
954
- 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
+ ),
955
1346
  "aria-hidden": "true",
956
- 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" })
957
1348
  }
958
1349
  ),
959
1350
  /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "flex flex-col gap-2", children: [
@@ -963,7 +1354,7 @@ function WelcomeScreen({
963
1354
  suggestedQuestions.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
964
1355
  "div",
965
1356
  {
966
- className: "flex flex-wrap justify-center gap-2 max-w-md",
1357
+ className: "flex flex-wrap justify-center gap-2 max-w-xl",
967
1358
  role: "group",
968
1359
  "aria-label": "Suggested questions",
969
1360
  children: suggestedQuestions.map((question) => /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
@@ -972,7 +1363,7 @@ function WelcomeScreen({
972
1363
  type: "button",
973
1364
  onClick: () => onQuestionSelect?.(question),
974
1365
  className: (0, import_tailwind_merge7.twMerge)(
975
- "px-4 py-2 rounded-full text-sm",
1366
+ "px-3.5 py-1.5 rounded-full text-[12px]",
976
1367
  "border border-border bg-transparent text-text-secondary",
977
1368
  "hover:bg-accent/10 hover:border-interactive hover:text-text-primary",
978
1369
  "focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-accent",
@@ -990,25 +1381,26 @@ function WelcomeScreen({
990
1381
  }
991
1382
 
992
1383
  // src/streaming/StreamingMessage/StreamingMessage.tsx
993
- var import_react6 = require("react");
1384
+ var import_react7 = require("react");
1385
+ var import_tailwind_merge8 = require("tailwind-merge");
994
1386
  var import_core3 = require("@surf-kit/core");
995
1387
 
996
1388
  // src/hooks/useCharacterDrain.ts
997
- var import_react5 = require("react");
1389
+ var import_react6 = require("react");
998
1390
  function useCharacterDrain(target, msPerChar = 15) {
999
- const [displayed, setDisplayed] = (0, import_react5.useState)("");
1000
- const indexRef = (0, import_react5.useRef)(0);
1001
- const lastTimeRef = (0, import_react5.useRef)(0);
1002
- const rafRef = (0, import_react5.useRef)(null);
1003
- const drainTargetRef = (0, import_react5.useRef)("");
1004
- 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);
1005
1397
  msPerCharRef.current = msPerChar;
1006
1398
  if (target !== "") {
1007
1399
  drainTargetRef.current = target;
1008
1400
  }
1009
1401
  const drainTarget = drainTargetRef.current;
1010
1402
  const isDraining = displayed.length < drainTarget.length;
1011
- const tickRef = (0, import_react5.useRef)(() => {
1403
+ const tickRef = (0, import_react6.useRef)(() => {
1012
1404
  });
1013
1405
  tickRef.current = (now) => {
1014
1406
  const currentTarget = drainTargetRef.current;
@@ -1020,7 +1412,10 @@ function useCharacterDrain(target, msPerChar = 15) {
1020
1412
  const elapsed = now - lastTimeRef.current;
1021
1413
  const charsToAdvance = Math.floor(elapsed / msPerCharRef.current);
1022
1414
  if (charsToAdvance > 0 && indexRef.current < currentTarget.length) {
1023
- 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
+ }
1024
1419
  indexRef.current = nextIndex;
1025
1420
  lastTimeRef.current = now;
1026
1421
  setDisplayed(currentTarget.slice(0, nextIndex));
@@ -1031,12 +1426,12 @@ function useCharacterDrain(target, msPerChar = 15) {
1031
1426
  rafRef.current = null;
1032
1427
  }
1033
1428
  };
1034
- (0, import_react5.useEffect)(() => {
1429
+ (0, import_react6.useEffect)(() => {
1035
1430
  if (drainTargetRef.current !== "" && indexRef.current < drainTargetRef.current.length && rafRef.current === null) {
1036
1431
  rafRef.current = requestAnimationFrame((t) => tickRef.current(t));
1037
1432
  }
1038
1433
  }, [drainTarget]);
1039
- (0, import_react5.useEffect)(() => {
1434
+ (0, import_react6.useEffect)(() => {
1040
1435
  if (target === "" && !isDraining && displayed !== "") {
1041
1436
  indexRef.current = 0;
1042
1437
  lastTimeRef.current = 0;
@@ -1044,7 +1439,7 @@ function useCharacterDrain(target, msPerChar = 15) {
1044
1439
  setDisplayed("");
1045
1440
  }
1046
1441
  }, [target, isDraining, displayed]);
1047
- (0, import_react5.useEffect)(() => {
1442
+ (0, import_react6.useEffect)(() => {
1048
1443
  return () => {
1049
1444
  if (rafRef.current !== null) {
1050
1445
  cancelAnimationFrame(rafRef.current);
@@ -1065,51 +1460,77 @@ var phaseLabels = {
1065
1460
  generating: "Writing...",
1066
1461
  verifying: "Verifying..."
1067
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
+ `;
1068
1481
  function StreamingMessage({
1069
1482
  stream,
1070
1483
  onComplete,
1484
+ onDraining,
1071
1485
  showPhases = true,
1072
1486
  className
1073
1487
  }) {
1074
- const onCompleteRef = (0, import_react6.useRef)(onComplete);
1488
+ const onCompleteRef = (0, import_react7.useRef)(onComplete);
1075
1489
  onCompleteRef.current = onComplete;
1076
- const wasActiveRef = (0, import_react6.useRef)(stream.active);
1077
- (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)(() => {
1078
1494
  if (wasActiveRef.current && !stream.active) {
1079
1495
  onCompleteRef.current?.();
1080
1496
  }
1081
1497
  wasActiveRef.current = stream.active;
1082
1498
  }, [stream.active]);
1083
1499
  const phaseLabel = phaseLabels[stream.phase];
1084
- const { displayed: displayedContent } = useCharacterDrain(stream.content);
1085
- 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: [
1086
1509
  /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { "aria-live": "assertive", className: "sr-only", children: [
1087
1510
  stream.active && stream.phase !== "idle" && "Response started",
1088
1511
  !stream.active && stream.content && "Response complete"
1089
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 }),
1090
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: [
1091
- showPhases && stream.active && stream.phase !== "idle" && /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
1516
+ showPhaseIndicator && /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
1092
1517
  "div",
1093
1518
  {
1094
- 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",
1095
1520
  "data-testid": "phase-indicator",
1096
1521
  children: [
1097
- /* @__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" }) }),
1098
1523
  /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { children: phaseLabel })
1099
1524
  ]
1100
1525
  }
1101
1526
  ),
1102
- /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "text-sm leading-relaxed text-text-primary whitespace-pre-wrap", children: [
1103
- displayedContent,
1104
- stream.active && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
1105
- "span",
1106
- {
1107
- className: "inline-block w-0.5 h-4 bg-accent align-text-bottom animate-pulse ml-0.5",
1108
- "aria-hidden": "true",
1109
- "data-testid": "streaming-cursor"
1110
- }
1111
- )
1112
- ] })
1527
+ displayedContent && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
1528
+ ResponseMessage,
1529
+ {
1530
+ content: displayedContent,
1531
+ className: showCursor ? "sk-streaming-cursor" : void 0
1532
+ }
1533
+ )
1113
1534
  ] })
1114
1535
  ] });
1115
1536
  }
@@ -1140,7 +1561,7 @@ function AgentChat({
1140
1561
  return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
1141
1562
  "div",
1142
1563
  {
1143
- className: (0, import_tailwind_merge8.twMerge)(
1564
+ className: (0, import_tailwind_merge9.twMerge)(
1144
1565
  "flex flex-col h-full bg-canvas border border-border rounded-xl overflow-hidden",
1145
1566
  className
1146
1567
  ),
@@ -1183,7 +1604,7 @@ function AgentChat({
1183
1604
  }
1184
1605
 
1185
1606
  // src/chat/ConversationList/ConversationList.tsx
1186
- var import_tailwind_merge9 = require("tailwind-merge");
1607
+ var import_tailwind_merge10 = require("tailwind-merge");
1187
1608
  var import_jsx_runtime13 = require("react/jsx-runtime");
1188
1609
  function ConversationList({
1189
1610
  conversations,
@@ -1197,7 +1618,7 @@ function ConversationList({
1197
1618
  "nav",
1198
1619
  {
1199
1620
  "aria-label": "Conversation list",
1200
- 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),
1201
1622
  children: [
1202
1623
  onNew && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { className: "p-3 border-b border-border", children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
1203
1624
  "button",
@@ -1214,7 +1635,7 @@ function ConversationList({
1214
1635
  return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
1215
1636
  "li",
1216
1637
  {
1217
- className: (0, import_tailwind_merge9.twMerge)(
1638
+ className: (0, import_tailwind_merge10.twMerge)(
1218
1639
  "flex items-start border-b border-border transition-colors duration-200",
1219
1640
  "hover:bg-surface",
1220
1641
  isActive && "bg-surface-raised border-l-2 border-l-accent"
@@ -1286,8 +1707,8 @@ function AgentFullPage({
1286
1707
  onNewConversation,
1287
1708
  className
1288
1709
  }) {
1289
- const [sidebarOpen, setSidebarOpen] = (0, import_react7.useState)(false);
1290
- const handleSelect = (0, import_react7.useCallback)(
1710
+ const [sidebarOpen, setSidebarOpen] = (0, import_react8.useState)(false);
1711
+ const handleSelect = (0, import_react8.useCallback)(
1291
1712
  (id) => {
1292
1713
  onConversationSelect?.(id);
1293
1714
  setSidebarOpen(false);
@@ -1297,7 +1718,7 @@ function AgentFullPage({
1297
1718
  return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(
1298
1719
  "div",
1299
1720
  {
1300
- 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),
1301
1722
  "data-testid": "agent-full-page",
1302
1723
  children: [
1303
1724
  showConversationList && /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(import_jsx_runtime14.Fragment, { children: [
@@ -1312,7 +1733,7 @@ function AgentFullPage({
1312
1733
  /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
1313
1734
  "aside",
1314
1735
  {
1315
- className: (0, import_tailwind_merge10.twMerge)(
1736
+ className: (0, import_tailwind_merge11.twMerge)(
1316
1737
  "bg-brand-dark border-r border-brand-gold/15 w-72 shrink-0 flex-col z-40",
1317
1738
  // Desktop: always visible
1318
1739
  "hidden md:flex",
@@ -1378,8 +1799,8 @@ function AgentFullPage({
1378
1799
  }
1379
1800
 
1380
1801
  // src/layouts/AgentPanel/AgentPanel.tsx
1381
- var import_tailwind_merge11 = require("tailwind-merge");
1382
- var import_react8 = require("react");
1802
+ var import_tailwind_merge12 = require("tailwind-merge");
1803
+ var import_react9 = require("react");
1383
1804
  var import_jsx_runtime15 = require("react/jsx-runtime");
1384
1805
  function AgentPanel({
1385
1806
  endpoint,
@@ -1390,8 +1811,8 @@ function AgentPanel({
1390
1811
  title = "Chat",
1391
1812
  className
1392
1813
  }) {
1393
- const panelRef = (0, import_react8.useRef)(null);
1394
- (0, import_react8.useEffect)(() => {
1814
+ const panelRef = (0, import_react9.useRef)(null);
1815
+ (0, import_react9.useEffect)(() => {
1395
1816
  if (!isOpen) return;
1396
1817
  const handleKeyDown = (e) => {
1397
1818
  if (e.key === "Escape") onClose();
@@ -1403,13 +1824,13 @@ function AgentPanel({
1403
1824
  return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(
1404
1825
  "div",
1405
1826
  {
1406
- 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"),
1407
1828
  "aria-hidden": !isOpen,
1408
1829
  children: [
1409
1830
  /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
1410
1831
  "div",
1411
1832
  {
1412
- className: (0, import_tailwind_merge11.twMerge)(
1833
+ className: (0, import_tailwind_merge12.twMerge)(
1413
1834
  "fixed inset-0 transition-opacity duration-300",
1414
1835
  isOpen ? "opacity-100 bg-brand-dark/70 backdrop-blur-sm pointer-events-auto" : "opacity-0 pointer-events-none"
1415
1836
  ),
@@ -1425,7 +1846,7 @@ function AgentPanel({
1425
1846
  "aria-label": title,
1426
1847
  "aria-modal": isOpen ? "true" : void 0,
1427
1848
  style: { width: widthStyle, maxWidth: "100vw" },
1428
- className: (0, import_tailwind_merge11.twMerge)(
1849
+ className: (0, import_tailwind_merge12.twMerge)(
1429
1850
  "fixed top-0 h-full flex flex-col z-50 bg-brand-dark shadow-card",
1430
1851
  "transition-transform duration-300 ease-in-out",
1431
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"}`,
@@ -1467,8 +1888,8 @@ function AgentPanel({
1467
1888
  }
1468
1889
 
1469
1890
  // src/layouts/AgentWidget/AgentWidget.tsx
1470
- var import_tailwind_merge12 = require("tailwind-merge");
1471
- var import_react9 = require("react");
1891
+ var import_tailwind_merge13 = require("tailwind-merge");
1892
+ var import_react10 = require("react");
1472
1893
  var import_jsx_runtime16 = require("react/jsx-runtime");
1473
1894
  function AgentWidget({
1474
1895
  endpoint,
@@ -1477,8 +1898,8 @@ function AgentWidget({
1477
1898
  title = "Chat",
1478
1899
  className
1479
1900
  }) {
1480
- const [isOpen, setIsOpen] = (0, import_react9.useState)(false);
1481
- const toggle = (0, import_react9.useCallback)(() => {
1901
+ const [isOpen, setIsOpen] = (0, import_react10.useState)(false);
1902
+ const toggle = (0, import_react10.useCallback)(() => {
1482
1903
  setIsOpen((prev) => !prev);
1483
1904
  }, []);
1484
1905
  const positionClasses = position === "bottom-left" ? "left-4 bottom-4" : "right-4 bottom-4";
@@ -1491,7 +1912,7 @@ function AgentWidget({
1491
1912
  role: "dialog",
1492
1913
  "aria-label": title,
1493
1914
  "aria-hidden": !isOpen,
1494
- className: (0, import_tailwind_merge12.twMerge)(
1915
+ className: (0, import_tailwind_merge13.twMerge)(
1495
1916
  "fixed z-50 flex flex-col",
1496
1917
  "w-[min(400px,calc(100vw-2rem))] h-[min(600px,calc(100vh-6rem))]",
1497
1918
  "rounded-2xl overflow-hidden border border-brand-gold/15",
@@ -1538,7 +1959,7 @@ function AgentWidget({
1538
1959
  onClick: toggle,
1539
1960
  "aria-label": isOpen ? "Close chat" : triggerLabel,
1540
1961
  "aria-expanded": isOpen,
1541
- className: (0, import_tailwind_merge12.twMerge)(
1962
+ className: (0, import_tailwind_merge13.twMerge)(
1542
1963
  "fixed z-50 flex items-center justify-center w-14 h-14 rounded-full",
1543
1964
  "bg-brand-blue text-brand-cream shadow-glow-cyan",
1544
1965
  "hover:bg-brand-cyan hover:shadow-glow-cyan hover:scale-105",
@@ -1556,7 +1977,7 @@ function AgentWidget({
1556
1977
  }
1557
1978
 
1558
1979
  // src/layouts/AgentEmbed/AgentEmbed.tsx
1559
- var import_tailwind_merge13 = require("tailwind-merge");
1980
+ var import_tailwind_merge14 = require("tailwind-merge");
1560
1981
  var import_jsx_runtime17 = require("react/jsx-runtime");
1561
1982
  function AgentEmbed({
1562
1983
  endpoint,
@@ -1566,7 +1987,7 @@ function AgentEmbed({
1566
1987
  return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
1567
1988
  "div",
1568
1989
  {
1569
- 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),
1570
1991
  "data-testid": "agent-embed",
1571
1992
  children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
1572
1993
  AgentChat,