@surf-kit/agent 0.2.2 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/chat/index.cjs +625 -204
- package/dist/chat/index.cjs.map +1 -1
- package/dist/chat/index.d.cts +11 -6
- package/dist/chat/index.d.ts +11 -6
- package/dist/chat/index.js +606 -185
- package/dist/chat/index.js.map +1 -1
- package/dist/{chat--OifhIRe.d.ts → chat-BIIDOGrD.d.ts} +10 -1
- package/dist/{chat-ChYl2XjV.d.cts → chat-CGamM7Mz.d.cts} +10 -1
- package/dist/{hooks-DLfF18IU.d.cts → hooks-B1NYoLLs.d.cts} +21 -5
- package/dist/{hooks-BGs8-4GK.d.ts → hooks-CTeEqnBQ.d.ts} +21 -5
- package/dist/hooks.cjs +126 -81
- package/dist/hooks.cjs.map +1 -1
- package/dist/hooks.d.cts +3 -3
- package/dist/hooks.d.ts +3 -3
- package/dist/hooks.js +126 -81
- package/dist/hooks.js.map +1 -1
- package/dist/index.cjs +686 -265
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +3 -3
- package/dist/index.d.ts +3 -3
- package/dist/index.js +645 -224
- package/dist/index.js.map +1 -1
- package/dist/layouts/index.cjs +646 -225
- package/dist/layouts/index.cjs.map +1 -1
- package/dist/layouts/index.d.cts +1 -1
- package/dist/layouts/index.d.ts +1 -1
- package/dist/layouts/index.js +622 -201
- package/dist/layouts/index.js.map +1 -1
- package/dist/mcp/index.cjs +1 -1
- package/dist/mcp/index.cjs.map +1 -1
- package/dist/mcp/index.js +2 -2
- package/dist/mcp/index.js.map +1 -1
- package/dist/response/index.cjs +66 -12
- package/dist/response/index.cjs.map +1 -1
- package/dist/response/index.d.cts +2 -2
- package/dist/response/index.d.ts +2 -2
- package/dist/response/index.js +64 -10
- package/dist/response/index.js.map +1 -1
- package/dist/sources/index.cjs +30 -1
- package/dist/sources/index.cjs.map +1 -1
- package/dist/sources/index.js +30 -1
- package/dist/sources/index.js.map +1 -1
- package/dist/streaming/index.cjs +202 -93
- package/dist/streaming/index.cjs.map +1 -1
- package/dist/streaming/index.d.cts +4 -3
- package/dist/streaming/index.d.ts +4 -3
- package/dist/streaming/index.js +172 -73
- package/dist/streaming/index.js.map +1 -1
- package/dist/{streaming-DbQxScpi.d.ts → streaming-Bx-ff2tt.d.ts} +1 -1
- package/dist/{streaming-DfT22A0z.d.cts → streaming-x7umFHoP.d.cts} +1 -1
- package/package.json +15 -4
package/dist/layouts/index.cjs
CHANGED
|
@@ -39,11 +39,11 @@ __export(layouts_exports, {
|
|
|
39
39
|
module.exports = __toCommonJS(layouts_exports);
|
|
40
40
|
|
|
41
41
|
// src/layouts/AgentFullPage/AgentFullPage.tsx
|
|
42
|
-
var
|
|
43
|
-
var
|
|
42
|
+
var import_tailwind_merge11 = require("tailwind-merge");
|
|
43
|
+
var import_react8 = require("react");
|
|
44
44
|
|
|
45
45
|
// src/chat/AgentChat/AgentChat.tsx
|
|
46
|
-
var
|
|
46
|
+
var import_tailwind_merge9 = require("tailwind-merge");
|
|
47
47
|
|
|
48
48
|
// src/hooks/useAgentChat.ts
|
|
49
49
|
var import_react = require("react");
|
|
@@ -54,7 +54,8 @@ var initialState = {
|
|
|
54
54
|
error: null,
|
|
55
55
|
inputValue: "",
|
|
56
56
|
streamPhase: "idle",
|
|
57
|
-
streamingContent: ""
|
|
57
|
+
streamingContent: "",
|
|
58
|
+
streamingAgent: null
|
|
58
59
|
};
|
|
59
60
|
function reducer(state, action) {
|
|
60
61
|
switch (action.type) {
|
|
@@ -68,12 +69,15 @@ function reducer(state, action) {
|
|
|
68
69
|
error: null,
|
|
69
70
|
inputValue: "",
|
|
70
71
|
streamPhase: "thinking",
|
|
71
|
-
streamingContent: ""
|
|
72
|
+
streamingContent: "",
|
|
73
|
+
streamingAgent: null
|
|
72
74
|
};
|
|
73
75
|
case "STREAM_PHASE":
|
|
74
76
|
return { ...state, streamPhase: action.phase };
|
|
75
77
|
case "STREAM_CONTENT":
|
|
76
78
|
return { ...state, streamingContent: state.streamingContent + action.content };
|
|
79
|
+
case "STREAM_AGENT":
|
|
80
|
+
return { ...state, streamingAgent: action.agent };
|
|
77
81
|
case "SEND_SUCCESS":
|
|
78
82
|
return {
|
|
79
83
|
...state,
|
|
@@ -89,7 +93,8 @@ function reducer(state, action) {
|
|
|
89
93
|
isLoading: false,
|
|
90
94
|
error: action.error,
|
|
91
95
|
streamPhase: "idle",
|
|
92
|
-
streamingContent: ""
|
|
96
|
+
streamingContent: "",
|
|
97
|
+
streamingAgent: null
|
|
93
98
|
};
|
|
94
99
|
case "LOAD_CONVERSATION":
|
|
95
100
|
return {
|
|
@@ -115,107 +120,142 @@ function useAgentChat(config) {
|
|
|
115
120
|
const configRef = (0, import_react.useRef)(config);
|
|
116
121
|
configRef.current = config;
|
|
117
122
|
const lastUserMessageRef = (0, import_react.useRef)(null);
|
|
123
|
+
const lastUserAttachmentsRef = (0, import_react.useRef)(void 0);
|
|
118
124
|
const sendMessage = (0, import_react.useCallback)(
|
|
119
|
-
async (content) => {
|
|
120
|
-
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 ?? {};
|
|
121
128
|
lastUserMessageRef.current = content;
|
|
129
|
+
lastUserAttachmentsRef.current = attachments;
|
|
122
130
|
const userMessage = {
|
|
123
131
|
id: generateMessageId(),
|
|
124
132
|
role: "user",
|
|
125
133
|
content,
|
|
134
|
+
attachments,
|
|
126
135
|
timestamp: /* @__PURE__ */ new Date()
|
|
127
136
|
};
|
|
128
137
|
dispatch({ type: "SEND_START", message: userMessage });
|
|
129
138
|
const controller = new AbortController();
|
|
130
139
|
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
131
140
|
try {
|
|
132
|
-
const
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
error: {
|
|
150
|
-
code: "API_ERROR",
|
|
151
|
-
message: `HTTP ${response.status}: ${response.statusText}`,
|
|
152
|
-
retryable: response.status >= 500
|
|
153
|
-
}
|
|
154
|
-
});
|
|
155
|
-
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
|
+
}));
|
|
156
158
|
}
|
|
157
|
-
const
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
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
|
|
162
204
|
});
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
const
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
dispatch({ type: "SEND_ERROR", error: event.error });
|
|
200
|
-
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 {
|
|
201
241
|
}
|
|
202
|
-
} catch {
|
|
203
242
|
}
|
|
204
243
|
}
|
|
205
244
|
}
|
|
245
|
+
if (ctx.hadStreamError) return;
|
|
206
246
|
const assistantMessage = {
|
|
207
247
|
id: generateMessageId(),
|
|
208
248
|
role: "assistant",
|
|
209
|
-
content: agentResponse?.message ?? accumulatedContent,
|
|
210
|
-
response: agentResponse ?? void 0,
|
|
211
|
-
agent: capturedAgent ?? void 0,
|
|
249
|
+
content: ctx.agentResponse?.message ?? ctx.accumulatedContent,
|
|
250
|
+
response: ctx.agentResponse ?? void 0,
|
|
251
|
+
agent: ctx.capturedAgent ?? void 0,
|
|
212
252
|
timestamp: /* @__PURE__ */ new Date()
|
|
213
253
|
};
|
|
214
254
|
dispatch({
|
|
215
255
|
type: "SEND_SUCCESS",
|
|
216
256
|
message: assistantMessage,
|
|
217
|
-
streamingContent: accumulatedContent,
|
|
218
|
-
conversationId: capturedConversationId
|
|
257
|
+
streamingContent: ctx.accumulatedContent,
|
|
258
|
+
conversationId: ctx.capturedConversationId
|
|
219
259
|
});
|
|
220
260
|
} catch (err) {
|
|
221
261
|
clearTimeout(timeoutId);
|
|
@@ -246,7 +286,8 @@ function useAgentChat(config) {
|
|
|
246
286
|
}, []);
|
|
247
287
|
const submitFeedback = (0, import_react.useCallback)(
|
|
248
288
|
async (messageId, rating, comment) => {
|
|
249
|
-
const { apiUrl, feedbackPath = "/feedback", headers
|
|
289
|
+
const { apiUrl, feedbackPath = "/feedback", headers: headersOrFn } = configRef.current;
|
|
290
|
+
const headers = typeof headersOrFn === "function" ? await headersOrFn() : headersOrFn ?? {};
|
|
250
291
|
await fetch(`${apiUrl}${feedbackPath}`, {
|
|
251
292
|
method: "POST",
|
|
252
293
|
headers: { "Content-Type": "application/json", ...headers },
|
|
@@ -257,12 +298,13 @@ function useAgentChat(config) {
|
|
|
257
298
|
);
|
|
258
299
|
const retry = (0, import_react.useCallback)(async () => {
|
|
259
300
|
if (lastUserMessageRef.current) {
|
|
260
|
-
await sendMessage(lastUserMessageRef.current);
|
|
301
|
+
await sendMessage(lastUserMessageRef.current, lastUserAttachmentsRef.current);
|
|
261
302
|
}
|
|
262
303
|
}, [sendMessage]);
|
|
263
304
|
const reset = (0, import_react.useCallback)(() => {
|
|
264
305
|
dispatch({ type: "RESET" });
|
|
265
306
|
lastUserMessageRef.current = null;
|
|
307
|
+
lastUserAttachmentsRef.current = void 0;
|
|
266
308
|
}, []);
|
|
267
309
|
const actions = {
|
|
268
310
|
sendMessage,
|
|
@@ -277,7 +319,7 @@ function useAgentChat(config) {
|
|
|
277
319
|
|
|
278
320
|
// src/chat/MessageThread/MessageThread.tsx
|
|
279
321
|
var import_tailwind_merge5 = require("tailwind-merge");
|
|
280
|
-
var
|
|
322
|
+
var import_react4 = require("react");
|
|
281
323
|
|
|
282
324
|
// src/chat/MessageBubble/MessageBubble.tsx
|
|
283
325
|
var import_tailwind_merge4 = require("tailwind-merge");
|
|
@@ -286,6 +328,7 @@ var import_tailwind_merge4 = require("tailwind-merge");
|
|
|
286
328
|
var import_core2 = require("@surf-kit/core");
|
|
287
329
|
|
|
288
330
|
// src/response/ResponseMessage/ResponseMessage.tsx
|
|
331
|
+
var import_react2 = __toESM(require("react"), 1);
|
|
289
332
|
var import_react_markdown = __toESM(require("react-markdown"), 1);
|
|
290
333
|
var import_rehype_sanitize = __toESM(require("rehype-sanitize"), 1);
|
|
291
334
|
var import_tailwind_merge = require("tailwind-merge");
|
|
@@ -310,6 +353,7 @@ function ResponseMessage({ content, className }) {
|
|
|
310
353
|
"[&_h3]:text-sm [&_h3]:font-semibold [&_h3]:text-accent [&_h3]:mt-2 [&_h3]:mb-1",
|
|
311
354
|
"[&_code]:bg-surface-raised [&_code]:text-accent [&_code]:px-1.5 [&_code]:py-0.5 [&_code]:rounded [&_code]:text-xs [&_code]:font-mono",
|
|
312
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",
|
|
313
357
|
"[&_blockquote]:border-l-2 [&_blockquote]:border-border-strong [&_blockquote]:pl-4 [&_blockquote]:text-text-secondary",
|
|
314
358
|
"[&_a]:text-accent [&_a]:underline-offset-2 [&_a]:hover:text-accent/80",
|
|
315
359
|
className
|
|
@@ -325,11 +369,24 @@ function ResponseMessage({ content, className }) {
|
|
|
325
369
|
p: ({ children }) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "my-2", children }),
|
|
326
370
|
ul: ({ children }) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("ul", { className: "my-2 list-disc pl-6", children }),
|
|
327
371
|
ol: ({ children }) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("ol", { className: "my-2 list-decimal pl-6", children }),
|
|
328
|
-
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
|
+
},
|
|
329
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 }),
|
|
330
386
|
h1: ({ children }) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h1", { className: "text-base font-bold mt-4 mb-2", children }),
|
|
331
387
|
h2: ({ children }) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h2", { className: "text-sm font-bold mt-3 mb-1", children }),
|
|
332
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" }),
|
|
333
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 })
|
|
334
391
|
},
|
|
335
392
|
children: normalizeMarkdownLists(content)
|
|
@@ -465,7 +522,14 @@ function renderWarning(data) {
|
|
|
465
522
|
}
|
|
466
523
|
);
|
|
467
524
|
}
|
|
468
|
-
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;
|
|
469
533
|
if (!data) return null;
|
|
470
534
|
let content;
|
|
471
535
|
switch (uiHint) {
|
|
@@ -495,7 +559,7 @@ function StructuredResponse({ uiHint, data, className }) {
|
|
|
495
559
|
}
|
|
496
560
|
|
|
497
561
|
// src/sources/SourceList/SourceList.tsx
|
|
498
|
-
var
|
|
562
|
+
var import_react3 = require("react");
|
|
499
563
|
|
|
500
564
|
// src/sources/SourceCard/SourceCard.tsx
|
|
501
565
|
var import_core = require("@surf-kit/core");
|
|
@@ -545,7 +609,36 @@ function SourceCard({ source, variant = "compact", onNavigate, className }) {
|
|
|
545
609
|
children: [
|
|
546
610
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex items-start justify-between gap-2", children: [
|
|
547
611
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex-1 min-w-0", children: [
|
|
548
|
-
/* @__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 }),
|
|
549
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 })
|
|
550
643
|
] }),
|
|
551
644
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
@@ -575,7 +668,7 @@ function SourceList({
|
|
|
575
668
|
onNavigate,
|
|
576
669
|
className
|
|
577
670
|
}) {
|
|
578
|
-
const [isExpanded, setIsExpanded] = (0,
|
|
671
|
+
const [isExpanded, setIsExpanded] = (0, import_react3.useState)(defaultExpanded);
|
|
579
672
|
if (sources.length === 0) return null;
|
|
580
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)(
|
|
581
674
|
SourceCard,
|
|
@@ -679,13 +772,16 @@ function AgentResponse({
|
|
|
679
772
|
}) {
|
|
680
773
|
return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: `flex flex-col gap-4 ${className ?? ""}`, "data-testid": "agent-response", children: [
|
|
681
774
|
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(ResponseMessage, { content: response.message }),
|
|
682
|
-
response.ui_hint !== "text" && response.structured_data &&
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
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
|
+
})(),
|
|
689
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: [
|
|
690
786
|
showConfidence && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
|
|
691
787
|
import_core2.Badge,
|
|
@@ -736,6 +832,31 @@ function AgentResponse({
|
|
|
736
832
|
|
|
737
833
|
// src/chat/MessageBubble/MessageBubble.tsx
|
|
738
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
|
+
}
|
|
739
860
|
function MessageBubble({
|
|
740
861
|
message,
|
|
741
862
|
showAgent,
|
|
@@ -743,23 +864,29 @@ function MessageBubble({
|
|
|
743
864
|
showConfidence = true,
|
|
744
865
|
showVerification = true,
|
|
745
866
|
animated = true,
|
|
867
|
+
userBubbleClassName,
|
|
746
868
|
className
|
|
747
869
|
}) {
|
|
748
870
|
const isUser = message.role === "user";
|
|
871
|
+
const hasAttachments = message.attachments && message.attachments.length > 0;
|
|
749
872
|
if (isUser) {
|
|
750
873
|
return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
751
874
|
"div",
|
|
752
875
|
{
|
|
753
876
|
"data-message-id": message.id,
|
|
754
877
|
className: (0, import_tailwind_merge4.twMerge)("flex w-full justify-end", className),
|
|
755
|
-
children: /* @__PURE__ */ (0, import_jsx_runtime7.
|
|
878
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
|
|
756
879
|
"div",
|
|
757
880
|
{
|
|
758
881
|
className: (0, import_tailwind_merge4.twMerge)(
|
|
759
|
-
"max-w-[70%] rounded-[18px] rounded-br-[4px] px-4 py-2.5 bg-
|
|
760
|
-
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
|
|
761
885
|
),
|
|
762
|
-
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
|
+
]
|
|
763
890
|
}
|
|
764
891
|
)
|
|
765
892
|
}
|
|
@@ -771,7 +898,7 @@ function MessageBubble({
|
|
|
771
898
|
"data-message-id": message.id,
|
|
772
899
|
className: (0, import_tailwind_merge4.twMerge)("flex w-full flex-col items-start gap-1.5", className),
|
|
773
900
|
children: [
|
|
774
|
-
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("_", " ") }),
|
|
775
902
|
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
776
903
|
"div",
|
|
777
904
|
{
|
|
@@ -797,34 +924,70 @@ function MessageBubble({
|
|
|
797
924
|
|
|
798
925
|
// src/chat/MessageThread/MessageThread.tsx
|
|
799
926
|
var import_jsx_runtime8 = require("react/jsx-runtime");
|
|
800
|
-
function MessageThread({ messages, streamingSlot, showSources, showConfidence, showVerification, className }) {
|
|
801
|
-
const
|
|
802
|
-
(0,
|
|
803
|
-
|
|
804
|
-
|
|
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]);
|
|
805
959
|
return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
|
|
806
960
|
"div",
|
|
807
961
|
{
|
|
962
|
+
ref: scrollRef,
|
|
808
963
|
role: "log",
|
|
809
964
|
"aria-live": "polite",
|
|
810
965
|
"aria-label": "Message thread",
|
|
966
|
+
onScroll: handleScroll,
|
|
811
967
|
className: (0, import_tailwind_merge5.twMerge)(
|
|
812
968
|
"flex flex-col gap-4 overflow-y-auto flex-1 px-4 py-6",
|
|
813
969
|
className
|
|
814
970
|
),
|
|
815
971
|
children: [
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
{
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
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
|
|
828
991
|
]
|
|
829
992
|
}
|
|
830
993
|
);
|
|
@@ -832,32 +995,126 @@ function MessageThread({ messages, streamingSlot, showSources, showConfidence, s
|
|
|
832
995
|
|
|
833
996
|
// src/chat/MessageComposer/MessageComposer.tsx
|
|
834
997
|
var import_tailwind_merge6 = require("tailwind-merge");
|
|
835
|
-
var
|
|
998
|
+
var import_react5 = require("react");
|
|
836
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
|
+
}
|
|
837
1088
|
function MessageComposer({
|
|
838
1089
|
onSend,
|
|
839
1090
|
isLoading = false,
|
|
840
1091
|
placeholder = "Type a message...",
|
|
841
1092
|
className
|
|
842
1093
|
}) {
|
|
843
|
-
const [value, setValue] = (0,
|
|
844
|
-
const
|
|
845
|
-
const
|
|
846
|
-
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)(() => {
|
|
847
1101
|
const el = textareaRef.current;
|
|
848
1102
|
if (el) {
|
|
849
1103
|
el.style.height = "auto";
|
|
850
1104
|
el.style.overflowY = "hidden";
|
|
851
1105
|
}
|
|
852
1106
|
}, []);
|
|
853
|
-
const handleSend = (0,
|
|
1107
|
+
const handleSend = (0, import_react5.useCallback)(() => {
|
|
854
1108
|
if (!canSend) return;
|
|
855
|
-
|
|
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);
|
|
856
1112
|
setValue("");
|
|
1113
|
+
setAttachments([]);
|
|
857
1114
|
resetHeight();
|
|
858
1115
|
textareaRef.current?.focus();
|
|
859
|
-
}, [canSend, onSend, value, resetHeight]);
|
|
860
|
-
const handleKeyDown = (0,
|
|
1116
|
+
}, [canSend, onSend, value, attachments, resetHeight]);
|
|
1117
|
+
const handleKeyDown = (0, import_react5.useCallback)(
|
|
861
1118
|
(e) => {
|
|
862
1119
|
if (e.key === "Enter" && !e.shiftKey) {
|
|
863
1120
|
e.preventDefault();
|
|
@@ -866,64 +1123,194 @@ function MessageComposer({
|
|
|
866
1123
|
},
|
|
867
1124
|
[handleSend]
|
|
868
1125
|
);
|
|
869
|
-
const handleChange = (0,
|
|
1126
|
+
const handleChange = (0, import_react5.useCallback)(
|
|
870
1127
|
(e) => {
|
|
871
1128
|
setValue(e.target.value);
|
|
872
1129
|
const el = e.target;
|
|
873
1130
|
el.style.height = "auto";
|
|
874
|
-
const capped = Math.min(el.scrollHeight,
|
|
1131
|
+
const capped = Math.min(el.scrollHeight, 200);
|
|
875
1132
|
el.style.height = `${capped}px`;
|
|
876
|
-
el.style.overflowY = el.scrollHeight >
|
|
1133
|
+
el.style.overflowY = el.scrollHeight > 200 ? "auto" : "hidden";
|
|
877
1134
|
},
|
|
878
1135
|
[]
|
|
879
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
|
+
);
|
|
880
1216
|
return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
|
|
881
1217
|
"div",
|
|
882
1218
|
{
|
|
883
1219
|
className: (0, import_tailwind_merge6.twMerge)(
|
|
884
|
-
"
|
|
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",
|
|
885
1225
|
className
|
|
886
1226
|
),
|
|
1227
|
+
onDragOver: handleDragOver,
|
|
1228
|
+
onDragLeave: handleDragLeave,
|
|
1229
|
+
onDrop: handleDrop,
|
|
887
1230
|
children: [
|
|
888
1231
|
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
|
|
889
|
-
"
|
|
1232
|
+
"input",
|
|
890
1233
|
{
|
|
891
|
-
ref:
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
className: (0, import_tailwind_merge6.twMerge)(
|
|
899
|
-
"flex-1 resize-none rounded-xl border border-border bg-surface/80",
|
|
900
|
-
"px-4 py-2.5 text-sm text-text-primary placeholder:text-text-muted",
|
|
901
|
-
"focus:border-transparent focus:ring-2 focus:ring-accent/40 focus:outline-none",
|
|
902
|
-
"disabled:opacity-50 disabled:cursor-not-allowed",
|
|
903
|
-
"overflow-hidden",
|
|
904
|
-
"transition-all duration-200"
|
|
905
|
-
),
|
|
906
|
-
style: { colorScheme: "dark" },
|
|
907
|
-
"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"
|
|
908
1241
|
}
|
|
909
1242
|
),
|
|
910
|
-
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
|
|
911
|
-
|
|
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,
|
|
912
1245
|
{
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
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
|
+
] })
|
|
927
1314
|
]
|
|
928
1315
|
}
|
|
929
1316
|
);
|
|
@@ -936,6 +1323,7 @@ function WelcomeScreen({
|
|
|
936
1323
|
title = "Welcome",
|
|
937
1324
|
message = "How can I help you today?",
|
|
938
1325
|
icon,
|
|
1326
|
+
iconClassName,
|
|
939
1327
|
suggestedQuestions = [],
|
|
940
1328
|
onQuestionSelect,
|
|
941
1329
|
className
|
|
@@ -948,12 +1336,15 @@ function WelcomeScreen({
|
|
|
948
1336
|
className
|
|
949
1337
|
),
|
|
950
1338
|
children: [
|
|
951
|
-
/* @__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)(
|
|
952
1340
|
"div",
|
|
953
1341
|
{
|
|
954
|
-
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
|
+
),
|
|
955
1346
|
"aria-hidden": "true",
|
|
956
|
-
children:
|
|
1347
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "text-2xl", children: "\u2726" })
|
|
957
1348
|
}
|
|
958
1349
|
),
|
|
959
1350
|
/* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "flex flex-col gap-2", children: [
|
|
@@ -963,7 +1354,7 @@ function WelcomeScreen({
|
|
|
963
1354
|
suggestedQuestions.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
|
|
964
1355
|
"div",
|
|
965
1356
|
{
|
|
966
|
-
className: "flex flex-wrap justify-center gap-2 max-w-
|
|
1357
|
+
className: "flex flex-wrap justify-center gap-2 max-w-xl",
|
|
967
1358
|
role: "group",
|
|
968
1359
|
"aria-label": "Suggested questions",
|
|
969
1360
|
children: suggestedQuestions.map((question) => /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
|
|
@@ -972,7 +1363,7 @@ function WelcomeScreen({
|
|
|
972
1363
|
type: "button",
|
|
973
1364
|
onClick: () => onQuestionSelect?.(question),
|
|
974
1365
|
className: (0, import_tailwind_merge7.twMerge)(
|
|
975
|
-
"px-
|
|
1366
|
+
"px-3.5 py-1.5 rounded-full text-[12px]",
|
|
976
1367
|
"border border-border bg-transparent text-text-secondary",
|
|
977
1368
|
"hover:bg-accent/10 hover:border-interactive hover:text-text-primary",
|
|
978
1369
|
"focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-accent",
|
|
@@ -990,25 +1381,26 @@ function WelcomeScreen({
|
|
|
990
1381
|
}
|
|
991
1382
|
|
|
992
1383
|
// src/streaming/StreamingMessage/StreamingMessage.tsx
|
|
993
|
-
var
|
|
1384
|
+
var import_react7 = require("react");
|
|
1385
|
+
var import_tailwind_merge8 = require("tailwind-merge");
|
|
994
1386
|
var import_core3 = require("@surf-kit/core");
|
|
995
1387
|
|
|
996
1388
|
// src/hooks/useCharacterDrain.ts
|
|
997
|
-
var
|
|
1389
|
+
var import_react6 = require("react");
|
|
998
1390
|
function useCharacterDrain(target, msPerChar = 15) {
|
|
999
|
-
const [displayed, setDisplayed] = (0,
|
|
1000
|
-
const indexRef = (0,
|
|
1001
|
-
const lastTimeRef = (0,
|
|
1002
|
-
const rafRef = (0,
|
|
1003
|
-
const drainTargetRef = (0,
|
|
1004
|
-
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);
|
|
1005
1397
|
msPerCharRef.current = msPerChar;
|
|
1006
1398
|
if (target !== "") {
|
|
1007
1399
|
drainTargetRef.current = target;
|
|
1008
1400
|
}
|
|
1009
1401
|
const drainTarget = drainTargetRef.current;
|
|
1010
1402
|
const isDraining = displayed.length < drainTarget.length;
|
|
1011
|
-
const tickRef = (0,
|
|
1403
|
+
const tickRef = (0, import_react6.useRef)(() => {
|
|
1012
1404
|
});
|
|
1013
1405
|
tickRef.current = (now) => {
|
|
1014
1406
|
const currentTarget = drainTargetRef.current;
|
|
@@ -1020,7 +1412,10 @@ function useCharacterDrain(target, msPerChar = 15) {
|
|
|
1020
1412
|
const elapsed = now - lastTimeRef.current;
|
|
1021
1413
|
const charsToAdvance = Math.floor(elapsed / msPerCharRef.current);
|
|
1022
1414
|
if (charsToAdvance > 0 && indexRef.current < currentTarget.length) {
|
|
1023
|
-
|
|
1415
|
+
let nextIndex = Math.min(indexRef.current + charsToAdvance, currentTarget.length);
|
|
1416
|
+
while (nextIndex < currentTarget.length && currentTarget[nextIndex - 1].trim() === "") {
|
|
1417
|
+
nextIndex++;
|
|
1418
|
+
}
|
|
1024
1419
|
indexRef.current = nextIndex;
|
|
1025
1420
|
lastTimeRef.current = now;
|
|
1026
1421
|
setDisplayed(currentTarget.slice(0, nextIndex));
|
|
@@ -1031,12 +1426,12 @@ function useCharacterDrain(target, msPerChar = 15) {
|
|
|
1031
1426
|
rafRef.current = null;
|
|
1032
1427
|
}
|
|
1033
1428
|
};
|
|
1034
|
-
(0,
|
|
1429
|
+
(0, import_react6.useEffect)(() => {
|
|
1035
1430
|
if (drainTargetRef.current !== "" && indexRef.current < drainTargetRef.current.length && rafRef.current === null) {
|
|
1036
1431
|
rafRef.current = requestAnimationFrame((t) => tickRef.current(t));
|
|
1037
1432
|
}
|
|
1038
1433
|
}, [drainTarget]);
|
|
1039
|
-
(0,
|
|
1434
|
+
(0, import_react6.useEffect)(() => {
|
|
1040
1435
|
if (target === "" && !isDraining && displayed !== "") {
|
|
1041
1436
|
indexRef.current = 0;
|
|
1042
1437
|
lastTimeRef.current = 0;
|
|
@@ -1044,7 +1439,7 @@ function useCharacterDrain(target, msPerChar = 15) {
|
|
|
1044
1439
|
setDisplayed("");
|
|
1045
1440
|
}
|
|
1046
1441
|
}, [target, isDraining, displayed]);
|
|
1047
|
-
(0,
|
|
1442
|
+
(0, import_react6.useEffect)(() => {
|
|
1048
1443
|
return () => {
|
|
1049
1444
|
if (rafRef.current !== null) {
|
|
1050
1445
|
cancelAnimationFrame(rafRef.current);
|
|
@@ -1065,51 +1460,77 @@ var phaseLabels = {
|
|
|
1065
1460
|
generating: "Writing...",
|
|
1066
1461
|
verifying: "Verifying..."
|
|
1067
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
|
+
`;
|
|
1068
1481
|
function StreamingMessage({
|
|
1069
1482
|
stream,
|
|
1070
1483
|
onComplete,
|
|
1484
|
+
onDraining,
|
|
1071
1485
|
showPhases = true,
|
|
1072
1486
|
className
|
|
1073
1487
|
}) {
|
|
1074
|
-
const onCompleteRef = (0,
|
|
1488
|
+
const onCompleteRef = (0, import_react7.useRef)(onComplete);
|
|
1075
1489
|
onCompleteRef.current = onComplete;
|
|
1076
|
-
const
|
|
1077
|
-
|
|
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)(() => {
|
|
1078
1494
|
if (wasActiveRef.current && !stream.active) {
|
|
1079
1495
|
onCompleteRef.current?.();
|
|
1080
1496
|
}
|
|
1081
1497
|
wasActiveRef.current = stream.active;
|
|
1082
1498
|
}, [stream.active]);
|
|
1083
1499
|
const phaseLabel = phaseLabels[stream.phase];
|
|
1084
|
-
const { displayed:
|
|
1085
|
-
|
|
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: [
|
|
1086
1509
|
/* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { "aria-live": "assertive", className: "sr-only", children: [
|
|
1087
1510
|
stream.active && stream.phase !== "idle" && "Response started",
|
|
1088
1511
|
!stream.active && stream.content && "Response complete"
|
|
1089
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 }),
|
|
1090
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: [
|
|
1091
|
-
|
|
1516
|
+
showPhaseIndicator && /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
|
|
1092
1517
|
"div",
|
|
1093
1518
|
{
|
|
1094
|
-
className: "flex items-center gap-2
|
|
1519
|
+
className: "flex items-center gap-2 text-sm text-text-secondary",
|
|
1095
1520
|
"data-testid": "phase-indicator",
|
|
1096
1521
|
children: [
|
|
1097
|
-
/* @__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" }) }),
|
|
1098
1523
|
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { children: phaseLabel })
|
|
1099
1524
|
]
|
|
1100
1525
|
}
|
|
1101
1526
|
),
|
|
1102
|
-
/* @__PURE__ */ (0, import_jsx_runtime11.
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
"data-testid": "streaming-cursor"
|
|
1110
|
-
}
|
|
1111
|
-
)
|
|
1112
|
-
] })
|
|
1527
|
+
displayedContent && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
|
|
1528
|
+
ResponseMessage,
|
|
1529
|
+
{
|
|
1530
|
+
content: displayedContent,
|
|
1531
|
+
className: showCursor ? "sk-streaming-cursor" : void 0
|
|
1532
|
+
}
|
|
1533
|
+
)
|
|
1113
1534
|
] })
|
|
1114
1535
|
] });
|
|
1115
1536
|
}
|
|
@@ -1140,7 +1561,7 @@ function AgentChat({
|
|
|
1140
1561
|
return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
|
|
1141
1562
|
"div",
|
|
1142
1563
|
{
|
|
1143
|
-
className: (0,
|
|
1564
|
+
className: (0, import_tailwind_merge9.twMerge)(
|
|
1144
1565
|
"flex flex-col h-full bg-canvas border border-border rounded-xl overflow-hidden",
|
|
1145
1566
|
className
|
|
1146
1567
|
),
|
|
@@ -1183,7 +1604,7 @@ function AgentChat({
|
|
|
1183
1604
|
}
|
|
1184
1605
|
|
|
1185
1606
|
// src/chat/ConversationList/ConversationList.tsx
|
|
1186
|
-
var
|
|
1607
|
+
var import_tailwind_merge10 = require("tailwind-merge");
|
|
1187
1608
|
var import_jsx_runtime13 = require("react/jsx-runtime");
|
|
1188
1609
|
function ConversationList({
|
|
1189
1610
|
conversations,
|
|
@@ -1197,7 +1618,7 @@ function ConversationList({
|
|
|
1197
1618
|
"nav",
|
|
1198
1619
|
{
|
|
1199
1620
|
"aria-label": "Conversation list",
|
|
1200
|
-
className: (0,
|
|
1621
|
+
className: (0, import_tailwind_merge10.twMerge)("flex flex-col h-full bg-canvas", className),
|
|
1201
1622
|
children: [
|
|
1202
1623
|
onNew && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { className: "p-3 border-b border-border", children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
|
|
1203
1624
|
"button",
|
|
@@ -1214,7 +1635,7 @@ function ConversationList({
|
|
|
1214
1635
|
return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
|
|
1215
1636
|
"li",
|
|
1216
1637
|
{
|
|
1217
|
-
className: (0,
|
|
1638
|
+
className: (0, import_tailwind_merge10.twMerge)(
|
|
1218
1639
|
"flex items-start border-b border-border transition-colors duration-200",
|
|
1219
1640
|
"hover:bg-surface",
|
|
1220
1641
|
isActive && "bg-surface-raised border-l-2 border-l-accent"
|
|
@@ -1286,8 +1707,8 @@ function AgentFullPage({
|
|
|
1286
1707
|
onNewConversation,
|
|
1287
1708
|
className
|
|
1288
1709
|
}) {
|
|
1289
|
-
const [sidebarOpen, setSidebarOpen] = (0,
|
|
1290
|
-
const handleSelect = (0,
|
|
1710
|
+
const [sidebarOpen, setSidebarOpen] = (0, import_react8.useState)(false);
|
|
1711
|
+
const handleSelect = (0, import_react8.useCallback)(
|
|
1291
1712
|
(id) => {
|
|
1292
1713
|
onConversationSelect?.(id);
|
|
1293
1714
|
setSidebarOpen(false);
|
|
@@ -1297,7 +1718,7 @@ function AgentFullPage({
|
|
|
1297
1718
|
return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(
|
|
1298
1719
|
"div",
|
|
1299
1720
|
{
|
|
1300
|
-
className: (0,
|
|
1721
|
+
className: (0, import_tailwind_merge11.twMerge)("flex h-screen w-full overflow-hidden bg-brand-dark", className),
|
|
1301
1722
|
"data-testid": "agent-full-page",
|
|
1302
1723
|
children: [
|
|
1303
1724
|
showConversationList && /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(import_jsx_runtime14.Fragment, { children: [
|
|
@@ -1312,7 +1733,7 @@ function AgentFullPage({
|
|
|
1312
1733
|
/* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
|
|
1313
1734
|
"aside",
|
|
1314
1735
|
{
|
|
1315
|
-
className: (0,
|
|
1736
|
+
className: (0, import_tailwind_merge11.twMerge)(
|
|
1316
1737
|
"bg-brand-dark border-r border-brand-gold/15 w-72 shrink-0 flex-col z-40",
|
|
1317
1738
|
// Desktop: always visible
|
|
1318
1739
|
"hidden md:flex",
|
|
@@ -1378,8 +1799,8 @@ function AgentFullPage({
|
|
|
1378
1799
|
}
|
|
1379
1800
|
|
|
1380
1801
|
// src/layouts/AgentPanel/AgentPanel.tsx
|
|
1381
|
-
var
|
|
1382
|
-
var
|
|
1802
|
+
var import_tailwind_merge12 = require("tailwind-merge");
|
|
1803
|
+
var import_react9 = require("react");
|
|
1383
1804
|
var import_jsx_runtime15 = require("react/jsx-runtime");
|
|
1384
1805
|
function AgentPanel({
|
|
1385
1806
|
endpoint,
|
|
@@ -1390,8 +1811,8 @@ function AgentPanel({
|
|
|
1390
1811
|
title = "Chat",
|
|
1391
1812
|
className
|
|
1392
1813
|
}) {
|
|
1393
|
-
const panelRef = (0,
|
|
1394
|
-
(0,
|
|
1814
|
+
const panelRef = (0, import_react9.useRef)(null);
|
|
1815
|
+
(0, import_react9.useEffect)(() => {
|
|
1395
1816
|
if (!isOpen) return;
|
|
1396
1817
|
const handleKeyDown = (e) => {
|
|
1397
1818
|
if (e.key === "Escape") onClose();
|
|
@@ -1403,13 +1824,13 @@ function AgentPanel({
|
|
|
1403
1824
|
return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(
|
|
1404
1825
|
"div",
|
|
1405
1826
|
{
|
|
1406
|
-
className: (0,
|
|
1827
|
+
className: (0, import_tailwind_merge12.twMerge)("fixed inset-0 z-50", !isOpen && "pointer-events-none"),
|
|
1407
1828
|
"aria-hidden": !isOpen,
|
|
1408
1829
|
children: [
|
|
1409
1830
|
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
|
|
1410
1831
|
"div",
|
|
1411
1832
|
{
|
|
1412
|
-
className: (0,
|
|
1833
|
+
className: (0, import_tailwind_merge12.twMerge)(
|
|
1413
1834
|
"fixed inset-0 transition-opacity duration-300",
|
|
1414
1835
|
isOpen ? "opacity-100 bg-brand-dark/70 backdrop-blur-sm pointer-events-auto" : "opacity-0 pointer-events-none"
|
|
1415
1836
|
),
|
|
@@ -1425,7 +1846,7 @@ function AgentPanel({
|
|
|
1425
1846
|
"aria-label": title,
|
|
1426
1847
|
"aria-modal": isOpen ? "true" : void 0,
|
|
1427
1848
|
style: { width: widthStyle, maxWidth: "100vw" },
|
|
1428
|
-
className: (0,
|
|
1849
|
+
className: (0, import_tailwind_merge12.twMerge)(
|
|
1429
1850
|
"fixed top-0 h-full flex flex-col z-50 bg-brand-dark shadow-card",
|
|
1430
1851
|
"transition-transform duration-300 ease-in-out",
|
|
1431
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"}`,
|
|
@@ -1467,8 +1888,8 @@ function AgentPanel({
|
|
|
1467
1888
|
}
|
|
1468
1889
|
|
|
1469
1890
|
// src/layouts/AgentWidget/AgentWidget.tsx
|
|
1470
|
-
var
|
|
1471
|
-
var
|
|
1891
|
+
var import_tailwind_merge13 = require("tailwind-merge");
|
|
1892
|
+
var import_react10 = require("react");
|
|
1472
1893
|
var import_jsx_runtime16 = require("react/jsx-runtime");
|
|
1473
1894
|
function AgentWidget({
|
|
1474
1895
|
endpoint,
|
|
@@ -1477,8 +1898,8 @@ function AgentWidget({
|
|
|
1477
1898
|
title = "Chat",
|
|
1478
1899
|
className
|
|
1479
1900
|
}) {
|
|
1480
|
-
const [isOpen, setIsOpen] = (0,
|
|
1481
|
-
const toggle = (0,
|
|
1901
|
+
const [isOpen, setIsOpen] = (0, import_react10.useState)(false);
|
|
1902
|
+
const toggle = (0, import_react10.useCallback)(() => {
|
|
1482
1903
|
setIsOpen((prev) => !prev);
|
|
1483
1904
|
}, []);
|
|
1484
1905
|
const positionClasses = position === "bottom-left" ? "left-4 bottom-4" : "right-4 bottom-4";
|
|
@@ -1491,7 +1912,7 @@ function AgentWidget({
|
|
|
1491
1912
|
role: "dialog",
|
|
1492
1913
|
"aria-label": title,
|
|
1493
1914
|
"aria-hidden": !isOpen,
|
|
1494
|
-
className: (0,
|
|
1915
|
+
className: (0, import_tailwind_merge13.twMerge)(
|
|
1495
1916
|
"fixed z-50 flex flex-col",
|
|
1496
1917
|
"w-[min(400px,calc(100vw-2rem))] h-[min(600px,calc(100vh-6rem))]",
|
|
1497
1918
|
"rounded-2xl overflow-hidden border border-brand-gold/15",
|
|
@@ -1538,7 +1959,7 @@ function AgentWidget({
|
|
|
1538
1959
|
onClick: toggle,
|
|
1539
1960
|
"aria-label": isOpen ? "Close chat" : triggerLabel,
|
|
1540
1961
|
"aria-expanded": isOpen,
|
|
1541
|
-
className: (0,
|
|
1962
|
+
className: (0, import_tailwind_merge13.twMerge)(
|
|
1542
1963
|
"fixed z-50 flex items-center justify-center w-14 h-14 rounded-full",
|
|
1543
1964
|
"bg-brand-blue text-brand-cream shadow-glow-cyan",
|
|
1544
1965
|
"hover:bg-brand-cyan hover:shadow-glow-cyan hover:scale-105",
|
|
@@ -1556,7 +1977,7 @@ function AgentWidget({
|
|
|
1556
1977
|
}
|
|
1557
1978
|
|
|
1558
1979
|
// src/layouts/AgentEmbed/AgentEmbed.tsx
|
|
1559
|
-
var
|
|
1980
|
+
var import_tailwind_merge14 = require("tailwind-merge");
|
|
1560
1981
|
var import_jsx_runtime17 = require("react/jsx-runtime");
|
|
1561
1982
|
function AgentEmbed({
|
|
1562
1983
|
endpoint,
|
|
@@ -1566,7 +1987,7 @@ function AgentEmbed({
|
|
|
1566
1987
|
return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
|
|
1567
1988
|
"div",
|
|
1568
1989
|
{
|
|
1569
|
-
className: (0,
|
|
1990
|
+
className: (0, import_tailwind_merge14.twMerge)("w-full h-full min-h-0", className),
|
|
1570
1991
|
"data-testid": "agent-embed",
|
|
1571
1992
|
children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
|
|
1572
1993
|
AgentChat,
|