@djangocfg/layouts 2.1.101 → 2.1.102

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.
Files changed (49) hide show
  1. package/dist/AIChatWidget-LUPM7S2O.mjs +1644 -0
  2. package/dist/AIChatWidget-LUPM7S2O.mjs.map +1 -0
  3. package/dist/AIChatWidget-O23TJJ7C.mjs +3 -0
  4. package/dist/AIChatWidget-O23TJJ7C.mjs.map +1 -0
  5. package/dist/chunk-53YKWR6F.mjs +6 -0
  6. package/dist/chunk-53YKWR6F.mjs.map +1 -0
  7. package/dist/chunk-EI7TDN2G.mjs +1652 -0
  8. package/dist/chunk-EI7TDN2G.mjs.map +1 -0
  9. package/dist/components.cjs +925 -0
  10. package/dist/components.cjs.map +1 -0
  11. package/dist/components.d.mts +583 -0
  12. package/dist/components.d.ts +583 -0
  13. package/dist/components.mjs +879 -0
  14. package/dist/components.mjs.map +1 -0
  15. package/dist/index.cjs +7573 -0
  16. package/dist/index.cjs.map +1 -0
  17. package/dist/index.d.mts +2376 -0
  18. package/dist/index.d.ts +2376 -0
  19. package/dist/index.mjs +5673 -0
  20. package/dist/index.mjs.map +1 -0
  21. package/dist/layouts.cjs +6530 -0
  22. package/dist/layouts.cjs.map +1 -0
  23. package/dist/layouts.d.mts +748 -0
  24. package/dist/layouts.d.ts +748 -0
  25. package/dist/layouts.mjs +4741 -0
  26. package/dist/layouts.mjs.map +1 -0
  27. package/dist/pages.cjs +178 -0
  28. package/dist/pages.cjs.map +1 -0
  29. package/dist/pages.d.mts +57 -0
  30. package/dist/pages.d.ts +57 -0
  31. package/dist/pages.mjs +168 -0
  32. package/dist/pages.mjs.map +1 -0
  33. package/dist/snippets.cjs +3793 -0
  34. package/dist/snippets.cjs.map +1 -0
  35. package/dist/snippets.d.mts +1192 -0
  36. package/dist/snippets.d.ts +1192 -0
  37. package/dist/snippets.mjs +3738 -0
  38. package/dist/snippets.mjs.map +1 -0
  39. package/dist/utils.cjs +34 -0
  40. package/dist/utils.cjs.map +1 -0
  41. package/dist/utils.d.mts +40 -0
  42. package/dist/utils.d.ts +40 -0
  43. package/dist/utils.mjs +25 -0
  44. package/dist/utils.mjs.map +1 -0
  45. package/package.json +38 -47
  46. package/src/components/errors/ErrorsTracker/components/ErrorButtons.tsx +2 -1
  47. package/src/layouts/ProfileLayout/ProfileLayout.tsx +507 -86
  48. package/src/layouts/ProfileLayout/components/DeleteAccountSection.tsx +2 -2
  49. package/src/snippets/AuthDialog/useAuthDialog.ts +1 -1
@@ -0,0 +1,1652 @@
1
+ import { User, Bot, Loader2, ExternalLink, MessageSquare, StopCircle, Send, RotateCcw, PanelRight, X, GripVertical, PanelRightClose, Zap } from 'lucide-react';
2
+ import React, { createContext, forwardRef, useRef, useImperativeHandle, useState, useCallback, useEffect, useContext, useMemo } from 'react';
3
+ import { Avatar, AvatarImage, AvatarFallback, Card, CardContent, Badge, Button, CardHeader, CardFooter, Portal } from '@djangocfg/ui-nextjs';
4
+ import { useLocalStorage, useIsMobile } from '@djangocfg/ui-nextjs/hooks';
5
+ import { jsxs, jsx } from 'react/jsx-runtime';
6
+ import { useAuth } from '@djangocfg/api/auth';
7
+ import { MarkdownMessage } from '@djangocfg/ui-tools';
8
+
9
+ var __defProp = Object.defineProperty;
10
+ var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
11
+
12
+ // src/snippets/McpChat/config.ts
13
+ var PROD_HOST = "https://mcp.djangocfg.com";
14
+ var DEV_HOST = "http://localhost:3002";
15
+ function getHost(autoDetect = false) {
16
+ if (autoDetect && true) {
17
+ return DEV_HOST;
18
+ }
19
+ return PROD_HOST;
20
+ }
21
+ __name(getHost, "getHost");
22
+ function getMcpEndpoints(autoDetect = false) {
23
+ const HOST = getHost(autoDetect);
24
+ return {
25
+ /** Base URL */
26
+ baseUrl: HOST,
27
+ /** Chat API endpoint */
28
+ chat: `${HOST}/api/chat`,
29
+ /** Search API endpoint */
30
+ search: `${HOST}/api/search`,
31
+ /** Conversations API endpoint */
32
+ conversations: `${HOST}/api/conversations`,
33
+ /** Health check endpoint */
34
+ health: `${HOST}/health`,
35
+ /** MCP protocol endpoint (Streamable HTTP) */
36
+ mcp: `${HOST}/mcp`,
37
+ /** SSE endpoint for legacy clients */
38
+ sse: `${HOST}/mcp/sse`
39
+ };
40
+ }
41
+ __name(getMcpEndpoints, "getMcpEndpoints");
42
+ var mcpEndpoints = getMcpEndpoints(false);
43
+ var sidebarConfig = {
44
+ /** Minimum sidebar width in pixels */
45
+ minWidth: 320,
46
+ /** Maximum sidebar width in pixels */
47
+ maxWidth: 600,
48
+ /** Default sidebar width in pixels */
49
+ defaultWidth: 400,
50
+ /** Z-index for chat elements */
51
+ zIndex: 300,
52
+ /** Animation duration in milliseconds */
53
+ animationDuration: 200
54
+ };
55
+ var fabConfig = {
56
+ /** Bottom offset in pixels */
57
+ bottom: 24,
58
+ /** Right offset in pixels */
59
+ right: 24};
60
+ var storageKeys = {
61
+ /** Display mode (closed, floating, sidebar) */
62
+ mode: "djangocfg-chat-mode",
63
+ /** User ID for conversation tracking */
64
+ userId: "djangocfg-chat-user-id",
65
+ /** Chat messages history */
66
+ messages: "djangocfg-chat-messages",
67
+ /** Sidebar width */
68
+ sidebarWidth: "djangocfg-chat-sidebar-width"
69
+ };
70
+
71
+ // src/snippets/McpChat/hooks/useAIChat.ts
72
+ function generateId() {
73
+ return `msg_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
74
+ }
75
+ __name(generateId, "generateId");
76
+ function generateThreadId() {
77
+ return `thread_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
78
+ }
79
+ __name(generateThreadId, "generateThreadId");
80
+ function generateUserId() {
81
+ return `user_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
82
+ }
83
+ __name(generateUserId, "generateUserId");
84
+ var STORAGE_KEY = "djangocfg_chat";
85
+ function getPersistedIds() {
86
+ if (typeof window === "undefined") {
87
+ return { threadId: generateThreadId(), userId: generateUserId() };
88
+ }
89
+ try {
90
+ const stored = localStorage.getItem(STORAGE_KEY);
91
+ if (stored) {
92
+ const data = JSON.parse(stored);
93
+ if (data.threadId && data.userId) {
94
+ return data;
95
+ }
96
+ }
97
+ } catch {
98
+ }
99
+ const ids = { threadId: generateThreadId(), userId: generateUserId() };
100
+ try {
101
+ localStorage.setItem(STORAGE_KEY, JSON.stringify(ids));
102
+ } catch {
103
+ }
104
+ return ids;
105
+ }
106
+ __name(getPersistedIds, "getPersistedIds");
107
+ function persistThreadId(threadId, userId) {
108
+ if (typeof window === "undefined") return;
109
+ try {
110
+ localStorage.setItem(STORAGE_KEY, JSON.stringify({ threadId, userId }));
111
+ } catch {
112
+ }
113
+ }
114
+ __name(persistThreadId, "persistThreadId");
115
+ async function saveMessageToServer(threadId, userId, message) {
116
+ try {
117
+ await fetch(`${mcpEndpoints.conversations}/${threadId}/messages`, {
118
+ method: "POST",
119
+ headers: { "Content-Type": "application/json" },
120
+ body: JSON.stringify({
121
+ userId,
122
+ message: {
123
+ id: message.id,
124
+ role: message.role,
125
+ content: message.content,
126
+ timestamp: message.timestamp.getTime(),
127
+ sources: message.sources
128
+ }
129
+ })
130
+ });
131
+ } catch (error) {
132
+ console.warn("[Chat] Failed to save message to server:", error);
133
+ }
134
+ }
135
+ __name(saveMessageToServer, "saveMessageToServer");
136
+ async function loadConversationFromServer(threadId) {
137
+ try {
138
+ const response = await fetch(`${mcpEndpoints.conversations}/${threadId}`);
139
+ if (response.status === 404) return null;
140
+ if (!response.ok) return [];
141
+ const data = await response.json();
142
+ if (!data.messages || !Array.isArray(data.messages)) return [];
143
+ return data.messages.map((m) => ({
144
+ id: m.id,
145
+ role: m.role,
146
+ content: m.content,
147
+ timestamp: new Date(m.timestamp),
148
+ sources: m.sources
149
+ }));
150
+ } catch (error) {
151
+ console.warn("[Chat] Failed to load conversation from server:", error);
152
+ return [];
153
+ }
154
+ }
155
+ __name(loadConversationFromServer, "loadConversationFromServer");
156
+ async function deleteConversationFromServer(threadId) {
157
+ try {
158
+ await fetch(`${mcpEndpoints.conversations}/${threadId}`, {
159
+ method: "DELETE"
160
+ });
161
+ } catch (error) {
162
+ console.warn("[Chat] Failed to delete conversation from server:", error);
163
+ }
164
+ }
165
+ __name(deleteConversationFromServer, "deleteConversationFromServer");
166
+ function useAIChat(options) {
167
+ const {
168
+ apiEndpoint = mcpEndpoints.chat,
169
+ initialMessages = [],
170
+ onError,
171
+ enableStreaming = true,
172
+ threadId: initialThreadId,
173
+ userId: initialUserId
174
+ } = options;
175
+ const [messages, setMessages] = useState(initialMessages);
176
+ const [isLoadingHistory, setIsLoadingHistory] = useState(true);
177
+ const persistedIds = useRef(null);
178
+ if (persistedIds.current === null && typeof window !== "undefined") {
179
+ persistedIds.current = getPersistedIds();
180
+ }
181
+ const [threadId, setThreadId] = useState(
182
+ () => initialThreadId || persistedIds.current?.threadId || generateThreadId()
183
+ );
184
+ const [userId] = useState(
185
+ () => initialUserId || persistedIds.current?.userId || generateUserId()
186
+ );
187
+ const [isLoading, setIsLoading] = useState(false);
188
+ const [error, setError] = useState(null);
189
+ const abortControllerRef = useRef(null);
190
+ useEffect(() => {
191
+ if (typeof window === "undefined") {
192
+ setIsLoadingHistory(false);
193
+ return;
194
+ }
195
+ const loadHistory = /* @__PURE__ */ __name(async () => {
196
+ const serverMessages = await loadConversationFromServer(threadId);
197
+ if (serverMessages === null) {
198
+ console.log("[Chat] Session expired or invalid, starting new session");
199
+ const newThreadId = generateThreadId();
200
+ setThreadId(newThreadId);
201
+ persistThreadId(newThreadId, userId);
202
+ setMessages([]);
203
+ } else if (serverMessages.length > 0) {
204
+ setMessages(serverMessages);
205
+ }
206
+ setIsLoadingHistory(false);
207
+ }, "loadHistory");
208
+ loadHistory();
209
+ }, [threadId, userId]);
210
+ const sendMessage = useCallback(
211
+ async (content) => {
212
+ if (!content.trim() || isLoading) return;
213
+ if (abortControllerRef.current) {
214
+ abortControllerRef.current.abort();
215
+ }
216
+ abortControllerRef.current = new AbortController();
217
+ const userMessage = {
218
+ id: generateId(),
219
+ role: "user",
220
+ content: content.trim(),
221
+ timestamp: /* @__PURE__ */ new Date()
222
+ };
223
+ const assistantMessageId = generateId();
224
+ const assistantMessage = {
225
+ id: assistantMessageId,
226
+ role: "assistant",
227
+ content: "",
228
+ timestamp: /* @__PURE__ */ new Date(),
229
+ isStreaming: true
230
+ };
231
+ setMessages((prev) => [...prev, userMessage, assistantMessage]);
232
+ setIsLoading(true);
233
+ setError(null);
234
+ saveMessageToServer(threadId, userId, userMessage);
235
+ try {
236
+ const chatMessages = [
237
+ ...messages.filter((m) => m.role !== "system").slice(-10).map((m) => ({
238
+ role: m.role,
239
+ content: m.content
240
+ })),
241
+ { role: "user", content }
242
+ ];
243
+ const response = await fetch(apiEndpoint, {
244
+ method: "POST",
245
+ headers: {
246
+ "Content-Type": "application/json"
247
+ },
248
+ body: JSON.stringify({
249
+ messages: chatMessages,
250
+ stream: enableStreaming
251
+ }),
252
+ signal: abortControllerRef.current.signal
253
+ });
254
+ if (!response.ok) {
255
+ throw new Error(`HTTP error: ${response.status}`);
256
+ }
257
+ if (enableStreaming && response.headers.get("content-type")?.includes("text/event-stream")) {
258
+ await handleStreamingResponse(response, assistantMessageId);
259
+ } else {
260
+ const data = await response.json();
261
+ if (!data.success) {
262
+ throw new Error(data.error || "Failed to get response");
263
+ }
264
+ if (data.threadId && data.threadId !== threadId) {
265
+ setThreadId(data.threadId);
266
+ }
267
+ const sources = data.sources?.map((s) => ({
268
+ title: s.title,
269
+ path: s.path,
270
+ url: s.url,
271
+ section: s.section,
272
+ score: s.score
273
+ })) || [];
274
+ const finalContent = data.content || "I found some relevant documentation.";
275
+ setMessages(
276
+ (prev) => prev.map(
277
+ (m) => m.id === assistantMessageId ? {
278
+ ...m,
279
+ content: finalContent,
280
+ sources,
281
+ isStreaming: false
282
+ } : m
283
+ )
284
+ );
285
+ saveMessageToServer(threadId, userId, {
286
+ id: assistantMessageId,
287
+ role: "assistant",
288
+ content: finalContent,
289
+ timestamp: /* @__PURE__ */ new Date(),
290
+ sources
291
+ });
292
+ }
293
+ } catch (err) {
294
+ if (err instanceof Error && err.name === "AbortError") {
295
+ setMessages((prev) => prev.filter((m) => m.id !== assistantMessageId));
296
+ return;
297
+ }
298
+ const error2 = err instanceof Error ? err : new Error("Unknown error");
299
+ setError(error2);
300
+ onError?.(error2);
301
+ setMessages(
302
+ (prev) => prev.map(
303
+ (m) => m.id === assistantMessageId ? {
304
+ ...m,
305
+ content: `Sorry, I encountered an error: ${error2.message}. Please try again.`,
306
+ isStreaming: false
307
+ } : m
308
+ )
309
+ );
310
+ } finally {
311
+ setIsLoading(false);
312
+ abortControllerRef.current = null;
313
+ }
314
+ },
315
+ [apiEndpoint, isLoading, messages, threadId, userId, enableStreaming, onError]
316
+ );
317
+ const handleStreamingResponse = /* @__PURE__ */ __name(async (response, messageId) => {
318
+ const reader = response.body?.getReader();
319
+ if (!reader) {
320
+ throw new Error("No response body");
321
+ }
322
+ const decoder = new TextDecoder();
323
+ let buffer = "";
324
+ let fullContent = "";
325
+ const sources = [];
326
+ try {
327
+ while (true) {
328
+ const { done, value } = await reader.read();
329
+ if (done) break;
330
+ buffer += decoder.decode(value, { stream: true });
331
+ const lines = buffer.split("\n");
332
+ buffer = lines.pop() || "";
333
+ for (const line of lines) {
334
+ if (line.startsWith("data: ")) {
335
+ const data = line.slice(6);
336
+ if (data === "[DONE]") {
337
+ setMessages(
338
+ (prev) => prev.map(
339
+ (m) => m.id === messageId ? {
340
+ ...m,
341
+ content: fullContent,
342
+ sources,
343
+ isStreaming: false
344
+ } : m
345
+ )
346
+ );
347
+ saveMessageToServer(threadId, userId, {
348
+ id: messageId,
349
+ role: "assistant",
350
+ content: fullContent,
351
+ timestamp: /* @__PURE__ */ new Date(),
352
+ sources
353
+ });
354
+ return;
355
+ }
356
+ try {
357
+ const parsed = JSON.parse(data);
358
+ if (parsed.type === "text" && parsed.content) {
359
+ fullContent += parsed.content;
360
+ setMessages(
361
+ (prev) => prev.map(
362
+ (m) => m.id === messageId ? {
363
+ ...m,
364
+ content: fullContent,
365
+ isStreaming: true
366
+ } : m
367
+ )
368
+ );
369
+ } else if (parsed.type === "source" && parsed.source) {
370
+ sources.push({
371
+ title: parsed.source.title,
372
+ path: parsed.source.path,
373
+ url: parsed.source.url,
374
+ section: parsed.source.section,
375
+ score: parsed.source.score
376
+ });
377
+ } else if (parsed.type === "done") {
378
+ setMessages(
379
+ (prev) => prev.map(
380
+ (m) => m.id === messageId ? {
381
+ ...m,
382
+ content: fullContent,
383
+ sources,
384
+ isStreaming: false
385
+ } : m
386
+ )
387
+ );
388
+ saveMessageToServer(threadId, userId, {
389
+ id: messageId,
390
+ role: "assistant",
391
+ content: fullContent,
392
+ timestamp: /* @__PURE__ */ new Date(),
393
+ sources
394
+ });
395
+ } else if (parsed.type === "error") {
396
+ throw new Error(parsed.error || "Stream error");
397
+ }
398
+ } catch {
399
+ }
400
+ }
401
+ }
402
+ }
403
+ } finally {
404
+ reader.releaseLock();
405
+ }
406
+ }, "handleStreamingResponse");
407
+ const clearMessages = useCallback(async () => {
408
+ if (abortControllerRef.current) {
409
+ abortControllerRef.current.abort();
410
+ }
411
+ await deleteConversationFromServer(threadId);
412
+ setMessages([]);
413
+ setError(null);
414
+ const newThreadId = generateThreadId();
415
+ setThreadId(newThreadId);
416
+ persistThreadId(newThreadId, userId);
417
+ }, [threadId, userId]);
418
+ const stopStreaming = useCallback(() => {
419
+ if (abortControllerRef.current) {
420
+ abortControllerRef.current.abort();
421
+ }
422
+ }, []);
423
+ return {
424
+ messages,
425
+ isLoading: isLoading || isLoadingHistory,
426
+ error,
427
+ threadId,
428
+ userId,
429
+ sendMessage,
430
+ clearMessages,
431
+ stopStreaming
432
+ };
433
+ }
434
+ __name(useAIChat, "useAIChat");
435
+ var STORAGE_KEY_MODE = "djangocfg-ai-chat-mode";
436
+ var AIChatContext = createContext(null);
437
+ function AIChatProvider({
438
+ children,
439
+ apiEndpoint = mcpEndpoints.chat,
440
+ config: userConfig = {},
441
+ onError,
442
+ enableStreaming = true
443
+ }) {
444
+ const {
445
+ messages,
446
+ isLoading,
447
+ error,
448
+ threadId,
449
+ userId,
450
+ sendMessage: sendAIMessage,
451
+ clearMessages: clearAIMessages,
452
+ stopStreaming
453
+ } = useAIChat({
454
+ apiEndpoint,
455
+ onError,
456
+ enableStreaming
457
+ });
458
+ const [isMinimized, setIsMinimized] = useState(false);
459
+ const [storedMode, setStoredMode] = useLocalStorage(STORAGE_KEY_MODE, "closed");
460
+ const isMobile = useIsMobile();
461
+ const displayMode = useMemo(() => {
462
+ if (isMobile && storedMode === "sidebar") {
463
+ return "floating";
464
+ }
465
+ return storedMode;
466
+ }, [isMobile, storedMode]);
467
+ const isOpen = displayMode !== "closed";
468
+ const isOpenRef = useRef(isOpen);
469
+ useEffect(() => {
470
+ isOpenRef.current = isOpen;
471
+ }, [isOpen]);
472
+ const lastActiveModeRef = useRef("floating");
473
+ useEffect(() => {
474
+ if (displayMode !== "closed") {
475
+ lastActiveModeRef.current = displayMode;
476
+ }
477
+ }, [displayMode]);
478
+ const config = useMemo(
479
+ () => ({
480
+ apiEndpoint,
481
+ title: "DjangoCFG AI",
482
+ placeholder: "Ask about DjangoCFG...",
483
+ greeting: "Hi! I'm your DjangoCFG AI assistant powered by GPT. Ask me anything about configuration, features, or how to use the library.",
484
+ position: "bottom-right",
485
+ variant: "default",
486
+ ...userConfig
487
+ }),
488
+ [apiEndpoint, userConfig]
489
+ );
490
+ const sendMessage = useCallback(
491
+ async (content) => {
492
+ await sendAIMessage(content);
493
+ },
494
+ [sendAIMessage]
495
+ );
496
+ const clearMessages = useCallback(() => {
497
+ clearAIMessages();
498
+ }, [clearAIMessages]);
499
+ const openChat = useCallback(() => {
500
+ setStoredMode(lastActiveModeRef.current);
501
+ setIsMinimized(false);
502
+ }, [setStoredMode]);
503
+ const closeChat = useCallback(() => {
504
+ setStoredMode("closed");
505
+ setIsMinimized(false);
506
+ }, [setStoredMode]);
507
+ const toggleChat = useCallback(() => {
508
+ if (displayMode === "closed") {
509
+ setStoredMode("floating");
510
+ setIsMinimized(false);
511
+ } else {
512
+ setStoredMode("closed");
513
+ }
514
+ }, [displayMode, setStoredMode]);
515
+ const toggleMinimize = useCallback(() => {
516
+ setIsMinimized((prev) => !prev);
517
+ }, []);
518
+ const setDisplayMode = useCallback(
519
+ (mode) => {
520
+ if (isMobile && mode === "sidebar") {
521
+ setStoredMode("floating");
522
+ } else {
523
+ setStoredMode(mode);
524
+ }
525
+ setIsMinimized(false);
526
+ },
527
+ [isMobile, setStoredMode]
528
+ );
529
+ const value = useMemo(
530
+ () => ({
531
+ messages,
532
+ isLoading,
533
+ error,
534
+ isOpen,
535
+ isMinimized,
536
+ config,
537
+ displayMode,
538
+ isMobile,
539
+ threadId,
540
+ userId,
541
+ sendMessage,
542
+ clearMessages,
543
+ openChat,
544
+ closeChat,
545
+ toggleChat,
546
+ toggleMinimize,
547
+ setDisplayMode,
548
+ stopStreaming
549
+ }),
550
+ [
551
+ messages,
552
+ isLoading,
553
+ error,
554
+ isOpen,
555
+ isMinimized,
556
+ config,
557
+ displayMode,
558
+ isMobile,
559
+ threadId,
560
+ userId,
561
+ sendMessage,
562
+ clearMessages,
563
+ openChat,
564
+ closeChat,
565
+ toggleChat,
566
+ toggleMinimize,
567
+ setDisplayMode,
568
+ stopStreaming
569
+ ]
570
+ );
571
+ useEffect(() => {
572
+ if (typeof window !== "undefined") {
573
+ window.__MCP_CHAT_AVAILABLE__ = true;
574
+ }
575
+ const handleChatEvent = /* @__PURE__ */ __name((event) => {
576
+ const customEvent = event;
577
+ const { message, context, autoSend = true, displayMode: requestedMode } = customEvent.detail;
578
+ window.dispatchEvent(new CustomEvent("mcp:chat:handled"));
579
+ let fullMessage = message;
580
+ if (context) {
581
+ if (context.type) {
582
+ fullMessage = `[${context.type.toUpperCase()}] ${message}`;
583
+ }
584
+ if (context.data) {
585
+ fullMessage += `
586
+
587
+ **Context:**
588
+ \`\`\`json
589
+ ${JSON.stringify(context.data, null, 2)}
590
+ \`\`\``;
591
+ }
592
+ if (context.source) {
593
+ fullMessage += `
594
+
595
+ _Source: ${context.source}_`;
596
+ }
597
+ }
598
+ if (requestedMode) {
599
+ setDisplayMode(requestedMode);
600
+ } else if (!isOpenRef.current) {
601
+ openChat();
602
+ }
603
+ if (autoSend) {
604
+ setTimeout(() => {
605
+ sendMessage(fullMessage);
606
+ }, 100);
607
+ }
608
+ }, "handleChatEvent");
609
+ window.addEventListener("mcp:chat:send", handleChatEvent);
610
+ return () => {
611
+ window.removeEventListener("mcp:chat:send", handleChatEvent);
612
+ if (typeof window !== "undefined") {
613
+ window.__MCP_CHAT_AVAILABLE__ = false;
614
+ }
615
+ };
616
+ }, [sendMessage, setDisplayMode, openChat]);
617
+ return /* @__PURE__ */ jsx(AIChatContext.Provider, { value, children });
618
+ }
619
+ __name(AIChatProvider, "AIChatProvider");
620
+ function useAIChatContext() {
621
+ const context = useContext(AIChatContext);
622
+ if (!context) {
623
+ throw new Error("useAIChatContext must be used within an AIChatProvider");
624
+ }
625
+ return context;
626
+ }
627
+ __name(useAIChatContext, "useAIChatContext");
628
+ function useAIChatContextOptional() {
629
+ return useContext(AIChatContext);
630
+ }
631
+ __name(useAIChatContextOptional, "useAIChatContextOptional");
632
+ var MIN_SIDEBAR_WIDTH = sidebarConfig.minWidth;
633
+ var MAX_SIDEBAR_WIDTH = sidebarConfig.maxWidth;
634
+ var DEFAULT_CONFIG = {
635
+ initialWidth: sidebarConfig.defaultWidth,
636
+ animationDuration: sidebarConfig.animationDuration,
637
+ pushTarget: "body"
638
+ };
639
+ function useChatLayout(config) {
640
+ const mergedConfig = { ...DEFAULT_CONFIG, ...config };
641
+ const { initialWidth, animationDuration, pushTarget } = mergedConfig;
642
+ const [storedWidth, setStoredWidth] = useLocalStorage(storageKeys.sidebarWidth, initialWidth);
643
+ const sidebarWidth = Math.max(MIN_SIDEBAR_WIDTH, Math.min(MAX_SIDEBAR_WIDTH, storedWidth));
644
+ const sidebarWidthRef = useRef(sidebarWidth);
645
+ const [isResizing, setIsResizing] = useState(false);
646
+ useEffect(() => {
647
+ sidebarWidthRef.current = sidebarWidth;
648
+ }, [sidebarWidth]);
649
+ const originalStylesRef = useRef(null);
650
+ const fixedElementsRef = useRef([]);
651
+ const currentModeRef = useRef("closed");
652
+ const getTargetElement = useCallback(() => {
653
+ if (typeof window === "undefined") return null;
654
+ if (pushTarget === "body") {
655
+ return document.body;
656
+ } else if (pushTarget === "main") {
657
+ return document.querySelector("main");
658
+ } else {
659
+ return document.querySelector(pushTarget);
660
+ }
661
+ }, [pushTarget]);
662
+ const getFixedElements = useCallback(() => {
663
+ if (typeof window === "undefined") return [];
664
+ const elements = [];
665
+ const allElements = document.querySelectorAll("*");
666
+ allElements.forEach((el) => {
667
+ if (!(el instanceof HTMLElement)) return;
668
+ if (el.closest("[data-chat-sidebar-panel]")) return;
669
+ const style = window.getComputedStyle(el);
670
+ const position = style.position;
671
+ const right = style.right;
672
+ if ((position === "fixed" || position === "sticky") && right === "0px") {
673
+ elements.push(el);
674
+ }
675
+ });
676
+ return elements;
677
+ }, []);
678
+ const saveOriginalStyles = useCallback((element) => {
679
+ if (!originalStylesRef.current) {
680
+ originalStylesRef.current = {
681
+ marginRight: element.style.marginRight,
682
+ overflowX: element.style.overflowX,
683
+ transition: element.style.transition
684
+ };
685
+ }
686
+ }, []);
687
+ const restoreOriginalStyles = useCallback((element) => {
688
+ if (originalStylesRef.current) {
689
+ element.style.marginRight = originalStylesRef.current.marginRight || "";
690
+ element.style.overflowX = originalStylesRef.current.overflowX || "";
691
+ element.style.transition = originalStylesRef.current.transition || "";
692
+ element.removeAttribute("data-chat-sidebar");
693
+ originalStylesRef.current = null;
694
+ }
695
+ }, []);
696
+ const adjustFixedElements = useCallback(
697
+ (open) => {
698
+ const currentWidth = sidebarWidthRef.current;
699
+ if (open) {
700
+ const fixedElements = getFixedElements();
701
+ fixedElementsRef.current = fixedElements.map((el) => ({
702
+ element: el,
703
+ right: el.style.right,
704
+ transition: el.style.transition
705
+ }));
706
+ fixedElements.forEach((el) => {
707
+ el.style.transition = `right ${animationDuration}ms ease`;
708
+ el.style.right = `${currentWidth}px`;
709
+ });
710
+ } else {
711
+ fixedElementsRef.current.forEach(({ element, right, transition }) => {
712
+ element.style.transition = `right ${animationDuration}ms ease`;
713
+ element.style.right = "0px";
714
+ setTimeout(() => {
715
+ element.style.right = right;
716
+ element.style.transition = transition;
717
+ }, animationDuration);
718
+ });
719
+ fixedElementsRef.current = [];
720
+ }
721
+ },
722
+ [getFixedElements, animationDuration]
723
+ );
724
+ const applySidebarLayout = useCallback(() => {
725
+ const target = getTargetElement();
726
+ if (!target) return;
727
+ const currentWidth = sidebarWidthRef.current;
728
+ saveOriginalStyles(target);
729
+ target.style.transition = `margin-right ${animationDuration}ms ease`;
730
+ target.style.marginRight = `${currentWidth}px`;
731
+ target.style.overflowX = "hidden";
732
+ target.setAttribute("data-chat-sidebar", "open");
733
+ adjustFixedElements(true);
734
+ currentModeRef.current = "sidebar";
735
+ }, [getTargetElement, saveOriginalStyles, animationDuration, adjustFixedElements]);
736
+ const applyDefaultLayout = useCallback(
737
+ (mode) => {
738
+ const target = getTargetElement();
739
+ if (!target) return;
740
+ if (currentModeRef.current === "sidebar") {
741
+ target.style.transition = `margin-right ${animationDuration}ms ease`;
742
+ target.style.marginRight = "0px";
743
+ adjustFixedElements(false);
744
+ setTimeout(() => {
745
+ restoreOriginalStyles(target);
746
+ }, animationDuration);
747
+ }
748
+ currentModeRef.current = mode;
749
+ },
750
+ [getTargetElement, restoreOriginalStyles, animationDuration, adjustFixedElements]
751
+ );
752
+ const applyLayout = useCallback(
753
+ (mode) => {
754
+ if (mode === "sidebar") {
755
+ applySidebarLayout();
756
+ } else {
757
+ applyDefaultLayout(mode);
758
+ }
759
+ },
760
+ [applySidebarLayout, applyDefaultLayout]
761
+ );
762
+ const resetLayout = useCallback(() => {
763
+ const target = getTargetElement();
764
+ if (target && originalStylesRef.current) {
765
+ restoreOriginalStyles(target);
766
+ }
767
+ fixedElementsRef.current.forEach(({ element, right, transition }) => {
768
+ element.style.right = right;
769
+ element.style.transition = transition;
770
+ });
771
+ fixedElementsRef.current = [];
772
+ currentModeRef.current = "closed";
773
+ }, [getTargetElement, restoreOriginalStyles]);
774
+ const updateWidthImmediate = useCallback(
775
+ (newWidth) => {
776
+ const clampedWidth = Math.max(MIN_SIDEBAR_WIDTH, Math.min(MAX_SIDEBAR_WIDTH, newWidth));
777
+ const target = getTargetElement();
778
+ if (target && currentModeRef.current === "sidebar") {
779
+ target.style.transition = "none";
780
+ target.style.marginRight = `${clampedWidth}px`;
781
+ }
782
+ fixedElementsRef.current.forEach(({ element }) => {
783
+ element.style.transition = "none";
784
+ element.style.right = `${clampedWidth}px`;
785
+ });
786
+ return clampedWidth;
787
+ },
788
+ [getTargetElement]
789
+ );
790
+ const updateWidth = useCallback(
791
+ (newWidth) => {
792
+ const clampedWidth = updateWidthImmediate(newWidth);
793
+ setStoredWidth(clampedWidth);
794
+ },
795
+ [updateWidthImmediate, setStoredWidth]
796
+ );
797
+ const startResize = useCallback(
798
+ (e) => {
799
+ e.preventDefault();
800
+ setIsResizing(true);
801
+ const startX = e.clientX;
802
+ const startWidth = sidebarWidthRef.current;
803
+ const handleMouseMove = /* @__PURE__ */ __name((moveEvent) => {
804
+ const deltaX = startX - moveEvent.clientX;
805
+ const newWidth = startWidth + deltaX;
806
+ const clampedWidth = Math.max(MIN_SIDEBAR_WIDTH, Math.min(MAX_SIDEBAR_WIDTH, newWidth));
807
+ updateWidthImmediate(clampedWidth);
808
+ sidebarWidthRef.current = clampedWidth;
809
+ setStoredWidth(clampedWidth);
810
+ }, "handleMouseMove");
811
+ const handleMouseUp = /* @__PURE__ */ __name(() => {
812
+ setIsResizing(false);
813
+ document.removeEventListener("mousemove", handleMouseMove);
814
+ document.removeEventListener("mouseup", handleMouseUp);
815
+ document.body.style.cursor = "";
816
+ document.body.style.userSelect = "";
817
+ }, "handleMouseUp");
818
+ document.addEventListener("mousemove", handleMouseMove);
819
+ document.addEventListener("mouseup", handleMouseUp);
820
+ document.body.style.cursor = "ew-resize";
821
+ document.body.style.userSelect = "none";
822
+ },
823
+ [updateWidthImmediate, setStoredWidth]
824
+ );
825
+ const getSidebarStyles = useCallback(() => {
826
+ return {
827
+ position: "fixed",
828
+ top: 0,
829
+ right: 0,
830
+ bottom: 0,
831
+ width: `${sidebarWidth}px`,
832
+ zIndex: sidebarConfig.zIndex
833
+ };
834
+ }, [sidebarWidth]);
835
+ const getFloatingStyles = useCallback(
836
+ (position) => {
837
+ return {
838
+ position: "fixed",
839
+ zIndex: sidebarConfig.zIndex - 50,
840
+ bottom: fabConfig.bottom,
841
+ ...position === "bottom-right" ? { right: fabConfig.right } : { left: fabConfig.right }
842
+ };
843
+ },
844
+ []
845
+ );
846
+ const getFabStyles = useCallback(
847
+ (position) => {
848
+ return {
849
+ position: "fixed",
850
+ zIndex: sidebarConfig.zIndex - 50,
851
+ bottom: fabConfig.bottom,
852
+ ...position === "bottom-right" ? { right: fabConfig.right } : { left: fabConfig.right }
853
+ };
854
+ },
855
+ []
856
+ );
857
+ useEffect(() => {
858
+ return () => {
859
+ resetLayout();
860
+ };
861
+ }, [resetLayout]);
862
+ return {
863
+ sidebarWidth,
864
+ applyLayout,
865
+ resetLayout,
866
+ updateWidth,
867
+ startResize,
868
+ isResizing,
869
+ getSidebarStyles,
870
+ getFloatingStyles,
871
+ getFabStyles
872
+ };
873
+ }
874
+ __name(useChatLayout, "useChatLayout");
875
+ function formatTime(date) {
876
+ return date.toLocaleTimeString("en-US", {
877
+ hour: "2-digit",
878
+ minute: "2-digit"
879
+ });
880
+ }
881
+ __name(formatTime, "formatTime");
882
+ var MessageBubble = React.memo(
883
+ ({ message, isCompact = false }) => {
884
+ const isUser = message.role === "user";
885
+ const isAssistant = message.role === "assistant";
886
+ const { user, isAuthenticated } = useAuth();
887
+ const showUserAvatar = isUser && isAuthenticated && user;
888
+ const userAvatar = user?.avatar || "";
889
+ const userDisplayName = user?.display_username || user?.email || "User";
890
+ const userInitial = userDisplayName.charAt(0).toUpperCase();
891
+ const avatarSize = isCompact ? "28px" : "36px";
892
+ const iconSize = isCompact ? "h-3.5 w-3.5" : "h-4 w-4";
893
+ return /* @__PURE__ */ jsxs(
894
+ "div",
895
+ {
896
+ className: `flex gap-3 animate-in fade-in slide-in-from-bottom-2 duration-300 max-w-full overflow-hidden ${isUser ? "flex-row-reverse" : ""}`,
897
+ children: [
898
+ showUserAvatar ? (
899
+ // Authenticated user avatar
900
+ /* @__PURE__ */ jsxs(Avatar, { className: "flex-shrink-0", style: { width: avatarSize, height: avatarSize }, children: [
901
+ /* @__PURE__ */ jsx(AvatarImage, { src: userAvatar, alt: userDisplayName }),
902
+ /* @__PURE__ */ jsx(AvatarFallback, { className: "bg-primary text-primary-foreground text-xs", children: userInitial })
903
+ ] })
904
+ ) : isUser ? (
905
+ // Guest user icon
906
+ /* @__PURE__ */ jsx(
907
+ "div",
908
+ {
909
+ className: "flex-shrink-0 rounded-full flex items-center justify-center bg-primary text-primary-foreground",
910
+ style: { width: avatarSize, height: avatarSize },
911
+ children: /* @__PURE__ */ jsx(User, { className: iconSize })
912
+ }
913
+ )
914
+ ) : (
915
+ // Bot icon
916
+ /* @__PURE__ */ jsx(
917
+ "div",
918
+ {
919
+ className: "flex-shrink-0 rounded-full flex items-center justify-center bg-muted text-muted-foreground",
920
+ style: { width: avatarSize, height: avatarSize },
921
+ children: /* @__PURE__ */ jsx(Bot, { className: iconSize })
922
+ }
923
+ )
924
+ ),
925
+ /* @__PURE__ */ jsxs("div", { className: `flex-1 min-w-0 ${isUser ? "max-w-[80%] ml-auto" : "max-w-[85%]"}`, children: [
926
+ /* @__PURE__ */ jsxs("div", { className: `flex items-baseline gap-2 mb-1 ${isUser ? "justify-end" : ""}`, children: [
927
+ /* @__PURE__ */ jsx("span", { className: `font-medium ${isCompact ? "text-xs" : "text-sm"}`, children: isUser ? userDisplayName : "DjangoCFG AI" }),
928
+ /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: formatTime(message.timestamp) })
929
+ ] }),
930
+ /* @__PURE__ */ jsx(
931
+ Card,
932
+ {
933
+ className: `transition-all duration-200 ${isUser ? "bg-primary text-primary-foreground ml-auto" : "bg-muted"}`,
934
+ children: /* @__PURE__ */ jsxs(CardContent, { className: isCompact ? "p-2" : "p-3", children: [
935
+ /* @__PURE__ */ jsxs("div", { className: `${isCompact ? "text-xs" : "text-sm"} overflow-hidden`, style: { overflowWrap: "anywhere", wordBreak: "break-word" }, children: [
936
+ /* @__PURE__ */ jsx(
937
+ MarkdownMessage,
938
+ {
939
+ content: message.content,
940
+ isUser,
941
+ isCompact
942
+ }
943
+ ),
944
+ message.isStreaming && /* @__PURE__ */ jsx(Loader2, { className: "inline-block ml-1 h-3 w-3 animate-spin" })
945
+ ] }),
946
+ isAssistant && message.sources && message.sources.length > 0 && /* @__PURE__ */ jsxs("div", { className: "mt-3 pt-2 border-t border-border/50", children: [
947
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground mb-1.5", children: "Related docs:" }),
948
+ /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-1.5", children: message.sources.slice(0, 3).map((source, idx) => /* @__PURE__ */ jsx(
949
+ "a",
950
+ {
951
+ href: source.url || source.path,
952
+ target: "_blank",
953
+ rel: "noopener noreferrer",
954
+ className: "inline-block",
955
+ children: /* @__PURE__ */ jsxs(
956
+ Badge,
957
+ {
958
+ variant: "outline",
959
+ className: "text-xs gap-1 hover:bg-primary/10 transition-colors cursor-pointer",
960
+ children: [
961
+ source.title,
962
+ source.section && ` - ${source.section}`,
963
+ /* @__PURE__ */ jsx(ExternalLink, { className: "h-2.5 w-2.5" })
964
+ ]
965
+ }
966
+ )
967
+ },
968
+ idx
969
+ )) })
970
+ ] })
971
+ ] })
972
+ }
973
+ )
974
+ ] })
975
+ ]
976
+ }
977
+ );
978
+ }
979
+ );
980
+ MessageBubble.displayName = "MessageBubble";
981
+ var ChatMessages = forwardRef(
982
+ ({
983
+ messages,
984
+ isLoading,
985
+ greeting,
986
+ onStopStreaming,
987
+ isCompact = false,
988
+ largeGreetingIcon = false,
989
+ greetingIcon = "bot",
990
+ greetingTitle
991
+ }, ref) => {
992
+ const scrollContainerRef = useRef(null);
993
+ useImperativeHandle(ref, () => ({
994
+ scrollToBottom: /* @__PURE__ */ __name(() => {
995
+ scrollContainerRef.current?.scrollTo({ top: 0, behavior: "smooth" });
996
+ }, "scrollToBottom"),
997
+ scrollToLastMessage: /* @__PURE__ */ __name(() => {
998
+ scrollContainerRef.current?.scrollTo({ top: 0, behavior: "smooth" });
999
+ }, "scrollToLastMessage")
1000
+ }), []);
1001
+ const GreetingIcon = greetingIcon === "message" ? MessageSquare : Bot;
1002
+ const iconSize = largeGreetingIcon ? { container: "64px", icon: "h-8 w-8" } : { container: "48px", icon: "h-6 w-6" };
1003
+ const padding = largeGreetingIcon ? "py-12" : "py-8";
1004
+ return /* @__PURE__ */ jsx("div", { ref: scrollContainerRef, className: "h-full w-full overflow-y-auto flex flex-col-reverse", children: /* @__PURE__ */ jsxs("div", { className: `${isCompact ? "p-3" : "p-4"} space-y-4 max-w-full overflow-x-hidden`, children: [
1005
+ messages.length === 0 && greeting && /* @__PURE__ */ jsxs("div", { className: `text-center ${padding}`, children: [
1006
+ /* @__PURE__ */ jsx(
1007
+ "div",
1008
+ {
1009
+ className: "mx-auto mb-4 rounded-full bg-primary/10 flex items-center justify-center",
1010
+ style: { width: iconSize.container, height: iconSize.container },
1011
+ children: /* @__PURE__ */ jsx(GreetingIcon, { className: `${iconSize.icon} text-primary` })
1012
+ }
1013
+ ),
1014
+ greetingTitle && /* @__PURE__ */ jsx("h4", { className: "font-medium mb-2", children: greetingTitle }),
1015
+ /* @__PURE__ */ jsx("p", { className: `text-sm text-muted-foreground ${largeGreetingIcon ? "max-w-[300px]" : "max-w-[280px]"} mx-auto`, children: greeting })
1016
+ ] }),
1017
+ messages.map((message) => /* @__PURE__ */ jsx("div", { "data-message-bubble": true, children: /* @__PURE__ */ jsx(MessageBubble, { message, isCompact }) }, message.id)),
1018
+ isLoading && messages.length > 0 && /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between text-muted-foreground text-sm", children: [
1019
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
1020
+ /* @__PURE__ */ jsxs("div", { className: "flex gap-1", children: [
1021
+ /* @__PURE__ */ jsx("span", { className: "animate-bounce", style: { animationDelay: "0ms" }, children: "." }),
1022
+ /* @__PURE__ */ jsx("span", { className: "animate-bounce", style: { animationDelay: "150ms" }, children: "." }),
1023
+ /* @__PURE__ */ jsx("span", { className: "animate-bounce", style: { animationDelay: "300ms" }, children: "." })
1024
+ ] }),
1025
+ /* @__PURE__ */ jsx("span", { children: "Generating response..." })
1026
+ ] }),
1027
+ onStopStreaming && /* @__PURE__ */ jsxs(
1028
+ Button,
1029
+ {
1030
+ variant: "ghost",
1031
+ size: "sm",
1032
+ onClick: onStopStreaming,
1033
+ className: "h-6 px-2 text-xs",
1034
+ children: [
1035
+ /* @__PURE__ */ jsx(StopCircle, { className: "h-3 w-3 mr-1" }),
1036
+ "Stop"
1037
+ ]
1038
+ }
1039
+ )
1040
+ ] })
1041
+ ] }) });
1042
+ }
1043
+ );
1044
+ ChatMessages.displayName = "ChatMessages";
1045
+ var AIMessageInput = React.memo(
1046
+ ({
1047
+ onSend,
1048
+ disabled = false,
1049
+ isLoading = false,
1050
+ placeholder = "Ask about DjangoCFG...",
1051
+ maxRows = 5
1052
+ }) => {
1053
+ const [value, setValue] = useState("");
1054
+ const textareaRef = useRef(null);
1055
+ const adjustHeight = useCallback(() => {
1056
+ const textarea = textareaRef.current;
1057
+ if (!textarea) return;
1058
+ textarea.style.height = "auto";
1059
+ const lineHeight = 24;
1060
+ const minHeight = 44;
1061
+ const maxHeight = lineHeight * maxRows + 20;
1062
+ const newHeight = Math.min(Math.max(textarea.scrollHeight, minHeight), maxHeight);
1063
+ textarea.style.height = `${newHeight}px`;
1064
+ }, [maxRows]);
1065
+ useEffect(() => {
1066
+ adjustHeight();
1067
+ }, [value, adjustHeight]);
1068
+ const handleSubmit = useCallback(
1069
+ (e) => {
1070
+ e?.preventDefault();
1071
+ const trimmed = value.trim();
1072
+ if (!trimmed || disabled || isLoading) return;
1073
+ onSend(trimmed);
1074
+ setValue("");
1075
+ if (textareaRef.current) {
1076
+ textareaRef.current.style.height = "auto";
1077
+ }
1078
+ textareaRef.current?.focus();
1079
+ },
1080
+ [value, disabled, isLoading, onSend]
1081
+ );
1082
+ const handleKeyDown = useCallback(
1083
+ (e) => {
1084
+ if (e.key === "Enter" && !e.shiftKey) {
1085
+ e.preventDefault();
1086
+ handleSubmit();
1087
+ }
1088
+ },
1089
+ [handleSubmit]
1090
+ );
1091
+ const canSend = value.trim().length > 0 && !disabled && !isLoading;
1092
+ return /* @__PURE__ */ jsxs("form", { onSubmit: handleSubmit, className: "w-full", children: [
1093
+ /* @__PURE__ */ jsxs(
1094
+ "div",
1095
+ {
1096
+ className: "relative flex items-end rounded-2xl border border-input bg-background transition-colors focus-within:ring-1 focus-within:ring-ring focus-within:border-ring",
1097
+ style: { minHeight: "44px" },
1098
+ children: [
1099
+ /* @__PURE__ */ jsx(
1100
+ "textarea",
1101
+ {
1102
+ ref: textareaRef,
1103
+ value,
1104
+ onChange: (e) => setValue(e.target.value),
1105
+ onKeyDown: handleKeyDown,
1106
+ placeholder,
1107
+ disabled: disabled || isLoading,
1108
+ rows: 1,
1109
+ className: "flex-1 resize-none bg-transparent px-4 py-3 text-sm placeholder:text-muted-foreground focus:outline-none disabled:cursor-not-allowed disabled:opacity-50 pr-12",
1110
+ style: {
1111
+ minHeight: "44px",
1112
+ maxHeight: `${24 * maxRows + 20}px`,
1113
+ lineHeight: "1.5rem"
1114
+ },
1115
+ autoComplete: "off"
1116
+ }
1117
+ ),
1118
+ /* @__PURE__ */ jsx(
1119
+ "div",
1120
+ {
1121
+ className: "absolute flex items-center justify-center",
1122
+ style: {
1123
+ right: "6px",
1124
+ bottom: "6px"
1125
+ },
1126
+ children: /* @__PURE__ */ jsx(
1127
+ Button,
1128
+ {
1129
+ type: "submit",
1130
+ size: "icon",
1131
+ disabled: !canSend,
1132
+ className: "h-8 w-8 rounded-full transition-all",
1133
+ style: {
1134
+ opacity: canSend ? 1 : 0.5
1135
+ },
1136
+ children: isLoading ? /* @__PURE__ */ jsx(Loader2, { className: "h-4 w-4 animate-spin" }) : /* @__PURE__ */ jsx(Send, { className: "h-4 w-4" })
1137
+ }
1138
+ )
1139
+ }
1140
+ )
1141
+ ]
1142
+ }
1143
+ ),
1144
+ /* @__PURE__ */ jsx("p", { className: "mt-1.5 text-xs text-muted-foreground text-center", children: "Press Enter to send, Shift+Enter for new line" })
1145
+ ] });
1146
+ }
1147
+ );
1148
+ AIMessageInput.displayName = "AIMessageInput";
1149
+ var ChatPanel = React.memo(() => {
1150
+ const {
1151
+ messages,
1152
+ isLoading,
1153
+ config,
1154
+ isMobile,
1155
+ sendMessage,
1156
+ closeChat,
1157
+ setDisplayMode,
1158
+ stopStreaming,
1159
+ clearMessages
1160
+ } = useAIChatContext();
1161
+ const panelStyles = isMobile ? {
1162
+ position: "absolute",
1163
+ top: 0,
1164
+ left: 0,
1165
+ right: 0,
1166
+ bottom: 0,
1167
+ width: "100%",
1168
+ height: "100%",
1169
+ maxHeight: "100dvh",
1170
+ borderRadius: 0,
1171
+ display: "flex",
1172
+ flexDirection: "column",
1173
+ margin: 0,
1174
+ border: "none"
1175
+ } : {
1176
+ width: "380px",
1177
+ height: "520px",
1178
+ maxHeight: "calc(100vh - 100px)"
1179
+ };
1180
+ return /* @__PURE__ */ jsxs(
1181
+ Card,
1182
+ {
1183
+ className: `flex flex-col ${isMobile ? "rounded-none border-0 shadow-none" : "shadow-2xl border-border/50"}`,
1184
+ style: panelStyles,
1185
+ children: [
1186
+ /* @__PURE__ */ jsxs(CardHeader, { className: "flex flex-row items-center justify-between p-3 border-b", children: [
1187
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
1188
+ /* @__PURE__ */ jsx(
1189
+ "div",
1190
+ {
1191
+ className: "rounded-full bg-primary/10 flex items-center justify-center",
1192
+ style: { width: "32px", height: "32px" },
1193
+ children: /* @__PURE__ */ jsx(Bot, { className: "h-4 w-4 text-primary" })
1194
+ }
1195
+ ),
1196
+ /* @__PURE__ */ jsxs("div", { children: [
1197
+ /* @__PURE__ */ jsx("h3", { className: "font-semibold text-sm", children: config.title || "DjangoCFG AI" }),
1198
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: "AI Assistant" })
1199
+ ] })
1200
+ ] }),
1201
+ /* @__PURE__ */ jsxs("div", { className: "flex gap-1", children: [
1202
+ messages.length > 0 && /* @__PURE__ */ jsx(
1203
+ Button,
1204
+ {
1205
+ variant: "ghost",
1206
+ size: "icon",
1207
+ className: "h-8 w-8",
1208
+ onClick: clearMessages,
1209
+ title: "New chat",
1210
+ children: /* @__PURE__ */ jsx(RotateCcw, { className: "h-4 w-4" })
1211
+ }
1212
+ ),
1213
+ !isMobile && /* @__PURE__ */ jsx(
1214
+ Button,
1215
+ {
1216
+ variant: "ghost",
1217
+ size: "icon",
1218
+ className: "h-8 w-8",
1219
+ onClick: () => setDisplayMode("sidebar"),
1220
+ title: "Switch to sidebar mode",
1221
+ children: /* @__PURE__ */ jsx(PanelRight, { className: "h-4 w-4" })
1222
+ }
1223
+ ),
1224
+ /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "icon", className: "h-8 w-8", onClick: closeChat, title: "Close", children: /* @__PURE__ */ jsx(X, { className: "h-4 w-4" }) })
1225
+ ] })
1226
+ ] }),
1227
+ /* @__PURE__ */ jsx(CardContent, { className: "flex-1 p-0 overflow-hidden", children: /* @__PURE__ */ jsx(
1228
+ ChatMessages,
1229
+ {
1230
+ messages,
1231
+ isLoading,
1232
+ greeting: config.greeting,
1233
+ onStopStreaming: stopStreaming,
1234
+ isCompact: true,
1235
+ greetingIcon: "bot"
1236
+ }
1237
+ ) }),
1238
+ /* @__PURE__ */ jsx(CardFooter, { className: "p-3 border-t", children: /* @__PURE__ */ jsx(
1239
+ AIMessageInput,
1240
+ {
1241
+ onSend: sendMessage,
1242
+ isLoading,
1243
+ placeholder: config.placeholder
1244
+ }
1245
+ ) })
1246
+ ]
1247
+ }
1248
+ );
1249
+ });
1250
+ ChatPanel.displayName = "ChatPanel";
1251
+ var ChatSidebar = React.memo(({
1252
+ resizeHandleWidth = 12,
1253
+ showResizeIcon = true,
1254
+ resizeHandleClassName
1255
+ }) => {
1256
+ const {
1257
+ messages,
1258
+ isLoading,
1259
+ config,
1260
+ sendMessage,
1261
+ closeChat,
1262
+ setDisplayMode,
1263
+ stopStreaming,
1264
+ clearMessages
1265
+ } = useAIChatContext();
1266
+ const { applyLayout, getSidebarStyles, startResize, isResizing } = useChatLayout();
1267
+ useEffect(() => {
1268
+ applyLayout("sidebar");
1269
+ return () => {
1270
+ applyLayout("closed");
1271
+ };
1272
+ }, []);
1273
+ const sidebarStyles = getSidebarStyles();
1274
+ return /* @__PURE__ */ jsxs(
1275
+ "div",
1276
+ {
1277
+ className: "flex bg-background",
1278
+ style: sidebarStyles,
1279
+ "data-chat-sidebar-panel": true,
1280
+ children: [
1281
+ /* @__PURE__ */ jsx(
1282
+ "div",
1283
+ {
1284
+ className: `
1285
+ flex items-center justify-center cursor-ew-resize
1286
+ border-l border-border transition-colors select-none flex-shrink-0
1287
+ ${isResizing ? "bg-primary/20" : "bg-muted/30 hover:bg-muted/50"}
1288
+ ${resizeHandleClassName || ""}
1289
+ `,
1290
+ style: { width: resizeHandleWidth },
1291
+ onMouseDown: startResize,
1292
+ title: "Drag to resize",
1293
+ children: showResizeIcon && /* @__PURE__ */ jsx(GripVertical, { className: `h-4 w-4 ${isResizing ? "text-primary" : "text-muted-foreground/50"}` })
1294
+ }
1295
+ ),
1296
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-col flex-1 min-w-0", children: [
1297
+ /* @__PURE__ */ jsxs(
1298
+ "div",
1299
+ {
1300
+ className: "flex items-center justify-between px-4 border-b border-border bg-muted/30",
1301
+ style: { height: "var(--nextra-navbar-height, 64px)", minHeight: "var(--nextra-navbar-height, 64px)" },
1302
+ children: [
1303
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
1304
+ /* @__PURE__ */ jsx(
1305
+ "div",
1306
+ {
1307
+ className: "rounded-full bg-primary/10 flex items-center justify-center",
1308
+ style: { width: "32px", height: "32px" },
1309
+ children: /* @__PURE__ */ jsx(Bot, { className: "h-4 w-4 text-primary" })
1310
+ }
1311
+ ),
1312
+ /* @__PURE__ */ jsxs("div", { children: [
1313
+ /* @__PURE__ */ jsx("h3", { className: "font-semibold text-sm", children: config.title || "DjangoCFG AI" }),
1314
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: "AI Assistant" })
1315
+ ] })
1316
+ ] }),
1317
+ /* @__PURE__ */ jsxs("div", { className: "flex gap-1", children: [
1318
+ messages.length > 0 && /* @__PURE__ */ jsx(
1319
+ Button,
1320
+ {
1321
+ variant: "ghost",
1322
+ size: "icon",
1323
+ className: "h-8 w-8",
1324
+ onClick: clearMessages,
1325
+ title: "New chat",
1326
+ children: /* @__PURE__ */ jsx(RotateCcw, { className: "h-4 w-4" })
1327
+ }
1328
+ ),
1329
+ /* @__PURE__ */ jsx(
1330
+ Button,
1331
+ {
1332
+ variant: "ghost",
1333
+ size: "icon",
1334
+ className: "h-8 w-8",
1335
+ onClick: () => setDisplayMode("floating"),
1336
+ title: "Switch to floating mode",
1337
+ children: /* @__PURE__ */ jsx(PanelRightClose, { className: "h-4 w-4" })
1338
+ }
1339
+ ),
1340
+ /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "icon", className: "h-8 w-8", onClick: closeChat, title: "Close chat", children: /* @__PURE__ */ jsx(X, { className: "h-4 w-4" }) })
1341
+ ] })
1342
+ ]
1343
+ }
1344
+ ),
1345
+ /* @__PURE__ */ jsx("div", { className: "flex-1 overflow-hidden", children: /* @__PURE__ */ jsx(
1346
+ ChatMessages,
1347
+ {
1348
+ messages,
1349
+ isLoading,
1350
+ greeting: config.greeting,
1351
+ onStopStreaming: stopStreaming,
1352
+ isCompact: false,
1353
+ largeGreetingIcon: true,
1354
+ greetingIcon: "message",
1355
+ greetingTitle: "How can I help?"
1356
+ }
1357
+ ) }),
1358
+ /* @__PURE__ */ jsx("div", { className: "p-4 border-t border-border bg-muted/30", children: /* @__PURE__ */ jsx(AIMessageInput, { onSend: sendMessage, isLoading, placeholder: config.placeholder }) })
1359
+ ] })
1360
+ ]
1361
+ }
1362
+ );
1363
+ });
1364
+ ChatSidebar.displayName = "ChatSidebar";
1365
+ var fabAnimationStyles = `
1366
+ @keyframes rotate-gradient {
1367
+ 0% { transform: rotate(0deg); }
1368
+ 100% { transform: rotate(360deg); }
1369
+ }
1370
+
1371
+ @keyframes rotate-gradient-reverse {
1372
+ 0% { transform: rotate(360deg); }
1373
+ 100% { transform: rotate(0deg); }
1374
+ }
1375
+
1376
+ @keyframes color-shift-glow {
1377
+ 0%, 100% {
1378
+ box-shadow:
1379
+ 0 0 20px rgba(251, 191, 36, 0.5),
1380
+ 0 0 40px rgba(168, 85, 247, 0.3),
1381
+ 0 0 60px rgba(20, 184, 166, 0.2);
1382
+ }
1383
+ 33% {
1384
+ box-shadow:
1385
+ 0 0 20px rgba(168, 85, 247, 0.5),
1386
+ 0 0 40px rgba(20, 184, 166, 0.3),
1387
+ 0 0 60px rgba(251, 191, 36, 0.2);
1388
+ }
1389
+ 66% {
1390
+ box-shadow:
1391
+ 0 0 20px rgba(20, 184, 166, 0.5),
1392
+ 0 0 40px rgba(236, 72, 153, 0.3),
1393
+ 0 0 60px rgba(168, 85, 247, 0.2);
1394
+ }
1395
+ }
1396
+
1397
+ @keyframes icon-pulse {
1398
+ 0%, 100% {
1399
+ opacity: 1;
1400
+ transform: scale(1);
1401
+ filter: drop-shadow(0 0 4px rgba(251, 191, 36, 0.7));
1402
+ }
1403
+ 50% {
1404
+ opacity: 0.85;
1405
+ transform: scale(1.15);
1406
+ filter: drop-shadow(0 0 12px rgba(251, 191, 36, 1));
1407
+ }
1408
+ }
1409
+
1410
+ @keyframes border-pulse {
1411
+ 0%, 100% {
1412
+ opacity: 1;
1413
+ filter: blur(0px);
1414
+ }
1415
+ 50% {
1416
+ opacity: 0.85;
1417
+ filter: blur(0.5px);
1418
+ }
1419
+ }
1420
+
1421
+ @keyframes inner-glow-pulse {
1422
+ 0%, 100% {
1423
+ box-shadow:
1424
+ inset 0 0 15px rgba(251, 191, 36, 0.3),
1425
+ inset 0 0 25px rgba(168, 85, 247, 0.2);
1426
+ }
1427
+ 50% {
1428
+ box-shadow:
1429
+ inset 0 0 20px rgba(168, 85, 247, 0.35),
1430
+ inset 0 0 30px rgba(20, 184, 166, 0.25);
1431
+ }
1432
+ }
1433
+
1434
+ @keyframes fab-entrance {
1435
+ 0% {
1436
+ transform: scale(0);
1437
+ }
1438
+ 50% {
1439
+ transform: scale(1.08);
1440
+ }
1441
+ 70% {
1442
+ transform: scale(0.98);
1443
+ }
1444
+ 85% {
1445
+ transform: scale(1.02);
1446
+ }
1447
+ 100% {
1448
+ transform: scale(1);
1449
+ }
1450
+ }
1451
+
1452
+ @keyframes fab-glow-entrance {
1453
+ 0% {
1454
+ box-shadow: 0 0 0 rgba(251, 191, 36, 0);
1455
+ }
1456
+ 40% {
1457
+ box-shadow:
1458
+ 0 0 25px rgba(251, 191, 36, 0.6),
1459
+ 0 0 50px rgba(168, 85, 247, 0.4),
1460
+ 0 0 75px rgba(20, 184, 166, 0.25);
1461
+ }
1462
+ 100% {
1463
+ box-shadow:
1464
+ 0 0 20px rgba(251, 191, 36, 0.5),
1465
+ 0 0 40px rgba(168, 85, 247, 0.3),
1466
+ 0 0 60px rgba(20, 184, 166, 0.2);
1467
+ }
1468
+ }
1469
+ `;
1470
+ var AIChatWidgetInternal = React.memo(({ className }) => {
1471
+ const { config, displayMode, openChat, isMobile } = useAIChatContext();
1472
+ const { getFabStyles, getFloatingStyles } = useChatLayout();
1473
+ const position = config.position || "bottom-right";
1474
+ const fabStyles = getFabStyles(position);
1475
+ const floatingStyles = getFloatingStyles(position);
1476
+ if (displayMode === "closed") {
1477
+ return /* @__PURE__ */ jsxs(Portal, { children: [
1478
+ /* @__PURE__ */ jsx("style", { children: fabAnimationStyles }),
1479
+ /* @__PURE__ */ jsx("div", { style: fabStyles, className: className || "", children: /* @__PURE__ */ jsx(
1480
+ "div",
1481
+ {
1482
+ className: "relative rounded-full",
1483
+ style: {
1484
+ width: "68px",
1485
+ height: "68px",
1486
+ overflow: "hidden",
1487
+ animation: "fab-entrance 0.6s cubic-bezier(0.34, 1.45, 0.64, 1) 0s 1 normal forwards, fab-glow-entrance 0.8s ease-out 0s 1 normal forwards, color-shift-glow 8s ease-in-out 0.6s infinite"
1488
+ },
1489
+ children: /* @__PURE__ */ jsxs(
1490
+ "div",
1491
+ {
1492
+ className: "absolute rounded-full",
1493
+ style: {
1494
+ inset: "0",
1495
+ overflow: "hidden"
1496
+ },
1497
+ children: [
1498
+ /* @__PURE__ */ jsx(
1499
+ "div",
1500
+ {
1501
+ className: "absolute rounded-full",
1502
+ style: {
1503
+ inset: "0",
1504
+ background: `conic-gradient(
1505
+ from 0deg,
1506
+ rgba(251, 191, 36, 1) 0%,
1507
+ rgba(251, 191, 36, 0.7) 8%,
1508
+ rgba(251, 191, 36, 0) 15%,
1509
+ rgba(168, 85, 247, 0) 20%,
1510
+ rgba(168, 85, 247, 0.7) 28%,
1511
+ rgba(168, 85, 247, 1) 35%,
1512
+ rgba(168, 85, 247, 0.7) 42%,
1513
+ rgba(168, 85, 247, 0) 50%,
1514
+ rgba(20, 184, 166, 0) 55%,
1515
+ rgba(20, 184, 166, 0.7) 63%,
1516
+ rgba(20, 184, 166, 1) 70%,
1517
+ rgba(20, 184, 166, 0.7) 77%,
1518
+ rgba(20, 184, 166, 0) 85%,
1519
+ rgba(236, 72, 153, 0) 88%,
1520
+ rgba(236, 72, 153, 0.7) 93%,
1521
+ rgba(236, 72, 153, 1) 97%,
1522
+ rgba(251, 191, 36, 1) 100%
1523
+ )`,
1524
+ animation: "rotate-gradient 7s linear infinite, border-pulse 4s ease-in-out infinite",
1525
+ filter: "blur(1px)",
1526
+ opacity: 0.95
1527
+ }
1528
+ }
1529
+ ),
1530
+ /* @__PURE__ */ jsx(
1531
+ "div",
1532
+ {
1533
+ className: "absolute rounded-full",
1534
+ style: {
1535
+ inset: "1px",
1536
+ background: `conic-gradient(
1537
+ from 180deg,
1538
+ rgba(168, 85, 247, 0.85) 0%,
1539
+ rgba(168, 85, 247, 0.5) 10%,
1540
+ rgba(168, 85, 247, 0) 20%,
1541
+ rgba(20, 184, 166, 0) 30%,
1542
+ rgba(20, 184, 166, 0.5) 40%,
1543
+ rgba(20, 184, 166, 0.85) 50%,
1544
+ rgba(20, 184, 166, 0.5) 60%,
1545
+ rgba(20, 184, 166, 0) 70%,
1546
+ rgba(251, 191, 36, 0) 75%,
1547
+ rgba(251, 191, 36, 0.5) 85%,
1548
+ rgba(251, 191, 36, 0.85) 95%,
1549
+ rgba(168, 85, 247, 0.85) 100%
1550
+ )`,
1551
+ animation: "rotate-gradient-reverse 9s linear infinite",
1552
+ filter: "blur(0.75px)",
1553
+ opacity: 0.75
1554
+ }
1555
+ }
1556
+ ),
1557
+ /* @__PURE__ */ jsx(
1558
+ "div",
1559
+ {
1560
+ className: "absolute rounded-full bg-background",
1561
+ style: {
1562
+ inset: "4px",
1563
+ animation: "inner-glow-pulse 5s ease-in-out infinite"
1564
+ }
1565
+ }
1566
+ ),
1567
+ /* @__PURE__ */ jsx(
1568
+ Button,
1569
+ {
1570
+ onClick: openChat,
1571
+ variant: "ghost",
1572
+ className: "absolute rounded-full hover:scale-105 transition-all duration-300 bg-background/80 hover:bg-background/95 border-0 backdrop-blur-sm",
1573
+ style: {
1574
+ inset: "2.5px",
1575
+ width: "auto",
1576
+ height: "auto"
1577
+ },
1578
+ children: /* @__PURE__ */ jsx(
1579
+ Zap,
1580
+ {
1581
+ className: "h-6 w-6",
1582
+ style: {
1583
+ animation: "icon-pulse 2.5s ease-in-out infinite",
1584
+ color: "#fbbf24",
1585
+ fill: "#fbbf24"
1586
+ }
1587
+ }
1588
+ )
1589
+ }
1590
+ )
1591
+ ]
1592
+ }
1593
+ )
1594
+ }
1595
+ ) })
1596
+ ] });
1597
+ }
1598
+ if (displayMode === "sidebar") {
1599
+ return /* @__PURE__ */ jsx(Portal, { children: /* @__PURE__ */ jsx(ChatSidebar, {}) });
1600
+ }
1601
+ if (isMobile) {
1602
+ return /* @__PURE__ */ jsx(Portal, { children: /* @__PURE__ */ jsx(
1603
+ "div",
1604
+ {
1605
+ className: "z-[400] overflow-hidden",
1606
+ style: {
1607
+ position: "fixed",
1608
+ top: 0,
1609
+ left: 0,
1610
+ right: 0,
1611
+ bottom: 0,
1612
+ width: "100vw",
1613
+ height: "100dvh"
1614
+ },
1615
+ children: /* @__PURE__ */ jsx(ChatPanel, {})
1616
+ }
1617
+ ) });
1618
+ }
1619
+ return /* @__PURE__ */ jsx(Portal, { children: /* @__PURE__ */ jsx("div", { style: floatingStyles, className: className || "", children: /* @__PURE__ */ jsx(ChatPanel, {}) }) });
1620
+ });
1621
+ AIChatWidgetInternal.displayName = "AIChatWidgetInternal";
1622
+ var AIChatWidget = /* @__PURE__ */ __name(({
1623
+ apiEndpoint,
1624
+ title = "DjangoCFG AI",
1625
+ placeholder = "Ask about DjangoCFG...",
1626
+ greeting = "Hi! I'm your DjangoCFG AI assistant powered by GPT. Ask me anything about configuration, features, or how to use the library.",
1627
+ position = "bottom-right",
1628
+ variant = "default",
1629
+ className,
1630
+ enableStreaming = true,
1631
+ autoDetectEnvironment = false
1632
+ }) => {
1633
+ const existingContext = useAIChatContextOptional();
1634
+ if (existingContext) {
1635
+ return /* @__PURE__ */ jsx(AIChatWidgetInternal, { className });
1636
+ }
1637
+ const finalApiEndpoint = apiEndpoint || getMcpEndpoints(autoDetectEnvironment).chat;
1638
+ return /* @__PURE__ */ jsx(
1639
+ AIChatProvider,
1640
+ {
1641
+ apiEndpoint: finalApiEndpoint,
1642
+ config: { title, placeholder, greeting, position, variant, autoDetectEnvironment },
1643
+ enableStreaming,
1644
+ children: /* @__PURE__ */ jsx(AIChatWidgetInternal, { className })
1645
+ }
1646
+ );
1647
+ }, "AIChatWidget");
1648
+ AIChatWidget.displayName = "AIChatWidget";
1649
+
1650
+ export { AIChatProvider, AIChatWidget, AIMessageInput, ChatPanel, ChatSidebar, MessageBubble, __name, storageKeys, useAIChat, useAIChatContext, useAIChatContextOptional, useChatLayout };
1651
+ //# sourceMappingURL=chunk-EI7TDN2G.mjs.map
1652
+ //# sourceMappingURL=chunk-EI7TDN2G.mjs.map