@surf-kit/agent 0.2.1 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +184 -12
- package/README.md +1 -1
- package/dist/agent-identity/index.cjs +1 -0
- package/dist/agent-identity/index.cjs.map +1 -1
- package/dist/agent-identity/index.js +2 -0
- package/dist/agent-identity/index.js.map +1 -1
- package/dist/chat/index.cjs +626 -204
- package/dist/chat/index.cjs.map +1 -1
- package/dist/chat/index.d.cts +11 -6
- package/dist/chat/index.d.ts +11 -6
- package/dist/chat/index.js +608 -185
- package/dist/chat/index.js.map +1 -1
- package/dist/{chat--OifhIRe.d.ts → chat-BIIDOGrD.d.ts} +10 -1
- package/dist/{chat-ChYl2XjV.d.cts → chat-CGamM7Mz.d.cts} +10 -1
- package/dist/confidence/index.cjs +1 -0
- package/dist/confidence/index.cjs.map +1 -1
- package/dist/confidence/index.js +2 -0
- package/dist/confidence/index.js.map +1 -1
- package/dist/feedback/index.cjs +1 -0
- package/dist/feedback/index.cjs.map +1 -1
- package/dist/feedback/index.js +2 -0
- package/dist/feedback/index.js.map +1 -1
- package/dist/{hooks-DLfF18IU.d.cts → hooks-B1NYoLLs.d.cts} +21 -5
- package/dist/{hooks-BGs8-4GK.d.ts → hooks-CTeEqnBQ.d.ts} +21 -5
- package/dist/hooks.cjs +127 -81
- package/dist/hooks.cjs.map +1 -1
- package/dist/hooks.d.cts +3 -3
- package/dist/hooks.d.ts +3 -3
- package/dist/hooks.js +128 -81
- package/dist/hooks.js.map +1 -1
- package/dist/index.cjs +687 -265
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +3 -3
- package/dist/index.d.ts +3 -3
- package/dist/index.js +647 -224
- package/dist/index.js.map +1 -1
- package/dist/layouts/index.cjs +647 -225
- package/dist/layouts/index.cjs.map +1 -1
- package/dist/layouts/index.d.cts +1 -1
- package/dist/layouts/index.d.ts +1 -1
- package/dist/layouts/index.js +624 -201
- package/dist/layouts/index.js.map +1 -1
- package/dist/mcp/index.cjs +2 -1
- package/dist/mcp/index.cjs.map +1 -1
- package/dist/mcp/index.js +4 -2
- package/dist/mcp/index.js.map +1 -1
- package/dist/response/index.cjs +67 -12
- package/dist/response/index.cjs.map +1 -1
- package/dist/response/index.d.cts +2 -2
- package/dist/response/index.d.ts +2 -2
- package/dist/response/index.js +66 -10
- package/dist/response/index.js.map +1 -1
- package/dist/sources/index.cjs +31 -1
- package/dist/sources/index.cjs.map +1 -1
- package/dist/sources/index.js +32 -1
- package/dist/sources/index.js.map +1 -1
- package/dist/streaming/index.cjs +203 -93
- package/dist/streaming/index.cjs.map +1 -1
- package/dist/streaming/index.d.cts +4 -3
- package/dist/streaming/index.d.ts +4 -3
- package/dist/streaming/index.js +174 -73
- package/dist/streaming/index.js.map +1 -1
- package/dist/{streaming-DbQxScpi.d.ts → streaming-Bx-ff2tt.d.ts} +1 -1
- package/dist/{streaming-DfT22A0z.d.cts → streaming-x7umFHoP.d.cts} +1 -1
- package/package.json +17 -6
package/dist/layouts/index.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
1
3
|
// src/layouts/AgentFullPage/AgentFullPage.tsx
|
|
2
|
-
import { twMerge as
|
|
3
|
-
import { useState as useState4, useCallback as
|
|
4
|
+
import { twMerge as twMerge11 } from "tailwind-merge";
|
|
5
|
+
import { useState as useState4, useCallback as useCallback4 } from "react";
|
|
4
6
|
|
|
5
7
|
// src/chat/AgentChat/AgentChat.tsx
|
|
6
|
-
import { twMerge as
|
|
8
|
+
import { twMerge as twMerge9 } from "tailwind-merge";
|
|
7
9
|
|
|
8
10
|
// src/hooks/useAgentChat.ts
|
|
9
11
|
import { useReducer, useCallback, useRef } from "react";
|
|
@@ -14,7 +16,8 @@ var initialState = {
|
|
|
14
16
|
error: null,
|
|
15
17
|
inputValue: "",
|
|
16
18
|
streamPhase: "idle",
|
|
17
|
-
streamingContent: ""
|
|
19
|
+
streamingContent: "",
|
|
20
|
+
streamingAgent: null
|
|
18
21
|
};
|
|
19
22
|
function reducer(state, action) {
|
|
20
23
|
switch (action.type) {
|
|
@@ -28,12 +31,15 @@ function reducer(state, action) {
|
|
|
28
31
|
error: null,
|
|
29
32
|
inputValue: "",
|
|
30
33
|
streamPhase: "thinking",
|
|
31
|
-
streamingContent: ""
|
|
34
|
+
streamingContent: "",
|
|
35
|
+
streamingAgent: null
|
|
32
36
|
};
|
|
33
37
|
case "STREAM_PHASE":
|
|
34
38
|
return { ...state, streamPhase: action.phase };
|
|
35
39
|
case "STREAM_CONTENT":
|
|
36
40
|
return { ...state, streamingContent: state.streamingContent + action.content };
|
|
41
|
+
case "STREAM_AGENT":
|
|
42
|
+
return { ...state, streamingAgent: action.agent };
|
|
37
43
|
case "SEND_SUCCESS":
|
|
38
44
|
return {
|
|
39
45
|
...state,
|
|
@@ -49,7 +55,8 @@ function reducer(state, action) {
|
|
|
49
55
|
isLoading: false,
|
|
50
56
|
error: action.error,
|
|
51
57
|
streamPhase: "idle",
|
|
52
|
-
streamingContent: ""
|
|
58
|
+
streamingContent: "",
|
|
59
|
+
streamingAgent: null
|
|
53
60
|
};
|
|
54
61
|
case "LOAD_CONVERSATION":
|
|
55
62
|
return {
|
|
@@ -75,107 +82,142 @@ function useAgentChat(config) {
|
|
|
75
82
|
const configRef = useRef(config);
|
|
76
83
|
configRef.current = config;
|
|
77
84
|
const lastUserMessageRef = useRef(null);
|
|
85
|
+
const lastUserAttachmentsRef = useRef(void 0);
|
|
78
86
|
const sendMessage = useCallback(
|
|
79
|
-
async (content) => {
|
|
80
|
-
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 ?? {};
|
|
81
90
|
lastUserMessageRef.current = content;
|
|
91
|
+
lastUserAttachmentsRef.current = attachments;
|
|
82
92
|
const userMessage = {
|
|
83
93
|
id: generateMessageId(),
|
|
84
94
|
role: "user",
|
|
85
95
|
content,
|
|
96
|
+
attachments,
|
|
86
97
|
timestamp: /* @__PURE__ */ new Date()
|
|
87
98
|
};
|
|
88
99
|
dispatch({ type: "SEND_START", message: userMessage });
|
|
89
100
|
const controller = new AbortController();
|
|
90
101
|
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
91
102
|
try {
|
|
92
|
-
const
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
error: {
|
|
110
|
-
code: "API_ERROR",
|
|
111
|
-
message: `HTTP ${response.status}: ${response.statusText}`,
|
|
112
|
-
retryable: response.status >= 500
|
|
113
|
-
}
|
|
114
|
-
});
|
|
115
|
-
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
|
+
}));
|
|
116
120
|
}
|
|
117
|
-
const
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
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
|
|
122
166
|
});
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
const
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
dispatch({ type: "SEND_ERROR", error: event.error });
|
|
160
|
-
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 {
|
|
161
203
|
}
|
|
162
|
-
} catch {
|
|
163
204
|
}
|
|
164
205
|
}
|
|
165
206
|
}
|
|
207
|
+
if (ctx.hadStreamError) return;
|
|
166
208
|
const assistantMessage = {
|
|
167
209
|
id: generateMessageId(),
|
|
168
210
|
role: "assistant",
|
|
169
|
-
content: agentResponse?.message ?? accumulatedContent,
|
|
170
|
-
response: agentResponse ?? void 0,
|
|
171
|
-
agent: capturedAgent ?? void 0,
|
|
211
|
+
content: ctx.agentResponse?.message ?? ctx.accumulatedContent,
|
|
212
|
+
response: ctx.agentResponse ?? void 0,
|
|
213
|
+
agent: ctx.capturedAgent ?? void 0,
|
|
172
214
|
timestamp: /* @__PURE__ */ new Date()
|
|
173
215
|
};
|
|
174
216
|
dispatch({
|
|
175
217
|
type: "SEND_SUCCESS",
|
|
176
218
|
message: assistantMessage,
|
|
177
|
-
streamingContent: accumulatedContent,
|
|
178
|
-
conversationId: capturedConversationId
|
|
219
|
+
streamingContent: ctx.accumulatedContent,
|
|
220
|
+
conversationId: ctx.capturedConversationId
|
|
179
221
|
});
|
|
180
222
|
} catch (err) {
|
|
181
223
|
clearTimeout(timeoutId);
|
|
@@ -206,7 +248,8 @@ function useAgentChat(config) {
|
|
|
206
248
|
}, []);
|
|
207
249
|
const submitFeedback = useCallback(
|
|
208
250
|
async (messageId, rating, comment) => {
|
|
209
|
-
const { apiUrl, feedbackPath = "/feedback", headers
|
|
251
|
+
const { apiUrl, feedbackPath = "/feedback", headers: headersOrFn } = configRef.current;
|
|
252
|
+
const headers = typeof headersOrFn === "function" ? await headersOrFn() : headersOrFn ?? {};
|
|
210
253
|
await fetch(`${apiUrl}${feedbackPath}`, {
|
|
211
254
|
method: "POST",
|
|
212
255
|
headers: { "Content-Type": "application/json", ...headers },
|
|
@@ -217,12 +260,13 @@ function useAgentChat(config) {
|
|
|
217
260
|
);
|
|
218
261
|
const retry = useCallback(async () => {
|
|
219
262
|
if (lastUserMessageRef.current) {
|
|
220
|
-
await sendMessage(lastUserMessageRef.current);
|
|
263
|
+
await sendMessage(lastUserMessageRef.current, lastUserAttachmentsRef.current);
|
|
221
264
|
}
|
|
222
265
|
}, [sendMessage]);
|
|
223
266
|
const reset = useCallback(() => {
|
|
224
267
|
dispatch({ type: "RESET" });
|
|
225
268
|
lastUserMessageRef.current = null;
|
|
269
|
+
lastUserAttachmentsRef.current = void 0;
|
|
226
270
|
}, []);
|
|
227
271
|
const actions = {
|
|
228
272
|
sendMessage,
|
|
@@ -237,7 +281,7 @@ function useAgentChat(config) {
|
|
|
237
281
|
|
|
238
282
|
// src/chat/MessageThread/MessageThread.tsx
|
|
239
283
|
import { twMerge as twMerge5 } from "tailwind-merge";
|
|
240
|
-
import { useEffect, useRef as useRef2 } from "react";
|
|
284
|
+
import { useCallback as useCallback2, useEffect, useRef as useRef2 } from "react";
|
|
241
285
|
|
|
242
286
|
// src/chat/MessageBubble/MessageBubble.tsx
|
|
243
287
|
import { twMerge as twMerge4 } from "tailwind-merge";
|
|
@@ -246,6 +290,7 @@ import { twMerge as twMerge4 } from "tailwind-merge";
|
|
|
246
290
|
import { Badge as Badge2 } from "@surf-kit/core";
|
|
247
291
|
|
|
248
292
|
// src/response/ResponseMessage/ResponseMessage.tsx
|
|
293
|
+
import React from "react";
|
|
249
294
|
import ReactMarkdown from "react-markdown";
|
|
250
295
|
import rehypeSanitize from "rehype-sanitize";
|
|
251
296
|
import { twMerge } from "tailwind-merge";
|
|
@@ -270,6 +315,7 @@ function ResponseMessage({ content, className }) {
|
|
|
270
315
|
"[&_h3]:text-sm [&_h3]:font-semibold [&_h3]:text-accent [&_h3]:mt-2 [&_h3]:mb-1",
|
|
271
316
|
"[&_code]:bg-surface-raised [&_code]:text-accent [&_code]:px-1.5 [&_code]:py-0.5 [&_code]:rounded [&_code]:text-xs [&_code]:font-mono",
|
|
272
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",
|
|
273
319
|
"[&_blockquote]:border-l-2 [&_blockquote]:border-border-strong [&_blockquote]:pl-4 [&_blockquote]:text-text-secondary",
|
|
274
320
|
"[&_a]:text-accent [&_a]:underline-offset-2 [&_a]:hover:text-accent/80",
|
|
275
321
|
className
|
|
@@ -285,11 +331,24 @@ function ResponseMessage({ content, className }) {
|
|
|
285
331
|
p: ({ children }) => /* @__PURE__ */ jsx("p", { className: "my-2", children }),
|
|
286
332
|
ul: ({ children }) => /* @__PURE__ */ jsx("ul", { className: "my-2 list-disc pl-6", children }),
|
|
287
333
|
ol: ({ children }) => /* @__PURE__ */ jsx("ol", { className: "my-2 list-decimal pl-6", children }),
|
|
288
|
-
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
|
+
},
|
|
289
346
|
strong: ({ children }) => /* @__PURE__ */ jsx("strong", { className: "font-semibold", children }),
|
|
347
|
+
em: ({ children }) => /* @__PURE__ */ jsx("em", { className: "italic text-text-secondary", children }),
|
|
290
348
|
h1: ({ children }) => /* @__PURE__ */ jsx("h1", { className: "text-base font-bold mt-4 mb-2", children }),
|
|
291
349
|
h2: ({ children }) => /* @__PURE__ */ jsx("h2", { className: "text-sm font-bold mt-3 mb-1", children }),
|
|
292
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" }),
|
|
293
352
|
code: ({ children }) => /* @__PURE__ */ jsx("code", { className: "bg-surface-sunken rounded px-1 py-0.5 text-xs font-mono", children })
|
|
294
353
|
},
|
|
295
354
|
children: normalizeMarkdownLists(content)
|
|
@@ -425,7 +484,14 @@ function renderWarning(data) {
|
|
|
425
484
|
}
|
|
426
485
|
);
|
|
427
486
|
}
|
|
428
|
-
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;
|
|
429
495
|
if (!data) return null;
|
|
430
496
|
let content;
|
|
431
497
|
switch (uiHint) {
|
|
@@ -505,7 +571,36 @@ function SourceCard({ source, variant = "compact", onNavigate, className }) {
|
|
|
505
571
|
children: [
|
|
506
572
|
/* @__PURE__ */ jsxs2("div", { className: "flex items-start justify-between gap-2", children: [
|
|
507
573
|
/* @__PURE__ */ jsxs2("div", { className: "flex-1 min-w-0", children: [
|
|
508
|
-
/* @__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 }),
|
|
509
604
|
source.section && /* @__PURE__ */ jsx3("p", { className: "text-[11px] font-semibold uppercase tracking-wider text-text-secondary truncate mt-0.5", children: source.section })
|
|
510
605
|
] }),
|
|
511
606
|
/* @__PURE__ */ jsx3(
|
|
@@ -639,13 +734,16 @@ function AgentResponse({
|
|
|
639
734
|
}) {
|
|
640
735
|
return /* @__PURE__ */ jsxs4("div", { className: `flex flex-col gap-4 ${className ?? ""}`, "data-testid": "agent-response", children: [
|
|
641
736
|
/* @__PURE__ */ jsx6(ResponseMessage, { content: response.message }),
|
|
642
|
-
response.ui_hint !== "text" && response.structured_data &&
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
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
|
+
})(),
|
|
649
747
|
(showConfidence || showVerification) && /* @__PURE__ */ jsxs4("div", { className: "flex flex-wrap items-center gap-2 mt-1", "data-testid": "response-meta", children: [
|
|
650
748
|
showConfidence && /* @__PURE__ */ jsxs4(
|
|
651
749
|
Badge2,
|
|
@@ -696,6 +794,31 @@ function AgentResponse({
|
|
|
696
794
|
|
|
697
795
|
// src/chat/MessageBubble/MessageBubble.tsx
|
|
698
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
|
+
}
|
|
699
822
|
function MessageBubble({
|
|
700
823
|
message,
|
|
701
824
|
showAgent,
|
|
@@ -703,23 +826,29 @@ function MessageBubble({
|
|
|
703
826
|
showConfidence = true,
|
|
704
827
|
showVerification = true,
|
|
705
828
|
animated = true,
|
|
829
|
+
userBubbleClassName,
|
|
706
830
|
className
|
|
707
831
|
}) {
|
|
708
832
|
const isUser = message.role === "user";
|
|
833
|
+
const hasAttachments = message.attachments && message.attachments.length > 0;
|
|
709
834
|
if (isUser) {
|
|
710
835
|
return /* @__PURE__ */ jsx7(
|
|
711
836
|
"div",
|
|
712
837
|
{
|
|
713
838
|
"data-message-id": message.id,
|
|
714
839
|
className: twMerge4("flex w-full justify-end", className),
|
|
715
|
-
children: /* @__PURE__ */
|
|
840
|
+
children: /* @__PURE__ */ jsxs5(
|
|
716
841
|
"div",
|
|
717
842
|
{
|
|
718
843
|
className: twMerge4(
|
|
719
|
-
"max-w-[70%] rounded-[18px] rounded-br-[4px] px-4 py-2.5 bg-
|
|
720
|
-
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
|
|
721
847
|
),
|
|
722
|
-
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
|
+
]
|
|
723
852
|
}
|
|
724
853
|
)
|
|
725
854
|
}
|
|
@@ -731,7 +860,7 @@ function MessageBubble({
|
|
|
731
860
|
"data-message-id": message.id,
|
|
732
861
|
className: twMerge4("flex w-full flex-col items-start gap-1.5", className),
|
|
733
862
|
children: [
|
|
734
|
-
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("_", " ") }),
|
|
735
864
|
/* @__PURE__ */ jsx7(
|
|
736
865
|
"div",
|
|
737
866
|
{
|
|
@@ -757,34 +886,70 @@ function MessageBubble({
|
|
|
757
886
|
|
|
758
887
|
// src/chat/MessageThread/MessageThread.tsx
|
|
759
888
|
import { jsx as jsx8, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
760
|
-
function MessageThread({ messages, streamingSlot, showSources, showConfidence, showVerification, className }) {
|
|
761
|
-
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]);
|
|
762
911
|
useEffect(() => {
|
|
763
|
-
|
|
764
|
-
|
|
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]);
|
|
765
921
|
return /* @__PURE__ */ jsxs6(
|
|
766
922
|
"div",
|
|
767
923
|
{
|
|
924
|
+
ref: scrollRef,
|
|
768
925
|
role: "log",
|
|
769
926
|
"aria-live": "polite",
|
|
770
927
|
"aria-label": "Message thread",
|
|
928
|
+
onScroll: handleScroll,
|
|
771
929
|
className: twMerge5(
|
|
772
930
|
"flex flex-col gap-4 overflow-y-auto flex-1 px-4 py-6",
|
|
773
931
|
className
|
|
774
932
|
),
|
|
775
933
|
children: [
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
{
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
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
|
|
788
953
|
]
|
|
789
954
|
}
|
|
790
955
|
);
|
|
@@ -792,8 +957,96 @@ function MessageThread({ messages, streamingSlot, showSources, showConfidence, s
|
|
|
792
957
|
|
|
793
958
|
// src/chat/MessageComposer/MessageComposer.tsx
|
|
794
959
|
import { twMerge as twMerge6 } from "tailwind-merge";
|
|
795
|
-
import { useState as useState2, useRef as useRef3, useCallback as
|
|
960
|
+
import { useState as useState2, useRef as useRef3, useCallback as useCallback3 } from "react";
|
|
796
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
|
+
}
|
|
797
1050
|
function MessageComposer({
|
|
798
1051
|
onSend,
|
|
799
1052
|
isLoading = false,
|
|
@@ -801,23 +1054,29 @@ function MessageComposer({
|
|
|
801
1054
|
className
|
|
802
1055
|
}) {
|
|
803
1056
|
const [value, setValue] = useState2("");
|
|
1057
|
+
const [attachments, setAttachments] = useState2([]);
|
|
1058
|
+
const [dragOver, setDragOver] = useState2(false);
|
|
804
1059
|
const textareaRef = useRef3(null);
|
|
805
|
-
const
|
|
806
|
-
const
|
|
1060
|
+
const fileInputRef = useRef3(null);
|
|
1061
|
+
const canSend = (value.trim().length > 0 || attachments.length > 0) && !isLoading;
|
|
1062
|
+
const resetHeight = useCallback3(() => {
|
|
807
1063
|
const el = textareaRef.current;
|
|
808
1064
|
if (el) {
|
|
809
1065
|
el.style.height = "auto";
|
|
810
1066
|
el.style.overflowY = "hidden";
|
|
811
1067
|
}
|
|
812
1068
|
}, []);
|
|
813
|
-
const handleSend =
|
|
1069
|
+
const handleSend = useCallback3(() => {
|
|
814
1070
|
if (!canSend) return;
|
|
815
|
-
|
|
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);
|
|
816
1074
|
setValue("");
|
|
1075
|
+
setAttachments([]);
|
|
817
1076
|
resetHeight();
|
|
818
1077
|
textareaRef.current?.focus();
|
|
819
|
-
}, [canSend, onSend, value, resetHeight]);
|
|
820
|
-
const handleKeyDown =
|
|
1078
|
+
}, [canSend, onSend, value, attachments, resetHeight]);
|
|
1079
|
+
const handleKeyDown = useCallback3(
|
|
821
1080
|
(e) => {
|
|
822
1081
|
if (e.key === "Enter" && !e.shiftKey) {
|
|
823
1082
|
e.preventDefault();
|
|
@@ -826,64 +1085,194 @@ function MessageComposer({
|
|
|
826
1085
|
},
|
|
827
1086
|
[handleSend]
|
|
828
1087
|
);
|
|
829
|
-
const handleChange =
|
|
1088
|
+
const handleChange = useCallback3(
|
|
830
1089
|
(e) => {
|
|
831
1090
|
setValue(e.target.value);
|
|
832
1091
|
const el = e.target;
|
|
833
1092
|
el.style.height = "auto";
|
|
834
|
-
const capped = Math.min(el.scrollHeight,
|
|
1093
|
+
const capped = Math.min(el.scrollHeight, 200);
|
|
835
1094
|
el.style.height = `${capped}px`;
|
|
836
|
-
el.style.overflowY = el.scrollHeight >
|
|
1095
|
+
el.style.overflowY = el.scrollHeight > 200 ? "auto" : "hidden";
|
|
837
1096
|
},
|
|
838
1097
|
[]
|
|
839
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
|
+
);
|
|
840
1178
|
return /* @__PURE__ */ jsxs7(
|
|
841
1179
|
"div",
|
|
842
1180
|
{
|
|
843
1181
|
className: twMerge6(
|
|
844
|
-
"
|
|
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",
|
|
845
1187
|
className
|
|
846
1188
|
),
|
|
1189
|
+
onDragOver: handleDragOver,
|
|
1190
|
+
onDragLeave: handleDragLeave,
|
|
1191
|
+
onDrop: handleDrop,
|
|
847
1192
|
children: [
|
|
848
1193
|
/* @__PURE__ */ jsx9(
|
|
849
|
-
"
|
|
1194
|
+
"input",
|
|
850
1195
|
{
|
|
851
|
-
ref:
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
className: twMerge6(
|
|
859
|
-
"flex-1 resize-none rounded-xl border border-border bg-surface/80",
|
|
860
|
-
"px-4 py-2.5 text-sm text-text-primary placeholder:text-text-muted",
|
|
861
|
-
"focus:border-transparent focus:ring-2 focus:ring-accent/40 focus:outline-none",
|
|
862
|
-
"disabled:opacity-50 disabled:cursor-not-allowed",
|
|
863
|
-
"overflow-hidden",
|
|
864
|
-
"transition-all duration-200"
|
|
865
|
-
),
|
|
866
|
-
style: { colorScheme: "dark" },
|
|
867
|
-
"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"
|
|
868
1203
|
}
|
|
869
1204
|
),
|
|
870
|
-
/* @__PURE__ */ jsx9(
|
|
871
|
-
|
|
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,
|
|
872
1207
|
{
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
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
|
+
] })
|
|
887
1276
|
]
|
|
888
1277
|
}
|
|
889
1278
|
);
|
|
@@ -896,6 +1285,7 @@ function WelcomeScreen({
|
|
|
896
1285
|
title = "Welcome",
|
|
897
1286
|
message = "How can I help you today?",
|
|
898
1287
|
icon,
|
|
1288
|
+
iconClassName,
|
|
899
1289
|
suggestedQuestions = [],
|
|
900
1290
|
onQuestionSelect,
|
|
901
1291
|
className
|
|
@@ -908,12 +1298,15 @@ function WelcomeScreen({
|
|
|
908
1298
|
className
|
|
909
1299
|
),
|
|
910
1300
|
children: [
|
|
911
|
-
/* @__PURE__ */ jsx10(
|
|
1301
|
+
icon ? iconClassName ? /* @__PURE__ */ jsx10("div", { className: iconClassName, "aria-hidden": "true", children: icon }) : icon : /* @__PURE__ */ jsx10(
|
|
912
1302
|
"div",
|
|
913
1303
|
{
|
|
914
|
-
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
|
+
),
|
|
915
1308
|
"aria-hidden": "true",
|
|
916
|
-
children:
|
|
1309
|
+
children: /* @__PURE__ */ jsx10("span", { className: "text-2xl", children: "\u2726" })
|
|
917
1310
|
}
|
|
918
1311
|
),
|
|
919
1312
|
/* @__PURE__ */ jsxs8("div", { className: "flex flex-col gap-2", children: [
|
|
@@ -923,7 +1316,7 @@ function WelcomeScreen({
|
|
|
923
1316
|
suggestedQuestions.length > 0 && /* @__PURE__ */ jsx10(
|
|
924
1317
|
"div",
|
|
925
1318
|
{
|
|
926
|
-
className: "flex flex-wrap justify-center gap-2 max-w-
|
|
1319
|
+
className: "flex flex-wrap justify-center gap-2 max-w-xl",
|
|
927
1320
|
role: "group",
|
|
928
1321
|
"aria-label": "Suggested questions",
|
|
929
1322
|
children: suggestedQuestions.map((question) => /* @__PURE__ */ jsx10(
|
|
@@ -932,7 +1325,7 @@ function WelcomeScreen({
|
|
|
932
1325
|
type: "button",
|
|
933
1326
|
onClick: () => onQuestionSelect?.(question),
|
|
934
1327
|
className: twMerge7(
|
|
935
|
-
"px-
|
|
1328
|
+
"px-3.5 py-1.5 rounded-full text-[12px]",
|
|
936
1329
|
"border border-border bg-transparent text-text-secondary",
|
|
937
1330
|
"hover:bg-accent/10 hover:border-interactive hover:text-text-primary",
|
|
938
1331
|
"focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-accent",
|
|
@@ -951,7 +1344,8 @@ function WelcomeScreen({
|
|
|
951
1344
|
|
|
952
1345
|
// src/streaming/StreamingMessage/StreamingMessage.tsx
|
|
953
1346
|
import { useEffect as useEffect3, useRef as useRef5 } from "react";
|
|
954
|
-
import {
|
|
1347
|
+
import { twMerge as twMerge8 } from "tailwind-merge";
|
|
1348
|
+
import { WaveLoader } from "@surf-kit/core";
|
|
955
1349
|
|
|
956
1350
|
// src/hooks/useCharacterDrain.ts
|
|
957
1351
|
import { useState as useState3, useRef as useRef4, useEffect as useEffect2 } from "react";
|
|
@@ -980,7 +1374,10 @@ function useCharacterDrain(target, msPerChar = 15) {
|
|
|
980
1374
|
const elapsed = now - lastTimeRef.current;
|
|
981
1375
|
const charsToAdvance = Math.floor(elapsed / msPerCharRef.current);
|
|
982
1376
|
if (charsToAdvance > 0 && indexRef.current < currentTarget.length) {
|
|
983
|
-
|
|
1377
|
+
let nextIndex = Math.min(indexRef.current + charsToAdvance, currentTarget.length);
|
|
1378
|
+
while (nextIndex < currentTarget.length && currentTarget[nextIndex - 1].trim() === "") {
|
|
1379
|
+
nextIndex++;
|
|
1380
|
+
}
|
|
984
1381
|
indexRef.current = nextIndex;
|
|
985
1382
|
lastTimeRef.current = now;
|
|
986
1383
|
setDisplayed(currentTarget.slice(0, nextIndex));
|
|
@@ -1025,14 +1422,35 @@ var phaseLabels = {
|
|
|
1025
1422
|
generating: "Writing...",
|
|
1026
1423
|
verifying: "Verifying..."
|
|
1027
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
|
+
`;
|
|
1028
1443
|
function StreamingMessage({
|
|
1029
1444
|
stream,
|
|
1030
1445
|
onComplete,
|
|
1446
|
+
onDraining,
|
|
1031
1447
|
showPhases = true,
|
|
1032
1448
|
className
|
|
1033
1449
|
}) {
|
|
1034
1450
|
const onCompleteRef = useRef5(onComplete);
|
|
1035
1451
|
onCompleteRef.current = onComplete;
|
|
1452
|
+
const onDrainingRef = useRef5(onDraining);
|
|
1453
|
+
onDrainingRef.current = onDraining;
|
|
1036
1454
|
const wasActiveRef = useRef5(stream.active);
|
|
1037
1455
|
useEffect3(() => {
|
|
1038
1456
|
if (wasActiveRef.current && !stream.active) {
|
|
@@ -1041,35 +1459,40 @@ function StreamingMessage({
|
|
|
1041
1459
|
wasActiveRef.current = stream.active;
|
|
1042
1460
|
}, [stream.active]);
|
|
1043
1461
|
const phaseLabel = phaseLabels[stream.phase];
|
|
1044
|
-
const { displayed:
|
|
1045
|
-
|
|
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: [
|
|
1046
1471
|
/* @__PURE__ */ jsxs9("div", { "aria-live": "assertive", className: "sr-only", children: [
|
|
1047
1472
|
stream.active && stream.phase !== "idle" && "Response started",
|
|
1048
1473
|
!stream.active && stream.content && "Response complete"
|
|
1049
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 }),
|
|
1050
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: [
|
|
1051
|
-
|
|
1478
|
+
showPhaseIndicator && /* @__PURE__ */ jsxs9(
|
|
1052
1479
|
"div",
|
|
1053
1480
|
{
|
|
1054
|
-
className: "flex items-center gap-2
|
|
1481
|
+
className: "flex items-center gap-2 text-sm text-text-secondary",
|
|
1055
1482
|
"data-testid": "phase-indicator",
|
|
1056
1483
|
children: [
|
|
1057
|
-
/* @__PURE__ */ jsx11("span", { "aria-hidden": "true", children: /* @__PURE__ */ jsx11(
|
|
1484
|
+
/* @__PURE__ */ jsx11("span", { "aria-hidden": "true", children: /* @__PURE__ */ jsx11(WaveLoader, { size: "sm", color: "#38bdf8" }) }),
|
|
1058
1485
|
/* @__PURE__ */ jsx11("span", { children: phaseLabel })
|
|
1059
1486
|
]
|
|
1060
1487
|
}
|
|
1061
1488
|
),
|
|
1062
|
-
/* @__PURE__ */
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
"data-testid": "streaming-cursor"
|
|
1070
|
-
}
|
|
1071
|
-
)
|
|
1072
|
-
] })
|
|
1489
|
+
displayedContent && /* @__PURE__ */ jsx11(
|
|
1490
|
+
ResponseMessage,
|
|
1491
|
+
{
|
|
1492
|
+
content: displayedContent,
|
|
1493
|
+
className: showCursor ? "sk-streaming-cursor" : void 0
|
|
1494
|
+
}
|
|
1495
|
+
)
|
|
1073
1496
|
] })
|
|
1074
1497
|
] });
|
|
1075
1498
|
}
|
|
@@ -1100,7 +1523,7 @@ function AgentChat({
|
|
|
1100
1523
|
return /* @__PURE__ */ jsxs10(
|
|
1101
1524
|
"div",
|
|
1102
1525
|
{
|
|
1103
|
-
className:
|
|
1526
|
+
className: twMerge9(
|
|
1104
1527
|
"flex flex-col h-full bg-canvas border border-border rounded-xl overflow-hidden",
|
|
1105
1528
|
className
|
|
1106
1529
|
),
|
|
@@ -1143,7 +1566,7 @@ function AgentChat({
|
|
|
1143
1566
|
}
|
|
1144
1567
|
|
|
1145
1568
|
// src/chat/ConversationList/ConversationList.tsx
|
|
1146
|
-
import { twMerge as
|
|
1569
|
+
import { twMerge as twMerge10 } from "tailwind-merge";
|
|
1147
1570
|
import { jsx as jsx13, jsxs as jsxs11 } from "react/jsx-runtime";
|
|
1148
1571
|
function ConversationList({
|
|
1149
1572
|
conversations,
|
|
@@ -1157,7 +1580,7 @@ function ConversationList({
|
|
|
1157
1580
|
"nav",
|
|
1158
1581
|
{
|
|
1159
1582
|
"aria-label": "Conversation list",
|
|
1160
|
-
className:
|
|
1583
|
+
className: twMerge10("flex flex-col h-full bg-canvas", className),
|
|
1161
1584
|
children: [
|
|
1162
1585
|
onNew && /* @__PURE__ */ jsx13("div", { className: "p-3 border-b border-border", children: /* @__PURE__ */ jsx13(
|
|
1163
1586
|
"button",
|
|
@@ -1174,7 +1597,7 @@ function ConversationList({
|
|
|
1174
1597
|
return /* @__PURE__ */ jsxs11(
|
|
1175
1598
|
"li",
|
|
1176
1599
|
{
|
|
1177
|
-
className:
|
|
1600
|
+
className: twMerge10(
|
|
1178
1601
|
"flex items-start border-b border-border transition-colors duration-200",
|
|
1179
1602
|
"hover:bg-surface",
|
|
1180
1603
|
isActive && "bg-surface-raised border-l-2 border-l-accent"
|
|
@@ -1247,7 +1670,7 @@ function AgentFullPage({
|
|
|
1247
1670
|
className
|
|
1248
1671
|
}) {
|
|
1249
1672
|
const [sidebarOpen, setSidebarOpen] = useState4(false);
|
|
1250
|
-
const handleSelect =
|
|
1673
|
+
const handleSelect = useCallback4(
|
|
1251
1674
|
(id) => {
|
|
1252
1675
|
onConversationSelect?.(id);
|
|
1253
1676
|
setSidebarOpen(false);
|
|
@@ -1257,7 +1680,7 @@ function AgentFullPage({
|
|
|
1257
1680
|
return /* @__PURE__ */ jsxs12(
|
|
1258
1681
|
"div",
|
|
1259
1682
|
{
|
|
1260
|
-
className:
|
|
1683
|
+
className: twMerge11("flex h-screen w-full overflow-hidden bg-brand-dark", className),
|
|
1261
1684
|
"data-testid": "agent-full-page",
|
|
1262
1685
|
children: [
|
|
1263
1686
|
showConversationList && /* @__PURE__ */ jsxs12(Fragment, { children: [
|
|
@@ -1272,7 +1695,7 @@ function AgentFullPage({
|
|
|
1272
1695
|
/* @__PURE__ */ jsx14(
|
|
1273
1696
|
"aside",
|
|
1274
1697
|
{
|
|
1275
|
-
className:
|
|
1698
|
+
className: twMerge11(
|
|
1276
1699
|
"bg-brand-dark border-r border-brand-gold/15 w-72 shrink-0 flex-col z-40",
|
|
1277
1700
|
// Desktop: always visible
|
|
1278
1701
|
"hidden md:flex",
|
|
@@ -1338,7 +1761,7 @@ function AgentFullPage({
|
|
|
1338
1761
|
}
|
|
1339
1762
|
|
|
1340
1763
|
// src/layouts/AgentPanel/AgentPanel.tsx
|
|
1341
|
-
import { twMerge as
|
|
1764
|
+
import { twMerge as twMerge12 } from "tailwind-merge";
|
|
1342
1765
|
import { useRef as useRef6, useEffect as useEffect4 } from "react";
|
|
1343
1766
|
import { jsx as jsx15, jsxs as jsxs13 } from "react/jsx-runtime";
|
|
1344
1767
|
function AgentPanel({
|
|
@@ -1363,13 +1786,13 @@ function AgentPanel({
|
|
|
1363
1786
|
return /* @__PURE__ */ jsxs13(
|
|
1364
1787
|
"div",
|
|
1365
1788
|
{
|
|
1366
|
-
className:
|
|
1789
|
+
className: twMerge12("fixed inset-0 z-50", !isOpen && "pointer-events-none"),
|
|
1367
1790
|
"aria-hidden": !isOpen,
|
|
1368
1791
|
children: [
|
|
1369
1792
|
/* @__PURE__ */ jsx15(
|
|
1370
1793
|
"div",
|
|
1371
1794
|
{
|
|
1372
|
-
className:
|
|
1795
|
+
className: twMerge12(
|
|
1373
1796
|
"fixed inset-0 transition-opacity duration-300",
|
|
1374
1797
|
isOpen ? "opacity-100 bg-brand-dark/70 backdrop-blur-sm pointer-events-auto" : "opacity-0 pointer-events-none"
|
|
1375
1798
|
),
|
|
@@ -1385,7 +1808,7 @@ function AgentPanel({
|
|
|
1385
1808
|
"aria-label": title,
|
|
1386
1809
|
"aria-modal": isOpen ? "true" : void 0,
|
|
1387
1810
|
style: { width: widthStyle, maxWidth: "100vw" },
|
|
1388
|
-
className:
|
|
1811
|
+
className: twMerge12(
|
|
1389
1812
|
"fixed top-0 h-full flex flex-col z-50 bg-brand-dark shadow-card",
|
|
1390
1813
|
"transition-transform duration-300 ease-in-out",
|
|
1391
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"}`,
|
|
@@ -1427,8 +1850,8 @@ function AgentPanel({
|
|
|
1427
1850
|
}
|
|
1428
1851
|
|
|
1429
1852
|
// src/layouts/AgentWidget/AgentWidget.tsx
|
|
1430
|
-
import { twMerge as
|
|
1431
|
-
import { useState as useState5, useCallback as
|
|
1853
|
+
import { twMerge as twMerge13 } from "tailwind-merge";
|
|
1854
|
+
import { useState as useState5, useCallback as useCallback5 } from "react";
|
|
1432
1855
|
import { jsx as jsx16, jsxs as jsxs14 } from "react/jsx-runtime";
|
|
1433
1856
|
function AgentWidget({
|
|
1434
1857
|
endpoint,
|
|
@@ -1438,7 +1861,7 @@ function AgentWidget({
|
|
|
1438
1861
|
className
|
|
1439
1862
|
}) {
|
|
1440
1863
|
const [isOpen, setIsOpen] = useState5(false);
|
|
1441
|
-
const toggle =
|
|
1864
|
+
const toggle = useCallback5(() => {
|
|
1442
1865
|
setIsOpen((prev) => !prev);
|
|
1443
1866
|
}, []);
|
|
1444
1867
|
const positionClasses = position === "bottom-left" ? "left-4 bottom-4" : "right-4 bottom-4";
|
|
@@ -1451,7 +1874,7 @@ function AgentWidget({
|
|
|
1451
1874
|
role: "dialog",
|
|
1452
1875
|
"aria-label": title,
|
|
1453
1876
|
"aria-hidden": !isOpen,
|
|
1454
|
-
className:
|
|
1877
|
+
className: twMerge13(
|
|
1455
1878
|
"fixed z-50 flex flex-col",
|
|
1456
1879
|
"w-[min(400px,calc(100vw-2rem))] h-[min(600px,calc(100vh-6rem))]",
|
|
1457
1880
|
"rounded-2xl overflow-hidden border border-brand-gold/15",
|
|
@@ -1498,7 +1921,7 @@ function AgentWidget({
|
|
|
1498
1921
|
onClick: toggle,
|
|
1499
1922
|
"aria-label": isOpen ? "Close chat" : triggerLabel,
|
|
1500
1923
|
"aria-expanded": isOpen,
|
|
1501
|
-
className:
|
|
1924
|
+
className: twMerge13(
|
|
1502
1925
|
"fixed z-50 flex items-center justify-center w-14 h-14 rounded-full",
|
|
1503
1926
|
"bg-brand-blue text-brand-cream shadow-glow-cyan",
|
|
1504
1927
|
"hover:bg-brand-cyan hover:shadow-glow-cyan hover:scale-105",
|
|
@@ -1516,7 +1939,7 @@ function AgentWidget({
|
|
|
1516
1939
|
}
|
|
1517
1940
|
|
|
1518
1941
|
// src/layouts/AgentEmbed/AgentEmbed.tsx
|
|
1519
|
-
import { twMerge as
|
|
1942
|
+
import { twMerge as twMerge14 } from "tailwind-merge";
|
|
1520
1943
|
import { jsx as jsx17 } from "react/jsx-runtime";
|
|
1521
1944
|
function AgentEmbed({
|
|
1522
1945
|
endpoint,
|
|
@@ -1526,7 +1949,7 @@ function AgentEmbed({
|
|
|
1526
1949
|
return /* @__PURE__ */ jsx17(
|
|
1527
1950
|
"div",
|
|
1528
1951
|
{
|
|
1529
|
-
className:
|
|
1952
|
+
className: twMerge14("w-full h-full min-h-0", className),
|
|
1530
1953
|
"data-testid": "agent-embed",
|
|
1531
1954
|
children: /* @__PURE__ */ jsx17(
|
|
1532
1955
|
AgentChat,
|