@surf-kit/agent 0.2.2 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/chat/index.cjs +625 -204
- package/dist/chat/index.cjs.map +1 -1
- package/dist/chat/index.d.cts +11 -6
- package/dist/chat/index.d.ts +11 -6
- package/dist/chat/index.js +606 -185
- package/dist/chat/index.js.map +1 -1
- package/dist/{chat--OifhIRe.d.ts → chat-BIIDOGrD.d.ts} +10 -1
- package/dist/{chat-ChYl2XjV.d.cts → chat-CGamM7Mz.d.cts} +10 -1
- package/dist/{hooks-DLfF18IU.d.cts → hooks-B1NYoLLs.d.cts} +21 -5
- package/dist/{hooks-BGs8-4GK.d.ts → hooks-CTeEqnBQ.d.ts} +21 -5
- package/dist/hooks.cjs +126 -81
- package/dist/hooks.cjs.map +1 -1
- package/dist/hooks.d.cts +3 -3
- package/dist/hooks.d.ts +3 -3
- package/dist/hooks.js +126 -81
- package/dist/hooks.js.map +1 -1
- package/dist/index.cjs +686 -265
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +3 -3
- package/dist/index.d.ts +3 -3
- package/dist/index.js +645 -224
- package/dist/index.js.map +1 -1
- package/dist/layouts/index.cjs +646 -225
- package/dist/layouts/index.cjs.map +1 -1
- package/dist/layouts/index.d.cts +1 -1
- package/dist/layouts/index.d.ts +1 -1
- package/dist/layouts/index.js +622 -201
- package/dist/layouts/index.js.map +1 -1
- package/dist/mcp/index.cjs +1 -1
- package/dist/mcp/index.cjs.map +1 -1
- package/dist/mcp/index.js +2 -2
- package/dist/mcp/index.js.map +1 -1
- package/dist/response/index.cjs +66 -12
- package/dist/response/index.cjs.map +1 -1
- package/dist/response/index.d.cts +2 -2
- package/dist/response/index.d.ts +2 -2
- package/dist/response/index.js +64 -10
- package/dist/response/index.js.map +1 -1
- package/dist/sources/index.cjs +30 -1
- package/dist/sources/index.cjs.map +1 -1
- package/dist/sources/index.js +30 -1
- package/dist/sources/index.js.map +1 -1
- package/dist/streaming/index.cjs +202 -93
- package/dist/streaming/index.cjs.map +1 -1
- package/dist/streaming/index.d.cts +4 -3
- package/dist/streaming/index.d.ts +4 -3
- package/dist/streaming/index.js +172 -73
- package/dist/streaming/index.js.map +1 -1
- package/dist/{streaming-DbQxScpi.d.ts → streaming-Bx-ff2tt.d.ts} +1 -1
- package/dist/{streaming-DfT22A0z.d.cts → streaming-x7umFHoP.d.cts} +1 -1
- package/package.json +15 -4
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
3
|
// src/chat/AgentChat/AgentChat.tsx
|
|
4
|
-
import { twMerge as
|
|
4
|
+
import { twMerge as twMerge9 } from "tailwind-merge";
|
|
5
5
|
|
|
6
6
|
// src/hooks/useAgentChat.ts
|
|
7
7
|
import { useReducer, useCallback, useRef } from "react";
|
|
@@ -12,7 +12,8 @@ var initialState = {
|
|
|
12
12
|
error: null,
|
|
13
13
|
inputValue: "",
|
|
14
14
|
streamPhase: "idle",
|
|
15
|
-
streamingContent: ""
|
|
15
|
+
streamingContent: "",
|
|
16
|
+
streamingAgent: null
|
|
16
17
|
};
|
|
17
18
|
function reducer(state, action) {
|
|
18
19
|
switch (action.type) {
|
|
@@ -26,12 +27,15 @@ function reducer(state, action) {
|
|
|
26
27
|
error: null,
|
|
27
28
|
inputValue: "",
|
|
28
29
|
streamPhase: "thinking",
|
|
29
|
-
streamingContent: ""
|
|
30
|
+
streamingContent: "",
|
|
31
|
+
streamingAgent: null
|
|
30
32
|
};
|
|
31
33
|
case "STREAM_PHASE":
|
|
32
34
|
return { ...state, streamPhase: action.phase };
|
|
33
35
|
case "STREAM_CONTENT":
|
|
34
36
|
return { ...state, streamingContent: state.streamingContent + action.content };
|
|
37
|
+
case "STREAM_AGENT":
|
|
38
|
+
return { ...state, streamingAgent: action.agent };
|
|
35
39
|
case "SEND_SUCCESS":
|
|
36
40
|
return {
|
|
37
41
|
...state,
|
|
@@ -47,7 +51,8 @@ function reducer(state, action) {
|
|
|
47
51
|
isLoading: false,
|
|
48
52
|
error: action.error,
|
|
49
53
|
streamPhase: "idle",
|
|
50
|
-
streamingContent: ""
|
|
54
|
+
streamingContent: "",
|
|
55
|
+
streamingAgent: null
|
|
51
56
|
};
|
|
52
57
|
case "LOAD_CONVERSATION":
|
|
53
58
|
return {
|
|
@@ -73,107 +78,142 @@ function useAgentChat(config2) {
|
|
|
73
78
|
const configRef = useRef(config2);
|
|
74
79
|
configRef.current = config2;
|
|
75
80
|
const lastUserMessageRef = useRef(null);
|
|
81
|
+
const lastUserAttachmentsRef = useRef(void 0);
|
|
76
82
|
const sendMessage = useCallback(
|
|
77
|
-
async (content) => {
|
|
78
|
-
const { apiUrl, streamPath = "/chat/stream", headers
|
|
83
|
+
async (content, attachments) => {
|
|
84
|
+
const { apiUrl, streamPath = "/chat/stream", headers: headersOrFn, timeout = 3e4, bodyExtra } = configRef.current;
|
|
85
|
+
const headers = typeof headersOrFn === "function" ? await headersOrFn() : headersOrFn ?? {};
|
|
79
86
|
lastUserMessageRef.current = content;
|
|
87
|
+
lastUserAttachmentsRef.current = attachments;
|
|
80
88
|
const userMessage = {
|
|
81
89
|
id: generateMessageId(),
|
|
82
90
|
role: "user",
|
|
83
91
|
content,
|
|
92
|
+
attachments,
|
|
84
93
|
timestamp: /* @__PURE__ */ new Date()
|
|
85
94
|
};
|
|
86
95
|
dispatch({ type: "SEND_START", message: userMessage });
|
|
87
96
|
const controller = new AbortController();
|
|
88
97
|
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
89
98
|
try {
|
|
90
|
-
const
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
error: {
|
|
108
|
-
code: "API_ERROR",
|
|
109
|
-
message: `HTTP ${response.status}: ${response.statusText}`,
|
|
110
|
-
retryable: response.status >= 500
|
|
111
|
-
}
|
|
112
|
-
});
|
|
113
|
-
return;
|
|
99
|
+
const url = `${apiUrl}${streamPath}`;
|
|
100
|
+
const mergedHeaders = {
|
|
101
|
+
"Content-Type": "application/json",
|
|
102
|
+
Accept: "text/event-stream",
|
|
103
|
+
...headers
|
|
104
|
+
};
|
|
105
|
+
const requestBody = {
|
|
106
|
+
message: content,
|
|
107
|
+
conversation_id: state.conversationId,
|
|
108
|
+
...bodyExtra
|
|
109
|
+
};
|
|
110
|
+
if (attachments && attachments.length > 0) {
|
|
111
|
+
requestBody.attachments = attachments.map((a) => ({
|
|
112
|
+
filename: a.filename,
|
|
113
|
+
content_type: a.content_type,
|
|
114
|
+
data: a.data
|
|
115
|
+
}));
|
|
114
116
|
}
|
|
115
|
-
const
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
117
|
+
const body = JSON.stringify(requestBody);
|
|
118
|
+
const ctx = {
|
|
119
|
+
accumulatedContent: "",
|
|
120
|
+
agentResponse: null,
|
|
121
|
+
capturedAgent: null,
|
|
122
|
+
capturedConversationId: null,
|
|
123
|
+
hadStreamError: false
|
|
124
|
+
};
|
|
125
|
+
const handleEvent = (event) => {
|
|
126
|
+
switch (event.type) {
|
|
127
|
+
case "agent":
|
|
128
|
+
ctx.capturedAgent = event.agent;
|
|
129
|
+
dispatch({ type: "STREAM_AGENT", agent: ctx.capturedAgent });
|
|
130
|
+
break;
|
|
131
|
+
case "phase":
|
|
132
|
+
dispatch({ type: "STREAM_PHASE", phase: event.phase });
|
|
133
|
+
break;
|
|
134
|
+
case "delta":
|
|
135
|
+
ctx.accumulatedContent += event.content;
|
|
136
|
+
dispatch({ type: "STREAM_CONTENT", content: event.content });
|
|
137
|
+
break;
|
|
138
|
+
case "done":
|
|
139
|
+
ctx.agentResponse = event.response;
|
|
140
|
+
ctx.capturedConversationId = event.conversation_id ?? null;
|
|
141
|
+
break;
|
|
142
|
+
case "error":
|
|
143
|
+
ctx.hadStreamError = true;
|
|
144
|
+
dispatch({ type: "SEND_ERROR", error: event.error });
|
|
145
|
+
break;
|
|
146
|
+
}
|
|
147
|
+
};
|
|
148
|
+
const { streamAdapter } = configRef.current;
|
|
149
|
+
if (streamAdapter) {
|
|
150
|
+
await streamAdapter(
|
|
151
|
+
url,
|
|
152
|
+
{ method: "POST", headers: mergedHeaders, body, signal: controller.signal },
|
|
153
|
+
handleEvent
|
|
154
|
+
);
|
|
155
|
+
clearTimeout(timeoutId);
|
|
156
|
+
} else {
|
|
157
|
+
const response = await fetch(url, {
|
|
158
|
+
method: "POST",
|
|
159
|
+
headers: mergedHeaders,
|
|
160
|
+
body,
|
|
161
|
+
signal: controller.signal
|
|
120
162
|
});
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
const
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
dispatch({ type: "SEND_ERROR", error: event.error });
|
|
158
|
-
return;
|
|
163
|
+
clearTimeout(timeoutId);
|
|
164
|
+
if (!response.ok) {
|
|
165
|
+
dispatch({
|
|
166
|
+
type: "SEND_ERROR",
|
|
167
|
+
error: {
|
|
168
|
+
code: "API_ERROR",
|
|
169
|
+
message: `HTTP ${response.status}: ${response.statusText}`,
|
|
170
|
+
retryable: response.status >= 500
|
|
171
|
+
}
|
|
172
|
+
});
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
const reader = response.body?.getReader();
|
|
176
|
+
if (!reader) {
|
|
177
|
+
dispatch({
|
|
178
|
+
type: "SEND_ERROR",
|
|
179
|
+
error: { code: "STREAM_ERROR", message: "No response body", retryable: true }
|
|
180
|
+
});
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
const decoder = new TextDecoder();
|
|
184
|
+
let buffer = "";
|
|
185
|
+
while (true) {
|
|
186
|
+
const { done, value } = await reader.read();
|
|
187
|
+
if (done) break;
|
|
188
|
+
buffer += decoder.decode(value, { stream: true });
|
|
189
|
+
const lines = buffer.split("\n");
|
|
190
|
+
buffer = lines.pop() ?? "";
|
|
191
|
+
for (const line of lines) {
|
|
192
|
+
if (!line.startsWith("data: ")) continue;
|
|
193
|
+
const data = line.slice(6).trim();
|
|
194
|
+
if (data === "[DONE]") continue;
|
|
195
|
+
try {
|
|
196
|
+
const event = JSON.parse(data);
|
|
197
|
+
handleEvent(event);
|
|
198
|
+
} catch {
|
|
159
199
|
}
|
|
160
|
-
} catch {
|
|
161
200
|
}
|
|
162
201
|
}
|
|
163
202
|
}
|
|
203
|
+
if (ctx.hadStreamError) return;
|
|
164
204
|
const assistantMessage = {
|
|
165
205
|
id: generateMessageId(),
|
|
166
206
|
role: "assistant",
|
|
167
|
-
content: agentResponse?.message ?? accumulatedContent,
|
|
168
|
-
response: agentResponse ?? void 0,
|
|
169
|
-
agent: capturedAgent ?? void 0,
|
|
207
|
+
content: ctx.agentResponse?.message ?? ctx.accumulatedContent,
|
|
208
|
+
response: ctx.agentResponse ?? void 0,
|
|
209
|
+
agent: ctx.capturedAgent ?? void 0,
|
|
170
210
|
timestamp: /* @__PURE__ */ new Date()
|
|
171
211
|
};
|
|
172
212
|
dispatch({
|
|
173
213
|
type: "SEND_SUCCESS",
|
|
174
214
|
message: assistantMessage,
|
|
175
|
-
streamingContent: accumulatedContent,
|
|
176
|
-
conversationId: capturedConversationId
|
|
215
|
+
streamingContent: ctx.accumulatedContent,
|
|
216
|
+
conversationId: ctx.capturedConversationId
|
|
177
217
|
});
|
|
178
218
|
} catch (err) {
|
|
179
219
|
clearTimeout(timeoutId);
|
|
@@ -204,7 +244,8 @@ function useAgentChat(config2) {
|
|
|
204
244
|
}, []);
|
|
205
245
|
const submitFeedback = useCallback(
|
|
206
246
|
async (messageId, rating, comment) => {
|
|
207
|
-
const { apiUrl, feedbackPath = "/feedback", headers
|
|
247
|
+
const { apiUrl, feedbackPath = "/feedback", headers: headersOrFn } = configRef.current;
|
|
248
|
+
const headers = typeof headersOrFn === "function" ? await headersOrFn() : headersOrFn ?? {};
|
|
208
249
|
await fetch(`${apiUrl}${feedbackPath}`, {
|
|
209
250
|
method: "POST",
|
|
210
251
|
headers: { "Content-Type": "application/json", ...headers },
|
|
@@ -215,12 +256,13 @@ function useAgentChat(config2) {
|
|
|
215
256
|
);
|
|
216
257
|
const retry = useCallback(async () => {
|
|
217
258
|
if (lastUserMessageRef.current) {
|
|
218
|
-
await sendMessage(lastUserMessageRef.current);
|
|
259
|
+
await sendMessage(lastUserMessageRef.current, lastUserAttachmentsRef.current);
|
|
219
260
|
}
|
|
220
261
|
}, [sendMessage]);
|
|
221
262
|
const reset = useCallback(() => {
|
|
222
263
|
dispatch({ type: "RESET" });
|
|
223
264
|
lastUserMessageRef.current = null;
|
|
265
|
+
lastUserAttachmentsRef.current = void 0;
|
|
224
266
|
}, []);
|
|
225
267
|
const actions = {
|
|
226
268
|
sendMessage,
|
|
@@ -235,7 +277,7 @@ function useAgentChat(config2) {
|
|
|
235
277
|
|
|
236
278
|
// src/chat/MessageThread/MessageThread.tsx
|
|
237
279
|
import { twMerge as twMerge5 } from "tailwind-merge";
|
|
238
|
-
import { useEffect, useRef as useRef2 } from "react";
|
|
280
|
+
import { useCallback as useCallback2, useEffect, useRef as useRef2 } from "react";
|
|
239
281
|
|
|
240
282
|
// src/chat/MessageBubble/MessageBubble.tsx
|
|
241
283
|
import { twMerge as twMerge4 } from "tailwind-merge";
|
|
@@ -244,6 +286,7 @@ import { twMerge as twMerge4 } from "tailwind-merge";
|
|
|
244
286
|
import { Badge as Badge2 } from "@surf-kit/core";
|
|
245
287
|
|
|
246
288
|
// src/response/ResponseMessage/ResponseMessage.tsx
|
|
289
|
+
import React from "react";
|
|
247
290
|
import ReactMarkdown from "react-markdown";
|
|
248
291
|
import rehypeSanitize from "rehype-sanitize";
|
|
249
292
|
import { twMerge } from "tailwind-merge";
|
|
@@ -268,6 +311,7 @@ function ResponseMessage({ content, className }) {
|
|
|
268
311
|
"[&_h3]:text-sm [&_h3]:font-semibold [&_h3]:text-accent [&_h3]:mt-2 [&_h3]:mb-1",
|
|
269
312
|
"[&_code]:bg-surface-raised [&_code]:text-accent [&_code]:px-1.5 [&_code]:py-0.5 [&_code]:rounded [&_code]:text-xs [&_code]:font-mono",
|
|
270
313
|
"[&_pre]:bg-surface-raised [&_pre]:border [&_pre]:border-border [&_pre]:rounded-xl [&_pre]:p-4 [&_pre]:overflow-x-auto",
|
|
314
|
+
"[&_hr]:my-3 [&_hr]:border-border",
|
|
271
315
|
"[&_blockquote]:border-l-2 [&_blockquote]:border-border-strong [&_blockquote]:pl-4 [&_blockquote]:text-text-secondary",
|
|
272
316
|
"[&_a]:text-accent [&_a]:underline-offset-2 [&_a]:hover:text-accent/80",
|
|
273
317
|
className
|
|
@@ -283,11 +327,24 @@ function ResponseMessage({ content, className }) {
|
|
|
283
327
|
p: ({ children }) => /* @__PURE__ */ jsx("p", { className: "my-2", children }),
|
|
284
328
|
ul: ({ children }) => /* @__PURE__ */ jsx("ul", { className: "my-2 list-disc pl-6", children }),
|
|
285
329
|
ol: ({ children }) => /* @__PURE__ */ jsx("ol", { className: "my-2 list-decimal pl-6", children }),
|
|
286
|
-
li: ({ children }) =>
|
|
330
|
+
li: ({ children, ...props }) => {
|
|
331
|
+
let content2 = children;
|
|
332
|
+
if (props.ordered) {
|
|
333
|
+
content2 = React.Children.map(children, (child, i) => {
|
|
334
|
+
if (i === 0 && typeof child === "string") {
|
|
335
|
+
return child.replace(/^\d+[.)]\s*/, "");
|
|
336
|
+
}
|
|
337
|
+
return child;
|
|
338
|
+
});
|
|
339
|
+
}
|
|
340
|
+
return /* @__PURE__ */ jsx("li", { className: "my-1", children: content2 });
|
|
341
|
+
},
|
|
287
342
|
strong: ({ children }) => /* @__PURE__ */ jsx("strong", { className: "font-semibold", children }),
|
|
343
|
+
em: ({ children }) => /* @__PURE__ */ jsx("em", { className: "italic text-text-secondary", children }),
|
|
288
344
|
h1: ({ children }) => /* @__PURE__ */ jsx("h1", { className: "text-base font-bold mt-4 mb-2", children }),
|
|
289
345
|
h2: ({ children }) => /* @__PURE__ */ jsx("h2", { className: "text-sm font-bold mt-3 mb-1", children }),
|
|
290
346
|
h3: ({ children }) => /* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold mt-2 mb-1", children }),
|
|
347
|
+
hr: () => /* @__PURE__ */ jsx("hr", { className: "my-3 border-border" }),
|
|
291
348
|
code: ({ children }) => /* @__PURE__ */ jsx("code", { className: "bg-surface-sunken rounded px-1 py-0.5 text-xs font-mono", children })
|
|
292
349
|
},
|
|
293
350
|
children: normalizeMarkdownLists(content)
|
|
@@ -423,7 +480,14 @@ function renderWarning(data) {
|
|
|
423
480
|
}
|
|
424
481
|
);
|
|
425
482
|
}
|
|
426
|
-
function StructuredResponse({ uiHint, data, className }) {
|
|
483
|
+
function StructuredResponse({ uiHint, data: rawData, className }) {
|
|
484
|
+
const data = typeof rawData === "string" ? (() => {
|
|
485
|
+
try {
|
|
486
|
+
return JSON.parse(rawData);
|
|
487
|
+
} catch {
|
|
488
|
+
return null;
|
|
489
|
+
}
|
|
490
|
+
})() : rawData;
|
|
427
491
|
if (!data) return null;
|
|
428
492
|
let content;
|
|
429
493
|
switch (uiHint) {
|
|
@@ -503,7 +567,36 @@ function SourceCard({ source, variant = "compact", onNavigate, className }) {
|
|
|
503
567
|
children: [
|
|
504
568
|
/* @__PURE__ */ jsxs2("div", { className: "flex items-start justify-between gap-2", children: [
|
|
505
569
|
/* @__PURE__ */ jsxs2("div", { className: "flex-1 min-w-0", children: [
|
|
506
|
-
/* @__PURE__ */
|
|
570
|
+
source.url ? /* @__PURE__ */ jsxs2(
|
|
571
|
+
"a",
|
|
572
|
+
{
|
|
573
|
+
href: source.url,
|
|
574
|
+
target: "_blank",
|
|
575
|
+
rel: "noopener noreferrer",
|
|
576
|
+
className: "text-sm font-medium text-accent hover:underline truncate block",
|
|
577
|
+
onClick: (e) => e.stopPropagation(),
|
|
578
|
+
children: [
|
|
579
|
+
source.title,
|
|
580
|
+
/* @__PURE__ */ jsxs2(
|
|
581
|
+
"svg",
|
|
582
|
+
{
|
|
583
|
+
className: "inline-block ml-1 w-3 h-3 opacity-60",
|
|
584
|
+
viewBox: "0 0 24 24",
|
|
585
|
+
fill: "none",
|
|
586
|
+
stroke: "currentColor",
|
|
587
|
+
strokeWidth: "2",
|
|
588
|
+
strokeLinecap: "round",
|
|
589
|
+
strokeLinejoin: "round",
|
|
590
|
+
children: [
|
|
591
|
+
/* @__PURE__ */ jsx3("path", { d: "M18 13v6a2 2 0 01-2 2H5a2 2 0 01-2-2V8a2 2 0 012-2h6" }),
|
|
592
|
+
/* @__PURE__ */ jsx3("polyline", { points: "15 3 21 3 21 9" }),
|
|
593
|
+
/* @__PURE__ */ jsx3("line", { x1: "10", y1: "14", x2: "21", y2: "3" })
|
|
594
|
+
]
|
|
595
|
+
}
|
|
596
|
+
)
|
|
597
|
+
]
|
|
598
|
+
}
|
|
599
|
+
) : /* @__PURE__ */ jsx3("p", { className: "text-sm font-medium text-text-primary truncate", children: source.title }),
|
|
507
600
|
source.section && /* @__PURE__ */ jsx3("p", { className: "text-[11px] font-semibold uppercase tracking-wider text-text-secondary truncate mt-0.5", children: source.section })
|
|
508
601
|
] }),
|
|
509
602
|
/* @__PURE__ */ jsx3(
|
|
@@ -637,13 +730,16 @@ function AgentResponse({
|
|
|
637
730
|
}) {
|
|
638
731
|
return /* @__PURE__ */ jsxs4("div", { className: `flex flex-col gap-4 ${className ?? ""}`, "data-testid": "agent-response", children: [
|
|
639
732
|
/* @__PURE__ */ jsx6(ResponseMessage, { content: response.message }),
|
|
640
|
-
response.ui_hint !== "text" && response.structured_data &&
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
733
|
+
response.ui_hint !== "text" && response.structured_data && (() => {
|
|
734
|
+
const parsed = typeof response.structured_data === "string" ? (() => {
|
|
735
|
+
try {
|
|
736
|
+
return JSON.parse(response.structured_data);
|
|
737
|
+
} catch {
|
|
738
|
+
return null;
|
|
739
|
+
}
|
|
740
|
+
})() : response.structured_data;
|
|
741
|
+
return parsed ? /* @__PURE__ */ jsx6(StructuredResponse, { uiHint: response.ui_hint, data: parsed }) : null;
|
|
742
|
+
})(),
|
|
647
743
|
(showConfidence || showVerification) && /* @__PURE__ */ jsxs4("div", { className: "flex flex-wrap items-center gap-2 mt-1", "data-testid": "response-meta", children: [
|
|
648
744
|
showConfidence && /* @__PURE__ */ jsxs4(
|
|
649
745
|
Badge2,
|
|
@@ -694,6 +790,31 @@ function AgentResponse({
|
|
|
694
790
|
|
|
695
791
|
// src/chat/MessageBubble/MessageBubble.tsx
|
|
696
792
|
import { jsx as jsx7, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
793
|
+
function DocumentIcon() {
|
|
794
|
+
return /* @__PURE__ */ jsxs5("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round", children: [
|
|
795
|
+
/* @__PURE__ */ jsx7("path", { d: "M14 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V8z" }),
|
|
796
|
+
/* @__PURE__ */ jsx7("polyline", { points: "14 2 14 8 20 8" }),
|
|
797
|
+
/* @__PURE__ */ jsx7("line", { x1: "16", y1: "13", x2: "8", y2: "13" }),
|
|
798
|
+
/* @__PURE__ */ jsx7("line", { x1: "16", y1: "17", x2: "8", y2: "17" })
|
|
799
|
+
] });
|
|
800
|
+
}
|
|
801
|
+
function AttachmentThumbnail({ attachment }) {
|
|
802
|
+
const isImage = attachment.content_type.startsWith("image/");
|
|
803
|
+
if (isImage) {
|
|
804
|
+
return /* @__PURE__ */ jsx7("div", { className: "rounded-lg overflow-hidden border border-black/10 max-w-[240px]", children: /* @__PURE__ */ jsx7(
|
|
805
|
+
"img",
|
|
806
|
+
{
|
|
807
|
+
src: attachment.preview_url ?? `data:${attachment.content_type};base64,${attachment.data}`,
|
|
808
|
+
alt: attachment.filename,
|
|
809
|
+
className: "max-w-full max-h-[200px] object-contain"
|
|
810
|
+
}
|
|
811
|
+
) });
|
|
812
|
+
}
|
|
813
|
+
return /* @__PURE__ */ jsxs5("div", { className: "flex items-center gap-2 px-3 py-2 rounded-lg border border-black/10 bg-black/5", children: [
|
|
814
|
+
/* @__PURE__ */ jsx7(DocumentIcon, {}),
|
|
815
|
+
/* @__PURE__ */ jsx7("span", { className: "text-xs truncate max-w-[160px]", children: attachment.filename })
|
|
816
|
+
] });
|
|
817
|
+
}
|
|
697
818
|
function MessageBubble({
|
|
698
819
|
message,
|
|
699
820
|
showAgent,
|
|
@@ -701,23 +822,29 @@ function MessageBubble({
|
|
|
701
822
|
showConfidence = true,
|
|
702
823
|
showVerification = true,
|
|
703
824
|
animated = true,
|
|
825
|
+
userBubbleClassName,
|
|
704
826
|
className
|
|
705
827
|
}) {
|
|
706
828
|
const isUser = message.role === "user";
|
|
829
|
+
const hasAttachments = message.attachments && message.attachments.length > 0;
|
|
707
830
|
if (isUser) {
|
|
708
831
|
return /* @__PURE__ */ jsx7(
|
|
709
832
|
"div",
|
|
710
833
|
{
|
|
711
834
|
"data-message-id": message.id,
|
|
712
835
|
className: twMerge4("flex w-full justify-end", className),
|
|
713
|
-
children: /* @__PURE__ */
|
|
836
|
+
children: /* @__PURE__ */ jsxs5(
|
|
714
837
|
"div",
|
|
715
838
|
{
|
|
716
839
|
className: twMerge4(
|
|
717
|
-
"max-w-[70%] rounded-[18px] rounded-br-[4px] px-4 py-2.5 bg-
|
|
718
|
-
animated && "motion-safe:animate-slideFromRight"
|
|
840
|
+
"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",
|
|
841
|
+
animated && "motion-safe:animate-slideFromRight",
|
|
842
|
+
userBubbleClassName
|
|
719
843
|
),
|
|
720
|
-
children:
|
|
844
|
+
children: [
|
|
845
|
+
hasAttachments && /* @__PURE__ */ jsx7("div", { className: "flex flex-wrap gap-2 mb-2", children: message.attachments.map((att, i) => /* @__PURE__ */ jsx7(AttachmentThumbnail, { attachment: att }, `${att.filename}-${i}`)) }),
|
|
846
|
+
message.content
|
|
847
|
+
]
|
|
721
848
|
}
|
|
722
849
|
)
|
|
723
850
|
}
|
|
@@ -729,7 +856,7 @@ function MessageBubble({
|
|
|
729
856
|
"data-message-id": message.id,
|
|
730
857
|
className: twMerge4("flex w-full flex-col items-start gap-1.5", className),
|
|
731
858
|
children: [
|
|
732
|
-
showAgent && message.agent && /* @__PURE__ */ jsx7("div", { className: "text-[11px] font-semibold uppercase tracking-[0.08em] text-text-muted px-1", children: message.agent.replace("_agent", "").replace("_", " ") }),
|
|
859
|
+
showAgent && message.agent && /* @__PURE__ */ jsx7("div", { className: "text-[11px] font-display font-semibold uppercase tracking-[0.08em] text-text-muted px-1", children: message.agent.replace("_agent", "").replace("_", " ") }),
|
|
733
860
|
/* @__PURE__ */ jsx7(
|
|
734
861
|
"div",
|
|
735
862
|
{
|
|
@@ -755,34 +882,70 @@ function MessageBubble({
|
|
|
755
882
|
|
|
756
883
|
// src/chat/MessageThread/MessageThread.tsx
|
|
757
884
|
import { jsx as jsx8, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
758
|
-
function MessageThread({ messages, streamingSlot, showSources, showConfidence, showVerification, className }) {
|
|
759
|
-
const
|
|
885
|
+
function MessageThread({ messages, streamingSlot, showAgent, showSources, showConfidence, showVerification, hideLastAssistant, userBubbleClassName, className }) {
|
|
886
|
+
const scrollRef = useRef2(null);
|
|
887
|
+
const isNearBottom = useRef2(true);
|
|
888
|
+
const isProgrammaticScroll = useRef2(false);
|
|
889
|
+
const hasStreaming = !!streamingSlot;
|
|
890
|
+
const scrollToBottom = useCallback2(() => {
|
|
891
|
+
const el = scrollRef.current;
|
|
892
|
+
if (el && isNearBottom.current) {
|
|
893
|
+
isProgrammaticScroll.current = true;
|
|
894
|
+
el.scrollTop = el.scrollHeight;
|
|
895
|
+
}
|
|
896
|
+
}, []);
|
|
897
|
+
const handleScroll = useCallback2(() => {
|
|
898
|
+
if (isProgrammaticScroll.current) {
|
|
899
|
+
isProgrammaticScroll.current = false;
|
|
900
|
+
return;
|
|
901
|
+
}
|
|
902
|
+
const el = scrollRef.current;
|
|
903
|
+
if (!el) return;
|
|
904
|
+
isNearBottom.current = el.scrollHeight - el.scrollTop - el.clientHeight < 80;
|
|
905
|
+
}, []);
|
|
906
|
+
useEffect(scrollToBottom, [messages.length, scrollToBottom]);
|
|
760
907
|
useEffect(() => {
|
|
761
|
-
|
|
762
|
-
|
|
908
|
+
if (!hasStreaming) return;
|
|
909
|
+
let raf;
|
|
910
|
+
const tick = () => {
|
|
911
|
+
scrollToBottom();
|
|
912
|
+
raf = requestAnimationFrame(tick);
|
|
913
|
+
};
|
|
914
|
+
raf = requestAnimationFrame(tick);
|
|
915
|
+
return () => cancelAnimationFrame(raf);
|
|
916
|
+
}, [hasStreaming, scrollToBottom]);
|
|
763
917
|
return /* @__PURE__ */ jsxs6(
|
|
764
918
|
"div",
|
|
765
919
|
{
|
|
920
|
+
ref: scrollRef,
|
|
766
921
|
role: "log",
|
|
767
922
|
"aria-live": "polite",
|
|
768
923
|
"aria-label": "Message thread",
|
|
924
|
+
onScroll: handleScroll,
|
|
769
925
|
className: twMerge5(
|
|
770
926
|
"flex flex-col gap-4 overflow-y-auto flex-1 px-4 py-6",
|
|
771
927
|
className
|
|
772
928
|
),
|
|
773
929
|
children: [
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
{
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
930
|
+
/* @__PURE__ */ jsx8("div", { className: "flex-1 shrink-0" }),
|
|
931
|
+
messages.map((message, i) => {
|
|
932
|
+
if (hideLastAssistant && i === messages.length - 1 && message.role === "assistant") {
|
|
933
|
+
return null;
|
|
934
|
+
}
|
|
935
|
+
return /* @__PURE__ */ jsx8(
|
|
936
|
+
MessageBubble,
|
|
937
|
+
{
|
|
938
|
+
message,
|
|
939
|
+
showAgent,
|
|
940
|
+
showSources,
|
|
941
|
+
showConfidence,
|
|
942
|
+
showVerification,
|
|
943
|
+
userBubbleClassName
|
|
944
|
+
},
|
|
945
|
+
message.id
|
|
946
|
+
);
|
|
947
|
+
}),
|
|
948
|
+
streamingSlot
|
|
786
949
|
]
|
|
787
950
|
}
|
|
788
951
|
);
|
|
@@ -790,8 +953,96 @@ function MessageThread({ messages, streamingSlot, showSources, showConfidence, s
|
|
|
790
953
|
|
|
791
954
|
// src/chat/MessageComposer/MessageComposer.tsx
|
|
792
955
|
import { twMerge as twMerge6 } from "tailwind-merge";
|
|
793
|
-
import { useState as useState2, useRef as useRef3, useCallback as
|
|
956
|
+
import { useState as useState2, useRef as useRef3, useCallback as useCallback3 } from "react";
|
|
794
957
|
import { jsx as jsx9, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
958
|
+
var ALLOWED_TYPES = /* @__PURE__ */ new Set([
|
|
959
|
+
"image/png",
|
|
960
|
+
"image/jpeg",
|
|
961
|
+
"image/gif",
|
|
962
|
+
"image/webp",
|
|
963
|
+
"application/pdf"
|
|
964
|
+
]);
|
|
965
|
+
var MAX_FILE_SIZE = 10 * 1024 * 1024;
|
|
966
|
+
var MAX_ATTACHMENTS = 5;
|
|
967
|
+
function ArrowUpIcon() {
|
|
968
|
+
return /* @__PURE__ */ jsxs7("svg", { width: "20", height: "20", viewBox: "0 0 20 20", fill: "none", stroke: "currentColor", strokeWidth: "2.5", strokeLinecap: "round", strokeLinejoin: "round", children: [
|
|
969
|
+
/* @__PURE__ */ jsx9("path", { d: "M10 16V4" }),
|
|
970
|
+
/* @__PURE__ */ jsx9("path", { d: "M4 10l6-6 6 6" })
|
|
971
|
+
] });
|
|
972
|
+
}
|
|
973
|
+
function StopIcon() {
|
|
974
|
+
return /* @__PURE__ */ jsx9("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "currentColor", children: /* @__PURE__ */ jsx9("rect", { x: "3", y: "3", width: "10", height: "10", rx: "2" }) });
|
|
975
|
+
}
|
|
976
|
+
function PaperclipIcon() {
|
|
977
|
+
return /* @__PURE__ */ jsx9("svg", { width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ jsx9("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" }) });
|
|
978
|
+
}
|
|
979
|
+
function XIcon({ size = 14 }) {
|
|
980
|
+
return /* @__PURE__ */ jsxs7("svg", { width: size, height: size, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.5", strokeLinecap: "round", strokeLinejoin: "round", children: [
|
|
981
|
+
/* @__PURE__ */ jsx9("path", { d: "M18 6L6 18" }),
|
|
982
|
+
/* @__PURE__ */ jsx9("path", { d: "M6 6l12 12" })
|
|
983
|
+
] });
|
|
984
|
+
}
|
|
985
|
+
function DocumentIcon2() {
|
|
986
|
+
return /* @__PURE__ */ jsxs7("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round", children: [
|
|
987
|
+
/* @__PURE__ */ jsx9("path", { d: "M14 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V8z" }),
|
|
988
|
+
/* @__PURE__ */ jsx9("polyline", { points: "14 2 14 8 20 8" }),
|
|
989
|
+
/* @__PURE__ */ jsx9("line", { x1: "16", y1: "13", x2: "8", y2: "13" }),
|
|
990
|
+
/* @__PURE__ */ jsx9("line", { x1: "16", y1: "17", x2: "8", y2: "17" }),
|
|
991
|
+
/* @__PURE__ */ jsx9("polyline", { points: "10 9 9 9 8 9" })
|
|
992
|
+
] });
|
|
993
|
+
}
|
|
994
|
+
function fileToBase64(file) {
|
|
995
|
+
return new Promise((resolve, reject) => {
|
|
996
|
+
const reader = new FileReader();
|
|
997
|
+
reader.onload = () => {
|
|
998
|
+
const result = reader.result;
|
|
999
|
+
const base64 = result.split(",")[1];
|
|
1000
|
+
resolve(base64);
|
|
1001
|
+
};
|
|
1002
|
+
reader.onerror = reject;
|
|
1003
|
+
reader.readAsDataURL(file);
|
|
1004
|
+
});
|
|
1005
|
+
}
|
|
1006
|
+
function AttachmentPreview({
|
|
1007
|
+
attachment,
|
|
1008
|
+
onRemove
|
|
1009
|
+
}) {
|
|
1010
|
+
const isImage = attachment.content_type.startsWith("image/");
|
|
1011
|
+
return /* @__PURE__ */ jsxs7("div", { className: "relative group flex-shrink-0", children: [
|
|
1012
|
+
isImage ? /* @__PURE__ */ jsx9("div", { className: "w-16 h-16 rounded-lg overflow-hidden border border-border/60 bg-surface-alt", children: /* @__PURE__ */ jsx9(
|
|
1013
|
+
"img",
|
|
1014
|
+
{
|
|
1015
|
+
src: attachment.preview_url ?? `data:${attachment.content_type};base64,${attachment.data}`,
|
|
1016
|
+
alt: attachment.filename,
|
|
1017
|
+
className: "w-full h-full object-cover"
|
|
1018
|
+
}
|
|
1019
|
+
) }) : /* @__PURE__ */ jsxs7("div", { className: "h-16 px-3 rounded-lg border border-border/60 bg-surface-alt flex items-center gap-2", children: [
|
|
1020
|
+
/* @__PURE__ */ jsx9("div", { className: "text-text-muted", children: /* @__PURE__ */ jsx9(DocumentIcon2, {}) }),
|
|
1021
|
+
/* @__PURE__ */ jsxs7("div", { className: "flex flex-col min-w-0", children: [
|
|
1022
|
+
/* @__PURE__ */ jsx9("span", { className: "text-xs text-text-primary truncate max-w-[120px]", children: attachment.filename }),
|
|
1023
|
+
/* @__PURE__ */ jsx9("span", { className: "text-[10px] text-text-muted", children: "PDF" })
|
|
1024
|
+
] })
|
|
1025
|
+
] }),
|
|
1026
|
+
/* @__PURE__ */ jsx9(
|
|
1027
|
+
"button",
|
|
1028
|
+
{
|
|
1029
|
+
type: "button",
|
|
1030
|
+
onClick: onRemove,
|
|
1031
|
+
className: twMerge6(
|
|
1032
|
+
"absolute -top-1.5 -right-1.5",
|
|
1033
|
+
"w-5 h-5 rounded-full",
|
|
1034
|
+
"bg-text-muted/80 text-white",
|
|
1035
|
+
"flex items-center justify-center",
|
|
1036
|
+
"opacity-0 group-hover:opacity-100",
|
|
1037
|
+
"transition-opacity duration-150",
|
|
1038
|
+
"hover:bg-text-primary"
|
|
1039
|
+
),
|
|
1040
|
+
"aria-label": `Remove ${attachment.filename}`,
|
|
1041
|
+
children: /* @__PURE__ */ jsx9(XIcon, { size: 10 })
|
|
1042
|
+
}
|
|
1043
|
+
)
|
|
1044
|
+
] });
|
|
1045
|
+
}
|
|
795
1046
|
function MessageComposer({
|
|
796
1047
|
onSend,
|
|
797
1048
|
isLoading = false,
|
|
@@ -799,23 +1050,29 @@ function MessageComposer({
|
|
|
799
1050
|
className
|
|
800
1051
|
}) {
|
|
801
1052
|
const [value, setValue] = useState2("");
|
|
1053
|
+
const [attachments, setAttachments] = useState2([]);
|
|
1054
|
+
const [dragOver, setDragOver] = useState2(false);
|
|
802
1055
|
const textareaRef = useRef3(null);
|
|
803
|
-
const
|
|
804
|
-
const
|
|
1056
|
+
const fileInputRef = useRef3(null);
|
|
1057
|
+
const canSend = (value.trim().length > 0 || attachments.length > 0) && !isLoading;
|
|
1058
|
+
const resetHeight = useCallback3(() => {
|
|
805
1059
|
const el = textareaRef.current;
|
|
806
1060
|
if (el) {
|
|
807
1061
|
el.style.height = "auto";
|
|
808
1062
|
el.style.overflowY = "hidden";
|
|
809
1063
|
}
|
|
810
1064
|
}, []);
|
|
811
|
-
const handleSend =
|
|
1065
|
+
const handleSend = useCallback3(() => {
|
|
812
1066
|
if (!canSend) return;
|
|
813
|
-
|
|
1067
|
+
const message = value.trim() || (attachments.length > 0 ? "Please analyse the attached file(s)." : "");
|
|
1068
|
+
if (!message && attachments.length === 0) return;
|
|
1069
|
+
onSend(message, attachments.length > 0 ? attachments : void 0);
|
|
814
1070
|
setValue("");
|
|
1071
|
+
setAttachments([]);
|
|
815
1072
|
resetHeight();
|
|
816
1073
|
textareaRef.current?.focus();
|
|
817
|
-
}, [canSend, onSend, value, resetHeight]);
|
|
818
|
-
const handleKeyDown =
|
|
1074
|
+
}, [canSend, onSend, value, attachments, resetHeight]);
|
|
1075
|
+
const handleKeyDown = useCallback3(
|
|
819
1076
|
(e) => {
|
|
820
1077
|
if (e.key === "Enter" && !e.shiftKey) {
|
|
821
1078
|
e.preventDefault();
|
|
@@ -824,64 +1081,194 @@ function MessageComposer({
|
|
|
824
1081
|
},
|
|
825
1082
|
[handleSend]
|
|
826
1083
|
);
|
|
827
|
-
const handleChange =
|
|
1084
|
+
const handleChange = useCallback3(
|
|
828
1085
|
(e) => {
|
|
829
1086
|
setValue(e.target.value);
|
|
830
1087
|
const el = e.target;
|
|
831
1088
|
el.style.height = "auto";
|
|
832
|
-
const capped = Math.min(el.scrollHeight,
|
|
1089
|
+
const capped = Math.min(el.scrollHeight, 200);
|
|
833
1090
|
el.style.height = `${capped}px`;
|
|
834
|
-
el.style.overflowY = el.scrollHeight >
|
|
1091
|
+
el.style.overflowY = el.scrollHeight > 200 ? "auto" : "hidden";
|
|
835
1092
|
},
|
|
836
1093
|
[]
|
|
837
1094
|
);
|
|
1095
|
+
const addFiles = useCallback3(async (files) => {
|
|
1096
|
+
const fileArray = Array.from(files);
|
|
1097
|
+
for (const file of fileArray) {
|
|
1098
|
+
if (attachments.length >= MAX_ATTACHMENTS) break;
|
|
1099
|
+
if (!ALLOWED_TYPES.has(file.type)) continue;
|
|
1100
|
+
if (file.size > MAX_FILE_SIZE) continue;
|
|
1101
|
+
try {
|
|
1102
|
+
const data = await fileToBase64(file);
|
|
1103
|
+
const previewUrl = file.type.startsWith("image/") ? URL.createObjectURL(file) : void 0;
|
|
1104
|
+
const attachment = {
|
|
1105
|
+
filename: file.name,
|
|
1106
|
+
content_type: file.type,
|
|
1107
|
+
data,
|
|
1108
|
+
preview_url: previewUrl
|
|
1109
|
+
};
|
|
1110
|
+
setAttachments((prev) => {
|
|
1111
|
+
if (prev.length >= MAX_ATTACHMENTS) return prev;
|
|
1112
|
+
return [...prev, attachment];
|
|
1113
|
+
});
|
|
1114
|
+
} catch {
|
|
1115
|
+
}
|
|
1116
|
+
}
|
|
1117
|
+
}, [attachments.length]);
|
|
1118
|
+
const handleFileSelect = useCallback3(() => {
|
|
1119
|
+
fileInputRef.current?.click();
|
|
1120
|
+
}, []);
|
|
1121
|
+
const handleFileInputChange = useCallback3(
|
|
1122
|
+
(e) => {
|
|
1123
|
+
if (e.target.files) {
|
|
1124
|
+
void addFiles(e.target.files);
|
|
1125
|
+
e.target.value = "";
|
|
1126
|
+
}
|
|
1127
|
+
},
|
|
1128
|
+
[addFiles]
|
|
1129
|
+
);
|
|
1130
|
+
const removeAttachment = useCallback3((index) => {
|
|
1131
|
+
setAttachments((prev) => {
|
|
1132
|
+
const removed = prev[index];
|
|
1133
|
+
if (removed?.preview_url) URL.revokeObjectURL(removed.preview_url);
|
|
1134
|
+
return prev.filter((_, i) => i !== index);
|
|
1135
|
+
});
|
|
1136
|
+
}, []);
|
|
1137
|
+
const handlePaste = useCallback3(
|
|
1138
|
+
(e) => {
|
|
1139
|
+
const items = e.clipboardData.items;
|
|
1140
|
+
const files = [];
|
|
1141
|
+
for (const item of items) {
|
|
1142
|
+
if (item.kind === "file" && ALLOWED_TYPES.has(item.type)) {
|
|
1143
|
+
const file = item.getAsFile();
|
|
1144
|
+
if (file) files.push(file);
|
|
1145
|
+
}
|
|
1146
|
+
}
|
|
1147
|
+
if (files.length > 0) {
|
|
1148
|
+
void addFiles(files);
|
|
1149
|
+
}
|
|
1150
|
+
},
|
|
1151
|
+
[addFiles]
|
|
1152
|
+
);
|
|
1153
|
+
const handleDragOver = useCallback3((e) => {
|
|
1154
|
+
e.preventDefault();
|
|
1155
|
+
e.stopPropagation();
|
|
1156
|
+
setDragOver(true);
|
|
1157
|
+
}, []);
|
|
1158
|
+
const handleDragLeave = useCallback3((e) => {
|
|
1159
|
+
e.preventDefault();
|
|
1160
|
+
e.stopPropagation();
|
|
1161
|
+
setDragOver(false);
|
|
1162
|
+
}, []);
|
|
1163
|
+
const handleDrop = useCallback3(
|
|
1164
|
+
(e) => {
|
|
1165
|
+
e.preventDefault();
|
|
1166
|
+
e.stopPropagation();
|
|
1167
|
+
setDragOver(false);
|
|
1168
|
+
if (e.dataTransfer.files.length > 0) {
|
|
1169
|
+
void addFiles(e.dataTransfer.files);
|
|
1170
|
+
}
|
|
1171
|
+
},
|
|
1172
|
+
[addFiles]
|
|
1173
|
+
);
|
|
838
1174
|
return /* @__PURE__ */ jsxs7(
|
|
839
1175
|
"div",
|
|
840
1176
|
{
|
|
841
1177
|
className: twMerge6(
|
|
842
|
-
"
|
|
1178
|
+
"relative shrink-0 rounded-3xl border bg-surface",
|
|
1179
|
+
"shadow-lg shadow-black/10",
|
|
1180
|
+
"transition-all duration-200",
|
|
1181
|
+
"focus-within:border-accent/40 focus-within:shadow-accent/5",
|
|
1182
|
+
dragOver ? "border-accent/60 bg-accent/5" : "border-border/60",
|
|
843
1183
|
className
|
|
844
1184
|
),
|
|
1185
|
+
onDragOver: handleDragOver,
|
|
1186
|
+
onDragLeave: handleDragLeave,
|
|
1187
|
+
onDrop: handleDrop,
|
|
845
1188
|
children: [
|
|
846
1189
|
/* @__PURE__ */ jsx9(
|
|
847
|
-
"
|
|
1190
|
+
"input",
|
|
848
1191
|
{
|
|
849
|
-
ref:
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
className: twMerge6(
|
|
857
|
-
"flex-1 resize-none rounded-xl border border-border bg-surface/80",
|
|
858
|
-
"px-4 py-2.5 text-sm text-text-primary placeholder:text-text-muted",
|
|
859
|
-
"focus:border-transparent focus:ring-2 focus:ring-accent/40 focus:outline-none",
|
|
860
|
-
"disabled:opacity-50 disabled:cursor-not-allowed",
|
|
861
|
-
"overflow-hidden",
|
|
862
|
-
"transition-all duration-200"
|
|
863
|
-
),
|
|
864
|
-
style: { colorScheme: "dark" },
|
|
865
|
-
"aria-label": "Message input"
|
|
1192
|
+
ref: fileInputRef,
|
|
1193
|
+
type: "file",
|
|
1194
|
+
multiple: true,
|
|
1195
|
+
accept: "image/png,image/jpeg,image/gif,image/webp,application/pdf",
|
|
1196
|
+
onChange: handleFileInputChange,
|
|
1197
|
+
className: "hidden",
|
|
1198
|
+
"aria-hidden": "true"
|
|
866
1199
|
}
|
|
867
1200
|
),
|
|
868
|
-
/* @__PURE__ */ jsx9(
|
|
869
|
-
|
|
1201
|
+
attachments.length > 0 && /* @__PURE__ */ jsx9("div", { className: "flex gap-2 px-4 pt-3 pb-1 overflow-x-auto", children: attachments.map((att, i) => /* @__PURE__ */ jsx9(
|
|
1202
|
+
AttachmentPreview,
|
|
870
1203
|
{
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
1204
|
+
attachment: att,
|
|
1205
|
+
onRemove: () => removeAttachment(i)
|
|
1206
|
+
},
|
|
1207
|
+
`${att.filename}-${i}`
|
|
1208
|
+
)) }),
|
|
1209
|
+
dragOver && /* @__PURE__ */ jsx9("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__ */ jsx9("span", { className: "text-sm font-display font-semibold text-accent", children: "Drop files here" }) }),
|
|
1210
|
+
/* @__PURE__ */ jsxs7("div", { className: "flex items-end", children: [
|
|
1211
|
+
/* @__PURE__ */ jsx9(
|
|
1212
|
+
"button",
|
|
1213
|
+
{
|
|
1214
|
+
type: "button",
|
|
1215
|
+
onClick: handleFileSelect,
|
|
1216
|
+
disabled: isLoading || attachments.length >= MAX_ATTACHMENTS,
|
|
1217
|
+
"aria-label": "Attach file",
|
|
1218
|
+
className: twMerge6(
|
|
1219
|
+
"flex-shrink-0 ml-2 mb-3",
|
|
1220
|
+
"inline-flex items-center justify-center",
|
|
1221
|
+
"w-9 h-9 rounded-full",
|
|
1222
|
+
"transition-all duration-200",
|
|
1223
|
+
"text-text-muted/60 hover:text-text-secondary hover:bg-text-muted/10",
|
|
1224
|
+
"focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-accent",
|
|
1225
|
+
"disabled:opacity-30 disabled:cursor-not-allowed disabled:hover:bg-transparent"
|
|
1226
|
+
),
|
|
1227
|
+
children: /* @__PURE__ */ jsx9(PaperclipIcon, {})
|
|
1228
|
+
}
|
|
1229
|
+
),
|
|
1230
|
+
/* @__PURE__ */ jsx9(
|
|
1231
|
+
"textarea",
|
|
1232
|
+
{
|
|
1233
|
+
ref: textareaRef,
|
|
1234
|
+
value,
|
|
1235
|
+
onChange: handleChange,
|
|
1236
|
+
onKeyDown: handleKeyDown,
|
|
1237
|
+
onPaste: handlePaste,
|
|
1238
|
+
placeholder,
|
|
1239
|
+
rows: 1,
|
|
1240
|
+
disabled: isLoading,
|
|
1241
|
+
className: twMerge6(
|
|
1242
|
+
"flex-1 resize-none bg-transparent",
|
|
1243
|
+
"pl-2 pr-14 pt-4 pb-4 text-[15px] leading-relaxed",
|
|
1244
|
+
"text-text-primary placeholder:text-text-muted/70",
|
|
1245
|
+
"focus:outline-none",
|
|
1246
|
+
"disabled:opacity-50 disabled:cursor-not-allowed",
|
|
1247
|
+
"overflow-hidden"
|
|
1248
|
+
),
|
|
1249
|
+
style: { colorScheme: "dark" },
|
|
1250
|
+
"aria-label": "Message input"
|
|
1251
|
+
}
|
|
1252
|
+
),
|
|
1253
|
+
/* @__PURE__ */ jsx9(
|
|
1254
|
+
"button",
|
|
1255
|
+
{
|
|
1256
|
+
type: "button",
|
|
1257
|
+
onClick: handleSend,
|
|
1258
|
+
disabled: !canSend,
|
|
1259
|
+
"aria-label": "Send message",
|
|
1260
|
+
className: twMerge6(
|
|
1261
|
+
"absolute bottom-3 right-3",
|
|
1262
|
+
"inline-flex items-center justify-center",
|
|
1263
|
+
"w-9 h-9 rounded-full",
|
|
1264
|
+
"transition-all duration-200",
|
|
1265
|
+
"focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-accent",
|
|
1266
|
+
canSend ? "bg-accent text-white hover:bg-accent-hover active:scale-90 shadow-md shadow-accent/25" : isLoading ? "bg-text-muted/20 text-text-secondary hover:bg-text-muted/30" : "bg-transparent text-text-muted/40 cursor-default"
|
|
1267
|
+
),
|
|
1268
|
+
children: isLoading ? /* @__PURE__ */ jsx9(StopIcon, {}) : /* @__PURE__ */ jsx9(ArrowUpIcon, {})
|
|
1269
|
+
}
|
|
1270
|
+
)
|
|
1271
|
+
] })
|
|
885
1272
|
]
|
|
886
1273
|
}
|
|
887
1274
|
);
|
|
@@ -894,6 +1281,7 @@ function WelcomeScreen({
|
|
|
894
1281
|
title = "Welcome",
|
|
895
1282
|
message = "How can I help you today?",
|
|
896
1283
|
icon,
|
|
1284
|
+
iconClassName,
|
|
897
1285
|
suggestedQuestions = [],
|
|
898
1286
|
onQuestionSelect,
|
|
899
1287
|
className
|
|
@@ -906,12 +1294,15 @@ function WelcomeScreen({
|
|
|
906
1294
|
className
|
|
907
1295
|
),
|
|
908
1296
|
children: [
|
|
909
|
-
/* @__PURE__ */ jsx10(
|
|
1297
|
+
icon ? iconClassName ? /* @__PURE__ */ jsx10("div", { className: iconClassName, "aria-hidden": "true", children: icon }) : icon : /* @__PURE__ */ jsx10(
|
|
910
1298
|
"div",
|
|
911
1299
|
{
|
|
912
|
-
className:
|
|
1300
|
+
className: twMerge7(
|
|
1301
|
+
"w-14 h-14 rounded-2xl bg-accent/10 border border-border flex items-center justify-center pulse-glow",
|
|
1302
|
+
iconClassName
|
|
1303
|
+
),
|
|
913
1304
|
"aria-hidden": "true",
|
|
914
|
-
children:
|
|
1305
|
+
children: /* @__PURE__ */ jsx10("span", { className: "text-2xl", children: "\u2726" })
|
|
915
1306
|
}
|
|
916
1307
|
),
|
|
917
1308
|
/* @__PURE__ */ jsxs8("div", { className: "flex flex-col gap-2", children: [
|
|
@@ -921,7 +1312,7 @@ function WelcomeScreen({
|
|
|
921
1312
|
suggestedQuestions.length > 0 && /* @__PURE__ */ jsx10(
|
|
922
1313
|
"div",
|
|
923
1314
|
{
|
|
924
|
-
className: "flex flex-wrap justify-center gap-2 max-w-
|
|
1315
|
+
className: "flex flex-wrap justify-center gap-2 max-w-xl",
|
|
925
1316
|
role: "group",
|
|
926
1317
|
"aria-label": "Suggested questions",
|
|
927
1318
|
children: suggestedQuestions.map((question) => /* @__PURE__ */ jsx10(
|
|
@@ -930,7 +1321,7 @@ function WelcomeScreen({
|
|
|
930
1321
|
type: "button",
|
|
931
1322
|
onClick: () => onQuestionSelect?.(question),
|
|
932
1323
|
className: twMerge7(
|
|
933
|
-
"px-
|
|
1324
|
+
"px-3.5 py-1.5 rounded-full text-[12px]",
|
|
934
1325
|
"border border-border bg-transparent text-text-secondary",
|
|
935
1326
|
"hover:bg-accent/10 hover:border-interactive hover:text-text-primary",
|
|
936
1327
|
"focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-accent",
|
|
@@ -949,7 +1340,8 @@ function WelcomeScreen({
|
|
|
949
1340
|
|
|
950
1341
|
// src/streaming/StreamingMessage/StreamingMessage.tsx
|
|
951
1342
|
import { useEffect as useEffect3, useRef as useRef5 } from "react";
|
|
952
|
-
import {
|
|
1343
|
+
import { twMerge as twMerge8 } from "tailwind-merge";
|
|
1344
|
+
import { WaveLoader } from "@surf-kit/core";
|
|
953
1345
|
|
|
954
1346
|
// src/hooks/useCharacterDrain.ts
|
|
955
1347
|
import { useState as useState3, useRef as useRef4, useEffect as useEffect2 } from "react";
|
|
@@ -978,7 +1370,10 @@ function useCharacterDrain(target, msPerChar = 15) {
|
|
|
978
1370
|
const elapsed = now - lastTimeRef.current;
|
|
979
1371
|
const charsToAdvance = Math.floor(elapsed / msPerCharRef.current);
|
|
980
1372
|
if (charsToAdvance > 0 && indexRef.current < currentTarget.length) {
|
|
981
|
-
|
|
1373
|
+
let nextIndex = Math.min(indexRef.current + charsToAdvance, currentTarget.length);
|
|
1374
|
+
while (nextIndex < currentTarget.length && currentTarget[nextIndex - 1].trim() === "") {
|
|
1375
|
+
nextIndex++;
|
|
1376
|
+
}
|
|
982
1377
|
indexRef.current = nextIndex;
|
|
983
1378
|
lastTimeRef.current = now;
|
|
984
1379
|
setDisplayed(currentTarget.slice(0, nextIndex));
|
|
@@ -1023,14 +1418,35 @@ var phaseLabels = {
|
|
|
1023
1418
|
generating: "Writing...",
|
|
1024
1419
|
verifying: "Verifying..."
|
|
1025
1420
|
};
|
|
1421
|
+
var CURSOR_STYLES = `
|
|
1422
|
+
.sk-streaming-cursor > :not(ul,ol,blockquote):last-child::after,
|
|
1423
|
+
.sk-streaming-cursor > :is(ul,ol):last-child > li:last-child::after,
|
|
1424
|
+
.sk-streaming-cursor > blockquote:last-child > p:last-child::after {
|
|
1425
|
+
content: "";
|
|
1426
|
+
display: inline-block;
|
|
1427
|
+
width: 2px;
|
|
1428
|
+
height: 1em;
|
|
1429
|
+
background: var(--color-accent, #38bdf8);
|
|
1430
|
+
animation: sk-cursor-blink 0.8s steps(1) infinite;
|
|
1431
|
+
margin-left: 2px;
|
|
1432
|
+
vertical-align: text-bottom;
|
|
1433
|
+
}
|
|
1434
|
+
@keyframes sk-cursor-blink {
|
|
1435
|
+
0%, 60% { opacity: 1; }
|
|
1436
|
+
61%, 100% { opacity: 0; }
|
|
1437
|
+
}
|
|
1438
|
+
`;
|
|
1026
1439
|
function StreamingMessage({
|
|
1027
1440
|
stream,
|
|
1028
1441
|
onComplete,
|
|
1442
|
+
onDraining,
|
|
1029
1443
|
showPhases = true,
|
|
1030
1444
|
className
|
|
1031
1445
|
}) {
|
|
1032
1446
|
const onCompleteRef = useRef5(onComplete);
|
|
1033
1447
|
onCompleteRef.current = onComplete;
|
|
1448
|
+
const onDrainingRef = useRef5(onDraining);
|
|
1449
|
+
onDrainingRef.current = onDraining;
|
|
1034
1450
|
const wasActiveRef = useRef5(stream.active);
|
|
1035
1451
|
useEffect3(() => {
|
|
1036
1452
|
if (wasActiveRef.current && !stream.active) {
|
|
@@ -1039,35 +1455,40 @@ function StreamingMessage({
|
|
|
1039
1455
|
wasActiveRef.current = stream.active;
|
|
1040
1456
|
}, [stream.active]);
|
|
1041
1457
|
const phaseLabel = phaseLabels[stream.phase];
|
|
1042
|
-
const { displayed:
|
|
1043
|
-
|
|
1458
|
+
const { displayed: rawDisplayed, isDraining } = useCharacterDrain(stream.content);
|
|
1459
|
+
const displayedContent = stream.active || isDraining ? rawDisplayed.trimEnd() : rawDisplayed;
|
|
1460
|
+
useEffect3(() => {
|
|
1461
|
+
onDrainingRef.current?.(isDraining);
|
|
1462
|
+
}, [isDraining]);
|
|
1463
|
+
const agentLabel = stream.agent ? stream.agent.replace("_agent", "").replace("_", " ") : null;
|
|
1464
|
+
const showPhaseIndicator = showPhases && stream.active && stream.phase !== "idle" && !displayedContent;
|
|
1465
|
+
const showCursor = (stream.active || isDraining) && !!displayedContent;
|
|
1466
|
+
return /* @__PURE__ */ jsxs9("div", { className: twMerge8("flex w-full flex-col items-start", className), "data-testid": "streaming-message", children: [
|
|
1044
1467
|
/* @__PURE__ */ jsxs9("div", { "aria-live": "assertive", className: "sr-only", children: [
|
|
1045
1468
|
stream.active && stream.phase !== "idle" && "Response started",
|
|
1046
1469
|
!stream.active && stream.content && "Response complete"
|
|
1047
1470
|
] }),
|
|
1471
|
+
showCursor && /* @__PURE__ */ jsx11("style", { children: CURSOR_STYLES }),
|
|
1472
|
+
agentLabel && /* @__PURE__ */ jsx11("div", { className: "text-[11px] font-display font-semibold uppercase tracking-[0.08em] text-text-muted px-1 mb-1.5", children: agentLabel }),
|
|
1048
1473
|
/* @__PURE__ */ jsxs9("div", { className: "max-w-[88%] px-4 py-3 rounded-[18px] rounded-tl-[4px] bg-surface border border-border motion-safe:animate-springFromLeft", children: [
|
|
1049
|
-
|
|
1474
|
+
showPhaseIndicator && /* @__PURE__ */ jsxs9(
|
|
1050
1475
|
"div",
|
|
1051
1476
|
{
|
|
1052
|
-
className: "flex items-center gap-2
|
|
1477
|
+
className: "flex items-center gap-2 text-sm text-text-secondary",
|
|
1053
1478
|
"data-testid": "phase-indicator",
|
|
1054
1479
|
children: [
|
|
1055
|
-
/* @__PURE__ */ jsx11("span", { "aria-hidden": "true", children: /* @__PURE__ */ jsx11(
|
|
1480
|
+
/* @__PURE__ */ jsx11("span", { "aria-hidden": "true", children: /* @__PURE__ */ jsx11(WaveLoader, { size: "sm", color: "#38bdf8" }) }),
|
|
1056
1481
|
/* @__PURE__ */ jsx11("span", { children: phaseLabel })
|
|
1057
1482
|
]
|
|
1058
1483
|
}
|
|
1059
1484
|
),
|
|
1060
|
-
/* @__PURE__ */
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
"data-testid": "streaming-cursor"
|
|
1068
|
-
}
|
|
1069
|
-
)
|
|
1070
|
-
] })
|
|
1485
|
+
displayedContent && /* @__PURE__ */ jsx11(
|
|
1486
|
+
ResponseMessage,
|
|
1487
|
+
{
|
|
1488
|
+
content: displayedContent,
|
|
1489
|
+
className: showCursor ? "sk-streaming-cursor" : void 0
|
|
1490
|
+
}
|
|
1491
|
+
)
|
|
1071
1492
|
] })
|
|
1072
1493
|
] });
|
|
1073
1494
|
}
|
|
@@ -1098,7 +1519,7 @@ function AgentChat({
|
|
|
1098
1519
|
return /* @__PURE__ */ jsxs10(
|
|
1099
1520
|
"div",
|
|
1100
1521
|
{
|
|
1101
|
-
className:
|
|
1522
|
+
className: twMerge9(
|
|
1102
1523
|
"flex flex-col h-full bg-canvas border border-border rounded-xl overflow-hidden",
|
|
1103
1524
|
className
|
|
1104
1525
|
),
|
|
@@ -1601,7 +2022,7 @@ function ThinkingIndicator({ label = "Thinking...", className }) {
|
|
|
1601
2022
|
}
|
|
1602
2023
|
|
|
1603
2024
|
// src/streaming/ToolExecution/ToolExecution.tsx
|
|
1604
|
-
import {
|
|
2025
|
+
import { WaveLoader as WaveLoader2 } from "@surf-kit/core";
|
|
1605
2026
|
import { jsx as jsx26, jsxs as jsxs23 } from "react/jsx-runtime";
|
|
1606
2027
|
var defaultLabels = {
|
|
1607
2028
|
search: "Searching knowledge base...",
|
|
@@ -1617,7 +2038,7 @@ function ToolExecution({ tool, label, className }) {
|
|
|
1617
2038
|
role: "status",
|
|
1618
2039
|
"data-testid": "tool-execution",
|
|
1619
2040
|
children: [
|
|
1620
|
-
/* @__PURE__ */ jsx26("span", { "aria-hidden": "true", children: /* @__PURE__ */ jsx26(
|
|
2041
|
+
/* @__PURE__ */ jsx26("span", { "aria-hidden": "true", children: /* @__PURE__ */ jsx26(WaveLoader2, { size: "sm", color: "#38bdf8" }) }),
|
|
1621
2042
|
/* @__PURE__ */ jsx26("span", { children: displayLabel })
|
|
1622
2043
|
]
|
|
1623
2044
|
}
|
|
@@ -1625,7 +2046,7 @@ function ToolExecution({ tool, label, className }) {
|
|
|
1625
2046
|
}
|
|
1626
2047
|
|
|
1627
2048
|
// src/streaming/RetrievalProgress/RetrievalProgress.tsx
|
|
1628
|
-
import {
|
|
2049
|
+
import { WaveLoader as WaveLoader3 } from "@surf-kit/core";
|
|
1629
2050
|
import { jsx as jsx27, jsxs as jsxs24 } from "react/jsx-runtime";
|
|
1630
2051
|
function RetrievalProgress({ sources, isActive, className }) {
|
|
1631
2052
|
return /* @__PURE__ */ jsxs24(
|
|
@@ -1637,7 +2058,7 @@ function RetrievalProgress({ sources, isActive, className }) {
|
|
|
1637
2058
|
"data-testid": "retrieval-progress",
|
|
1638
2059
|
children: [
|
|
1639
2060
|
isActive && /* @__PURE__ */ jsxs24("div", { className: "flex items-center gap-2 text-sm text-text-secondary", children: [
|
|
1640
|
-
/* @__PURE__ */ jsx27("span", { "aria-hidden": "true", children: /* @__PURE__ */ jsx27(
|
|
2061
|
+
/* @__PURE__ */ jsx27("span", { "aria-hidden": "true", children: /* @__PURE__ */ jsx27(WaveLoader3, { size: "sm", color: "#38bdf8" }) }),
|
|
1641
2062
|
/* @__PURE__ */ jsx27("span", { children: "Retrieving sources..." })
|
|
1642
2063
|
] }),
|
|
1643
2064
|
sources.length > 0 && /* @__PURE__ */ jsx27("ul", { className: "space-y-1", "data-testid": "source-list", children: sources.map((source, index) => /* @__PURE__ */ jsxs24(
|
|
@@ -1735,7 +2156,7 @@ function TypewriterText({
|
|
|
1735
2156
|
}
|
|
1736
2157
|
|
|
1737
2158
|
// src/streaming/TypingIndicator/TypingIndicator.tsx
|
|
1738
|
-
import { twMerge as
|
|
2159
|
+
import { twMerge as twMerge10 } from "tailwind-merge";
|
|
1739
2160
|
import { useReducedMotion as useReducedMotion2 } from "@surf-kit/hooks";
|
|
1740
2161
|
import { jsx as jsx30, jsxs as jsxs27 } from "react/jsx-runtime";
|
|
1741
2162
|
var bounceKeyframes = `
|
|
@@ -1755,7 +2176,7 @@ function TypingIndicator({
|
|
|
1755
2176
|
{
|
|
1756
2177
|
role: "status",
|
|
1757
2178
|
"aria-label": label ?? "typing",
|
|
1758
|
-
className:
|
|
2179
|
+
className: twMerge10("inline-flex items-center gap-2", className),
|
|
1759
2180
|
"data-testid": "typing-indicator",
|
|
1760
2181
|
children: [
|
|
1761
2182
|
!reducedMotion && /* @__PURE__ */ jsx30("style", { children: bounceKeyframes }),
|
|
@@ -1777,7 +2198,7 @@ function TypingIndicator({
|
|
|
1777
2198
|
}
|
|
1778
2199
|
|
|
1779
2200
|
// src/streaming/TextGlimmer/TextGlimmer.tsx
|
|
1780
|
-
import { twMerge as
|
|
2201
|
+
import { twMerge as twMerge11 } from "tailwind-merge";
|
|
1781
2202
|
import { useReducedMotion as useReducedMotion3 } from "@surf-kit/hooks";
|
|
1782
2203
|
import { jsx as jsx31, jsxs as jsxs28 } from "react/jsx-runtime";
|
|
1783
2204
|
var shimmerKeyframes = `
|
|
@@ -1794,7 +2215,7 @@ function TextGlimmer({ lines = 3, className }) {
|
|
|
1794
2215
|
{
|
|
1795
2216
|
role: "status",
|
|
1796
2217
|
"aria-label": "Loading",
|
|
1797
|
-
className:
|
|
2218
|
+
className: twMerge11("flex flex-col gap-2", className),
|
|
1798
2219
|
"data-testid": "text-glimmer",
|
|
1799
2220
|
children: [
|
|
1800
2221
|
!reducedMotion && /* @__PURE__ */ jsx31("style", { children: shimmerKeyframes }),
|
|
@@ -1820,7 +2241,7 @@ function TextGlimmer({ lines = 3, className }) {
|
|
|
1820
2241
|
}
|
|
1821
2242
|
|
|
1822
2243
|
// src/streaming/StreamingList/StreamingList.tsx
|
|
1823
|
-
import { twMerge as
|
|
2244
|
+
import { twMerge as twMerge12 } from "tailwind-merge";
|
|
1824
2245
|
import { useReducedMotion as useReducedMotion4 } from "@surf-kit/hooks";
|
|
1825
2246
|
import { jsx as jsx32, jsxs as jsxs29 } from "react/jsx-runtime";
|
|
1826
2247
|
var fadeSlideInKeyframes = `
|
|
@@ -1838,13 +2259,13 @@ function StreamingList({
|
|
|
1838
2259
|
}) {
|
|
1839
2260
|
const reducedMotion = useReducedMotion4();
|
|
1840
2261
|
if (items.length === 0 && !isStreaming) {
|
|
1841
|
-
return emptyMessage ? /* @__PURE__ */ jsx32("p", { className:
|
|
2262
|
+
return emptyMessage ? /* @__PURE__ */ jsx32("p", { className: twMerge12("text-sm text-text-secondary", className), "data-testid": "streaming-list-empty", children: emptyMessage }) : null;
|
|
1842
2263
|
}
|
|
1843
2264
|
return /* @__PURE__ */ jsxs29(
|
|
1844
2265
|
"ul",
|
|
1845
2266
|
{
|
|
1846
2267
|
"aria-live": "polite",
|
|
1847
|
-
className:
|
|
2268
|
+
className: twMerge12("list-none p-0 m-0", className),
|
|
1848
2269
|
"data-testid": "streaming-list",
|
|
1849
2270
|
children: [
|
|
1850
2271
|
!reducedMotion && /* @__PURE__ */ jsx32("style", { children: fadeSlideInKeyframes }),
|
|
@@ -1864,7 +2285,7 @@ function StreamingList({
|
|
|
1864
2285
|
}
|
|
1865
2286
|
|
|
1866
2287
|
// src/streaming/StreamingStructure/StreamingStructure.tsx
|
|
1867
|
-
import { twMerge as
|
|
2288
|
+
import { twMerge as twMerge13 } from "tailwind-merge";
|
|
1868
2289
|
import { useReducedMotion as useReducedMotion5 } from "@surf-kit/hooks";
|
|
1869
2290
|
import { jsx as jsx33, jsxs as jsxs30 } from "react/jsx-runtime";
|
|
1870
2291
|
var fadeSlideInKeyframes2 = `
|
|
@@ -1913,7 +2334,7 @@ function StreamingStructure({
|
|
|
1913
2334
|
"dl",
|
|
1914
2335
|
{
|
|
1915
2336
|
"aria-live": "polite",
|
|
1916
|
-
className:
|
|
2337
|
+
className: twMerge13("m-0", className),
|
|
1917
2338
|
"data-testid": "streaming-structure",
|
|
1918
2339
|
children: [
|
|
1919
2340
|
!reducedMotion && /* @__PURE__ */ jsx33("style", { children: fadeSlideInKeyframes2 }),
|
|
@@ -1936,7 +2357,7 @@ function StreamingStructure({
|
|
|
1936
2357
|
}
|
|
1937
2358
|
|
|
1938
2359
|
// src/chat/ConversationList/ConversationList.tsx
|
|
1939
|
-
import { twMerge as
|
|
2360
|
+
import { twMerge as twMerge14 } from "tailwind-merge";
|
|
1940
2361
|
import { jsx as jsx34, jsxs as jsxs31 } from "react/jsx-runtime";
|
|
1941
2362
|
function ConversationList({
|
|
1942
2363
|
conversations,
|
|
@@ -1950,7 +2371,7 @@ function ConversationList({
|
|
|
1950
2371
|
"nav",
|
|
1951
2372
|
{
|
|
1952
2373
|
"aria-label": "Conversation list",
|
|
1953
|
-
className:
|
|
2374
|
+
className: twMerge14("flex flex-col h-full bg-canvas", className),
|
|
1954
2375
|
children: [
|
|
1955
2376
|
onNew && /* @__PURE__ */ jsx34("div", { className: "p-3 border-b border-border", children: /* @__PURE__ */ jsx34(
|
|
1956
2377
|
"button",
|
|
@@ -1967,7 +2388,7 @@ function ConversationList({
|
|
|
1967
2388
|
return /* @__PURE__ */ jsxs31(
|
|
1968
2389
|
"li",
|
|
1969
2390
|
{
|
|
1970
|
-
className:
|
|
2391
|
+
className: twMerge14(
|
|
1971
2392
|
"flex items-start border-b border-border transition-colors duration-200",
|
|
1972
2393
|
"hover:bg-surface",
|
|
1973
2394
|
isActive && "bg-surface-raised border-l-2 border-l-accent"
|
|
@@ -2027,8 +2448,8 @@ function ConversationList({
|
|
|
2027
2448
|
}
|
|
2028
2449
|
|
|
2029
2450
|
// src/layouts/AgentFullPage/AgentFullPage.tsx
|
|
2030
|
-
import { twMerge as
|
|
2031
|
-
import { useState as useState7, useCallback as
|
|
2451
|
+
import { twMerge as twMerge15 } from "tailwind-merge";
|
|
2452
|
+
import { useState as useState7, useCallback as useCallback4 } from "react";
|
|
2032
2453
|
import { Fragment, jsx as jsx35, jsxs as jsxs32 } from "react/jsx-runtime";
|
|
2033
2454
|
function AgentFullPage({
|
|
2034
2455
|
endpoint,
|
|
@@ -2042,7 +2463,7 @@ function AgentFullPage({
|
|
|
2042
2463
|
className
|
|
2043
2464
|
}) {
|
|
2044
2465
|
const [sidebarOpen, setSidebarOpen] = useState7(false);
|
|
2045
|
-
const handleSelect =
|
|
2466
|
+
const handleSelect = useCallback4(
|
|
2046
2467
|
(id) => {
|
|
2047
2468
|
onConversationSelect?.(id);
|
|
2048
2469
|
setSidebarOpen(false);
|
|
@@ -2052,7 +2473,7 @@ function AgentFullPage({
|
|
|
2052
2473
|
return /* @__PURE__ */ jsxs32(
|
|
2053
2474
|
"div",
|
|
2054
2475
|
{
|
|
2055
|
-
className:
|
|
2476
|
+
className: twMerge15("flex h-screen w-full overflow-hidden bg-brand-dark", className),
|
|
2056
2477
|
"data-testid": "agent-full-page",
|
|
2057
2478
|
children: [
|
|
2058
2479
|
showConversationList && /* @__PURE__ */ jsxs32(Fragment, { children: [
|
|
@@ -2067,7 +2488,7 @@ function AgentFullPage({
|
|
|
2067
2488
|
/* @__PURE__ */ jsx35(
|
|
2068
2489
|
"aside",
|
|
2069
2490
|
{
|
|
2070
|
-
className:
|
|
2491
|
+
className: twMerge15(
|
|
2071
2492
|
"bg-brand-dark border-r border-brand-gold/15 w-72 shrink-0 flex-col z-40",
|
|
2072
2493
|
// Desktop: always visible
|
|
2073
2494
|
"hidden md:flex",
|
|
@@ -2133,7 +2554,7 @@ function AgentFullPage({
|
|
|
2133
2554
|
}
|
|
2134
2555
|
|
|
2135
2556
|
// src/layouts/AgentPanel/AgentPanel.tsx
|
|
2136
|
-
import { twMerge as
|
|
2557
|
+
import { twMerge as twMerge16 } from "tailwind-merge";
|
|
2137
2558
|
import { useRef as useRef6, useEffect as useEffect5 } from "react";
|
|
2138
2559
|
import { jsx as jsx36, jsxs as jsxs33 } from "react/jsx-runtime";
|
|
2139
2560
|
function AgentPanel({
|
|
@@ -2158,13 +2579,13 @@ function AgentPanel({
|
|
|
2158
2579
|
return /* @__PURE__ */ jsxs33(
|
|
2159
2580
|
"div",
|
|
2160
2581
|
{
|
|
2161
|
-
className:
|
|
2582
|
+
className: twMerge16("fixed inset-0 z-50", !isOpen && "pointer-events-none"),
|
|
2162
2583
|
"aria-hidden": !isOpen,
|
|
2163
2584
|
children: [
|
|
2164
2585
|
/* @__PURE__ */ jsx36(
|
|
2165
2586
|
"div",
|
|
2166
2587
|
{
|
|
2167
|
-
className:
|
|
2588
|
+
className: twMerge16(
|
|
2168
2589
|
"fixed inset-0 transition-opacity duration-300",
|
|
2169
2590
|
isOpen ? "opacity-100 bg-brand-dark/70 backdrop-blur-sm pointer-events-auto" : "opacity-0 pointer-events-none"
|
|
2170
2591
|
),
|
|
@@ -2180,7 +2601,7 @@ function AgentPanel({
|
|
|
2180
2601
|
"aria-label": title,
|
|
2181
2602
|
"aria-modal": isOpen ? "true" : void 0,
|
|
2182
2603
|
style: { width: widthStyle, maxWidth: "100vw" },
|
|
2183
|
-
className:
|
|
2604
|
+
className: twMerge16(
|
|
2184
2605
|
"fixed top-0 h-full flex flex-col z-50 bg-brand-dark shadow-card",
|
|
2185
2606
|
"transition-transform duration-300 ease-in-out",
|
|
2186
2607
|
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"}`,
|
|
@@ -2222,8 +2643,8 @@ function AgentPanel({
|
|
|
2222
2643
|
}
|
|
2223
2644
|
|
|
2224
2645
|
// src/layouts/AgentWidget/AgentWidget.tsx
|
|
2225
|
-
import { twMerge as
|
|
2226
|
-
import { useState as useState8, useCallback as
|
|
2646
|
+
import { twMerge as twMerge17 } from "tailwind-merge";
|
|
2647
|
+
import { useState as useState8, useCallback as useCallback5 } from "react";
|
|
2227
2648
|
import { jsx as jsx37, jsxs as jsxs34 } from "react/jsx-runtime";
|
|
2228
2649
|
function AgentWidget({
|
|
2229
2650
|
endpoint,
|
|
@@ -2233,7 +2654,7 @@ function AgentWidget({
|
|
|
2233
2654
|
className
|
|
2234
2655
|
}) {
|
|
2235
2656
|
const [isOpen, setIsOpen] = useState8(false);
|
|
2236
|
-
const toggle =
|
|
2657
|
+
const toggle = useCallback5(() => {
|
|
2237
2658
|
setIsOpen((prev) => !prev);
|
|
2238
2659
|
}, []);
|
|
2239
2660
|
const positionClasses = position === "bottom-left" ? "left-4 bottom-4" : "right-4 bottom-4";
|
|
@@ -2246,7 +2667,7 @@ function AgentWidget({
|
|
|
2246
2667
|
role: "dialog",
|
|
2247
2668
|
"aria-label": title,
|
|
2248
2669
|
"aria-hidden": !isOpen,
|
|
2249
|
-
className:
|
|
2670
|
+
className: twMerge17(
|
|
2250
2671
|
"fixed z-50 flex flex-col",
|
|
2251
2672
|
"w-[min(400px,calc(100vw-2rem))] h-[min(600px,calc(100vh-6rem))]",
|
|
2252
2673
|
"rounded-2xl overflow-hidden border border-brand-gold/15",
|
|
@@ -2293,7 +2714,7 @@ function AgentWidget({
|
|
|
2293
2714
|
onClick: toggle,
|
|
2294
2715
|
"aria-label": isOpen ? "Close chat" : triggerLabel,
|
|
2295
2716
|
"aria-expanded": isOpen,
|
|
2296
|
-
className:
|
|
2717
|
+
className: twMerge17(
|
|
2297
2718
|
"fixed z-50 flex items-center justify-center w-14 h-14 rounded-full",
|
|
2298
2719
|
"bg-brand-blue text-brand-cream shadow-glow-cyan",
|
|
2299
2720
|
"hover:bg-brand-cyan hover:shadow-glow-cyan hover:scale-105",
|
|
@@ -2311,7 +2732,7 @@ function AgentWidget({
|
|
|
2311
2732
|
}
|
|
2312
2733
|
|
|
2313
2734
|
// src/layouts/AgentEmbed/AgentEmbed.tsx
|
|
2314
|
-
import { twMerge as
|
|
2735
|
+
import { twMerge as twMerge18 } from "tailwind-merge";
|
|
2315
2736
|
import { jsx as jsx38 } from "react/jsx-runtime";
|
|
2316
2737
|
function AgentEmbed({
|
|
2317
2738
|
endpoint,
|
|
@@ -2321,7 +2742,7 @@ function AgentEmbed({
|
|
|
2321
2742
|
return /* @__PURE__ */ jsx38(
|
|
2322
2743
|
"div",
|
|
2323
2744
|
{
|
|
2324
|
-
className:
|
|
2745
|
+
className: twMerge18("w-full h-full min-h-0", className),
|
|
2325
2746
|
"data-testid": "agent-embed",
|
|
2326
2747
|
children: /* @__PURE__ */ jsx38(
|
|
2327
2748
|
AgentChat,
|
|
@@ -2337,8 +2758,8 @@ function AgentEmbed({
|
|
|
2337
2758
|
|
|
2338
2759
|
// src/mcp/MCPToolCall/MCPToolCall.tsx
|
|
2339
2760
|
import { cva } from "class-variance-authority";
|
|
2340
|
-
import { twMerge as
|
|
2341
|
-
import { Badge as Badge7,
|
|
2761
|
+
import { twMerge as twMerge19 } from "tailwind-merge";
|
|
2762
|
+
import { Badge as Badge7, WaveLoader as WaveLoader4 } from "@surf-kit/core";
|
|
2342
2763
|
import { jsx as jsx39, jsxs as jsxs35 } from "react/jsx-runtime";
|
|
2343
2764
|
var statusBadgeIntent = {
|
|
2344
2765
|
pending: "default",
|
|
@@ -2381,7 +2802,7 @@ function MCPToolCall({ call, isExpanded = false, onToggleExpand, className }) {
|
|
|
2381
2802
|
return /* @__PURE__ */ jsxs35(
|
|
2382
2803
|
"div",
|
|
2383
2804
|
{
|
|
2384
|
-
className:
|
|
2805
|
+
className: twMerge19(container({ status: call.status }), className),
|
|
2385
2806
|
"data-testid": "mcp-tool-call",
|
|
2386
2807
|
children: [
|
|
2387
2808
|
/* @__PURE__ */ jsxs35(
|
|
@@ -2398,7 +2819,7 @@ function MCPToolCall({ call, isExpanded = false, onToggleExpand, className }) {
|
|
|
2398
2819
|
call.serverName && /* @__PURE__ */ jsx39("span", { className: "text-xs text-text-secondary truncate", children: call.serverName })
|
|
2399
2820
|
] }),
|
|
2400
2821
|
/* @__PURE__ */ jsxs35("div", { className: "flex items-center gap-2 shrink-0", children: [
|
|
2401
|
-
call.status === "running" && /* @__PURE__ */ jsx39("span", { "aria-hidden": "true", children: /* @__PURE__ */ jsx39(
|
|
2822
|
+
call.status === "running" && /* @__PURE__ */ jsx39("span", { "aria-hidden": "true", children: /* @__PURE__ */ jsx39(WaveLoader4, { size: "sm", color: "#38bdf8" }) }),
|
|
2402
2823
|
/* @__PURE__ */ jsx39(
|
|
2403
2824
|
Badge7,
|
|
2404
2825
|
{
|
|
@@ -2472,7 +2893,7 @@ function MCPToolCall({ call, isExpanded = false, onToggleExpand, className }) {
|
|
|
2472
2893
|
}
|
|
2473
2894
|
|
|
2474
2895
|
// src/mcp/MCPResourceView/MCPResourceView.tsx
|
|
2475
|
-
import { twMerge as
|
|
2896
|
+
import { twMerge as twMerge20 } from "tailwind-merge";
|
|
2476
2897
|
import { jsx as jsx40, jsxs as jsxs36 } from "react/jsx-runtime";
|
|
2477
2898
|
function isImageMime(mime) {
|
|
2478
2899
|
return !!mime && mime.startsWith("image/");
|
|
@@ -2490,7 +2911,7 @@ function MCPResourceView({ resource, className }) {
|
|
|
2490
2911
|
return /* @__PURE__ */ jsxs36(
|
|
2491
2912
|
"div",
|
|
2492
2913
|
{
|
|
2493
|
-
className:
|
|
2914
|
+
className: twMerge20("rounded-lg border border-border bg-surface p-3 text-sm", className),
|
|
2494
2915
|
"data-testid": "mcp-resource-view",
|
|
2495
2916
|
children: [
|
|
2496
2917
|
/* @__PURE__ */ jsxs36("div", { className: "mb-2", children: [
|
|
@@ -2542,7 +2963,7 @@ function MCPResourceView({ resource, className }) {
|
|
|
2542
2963
|
// src/mcp/MCPServerStatus/MCPServerStatus.tsx
|
|
2543
2964
|
import { useState as useState9 } from "react";
|
|
2544
2965
|
import { cva as cva2 } from "class-variance-authority";
|
|
2545
|
-
import { twMerge as
|
|
2966
|
+
import { twMerge as twMerge21 } from "tailwind-merge";
|
|
2546
2967
|
import { jsx as jsx41, jsxs as jsxs37 } from "react/jsx-runtime";
|
|
2547
2968
|
var statusDot = cva2("inline-block h-2 w-2 rounded-full shrink-0", {
|
|
2548
2969
|
variants: {
|
|
@@ -2570,7 +2991,7 @@ function MCPServerStatus({ server, className }) {
|
|
|
2570
2991
|
return /* @__PURE__ */ jsxs37(
|
|
2571
2992
|
"div",
|
|
2572
2993
|
{
|
|
2573
|
-
className:
|
|
2994
|
+
className: twMerge21("rounded-lg border border-border bg-surface p-3 text-sm", className),
|
|
2574
2995
|
"data-testid": "mcp-server-status",
|
|
2575
2996
|
children: [
|
|
2576
2997
|
/* @__PURE__ */ jsxs37("div", { className: "flex items-center gap-2 mb-1", children: [
|
|
@@ -2684,7 +3105,7 @@ function MCPServerStatus({ server, className }) {
|
|
|
2684
3105
|
// src/mcp/MCPApprovalDialog/MCPApprovalDialog.tsx
|
|
2685
3106
|
import { useRef as useRef7, useEffect as useEffect6 } from "react";
|
|
2686
3107
|
import { cva as cva3 } from "class-variance-authority";
|
|
2687
|
-
import { twMerge as
|
|
3108
|
+
import { twMerge as twMerge22 } from "tailwind-merge";
|
|
2688
3109
|
import { useDialog, FocusScope } from "react-aria";
|
|
2689
3110
|
import { Button as Button2, Badge as Badge8 } from "@surf-kit/core";
|
|
2690
3111
|
import { jsx as jsx42, jsxs as jsxs38 } from "react/jsx-runtime";
|
|
@@ -2744,7 +3165,7 @@ function MCPApprovalDialog({
|
|
|
2744
3165
|
{
|
|
2745
3166
|
...dialogProps,
|
|
2746
3167
|
ref,
|
|
2747
|
-
className:
|
|
3168
|
+
className: twMerge22(riskBorder({ risk: riskLevel }), className),
|
|
2748
3169
|
"data-testid": "mcp-approval-dialog",
|
|
2749
3170
|
children: [
|
|
2750
3171
|
/* @__PURE__ */ jsxs38("div", { className: "flex items-center justify-between mb-4", children: [
|