@surf-kit/agent 0.2.2 → 0.4.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 +733 -222
- package/dist/chat/index.cjs.map +1 -1
- package/dist/chat/index.d.cts +13 -7
- package/dist/chat/index.d.ts +13 -7
- package/dist/chat/index.js +715 -204
- package/dist/chat/index.js.map +1 -1
- package/dist/{chat-ChYl2XjV.d.cts → chat-BRY3xGg_.d.cts} +11 -2
- package/dist/{chat--OifhIRe.d.ts → chat-CcKc6OAR.d.ts} +11 -2
- package/dist/{hooks-BGs8-4GK.d.ts → hooks-BLeiVk-x.d.ts} +22 -5
- package/dist/{hooks-DLfF18IU.d.cts → hooks-CSGGLd7j.d.cts} +22 -5
- package/dist/hooks.cjs +160 -85
- 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 +160 -85
- package/dist/hooks.js.map +1 -1
- package/dist/index.cjs +794 -283
- 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 +758 -247
- package/dist/index.js.map +1 -1
- package/dist/layouts/index.cjs +754 -243
- 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 +733 -222
- 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 +100 -15
- 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 +99 -14
- 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 +213 -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 +183 -73
- package/dist/streaming/index.js.map +1 -1
- package/dist/{streaming-DfT22A0z.d.cts → streaming-BHPXnwwo.d.cts} +3 -1
- package/dist/{streaming-DbQxScpi.d.ts → streaming-C6mbU7My.d.ts} +3 -1
- package/package.json +17 -5
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,17 @@ 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_CONTENT_RESET":
|
|
80
|
+
return { ...state, streamingContent: "" };
|
|
81
|
+
case "STREAM_AGENT":
|
|
82
|
+
return { ...state, streamingAgent: action.agent };
|
|
77
83
|
case "SEND_SUCCESS":
|
|
78
84
|
return {
|
|
79
85
|
...state,
|
|
@@ -89,7 +95,8 @@ function reducer(state, action) {
|
|
|
89
95
|
isLoading: false,
|
|
90
96
|
error: action.error,
|
|
91
97
|
streamPhase: "idle",
|
|
92
|
-
streamingContent: ""
|
|
98
|
+
streamingContent: "",
|
|
99
|
+
streamingAgent: null
|
|
93
100
|
};
|
|
94
101
|
case "LOAD_CONVERSATION":
|
|
95
102
|
return {
|
|
@@ -115,115 +122,172 @@ function useAgentChat(config) {
|
|
|
115
122
|
const configRef = (0, import_react.useRef)(config);
|
|
116
123
|
configRef.current = config;
|
|
117
124
|
const lastUserMessageRef = (0, import_react.useRef)(null);
|
|
125
|
+
const lastUserAttachmentsRef = (0, import_react.useRef)(void 0);
|
|
126
|
+
const abortControllerRef = (0, import_react.useRef)(null);
|
|
118
127
|
const sendMessage = (0, import_react.useCallback)(
|
|
119
|
-
async (content) => {
|
|
120
|
-
const { apiUrl, streamPath = "/chat/stream", headers
|
|
128
|
+
async (content, attachments) => {
|
|
129
|
+
const { apiUrl, streamPath = "/chat/stream", headers: headersOrFn, timeout = 3e4, bodyExtra } = configRef.current;
|
|
130
|
+
const headers = typeof headersOrFn === "function" ? await headersOrFn() : headersOrFn ?? {};
|
|
121
131
|
lastUserMessageRef.current = content;
|
|
132
|
+
lastUserAttachmentsRef.current = attachments;
|
|
122
133
|
const userMessage = {
|
|
123
134
|
id: generateMessageId(),
|
|
124
135
|
role: "user",
|
|
125
136
|
content,
|
|
137
|
+
attachments,
|
|
126
138
|
timestamp: /* @__PURE__ */ new Date()
|
|
127
139
|
};
|
|
128
140
|
dispatch({ type: "SEND_START", message: userMessage });
|
|
129
141
|
const controller = new AbortController();
|
|
142
|
+
abortControllerRef.current = controller;
|
|
130
143
|
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
144
|
+
const ctx = {
|
|
145
|
+
accumulatedContent: "",
|
|
146
|
+
agentResponse: null,
|
|
147
|
+
capturedAgent: null,
|
|
148
|
+
capturedConversationId: null,
|
|
149
|
+
hadStreamError: false
|
|
150
|
+
};
|
|
131
151
|
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;
|
|
152
|
+
const url = `${apiUrl}${streamPath}`;
|
|
153
|
+
const mergedHeaders = {
|
|
154
|
+
"Content-Type": "application/json",
|
|
155
|
+
Accept: "text/event-stream",
|
|
156
|
+
...headers
|
|
157
|
+
};
|
|
158
|
+
const requestBody = {
|
|
159
|
+
message: content,
|
|
160
|
+
conversation_id: state.conversationId,
|
|
161
|
+
...bodyExtra
|
|
162
|
+
};
|
|
163
|
+
if (attachments && attachments.length > 0) {
|
|
164
|
+
requestBody.attachments = attachments.map((a) => ({
|
|
165
|
+
filename: a.filename,
|
|
166
|
+
content_type: a.content_type,
|
|
167
|
+
data: a.data
|
|
168
|
+
}));
|
|
156
169
|
}
|
|
157
|
-
const
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
170
|
+
const body = JSON.stringify(requestBody);
|
|
171
|
+
const handleEvent = (event) => {
|
|
172
|
+
switch (event.type) {
|
|
173
|
+
case "agent":
|
|
174
|
+
ctx.capturedAgent = event.agent;
|
|
175
|
+
dispatch({ type: "STREAM_AGENT", agent: ctx.capturedAgent });
|
|
176
|
+
break;
|
|
177
|
+
case "phase":
|
|
178
|
+
dispatch({ type: "STREAM_PHASE", phase: event.phase });
|
|
179
|
+
break;
|
|
180
|
+
case "delta":
|
|
181
|
+
ctx.accumulatedContent += event.content;
|
|
182
|
+
dispatch({ type: "STREAM_CONTENT", content: event.content });
|
|
183
|
+
break;
|
|
184
|
+
case "delta_reset":
|
|
185
|
+
ctx.accumulatedContent = "";
|
|
186
|
+
dispatch({ type: "STREAM_CONTENT_RESET" });
|
|
187
|
+
break;
|
|
188
|
+
case "done":
|
|
189
|
+
ctx.agentResponse = event.response;
|
|
190
|
+
ctx.capturedConversationId = event.conversation_id ?? null;
|
|
191
|
+
break;
|
|
192
|
+
case "error":
|
|
193
|
+
ctx.hadStreamError = true;
|
|
194
|
+
dispatch({ type: "SEND_ERROR", error: event.error });
|
|
195
|
+
break;
|
|
196
|
+
}
|
|
197
|
+
};
|
|
198
|
+
const { streamAdapter } = configRef.current;
|
|
199
|
+
if (streamAdapter) {
|
|
200
|
+
await streamAdapter(
|
|
201
|
+
url,
|
|
202
|
+
{ method: "POST", headers: mergedHeaders, body, signal: controller.signal },
|
|
203
|
+
handleEvent
|
|
204
|
+
);
|
|
205
|
+
clearTimeout(timeoutId);
|
|
206
|
+
} else {
|
|
207
|
+
const response = await fetch(url, {
|
|
208
|
+
method: "POST",
|
|
209
|
+
headers: mergedHeaders,
|
|
210
|
+
body,
|
|
211
|
+
signal: controller.signal
|
|
162
212
|
});
|
|
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;
|
|
213
|
+
clearTimeout(timeoutId);
|
|
214
|
+
if (!response.ok) {
|
|
215
|
+
dispatch({
|
|
216
|
+
type: "SEND_ERROR",
|
|
217
|
+
error: {
|
|
218
|
+
code: "API_ERROR",
|
|
219
|
+
message: `HTTP ${response.status}: ${response.statusText}`,
|
|
220
|
+
retryable: response.status >= 500
|
|
221
|
+
}
|
|
222
|
+
});
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
const reader = response.body?.getReader();
|
|
226
|
+
if (!reader) {
|
|
227
|
+
dispatch({
|
|
228
|
+
type: "SEND_ERROR",
|
|
229
|
+
error: { code: "STREAM_ERROR", message: "No response body", retryable: true }
|
|
230
|
+
});
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
const decoder = new TextDecoder();
|
|
234
|
+
let buffer = "";
|
|
235
|
+
while (true) {
|
|
236
|
+
const { done, value } = await reader.read();
|
|
237
|
+
if (done) break;
|
|
238
|
+
buffer += decoder.decode(value, { stream: true });
|
|
239
|
+
const lines = buffer.split("\n");
|
|
240
|
+
buffer = lines.pop() ?? "";
|
|
241
|
+
for (const line of lines) {
|
|
242
|
+
if (!line.startsWith("data: ")) continue;
|
|
243
|
+
const data = line.slice(6).trim();
|
|
244
|
+
if (data === "[DONE]") continue;
|
|
245
|
+
try {
|
|
246
|
+
const event = JSON.parse(data);
|
|
247
|
+
handleEvent(event);
|
|
248
|
+
} catch {
|
|
201
249
|
}
|
|
202
|
-
} catch {
|
|
203
250
|
}
|
|
204
251
|
}
|
|
205
252
|
}
|
|
253
|
+
if (ctx.hadStreamError) return;
|
|
206
254
|
const assistantMessage = {
|
|
207
255
|
id: generateMessageId(),
|
|
208
256
|
role: "assistant",
|
|
209
|
-
content: agentResponse?.message ?? accumulatedContent,
|
|
210
|
-
response: agentResponse ?? void 0,
|
|
211
|
-
agent: capturedAgent ?? void 0,
|
|
257
|
+
content: ctx.agentResponse?.message ?? ctx.accumulatedContent,
|
|
258
|
+
response: ctx.agentResponse ?? void 0,
|
|
259
|
+
agent: ctx.capturedAgent ?? void 0,
|
|
212
260
|
timestamp: /* @__PURE__ */ new Date()
|
|
213
261
|
};
|
|
214
262
|
dispatch({
|
|
215
263
|
type: "SEND_SUCCESS",
|
|
216
264
|
message: assistantMessage,
|
|
217
|
-
streamingContent: accumulatedContent,
|
|
218
|
-
conversationId: capturedConversationId
|
|
265
|
+
streamingContent: ctx.accumulatedContent,
|
|
266
|
+
conversationId: ctx.capturedConversationId
|
|
219
267
|
});
|
|
220
268
|
} catch (err) {
|
|
221
269
|
clearTimeout(timeoutId);
|
|
222
270
|
if (err.name === "AbortError") {
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
271
|
+
if (ctx.accumulatedContent) {
|
|
272
|
+
const partialMessage = {
|
|
273
|
+
id: generateMessageId(),
|
|
274
|
+
role: "assistant",
|
|
275
|
+
content: ctx.accumulatedContent,
|
|
276
|
+
agent: ctx.capturedAgent ?? void 0,
|
|
277
|
+
timestamp: /* @__PURE__ */ new Date()
|
|
278
|
+
};
|
|
279
|
+
dispatch({
|
|
280
|
+
type: "SEND_SUCCESS",
|
|
281
|
+
message: partialMessage,
|
|
282
|
+
streamingContent: ctx.accumulatedContent,
|
|
283
|
+
conversationId: ctx.capturedConversationId
|
|
284
|
+
});
|
|
285
|
+
} else {
|
|
286
|
+
dispatch({
|
|
287
|
+
type: "SEND_ERROR",
|
|
288
|
+
error: { code: "ABORTED", message: "Request stopped", retryable: true }
|
|
289
|
+
});
|
|
290
|
+
}
|
|
227
291
|
} else {
|
|
228
292
|
dispatch({
|
|
229
293
|
type: "SEND_ERROR",
|
|
@@ -234,6 +298,8 @@ function useAgentChat(config) {
|
|
|
234
298
|
}
|
|
235
299
|
});
|
|
236
300
|
}
|
|
301
|
+
} finally {
|
|
302
|
+
abortControllerRef.current = null;
|
|
237
303
|
}
|
|
238
304
|
},
|
|
239
305
|
[state.conversationId]
|
|
@@ -246,7 +312,8 @@ function useAgentChat(config) {
|
|
|
246
312
|
}, []);
|
|
247
313
|
const submitFeedback = (0, import_react.useCallback)(
|
|
248
314
|
async (messageId, rating, comment) => {
|
|
249
|
-
const { apiUrl, feedbackPath = "/feedback", headers
|
|
315
|
+
const { apiUrl, feedbackPath = "/feedback", headers: headersOrFn } = configRef.current;
|
|
316
|
+
const headers = typeof headersOrFn === "function" ? await headersOrFn() : headersOrFn ?? {};
|
|
250
317
|
await fetch(`${apiUrl}${feedbackPath}`, {
|
|
251
318
|
method: "POST",
|
|
252
319
|
headers: { "Content-Type": "application/json", ...headers },
|
|
@@ -257,12 +324,16 @@ function useAgentChat(config) {
|
|
|
257
324
|
);
|
|
258
325
|
const retry = (0, import_react.useCallback)(async () => {
|
|
259
326
|
if (lastUserMessageRef.current) {
|
|
260
|
-
await sendMessage(lastUserMessageRef.current);
|
|
327
|
+
await sendMessage(lastUserMessageRef.current, lastUserAttachmentsRef.current);
|
|
261
328
|
}
|
|
262
329
|
}, [sendMessage]);
|
|
330
|
+
const stop = (0, import_react.useCallback)(() => {
|
|
331
|
+
abortControllerRef.current?.abort();
|
|
332
|
+
}, []);
|
|
263
333
|
const reset = (0, import_react.useCallback)(() => {
|
|
264
334
|
dispatch({ type: "RESET" });
|
|
265
335
|
lastUserMessageRef.current = null;
|
|
336
|
+
lastUserAttachmentsRef.current = void 0;
|
|
266
337
|
}, []);
|
|
267
338
|
const actions = {
|
|
268
339
|
sendMessage,
|
|
@@ -270,6 +341,7 @@ function useAgentChat(config) {
|
|
|
270
341
|
loadConversation,
|
|
271
342
|
submitFeedback,
|
|
272
343
|
retry,
|
|
344
|
+
stop,
|
|
273
345
|
reset
|
|
274
346
|
};
|
|
275
347
|
return { state, actions };
|
|
@@ -277,7 +349,7 @@ function useAgentChat(config) {
|
|
|
277
349
|
|
|
278
350
|
// src/chat/MessageThread/MessageThread.tsx
|
|
279
351
|
var import_tailwind_merge5 = require("tailwind-merge");
|
|
280
|
-
var
|
|
352
|
+
var import_react4 = require("react");
|
|
281
353
|
|
|
282
354
|
// src/chat/MessageBubble/MessageBubble.tsx
|
|
283
355
|
var import_tailwind_merge4 = require("tailwind-merge");
|
|
@@ -286,8 +358,10 @@ var import_tailwind_merge4 = require("tailwind-merge");
|
|
|
286
358
|
var import_core2 = require("@surf-kit/core");
|
|
287
359
|
|
|
288
360
|
// src/response/ResponseMessage/ResponseMessage.tsx
|
|
361
|
+
var import_react2 = __toESM(require("react"), 1);
|
|
289
362
|
var import_react_markdown = __toESM(require("react-markdown"), 1);
|
|
290
363
|
var import_rehype_sanitize = __toESM(require("rehype-sanitize"), 1);
|
|
364
|
+
var import_remark_gfm = __toESM(require("remark-gfm"), 1);
|
|
291
365
|
var import_tailwind_merge = require("tailwind-merge");
|
|
292
366
|
var import_jsx_runtime = require("react/jsx-runtime");
|
|
293
367
|
function normalizeMarkdownLists(content) {
|
|
@@ -310,7 +384,12 @@ function ResponseMessage({ content, className }) {
|
|
|
310
384
|
"[&_h3]:text-sm [&_h3]:font-semibold [&_h3]:text-accent [&_h3]:mt-2 [&_h3]:mb-1",
|
|
311
385
|
"[&_code]:bg-surface-raised [&_code]:text-accent [&_code]:px-1.5 [&_code]:py-0.5 [&_code]:rounded [&_code]:text-xs [&_code]:font-mono",
|
|
312
386
|
"[&_pre]:bg-surface-raised [&_pre]:border [&_pre]:border-border [&_pre]:rounded-xl [&_pre]:p-4 [&_pre]:overflow-x-auto",
|
|
387
|
+
"[&_hr]:my-3 [&_hr]:border-border",
|
|
313
388
|
"[&_blockquote]:border-l-2 [&_blockquote]:border-border-strong [&_blockquote]:pl-4 [&_blockquote]:text-text-secondary",
|
|
389
|
+
"[&_table]:w-full [&_table]:text-sm [&_table]:border-collapse [&_table]:my-2",
|
|
390
|
+
"[&_thead]:border-b [&_thead]:border-border",
|
|
391
|
+
"[&_th]:text-left [&_th]:px-2 [&_th]:py-1.5 [&_th]:font-semibold",
|
|
392
|
+
"[&_td]:px-2 [&_td]:py-1.5 [&_td]:border-t [&_td]:border-border/50",
|
|
314
393
|
"[&_a]:text-accent [&_a]:underline-offset-2 [&_a]:hover:text-accent/80",
|
|
315
394
|
className
|
|
316
395
|
),
|
|
@@ -318,6 +397,7 @@ function ResponseMessage({ content, className }) {
|
|
|
318
397
|
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
319
398
|
import_react_markdown.default,
|
|
320
399
|
{
|
|
400
|
+
remarkPlugins: [import_remark_gfm.default],
|
|
321
401
|
rehypePlugins: [import_rehype_sanitize.default],
|
|
322
402
|
components: {
|
|
323
403
|
script: () => null,
|
|
@@ -325,12 +405,29 @@ function ResponseMessage({ content, className }) {
|
|
|
325
405
|
p: ({ children }) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "my-2", children }),
|
|
326
406
|
ul: ({ children }) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("ul", { className: "my-2 list-disc pl-6", children }),
|
|
327
407
|
ol: ({ children }) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("ol", { className: "my-2 list-decimal pl-6", children }),
|
|
328
|
-
li: ({ children }) =>
|
|
408
|
+
li: ({ children, ...props }) => {
|
|
409
|
+
let content2 = children;
|
|
410
|
+
if (props.ordered) {
|
|
411
|
+
content2 = import_react2.default.Children.map(children, (child, i) => {
|
|
412
|
+
if (i === 0 && typeof child === "string") {
|
|
413
|
+
return child.replace(/^\d+[.)]\s*/, "");
|
|
414
|
+
}
|
|
415
|
+
return child;
|
|
416
|
+
});
|
|
417
|
+
}
|
|
418
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("li", { className: "my-1", children: content2 });
|
|
419
|
+
},
|
|
329
420
|
strong: ({ children }) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("strong", { className: "font-semibold", children }),
|
|
421
|
+
em: ({ children }) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("em", { className: "italic text-text-secondary", children }),
|
|
330
422
|
h1: ({ children }) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h1", { className: "text-base font-bold mt-4 mb-2", children }),
|
|
331
423
|
h2: ({ children }) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h2", { className: "text-sm font-bold mt-3 mb-1", children }),
|
|
332
424
|
h3: ({ children }) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h3", { className: "text-sm font-semibold mt-2 mb-1", children }),
|
|
333
|
-
|
|
425
|
+
hr: () => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("hr", { className: "my-3 border-border" }),
|
|
426
|
+
code: ({ children }) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("code", { className: "bg-surface-sunken rounded px-1 py-0.5 text-xs font-mono", children }),
|
|
427
|
+
table: ({ children }) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "overflow-x-auto my-2", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("table", { className: "w-full text-sm border-collapse", children }) }),
|
|
428
|
+
thead: ({ children }) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("thead", { className: "border-b border-border", children }),
|
|
429
|
+
th: ({ children }) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", { className: "text-left px-2 py-1.5 font-semibold", children }),
|
|
430
|
+
td: ({ children }) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", { className: "px-2 py-1.5 border-t border-border/50", children })
|
|
334
431
|
},
|
|
335
432
|
children: normalizeMarkdownLists(content)
|
|
336
433
|
}
|
|
@@ -340,6 +437,8 @@ function ResponseMessage({ content, className }) {
|
|
|
340
437
|
}
|
|
341
438
|
|
|
342
439
|
// src/response/StructuredResponse/StructuredResponse.tsx
|
|
440
|
+
var import_react_markdown2 = __toESM(require("react-markdown"), 1);
|
|
441
|
+
var import_rehype_sanitize2 = __toESM(require("rehype-sanitize"), 1);
|
|
343
442
|
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
344
443
|
function tryParse(value) {
|
|
345
444
|
if (value === void 0 || value === null) return null;
|
|
@@ -352,6 +451,25 @@ function tryParse(value) {
|
|
|
352
451
|
}
|
|
353
452
|
return value;
|
|
354
453
|
}
|
|
454
|
+
function InlineMarkdown({ text }) {
|
|
455
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
456
|
+
import_react_markdown2.default,
|
|
457
|
+
{
|
|
458
|
+
rehypePlugins: [import_rehype_sanitize2.default],
|
|
459
|
+
components: {
|
|
460
|
+
// Unwrap block-level <p> so content stays inline within its parent
|
|
461
|
+
p: ({ children }) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_jsx_runtime2.Fragment, { children }),
|
|
462
|
+
strong: ({ children }) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("strong", { className: "font-semibold", children }),
|
|
463
|
+
em: ({ children }) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("em", { className: "italic", children }),
|
|
464
|
+
code: ({ children }) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("code", { className: "bg-surface-sunken rounded px-1 py-0.5 text-xs font-mono", children }),
|
|
465
|
+
// Prevent block elements that would break layout
|
|
466
|
+
script: () => null,
|
|
467
|
+
iframe: () => null
|
|
468
|
+
},
|
|
469
|
+
children: text
|
|
470
|
+
}
|
|
471
|
+
);
|
|
472
|
+
}
|
|
355
473
|
function renderSteps(data) {
|
|
356
474
|
const steps = tryParse(data.steps);
|
|
357
475
|
if (!steps || !Array.isArray(steps)) return null;
|
|
@@ -364,7 +482,7 @@ function renderSteps(data) {
|
|
|
364
482
|
children: i + 1
|
|
365
483
|
}
|
|
366
484
|
),
|
|
367
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "text-sm text-text-primary leading-relaxed", children: step })
|
|
485
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "text-sm text-text-primary leading-relaxed", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(InlineMarkdown, { text: step }) })
|
|
368
486
|
] }, i)) });
|
|
369
487
|
}
|
|
370
488
|
function renderTable(data) {
|
|
@@ -430,7 +548,7 @@ function renderList(data) {
|
|
|
430
548
|
title && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "text-xs font-semibold uppercase tracking-wider text-text-secondary mb-1", children: title }),
|
|
431
549
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("ul", { className: "flex flex-col gap-1.5", children: items.map((item, i) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("li", { className: "flex items-start gap-2.5", children: [
|
|
432
550
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "mt-1.5 h-1.5 w-1.5 shrink-0 rounded-full bg-accent", "aria-hidden": "true" }),
|
|
433
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "text-sm text-text-primary leading-relaxed", children: item })
|
|
551
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "text-sm text-text-primary leading-relaxed", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(InlineMarkdown, { text: item }) })
|
|
434
552
|
] }, i)) })
|
|
435
553
|
] });
|
|
436
554
|
}
|
|
@@ -465,7 +583,14 @@ function renderWarning(data) {
|
|
|
465
583
|
}
|
|
466
584
|
);
|
|
467
585
|
}
|
|
468
|
-
function StructuredResponse({ uiHint, data, className }) {
|
|
586
|
+
function StructuredResponse({ uiHint, data: rawData, className }) {
|
|
587
|
+
const data = typeof rawData === "string" ? (() => {
|
|
588
|
+
try {
|
|
589
|
+
return JSON.parse(rawData);
|
|
590
|
+
} catch {
|
|
591
|
+
return null;
|
|
592
|
+
}
|
|
593
|
+
})() : rawData;
|
|
469
594
|
if (!data) return null;
|
|
470
595
|
let content;
|
|
471
596
|
switch (uiHint) {
|
|
@@ -495,7 +620,7 @@ function StructuredResponse({ uiHint, data, className }) {
|
|
|
495
620
|
}
|
|
496
621
|
|
|
497
622
|
// src/sources/SourceList/SourceList.tsx
|
|
498
|
-
var
|
|
623
|
+
var import_react3 = require("react");
|
|
499
624
|
|
|
500
625
|
// src/sources/SourceCard/SourceCard.tsx
|
|
501
626
|
var import_core = require("@surf-kit/core");
|
|
@@ -545,7 +670,36 @@ function SourceCard({ source, variant = "compact", onNavigate, className }) {
|
|
|
545
670
|
children: [
|
|
546
671
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex items-start justify-between gap-2", children: [
|
|
547
672
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex-1 min-w-0", children: [
|
|
548
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.
|
|
673
|
+
source.url ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
|
|
674
|
+
"a",
|
|
675
|
+
{
|
|
676
|
+
href: source.url,
|
|
677
|
+
target: "_blank",
|
|
678
|
+
rel: "noopener noreferrer",
|
|
679
|
+
className: "text-sm font-medium text-accent hover:underline truncate block",
|
|
680
|
+
onClick: (e) => e.stopPropagation(),
|
|
681
|
+
children: [
|
|
682
|
+
source.title,
|
|
683
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
|
|
684
|
+
"svg",
|
|
685
|
+
{
|
|
686
|
+
className: "inline-block ml-1 w-3 h-3 opacity-60",
|
|
687
|
+
viewBox: "0 0 24 24",
|
|
688
|
+
fill: "none",
|
|
689
|
+
stroke: "currentColor",
|
|
690
|
+
strokeWidth: "2",
|
|
691
|
+
strokeLinecap: "round",
|
|
692
|
+
strokeLinejoin: "round",
|
|
693
|
+
children: [
|
|
694
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "M18 13v6a2 2 0 01-2 2H5a2 2 0 01-2-2V8a2 2 0 012-2h6" }),
|
|
695
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("polyline", { points: "15 3 21 3 21 9" }),
|
|
696
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("line", { x1: "10", y1: "14", x2: "21", y2: "3" })
|
|
697
|
+
]
|
|
698
|
+
}
|
|
699
|
+
)
|
|
700
|
+
]
|
|
701
|
+
}
|
|
702
|
+
) : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { className: "text-sm font-medium text-text-primary truncate", children: source.title }),
|
|
549
703
|
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
704
|
] }),
|
|
551
705
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
@@ -575,7 +729,7 @@ function SourceList({
|
|
|
575
729
|
onNavigate,
|
|
576
730
|
className
|
|
577
731
|
}) {
|
|
578
|
-
const [isExpanded, setIsExpanded] = (0,
|
|
732
|
+
const [isExpanded, setIsExpanded] = (0, import_react3.useState)(defaultExpanded);
|
|
579
733
|
if (sources.length === 0) return null;
|
|
580
734
|
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
735
|
SourceCard,
|
|
@@ -679,13 +833,16 @@ function AgentResponse({
|
|
|
679
833
|
}) {
|
|
680
834
|
return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: `flex flex-col gap-4 ${className ?? ""}`, "data-testid": "agent-response", children: [
|
|
681
835
|
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(ResponseMessage, { content: response.message }),
|
|
682
|
-
response.ui_hint !== "text" && response.structured_data &&
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
836
|
+
response.ui_hint !== "text" && response.structured_data && (() => {
|
|
837
|
+
const parsed = typeof response.structured_data === "string" ? (() => {
|
|
838
|
+
try {
|
|
839
|
+
return JSON.parse(response.structured_data);
|
|
840
|
+
} catch {
|
|
841
|
+
return null;
|
|
842
|
+
}
|
|
843
|
+
})() : response.structured_data;
|
|
844
|
+
return parsed ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(StructuredResponse, { uiHint: response.ui_hint, data: parsed }) : null;
|
|
845
|
+
})(),
|
|
689
846
|
(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
847
|
showConfidence && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
|
|
691
848
|
import_core2.Badge,
|
|
@@ -736,6 +893,31 @@ function AgentResponse({
|
|
|
736
893
|
|
|
737
894
|
// src/chat/MessageBubble/MessageBubble.tsx
|
|
738
895
|
var import_jsx_runtime7 = require("react/jsx-runtime");
|
|
896
|
+
function DocumentIcon() {
|
|
897
|
+
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: [
|
|
898
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("path", { d: "M14 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V8z" }),
|
|
899
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("polyline", { points: "14 2 14 8 20 8" }),
|
|
900
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("line", { x1: "16", y1: "13", x2: "8", y2: "13" }),
|
|
901
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("line", { x1: "16", y1: "17", x2: "8", y2: "17" })
|
|
902
|
+
] });
|
|
903
|
+
}
|
|
904
|
+
function AttachmentThumbnail({ attachment }) {
|
|
905
|
+
const isImage = attachment.content_type.startsWith("image/");
|
|
906
|
+
if (isImage) {
|
|
907
|
+
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)(
|
|
908
|
+
"img",
|
|
909
|
+
{
|
|
910
|
+
src: attachment.preview_url ?? `data:${attachment.content_type};base64,${attachment.data}`,
|
|
911
|
+
alt: attachment.filename,
|
|
912
|
+
className: "max-w-full max-h-[200px] object-contain"
|
|
913
|
+
}
|
|
914
|
+
) });
|
|
915
|
+
}
|
|
916
|
+
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: [
|
|
917
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(DocumentIcon, {}),
|
|
918
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "text-xs truncate max-w-[160px]", children: attachment.filename })
|
|
919
|
+
] });
|
|
920
|
+
}
|
|
739
921
|
function MessageBubble({
|
|
740
922
|
message,
|
|
741
923
|
showAgent,
|
|
@@ -743,23 +925,29 @@ function MessageBubble({
|
|
|
743
925
|
showConfidence = true,
|
|
744
926
|
showVerification = true,
|
|
745
927
|
animated = true,
|
|
928
|
+
userBubbleClassName,
|
|
746
929
|
className
|
|
747
930
|
}) {
|
|
748
931
|
const isUser = message.role === "user";
|
|
932
|
+
const hasAttachments = message.attachments && message.attachments.length > 0;
|
|
749
933
|
if (isUser) {
|
|
750
934
|
return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
751
935
|
"div",
|
|
752
936
|
{
|
|
753
937
|
"data-message-id": message.id,
|
|
754
938
|
className: (0, import_tailwind_merge4.twMerge)("flex w-full justify-end", className),
|
|
755
|
-
children: /* @__PURE__ */ (0, import_jsx_runtime7.
|
|
939
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
|
|
756
940
|
"div",
|
|
757
941
|
{
|
|
758
942
|
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"
|
|
943
|
+
"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",
|
|
944
|
+
animated && "motion-safe:animate-slideFromRight",
|
|
945
|
+
userBubbleClassName
|
|
761
946
|
),
|
|
762
|
-
children:
|
|
947
|
+
children: [
|
|
948
|
+
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}`)) }),
|
|
949
|
+
message.content
|
|
950
|
+
]
|
|
763
951
|
}
|
|
764
952
|
)
|
|
765
953
|
}
|
|
@@ -771,7 +959,7 @@ function MessageBubble({
|
|
|
771
959
|
"data-message-id": message.id,
|
|
772
960
|
className: (0, import_tailwind_merge4.twMerge)("flex w-full flex-col items-start gap-1.5", className),
|
|
773
961
|
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("_", " ") }),
|
|
962
|
+
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
963
|
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
776
964
|
"div",
|
|
777
965
|
{
|
|
@@ -797,34 +985,97 @@ function MessageBubble({
|
|
|
797
985
|
|
|
798
986
|
// src/chat/MessageThread/MessageThread.tsx
|
|
799
987
|
var import_jsx_runtime8 = require("react/jsx-runtime");
|
|
800
|
-
function MessageThread({ messages, streamingSlot, showSources, showConfidence, showVerification, className }) {
|
|
801
|
-
const
|
|
802
|
-
(0,
|
|
803
|
-
|
|
804
|
-
|
|
988
|
+
function MessageThread({ messages, streamingSlot, showAgent, showSources, showConfidence, showVerification, hideLastAssistant, userBubbleClassName, className }) {
|
|
989
|
+
const scrollRef = (0, import_react4.useRef)(null);
|
|
990
|
+
const shouldAutoScroll = (0, import_react4.useRef)(true);
|
|
991
|
+
const hasStreaming = !!streamingSlot;
|
|
992
|
+
const scrollToBottom = (0, import_react4.useCallback)(() => {
|
|
993
|
+
const el = scrollRef.current;
|
|
994
|
+
if (el && shouldAutoScroll.current) {
|
|
995
|
+
el.scrollTop = el.scrollHeight;
|
|
996
|
+
}
|
|
997
|
+
}, []);
|
|
998
|
+
(0, import_react4.useEffect)(() => {
|
|
999
|
+
const el = scrollRef.current;
|
|
1000
|
+
if (!el) return;
|
|
1001
|
+
const onWheel = (e) => {
|
|
1002
|
+
if (e.deltaY < 0) {
|
|
1003
|
+
shouldAutoScroll.current = false;
|
|
1004
|
+
}
|
|
1005
|
+
};
|
|
1006
|
+
const onPointerDown = () => {
|
|
1007
|
+
el.dataset.userPointer = "1";
|
|
1008
|
+
};
|
|
1009
|
+
const onPointerUp = () => {
|
|
1010
|
+
delete el.dataset.userPointer;
|
|
1011
|
+
};
|
|
1012
|
+
el.addEventListener("wheel", onWheel, { passive: true });
|
|
1013
|
+
el.addEventListener("pointerdown", onPointerDown);
|
|
1014
|
+
window.addEventListener("pointerup", onPointerUp);
|
|
1015
|
+
return () => {
|
|
1016
|
+
el.removeEventListener("wheel", onWheel);
|
|
1017
|
+
el.removeEventListener("pointerdown", onPointerDown);
|
|
1018
|
+
window.removeEventListener("pointerup", onPointerUp);
|
|
1019
|
+
};
|
|
1020
|
+
}, []);
|
|
1021
|
+
const handleScroll = (0, import_react4.useCallback)(() => {
|
|
1022
|
+
const el = scrollRef.current;
|
|
1023
|
+
if (!el) return;
|
|
1024
|
+
const nearBottom = el.scrollHeight - el.scrollTop - el.clientHeight < 80;
|
|
1025
|
+
if (nearBottom) {
|
|
1026
|
+
shouldAutoScroll.current = true;
|
|
1027
|
+
} else if (el.dataset.userPointer) {
|
|
1028
|
+
shouldAutoScroll.current = false;
|
|
1029
|
+
}
|
|
1030
|
+
}, []);
|
|
1031
|
+
(0, import_react4.useEffect)(scrollToBottom, [messages.length, scrollToBottom]);
|
|
1032
|
+
(0, import_react4.useEffect)(() => {
|
|
1033
|
+
if (!hasStreaming) return;
|
|
1034
|
+
let raf;
|
|
1035
|
+
const tick = () => {
|
|
1036
|
+
scrollToBottom();
|
|
1037
|
+
raf = requestAnimationFrame(tick);
|
|
1038
|
+
};
|
|
1039
|
+
raf = requestAnimationFrame(tick);
|
|
1040
|
+
return () => cancelAnimationFrame(raf);
|
|
1041
|
+
}, [hasStreaming, scrollToBottom]);
|
|
1042
|
+
(0, import_react4.useEffect)(() => {
|
|
1043
|
+
if (!hasStreaming) {
|
|
1044
|
+
shouldAutoScroll.current = true;
|
|
1045
|
+
}
|
|
1046
|
+
}, [hasStreaming]);
|
|
805
1047
|
return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
|
|
806
1048
|
"div",
|
|
807
1049
|
{
|
|
1050
|
+
ref: scrollRef,
|
|
808
1051
|
role: "log",
|
|
809
1052
|
"aria-live": "polite",
|
|
810
1053
|
"aria-label": "Message thread",
|
|
1054
|
+
onScroll: handleScroll,
|
|
811
1055
|
className: (0, import_tailwind_merge5.twMerge)(
|
|
812
1056
|
"flex flex-col gap-4 overflow-y-auto flex-1 px-4 py-6",
|
|
813
1057
|
className
|
|
814
1058
|
),
|
|
815
1059
|
children: [
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
{
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
1060
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "flex-1 shrink-0" }),
|
|
1061
|
+
messages.map((message, i) => {
|
|
1062
|
+
if (hideLastAssistant && i === messages.length - 1 && message.role === "assistant") {
|
|
1063
|
+
return null;
|
|
1064
|
+
}
|
|
1065
|
+
return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
1066
|
+
MessageBubble,
|
|
1067
|
+
{
|
|
1068
|
+
message,
|
|
1069
|
+
showAgent,
|
|
1070
|
+
showSources,
|
|
1071
|
+
showConfidence,
|
|
1072
|
+
showVerification,
|
|
1073
|
+
userBubbleClassName
|
|
1074
|
+
},
|
|
1075
|
+
message.id
|
|
1076
|
+
);
|
|
1077
|
+
}),
|
|
1078
|
+
streamingSlot
|
|
828
1079
|
]
|
|
829
1080
|
}
|
|
830
1081
|
);
|
|
@@ -832,32 +1083,127 @@ function MessageThread({ messages, streamingSlot, showSources, showConfidence, s
|
|
|
832
1083
|
|
|
833
1084
|
// src/chat/MessageComposer/MessageComposer.tsx
|
|
834
1085
|
var import_tailwind_merge6 = require("tailwind-merge");
|
|
835
|
-
var
|
|
1086
|
+
var import_react5 = require("react");
|
|
836
1087
|
var import_jsx_runtime9 = require("react/jsx-runtime");
|
|
1088
|
+
var ALLOWED_TYPES = /* @__PURE__ */ new Set([
|
|
1089
|
+
"image/png",
|
|
1090
|
+
"image/jpeg",
|
|
1091
|
+
"image/gif",
|
|
1092
|
+
"image/webp",
|
|
1093
|
+
"application/pdf"
|
|
1094
|
+
]);
|
|
1095
|
+
var MAX_FILE_SIZE = 10 * 1024 * 1024;
|
|
1096
|
+
var MAX_ATTACHMENTS = 5;
|
|
1097
|
+
function ArrowUpIcon() {
|
|
1098
|
+
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: [
|
|
1099
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("path", { d: "M10 16V4" }),
|
|
1100
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("path", { d: "M4 10l6-6 6 6" })
|
|
1101
|
+
] });
|
|
1102
|
+
}
|
|
1103
|
+
function StopIcon() {
|
|
1104
|
+
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" }) });
|
|
1105
|
+
}
|
|
1106
|
+
function PaperclipIcon() {
|
|
1107
|
+
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" }) });
|
|
1108
|
+
}
|
|
1109
|
+
function XIcon({ size = 14 }) {
|
|
1110
|
+
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: [
|
|
1111
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("path", { d: "M18 6L6 18" }),
|
|
1112
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("path", { d: "M6 6l12 12" })
|
|
1113
|
+
] });
|
|
1114
|
+
}
|
|
1115
|
+
function DocumentIcon2() {
|
|
1116
|
+
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: [
|
|
1117
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("path", { d: "M14 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V8z" }),
|
|
1118
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("polyline", { points: "14 2 14 8 20 8" }),
|
|
1119
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("line", { x1: "16", y1: "13", x2: "8", y2: "13" }),
|
|
1120
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("line", { x1: "16", y1: "17", x2: "8", y2: "17" }),
|
|
1121
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("polyline", { points: "10 9 9 9 8 9" })
|
|
1122
|
+
] });
|
|
1123
|
+
}
|
|
1124
|
+
function fileToBase64(file) {
|
|
1125
|
+
return new Promise((resolve, reject) => {
|
|
1126
|
+
const reader = new FileReader();
|
|
1127
|
+
reader.onload = () => {
|
|
1128
|
+
const result = reader.result;
|
|
1129
|
+
const base64 = result.split(",")[1];
|
|
1130
|
+
resolve(base64);
|
|
1131
|
+
};
|
|
1132
|
+
reader.onerror = reject;
|
|
1133
|
+
reader.readAsDataURL(file);
|
|
1134
|
+
});
|
|
1135
|
+
}
|
|
1136
|
+
function AttachmentPreview({
|
|
1137
|
+
attachment,
|
|
1138
|
+
onRemove
|
|
1139
|
+
}) {
|
|
1140
|
+
const isImage = attachment.content_type.startsWith("image/");
|
|
1141
|
+
return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "relative group flex-shrink-0", children: [
|
|
1142
|
+
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)(
|
|
1143
|
+
"img",
|
|
1144
|
+
{
|
|
1145
|
+
src: attachment.preview_url ?? `data:${attachment.content_type};base64,${attachment.data}`,
|
|
1146
|
+
alt: attachment.filename,
|
|
1147
|
+
className: "w-full h-full object-cover"
|
|
1148
|
+
}
|
|
1149
|
+
) }) : /* @__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: [
|
|
1150
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "text-text-muted", children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(DocumentIcon2, {}) }),
|
|
1151
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "flex flex-col min-w-0", children: [
|
|
1152
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "text-xs text-text-primary truncate max-w-[120px]", children: attachment.filename }),
|
|
1153
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "text-[10px] text-text-muted", children: "PDF" })
|
|
1154
|
+
] })
|
|
1155
|
+
] }),
|
|
1156
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
|
|
1157
|
+
"button",
|
|
1158
|
+
{
|
|
1159
|
+
type: "button",
|
|
1160
|
+
onClick: onRemove,
|
|
1161
|
+
className: (0, import_tailwind_merge6.twMerge)(
|
|
1162
|
+
"absolute -top-1.5 -right-1.5",
|
|
1163
|
+
"w-5 h-5 rounded-full",
|
|
1164
|
+
"bg-text-muted/80 text-white",
|
|
1165
|
+
"flex items-center justify-center",
|
|
1166
|
+
"opacity-0 group-hover:opacity-100",
|
|
1167
|
+
"transition-opacity duration-150",
|
|
1168
|
+
"hover:bg-text-primary"
|
|
1169
|
+
),
|
|
1170
|
+
"aria-label": `Remove ${attachment.filename}`,
|
|
1171
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(XIcon, { size: 10 })
|
|
1172
|
+
}
|
|
1173
|
+
)
|
|
1174
|
+
] });
|
|
1175
|
+
}
|
|
837
1176
|
function MessageComposer({
|
|
838
1177
|
onSend,
|
|
1178
|
+
onStop,
|
|
839
1179
|
isLoading = false,
|
|
840
1180
|
placeholder = "Type a message...",
|
|
841
1181
|
className
|
|
842
1182
|
}) {
|
|
843
|
-
const [value, setValue] = (0,
|
|
844
|
-
const
|
|
845
|
-
const
|
|
846
|
-
const
|
|
1183
|
+
const [value, setValue] = (0, import_react5.useState)("");
|
|
1184
|
+
const [attachments, setAttachments] = (0, import_react5.useState)([]);
|
|
1185
|
+
const [dragOver, setDragOver] = (0, import_react5.useState)(false);
|
|
1186
|
+
const textareaRef = (0, import_react5.useRef)(null);
|
|
1187
|
+
const fileInputRef = (0, import_react5.useRef)(null);
|
|
1188
|
+
const canSend = (value.trim().length > 0 || attachments.length > 0) && !isLoading;
|
|
1189
|
+
const resetHeight = (0, import_react5.useCallback)(() => {
|
|
847
1190
|
const el = textareaRef.current;
|
|
848
1191
|
if (el) {
|
|
849
1192
|
el.style.height = "auto";
|
|
850
1193
|
el.style.overflowY = "hidden";
|
|
851
1194
|
}
|
|
852
1195
|
}, []);
|
|
853
|
-
const handleSend = (0,
|
|
1196
|
+
const handleSend = (0, import_react5.useCallback)(() => {
|
|
854
1197
|
if (!canSend) return;
|
|
855
|
-
|
|
1198
|
+
const message = value.trim() || (attachments.length > 0 ? "Please analyse the attached file(s)." : "");
|
|
1199
|
+
if (!message && attachments.length === 0) return;
|
|
1200
|
+
onSend(message, attachments.length > 0 ? attachments : void 0);
|
|
856
1201
|
setValue("");
|
|
1202
|
+
setAttachments([]);
|
|
857
1203
|
resetHeight();
|
|
858
1204
|
textareaRef.current?.focus();
|
|
859
|
-
}, [canSend, onSend, value, resetHeight]);
|
|
860
|
-
const handleKeyDown = (0,
|
|
1205
|
+
}, [canSend, onSend, value, attachments, resetHeight]);
|
|
1206
|
+
const handleKeyDown = (0, import_react5.useCallback)(
|
|
861
1207
|
(e) => {
|
|
862
1208
|
if (e.key === "Enter" && !e.shiftKey) {
|
|
863
1209
|
e.preventDefault();
|
|
@@ -866,64 +1212,194 @@ function MessageComposer({
|
|
|
866
1212
|
},
|
|
867
1213
|
[handleSend]
|
|
868
1214
|
);
|
|
869
|
-
const handleChange = (0,
|
|
1215
|
+
const handleChange = (0, import_react5.useCallback)(
|
|
870
1216
|
(e) => {
|
|
871
1217
|
setValue(e.target.value);
|
|
872
1218
|
const el = e.target;
|
|
873
1219
|
el.style.height = "auto";
|
|
874
|
-
const capped = Math.min(el.scrollHeight,
|
|
1220
|
+
const capped = Math.min(el.scrollHeight, 200);
|
|
875
1221
|
el.style.height = `${capped}px`;
|
|
876
|
-
el.style.overflowY = el.scrollHeight >
|
|
1222
|
+
el.style.overflowY = el.scrollHeight > 200 ? "auto" : "hidden";
|
|
877
1223
|
},
|
|
878
1224
|
[]
|
|
879
1225
|
);
|
|
1226
|
+
const addFiles = (0, import_react5.useCallback)(async (files) => {
|
|
1227
|
+
const fileArray = Array.from(files);
|
|
1228
|
+
for (const file of fileArray) {
|
|
1229
|
+
if (attachments.length >= MAX_ATTACHMENTS) break;
|
|
1230
|
+
if (!ALLOWED_TYPES.has(file.type)) continue;
|
|
1231
|
+
if (file.size > MAX_FILE_SIZE) continue;
|
|
1232
|
+
try {
|
|
1233
|
+
const data = await fileToBase64(file);
|
|
1234
|
+
const previewUrl = file.type.startsWith("image/") ? URL.createObjectURL(file) : void 0;
|
|
1235
|
+
const attachment = {
|
|
1236
|
+
filename: file.name,
|
|
1237
|
+
content_type: file.type,
|
|
1238
|
+
data,
|
|
1239
|
+
preview_url: previewUrl
|
|
1240
|
+
};
|
|
1241
|
+
setAttachments((prev) => {
|
|
1242
|
+
if (prev.length >= MAX_ATTACHMENTS) return prev;
|
|
1243
|
+
return [...prev, attachment];
|
|
1244
|
+
});
|
|
1245
|
+
} catch {
|
|
1246
|
+
}
|
|
1247
|
+
}
|
|
1248
|
+
}, [attachments.length]);
|
|
1249
|
+
const handleFileSelect = (0, import_react5.useCallback)(() => {
|
|
1250
|
+
fileInputRef.current?.click();
|
|
1251
|
+
}, []);
|
|
1252
|
+
const handleFileInputChange = (0, import_react5.useCallback)(
|
|
1253
|
+
(e) => {
|
|
1254
|
+
if (e.target.files) {
|
|
1255
|
+
void addFiles(e.target.files);
|
|
1256
|
+
e.target.value = "";
|
|
1257
|
+
}
|
|
1258
|
+
},
|
|
1259
|
+
[addFiles]
|
|
1260
|
+
);
|
|
1261
|
+
const removeAttachment = (0, import_react5.useCallback)((index) => {
|
|
1262
|
+
setAttachments((prev) => {
|
|
1263
|
+
const removed = prev[index];
|
|
1264
|
+
if (removed?.preview_url) URL.revokeObjectURL(removed.preview_url);
|
|
1265
|
+
return prev.filter((_, i) => i !== index);
|
|
1266
|
+
});
|
|
1267
|
+
}, []);
|
|
1268
|
+
const handlePaste = (0, import_react5.useCallback)(
|
|
1269
|
+
(e) => {
|
|
1270
|
+
const items = e.clipboardData.items;
|
|
1271
|
+
const files = [];
|
|
1272
|
+
for (const item of items) {
|
|
1273
|
+
if (item.kind === "file" && ALLOWED_TYPES.has(item.type)) {
|
|
1274
|
+
const file = item.getAsFile();
|
|
1275
|
+
if (file) files.push(file);
|
|
1276
|
+
}
|
|
1277
|
+
}
|
|
1278
|
+
if (files.length > 0) {
|
|
1279
|
+
void addFiles(files);
|
|
1280
|
+
}
|
|
1281
|
+
},
|
|
1282
|
+
[addFiles]
|
|
1283
|
+
);
|
|
1284
|
+
const handleDragOver = (0, import_react5.useCallback)((e) => {
|
|
1285
|
+
e.preventDefault();
|
|
1286
|
+
e.stopPropagation();
|
|
1287
|
+
setDragOver(true);
|
|
1288
|
+
}, []);
|
|
1289
|
+
const handleDragLeave = (0, import_react5.useCallback)((e) => {
|
|
1290
|
+
e.preventDefault();
|
|
1291
|
+
e.stopPropagation();
|
|
1292
|
+
setDragOver(false);
|
|
1293
|
+
}, []);
|
|
1294
|
+
const handleDrop = (0, import_react5.useCallback)(
|
|
1295
|
+
(e) => {
|
|
1296
|
+
e.preventDefault();
|
|
1297
|
+
e.stopPropagation();
|
|
1298
|
+
setDragOver(false);
|
|
1299
|
+
if (e.dataTransfer.files.length > 0) {
|
|
1300
|
+
void addFiles(e.dataTransfer.files);
|
|
1301
|
+
}
|
|
1302
|
+
},
|
|
1303
|
+
[addFiles]
|
|
1304
|
+
);
|
|
880
1305
|
return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
|
|
881
1306
|
"div",
|
|
882
1307
|
{
|
|
883
1308
|
className: (0, import_tailwind_merge6.twMerge)(
|
|
884
|
-
"
|
|
1309
|
+
"relative shrink-0 rounded-3xl border bg-surface",
|
|
1310
|
+
"shadow-lg shadow-black/10",
|
|
1311
|
+
"transition-all duration-200",
|
|
1312
|
+
"focus-within:border-accent/40 focus-within:shadow-accent/5",
|
|
1313
|
+
dragOver ? "border-accent/60 bg-accent/5" : "border-border/60",
|
|
885
1314
|
className
|
|
886
1315
|
),
|
|
1316
|
+
onDragOver: handleDragOver,
|
|
1317
|
+
onDragLeave: handleDragLeave,
|
|
1318
|
+
onDrop: handleDrop,
|
|
887
1319
|
children: [
|
|
888
1320
|
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
|
|
889
|
-
"
|
|
1321
|
+
"input",
|
|
890
1322
|
{
|
|
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"
|
|
1323
|
+
ref: fileInputRef,
|
|
1324
|
+
type: "file",
|
|
1325
|
+
multiple: true,
|
|
1326
|
+
accept: "image/png,image/jpeg,image/gif,image/webp,application/pdf",
|
|
1327
|
+
onChange: handleFileInputChange,
|
|
1328
|
+
className: "hidden",
|
|
1329
|
+
"aria-hidden": "true"
|
|
908
1330
|
}
|
|
909
1331
|
),
|
|
910
|
-
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
|
|
911
|
-
|
|
1332
|
+
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)(
|
|
1333
|
+
AttachmentPreview,
|
|
912
1334
|
{
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
1335
|
+
attachment: att,
|
|
1336
|
+
onRemove: () => removeAttachment(i)
|
|
1337
|
+
},
|
|
1338
|
+
`${att.filename}-${i}`
|
|
1339
|
+
)) }),
|
|
1340
|
+
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" }) }),
|
|
1341
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "flex items-end", children: [
|
|
1342
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
|
|
1343
|
+
"button",
|
|
1344
|
+
{
|
|
1345
|
+
type: "button",
|
|
1346
|
+
onClick: handleFileSelect,
|
|
1347
|
+
disabled: isLoading || attachments.length >= MAX_ATTACHMENTS,
|
|
1348
|
+
"aria-label": "Attach file",
|
|
1349
|
+
className: (0, import_tailwind_merge6.twMerge)(
|
|
1350
|
+
"flex-shrink-0 ml-2 mb-3",
|
|
1351
|
+
"inline-flex items-center justify-center",
|
|
1352
|
+
"w-9 h-9 rounded-full",
|
|
1353
|
+
"transition-all duration-200",
|
|
1354
|
+
"text-text-muted/60 hover:text-text-secondary hover:bg-text-muted/10",
|
|
1355
|
+
"focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-accent",
|
|
1356
|
+
"disabled:opacity-30 disabled:cursor-not-allowed disabled:hover:bg-transparent"
|
|
1357
|
+
),
|
|
1358
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(PaperclipIcon, {})
|
|
1359
|
+
}
|
|
1360
|
+
),
|
|
1361
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
|
|
1362
|
+
"textarea",
|
|
1363
|
+
{
|
|
1364
|
+
ref: textareaRef,
|
|
1365
|
+
value,
|
|
1366
|
+
onChange: handleChange,
|
|
1367
|
+
onKeyDown: handleKeyDown,
|
|
1368
|
+
onPaste: handlePaste,
|
|
1369
|
+
placeholder,
|
|
1370
|
+
rows: 1,
|
|
1371
|
+
disabled: isLoading,
|
|
1372
|
+
className: (0, import_tailwind_merge6.twMerge)(
|
|
1373
|
+
"flex-1 resize-none bg-transparent",
|
|
1374
|
+
"pl-2 pr-14 pt-4 pb-4 text-[15px] leading-relaxed",
|
|
1375
|
+
"text-text-primary placeholder:text-text-muted/70",
|
|
1376
|
+
"focus:outline-none",
|
|
1377
|
+
"disabled:opacity-50 disabled:cursor-not-allowed",
|
|
1378
|
+
"overflow-hidden"
|
|
1379
|
+
),
|
|
1380
|
+
style: { colorScheme: "dark" },
|
|
1381
|
+
"aria-label": "Message input"
|
|
1382
|
+
}
|
|
1383
|
+
),
|
|
1384
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
|
|
1385
|
+
"button",
|
|
1386
|
+
{
|
|
1387
|
+
type: "button",
|
|
1388
|
+
onClick: isLoading && onStop ? onStop : handleSend,
|
|
1389
|
+
disabled: !canSend && !isLoading,
|
|
1390
|
+
"aria-label": isLoading ? "Stop generating" : "Send message",
|
|
1391
|
+
className: (0, import_tailwind_merge6.twMerge)(
|
|
1392
|
+
"absolute bottom-3 right-3",
|
|
1393
|
+
"inline-flex items-center justify-center",
|
|
1394
|
+
"w-9 h-9 rounded-full",
|
|
1395
|
+
"transition-all duration-200",
|
|
1396
|
+
"focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-accent",
|
|
1397
|
+
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 cursor-pointer" : "bg-transparent text-text-muted/40 cursor-default"
|
|
1398
|
+
),
|
|
1399
|
+
children: isLoading ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(StopIcon, {}) : /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(ArrowUpIcon, {})
|
|
1400
|
+
}
|
|
1401
|
+
)
|
|
1402
|
+
] })
|
|
927
1403
|
]
|
|
928
1404
|
}
|
|
929
1405
|
);
|
|
@@ -936,6 +1412,7 @@ function WelcomeScreen({
|
|
|
936
1412
|
title = "Welcome",
|
|
937
1413
|
message = "How can I help you today?",
|
|
938
1414
|
icon,
|
|
1415
|
+
iconClassName,
|
|
939
1416
|
suggestedQuestions = [],
|
|
940
1417
|
onQuestionSelect,
|
|
941
1418
|
className
|
|
@@ -948,12 +1425,15 @@ function WelcomeScreen({
|
|
|
948
1425
|
className
|
|
949
1426
|
),
|
|
950
1427
|
children: [
|
|
951
|
-
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
|
|
1428
|
+
icon ? iconClassName ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: iconClassName, "aria-hidden": "true", children: icon }) : icon : /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
|
|
952
1429
|
"div",
|
|
953
1430
|
{
|
|
954
|
-
className:
|
|
1431
|
+
className: (0, import_tailwind_merge7.twMerge)(
|
|
1432
|
+
"w-14 h-14 rounded-2xl bg-accent/10 border border-border flex items-center justify-center pulse-glow",
|
|
1433
|
+
iconClassName
|
|
1434
|
+
),
|
|
955
1435
|
"aria-hidden": "true",
|
|
956
|
-
children:
|
|
1436
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "text-2xl", children: "\u2726" })
|
|
957
1437
|
}
|
|
958
1438
|
),
|
|
959
1439
|
/* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "flex flex-col gap-2", children: [
|
|
@@ -963,7 +1443,7 @@ function WelcomeScreen({
|
|
|
963
1443
|
suggestedQuestions.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
|
|
964
1444
|
"div",
|
|
965
1445
|
{
|
|
966
|
-
className: "flex flex-wrap justify-center gap-2 max-w-
|
|
1446
|
+
className: "flex flex-wrap justify-center gap-2 max-w-xl",
|
|
967
1447
|
role: "group",
|
|
968
1448
|
"aria-label": "Suggested questions",
|
|
969
1449
|
children: suggestedQuestions.map((question) => /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
|
|
@@ -972,7 +1452,7 @@ function WelcomeScreen({
|
|
|
972
1452
|
type: "button",
|
|
973
1453
|
onClick: () => onQuestionSelect?.(question),
|
|
974
1454
|
className: (0, import_tailwind_merge7.twMerge)(
|
|
975
|
-
"px-
|
|
1455
|
+
"px-3.5 py-1.5 rounded-full text-[12px]",
|
|
976
1456
|
"border border-border bg-transparent text-text-secondary",
|
|
977
1457
|
"hover:bg-accent/10 hover:border-interactive hover:text-text-primary",
|
|
978
1458
|
"focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-accent",
|
|
@@ -990,25 +1470,26 @@ function WelcomeScreen({
|
|
|
990
1470
|
}
|
|
991
1471
|
|
|
992
1472
|
// src/streaming/StreamingMessage/StreamingMessage.tsx
|
|
993
|
-
var
|
|
1473
|
+
var import_react7 = require("react");
|
|
1474
|
+
var import_tailwind_merge8 = require("tailwind-merge");
|
|
994
1475
|
var import_core3 = require("@surf-kit/core");
|
|
995
1476
|
|
|
996
1477
|
// src/hooks/useCharacterDrain.ts
|
|
997
|
-
var
|
|
1478
|
+
var import_react6 = require("react");
|
|
998
1479
|
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,
|
|
1480
|
+
const [displayed, setDisplayed] = (0, import_react6.useState)("");
|
|
1481
|
+
const indexRef = (0, import_react6.useRef)(0);
|
|
1482
|
+
const lastTimeRef = (0, import_react6.useRef)(0);
|
|
1483
|
+
const rafRef = (0, import_react6.useRef)(null);
|
|
1484
|
+
const drainTargetRef = (0, import_react6.useRef)("");
|
|
1485
|
+
const msPerCharRef = (0, import_react6.useRef)(msPerChar);
|
|
1005
1486
|
msPerCharRef.current = msPerChar;
|
|
1006
1487
|
if (target !== "") {
|
|
1007
1488
|
drainTargetRef.current = target;
|
|
1008
1489
|
}
|
|
1009
1490
|
const drainTarget = drainTargetRef.current;
|
|
1010
1491
|
const isDraining = displayed.length < drainTarget.length;
|
|
1011
|
-
const tickRef = (0,
|
|
1492
|
+
const tickRef = (0, import_react6.useRef)(() => {
|
|
1012
1493
|
});
|
|
1013
1494
|
tickRef.current = (now) => {
|
|
1014
1495
|
const currentTarget = drainTargetRef.current;
|
|
@@ -1020,7 +1501,10 @@ function useCharacterDrain(target, msPerChar = 15) {
|
|
|
1020
1501
|
const elapsed = now - lastTimeRef.current;
|
|
1021
1502
|
const charsToAdvance = Math.floor(elapsed / msPerCharRef.current);
|
|
1022
1503
|
if (charsToAdvance > 0 && indexRef.current < currentTarget.length) {
|
|
1023
|
-
|
|
1504
|
+
let nextIndex = Math.min(indexRef.current + charsToAdvance, currentTarget.length);
|
|
1505
|
+
while (nextIndex < currentTarget.length && currentTarget[nextIndex - 1].trim() === "") {
|
|
1506
|
+
nextIndex++;
|
|
1507
|
+
}
|
|
1024
1508
|
indexRef.current = nextIndex;
|
|
1025
1509
|
lastTimeRef.current = now;
|
|
1026
1510
|
setDisplayed(currentTarget.slice(0, nextIndex));
|
|
@@ -1031,12 +1515,12 @@ function useCharacterDrain(target, msPerChar = 15) {
|
|
|
1031
1515
|
rafRef.current = null;
|
|
1032
1516
|
}
|
|
1033
1517
|
};
|
|
1034
|
-
(0,
|
|
1518
|
+
(0, import_react6.useEffect)(() => {
|
|
1035
1519
|
if (drainTargetRef.current !== "" && indexRef.current < drainTargetRef.current.length && rafRef.current === null) {
|
|
1036
1520
|
rafRef.current = requestAnimationFrame((t) => tickRef.current(t));
|
|
1037
1521
|
}
|
|
1038
1522
|
}, [drainTarget]);
|
|
1039
|
-
(0,
|
|
1523
|
+
(0, import_react6.useEffect)(() => {
|
|
1040
1524
|
if (target === "" && !isDraining && displayed !== "") {
|
|
1041
1525
|
indexRef.current = 0;
|
|
1042
1526
|
lastTimeRef.current = 0;
|
|
@@ -1044,7 +1528,7 @@ function useCharacterDrain(target, msPerChar = 15) {
|
|
|
1044
1528
|
setDisplayed("");
|
|
1045
1529
|
}
|
|
1046
1530
|
}, [target, isDraining, displayed]);
|
|
1047
|
-
(0,
|
|
1531
|
+
(0, import_react6.useEffect)(() => {
|
|
1048
1532
|
return () => {
|
|
1049
1533
|
if (rafRef.current !== null) {
|
|
1050
1534
|
cancelAnimationFrame(rafRef.current);
|
|
@@ -1065,51 +1549,78 @@ var phaseLabels = {
|
|
|
1065
1549
|
generating: "Writing...",
|
|
1066
1550
|
verifying: "Verifying..."
|
|
1067
1551
|
};
|
|
1552
|
+
var CURSOR_STYLES = `
|
|
1553
|
+
.sk-streaming-cursor > :not(ul,ol,blockquote,div:has(table)):last-child::after,
|
|
1554
|
+
.sk-streaming-cursor > :is(ul,ol):last-child > li:last-child::after,
|
|
1555
|
+
.sk-streaming-cursor > blockquote:last-child > p:last-child::after,
|
|
1556
|
+
.sk-streaming-cursor > div:has(table):last-child table tbody tr:last-child td:last-child::after {
|
|
1557
|
+
content: "";
|
|
1558
|
+
display: inline-block;
|
|
1559
|
+
width: 2px;
|
|
1560
|
+
height: 1em;
|
|
1561
|
+
background: var(--color-accent, #38bdf8);
|
|
1562
|
+
animation: sk-cursor-blink 0.8s steps(1) infinite;
|
|
1563
|
+
margin-left: 2px;
|
|
1564
|
+
vertical-align: text-bottom;
|
|
1565
|
+
}
|
|
1566
|
+
@keyframes sk-cursor-blink {
|
|
1567
|
+
0%, 60% { opacity: 1; }
|
|
1568
|
+
61%, 100% { opacity: 0; }
|
|
1569
|
+
}
|
|
1570
|
+
`;
|
|
1068
1571
|
function StreamingMessage({
|
|
1069
1572
|
stream,
|
|
1070
1573
|
onComplete,
|
|
1574
|
+
onDraining,
|
|
1071
1575
|
showPhases = true,
|
|
1072
1576
|
className
|
|
1073
1577
|
}) {
|
|
1074
|
-
const onCompleteRef = (0,
|
|
1578
|
+
const onCompleteRef = (0, import_react7.useRef)(onComplete);
|
|
1075
1579
|
onCompleteRef.current = onComplete;
|
|
1076
|
-
const
|
|
1077
|
-
|
|
1580
|
+
const onDrainingRef = (0, import_react7.useRef)(onDraining);
|
|
1581
|
+
onDrainingRef.current = onDraining;
|
|
1582
|
+
const wasActiveRef = (0, import_react7.useRef)(stream.active);
|
|
1583
|
+
(0, import_react7.useEffect)(() => {
|
|
1078
1584
|
if (wasActiveRef.current && !stream.active) {
|
|
1079
1585
|
onCompleteRef.current?.();
|
|
1080
1586
|
}
|
|
1081
1587
|
wasActiveRef.current = stream.active;
|
|
1082
1588
|
}, [stream.active]);
|
|
1083
1589
|
const phaseLabel = phaseLabels[stream.phase];
|
|
1084
|
-
const { displayed:
|
|
1085
|
-
|
|
1590
|
+
const { displayed: rawDisplayed, isDraining } = useCharacterDrain(stream.content);
|
|
1591
|
+
const displayedContent = stream.active || isDraining ? rawDisplayed.trimEnd() : rawDisplayed;
|
|
1592
|
+
(0, import_react7.useEffect)(() => {
|
|
1593
|
+
onDrainingRef.current?.(isDraining);
|
|
1594
|
+
}, [isDraining]);
|
|
1595
|
+
const agentLabel = stream.agent ? stream.agent.replace("_agent", "").replace("_", " ") : null;
|
|
1596
|
+
const showPhaseIndicator = showPhases && stream.active && stream.phase !== "idle" && !displayedContent;
|
|
1597
|
+
const showCursor = (stream.active || isDraining) && !!displayedContent;
|
|
1598
|
+
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
1599
|
/* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { "aria-live": "assertive", className: "sr-only", children: [
|
|
1087
1600
|
stream.active && stream.phase !== "idle" && "Response started",
|
|
1088
1601
|
!stream.active && stream.content && "Response complete"
|
|
1089
1602
|
] }),
|
|
1603
|
+
showCursor && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("style", { children: CURSOR_STYLES }),
|
|
1604
|
+
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
1605
|
/* @__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
|
-
|
|
1606
|
+
showPhaseIndicator && /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
|
|
1092
1607
|
"div",
|
|
1093
1608
|
{
|
|
1094
|
-
className: "flex items-center gap-2
|
|
1609
|
+
className: "flex items-center gap-2 text-sm text-text-secondary",
|
|
1095
1610
|
"data-testid": "phase-indicator",
|
|
1096
1611
|
children: [
|
|
1097
|
-
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_core3.
|
|
1612
|
+
/* @__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
1613
|
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { children: phaseLabel })
|
|
1099
1614
|
]
|
|
1100
1615
|
}
|
|
1101
1616
|
),
|
|
1102
|
-
/* @__PURE__ */ (0, import_jsx_runtime11.
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
"data-testid": "streaming-cursor"
|
|
1110
|
-
}
|
|
1111
|
-
)
|
|
1112
|
-
] })
|
|
1617
|
+
displayedContent && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
|
|
1618
|
+
ResponseMessage,
|
|
1619
|
+
{
|
|
1620
|
+
content: displayedContent,
|
|
1621
|
+
className: showCursor ? "sk-streaming-cursor" : void 0
|
|
1622
|
+
}
|
|
1623
|
+
)
|
|
1113
1624
|
] })
|
|
1114
1625
|
] });
|
|
1115
1626
|
}
|
|
@@ -1140,7 +1651,7 @@ function AgentChat({
|
|
|
1140
1651
|
return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
|
|
1141
1652
|
"div",
|
|
1142
1653
|
{
|
|
1143
|
-
className: (0,
|
|
1654
|
+
className: (0, import_tailwind_merge9.twMerge)(
|
|
1144
1655
|
"flex flex-col h-full bg-canvas border border-border rounded-xl overflow-hidden",
|
|
1145
1656
|
className
|
|
1146
1657
|
),
|
|
@@ -1176,14 +1687,14 @@ function AgentChat({
|
|
|
1176
1687
|
onQuestionSelect: handleQuestionSelect
|
|
1177
1688
|
}
|
|
1178
1689
|
),
|
|
1179
|
-
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)(MessageComposer, { onSend: handleSend, isLoading: state.isLoading })
|
|
1690
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)(MessageComposer, { onSend: handleSend, onStop: actions.stop, isLoading: state.isLoading })
|
|
1180
1691
|
]
|
|
1181
1692
|
}
|
|
1182
1693
|
);
|
|
1183
1694
|
}
|
|
1184
1695
|
|
|
1185
1696
|
// src/chat/ConversationList/ConversationList.tsx
|
|
1186
|
-
var
|
|
1697
|
+
var import_tailwind_merge10 = require("tailwind-merge");
|
|
1187
1698
|
var import_jsx_runtime13 = require("react/jsx-runtime");
|
|
1188
1699
|
function ConversationList({
|
|
1189
1700
|
conversations,
|
|
@@ -1197,14 +1708,14 @@ function ConversationList({
|
|
|
1197
1708
|
"nav",
|
|
1198
1709
|
{
|
|
1199
1710
|
"aria-label": "Conversation list",
|
|
1200
|
-
className: (0,
|
|
1711
|
+
className: (0, import_tailwind_merge10.twMerge)("flex flex-col flex-1 min-h-0", className),
|
|
1201
1712
|
children: [
|
|
1202
|
-
onNew && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { className: "
|
|
1713
|
+
onNew && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { className: "px-5 pt-1 pb-3 border-b border-border", children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
|
|
1203
1714
|
"button",
|
|
1204
1715
|
{
|
|
1205
1716
|
type: "button",
|
|
1206
1717
|
onClick: onNew,
|
|
1207
|
-
className: "w-full px-4 py-2
|
|
1718
|
+
className: "w-full px-4 py-2 rounded-lg text-sm font-medium border border-border text-text-secondary hover:text-text-primary hover:bg-surface hover:border-border-strong transition-colors duration-150",
|
|
1208
1719
|
children: "New conversation"
|
|
1209
1720
|
}
|
|
1210
1721
|
) }),
|
|
@@ -1214,10 +1725,10 @@ function ConversationList({
|
|
|
1214
1725
|
return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
|
|
1215
1726
|
"li",
|
|
1216
1727
|
{
|
|
1217
|
-
className: (0,
|
|
1218
|
-
"flex items-start
|
|
1219
|
-
"hover:bg-surface",
|
|
1220
|
-
isActive
|
|
1728
|
+
className: (0, import_tailwind_merge10.twMerge)(
|
|
1729
|
+
"flex items-start transition-colors duration-150",
|
|
1730
|
+
"hover:bg-surface-raised",
|
|
1731
|
+
isActive ? "bg-accent-subtlest border-l-[3px] border-l-accent" : "border-l-[3px] border-l-transparent"
|
|
1221
1732
|
),
|
|
1222
1733
|
children: [
|
|
1223
1734
|
/* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
|
|
@@ -1226,10 +1737,10 @@ function ConversationList({
|
|
|
1226
1737
|
type: "button",
|
|
1227
1738
|
onClick: () => onSelect(conversation.id),
|
|
1228
1739
|
"aria-current": isActive ? "true" : void 0,
|
|
1229
|
-
className: "flex-1 min-w-0 text-left px-
|
|
1740
|
+
className: "flex-1 min-w-0 text-left px-5 py-2.5",
|
|
1230
1741
|
children: [
|
|
1231
|
-
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { className: "text-sm font-medium text-
|
|
1232
|
-
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { className: "text-xs text-
|
|
1742
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { className: "text-sm font-medium text-text-primary truncate", children: conversation.title }),
|
|
1743
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { className: "text-xs text-text-muted truncate mt-0.5 leading-relaxed", children: conversation.lastMessage })
|
|
1233
1744
|
]
|
|
1234
1745
|
}
|
|
1235
1746
|
),
|
|
@@ -1239,7 +1750,7 @@ function ConversationList({
|
|
|
1239
1750
|
type: "button",
|
|
1240
1751
|
onClick: () => onDelete(conversation.id),
|
|
1241
1752
|
"aria-label": `Delete ${conversation.title}`,
|
|
1242
|
-
className: "shrink-0 p-1.5 m-2 rounded-lg text-
|
|
1753
|
+
className: "shrink-0 p-1.5 m-2 rounded-lg text-text-muted hover:text-status-error hover:bg-status-error/10 transition-colors duration-150",
|
|
1243
1754
|
children: /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
|
|
1244
1755
|
"svg",
|
|
1245
1756
|
{
|
|
@@ -1266,7 +1777,7 @@ function ConversationList({
|
|
|
1266
1777
|
conversation.id
|
|
1267
1778
|
);
|
|
1268
1779
|
}),
|
|
1269
|
-
conversations.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("li", { className: "px-
|
|
1780
|
+
conversations.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("li", { className: "px-5 py-12 text-center", children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { className: "text-sm text-text-muted font-body", children: "No conversations yet" }) })
|
|
1270
1781
|
] })
|
|
1271
1782
|
]
|
|
1272
1783
|
}
|
|
@@ -1286,8 +1797,8 @@ function AgentFullPage({
|
|
|
1286
1797
|
onNewConversation,
|
|
1287
1798
|
className
|
|
1288
1799
|
}) {
|
|
1289
|
-
const [sidebarOpen, setSidebarOpen] = (0,
|
|
1290
|
-
const handleSelect = (0,
|
|
1800
|
+
const [sidebarOpen, setSidebarOpen] = (0, import_react8.useState)(false);
|
|
1801
|
+
const handleSelect = (0, import_react8.useCallback)(
|
|
1291
1802
|
(id) => {
|
|
1292
1803
|
onConversationSelect?.(id);
|
|
1293
1804
|
setSidebarOpen(false);
|
|
@@ -1297,7 +1808,7 @@ function AgentFullPage({
|
|
|
1297
1808
|
return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(
|
|
1298
1809
|
"div",
|
|
1299
1810
|
{
|
|
1300
|
-
className: (0,
|
|
1811
|
+
className: (0, import_tailwind_merge11.twMerge)("flex h-screen w-full overflow-hidden bg-brand-dark", className),
|
|
1301
1812
|
"data-testid": "agent-full-page",
|
|
1302
1813
|
children: [
|
|
1303
1814
|
showConversationList && /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(import_jsx_runtime14.Fragment, { children: [
|
|
@@ -1312,7 +1823,7 @@ function AgentFullPage({
|
|
|
1312
1823
|
/* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
|
|
1313
1824
|
"aside",
|
|
1314
1825
|
{
|
|
1315
|
-
className: (0,
|
|
1826
|
+
className: (0, import_tailwind_merge11.twMerge)(
|
|
1316
1827
|
"bg-brand-dark border-r border-brand-gold/15 w-72 shrink-0 flex-col z-40",
|
|
1317
1828
|
// Desktop: always visible
|
|
1318
1829
|
"hidden md:flex",
|
|
@@ -1378,8 +1889,8 @@ function AgentFullPage({
|
|
|
1378
1889
|
}
|
|
1379
1890
|
|
|
1380
1891
|
// src/layouts/AgentPanel/AgentPanel.tsx
|
|
1381
|
-
var
|
|
1382
|
-
var
|
|
1892
|
+
var import_tailwind_merge12 = require("tailwind-merge");
|
|
1893
|
+
var import_react9 = require("react");
|
|
1383
1894
|
var import_jsx_runtime15 = require("react/jsx-runtime");
|
|
1384
1895
|
function AgentPanel({
|
|
1385
1896
|
endpoint,
|
|
@@ -1390,8 +1901,8 @@ function AgentPanel({
|
|
|
1390
1901
|
title = "Chat",
|
|
1391
1902
|
className
|
|
1392
1903
|
}) {
|
|
1393
|
-
const panelRef = (0,
|
|
1394
|
-
(0,
|
|
1904
|
+
const panelRef = (0, import_react9.useRef)(null);
|
|
1905
|
+
(0, import_react9.useEffect)(() => {
|
|
1395
1906
|
if (!isOpen) return;
|
|
1396
1907
|
const handleKeyDown = (e) => {
|
|
1397
1908
|
if (e.key === "Escape") onClose();
|
|
@@ -1403,13 +1914,13 @@ function AgentPanel({
|
|
|
1403
1914
|
return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(
|
|
1404
1915
|
"div",
|
|
1405
1916
|
{
|
|
1406
|
-
className: (0,
|
|
1917
|
+
className: (0, import_tailwind_merge12.twMerge)("fixed inset-0 z-50", !isOpen && "pointer-events-none"),
|
|
1407
1918
|
"aria-hidden": !isOpen,
|
|
1408
1919
|
children: [
|
|
1409
1920
|
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
|
|
1410
1921
|
"div",
|
|
1411
1922
|
{
|
|
1412
|
-
className: (0,
|
|
1923
|
+
className: (0, import_tailwind_merge12.twMerge)(
|
|
1413
1924
|
"fixed inset-0 transition-opacity duration-300",
|
|
1414
1925
|
isOpen ? "opacity-100 bg-brand-dark/70 backdrop-blur-sm pointer-events-auto" : "opacity-0 pointer-events-none"
|
|
1415
1926
|
),
|
|
@@ -1425,7 +1936,7 @@ function AgentPanel({
|
|
|
1425
1936
|
"aria-label": title,
|
|
1426
1937
|
"aria-modal": isOpen ? "true" : void 0,
|
|
1427
1938
|
style: { width: widthStyle, maxWidth: "100vw" },
|
|
1428
|
-
className: (0,
|
|
1939
|
+
className: (0, import_tailwind_merge12.twMerge)(
|
|
1429
1940
|
"fixed top-0 h-full flex flex-col z-50 bg-brand-dark shadow-card",
|
|
1430
1941
|
"transition-transform duration-300 ease-in-out",
|
|
1431
1942
|
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 +1978,8 @@ function AgentPanel({
|
|
|
1467
1978
|
}
|
|
1468
1979
|
|
|
1469
1980
|
// src/layouts/AgentWidget/AgentWidget.tsx
|
|
1470
|
-
var
|
|
1471
|
-
var
|
|
1981
|
+
var import_tailwind_merge13 = require("tailwind-merge");
|
|
1982
|
+
var import_react10 = require("react");
|
|
1472
1983
|
var import_jsx_runtime16 = require("react/jsx-runtime");
|
|
1473
1984
|
function AgentWidget({
|
|
1474
1985
|
endpoint,
|
|
@@ -1477,8 +1988,8 @@ function AgentWidget({
|
|
|
1477
1988
|
title = "Chat",
|
|
1478
1989
|
className
|
|
1479
1990
|
}) {
|
|
1480
|
-
const [isOpen, setIsOpen] = (0,
|
|
1481
|
-
const toggle = (0,
|
|
1991
|
+
const [isOpen, setIsOpen] = (0, import_react10.useState)(false);
|
|
1992
|
+
const toggle = (0, import_react10.useCallback)(() => {
|
|
1482
1993
|
setIsOpen((prev) => !prev);
|
|
1483
1994
|
}, []);
|
|
1484
1995
|
const positionClasses = position === "bottom-left" ? "left-4 bottom-4" : "right-4 bottom-4";
|
|
@@ -1491,7 +2002,7 @@ function AgentWidget({
|
|
|
1491
2002
|
role: "dialog",
|
|
1492
2003
|
"aria-label": title,
|
|
1493
2004
|
"aria-hidden": !isOpen,
|
|
1494
|
-
className: (0,
|
|
2005
|
+
className: (0, import_tailwind_merge13.twMerge)(
|
|
1495
2006
|
"fixed z-50 flex flex-col",
|
|
1496
2007
|
"w-[min(400px,calc(100vw-2rem))] h-[min(600px,calc(100vh-6rem))]",
|
|
1497
2008
|
"rounded-2xl overflow-hidden border border-brand-gold/15",
|
|
@@ -1538,7 +2049,7 @@ function AgentWidget({
|
|
|
1538
2049
|
onClick: toggle,
|
|
1539
2050
|
"aria-label": isOpen ? "Close chat" : triggerLabel,
|
|
1540
2051
|
"aria-expanded": isOpen,
|
|
1541
|
-
className: (0,
|
|
2052
|
+
className: (0, import_tailwind_merge13.twMerge)(
|
|
1542
2053
|
"fixed z-50 flex items-center justify-center w-14 h-14 rounded-full",
|
|
1543
2054
|
"bg-brand-blue text-brand-cream shadow-glow-cyan",
|
|
1544
2055
|
"hover:bg-brand-cyan hover:shadow-glow-cyan hover:scale-105",
|
|
@@ -1556,7 +2067,7 @@ function AgentWidget({
|
|
|
1556
2067
|
}
|
|
1557
2068
|
|
|
1558
2069
|
// src/layouts/AgentEmbed/AgentEmbed.tsx
|
|
1559
|
-
var
|
|
2070
|
+
var import_tailwind_merge14 = require("tailwind-merge");
|
|
1560
2071
|
var import_jsx_runtime17 = require("react/jsx-runtime");
|
|
1561
2072
|
function AgentEmbed({
|
|
1562
2073
|
endpoint,
|
|
@@ -1566,7 +2077,7 @@ function AgentEmbed({
|
|
|
1566
2077
|
return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
|
|
1567
2078
|
"div",
|
|
1568
2079
|
{
|
|
1569
|
-
className: (0,
|
|
2080
|
+
className: (0, import_tailwind_merge14.twMerge)("w-full h-full min-h-0", className),
|
|
1570
2081
|
"data-testid": "agent-embed",
|
|
1571
2082
|
children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
|
|
1572
2083
|
AgentChat,
|