@surf-kit/agent 0.2.2 → 0.4.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 +733 -222
  2. package/dist/chat/index.cjs.map +1 -1
  3. package/dist/chat/index.d.cts +13 -7
  4. package/dist/chat/index.d.ts +13 -7
  5. package/dist/chat/index.js +715 -204
  6. package/dist/chat/index.js.map +1 -1
  7. package/dist/{chat-ChYl2XjV.d.cts → chat-BRY3xGg_.d.cts} +11 -2
  8. package/dist/{chat--OifhIRe.d.ts → chat-CcKc6OAR.d.ts} +11 -2
  9. package/dist/{hooks-BGs8-4GK.d.ts → hooks-BLeiVk-x.d.ts} +22 -5
  10. package/dist/{hooks-DLfF18IU.d.cts → hooks-CSGGLd7j.d.cts} +22 -5
  11. package/dist/hooks.cjs +160 -85
  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 +160 -85
  16. package/dist/hooks.js.map +1 -1
  17. package/dist/index.cjs +794 -283
  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 +758 -247
  22. package/dist/index.js.map +1 -1
  23. package/dist/layouts/index.cjs +754 -243
  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 +733 -222
  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 +100 -15
  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 +99 -14
  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 +213 -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 +183 -73
  48. package/dist/streaming/index.js.map +1 -1
  49. package/dist/{streaming-DfT22A0z.d.cts → streaming-BHPXnwwo.d.cts} +3 -1
  50. package/dist/{streaming-DbQxScpi.d.ts → streaming-C6mbU7My.d.ts} +3 -1
  51. package/package.json +17 -5
package/dist/index.cjs CHANGED
@@ -81,7 +81,7 @@ __export(index_exports, {
81
81
  module.exports = __toCommonJS(index_exports);
82
82
 
83
83
  // src/chat/AgentChat/AgentChat.tsx
84
- var import_tailwind_merge8 = require("tailwind-merge");
84
+ var import_tailwind_merge9 = require("tailwind-merge");
85
85
 
86
86
  // src/hooks/useAgentChat.ts
87
87
  var import_react = require("react");
@@ -92,7 +92,8 @@ var initialState = {
92
92
  error: null,
93
93
  inputValue: "",
94
94
  streamPhase: "idle",
95
- streamingContent: ""
95
+ streamingContent: "",
96
+ streamingAgent: null
96
97
  };
97
98
  function reducer(state, action) {
98
99
  switch (action.type) {
@@ -106,12 +107,17 @@ function reducer(state, action) {
106
107
  error: null,
107
108
  inputValue: "",
108
109
  streamPhase: "thinking",
109
- streamingContent: ""
110
+ streamingContent: "",
111
+ streamingAgent: null
110
112
  };
111
113
  case "STREAM_PHASE":
112
114
  return { ...state, streamPhase: action.phase };
113
115
  case "STREAM_CONTENT":
114
116
  return { ...state, streamingContent: state.streamingContent + action.content };
117
+ case "STREAM_CONTENT_RESET":
118
+ return { ...state, streamingContent: "" };
119
+ case "STREAM_AGENT":
120
+ return { ...state, streamingAgent: action.agent };
115
121
  case "SEND_SUCCESS":
116
122
  return {
117
123
  ...state,
@@ -127,7 +133,8 @@ function reducer(state, action) {
127
133
  isLoading: false,
128
134
  error: action.error,
129
135
  streamPhase: "idle",
130
- streamingContent: ""
136
+ streamingContent: "",
137
+ streamingAgent: null
131
138
  };
132
139
  case "LOAD_CONVERSATION":
133
140
  return {
@@ -153,115 +160,172 @@ function useAgentChat(config2) {
153
160
  const configRef = (0, import_react.useRef)(config2);
154
161
  configRef.current = config2;
155
162
  const lastUserMessageRef = (0, import_react.useRef)(null);
163
+ const lastUserAttachmentsRef = (0, import_react.useRef)(void 0);
164
+ const abortControllerRef = (0, import_react.useRef)(null);
156
165
  const sendMessage = (0, import_react.useCallback)(
157
- async (content) => {
158
- const { apiUrl, streamPath = "/chat/stream", headers = {}, timeout = 3e4 } = configRef.current;
166
+ async (content, attachments) => {
167
+ const { apiUrl, streamPath = "/chat/stream", headers: headersOrFn, timeout = 3e4, bodyExtra } = configRef.current;
168
+ const headers = typeof headersOrFn === "function" ? await headersOrFn() : headersOrFn ?? {};
159
169
  lastUserMessageRef.current = content;
170
+ lastUserAttachmentsRef.current = attachments;
160
171
  const userMessage = {
161
172
  id: generateMessageId(),
162
173
  role: "user",
163
174
  content,
175
+ attachments,
164
176
  timestamp: /* @__PURE__ */ new Date()
165
177
  };
166
178
  dispatch({ type: "SEND_START", message: userMessage });
167
179
  const controller = new AbortController();
180
+ abortControllerRef.current = controller;
168
181
  const timeoutId = setTimeout(() => controller.abort(), timeout);
182
+ const ctx = {
183
+ accumulatedContent: "",
184
+ agentResponse: null,
185
+ capturedAgent: null,
186
+ capturedConversationId: null,
187
+ hadStreamError: false
188
+ };
169
189
  try {
170
- const response = await fetch(`${apiUrl}${streamPath}`, {
171
- method: "POST",
172
- headers: {
173
- "Content-Type": "application/json",
174
- Accept: "text/event-stream",
175
- ...headers
176
- },
177
- body: JSON.stringify({
178
- message: content,
179
- conversation_id: state.conversationId
180
- }),
181
- signal: controller.signal
182
- });
183
- clearTimeout(timeoutId);
184
- if (!response.ok) {
185
- dispatch({
186
- type: "SEND_ERROR",
187
- error: {
188
- code: "API_ERROR",
189
- message: `HTTP ${response.status}: ${response.statusText}`,
190
- retryable: response.status >= 500
191
- }
192
- });
193
- return;
190
+ const url = `${apiUrl}${streamPath}`;
191
+ const mergedHeaders = {
192
+ "Content-Type": "application/json",
193
+ Accept: "text/event-stream",
194
+ ...headers
195
+ };
196
+ const requestBody = {
197
+ message: content,
198
+ conversation_id: state.conversationId,
199
+ ...bodyExtra
200
+ };
201
+ if (attachments && attachments.length > 0) {
202
+ requestBody.attachments = attachments.map((a) => ({
203
+ filename: a.filename,
204
+ content_type: a.content_type,
205
+ data: a.data
206
+ }));
194
207
  }
195
- const reader = response.body?.getReader();
196
- if (!reader) {
197
- dispatch({
198
- type: "SEND_ERROR",
199
- error: { code: "STREAM_ERROR", message: "No response body", retryable: true }
208
+ const body = JSON.stringify(requestBody);
209
+ const handleEvent = (event) => {
210
+ switch (event.type) {
211
+ case "agent":
212
+ ctx.capturedAgent = event.agent;
213
+ dispatch({ type: "STREAM_AGENT", agent: ctx.capturedAgent });
214
+ break;
215
+ case "phase":
216
+ dispatch({ type: "STREAM_PHASE", phase: event.phase });
217
+ break;
218
+ case "delta":
219
+ ctx.accumulatedContent += event.content;
220
+ dispatch({ type: "STREAM_CONTENT", content: event.content });
221
+ break;
222
+ case "delta_reset":
223
+ ctx.accumulatedContent = "";
224
+ dispatch({ type: "STREAM_CONTENT_RESET" });
225
+ break;
226
+ case "done":
227
+ ctx.agentResponse = event.response;
228
+ ctx.capturedConversationId = event.conversation_id ?? null;
229
+ break;
230
+ case "error":
231
+ ctx.hadStreamError = true;
232
+ dispatch({ type: "SEND_ERROR", error: event.error });
233
+ break;
234
+ }
235
+ };
236
+ const { streamAdapter } = configRef.current;
237
+ if (streamAdapter) {
238
+ await streamAdapter(
239
+ url,
240
+ { method: "POST", headers: mergedHeaders, body, signal: controller.signal },
241
+ handleEvent
242
+ );
243
+ clearTimeout(timeoutId);
244
+ } else {
245
+ const response = await fetch(url, {
246
+ method: "POST",
247
+ headers: mergedHeaders,
248
+ body,
249
+ signal: controller.signal
200
250
  });
201
- return;
202
- }
203
- const decoder = new TextDecoder();
204
- let buffer = "";
205
- let accumulatedContent = "";
206
- let agentResponse = null;
207
- let capturedAgent = null;
208
- let capturedConversationId = null;
209
- while (true) {
210
- const { done, value } = await reader.read();
211
- if (done) break;
212
- buffer += decoder.decode(value, { stream: true });
213
- const lines = buffer.split("\n");
214
- buffer = lines.pop() ?? "";
215
- for (const line of lines) {
216
- if (!line.startsWith("data: ")) continue;
217
- const data = line.slice(6).trim();
218
- if (data === "[DONE]") continue;
219
- try {
220
- const event = JSON.parse(data);
221
- switch (event.type) {
222
- case "agent":
223
- capturedAgent = event.agent;
224
- break;
225
- case "phase":
226
- dispatch({ type: "STREAM_PHASE", phase: event.phase });
227
- break;
228
- case "delta":
229
- accumulatedContent += event.content;
230
- dispatch({ type: "STREAM_CONTENT", content: event.content });
231
- break;
232
- case "done":
233
- agentResponse = event.response;
234
- capturedConversationId = event.conversation_id ?? null;
235
- break;
236
- case "error":
237
- dispatch({ type: "SEND_ERROR", error: event.error });
238
- return;
251
+ clearTimeout(timeoutId);
252
+ if (!response.ok) {
253
+ dispatch({
254
+ type: "SEND_ERROR",
255
+ error: {
256
+ code: "API_ERROR",
257
+ message: `HTTP ${response.status}: ${response.statusText}`,
258
+ retryable: response.status >= 500
259
+ }
260
+ });
261
+ return;
262
+ }
263
+ const reader = response.body?.getReader();
264
+ if (!reader) {
265
+ dispatch({
266
+ type: "SEND_ERROR",
267
+ error: { code: "STREAM_ERROR", message: "No response body", retryable: true }
268
+ });
269
+ return;
270
+ }
271
+ const decoder = new TextDecoder();
272
+ let buffer = "";
273
+ while (true) {
274
+ const { done, value } = await reader.read();
275
+ if (done) break;
276
+ buffer += decoder.decode(value, { stream: true });
277
+ const lines = buffer.split("\n");
278
+ buffer = lines.pop() ?? "";
279
+ for (const line of lines) {
280
+ if (!line.startsWith("data: ")) continue;
281
+ const data = line.slice(6).trim();
282
+ if (data === "[DONE]") continue;
283
+ try {
284
+ const event = JSON.parse(data);
285
+ handleEvent(event);
286
+ } catch {
239
287
  }
240
- } catch {
241
288
  }
242
289
  }
243
290
  }
291
+ if (ctx.hadStreamError) return;
244
292
  const assistantMessage = {
245
293
  id: generateMessageId(),
246
294
  role: "assistant",
247
- content: agentResponse?.message ?? accumulatedContent,
248
- response: agentResponse ?? void 0,
249
- agent: capturedAgent ?? void 0,
295
+ content: ctx.agentResponse?.message ?? ctx.accumulatedContent,
296
+ response: ctx.agentResponse ?? void 0,
297
+ agent: ctx.capturedAgent ?? void 0,
250
298
  timestamp: /* @__PURE__ */ new Date()
251
299
  };
252
300
  dispatch({
253
301
  type: "SEND_SUCCESS",
254
302
  message: assistantMessage,
255
- streamingContent: accumulatedContent,
256
- conversationId: capturedConversationId
303
+ streamingContent: ctx.accumulatedContent,
304
+ conversationId: ctx.capturedConversationId
257
305
  });
258
306
  } catch (err) {
259
307
  clearTimeout(timeoutId);
260
308
  if (err.name === "AbortError") {
261
- dispatch({
262
- type: "SEND_ERROR",
263
- error: { code: "TIMEOUT", message: "Request timed out", retryable: true }
264
- });
309
+ if (ctx.accumulatedContent) {
310
+ const partialMessage = {
311
+ id: generateMessageId(),
312
+ role: "assistant",
313
+ content: ctx.accumulatedContent,
314
+ agent: ctx.capturedAgent ?? void 0,
315
+ timestamp: /* @__PURE__ */ new Date()
316
+ };
317
+ dispatch({
318
+ type: "SEND_SUCCESS",
319
+ message: partialMessage,
320
+ streamingContent: ctx.accumulatedContent,
321
+ conversationId: ctx.capturedConversationId
322
+ });
323
+ } else {
324
+ dispatch({
325
+ type: "SEND_ERROR",
326
+ error: { code: "ABORTED", message: "Request stopped", retryable: true }
327
+ });
328
+ }
265
329
  } else {
266
330
  dispatch({
267
331
  type: "SEND_ERROR",
@@ -272,6 +336,8 @@ function useAgentChat(config2) {
272
336
  }
273
337
  });
274
338
  }
339
+ } finally {
340
+ abortControllerRef.current = null;
275
341
  }
276
342
  },
277
343
  [state.conversationId]
@@ -284,7 +350,8 @@ function useAgentChat(config2) {
284
350
  }, []);
285
351
  const submitFeedback = (0, import_react.useCallback)(
286
352
  async (messageId, rating, comment) => {
287
- const { apiUrl, feedbackPath = "/feedback", headers = {} } = configRef.current;
353
+ const { apiUrl, feedbackPath = "/feedback", headers: headersOrFn } = configRef.current;
354
+ const headers = typeof headersOrFn === "function" ? await headersOrFn() : headersOrFn ?? {};
288
355
  await fetch(`${apiUrl}${feedbackPath}`, {
289
356
  method: "POST",
290
357
  headers: { "Content-Type": "application/json", ...headers },
@@ -295,12 +362,16 @@ function useAgentChat(config2) {
295
362
  );
296
363
  const retry = (0, import_react.useCallback)(async () => {
297
364
  if (lastUserMessageRef.current) {
298
- await sendMessage(lastUserMessageRef.current);
365
+ await sendMessage(lastUserMessageRef.current, lastUserAttachmentsRef.current);
299
366
  }
300
367
  }, [sendMessage]);
368
+ const stop = (0, import_react.useCallback)(() => {
369
+ abortControllerRef.current?.abort();
370
+ }, []);
301
371
  const reset = (0, import_react.useCallback)(() => {
302
372
  dispatch({ type: "RESET" });
303
373
  lastUserMessageRef.current = null;
374
+ lastUserAttachmentsRef.current = void 0;
304
375
  }, []);
305
376
  const actions = {
306
377
  sendMessage,
@@ -308,6 +379,7 @@ function useAgentChat(config2) {
308
379
  loadConversation,
309
380
  submitFeedback,
310
381
  retry,
382
+ stop,
311
383
  reset
312
384
  };
313
385
  return { state, actions };
@@ -315,7 +387,7 @@ function useAgentChat(config2) {
315
387
 
316
388
  // src/chat/MessageThread/MessageThread.tsx
317
389
  var import_tailwind_merge5 = require("tailwind-merge");
318
- var import_react3 = require("react");
390
+ var import_react4 = require("react");
319
391
 
320
392
  // src/chat/MessageBubble/MessageBubble.tsx
321
393
  var import_tailwind_merge4 = require("tailwind-merge");
@@ -324,8 +396,10 @@ var import_tailwind_merge4 = require("tailwind-merge");
324
396
  var import_core2 = require("@surf-kit/core");
325
397
 
326
398
  // src/response/ResponseMessage/ResponseMessage.tsx
399
+ var import_react2 = __toESM(require("react"), 1);
327
400
  var import_react_markdown = __toESM(require("react-markdown"), 1);
328
401
  var import_rehype_sanitize = __toESM(require("rehype-sanitize"), 1);
402
+ var import_remark_gfm = __toESM(require("remark-gfm"), 1);
329
403
  var import_tailwind_merge = require("tailwind-merge");
330
404
  var import_jsx_runtime = require("react/jsx-runtime");
331
405
  function normalizeMarkdownLists(content) {
@@ -348,7 +422,12 @@ function ResponseMessage({ content, className }) {
348
422
  "[&_h3]:text-sm [&_h3]:font-semibold [&_h3]:text-accent [&_h3]:mt-2 [&_h3]:mb-1",
349
423
  "[&_code]:bg-surface-raised [&_code]:text-accent [&_code]:px-1.5 [&_code]:py-0.5 [&_code]:rounded [&_code]:text-xs [&_code]:font-mono",
350
424
  "[&_pre]:bg-surface-raised [&_pre]:border [&_pre]:border-border [&_pre]:rounded-xl [&_pre]:p-4 [&_pre]:overflow-x-auto",
425
+ "[&_hr]:my-3 [&_hr]:border-border",
351
426
  "[&_blockquote]:border-l-2 [&_blockquote]:border-border-strong [&_blockquote]:pl-4 [&_blockquote]:text-text-secondary",
427
+ "[&_table]:w-full [&_table]:text-sm [&_table]:border-collapse [&_table]:my-2",
428
+ "[&_thead]:border-b [&_thead]:border-border",
429
+ "[&_th]:text-left [&_th]:px-2 [&_th]:py-1.5 [&_th]:font-semibold",
430
+ "[&_td]:px-2 [&_td]:py-1.5 [&_td]:border-t [&_td]:border-border/50",
352
431
  "[&_a]:text-accent [&_a]:underline-offset-2 [&_a]:hover:text-accent/80",
353
432
  className
354
433
  ),
@@ -356,6 +435,7 @@ function ResponseMessage({ content, className }) {
356
435
  children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
357
436
  import_react_markdown.default,
358
437
  {
438
+ remarkPlugins: [import_remark_gfm.default],
359
439
  rehypePlugins: [import_rehype_sanitize.default],
360
440
  components: {
361
441
  script: () => null,
@@ -363,12 +443,29 @@ function ResponseMessage({ content, className }) {
363
443
  p: ({ children }) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "my-2", children }),
364
444
  ul: ({ children }) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("ul", { className: "my-2 list-disc pl-6", children }),
365
445
  ol: ({ children }) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("ol", { className: "my-2 list-decimal pl-6", children }),
366
- li: ({ children }) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("li", { className: "my-1", children }),
446
+ li: ({ children, ...props }) => {
447
+ let content2 = children;
448
+ if (props.ordered) {
449
+ content2 = import_react2.default.Children.map(children, (child, i) => {
450
+ if (i === 0 && typeof child === "string") {
451
+ return child.replace(/^\d+[.)]\s*/, "");
452
+ }
453
+ return child;
454
+ });
455
+ }
456
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("li", { className: "my-1", children: content2 });
457
+ },
367
458
  strong: ({ children }) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("strong", { className: "font-semibold", children }),
459
+ em: ({ children }) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("em", { className: "italic text-text-secondary", children }),
368
460
  h1: ({ children }) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h1", { className: "text-base font-bold mt-4 mb-2", children }),
369
461
  h2: ({ children }) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h2", { className: "text-sm font-bold mt-3 mb-1", children }),
370
462
  h3: ({ children }) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h3", { className: "text-sm font-semibold mt-2 mb-1", children }),
371
- code: ({ children }) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("code", { className: "bg-surface-sunken rounded px-1 py-0.5 text-xs font-mono", children })
463
+ hr: () => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("hr", { className: "my-3 border-border" }),
464
+ code: ({ children }) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("code", { className: "bg-surface-sunken rounded px-1 py-0.5 text-xs font-mono", children }),
465
+ table: ({ children }) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "overflow-x-auto my-2", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("table", { className: "w-full text-sm border-collapse", children }) }),
466
+ thead: ({ children }) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("thead", { className: "border-b border-border", children }),
467
+ th: ({ children }) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", { className: "text-left px-2 py-1.5 font-semibold", children }),
468
+ td: ({ children }) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", { className: "px-2 py-1.5 border-t border-border/50", children })
372
469
  },
373
470
  children: normalizeMarkdownLists(content)
374
471
  }
@@ -378,6 +475,8 @@ function ResponseMessage({ content, className }) {
378
475
  }
379
476
 
380
477
  // src/response/StructuredResponse/StructuredResponse.tsx
478
+ var import_react_markdown2 = __toESM(require("react-markdown"), 1);
479
+ var import_rehype_sanitize2 = __toESM(require("rehype-sanitize"), 1);
381
480
  var import_jsx_runtime2 = require("react/jsx-runtime");
382
481
  function tryParse(value) {
383
482
  if (value === void 0 || value === null) return null;
@@ -390,6 +489,25 @@ function tryParse(value) {
390
489
  }
391
490
  return value;
392
491
  }
492
+ function InlineMarkdown({ text }) {
493
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
494
+ import_react_markdown2.default,
495
+ {
496
+ rehypePlugins: [import_rehype_sanitize2.default],
497
+ components: {
498
+ // Unwrap block-level <p> so content stays inline within its parent
499
+ p: ({ children }) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_jsx_runtime2.Fragment, { children }),
500
+ strong: ({ children }) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("strong", { className: "font-semibold", children }),
501
+ em: ({ children }) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("em", { className: "italic", children }),
502
+ code: ({ children }) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("code", { className: "bg-surface-sunken rounded px-1 py-0.5 text-xs font-mono", children }),
503
+ // Prevent block elements that would break layout
504
+ script: () => null,
505
+ iframe: () => null
506
+ },
507
+ children: text
508
+ }
509
+ );
510
+ }
393
511
  function renderSteps(data) {
394
512
  const steps = tryParse(data.steps);
395
513
  if (!steps || !Array.isArray(steps)) return null;
@@ -402,7 +520,7 @@ function renderSteps(data) {
402
520
  children: i + 1
403
521
  }
404
522
  ),
405
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "text-sm text-text-primary leading-relaxed", children: step })
523
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "text-sm text-text-primary leading-relaxed", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(InlineMarkdown, { text: step }) })
406
524
  ] }, i)) });
407
525
  }
408
526
  function renderTable(data) {
@@ -468,7 +586,7 @@ function renderList(data) {
468
586
  title && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "text-xs font-semibold uppercase tracking-wider text-text-secondary mb-1", children: title }),
469
587
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("ul", { className: "flex flex-col gap-1.5", children: items.map((item, i) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("li", { className: "flex items-start gap-2.5", children: [
470
588
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "mt-1.5 h-1.5 w-1.5 shrink-0 rounded-full bg-accent", "aria-hidden": "true" }),
471
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "text-sm text-text-primary leading-relaxed", children: item })
589
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "text-sm text-text-primary leading-relaxed", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(InlineMarkdown, { text: item }) })
472
590
  ] }, i)) })
473
591
  ] });
474
592
  }
@@ -503,7 +621,14 @@ function renderWarning(data) {
503
621
  }
504
622
  );
505
623
  }
506
- function StructuredResponse({ uiHint, data, className }) {
624
+ function StructuredResponse({ uiHint, data: rawData, className }) {
625
+ const data = typeof rawData === "string" ? (() => {
626
+ try {
627
+ return JSON.parse(rawData);
628
+ } catch {
629
+ return null;
630
+ }
631
+ })() : rawData;
507
632
  if (!data) return null;
508
633
  let content;
509
634
  switch (uiHint) {
@@ -533,7 +658,7 @@ function StructuredResponse({ uiHint, data, className }) {
533
658
  }
534
659
 
535
660
  // src/sources/SourceList/SourceList.tsx
536
- var import_react2 = require("react");
661
+ var import_react3 = require("react");
537
662
 
538
663
  // src/sources/SourceCard/SourceCard.tsx
539
664
  var import_core = require("@surf-kit/core");
@@ -583,7 +708,36 @@ function SourceCard({ source, variant = "compact", onNavigate, className }) {
583
708
  children: [
584
709
  /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex items-start justify-between gap-2", children: [
585
710
  /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex-1 min-w-0", children: [
586
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { className: "text-sm font-medium text-text-primary truncate", children: source.title }),
711
+ source.url ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
712
+ "a",
713
+ {
714
+ href: source.url,
715
+ target: "_blank",
716
+ rel: "noopener noreferrer",
717
+ className: "text-sm font-medium text-accent hover:underline truncate block",
718
+ onClick: (e) => e.stopPropagation(),
719
+ children: [
720
+ source.title,
721
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
722
+ "svg",
723
+ {
724
+ className: "inline-block ml-1 w-3 h-3 opacity-60",
725
+ viewBox: "0 0 24 24",
726
+ fill: "none",
727
+ stroke: "currentColor",
728
+ strokeWidth: "2",
729
+ strokeLinecap: "round",
730
+ strokeLinejoin: "round",
731
+ children: [
732
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "M18 13v6a2 2 0 01-2 2H5a2 2 0 01-2-2V8a2 2 0 012-2h6" }),
733
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("polyline", { points: "15 3 21 3 21 9" }),
734
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("line", { x1: "10", y1: "14", x2: "21", y2: "3" })
735
+ ]
736
+ }
737
+ )
738
+ ]
739
+ }
740
+ ) : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { className: "text-sm font-medium text-text-primary truncate", children: source.title }),
587
741
  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 })
588
742
  ] }),
589
743
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
@@ -613,7 +767,7 @@ function SourceList({
613
767
  onNavigate,
614
768
  className
615
769
  }) {
616
- const [isExpanded, setIsExpanded] = (0, import_react2.useState)(defaultExpanded);
770
+ const [isExpanded, setIsExpanded] = (0, import_react3.useState)(defaultExpanded);
617
771
  if (sources.length === 0) return null;
618
772
  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)(
619
773
  SourceCard,
@@ -717,13 +871,16 @@ function AgentResponse({
717
871
  }) {
718
872
  return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: `flex flex-col gap-4 ${className ?? ""}`, "data-testid": "agent-response", children: [
719
873
  /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(ResponseMessage, { content: response.message }),
720
- response.ui_hint !== "text" && response.structured_data && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
721
- StructuredResponse,
722
- {
723
- uiHint: response.ui_hint,
724
- data: response.structured_data
725
- }
726
- ),
874
+ response.ui_hint !== "text" && response.structured_data && (() => {
875
+ const parsed = typeof response.structured_data === "string" ? (() => {
876
+ try {
877
+ return JSON.parse(response.structured_data);
878
+ } catch {
879
+ return null;
880
+ }
881
+ })() : response.structured_data;
882
+ return parsed ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(StructuredResponse, { uiHint: response.ui_hint, data: parsed }) : null;
883
+ })(),
727
884
  (showConfidence || showVerification) && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "flex flex-wrap items-center gap-2 mt-1", "data-testid": "response-meta", children: [
728
885
  showConfidence && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
729
886
  import_core2.Badge,
@@ -774,6 +931,31 @@ function AgentResponse({
774
931
 
775
932
  // src/chat/MessageBubble/MessageBubble.tsx
776
933
  var import_jsx_runtime7 = require("react/jsx-runtime");
934
+ function DocumentIcon() {
935
+ 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: [
936
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("path", { d: "M14 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V8z" }),
937
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("polyline", { points: "14 2 14 8 20 8" }),
938
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("line", { x1: "16", y1: "13", x2: "8", y2: "13" }),
939
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("line", { x1: "16", y1: "17", x2: "8", y2: "17" })
940
+ ] });
941
+ }
942
+ function AttachmentThumbnail({ attachment }) {
943
+ const isImage = attachment.content_type.startsWith("image/");
944
+ if (isImage) {
945
+ 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)(
946
+ "img",
947
+ {
948
+ src: attachment.preview_url ?? `data:${attachment.content_type};base64,${attachment.data}`,
949
+ alt: attachment.filename,
950
+ className: "max-w-full max-h-[200px] object-contain"
951
+ }
952
+ ) });
953
+ }
954
+ 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: [
955
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(DocumentIcon, {}),
956
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "text-xs truncate max-w-[160px]", children: attachment.filename })
957
+ ] });
958
+ }
777
959
  function MessageBubble({
778
960
  message,
779
961
  showAgent,
@@ -781,23 +963,29 @@ function MessageBubble({
781
963
  showConfidence = true,
782
964
  showVerification = true,
783
965
  animated = true,
966
+ userBubbleClassName,
784
967
  className
785
968
  }) {
786
969
  const isUser = message.role === "user";
970
+ const hasAttachments = message.attachments && message.attachments.length > 0;
787
971
  if (isUser) {
788
972
  return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
789
973
  "div",
790
974
  {
791
975
  "data-message-id": message.id,
792
976
  className: (0, import_tailwind_merge4.twMerge)("flex w-full justify-end", className),
793
- children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
977
+ children: /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
794
978
  "div",
795
979
  {
796
980
  className: (0, import_tailwind_merge4.twMerge)(
797
- "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",
798
- animated && "motion-safe:animate-slideFromRight"
981
+ "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",
982
+ animated && "motion-safe:animate-slideFromRight",
983
+ userBubbleClassName
799
984
  ),
800
- children: message.content
985
+ children: [
986
+ 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}`)) }),
987
+ message.content
988
+ ]
801
989
  }
802
990
  )
803
991
  }
@@ -809,7 +997,7 @@ function MessageBubble({
809
997
  "data-message-id": message.id,
810
998
  className: (0, import_tailwind_merge4.twMerge)("flex w-full flex-col items-start gap-1.5", className),
811
999
  children: [
812
- 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("_", " ") }),
1000
+ 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("_", " ") }),
813
1001
  /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
814
1002
  "div",
815
1003
  {
@@ -835,34 +1023,97 @@ function MessageBubble({
835
1023
 
836
1024
  // src/chat/MessageThread/MessageThread.tsx
837
1025
  var import_jsx_runtime8 = require("react/jsx-runtime");
838
- function MessageThread({ messages, streamingSlot, showSources, showConfidence, showVerification, className }) {
839
- const bottomRef = (0, import_react3.useRef)(null);
840
- (0, import_react3.useEffect)(() => {
841
- bottomRef.current?.scrollIntoView?.({ behavior: "smooth" });
842
- }, [messages.length, streamingSlot]);
1026
+ function MessageThread({ messages, streamingSlot, showAgent, showSources, showConfidence, showVerification, hideLastAssistant, userBubbleClassName, className }) {
1027
+ const scrollRef = (0, import_react4.useRef)(null);
1028
+ const shouldAutoScroll = (0, import_react4.useRef)(true);
1029
+ const hasStreaming = !!streamingSlot;
1030
+ const scrollToBottom = (0, import_react4.useCallback)(() => {
1031
+ const el = scrollRef.current;
1032
+ if (el && shouldAutoScroll.current) {
1033
+ el.scrollTop = el.scrollHeight;
1034
+ }
1035
+ }, []);
1036
+ (0, import_react4.useEffect)(() => {
1037
+ const el = scrollRef.current;
1038
+ if (!el) return;
1039
+ const onWheel = (e) => {
1040
+ if (e.deltaY < 0) {
1041
+ shouldAutoScroll.current = false;
1042
+ }
1043
+ };
1044
+ const onPointerDown = () => {
1045
+ el.dataset.userPointer = "1";
1046
+ };
1047
+ const onPointerUp = () => {
1048
+ delete el.dataset.userPointer;
1049
+ };
1050
+ el.addEventListener("wheel", onWheel, { passive: true });
1051
+ el.addEventListener("pointerdown", onPointerDown);
1052
+ window.addEventListener("pointerup", onPointerUp);
1053
+ return () => {
1054
+ el.removeEventListener("wheel", onWheel);
1055
+ el.removeEventListener("pointerdown", onPointerDown);
1056
+ window.removeEventListener("pointerup", onPointerUp);
1057
+ };
1058
+ }, []);
1059
+ const handleScroll = (0, import_react4.useCallback)(() => {
1060
+ const el = scrollRef.current;
1061
+ if (!el) return;
1062
+ const nearBottom = el.scrollHeight - el.scrollTop - el.clientHeight < 80;
1063
+ if (nearBottom) {
1064
+ shouldAutoScroll.current = true;
1065
+ } else if (el.dataset.userPointer) {
1066
+ shouldAutoScroll.current = false;
1067
+ }
1068
+ }, []);
1069
+ (0, import_react4.useEffect)(scrollToBottom, [messages.length, scrollToBottom]);
1070
+ (0, import_react4.useEffect)(() => {
1071
+ if (!hasStreaming) return;
1072
+ let raf;
1073
+ const tick = () => {
1074
+ scrollToBottom();
1075
+ raf = requestAnimationFrame(tick);
1076
+ };
1077
+ raf = requestAnimationFrame(tick);
1078
+ return () => cancelAnimationFrame(raf);
1079
+ }, [hasStreaming, scrollToBottom]);
1080
+ (0, import_react4.useEffect)(() => {
1081
+ if (!hasStreaming) {
1082
+ shouldAutoScroll.current = true;
1083
+ }
1084
+ }, [hasStreaming]);
843
1085
  return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
844
1086
  "div",
845
1087
  {
1088
+ ref: scrollRef,
846
1089
  role: "log",
847
1090
  "aria-live": "polite",
848
1091
  "aria-label": "Message thread",
1092
+ onScroll: handleScroll,
849
1093
  className: (0, import_tailwind_merge5.twMerge)(
850
1094
  "flex flex-col gap-4 overflow-y-auto flex-1 px-4 py-6",
851
1095
  className
852
1096
  ),
853
1097
  children: [
854
- messages.map((message) => /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
855
- MessageBubble,
856
- {
857
- message,
858
- showSources,
859
- showConfidence,
860
- showVerification
861
- },
862
- message.id
863
- )),
864
- streamingSlot,
865
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { ref: bottomRef })
1098
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "flex-1 shrink-0" }),
1099
+ messages.map((message, i) => {
1100
+ if (hideLastAssistant && i === messages.length - 1 && message.role === "assistant") {
1101
+ return null;
1102
+ }
1103
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1104
+ MessageBubble,
1105
+ {
1106
+ message,
1107
+ showAgent,
1108
+ showSources,
1109
+ showConfidence,
1110
+ showVerification,
1111
+ userBubbleClassName
1112
+ },
1113
+ message.id
1114
+ );
1115
+ }),
1116
+ streamingSlot
866
1117
  ]
867
1118
  }
868
1119
  );
@@ -870,32 +1121,127 @@ function MessageThread({ messages, streamingSlot, showSources, showConfidence, s
870
1121
 
871
1122
  // src/chat/MessageComposer/MessageComposer.tsx
872
1123
  var import_tailwind_merge6 = require("tailwind-merge");
873
- var import_react4 = require("react");
1124
+ var import_react5 = require("react");
874
1125
  var import_jsx_runtime9 = require("react/jsx-runtime");
1126
+ var ALLOWED_TYPES = /* @__PURE__ */ new Set([
1127
+ "image/png",
1128
+ "image/jpeg",
1129
+ "image/gif",
1130
+ "image/webp",
1131
+ "application/pdf"
1132
+ ]);
1133
+ var MAX_FILE_SIZE = 10 * 1024 * 1024;
1134
+ var MAX_ATTACHMENTS = 5;
1135
+ function ArrowUpIcon() {
1136
+ 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: [
1137
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("path", { d: "M10 16V4" }),
1138
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("path", { d: "M4 10l6-6 6 6" })
1139
+ ] });
1140
+ }
1141
+ function StopIcon() {
1142
+ 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" }) });
1143
+ }
1144
+ function PaperclipIcon() {
1145
+ 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" }) });
1146
+ }
1147
+ function XIcon({ size = 14 }) {
1148
+ 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: [
1149
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("path", { d: "M18 6L6 18" }),
1150
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("path", { d: "M6 6l12 12" })
1151
+ ] });
1152
+ }
1153
+ function DocumentIcon2() {
1154
+ 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: [
1155
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("path", { d: "M14 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V8z" }),
1156
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("polyline", { points: "14 2 14 8 20 8" }),
1157
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("line", { x1: "16", y1: "13", x2: "8", y2: "13" }),
1158
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("line", { x1: "16", y1: "17", x2: "8", y2: "17" }),
1159
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("polyline", { points: "10 9 9 9 8 9" })
1160
+ ] });
1161
+ }
1162
+ function fileToBase64(file) {
1163
+ return new Promise((resolve, reject) => {
1164
+ const reader = new FileReader();
1165
+ reader.onload = () => {
1166
+ const result = reader.result;
1167
+ const base64 = result.split(",")[1];
1168
+ resolve(base64);
1169
+ };
1170
+ reader.onerror = reject;
1171
+ reader.readAsDataURL(file);
1172
+ });
1173
+ }
1174
+ function AttachmentPreview({
1175
+ attachment,
1176
+ onRemove
1177
+ }) {
1178
+ const isImage = attachment.content_type.startsWith("image/");
1179
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "relative group flex-shrink-0", children: [
1180
+ 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)(
1181
+ "img",
1182
+ {
1183
+ src: attachment.preview_url ?? `data:${attachment.content_type};base64,${attachment.data}`,
1184
+ alt: attachment.filename,
1185
+ className: "w-full h-full object-cover"
1186
+ }
1187
+ ) }) : /* @__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: [
1188
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "text-text-muted", children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(DocumentIcon2, {}) }),
1189
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "flex flex-col min-w-0", children: [
1190
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "text-xs text-text-primary truncate max-w-[120px]", children: attachment.filename }),
1191
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "text-[10px] text-text-muted", children: "PDF" })
1192
+ ] })
1193
+ ] }),
1194
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1195
+ "button",
1196
+ {
1197
+ type: "button",
1198
+ onClick: onRemove,
1199
+ className: (0, import_tailwind_merge6.twMerge)(
1200
+ "absolute -top-1.5 -right-1.5",
1201
+ "w-5 h-5 rounded-full",
1202
+ "bg-text-muted/80 text-white",
1203
+ "flex items-center justify-center",
1204
+ "opacity-0 group-hover:opacity-100",
1205
+ "transition-opacity duration-150",
1206
+ "hover:bg-text-primary"
1207
+ ),
1208
+ "aria-label": `Remove ${attachment.filename}`,
1209
+ children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(XIcon, { size: 10 })
1210
+ }
1211
+ )
1212
+ ] });
1213
+ }
875
1214
  function MessageComposer({
876
1215
  onSend,
1216
+ onStop,
877
1217
  isLoading = false,
878
1218
  placeholder = "Type a message...",
879
1219
  className
880
1220
  }) {
881
- const [value, setValue] = (0, import_react4.useState)("");
882
- const textareaRef = (0, import_react4.useRef)(null);
883
- const canSend = value.trim().length > 0 && !isLoading;
884
- const resetHeight = (0, import_react4.useCallback)(() => {
1221
+ const [value, setValue] = (0, import_react5.useState)("");
1222
+ const [attachments, setAttachments] = (0, import_react5.useState)([]);
1223
+ const [dragOver, setDragOver] = (0, import_react5.useState)(false);
1224
+ const textareaRef = (0, import_react5.useRef)(null);
1225
+ const fileInputRef = (0, import_react5.useRef)(null);
1226
+ const canSend = (value.trim().length > 0 || attachments.length > 0) && !isLoading;
1227
+ const resetHeight = (0, import_react5.useCallback)(() => {
885
1228
  const el = textareaRef.current;
886
1229
  if (el) {
887
1230
  el.style.height = "auto";
888
1231
  el.style.overflowY = "hidden";
889
1232
  }
890
1233
  }, []);
891
- const handleSend = (0, import_react4.useCallback)(() => {
1234
+ const handleSend = (0, import_react5.useCallback)(() => {
892
1235
  if (!canSend) return;
893
- onSend(value.trim());
1236
+ const message = value.trim() || (attachments.length > 0 ? "Please analyse the attached file(s)." : "");
1237
+ if (!message && attachments.length === 0) return;
1238
+ onSend(message, attachments.length > 0 ? attachments : void 0);
894
1239
  setValue("");
1240
+ setAttachments([]);
895
1241
  resetHeight();
896
1242
  textareaRef.current?.focus();
897
- }, [canSend, onSend, value, resetHeight]);
898
- const handleKeyDown = (0, import_react4.useCallback)(
1243
+ }, [canSend, onSend, value, attachments, resetHeight]);
1244
+ const handleKeyDown = (0, import_react5.useCallback)(
899
1245
  (e) => {
900
1246
  if (e.key === "Enter" && !e.shiftKey) {
901
1247
  e.preventDefault();
@@ -904,64 +1250,194 @@ function MessageComposer({
904
1250
  },
905
1251
  [handleSend]
906
1252
  );
907
- const handleChange = (0, import_react4.useCallback)(
1253
+ const handleChange = (0, import_react5.useCallback)(
908
1254
  (e) => {
909
1255
  setValue(e.target.value);
910
1256
  const el = e.target;
911
1257
  el.style.height = "auto";
912
- const capped = Math.min(el.scrollHeight, 128);
1258
+ const capped = Math.min(el.scrollHeight, 200);
913
1259
  el.style.height = `${capped}px`;
914
- el.style.overflowY = el.scrollHeight > 128 ? "auto" : "hidden";
1260
+ el.style.overflowY = el.scrollHeight > 200 ? "auto" : "hidden";
915
1261
  },
916
1262
  []
917
1263
  );
1264
+ const addFiles = (0, import_react5.useCallback)(async (files) => {
1265
+ const fileArray = Array.from(files);
1266
+ for (const file of fileArray) {
1267
+ if (attachments.length >= MAX_ATTACHMENTS) break;
1268
+ if (!ALLOWED_TYPES.has(file.type)) continue;
1269
+ if (file.size > MAX_FILE_SIZE) continue;
1270
+ try {
1271
+ const data = await fileToBase64(file);
1272
+ const previewUrl = file.type.startsWith("image/") ? URL.createObjectURL(file) : void 0;
1273
+ const attachment = {
1274
+ filename: file.name,
1275
+ content_type: file.type,
1276
+ data,
1277
+ preview_url: previewUrl
1278
+ };
1279
+ setAttachments((prev) => {
1280
+ if (prev.length >= MAX_ATTACHMENTS) return prev;
1281
+ return [...prev, attachment];
1282
+ });
1283
+ } catch {
1284
+ }
1285
+ }
1286
+ }, [attachments.length]);
1287
+ const handleFileSelect = (0, import_react5.useCallback)(() => {
1288
+ fileInputRef.current?.click();
1289
+ }, []);
1290
+ const handleFileInputChange = (0, import_react5.useCallback)(
1291
+ (e) => {
1292
+ if (e.target.files) {
1293
+ void addFiles(e.target.files);
1294
+ e.target.value = "";
1295
+ }
1296
+ },
1297
+ [addFiles]
1298
+ );
1299
+ const removeAttachment = (0, import_react5.useCallback)((index) => {
1300
+ setAttachments((prev) => {
1301
+ const removed = prev[index];
1302
+ if (removed?.preview_url) URL.revokeObjectURL(removed.preview_url);
1303
+ return prev.filter((_, i) => i !== index);
1304
+ });
1305
+ }, []);
1306
+ const handlePaste = (0, import_react5.useCallback)(
1307
+ (e) => {
1308
+ const items = e.clipboardData.items;
1309
+ const files = [];
1310
+ for (const item of items) {
1311
+ if (item.kind === "file" && ALLOWED_TYPES.has(item.type)) {
1312
+ const file = item.getAsFile();
1313
+ if (file) files.push(file);
1314
+ }
1315
+ }
1316
+ if (files.length > 0) {
1317
+ void addFiles(files);
1318
+ }
1319
+ },
1320
+ [addFiles]
1321
+ );
1322
+ const handleDragOver = (0, import_react5.useCallback)((e) => {
1323
+ e.preventDefault();
1324
+ e.stopPropagation();
1325
+ setDragOver(true);
1326
+ }, []);
1327
+ const handleDragLeave = (0, import_react5.useCallback)((e) => {
1328
+ e.preventDefault();
1329
+ e.stopPropagation();
1330
+ setDragOver(false);
1331
+ }, []);
1332
+ const handleDrop = (0, import_react5.useCallback)(
1333
+ (e) => {
1334
+ e.preventDefault();
1335
+ e.stopPropagation();
1336
+ setDragOver(false);
1337
+ if (e.dataTransfer.files.length > 0) {
1338
+ void addFiles(e.dataTransfer.files);
1339
+ }
1340
+ },
1341
+ [addFiles]
1342
+ );
918
1343
  return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
919
1344
  "div",
920
1345
  {
921
1346
  className: (0, import_tailwind_merge6.twMerge)(
922
- "flex items-end gap-3 shrink-0 border-t border-border px-4 py-3",
1347
+ "relative shrink-0 rounded-3xl border bg-surface",
1348
+ "shadow-lg shadow-black/10",
1349
+ "transition-all duration-200",
1350
+ "focus-within:border-accent/40 focus-within:shadow-accent/5",
1351
+ dragOver ? "border-accent/60 bg-accent/5" : "border-border/60",
923
1352
  className
924
1353
  ),
1354
+ onDragOver: handleDragOver,
1355
+ onDragLeave: handleDragLeave,
1356
+ onDrop: handleDrop,
925
1357
  children: [
926
1358
  /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
927
- "textarea",
1359
+ "input",
928
1360
  {
929
- ref: textareaRef,
930
- value,
931
- onChange: handleChange,
932
- onKeyDown: handleKeyDown,
933
- placeholder,
934
- rows: 1,
935
- disabled: isLoading,
936
- className: (0, import_tailwind_merge6.twMerge)(
937
- "flex-1 resize-none rounded-xl border border-border bg-surface/80",
938
- "px-4 py-2.5 text-sm text-text-primary placeholder:text-text-muted",
939
- "focus:border-transparent focus:ring-2 focus:ring-accent/40 focus:outline-none",
940
- "disabled:opacity-50 disabled:cursor-not-allowed",
941
- "overflow-hidden",
942
- "transition-all duration-200"
943
- ),
944
- style: { colorScheme: "dark" },
945
- "aria-label": "Message input"
1361
+ ref: fileInputRef,
1362
+ type: "file",
1363
+ multiple: true,
1364
+ accept: "image/png,image/jpeg,image/gif,image/webp,application/pdf",
1365
+ onChange: handleFileInputChange,
1366
+ className: "hidden",
1367
+ "aria-hidden": "true"
946
1368
  }
947
1369
  ),
948
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
949
- "button",
1370
+ 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)(
1371
+ AttachmentPreview,
950
1372
  {
951
- type: "button",
952
- onClick: handleSend,
953
- disabled: !value.trim() || isLoading,
954
- "aria-label": "Send message",
955
- className: (0, import_tailwind_merge6.twMerge)(
956
- "inline-flex items-center justify-center rounded-xl px-5 py-2.5",
957
- "text-sm font-semibold text-white shrink-0",
958
- "transition-all duration-200",
959
- "focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-accent",
960
- 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"
961
- ),
962
- children: "Send"
963
- }
964
- )
1373
+ attachment: att,
1374
+ onRemove: () => removeAttachment(i)
1375
+ },
1376
+ `${att.filename}-${i}`
1377
+ )) }),
1378
+ 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" }) }),
1379
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "flex items-end", children: [
1380
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1381
+ "button",
1382
+ {
1383
+ type: "button",
1384
+ onClick: handleFileSelect,
1385
+ disabled: isLoading || attachments.length >= MAX_ATTACHMENTS,
1386
+ "aria-label": "Attach file",
1387
+ className: (0, import_tailwind_merge6.twMerge)(
1388
+ "flex-shrink-0 ml-2 mb-3",
1389
+ "inline-flex items-center justify-center",
1390
+ "w-9 h-9 rounded-full",
1391
+ "transition-all duration-200",
1392
+ "text-text-muted/60 hover:text-text-secondary hover:bg-text-muted/10",
1393
+ "focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-accent",
1394
+ "disabled:opacity-30 disabled:cursor-not-allowed disabled:hover:bg-transparent"
1395
+ ),
1396
+ children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(PaperclipIcon, {})
1397
+ }
1398
+ ),
1399
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1400
+ "textarea",
1401
+ {
1402
+ ref: textareaRef,
1403
+ value,
1404
+ onChange: handleChange,
1405
+ onKeyDown: handleKeyDown,
1406
+ onPaste: handlePaste,
1407
+ placeholder,
1408
+ rows: 1,
1409
+ disabled: isLoading,
1410
+ className: (0, import_tailwind_merge6.twMerge)(
1411
+ "flex-1 resize-none bg-transparent",
1412
+ "pl-2 pr-14 pt-4 pb-4 text-[15px] leading-relaxed",
1413
+ "text-text-primary placeholder:text-text-muted/70",
1414
+ "focus:outline-none",
1415
+ "disabled:opacity-50 disabled:cursor-not-allowed",
1416
+ "overflow-hidden"
1417
+ ),
1418
+ style: { colorScheme: "dark" },
1419
+ "aria-label": "Message input"
1420
+ }
1421
+ ),
1422
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1423
+ "button",
1424
+ {
1425
+ type: "button",
1426
+ onClick: isLoading && onStop ? onStop : handleSend,
1427
+ disabled: !canSend && !isLoading,
1428
+ "aria-label": isLoading ? "Stop generating" : "Send message",
1429
+ className: (0, import_tailwind_merge6.twMerge)(
1430
+ "absolute bottom-3 right-3",
1431
+ "inline-flex items-center justify-center",
1432
+ "w-9 h-9 rounded-full",
1433
+ "transition-all duration-200",
1434
+ "focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-accent",
1435
+ 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 cursor-pointer" : "bg-transparent text-text-muted/40 cursor-default"
1436
+ ),
1437
+ children: isLoading ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(StopIcon, {}) : /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(ArrowUpIcon, {})
1438
+ }
1439
+ )
1440
+ ] })
965
1441
  ]
966
1442
  }
967
1443
  );
@@ -974,6 +1450,7 @@ function WelcomeScreen({
974
1450
  title = "Welcome",
975
1451
  message = "How can I help you today?",
976
1452
  icon,
1453
+ iconClassName,
977
1454
  suggestedQuestions = [],
978
1455
  onQuestionSelect,
979
1456
  className
@@ -986,12 +1463,15 @@ function WelcomeScreen({
986
1463
  className
987
1464
  ),
988
1465
  children: [
989
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1466
+ icon ? iconClassName ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: iconClassName, "aria-hidden": "true", children: icon }) : icon : /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
990
1467
  "div",
991
1468
  {
992
- className: "w-14 h-14 rounded-2xl bg-accent/10 border border-border flex items-center justify-center pulse-glow",
1469
+ className: (0, import_tailwind_merge7.twMerge)(
1470
+ "w-14 h-14 rounded-2xl bg-accent/10 border border-border flex items-center justify-center pulse-glow",
1471
+ iconClassName
1472
+ ),
993
1473
  "aria-hidden": "true",
994
- children: icon ?? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "text-2xl", children: "\u2726" })
1474
+ children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "text-2xl", children: "\u2726" })
995
1475
  }
996
1476
  ),
997
1477
  /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "flex flex-col gap-2", children: [
@@ -1001,7 +1481,7 @@ function WelcomeScreen({
1001
1481
  suggestedQuestions.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1002
1482
  "div",
1003
1483
  {
1004
- className: "flex flex-wrap justify-center gap-2 max-w-md",
1484
+ className: "flex flex-wrap justify-center gap-2 max-w-xl",
1005
1485
  role: "group",
1006
1486
  "aria-label": "Suggested questions",
1007
1487
  children: suggestedQuestions.map((question) => /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
@@ -1010,7 +1490,7 @@ function WelcomeScreen({
1010
1490
  type: "button",
1011
1491
  onClick: () => onQuestionSelect?.(question),
1012
1492
  className: (0, import_tailwind_merge7.twMerge)(
1013
- "px-4 py-2 rounded-full text-sm",
1493
+ "px-3.5 py-1.5 rounded-full text-[12px]",
1014
1494
  "border border-border bg-transparent text-text-secondary",
1015
1495
  "hover:bg-accent/10 hover:border-interactive hover:text-text-primary",
1016
1496
  "focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-accent",
@@ -1028,25 +1508,26 @@ function WelcomeScreen({
1028
1508
  }
1029
1509
 
1030
1510
  // src/streaming/StreamingMessage/StreamingMessage.tsx
1031
- var import_react6 = require("react");
1511
+ var import_react7 = require("react");
1512
+ var import_tailwind_merge8 = require("tailwind-merge");
1032
1513
  var import_core3 = require("@surf-kit/core");
1033
1514
 
1034
1515
  // src/hooks/useCharacterDrain.ts
1035
- var import_react5 = require("react");
1516
+ var import_react6 = require("react");
1036
1517
  function useCharacterDrain(target, msPerChar = 15) {
1037
- const [displayed, setDisplayed] = (0, import_react5.useState)("");
1038
- const indexRef = (0, import_react5.useRef)(0);
1039
- const lastTimeRef = (0, import_react5.useRef)(0);
1040
- const rafRef = (0, import_react5.useRef)(null);
1041
- const drainTargetRef = (0, import_react5.useRef)("");
1042
- const msPerCharRef = (0, import_react5.useRef)(msPerChar);
1518
+ const [displayed, setDisplayed] = (0, import_react6.useState)("");
1519
+ const indexRef = (0, import_react6.useRef)(0);
1520
+ const lastTimeRef = (0, import_react6.useRef)(0);
1521
+ const rafRef = (0, import_react6.useRef)(null);
1522
+ const drainTargetRef = (0, import_react6.useRef)("");
1523
+ const msPerCharRef = (0, import_react6.useRef)(msPerChar);
1043
1524
  msPerCharRef.current = msPerChar;
1044
1525
  if (target !== "") {
1045
1526
  drainTargetRef.current = target;
1046
1527
  }
1047
1528
  const drainTarget = drainTargetRef.current;
1048
1529
  const isDraining = displayed.length < drainTarget.length;
1049
- const tickRef = (0, import_react5.useRef)(() => {
1530
+ const tickRef = (0, import_react6.useRef)(() => {
1050
1531
  });
1051
1532
  tickRef.current = (now) => {
1052
1533
  const currentTarget = drainTargetRef.current;
@@ -1058,7 +1539,10 @@ function useCharacterDrain(target, msPerChar = 15) {
1058
1539
  const elapsed = now - lastTimeRef.current;
1059
1540
  const charsToAdvance = Math.floor(elapsed / msPerCharRef.current);
1060
1541
  if (charsToAdvance > 0 && indexRef.current < currentTarget.length) {
1061
- const nextIndex = Math.min(indexRef.current + charsToAdvance, currentTarget.length);
1542
+ let nextIndex = Math.min(indexRef.current + charsToAdvance, currentTarget.length);
1543
+ while (nextIndex < currentTarget.length && currentTarget[nextIndex - 1].trim() === "") {
1544
+ nextIndex++;
1545
+ }
1062
1546
  indexRef.current = nextIndex;
1063
1547
  lastTimeRef.current = now;
1064
1548
  setDisplayed(currentTarget.slice(0, nextIndex));
@@ -1069,12 +1553,12 @@ function useCharacterDrain(target, msPerChar = 15) {
1069
1553
  rafRef.current = null;
1070
1554
  }
1071
1555
  };
1072
- (0, import_react5.useEffect)(() => {
1556
+ (0, import_react6.useEffect)(() => {
1073
1557
  if (drainTargetRef.current !== "" && indexRef.current < drainTargetRef.current.length && rafRef.current === null) {
1074
1558
  rafRef.current = requestAnimationFrame((t) => tickRef.current(t));
1075
1559
  }
1076
1560
  }, [drainTarget]);
1077
- (0, import_react5.useEffect)(() => {
1561
+ (0, import_react6.useEffect)(() => {
1078
1562
  if (target === "" && !isDraining && displayed !== "") {
1079
1563
  indexRef.current = 0;
1080
1564
  lastTimeRef.current = 0;
@@ -1082,7 +1566,7 @@ function useCharacterDrain(target, msPerChar = 15) {
1082
1566
  setDisplayed("");
1083
1567
  }
1084
1568
  }, [target, isDraining, displayed]);
1085
- (0, import_react5.useEffect)(() => {
1569
+ (0, import_react6.useEffect)(() => {
1086
1570
  return () => {
1087
1571
  if (rafRef.current !== null) {
1088
1572
  cancelAnimationFrame(rafRef.current);
@@ -1103,51 +1587,78 @@ var phaseLabels = {
1103
1587
  generating: "Writing...",
1104
1588
  verifying: "Verifying..."
1105
1589
  };
1590
+ var CURSOR_STYLES = `
1591
+ .sk-streaming-cursor > :not(ul,ol,blockquote,div:has(table)):last-child::after,
1592
+ .sk-streaming-cursor > :is(ul,ol):last-child > li:last-child::after,
1593
+ .sk-streaming-cursor > blockquote:last-child > p:last-child::after,
1594
+ .sk-streaming-cursor > div:has(table):last-child table tbody tr:last-child td:last-child::after {
1595
+ content: "";
1596
+ display: inline-block;
1597
+ width: 2px;
1598
+ height: 1em;
1599
+ background: var(--color-accent, #38bdf8);
1600
+ animation: sk-cursor-blink 0.8s steps(1) infinite;
1601
+ margin-left: 2px;
1602
+ vertical-align: text-bottom;
1603
+ }
1604
+ @keyframes sk-cursor-blink {
1605
+ 0%, 60% { opacity: 1; }
1606
+ 61%, 100% { opacity: 0; }
1607
+ }
1608
+ `;
1106
1609
  function StreamingMessage({
1107
1610
  stream,
1108
1611
  onComplete,
1612
+ onDraining,
1109
1613
  showPhases = true,
1110
1614
  className
1111
1615
  }) {
1112
- const onCompleteRef = (0, import_react6.useRef)(onComplete);
1616
+ const onCompleteRef = (0, import_react7.useRef)(onComplete);
1113
1617
  onCompleteRef.current = onComplete;
1114
- const wasActiveRef = (0, import_react6.useRef)(stream.active);
1115
- (0, import_react6.useEffect)(() => {
1618
+ const onDrainingRef = (0, import_react7.useRef)(onDraining);
1619
+ onDrainingRef.current = onDraining;
1620
+ const wasActiveRef = (0, import_react7.useRef)(stream.active);
1621
+ (0, import_react7.useEffect)(() => {
1116
1622
  if (wasActiveRef.current && !stream.active) {
1117
1623
  onCompleteRef.current?.();
1118
1624
  }
1119
1625
  wasActiveRef.current = stream.active;
1120
1626
  }, [stream.active]);
1121
1627
  const phaseLabel = phaseLabels[stream.phase];
1122
- const { displayed: displayedContent } = useCharacterDrain(stream.content);
1123
- return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className, "data-testid": "streaming-message", children: [
1628
+ const { displayed: rawDisplayed, isDraining } = useCharacterDrain(stream.content);
1629
+ const displayedContent = stream.active || isDraining ? rawDisplayed.trimEnd() : rawDisplayed;
1630
+ (0, import_react7.useEffect)(() => {
1631
+ onDrainingRef.current?.(isDraining);
1632
+ }, [isDraining]);
1633
+ const agentLabel = stream.agent ? stream.agent.replace("_agent", "").replace("_", " ") : null;
1634
+ const showPhaseIndicator = showPhases && stream.active && stream.phase !== "idle" && !displayedContent;
1635
+ const showCursor = (stream.active || isDraining) && !!displayedContent;
1636
+ 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: [
1124
1637
  /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { "aria-live": "assertive", className: "sr-only", children: [
1125
1638
  stream.active && stream.phase !== "idle" && "Response started",
1126
1639
  !stream.active && stream.content && "Response complete"
1127
1640
  ] }),
1641
+ showCursor && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("style", { children: CURSOR_STYLES }),
1642
+ 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 }),
1128
1643
  /* @__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: [
1129
- showPhases && stream.active && stream.phase !== "idle" && /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
1644
+ showPhaseIndicator && /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
1130
1645
  "div",
1131
1646
  {
1132
- className: "flex items-center gap-2 mb-2 text-sm text-text-secondary",
1647
+ className: "flex items-center gap-2 text-sm text-text-secondary",
1133
1648
  "data-testid": "phase-indicator",
1134
1649
  children: [
1135
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_core3.Spinner, { size: "sm" }) }),
1650
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_core3.WaveLoader, { size: "sm", color: "#38bdf8" }) }),
1136
1651
  /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { children: phaseLabel })
1137
1652
  ]
1138
1653
  }
1139
1654
  ),
1140
- /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "text-sm leading-relaxed text-text-primary whitespace-pre-wrap", children: [
1141
- displayedContent,
1142
- stream.active && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
1143
- "span",
1144
- {
1145
- className: "inline-block w-0.5 h-4 bg-accent align-text-bottom animate-pulse ml-0.5",
1146
- "aria-hidden": "true",
1147
- "data-testid": "streaming-cursor"
1148
- }
1149
- )
1150
- ] })
1655
+ displayedContent && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
1656
+ ResponseMessage,
1657
+ {
1658
+ content: displayedContent,
1659
+ className: showCursor ? "sk-streaming-cursor" : void 0
1660
+ }
1661
+ )
1151
1662
  ] })
1152
1663
  ] });
1153
1664
  }
@@ -1178,7 +1689,7 @@ function AgentChat({
1178
1689
  return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
1179
1690
  "div",
1180
1691
  {
1181
- className: (0, import_tailwind_merge8.twMerge)(
1692
+ className: (0, import_tailwind_merge9.twMerge)(
1182
1693
  "flex flex-col h-full bg-canvas border border-border rounded-xl overflow-hidden",
1183
1694
  className
1184
1695
  ),
@@ -1214,7 +1725,7 @@ function AgentChat({
1214
1725
  onQuestionSelect: handleQuestionSelect
1215
1726
  }
1216
1727
  ),
1217
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(MessageComposer, { onSend: handleSend, isLoading: state.isLoading })
1728
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(MessageComposer, { onSend: handleSend, onStop: actions.stop, isLoading: state.isLoading })
1218
1729
  ]
1219
1730
  }
1220
1731
  );
@@ -1350,7 +1861,7 @@ function ConfidenceBadge({ confidence, className }) {
1350
1861
  }
1351
1862
 
1352
1863
  // src/confidence/ConfidenceBreakdown/ConfidenceBreakdown.tsx
1353
- var import_react7 = require("react");
1864
+ var import_react8 = require("react");
1354
1865
 
1355
1866
  // src/confidence/ConfidenceMeter/ConfidenceMeter.tsx
1356
1867
  var import_jsx_runtime18 = require("react/jsx-runtime");
@@ -1405,7 +1916,7 @@ function ConfidenceBreakdown({
1405
1916
  defaultExpanded = false,
1406
1917
  className
1407
1918
  }) {
1408
- const [expanded, setExpanded] = (0, import_react7.useState)(defaultExpanded);
1919
+ const [expanded, setExpanded] = (0, import_react8.useState)(defaultExpanded);
1409
1920
  const isExpanded = expandable ? expanded : true;
1410
1921
  return /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { className: `rounded-xl border border-border bg-surface ${className ?? ""}`, "data-testid": "confidence-breakdown", children: [
1411
1922
  /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(
@@ -1485,7 +1996,7 @@ function VerificationBadge({ verification, className }) {
1485
1996
  }
1486
1997
 
1487
1998
  // src/confidence/VerificationDetail/VerificationDetail.tsx
1488
- var import_react8 = require("react");
1999
+ var import_react9 = require("react");
1489
2000
  var import_jsx_runtime21 = require("react/jsx-runtime");
1490
2001
  function VerificationDetail({
1491
2002
  verification,
@@ -1493,7 +2004,7 @@ function VerificationDetail({
1493
2004
  defaultExpanded = false,
1494
2005
  className
1495
2006
  }) {
1496
- const [expanded, setExpanded] = (0, import_react8.useState)(defaultExpanded);
2007
+ const [expanded, setExpanded] = (0, import_react9.useState)(defaultExpanded);
1497
2008
  const isExpanded = expandable ? expanded : true;
1498
2009
  return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("div", { className: `rounded-xl border border-border bg-surface ${className ?? ""}`, "data-testid": "verification-detail", children: [
1499
2010
  /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(
@@ -1537,11 +2048,11 @@ function VerificationDetail({
1537
2048
  }
1538
2049
 
1539
2050
  // src/hooks/useAgentTheme.ts
1540
- var import_react9 = require("react");
2051
+ var import_react10 = require("react");
1541
2052
  var DEFAULT_ACCENT = "#6366f1";
1542
2053
  var DEFAULT_LABEL = "Agent";
1543
2054
  function useAgentTheme(agentId, agentThemes) {
1544
- return (0, import_react9.useMemo)(() => {
2055
+ return (0, import_react10.useMemo)(() => {
1545
2056
  if (!agentId) {
1546
2057
  return { accent: DEFAULT_ACCENT, icon: null, label: DEFAULT_LABEL };
1547
2058
  }
@@ -1697,7 +2208,7 @@ function ToolExecution({ tool, label, className }) {
1697
2208
  role: "status",
1698
2209
  "data-testid": "tool-execution",
1699
2210
  children: [
1700
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("span", { "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(import_core10.Spinner, { size: "sm" }) }),
2211
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("span", { "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(import_core10.WaveLoader, { size: "sm", color: "#38bdf8" }) }),
1701
2212
  /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("span", { children: displayLabel })
1702
2213
  ]
1703
2214
  }
@@ -1717,7 +2228,7 @@ function RetrievalProgress({ sources, isActive, className }) {
1717
2228
  "data-testid": "retrieval-progress",
1718
2229
  children: [
1719
2230
  isActive && /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("div", { className: "flex items-center gap-2 text-sm text-text-secondary", children: [
1720
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("span", { "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(import_core11.Spinner, { size: "sm" }) }),
2231
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("span", { "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(import_core11.WaveLoader, { size: "sm", color: "#38bdf8" }) }),
1721
2232
  /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("span", { children: "Retrieving sources..." })
1722
2233
  ] }),
1723
2234
  sources.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("ul", { className: "space-y-1", "data-testid": "source-list", children: sources.map((source, index) => /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)(
@@ -1774,7 +2285,7 @@ function VerificationProgress({
1774
2285
  }
1775
2286
 
1776
2287
  // src/streaming/TypewriterText/TypewriterText.tsx
1777
- var import_react10 = require("react");
2288
+ var import_react11 = require("react");
1778
2289
  var import_jsx_runtime30 = require("react/jsx-runtime");
1779
2290
  function TypewriterText({
1780
2291
  text,
@@ -1784,9 +2295,9 @@ function TypewriterText({
1784
2295
  className = "",
1785
2296
  showCursor = true
1786
2297
  }) {
1787
- const [displayedText, setDisplayedText] = (0, import_react10.useState)("");
1788
- const [isComplete, setIsComplete] = (0, import_react10.useState)(false);
1789
- (0, import_react10.useEffect)(() => {
2298
+ const [displayedText, setDisplayedText] = (0, import_react11.useState)("");
2299
+ const [isComplete, setIsComplete] = (0, import_react11.useState)(false);
2300
+ (0, import_react11.useEffect)(() => {
1790
2301
  setDisplayedText("");
1791
2302
  setIsComplete(false);
1792
2303
  let index = 0;
@@ -1815,7 +2326,7 @@ function TypewriterText({
1815
2326
  }
1816
2327
 
1817
2328
  // src/streaming/TypingIndicator/TypingIndicator.tsx
1818
- var import_tailwind_merge9 = require("tailwind-merge");
2329
+ var import_tailwind_merge10 = require("tailwind-merge");
1819
2330
  var import_hooks2 = require("@surf-kit/hooks");
1820
2331
  var import_jsx_runtime31 = require("react/jsx-runtime");
1821
2332
  var bounceKeyframes = `
@@ -1835,7 +2346,7 @@ function TypingIndicator({
1835
2346
  {
1836
2347
  role: "status",
1837
2348
  "aria-label": label ?? "typing",
1838
- className: (0, import_tailwind_merge9.twMerge)("inline-flex items-center gap-2", className),
2349
+ className: (0, import_tailwind_merge10.twMerge)("inline-flex items-center gap-2", className),
1839
2350
  "data-testid": "typing-indicator",
1840
2351
  children: [
1841
2352
  !reducedMotion && /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("style", { children: bounceKeyframes }),
@@ -1857,7 +2368,7 @@ function TypingIndicator({
1857
2368
  }
1858
2369
 
1859
2370
  // src/streaming/TextGlimmer/TextGlimmer.tsx
1860
- var import_tailwind_merge10 = require("tailwind-merge");
2371
+ var import_tailwind_merge11 = require("tailwind-merge");
1861
2372
  var import_hooks3 = require("@surf-kit/hooks");
1862
2373
  var import_jsx_runtime32 = require("react/jsx-runtime");
1863
2374
  var shimmerKeyframes = `
@@ -1874,7 +2385,7 @@ function TextGlimmer({ lines = 3, className }) {
1874
2385
  {
1875
2386
  role: "status",
1876
2387
  "aria-label": "Loading",
1877
- className: (0, import_tailwind_merge10.twMerge)("flex flex-col gap-2", className),
2388
+ className: (0, import_tailwind_merge11.twMerge)("flex flex-col gap-2", className),
1878
2389
  "data-testid": "text-glimmer",
1879
2390
  children: [
1880
2391
  !reducedMotion && /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("style", { children: shimmerKeyframes }),
@@ -1900,7 +2411,7 @@ function TextGlimmer({ lines = 3, className }) {
1900
2411
  }
1901
2412
 
1902
2413
  // src/streaming/StreamingList/StreamingList.tsx
1903
- var import_tailwind_merge11 = require("tailwind-merge");
2414
+ var import_tailwind_merge12 = require("tailwind-merge");
1904
2415
  var import_hooks4 = require("@surf-kit/hooks");
1905
2416
  var import_jsx_runtime33 = require("react/jsx-runtime");
1906
2417
  var fadeSlideInKeyframes = `
@@ -1918,13 +2429,13 @@ function StreamingList({
1918
2429
  }) {
1919
2430
  const reducedMotion = (0, import_hooks4.useReducedMotion)();
1920
2431
  if (items.length === 0 && !isStreaming) {
1921
- return emptyMessage ? /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("p", { className: (0, import_tailwind_merge11.twMerge)("text-sm text-text-secondary", className), "data-testid": "streaming-list-empty", children: emptyMessage }) : null;
2432
+ return emptyMessage ? /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("p", { className: (0, import_tailwind_merge12.twMerge)("text-sm text-text-secondary", className), "data-testid": "streaming-list-empty", children: emptyMessage }) : null;
1922
2433
  }
1923
2434
  return /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)(
1924
2435
  "ul",
1925
2436
  {
1926
2437
  "aria-live": "polite",
1927
- className: (0, import_tailwind_merge11.twMerge)("list-none p-0 m-0", className),
2438
+ className: (0, import_tailwind_merge12.twMerge)("list-none p-0 m-0", className),
1928
2439
  "data-testid": "streaming-list",
1929
2440
  children: [
1930
2441
  !reducedMotion && /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("style", { children: fadeSlideInKeyframes }),
@@ -1944,7 +2455,7 @@ function StreamingList({
1944
2455
  }
1945
2456
 
1946
2457
  // src/streaming/StreamingStructure/StreamingStructure.tsx
1947
- var import_tailwind_merge12 = require("tailwind-merge");
2458
+ var import_tailwind_merge13 = require("tailwind-merge");
1948
2459
  var import_hooks5 = require("@surf-kit/hooks");
1949
2460
  var import_jsx_runtime34 = require("react/jsx-runtime");
1950
2461
  var fadeSlideInKeyframes2 = `
@@ -1993,7 +2504,7 @@ function StreamingStructure({
1993
2504
  "dl",
1994
2505
  {
1995
2506
  "aria-live": "polite",
1996
- className: (0, import_tailwind_merge12.twMerge)("m-0", className),
2507
+ className: (0, import_tailwind_merge13.twMerge)("m-0", className),
1997
2508
  "data-testid": "streaming-structure",
1998
2509
  children: [
1999
2510
  !reducedMotion && /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("style", { children: fadeSlideInKeyframes2 }),
@@ -2016,7 +2527,7 @@ function StreamingStructure({
2016
2527
  }
2017
2528
 
2018
2529
  // src/chat/ConversationList/ConversationList.tsx
2019
- var import_tailwind_merge13 = require("tailwind-merge");
2530
+ var import_tailwind_merge14 = require("tailwind-merge");
2020
2531
  var import_jsx_runtime35 = require("react/jsx-runtime");
2021
2532
  function ConversationList({
2022
2533
  conversations,
@@ -2030,14 +2541,14 @@ function ConversationList({
2030
2541
  "nav",
2031
2542
  {
2032
2543
  "aria-label": "Conversation list",
2033
- className: (0, import_tailwind_merge13.twMerge)("flex flex-col h-full bg-canvas", className),
2544
+ className: (0, import_tailwind_merge14.twMerge)("flex flex-col flex-1 min-h-0", className),
2034
2545
  children: [
2035
- onNew && /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("div", { className: "p-3 border-b border-border", children: /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
2546
+ onNew && /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("div", { className: "px-5 pt-1 pb-3 border-b border-border", children: /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
2036
2547
  "button",
2037
2548
  {
2038
2549
  type: "button",
2039
2550
  onClick: onNew,
2040
- className: "w-full px-4 py-2.5 rounded-xl text-sm font-semibold bg-accent text-white hover:bg-accent-hover transition-all duration-200",
2551
+ className: "w-full px-4 py-2 rounded-lg text-sm font-medium border border-border text-text-secondary hover:text-text-primary hover:bg-surface hover:border-border-strong transition-colors duration-150",
2041
2552
  children: "New conversation"
2042
2553
  }
2043
2554
  ) }),
@@ -2047,10 +2558,10 @@ function ConversationList({
2047
2558
  return /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)(
2048
2559
  "li",
2049
2560
  {
2050
- className: (0, import_tailwind_merge13.twMerge)(
2051
- "flex items-start border-b border-border transition-colors duration-200",
2052
- "hover:bg-surface",
2053
- isActive && "bg-surface-raised border-l-2 border-l-accent"
2561
+ className: (0, import_tailwind_merge14.twMerge)(
2562
+ "flex items-start transition-colors duration-150",
2563
+ "hover:bg-surface-raised",
2564
+ isActive ? "bg-accent-subtlest border-l-[3px] border-l-accent" : "border-l-[3px] border-l-transparent"
2054
2565
  ),
2055
2566
  children: [
2056
2567
  /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)(
@@ -2059,10 +2570,10 @@ function ConversationList({
2059
2570
  type: "button",
2060
2571
  onClick: () => onSelect(conversation.id),
2061
2572
  "aria-current": isActive ? "true" : void 0,
2062
- className: "flex-1 min-w-0 text-left px-4 py-3",
2573
+ className: "flex-1 min-w-0 text-left px-5 py-2.5",
2063
2574
  children: [
2064
- /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("div", { className: "text-sm font-medium text-brand-cream truncate", children: conversation.title }),
2065
- /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("div", { className: "text-xs text-brand-cream/40 truncate mt-0.5 leading-relaxed", children: conversation.lastMessage })
2575
+ /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("div", { className: "text-sm font-medium text-text-primary truncate", children: conversation.title }),
2576
+ /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("div", { className: "text-xs text-text-muted truncate mt-0.5 leading-relaxed", children: conversation.lastMessage })
2066
2577
  ]
2067
2578
  }
2068
2579
  ),
@@ -2072,7 +2583,7 @@ function ConversationList({
2072
2583
  type: "button",
2073
2584
  onClick: () => onDelete(conversation.id),
2074
2585
  "aria-label": `Delete ${conversation.title}`,
2075
- className: "shrink-0 p-1.5 m-2 rounded-lg text-brand-cream/25 hover:text-brand-watermelon hover:bg-brand-watermelon/10 transition-colors duration-200",
2586
+ className: "shrink-0 p-1.5 m-2 rounded-lg text-text-muted hover:text-status-error hover:bg-status-error/10 transition-colors duration-150",
2076
2587
  children: /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)(
2077
2588
  "svg",
2078
2589
  {
@@ -2099,7 +2610,7 @@ function ConversationList({
2099
2610
  conversation.id
2100
2611
  );
2101
2612
  }),
2102
- conversations.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("li", { className: "px-4 py-8 text-center", children: /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("span", { className: "text-sm text-brand-cream/30 font-body", children: "No conversations yet" }) })
2613
+ conversations.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("li", { className: "px-5 py-12 text-center", children: /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("span", { className: "text-sm text-text-muted font-body", children: "No conversations yet" }) })
2103
2614
  ] })
2104
2615
  ]
2105
2616
  }
@@ -2107,8 +2618,8 @@ function ConversationList({
2107
2618
  }
2108
2619
 
2109
2620
  // src/layouts/AgentFullPage/AgentFullPage.tsx
2110
- var import_tailwind_merge14 = require("tailwind-merge");
2111
- var import_react11 = require("react");
2621
+ var import_tailwind_merge15 = require("tailwind-merge");
2622
+ var import_react12 = require("react");
2112
2623
  var import_jsx_runtime36 = require("react/jsx-runtime");
2113
2624
  function AgentFullPage({
2114
2625
  endpoint,
@@ -2121,8 +2632,8 @@ function AgentFullPage({
2121
2632
  onNewConversation,
2122
2633
  className
2123
2634
  }) {
2124
- const [sidebarOpen, setSidebarOpen] = (0, import_react11.useState)(false);
2125
- const handleSelect = (0, import_react11.useCallback)(
2635
+ const [sidebarOpen, setSidebarOpen] = (0, import_react12.useState)(false);
2636
+ const handleSelect = (0, import_react12.useCallback)(
2126
2637
  (id) => {
2127
2638
  onConversationSelect?.(id);
2128
2639
  setSidebarOpen(false);
@@ -2132,7 +2643,7 @@ function AgentFullPage({
2132
2643
  return /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)(
2133
2644
  "div",
2134
2645
  {
2135
- className: (0, import_tailwind_merge14.twMerge)("flex h-screen w-full overflow-hidden bg-brand-dark", className),
2646
+ className: (0, import_tailwind_merge15.twMerge)("flex h-screen w-full overflow-hidden bg-brand-dark", className),
2136
2647
  "data-testid": "agent-full-page",
2137
2648
  children: [
2138
2649
  showConversationList && /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)(import_jsx_runtime36.Fragment, { children: [
@@ -2147,7 +2658,7 @@ function AgentFullPage({
2147
2658
  /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
2148
2659
  "aside",
2149
2660
  {
2150
- className: (0, import_tailwind_merge14.twMerge)(
2661
+ className: (0, import_tailwind_merge15.twMerge)(
2151
2662
  "bg-brand-dark border-r border-brand-gold/15 w-72 shrink-0 flex-col z-40",
2152
2663
  // Desktop: always visible
2153
2664
  "hidden md:flex",
@@ -2213,8 +2724,8 @@ function AgentFullPage({
2213
2724
  }
2214
2725
 
2215
2726
  // src/layouts/AgentPanel/AgentPanel.tsx
2216
- var import_tailwind_merge15 = require("tailwind-merge");
2217
- var import_react12 = require("react");
2727
+ var import_tailwind_merge16 = require("tailwind-merge");
2728
+ var import_react13 = require("react");
2218
2729
  var import_jsx_runtime37 = require("react/jsx-runtime");
2219
2730
  function AgentPanel({
2220
2731
  endpoint,
@@ -2225,8 +2736,8 @@ function AgentPanel({
2225
2736
  title = "Chat",
2226
2737
  className
2227
2738
  }) {
2228
- const panelRef = (0, import_react12.useRef)(null);
2229
- (0, import_react12.useEffect)(() => {
2739
+ const panelRef = (0, import_react13.useRef)(null);
2740
+ (0, import_react13.useEffect)(() => {
2230
2741
  if (!isOpen) return;
2231
2742
  const handleKeyDown = (e) => {
2232
2743
  if (e.key === "Escape") onClose();
@@ -2238,13 +2749,13 @@ function AgentPanel({
2238
2749
  return /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)(
2239
2750
  "div",
2240
2751
  {
2241
- className: (0, import_tailwind_merge15.twMerge)("fixed inset-0 z-50", !isOpen && "pointer-events-none"),
2752
+ className: (0, import_tailwind_merge16.twMerge)("fixed inset-0 z-50", !isOpen && "pointer-events-none"),
2242
2753
  "aria-hidden": !isOpen,
2243
2754
  children: [
2244
2755
  /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(
2245
2756
  "div",
2246
2757
  {
2247
- className: (0, import_tailwind_merge15.twMerge)(
2758
+ className: (0, import_tailwind_merge16.twMerge)(
2248
2759
  "fixed inset-0 transition-opacity duration-300",
2249
2760
  isOpen ? "opacity-100 bg-brand-dark/70 backdrop-blur-sm pointer-events-auto" : "opacity-0 pointer-events-none"
2250
2761
  ),
@@ -2260,7 +2771,7 @@ function AgentPanel({
2260
2771
  "aria-label": title,
2261
2772
  "aria-modal": isOpen ? "true" : void 0,
2262
2773
  style: { width: widthStyle, maxWidth: "100vw" },
2263
- className: (0, import_tailwind_merge15.twMerge)(
2774
+ className: (0, import_tailwind_merge16.twMerge)(
2264
2775
  "fixed top-0 h-full flex flex-col z-50 bg-brand-dark shadow-card",
2265
2776
  "transition-transform duration-300 ease-in-out",
2266
2777
  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"}`,
@@ -2302,8 +2813,8 @@ function AgentPanel({
2302
2813
  }
2303
2814
 
2304
2815
  // src/layouts/AgentWidget/AgentWidget.tsx
2305
- var import_tailwind_merge16 = require("tailwind-merge");
2306
- var import_react13 = require("react");
2816
+ var import_tailwind_merge17 = require("tailwind-merge");
2817
+ var import_react14 = require("react");
2307
2818
  var import_jsx_runtime38 = require("react/jsx-runtime");
2308
2819
  function AgentWidget({
2309
2820
  endpoint,
@@ -2312,8 +2823,8 @@ function AgentWidget({
2312
2823
  title = "Chat",
2313
2824
  className
2314
2825
  }) {
2315
- const [isOpen, setIsOpen] = (0, import_react13.useState)(false);
2316
- const toggle = (0, import_react13.useCallback)(() => {
2826
+ const [isOpen, setIsOpen] = (0, import_react14.useState)(false);
2827
+ const toggle = (0, import_react14.useCallback)(() => {
2317
2828
  setIsOpen((prev) => !prev);
2318
2829
  }, []);
2319
2830
  const positionClasses = position === "bottom-left" ? "left-4 bottom-4" : "right-4 bottom-4";
@@ -2326,7 +2837,7 @@ function AgentWidget({
2326
2837
  role: "dialog",
2327
2838
  "aria-label": title,
2328
2839
  "aria-hidden": !isOpen,
2329
- className: (0, import_tailwind_merge16.twMerge)(
2840
+ className: (0, import_tailwind_merge17.twMerge)(
2330
2841
  "fixed z-50 flex flex-col",
2331
2842
  "w-[min(400px,calc(100vw-2rem))] h-[min(600px,calc(100vh-6rem))]",
2332
2843
  "rounded-2xl overflow-hidden border border-brand-gold/15",
@@ -2373,7 +2884,7 @@ function AgentWidget({
2373
2884
  onClick: toggle,
2374
2885
  "aria-label": isOpen ? "Close chat" : triggerLabel,
2375
2886
  "aria-expanded": isOpen,
2376
- className: (0, import_tailwind_merge16.twMerge)(
2887
+ className: (0, import_tailwind_merge17.twMerge)(
2377
2888
  "fixed z-50 flex items-center justify-center w-14 h-14 rounded-full",
2378
2889
  "bg-brand-blue text-brand-cream shadow-glow-cyan",
2379
2890
  "hover:bg-brand-cyan hover:shadow-glow-cyan hover:scale-105",
@@ -2391,7 +2902,7 @@ function AgentWidget({
2391
2902
  }
2392
2903
 
2393
2904
  // src/layouts/AgentEmbed/AgentEmbed.tsx
2394
- var import_tailwind_merge17 = require("tailwind-merge");
2905
+ var import_tailwind_merge18 = require("tailwind-merge");
2395
2906
  var import_jsx_runtime39 = require("react/jsx-runtime");
2396
2907
  function AgentEmbed({
2397
2908
  endpoint,
@@ -2401,7 +2912,7 @@ function AgentEmbed({
2401
2912
  return /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
2402
2913
  "div",
2403
2914
  {
2404
- className: (0, import_tailwind_merge17.twMerge)("w-full h-full min-h-0", className),
2915
+ className: (0, import_tailwind_merge18.twMerge)("w-full h-full min-h-0", className),
2405
2916
  "data-testid": "agent-embed",
2406
2917
  children: /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
2407
2918
  AgentChat,
@@ -2417,7 +2928,7 @@ function AgentEmbed({
2417
2928
 
2418
2929
  // src/mcp/MCPToolCall/MCPToolCall.tsx
2419
2930
  var import_class_variance_authority = require("class-variance-authority");
2420
- var import_tailwind_merge18 = require("tailwind-merge");
2931
+ var import_tailwind_merge19 = require("tailwind-merge");
2421
2932
  var import_core12 = require("@surf-kit/core");
2422
2933
  var import_jsx_runtime40 = require("react/jsx-runtime");
2423
2934
  var statusBadgeIntent = {
@@ -2461,7 +2972,7 @@ function MCPToolCall({ call, isExpanded = false, onToggleExpand, className }) {
2461
2972
  return /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)(
2462
2973
  "div",
2463
2974
  {
2464
- className: (0, import_tailwind_merge18.twMerge)(container({ status: call.status }), className),
2975
+ className: (0, import_tailwind_merge19.twMerge)(container({ status: call.status }), className),
2465
2976
  "data-testid": "mcp-tool-call",
2466
2977
  children: [
2467
2978
  /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)(
@@ -2478,7 +2989,7 @@ function MCPToolCall({ call, isExpanded = false, onToggleExpand, className }) {
2478
2989
  call.serverName && /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("span", { className: "text-xs text-text-secondary truncate", children: call.serverName })
2479
2990
  ] }),
2480
2991
  /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)("div", { className: "flex items-center gap-2 shrink-0", children: [
2481
- call.status === "running" && /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("span", { "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(import_core12.Spinner, { size: "sm" }) }),
2992
+ call.status === "running" && /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("span", { "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(import_core12.WaveLoader, { size: "sm", color: "#38bdf8" }) }),
2482
2993
  /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(
2483
2994
  import_core12.Badge,
2484
2995
  {
@@ -2552,7 +3063,7 @@ function MCPToolCall({ call, isExpanded = false, onToggleExpand, className }) {
2552
3063
  }
2553
3064
 
2554
3065
  // src/mcp/MCPResourceView/MCPResourceView.tsx
2555
- var import_tailwind_merge19 = require("tailwind-merge");
3066
+ var import_tailwind_merge20 = require("tailwind-merge");
2556
3067
  var import_jsx_runtime41 = require("react/jsx-runtime");
2557
3068
  function isImageMime(mime) {
2558
3069
  return !!mime && mime.startsWith("image/");
@@ -2570,7 +3081,7 @@ function MCPResourceView({ resource, className }) {
2570
3081
  return /* @__PURE__ */ (0, import_jsx_runtime41.jsxs)(
2571
3082
  "div",
2572
3083
  {
2573
- className: (0, import_tailwind_merge19.twMerge)("rounded-lg border border-border bg-surface p-3 text-sm", className),
3084
+ className: (0, import_tailwind_merge20.twMerge)("rounded-lg border border-border bg-surface p-3 text-sm", className),
2574
3085
  "data-testid": "mcp-resource-view",
2575
3086
  children: [
2576
3087
  /* @__PURE__ */ (0, import_jsx_runtime41.jsxs)("div", { className: "mb-2", children: [
@@ -2620,9 +3131,9 @@ function MCPResourceView({ resource, className }) {
2620
3131
  }
2621
3132
 
2622
3133
  // src/mcp/MCPServerStatus/MCPServerStatus.tsx
2623
- var import_react14 = require("react");
3134
+ var import_react15 = require("react");
2624
3135
  var import_class_variance_authority2 = require("class-variance-authority");
2625
- var import_tailwind_merge20 = require("tailwind-merge");
3136
+ var import_tailwind_merge21 = require("tailwind-merge");
2626
3137
  var import_jsx_runtime42 = require("react/jsx-runtime");
2627
3138
  var statusDot = (0, import_class_variance_authority2.cva)("inline-block h-2 w-2 rounded-full shrink-0", {
2628
3139
  variants: {
@@ -2644,13 +3155,13 @@ function formatLastPing(date) {
2644
3155
  return date.toLocaleTimeString();
2645
3156
  }
2646
3157
  function MCPServerStatus({ server, className }) {
2647
- const [showTools, setShowTools] = (0, import_react14.useState)(false);
2648
- const [showResources, setShowResources] = (0, import_react14.useState)(false);
3158
+ const [showTools, setShowTools] = (0, import_react15.useState)(false);
3159
+ const [showResources, setShowResources] = (0, import_react15.useState)(false);
2649
3160
  const lastPing = formatLastPing(server.lastPing);
2650
3161
  return /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(
2651
3162
  "div",
2652
3163
  {
2653
- className: (0, import_tailwind_merge20.twMerge)("rounded-lg border border-border bg-surface p-3 text-sm", className),
3164
+ className: (0, import_tailwind_merge21.twMerge)("rounded-lg border border-border bg-surface p-3 text-sm", className),
2654
3165
  "data-testid": "mcp-server-status",
2655
3166
  children: [
2656
3167
  /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("div", { className: "flex items-center gap-2 mb-1", children: [
@@ -2762,9 +3273,9 @@ function MCPServerStatus({ server, className }) {
2762
3273
  }
2763
3274
 
2764
3275
  // src/mcp/MCPApprovalDialog/MCPApprovalDialog.tsx
2765
- var import_react15 = require("react");
3276
+ var import_react16 = require("react");
2766
3277
  var import_class_variance_authority3 = require("class-variance-authority");
2767
- var import_tailwind_merge21 = require("tailwind-merge");
3278
+ var import_tailwind_merge22 = require("tailwind-merge");
2768
3279
  var import_react_aria = require("react-aria");
2769
3280
  var import_core13 = require("@surf-kit/core");
2770
3281
  var import_jsx_runtime43 = require("react/jsx-runtime");
@@ -2800,9 +3311,9 @@ function MCPApprovalDialog({
2800
3311
  isOpen,
2801
3312
  className
2802
3313
  }) {
2803
- const ref = (0, import_react15.useRef)(null);
3314
+ const ref = (0, import_react16.useRef)(null);
2804
3315
  const { dialogProps, titleProps } = (0, import_react_aria.useDialog)({ role: "alertdialog" }, ref);
2805
- (0, import_react15.useEffect)(() => {
3316
+ (0, import_react16.useEffect)(() => {
2806
3317
  if (!isOpen) return;
2807
3318
  const handleKeyDown = (e) => {
2808
3319
  if (e.key === "Escape") {
@@ -2824,7 +3335,7 @@ function MCPApprovalDialog({
2824
3335
  {
2825
3336
  ...dialogProps,
2826
3337
  ref,
2827
- className: (0, import_tailwind_merge21.twMerge)(riskBorder({ risk: riskLevel }), className),
3338
+ className: (0, import_tailwind_merge22.twMerge)(riskBorder({ risk: riskLevel }), className),
2828
3339
  "data-testid": "mcp-approval-dialog",
2829
3340
  children: [
2830
3341
  /* @__PURE__ */ (0, import_jsx_runtime43.jsxs)("div", { className: "flex items-center justify-between mb-4", children: [
@@ -2902,7 +3413,7 @@ function MCPApprovalDialog({
2902
3413
  }
2903
3414
 
2904
3415
  // src/feedback/ThumbsFeedback/ThumbsFeedback.tsx
2905
- var import_react16 = require("react");
3416
+ var import_react17 = require("react");
2906
3417
  var import_jsx_runtime44 = require("react/jsx-runtime");
2907
3418
  function ThumbsFeedback({
2908
3419
  messageId,
@@ -2911,7 +3422,7 @@ function ThumbsFeedback({
2911
3422
  onNegative,
2912
3423
  className
2913
3424
  }) {
2914
- const [selected, setSelected] = (0, import_react16.useState)(state);
3425
+ const [selected, setSelected] = (0, import_react17.useState)(state);
2915
3426
  const handleClick = (rating) => {
2916
3427
  setSelected(rating);
2917
3428
  onFeedback(messageId, rating);
@@ -2991,11 +3502,11 @@ function ThumbsFeedback({
2991
3502
  }
2992
3503
 
2993
3504
  // src/feedback/FeedbackDialog/FeedbackDialog.tsx
2994
- var import_react17 = require("react");
3505
+ var import_react18 = require("react");
2995
3506
  var import_core14 = require("@surf-kit/core");
2996
3507
  var import_jsx_runtime45 = require("react/jsx-runtime");
2997
3508
  function FeedbackDialog({ isOpen, onClose, onSubmit, className }) {
2998
- const [comment, setComment] = (0, import_react17.useState)("");
3509
+ const [comment, setComment] = (0, import_react18.useState)("");
2999
3510
  const handleSubmit = () => {
3000
3511
  onSubmit(comment);
3001
3512
  setComment("");