@iota-uz/sdk 0.1.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 +201 -0
- package/README.MD +164 -0
- package/assets/fonts/Actay/Actay-Regular.otf +0 -0
- package/assets/fonts/Actay/Actay-RegularItalic.otf +0 -0
- package/assets/fonts/Actay/ActayCondensed-Thin.otf +0 -0
- package/assets/fonts/Actay/ActayCondensed-ThinItalic.otf +0 -0
- package/assets/fonts/Actay/ActayWide-Bold.otf +0 -0
- package/assets/fonts/Actay/ActayWide-BoldItalic.otf +0 -0
- package/assets/fonts/Gilroy/Gilroy-Black.woff2 +0 -0
- package/assets/fonts/Gilroy/Gilroy-BlackItalic.woff2 +0 -0
- package/assets/fonts/Gilroy/Gilroy-Bold.woff2 +0 -0
- package/assets/fonts/Gilroy/Gilroy-BoldItalic.woff2 +0 -0
- package/assets/fonts/Gilroy/Gilroy-Extrabold.woff2 +0 -0
- package/assets/fonts/Gilroy/Gilroy-ExtraboldItalic.woff2 +0 -0
- package/assets/fonts/Gilroy/Gilroy-Heavy.woff2 +0 -0
- package/assets/fonts/Gilroy/Gilroy-HeavyItalic.woff2 +0 -0
- package/assets/fonts/Gilroy/Gilroy-Light.woff2 +0 -0
- package/assets/fonts/Gilroy/Gilroy-LightItalic.woff2 +0 -0
- package/assets/fonts/Gilroy/Gilroy-Medium.woff2 +0 -0
- package/assets/fonts/Gilroy/Gilroy-MediumItalic.woff2 +0 -0
- package/assets/fonts/Gilroy/Gilroy-Regular.woff2 +0 -0
- package/assets/fonts/Gilroy/Gilroy-RegularItalic.woff2 +0 -0
- package/assets/fonts/Gilroy/Gilroy-Semibold.woff2 +0 -0
- package/assets/fonts/Gilroy/Gilroy-SemiboldItalic.woff2 +0 -0
- package/assets/fonts/Gilroy/Gilroy-Thin.woff2 +0 -0
- package/assets/fonts/Gilroy/Gilroy-ThinItalic.woff2 +0 -0
- package/assets/fonts/Gilroy/Gilroy-UltraLight.woff2 +0 -0
- package/assets/fonts/Gilroy/Gilroy-UltraLightItalic.woff2 +0 -0
- package/assets/fonts/Inter.var.woff2 +0 -0
- package/dist/bichat/index.cjs +3033 -0
- package/dist/bichat/index.cjs.map +1 -0
- package/dist/bichat/index.css +139 -0
- package/dist/bichat/index.css.map +1 -0
- package/dist/bichat/index.d.cts +1081 -0
- package/dist/bichat/index.d.ts +1081 -0
- package/dist/bichat/index.mjs +2959 -0
- package/dist/bichat/index.mjs.map +1 -0
- package/dist/bichat/styles.css +160 -0
- package/dist/index.cjs +191 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +237 -0
- package/dist/index.d.ts +237 -0
- package/dist/index.mjs +178 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +70 -0
- package/tailwind/create-config.cjs +142 -0
- package/tailwind/iota.css +1106 -0
- package/tailwind/main.css +2 -0
|
@@ -0,0 +1,3033 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var ReactMarkdown = require('react-markdown');
|
|
4
|
+
var remarkGfm = require('remark-gfm');
|
|
5
|
+
var rehypeSanitize = require('rehype-sanitize');
|
|
6
|
+
var reactSyntaxHighlighter = require('react-syntax-highlighter');
|
|
7
|
+
var prism = require('react-syntax-highlighter/dist/esm/styles/prism');
|
|
8
|
+
var jsxRuntime = require('react/jsx-runtime');
|
|
9
|
+
var react = require('react');
|
|
10
|
+
var react$1 = require('@phosphor-icons/react');
|
|
11
|
+
var dateFns = require('date-fns');
|
|
12
|
+
var recharts = require('recharts');
|
|
13
|
+
var framerMotion = require('framer-motion');
|
|
14
|
+
|
|
15
|
+
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
16
|
+
|
|
17
|
+
var ReactMarkdown__default = /*#__PURE__*/_interopDefault(ReactMarkdown);
|
|
18
|
+
var remarkGfm__default = /*#__PURE__*/_interopDefault(remarkGfm);
|
|
19
|
+
var rehypeSanitize__default = /*#__PURE__*/_interopDefault(rehypeSanitize);
|
|
20
|
+
|
|
21
|
+
var __defProp = Object.defineProperty;
|
|
22
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
23
|
+
var __esm = (fn, res) => function __init() {
|
|
24
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
25
|
+
};
|
|
26
|
+
var __export = (target, all) => {
|
|
27
|
+
for (var name in all)
|
|
28
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
// ui/src/bichat/components/MarkdownRenderer.tsx
|
|
32
|
+
var MarkdownRenderer_exports = {};
|
|
33
|
+
__export(MarkdownRenderer_exports, {
|
|
34
|
+
MarkdownRenderer: () => MarkdownRenderer
|
|
35
|
+
});
|
|
36
|
+
function MarkdownRenderer({ content }) {
|
|
37
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "prose prose-sm max-w-none", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
38
|
+
ReactMarkdown__default.default,
|
|
39
|
+
{
|
|
40
|
+
remarkPlugins: [remarkGfm__default.default],
|
|
41
|
+
rehypePlugins: [rehypeSanitize__default.default],
|
|
42
|
+
components: {
|
|
43
|
+
code({ className, children, ...props }) {
|
|
44
|
+
const match = /language-(\w+)/.exec(className || "");
|
|
45
|
+
const isInline = !match;
|
|
46
|
+
return !isInline && match ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
47
|
+
reactSyntaxHighlighter.Prism,
|
|
48
|
+
{
|
|
49
|
+
style: prism.tomorrow,
|
|
50
|
+
language: match[1],
|
|
51
|
+
PreTag: "div",
|
|
52
|
+
children: String(children).replace(/\n$/, "")
|
|
53
|
+
}
|
|
54
|
+
) : /* @__PURE__ */ jsxRuntime.jsx("code", { className, ...props, children });
|
|
55
|
+
}
|
|
56
|
+
},
|
|
57
|
+
children: content
|
|
58
|
+
}
|
|
59
|
+
) });
|
|
60
|
+
}
|
|
61
|
+
var init_MarkdownRenderer = __esm({
|
|
62
|
+
"ui/src/bichat/components/MarkdownRenderer.tsx"() {
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
// ui/src/bichat/types/index.ts
|
|
67
|
+
var MessageRole = /* @__PURE__ */ ((MessageRole2) => {
|
|
68
|
+
MessageRole2["User"] = "user";
|
|
69
|
+
MessageRole2["Assistant"] = "assistant";
|
|
70
|
+
MessageRole2["System"] = "system";
|
|
71
|
+
MessageRole2["Tool"] = "tool";
|
|
72
|
+
return MessageRole2;
|
|
73
|
+
})(MessageRole || {});
|
|
74
|
+
|
|
75
|
+
// ui/src/bichat/utils/RateLimiter.ts
|
|
76
|
+
var RateLimiter = class {
|
|
77
|
+
constructor(config) {
|
|
78
|
+
this.timestamps = [];
|
|
79
|
+
this.maxRequests = config.maxRequests;
|
|
80
|
+
this.windowMs = config.windowMs;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Check if a request can be made
|
|
84
|
+
* Updates internal state if request is allowed
|
|
85
|
+
*/
|
|
86
|
+
canMakeRequest() {
|
|
87
|
+
const now = Date.now();
|
|
88
|
+
this.timestamps = this.timestamps.filter((t) => now - t < this.windowMs);
|
|
89
|
+
if (this.timestamps.length >= this.maxRequests) {
|
|
90
|
+
return false;
|
|
91
|
+
}
|
|
92
|
+
this.timestamps.push(now);
|
|
93
|
+
return true;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Get milliseconds until next request is allowed
|
|
97
|
+
* Returns 0 if request can be made immediately
|
|
98
|
+
*/
|
|
99
|
+
getTimeUntilNextRequest() {
|
|
100
|
+
if (this.timestamps.length < this.maxRequests) {
|
|
101
|
+
return 0;
|
|
102
|
+
}
|
|
103
|
+
const now = Date.now();
|
|
104
|
+
const oldestTimestamp = this.timestamps[0];
|
|
105
|
+
const timeElapsed = now - oldestTimestamp;
|
|
106
|
+
const timeRemaining = this.windowMs - timeElapsed;
|
|
107
|
+
return Math.max(0, timeRemaining);
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Reset the rate limiter state
|
|
111
|
+
*/
|
|
112
|
+
reset() {
|
|
113
|
+
this.timestamps = [];
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
var ChatSessionContext = react.createContext(null);
|
|
117
|
+
var DEFAULT_RATE_LIMIT_CONFIG = {
|
|
118
|
+
maxRequests: 20,
|
|
119
|
+
windowMs: 6e4
|
|
120
|
+
// 1 minute
|
|
121
|
+
};
|
|
122
|
+
function ChatSessionProvider({
|
|
123
|
+
dataSource,
|
|
124
|
+
sessionId,
|
|
125
|
+
rateLimiter: externalRateLimiter,
|
|
126
|
+
children
|
|
127
|
+
}) {
|
|
128
|
+
const [message, setMessage] = react.useState("");
|
|
129
|
+
const [messages, setMessages] = react.useState([]);
|
|
130
|
+
const [loading, setLoading] = react.useState(false);
|
|
131
|
+
const [error, setError] = react.useState(null);
|
|
132
|
+
const [currentSessionId, setCurrentSessionId] = react.useState(sessionId);
|
|
133
|
+
const [session, setSession] = react.useState(null);
|
|
134
|
+
const [fetching, setFetching] = react.useState(false);
|
|
135
|
+
const [pendingQuestion, setPendingQuestion] = react.useState(null);
|
|
136
|
+
const [streamingContent, setStreamingContent] = react.useState("");
|
|
137
|
+
const [isStreaming, setIsStreaming] = react.useState(false);
|
|
138
|
+
const abortControllerRef = react.useRef(null);
|
|
139
|
+
const [messageQueue, setMessageQueue] = react.useState([]);
|
|
140
|
+
const [codeOutputs, setCodeOutputs] = react.useState([]);
|
|
141
|
+
const rateLimiterRef = react.useRef(
|
|
142
|
+
externalRateLimiter || new RateLimiter(DEFAULT_RATE_LIMIT_CONFIG)
|
|
143
|
+
);
|
|
144
|
+
react.useEffect(() => {
|
|
145
|
+
setCurrentSessionId(sessionId);
|
|
146
|
+
}, [sessionId]);
|
|
147
|
+
react.useEffect(() => {
|
|
148
|
+
if (!currentSessionId || currentSessionId === "new") {
|
|
149
|
+
setSession(null);
|
|
150
|
+
setMessages([]);
|
|
151
|
+
setPendingQuestion(null);
|
|
152
|
+
setFetching(false);
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
let cancelled = false;
|
|
156
|
+
setFetching(true);
|
|
157
|
+
setError(null);
|
|
158
|
+
dataSource.fetchSession(currentSessionId).then((state) => {
|
|
159
|
+
if (cancelled) return;
|
|
160
|
+
if (state) {
|
|
161
|
+
setSession(state.session);
|
|
162
|
+
setMessages(state.messages);
|
|
163
|
+
setPendingQuestion(state.pendingQuestion || null);
|
|
164
|
+
} else {
|
|
165
|
+
setError("Session not found");
|
|
166
|
+
}
|
|
167
|
+
setFetching(false);
|
|
168
|
+
}).catch((err) => {
|
|
169
|
+
if (cancelled) return;
|
|
170
|
+
setError(err.message || "Failed to load session");
|
|
171
|
+
setFetching(false);
|
|
172
|
+
});
|
|
173
|
+
return () => {
|
|
174
|
+
cancelled = true;
|
|
175
|
+
};
|
|
176
|
+
}, [dataSource, currentSessionId]);
|
|
177
|
+
const handleCopy = react.useCallback(async (text) => {
|
|
178
|
+
await navigator.clipboard.writeText(text);
|
|
179
|
+
}, []);
|
|
180
|
+
const sendMessageDirect = react.useCallback(
|
|
181
|
+
async (content, attachments = []) => {
|
|
182
|
+
if (!content.trim() || loading) return;
|
|
183
|
+
if (!rateLimiterRef.current.canMakeRequest()) {
|
|
184
|
+
const timeUntilNext = rateLimiterRef.current.getTimeUntilNextRequest();
|
|
185
|
+
const seconds = Math.ceil(timeUntilNext / 1e3);
|
|
186
|
+
setError(`Rate limit exceeded. Please wait ${seconds} seconds before sending another message.`);
|
|
187
|
+
setTimeout(() => setError(null), 5e3);
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
setMessage("");
|
|
191
|
+
setLoading(true);
|
|
192
|
+
setError(null);
|
|
193
|
+
setStreamingContent("");
|
|
194
|
+
abortControllerRef.current = new AbortController();
|
|
195
|
+
const tempUserMessage = {
|
|
196
|
+
id: `temp-user-${Date.now()}`,
|
|
197
|
+
sessionId: currentSessionId || "new",
|
|
198
|
+
role: "user" /* User */,
|
|
199
|
+
content,
|
|
200
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
201
|
+
};
|
|
202
|
+
setMessages((prev) => [...prev, tempUserMessage]);
|
|
203
|
+
try {
|
|
204
|
+
let activeSessionId = currentSessionId;
|
|
205
|
+
let shouldNavigateAfter = false;
|
|
206
|
+
if (!activeSessionId || activeSessionId === "new") {
|
|
207
|
+
const result = await dataSource.createSession();
|
|
208
|
+
if (result) {
|
|
209
|
+
activeSessionId = result.id;
|
|
210
|
+
setCurrentSessionId(activeSessionId);
|
|
211
|
+
shouldNavigateAfter = true;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
let accumulatedContent = "";
|
|
215
|
+
let createdSessionId;
|
|
216
|
+
setIsStreaming(true);
|
|
217
|
+
for await (const chunk of dataSource.sendMessage(
|
|
218
|
+
activeSessionId || "new",
|
|
219
|
+
content,
|
|
220
|
+
attachments,
|
|
221
|
+
abortControllerRef.current?.signal
|
|
222
|
+
)) {
|
|
223
|
+
if (abortControllerRef.current?.signal.aborted) {
|
|
224
|
+
break;
|
|
225
|
+
}
|
|
226
|
+
if (chunk.type === "chunk" && chunk.content) {
|
|
227
|
+
accumulatedContent += chunk.content;
|
|
228
|
+
setStreamingContent(accumulatedContent);
|
|
229
|
+
} else if (chunk.type === "error") {
|
|
230
|
+
throw new Error(chunk.error || "Stream error");
|
|
231
|
+
} else if (chunk.type === "done") {
|
|
232
|
+
if (chunk.sessionId) {
|
|
233
|
+
createdSessionId = chunk.sessionId;
|
|
234
|
+
}
|
|
235
|
+
const finalSessionId = createdSessionId || activeSessionId;
|
|
236
|
+
if (finalSessionId && finalSessionId !== "new") {
|
|
237
|
+
const state = await dataSource.fetchSession(finalSessionId);
|
|
238
|
+
if (state) {
|
|
239
|
+
setSession(state.session);
|
|
240
|
+
setMessages(state.messages);
|
|
241
|
+
setPendingQuestion(state.pendingQuestion || null);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
} else if (chunk.type === "user_message" && chunk.sessionId) {
|
|
245
|
+
createdSessionId = chunk.sessionId;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
const targetSessionId = createdSessionId || activeSessionId;
|
|
249
|
+
if (shouldNavigateAfter && targetSessionId && targetSessionId !== "new") {
|
|
250
|
+
dataSource.navigateToSession?.(targetSessionId);
|
|
251
|
+
}
|
|
252
|
+
} catch (err) {
|
|
253
|
+
if (err instanceof Error && err.name === "AbortError") {
|
|
254
|
+
setMessage(content);
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
setMessages((prev) => prev.filter((m) => m.id !== tempUserMessage.id));
|
|
258
|
+
const errorMessage = err instanceof Error ? err.message : "Failed to send message";
|
|
259
|
+
setError(errorMessage);
|
|
260
|
+
console.error("Send message error:", err);
|
|
261
|
+
} finally {
|
|
262
|
+
setLoading(false);
|
|
263
|
+
setStreamingContent("");
|
|
264
|
+
setIsStreaming(false);
|
|
265
|
+
abortControllerRef.current = null;
|
|
266
|
+
}
|
|
267
|
+
},
|
|
268
|
+
[currentSessionId, loading, dataSource]
|
|
269
|
+
);
|
|
270
|
+
const cancelStream = react.useCallback(() => {
|
|
271
|
+
if (abortControllerRef.current) {
|
|
272
|
+
abortControllerRef.current.abort();
|
|
273
|
+
abortControllerRef.current = null;
|
|
274
|
+
setIsStreaming(false);
|
|
275
|
+
setLoading(false);
|
|
276
|
+
}
|
|
277
|
+
}, []);
|
|
278
|
+
const handleSubmit = react.useCallback(
|
|
279
|
+
(e, attachments = []) => {
|
|
280
|
+
e.preventDefault();
|
|
281
|
+
if (!message.trim() && attachments.length === 0) return;
|
|
282
|
+
const convertedAttachments = attachments.map((att) => ({
|
|
283
|
+
id: "",
|
|
284
|
+
// Will be assigned by backend
|
|
285
|
+
filename: att.filename,
|
|
286
|
+
mimeType: att.mimeType,
|
|
287
|
+
sizeBytes: att.sizeBytes,
|
|
288
|
+
base64Data: att.base64Data
|
|
289
|
+
}));
|
|
290
|
+
sendMessageDirect(message, convertedAttachments);
|
|
291
|
+
},
|
|
292
|
+
[message, sendMessageDirect]
|
|
293
|
+
);
|
|
294
|
+
const handleUnqueue = react.useCallback(() => {
|
|
295
|
+
if (messageQueue.length === 0) {
|
|
296
|
+
return null;
|
|
297
|
+
}
|
|
298
|
+
const lastQueued = messageQueue[messageQueue.length - 1];
|
|
299
|
+
setMessageQueue((prev) => prev.slice(0, -1));
|
|
300
|
+
return {
|
|
301
|
+
content: lastQueued.content,
|
|
302
|
+
attachments: lastQueued.attachments
|
|
303
|
+
};
|
|
304
|
+
}, [messageQueue]);
|
|
305
|
+
const handleRegenerate = react.useCallback(
|
|
306
|
+
async (messageId) => {
|
|
307
|
+
if (!currentSessionId || currentSessionId === "new") return;
|
|
308
|
+
const messageIndex = messages.findIndex((m) => m.id === messageId);
|
|
309
|
+
if (messageIndex <= 0) return;
|
|
310
|
+
setLoading(true);
|
|
311
|
+
setError(null);
|
|
312
|
+
try {
|
|
313
|
+
const userMessage = messages[messageIndex - 1];
|
|
314
|
+
if (userMessage && userMessage.role === "user" /* User */) {
|
|
315
|
+
await sendMessageDirect(userMessage.content, []);
|
|
316
|
+
}
|
|
317
|
+
} catch (err) {
|
|
318
|
+
const errorMessage = err instanceof Error ? err.message : "Failed to regenerate message";
|
|
319
|
+
setError(errorMessage);
|
|
320
|
+
console.error("Regenerate error:", err);
|
|
321
|
+
} finally {
|
|
322
|
+
setLoading(false);
|
|
323
|
+
}
|
|
324
|
+
},
|
|
325
|
+
[messages, currentSessionId, sendMessageDirect]
|
|
326
|
+
);
|
|
327
|
+
const handleEdit = react.useCallback(
|
|
328
|
+
async (messageId, newContent) => {
|
|
329
|
+
if (!currentSessionId || currentSessionId === "new") {
|
|
330
|
+
setMessage(newContent);
|
|
331
|
+
setMessages((prev) => prev.filter((m) => m.id !== messageId));
|
|
332
|
+
return;
|
|
333
|
+
}
|
|
334
|
+
setLoading(true);
|
|
335
|
+
setError(null);
|
|
336
|
+
try {
|
|
337
|
+
await sendMessageDirect(newContent, []);
|
|
338
|
+
} catch (err) {
|
|
339
|
+
const errorMessage = err instanceof Error ? err.message : "Failed to edit message";
|
|
340
|
+
setError(errorMessage);
|
|
341
|
+
console.error("Edit error:", err);
|
|
342
|
+
} finally {
|
|
343
|
+
setLoading(false);
|
|
344
|
+
}
|
|
345
|
+
},
|
|
346
|
+
[currentSessionId, sendMessageDirect]
|
|
347
|
+
);
|
|
348
|
+
const handleSubmitQuestionAnswers = react.useCallback(
|
|
349
|
+
(answers) => {
|
|
350
|
+
if (!currentSessionId || !pendingQuestion) return;
|
|
351
|
+
setLoading(true);
|
|
352
|
+
setError(null);
|
|
353
|
+
const previousPendingQuestion = pendingQuestion;
|
|
354
|
+
setPendingQuestion(null);
|
|
355
|
+
(async () => {
|
|
356
|
+
try {
|
|
357
|
+
const result = await dataSource.submitQuestionAnswers(
|
|
358
|
+
currentSessionId,
|
|
359
|
+
previousPendingQuestion.id,
|
|
360
|
+
answers
|
|
361
|
+
);
|
|
362
|
+
if (result.success) {
|
|
363
|
+
if (currentSessionId !== "new") {
|
|
364
|
+
try {
|
|
365
|
+
const state = await dataSource.fetchSession(currentSessionId);
|
|
366
|
+
if (state) {
|
|
367
|
+
setMessages(state.messages);
|
|
368
|
+
setPendingQuestion(state.pendingQuestion || null);
|
|
369
|
+
} else {
|
|
370
|
+
setPendingQuestion(previousPendingQuestion);
|
|
371
|
+
setError("Failed to load updated session");
|
|
372
|
+
}
|
|
373
|
+
} catch (fetchErr) {
|
|
374
|
+
setPendingQuestion(previousPendingQuestion);
|
|
375
|
+
const errorMessage = fetchErr instanceof Error ? fetchErr.message : "Failed to load updated session";
|
|
376
|
+
setError(errorMessage);
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
} else {
|
|
380
|
+
setPendingQuestion(previousPendingQuestion);
|
|
381
|
+
setError(result.error || "Failed to submit answers");
|
|
382
|
+
}
|
|
383
|
+
} catch (err) {
|
|
384
|
+
setPendingQuestion(previousPendingQuestion);
|
|
385
|
+
const errorMessage = err instanceof Error ? err.message : "Failed to submit answers";
|
|
386
|
+
setError(errorMessage);
|
|
387
|
+
} finally {
|
|
388
|
+
setLoading(false);
|
|
389
|
+
}
|
|
390
|
+
})();
|
|
391
|
+
},
|
|
392
|
+
[currentSessionId, pendingQuestion, dataSource]
|
|
393
|
+
);
|
|
394
|
+
const handleCancelPendingQuestion = react.useCallback(async () => {
|
|
395
|
+
if (!currentSessionId || !pendingQuestion) return;
|
|
396
|
+
try {
|
|
397
|
+
const result = await dataSource.cancelPendingQuestion(pendingQuestion.id);
|
|
398
|
+
if (result.success) {
|
|
399
|
+
setPendingQuestion(null);
|
|
400
|
+
} else {
|
|
401
|
+
setError(result.error || "Failed to cancel question");
|
|
402
|
+
}
|
|
403
|
+
} catch (err) {
|
|
404
|
+
const errorMessage = err instanceof Error ? err.message : "Failed to cancel question";
|
|
405
|
+
setError(errorMessage);
|
|
406
|
+
}
|
|
407
|
+
}, [currentSessionId, pendingQuestion, dataSource]);
|
|
408
|
+
const value = {
|
|
409
|
+
// State
|
|
410
|
+
message,
|
|
411
|
+
messages,
|
|
412
|
+
loading,
|
|
413
|
+
error,
|
|
414
|
+
currentSessionId,
|
|
415
|
+
pendingQuestion,
|
|
416
|
+
session,
|
|
417
|
+
fetching,
|
|
418
|
+
streamingContent,
|
|
419
|
+
isStreaming,
|
|
420
|
+
messageQueue,
|
|
421
|
+
codeOutputs,
|
|
422
|
+
// Setters
|
|
423
|
+
setMessage,
|
|
424
|
+
setError,
|
|
425
|
+
setCodeOutputs,
|
|
426
|
+
// Handlers
|
|
427
|
+
handleCopy,
|
|
428
|
+
handleRegenerate,
|
|
429
|
+
handleEdit,
|
|
430
|
+
handleSubmit,
|
|
431
|
+
handleSubmitQuestionAnswers,
|
|
432
|
+
handleCancelPendingQuestion,
|
|
433
|
+
handleUnqueue,
|
|
434
|
+
sendMessage: sendMessageDirect,
|
|
435
|
+
cancel: cancelStream
|
|
436
|
+
};
|
|
437
|
+
return /* @__PURE__ */ jsxRuntime.jsx(ChatSessionContext.Provider, { value, children });
|
|
438
|
+
}
|
|
439
|
+
function useChat() {
|
|
440
|
+
const context = react.useContext(ChatSessionContext);
|
|
441
|
+
if (!context) {
|
|
442
|
+
throw new Error("useChat must be used within ChatSessionProvider");
|
|
443
|
+
}
|
|
444
|
+
return context;
|
|
445
|
+
}
|
|
446
|
+
var IotaContext = react.createContext(null);
|
|
447
|
+
function IotaContextProvider({ children }) {
|
|
448
|
+
const initialContext = window.__BICHAT_CONTEXT__;
|
|
449
|
+
if (!initialContext) {
|
|
450
|
+
throw new Error("BICHAT_CONTEXT not found. Ensure server injected context into window object.");
|
|
451
|
+
}
|
|
452
|
+
return /* @__PURE__ */ jsxRuntime.jsx(IotaContext.Provider, { value: initialContext, children });
|
|
453
|
+
}
|
|
454
|
+
function useIotaContext() {
|
|
455
|
+
const context = react.useContext(IotaContext);
|
|
456
|
+
if (!context) {
|
|
457
|
+
throw new Error("useIotaContext must be used within IotaContextProvider");
|
|
458
|
+
}
|
|
459
|
+
return context;
|
|
460
|
+
}
|
|
461
|
+
function hasPermission(permission) {
|
|
462
|
+
const context = window.__BICHAT_CONTEXT__;
|
|
463
|
+
if (!context) {
|
|
464
|
+
return false;
|
|
465
|
+
}
|
|
466
|
+
return context.user.permissions.includes(permission);
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
// ui/src/bichat/locales/defaults.ts
|
|
470
|
+
var defaultTranslations = {
|
|
471
|
+
// Welcome screen
|
|
472
|
+
"welcome.title": "Welcome to BiChat",
|
|
473
|
+
"welcome.description": "Your intelligent business analytics assistant. Ask questions about your data, generate reports, or explore insights.",
|
|
474
|
+
"welcome.tryAsking": "Try asking",
|
|
475
|
+
// Chat header
|
|
476
|
+
"chat.newChat": "New Chat",
|
|
477
|
+
"chat.archived": "Archived",
|
|
478
|
+
"chat.pinned": "Pinned",
|
|
479
|
+
"chat.goBack": "Go back",
|
|
480
|
+
// Message input
|
|
481
|
+
"input.placeholder": "Type a message...",
|
|
482
|
+
"input.attachFiles": "Attach files",
|
|
483
|
+
"input.attachImages": "Attach images",
|
|
484
|
+
"input.dropImages": "Drop images here",
|
|
485
|
+
"input.sendMessage": "Send message",
|
|
486
|
+
"input.aiThinking": "AI is thinking...",
|
|
487
|
+
"input.processing": "Processing...",
|
|
488
|
+
"input.messagesQueued": "{count} message(s) queued",
|
|
489
|
+
"input.dismissError": "Dismiss error",
|
|
490
|
+
// Message actions
|
|
491
|
+
"message.copy": "Copy",
|
|
492
|
+
"message.copied": "Copied!",
|
|
493
|
+
"message.regenerate": "Regenerate",
|
|
494
|
+
"message.edit": "Edit",
|
|
495
|
+
"message.save": "Save",
|
|
496
|
+
"message.cancel": "Cancel",
|
|
497
|
+
// Assistant turn
|
|
498
|
+
"assistant.thinking": "Thinking...",
|
|
499
|
+
"assistant.toolCall": "Using tool: {name}",
|
|
500
|
+
"assistant.generating": "Generating response...",
|
|
501
|
+
// Question form
|
|
502
|
+
"question.submit": "Submit",
|
|
503
|
+
"question.selectOne": "Select one option",
|
|
504
|
+
"question.selectMulti": "Select one or more options",
|
|
505
|
+
"question.required": "This field is required",
|
|
506
|
+
"question.other": "Other",
|
|
507
|
+
"question.specifyOther": "Please specify",
|
|
508
|
+
// Errors
|
|
509
|
+
"error.generic": "Something went wrong",
|
|
510
|
+
"error.networkError": "Network error. Please try again.",
|
|
511
|
+
"error.sessionExpired": "Session expired. Please refresh.",
|
|
512
|
+
"error.fileTooLarge": "File is too large",
|
|
513
|
+
"error.invalidFile": "Invalid file type",
|
|
514
|
+
"error.maxFiles": "Maximum {max} files allowed",
|
|
515
|
+
// Empty states
|
|
516
|
+
"empty.noMessages": "No messages yet",
|
|
517
|
+
"empty.noSessions": "No chat sessions",
|
|
518
|
+
"empty.startChat": "Start a new chat to begin",
|
|
519
|
+
// Sources panel
|
|
520
|
+
"sources.title": "Sources",
|
|
521
|
+
"sources.viewMore": "View more",
|
|
522
|
+
"sources.citations": "{count} citation(s)",
|
|
523
|
+
// Code outputs
|
|
524
|
+
"codeOutputs.title": "Code Outputs",
|
|
525
|
+
"codeOutputs.download": "Download",
|
|
526
|
+
"codeOutputs.expand": "Expand",
|
|
527
|
+
"codeOutputs.collapse": "Collapse",
|
|
528
|
+
// Charts
|
|
529
|
+
"chart.download": "Download chart",
|
|
530
|
+
"chart.fullscreen": "View fullscreen",
|
|
531
|
+
"chart.noData": "No data available",
|
|
532
|
+
// Example prompt categories
|
|
533
|
+
"category.analysis": "Data Analysis",
|
|
534
|
+
"category.reports": "Reports",
|
|
535
|
+
"category.insights": "Insights"
|
|
536
|
+
};
|
|
537
|
+
|
|
538
|
+
// ui/src/bichat/hooks/useTranslation.ts
|
|
539
|
+
function useTranslation() {
|
|
540
|
+
const { locale } = useIotaContext();
|
|
541
|
+
const t = (key, params) => {
|
|
542
|
+
let text = locale.translations[key] || defaultTranslations[key] || key;
|
|
543
|
+
if (params) {
|
|
544
|
+
Object.keys(params).forEach((paramKey) => {
|
|
545
|
+
const value = params[paramKey];
|
|
546
|
+
text = text.replace(new RegExp(`{{${paramKey}}}`, "g"), String(value));
|
|
547
|
+
});
|
|
548
|
+
}
|
|
549
|
+
return text;
|
|
550
|
+
};
|
|
551
|
+
return {
|
|
552
|
+
t,
|
|
553
|
+
locale: locale.language
|
|
554
|
+
};
|
|
555
|
+
}
|
|
556
|
+
var defaultExamplePrompts = [
|
|
557
|
+
{
|
|
558
|
+
category: "Data Analysis",
|
|
559
|
+
text: "Show me sales trends for the last quarter",
|
|
560
|
+
icon: "chart-bar"
|
|
561
|
+
},
|
|
562
|
+
{
|
|
563
|
+
category: "Reports",
|
|
564
|
+
text: "Generate a summary of recent activity",
|
|
565
|
+
icon: "file-text"
|
|
566
|
+
},
|
|
567
|
+
{
|
|
568
|
+
category: "Insights",
|
|
569
|
+
text: "What are the top performing items?",
|
|
570
|
+
icon: "lightbulb"
|
|
571
|
+
}
|
|
572
|
+
];
|
|
573
|
+
function useBranding() {
|
|
574
|
+
const context = useIotaContext();
|
|
575
|
+
const { t } = useTranslation();
|
|
576
|
+
const branding = react.useMemo(() => {
|
|
577
|
+
const customBranding = context.extensions?.branding || {};
|
|
578
|
+
let examplePrompts = customBranding.welcome?.examplePrompts;
|
|
579
|
+
if (!examplePrompts || examplePrompts.length === 0) {
|
|
580
|
+
examplePrompts = defaultExamplePrompts.map((p) => ({
|
|
581
|
+
...p,
|
|
582
|
+
category: t(`category.${p.category.toLowerCase().replace(/\s+/g, "")}`) || p.category
|
|
583
|
+
}));
|
|
584
|
+
}
|
|
585
|
+
return {
|
|
586
|
+
appName: customBranding.appName || "BiChat",
|
|
587
|
+
logoUrl: customBranding.logoUrl,
|
|
588
|
+
welcome: {
|
|
589
|
+
title: customBranding.welcome?.title || t("welcome.title"),
|
|
590
|
+
description: customBranding.welcome?.description || t("welcome.description"),
|
|
591
|
+
examplePrompts
|
|
592
|
+
},
|
|
593
|
+
theme: customBranding.theme
|
|
594
|
+
};
|
|
595
|
+
}, [context.extensions?.branding, t]);
|
|
596
|
+
return branding;
|
|
597
|
+
}
|
|
598
|
+
function ChatHeader({ session, onBack, logoSlot, actionsSlot }) {
|
|
599
|
+
const { t } = useTranslation();
|
|
600
|
+
const branding = useBranding();
|
|
601
|
+
const BackButton = onBack ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
602
|
+
"button",
|
|
603
|
+
{
|
|
604
|
+
onClick: onBack,
|
|
605
|
+
className: "p-2 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-lg transition-colors",
|
|
606
|
+
"aria-label": t("chat.goBack"),
|
|
607
|
+
children: /* @__PURE__ */ jsxRuntime.jsx("svg", { className: "w-5 h-5", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsxRuntime.jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M15 19l-7-7 7-7" }) })
|
|
608
|
+
}
|
|
609
|
+
) : null;
|
|
610
|
+
const Logo = logoSlot || (branding.logoUrl ? /* @__PURE__ */ jsxRuntime.jsx("img", { src: branding.logoUrl, alt: branding.appName, className: "h-6 w-auto" }) : null);
|
|
611
|
+
if (!session) {
|
|
612
|
+
return /* @__PURE__ */ jsxRuntime.jsx("header", { className: "bichat-header border-b border-[var(--bichat-border)] px-4 py-3", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between", children: [
|
|
613
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3", children: [
|
|
614
|
+
BackButton,
|
|
615
|
+
Logo,
|
|
616
|
+
/* @__PURE__ */ jsxRuntime.jsx("h1", { className: "text-lg font-semibold text-[var(--bichat-text)]", children: t("chat.newChat") })
|
|
617
|
+
] }),
|
|
618
|
+
actionsSlot && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center gap-2", children: actionsSlot })
|
|
619
|
+
] }) });
|
|
620
|
+
}
|
|
621
|
+
return /* @__PURE__ */ jsxRuntime.jsx("header", { className: "bichat-header border-b border-[var(--bichat-border)] px-4 py-3", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between", children: [
|
|
622
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3", children: [
|
|
623
|
+
BackButton,
|
|
624
|
+
Logo,
|
|
625
|
+
/* @__PURE__ */ jsxRuntime.jsx("h1", { className: "text-lg font-semibold text-[var(--bichat-text)]", children: session.title }),
|
|
626
|
+
session.pinned && /* @__PURE__ */ jsxRuntime.jsx(
|
|
627
|
+
"svg",
|
|
628
|
+
{
|
|
629
|
+
className: "w-4 h-4 text-[var(--bichat-primary)]",
|
|
630
|
+
fill: "currentColor",
|
|
631
|
+
viewBox: "0 0 20 20",
|
|
632
|
+
"aria-label": t("chat.pinned"),
|
|
633
|
+
children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M10 2a1 1 0 011 1v1.323l3.954 1.582 1.599-.8a1 1 0 01.894 1.79l-1.233.616 1.738 5.42a1 1 0 01-.285 1.05A3.989 3.989 0 0115 15a3.989 3.989 0 01-2.667-1.019 1 1 0 01-.285-1.05l1.715-5.349L11 6.477V16h2a1 1 0 110 2H7a1 1 0 110-2h2V6.477L6.237 7.582l1.715 5.349a1 1 0 01-.285 1.05A3.989 3.989 0 015 15a3.989 3.989 0 01-2.667-1.019 1 1 0 01-.285-1.05l1.738-5.42-1.233-.617a1 1 0 01.894-1.788l1.599.799L9 4.323V3a1 1 0 011-1z" })
|
|
634
|
+
}
|
|
635
|
+
)
|
|
636
|
+
] }),
|
|
637
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
|
|
638
|
+
session.status === "archived" && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "px-2 py-1 text-xs bg-gray-200 dark:bg-gray-700 text-gray-700 dark:text-gray-300 rounded", children: t("chat.archived") }),
|
|
639
|
+
actionsSlot
|
|
640
|
+
] })
|
|
641
|
+
] }) });
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
// ui/src/bichat/utils/fileUtils.ts
|
|
645
|
+
var MAX_FILE_SIZE_BYTES = 20 * 1024 * 1024;
|
|
646
|
+
var ALLOWED_IMAGE_TYPES = ["image/png", "image/jpeg", "image/webp", "image/gif"];
|
|
647
|
+
function validateImageFile(file, maxSizeBytes = MAX_FILE_SIZE_BYTES) {
|
|
648
|
+
if (!ALLOWED_IMAGE_TYPES.includes(file.type)) {
|
|
649
|
+
throw new Error(`Invalid file type: ${file.type}. Only PNG, JPEG, WebP, and GIF are allowed.`);
|
|
650
|
+
}
|
|
651
|
+
if (file.size > maxSizeBytes) {
|
|
652
|
+
const sizeMB = (file.size / 1024 / 1024).toFixed(1);
|
|
653
|
+
const maxSizeMB = (maxSizeBytes / 1024 / 1024).toFixed(0);
|
|
654
|
+
throw new Error(`File too large: ${sizeMB}MB exceeds ${maxSizeMB}MB limit`);
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
async function convertToBase64(file) {
|
|
658
|
+
return new Promise((resolve, reject) => {
|
|
659
|
+
const reader = new FileReader();
|
|
660
|
+
reader.onload = () => {
|
|
661
|
+
const result = reader.result;
|
|
662
|
+
const base64 = result.split(",")[1];
|
|
663
|
+
resolve(base64);
|
|
664
|
+
};
|
|
665
|
+
reader.onerror = () => reject(new Error("Failed to read file"));
|
|
666
|
+
reader.readAsDataURL(file);
|
|
667
|
+
});
|
|
668
|
+
}
|
|
669
|
+
function createDataUrl(base64, mimeType) {
|
|
670
|
+
return `data:${mimeType};base64,${base64}`;
|
|
671
|
+
}
|
|
672
|
+
function formatFileSize(bytes) {
|
|
673
|
+
if (bytes < 1024) return `${bytes} B`;
|
|
674
|
+
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
|
|
675
|
+
return `${(bytes / 1024 / 1024).toFixed(1)} MB`;
|
|
676
|
+
}
|
|
677
|
+
function validateFileCount(currentCount, newCount, maxCount = 10) {
|
|
678
|
+
const total = currentCount + newCount;
|
|
679
|
+
if (total > maxCount) {
|
|
680
|
+
throw new Error(`Cannot attach more than ${maxCount} files (attempting to add ${total})`);
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
function AttachmentGrid({
|
|
684
|
+
attachments,
|
|
685
|
+
onRemove,
|
|
686
|
+
onView,
|
|
687
|
+
className = ""
|
|
688
|
+
}) {
|
|
689
|
+
if (attachments.length === 0) return null;
|
|
690
|
+
const isEditable = !!onRemove;
|
|
691
|
+
const isViewable = !!onView;
|
|
692
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: `grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 gap-2 ${className}`, children: attachments.map((attachment, index) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative group", children: [
|
|
693
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
694
|
+
"img",
|
|
695
|
+
{
|
|
696
|
+
src: attachment.preview,
|
|
697
|
+
alt: attachment.filename,
|
|
698
|
+
className: `w-full h-24 object-cover rounded-lg border border-gray-200 dark:border-gray-700 ${isViewable ? "cursor-pointer hover:opacity-80 transition-opacity" : ""}`,
|
|
699
|
+
onClick: () => isViewable && onView(index),
|
|
700
|
+
role: isViewable ? "button" : void 0,
|
|
701
|
+
tabIndex: isViewable ? 0 : void 0,
|
|
702
|
+
onKeyDown: (e) => {
|
|
703
|
+
if (isViewable && (e.key === "Enter" || e.key === " ")) {
|
|
704
|
+
e.preventDefault();
|
|
705
|
+
onView(index);
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
),
|
|
710
|
+
isEditable && /* @__PURE__ */ jsxRuntime.jsx(
|
|
711
|
+
"button",
|
|
712
|
+
{
|
|
713
|
+
type: "button",
|
|
714
|
+
onClick: (e) => {
|
|
715
|
+
e.stopPropagation();
|
|
716
|
+
onRemove(index);
|
|
717
|
+
},
|
|
718
|
+
className: "absolute top-1 right-1 p-1 bg-red-500 hover:bg-red-600 text-white rounded-full opacity-0 group-hover:opacity-100 transition-opacity shadow-md",
|
|
719
|
+
"aria-label": `Remove ${attachment.filename}`,
|
|
720
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(react$1.X, { size: 16, weight: "bold" })
|
|
721
|
+
}
|
|
722
|
+
),
|
|
723
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-1 px-1", children: [
|
|
724
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-xs text-gray-600 dark:text-gray-400 truncate", title: attachment.filename, children: attachment.filename.length > 20 ? `${attachment.filename.substring(0, 20)}...` : attachment.filename }),
|
|
725
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-xs text-gray-500 dark:text-gray-500", children: formatFileSize(attachment.sizeBytes) })
|
|
726
|
+
] })
|
|
727
|
+
] }, index)) });
|
|
728
|
+
}
|
|
729
|
+
function ImageModal({ images, initialIndex, onClose }) {
|
|
730
|
+
const [currentIndex, setCurrentIndex] = react.useState(initialIndex);
|
|
731
|
+
const handlePrevious = () => {
|
|
732
|
+
setCurrentIndex((prev) => prev > 0 ? prev - 1 : images.length - 1);
|
|
733
|
+
};
|
|
734
|
+
const handleNext = () => {
|
|
735
|
+
setCurrentIndex((prev) => prev < images.length - 1 ? prev + 1 : 0);
|
|
736
|
+
};
|
|
737
|
+
react.useEffect(() => {
|
|
738
|
+
const handleKeyDown = (e) => {
|
|
739
|
+
if (e.key === "Escape") {
|
|
740
|
+
onClose();
|
|
741
|
+
} else if (e.key === "ArrowLeft") {
|
|
742
|
+
handlePrevious();
|
|
743
|
+
} else if (e.key === "ArrowRight") {
|
|
744
|
+
handleNext();
|
|
745
|
+
}
|
|
746
|
+
};
|
|
747
|
+
window.addEventListener("keydown", handleKeyDown);
|
|
748
|
+
return () => window.removeEventListener("keydown", handleKeyDown);
|
|
749
|
+
}, [onClose]);
|
|
750
|
+
react.useEffect(() => {
|
|
751
|
+
document.body.style.overflow = "hidden";
|
|
752
|
+
return () => {
|
|
753
|
+
document.body.style.overflow = "";
|
|
754
|
+
};
|
|
755
|
+
}, []);
|
|
756
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
757
|
+
"div",
|
|
758
|
+
{
|
|
759
|
+
className: "fixed inset-0 z-50 bg-black/90 flex items-center justify-center",
|
|
760
|
+
onClick: onClose,
|
|
761
|
+
role: "dialog",
|
|
762
|
+
"aria-modal": "true",
|
|
763
|
+
"aria-label": "Image viewer",
|
|
764
|
+
children: [
|
|
765
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
766
|
+
"button",
|
|
767
|
+
{
|
|
768
|
+
onClick: onClose,
|
|
769
|
+
className: "absolute top-4 right-4 p-2 text-white hover:bg-white/10 rounded-lg transition-colors",
|
|
770
|
+
"aria-label": "Close image viewer",
|
|
771
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(react$1.X, { size: 24, weight: "bold" })
|
|
772
|
+
}
|
|
773
|
+
),
|
|
774
|
+
images.length > 1 && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
775
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
776
|
+
"button",
|
|
777
|
+
{
|
|
778
|
+
onClick: (e) => {
|
|
779
|
+
e.stopPropagation();
|
|
780
|
+
handlePrevious();
|
|
781
|
+
},
|
|
782
|
+
className: "absolute left-4 p-2 text-white hover:bg-white/10 rounded-lg transition-colors",
|
|
783
|
+
"aria-label": "Previous image",
|
|
784
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(react$1.CaretLeft, { size: 32, weight: "bold" })
|
|
785
|
+
}
|
|
786
|
+
),
|
|
787
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
788
|
+
"button",
|
|
789
|
+
{
|
|
790
|
+
onClick: (e) => {
|
|
791
|
+
e.stopPropagation();
|
|
792
|
+
handleNext();
|
|
793
|
+
},
|
|
794
|
+
className: "absolute right-4 p-2 text-white hover:bg-white/10 rounded-lg transition-colors",
|
|
795
|
+
"aria-label": "Next image",
|
|
796
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(react$1.CaretRight, { size: 32, weight: "bold" })
|
|
797
|
+
}
|
|
798
|
+
)
|
|
799
|
+
] }),
|
|
800
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
801
|
+
"img",
|
|
802
|
+
{
|
|
803
|
+
src: images[currentIndex].preview,
|
|
804
|
+
alt: images[currentIndex].filename,
|
|
805
|
+
className: "max-w-[90vw] max-h-[90vh] object-contain",
|
|
806
|
+
onClick: (e) => e.stopPropagation()
|
|
807
|
+
}
|
|
808
|
+
),
|
|
809
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
810
|
+
"div",
|
|
811
|
+
{
|
|
812
|
+
className: "absolute bottom-4 left-1/2 transform -translate-x-1/2 text-white text-sm bg-black/50 px-4 py-2 rounded-lg backdrop-blur",
|
|
813
|
+
onClick: (e) => e.stopPropagation(),
|
|
814
|
+
children: [
|
|
815
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-center font-medium mb-1", children: images[currentIndex].filename }),
|
|
816
|
+
images.length > 1 && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-center text-xs opacity-80", children: [
|
|
817
|
+
currentIndex + 1,
|
|
818
|
+
" / ",
|
|
819
|
+
images.length
|
|
820
|
+
] })
|
|
821
|
+
]
|
|
822
|
+
}
|
|
823
|
+
)
|
|
824
|
+
]
|
|
825
|
+
}
|
|
826
|
+
);
|
|
827
|
+
}
|
|
828
|
+
function UserTurnView({ message }) {
|
|
829
|
+
const { handleEdit, handleCopy } = useChat();
|
|
830
|
+
const [selectedImageIndex, setSelectedImageIndex] = react.useState(null);
|
|
831
|
+
const handleCopyClick = async () => {
|
|
832
|
+
if (handleCopy) {
|
|
833
|
+
await handleCopy(message.content);
|
|
834
|
+
} else {
|
|
835
|
+
try {
|
|
836
|
+
await navigator.clipboard.writeText(message.content);
|
|
837
|
+
} catch (err) {
|
|
838
|
+
console.error("Failed to copy:", err);
|
|
839
|
+
}
|
|
840
|
+
}
|
|
841
|
+
};
|
|
842
|
+
const handleEditClick = () => {
|
|
843
|
+
if (handleEdit) {
|
|
844
|
+
const newContent = prompt("Edit message:", message.content);
|
|
845
|
+
if (newContent && newContent !== message.content) {
|
|
846
|
+
handleEdit(message.id, newContent);
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
};
|
|
850
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex gap-3 justify-end group", children: [
|
|
851
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1 flex flex-col items-end max-w-[75%]", children: [
|
|
852
|
+
message.attachments && message.attachments.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mb-2 w-full", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
853
|
+
AttachmentGrid,
|
|
854
|
+
{
|
|
855
|
+
attachments: message.attachments,
|
|
856
|
+
onView: (index) => setSelectedImageIndex(index)
|
|
857
|
+
}
|
|
858
|
+
) }),
|
|
859
|
+
message.content && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "bg-primary-600 text-white rounded-2xl rounded-br-sm px-4 py-3", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-[15px] whitespace-pre-wrap break-words leading-relaxed", children: message.content }) }),
|
|
860
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1 mt-2 opacity-0 group-hover:opacity-100 transition-opacity duration-150", children: [
|
|
861
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs text-gray-400 dark:text-gray-500 mr-1", children: dateFns.formatDistanceToNow(new Date(message.createdAt), { addSuffix: true }) }),
|
|
862
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
863
|
+
"button",
|
|
864
|
+
{
|
|
865
|
+
onClick: handleCopyClick,
|
|
866
|
+
className: "p-1.5 text-gray-400 hover:text-gray-600 dark:text-gray-500 dark:hover:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-800 rounded-md transition-colors duration-150",
|
|
867
|
+
"aria-label": "Copy message",
|
|
868
|
+
title: "Copy",
|
|
869
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(react$1.Copy, { size: 14, weight: "regular" })
|
|
870
|
+
}
|
|
871
|
+
),
|
|
872
|
+
handleEdit && /* @__PURE__ */ jsxRuntime.jsx(
|
|
873
|
+
"button",
|
|
874
|
+
{
|
|
875
|
+
onClick: handleEditClick,
|
|
876
|
+
className: "p-1.5 text-gray-400 hover:text-gray-600 dark:text-gray-500 dark:hover:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-800 rounded-md transition-colors duration-150",
|
|
877
|
+
"aria-label": "Edit message",
|
|
878
|
+
title: "Edit",
|
|
879
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(react$1.PencilSimple, { size: 14, weight: "regular" })
|
|
880
|
+
}
|
|
881
|
+
)
|
|
882
|
+
] })
|
|
883
|
+
] }),
|
|
884
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-shrink-0 w-8 h-8 rounded-full bg-primary-600 flex items-center justify-center text-white font-medium text-sm", children: "U" }),
|
|
885
|
+
selectedImageIndex !== null && message.attachments && /* @__PURE__ */ jsxRuntime.jsx(
|
|
886
|
+
ImageModal,
|
|
887
|
+
{
|
|
888
|
+
images: message.attachments,
|
|
889
|
+
initialIndex: selectedImageIndex,
|
|
890
|
+
onClose: () => setSelectedImageIndex(null)
|
|
891
|
+
}
|
|
892
|
+
)
|
|
893
|
+
] });
|
|
894
|
+
}
|
|
895
|
+
function CodeOutputsPanel({ outputs }) {
|
|
896
|
+
if (!outputs || outputs.length === 0) return null;
|
|
897
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mb-2 p-3 bg-gray-50 dark:bg-gray-900/50 rounded-lg border border-gray-200 dark:border-gray-700", children: [
|
|
898
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-xs font-semibold text-gray-600 dark:text-gray-400 mb-2", children: "Code Output" }),
|
|
899
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "space-y-2", children: outputs.map((output, index) => /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
900
|
+
output.type === "image" && /* @__PURE__ */ jsxRuntime.jsx(
|
|
901
|
+
"img",
|
|
902
|
+
{
|
|
903
|
+
src: output.content.preview,
|
|
904
|
+
alt: "Code output",
|
|
905
|
+
className: "max-w-full rounded border border-gray-300 dark:border-gray-600"
|
|
906
|
+
}
|
|
907
|
+
),
|
|
908
|
+
output.type === "text" && /* @__PURE__ */ jsxRuntime.jsx("pre", { className: "text-xs bg-white dark:bg-gray-800 p-2 rounded overflow-x-auto border border-gray-200 dark:border-gray-700", children: /* @__PURE__ */ jsxRuntime.jsx("code", { className: "text-gray-900 dark:text-gray-100", children: output.content }) }),
|
|
909
|
+
output.type === "error" && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-xs text-red-600 dark:text-red-400 bg-red-50 dark:bg-red-900/20 p-2 rounded border border-red-200 dark:border-red-800", children: output.content })
|
|
910
|
+
] }, index)) })
|
|
911
|
+
] });
|
|
912
|
+
}
|
|
913
|
+
function StreamingCursor() {
|
|
914
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
915
|
+
"span",
|
|
916
|
+
{
|
|
917
|
+
className: "inline-block w-1.5 h-4 ml-0.5 bg-primary-600 dark:bg-primary-500 animate-pulse",
|
|
918
|
+
"aria-label": "AI is typing"
|
|
919
|
+
}
|
|
920
|
+
);
|
|
921
|
+
}
|
|
922
|
+
var COLORS = ["#3b82f6", "#10b981", "#f59e0b", "#ef4444", "#8b5cf6", "#ec4899"];
|
|
923
|
+
function ChartCard({ chartData }) {
|
|
924
|
+
const { type, title, data, xAxisKey = "name", yAxisKey = "value" } = chartData;
|
|
925
|
+
const renderChart = () => {
|
|
926
|
+
switch (type) {
|
|
927
|
+
case "bar":
|
|
928
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(recharts.BarChart, { data, children: [
|
|
929
|
+
/* @__PURE__ */ jsxRuntime.jsx(recharts.CartesianGrid, { strokeDasharray: "3 3" }),
|
|
930
|
+
/* @__PURE__ */ jsxRuntime.jsx(recharts.XAxis, { dataKey: xAxisKey }),
|
|
931
|
+
/* @__PURE__ */ jsxRuntime.jsx(recharts.YAxis, {}),
|
|
932
|
+
/* @__PURE__ */ jsxRuntime.jsx(recharts.Tooltip, {}),
|
|
933
|
+
/* @__PURE__ */ jsxRuntime.jsx(recharts.Legend, {}),
|
|
934
|
+
/* @__PURE__ */ jsxRuntime.jsx(recharts.Bar, { dataKey: yAxisKey, fill: "#3b82f6" })
|
|
935
|
+
] });
|
|
936
|
+
case "line":
|
|
937
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(recharts.LineChart, { data, children: [
|
|
938
|
+
/* @__PURE__ */ jsxRuntime.jsx(recharts.CartesianGrid, { strokeDasharray: "3 3" }),
|
|
939
|
+
/* @__PURE__ */ jsxRuntime.jsx(recharts.XAxis, { dataKey: xAxisKey }),
|
|
940
|
+
/* @__PURE__ */ jsxRuntime.jsx(recharts.YAxis, {}),
|
|
941
|
+
/* @__PURE__ */ jsxRuntime.jsx(recharts.Tooltip, {}),
|
|
942
|
+
/* @__PURE__ */ jsxRuntime.jsx(recharts.Legend, {}),
|
|
943
|
+
/* @__PURE__ */ jsxRuntime.jsx(recharts.Line, { type: "monotone", dataKey: yAxisKey, stroke: "#3b82f6" })
|
|
944
|
+
] });
|
|
945
|
+
case "pie":
|
|
946
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(recharts.PieChart, { children: [
|
|
947
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
948
|
+
recharts.Pie,
|
|
949
|
+
{
|
|
950
|
+
data,
|
|
951
|
+
dataKey: yAxisKey,
|
|
952
|
+
nameKey: xAxisKey,
|
|
953
|
+
cx: "50%",
|
|
954
|
+
cy: "50%",
|
|
955
|
+
outerRadius: 80,
|
|
956
|
+
label: true,
|
|
957
|
+
children: data.map((_entry, index) => /* @__PURE__ */ jsxRuntime.jsx(recharts.Cell, { fill: COLORS[index % COLORS.length] }, `cell-${index}`))
|
|
958
|
+
}
|
|
959
|
+
),
|
|
960
|
+
/* @__PURE__ */ jsxRuntime.jsx(recharts.Tooltip, {}),
|
|
961
|
+
/* @__PURE__ */ jsxRuntime.jsx(recharts.Legend, {})
|
|
962
|
+
] });
|
|
963
|
+
case "area":
|
|
964
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(recharts.AreaChart, { data, children: [
|
|
965
|
+
/* @__PURE__ */ jsxRuntime.jsx(recharts.CartesianGrid, { strokeDasharray: "3 3" }),
|
|
966
|
+
/* @__PURE__ */ jsxRuntime.jsx(recharts.XAxis, { dataKey: xAxisKey }),
|
|
967
|
+
/* @__PURE__ */ jsxRuntime.jsx(recharts.YAxis, {}),
|
|
968
|
+
/* @__PURE__ */ jsxRuntime.jsx(recharts.Tooltip, {}),
|
|
969
|
+
/* @__PURE__ */ jsxRuntime.jsx(recharts.Legend, {}),
|
|
970
|
+
/* @__PURE__ */ jsxRuntime.jsx(recharts.Area, { type: "monotone", dataKey: yAxisKey, stroke: "#3b82f6", fill: "#3b82f6" })
|
|
971
|
+
] });
|
|
972
|
+
default:
|
|
973
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { children: "Unsupported chart type" });
|
|
974
|
+
}
|
|
975
|
+
};
|
|
976
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "border border-[var(--bichat-border)] rounded-lg p-4 bg-white", children: [
|
|
977
|
+
title && /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "text-lg font-semibold mb-4", children: title }),
|
|
978
|
+
/* @__PURE__ */ jsxRuntime.jsx(recharts.ResponsiveContainer, { width: "100%", height: 300, children: renderChart() })
|
|
979
|
+
] });
|
|
980
|
+
}
|
|
981
|
+
function SourcesPanel({ citations }) {
|
|
982
|
+
const [expanded, setExpanded] = react.useState(false);
|
|
983
|
+
if (!citations || citations.length === 0) {
|
|
984
|
+
return null;
|
|
985
|
+
}
|
|
986
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-4 border-t border-[var(--bichat-border)] pt-3", children: [
|
|
987
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
988
|
+
"button",
|
|
989
|
+
{
|
|
990
|
+
type: "button",
|
|
991
|
+
onClick: () => setExpanded(!expanded),
|
|
992
|
+
className: "flex items-center gap-2 text-sm text-gray-600 hover:text-gray-800 transition-colors",
|
|
993
|
+
"aria-expanded": expanded,
|
|
994
|
+
children: [
|
|
995
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
996
|
+
"svg",
|
|
997
|
+
{
|
|
998
|
+
className: `w-4 h-4 transition-transform ${expanded ? "rotate-90" : ""}`,
|
|
999
|
+
fill: "none",
|
|
1000
|
+
stroke: "currentColor",
|
|
1001
|
+
viewBox: "0 0 24 24",
|
|
1002
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
1003
|
+
"path",
|
|
1004
|
+
{
|
|
1005
|
+
strokeLinecap: "round",
|
|
1006
|
+
strokeLinejoin: "round",
|
|
1007
|
+
strokeWidth: 2,
|
|
1008
|
+
d: "M9 5l7 7-7 7"
|
|
1009
|
+
}
|
|
1010
|
+
)
|
|
1011
|
+
}
|
|
1012
|
+
),
|
|
1013
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
|
|
1014
|
+
citations.length,
|
|
1015
|
+
" ",
|
|
1016
|
+
citations.length === 1 ? "source" : "sources"
|
|
1017
|
+
] })
|
|
1018
|
+
]
|
|
1019
|
+
}
|
|
1020
|
+
),
|
|
1021
|
+
expanded && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-2 space-y-2", children: citations.map((citation, index) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
1022
|
+
"div",
|
|
1023
|
+
{
|
|
1024
|
+
className: "p-3 bg-gray-50 rounded-lg text-sm",
|
|
1025
|
+
children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-start gap-2", children: [
|
|
1026
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex-shrink-0 w-5 h-5 bg-[var(--bichat-primary)] text-white rounded-full flex items-center justify-center text-xs", children: index + 1 }),
|
|
1027
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1", children: [
|
|
1028
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "font-medium text-gray-900", children: citation.source }),
|
|
1029
|
+
citation.url && /* @__PURE__ */ jsxRuntime.jsx(
|
|
1030
|
+
"a",
|
|
1031
|
+
{
|
|
1032
|
+
href: citation.url,
|
|
1033
|
+
target: "_blank",
|
|
1034
|
+
rel: "noopener noreferrer",
|
|
1035
|
+
className: "text-[var(--bichat-primary)] hover:underline",
|
|
1036
|
+
children: citation.url
|
|
1037
|
+
}
|
|
1038
|
+
),
|
|
1039
|
+
citation.excerpt && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-1 text-gray-600 italic", children: [
|
|
1040
|
+
'"',
|
|
1041
|
+
citation.excerpt,
|
|
1042
|
+
'"'
|
|
1043
|
+
] })
|
|
1044
|
+
] })
|
|
1045
|
+
] })
|
|
1046
|
+
},
|
|
1047
|
+
citation.id
|
|
1048
|
+
)) })
|
|
1049
|
+
] });
|
|
1050
|
+
}
|
|
1051
|
+
function DownloadCard({ artifact }) {
|
|
1052
|
+
const { type, filename, url, sizeReadable, rowCount, description } = artifact;
|
|
1053
|
+
const icon = type === "excel" ? /* @__PURE__ */ jsxRuntime.jsxs("svg", { className: "w-8 h-8 text-green-600", fill: "currentColor", viewBox: "0 0 20 20", children: [
|
|
1054
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "M9 2a2 2 0 00-2 2v8a2 2 0 002 2h6a2 2 0 002-2V6.414A2 2 0 0016.414 5L14 2.586A2 2 0 0012.586 2H9z" }),
|
|
1055
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "M3 8a2 2 0 012-2v10h8a2 2 0 01-2 2H5a2 2 0 01-2-2V8z" })
|
|
1056
|
+
] }) : /* @__PURE__ */ jsxRuntime.jsx("svg", { className: "w-8 h-8 text-red-600", fill: "currentColor", viewBox: "0 0 20 20", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
1057
|
+
"path",
|
|
1058
|
+
{
|
|
1059
|
+
fillRule: "evenodd",
|
|
1060
|
+
d: "M4 4a2 2 0 012-2h4.586A2 2 0 0112 2.586L15.414 6A2 2 0 0116 7.414V16a2 2 0 01-2 2H6a2 2 0 01-2-2V4z",
|
|
1061
|
+
clipRule: "evenodd"
|
|
1062
|
+
}
|
|
1063
|
+
) });
|
|
1064
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1065
|
+
"a",
|
|
1066
|
+
{
|
|
1067
|
+
href: url,
|
|
1068
|
+
download: filename,
|
|
1069
|
+
className: "flex items-center gap-3 p-4 border border-[var(--bichat-border)] rounded-lg hover:bg-gray-50 transition-colors",
|
|
1070
|
+
children: [
|
|
1071
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { children: icon }),
|
|
1072
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1 min-w-0", children: [
|
|
1073
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "font-medium text-gray-900 truncate", children: filename }),
|
|
1074
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2 text-sm text-gray-600", children: [
|
|
1075
|
+
sizeReadable && /* @__PURE__ */ jsxRuntime.jsx("span", { children: sizeReadable }),
|
|
1076
|
+
rowCount !== void 0 && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
1077
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { children: "\u2022" }),
|
|
1078
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
|
|
1079
|
+
rowCount,
|
|
1080
|
+
" rows"
|
|
1081
|
+
] })
|
|
1082
|
+
] })
|
|
1083
|
+
] }),
|
|
1084
|
+
description && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-sm text-gray-600 mt-1", children: description })
|
|
1085
|
+
] }),
|
|
1086
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1087
|
+
"svg",
|
|
1088
|
+
{
|
|
1089
|
+
className: "w-5 h-5 text-gray-400",
|
|
1090
|
+
fill: "none",
|
|
1091
|
+
stroke: "currentColor",
|
|
1092
|
+
viewBox: "0 0 24 24",
|
|
1093
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
1094
|
+
"path",
|
|
1095
|
+
{
|
|
1096
|
+
strokeLinecap: "round",
|
|
1097
|
+
strokeLinejoin: "round",
|
|
1098
|
+
strokeWidth: 2,
|
|
1099
|
+
d: "M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4"
|
|
1100
|
+
}
|
|
1101
|
+
)
|
|
1102
|
+
}
|
|
1103
|
+
)
|
|
1104
|
+
]
|
|
1105
|
+
}
|
|
1106
|
+
);
|
|
1107
|
+
}
|
|
1108
|
+
function InlineQuestionForm({ pendingQuestion }) {
|
|
1109
|
+
const { handleSubmitQuestionAnswers, handleCancelPendingQuestion } = useChat();
|
|
1110
|
+
const [answers, setAnswers] = react.useState({});
|
|
1111
|
+
const [textInput, setTextInput] = react.useState("");
|
|
1112
|
+
const handleSubmit = (e) => {
|
|
1113
|
+
e.preventDefault();
|
|
1114
|
+
if (pendingQuestion.type === "MULTIPLE_CHOICE" && !answers.choice) {
|
|
1115
|
+
return;
|
|
1116
|
+
}
|
|
1117
|
+
if (pendingQuestion.type === "FREE_TEXT" && !textInput.trim()) {
|
|
1118
|
+
return;
|
|
1119
|
+
}
|
|
1120
|
+
const finalAnswers = pendingQuestion.type === "MULTIPLE_CHOICE" ? answers : { answer: textInput };
|
|
1121
|
+
handleSubmitQuestionAnswers(finalAnswers);
|
|
1122
|
+
};
|
|
1123
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "border border-[var(--bichat-border)] rounded-lg p-4 bg-yellow-50", children: /* @__PURE__ */ jsxRuntime.jsxs("form", { onSubmit: handleSubmit, children: [
|
|
1124
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-start gap-2 mb-4", children: [
|
|
1125
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1126
|
+
"svg",
|
|
1127
|
+
{
|
|
1128
|
+
className: "w-5 h-5 text-yellow-600 flex-shrink-0 mt-0.5",
|
|
1129
|
+
fill: "currentColor",
|
|
1130
|
+
viewBox: "0 0 20 20",
|
|
1131
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
1132
|
+
"path",
|
|
1133
|
+
{
|
|
1134
|
+
fillRule: "evenodd",
|
|
1135
|
+
d: "M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-8-3a1 1 0 00-.867.5 1 1 0 11-1.731-1A3 3 0 0113 8a3.001 3.001 0 01-2 2.83V11a1 1 0 11-2 0v-1a1 1 0 011-1 1 1 0 100-2zm0 8a1 1 0 100-2 1 1 0 000 2z",
|
|
1136
|
+
clipRule: "evenodd"
|
|
1137
|
+
}
|
|
1138
|
+
)
|
|
1139
|
+
}
|
|
1140
|
+
),
|
|
1141
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1", children: [
|
|
1142
|
+
/* @__PURE__ */ jsxRuntime.jsx("h4", { className: "font-medium text-gray-900 mb-2", children: "Question from AI" }),
|
|
1143
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-gray-700", children: pendingQuestion.question })
|
|
1144
|
+
] })
|
|
1145
|
+
] }),
|
|
1146
|
+
pendingQuestion.type === "MULTIPLE_CHOICE" && pendingQuestion.options && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "space-y-2 mb-4", children: pendingQuestion.options.map((option, index) => /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1147
|
+
"label",
|
|
1148
|
+
{
|
|
1149
|
+
className: "flex items-center gap-2 p-2 hover:bg-yellow-100 rounded cursor-pointer",
|
|
1150
|
+
children: [
|
|
1151
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1152
|
+
"input",
|
|
1153
|
+
{
|
|
1154
|
+
type: "radio",
|
|
1155
|
+
name: "choice",
|
|
1156
|
+
value: option,
|
|
1157
|
+
checked: answers.choice === option,
|
|
1158
|
+
onChange: (e) => setAnswers({ ...answers, choice: e.target.value }),
|
|
1159
|
+
className: "w-4 h-4 text-[var(--bichat-primary)]"
|
|
1160
|
+
}
|
|
1161
|
+
),
|
|
1162
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-gray-900", children: option })
|
|
1163
|
+
]
|
|
1164
|
+
},
|
|
1165
|
+
index
|
|
1166
|
+
)) }),
|
|
1167
|
+
pendingQuestion.type === "FREE_TEXT" && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mb-4", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
1168
|
+
"textarea",
|
|
1169
|
+
{
|
|
1170
|
+
value: textInput,
|
|
1171
|
+
onChange: (e) => setTextInput(e.target.value),
|
|
1172
|
+
placeholder: "Type your answer...",
|
|
1173
|
+
className: "w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-[var(--bichat-primary)] focus:border-transparent resize-none",
|
|
1174
|
+
rows: 3
|
|
1175
|
+
}
|
|
1176
|
+
) }),
|
|
1177
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex gap-2", children: [
|
|
1178
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1179
|
+
"button",
|
|
1180
|
+
{
|
|
1181
|
+
type: "submit",
|
|
1182
|
+
className: "px-4 py-2 bg-[var(--bichat-primary)] text-white rounded-lg hover:opacity-90 transition-opacity",
|
|
1183
|
+
children: "Submit Answer"
|
|
1184
|
+
}
|
|
1185
|
+
),
|
|
1186
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1187
|
+
"button",
|
|
1188
|
+
{
|
|
1189
|
+
type: "button",
|
|
1190
|
+
onClick: handleCancelPendingQuestion,
|
|
1191
|
+
className: "px-4 py-2 border border-gray-300 rounded-lg hover:bg-gray-50 transition-colors",
|
|
1192
|
+
children: "Cancel"
|
|
1193
|
+
}
|
|
1194
|
+
)
|
|
1195
|
+
] })
|
|
1196
|
+
] }) });
|
|
1197
|
+
}
|
|
1198
|
+
var MarkdownRenderer2 = react.lazy(
|
|
1199
|
+
() => Promise.resolve().then(() => (init_MarkdownRenderer(), MarkdownRenderer_exports)).then((module) => ({ default: module.MarkdownRenderer }))
|
|
1200
|
+
);
|
|
1201
|
+
function AssistantTurnView({ message }) {
|
|
1202
|
+
const { handleCopy, handleRegenerate, pendingQuestion } = useChat();
|
|
1203
|
+
const [explanationExpanded, setExplanationExpanded] = react.useState(false);
|
|
1204
|
+
const hasContent = message.content?.trim().length > 0;
|
|
1205
|
+
const hasExplanation = !!message.explanation?.trim();
|
|
1206
|
+
const hasPendingQuestion = !!pendingQuestion && pendingQuestion.status === "PENDING" && pendingQuestion.turnId === message.id;
|
|
1207
|
+
const handleCopyClick = async () => {
|
|
1208
|
+
if (handleCopy) {
|
|
1209
|
+
await handleCopy(message.content);
|
|
1210
|
+
} else {
|
|
1211
|
+
try {
|
|
1212
|
+
await navigator.clipboard.writeText(message.content);
|
|
1213
|
+
} catch (err) {
|
|
1214
|
+
console.error("Failed to copy:", err);
|
|
1215
|
+
}
|
|
1216
|
+
}
|
|
1217
|
+
};
|
|
1218
|
+
const handleRegenerateClick = async () => {
|
|
1219
|
+
if (handleRegenerate) {
|
|
1220
|
+
await handleRegenerate(message.id);
|
|
1221
|
+
}
|
|
1222
|
+
};
|
|
1223
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex gap-3 group", children: [
|
|
1224
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-shrink-0 w-8 h-8 rounded-full bg-primary-600 flex items-center justify-center text-white font-medium text-xs", children: "AI" }),
|
|
1225
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1 flex flex-col gap-3 max-w-[85%]", children: [
|
|
1226
|
+
message.codeOutputs && message.codeOutputs.length > 0 && /* @__PURE__ */ jsxRuntime.jsx(CodeOutputsPanel, { outputs: message.codeOutputs }),
|
|
1227
|
+
message.chartData && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mb-1 w-full", children: /* @__PURE__ */ jsxRuntime.jsx(ChartCard, { chartData: message.chartData }) }),
|
|
1228
|
+
message.artifacts && message.artifacts.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mb-1 flex flex-wrap gap-2", children: message.artifacts.map((artifact, index) => /* @__PURE__ */ jsxRuntime.jsx(DownloadCard, { artifact }, `${artifact.filename}-${index}`)) }),
|
|
1229
|
+
hasContent && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-2xl rounded-bl-sm px-4 py-3", children: [
|
|
1230
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1231
|
+
react.Suspense,
|
|
1232
|
+
{
|
|
1233
|
+
fallback: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2 text-sm text-gray-400 dark:text-gray-500", children: [
|
|
1234
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-4 h-4 border-2 border-gray-300 dark:border-gray-600 border-t-transparent rounded-full animate-spin" }),
|
|
1235
|
+
"Loading..."
|
|
1236
|
+
] }),
|
|
1237
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(MarkdownRenderer2, { content: message.content, citations: message.citations })
|
|
1238
|
+
}
|
|
1239
|
+
),
|
|
1240
|
+
message.isStreaming && /* @__PURE__ */ jsxRuntime.jsx(StreamingCursor, {}),
|
|
1241
|
+
message.citations && message.citations.length > 0 && /* @__PURE__ */ jsxRuntime.jsx(SourcesPanel, { citations: message.citations }),
|
|
1242
|
+
hasExplanation && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-4 border-t border-gray-100 dark:border-gray-700 pt-4", children: [
|
|
1243
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
1244
|
+
"button",
|
|
1245
|
+
{
|
|
1246
|
+
type: "button",
|
|
1247
|
+
onClick: () => setExplanationExpanded(!explanationExpanded),
|
|
1248
|
+
className: "flex items-center gap-2 text-sm text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-300 transition-colors",
|
|
1249
|
+
"aria-expanded": explanationExpanded,
|
|
1250
|
+
children: [
|
|
1251
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1252
|
+
"svg",
|
|
1253
|
+
{
|
|
1254
|
+
className: `w-4 h-4 transition-transform duration-150 ${explanationExpanded ? "rotate-90" : ""}`,
|
|
1255
|
+
fill: "none",
|
|
1256
|
+
stroke: "currentColor",
|
|
1257
|
+
viewBox: "0 0 24 24",
|
|
1258
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
1259
|
+
"path",
|
|
1260
|
+
{
|
|
1261
|
+
strokeLinecap: "round",
|
|
1262
|
+
strokeLinejoin: "round",
|
|
1263
|
+
strokeWidth: 2,
|
|
1264
|
+
d: "M9 5l7 7-7 7"
|
|
1265
|
+
}
|
|
1266
|
+
)
|
|
1267
|
+
}
|
|
1268
|
+
),
|
|
1269
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-medium", children: "How I arrived at this" })
|
|
1270
|
+
]
|
|
1271
|
+
}
|
|
1272
|
+
),
|
|
1273
|
+
explanationExpanded && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "pt-3 text-sm text-gray-600 dark:text-gray-400", children: /* @__PURE__ */ jsxRuntime.jsx(react.Suspense, { fallback: /* @__PURE__ */ jsxRuntime.jsx("div", { children: "Loading..." }), children: /* @__PURE__ */ jsxRuntime.jsx(MarkdownRenderer2, { content: message.explanation }) }) })
|
|
1274
|
+
] })
|
|
1275
|
+
] }),
|
|
1276
|
+
hasPendingQuestion && /* @__PURE__ */ jsxRuntime.jsx(InlineQuestionForm, { pendingQuestion }),
|
|
1277
|
+
hasContent && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1 opacity-0 group-hover:opacity-100 transition-opacity duration-150", children: [
|
|
1278
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs text-gray-400 dark:text-gray-500 mr-1", children: dateFns.formatDistanceToNow(new Date(message.createdAt), { addSuffix: true }) }),
|
|
1279
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1280
|
+
"button",
|
|
1281
|
+
{
|
|
1282
|
+
onClick: handleCopyClick,
|
|
1283
|
+
className: "p-1.5 text-gray-400 hover:text-gray-600 dark:text-gray-500 dark:hover:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-800 rounded-md transition-colors duration-150",
|
|
1284
|
+
"aria-label": "Copy message",
|
|
1285
|
+
title: "Copy",
|
|
1286
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(react$1.Copy, { size: 14, weight: "regular" })
|
|
1287
|
+
}
|
|
1288
|
+
),
|
|
1289
|
+
handleRegenerate && /* @__PURE__ */ jsxRuntime.jsx(
|
|
1290
|
+
"button",
|
|
1291
|
+
{
|
|
1292
|
+
onClick: handleRegenerateClick,
|
|
1293
|
+
className: "p-1.5 text-gray-400 hover:text-gray-600 dark:text-gray-500 dark:hover:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-800 rounded-md transition-colors duration-150",
|
|
1294
|
+
"aria-label": "Regenerate message",
|
|
1295
|
+
title: "Regenerate",
|
|
1296
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(react$1.ArrowsClockwise, { size: 14, weight: "regular" })
|
|
1297
|
+
}
|
|
1298
|
+
)
|
|
1299
|
+
] })
|
|
1300
|
+
] })
|
|
1301
|
+
] });
|
|
1302
|
+
}
|
|
1303
|
+
function TurnBubble({
|
|
1304
|
+
message,
|
|
1305
|
+
renderUserMessage,
|
|
1306
|
+
renderAssistantMessage
|
|
1307
|
+
}) {
|
|
1308
|
+
if (message.role === "user" /* User */) {
|
|
1309
|
+
if (renderUserMessage) {
|
|
1310
|
+
return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: renderUserMessage(message) });
|
|
1311
|
+
}
|
|
1312
|
+
return /* @__PURE__ */ jsxRuntime.jsx(UserTurnView, { message });
|
|
1313
|
+
}
|
|
1314
|
+
if (message.role === "assistant" /* Assistant */) {
|
|
1315
|
+
if (renderAssistantMessage) {
|
|
1316
|
+
return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: renderAssistantMessage(message) });
|
|
1317
|
+
}
|
|
1318
|
+
return /* @__PURE__ */ jsxRuntime.jsx(AssistantTurnView, { message });
|
|
1319
|
+
}
|
|
1320
|
+
return null;
|
|
1321
|
+
}
|
|
1322
|
+
function ScrollToBottomButton({
|
|
1323
|
+
show,
|
|
1324
|
+
onClick,
|
|
1325
|
+
unreadCount = 0
|
|
1326
|
+
}) {
|
|
1327
|
+
return /* @__PURE__ */ jsxRuntime.jsx(framerMotion.AnimatePresence, { children: show && /* @__PURE__ */ jsxRuntime.jsx(
|
|
1328
|
+
framerMotion.motion.button,
|
|
1329
|
+
{
|
|
1330
|
+
initial: { opacity: 0, y: 10 },
|
|
1331
|
+
animate: { opacity: 1, y: 0 },
|
|
1332
|
+
exit: { opacity: 0, y: 10 },
|
|
1333
|
+
transition: { duration: 0.2 },
|
|
1334
|
+
onClick,
|
|
1335
|
+
className: "absolute bottom-24 right-8 p-3 bg-white dark:bg-gray-800 rounded-full shadow-lg border border-gray-200 dark:border-gray-700 hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors z-10",
|
|
1336
|
+
"aria-label": "Scroll to bottom",
|
|
1337
|
+
children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative", children: [
|
|
1338
|
+
/* @__PURE__ */ jsxRuntime.jsx(react$1.ArrowDown, { size: 20, weight: "bold", className: "text-gray-700 dark:text-gray-300" }),
|
|
1339
|
+
unreadCount > 0 && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "absolute -top-2 -right-2 min-w-[18px] h-[18px] bg-primary-600 dark:bg-primary-500 text-white text-xs font-semibold rounded-full flex items-center justify-center px-1", children: unreadCount > 99 ? "99+" : unreadCount })
|
|
1340
|
+
] })
|
|
1341
|
+
}
|
|
1342
|
+
) });
|
|
1343
|
+
}
|
|
1344
|
+
function MessageList({ renderUserMessage, renderAssistantMessage }) {
|
|
1345
|
+
const { messages, streamingContent, isStreaming } = useChat();
|
|
1346
|
+
const messagesEndRef = react.useRef(null);
|
|
1347
|
+
const containerRef = react.useRef(null);
|
|
1348
|
+
const [showScrollButton, setShowScrollButton] = react.useState(false);
|
|
1349
|
+
react.useEffect(() => {
|
|
1350
|
+
messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
|
|
1351
|
+
}, [messages.length, streamingContent]);
|
|
1352
|
+
react.useEffect(() => {
|
|
1353
|
+
const container = containerRef.current;
|
|
1354
|
+
if (!container) return;
|
|
1355
|
+
const handleScroll = () => {
|
|
1356
|
+
const { scrollTop, scrollHeight, clientHeight } = container;
|
|
1357
|
+
const isNearBottom = scrollHeight - scrollTop - clientHeight < 100;
|
|
1358
|
+
setShowScrollButton(!isNearBottom);
|
|
1359
|
+
};
|
|
1360
|
+
container.addEventListener("scroll", handleScroll);
|
|
1361
|
+
return () => container.removeEventListener("scroll", handleScroll);
|
|
1362
|
+
}, []);
|
|
1363
|
+
const scrollToBottom = () => {
|
|
1364
|
+
messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
|
|
1365
|
+
};
|
|
1366
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative flex-1 min-h-0", children: [
|
|
1367
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { ref: containerRef, className: "h-full overflow-y-auto px-4 py-6", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "max-w-4xl mx-auto space-y-6", children: [
|
|
1368
|
+
messages.map((message) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
1369
|
+
TurnBubble,
|
|
1370
|
+
{
|
|
1371
|
+
message,
|
|
1372
|
+
renderUserMessage,
|
|
1373
|
+
renderAssistantMessage
|
|
1374
|
+
},
|
|
1375
|
+
message.id
|
|
1376
|
+
)),
|
|
1377
|
+
isStreaming && streamingContent && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex gap-3", children: [
|
|
1378
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-shrink-0 w-8 h-8 rounded-full bg-primary-600 flex items-center justify-center text-white", children: "AI" }),
|
|
1379
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1 max-w-[80%] rounded-2xl px-4 py-3 bg-gray-100 dark:bg-gray-800 text-gray-900 dark:text-gray-100 shadow-sm", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "prose prose-sm max-w-none dark:prose-invert", children: [
|
|
1380
|
+
streamingContent,
|
|
1381
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "inline-block w-2 h-4 ml-1 bg-primary-600 animate-pulse" })
|
|
1382
|
+
] }) })
|
|
1383
|
+
] }),
|
|
1384
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { ref: messagesEndRef })
|
|
1385
|
+
] }) }),
|
|
1386
|
+
/* @__PURE__ */ jsxRuntime.jsx(ScrollToBottomButton, { show: showScrollButton, onClick: scrollToBottom })
|
|
1387
|
+
] });
|
|
1388
|
+
}
|
|
1389
|
+
var MAX_FILES_DEFAULT = 10;
|
|
1390
|
+
var MAX_FILE_SIZE_DEFAULT = 20 * 1024 * 1024;
|
|
1391
|
+
var MAX_HEIGHT = 192;
|
|
1392
|
+
var MessageInput = react.forwardRef(
|
|
1393
|
+
({
|
|
1394
|
+
message,
|
|
1395
|
+
loading,
|
|
1396
|
+
fetching = false,
|
|
1397
|
+
disabled = false,
|
|
1398
|
+
messageQueue = [],
|
|
1399
|
+
onMessageChange,
|
|
1400
|
+
onSubmit,
|
|
1401
|
+
onUnqueue,
|
|
1402
|
+
placeholder: placeholderOverride,
|
|
1403
|
+
maxFiles = MAX_FILES_DEFAULT,
|
|
1404
|
+
maxFileSize = MAX_FILE_SIZE_DEFAULT,
|
|
1405
|
+
containerClassName
|
|
1406
|
+
}, ref) => {
|
|
1407
|
+
const { t } = useTranslation();
|
|
1408
|
+
const [attachments, setAttachments] = react.useState([]);
|
|
1409
|
+
const [isDragging, setIsDragging] = react.useState(false);
|
|
1410
|
+
const [error, setError] = react.useState(null);
|
|
1411
|
+
const [isFocused, setIsFocused] = react.useState(false);
|
|
1412
|
+
const placeholder = placeholderOverride || t("input.placeholder");
|
|
1413
|
+
const textareaRef = react.useRef(null);
|
|
1414
|
+
const fileInputRef = react.useRef(null);
|
|
1415
|
+
const containerRef = react.useRef(null);
|
|
1416
|
+
react.useImperativeHandle(ref, () => ({
|
|
1417
|
+
focus: () => textareaRef.current?.focus(),
|
|
1418
|
+
clear: () => {
|
|
1419
|
+
onMessageChange("");
|
|
1420
|
+
setAttachments([]);
|
|
1421
|
+
setError(null);
|
|
1422
|
+
}
|
|
1423
|
+
}));
|
|
1424
|
+
react.useEffect(() => {
|
|
1425
|
+
const textarea = textareaRef.current;
|
|
1426
|
+
if (!textarea) return;
|
|
1427
|
+
textarea.style.height = "auto";
|
|
1428
|
+
const newHeight = Math.min(textarea.scrollHeight, MAX_HEIGHT);
|
|
1429
|
+
textarea.style.height = `${newHeight}px`;
|
|
1430
|
+
}, [message]);
|
|
1431
|
+
react.useEffect(() => {
|
|
1432
|
+
if (!error) return;
|
|
1433
|
+
const timer = setTimeout(() => setError(null), 5e3);
|
|
1434
|
+
return () => clearTimeout(timer);
|
|
1435
|
+
}, [error]);
|
|
1436
|
+
const handleFileSelect = async (files) => {
|
|
1437
|
+
if (!files || files.length === 0) return;
|
|
1438
|
+
try {
|
|
1439
|
+
validateFileCount(attachments.length, files.length, maxFiles);
|
|
1440
|
+
const newAttachments = [];
|
|
1441
|
+
for (let i = 0; i < files.length; i++) {
|
|
1442
|
+
const file = files[i];
|
|
1443
|
+
validateImageFile(file, maxFileSize);
|
|
1444
|
+
const base64Data = await convertToBase64(file);
|
|
1445
|
+
const preview = createDataUrl(base64Data, file.type);
|
|
1446
|
+
newAttachments.push({
|
|
1447
|
+
filename: file.name,
|
|
1448
|
+
mimeType: file.type,
|
|
1449
|
+
sizeBytes: file.size,
|
|
1450
|
+
base64Data,
|
|
1451
|
+
preview
|
|
1452
|
+
});
|
|
1453
|
+
}
|
|
1454
|
+
setAttachments((prev) => [...prev, ...newAttachments]);
|
|
1455
|
+
setError(null);
|
|
1456
|
+
} catch (err) {
|
|
1457
|
+
setError(err instanceof Error ? err.message : "Failed to process files");
|
|
1458
|
+
}
|
|
1459
|
+
};
|
|
1460
|
+
const handleFileInputChange = (e) => {
|
|
1461
|
+
handleFileSelect(e.target.files);
|
|
1462
|
+
e.target.value = "";
|
|
1463
|
+
};
|
|
1464
|
+
const handleRemoveAttachment = (index) => {
|
|
1465
|
+
setAttachments((prev) => prev.filter((_, i) => i !== index));
|
|
1466
|
+
setError(null);
|
|
1467
|
+
};
|
|
1468
|
+
const handleDragOver = (e) => {
|
|
1469
|
+
e.preventDefault();
|
|
1470
|
+
e.stopPropagation();
|
|
1471
|
+
setIsDragging(true);
|
|
1472
|
+
};
|
|
1473
|
+
const handleDragLeave = (e) => {
|
|
1474
|
+
e.preventDefault();
|
|
1475
|
+
e.stopPropagation();
|
|
1476
|
+
setIsDragging(false);
|
|
1477
|
+
};
|
|
1478
|
+
const handleDrop = async (e) => {
|
|
1479
|
+
e.preventDefault();
|
|
1480
|
+
e.stopPropagation();
|
|
1481
|
+
setIsDragging(false);
|
|
1482
|
+
await handleFileSelect(e.dataTransfer.files);
|
|
1483
|
+
};
|
|
1484
|
+
const handleKeyDown = (e) => {
|
|
1485
|
+
if (e.key === "Enter" && !e.shiftKey) {
|
|
1486
|
+
e.preventDefault();
|
|
1487
|
+
if (!loading && (message.trim() || attachments.length > 0)) {
|
|
1488
|
+
handleFormSubmit(e);
|
|
1489
|
+
}
|
|
1490
|
+
}
|
|
1491
|
+
if (e.key === "Escape") {
|
|
1492
|
+
onMessageChange("");
|
|
1493
|
+
setAttachments([]);
|
|
1494
|
+
setError(null);
|
|
1495
|
+
}
|
|
1496
|
+
if (e.key === "ArrowUp" && !message.trim() && onUnqueue) {
|
|
1497
|
+
const unqueued = onUnqueue();
|
|
1498
|
+
if (unqueued) {
|
|
1499
|
+
onMessageChange(unqueued.content);
|
|
1500
|
+
setAttachments(unqueued.attachments);
|
|
1501
|
+
}
|
|
1502
|
+
}
|
|
1503
|
+
};
|
|
1504
|
+
const handleFormSubmit = (e) => {
|
|
1505
|
+
e.preventDefault();
|
|
1506
|
+
if (loading || disabled || !message.trim() && attachments.length === 0) {
|
|
1507
|
+
return;
|
|
1508
|
+
}
|
|
1509
|
+
onSubmit(e, attachments);
|
|
1510
|
+
setAttachments([]);
|
|
1511
|
+
setError(null);
|
|
1512
|
+
};
|
|
1513
|
+
const canSubmit = !loading && !disabled && (message.trim() || attachments.length > 0);
|
|
1514
|
+
const defaultContainerClassName = "shrink-0 p-4 pb-6";
|
|
1515
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
1516
|
+
"div",
|
|
1517
|
+
{
|
|
1518
|
+
ref: containerRef,
|
|
1519
|
+
className: containerClassName ?? defaultContainerClassName,
|
|
1520
|
+
children: /* @__PURE__ */ jsxRuntime.jsxs("form", { onSubmit: handleFormSubmit, className: "max-w-4xl mx-auto", children: [
|
|
1521
|
+
error && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mb-3 p-3 bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800 rounded-lg text-sm text-red-600 dark:text-red-400 flex items-center justify-between", children: [
|
|
1522
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { children: error }),
|
|
1523
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1524
|
+
"button",
|
|
1525
|
+
{
|
|
1526
|
+
type: "button",
|
|
1527
|
+
onClick: () => setError(null),
|
|
1528
|
+
className: "ml-2 p-1 hover:bg-red-100 dark:hover:bg-red-800 rounded transition-colors",
|
|
1529
|
+
"aria-label": t("input.dismissError"),
|
|
1530
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(react$1.X, { size: 14 })
|
|
1531
|
+
}
|
|
1532
|
+
)
|
|
1533
|
+
] }),
|
|
1534
|
+
messageQueue.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mb-3 text-xs text-gray-500 dark:text-gray-400", children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: "px-2.5 py-1 bg-primary-50 dark:bg-primary-900/30 text-primary-600 dark:text-primary-400 rounded font-medium", children: t("input.messagesQueued", { count: messageQueue.length }) }) }),
|
|
1535
|
+
attachments.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mb-3", children: /* @__PURE__ */ jsxRuntime.jsx(AttachmentGrid, { attachments, onRemove: handleRemoveAttachment }) }),
|
|
1536
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
1537
|
+
"div",
|
|
1538
|
+
{
|
|
1539
|
+
className: "relative",
|
|
1540
|
+
onDragOver: handleDragOver,
|
|
1541
|
+
onDragLeave: handleDragLeave,
|
|
1542
|
+
onDrop: handleDrop,
|
|
1543
|
+
children: [
|
|
1544
|
+
isDragging && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute inset-0 z-10 bg-primary-50/95 dark:bg-primary-900/90 border-2 border-dashed border-primary-400 rounded-2xl flex items-center justify-center", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col items-center gap-2", children: [
|
|
1545
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-10 h-10 rounded-full bg-primary-100 dark:bg-primary-800 flex items-center justify-center", children: /* @__PURE__ */ jsxRuntime.jsx(react$1.Paperclip, { size: 20, className: "text-primary-600 dark:text-primary-400" }) }),
|
|
1546
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm text-primary-700 dark:text-primary-300 font-medium", children: t("input.dropImages") })
|
|
1547
|
+
] }) }),
|
|
1548
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
1549
|
+
"div",
|
|
1550
|
+
{
|
|
1551
|
+
className: `flex items-center gap-2 rounded-2xl p-2.5 bg-white dark:bg-gray-800 border shadow-sm transition-all duration-150 ${isFocused ? "border-primary-400 dark:border-primary-500 ring-3 ring-primary-500/10 dark:ring-primary-500/15" : "border-gray-200 dark:border-gray-700"}`,
|
|
1552
|
+
children: [
|
|
1553
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1554
|
+
"button",
|
|
1555
|
+
{
|
|
1556
|
+
type: "button",
|
|
1557
|
+
onClick: () => fileInputRef.current?.click(),
|
|
1558
|
+
disabled: loading || disabled || attachments.length >= maxFiles,
|
|
1559
|
+
className: "flex-shrink-0 self-center p-2 text-gray-400 hover:text-gray-600 dark:text-gray-500 dark:hover:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-lg transition-colors disabled:opacity-40 disabled:cursor-not-allowed",
|
|
1560
|
+
"aria-label": t("input.attachFiles"),
|
|
1561
|
+
title: t("input.attachImages"),
|
|
1562
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(react$1.Paperclip, { size: 18 })
|
|
1563
|
+
}
|
|
1564
|
+
),
|
|
1565
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1566
|
+
"input",
|
|
1567
|
+
{
|
|
1568
|
+
ref: fileInputRef,
|
|
1569
|
+
type: "file",
|
|
1570
|
+
accept: "image/png,image/jpeg,image/webp,image/gif",
|
|
1571
|
+
multiple: true,
|
|
1572
|
+
onChange: handleFileInputChange,
|
|
1573
|
+
className: "hidden",
|
|
1574
|
+
"aria-label": "File input"
|
|
1575
|
+
}
|
|
1576
|
+
),
|
|
1577
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1 self-stretch flex items-center", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
1578
|
+
"textarea",
|
|
1579
|
+
{
|
|
1580
|
+
ref: textareaRef,
|
|
1581
|
+
value: message,
|
|
1582
|
+
onChange: (e) => onMessageChange(e.target.value),
|
|
1583
|
+
onKeyDown: handleKeyDown,
|
|
1584
|
+
onFocus: () => setIsFocused(true),
|
|
1585
|
+
onBlur: () => setIsFocused(false),
|
|
1586
|
+
placeholder,
|
|
1587
|
+
className: "resize-none bg-transparent border-none outline-none px-1 py-2 w-full text-gray-900 dark:text-white placeholder-gray-400 dark:placeholder-gray-500 text-[15px] leading-relaxed",
|
|
1588
|
+
style: { maxHeight: `${MAX_HEIGHT}px` },
|
|
1589
|
+
rows: 1,
|
|
1590
|
+
disabled: loading || disabled,
|
|
1591
|
+
"aria-label": "Message input"
|
|
1592
|
+
}
|
|
1593
|
+
) }),
|
|
1594
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1595
|
+
"button",
|
|
1596
|
+
{
|
|
1597
|
+
type: "submit",
|
|
1598
|
+
disabled: !canSubmit,
|
|
1599
|
+
className: "flex-shrink-0 self-center p-2 rounded-lg bg-primary-600 hover:bg-primary-700 active:bg-primary-800 text-white shadow-sm transition-colors disabled:opacity-40 disabled:cursor-not-allowed disabled:hover:bg-primary-600",
|
|
1600
|
+
"aria-label": t("input.sendMessage"),
|
|
1601
|
+
children: loading ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-[18px] h-[18px] border-2 border-white/60 border-t-transparent rounded-full animate-spin" }) : /* @__PURE__ */ jsxRuntime.jsx(react$1.PaperPlaneRight, { size: 18, weight: "fill" })
|
|
1602
|
+
}
|
|
1603
|
+
)
|
|
1604
|
+
]
|
|
1605
|
+
}
|
|
1606
|
+
)
|
|
1607
|
+
]
|
|
1608
|
+
}
|
|
1609
|
+
),
|
|
1610
|
+
(loading || fetching) && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-3 flex items-center justify-center gap-2", children: [
|
|
1611
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1.5", children: [
|
|
1612
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-1.5 h-1.5 bg-gray-400 dark:bg-gray-500 rounded-full animate-pulse" }),
|
|
1613
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-1.5 h-1.5 bg-gray-400 dark:bg-gray-500 rounded-full animate-pulse", style: { animationDelay: "0.15s" } }),
|
|
1614
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-1.5 h-1.5 bg-gray-400 dark:bg-gray-500 rounded-full animate-pulse", style: { animationDelay: "0.3s" } })
|
|
1615
|
+
] }),
|
|
1616
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm text-gray-500 dark:text-gray-400", children: loading ? t("input.aiThinking") : t("input.processing") })
|
|
1617
|
+
] })
|
|
1618
|
+
] })
|
|
1619
|
+
}
|
|
1620
|
+
);
|
|
1621
|
+
}
|
|
1622
|
+
);
|
|
1623
|
+
MessageInput.displayName = "MessageInput";
|
|
1624
|
+
var EXAMPLE_PROMPTS = [
|
|
1625
|
+
{
|
|
1626
|
+
category: "Data Analysis",
|
|
1627
|
+
icon: react$1.ChartBar,
|
|
1628
|
+
text: "Show me sales trends for the last quarter"
|
|
1629
|
+
},
|
|
1630
|
+
{
|
|
1631
|
+
category: "Reports",
|
|
1632
|
+
icon: react$1.FileText,
|
|
1633
|
+
text: "Generate a summary of customer feedback"
|
|
1634
|
+
},
|
|
1635
|
+
{
|
|
1636
|
+
category: "Insights",
|
|
1637
|
+
icon: react$1.Lightbulb,
|
|
1638
|
+
text: "What are the top performing products?"
|
|
1639
|
+
}
|
|
1640
|
+
];
|
|
1641
|
+
var containerVariants = {
|
|
1642
|
+
hidden: { opacity: 0 },
|
|
1643
|
+
visible: {
|
|
1644
|
+
opacity: 1,
|
|
1645
|
+
transition: {
|
|
1646
|
+
staggerChildren: 0.08,
|
|
1647
|
+
delayChildren: 0.05
|
|
1648
|
+
}
|
|
1649
|
+
}
|
|
1650
|
+
};
|
|
1651
|
+
var itemVariants = {
|
|
1652
|
+
hidden: { opacity: 0, y: 12 },
|
|
1653
|
+
visible: {
|
|
1654
|
+
opacity: 1,
|
|
1655
|
+
y: 0,
|
|
1656
|
+
transition: {
|
|
1657
|
+
duration: 0.3,
|
|
1658
|
+
ease: [0.4, 0, 0.2, 1]
|
|
1659
|
+
}
|
|
1660
|
+
}
|
|
1661
|
+
};
|
|
1662
|
+
function WelcomeContent({
|
|
1663
|
+
onPromptSelect,
|
|
1664
|
+
title = "Welcome to BiChat",
|
|
1665
|
+
description = "Your intelligent business analytics assistant. Ask questions about your data, generate reports, or explore insights.",
|
|
1666
|
+
disabled = false
|
|
1667
|
+
}) {
|
|
1668
|
+
const handlePromptClick = (prompt2) => {
|
|
1669
|
+
if (onPromptSelect && !disabled) {
|
|
1670
|
+
onPromptSelect(prompt2);
|
|
1671
|
+
}
|
|
1672
|
+
};
|
|
1673
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1674
|
+
framerMotion.motion.div,
|
|
1675
|
+
{
|
|
1676
|
+
className: "w-full max-w-3xl mx-auto px-6 py-12 text-center",
|
|
1677
|
+
variants: containerVariants,
|
|
1678
|
+
initial: "hidden",
|
|
1679
|
+
animate: "visible",
|
|
1680
|
+
children: [
|
|
1681
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1682
|
+
framerMotion.motion.h1,
|
|
1683
|
+
{
|
|
1684
|
+
className: "text-3xl sm:text-4xl font-semibold text-gray-900 dark:text-white mb-4",
|
|
1685
|
+
variants: itemVariants,
|
|
1686
|
+
children: title
|
|
1687
|
+
}
|
|
1688
|
+
),
|
|
1689
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1690
|
+
framerMotion.motion.p,
|
|
1691
|
+
{
|
|
1692
|
+
className: "text-base text-gray-500 dark:text-gray-400 mb-12 max-w-xl mx-auto leading-relaxed",
|
|
1693
|
+
variants: itemVariants,
|
|
1694
|
+
children: description
|
|
1695
|
+
}
|
|
1696
|
+
),
|
|
1697
|
+
/* @__PURE__ */ jsxRuntime.jsxs(framerMotion.motion.div, { variants: itemVariants, children: [
|
|
1698
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs font-medium text-gray-400 dark:text-gray-500 uppercase tracking-wide mb-4", children: "Try asking" }),
|
|
1699
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "grid gap-3 sm:grid-cols-2 lg:grid-cols-3", children: EXAMPLE_PROMPTS.map((prompt2, index) => /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1700
|
+
framerMotion.motion.button,
|
|
1701
|
+
{
|
|
1702
|
+
onClick: () => handlePromptClick(prompt2.text),
|
|
1703
|
+
disabled,
|
|
1704
|
+
className: "group flex flex-col items-start text-left p-5 rounded-xl bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 shadow-sm hover:shadow-md hover:border-gray-300 dark:hover:border-gray-600 transition-all duration-150 focus:outline-none focus:ring-2 focus:ring-primary-500/50 focus:ring-offset-2 dark:focus:ring-offset-gray-900 disabled:opacity-50 disabled:cursor-not-allowed",
|
|
1705
|
+
variants: itemVariants,
|
|
1706
|
+
whileHover: disabled ? {} : { y: -2 },
|
|
1707
|
+
whileTap: disabled ? {} : { scale: 0.98 },
|
|
1708
|
+
children: [
|
|
1709
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-9 h-9 rounded-lg bg-gray-100 dark:bg-gray-700 flex items-center justify-center mb-3", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
1710
|
+
prompt2.icon,
|
|
1711
|
+
{
|
|
1712
|
+
size: 18,
|
|
1713
|
+
weight: "regular",
|
|
1714
|
+
className: "text-gray-600 dark:text-gray-300"
|
|
1715
|
+
}
|
|
1716
|
+
) }),
|
|
1717
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs font-medium text-gray-400 dark:text-gray-500 mb-1.5", children: prompt2.category }),
|
|
1718
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-gray-700 dark:text-gray-200 leading-relaxed", children: prompt2.text })
|
|
1719
|
+
]
|
|
1720
|
+
},
|
|
1721
|
+
index
|
|
1722
|
+
)) })
|
|
1723
|
+
] })
|
|
1724
|
+
]
|
|
1725
|
+
}
|
|
1726
|
+
);
|
|
1727
|
+
}
|
|
1728
|
+
function ChatSessionCore({
|
|
1729
|
+
isReadOnly,
|
|
1730
|
+
renderUserMessage,
|
|
1731
|
+
renderAssistantMessage,
|
|
1732
|
+
className = "",
|
|
1733
|
+
headerSlot,
|
|
1734
|
+
welcomeSlot,
|
|
1735
|
+
logoSlot,
|
|
1736
|
+
actionsSlot,
|
|
1737
|
+
onBack
|
|
1738
|
+
}) {
|
|
1739
|
+
const { t } = useTranslation();
|
|
1740
|
+
const {
|
|
1741
|
+
session,
|
|
1742
|
+
messages,
|
|
1743
|
+
fetching,
|
|
1744
|
+
error,
|
|
1745
|
+
message,
|
|
1746
|
+
setMessage,
|
|
1747
|
+
loading,
|
|
1748
|
+
handleSubmit,
|
|
1749
|
+
messageQueue,
|
|
1750
|
+
handleUnqueue
|
|
1751
|
+
} = useChat();
|
|
1752
|
+
if (fetching) {
|
|
1753
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center justify-center h-full", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-gray-500 dark:text-gray-400", children: t("input.processing") }) });
|
|
1754
|
+
}
|
|
1755
|
+
if (error) {
|
|
1756
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center justify-center h-full", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-red-500 dark:text-red-400", children: [
|
|
1757
|
+
t("error.generic"),
|
|
1758
|
+
": ",
|
|
1759
|
+
error
|
|
1760
|
+
] }) });
|
|
1761
|
+
}
|
|
1762
|
+
const showWelcome = !session && messages.length === 0;
|
|
1763
|
+
const handlePromptSelect = (prompt2) => {
|
|
1764
|
+
setMessage(prompt2);
|
|
1765
|
+
};
|
|
1766
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1767
|
+
"main",
|
|
1768
|
+
{
|
|
1769
|
+
className: `flex-1 flex flex-col overflow-hidden min-h-0 bg-gray-50 dark:bg-gray-900 ${className}`,
|
|
1770
|
+
children: [
|
|
1771
|
+
headerSlot || /* @__PURE__ */ jsxRuntime.jsx(ChatHeader, { session, onBack, logoSlot, actionsSlot }),
|
|
1772
|
+
showWelcome ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1 flex items-center justify-center overflow-auto", children: welcomeSlot || /* @__PURE__ */ jsxRuntime.jsx(WelcomeContent, { onPromptSelect: handlePromptSelect, disabled: loading }) }) : /* @__PURE__ */ jsxRuntime.jsx(
|
|
1773
|
+
MessageList,
|
|
1774
|
+
{
|
|
1775
|
+
renderUserMessage,
|
|
1776
|
+
renderAssistantMessage
|
|
1777
|
+
}
|
|
1778
|
+
),
|
|
1779
|
+
!isReadOnly && /* @__PURE__ */ jsxRuntime.jsx(
|
|
1780
|
+
MessageInput,
|
|
1781
|
+
{
|
|
1782
|
+
message,
|
|
1783
|
+
loading,
|
|
1784
|
+
fetching,
|
|
1785
|
+
onMessageChange: setMessage,
|
|
1786
|
+
onSubmit: handleSubmit,
|
|
1787
|
+
messageQueue,
|
|
1788
|
+
onUnqueue: handleUnqueue
|
|
1789
|
+
}
|
|
1790
|
+
)
|
|
1791
|
+
]
|
|
1792
|
+
}
|
|
1793
|
+
);
|
|
1794
|
+
}
|
|
1795
|
+
function ChatSession(props) {
|
|
1796
|
+
const { dataSource, sessionId, ...coreProps } = props;
|
|
1797
|
+
return /* @__PURE__ */ jsxRuntime.jsx(ChatSessionProvider, { dataSource, sessionId, children: /* @__PURE__ */ jsxRuntime.jsx(ChatSessionCore, { ...coreProps }) });
|
|
1798
|
+
}
|
|
1799
|
+
|
|
1800
|
+
// ui/src/bichat/index.ts
|
|
1801
|
+
init_MarkdownRenderer();
|
|
1802
|
+
|
|
1803
|
+
// ui/src/bichat/animations/variants.ts
|
|
1804
|
+
var prefersReducedMotion = () => {
|
|
1805
|
+
if (typeof window === "undefined") return false;
|
|
1806
|
+
return window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
1807
|
+
};
|
|
1808
|
+
var fadeInVariants = {
|
|
1809
|
+
initial: { opacity: 0 },
|
|
1810
|
+
animate: {
|
|
1811
|
+
opacity: 1,
|
|
1812
|
+
transition: {
|
|
1813
|
+
duration: prefersReducedMotion() ? 0 : 0.2
|
|
1814
|
+
}
|
|
1815
|
+
},
|
|
1816
|
+
exit: {
|
|
1817
|
+
opacity: 0,
|
|
1818
|
+
transition: {
|
|
1819
|
+
duration: prefersReducedMotion() ? 0 : 0.15
|
|
1820
|
+
}
|
|
1821
|
+
}
|
|
1822
|
+
};
|
|
1823
|
+
var fadeInUpVariants = {
|
|
1824
|
+
initial: { opacity: 0, y: 8 },
|
|
1825
|
+
animate: {
|
|
1826
|
+
opacity: 1,
|
|
1827
|
+
y: 0,
|
|
1828
|
+
transition: {
|
|
1829
|
+
duration: prefersReducedMotion() ? 0 : 0.2,
|
|
1830
|
+
ease: [0.4, 0, 0.2, 1]
|
|
1831
|
+
}
|
|
1832
|
+
},
|
|
1833
|
+
exit: {
|
|
1834
|
+
opacity: 0,
|
|
1835
|
+
y: 8,
|
|
1836
|
+
transition: {
|
|
1837
|
+
duration: prefersReducedMotion() ? 0 : 0.15
|
|
1838
|
+
}
|
|
1839
|
+
}
|
|
1840
|
+
};
|
|
1841
|
+
var scaleFadeVariants = {
|
|
1842
|
+
initial: { opacity: 0, scale: 0.98 },
|
|
1843
|
+
animate: {
|
|
1844
|
+
opacity: 1,
|
|
1845
|
+
scale: 1,
|
|
1846
|
+
transition: {
|
|
1847
|
+
duration: prefersReducedMotion() ? 0 : 0.15
|
|
1848
|
+
}
|
|
1849
|
+
},
|
|
1850
|
+
exit: {
|
|
1851
|
+
opacity: 0,
|
|
1852
|
+
scale: 0.98,
|
|
1853
|
+
transition: {
|
|
1854
|
+
duration: prefersReducedMotion() ? 0 : 0.1
|
|
1855
|
+
}
|
|
1856
|
+
}
|
|
1857
|
+
};
|
|
1858
|
+
var backdropVariants = {
|
|
1859
|
+
initial: { opacity: 0 },
|
|
1860
|
+
animate: {
|
|
1861
|
+
opacity: 1,
|
|
1862
|
+
transition: {
|
|
1863
|
+
duration: prefersReducedMotion() ? 0 : 0.15
|
|
1864
|
+
}
|
|
1865
|
+
},
|
|
1866
|
+
exit: {
|
|
1867
|
+
opacity: 0,
|
|
1868
|
+
transition: {
|
|
1869
|
+
duration: 0.15
|
|
1870
|
+
}
|
|
1871
|
+
}
|
|
1872
|
+
};
|
|
1873
|
+
var buttonVariants = {
|
|
1874
|
+
tap: {
|
|
1875
|
+
scale: 0.98
|
|
1876
|
+
}
|
|
1877
|
+
};
|
|
1878
|
+
var staggerContainerVariants = {
|
|
1879
|
+
hidden: { opacity: 0 },
|
|
1880
|
+
visible: {
|
|
1881
|
+
opacity: 1,
|
|
1882
|
+
transition: {
|
|
1883
|
+
staggerChildren: 0.03,
|
|
1884
|
+
delayChildren: 0.05
|
|
1885
|
+
}
|
|
1886
|
+
}
|
|
1887
|
+
};
|
|
1888
|
+
var listItemVariants = {
|
|
1889
|
+
initial: { opacity: 0, x: -8 },
|
|
1890
|
+
animate: {
|
|
1891
|
+
opacity: 1,
|
|
1892
|
+
x: 0,
|
|
1893
|
+
transition: { duration: 0.2 }
|
|
1894
|
+
},
|
|
1895
|
+
exit: {
|
|
1896
|
+
opacity: 0,
|
|
1897
|
+
x: -8,
|
|
1898
|
+
transition: { duration: 0.15 }
|
|
1899
|
+
}
|
|
1900
|
+
};
|
|
1901
|
+
var messageVariants = {
|
|
1902
|
+
initial: {
|
|
1903
|
+
opacity: 0,
|
|
1904
|
+
y: 8
|
|
1905
|
+
},
|
|
1906
|
+
animate: {
|
|
1907
|
+
opacity: 1,
|
|
1908
|
+
y: 0,
|
|
1909
|
+
transition: {
|
|
1910
|
+
duration: prefersReducedMotion() ? 0 : 0.2,
|
|
1911
|
+
ease: [0.4, 0, 0.2, 1]
|
|
1912
|
+
}
|
|
1913
|
+
},
|
|
1914
|
+
exit: {
|
|
1915
|
+
opacity: 0,
|
|
1916
|
+
transition: {
|
|
1917
|
+
duration: prefersReducedMotion() ? 0 : 0.15
|
|
1918
|
+
}
|
|
1919
|
+
}
|
|
1920
|
+
};
|
|
1921
|
+
var messageContainerVariants = {
|
|
1922
|
+
initial: { opacity: 0 },
|
|
1923
|
+
animate: {
|
|
1924
|
+
opacity: 1,
|
|
1925
|
+
transition: {
|
|
1926
|
+
staggerChildren: 0.05,
|
|
1927
|
+
delayChildren: 0.05
|
|
1928
|
+
}
|
|
1929
|
+
}
|
|
1930
|
+
};
|
|
1931
|
+
var typingDotVariants = {
|
|
1932
|
+
initial: { opacity: 0.4 },
|
|
1933
|
+
animate: {
|
|
1934
|
+
opacity: [0.4, 1, 0.4],
|
|
1935
|
+
transition: {
|
|
1936
|
+
duration: prefersReducedMotion() ? 0 : 1,
|
|
1937
|
+
repeat: Infinity,
|
|
1938
|
+
ease: "easeInOut"
|
|
1939
|
+
}
|
|
1940
|
+
}
|
|
1941
|
+
};
|
|
1942
|
+
var floatingButtonVariants = {
|
|
1943
|
+
initial: {
|
|
1944
|
+
opacity: 0,
|
|
1945
|
+
scale: 0.9
|
|
1946
|
+
},
|
|
1947
|
+
animate: {
|
|
1948
|
+
opacity: 1,
|
|
1949
|
+
scale: 1,
|
|
1950
|
+
transition: {
|
|
1951
|
+
duration: prefersReducedMotion() ? 0 : 0.2
|
|
1952
|
+
}
|
|
1953
|
+
},
|
|
1954
|
+
exit: {
|
|
1955
|
+
opacity: 0,
|
|
1956
|
+
scale: 0.9,
|
|
1957
|
+
transition: {
|
|
1958
|
+
duration: prefersReducedMotion() ? 0 : 0.15
|
|
1959
|
+
}
|
|
1960
|
+
}
|
|
1961
|
+
};
|
|
1962
|
+
var dropdownVariants = {
|
|
1963
|
+
initial: { opacity: 0, y: -4 },
|
|
1964
|
+
animate: {
|
|
1965
|
+
opacity: 1,
|
|
1966
|
+
y: 0,
|
|
1967
|
+
transition: { duration: prefersReducedMotion() ? 0 : 0.15 }
|
|
1968
|
+
},
|
|
1969
|
+
exit: {
|
|
1970
|
+
opacity: 0,
|
|
1971
|
+
y: -4,
|
|
1972
|
+
transition: { duration: 0.1 }
|
|
1973
|
+
}
|
|
1974
|
+
};
|
|
1975
|
+
var toastVariants = {
|
|
1976
|
+
initial: { opacity: 0, y: -8 },
|
|
1977
|
+
animate: {
|
|
1978
|
+
opacity: 1,
|
|
1979
|
+
y: 0,
|
|
1980
|
+
transition: {
|
|
1981
|
+
duration: prefersReducedMotion() ? 0 : 0.2
|
|
1982
|
+
}
|
|
1983
|
+
},
|
|
1984
|
+
exit: {
|
|
1985
|
+
opacity: 0,
|
|
1986
|
+
y: -8,
|
|
1987
|
+
transition: {
|
|
1988
|
+
duration: prefersReducedMotion() ? 0 : 0.15
|
|
1989
|
+
}
|
|
1990
|
+
}
|
|
1991
|
+
};
|
|
1992
|
+
var sizeClasses = {
|
|
1993
|
+
sm: {
|
|
1994
|
+
container: "py-6 px-3",
|
|
1995
|
+
title: "text-sm",
|
|
1996
|
+
description: "text-xs"
|
|
1997
|
+
},
|
|
1998
|
+
md: {
|
|
1999
|
+
container: "py-8 px-4",
|
|
2000
|
+
title: "text-base",
|
|
2001
|
+
description: "text-sm"
|
|
2002
|
+
},
|
|
2003
|
+
lg: {
|
|
2004
|
+
container: "py-12 px-6",
|
|
2005
|
+
title: "text-lg",
|
|
2006
|
+
description: "text-base"
|
|
2007
|
+
}
|
|
2008
|
+
};
|
|
2009
|
+
function EmptyState({
|
|
2010
|
+
icon,
|
|
2011
|
+
title,
|
|
2012
|
+
description,
|
|
2013
|
+
action,
|
|
2014
|
+
className = "",
|
|
2015
|
+
size = "md"
|
|
2016
|
+
}) {
|
|
2017
|
+
const sizes = sizeClasses[size];
|
|
2018
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
2019
|
+
framerMotion.motion.div,
|
|
2020
|
+
{
|
|
2021
|
+
className: `flex items-center justify-center ${sizes.container} ${className}`,
|
|
2022
|
+
variants: fadeInVariants,
|
|
2023
|
+
initial: "initial",
|
|
2024
|
+
animate: "animate",
|
|
2025
|
+
exit: "exit",
|
|
2026
|
+
children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-center max-w-md", children: [
|
|
2027
|
+
icon && /* @__PURE__ */ jsxRuntime.jsx(
|
|
2028
|
+
framerMotion.motion.div,
|
|
2029
|
+
{
|
|
2030
|
+
className: "mb-4 flex justify-center",
|
|
2031
|
+
initial: { opacity: 0, scale: 0.8 },
|
|
2032
|
+
animate: { opacity: 1, scale: 1 },
|
|
2033
|
+
transition: { duration: 0.4, delay: 0.1 },
|
|
2034
|
+
children: icon
|
|
2035
|
+
}
|
|
2036
|
+
),
|
|
2037
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2038
|
+
framerMotion.motion.h3,
|
|
2039
|
+
{
|
|
2040
|
+
className: `${sizes.title} font-medium text-gray-900 dark:text-white mb-2`,
|
|
2041
|
+
initial: { opacity: 0, y: 10 },
|
|
2042
|
+
animate: { opacity: 1, y: 0 },
|
|
2043
|
+
transition: { duration: 0.4, delay: 0.2 },
|
|
2044
|
+
children: title
|
|
2045
|
+
}
|
|
2046
|
+
),
|
|
2047
|
+
description && /* @__PURE__ */ jsxRuntime.jsx(
|
|
2048
|
+
framerMotion.motion.p,
|
|
2049
|
+
{
|
|
2050
|
+
className: `${sizes.description} text-gray-500 dark:text-gray-400 mb-4`,
|
|
2051
|
+
initial: { opacity: 0, y: 10 },
|
|
2052
|
+
animate: { opacity: 1, y: 0 },
|
|
2053
|
+
transition: { duration: 0.4, delay: 0.3 },
|
|
2054
|
+
children: description
|
|
2055
|
+
}
|
|
2056
|
+
),
|
|
2057
|
+
action && /* @__PURE__ */ jsxRuntime.jsx(
|
|
2058
|
+
framerMotion.motion.div,
|
|
2059
|
+
{
|
|
2060
|
+
initial: { opacity: 0, y: 10 },
|
|
2061
|
+
animate: { opacity: 1, y: 0 },
|
|
2062
|
+
transition: { duration: 0.4, delay: 0.4 },
|
|
2063
|
+
children: action
|
|
2064
|
+
}
|
|
2065
|
+
)
|
|
2066
|
+
] })
|
|
2067
|
+
}
|
|
2068
|
+
);
|
|
2069
|
+
}
|
|
2070
|
+
var EmptyState_default = react.memo(EmptyState);
|
|
2071
|
+
var sizeClasses2 = {
|
|
2072
|
+
sm: "text-sm",
|
|
2073
|
+
md: "text-base",
|
|
2074
|
+
lg: "text-lg"
|
|
2075
|
+
};
|
|
2076
|
+
var EditableText = react.forwardRef(
|
|
2077
|
+
({
|
|
2078
|
+
value,
|
|
2079
|
+
onSave,
|
|
2080
|
+
maxLength = 100,
|
|
2081
|
+
isLoading = false,
|
|
2082
|
+
placeholder = "Untitled",
|
|
2083
|
+
className = "",
|
|
2084
|
+
inputClassName = "",
|
|
2085
|
+
size = "sm"
|
|
2086
|
+
}, ref) => {
|
|
2087
|
+
const [isEditing, setIsEditing] = react.useState(false);
|
|
2088
|
+
const [editValue, setEditValue] = react.useState(value);
|
|
2089
|
+
const inputRef = react.useRef(null);
|
|
2090
|
+
react.useImperativeHandle(ref, () => ({
|
|
2091
|
+
startEditing: () => {
|
|
2092
|
+
if (!isLoading) {
|
|
2093
|
+
setIsEditing(true);
|
|
2094
|
+
}
|
|
2095
|
+
},
|
|
2096
|
+
cancelEditing: () => {
|
|
2097
|
+
setEditValue(value);
|
|
2098
|
+
setIsEditing(false);
|
|
2099
|
+
}
|
|
2100
|
+
}));
|
|
2101
|
+
react.useEffect(() => {
|
|
2102
|
+
setEditValue(value);
|
|
2103
|
+
}, [value]);
|
|
2104
|
+
react.useEffect(() => {
|
|
2105
|
+
if (isEditing && inputRef.current) {
|
|
2106
|
+
inputRef.current.focus();
|
|
2107
|
+
inputRef.current.select();
|
|
2108
|
+
}
|
|
2109
|
+
}, [isEditing]);
|
|
2110
|
+
const handleSave = () => {
|
|
2111
|
+
const trimmed = editValue.trim();
|
|
2112
|
+
if (!trimmed) {
|
|
2113
|
+
setEditValue(value);
|
|
2114
|
+
setIsEditing(false);
|
|
2115
|
+
return;
|
|
2116
|
+
}
|
|
2117
|
+
if (trimmed !== value) {
|
|
2118
|
+
onSave(trimmed);
|
|
2119
|
+
}
|
|
2120
|
+
setIsEditing(false);
|
|
2121
|
+
};
|
|
2122
|
+
const handleCancel = () => {
|
|
2123
|
+
setEditValue(value);
|
|
2124
|
+
setIsEditing(false);
|
|
2125
|
+
};
|
|
2126
|
+
const handleKeyDown = (e) => {
|
|
2127
|
+
if (e.key === "Enter") {
|
|
2128
|
+
e.preventDefault();
|
|
2129
|
+
handleSave();
|
|
2130
|
+
} else if (e.key === "Escape") {
|
|
2131
|
+
e.preventDefault();
|
|
2132
|
+
handleCancel();
|
|
2133
|
+
}
|
|
2134
|
+
};
|
|
2135
|
+
const handleDoubleClick = () => {
|
|
2136
|
+
if (!isLoading) {
|
|
2137
|
+
setIsEditing(true);
|
|
2138
|
+
}
|
|
2139
|
+
};
|
|
2140
|
+
const handleBlur = () => {
|
|
2141
|
+
handleSave();
|
|
2142
|
+
};
|
|
2143
|
+
const sizeClass = sizeClasses2[size];
|
|
2144
|
+
if (isEditing) {
|
|
2145
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
2146
|
+
"div",
|
|
2147
|
+
{
|
|
2148
|
+
className: "flex items-center gap-2 flex-1",
|
|
2149
|
+
onClick: (e) => e.preventDefault(),
|
|
2150
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
2151
|
+
"input",
|
|
2152
|
+
{
|
|
2153
|
+
ref: inputRef,
|
|
2154
|
+
type: "text",
|
|
2155
|
+
value: editValue,
|
|
2156
|
+
onChange: (e) => setEditValue(e.target.value),
|
|
2157
|
+
onKeyDown: handleKeyDown,
|
|
2158
|
+
onBlur: handleBlur,
|
|
2159
|
+
maxLength,
|
|
2160
|
+
placeholder,
|
|
2161
|
+
className: `flex-1 px-2 py-1 ${sizeClass} bg-white dark:bg-gray-700 border border-primary-500 dark:border-primary-600 rounded-lg focus:outline-none focus:ring-2 focus:ring-primary-500/30 dark:focus:ring-primary-600/30 text-gray-900 dark:text-white ${inputClassName}`,
|
|
2162
|
+
"aria-label": "Edit text. Press Enter to save, Escape to cancel"
|
|
2163
|
+
}
|
|
2164
|
+
)
|
|
2165
|
+
}
|
|
2166
|
+
);
|
|
2167
|
+
}
|
|
2168
|
+
const displayValue = value || placeholder;
|
|
2169
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
2170
|
+
"span",
|
|
2171
|
+
{
|
|
2172
|
+
onDoubleClick: handleDoubleClick,
|
|
2173
|
+
className: `${sizeClass} font-medium truncate flex-1 cursor-pointer select-none hover:text-primary-600 dark:hover:text-primary-400 transition-colors ${className}`,
|
|
2174
|
+
title: "Double-click to edit",
|
|
2175
|
+
role: "button",
|
|
2176
|
+
tabIndex: 0,
|
|
2177
|
+
onKeyDown: (e) => {
|
|
2178
|
+
if (e.key === "Enter" || e.key === " ") {
|
|
2179
|
+
e.preventDefault();
|
|
2180
|
+
handleDoubleClick();
|
|
2181
|
+
}
|
|
2182
|
+
},
|
|
2183
|
+
children: isLoading ? /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "inline-flex items-center gap-2 text-gray-400 dark:text-gray-500", children: [
|
|
2184
|
+
/* @__PURE__ */ jsxRuntime.jsx(react$1.CircleNotch, { size: 12, className: "animate-spin" }),
|
|
2185
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "italic", children: displayValue })
|
|
2186
|
+
] }) : /* @__PURE__ */ jsxRuntime.jsx("span", { className: !value ? "text-gray-400 dark:text-gray-500 italic" : "", children: displayValue })
|
|
2187
|
+
}
|
|
2188
|
+
);
|
|
2189
|
+
}
|
|
2190
|
+
);
|
|
2191
|
+
EditableText.displayName = "EditableText";
|
|
2192
|
+
var EditableText_default = react.memo(EditableText);
|
|
2193
|
+
var sizeClasses3 = {
|
|
2194
|
+
sm: {
|
|
2195
|
+
container: "py-1.5 pl-8 pr-8 text-xs",
|
|
2196
|
+
icon: 14,
|
|
2197
|
+
clearBtn: "p-1"
|
|
2198
|
+
},
|
|
2199
|
+
md: {
|
|
2200
|
+
container: "py-2.5 pl-10 pr-10 text-sm",
|
|
2201
|
+
icon: 16,
|
|
2202
|
+
clearBtn: "p-1.5"
|
|
2203
|
+
},
|
|
2204
|
+
lg: {
|
|
2205
|
+
container: "py-3 pl-12 pr-12 text-base",
|
|
2206
|
+
icon: 18,
|
|
2207
|
+
clearBtn: "p-2"
|
|
2208
|
+
}
|
|
2209
|
+
};
|
|
2210
|
+
function SearchInput({
|
|
2211
|
+
value,
|
|
2212
|
+
onChange,
|
|
2213
|
+
placeholder = "Search...",
|
|
2214
|
+
autoFocus = false,
|
|
2215
|
+
onSubmit,
|
|
2216
|
+
onEscape,
|
|
2217
|
+
className = "",
|
|
2218
|
+
size = "md",
|
|
2219
|
+
disabled = false,
|
|
2220
|
+
ariaLabel = "Search"
|
|
2221
|
+
}) {
|
|
2222
|
+
const inputRef = react.useRef(null);
|
|
2223
|
+
const sizes = sizeClasses3[size];
|
|
2224
|
+
react.useEffect(() => {
|
|
2225
|
+
if (autoFocus && inputRef.current) {
|
|
2226
|
+
inputRef.current.focus();
|
|
2227
|
+
}
|
|
2228
|
+
}, [autoFocus]);
|
|
2229
|
+
const handleClear = () => {
|
|
2230
|
+
onChange("");
|
|
2231
|
+
inputRef.current?.focus();
|
|
2232
|
+
};
|
|
2233
|
+
const handleKeyDown = (e) => {
|
|
2234
|
+
if (e.key === "Enter" && onSubmit) {
|
|
2235
|
+
e.preventDefault();
|
|
2236
|
+
onSubmit(value);
|
|
2237
|
+
} else if (e.key === "Escape") {
|
|
2238
|
+
e.preventDefault();
|
|
2239
|
+
if (value && !onEscape) {
|
|
2240
|
+
handleClear();
|
|
2241
|
+
} else if (onEscape) {
|
|
2242
|
+
onEscape();
|
|
2243
|
+
}
|
|
2244
|
+
}
|
|
2245
|
+
};
|
|
2246
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `relative w-full ${className}`, role: "search", children: [
|
|
2247
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "absolute inset-y-0 left-3 flex items-center pointer-events-none", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
2248
|
+
react$1.MagnifyingGlass,
|
|
2249
|
+
{
|
|
2250
|
+
size: sizes.icon,
|
|
2251
|
+
weight: "bold",
|
|
2252
|
+
className: "text-gray-400 dark:text-gray-500",
|
|
2253
|
+
"aria-hidden": "true"
|
|
2254
|
+
}
|
|
2255
|
+
) }),
|
|
2256
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2257
|
+
"input",
|
|
2258
|
+
{
|
|
2259
|
+
ref: inputRef,
|
|
2260
|
+
type: "search",
|
|
2261
|
+
value,
|
|
2262
|
+
onChange: (e) => onChange(e.target.value),
|
|
2263
|
+
onKeyDown: handleKeyDown,
|
|
2264
|
+
placeholder,
|
|
2265
|
+
disabled,
|
|
2266
|
+
className: `w-full ${sizes.container} bg-gray-50 dark:bg-gray-800/50 border border-gray-200 dark:border-gray-700/50 rounded-xl focus:outline-none focus:ring-2 focus:ring-primary-500/30 dark:focus:ring-primary-500/20 focus:border-primary-400 dark:focus:border-primary-600 text-gray-900 dark:text-white placeholder-gray-400 dark:placeholder-gray-500 transition-all duration-200 disabled:opacity-50 disabled:cursor-not-allowed`,
|
|
2267
|
+
"aria-label": ariaLabel
|
|
2268
|
+
}
|
|
2269
|
+
),
|
|
2270
|
+
value && !disabled && /* @__PURE__ */ jsxRuntime.jsx(
|
|
2271
|
+
"button",
|
|
2272
|
+
{
|
|
2273
|
+
type: "button",
|
|
2274
|
+
onClick: handleClear,
|
|
2275
|
+
className: `absolute inset-y-0 right-2 flex items-center ${sizes.clearBtn} rounded-lg hover:bg-gray-200 dark:hover:bg-gray-700 transition-all duration-200 text-gray-400 dark:text-gray-500 hover:text-gray-600 dark:hover:text-gray-300`,
|
|
2276
|
+
"aria-label": "Clear search",
|
|
2277
|
+
title: "Clear search",
|
|
2278
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(react$1.X, { size: sizes.icon - 2, weight: "bold" })
|
|
2279
|
+
}
|
|
2280
|
+
)
|
|
2281
|
+
] });
|
|
2282
|
+
}
|
|
2283
|
+
var SearchInput_default = react.memo(SearchInput);
|
|
2284
|
+
var variantClasses = {
|
|
2285
|
+
text: "rounded",
|
|
2286
|
+
circular: "rounded-full",
|
|
2287
|
+
rectangular: "rounded-none",
|
|
2288
|
+
rounded: "rounded-lg"
|
|
2289
|
+
};
|
|
2290
|
+
var gapClasses = {
|
|
2291
|
+
sm: "space-y-1",
|
|
2292
|
+
md: "space-y-2",
|
|
2293
|
+
lg: "space-y-3"
|
|
2294
|
+
};
|
|
2295
|
+
function Skeleton({
|
|
2296
|
+
variant = "text",
|
|
2297
|
+
width,
|
|
2298
|
+
height,
|
|
2299
|
+
className = "",
|
|
2300
|
+
animate = true
|
|
2301
|
+
}) {
|
|
2302
|
+
const variantClass = variantClasses[variant];
|
|
2303
|
+
const style = {
|
|
2304
|
+
width: typeof width === "number" ? `${width}px` : width,
|
|
2305
|
+
height: typeof height === "number" ? `${height}px` : height
|
|
2306
|
+
};
|
|
2307
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
2308
|
+
"div",
|
|
2309
|
+
{
|
|
2310
|
+
className: `bg-gray-200 dark:bg-gray-700 ${variantClass} ${animate ? "animate-pulse" : ""} ${className}`,
|
|
2311
|
+
style,
|
|
2312
|
+
"aria-hidden": "true"
|
|
2313
|
+
}
|
|
2314
|
+
);
|
|
2315
|
+
}
|
|
2316
|
+
function SkeletonGroup({
|
|
2317
|
+
count = 3,
|
|
2318
|
+
gap = "md",
|
|
2319
|
+
className = "",
|
|
2320
|
+
children
|
|
2321
|
+
}) {
|
|
2322
|
+
const gapClass = gapClasses[gap];
|
|
2323
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: `${gapClass} ${className}`, "aria-hidden": "true", children: Array.from({ length: count }).map(
|
|
2324
|
+
(_, index) => children ? /* @__PURE__ */ jsxRuntime.jsx("div", { children: children(index) }, index) : /* @__PURE__ */ jsxRuntime.jsx(Skeleton, { variant: "text", height: 16 }, index)
|
|
2325
|
+
) });
|
|
2326
|
+
}
|
|
2327
|
+
function SkeletonText({
|
|
2328
|
+
lines = 1,
|
|
2329
|
+
className = ""
|
|
2330
|
+
}) {
|
|
2331
|
+
const widths = ["100%", "90%", "80%", "95%", "85%"];
|
|
2332
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: `space-y-2 ${className}`, "aria-hidden": "true", children: Array.from({ length: lines }).map((_, index) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
2333
|
+
Skeleton,
|
|
2334
|
+
{
|
|
2335
|
+
variant: "text",
|
|
2336
|
+
width: widths[index % widths.length],
|
|
2337
|
+
height: 14
|
|
2338
|
+
},
|
|
2339
|
+
index
|
|
2340
|
+
)) });
|
|
2341
|
+
}
|
|
2342
|
+
function SkeletonAvatar({
|
|
2343
|
+
size = 40,
|
|
2344
|
+
className = ""
|
|
2345
|
+
}) {
|
|
2346
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
2347
|
+
Skeleton,
|
|
2348
|
+
{
|
|
2349
|
+
variant: "circular",
|
|
2350
|
+
width: size,
|
|
2351
|
+
height: size,
|
|
2352
|
+
className
|
|
2353
|
+
}
|
|
2354
|
+
);
|
|
2355
|
+
}
|
|
2356
|
+
function SkeletonCard({
|
|
2357
|
+
width,
|
|
2358
|
+
height = 120,
|
|
2359
|
+
className = ""
|
|
2360
|
+
}) {
|
|
2361
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
2362
|
+
Skeleton,
|
|
2363
|
+
{
|
|
2364
|
+
variant: "rounded",
|
|
2365
|
+
width,
|
|
2366
|
+
height,
|
|
2367
|
+
className
|
|
2368
|
+
}
|
|
2369
|
+
);
|
|
2370
|
+
}
|
|
2371
|
+
function ListItemSkeleton({ className = "" }) {
|
|
2372
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `flex items-center gap-3 px-3 py-2 ${className}`, children: [
|
|
2373
|
+
/* @__PURE__ */ jsxRuntime.jsx(Skeleton, { variant: "rounded", width: 20, height: 20 }),
|
|
2374
|
+
/* @__PURE__ */ jsxRuntime.jsx(Skeleton, { variant: "text", height: 16, className: "flex-1" })
|
|
2375
|
+
] });
|
|
2376
|
+
}
|
|
2377
|
+
var Skeleton_default = react.memo(Skeleton);
|
|
2378
|
+
var ConfigContext = react.createContext(null);
|
|
2379
|
+
function ConfigProvider({ config, useGlobalConfig = false, children }) {
|
|
2380
|
+
let resolvedConfig = null;
|
|
2381
|
+
if (config) {
|
|
2382
|
+
resolvedConfig = config;
|
|
2383
|
+
} else if (useGlobalConfig && typeof window !== "undefined") {
|
|
2384
|
+
const globalContext = window.__BICHAT_CONTEXT__;
|
|
2385
|
+
const globalCSRF = window.__CSRF_TOKEN__;
|
|
2386
|
+
if (globalContext) {
|
|
2387
|
+
resolvedConfig = {
|
|
2388
|
+
user: {
|
|
2389
|
+
id: String(globalContext.user?.id || ""),
|
|
2390
|
+
email: globalContext.user?.email || "",
|
|
2391
|
+
firstName: globalContext.user?.firstName || "",
|
|
2392
|
+
lastName: globalContext.user?.lastName || "",
|
|
2393
|
+
permissions: globalContext.user?.permissions || []
|
|
2394
|
+
},
|
|
2395
|
+
tenant: {
|
|
2396
|
+
id: globalContext.tenant?.id || "",
|
|
2397
|
+
name: globalContext.tenant?.name || ""
|
|
2398
|
+
},
|
|
2399
|
+
locale: {
|
|
2400
|
+
language: globalContext.locale?.language || "en",
|
|
2401
|
+
translations: globalContext.locale?.translations || {}
|
|
2402
|
+
},
|
|
2403
|
+
endpoints: {
|
|
2404
|
+
graphQL: globalContext.config?.graphQLEndpoint || "/graphql",
|
|
2405
|
+
stream: globalContext.config?.streamEndpoint || "/stream"
|
|
2406
|
+
},
|
|
2407
|
+
csrfToken: globalCSRF
|
|
2408
|
+
};
|
|
2409
|
+
}
|
|
2410
|
+
}
|
|
2411
|
+
return /* @__PURE__ */ jsxRuntime.jsx(ConfigContext.Provider, { value: resolvedConfig, children });
|
|
2412
|
+
}
|
|
2413
|
+
function useConfig() {
|
|
2414
|
+
return react.useContext(ConfigContext);
|
|
2415
|
+
}
|
|
2416
|
+
function useRequiredConfig() {
|
|
2417
|
+
const config = react.useContext(ConfigContext);
|
|
2418
|
+
if (!config) {
|
|
2419
|
+
throw new Error(
|
|
2420
|
+
"BiChat configuration not found. Wrap your app with <ConfigProvider config={...}> or use useGlobalConfig={true}."
|
|
2421
|
+
);
|
|
2422
|
+
}
|
|
2423
|
+
return config;
|
|
2424
|
+
}
|
|
2425
|
+
function hasPermission2(config, permission) {
|
|
2426
|
+
if (!config) {
|
|
2427
|
+
return false;
|
|
2428
|
+
}
|
|
2429
|
+
return config.user.permissions.includes(permission);
|
|
2430
|
+
}
|
|
2431
|
+
function useStreaming(options = {}) {
|
|
2432
|
+
const [content, setContent] = react.useState("");
|
|
2433
|
+
const [isStreaming, setIsStreaming] = react.useState(false);
|
|
2434
|
+
const [error, setError] = react.useState(null);
|
|
2435
|
+
const abortControllerRef = react.useRef(null);
|
|
2436
|
+
const processStream = react.useCallback(
|
|
2437
|
+
async (stream, signal) => {
|
|
2438
|
+
setIsStreaming(true);
|
|
2439
|
+
setError(null);
|
|
2440
|
+
setContent("");
|
|
2441
|
+
abortControllerRef.current = new AbortController();
|
|
2442
|
+
if (signal) {
|
|
2443
|
+
signal.addEventListener("abort", () => {
|
|
2444
|
+
abortControllerRef.current?.abort();
|
|
2445
|
+
});
|
|
2446
|
+
}
|
|
2447
|
+
try {
|
|
2448
|
+
for await (const chunk of stream) {
|
|
2449
|
+
if (abortControllerRef.current?.signal.aborted) {
|
|
2450
|
+
break;
|
|
2451
|
+
}
|
|
2452
|
+
if (chunk.type === "chunk" && chunk.content) {
|
|
2453
|
+
setContent((prev) => {
|
|
2454
|
+
const newContent = prev + chunk.content;
|
|
2455
|
+
options.onChunk?.(newContent);
|
|
2456
|
+
return newContent;
|
|
2457
|
+
});
|
|
2458
|
+
} else if (chunk.type === "error") {
|
|
2459
|
+
const errorMsg = chunk.error || "Stream error";
|
|
2460
|
+
const err = new Error(errorMsg);
|
|
2461
|
+
setError(err);
|
|
2462
|
+
options.onError?.(errorMsg);
|
|
2463
|
+
break;
|
|
2464
|
+
} else if (chunk.type === "done") {
|
|
2465
|
+
options.onDone?.();
|
|
2466
|
+
break;
|
|
2467
|
+
}
|
|
2468
|
+
}
|
|
2469
|
+
} catch (err) {
|
|
2470
|
+
if (err instanceof Error && err.name === "AbortError") {
|
|
2471
|
+
return;
|
|
2472
|
+
}
|
|
2473
|
+
const errorObj = err instanceof Error ? err : new Error("Unknown error");
|
|
2474
|
+
setError(errorObj);
|
|
2475
|
+
options.onError?.(errorObj.message);
|
|
2476
|
+
} finally {
|
|
2477
|
+
setIsStreaming(false);
|
|
2478
|
+
abortControllerRef.current = null;
|
|
2479
|
+
}
|
|
2480
|
+
},
|
|
2481
|
+
[options]
|
|
2482
|
+
);
|
|
2483
|
+
const cancel = react.useCallback(() => {
|
|
2484
|
+
if (abortControllerRef.current) {
|
|
2485
|
+
abortControllerRef.current.abort();
|
|
2486
|
+
abortControllerRef.current = null;
|
|
2487
|
+
setIsStreaming(false);
|
|
2488
|
+
}
|
|
2489
|
+
}, []);
|
|
2490
|
+
const reset = react.useCallback(() => {
|
|
2491
|
+
setContent("");
|
|
2492
|
+
setError(null);
|
|
2493
|
+
setIsStreaming(false);
|
|
2494
|
+
}, []);
|
|
2495
|
+
return {
|
|
2496
|
+
content,
|
|
2497
|
+
isStreaming,
|
|
2498
|
+
error,
|
|
2499
|
+
processStream,
|
|
2500
|
+
cancel,
|
|
2501
|
+
reset
|
|
2502
|
+
};
|
|
2503
|
+
}
|
|
2504
|
+
|
|
2505
|
+
// ui/src/bichat/theme/themes.ts
|
|
2506
|
+
var lightTheme = {
|
|
2507
|
+
name: "light",
|
|
2508
|
+
colors: {
|
|
2509
|
+
background: "#ffffff",
|
|
2510
|
+
surface: "#f9fafb",
|
|
2511
|
+
primary: "#3b82f6",
|
|
2512
|
+
secondary: "#6b7280",
|
|
2513
|
+
text: "#111827",
|
|
2514
|
+
textMuted: "#6b7280",
|
|
2515
|
+
border: "#e5e7eb",
|
|
2516
|
+
error: "#ef4444",
|
|
2517
|
+
success: "#10b981",
|
|
2518
|
+
warning: "#f59e0b",
|
|
2519
|
+
userBubble: "#3b82f6",
|
|
2520
|
+
assistantBubble: "#f3f4f6",
|
|
2521
|
+
userText: "#ffffff",
|
|
2522
|
+
assistantText: "#111827"
|
|
2523
|
+
},
|
|
2524
|
+
spacing: {
|
|
2525
|
+
xs: "0.25rem",
|
|
2526
|
+
sm: "0.5rem",
|
|
2527
|
+
md: "1rem",
|
|
2528
|
+
lg: "1.5rem",
|
|
2529
|
+
xl: "2rem"
|
|
2530
|
+
},
|
|
2531
|
+
borderRadius: {
|
|
2532
|
+
sm: "0.25rem",
|
|
2533
|
+
md: "0.5rem",
|
|
2534
|
+
lg: "0.75rem",
|
|
2535
|
+
full: "9999px"
|
|
2536
|
+
}
|
|
2537
|
+
};
|
|
2538
|
+
var darkTheme = {
|
|
2539
|
+
name: "dark",
|
|
2540
|
+
colors: {
|
|
2541
|
+
background: "#111827",
|
|
2542
|
+
surface: "#1f2937",
|
|
2543
|
+
primary: "#60a5fa",
|
|
2544
|
+
secondary: "#9ca3af",
|
|
2545
|
+
text: "#f9fafb",
|
|
2546
|
+
textMuted: "#9ca3af",
|
|
2547
|
+
border: "#374151",
|
|
2548
|
+
error: "#f87171",
|
|
2549
|
+
success: "#34d399",
|
|
2550
|
+
warning: "#fbbf24",
|
|
2551
|
+
userBubble: "#2563eb",
|
|
2552
|
+
assistantBubble: "#1f2937",
|
|
2553
|
+
userText: "#f9fafb",
|
|
2554
|
+
assistantText: "#f9fafb"
|
|
2555
|
+
},
|
|
2556
|
+
spacing: {
|
|
2557
|
+
xs: "0.25rem",
|
|
2558
|
+
sm: "0.5rem",
|
|
2559
|
+
md: "1rem",
|
|
2560
|
+
lg: "1.5rem",
|
|
2561
|
+
xl: "2rem"
|
|
2562
|
+
},
|
|
2563
|
+
borderRadius: {
|
|
2564
|
+
sm: "0.25rem",
|
|
2565
|
+
md: "0.5rem",
|
|
2566
|
+
lg: "0.75rem",
|
|
2567
|
+
full: "9999px"
|
|
2568
|
+
}
|
|
2569
|
+
};
|
|
2570
|
+
var ThemeContext = react.createContext(null);
|
|
2571
|
+
function getSystemTheme() {
|
|
2572
|
+
if (typeof window === "undefined") {
|
|
2573
|
+
return lightTheme;
|
|
2574
|
+
}
|
|
2575
|
+
const prefersDark = window.matchMedia("(prefers-color-scheme: dark)").matches;
|
|
2576
|
+
return prefersDark ? darkTheme : lightTheme;
|
|
2577
|
+
}
|
|
2578
|
+
function resolveTheme(themeProp) {
|
|
2579
|
+
if (typeof themeProp === "object") {
|
|
2580
|
+
return themeProp;
|
|
2581
|
+
}
|
|
2582
|
+
switch (themeProp) {
|
|
2583
|
+
case "light":
|
|
2584
|
+
return lightTheme;
|
|
2585
|
+
case "dark":
|
|
2586
|
+
return darkTheme;
|
|
2587
|
+
case "system":
|
|
2588
|
+
return getSystemTheme();
|
|
2589
|
+
default:
|
|
2590
|
+
return lightTheme;
|
|
2591
|
+
}
|
|
2592
|
+
}
|
|
2593
|
+
function applyThemeVariables(theme) {
|
|
2594
|
+
if (typeof document === "undefined") {
|
|
2595
|
+
return;
|
|
2596
|
+
}
|
|
2597
|
+
const root = document.documentElement;
|
|
2598
|
+
Object.entries(theme.colors).forEach(([key, value]) => {
|
|
2599
|
+
root.style.setProperty(`--bichat-${key}`, value);
|
|
2600
|
+
});
|
|
2601
|
+
Object.entries(theme.spacing).forEach(([key, value]) => {
|
|
2602
|
+
root.style.setProperty(`--bichat-spacing-${key}`, value);
|
|
2603
|
+
});
|
|
2604
|
+
Object.entries(theme.borderRadius).forEach(([key, value]) => {
|
|
2605
|
+
root.style.setProperty(`--bichat-radius-${key}`, value);
|
|
2606
|
+
});
|
|
2607
|
+
}
|
|
2608
|
+
function ThemeProvider({ theme = "system", children }) {
|
|
2609
|
+
const resolvedTheme = react.useMemo(() => resolveTheme(theme), [theme]);
|
|
2610
|
+
react.useEffect(() => {
|
|
2611
|
+
applyThemeVariables(resolvedTheme);
|
|
2612
|
+
}, [resolvedTheme]);
|
|
2613
|
+
react.useEffect(() => {
|
|
2614
|
+
if (theme !== "system") {
|
|
2615
|
+
return;
|
|
2616
|
+
}
|
|
2617
|
+
const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
|
|
2618
|
+
const handleChange = () => {
|
|
2619
|
+
const newTheme = getSystemTheme();
|
|
2620
|
+
applyThemeVariables(newTheme);
|
|
2621
|
+
};
|
|
2622
|
+
mediaQuery.addEventListener("change", handleChange);
|
|
2623
|
+
return () => {
|
|
2624
|
+
mediaQuery.removeEventListener("change", handleChange);
|
|
2625
|
+
};
|
|
2626
|
+
}, [theme]);
|
|
2627
|
+
const value = {
|
|
2628
|
+
theme: resolvedTheme
|
|
2629
|
+
};
|
|
2630
|
+
return /* @__PURE__ */ jsxRuntime.jsx(ThemeContext.Provider, { value, children });
|
|
2631
|
+
}
|
|
2632
|
+
function useTheme() {
|
|
2633
|
+
const context = react.useContext(ThemeContext);
|
|
2634
|
+
if (!context) {
|
|
2635
|
+
throw new Error("useTheme must be used within ThemeProvider");
|
|
2636
|
+
}
|
|
2637
|
+
return context.theme;
|
|
2638
|
+
}
|
|
2639
|
+
|
|
2640
|
+
// ui/src/bichat/api/csrf.ts
|
|
2641
|
+
function getCSRFToken() {
|
|
2642
|
+
const token = window.__CSRF_TOKEN__;
|
|
2643
|
+
if (!token) {
|
|
2644
|
+
console.warn("CSRF token not found in window object");
|
|
2645
|
+
return "";
|
|
2646
|
+
}
|
|
2647
|
+
return token;
|
|
2648
|
+
}
|
|
2649
|
+
function addCSRFHeader(headers) {
|
|
2650
|
+
const token = getCSRFToken();
|
|
2651
|
+
if (token) {
|
|
2652
|
+
headers.set("X-CSRF-Token", token);
|
|
2653
|
+
}
|
|
2654
|
+
return headers;
|
|
2655
|
+
}
|
|
2656
|
+
function createHeadersWithCSRF(init) {
|
|
2657
|
+
const headers = new Headers(init);
|
|
2658
|
+
return addCSRFHeader(headers);
|
|
2659
|
+
}
|
|
2660
|
+
|
|
2661
|
+
// ui/src/bichat/data/HttpDataSource.ts
|
|
2662
|
+
var HttpDataSource = class {
|
|
2663
|
+
constructor(config) {
|
|
2664
|
+
this.abortController = null;
|
|
2665
|
+
this.config = {
|
|
2666
|
+
graphQLEndpoint: "/graphql",
|
|
2667
|
+
streamEndpoint: "/stream",
|
|
2668
|
+
timeout: 3e4,
|
|
2669
|
+
...config
|
|
2670
|
+
};
|
|
2671
|
+
}
|
|
2672
|
+
/**
|
|
2673
|
+
* Get CSRF token from config
|
|
2674
|
+
*/
|
|
2675
|
+
getCSRFToken() {
|
|
2676
|
+
if (!this.config.csrfToken) {
|
|
2677
|
+
return "";
|
|
2678
|
+
}
|
|
2679
|
+
return typeof this.config.csrfToken === "function" ? this.config.csrfToken() : this.config.csrfToken;
|
|
2680
|
+
}
|
|
2681
|
+
/**
|
|
2682
|
+
* Create headers for HTTP requests
|
|
2683
|
+
*/
|
|
2684
|
+
createHeaders(additionalHeaders) {
|
|
2685
|
+
const headers = new Headers({
|
|
2686
|
+
"Content-Type": "application/json",
|
|
2687
|
+
...this.config.headers,
|
|
2688
|
+
...additionalHeaders
|
|
2689
|
+
});
|
|
2690
|
+
const csrfToken = this.getCSRFToken();
|
|
2691
|
+
if (csrfToken) {
|
|
2692
|
+
headers.set("X-CSRF-Token", csrfToken);
|
|
2693
|
+
}
|
|
2694
|
+
return headers;
|
|
2695
|
+
}
|
|
2696
|
+
/**
|
|
2697
|
+
* Execute GraphQL query
|
|
2698
|
+
*/
|
|
2699
|
+
async graphql(query, variables) {
|
|
2700
|
+
const url = `${this.config.baseUrl}${this.config.graphQLEndpoint}`;
|
|
2701
|
+
const response = await fetch(url, {
|
|
2702
|
+
method: "POST",
|
|
2703
|
+
headers: this.createHeaders(),
|
|
2704
|
+
body: JSON.stringify({ query, variables }),
|
|
2705
|
+
signal: this.abortController?.signal
|
|
2706
|
+
});
|
|
2707
|
+
if (!response.ok) {
|
|
2708
|
+
throw new Error(`GraphQL request failed: ${response.statusText}`);
|
|
2709
|
+
}
|
|
2710
|
+
const result = await response.json();
|
|
2711
|
+
if (result.errors && result.errors.length > 0) {
|
|
2712
|
+
throw new Error(result.errors[0].message || "GraphQL error");
|
|
2713
|
+
}
|
|
2714
|
+
return result.data;
|
|
2715
|
+
}
|
|
2716
|
+
/**
|
|
2717
|
+
* Create a new chat session
|
|
2718
|
+
*/
|
|
2719
|
+
async createSession() {
|
|
2720
|
+
const query = `
|
|
2721
|
+
mutation CreateChatSession {
|
|
2722
|
+
createChatSession {
|
|
2723
|
+
id
|
|
2724
|
+
title
|
|
2725
|
+
status
|
|
2726
|
+
pinned
|
|
2727
|
+
createdAt
|
|
2728
|
+
updatedAt
|
|
2729
|
+
}
|
|
2730
|
+
}
|
|
2731
|
+
`;
|
|
2732
|
+
const data = await this.graphql(query);
|
|
2733
|
+
return data.createChatSession;
|
|
2734
|
+
}
|
|
2735
|
+
/**
|
|
2736
|
+
* Fetch an existing session with messages
|
|
2737
|
+
*/
|
|
2738
|
+
async fetchSession(id) {
|
|
2739
|
+
const query = `
|
|
2740
|
+
query GetChatSession($id: ID!) {
|
|
2741
|
+
chatSession(id: $id) {
|
|
2742
|
+
session {
|
|
2743
|
+
id
|
|
2744
|
+
title
|
|
2745
|
+
status
|
|
2746
|
+
pinned
|
|
2747
|
+
createdAt
|
|
2748
|
+
updatedAt
|
|
2749
|
+
}
|
|
2750
|
+
messages {
|
|
2751
|
+
id
|
|
2752
|
+
sessionId
|
|
2753
|
+
role
|
|
2754
|
+
content
|
|
2755
|
+
createdAt
|
|
2756
|
+
toolCalls {
|
|
2757
|
+
id
|
|
2758
|
+
name
|
|
2759
|
+
arguments
|
|
2760
|
+
}
|
|
2761
|
+
citations {
|
|
2762
|
+
id
|
|
2763
|
+
source
|
|
2764
|
+
url
|
|
2765
|
+
excerpt
|
|
2766
|
+
}
|
|
2767
|
+
chartData {
|
|
2768
|
+
type
|
|
2769
|
+
title
|
|
2770
|
+
data
|
|
2771
|
+
xAxisKey
|
|
2772
|
+
yAxisKey
|
|
2773
|
+
}
|
|
2774
|
+
artifacts {
|
|
2775
|
+
type
|
|
2776
|
+
filename
|
|
2777
|
+
url
|
|
2778
|
+
sizeReadable
|
|
2779
|
+
rowCount
|
|
2780
|
+
description
|
|
2781
|
+
}
|
|
2782
|
+
explanation
|
|
2783
|
+
}
|
|
2784
|
+
pendingQuestion {
|
|
2785
|
+
id
|
|
2786
|
+
turnId
|
|
2787
|
+
question
|
|
2788
|
+
type
|
|
2789
|
+
options
|
|
2790
|
+
status
|
|
2791
|
+
}
|
|
2792
|
+
}
|
|
2793
|
+
}
|
|
2794
|
+
`;
|
|
2795
|
+
try {
|
|
2796
|
+
const data = await this.graphql(query, { id });
|
|
2797
|
+
return data.chatSession;
|
|
2798
|
+
} catch (err) {
|
|
2799
|
+
console.error("Failed to fetch session:", err);
|
|
2800
|
+
return null;
|
|
2801
|
+
}
|
|
2802
|
+
}
|
|
2803
|
+
/**
|
|
2804
|
+
* Send a message and stream the response using SSE
|
|
2805
|
+
*/
|
|
2806
|
+
async *sendMessage(sessionId, content, attachments = [], signal) {
|
|
2807
|
+
this.abortController = new AbortController();
|
|
2808
|
+
if (signal) {
|
|
2809
|
+
signal.addEventListener("abort", () => {
|
|
2810
|
+
this.abortController?.abort();
|
|
2811
|
+
});
|
|
2812
|
+
}
|
|
2813
|
+
const url = `${this.config.baseUrl}${this.config.streamEndpoint}`;
|
|
2814
|
+
const payload = {
|
|
2815
|
+
sessionId,
|
|
2816
|
+
content,
|
|
2817
|
+
attachments: attachments.map((a) => ({
|
|
2818
|
+
id: a.id,
|
|
2819
|
+
filename: a.filename,
|
|
2820
|
+
mimeType: a.mimeType,
|
|
2821
|
+
sizeBytes: a.sizeBytes,
|
|
2822
|
+
base64Data: a.base64Data
|
|
2823
|
+
}))
|
|
2824
|
+
};
|
|
2825
|
+
try {
|
|
2826
|
+
const response = await fetch(url, {
|
|
2827
|
+
method: "POST",
|
|
2828
|
+
headers: this.createHeaders(),
|
|
2829
|
+
body: JSON.stringify(payload),
|
|
2830
|
+
signal: this.abortController.signal
|
|
2831
|
+
});
|
|
2832
|
+
if (!response.ok) {
|
|
2833
|
+
throw new Error(`Stream request failed: ${response.statusText}`);
|
|
2834
|
+
}
|
|
2835
|
+
if (!response.body) {
|
|
2836
|
+
throw new Error("Response body is null");
|
|
2837
|
+
}
|
|
2838
|
+
const reader = response.body.getReader();
|
|
2839
|
+
const decoder = new TextDecoder();
|
|
2840
|
+
let buffer = "";
|
|
2841
|
+
try {
|
|
2842
|
+
while (true) {
|
|
2843
|
+
const { done, value } = await reader.read();
|
|
2844
|
+
if (done) {
|
|
2845
|
+
break;
|
|
2846
|
+
}
|
|
2847
|
+
buffer += decoder.decode(value, { stream: true });
|
|
2848
|
+
const lines = buffer.split("\n");
|
|
2849
|
+
buffer = lines.pop() || "";
|
|
2850
|
+
for (const line of lines) {
|
|
2851
|
+
if (!line.trim() || line.startsWith(":")) {
|
|
2852
|
+
continue;
|
|
2853
|
+
}
|
|
2854
|
+
if (line.startsWith("data: ")) {
|
|
2855
|
+
const data = line.slice(6);
|
|
2856
|
+
try {
|
|
2857
|
+
const chunk = JSON.parse(data);
|
|
2858
|
+
yield chunk;
|
|
2859
|
+
if (chunk.type === "done" || chunk.type === "error") {
|
|
2860
|
+
return;
|
|
2861
|
+
}
|
|
2862
|
+
} catch (parseErr) {
|
|
2863
|
+
console.error("Failed to parse SSE data:", parseErr);
|
|
2864
|
+
yield {
|
|
2865
|
+
type: "error",
|
|
2866
|
+
error: "Failed to parse stream data"
|
|
2867
|
+
};
|
|
2868
|
+
return;
|
|
2869
|
+
}
|
|
2870
|
+
}
|
|
2871
|
+
}
|
|
2872
|
+
}
|
|
2873
|
+
} finally {
|
|
2874
|
+
reader.releaseLock();
|
|
2875
|
+
}
|
|
2876
|
+
} catch (err) {
|
|
2877
|
+
if (err instanceof Error) {
|
|
2878
|
+
if (err.name === "AbortError") {
|
|
2879
|
+
yield {
|
|
2880
|
+
type: "error",
|
|
2881
|
+
error: "Stream cancelled"
|
|
2882
|
+
};
|
|
2883
|
+
} else {
|
|
2884
|
+
yield {
|
|
2885
|
+
type: "error",
|
|
2886
|
+
error: err.message
|
|
2887
|
+
};
|
|
2888
|
+
}
|
|
2889
|
+
} else {
|
|
2890
|
+
yield {
|
|
2891
|
+
type: "error",
|
|
2892
|
+
error: "Unknown error"
|
|
2893
|
+
};
|
|
2894
|
+
}
|
|
2895
|
+
} finally {
|
|
2896
|
+
this.abortController = null;
|
|
2897
|
+
}
|
|
2898
|
+
}
|
|
2899
|
+
/**
|
|
2900
|
+
* Cancel ongoing stream
|
|
2901
|
+
*/
|
|
2902
|
+
cancelStream() {
|
|
2903
|
+
if (this.abortController) {
|
|
2904
|
+
this.abortController.abort();
|
|
2905
|
+
this.abortController = null;
|
|
2906
|
+
}
|
|
2907
|
+
}
|
|
2908
|
+
/**
|
|
2909
|
+
* Submit answers to a pending question
|
|
2910
|
+
*/
|
|
2911
|
+
async submitQuestionAnswers(sessionId, questionId, answers) {
|
|
2912
|
+
const query = `
|
|
2913
|
+
mutation SubmitQuestionAnswers($sessionId: ID!, $questionId: ID!, $answers: JSON!) {
|
|
2914
|
+
submitQuestionAnswers(sessionId: $sessionId, questionId: $questionId, answers: $answers) {
|
|
2915
|
+
success
|
|
2916
|
+
error
|
|
2917
|
+
}
|
|
2918
|
+
}
|
|
2919
|
+
`;
|
|
2920
|
+
try {
|
|
2921
|
+
const data = await this.graphql(query, { sessionId, questionId, answers });
|
|
2922
|
+
return data.submitQuestionAnswers;
|
|
2923
|
+
} catch (err) {
|
|
2924
|
+
return {
|
|
2925
|
+
success: false,
|
|
2926
|
+
error: err instanceof Error ? err.message : "Failed to submit answers"
|
|
2927
|
+
};
|
|
2928
|
+
}
|
|
2929
|
+
}
|
|
2930
|
+
/**
|
|
2931
|
+
* Cancel a pending question
|
|
2932
|
+
*/
|
|
2933
|
+
async cancelPendingQuestion(questionId) {
|
|
2934
|
+
const query = `
|
|
2935
|
+
mutation CancelPendingQuestion($questionId: ID!) {
|
|
2936
|
+
cancelPendingQuestion(questionId: $questionId) {
|
|
2937
|
+
success
|
|
2938
|
+
error
|
|
2939
|
+
}
|
|
2940
|
+
}
|
|
2941
|
+
`;
|
|
2942
|
+
try {
|
|
2943
|
+
const data = await this.graphql(query, { questionId });
|
|
2944
|
+
return data.cancelPendingQuestion;
|
|
2945
|
+
} catch (err) {
|
|
2946
|
+
return {
|
|
2947
|
+
success: false,
|
|
2948
|
+
error: err instanceof Error ? err.message : "Failed to cancel question"
|
|
2949
|
+
};
|
|
2950
|
+
}
|
|
2951
|
+
}
|
|
2952
|
+
/**
|
|
2953
|
+
* Navigate to a session (optional, for SPA routing)
|
|
2954
|
+
*/
|
|
2955
|
+
navigateToSession(sessionId) {
|
|
2956
|
+
if (typeof window !== "undefined") {
|
|
2957
|
+
window.location.href = `/chat/${sessionId}`;
|
|
2958
|
+
}
|
|
2959
|
+
}
|
|
2960
|
+
};
|
|
2961
|
+
function createHttpDataSource(config) {
|
|
2962
|
+
return new HttpDataSource(config);
|
|
2963
|
+
}
|
|
2964
|
+
|
|
2965
|
+
exports.AssistantTurnView = AssistantTurnView;
|
|
2966
|
+
exports.AttachmentGrid = AttachmentGrid;
|
|
2967
|
+
exports.ChartCard = ChartCard;
|
|
2968
|
+
exports.ChatHeader = ChatHeader;
|
|
2969
|
+
exports.ChatSession = ChatSession;
|
|
2970
|
+
exports.ChatSessionProvider = ChatSessionProvider;
|
|
2971
|
+
exports.CodeOutputsPanel = CodeOutputsPanel;
|
|
2972
|
+
exports.ConfigProvider = ConfigProvider;
|
|
2973
|
+
exports.DownloadCard = DownloadCard;
|
|
2974
|
+
exports.EditableText = EditableText_default;
|
|
2975
|
+
exports.EmptyState = EmptyState_default;
|
|
2976
|
+
exports.HttpDataSource = HttpDataSource;
|
|
2977
|
+
exports.ImageModal = ImageModal;
|
|
2978
|
+
exports.InlineQuestionForm = InlineQuestionForm;
|
|
2979
|
+
exports.IotaContextProvider = IotaContextProvider;
|
|
2980
|
+
exports.ListItemSkeleton = ListItemSkeleton;
|
|
2981
|
+
exports.MarkdownRenderer = MarkdownRenderer;
|
|
2982
|
+
exports.MessageInput = MessageInput;
|
|
2983
|
+
exports.MessageList = MessageList;
|
|
2984
|
+
exports.MessageRole = MessageRole;
|
|
2985
|
+
exports.RateLimiter = RateLimiter;
|
|
2986
|
+
exports.ScrollToBottomButton = ScrollToBottomButton;
|
|
2987
|
+
exports.SearchInput = SearchInput_default;
|
|
2988
|
+
exports.Skeleton = Skeleton_default;
|
|
2989
|
+
exports.SkeletonAvatar = SkeletonAvatar;
|
|
2990
|
+
exports.SkeletonCard = SkeletonCard;
|
|
2991
|
+
exports.SkeletonGroup = SkeletonGroup;
|
|
2992
|
+
exports.SkeletonText = SkeletonText;
|
|
2993
|
+
exports.SourcesPanel = SourcesPanel;
|
|
2994
|
+
exports.StreamingCursor = StreamingCursor;
|
|
2995
|
+
exports.ThemeProvider = ThemeProvider;
|
|
2996
|
+
exports.TurnBubble = TurnBubble;
|
|
2997
|
+
exports.UserTurnView = UserTurnView;
|
|
2998
|
+
exports.WelcomeContent = WelcomeContent;
|
|
2999
|
+
exports.addCSRFHeader = addCSRFHeader;
|
|
3000
|
+
exports.backdropVariants = backdropVariants;
|
|
3001
|
+
exports.buttonVariants = buttonVariants;
|
|
3002
|
+
exports.convertToBase64 = convertToBase64;
|
|
3003
|
+
exports.createDataUrl = createDataUrl;
|
|
3004
|
+
exports.createHeadersWithCSRF = createHeadersWithCSRF;
|
|
3005
|
+
exports.createHttpDataSource = createHttpDataSource;
|
|
3006
|
+
exports.darkTheme = darkTheme;
|
|
3007
|
+
exports.dropdownVariants = dropdownVariants;
|
|
3008
|
+
exports.fadeInUpVariants = fadeInUpVariants;
|
|
3009
|
+
exports.fadeInVariants = fadeInVariants;
|
|
3010
|
+
exports.floatingButtonVariants = floatingButtonVariants;
|
|
3011
|
+
exports.formatFileSize = formatFileSize;
|
|
3012
|
+
exports.getCSRFToken = getCSRFToken;
|
|
3013
|
+
exports.hasConfigPermission = hasPermission2;
|
|
3014
|
+
exports.hasPermission = hasPermission;
|
|
3015
|
+
exports.lightTheme = lightTheme;
|
|
3016
|
+
exports.listItemVariants = listItemVariants;
|
|
3017
|
+
exports.messageContainerVariants = messageContainerVariants;
|
|
3018
|
+
exports.messageVariants = messageVariants;
|
|
3019
|
+
exports.scaleFadeVariants = scaleFadeVariants;
|
|
3020
|
+
exports.staggerContainerVariants = staggerContainerVariants;
|
|
3021
|
+
exports.toastVariants = toastVariants;
|
|
3022
|
+
exports.typingDotVariants = typingDotVariants;
|
|
3023
|
+
exports.useChat = useChat;
|
|
3024
|
+
exports.useConfig = useConfig;
|
|
3025
|
+
exports.useIotaContext = useIotaContext;
|
|
3026
|
+
exports.useRequiredConfig = useRequiredConfig;
|
|
3027
|
+
exports.useStreaming = useStreaming;
|
|
3028
|
+
exports.useTheme = useTheme;
|
|
3029
|
+
exports.useTranslation = useTranslation;
|
|
3030
|
+
exports.validateFileCount = validateFileCount;
|
|
3031
|
+
exports.validateImageFile = validateImageFile;
|
|
3032
|
+
//# sourceMappingURL=index.cjs.map
|
|
3033
|
+
//# sourceMappingURL=index.cjs.map
|