@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.
- package/package.json +19 -13
- package/src/contexts/LeadsContext.tsx +156 -0
- package/src/contexts/NewsletterContext.tsx +263 -0
- package/src/contexts/SupportContext.tsx +256 -0
- package/src/contexts/index.ts +59 -0
- package/src/contexts/knowbase/ChatContext.tsx +174 -0
- package/src/contexts/knowbase/DocumentsContext.tsx +304 -0
- package/src/contexts/knowbase/SessionsContext.tsx +174 -0
- package/src/contexts/knowbase/index.ts +61 -0
- package/src/contexts/payments/BalancesContext.tsx +65 -0
- package/src/contexts/payments/CurrenciesContext.tsx +66 -0
- package/src/contexts/payments/OverviewContext.tsx +174 -0
- package/src/contexts/payments/PaymentsContext.tsx +132 -0
- package/src/contexts/payments/README.md +201 -0
- package/src/contexts/payments/RootPaymentsContext.tsx +68 -0
- package/src/contexts/payments/index.ts +50 -0
- package/src/index.ts +4 -1
- package/src/layouts/AppLayout/AppLayout.tsx +20 -10
- package/src/layouts/PaymentsLayout/PaymentsLayout.tsx +1 -1
- package/src/layouts/PaymentsLayout/components/CreatePaymentDialog.tsx +1 -1
- package/src/layouts/PaymentsLayout/components/PaymentDetailsDialog.tsx +3 -22
- package/src/layouts/PaymentsLayout/views/overview/components/BalanceCard.tsx +1 -1
- package/src/layouts/PaymentsLayout/views/overview/components/RecentPayments.tsx +1 -1
- package/src/layouts/PaymentsLayout/views/transactions/components/TransactionsList.tsx +1 -1
- package/src/layouts/ProfileLayout/components/ProfileForm.tsx +1 -1
- package/src/layouts/SupportLayout/SupportLayout.tsx +1 -1
- package/src/layouts/SupportLayout/components/MessageList.tsx +1 -1
- package/src/layouts/SupportLayout/components/TicketCard.tsx +1 -1
- package/src/layouts/SupportLayout/components/TicketList.tsx +1 -1
- package/src/layouts/SupportLayout/context/SupportLayoutContext.tsx +1 -1
- package/src/layouts/SupportLayout/index.ts +2 -0
- package/src/layouts/SupportLayout/types.ts +2 -4
- package/src/snippets/Chat/ChatWidget.tsx +1 -1
- package/src/snippets/Chat/components/MessageList.tsx +1 -1
- package/src/snippets/Chat/components/SessionList.tsx +2 -2
- package/src/snippets/Chat/index.tsx +1 -1
- package/src/snippets/Chat/types.ts +7 -5
- package/src/snippets/ContactForm/ContactForm.tsx +20 -8
- package/src/snippets/McpChat/components/AIChatWidget.tsx +268 -0
- package/src/snippets/McpChat/components/ChatMessages.tsx +151 -0
- package/src/snippets/McpChat/components/ChatPanel.tsx +126 -0
- package/src/snippets/McpChat/components/ChatSidebar.tsx +119 -0
- package/src/snippets/McpChat/components/ChatWidget.tsx +134 -0
- package/src/snippets/McpChat/components/MessageBubble.tsx +125 -0
- package/src/snippets/McpChat/components/MessageInput.tsx +139 -0
- package/src/snippets/McpChat/components/index.ts +22 -0
- package/src/snippets/McpChat/config.ts +35 -0
- package/src/snippets/McpChat/context/AIChatContext.tsx +245 -0
- package/src/snippets/McpChat/context/ChatContext.tsx +350 -0
- package/src/snippets/McpChat/context/index.ts +7 -0
- package/src/snippets/McpChat/hooks/index.ts +5 -0
- package/src/snippets/McpChat/hooks/useAIChat.ts +487 -0
- package/src/snippets/McpChat/hooks/useChatLayout.ts +329 -0
- package/src/snippets/McpChat/index.ts +76 -0
- package/src/snippets/McpChat/types.ts +141 -0
- package/src/snippets/index.ts +32 -0
- package/src/utils/index.ts +0 -1
- 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/
|
|
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 {
|
|
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
|
-
<
|
|
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/
|
|
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/
|
|
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/
|
|
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 '
|
|
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/
|
|
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"
|
|
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/
|
|
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"
|
|
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/
|
|
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
|
-
|
|
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/
|
|
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}`}
|
|
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/
|
|
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"
|
|
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/
|
|
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
|
-
|
|
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
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
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';
|