@djangocfg/layouts 2.0.5 → 2.0.7

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 (58) hide show
  1. package/package.json +19 -13
  2. package/src/contexts/LeadsContext.tsx +156 -0
  3. package/src/contexts/NewsletterContext.tsx +263 -0
  4. package/src/contexts/SupportContext.tsx +256 -0
  5. package/src/contexts/index.ts +59 -0
  6. package/src/contexts/knowbase/ChatContext.tsx +174 -0
  7. package/src/contexts/knowbase/DocumentsContext.tsx +304 -0
  8. package/src/contexts/knowbase/SessionsContext.tsx +174 -0
  9. package/src/contexts/knowbase/index.ts +61 -0
  10. package/src/contexts/payments/BalancesContext.tsx +65 -0
  11. package/src/contexts/payments/CurrenciesContext.tsx +66 -0
  12. package/src/contexts/payments/OverviewContext.tsx +174 -0
  13. package/src/contexts/payments/PaymentsContext.tsx +132 -0
  14. package/src/contexts/payments/README.md +201 -0
  15. package/src/contexts/payments/RootPaymentsContext.tsx +68 -0
  16. package/src/contexts/payments/index.ts +50 -0
  17. package/src/index.ts +4 -1
  18. package/src/layouts/AppLayout/AppLayout.tsx +20 -10
  19. package/src/layouts/PaymentsLayout/PaymentsLayout.tsx +1 -1
  20. package/src/layouts/PaymentsLayout/components/CreatePaymentDialog.tsx +1 -1
  21. package/src/layouts/PaymentsLayout/components/PaymentDetailsDialog.tsx +3 -22
  22. package/src/layouts/PaymentsLayout/views/overview/components/BalanceCard.tsx +1 -1
  23. package/src/layouts/PaymentsLayout/views/overview/components/RecentPayments.tsx +1 -1
  24. package/src/layouts/PaymentsLayout/views/transactions/components/TransactionsList.tsx +1 -1
  25. package/src/layouts/ProfileLayout/components/ProfileForm.tsx +1 -1
  26. package/src/layouts/SupportLayout/SupportLayout.tsx +1 -1
  27. package/src/layouts/SupportLayout/components/MessageList.tsx +1 -1
  28. package/src/layouts/SupportLayout/components/TicketCard.tsx +1 -1
  29. package/src/layouts/SupportLayout/components/TicketList.tsx +1 -1
  30. package/src/layouts/SupportLayout/context/SupportLayoutContext.tsx +1 -1
  31. package/src/layouts/SupportLayout/index.ts +2 -0
  32. package/src/layouts/SupportLayout/types.ts +2 -4
  33. package/src/snippets/Chat/ChatWidget.tsx +1 -1
  34. package/src/snippets/Chat/components/MessageList.tsx +1 -1
  35. package/src/snippets/Chat/components/SessionList.tsx +2 -2
  36. package/src/snippets/Chat/index.tsx +1 -1
  37. package/src/snippets/Chat/types.ts +7 -5
  38. package/src/snippets/ContactForm/ContactForm.tsx +20 -8
  39. package/src/snippets/McpChat/components/AIChatWidget.tsx +268 -0
  40. package/src/snippets/McpChat/components/ChatMessages.tsx +151 -0
  41. package/src/snippets/McpChat/components/ChatPanel.tsx +126 -0
  42. package/src/snippets/McpChat/components/ChatSidebar.tsx +119 -0
  43. package/src/snippets/McpChat/components/ChatWidget.tsx +134 -0
  44. package/src/snippets/McpChat/components/MessageBubble.tsx +125 -0
  45. package/src/snippets/McpChat/components/MessageInput.tsx +139 -0
  46. package/src/snippets/McpChat/components/index.ts +22 -0
  47. package/src/snippets/McpChat/config.ts +35 -0
  48. package/src/snippets/McpChat/context/AIChatContext.tsx +245 -0
  49. package/src/snippets/McpChat/context/ChatContext.tsx +350 -0
  50. package/src/snippets/McpChat/context/index.ts +7 -0
  51. package/src/snippets/McpChat/hooks/index.ts +5 -0
  52. package/src/snippets/McpChat/hooks/useAIChat.ts +487 -0
  53. package/src/snippets/McpChat/hooks/useChatLayout.ts +329 -0
  54. package/src/snippets/McpChat/index.ts +76 -0
  55. package/src/snippets/McpChat/types.ts +141 -0
  56. package/src/snippets/index.ts +32 -0
  57. package/src/utils/index.ts +0 -1
  58. package/src/utils/og-image.ts +0 -169
@@ -34,7 +34,7 @@ import { Plus, RefreshCw } from 'lucide-react';
34
34
  import { useForm } from 'react-hook-form';
35
35
  import { zodResolver } from '@hookform/resolvers/zod';
36
36
  import { z } from 'zod';
37
- import { usePaymentsContext, useRootPaymentsContext } from '@djangocfg/api/cfg/contexts';
37
+ import { usePaymentsContext, useRootPaymentsContext } from '@djangocfg/layouts/contexts';
38
38
  import { PAYMENT_EVENTS, closePaymentsDialog } from '../events';
39
39
  import { openPaymentDetailsDialog } from '../events';
40
40
  import { paymentsLogger } from '../../../utils/logger';
@@ -15,8 +15,9 @@ import {
15
15
  DialogTitle,
16
16
  Button,
17
17
  TokenIcon,
18
+ CopyButton,
18
19
  } from '@djangocfg/ui';
19
- import { Copy, ExternalLink, CheckCircle2, Clock, XCircle, AlertCircle, RefreshCw } from 'lucide-react';
20
+ import { ExternalLink, CheckCircle2, Clock, XCircle, AlertCircle, RefreshCw } from 'lucide-react';
20
21
  import { Hooks, api } from '@djangocfg/api';
21
22
  import type { API } from '@djangocfg/api';
22
23
  import { PAYMENT_EVENTS } from '../events';
@@ -24,7 +25,6 @@ import { PAYMENT_EVENTS } from '../events';
24
25
  export const PaymentDetailsDialog: React.FC = () => {
25
26
  const [open, setOpen] = useState(false);
26
27
  const [paymentId, setPaymentId] = useState<string | null>(null);
27
- const [copied, setCopied] = useState(false);
28
28
  const [timeLeft, setTimeLeft] = useState<string>('');
29
29
 
30
30
  // Load payment data by ID using hook
@@ -61,14 +61,6 @@ export const PaymentDetailsDialog: React.FC = () => {
61
61
  setPaymentId(null);
62
62
  };
63
63
 
64
- const handleCopyAddress = async () => {
65
- if (payment?.pay_address) {
66
- await navigator.clipboard.writeText(payment.pay_address);
67
- setCopied(true);
68
- setTimeout(() => setCopied(false), 2000);
69
- }
70
- };
71
-
72
64
  // Calculate time left until expiration
73
65
  useEffect(() => {
74
66
  if (!payment?.expires_at) return;
@@ -237,18 +229,7 @@ export const PaymentDetailsDialog: React.FC = () => {
237
229
  <div className="flex-1 p-3 bg-muted rounded-sm font-mono text-sm break-all">
238
230
  {payment.pay_address}
239
231
  </div>
240
- <Button
241
- variant="outline"
242
- size="icon"
243
- onClick={handleCopyAddress}
244
- className="shrink-0"
245
- >
246
- {copied ? (
247
- <CheckCircle2 className="h-4 w-4 text-green-500" />
248
- ) : (
249
- <Copy className="h-4 w-4" />
250
- )}
251
- </Button>
232
+ <CopyButton value={payment.pay_address} variant="outline" />
252
233
  </div>
253
234
  </div>
254
235
  )}
@@ -16,7 +16,7 @@ import {
16
16
  Skeleton,
17
17
  } from '@djangocfg/ui';
18
18
  import { Wallet, RefreshCw, Plus } from 'lucide-react';
19
- import { useOverviewContext } from '@djangocfg/api/cfg/contexts';
19
+ import { useOverviewContext } from '@djangocfg/layouts/contexts';
20
20
  import { openCreatePaymentDialog } from '../../../events';
21
21
 
22
22
  export const BalanceCard: React.FC = () => {
@@ -16,7 +16,7 @@ import {
16
16
  Skeleton,
17
17
  } from '@djangocfg/ui';
18
18
  import { History, ExternalLink } from 'lucide-react';
19
- import { useOverviewContext } from '@djangocfg/api/cfg/contexts';
19
+ import { useOverviewContext } from '@djangocfg/layouts/contexts';
20
20
  import { openPaymentDetailsDialog } from '../../../events';
21
21
 
22
22
  export const RecentPayments: React.FC = () => {
@@ -28,7 +28,7 @@ import {
28
28
  Skeleton,
29
29
  } from '@djangocfg/ui';
30
30
  import { History, Search, Filter, RefreshCw, ArrowUpRight, ArrowDownLeft } from 'lucide-react';
31
- import { useOverviewContext } from '@djangocfg/api/cfg/contexts';
31
+ import { useOverviewContext } from '@djangocfg/layouts/contexts';
32
32
 
33
33
  export const TransactionsList: React.FC = () => {
34
34
  const {
@@ -23,7 +23,7 @@ import {
23
23
  useAccountsContext,
24
24
  PatchedUserProfileUpdateRequestSchema,
25
25
  type PatchedUserProfileUpdateRequest
26
- } from '@djangocfg/layouts/auth/context';
26
+ } from '../../../auth/context';
27
27
  import { useAuth } from '../../../auth';
28
28
 
29
29
  export const ProfileForm = () => {
@@ -7,7 +7,7 @@
7
7
  'use client';
8
8
 
9
9
  import React from 'react';
10
- import { SupportProvider } from '@djangocfg/api/cfg/contexts';
10
+ import { SupportProvider } from '@djangocfg/layouts/contexts';
11
11
  import { SupportLayoutProvider, useSupportLayoutContext } from './context';
12
12
  import {
13
13
  TicketList,
@@ -230,7 +230,7 @@ export const MessageList: React.FC = () => {
230
230
  }
231
231
 
232
232
  return (
233
- <ScrollArea className="h-full bg-muted/50" ref={scrollAreaRef}>
233
+ <ScrollArea className="h-full bg-muted/50" viewportRef={scrollAreaRef}>
234
234
  <div className="p-6 space-y-4" ref={scrollRef}>
235
235
  {/* Load more trigger at the top */}
236
236
  <div ref={loadMoreRef} className="h-2" />
@@ -8,7 +8,7 @@
8
8
  import React from 'react';
9
9
  import { Badge, Card, CardContent, cn } from '@djangocfg/ui';
10
10
  import { Clock, MessageSquare } from 'lucide-react';
11
- import type { Ticket } from '@djangocfg/api/cfg/contexts';
11
+ import type { Ticket } from '@djangocfg/layouts/contexts';
12
12
 
13
13
  interface TicketCardProps {
14
14
  ticket: Ticket;
@@ -97,7 +97,7 @@ export const TicketList: React.FC = () => {
97
97
  }
98
98
 
99
99
  return (
100
- <ScrollArea className="h-full" ref={scrollRef}>
100
+ <ScrollArea className="h-full" viewportRef={scrollRef}>
101
101
  <div className="p-4 space-y-2">
102
102
  {tickets.map((ticket, index) => (
103
103
  <div
@@ -6,7 +6,7 @@
6
6
  'use client';
7
7
 
8
8
  import React, { createContext, useCallback, useContext, useEffect, useState, type ReactNode } from 'react';
9
- import { useSupportContext, type Ticket } from '@djangocfg/api/cfg/contexts';
9
+ import { useSupportContext, type Ticket } from '@djangocfg/layouts/contexts';
10
10
  import { CfgSupportTypes } from '@djangocfg/api';
11
11
  import { supportLogger } from '../../../utils/logger';
12
12
 
@@ -1,6 +1,8 @@
1
1
  export * from './SupportLayout';
2
2
  export * from './context';
3
3
  export * from './events';
4
+ // types.ts only contains UI-specific types (SupportUIState, TicketFormData)
5
+ // Ticket and Message are exported from @djangocfg/layouts/contexts
4
6
  export * from './types';
5
7
  export * from './components';
6
8
 
@@ -3,10 +3,8 @@
3
3
  * Types for SupportLayout - combines API types with UI state
4
4
  */
5
5
 
6
- import type { Ticket, Message } from '@djangocfg/api/cfg/contexts';
7
-
8
- // Re-export API types
9
- export type { Ticket, Message };
6
+ // Note: Ticket and Message types are exported from @djangocfg/layouts/contexts
7
+ // Import them directly: import type { Ticket, Message } from '@djangocfg/layouts/contexts';
10
8
 
11
9
  // UI State
12
10
  export interface SupportUIState {
@@ -17,7 +17,7 @@ import {
17
17
  Plus,
18
18
  List,
19
19
  } from 'lucide-react';
20
- import { useKnowbaseChatContext, useKnowbaseSessionsContext, type ChatSource } from '@djangocfg/api/cfg/contexts';
20
+ import { useKnowbaseChatContext, useKnowbaseSessionsContext, type ChatSource } from '@djangocfg/layouts/contexts';
21
21
  import { Enums } from '@djangocfg/api/cfg/generated';
22
22
  import { chatLogger } from '../../utils/logger';
23
23
  import { useChatUI } from './ChatUIContext';
@@ -43,7 +43,7 @@ export const MessageList: React.FC<MessageListProps> = ({
43
43
  };
44
44
 
45
45
  return (
46
- <ScrollArea className={`h-full bg-muted/50 ${className}`} ref={scrollRef}>
46
+ <ScrollArea className={`h-full bg-muted/50 ${className}`} viewportRef={scrollRef}>
47
47
  <div className="space-y-4 p-4">
48
48
  {messages.length === 0 && !isLoading ? (
49
49
  <div className="flex flex-col items-center justify-center h-full text-center py-12">
@@ -17,7 +17,7 @@ import {
17
17
  Badge,
18
18
  } from '@djangocfg/ui';
19
19
  import { MessageSquare, Clock, Archive, Trash2, Loader2 } from 'lucide-react';
20
- import { useKnowbaseSessionsContext } from '@djangocfg/api/cfg/contexts';
20
+ import { useKnowbaseSessionsContext } from '@djangocfg/layouts/contexts';
21
21
  import { useInfiniteSessions } from '../hooks/useInfiniteSessions';
22
22
  import type { SessionListProps } from '../types';
23
23
 
@@ -82,7 +82,7 @@ export const SessionList: React.FC<SessionListProps> = ({
82
82
  </SheetDescription>
83
83
  </SheetHeader>
84
84
 
85
- <ScrollArea className="h-[calc(100vh-120px)] mt-6" ref={scrollRef}>
85
+ <ScrollArea className="h-[calc(100vh-120px)] mt-6" viewportRef={scrollRef}>
86
86
  {isLoading ? (
87
87
  <div className="flex items-center justify-center py-12">
88
88
  <Loader2 className="h-6 w-6 animate-spin text-muted-foreground" />
@@ -7,7 +7,7 @@
7
7
  'use client';
8
8
 
9
9
  import React from 'react';
10
- import { KnowbaseChatProvider, KnowbaseSessionsProvider } from '@djangocfg/api/cfg/contexts';
10
+ import { KnowbaseChatProvider, KnowbaseSessionsProvider } from '@djangocfg/layouts/contexts';
11
11
  import { ChatUIProvider } from './ChatUIContext';
12
12
  import { ChatWidget } from './ChatWidget';
13
13
  import type { ChatWidgetProps } from './types';
@@ -3,7 +3,13 @@
3
3
  * Type definitions for RAG-powered chat widget
4
4
  */
5
5
 
6
- import type { ChatMessage, ChatSource } from '@djangocfg/api/cfg/contexts';
6
+ // Import types from Schemas namespace - explicitly exported and works reliably
7
+ // Types are available via Schemas namespace even when direct exports have issues
8
+ import type { Schemas } from '@djangocfg/api';
9
+
10
+ // Extract types from Schemas namespace
11
+ export type ChatMessage = Schemas.ChatMessage;
12
+ export type ChatSource = Schemas.ChatSource;
7
13
 
8
14
  // ─────────────────────────────────────────────────────────────────────────
9
15
  // Extended Message Type (for UI with sources)
@@ -70,9 +76,5 @@ export interface SessionListProps {
70
76
  className?: string;
71
77
  }
72
78
 
73
- // ─────────────────────────────────────────────────────────────────────────
74
- // Re-export types from API
75
- // ─────────────────────────────────────────────────────────────────────────
76
79
 
77
- export type { ChatSource, ChatMessage } from '@djangocfg/api/cfg/contexts';
78
80
 
@@ -145,20 +145,32 @@ function ContactFormInner({
145
145
 
146
146
  // Watch form values and save to localStorage
147
147
  const watchedValues = useWatch({ control: form.control });
148
+ const prevDraftRef = React.useRef<FormDraft>(draft);
148
149
 
149
150
  useEffect(() => {
150
151
  // Only save to localStorage after hydration to avoid unnecessary writes
151
152
  if (!isHydrated) return;
152
153
 
153
154
  const { name, email, company, subject, message } = watchedValues;
154
- if (name || email || company || subject || message) {
155
- setDraft({
156
- name: name || '',
157
- email: email || '',
158
- company: company || '',
159
- subject: subject || '',
160
- message: message || '',
161
- });
155
+ const newDraft: FormDraft = {
156
+ name: name || '',
157
+ email: email || '',
158
+ company: company || '',
159
+ subject: subject || '',
160
+ message: message || '',
161
+ };
162
+
163
+ // Only update if draft actually changed to prevent infinite loop
164
+ const hasChanges =
165
+ prevDraftRef.current.name !== newDraft.name ||
166
+ prevDraftRef.current.email !== newDraft.email ||
167
+ prevDraftRef.current.company !== newDraft.company ||
168
+ prevDraftRef.current.subject !== newDraft.subject ||
169
+ prevDraftRef.current.message !== newDraft.message;
170
+
171
+ if (hasChanges && (name || email || company || subject || message)) {
172
+ prevDraftRef.current = newDraft;
173
+ setDraft(newDraft);
162
174
  }
163
175
  }, [watchedValues, setDraft, isHydrated]);
164
176
 
@@ -0,0 +1,268 @@
1
+ 'use client';
2
+
3
+ import React from 'react';
4
+ import { Button, Portal } from '@djangocfg/ui';
5
+ import { Zap } from 'lucide-react';
6
+ import { ChatPanel } from './ChatPanel';
7
+ import { ChatSidebar } from './ChatSidebar';
8
+ import { useAIChatContext, useAIChatContextOptional, AIChatProvider } from '../context/AIChatContext';
9
+ import { useChatLayout } from '../hooks/useChatLayout';
10
+ import { mcpEndpoints, type ChatWidgetConfig } from '../types';
11
+
12
+ // CSS for mysterious rotating border animation with multi-color ethereal effects
13
+ const fabAnimationStyles = `
14
+ @keyframes rotate-gradient {
15
+ 0% { transform: rotate(0deg); }
16
+ 100% { transform: rotate(360deg); }
17
+ }
18
+ @keyframes color-shift-glow {
19
+ 0%, 100% {
20
+ box-shadow:
21
+ 0 0 15px rgba(251, 191, 36, 0.4),
22
+ 0 0 30px rgba(168, 85, 247, 0.2),
23
+ 0 0 45px rgba(20, 184, 166, 0.1);
24
+ }
25
+ 25% {
26
+ box-shadow:
27
+ 0 0 18px rgba(168, 85, 247, 0.4),
28
+ 0 0 35px rgba(20, 184, 166, 0.25),
29
+ 0 0 50px rgba(251, 191, 36, 0.1);
30
+ }
31
+ 50% {
32
+ box-shadow:
33
+ 0 0 20px rgba(20, 184, 166, 0.4),
34
+ 0 0 40px rgba(251, 191, 36, 0.2),
35
+ 0 0 55px rgba(168, 85, 247, 0.15);
36
+ }
37
+ 75% {
38
+ box-shadow:
39
+ 0 0 17px rgba(236, 72, 153, 0.35),
40
+ 0 0 32px rgba(168, 85, 247, 0.25),
41
+ 0 0 48px rgba(20, 184, 166, 0.1);
42
+ }
43
+ }
44
+ @keyframes icon-pulse {
45
+ 0%, 100% {
46
+ opacity: 1;
47
+ transform: scale(1);
48
+ filter: drop-shadow(0 0 2px rgba(251, 191, 36, 0.5));
49
+ }
50
+ 50% {
51
+ opacity: 0.85;
52
+ transform: scale(0.95);
53
+ filter: drop-shadow(0 0 6px rgba(251, 191, 36, 0.8));
54
+ }
55
+ }
56
+ `;
57
+
58
+ export interface AIChatWidgetProps extends ChatWidgetConfig {
59
+ /** Custom class name for the container */
60
+ className?: string;
61
+ /** Enable streaming responses (default: true) */
62
+ enableStreaming?: boolean;
63
+ }
64
+
65
+ /**
66
+ * Internal AI chat widget that uses context
67
+ */
68
+ const AIChatWidgetInternal = React.memo<{ className?: string }>(({ className }) => {
69
+ const {
70
+ messages,
71
+ isLoading,
72
+ isMinimized,
73
+ config,
74
+ displayMode,
75
+ isMobile,
76
+ sendMessage,
77
+ openChat,
78
+ closeChat,
79
+ toggleMinimize,
80
+ setDisplayMode,
81
+ stopStreaming,
82
+ } = useAIChatContext();
83
+
84
+ // Use layout hook for consistent positioning
85
+ const { getFabStyles, getFloatingStyles } = useChatLayout();
86
+
87
+ const position = config.position || 'bottom-right';
88
+ const fabStyles = getFabStyles(position);
89
+ const floatingStyles = getFloatingStyles(position);
90
+
91
+ // Mode: closed - just show FAB with multi-color rotating border animation
92
+ if (displayMode === 'closed') {
93
+ return (
94
+ <Portal>
95
+ <style>{fabAnimationStyles}</style>
96
+ <div style={fabStyles} className={className || ''}>
97
+ {/* Outer glow container with color-shifting animation */}
98
+ <div
99
+ className="relative rounded-full"
100
+ style={{
101
+ width: '64px',
102
+ height: '64px',
103
+ animation: 'color-shift-glow 6s ease-in-out infinite',
104
+ }}
105
+ >
106
+ {/* Border container - clips the rotating gradient */}
107
+ <div
108
+ className="absolute rounded-full group"
109
+ style={{
110
+ inset: '0',
111
+ overflow: 'hidden',
112
+ }}
113
+ >
114
+ {/* Rotating conic gradient - multi-color organic glow */}
115
+ <div
116
+ className="absolute"
117
+ style={{
118
+ width: '200%',
119
+ height: '200%',
120
+ top: '-50%',
121
+ left: '-50%',
122
+ background: `conic-gradient(
123
+ from 0deg,
124
+ #fbbf24 0%,
125
+ transparent 8%,
126
+ #a855f7 18%,
127
+ transparent 28%,
128
+ #14b8a6 38%,
129
+ transparent 52%,
130
+ #ec4899 62%,
131
+ transparent 75%,
132
+ #fbbf24 88%,
133
+ transparent 95%,
134
+ #a855f7 100%
135
+ )`,
136
+ animation: 'rotate-gradient 5s linear infinite',
137
+ opacity: 0.9,
138
+ }}
139
+ />
140
+ {/* Inner mask - creates the thin organic border */}
141
+ <div
142
+ className="absolute rounded-full bg-background"
143
+ style={{ inset: '1.5px' }}
144
+ />
145
+ {/* Main FAB button */}
146
+ <Button
147
+ onClick={openChat}
148
+ variant="ghost"
149
+ className="absolute rounded-full hover:scale-105 transition-all duration-300 bg-background hover:bg-background/95 border-0"
150
+ style={{
151
+ inset: '1.5px',
152
+ width: 'auto',
153
+ height: 'auto',
154
+ }}
155
+ >
156
+ <Zap
157
+ className="h-6 w-6"
158
+ style={{
159
+ animation: 'icon-pulse 2s ease-in-out infinite',
160
+ color: '#fbbf24',
161
+ fill: '#fbbf24',
162
+ }}
163
+ />
164
+ </Button>
165
+ </div>
166
+ </div>
167
+ </div>
168
+ </Portal>
169
+ );
170
+ }
171
+
172
+ // Mode: sidebar - full-height panel on the right (desktop only)
173
+ if (displayMode === 'sidebar') {
174
+ return (
175
+ <Portal>
176
+ <ChatSidebar
177
+ messages={messages}
178
+ isLoading={isLoading}
179
+ onSendMessage={sendMessage}
180
+ onClose={closeChat}
181
+ onModeChange={setDisplayMode}
182
+ onStopStreaming={stopStreaming}
183
+ title={config.title}
184
+ placeholder={config.placeholder}
185
+ greeting={config.greeting}
186
+ />
187
+ </Portal>
188
+ );
189
+ }
190
+
191
+ // Mode: floating - floating panel
192
+ return (
193
+ <Portal>
194
+ <div style={floatingStyles} className={className || ''}>
195
+ <ChatPanel
196
+ messages={messages}
197
+ isLoading={isLoading}
198
+ onSendMessage={sendMessage}
199
+ onClose={closeChat}
200
+ onMinimize={toggleMinimize}
201
+ onModeChange={setDisplayMode}
202
+ onStopStreaming={stopStreaming}
203
+ isMinimized={isMinimized}
204
+ isMobile={isMobile}
205
+ title={config.title}
206
+ placeholder={config.placeholder}
207
+ greeting={config.greeting}
208
+ />
209
+ </div>
210
+ </Portal>
211
+ );
212
+ });
213
+
214
+ AIChatWidgetInternal.displayName = 'AIChatWidgetInternal';
215
+
216
+ /**
217
+ * AI Chat Widget component
218
+ *
219
+ * AI-powered documentation assistant with streaming support.
220
+ * Uses Mastra agent backend for intelligent responses.
221
+ *
222
+ * Can be used in two ways:
223
+ * 1. Standalone (wraps itself in AIChatProvider)
224
+ * 2. Inside an AIChatProvider (uses context directly)
225
+ *
226
+ * @example
227
+ * ```tsx
228
+ * // Standalone usage
229
+ * <AIChatWidget apiEndpoint="/api/ai/chat" />
230
+ *
231
+ * // With provider for custom control
232
+ * <AIChatProvider apiEndpoint="/api/ai/chat">
233
+ * <MyApp />
234
+ * <AIChatWidget />
235
+ * </AIChatProvider>
236
+ * ```
237
+ */
238
+ export const AIChatWidget: React.FC<AIChatWidgetProps> = ({
239
+ apiEndpoint = mcpEndpoints.chat,
240
+ title = 'DjangoCFG AI Assistant',
241
+ placeholder = 'Ask about DjangoCFG...',
242
+ greeting = "Hi! I'm your DjangoCFG AI assistant powered by GPT. Ask me anything about configuration, features, or how to use the library.",
243
+ position = 'bottom-right',
244
+ variant = 'default',
245
+ className,
246
+ enableStreaming = true,
247
+ }) => {
248
+ // Check if we're inside an AIChatProvider
249
+ const existingContext = useAIChatContextOptional();
250
+
251
+ // If already in context, use internal widget directly
252
+ if (existingContext) {
253
+ return <AIChatWidgetInternal className={className} />;
254
+ }
255
+
256
+ // Otherwise, wrap in provider
257
+ return (
258
+ <AIChatProvider
259
+ apiEndpoint={apiEndpoint}
260
+ config={{ title, placeholder, greeting, position, variant }}
261
+ enableStreaming={enableStreaming}
262
+ >
263
+ <AIChatWidgetInternal className={className} />
264
+ </AIChatProvider>
265
+ );
266
+ };
267
+
268
+ AIChatWidget.displayName = 'AIChatWidget';