@surf-kit/agent 0.2.1 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +184 -12
- package/README.md +1 -1
- package/dist/agent-identity/index.cjs +1 -0
- package/dist/agent-identity/index.cjs.map +1 -1
- package/dist/agent-identity/index.js +2 -0
- package/dist/agent-identity/index.js.map +1 -1
- package/dist/chat/index.cjs +626 -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 +608 -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/confidence/index.cjs +1 -0
- package/dist/confidence/index.cjs.map +1 -1
- package/dist/confidence/index.js +2 -0
- package/dist/confidence/index.js.map +1 -1
- package/dist/feedback/index.cjs +1 -0
- package/dist/feedback/index.cjs.map +1 -1
- package/dist/feedback/index.js +2 -0
- package/dist/feedback/index.js.map +1 -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 +127 -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 +128 -81
- package/dist/hooks.js.map +1 -1
- package/dist/index.cjs +687 -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 +647 -224
- package/dist/index.js.map +1 -1
- package/dist/layouts/index.cjs +647 -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 +624 -201
- package/dist/layouts/index.js.map +1 -1
- package/dist/mcp/index.cjs +2 -1
- package/dist/mcp/index.cjs.map +1 -1
- package/dist/mcp/index.js +4 -2
- package/dist/mcp/index.js.map +1 -1
- package/dist/response/index.cjs +67 -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 +66 -10
- package/dist/response/index.js.map +1 -1
- package/dist/sources/index.cjs +31 -1
- package/dist/sources/index.cjs.map +1 -1
- package/dist/sources/index.js +32 -1
- package/dist/sources/index.js.map +1 -1
- package/dist/streaming/index.cjs +203 -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 +174 -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 +17 -6
package/dist/index.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
1
3
|
// src/chat/AgentChat/AgentChat.tsx
|
|
2
|
-
import { twMerge as
|
|
4
|
+
import { twMerge as twMerge9 } from "tailwind-merge";
|
|
3
5
|
|
|
4
6
|
// src/hooks/useAgentChat.ts
|
|
5
7
|
import { useReducer, useCallback, useRef } from "react";
|
|
@@ -10,7 +12,8 @@ var initialState = {
|
|
|
10
12
|
error: null,
|
|
11
13
|
inputValue: "",
|
|
12
14
|
streamPhase: "idle",
|
|
13
|
-
streamingContent: ""
|
|
15
|
+
streamingContent: "",
|
|
16
|
+
streamingAgent: null
|
|
14
17
|
};
|
|
15
18
|
function reducer(state, action) {
|
|
16
19
|
switch (action.type) {
|
|
@@ -24,12 +27,15 @@ function reducer(state, action) {
|
|
|
24
27
|
error: null,
|
|
25
28
|
inputValue: "",
|
|
26
29
|
streamPhase: "thinking",
|
|
27
|
-
streamingContent: ""
|
|
30
|
+
streamingContent: "",
|
|
31
|
+
streamingAgent: null
|
|
28
32
|
};
|
|
29
33
|
case "STREAM_PHASE":
|
|
30
34
|
return { ...state, streamPhase: action.phase };
|
|
31
35
|
case "STREAM_CONTENT":
|
|
32
36
|
return { ...state, streamingContent: state.streamingContent + action.content };
|
|
37
|
+
case "STREAM_AGENT":
|
|
38
|
+
return { ...state, streamingAgent: action.agent };
|
|
33
39
|
case "SEND_SUCCESS":
|
|
34
40
|
return {
|
|
35
41
|
...state,
|
|
@@ -45,7 +51,8 @@ function reducer(state, action) {
|
|
|
45
51
|
isLoading: false,
|
|
46
52
|
error: action.error,
|
|
47
53
|
streamPhase: "idle",
|
|
48
|
-
streamingContent: ""
|
|
54
|
+
streamingContent: "",
|
|
55
|
+
streamingAgent: null
|
|
49
56
|
};
|
|
50
57
|
case "LOAD_CONVERSATION":
|
|
51
58
|
return {
|
|
@@ -71,107 +78,142 @@ function useAgentChat(config2) {
|
|
|
71
78
|
const configRef = useRef(config2);
|
|
72
79
|
configRef.current = config2;
|
|
73
80
|
const lastUserMessageRef = useRef(null);
|
|
81
|
+
const lastUserAttachmentsRef = useRef(void 0);
|
|
74
82
|
const sendMessage = useCallback(
|
|
75
|
-
async (content) => {
|
|
76
|
-
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 ?? {};
|
|
77
86
|
lastUserMessageRef.current = content;
|
|
87
|
+
lastUserAttachmentsRef.current = attachments;
|
|
78
88
|
const userMessage = {
|
|
79
89
|
id: generateMessageId(),
|
|
80
90
|
role: "user",
|
|
81
91
|
content,
|
|
92
|
+
attachments,
|
|
82
93
|
timestamp: /* @__PURE__ */ new Date()
|
|
83
94
|
};
|
|
84
95
|
dispatch({ type: "SEND_START", message: userMessage });
|
|
85
96
|
const controller = new AbortController();
|
|
86
97
|
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
87
98
|
try {
|
|
88
|
-
const
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
error: {
|
|
106
|
-
code: "API_ERROR",
|
|
107
|
-
message: `HTTP ${response.status}: ${response.statusText}`,
|
|
108
|
-
retryable: response.status >= 500
|
|
109
|
-
}
|
|
110
|
-
});
|
|
111
|
-
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
|
+
}));
|
|
112
116
|
}
|
|
113
|
-
const
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
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
|
|
118
162
|
});
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
const
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
dispatch({ type: "SEND_ERROR", error: event.error });
|
|
156
|
-
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 {
|
|
157
199
|
}
|
|
158
|
-
} catch {
|
|
159
200
|
}
|
|
160
201
|
}
|
|
161
202
|
}
|
|
203
|
+
if (ctx.hadStreamError) return;
|
|
162
204
|
const assistantMessage = {
|
|
163
205
|
id: generateMessageId(),
|
|
164
206
|
role: "assistant",
|
|
165
|
-
content: agentResponse?.message ?? accumulatedContent,
|
|
166
|
-
response: agentResponse ?? void 0,
|
|
167
|
-
agent: capturedAgent ?? void 0,
|
|
207
|
+
content: ctx.agentResponse?.message ?? ctx.accumulatedContent,
|
|
208
|
+
response: ctx.agentResponse ?? void 0,
|
|
209
|
+
agent: ctx.capturedAgent ?? void 0,
|
|
168
210
|
timestamp: /* @__PURE__ */ new Date()
|
|
169
211
|
};
|
|
170
212
|
dispatch({
|
|
171
213
|
type: "SEND_SUCCESS",
|
|
172
214
|
message: assistantMessage,
|
|
173
|
-
streamingContent: accumulatedContent,
|
|
174
|
-
conversationId: capturedConversationId
|
|
215
|
+
streamingContent: ctx.accumulatedContent,
|
|
216
|
+
conversationId: ctx.capturedConversationId
|
|
175
217
|
});
|
|
176
218
|
} catch (err) {
|
|
177
219
|
clearTimeout(timeoutId);
|
|
@@ -202,7 +244,8 @@ function useAgentChat(config2) {
|
|
|
202
244
|
}, []);
|
|
203
245
|
const submitFeedback = useCallback(
|
|
204
246
|
async (messageId, rating, comment) => {
|
|
205
|
-
const { apiUrl, feedbackPath = "/feedback", headers
|
|
247
|
+
const { apiUrl, feedbackPath = "/feedback", headers: headersOrFn } = configRef.current;
|
|
248
|
+
const headers = typeof headersOrFn === "function" ? await headersOrFn() : headersOrFn ?? {};
|
|
206
249
|
await fetch(`${apiUrl}${feedbackPath}`, {
|
|
207
250
|
method: "POST",
|
|
208
251
|
headers: { "Content-Type": "application/json", ...headers },
|
|
@@ -213,12 +256,13 @@ function useAgentChat(config2) {
|
|
|
213
256
|
);
|
|
214
257
|
const retry = useCallback(async () => {
|
|
215
258
|
if (lastUserMessageRef.current) {
|
|
216
|
-
await sendMessage(lastUserMessageRef.current);
|
|
259
|
+
await sendMessage(lastUserMessageRef.current, lastUserAttachmentsRef.current);
|
|
217
260
|
}
|
|
218
261
|
}, [sendMessage]);
|
|
219
262
|
const reset = useCallback(() => {
|
|
220
263
|
dispatch({ type: "RESET" });
|
|
221
264
|
lastUserMessageRef.current = null;
|
|
265
|
+
lastUserAttachmentsRef.current = void 0;
|
|
222
266
|
}, []);
|
|
223
267
|
const actions = {
|
|
224
268
|
sendMessage,
|
|
@@ -233,7 +277,7 @@ function useAgentChat(config2) {
|
|
|
233
277
|
|
|
234
278
|
// src/chat/MessageThread/MessageThread.tsx
|
|
235
279
|
import { twMerge as twMerge5 } from "tailwind-merge";
|
|
236
|
-
import { useEffect, useRef as useRef2 } from "react";
|
|
280
|
+
import { useCallback as useCallback2, useEffect, useRef as useRef2 } from "react";
|
|
237
281
|
|
|
238
282
|
// src/chat/MessageBubble/MessageBubble.tsx
|
|
239
283
|
import { twMerge as twMerge4 } from "tailwind-merge";
|
|
@@ -242,6 +286,7 @@ import { twMerge as twMerge4 } from "tailwind-merge";
|
|
|
242
286
|
import { Badge as Badge2 } from "@surf-kit/core";
|
|
243
287
|
|
|
244
288
|
// src/response/ResponseMessage/ResponseMessage.tsx
|
|
289
|
+
import React from "react";
|
|
245
290
|
import ReactMarkdown from "react-markdown";
|
|
246
291
|
import rehypeSanitize from "rehype-sanitize";
|
|
247
292
|
import { twMerge } from "tailwind-merge";
|
|
@@ -266,6 +311,7 @@ function ResponseMessage({ content, className }) {
|
|
|
266
311
|
"[&_h3]:text-sm [&_h3]:font-semibold [&_h3]:text-accent [&_h3]:mt-2 [&_h3]:mb-1",
|
|
267
312
|
"[&_code]:bg-surface-raised [&_code]:text-accent [&_code]:px-1.5 [&_code]:py-0.5 [&_code]:rounded [&_code]:text-xs [&_code]:font-mono",
|
|
268
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",
|
|
269
315
|
"[&_blockquote]:border-l-2 [&_blockquote]:border-border-strong [&_blockquote]:pl-4 [&_blockquote]:text-text-secondary",
|
|
270
316
|
"[&_a]:text-accent [&_a]:underline-offset-2 [&_a]:hover:text-accent/80",
|
|
271
317
|
className
|
|
@@ -281,11 +327,24 @@ function ResponseMessage({ content, className }) {
|
|
|
281
327
|
p: ({ children }) => /* @__PURE__ */ jsx("p", { className: "my-2", children }),
|
|
282
328
|
ul: ({ children }) => /* @__PURE__ */ jsx("ul", { className: "my-2 list-disc pl-6", children }),
|
|
283
329
|
ol: ({ children }) => /* @__PURE__ */ jsx("ol", { className: "my-2 list-decimal pl-6", children }),
|
|
284
|
-
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
|
+
},
|
|
285
342
|
strong: ({ children }) => /* @__PURE__ */ jsx("strong", { className: "font-semibold", children }),
|
|
343
|
+
em: ({ children }) => /* @__PURE__ */ jsx("em", { className: "italic text-text-secondary", children }),
|
|
286
344
|
h1: ({ children }) => /* @__PURE__ */ jsx("h1", { className: "text-base font-bold mt-4 mb-2", children }),
|
|
287
345
|
h2: ({ children }) => /* @__PURE__ */ jsx("h2", { className: "text-sm font-bold mt-3 mb-1", children }),
|
|
288
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" }),
|
|
289
348
|
code: ({ children }) => /* @__PURE__ */ jsx("code", { className: "bg-surface-sunken rounded px-1 py-0.5 text-xs font-mono", children })
|
|
290
349
|
},
|
|
291
350
|
children: normalizeMarkdownLists(content)
|
|
@@ -421,7 +480,14 @@ function renderWarning(data) {
|
|
|
421
480
|
}
|
|
422
481
|
);
|
|
423
482
|
}
|
|
424
|
-
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;
|
|
425
491
|
if (!data) return null;
|
|
426
492
|
let content;
|
|
427
493
|
switch (uiHint) {
|
|
@@ -501,7 +567,36 @@ function SourceCard({ source, variant = "compact", onNavigate, className }) {
|
|
|
501
567
|
children: [
|
|
502
568
|
/* @__PURE__ */ jsxs2("div", { className: "flex items-start justify-between gap-2", children: [
|
|
503
569
|
/* @__PURE__ */ jsxs2("div", { className: "flex-1 min-w-0", children: [
|
|
504
|
-
/* @__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 }),
|
|
505
600
|
source.section && /* @__PURE__ */ jsx3("p", { className: "text-[11px] font-semibold uppercase tracking-wider text-text-secondary truncate mt-0.5", children: source.section })
|
|
506
601
|
] }),
|
|
507
602
|
/* @__PURE__ */ jsx3(
|
|
@@ -635,13 +730,16 @@ function AgentResponse({
|
|
|
635
730
|
}) {
|
|
636
731
|
return /* @__PURE__ */ jsxs4("div", { className: `flex flex-col gap-4 ${className ?? ""}`, "data-testid": "agent-response", children: [
|
|
637
732
|
/* @__PURE__ */ jsx6(ResponseMessage, { content: response.message }),
|
|
638
|
-
response.ui_hint !== "text" && response.structured_data &&
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
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
|
+
})(),
|
|
645
743
|
(showConfidence || showVerification) && /* @__PURE__ */ jsxs4("div", { className: "flex flex-wrap items-center gap-2 mt-1", "data-testid": "response-meta", children: [
|
|
646
744
|
showConfidence && /* @__PURE__ */ jsxs4(
|
|
647
745
|
Badge2,
|
|
@@ -692,6 +790,31 @@ function AgentResponse({
|
|
|
692
790
|
|
|
693
791
|
// src/chat/MessageBubble/MessageBubble.tsx
|
|
694
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
|
+
}
|
|
695
818
|
function MessageBubble({
|
|
696
819
|
message,
|
|
697
820
|
showAgent,
|
|
@@ -699,23 +822,29 @@ function MessageBubble({
|
|
|
699
822
|
showConfidence = true,
|
|
700
823
|
showVerification = true,
|
|
701
824
|
animated = true,
|
|
825
|
+
userBubbleClassName,
|
|
702
826
|
className
|
|
703
827
|
}) {
|
|
704
828
|
const isUser = message.role === "user";
|
|
829
|
+
const hasAttachments = message.attachments && message.attachments.length > 0;
|
|
705
830
|
if (isUser) {
|
|
706
831
|
return /* @__PURE__ */ jsx7(
|
|
707
832
|
"div",
|
|
708
833
|
{
|
|
709
834
|
"data-message-id": message.id,
|
|
710
835
|
className: twMerge4("flex w-full justify-end", className),
|
|
711
|
-
children: /* @__PURE__ */
|
|
836
|
+
children: /* @__PURE__ */ jsxs5(
|
|
712
837
|
"div",
|
|
713
838
|
{
|
|
714
839
|
className: twMerge4(
|
|
715
|
-
"max-w-[70%] rounded-[18px] rounded-br-[4px] px-4 py-2.5 bg-
|
|
716
|
-
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
|
|
717
843
|
),
|
|
718
|
-
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
|
+
]
|
|
719
848
|
}
|
|
720
849
|
)
|
|
721
850
|
}
|
|
@@ -727,7 +856,7 @@ function MessageBubble({
|
|
|
727
856
|
"data-message-id": message.id,
|
|
728
857
|
className: twMerge4("flex w-full flex-col items-start gap-1.5", className),
|
|
729
858
|
children: [
|
|
730
|
-
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("_", " ") }),
|
|
731
860
|
/* @__PURE__ */ jsx7(
|
|
732
861
|
"div",
|
|
733
862
|
{
|
|
@@ -753,34 +882,70 @@ function MessageBubble({
|
|
|
753
882
|
|
|
754
883
|
// src/chat/MessageThread/MessageThread.tsx
|
|
755
884
|
import { jsx as jsx8, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
756
|
-
function MessageThread({ messages, streamingSlot, showSources, showConfidence, showVerification, className }) {
|
|
757
|
-
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]);
|
|
758
907
|
useEffect(() => {
|
|
759
|
-
|
|
760
|
-
|
|
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]);
|
|
761
917
|
return /* @__PURE__ */ jsxs6(
|
|
762
918
|
"div",
|
|
763
919
|
{
|
|
920
|
+
ref: scrollRef,
|
|
764
921
|
role: "log",
|
|
765
922
|
"aria-live": "polite",
|
|
766
923
|
"aria-label": "Message thread",
|
|
924
|
+
onScroll: handleScroll,
|
|
767
925
|
className: twMerge5(
|
|
768
926
|
"flex flex-col gap-4 overflow-y-auto flex-1 px-4 py-6",
|
|
769
927
|
className
|
|
770
928
|
),
|
|
771
929
|
children: [
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
{
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
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
|
|
784
949
|
]
|
|
785
950
|
}
|
|
786
951
|
);
|
|
@@ -788,8 +953,96 @@ function MessageThread({ messages, streamingSlot, showSources, showConfidence, s
|
|
|
788
953
|
|
|
789
954
|
// src/chat/MessageComposer/MessageComposer.tsx
|
|
790
955
|
import { twMerge as twMerge6 } from "tailwind-merge";
|
|
791
|
-
import { useState as useState2, useRef as useRef3, useCallback as
|
|
956
|
+
import { useState as useState2, useRef as useRef3, useCallback as useCallback3 } from "react";
|
|
792
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
|
+
}
|
|
793
1046
|
function MessageComposer({
|
|
794
1047
|
onSend,
|
|
795
1048
|
isLoading = false,
|
|
@@ -797,23 +1050,29 @@ function MessageComposer({
|
|
|
797
1050
|
className
|
|
798
1051
|
}) {
|
|
799
1052
|
const [value, setValue] = useState2("");
|
|
1053
|
+
const [attachments, setAttachments] = useState2([]);
|
|
1054
|
+
const [dragOver, setDragOver] = useState2(false);
|
|
800
1055
|
const textareaRef = useRef3(null);
|
|
801
|
-
const
|
|
802
|
-
const
|
|
1056
|
+
const fileInputRef = useRef3(null);
|
|
1057
|
+
const canSend = (value.trim().length > 0 || attachments.length > 0) && !isLoading;
|
|
1058
|
+
const resetHeight = useCallback3(() => {
|
|
803
1059
|
const el = textareaRef.current;
|
|
804
1060
|
if (el) {
|
|
805
1061
|
el.style.height = "auto";
|
|
806
1062
|
el.style.overflowY = "hidden";
|
|
807
1063
|
}
|
|
808
1064
|
}, []);
|
|
809
|
-
const handleSend =
|
|
1065
|
+
const handleSend = useCallback3(() => {
|
|
810
1066
|
if (!canSend) return;
|
|
811
|
-
|
|
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);
|
|
812
1070
|
setValue("");
|
|
1071
|
+
setAttachments([]);
|
|
813
1072
|
resetHeight();
|
|
814
1073
|
textareaRef.current?.focus();
|
|
815
|
-
}, [canSend, onSend, value, resetHeight]);
|
|
816
|
-
const handleKeyDown =
|
|
1074
|
+
}, [canSend, onSend, value, attachments, resetHeight]);
|
|
1075
|
+
const handleKeyDown = useCallback3(
|
|
817
1076
|
(e) => {
|
|
818
1077
|
if (e.key === "Enter" && !e.shiftKey) {
|
|
819
1078
|
e.preventDefault();
|
|
@@ -822,64 +1081,194 @@ function MessageComposer({
|
|
|
822
1081
|
},
|
|
823
1082
|
[handleSend]
|
|
824
1083
|
);
|
|
825
|
-
const handleChange =
|
|
1084
|
+
const handleChange = useCallback3(
|
|
826
1085
|
(e) => {
|
|
827
1086
|
setValue(e.target.value);
|
|
828
1087
|
const el = e.target;
|
|
829
1088
|
el.style.height = "auto";
|
|
830
|
-
const capped = Math.min(el.scrollHeight,
|
|
1089
|
+
const capped = Math.min(el.scrollHeight, 200);
|
|
831
1090
|
el.style.height = `${capped}px`;
|
|
832
|
-
el.style.overflowY = el.scrollHeight >
|
|
1091
|
+
el.style.overflowY = el.scrollHeight > 200 ? "auto" : "hidden";
|
|
833
1092
|
},
|
|
834
1093
|
[]
|
|
835
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
|
+
);
|
|
836
1174
|
return /* @__PURE__ */ jsxs7(
|
|
837
1175
|
"div",
|
|
838
1176
|
{
|
|
839
1177
|
className: twMerge6(
|
|
840
|
-
"
|
|
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",
|
|
841
1183
|
className
|
|
842
1184
|
),
|
|
1185
|
+
onDragOver: handleDragOver,
|
|
1186
|
+
onDragLeave: handleDragLeave,
|
|
1187
|
+
onDrop: handleDrop,
|
|
843
1188
|
children: [
|
|
844
1189
|
/* @__PURE__ */ jsx9(
|
|
845
|
-
"
|
|
1190
|
+
"input",
|
|
846
1191
|
{
|
|
847
|
-
ref:
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
className: twMerge6(
|
|
855
|
-
"flex-1 resize-none rounded-xl border border-border bg-surface/80",
|
|
856
|
-
"px-4 py-2.5 text-sm text-text-primary placeholder:text-text-muted",
|
|
857
|
-
"focus:border-transparent focus:ring-2 focus:ring-accent/40 focus:outline-none",
|
|
858
|
-
"disabled:opacity-50 disabled:cursor-not-allowed",
|
|
859
|
-
"overflow-hidden",
|
|
860
|
-
"transition-all duration-200"
|
|
861
|
-
),
|
|
862
|
-
style: { colorScheme: "dark" },
|
|
863
|
-
"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"
|
|
864
1199
|
}
|
|
865
1200
|
),
|
|
866
|
-
/* @__PURE__ */ jsx9(
|
|
867
|
-
|
|
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,
|
|
868
1203
|
{
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
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
|
+
] })
|
|
883
1272
|
]
|
|
884
1273
|
}
|
|
885
1274
|
);
|
|
@@ -892,6 +1281,7 @@ function WelcomeScreen({
|
|
|
892
1281
|
title = "Welcome",
|
|
893
1282
|
message = "How can I help you today?",
|
|
894
1283
|
icon,
|
|
1284
|
+
iconClassName,
|
|
895
1285
|
suggestedQuestions = [],
|
|
896
1286
|
onQuestionSelect,
|
|
897
1287
|
className
|
|
@@ -904,12 +1294,15 @@ function WelcomeScreen({
|
|
|
904
1294
|
className
|
|
905
1295
|
),
|
|
906
1296
|
children: [
|
|
907
|
-
/* @__PURE__ */ jsx10(
|
|
1297
|
+
icon ? iconClassName ? /* @__PURE__ */ jsx10("div", { className: iconClassName, "aria-hidden": "true", children: icon }) : icon : /* @__PURE__ */ jsx10(
|
|
908
1298
|
"div",
|
|
909
1299
|
{
|
|
910
|
-
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
|
+
),
|
|
911
1304
|
"aria-hidden": "true",
|
|
912
|
-
children:
|
|
1305
|
+
children: /* @__PURE__ */ jsx10("span", { className: "text-2xl", children: "\u2726" })
|
|
913
1306
|
}
|
|
914
1307
|
),
|
|
915
1308
|
/* @__PURE__ */ jsxs8("div", { className: "flex flex-col gap-2", children: [
|
|
@@ -919,7 +1312,7 @@ function WelcomeScreen({
|
|
|
919
1312
|
suggestedQuestions.length > 0 && /* @__PURE__ */ jsx10(
|
|
920
1313
|
"div",
|
|
921
1314
|
{
|
|
922
|
-
className: "flex flex-wrap justify-center gap-2 max-w-
|
|
1315
|
+
className: "flex flex-wrap justify-center gap-2 max-w-xl",
|
|
923
1316
|
role: "group",
|
|
924
1317
|
"aria-label": "Suggested questions",
|
|
925
1318
|
children: suggestedQuestions.map((question) => /* @__PURE__ */ jsx10(
|
|
@@ -928,7 +1321,7 @@ function WelcomeScreen({
|
|
|
928
1321
|
type: "button",
|
|
929
1322
|
onClick: () => onQuestionSelect?.(question),
|
|
930
1323
|
className: twMerge7(
|
|
931
|
-
"px-
|
|
1324
|
+
"px-3.5 py-1.5 rounded-full text-[12px]",
|
|
932
1325
|
"border border-border bg-transparent text-text-secondary",
|
|
933
1326
|
"hover:bg-accent/10 hover:border-interactive hover:text-text-primary",
|
|
934
1327
|
"focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-accent",
|
|
@@ -947,7 +1340,8 @@ function WelcomeScreen({
|
|
|
947
1340
|
|
|
948
1341
|
// src/streaming/StreamingMessage/StreamingMessage.tsx
|
|
949
1342
|
import { useEffect as useEffect3, useRef as useRef5 } from "react";
|
|
950
|
-
import {
|
|
1343
|
+
import { twMerge as twMerge8 } from "tailwind-merge";
|
|
1344
|
+
import { WaveLoader } from "@surf-kit/core";
|
|
951
1345
|
|
|
952
1346
|
// src/hooks/useCharacterDrain.ts
|
|
953
1347
|
import { useState as useState3, useRef as useRef4, useEffect as useEffect2 } from "react";
|
|
@@ -976,7 +1370,10 @@ function useCharacterDrain(target, msPerChar = 15) {
|
|
|
976
1370
|
const elapsed = now - lastTimeRef.current;
|
|
977
1371
|
const charsToAdvance = Math.floor(elapsed / msPerCharRef.current);
|
|
978
1372
|
if (charsToAdvance > 0 && indexRef.current < currentTarget.length) {
|
|
979
|
-
|
|
1373
|
+
let nextIndex = Math.min(indexRef.current + charsToAdvance, currentTarget.length);
|
|
1374
|
+
while (nextIndex < currentTarget.length && currentTarget[nextIndex - 1].trim() === "") {
|
|
1375
|
+
nextIndex++;
|
|
1376
|
+
}
|
|
980
1377
|
indexRef.current = nextIndex;
|
|
981
1378
|
lastTimeRef.current = now;
|
|
982
1379
|
setDisplayed(currentTarget.slice(0, nextIndex));
|
|
@@ -1021,14 +1418,35 @@ var phaseLabels = {
|
|
|
1021
1418
|
generating: "Writing...",
|
|
1022
1419
|
verifying: "Verifying..."
|
|
1023
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
|
+
`;
|
|
1024
1439
|
function StreamingMessage({
|
|
1025
1440
|
stream,
|
|
1026
1441
|
onComplete,
|
|
1442
|
+
onDraining,
|
|
1027
1443
|
showPhases = true,
|
|
1028
1444
|
className
|
|
1029
1445
|
}) {
|
|
1030
1446
|
const onCompleteRef = useRef5(onComplete);
|
|
1031
1447
|
onCompleteRef.current = onComplete;
|
|
1448
|
+
const onDrainingRef = useRef5(onDraining);
|
|
1449
|
+
onDrainingRef.current = onDraining;
|
|
1032
1450
|
const wasActiveRef = useRef5(stream.active);
|
|
1033
1451
|
useEffect3(() => {
|
|
1034
1452
|
if (wasActiveRef.current && !stream.active) {
|
|
@@ -1037,35 +1455,40 @@ function StreamingMessage({
|
|
|
1037
1455
|
wasActiveRef.current = stream.active;
|
|
1038
1456
|
}, [stream.active]);
|
|
1039
1457
|
const phaseLabel = phaseLabels[stream.phase];
|
|
1040
|
-
const { displayed:
|
|
1041
|
-
|
|
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: [
|
|
1042
1467
|
/* @__PURE__ */ jsxs9("div", { "aria-live": "assertive", className: "sr-only", children: [
|
|
1043
1468
|
stream.active && stream.phase !== "idle" && "Response started",
|
|
1044
1469
|
!stream.active && stream.content && "Response complete"
|
|
1045
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 }),
|
|
1046
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: [
|
|
1047
|
-
|
|
1474
|
+
showPhaseIndicator && /* @__PURE__ */ jsxs9(
|
|
1048
1475
|
"div",
|
|
1049
1476
|
{
|
|
1050
|
-
className: "flex items-center gap-2
|
|
1477
|
+
className: "flex items-center gap-2 text-sm text-text-secondary",
|
|
1051
1478
|
"data-testid": "phase-indicator",
|
|
1052
1479
|
children: [
|
|
1053
|
-
/* @__PURE__ */ jsx11("span", { "aria-hidden": "true", children: /* @__PURE__ */ jsx11(
|
|
1480
|
+
/* @__PURE__ */ jsx11("span", { "aria-hidden": "true", children: /* @__PURE__ */ jsx11(WaveLoader, { size: "sm", color: "#38bdf8" }) }),
|
|
1054
1481
|
/* @__PURE__ */ jsx11("span", { children: phaseLabel })
|
|
1055
1482
|
]
|
|
1056
1483
|
}
|
|
1057
1484
|
),
|
|
1058
|
-
/* @__PURE__ */
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
"data-testid": "streaming-cursor"
|
|
1066
|
-
}
|
|
1067
|
-
)
|
|
1068
|
-
] })
|
|
1485
|
+
displayedContent && /* @__PURE__ */ jsx11(
|
|
1486
|
+
ResponseMessage,
|
|
1487
|
+
{
|
|
1488
|
+
content: displayedContent,
|
|
1489
|
+
className: showCursor ? "sk-streaming-cursor" : void 0
|
|
1490
|
+
}
|
|
1491
|
+
)
|
|
1069
1492
|
] })
|
|
1070
1493
|
] });
|
|
1071
1494
|
}
|
|
@@ -1096,7 +1519,7 @@ function AgentChat({
|
|
|
1096
1519
|
return /* @__PURE__ */ jsxs10(
|
|
1097
1520
|
"div",
|
|
1098
1521
|
{
|
|
1099
|
-
className:
|
|
1522
|
+
className: twMerge9(
|
|
1100
1523
|
"flex flex-col h-full bg-canvas border border-border rounded-xl overflow-hidden",
|
|
1101
1524
|
className
|
|
1102
1525
|
),
|
|
@@ -1599,7 +2022,7 @@ function ThinkingIndicator({ label = "Thinking...", className }) {
|
|
|
1599
2022
|
}
|
|
1600
2023
|
|
|
1601
2024
|
// src/streaming/ToolExecution/ToolExecution.tsx
|
|
1602
|
-
import {
|
|
2025
|
+
import { WaveLoader as WaveLoader2 } from "@surf-kit/core";
|
|
1603
2026
|
import { jsx as jsx26, jsxs as jsxs23 } from "react/jsx-runtime";
|
|
1604
2027
|
var defaultLabels = {
|
|
1605
2028
|
search: "Searching knowledge base...",
|
|
@@ -1615,7 +2038,7 @@ function ToolExecution({ tool, label, className }) {
|
|
|
1615
2038
|
role: "status",
|
|
1616
2039
|
"data-testid": "tool-execution",
|
|
1617
2040
|
children: [
|
|
1618
|
-
/* @__PURE__ */ jsx26("span", { "aria-hidden": "true", children: /* @__PURE__ */ jsx26(
|
|
2041
|
+
/* @__PURE__ */ jsx26("span", { "aria-hidden": "true", children: /* @__PURE__ */ jsx26(WaveLoader2, { size: "sm", color: "#38bdf8" }) }),
|
|
1619
2042
|
/* @__PURE__ */ jsx26("span", { children: displayLabel })
|
|
1620
2043
|
]
|
|
1621
2044
|
}
|
|
@@ -1623,7 +2046,7 @@ function ToolExecution({ tool, label, className }) {
|
|
|
1623
2046
|
}
|
|
1624
2047
|
|
|
1625
2048
|
// src/streaming/RetrievalProgress/RetrievalProgress.tsx
|
|
1626
|
-
import {
|
|
2049
|
+
import { WaveLoader as WaveLoader3 } from "@surf-kit/core";
|
|
1627
2050
|
import { jsx as jsx27, jsxs as jsxs24 } from "react/jsx-runtime";
|
|
1628
2051
|
function RetrievalProgress({ sources, isActive, className }) {
|
|
1629
2052
|
return /* @__PURE__ */ jsxs24(
|
|
@@ -1635,7 +2058,7 @@ function RetrievalProgress({ sources, isActive, className }) {
|
|
|
1635
2058
|
"data-testid": "retrieval-progress",
|
|
1636
2059
|
children: [
|
|
1637
2060
|
isActive && /* @__PURE__ */ jsxs24("div", { className: "flex items-center gap-2 text-sm text-text-secondary", children: [
|
|
1638
|
-
/* @__PURE__ */ jsx27("span", { "aria-hidden": "true", children: /* @__PURE__ */ jsx27(
|
|
2061
|
+
/* @__PURE__ */ jsx27("span", { "aria-hidden": "true", children: /* @__PURE__ */ jsx27(WaveLoader3, { size: "sm", color: "#38bdf8" }) }),
|
|
1639
2062
|
/* @__PURE__ */ jsx27("span", { children: "Retrieving sources..." })
|
|
1640
2063
|
] }),
|
|
1641
2064
|
sources.length > 0 && /* @__PURE__ */ jsx27("ul", { className: "space-y-1", "data-testid": "source-list", children: sources.map((source, index) => /* @__PURE__ */ jsxs24(
|
|
@@ -1733,7 +2156,7 @@ function TypewriterText({
|
|
|
1733
2156
|
}
|
|
1734
2157
|
|
|
1735
2158
|
// src/streaming/TypingIndicator/TypingIndicator.tsx
|
|
1736
|
-
import { twMerge as
|
|
2159
|
+
import { twMerge as twMerge10 } from "tailwind-merge";
|
|
1737
2160
|
import { useReducedMotion as useReducedMotion2 } from "@surf-kit/hooks";
|
|
1738
2161
|
import { jsx as jsx30, jsxs as jsxs27 } from "react/jsx-runtime";
|
|
1739
2162
|
var bounceKeyframes = `
|
|
@@ -1753,7 +2176,7 @@ function TypingIndicator({
|
|
|
1753
2176
|
{
|
|
1754
2177
|
role: "status",
|
|
1755
2178
|
"aria-label": label ?? "typing",
|
|
1756
|
-
className:
|
|
2179
|
+
className: twMerge10("inline-flex items-center gap-2", className),
|
|
1757
2180
|
"data-testid": "typing-indicator",
|
|
1758
2181
|
children: [
|
|
1759
2182
|
!reducedMotion && /* @__PURE__ */ jsx30("style", { children: bounceKeyframes }),
|
|
@@ -1775,7 +2198,7 @@ function TypingIndicator({
|
|
|
1775
2198
|
}
|
|
1776
2199
|
|
|
1777
2200
|
// src/streaming/TextGlimmer/TextGlimmer.tsx
|
|
1778
|
-
import { twMerge as
|
|
2201
|
+
import { twMerge as twMerge11 } from "tailwind-merge";
|
|
1779
2202
|
import { useReducedMotion as useReducedMotion3 } from "@surf-kit/hooks";
|
|
1780
2203
|
import { jsx as jsx31, jsxs as jsxs28 } from "react/jsx-runtime";
|
|
1781
2204
|
var shimmerKeyframes = `
|
|
@@ -1792,7 +2215,7 @@ function TextGlimmer({ lines = 3, className }) {
|
|
|
1792
2215
|
{
|
|
1793
2216
|
role: "status",
|
|
1794
2217
|
"aria-label": "Loading",
|
|
1795
|
-
className:
|
|
2218
|
+
className: twMerge11("flex flex-col gap-2", className),
|
|
1796
2219
|
"data-testid": "text-glimmer",
|
|
1797
2220
|
children: [
|
|
1798
2221
|
!reducedMotion && /* @__PURE__ */ jsx31("style", { children: shimmerKeyframes }),
|
|
@@ -1818,7 +2241,7 @@ function TextGlimmer({ lines = 3, className }) {
|
|
|
1818
2241
|
}
|
|
1819
2242
|
|
|
1820
2243
|
// src/streaming/StreamingList/StreamingList.tsx
|
|
1821
|
-
import { twMerge as
|
|
2244
|
+
import { twMerge as twMerge12 } from "tailwind-merge";
|
|
1822
2245
|
import { useReducedMotion as useReducedMotion4 } from "@surf-kit/hooks";
|
|
1823
2246
|
import { jsx as jsx32, jsxs as jsxs29 } from "react/jsx-runtime";
|
|
1824
2247
|
var fadeSlideInKeyframes = `
|
|
@@ -1836,13 +2259,13 @@ function StreamingList({
|
|
|
1836
2259
|
}) {
|
|
1837
2260
|
const reducedMotion = useReducedMotion4();
|
|
1838
2261
|
if (items.length === 0 && !isStreaming) {
|
|
1839
|
-
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;
|
|
1840
2263
|
}
|
|
1841
2264
|
return /* @__PURE__ */ jsxs29(
|
|
1842
2265
|
"ul",
|
|
1843
2266
|
{
|
|
1844
2267
|
"aria-live": "polite",
|
|
1845
|
-
className:
|
|
2268
|
+
className: twMerge12("list-none p-0 m-0", className),
|
|
1846
2269
|
"data-testid": "streaming-list",
|
|
1847
2270
|
children: [
|
|
1848
2271
|
!reducedMotion && /* @__PURE__ */ jsx32("style", { children: fadeSlideInKeyframes }),
|
|
@@ -1862,7 +2285,7 @@ function StreamingList({
|
|
|
1862
2285
|
}
|
|
1863
2286
|
|
|
1864
2287
|
// src/streaming/StreamingStructure/StreamingStructure.tsx
|
|
1865
|
-
import { twMerge as
|
|
2288
|
+
import { twMerge as twMerge13 } from "tailwind-merge";
|
|
1866
2289
|
import { useReducedMotion as useReducedMotion5 } from "@surf-kit/hooks";
|
|
1867
2290
|
import { jsx as jsx33, jsxs as jsxs30 } from "react/jsx-runtime";
|
|
1868
2291
|
var fadeSlideInKeyframes2 = `
|
|
@@ -1911,7 +2334,7 @@ function StreamingStructure({
|
|
|
1911
2334
|
"dl",
|
|
1912
2335
|
{
|
|
1913
2336
|
"aria-live": "polite",
|
|
1914
|
-
className:
|
|
2337
|
+
className: twMerge13("m-0", className),
|
|
1915
2338
|
"data-testid": "streaming-structure",
|
|
1916
2339
|
children: [
|
|
1917
2340
|
!reducedMotion && /* @__PURE__ */ jsx33("style", { children: fadeSlideInKeyframes2 }),
|
|
@@ -1934,7 +2357,7 @@ function StreamingStructure({
|
|
|
1934
2357
|
}
|
|
1935
2358
|
|
|
1936
2359
|
// src/chat/ConversationList/ConversationList.tsx
|
|
1937
|
-
import { twMerge as
|
|
2360
|
+
import { twMerge as twMerge14 } from "tailwind-merge";
|
|
1938
2361
|
import { jsx as jsx34, jsxs as jsxs31 } from "react/jsx-runtime";
|
|
1939
2362
|
function ConversationList({
|
|
1940
2363
|
conversations,
|
|
@@ -1948,7 +2371,7 @@ function ConversationList({
|
|
|
1948
2371
|
"nav",
|
|
1949
2372
|
{
|
|
1950
2373
|
"aria-label": "Conversation list",
|
|
1951
|
-
className:
|
|
2374
|
+
className: twMerge14("flex flex-col h-full bg-canvas", className),
|
|
1952
2375
|
children: [
|
|
1953
2376
|
onNew && /* @__PURE__ */ jsx34("div", { className: "p-3 border-b border-border", children: /* @__PURE__ */ jsx34(
|
|
1954
2377
|
"button",
|
|
@@ -1965,7 +2388,7 @@ function ConversationList({
|
|
|
1965
2388
|
return /* @__PURE__ */ jsxs31(
|
|
1966
2389
|
"li",
|
|
1967
2390
|
{
|
|
1968
|
-
className:
|
|
2391
|
+
className: twMerge14(
|
|
1969
2392
|
"flex items-start border-b border-border transition-colors duration-200",
|
|
1970
2393
|
"hover:bg-surface",
|
|
1971
2394
|
isActive && "bg-surface-raised border-l-2 border-l-accent"
|
|
@@ -2025,8 +2448,8 @@ function ConversationList({
|
|
|
2025
2448
|
}
|
|
2026
2449
|
|
|
2027
2450
|
// src/layouts/AgentFullPage/AgentFullPage.tsx
|
|
2028
|
-
import { twMerge as
|
|
2029
|
-
import { useState as useState7, useCallback as
|
|
2451
|
+
import { twMerge as twMerge15 } from "tailwind-merge";
|
|
2452
|
+
import { useState as useState7, useCallback as useCallback4 } from "react";
|
|
2030
2453
|
import { Fragment, jsx as jsx35, jsxs as jsxs32 } from "react/jsx-runtime";
|
|
2031
2454
|
function AgentFullPage({
|
|
2032
2455
|
endpoint,
|
|
@@ -2040,7 +2463,7 @@ function AgentFullPage({
|
|
|
2040
2463
|
className
|
|
2041
2464
|
}) {
|
|
2042
2465
|
const [sidebarOpen, setSidebarOpen] = useState7(false);
|
|
2043
|
-
const handleSelect =
|
|
2466
|
+
const handleSelect = useCallback4(
|
|
2044
2467
|
(id) => {
|
|
2045
2468
|
onConversationSelect?.(id);
|
|
2046
2469
|
setSidebarOpen(false);
|
|
@@ -2050,7 +2473,7 @@ function AgentFullPage({
|
|
|
2050
2473
|
return /* @__PURE__ */ jsxs32(
|
|
2051
2474
|
"div",
|
|
2052
2475
|
{
|
|
2053
|
-
className:
|
|
2476
|
+
className: twMerge15("flex h-screen w-full overflow-hidden bg-brand-dark", className),
|
|
2054
2477
|
"data-testid": "agent-full-page",
|
|
2055
2478
|
children: [
|
|
2056
2479
|
showConversationList && /* @__PURE__ */ jsxs32(Fragment, { children: [
|
|
@@ -2065,7 +2488,7 @@ function AgentFullPage({
|
|
|
2065
2488
|
/* @__PURE__ */ jsx35(
|
|
2066
2489
|
"aside",
|
|
2067
2490
|
{
|
|
2068
|
-
className:
|
|
2491
|
+
className: twMerge15(
|
|
2069
2492
|
"bg-brand-dark border-r border-brand-gold/15 w-72 shrink-0 flex-col z-40",
|
|
2070
2493
|
// Desktop: always visible
|
|
2071
2494
|
"hidden md:flex",
|
|
@@ -2131,7 +2554,7 @@ function AgentFullPage({
|
|
|
2131
2554
|
}
|
|
2132
2555
|
|
|
2133
2556
|
// src/layouts/AgentPanel/AgentPanel.tsx
|
|
2134
|
-
import { twMerge as
|
|
2557
|
+
import { twMerge as twMerge16 } from "tailwind-merge";
|
|
2135
2558
|
import { useRef as useRef6, useEffect as useEffect5 } from "react";
|
|
2136
2559
|
import { jsx as jsx36, jsxs as jsxs33 } from "react/jsx-runtime";
|
|
2137
2560
|
function AgentPanel({
|
|
@@ -2156,13 +2579,13 @@ function AgentPanel({
|
|
|
2156
2579
|
return /* @__PURE__ */ jsxs33(
|
|
2157
2580
|
"div",
|
|
2158
2581
|
{
|
|
2159
|
-
className:
|
|
2582
|
+
className: twMerge16("fixed inset-0 z-50", !isOpen && "pointer-events-none"),
|
|
2160
2583
|
"aria-hidden": !isOpen,
|
|
2161
2584
|
children: [
|
|
2162
2585
|
/* @__PURE__ */ jsx36(
|
|
2163
2586
|
"div",
|
|
2164
2587
|
{
|
|
2165
|
-
className:
|
|
2588
|
+
className: twMerge16(
|
|
2166
2589
|
"fixed inset-0 transition-opacity duration-300",
|
|
2167
2590
|
isOpen ? "opacity-100 bg-brand-dark/70 backdrop-blur-sm pointer-events-auto" : "opacity-0 pointer-events-none"
|
|
2168
2591
|
),
|
|
@@ -2178,7 +2601,7 @@ function AgentPanel({
|
|
|
2178
2601
|
"aria-label": title,
|
|
2179
2602
|
"aria-modal": isOpen ? "true" : void 0,
|
|
2180
2603
|
style: { width: widthStyle, maxWidth: "100vw" },
|
|
2181
|
-
className:
|
|
2604
|
+
className: twMerge16(
|
|
2182
2605
|
"fixed top-0 h-full flex flex-col z-50 bg-brand-dark shadow-card",
|
|
2183
2606
|
"transition-transform duration-300 ease-in-out",
|
|
2184
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"}`,
|
|
@@ -2220,8 +2643,8 @@ function AgentPanel({
|
|
|
2220
2643
|
}
|
|
2221
2644
|
|
|
2222
2645
|
// src/layouts/AgentWidget/AgentWidget.tsx
|
|
2223
|
-
import { twMerge as
|
|
2224
|
-
import { useState as useState8, useCallback as
|
|
2646
|
+
import { twMerge as twMerge17 } from "tailwind-merge";
|
|
2647
|
+
import { useState as useState8, useCallback as useCallback5 } from "react";
|
|
2225
2648
|
import { jsx as jsx37, jsxs as jsxs34 } from "react/jsx-runtime";
|
|
2226
2649
|
function AgentWidget({
|
|
2227
2650
|
endpoint,
|
|
@@ -2231,7 +2654,7 @@ function AgentWidget({
|
|
|
2231
2654
|
className
|
|
2232
2655
|
}) {
|
|
2233
2656
|
const [isOpen, setIsOpen] = useState8(false);
|
|
2234
|
-
const toggle =
|
|
2657
|
+
const toggle = useCallback5(() => {
|
|
2235
2658
|
setIsOpen((prev) => !prev);
|
|
2236
2659
|
}, []);
|
|
2237
2660
|
const positionClasses = position === "bottom-left" ? "left-4 bottom-4" : "right-4 bottom-4";
|
|
@@ -2244,7 +2667,7 @@ function AgentWidget({
|
|
|
2244
2667
|
role: "dialog",
|
|
2245
2668
|
"aria-label": title,
|
|
2246
2669
|
"aria-hidden": !isOpen,
|
|
2247
|
-
className:
|
|
2670
|
+
className: twMerge17(
|
|
2248
2671
|
"fixed z-50 flex flex-col",
|
|
2249
2672
|
"w-[min(400px,calc(100vw-2rem))] h-[min(600px,calc(100vh-6rem))]",
|
|
2250
2673
|
"rounded-2xl overflow-hidden border border-brand-gold/15",
|
|
@@ -2291,7 +2714,7 @@ function AgentWidget({
|
|
|
2291
2714
|
onClick: toggle,
|
|
2292
2715
|
"aria-label": isOpen ? "Close chat" : triggerLabel,
|
|
2293
2716
|
"aria-expanded": isOpen,
|
|
2294
|
-
className:
|
|
2717
|
+
className: twMerge17(
|
|
2295
2718
|
"fixed z-50 flex items-center justify-center w-14 h-14 rounded-full",
|
|
2296
2719
|
"bg-brand-blue text-brand-cream shadow-glow-cyan",
|
|
2297
2720
|
"hover:bg-brand-cyan hover:shadow-glow-cyan hover:scale-105",
|
|
@@ -2309,7 +2732,7 @@ function AgentWidget({
|
|
|
2309
2732
|
}
|
|
2310
2733
|
|
|
2311
2734
|
// src/layouts/AgentEmbed/AgentEmbed.tsx
|
|
2312
|
-
import { twMerge as
|
|
2735
|
+
import { twMerge as twMerge18 } from "tailwind-merge";
|
|
2313
2736
|
import { jsx as jsx38 } from "react/jsx-runtime";
|
|
2314
2737
|
function AgentEmbed({
|
|
2315
2738
|
endpoint,
|
|
@@ -2319,7 +2742,7 @@ function AgentEmbed({
|
|
|
2319
2742
|
return /* @__PURE__ */ jsx38(
|
|
2320
2743
|
"div",
|
|
2321
2744
|
{
|
|
2322
|
-
className:
|
|
2745
|
+
className: twMerge18("w-full h-full min-h-0", className),
|
|
2323
2746
|
"data-testid": "agent-embed",
|
|
2324
2747
|
children: /* @__PURE__ */ jsx38(
|
|
2325
2748
|
AgentChat,
|
|
@@ -2335,8 +2758,8 @@ function AgentEmbed({
|
|
|
2335
2758
|
|
|
2336
2759
|
// src/mcp/MCPToolCall/MCPToolCall.tsx
|
|
2337
2760
|
import { cva } from "class-variance-authority";
|
|
2338
|
-
import { twMerge as
|
|
2339
|
-
import { Badge as Badge7,
|
|
2761
|
+
import { twMerge as twMerge19 } from "tailwind-merge";
|
|
2762
|
+
import { Badge as Badge7, WaveLoader as WaveLoader4 } from "@surf-kit/core";
|
|
2340
2763
|
import { jsx as jsx39, jsxs as jsxs35 } from "react/jsx-runtime";
|
|
2341
2764
|
var statusBadgeIntent = {
|
|
2342
2765
|
pending: "default",
|
|
@@ -2379,7 +2802,7 @@ function MCPToolCall({ call, isExpanded = false, onToggleExpand, className }) {
|
|
|
2379
2802
|
return /* @__PURE__ */ jsxs35(
|
|
2380
2803
|
"div",
|
|
2381
2804
|
{
|
|
2382
|
-
className:
|
|
2805
|
+
className: twMerge19(container({ status: call.status }), className),
|
|
2383
2806
|
"data-testid": "mcp-tool-call",
|
|
2384
2807
|
children: [
|
|
2385
2808
|
/* @__PURE__ */ jsxs35(
|
|
@@ -2396,7 +2819,7 @@ function MCPToolCall({ call, isExpanded = false, onToggleExpand, className }) {
|
|
|
2396
2819
|
call.serverName && /* @__PURE__ */ jsx39("span", { className: "text-xs text-text-secondary truncate", children: call.serverName })
|
|
2397
2820
|
] }),
|
|
2398
2821
|
/* @__PURE__ */ jsxs35("div", { className: "flex items-center gap-2 shrink-0", children: [
|
|
2399
|
-
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" }) }),
|
|
2400
2823
|
/* @__PURE__ */ jsx39(
|
|
2401
2824
|
Badge7,
|
|
2402
2825
|
{
|
|
@@ -2470,7 +2893,7 @@ function MCPToolCall({ call, isExpanded = false, onToggleExpand, className }) {
|
|
|
2470
2893
|
}
|
|
2471
2894
|
|
|
2472
2895
|
// src/mcp/MCPResourceView/MCPResourceView.tsx
|
|
2473
|
-
import { twMerge as
|
|
2896
|
+
import { twMerge as twMerge20 } from "tailwind-merge";
|
|
2474
2897
|
import { jsx as jsx40, jsxs as jsxs36 } from "react/jsx-runtime";
|
|
2475
2898
|
function isImageMime(mime) {
|
|
2476
2899
|
return !!mime && mime.startsWith("image/");
|
|
@@ -2488,7 +2911,7 @@ function MCPResourceView({ resource, className }) {
|
|
|
2488
2911
|
return /* @__PURE__ */ jsxs36(
|
|
2489
2912
|
"div",
|
|
2490
2913
|
{
|
|
2491
|
-
className:
|
|
2914
|
+
className: twMerge20("rounded-lg border border-border bg-surface p-3 text-sm", className),
|
|
2492
2915
|
"data-testid": "mcp-resource-view",
|
|
2493
2916
|
children: [
|
|
2494
2917
|
/* @__PURE__ */ jsxs36("div", { className: "mb-2", children: [
|
|
@@ -2540,7 +2963,7 @@ function MCPResourceView({ resource, className }) {
|
|
|
2540
2963
|
// src/mcp/MCPServerStatus/MCPServerStatus.tsx
|
|
2541
2964
|
import { useState as useState9 } from "react";
|
|
2542
2965
|
import { cva as cva2 } from "class-variance-authority";
|
|
2543
|
-
import { twMerge as
|
|
2966
|
+
import { twMerge as twMerge21 } from "tailwind-merge";
|
|
2544
2967
|
import { jsx as jsx41, jsxs as jsxs37 } from "react/jsx-runtime";
|
|
2545
2968
|
var statusDot = cva2("inline-block h-2 w-2 rounded-full shrink-0", {
|
|
2546
2969
|
variants: {
|
|
@@ -2568,7 +2991,7 @@ function MCPServerStatus({ server, className }) {
|
|
|
2568
2991
|
return /* @__PURE__ */ jsxs37(
|
|
2569
2992
|
"div",
|
|
2570
2993
|
{
|
|
2571
|
-
className:
|
|
2994
|
+
className: twMerge21("rounded-lg border border-border bg-surface p-3 text-sm", className),
|
|
2572
2995
|
"data-testid": "mcp-server-status",
|
|
2573
2996
|
children: [
|
|
2574
2997
|
/* @__PURE__ */ jsxs37("div", { className: "flex items-center gap-2 mb-1", children: [
|
|
@@ -2682,7 +3105,7 @@ function MCPServerStatus({ server, className }) {
|
|
|
2682
3105
|
// src/mcp/MCPApprovalDialog/MCPApprovalDialog.tsx
|
|
2683
3106
|
import { useRef as useRef7, useEffect as useEffect6 } from "react";
|
|
2684
3107
|
import { cva as cva3 } from "class-variance-authority";
|
|
2685
|
-
import { twMerge as
|
|
3108
|
+
import { twMerge as twMerge22 } from "tailwind-merge";
|
|
2686
3109
|
import { useDialog, FocusScope } from "react-aria";
|
|
2687
3110
|
import { Button as Button2, Badge as Badge8 } from "@surf-kit/core";
|
|
2688
3111
|
import { jsx as jsx42, jsxs as jsxs38 } from "react/jsx-runtime";
|
|
@@ -2742,7 +3165,7 @@ function MCPApprovalDialog({
|
|
|
2742
3165
|
{
|
|
2743
3166
|
...dialogProps,
|
|
2744
3167
|
ref,
|
|
2745
|
-
className:
|
|
3168
|
+
className: twMerge22(riskBorder({ risk: riskLevel }), className),
|
|
2746
3169
|
"data-testid": "mcp-approval-dialog",
|
|
2747
3170
|
children: [
|
|
2748
3171
|
/* @__PURE__ */ jsxs38("div", { className: "flex items-center justify-between mb-4", children: [
|