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