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