@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.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
3
|
// src/layouts/AgentFullPage/AgentFullPage.tsx
|
|
4
|
-
import { twMerge as
|
|
5
|
-
import { useState as useState4, useCallback as
|
|
4
|
+
import { twMerge as twMerge11 } from "tailwind-merge";
|
|
5
|
+
import { useState as useState4, useCallback as useCallback4 } from "react";
|
|
6
6
|
|
|
7
7
|
// src/chat/AgentChat/AgentChat.tsx
|
|
8
|
-
import { twMerge as
|
|
8
|
+
import { twMerge as twMerge9 } from "tailwind-merge";
|
|
9
9
|
|
|
10
10
|
// src/hooks/useAgentChat.ts
|
|
11
11
|
import { useReducer, useCallback, useRef } from "react";
|
|
@@ -16,7 +16,8 @@ var initialState = {
|
|
|
16
16
|
error: null,
|
|
17
17
|
inputValue: "",
|
|
18
18
|
streamPhase: "idle",
|
|
19
|
-
streamingContent: ""
|
|
19
|
+
streamingContent: "",
|
|
20
|
+
streamingAgent: null
|
|
20
21
|
};
|
|
21
22
|
function reducer(state, action) {
|
|
22
23
|
switch (action.type) {
|
|
@@ -30,12 +31,15 @@ function reducer(state, action) {
|
|
|
30
31
|
error: null,
|
|
31
32
|
inputValue: "",
|
|
32
33
|
streamPhase: "thinking",
|
|
33
|
-
streamingContent: ""
|
|
34
|
+
streamingContent: "",
|
|
35
|
+
streamingAgent: null
|
|
34
36
|
};
|
|
35
37
|
case "STREAM_PHASE":
|
|
36
38
|
return { ...state, streamPhase: action.phase };
|
|
37
39
|
case "STREAM_CONTENT":
|
|
38
40
|
return { ...state, streamingContent: state.streamingContent + action.content };
|
|
41
|
+
case "STREAM_AGENT":
|
|
42
|
+
return { ...state, streamingAgent: action.agent };
|
|
39
43
|
case "SEND_SUCCESS":
|
|
40
44
|
return {
|
|
41
45
|
...state,
|
|
@@ -51,7 +55,8 @@ function reducer(state, action) {
|
|
|
51
55
|
isLoading: false,
|
|
52
56
|
error: action.error,
|
|
53
57
|
streamPhase: "idle",
|
|
54
|
-
streamingContent: ""
|
|
58
|
+
streamingContent: "",
|
|
59
|
+
streamingAgent: null
|
|
55
60
|
};
|
|
56
61
|
case "LOAD_CONVERSATION":
|
|
57
62
|
return {
|
|
@@ -77,107 +82,142 @@ function useAgentChat(config) {
|
|
|
77
82
|
const configRef = useRef(config);
|
|
78
83
|
configRef.current = config;
|
|
79
84
|
const lastUserMessageRef = useRef(null);
|
|
85
|
+
const lastUserAttachmentsRef = useRef(void 0);
|
|
80
86
|
const sendMessage = useCallback(
|
|
81
|
-
async (content) => {
|
|
82
|
-
const { apiUrl, streamPath = "/chat/stream", headers
|
|
87
|
+
async (content, attachments) => {
|
|
88
|
+
const { apiUrl, streamPath = "/chat/stream", headers: headersOrFn, timeout = 3e4, bodyExtra } = configRef.current;
|
|
89
|
+
const headers = typeof headersOrFn === "function" ? await headersOrFn() : headersOrFn ?? {};
|
|
83
90
|
lastUserMessageRef.current = content;
|
|
91
|
+
lastUserAttachmentsRef.current = attachments;
|
|
84
92
|
const userMessage = {
|
|
85
93
|
id: generateMessageId(),
|
|
86
94
|
role: "user",
|
|
87
95
|
content,
|
|
96
|
+
attachments,
|
|
88
97
|
timestamp: /* @__PURE__ */ new Date()
|
|
89
98
|
};
|
|
90
99
|
dispatch({ type: "SEND_START", message: userMessage });
|
|
91
100
|
const controller = new AbortController();
|
|
92
101
|
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
93
102
|
try {
|
|
94
|
-
const
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
error: {
|
|
112
|
-
code: "API_ERROR",
|
|
113
|
-
message: `HTTP ${response.status}: ${response.statusText}`,
|
|
114
|
-
retryable: response.status >= 500
|
|
115
|
-
}
|
|
116
|
-
});
|
|
117
|
-
return;
|
|
103
|
+
const url = `${apiUrl}${streamPath}`;
|
|
104
|
+
const mergedHeaders = {
|
|
105
|
+
"Content-Type": "application/json",
|
|
106
|
+
Accept: "text/event-stream",
|
|
107
|
+
...headers
|
|
108
|
+
};
|
|
109
|
+
const requestBody = {
|
|
110
|
+
message: content,
|
|
111
|
+
conversation_id: state.conversationId,
|
|
112
|
+
...bodyExtra
|
|
113
|
+
};
|
|
114
|
+
if (attachments && attachments.length > 0) {
|
|
115
|
+
requestBody.attachments = attachments.map((a) => ({
|
|
116
|
+
filename: a.filename,
|
|
117
|
+
content_type: a.content_type,
|
|
118
|
+
data: a.data
|
|
119
|
+
}));
|
|
118
120
|
}
|
|
119
|
-
const
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
121
|
+
const body = JSON.stringify(requestBody);
|
|
122
|
+
const ctx = {
|
|
123
|
+
accumulatedContent: "",
|
|
124
|
+
agentResponse: null,
|
|
125
|
+
capturedAgent: null,
|
|
126
|
+
capturedConversationId: null,
|
|
127
|
+
hadStreamError: false
|
|
128
|
+
};
|
|
129
|
+
const handleEvent = (event) => {
|
|
130
|
+
switch (event.type) {
|
|
131
|
+
case "agent":
|
|
132
|
+
ctx.capturedAgent = event.agent;
|
|
133
|
+
dispatch({ type: "STREAM_AGENT", agent: ctx.capturedAgent });
|
|
134
|
+
break;
|
|
135
|
+
case "phase":
|
|
136
|
+
dispatch({ type: "STREAM_PHASE", phase: event.phase });
|
|
137
|
+
break;
|
|
138
|
+
case "delta":
|
|
139
|
+
ctx.accumulatedContent += event.content;
|
|
140
|
+
dispatch({ type: "STREAM_CONTENT", content: event.content });
|
|
141
|
+
break;
|
|
142
|
+
case "done":
|
|
143
|
+
ctx.agentResponse = event.response;
|
|
144
|
+
ctx.capturedConversationId = event.conversation_id ?? null;
|
|
145
|
+
break;
|
|
146
|
+
case "error":
|
|
147
|
+
ctx.hadStreamError = true;
|
|
148
|
+
dispatch({ type: "SEND_ERROR", error: event.error });
|
|
149
|
+
break;
|
|
150
|
+
}
|
|
151
|
+
};
|
|
152
|
+
const { streamAdapter } = configRef.current;
|
|
153
|
+
if (streamAdapter) {
|
|
154
|
+
await streamAdapter(
|
|
155
|
+
url,
|
|
156
|
+
{ method: "POST", headers: mergedHeaders, body, signal: controller.signal },
|
|
157
|
+
handleEvent
|
|
158
|
+
);
|
|
159
|
+
clearTimeout(timeoutId);
|
|
160
|
+
} else {
|
|
161
|
+
const response = await fetch(url, {
|
|
162
|
+
method: "POST",
|
|
163
|
+
headers: mergedHeaders,
|
|
164
|
+
body,
|
|
165
|
+
signal: controller.signal
|
|
124
166
|
});
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
const
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
dispatch({ type: "SEND_ERROR", error: event.error });
|
|
162
|
-
return;
|
|
167
|
+
clearTimeout(timeoutId);
|
|
168
|
+
if (!response.ok) {
|
|
169
|
+
dispatch({
|
|
170
|
+
type: "SEND_ERROR",
|
|
171
|
+
error: {
|
|
172
|
+
code: "API_ERROR",
|
|
173
|
+
message: `HTTP ${response.status}: ${response.statusText}`,
|
|
174
|
+
retryable: response.status >= 500
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
const reader = response.body?.getReader();
|
|
180
|
+
if (!reader) {
|
|
181
|
+
dispatch({
|
|
182
|
+
type: "SEND_ERROR",
|
|
183
|
+
error: { code: "STREAM_ERROR", message: "No response body", retryable: true }
|
|
184
|
+
});
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
const decoder = new TextDecoder();
|
|
188
|
+
let buffer = "";
|
|
189
|
+
while (true) {
|
|
190
|
+
const { done, value } = await reader.read();
|
|
191
|
+
if (done) break;
|
|
192
|
+
buffer += decoder.decode(value, { stream: true });
|
|
193
|
+
const lines = buffer.split("\n");
|
|
194
|
+
buffer = lines.pop() ?? "";
|
|
195
|
+
for (const line of lines) {
|
|
196
|
+
if (!line.startsWith("data: ")) continue;
|
|
197
|
+
const data = line.slice(6).trim();
|
|
198
|
+
if (data === "[DONE]") continue;
|
|
199
|
+
try {
|
|
200
|
+
const event = JSON.parse(data);
|
|
201
|
+
handleEvent(event);
|
|
202
|
+
} catch {
|
|
163
203
|
}
|
|
164
|
-
} catch {
|
|
165
204
|
}
|
|
166
205
|
}
|
|
167
206
|
}
|
|
207
|
+
if (ctx.hadStreamError) return;
|
|
168
208
|
const assistantMessage = {
|
|
169
209
|
id: generateMessageId(),
|
|
170
210
|
role: "assistant",
|
|
171
|
-
content: agentResponse?.message ?? accumulatedContent,
|
|
172
|
-
response: agentResponse ?? void 0,
|
|
173
|
-
agent: capturedAgent ?? void 0,
|
|
211
|
+
content: ctx.agentResponse?.message ?? ctx.accumulatedContent,
|
|
212
|
+
response: ctx.agentResponse ?? void 0,
|
|
213
|
+
agent: ctx.capturedAgent ?? void 0,
|
|
174
214
|
timestamp: /* @__PURE__ */ new Date()
|
|
175
215
|
};
|
|
176
216
|
dispatch({
|
|
177
217
|
type: "SEND_SUCCESS",
|
|
178
218
|
message: assistantMessage,
|
|
179
|
-
streamingContent: accumulatedContent,
|
|
180
|
-
conversationId: capturedConversationId
|
|
219
|
+
streamingContent: ctx.accumulatedContent,
|
|
220
|
+
conversationId: ctx.capturedConversationId
|
|
181
221
|
});
|
|
182
222
|
} catch (err) {
|
|
183
223
|
clearTimeout(timeoutId);
|
|
@@ -208,7 +248,8 @@ function useAgentChat(config) {
|
|
|
208
248
|
}, []);
|
|
209
249
|
const submitFeedback = useCallback(
|
|
210
250
|
async (messageId, rating, comment) => {
|
|
211
|
-
const { apiUrl, feedbackPath = "/feedback", headers
|
|
251
|
+
const { apiUrl, feedbackPath = "/feedback", headers: headersOrFn } = configRef.current;
|
|
252
|
+
const headers = typeof headersOrFn === "function" ? await headersOrFn() : headersOrFn ?? {};
|
|
212
253
|
await fetch(`${apiUrl}${feedbackPath}`, {
|
|
213
254
|
method: "POST",
|
|
214
255
|
headers: { "Content-Type": "application/json", ...headers },
|
|
@@ -219,12 +260,13 @@ function useAgentChat(config) {
|
|
|
219
260
|
);
|
|
220
261
|
const retry = useCallback(async () => {
|
|
221
262
|
if (lastUserMessageRef.current) {
|
|
222
|
-
await sendMessage(lastUserMessageRef.current);
|
|
263
|
+
await sendMessage(lastUserMessageRef.current, lastUserAttachmentsRef.current);
|
|
223
264
|
}
|
|
224
265
|
}, [sendMessage]);
|
|
225
266
|
const reset = useCallback(() => {
|
|
226
267
|
dispatch({ type: "RESET" });
|
|
227
268
|
lastUserMessageRef.current = null;
|
|
269
|
+
lastUserAttachmentsRef.current = void 0;
|
|
228
270
|
}, []);
|
|
229
271
|
const actions = {
|
|
230
272
|
sendMessage,
|
|
@@ -239,7 +281,7 @@ function useAgentChat(config) {
|
|
|
239
281
|
|
|
240
282
|
// src/chat/MessageThread/MessageThread.tsx
|
|
241
283
|
import { twMerge as twMerge5 } from "tailwind-merge";
|
|
242
|
-
import { useEffect, useRef as useRef2 } from "react";
|
|
284
|
+
import { useCallback as useCallback2, useEffect, useRef as useRef2 } from "react";
|
|
243
285
|
|
|
244
286
|
// src/chat/MessageBubble/MessageBubble.tsx
|
|
245
287
|
import { twMerge as twMerge4 } from "tailwind-merge";
|
|
@@ -248,6 +290,7 @@ import { twMerge as twMerge4 } from "tailwind-merge";
|
|
|
248
290
|
import { Badge as Badge2 } from "@surf-kit/core";
|
|
249
291
|
|
|
250
292
|
// src/response/ResponseMessage/ResponseMessage.tsx
|
|
293
|
+
import React from "react";
|
|
251
294
|
import ReactMarkdown from "react-markdown";
|
|
252
295
|
import rehypeSanitize from "rehype-sanitize";
|
|
253
296
|
import { twMerge } from "tailwind-merge";
|
|
@@ -272,6 +315,7 @@ function ResponseMessage({ content, className }) {
|
|
|
272
315
|
"[&_h3]:text-sm [&_h3]:font-semibold [&_h3]:text-accent [&_h3]:mt-2 [&_h3]:mb-1",
|
|
273
316
|
"[&_code]:bg-surface-raised [&_code]:text-accent [&_code]:px-1.5 [&_code]:py-0.5 [&_code]:rounded [&_code]:text-xs [&_code]:font-mono",
|
|
274
317
|
"[&_pre]:bg-surface-raised [&_pre]:border [&_pre]:border-border [&_pre]:rounded-xl [&_pre]:p-4 [&_pre]:overflow-x-auto",
|
|
318
|
+
"[&_hr]:my-3 [&_hr]:border-border",
|
|
275
319
|
"[&_blockquote]:border-l-2 [&_blockquote]:border-border-strong [&_blockquote]:pl-4 [&_blockquote]:text-text-secondary",
|
|
276
320
|
"[&_a]:text-accent [&_a]:underline-offset-2 [&_a]:hover:text-accent/80",
|
|
277
321
|
className
|
|
@@ -287,11 +331,24 @@ function ResponseMessage({ content, className }) {
|
|
|
287
331
|
p: ({ children }) => /* @__PURE__ */ jsx("p", { className: "my-2", children }),
|
|
288
332
|
ul: ({ children }) => /* @__PURE__ */ jsx("ul", { className: "my-2 list-disc pl-6", children }),
|
|
289
333
|
ol: ({ children }) => /* @__PURE__ */ jsx("ol", { className: "my-2 list-decimal pl-6", children }),
|
|
290
|
-
li: ({ children }) =>
|
|
334
|
+
li: ({ children, ...props }) => {
|
|
335
|
+
let content2 = children;
|
|
336
|
+
if (props.ordered) {
|
|
337
|
+
content2 = React.Children.map(children, (child, i) => {
|
|
338
|
+
if (i === 0 && typeof child === "string") {
|
|
339
|
+
return child.replace(/^\d+[.)]\s*/, "");
|
|
340
|
+
}
|
|
341
|
+
return child;
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
return /* @__PURE__ */ jsx("li", { className: "my-1", children: content2 });
|
|
345
|
+
},
|
|
291
346
|
strong: ({ children }) => /* @__PURE__ */ jsx("strong", { className: "font-semibold", children }),
|
|
347
|
+
em: ({ children }) => /* @__PURE__ */ jsx("em", { className: "italic text-text-secondary", children }),
|
|
292
348
|
h1: ({ children }) => /* @__PURE__ */ jsx("h1", { className: "text-base font-bold mt-4 mb-2", children }),
|
|
293
349
|
h2: ({ children }) => /* @__PURE__ */ jsx("h2", { className: "text-sm font-bold mt-3 mb-1", children }),
|
|
294
350
|
h3: ({ children }) => /* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold mt-2 mb-1", children }),
|
|
351
|
+
hr: () => /* @__PURE__ */ jsx("hr", { className: "my-3 border-border" }),
|
|
295
352
|
code: ({ children }) => /* @__PURE__ */ jsx("code", { className: "bg-surface-sunken rounded px-1 py-0.5 text-xs font-mono", children })
|
|
296
353
|
},
|
|
297
354
|
children: normalizeMarkdownLists(content)
|
|
@@ -427,7 +484,14 @@ function renderWarning(data) {
|
|
|
427
484
|
}
|
|
428
485
|
);
|
|
429
486
|
}
|
|
430
|
-
function StructuredResponse({ uiHint, data, className }) {
|
|
487
|
+
function StructuredResponse({ uiHint, data: rawData, className }) {
|
|
488
|
+
const data = typeof rawData === "string" ? (() => {
|
|
489
|
+
try {
|
|
490
|
+
return JSON.parse(rawData);
|
|
491
|
+
} catch {
|
|
492
|
+
return null;
|
|
493
|
+
}
|
|
494
|
+
})() : rawData;
|
|
431
495
|
if (!data) return null;
|
|
432
496
|
let content;
|
|
433
497
|
switch (uiHint) {
|
|
@@ -507,7 +571,36 @@ function SourceCard({ source, variant = "compact", onNavigate, className }) {
|
|
|
507
571
|
children: [
|
|
508
572
|
/* @__PURE__ */ jsxs2("div", { className: "flex items-start justify-between gap-2", children: [
|
|
509
573
|
/* @__PURE__ */ jsxs2("div", { className: "flex-1 min-w-0", children: [
|
|
510
|
-
/* @__PURE__ */
|
|
574
|
+
source.url ? /* @__PURE__ */ jsxs2(
|
|
575
|
+
"a",
|
|
576
|
+
{
|
|
577
|
+
href: source.url,
|
|
578
|
+
target: "_blank",
|
|
579
|
+
rel: "noopener noreferrer",
|
|
580
|
+
className: "text-sm font-medium text-accent hover:underline truncate block",
|
|
581
|
+
onClick: (e) => e.stopPropagation(),
|
|
582
|
+
children: [
|
|
583
|
+
source.title,
|
|
584
|
+
/* @__PURE__ */ jsxs2(
|
|
585
|
+
"svg",
|
|
586
|
+
{
|
|
587
|
+
className: "inline-block ml-1 w-3 h-3 opacity-60",
|
|
588
|
+
viewBox: "0 0 24 24",
|
|
589
|
+
fill: "none",
|
|
590
|
+
stroke: "currentColor",
|
|
591
|
+
strokeWidth: "2",
|
|
592
|
+
strokeLinecap: "round",
|
|
593
|
+
strokeLinejoin: "round",
|
|
594
|
+
children: [
|
|
595
|
+
/* @__PURE__ */ jsx3("path", { d: "M18 13v6a2 2 0 01-2 2H5a2 2 0 01-2-2V8a2 2 0 012-2h6" }),
|
|
596
|
+
/* @__PURE__ */ jsx3("polyline", { points: "15 3 21 3 21 9" }),
|
|
597
|
+
/* @__PURE__ */ jsx3("line", { x1: "10", y1: "14", x2: "21", y2: "3" })
|
|
598
|
+
]
|
|
599
|
+
}
|
|
600
|
+
)
|
|
601
|
+
]
|
|
602
|
+
}
|
|
603
|
+
) : /* @__PURE__ */ jsx3("p", { className: "text-sm font-medium text-text-primary truncate", children: source.title }),
|
|
511
604
|
source.section && /* @__PURE__ */ jsx3("p", { className: "text-[11px] font-semibold uppercase tracking-wider text-text-secondary truncate mt-0.5", children: source.section })
|
|
512
605
|
] }),
|
|
513
606
|
/* @__PURE__ */ jsx3(
|
|
@@ -641,13 +734,16 @@ function AgentResponse({
|
|
|
641
734
|
}) {
|
|
642
735
|
return /* @__PURE__ */ jsxs4("div", { className: `flex flex-col gap-4 ${className ?? ""}`, "data-testid": "agent-response", children: [
|
|
643
736
|
/* @__PURE__ */ jsx6(ResponseMessage, { content: response.message }),
|
|
644
|
-
response.ui_hint !== "text" && response.structured_data &&
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
737
|
+
response.ui_hint !== "text" && response.structured_data && (() => {
|
|
738
|
+
const parsed = typeof response.structured_data === "string" ? (() => {
|
|
739
|
+
try {
|
|
740
|
+
return JSON.parse(response.structured_data);
|
|
741
|
+
} catch {
|
|
742
|
+
return null;
|
|
743
|
+
}
|
|
744
|
+
})() : response.structured_data;
|
|
745
|
+
return parsed ? /* @__PURE__ */ jsx6(StructuredResponse, { uiHint: response.ui_hint, data: parsed }) : null;
|
|
746
|
+
})(),
|
|
651
747
|
(showConfidence || showVerification) && /* @__PURE__ */ jsxs4("div", { className: "flex flex-wrap items-center gap-2 mt-1", "data-testid": "response-meta", children: [
|
|
652
748
|
showConfidence && /* @__PURE__ */ jsxs4(
|
|
653
749
|
Badge2,
|
|
@@ -698,6 +794,31 @@ function AgentResponse({
|
|
|
698
794
|
|
|
699
795
|
// src/chat/MessageBubble/MessageBubble.tsx
|
|
700
796
|
import { jsx as jsx7, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
797
|
+
function DocumentIcon() {
|
|
798
|
+
return /* @__PURE__ */ jsxs5("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round", children: [
|
|
799
|
+
/* @__PURE__ */ jsx7("path", { d: "M14 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V8z" }),
|
|
800
|
+
/* @__PURE__ */ jsx7("polyline", { points: "14 2 14 8 20 8" }),
|
|
801
|
+
/* @__PURE__ */ jsx7("line", { x1: "16", y1: "13", x2: "8", y2: "13" }),
|
|
802
|
+
/* @__PURE__ */ jsx7("line", { x1: "16", y1: "17", x2: "8", y2: "17" })
|
|
803
|
+
] });
|
|
804
|
+
}
|
|
805
|
+
function AttachmentThumbnail({ attachment }) {
|
|
806
|
+
const isImage = attachment.content_type.startsWith("image/");
|
|
807
|
+
if (isImage) {
|
|
808
|
+
return /* @__PURE__ */ jsx7("div", { className: "rounded-lg overflow-hidden border border-black/10 max-w-[240px]", children: /* @__PURE__ */ jsx7(
|
|
809
|
+
"img",
|
|
810
|
+
{
|
|
811
|
+
src: attachment.preview_url ?? `data:${attachment.content_type};base64,${attachment.data}`,
|
|
812
|
+
alt: attachment.filename,
|
|
813
|
+
className: "max-w-full max-h-[200px] object-contain"
|
|
814
|
+
}
|
|
815
|
+
) });
|
|
816
|
+
}
|
|
817
|
+
return /* @__PURE__ */ jsxs5("div", { className: "flex items-center gap-2 px-3 py-2 rounded-lg border border-black/10 bg-black/5", children: [
|
|
818
|
+
/* @__PURE__ */ jsx7(DocumentIcon, {}),
|
|
819
|
+
/* @__PURE__ */ jsx7("span", { className: "text-xs truncate max-w-[160px]", children: attachment.filename })
|
|
820
|
+
] });
|
|
821
|
+
}
|
|
701
822
|
function MessageBubble({
|
|
702
823
|
message,
|
|
703
824
|
showAgent,
|
|
@@ -705,23 +826,29 @@ function MessageBubble({
|
|
|
705
826
|
showConfidence = true,
|
|
706
827
|
showVerification = true,
|
|
707
828
|
animated = true,
|
|
829
|
+
userBubbleClassName,
|
|
708
830
|
className
|
|
709
831
|
}) {
|
|
710
832
|
const isUser = message.role === "user";
|
|
833
|
+
const hasAttachments = message.attachments && message.attachments.length > 0;
|
|
711
834
|
if (isUser) {
|
|
712
835
|
return /* @__PURE__ */ jsx7(
|
|
713
836
|
"div",
|
|
714
837
|
{
|
|
715
838
|
"data-message-id": message.id,
|
|
716
839
|
className: twMerge4("flex w-full justify-end", className),
|
|
717
|
-
children: /* @__PURE__ */
|
|
840
|
+
children: /* @__PURE__ */ jsxs5(
|
|
718
841
|
"div",
|
|
719
842
|
{
|
|
720
843
|
className: twMerge4(
|
|
721
|
-
"max-w-[70%] rounded-[18px] rounded-br-[4px] px-4 py-2.5 bg-
|
|
722
|
-
animated && "motion-safe:animate-slideFromRight"
|
|
844
|
+
"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",
|
|
845
|
+
animated && "motion-safe:animate-slideFromRight",
|
|
846
|
+
userBubbleClassName
|
|
723
847
|
),
|
|
724
|
-
children:
|
|
848
|
+
children: [
|
|
849
|
+
hasAttachments && /* @__PURE__ */ jsx7("div", { className: "flex flex-wrap gap-2 mb-2", children: message.attachments.map((att, i) => /* @__PURE__ */ jsx7(AttachmentThumbnail, { attachment: att }, `${att.filename}-${i}`)) }),
|
|
850
|
+
message.content
|
|
851
|
+
]
|
|
725
852
|
}
|
|
726
853
|
)
|
|
727
854
|
}
|
|
@@ -733,7 +860,7 @@ function MessageBubble({
|
|
|
733
860
|
"data-message-id": message.id,
|
|
734
861
|
className: twMerge4("flex w-full flex-col items-start gap-1.5", className),
|
|
735
862
|
children: [
|
|
736
|
-
showAgent && message.agent && /* @__PURE__ */ jsx7("div", { className: "text-[11px] font-semibold uppercase tracking-[0.08em] text-text-muted px-1", children: message.agent.replace("_agent", "").replace("_", " ") }),
|
|
863
|
+
showAgent && message.agent && /* @__PURE__ */ jsx7("div", { className: "text-[11px] font-display font-semibold uppercase tracking-[0.08em] text-text-muted px-1", children: message.agent.replace("_agent", "").replace("_", " ") }),
|
|
737
864
|
/* @__PURE__ */ jsx7(
|
|
738
865
|
"div",
|
|
739
866
|
{
|
|
@@ -759,34 +886,70 @@ function MessageBubble({
|
|
|
759
886
|
|
|
760
887
|
// src/chat/MessageThread/MessageThread.tsx
|
|
761
888
|
import { jsx as jsx8, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
762
|
-
function MessageThread({ messages, streamingSlot, showSources, showConfidence, showVerification, className }) {
|
|
763
|
-
const
|
|
889
|
+
function MessageThread({ messages, streamingSlot, showAgent, showSources, showConfidence, showVerification, hideLastAssistant, userBubbleClassName, className }) {
|
|
890
|
+
const scrollRef = useRef2(null);
|
|
891
|
+
const isNearBottom = useRef2(true);
|
|
892
|
+
const isProgrammaticScroll = useRef2(false);
|
|
893
|
+
const hasStreaming = !!streamingSlot;
|
|
894
|
+
const scrollToBottom = useCallback2(() => {
|
|
895
|
+
const el = scrollRef.current;
|
|
896
|
+
if (el && isNearBottom.current) {
|
|
897
|
+
isProgrammaticScroll.current = true;
|
|
898
|
+
el.scrollTop = el.scrollHeight;
|
|
899
|
+
}
|
|
900
|
+
}, []);
|
|
901
|
+
const handleScroll = useCallback2(() => {
|
|
902
|
+
if (isProgrammaticScroll.current) {
|
|
903
|
+
isProgrammaticScroll.current = false;
|
|
904
|
+
return;
|
|
905
|
+
}
|
|
906
|
+
const el = scrollRef.current;
|
|
907
|
+
if (!el) return;
|
|
908
|
+
isNearBottom.current = el.scrollHeight - el.scrollTop - el.clientHeight < 80;
|
|
909
|
+
}, []);
|
|
910
|
+
useEffect(scrollToBottom, [messages.length, scrollToBottom]);
|
|
764
911
|
useEffect(() => {
|
|
765
|
-
|
|
766
|
-
|
|
912
|
+
if (!hasStreaming) return;
|
|
913
|
+
let raf;
|
|
914
|
+
const tick = () => {
|
|
915
|
+
scrollToBottom();
|
|
916
|
+
raf = requestAnimationFrame(tick);
|
|
917
|
+
};
|
|
918
|
+
raf = requestAnimationFrame(tick);
|
|
919
|
+
return () => cancelAnimationFrame(raf);
|
|
920
|
+
}, [hasStreaming, scrollToBottom]);
|
|
767
921
|
return /* @__PURE__ */ jsxs6(
|
|
768
922
|
"div",
|
|
769
923
|
{
|
|
924
|
+
ref: scrollRef,
|
|
770
925
|
role: "log",
|
|
771
926
|
"aria-live": "polite",
|
|
772
927
|
"aria-label": "Message thread",
|
|
928
|
+
onScroll: handleScroll,
|
|
773
929
|
className: twMerge5(
|
|
774
930
|
"flex flex-col gap-4 overflow-y-auto flex-1 px-4 py-6",
|
|
775
931
|
className
|
|
776
932
|
),
|
|
777
933
|
children: [
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
{
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
934
|
+
/* @__PURE__ */ jsx8("div", { className: "flex-1 shrink-0" }),
|
|
935
|
+
messages.map((message, i) => {
|
|
936
|
+
if (hideLastAssistant && i === messages.length - 1 && message.role === "assistant") {
|
|
937
|
+
return null;
|
|
938
|
+
}
|
|
939
|
+
return /* @__PURE__ */ jsx8(
|
|
940
|
+
MessageBubble,
|
|
941
|
+
{
|
|
942
|
+
message,
|
|
943
|
+
showAgent,
|
|
944
|
+
showSources,
|
|
945
|
+
showConfidence,
|
|
946
|
+
showVerification,
|
|
947
|
+
userBubbleClassName
|
|
948
|
+
},
|
|
949
|
+
message.id
|
|
950
|
+
);
|
|
951
|
+
}),
|
|
952
|
+
streamingSlot
|
|
790
953
|
]
|
|
791
954
|
}
|
|
792
955
|
);
|
|
@@ -794,8 +957,96 @@ function MessageThread({ messages, streamingSlot, showSources, showConfidence, s
|
|
|
794
957
|
|
|
795
958
|
// src/chat/MessageComposer/MessageComposer.tsx
|
|
796
959
|
import { twMerge as twMerge6 } from "tailwind-merge";
|
|
797
|
-
import { useState as useState2, useRef as useRef3, useCallback as
|
|
960
|
+
import { useState as useState2, useRef as useRef3, useCallback as useCallback3 } from "react";
|
|
798
961
|
import { jsx as jsx9, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
962
|
+
var ALLOWED_TYPES = /* @__PURE__ */ new Set([
|
|
963
|
+
"image/png",
|
|
964
|
+
"image/jpeg",
|
|
965
|
+
"image/gif",
|
|
966
|
+
"image/webp",
|
|
967
|
+
"application/pdf"
|
|
968
|
+
]);
|
|
969
|
+
var MAX_FILE_SIZE = 10 * 1024 * 1024;
|
|
970
|
+
var MAX_ATTACHMENTS = 5;
|
|
971
|
+
function ArrowUpIcon() {
|
|
972
|
+
return /* @__PURE__ */ jsxs7("svg", { width: "20", height: "20", viewBox: "0 0 20 20", fill: "none", stroke: "currentColor", strokeWidth: "2.5", strokeLinecap: "round", strokeLinejoin: "round", children: [
|
|
973
|
+
/* @__PURE__ */ jsx9("path", { d: "M10 16V4" }),
|
|
974
|
+
/* @__PURE__ */ jsx9("path", { d: "M4 10l6-6 6 6" })
|
|
975
|
+
] });
|
|
976
|
+
}
|
|
977
|
+
function StopIcon() {
|
|
978
|
+
return /* @__PURE__ */ jsx9("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "currentColor", children: /* @__PURE__ */ jsx9("rect", { x: "3", y: "3", width: "10", height: "10", rx: "2" }) });
|
|
979
|
+
}
|
|
980
|
+
function PaperclipIcon() {
|
|
981
|
+
return /* @__PURE__ */ jsx9("svg", { width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ jsx9("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" }) });
|
|
982
|
+
}
|
|
983
|
+
function XIcon({ size = 14 }) {
|
|
984
|
+
return /* @__PURE__ */ jsxs7("svg", { width: size, height: size, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.5", strokeLinecap: "round", strokeLinejoin: "round", children: [
|
|
985
|
+
/* @__PURE__ */ jsx9("path", { d: "M18 6L6 18" }),
|
|
986
|
+
/* @__PURE__ */ jsx9("path", { d: "M6 6l12 12" })
|
|
987
|
+
] });
|
|
988
|
+
}
|
|
989
|
+
function DocumentIcon2() {
|
|
990
|
+
return /* @__PURE__ */ jsxs7("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round", children: [
|
|
991
|
+
/* @__PURE__ */ jsx9("path", { d: "M14 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V8z" }),
|
|
992
|
+
/* @__PURE__ */ jsx9("polyline", { points: "14 2 14 8 20 8" }),
|
|
993
|
+
/* @__PURE__ */ jsx9("line", { x1: "16", y1: "13", x2: "8", y2: "13" }),
|
|
994
|
+
/* @__PURE__ */ jsx9("line", { x1: "16", y1: "17", x2: "8", y2: "17" }),
|
|
995
|
+
/* @__PURE__ */ jsx9("polyline", { points: "10 9 9 9 8 9" })
|
|
996
|
+
] });
|
|
997
|
+
}
|
|
998
|
+
function fileToBase64(file) {
|
|
999
|
+
return new Promise((resolve, reject) => {
|
|
1000
|
+
const reader = new FileReader();
|
|
1001
|
+
reader.onload = () => {
|
|
1002
|
+
const result = reader.result;
|
|
1003
|
+
const base64 = result.split(",")[1];
|
|
1004
|
+
resolve(base64);
|
|
1005
|
+
};
|
|
1006
|
+
reader.onerror = reject;
|
|
1007
|
+
reader.readAsDataURL(file);
|
|
1008
|
+
});
|
|
1009
|
+
}
|
|
1010
|
+
function AttachmentPreview({
|
|
1011
|
+
attachment,
|
|
1012
|
+
onRemove
|
|
1013
|
+
}) {
|
|
1014
|
+
const isImage = attachment.content_type.startsWith("image/");
|
|
1015
|
+
return /* @__PURE__ */ jsxs7("div", { className: "relative group flex-shrink-0", children: [
|
|
1016
|
+
isImage ? /* @__PURE__ */ jsx9("div", { className: "w-16 h-16 rounded-lg overflow-hidden border border-border/60 bg-surface-alt", children: /* @__PURE__ */ jsx9(
|
|
1017
|
+
"img",
|
|
1018
|
+
{
|
|
1019
|
+
src: attachment.preview_url ?? `data:${attachment.content_type};base64,${attachment.data}`,
|
|
1020
|
+
alt: attachment.filename,
|
|
1021
|
+
className: "w-full h-full object-cover"
|
|
1022
|
+
}
|
|
1023
|
+
) }) : /* @__PURE__ */ jsxs7("div", { className: "h-16 px-3 rounded-lg border border-border/60 bg-surface-alt flex items-center gap-2", children: [
|
|
1024
|
+
/* @__PURE__ */ jsx9("div", { className: "text-text-muted", children: /* @__PURE__ */ jsx9(DocumentIcon2, {}) }),
|
|
1025
|
+
/* @__PURE__ */ jsxs7("div", { className: "flex flex-col min-w-0", children: [
|
|
1026
|
+
/* @__PURE__ */ jsx9("span", { className: "text-xs text-text-primary truncate max-w-[120px]", children: attachment.filename }),
|
|
1027
|
+
/* @__PURE__ */ jsx9("span", { className: "text-[10px] text-text-muted", children: "PDF" })
|
|
1028
|
+
] })
|
|
1029
|
+
] }),
|
|
1030
|
+
/* @__PURE__ */ jsx9(
|
|
1031
|
+
"button",
|
|
1032
|
+
{
|
|
1033
|
+
type: "button",
|
|
1034
|
+
onClick: onRemove,
|
|
1035
|
+
className: twMerge6(
|
|
1036
|
+
"absolute -top-1.5 -right-1.5",
|
|
1037
|
+
"w-5 h-5 rounded-full",
|
|
1038
|
+
"bg-text-muted/80 text-white",
|
|
1039
|
+
"flex items-center justify-center",
|
|
1040
|
+
"opacity-0 group-hover:opacity-100",
|
|
1041
|
+
"transition-opacity duration-150",
|
|
1042
|
+
"hover:bg-text-primary"
|
|
1043
|
+
),
|
|
1044
|
+
"aria-label": `Remove ${attachment.filename}`,
|
|
1045
|
+
children: /* @__PURE__ */ jsx9(XIcon, { size: 10 })
|
|
1046
|
+
}
|
|
1047
|
+
)
|
|
1048
|
+
] });
|
|
1049
|
+
}
|
|
799
1050
|
function MessageComposer({
|
|
800
1051
|
onSend,
|
|
801
1052
|
isLoading = false,
|
|
@@ -803,23 +1054,29 @@ function MessageComposer({
|
|
|
803
1054
|
className
|
|
804
1055
|
}) {
|
|
805
1056
|
const [value, setValue] = useState2("");
|
|
1057
|
+
const [attachments, setAttachments] = useState2([]);
|
|
1058
|
+
const [dragOver, setDragOver] = useState2(false);
|
|
806
1059
|
const textareaRef = useRef3(null);
|
|
807
|
-
const
|
|
808
|
-
const
|
|
1060
|
+
const fileInputRef = useRef3(null);
|
|
1061
|
+
const canSend = (value.trim().length > 0 || attachments.length > 0) && !isLoading;
|
|
1062
|
+
const resetHeight = useCallback3(() => {
|
|
809
1063
|
const el = textareaRef.current;
|
|
810
1064
|
if (el) {
|
|
811
1065
|
el.style.height = "auto";
|
|
812
1066
|
el.style.overflowY = "hidden";
|
|
813
1067
|
}
|
|
814
1068
|
}, []);
|
|
815
|
-
const handleSend =
|
|
1069
|
+
const handleSend = useCallback3(() => {
|
|
816
1070
|
if (!canSend) return;
|
|
817
|
-
|
|
1071
|
+
const message = value.trim() || (attachments.length > 0 ? "Please analyse the attached file(s)." : "");
|
|
1072
|
+
if (!message && attachments.length === 0) return;
|
|
1073
|
+
onSend(message, attachments.length > 0 ? attachments : void 0);
|
|
818
1074
|
setValue("");
|
|
1075
|
+
setAttachments([]);
|
|
819
1076
|
resetHeight();
|
|
820
1077
|
textareaRef.current?.focus();
|
|
821
|
-
}, [canSend, onSend, value, resetHeight]);
|
|
822
|
-
const handleKeyDown =
|
|
1078
|
+
}, [canSend, onSend, value, attachments, resetHeight]);
|
|
1079
|
+
const handleKeyDown = useCallback3(
|
|
823
1080
|
(e) => {
|
|
824
1081
|
if (e.key === "Enter" && !e.shiftKey) {
|
|
825
1082
|
e.preventDefault();
|
|
@@ -828,64 +1085,194 @@ function MessageComposer({
|
|
|
828
1085
|
},
|
|
829
1086
|
[handleSend]
|
|
830
1087
|
);
|
|
831
|
-
const handleChange =
|
|
1088
|
+
const handleChange = useCallback3(
|
|
832
1089
|
(e) => {
|
|
833
1090
|
setValue(e.target.value);
|
|
834
1091
|
const el = e.target;
|
|
835
1092
|
el.style.height = "auto";
|
|
836
|
-
const capped = Math.min(el.scrollHeight,
|
|
1093
|
+
const capped = Math.min(el.scrollHeight, 200);
|
|
837
1094
|
el.style.height = `${capped}px`;
|
|
838
|
-
el.style.overflowY = el.scrollHeight >
|
|
1095
|
+
el.style.overflowY = el.scrollHeight > 200 ? "auto" : "hidden";
|
|
839
1096
|
},
|
|
840
1097
|
[]
|
|
841
1098
|
);
|
|
1099
|
+
const addFiles = useCallback3(async (files) => {
|
|
1100
|
+
const fileArray = Array.from(files);
|
|
1101
|
+
for (const file of fileArray) {
|
|
1102
|
+
if (attachments.length >= MAX_ATTACHMENTS) break;
|
|
1103
|
+
if (!ALLOWED_TYPES.has(file.type)) continue;
|
|
1104
|
+
if (file.size > MAX_FILE_SIZE) continue;
|
|
1105
|
+
try {
|
|
1106
|
+
const data = await fileToBase64(file);
|
|
1107
|
+
const previewUrl = file.type.startsWith("image/") ? URL.createObjectURL(file) : void 0;
|
|
1108
|
+
const attachment = {
|
|
1109
|
+
filename: file.name,
|
|
1110
|
+
content_type: file.type,
|
|
1111
|
+
data,
|
|
1112
|
+
preview_url: previewUrl
|
|
1113
|
+
};
|
|
1114
|
+
setAttachments((prev) => {
|
|
1115
|
+
if (prev.length >= MAX_ATTACHMENTS) return prev;
|
|
1116
|
+
return [...prev, attachment];
|
|
1117
|
+
});
|
|
1118
|
+
} catch {
|
|
1119
|
+
}
|
|
1120
|
+
}
|
|
1121
|
+
}, [attachments.length]);
|
|
1122
|
+
const handleFileSelect = useCallback3(() => {
|
|
1123
|
+
fileInputRef.current?.click();
|
|
1124
|
+
}, []);
|
|
1125
|
+
const handleFileInputChange = useCallback3(
|
|
1126
|
+
(e) => {
|
|
1127
|
+
if (e.target.files) {
|
|
1128
|
+
void addFiles(e.target.files);
|
|
1129
|
+
e.target.value = "";
|
|
1130
|
+
}
|
|
1131
|
+
},
|
|
1132
|
+
[addFiles]
|
|
1133
|
+
);
|
|
1134
|
+
const removeAttachment = useCallback3((index) => {
|
|
1135
|
+
setAttachments((prev) => {
|
|
1136
|
+
const removed = prev[index];
|
|
1137
|
+
if (removed?.preview_url) URL.revokeObjectURL(removed.preview_url);
|
|
1138
|
+
return prev.filter((_, i) => i !== index);
|
|
1139
|
+
});
|
|
1140
|
+
}, []);
|
|
1141
|
+
const handlePaste = useCallback3(
|
|
1142
|
+
(e) => {
|
|
1143
|
+
const items = e.clipboardData.items;
|
|
1144
|
+
const files = [];
|
|
1145
|
+
for (const item of items) {
|
|
1146
|
+
if (item.kind === "file" && ALLOWED_TYPES.has(item.type)) {
|
|
1147
|
+
const file = item.getAsFile();
|
|
1148
|
+
if (file) files.push(file);
|
|
1149
|
+
}
|
|
1150
|
+
}
|
|
1151
|
+
if (files.length > 0) {
|
|
1152
|
+
void addFiles(files);
|
|
1153
|
+
}
|
|
1154
|
+
},
|
|
1155
|
+
[addFiles]
|
|
1156
|
+
);
|
|
1157
|
+
const handleDragOver = useCallback3((e) => {
|
|
1158
|
+
e.preventDefault();
|
|
1159
|
+
e.stopPropagation();
|
|
1160
|
+
setDragOver(true);
|
|
1161
|
+
}, []);
|
|
1162
|
+
const handleDragLeave = useCallback3((e) => {
|
|
1163
|
+
e.preventDefault();
|
|
1164
|
+
e.stopPropagation();
|
|
1165
|
+
setDragOver(false);
|
|
1166
|
+
}, []);
|
|
1167
|
+
const handleDrop = useCallback3(
|
|
1168
|
+
(e) => {
|
|
1169
|
+
e.preventDefault();
|
|
1170
|
+
e.stopPropagation();
|
|
1171
|
+
setDragOver(false);
|
|
1172
|
+
if (e.dataTransfer.files.length > 0) {
|
|
1173
|
+
void addFiles(e.dataTransfer.files);
|
|
1174
|
+
}
|
|
1175
|
+
},
|
|
1176
|
+
[addFiles]
|
|
1177
|
+
);
|
|
842
1178
|
return /* @__PURE__ */ jsxs7(
|
|
843
1179
|
"div",
|
|
844
1180
|
{
|
|
845
1181
|
className: twMerge6(
|
|
846
|
-
"
|
|
1182
|
+
"relative shrink-0 rounded-3xl border bg-surface",
|
|
1183
|
+
"shadow-lg shadow-black/10",
|
|
1184
|
+
"transition-all duration-200",
|
|
1185
|
+
"focus-within:border-accent/40 focus-within:shadow-accent/5",
|
|
1186
|
+
dragOver ? "border-accent/60 bg-accent/5" : "border-border/60",
|
|
847
1187
|
className
|
|
848
1188
|
),
|
|
1189
|
+
onDragOver: handleDragOver,
|
|
1190
|
+
onDragLeave: handleDragLeave,
|
|
1191
|
+
onDrop: handleDrop,
|
|
849
1192
|
children: [
|
|
850
1193
|
/* @__PURE__ */ jsx9(
|
|
851
|
-
"
|
|
1194
|
+
"input",
|
|
852
1195
|
{
|
|
853
|
-
ref:
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
className: twMerge6(
|
|
861
|
-
"flex-1 resize-none rounded-xl border border-border bg-surface/80",
|
|
862
|
-
"px-4 py-2.5 text-sm text-text-primary placeholder:text-text-muted",
|
|
863
|
-
"focus:border-transparent focus:ring-2 focus:ring-accent/40 focus:outline-none",
|
|
864
|
-
"disabled:opacity-50 disabled:cursor-not-allowed",
|
|
865
|
-
"overflow-hidden",
|
|
866
|
-
"transition-all duration-200"
|
|
867
|
-
),
|
|
868
|
-
style: { colorScheme: "dark" },
|
|
869
|
-
"aria-label": "Message input"
|
|
1196
|
+
ref: fileInputRef,
|
|
1197
|
+
type: "file",
|
|
1198
|
+
multiple: true,
|
|
1199
|
+
accept: "image/png,image/jpeg,image/gif,image/webp,application/pdf",
|
|
1200
|
+
onChange: handleFileInputChange,
|
|
1201
|
+
className: "hidden",
|
|
1202
|
+
"aria-hidden": "true"
|
|
870
1203
|
}
|
|
871
1204
|
),
|
|
872
|
-
/* @__PURE__ */ jsx9(
|
|
873
|
-
|
|
1205
|
+
attachments.length > 0 && /* @__PURE__ */ jsx9("div", { className: "flex gap-2 px-4 pt-3 pb-1 overflow-x-auto", children: attachments.map((att, i) => /* @__PURE__ */ jsx9(
|
|
1206
|
+
AttachmentPreview,
|
|
874
1207
|
{
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
1208
|
+
attachment: att,
|
|
1209
|
+
onRemove: () => removeAttachment(i)
|
|
1210
|
+
},
|
|
1211
|
+
`${att.filename}-${i}`
|
|
1212
|
+
)) }),
|
|
1213
|
+
dragOver && /* @__PURE__ */ jsx9("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__ */ jsx9("span", { className: "text-sm font-display font-semibold text-accent", children: "Drop files here" }) }),
|
|
1214
|
+
/* @__PURE__ */ jsxs7("div", { className: "flex items-end", children: [
|
|
1215
|
+
/* @__PURE__ */ jsx9(
|
|
1216
|
+
"button",
|
|
1217
|
+
{
|
|
1218
|
+
type: "button",
|
|
1219
|
+
onClick: handleFileSelect,
|
|
1220
|
+
disabled: isLoading || attachments.length >= MAX_ATTACHMENTS,
|
|
1221
|
+
"aria-label": "Attach file",
|
|
1222
|
+
className: twMerge6(
|
|
1223
|
+
"flex-shrink-0 ml-2 mb-3",
|
|
1224
|
+
"inline-flex items-center justify-center",
|
|
1225
|
+
"w-9 h-9 rounded-full",
|
|
1226
|
+
"transition-all duration-200",
|
|
1227
|
+
"text-text-muted/60 hover:text-text-secondary hover:bg-text-muted/10",
|
|
1228
|
+
"focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-accent",
|
|
1229
|
+
"disabled:opacity-30 disabled:cursor-not-allowed disabled:hover:bg-transparent"
|
|
1230
|
+
),
|
|
1231
|
+
children: /* @__PURE__ */ jsx9(PaperclipIcon, {})
|
|
1232
|
+
}
|
|
1233
|
+
),
|
|
1234
|
+
/* @__PURE__ */ jsx9(
|
|
1235
|
+
"textarea",
|
|
1236
|
+
{
|
|
1237
|
+
ref: textareaRef,
|
|
1238
|
+
value,
|
|
1239
|
+
onChange: handleChange,
|
|
1240
|
+
onKeyDown: handleKeyDown,
|
|
1241
|
+
onPaste: handlePaste,
|
|
1242
|
+
placeholder,
|
|
1243
|
+
rows: 1,
|
|
1244
|
+
disabled: isLoading,
|
|
1245
|
+
className: twMerge6(
|
|
1246
|
+
"flex-1 resize-none bg-transparent",
|
|
1247
|
+
"pl-2 pr-14 pt-4 pb-4 text-[15px] leading-relaxed",
|
|
1248
|
+
"text-text-primary placeholder:text-text-muted/70",
|
|
1249
|
+
"focus:outline-none",
|
|
1250
|
+
"disabled:opacity-50 disabled:cursor-not-allowed",
|
|
1251
|
+
"overflow-hidden"
|
|
1252
|
+
),
|
|
1253
|
+
style: { colorScheme: "dark" },
|
|
1254
|
+
"aria-label": "Message input"
|
|
1255
|
+
}
|
|
1256
|
+
),
|
|
1257
|
+
/* @__PURE__ */ jsx9(
|
|
1258
|
+
"button",
|
|
1259
|
+
{
|
|
1260
|
+
type: "button",
|
|
1261
|
+
onClick: handleSend,
|
|
1262
|
+
disabled: !canSend,
|
|
1263
|
+
"aria-label": "Send message",
|
|
1264
|
+
className: twMerge6(
|
|
1265
|
+
"absolute bottom-3 right-3",
|
|
1266
|
+
"inline-flex items-center justify-center",
|
|
1267
|
+
"w-9 h-9 rounded-full",
|
|
1268
|
+
"transition-all duration-200",
|
|
1269
|
+
"focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-accent",
|
|
1270
|
+
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"
|
|
1271
|
+
),
|
|
1272
|
+
children: isLoading ? /* @__PURE__ */ jsx9(StopIcon, {}) : /* @__PURE__ */ jsx9(ArrowUpIcon, {})
|
|
1273
|
+
}
|
|
1274
|
+
)
|
|
1275
|
+
] })
|
|
889
1276
|
]
|
|
890
1277
|
}
|
|
891
1278
|
);
|
|
@@ -898,6 +1285,7 @@ function WelcomeScreen({
|
|
|
898
1285
|
title = "Welcome",
|
|
899
1286
|
message = "How can I help you today?",
|
|
900
1287
|
icon,
|
|
1288
|
+
iconClassName,
|
|
901
1289
|
suggestedQuestions = [],
|
|
902
1290
|
onQuestionSelect,
|
|
903
1291
|
className
|
|
@@ -910,12 +1298,15 @@ function WelcomeScreen({
|
|
|
910
1298
|
className
|
|
911
1299
|
),
|
|
912
1300
|
children: [
|
|
913
|
-
/* @__PURE__ */ jsx10(
|
|
1301
|
+
icon ? iconClassName ? /* @__PURE__ */ jsx10("div", { className: iconClassName, "aria-hidden": "true", children: icon }) : icon : /* @__PURE__ */ jsx10(
|
|
914
1302
|
"div",
|
|
915
1303
|
{
|
|
916
|
-
className:
|
|
1304
|
+
className: twMerge7(
|
|
1305
|
+
"w-14 h-14 rounded-2xl bg-accent/10 border border-border flex items-center justify-center pulse-glow",
|
|
1306
|
+
iconClassName
|
|
1307
|
+
),
|
|
917
1308
|
"aria-hidden": "true",
|
|
918
|
-
children:
|
|
1309
|
+
children: /* @__PURE__ */ jsx10("span", { className: "text-2xl", children: "\u2726" })
|
|
919
1310
|
}
|
|
920
1311
|
),
|
|
921
1312
|
/* @__PURE__ */ jsxs8("div", { className: "flex flex-col gap-2", children: [
|
|
@@ -925,7 +1316,7 @@ function WelcomeScreen({
|
|
|
925
1316
|
suggestedQuestions.length > 0 && /* @__PURE__ */ jsx10(
|
|
926
1317
|
"div",
|
|
927
1318
|
{
|
|
928
|
-
className: "flex flex-wrap justify-center gap-2 max-w-
|
|
1319
|
+
className: "flex flex-wrap justify-center gap-2 max-w-xl",
|
|
929
1320
|
role: "group",
|
|
930
1321
|
"aria-label": "Suggested questions",
|
|
931
1322
|
children: suggestedQuestions.map((question) => /* @__PURE__ */ jsx10(
|
|
@@ -934,7 +1325,7 @@ function WelcomeScreen({
|
|
|
934
1325
|
type: "button",
|
|
935
1326
|
onClick: () => onQuestionSelect?.(question),
|
|
936
1327
|
className: twMerge7(
|
|
937
|
-
"px-
|
|
1328
|
+
"px-3.5 py-1.5 rounded-full text-[12px]",
|
|
938
1329
|
"border border-border bg-transparent text-text-secondary",
|
|
939
1330
|
"hover:bg-accent/10 hover:border-interactive hover:text-text-primary",
|
|
940
1331
|
"focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-accent",
|
|
@@ -953,7 +1344,8 @@ function WelcomeScreen({
|
|
|
953
1344
|
|
|
954
1345
|
// src/streaming/StreamingMessage/StreamingMessage.tsx
|
|
955
1346
|
import { useEffect as useEffect3, useRef as useRef5 } from "react";
|
|
956
|
-
import {
|
|
1347
|
+
import { twMerge as twMerge8 } from "tailwind-merge";
|
|
1348
|
+
import { WaveLoader } from "@surf-kit/core";
|
|
957
1349
|
|
|
958
1350
|
// src/hooks/useCharacterDrain.ts
|
|
959
1351
|
import { useState as useState3, useRef as useRef4, useEffect as useEffect2 } from "react";
|
|
@@ -982,7 +1374,10 @@ function useCharacterDrain(target, msPerChar = 15) {
|
|
|
982
1374
|
const elapsed = now - lastTimeRef.current;
|
|
983
1375
|
const charsToAdvance = Math.floor(elapsed / msPerCharRef.current);
|
|
984
1376
|
if (charsToAdvance > 0 && indexRef.current < currentTarget.length) {
|
|
985
|
-
|
|
1377
|
+
let nextIndex = Math.min(indexRef.current + charsToAdvance, currentTarget.length);
|
|
1378
|
+
while (nextIndex < currentTarget.length && currentTarget[nextIndex - 1].trim() === "") {
|
|
1379
|
+
nextIndex++;
|
|
1380
|
+
}
|
|
986
1381
|
indexRef.current = nextIndex;
|
|
987
1382
|
lastTimeRef.current = now;
|
|
988
1383
|
setDisplayed(currentTarget.slice(0, nextIndex));
|
|
@@ -1027,14 +1422,35 @@ var phaseLabels = {
|
|
|
1027
1422
|
generating: "Writing...",
|
|
1028
1423
|
verifying: "Verifying..."
|
|
1029
1424
|
};
|
|
1425
|
+
var CURSOR_STYLES = `
|
|
1426
|
+
.sk-streaming-cursor > :not(ul,ol,blockquote):last-child::after,
|
|
1427
|
+
.sk-streaming-cursor > :is(ul,ol):last-child > li:last-child::after,
|
|
1428
|
+
.sk-streaming-cursor > blockquote:last-child > p:last-child::after {
|
|
1429
|
+
content: "";
|
|
1430
|
+
display: inline-block;
|
|
1431
|
+
width: 2px;
|
|
1432
|
+
height: 1em;
|
|
1433
|
+
background: var(--color-accent, #38bdf8);
|
|
1434
|
+
animation: sk-cursor-blink 0.8s steps(1) infinite;
|
|
1435
|
+
margin-left: 2px;
|
|
1436
|
+
vertical-align: text-bottom;
|
|
1437
|
+
}
|
|
1438
|
+
@keyframes sk-cursor-blink {
|
|
1439
|
+
0%, 60% { opacity: 1; }
|
|
1440
|
+
61%, 100% { opacity: 0; }
|
|
1441
|
+
}
|
|
1442
|
+
`;
|
|
1030
1443
|
function StreamingMessage({
|
|
1031
1444
|
stream,
|
|
1032
1445
|
onComplete,
|
|
1446
|
+
onDraining,
|
|
1033
1447
|
showPhases = true,
|
|
1034
1448
|
className
|
|
1035
1449
|
}) {
|
|
1036
1450
|
const onCompleteRef = useRef5(onComplete);
|
|
1037
1451
|
onCompleteRef.current = onComplete;
|
|
1452
|
+
const onDrainingRef = useRef5(onDraining);
|
|
1453
|
+
onDrainingRef.current = onDraining;
|
|
1038
1454
|
const wasActiveRef = useRef5(stream.active);
|
|
1039
1455
|
useEffect3(() => {
|
|
1040
1456
|
if (wasActiveRef.current && !stream.active) {
|
|
@@ -1043,35 +1459,40 @@ function StreamingMessage({
|
|
|
1043
1459
|
wasActiveRef.current = stream.active;
|
|
1044
1460
|
}, [stream.active]);
|
|
1045
1461
|
const phaseLabel = phaseLabels[stream.phase];
|
|
1046
|
-
const { displayed:
|
|
1047
|
-
|
|
1462
|
+
const { displayed: rawDisplayed, isDraining } = useCharacterDrain(stream.content);
|
|
1463
|
+
const displayedContent = stream.active || isDraining ? rawDisplayed.trimEnd() : rawDisplayed;
|
|
1464
|
+
useEffect3(() => {
|
|
1465
|
+
onDrainingRef.current?.(isDraining);
|
|
1466
|
+
}, [isDraining]);
|
|
1467
|
+
const agentLabel = stream.agent ? stream.agent.replace("_agent", "").replace("_", " ") : null;
|
|
1468
|
+
const showPhaseIndicator = showPhases && stream.active && stream.phase !== "idle" && !displayedContent;
|
|
1469
|
+
const showCursor = (stream.active || isDraining) && !!displayedContent;
|
|
1470
|
+
return /* @__PURE__ */ jsxs9("div", { className: twMerge8("flex w-full flex-col items-start", className), "data-testid": "streaming-message", children: [
|
|
1048
1471
|
/* @__PURE__ */ jsxs9("div", { "aria-live": "assertive", className: "sr-only", children: [
|
|
1049
1472
|
stream.active && stream.phase !== "idle" && "Response started",
|
|
1050
1473
|
!stream.active && stream.content && "Response complete"
|
|
1051
1474
|
] }),
|
|
1475
|
+
showCursor && /* @__PURE__ */ jsx11("style", { children: CURSOR_STYLES }),
|
|
1476
|
+
agentLabel && /* @__PURE__ */ jsx11("div", { className: "text-[11px] font-display font-semibold uppercase tracking-[0.08em] text-text-muted px-1 mb-1.5", children: agentLabel }),
|
|
1052
1477
|
/* @__PURE__ */ jsxs9("div", { className: "max-w-[88%] px-4 py-3 rounded-[18px] rounded-tl-[4px] bg-surface border border-border motion-safe:animate-springFromLeft", children: [
|
|
1053
|
-
|
|
1478
|
+
showPhaseIndicator && /* @__PURE__ */ jsxs9(
|
|
1054
1479
|
"div",
|
|
1055
1480
|
{
|
|
1056
|
-
className: "flex items-center gap-2
|
|
1481
|
+
className: "flex items-center gap-2 text-sm text-text-secondary",
|
|
1057
1482
|
"data-testid": "phase-indicator",
|
|
1058
1483
|
children: [
|
|
1059
|
-
/* @__PURE__ */ jsx11("span", { "aria-hidden": "true", children: /* @__PURE__ */ jsx11(
|
|
1484
|
+
/* @__PURE__ */ jsx11("span", { "aria-hidden": "true", children: /* @__PURE__ */ jsx11(WaveLoader, { size: "sm", color: "#38bdf8" }) }),
|
|
1060
1485
|
/* @__PURE__ */ jsx11("span", { children: phaseLabel })
|
|
1061
1486
|
]
|
|
1062
1487
|
}
|
|
1063
1488
|
),
|
|
1064
|
-
/* @__PURE__ */
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
"data-testid": "streaming-cursor"
|
|
1072
|
-
}
|
|
1073
|
-
)
|
|
1074
|
-
] })
|
|
1489
|
+
displayedContent && /* @__PURE__ */ jsx11(
|
|
1490
|
+
ResponseMessage,
|
|
1491
|
+
{
|
|
1492
|
+
content: displayedContent,
|
|
1493
|
+
className: showCursor ? "sk-streaming-cursor" : void 0
|
|
1494
|
+
}
|
|
1495
|
+
)
|
|
1075
1496
|
] })
|
|
1076
1497
|
] });
|
|
1077
1498
|
}
|
|
@@ -1102,7 +1523,7 @@ function AgentChat({
|
|
|
1102
1523
|
return /* @__PURE__ */ jsxs10(
|
|
1103
1524
|
"div",
|
|
1104
1525
|
{
|
|
1105
|
-
className:
|
|
1526
|
+
className: twMerge9(
|
|
1106
1527
|
"flex flex-col h-full bg-canvas border border-border rounded-xl overflow-hidden",
|
|
1107
1528
|
className
|
|
1108
1529
|
),
|
|
@@ -1145,7 +1566,7 @@ function AgentChat({
|
|
|
1145
1566
|
}
|
|
1146
1567
|
|
|
1147
1568
|
// src/chat/ConversationList/ConversationList.tsx
|
|
1148
|
-
import { twMerge as
|
|
1569
|
+
import { twMerge as twMerge10 } from "tailwind-merge";
|
|
1149
1570
|
import { jsx as jsx13, jsxs as jsxs11 } from "react/jsx-runtime";
|
|
1150
1571
|
function ConversationList({
|
|
1151
1572
|
conversations,
|
|
@@ -1159,7 +1580,7 @@ function ConversationList({
|
|
|
1159
1580
|
"nav",
|
|
1160
1581
|
{
|
|
1161
1582
|
"aria-label": "Conversation list",
|
|
1162
|
-
className:
|
|
1583
|
+
className: twMerge10("flex flex-col h-full bg-canvas", className),
|
|
1163
1584
|
children: [
|
|
1164
1585
|
onNew && /* @__PURE__ */ jsx13("div", { className: "p-3 border-b border-border", children: /* @__PURE__ */ jsx13(
|
|
1165
1586
|
"button",
|
|
@@ -1176,7 +1597,7 @@ function ConversationList({
|
|
|
1176
1597
|
return /* @__PURE__ */ jsxs11(
|
|
1177
1598
|
"li",
|
|
1178
1599
|
{
|
|
1179
|
-
className:
|
|
1600
|
+
className: twMerge10(
|
|
1180
1601
|
"flex items-start border-b border-border transition-colors duration-200",
|
|
1181
1602
|
"hover:bg-surface",
|
|
1182
1603
|
isActive && "bg-surface-raised border-l-2 border-l-accent"
|
|
@@ -1249,7 +1670,7 @@ function AgentFullPage({
|
|
|
1249
1670
|
className
|
|
1250
1671
|
}) {
|
|
1251
1672
|
const [sidebarOpen, setSidebarOpen] = useState4(false);
|
|
1252
|
-
const handleSelect =
|
|
1673
|
+
const handleSelect = useCallback4(
|
|
1253
1674
|
(id) => {
|
|
1254
1675
|
onConversationSelect?.(id);
|
|
1255
1676
|
setSidebarOpen(false);
|
|
@@ -1259,7 +1680,7 @@ function AgentFullPage({
|
|
|
1259
1680
|
return /* @__PURE__ */ jsxs12(
|
|
1260
1681
|
"div",
|
|
1261
1682
|
{
|
|
1262
|
-
className:
|
|
1683
|
+
className: twMerge11("flex h-screen w-full overflow-hidden bg-brand-dark", className),
|
|
1263
1684
|
"data-testid": "agent-full-page",
|
|
1264
1685
|
children: [
|
|
1265
1686
|
showConversationList && /* @__PURE__ */ jsxs12(Fragment, { children: [
|
|
@@ -1274,7 +1695,7 @@ function AgentFullPage({
|
|
|
1274
1695
|
/* @__PURE__ */ jsx14(
|
|
1275
1696
|
"aside",
|
|
1276
1697
|
{
|
|
1277
|
-
className:
|
|
1698
|
+
className: twMerge11(
|
|
1278
1699
|
"bg-brand-dark border-r border-brand-gold/15 w-72 shrink-0 flex-col z-40",
|
|
1279
1700
|
// Desktop: always visible
|
|
1280
1701
|
"hidden md:flex",
|
|
@@ -1340,7 +1761,7 @@ function AgentFullPage({
|
|
|
1340
1761
|
}
|
|
1341
1762
|
|
|
1342
1763
|
// src/layouts/AgentPanel/AgentPanel.tsx
|
|
1343
|
-
import { twMerge as
|
|
1764
|
+
import { twMerge as twMerge12 } from "tailwind-merge";
|
|
1344
1765
|
import { useRef as useRef6, useEffect as useEffect4 } from "react";
|
|
1345
1766
|
import { jsx as jsx15, jsxs as jsxs13 } from "react/jsx-runtime";
|
|
1346
1767
|
function AgentPanel({
|
|
@@ -1365,13 +1786,13 @@ function AgentPanel({
|
|
|
1365
1786
|
return /* @__PURE__ */ jsxs13(
|
|
1366
1787
|
"div",
|
|
1367
1788
|
{
|
|
1368
|
-
className:
|
|
1789
|
+
className: twMerge12("fixed inset-0 z-50", !isOpen && "pointer-events-none"),
|
|
1369
1790
|
"aria-hidden": !isOpen,
|
|
1370
1791
|
children: [
|
|
1371
1792
|
/* @__PURE__ */ jsx15(
|
|
1372
1793
|
"div",
|
|
1373
1794
|
{
|
|
1374
|
-
className:
|
|
1795
|
+
className: twMerge12(
|
|
1375
1796
|
"fixed inset-0 transition-opacity duration-300",
|
|
1376
1797
|
isOpen ? "opacity-100 bg-brand-dark/70 backdrop-blur-sm pointer-events-auto" : "opacity-0 pointer-events-none"
|
|
1377
1798
|
),
|
|
@@ -1387,7 +1808,7 @@ function AgentPanel({
|
|
|
1387
1808
|
"aria-label": title,
|
|
1388
1809
|
"aria-modal": isOpen ? "true" : void 0,
|
|
1389
1810
|
style: { width: widthStyle, maxWidth: "100vw" },
|
|
1390
|
-
className:
|
|
1811
|
+
className: twMerge12(
|
|
1391
1812
|
"fixed top-0 h-full flex flex-col z-50 bg-brand-dark shadow-card",
|
|
1392
1813
|
"transition-transform duration-300 ease-in-out",
|
|
1393
1814
|
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"}`,
|
|
@@ -1429,8 +1850,8 @@ function AgentPanel({
|
|
|
1429
1850
|
}
|
|
1430
1851
|
|
|
1431
1852
|
// src/layouts/AgentWidget/AgentWidget.tsx
|
|
1432
|
-
import { twMerge as
|
|
1433
|
-
import { useState as useState5, useCallback as
|
|
1853
|
+
import { twMerge as twMerge13 } from "tailwind-merge";
|
|
1854
|
+
import { useState as useState5, useCallback as useCallback5 } from "react";
|
|
1434
1855
|
import { jsx as jsx16, jsxs as jsxs14 } from "react/jsx-runtime";
|
|
1435
1856
|
function AgentWidget({
|
|
1436
1857
|
endpoint,
|
|
@@ -1440,7 +1861,7 @@ function AgentWidget({
|
|
|
1440
1861
|
className
|
|
1441
1862
|
}) {
|
|
1442
1863
|
const [isOpen, setIsOpen] = useState5(false);
|
|
1443
|
-
const toggle =
|
|
1864
|
+
const toggle = useCallback5(() => {
|
|
1444
1865
|
setIsOpen((prev) => !prev);
|
|
1445
1866
|
}, []);
|
|
1446
1867
|
const positionClasses = position === "bottom-left" ? "left-4 bottom-4" : "right-4 bottom-4";
|
|
@@ -1453,7 +1874,7 @@ function AgentWidget({
|
|
|
1453
1874
|
role: "dialog",
|
|
1454
1875
|
"aria-label": title,
|
|
1455
1876
|
"aria-hidden": !isOpen,
|
|
1456
|
-
className:
|
|
1877
|
+
className: twMerge13(
|
|
1457
1878
|
"fixed z-50 flex flex-col",
|
|
1458
1879
|
"w-[min(400px,calc(100vw-2rem))] h-[min(600px,calc(100vh-6rem))]",
|
|
1459
1880
|
"rounded-2xl overflow-hidden border border-brand-gold/15",
|
|
@@ -1500,7 +1921,7 @@ function AgentWidget({
|
|
|
1500
1921
|
onClick: toggle,
|
|
1501
1922
|
"aria-label": isOpen ? "Close chat" : triggerLabel,
|
|
1502
1923
|
"aria-expanded": isOpen,
|
|
1503
|
-
className:
|
|
1924
|
+
className: twMerge13(
|
|
1504
1925
|
"fixed z-50 flex items-center justify-center w-14 h-14 rounded-full",
|
|
1505
1926
|
"bg-brand-blue text-brand-cream shadow-glow-cyan",
|
|
1506
1927
|
"hover:bg-brand-cyan hover:shadow-glow-cyan hover:scale-105",
|
|
@@ -1518,7 +1939,7 @@ function AgentWidget({
|
|
|
1518
1939
|
}
|
|
1519
1940
|
|
|
1520
1941
|
// src/layouts/AgentEmbed/AgentEmbed.tsx
|
|
1521
|
-
import { twMerge as
|
|
1942
|
+
import { twMerge as twMerge14 } from "tailwind-merge";
|
|
1522
1943
|
import { jsx as jsx17 } from "react/jsx-runtime";
|
|
1523
1944
|
function AgentEmbed({
|
|
1524
1945
|
endpoint,
|
|
@@ -1528,7 +1949,7 @@ function AgentEmbed({
|
|
|
1528
1949
|
return /* @__PURE__ */ jsx17(
|
|
1529
1950
|
"div",
|
|
1530
1951
|
{
|
|
1531
|
-
className:
|
|
1952
|
+
className: twMerge14("w-full h-full min-h-0", className),
|
|
1532
1953
|
"data-testid": "agent-embed",
|
|
1533
1954
|
children: /* @__PURE__ */ jsx17(
|
|
1534
1955
|
AgentChat,
|