@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/chat/index.cjs
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
'use client';
|
|
1
2
|
"use strict";
|
|
2
3
|
var __create = Object.create;
|
|
3
4
|
var __defProp = Object.defineProperty;
|
|
@@ -40,7 +41,7 @@ __export(chat_exports, {
|
|
|
40
41
|
module.exports = __toCommonJS(chat_exports);
|
|
41
42
|
|
|
42
43
|
// src/chat/AgentChat/AgentChat.tsx
|
|
43
|
-
var
|
|
44
|
+
var import_tailwind_merge9 = require("tailwind-merge");
|
|
44
45
|
|
|
45
46
|
// src/hooks/useAgentChat.ts
|
|
46
47
|
var import_react = require("react");
|
|
@@ -51,7 +52,8 @@ var initialState = {
|
|
|
51
52
|
error: null,
|
|
52
53
|
inputValue: "",
|
|
53
54
|
streamPhase: "idle",
|
|
54
|
-
streamingContent: ""
|
|
55
|
+
streamingContent: "",
|
|
56
|
+
streamingAgent: null
|
|
55
57
|
};
|
|
56
58
|
function reducer(state, action) {
|
|
57
59
|
switch (action.type) {
|
|
@@ -65,12 +67,15 @@ function reducer(state, action) {
|
|
|
65
67
|
error: null,
|
|
66
68
|
inputValue: "",
|
|
67
69
|
streamPhase: "thinking",
|
|
68
|
-
streamingContent: ""
|
|
70
|
+
streamingContent: "",
|
|
71
|
+
streamingAgent: null
|
|
69
72
|
};
|
|
70
73
|
case "STREAM_PHASE":
|
|
71
74
|
return { ...state, streamPhase: action.phase };
|
|
72
75
|
case "STREAM_CONTENT":
|
|
73
76
|
return { ...state, streamingContent: state.streamingContent + action.content };
|
|
77
|
+
case "STREAM_AGENT":
|
|
78
|
+
return { ...state, streamingAgent: action.agent };
|
|
74
79
|
case "SEND_SUCCESS":
|
|
75
80
|
return {
|
|
76
81
|
...state,
|
|
@@ -86,7 +91,8 @@ function reducer(state, action) {
|
|
|
86
91
|
isLoading: false,
|
|
87
92
|
error: action.error,
|
|
88
93
|
streamPhase: "idle",
|
|
89
|
-
streamingContent: ""
|
|
94
|
+
streamingContent: "",
|
|
95
|
+
streamingAgent: null
|
|
90
96
|
};
|
|
91
97
|
case "LOAD_CONVERSATION":
|
|
92
98
|
return {
|
|
@@ -112,107 +118,142 @@ function useAgentChat(config) {
|
|
|
112
118
|
const configRef = (0, import_react.useRef)(config);
|
|
113
119
|
configRef.current = config;
|
|
114
120
|
const lastUserMessageRef = (0, import_react.useRef)(null);
|
|
121
|
+
const lastUserAttachmentsRef = (0, import_react.useRef)(void 0);
|
|
115
122
|
const sendMessage = (0, import_react.useCallback)(
|
|
116
|
-
async (content) => {
|
|
117
|
-
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 ?? {};
|
|
118
126
|
lastUserMessageRef.current = content;
|
|
127
|
+
lastUserAttachmentsRef.current = attachments;
|
|
119
128
|
const userMessage = {
|
|
120
129
|
id: generateMessageId(),
|
|
121
130
|
role: "user",
|
|
122
131
|
content,
|
|
132
|
+
attachments,
|
|
123
133
|
timestamp: /* @__PURE__ */ new Date()
|
|
124
134
|
};
|
|
125
135
|
dispatch({ type: "SEND_START", message: userMessage });
|
|
126
136
|
const controller = new AbortController();
|
|
127
137
|
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
128
138
|
try {
|
|
129
|
-
const
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
error: {
|
|
147
|
-
code: "API_ERROR",
|
|
148
|
-
message: `HTTP ${response.status}: ${response.statusText}`,
|
|
149
|
-
retryable: response.status >= 500
|
|
150
|
-
}
|
|
151
|
-
});
|
|
152
|
-
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
|
+
}));
|
|
153
156
|
}
|
|
154
|
-
const
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
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
|
|
159
202
|
});
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
const
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
dispatch({ type: "SEND_ERROR", error: event.error });
|
|
197
|
-
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 {
|
|
198
239
|
}
|
|
199
|
-
} catch {
|
|
200
240
|
}
|
|
201
241
|
}
|
|
202
242
|
}
|
|
243
|
+
if (ctx.hadStreamError) return;
|
|
203
244
|
const assistantMessage = {
|
|
204
245
|
id: generateMessageId(),
|
|
205
246
|
role: "assistant",
|
|
206
|
-
content: agentResponse?.message ?? accumulatedContent,
|
|
207
|
-
response: agentResponse ?? void 0,
|
|
208
|
-
agent: capturedAgent ?? void 0,
|
|
247
|
+
content: ctx.agentResponse?.message ?? ctx.accumulatedContent,
|
|
248
|
+
response: ctx.agentResponse ?? void 0,
|
|
249
|
+
agent: ctx.capturedAgent ?? void 0,
|
|
209
250
|
timestamp: /* @__PURE__ */ new Date()
|
|
210
251
|
};
|
|
211
252
|
dispatch({
|
|
212
253
|
type: "SEND_SUCCESS",
|
|
213
254
|
message: assistantMessage,
|
|
214
|
-
streamingContent: accumulatedContent,
|
|
215
|
-
conversationId: capturedConversationId
|
|
255
|
+
streamingContent: ctx.accumulatedContent,
|
|
256
|
+
conversationId: ctx.capturedConversationId
|
|
216
257
|
});
|
|
217
258
|
} catch (err) {
|
|
218
259
|
clearTimeout(timeoutId);
|
|
@@ -243,7 +284,8 @@ function useAgentChat(config) {
|
|
|
243
284
|
}, []);
|
|
244
285
|
const submitFeedback = (0, import_react.useCallback)(
|
|
245
286
|
async (messageId, rating, comment) => {
|
|
246
|
-
const { apiUrl, feedbackPath = "/feedback", headers
|
|
287
|
+
const { apiUrl, feedbackPath = "/feedback", headers: headersOrFn } = configRef.current;
|
|
288
|
+
const headers = typeof headersOrFn === "function" ? await headersOrFn() : headersOrFn ?? {};
|
|
247
289
|
await fetch(`${apiUrl}${feedbackPath}`, {
|
|
248
290
|
method: "POST",
|
|
249
291
|
headers: { "Content-Type": "application/json", ...headers },
|
|
@@ -254,12 +296,13 @@ function useAgentChat(config) {
|
|
|
254
296
|
);
|
|
255
297
|
const retry = (0, import_react.useCallback)(async () => {
|
|
256
298
|
if (lastUserMessageRef.current) {
|
|
257
|
-
await sendMessage(lastUserMessageRef.current);
|
|
299
|
+
await sendMessage(lastUserMessageRef.current, lastUserAttachmentsRef.current);
|
|
258
300
|
}
|
|
259
301
|
}, [sendMessage]);
|
|
260
302
|
const reset = (0, import_react.useCallback)(() => {
|
|
261
303
|
dispatch({ type: "RESET" });
|
|
262
304
|
lastUserMessageRef.current = null;
|
|
305
|
+
lastUserAttachmentsRef.current = void 0;
|
|
263
306
|
}, []);
|
|
264
307
|
const actions = {
|
|
265
308
|
sendMessage,
|
|
@@ -274,7 +317,7 @@ function useAgentChat(config) {
|
|
|
274
317
|
|
|
275
318
|
// src/chat/MessageThread/MessageThread.tsx
|
|
276
319
|
var import_tailwind_merge5 = require("tailwind-merge");
|
|
277
|
-
var
|
|
320
|
+
var import_react4 = require("react");
|
|
278
321
|
|
|
279
322
|
// src/chat/MessageBubble/MessageBubble.tsx
|
|
280
323
|
var import_tailwind_merge4 = require("tailwind-merge");
|
|
@@ -283,6 +326,7 @@ var import_tailwind_merge4 = require("tailwind-merge");
|
|
|
283
326
|
var import_core2 = require("@surf-kit/core");
|
|
284
327
|
|
|
285
328
|
// src/response/ResponseMessage/ResponseMessage.tsx
|
|
329
|
+
var import_react2 = __toESM(require("react"), 1);
|
|
286
330
|
var import_react_markdown = __toESM(require("react-markdown"), 1);
|
|
287
331
|
var import_rehype_sanitize = __toESM(require("rehype-sanitize"), 1);
|
|
288
332
|
var import_tailwind_merge = require("tailwind-merge");
|
|
@@ -307,6 +351,7 @@ function ResponseMessage({ content, className }) {
|
|
|
307
351
|
"[&_h3]:text-sm [&_h3]:font-semibold [&_h3]:text-accent [&_h3]:mt-2 [&_h3]:mb-1",
|
|
308
352
|
"[&_code]:bg-surface-raised [&_code]:text-accent [&_code]:px-1.5 [&_code]:py-0.5 [&_code]:rounded [&_code]:text-xs [&_code]:font-mono",
|
|
309
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",
|
|
310
355
|
"[&_blockquote]:border-l-2 [&_blockquote]:border-border-strong [&_blockquote]:pl-4 [&_blockquote]:text-text-secondary",
|
|
311
356
|
"[&_a]:text-accent [&_a]:underline-offset-2 [&_a]:hover:text-accent/80",
|
|
312
357
|
className
|
|
@@ -322,11 +367,24 @@ function ResponseMessage({ content, className }) {
|
|
|
322
367
|
p: ({ children }) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "my-2", children }),
|
|
323
368
|
ul: ({ children }) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("ul", { className: "my-2 list-disc pl-6", children }),
|
|
324
369
|
ol: ({ children }) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("ol", { className: "my-2 list-decimal pl-6", children }),
|
|
325
|
-
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
|
+
},
|
|
326
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 }),
|
|
327
384
|
h1: ({ children }) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h1", { className: "text-base font-bold mt-4 mb-2", children }),
|
|
328
385
|
h2: ({ children }) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h2", { className: "text-sm font-bold mt-3 mb-1", children }),
|
|
329
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" }),
|
|
330
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 })
|
|
331
389
|
},
|
|
332
390
|
children: normalizeMarkdownLists(content)
|
|
@@ -462,7 +520,14 @@ function renderWarning(data) {
|
|
|
462
520
|
}
|
|
463
521
|
);
|
|
464
522
|
}
|
|
465
|
-
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;
|
|
466
531
|
if (!data) return null;
|
|
467
532
|
let content;
|
|
468
533
|
switch (uiHint) {
|
|
@@ -492,7 +557,7 @@ function StructuredResponse({ uiHint, data, className }) {
|
|
|
492
557
|
}
|
|
493
558
|
|
|
494
559
|
// src/sources/SourceList/SourceList.tsx
|
|
495
|
-
var
|
|
560
|
+
var import_react3 = require("react");
|
|
496
561
|
|
|
497
562
|
// src/sources/SourceCard/SourceCard.tsx
|
|
498
563
|
var import_core = require("@surf-kit/core");
|
|
@@ -542,7 +607,36 @@ function SourceCard({ source, variant = "compact", onNavigate, className }) {
|
|
|
542
607
|
children: [
|
|
543
608
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex items-start justify-between gap-2", children: [
|
|
544
609
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex-1 min-w-0", children: [
|
|
545
|
-
/* @__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 }),
|
|
546
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 })
|
|
547
641
|
] }),
|
|
548
642
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
@@ -572,7 +666,7 @@ function SourceList({
|
|
|
572
666
|
onNavigate,
|
|
573
667
|
className
|
|
574
668
|
}) {
|
|
575
|
-
const [isExpanded, setIsExpanded] = (0,
|
|
669
|
+
const [isExpanded, setIsExpanded] = (0, import_react3.useState)(defaultExpanded);
|
|
576
670
|
if (sources.length === 0) return null;
|
|
577
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)(
|
|
578
672
|
SourceCard,
|
|
@@ -676,13 +770,16 @@ function AgentResponse({
|
|
|
676
770
|
}) {
|
|
677
771
|
return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: `flex flex-col gap-4 ${className ?? ""}`, "data-testid": "agent-response", children: [
|
|
678
772
|
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(ResponseMessage, { content: response.message }),
|
|
679
|
-
response.ui_hint !== "text" && response.structured_data &&
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
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
|
+
})(),
|
|
686
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: [
|
|
687
784
|
showConfidence && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
|
|
688
785
|
import_core2.Badge,
|
|
@@ -733,6 +830,31 @@ function AgentResponse({
|
|
|
733
830
|
|
|
734
831
|
// src/chat/MessageBubble/MessageBubble.tsx
|
|
735
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
|
+
}
|
|
736
858
|
function MessageBubble({
|
|
737
859
|
message,
|
|
738
860
|
showAgent,
|
|
@@ -740,23 +862,29 @@ function MessageBubble({
|
|
|
740
862
|
showConfidence = true,
|
|
741
863
|
showVerification = true,
|
|
742
864
|
animated = true,
|
|
865
|
+
userBubbleClassName,
|
|
743
866
|
className
|
|
744
867
|
}) {
|
|
745
868
|
const isUser = message.role === "user";
|
|
869
|
+
const hasAttachments = message.attachments && message.attachments.length > 0;
|
|
746
870
|
if (isUser) {
|
|
747
871
|
return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
748
872
|
"div",
|
|
749
873
|
{
|
|
750
874
|
"data-message-id": message.id,
|
|
751
875
|
className: (0, import_tailwind_merge4.twMerge)("flex w-full justify-end", className),
|
|
752
|
-
children: /* @__PURE__ */ (0, import_jsx_runtime7.
|
|
876
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
|
|
753
877
|
"div",
|
|
754
878
|
{
|
|
755
879
|
className: (0, import_tailwind_merge4.twMerge)(
|
|
756
|
-
"max-w-[70%] rounded-[18px] rounded-br-[4px] px-4 py-2.5 bg-
|
|
757
|
-
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
|
|
758
883
|
),
|
|
759
|
-
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
|
+
]
|
|
760
888
|
}
|
|
761
889
|
)
|
|
762
890
|
}
|
|
@@ -768,7 +896,7 @@ function MessageBubble({
|
|
|
768
896
|
"data-message-id": message.id,
|
|
769
897
|
className: (0, import_tailwind_merge4.twMerge)("flex w-full flex-col items-start gap-1.5", className),
|
|
770
898
|
children: [
|
|
771
|
-
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("_", " ") }),
|
|
772
900
|
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
773
901
|
"div",
|
|
774
902
|
{
|
|
@@ -794,34 +922,70 @@ function MessageBubble({
|
|
|
794
922
|
|
|
795
923
|
// src/chat/MessageThread/MessageThread.tsx
|
|
796
924
|
var import_jsx_runtime8 = require("react/jsx-runtime");
|
|
797
|
-
function MessageThread({ messages, streamingSlot, showSources, showConfidence, showVerification, className }) {
|
|
798
|
-
const
|
|
799
|
-
(0,
|
|
800
|
-
|
|
801
|
-
|
|
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]);
|
|
802
957
|
return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
|
|
803
958
|
"div",
|
|
804
959
|
{
|
|
960
|
+
ref: scrollRef,
|
|
805
961
|
role: "log",
|
|
806
962
|
"aria-live": "polite",
|
|
807
963
|
"aria-label": "Message thread",
|
|
964
|
+
onScroll: handleScroll,
|
|
808
965
|
className: (0, import_tailwind_merge5.twMerge)(
|
|
809
966
|
"flex flex-col gap-4 overflow-y-auto flex-1 px-4 py-6",
|
|
810
967
|
className
|
|
811
968
|
),
|
|
812
969
|
children: [
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
{
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
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
|
|
825
989
|
]
|
|
826
990
|
}
|
|
827
991
|
);
|
|
@@ -829,32 +993,126 @@ function MessageThread({ messages, streamingSlot, showSources, showConfidence, s
|
|
|
829
993
|
|
|
830
994
|
// src/chat/MessageComposer/MessageComposer.tsx
|
|
831
995
|
var import_tailwind_merge6 = require("tailwind-merge");
|
|
832
|
-
var
|
|
996
|
+
var import_react5 = require("react");
|
|
833
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
|
+
}
|
|
834
1086
|
function MessageComposer({
|
|
835
1087
|
onSend,
|
|
836
1088
|
isLoading = false,
|
|
837
1089
|
placeholder = "Type a message...",
|
|
838
1090
|
className
|
|
839
1091
|
}) {
|
|
840
|
-
const [value, setValue] = (0,
|
|
841
|
-
const
|
|
842
|
-
const
|
|
843
|
-
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)(() => {
|
|
844
1099
|
const el = textareaRef.current;
|
|
845
1100
|
if (el) {
|
|
846
1101
|
el.style.height = "auto";
|
|
847
1102
|
el.style.overflowY = "hidden";
|
|
848
1103
|
}
|
|
849
1104
|
}, []);
|
|
850
|
-
const handleSend = (0,
|
|
1105
|
+
const handleSend = (0, import_react5.useCallback)(() => {
|
|
851
1106
|
if (!canSend) return;
|
|
852
|
-
|
|
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);
|
|
853
1110
|
setValue("");
|
|
1111
|
+
setAttachments([]);
|
|
854
1112
|
resetHeight();
|
|
855
1113
|
textareaRef.current?.focus();
|
|
856
|
-
}, [canSend, onSend, value, resetHeight]);
|
|
857
|
-
const handleKeyDown = (0,
|
|
1114
|
+
}, [canSend, onSend, value, attachments, resetHeight]);
|
|
1115
|
+
const handleKeyDown = (0, import_react5.useCallback)(
|
|
858
1116
|
(e) => {
|
|
859
1117
|
if (e.key === "Enter" && !e.shiftKey) {
|
|
860
1118
|
e.preventDefault();
|
|
@@ -863,64 +1121,194 @@ function MessageComposer({
|
|
|
863
1121
|
},
|
|
864
1122
|
[handleSend]
|
|
865
1123
|
);
|
|
866
|
-
const handleChange = (0,
|
|
1124
|
+
const handleChange = (0, import_react5.useCallback)(
|
|
867
1125
|
(e) => {
|
|
868
1126
|
setValue(e.target.value);
|
|
869
1127
|
const el = e.target;
|
|
870
1128
|
el.style.height = "auto";
|
|
871
|
-
const capped = Math.min(el.scrollHeight,
|
|
1129
|
+
const capped = Math.min(el.scrollHeight, 200);
|
|
872
1130
|
el.style.height = `${capped}px`;
|
|
873
|
-
el.style.overflowY = el.scrollHeight >
|
|
1131
|
+
el.style.overflowY = el.scrollHeight > 200 ? "auto" : "hidden";
|
|
874
1132
|
},
|
|
875
1133
|
[]
|
|
876
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
|
+
);
|
|
877
1214
|
return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
|
|
878
1215
|
"div",
|
|
879
1216
|
{
|
|
880
1217
|
className: (0, import_tailwind_merge6.twMerge)(
|
|
881
|
-
"
|
|
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",
|
|
882
1223
|
className
|
|
883
1224
|
),
|
|
1225
|
+
onDragOver: handleDragOver,
|
|
1226
|
+
onDragLeave: handleDragLeave,
|
|
1227
|
+
onDrop: handleDrop,
|
|
884
1228
|
children: [
|
|
885
1229
|
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
|
|
886
|
-
"
|
|
1230
|
+
"input",
|
|
887
1231
|
{
|
|
888
|
-
ref:
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
className: (0, import_tailwind_merge6.twMerge)(
|
|
896
|
-
"flex-1 resize-none rounded-xl border border-border bg-surface/80",
|
|
897
|
-
"px-4 py-2.5 text-sm text-text-primary placeholder:text-text-muted",
|
|
898
|
-
"focus:border-transparent focus:ring-2 focus:ring-accent/40 focus:outline-none",
|
|
899
|
-
"disabled:opacity-50 disabled:cursor-not-allowed",
|
|
900
|
-
"overflow-hidden",
|
|
901
|
-
"transition-all duration-200"
|
|
902
|
-
),
|
|
903
|
-
style: { colorScheme: "dark" },
|
|
904
|
-
"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"
|
|
905
1239
|
}
|
|
906
1240
|
),
|
|
907
|
-
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
|
|
908
|
-
|
|
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,
|
|
909
1243
|
{
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
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
|
+
] })
|
|
924
1312
|
]
|
|
925
1313
|
}
|
|
926
1314
|
);
|
|
@@ -933,6 +1321,7 @@ function WelcomeScreen({
|
|
|
933
1321
|
title = "Welcome",
|
|
934
1322
|
message = "How can I help you today?",
|
|
935
1323
|
icon,
|
|
1324
|
+
iconClassName,
|
|
936
1325
|
suggestedQuestions = [],
|
|
937
1326
|
onQuestionSelect,
|
|
938
1327
|
className
|
|
@@ -945,12 +1334,15 @@ function WelcomeScreen({
|
|
|
945
1334
|
className
|
|
946
1335
|
),
|
|
947
1336
|
children: [
|
|
948
|
-
/* @__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)(
|
|
949
1338
|
"div",
|
|
950
1339
|
{
|
|
951
|
-
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
|
+
),
|
|
952
1344
|
"aria-hidden": "true",
|
|
953
|
-
children:
|
|
1345
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "text-2xl", children: "\u2726" })
|
|
954
1346
|
}
|
|
955
1347
|
),
|
|
956
1348
|
/* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "flex flex-col gap-2", children: [
|
|
@@ -960,7 +1352,7 @@ function WelcomeScreen({
|
|
|
960
1352
|
suggestedQuestions.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
|
|
961
1353
|
"div",
|
|
962
1354
|
{
|
|
963
|
-
className: "flex flex-wrap justify-center gap-2 max-w-
|
|
1355
|
+
className: "flex flex-wrap justify-center gap-2 max-w-xl",
|
|
964
1356
|
role: "group",
|
|
965
1357
|
"aria-label": "Suggested questions",
|
|
966
1358
|
children: suggestedQuestions.map((question) => /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
|
|
@@ -969,7 +1361,7 @@ function WelcomeScreen({
|
|
|
969
1361
|
type: "button",
|
|
970
1362
|
onClick: () => onQuestionSelect?.(question),
|
|
971
1363
|
className: (0, import_tailwind_merge7.twMerge)(
|
|
972
|
-
"px-
|
|
1364
|
+
"px-3.5 py-1.5 rounded-full text-[12px]",
|
|
973
1365
|
"border border-border bg-transparent text-text-secondary",
|
|
974
1366
|
"hover:bg-accent/10 hover:border-interactive hover:text-text-primary",
|
|
975
1367
|
"focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-accent",
|
|
@@ -987,25 +1379,26 @@ function WelcomeScreen({
|
|
|
987
1379
|
}
|
|
988
1380
|
|
|
989
1381
|
// src/streaming/StreamingMessage/StreamingMessage.tsx
|
|
990
|
-
var
|
|
1382
|
+
var import_react7 = require("react");
|
|
1383
|
+
var import_tailwind_merge8 = require("tailwind-merge");
|
|
991
1384
|
var import_core3 = require("@surf-kit/core");
|
|
992
1385
|
|
|
993
1386
|
// src/hooks/useCharacterDrain.ts
|
|
994
|
-
var
|
|
1387
|
+
var import_react6 = require("react");
|
|
995
1388
|
function useCharacterDrain(target, msPerChar = 15) {
|
|
996
|
-
const [displayed, setDisplayed] = (0,
|
|
997
|
-
const indexRef = (0,
|
|
998
|
-
const lastTimeRef = (0,
|
|
999
|
-
const rafRef = (0,
|
|
1000
|
-
const drainTargetRef = (0,
|
|
1001
|
-
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);
|
|
1002
1395
|
msPerCharRef.current = msPerChar;
|
|
1003
1396
|
if (target !== "") {
|
|
1004
1397
|
drainTargetRef.current = target;
|
|
1005
1398
|
}
|
|
1006
1399
|
const drainTarget = drainTargetRef.current;
|
|
1007
1400
|
const isDraining = displayed.length < drainTarget.length;
|
|
1008
|
-
const tickRef = (0,
|
|
1401
|
+
const tickRef = (0, import_react6.useRef)(() => {
|
|
1009
1402
|
});
|
|
1010
1403
|
tickRef.current = (now) => {
|
|
1011
1404
|
const currentTarget = drainTargetRef.current;
|
|
@@ -1017,7 +1410,10 @@ function useCharacterDrain(target, msPerChar = 15) {
|
|
|
1017
1410
|
const elapsed = now - lastTimeRef.current;
|
|
1018
1411
|
const charsToAdvance = Math.floor(elapsed / msPerCharRef.current);
|
|
1019
1412
|
if (charsToAdvance > 0 && indexRef.current < currentTarget.length) {
|
|
1020
|
-
|
|
1413
|
+
let nextIndex = Math.min(indexRef.current + charsToAdvance, currentTarget.length);
|
|
1414
|
+
while (nextIndex < currentTarget.length && currentTarget[nextIndex - 1].trim() === "") {
|
|
1415
|
+
nextIndex++;
|
|
1416
|
+
}
|
|
1021
1417
|
indexRef.current = nextIndex;
|
|
1022
1418
|
lastTimeRef.current = now;
|
|
1023
1419
|
setDisplayed(currentTarget.slice(0, nextIndex));
|
|
@@ -1028,12 +1424,12 @@ function useCharacterDrain(target, msPerChar = 15) {
|
|
|
1028
1424
|
rafRef.current = null;
|
|
1029
1425
|
}
|
|
1030
1426
|
};
|
|
1031
|
-
(0,
|
|
1427
|
+
(0, import_react6.useEffect)(() => {
|
|
1032
1428
|
if (drainTargetRef.current !== "" && indexRef.current < drainTargetRef.current.length && rafRef.current === null) {
|
|
1033
1429
|
rafRef.current = requestAnimationFrame((t) => tickRef.current(t));
|
|
1034
1430
|
}
|
|
1035
1431
|
}, [drainTarget]);
|
|
1036
|
-
(0,
|
|
1432
|
+
(0, import_react6.useEffect)(() => {
|
|
1037
1433
|
if (target === "" && !isDraining && displayed !== "") {
|
|
1038
1434
|
indexRef.current = 0;
|
|
1039
1435
|
lastTimeRef.current = 0;
|
|
@@ -1041,7 +1437,7 @@ function useCharacterDrain(target, msPerChar = 15) {
|
|
|
1041
1437
|
setDisplayed("");
|
|
1042
1438
|
}
|
|
1043
1439
|
}, [target, isDraining, displayed]);
|
|
1044
|
-
(0,
|
|
1440
|
+
(0, import_react6.useEffect)(() => {
|
|
1045
1441
|
return () => {
|
|
1046
1442
|
if (rafRef.current !== null) {
|
|
1047
1443
|
cancelAnimationFrame(rafRef.current);
|
|
@@ -1062,51 +1458,77 @@ var phaseLabels = {
|
|
|
1062
1458
|
generating: "Writing...",
|
|
1063
1459
|
verifying: "Verifying..."
|
|
1064
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
|
+
`;
|
|
1065
1479
|
function StreamingMessage({
|
|
1066
1480
|
stream,
|
|
1067
1481
|
onComplete,
|
|
1482
|
+
onDraining,
|
|
1068
1483
|
showPhases = true,
|
|
1069
1484
|
className
|
|
1070
1485
|
}) {
|
|
1071
|
-
const onCompleteRef = (0,
|
|
1486
|
+
const onCompleteRef = (0, import_react7.useRef)(onComplete);
|
|
1072
1487
|
onCompleteRef.current = onComplete;
|
|
1073
|
-
const
|
|
1074
|
-
|
|
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)(() => {
|
|
1075
1492
|
if (wasActiveRef.current && !stream.active) {
|
|
1076
1493
|
onCompleteRef.current?.();
|
|
1077
1494
|
}
|
|
1078
1495
|
wasActiveRef.current = stream.active;
|
|
1079
1496
|
}, [stream.active]);
|
|
1080
1497
|
const phaseLabel = phaseLabels[stream.phase];
|
|
1081
|
-
const { displayed:
|
|
1082
|
-
|
|
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: [
|
|
1083
1507
|
/* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { "aria-live": "assertive", className: "sr-only", children: [
|
|
1084
1508
|
stream.active && stream.phase !== "idle" && "Response started",
|
|
1085
1509
|
!stream.active && stream.content && "Response complete"
|
|
1086
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 }),
|
|
1087
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: [
|
|
1088
|
-
|
|
1514
|
+
showPhaseIndicator && /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
|
|
1089
1515
|
"div",
|
|
1090
1516
|
{
|
|
1091
|
-
className: "flex items-center gap-2
|
|
1517
|
+
className: "flex items-center gap-2 text-sm text-text-secondary",
|
|
1092
1518
|
"data-testid": "phase-indicator",
|
|
1093
1519
|
children: [
|
|
1094
|
-
/* @__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" }) }),
|
|
1095
1521
|
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { children: phaseLabel })
|
|
1096
1522
|
]
|
|
1097
1523
|
}
|
|
1098
1524
|
),
|
|
1099
|
-
/* @__PURE__ */ (0, import_jsx_runtime11.
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
"data-testid": "streaming-cursor"
|
|
1107
|
-
}
|
|
1108
|
-
)
|
|
1109
|
-
] })
|
|
1525
|
+
displayedContent && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
|
|
1526
|
+
ResponseMessage,
|
|
1527
|
+
{
|
|
1528
|
+
content: displayedContent,
|
|
1529
|
+
className: showCursor ? "sk-streaming-cursor" : void 0
|
|
1530
|
+
}
|
|
1531
|
+
)
|
|
1110
1532
|
] })
|
|
1111
1533
|
] });
|
|
1112
1534
|
}
|
|
@@ -1137,7 +1559,7 @@ function AgentChat({
|
|
|
1137
1559
|
return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
|
|
1138
1560
|
"div",
|
|
1139
1561
|
{
|
|
1140
|
-
className: (0,
|
|
1562
|
+
className: (0, import_tailwind_merge9.twMerge)(
|
|
1141
1563
|
"flex flex-col h-full bg-canvas border border-border rounded-xl overflow-hidden",
|
|
1142
1564
|
className
|
|
1143
1565
|
),
|
|
@@ -1180,7 +1602,7 @@ function AgentChat({
|
|
|
1180
1602
|
}
|
|
1181
1603
|
|
|
1182
1604
|
// src/chat/ConversationList/ConversationList.tsx
|
|
1183
|
-
var
|
|
1605
|
+
var import_tailwind_merge10 = require("tailwind-merge");
|
|
1184
1606
|
var import_jsx_runtime13 = require("react/jsx-runtime");
|
|
1185
1607
|
function ConversationList({
|
|
1186
1608
|
conversations,
|
|
@@ -1194,7 +1616,7 @@ function ConversationList({
|
|
|
1194
1616
|
"nav",
|
|
1195
1617
|
{
|
|
1196
1618
|
"aria-label": "Conversation list",
|
|
1197
|
-
className: (0,
|
|
1619
|
+
className: (0, import_tailwind_merge10.twMerge)("flex flex-col h-full bg-canvas", className),
|
|
1198
1620
|
children: [
|
|
1199
1621
|
onNew && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { className: "p-3 border-b border-border", children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
|
|
1200
1622
|
"button",
|
|
@@ -1211,7 +1633,7 @@ function ConversationList({
|
|
|
1211
1633
|
return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
|
|
1212
1634
|
"li",
|
|
1213
1635
|
{
|
|
1214
|
-
className: (0,
|
|
1636
|
+
className: (0, import_tailwind_merge10.twMerge)(
|
|
1215
1637
|
"flex items-start border-b border-border transition-colors duration-200",
|
|
1216
1638
|
"hover:bg-surface",
|
|
1217
1639
|
isActive && "bg-surface-raised border-l-2 border-l-accent"
|