@djangocfg/layouts 2.1.103 → 2.1.105

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