@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
|
@@ -0,0 +1,350 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import React, { createContext, useContext, useState, useCallback, useMemo, useEffect, useRef, type ReactNode } from 'react';
|
|
4
|
+
import { useLocalStorage, useIsMobile } from '@djangocfg/ui/hooks';
|
|
5
|
+
import { v4 as uuidv4 } from 'uuid';
|
|
6
|
+
import type { AIChatMessage, ChatApiResponse, AIChatSource, ChatWidgetConfig, ChatDisplayMode } from '../types';
|
|
7
|
+
|
|
8
|
+
const STORAGE_KEY_MODE = 'djangocfg-chat-mode';
|
|
9
|
+
const STORAGE_KEY_USER_ID = 'djangocfg-chat-user-id';
|
|
10
|
+
const STORAGE_KEY_MESSAGES = 'djangocfg-chat-messages';
|
|
11
|
+
const MAX_STORED_MESSAGES = 50;
|
|
12
|
+
|
|
13
|
+
function generateMessageId(): string {
|
|
14
|
+
return `msg_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Serialized message format for localStorage
|
|
19
|
+
*/
|
|
20
|
+
interface SerializedMessage {
|
|
21
|
+
id: string;
|
|
22
|
+
role: 'user' | 'assistant' | 'system';
|
|
23
|
+
content: string;
|
|
24
|
+
timestamp: string; // ISO string
|
|
25
|
+
sources?: AIChatSource[];
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Chat context state
|
|
30
|
+
*/
|
|
31
|
+
export interface ChatContextState {
|
|
32
|
+
/** All chat messages */
|
|
33
|
+
messages: AIChatMessage[];
|
|
34
|
+
/** Whether a request is in progress */
|
|
35
|
+
isLoading: boolean;
|
|
36
|
+
/** Last error if any */
|
|
37
|
+
error: Error | null;
|
|
38
|
+
/** Whether chat panel is open */
|
|
39
|
+
isOpen: boolean;
|
|
40
|
+
/** Whether chat is minimized */
|
|
41
|
+
isMinimized: boolean;
|
|
42
|
+
/** Configuration */
|
|
43
|
+
config: ChatWidgetConfig;
|
|
44
|
+
/** Current display mode */
|
|
45
|
+
displayMode: ChatDisplayMode;
|
|
46
|
+
/** Is on mobile device */
|
|
47
|
+
isMobile: boolean;
|
|
48
|
+
/** User ID for this session */
|
|
49
|
+
userId: string;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Chat context actions
|
|
54
|
+
*/
|
|
55
|
+
export interface ChatContextActions {
|
|
56
|
+
/** Send a message */
|
|
57
|
+
sendMessage: (content: string) => Promise<void>;
|
|
58
|
+
/** Clear all messages */
|
|
59
|
+
clearMessages: () => void;
|
|
60
|
+
/** Open chat panel */
|
|
61
|
+
openChat: () => void;
|
|
62
|
+
/** Close chat panel */
|
|
63
|
+
closeChat: () => void;
|
|
64
|
+
/** Toggle chat panel */
|
|
65
|
+
toggleChat: () => void;
|
|
66
|
+
/** Minimize/restore chat */
|
|
67
|
+
toggleMinimize: () => void;
|
|
68
|
+
/** Set display mode */
|
|
69
|
+
setDisplayMode: (mode: ChatDisplayMode) => void;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export type ChatContextValue = ChatContextState & ChatContextActions;
|
|
73
|
+
|
|
74
|
+
const ChatContext = createContext<ChatContextValue | null>(null);
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Chat provider props
|
|
78
|
+
*/
|
|
79
|
+
export interface ChatProviderProps {
|
|
80
|
+
children: ReactNode;
|
|
81
|
+
/** API endpoint for chat */
|
|
82
|
+
apiEndpoint?: string;
|
|
83
|
+
/** Widget configuration */
|
|
84
|
+
config?: Partial<ChatWidgetConfig>;
|
|
85
|
+
/** Callback on error */
|
|
86
|
+
onError?: (error: Error) => void;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Chat provider component
|
|
91
|
+
*/
|
|
92
|
+
export function ChatProvider({
|
|
93
|
+
children,
|
|
94
|
+
apiEndpoint = '/api/chat',
|
|
95
|
+
config: userConfig = {},
|
|
96
|
+
onError,
|
|
97
|
+
}: ChatProviderProps) {
|
|
98
|
+
const [messages, setMessages] = useState<AIChatMessage[]>([]);
|
|
99
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
100
|
+
const [error, setError] = useState<Error | null>(null);
|
|
101
|
+
const [isMinimized, setIsMinimized] = useState(false);
|
|
102
|
+
const isHydratedRef = useRef(false);
|
|
103
|
+
|
|
104
|
+
// Display mode with localStorage persistence
|
|
105
|
+
const [storedMode, setStoredMode] = useLocalStorage<ChatDisplayMode>(STORAGE_KEY_MODE, 'closed');
|
|
106
|
+
|
|
107
|
+
// User ID with localStorage persistence
|
|
108
|
+
const [userId, setUserId] = useLocalStorage<string>(STORAGE_KEY_USER_ID, '');
|
|
109
|
+
|
|
110
|
+
// Messages storage (serialized)
|
|
111
|
+
const [storedMessages, setStoredMessages] = useLocalStorage<SerializedMessage[]>(STORAGE_KEY_MESSAGES, []);
|
|
112
|
+
|
|
113
|
+
const isMobile = useIsMobile();
|
|
114
|
+
|
|
115
|
+
// Generate user ID if not exists
|
|
116
|
+
useEffect(() => {
|
|
117
|
+
if (!userId) {
|
|
118
|
+
setUserId(uuidv4());
|
|
119
|
+
}
|
|
120
|
+
}, [userId, setUserId]);
|
|
121
|
+
|
|
122
|
+
// Load messages from localStorage on mount (once)
|
|
123
|
+
useEffect(() => {
|
|
124
|
+
if (isHydratedRef.current) return;
|
|
125
|
+
isHydratedRef.current = true;
|
|
126
|
+
|
|
127
|
+
if (storedMessages.length > 0) {
|
|
128
|
+
const hydratedMessages: AIChatMessage[] = storedMessages.map((msg) => ({
|
|
129
|
+
...msg,
|
|
130
|
+
timestamp: new Date(msg.timestamp),
|
|
131
|
+
}));
|
|
132
|
+
setMessages(hydratedMessages);
|
|
133
|
+
}
|
|
134
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
135
|
+
}, [storedMessages]); // Depend on storedMessages to wait for localStorage
|
|
136
|
+
|
|
137
|
+
// Save messages to localStorage when they change
|
|
138
|
+
useEffect(() => {
|
|
139
|
+
if (!isHydratedRef.current) return;
|
|
140
|
+
|
|
141
|
+
const toStore: SerializedMessage[] = messages.slice(-MAX_STORED_MESSAGES).map((msg) => ({
|
|
142
|
+
id: msg.id,
|
|
143
|
+
role: msg.role,
|
|
144
|
+
content: msg.content,
|
|
145
|
+
timestamp: msg.timestamp.toISOString(),
|
|
146
|
+
sources: msg.sources,
|
|
147
|
+
}));
|
|
148
|
+
setStoredMessages(toStore);
|
|
149
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
150
|
+
}, [messages]);
|
|
151
|
+
|
|
152
|
+
// On mobile, sidebar mode is not available - fallback to floating
|
|
153
|
+
const displayMode: ChatDisplayMode = useMemo(() => {
|
|
154
|
+
if (isMobile && storedMode === 'sidebar') {
|
|
155
|
+
return 'floating';
|
|
156
|
+
}
|
|
157
|
+
return storedMode;
|
|
158
|
+
}, [isMobile, storedMode]);
|
|
159
|
+
|
|
160
|
+
// Derived state: isOpen is true when not in 'closed' mode
|
|
161
|
+
const isOpen = displayMode !== 'closed';
|
|
162
|
+
|
|
163
|
+
const config: ChatWidgetConfig = useMemo(
|
|
164
|
+
() => ({
|
|
165
|
+
apiEndpoint,
|
|
166
|
+
title: 'DjangoCFG Assistant',
|
|
167
|
+
placeholder: 'Ask about DjangoCFG...',
|
|
168
|
+
greeting:
|
|
169
|
+
"Hi! I'm your DjangoCFG documentation assistant. Ask me anything about configuration, features, or how to use the library.",
|
|
170
|
+
position: 'bottom-right',
|
|
171
|
+
variant: 'default',
|
|
172
|
+
...userConfig,
|
|
173
|
+
}),
|
|
174
|
+
[apiEndpoint, userConfig]
|
|
175
|
+
);
|
|
176
|
+
|
|
177
|
+
const sendMessage = useCallback(
|
|
178
|
+
async (content: string) => {
|
|
179
|
+
if (!content.trim() || isLoading) return;
|
|
180
|
+
|
|
181
|
+
const userMessage: AIChatMessage = {
|
|
182
|
+
id: generateMessageId(),
|
|
183
|
+
role: 'user',
|
|
184
|
+
content: content.trim(),
|
|
185
|
+
timestamp: new Date(),
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
setMessages((prev) => [...prev, userMessage]);
|
|
189
|
+
setIsLoading(true);
|
|
190
|
+
setError(null);
|
|
191
|
+
|
|
192
|
+
try {
|
|
193
|
+
const response = await fetch(config.apiEndpoint || apiEndpoint, {
|
|
194
|
+
method: 'POST',
|
|
195
|
+
headers: { 'Content-Type': 'application/json' },
|
|
196
|
+
body: JSON.stringify({
|
|
197
|
+
query: content,
|
|
198
|
+
userId: userId || undefined,
|
|
199
|
+
}),
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
if (!response.ok) {
|
|
203
|
+
throw new Error(`HTTP error: ${response.status}`);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const data: ChatApiResponse = await response.json();
|
|
207
|
+
|
|
208
|
+
if (!data.success) {
|
|
209
|
+
throw new Error(data.error || 'Failed to get response');
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
const sources: AIChatSource[] =
|
|
213
|
+
data.results?.map((r) => ({
|
|
214
|
+
title: r.chunk.title,
|
|
215
|
+
path: r.chunk.path,
|
|
216
|
+
url: r.chunk.url,
|
|
217
|
+
section: r.chunk.section,
|
|
218
|
+
score: r.score,
|
|
219
|
+
})) || [];
|
|
220
|
+
|
|
221
|
+
const assistantMessage: AIChatMessage = {
|
|
222
|
+
id: generateMessageId(),
|
|
223
|
+
role: 'assistant',
|
|
224
|
+
content: data.answer || 'I found some relevant documentation.',
|
|
225
|
+
timestamp: new Date(),
|
|
226
|
+
sources,
|
|
227
|
+
};
|
|
228
|
+
|
|
229
|
+
setMessages((prev) => [...prev, assistantMessage]);
|
|
230
|
+
} catch (err) {
|
|
231
|
+
const error = err instanceof Error ? err : new Error('Unknown error');
|
|
232
|
+
setError(error);
|
|
233
|
+
onError?.(error);
|
|
234
|
+
|
|
235
|
+
const errorMessage: AIChatMessage = {
|
|
236
|
+
id: generateMessageId(),
|
|
237
|
+
role: 'assistant',
|
|
238
|
+
content: `Sorry, I encountered an error: ${error.message}. Please try again.`,
|
|
239
|
+
timestamp: new Date(),
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
setMessages((prev) => [...prev, errorMessage]);
|
|
243
|
+
} finally {
|
|
244
|
+
setIsLoading(false);
|
|
245
|
+
}
|
|
246
|
+
},
|
|
247
|
+
[apiEndpoint, config.apiEndpoint, isLoading, onError, userId]
|
|
248
|
+
);
|
|
249
|
+
|
|
250
|
+
const clearMessages = useCallback(() => {
|
|
251
|
+
setMessages([]);
|
|
252
|
+
setStoredMessages([]);
|
|
253
|
+
setError(null);
|
|
254
|
+
}, [setStoredMessages]);
|
|
255
|
+
|
|
256
|
+
const openChat = useCallback(() => {
|
|
257
|
+
setStoredMode('floating');
|
|
258
|
+
setIsMinimized(false);
|
|
259
|
+
}, [setStoredMode]);
|
|
260
|
+
|
|
261
|
+
const closeChat = useCallback(() => {
|
|
262
|
+
setStoredMode('closed');
|
|
263
|
+
setIsMinimized(false);
|
|
264
|
+
}, [setStoredMode]);
|
|
265
|
+
|
|
266
|
+
const toggleChat = useCallback(() => {
|
|
267
|
+
if (displayMode === 'closed') {
|
|
268
|
+
setStoredMode('floating');
|
|
269
|
+
setIsMinimized(false);
|
|
270
|
+
} else {
|
|
271
|
+
setStoredMode('closed');
|
|
272
|
+
}
|
|
273
|
+
}, [displayMode, setStoredMode]);
|
|
274
|
+
|
|
275
|
+
const toggleMinimize = useCallback(() => {
|
|
276
|
+
setIsMinimized((prev) => !prev);
|
|
277
|
+
}, []);
|
|
278
|
+
|
|
279
|
+
const setDisplayMode = useCallback(
|
|
280
|
+
(mode: ChatDisplayMode) => {
|
|
281
|
+
// On mobile, sidebar is not available
|
|
282
|
+
if (isMobile && mode === 'sidebar') {
|
|
283
|
+
setStoredMode('floating');
|
|
284
|
+
} else {
|
|
285
|
+
setStoredMode(mode);
|
|
286
|
+
}
|
|
287
|
+
setIsMinimized(false);
|
|
288
|
+
},
|
|
289
|
+
[isMobile, setStoredMode]
|
|
290
|
+
);
|
|
291
|
+
|
|
292
|
+
const value = useMemo<ChatContextValue>(
|
|
293
|
+
() => ({
|
|
294
|
+
messages,
|
|
295
|
+
isLoading,
|
|
296
|
+
error,
|
|
297
|
+
isOpen,
|
|
298
|
+
isMinimized,
|
|
299
|
+
config,
|
|
300
|
+
displayMode,
|
|
301
|
+
isMobile,
|
|
302
|
+
userId: userId || '',
|
|
303
|
+
sendMessage,
|
|
304
|
+
clearMessages,
|
|
305
|
+
openChat,
|
|
306
|
+
closeChat,
|
|
307
|
+
toggleChat,
|
|
308
|
+
toggleMinimize,
|
|
309
|
+
setDisplayMode,
|
|
310
|
+
}),
|
|
311
|
+
[
|
|
312
|
+
messages,
|
|
313
|
+
isLoading,
|
|
314
|
+
error,
|
|
315
|
+
isOpen,
|
|
316
|
+
isMinimized,
|
|
317
|
+
config,
|
|
318
|
+
displayMode,
|
|
319
|
+
isMobile,
|
|
320
|
+
userId,
|
|
321
|
+
sendMessage,
|
|
322
|
+
clearMessages,
|
|
323
|
+
openChat,
|
|
324
|
+
closeChat,
|
|
325
|
+
toggleChat,
|
|
326
|
+
toggleMinimize,
|
|
327
|
+
setDisplayMode,
|
|
328
|
+
]
|
|
329
|
+
);
|
|
330
|
+
|
|
331
|
+
return <ChatContext.Provider value={value}>{children}</ChatContext.Provider>;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/**
|
|
335
|
+
* Hook to access chat context
|
|
336
|
+
*/
|
|
337
|
+
export function useChatContext(): ChatContextValue {
|
|
338
|
+
const context = useContext(ChatContext);
|
|
339
|
+
if (!context) {
|
|
340
|
+
throw new Error('useChatContext must be used within a ChatProvider');
|
|
341
|
+
}
|
|
342
|
+
return context;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* Hook to check if chat context is available
|
|
347
|
+
*/
|
|
348
|
+
export function useChatContextOptional(): ChatContextValue | null {
|
|
349
|
+
return useContext(ChatContext);
|
|
350
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
export { ChatProvider, useChatContext, useChatContextOptional } from './ChatContext';
|
|
4
|
+
export type { ChatContextState, ChatContextActions, ChatContextValue, ChatProviderProps } from './ChatContext';
|
|
5
|
+
|
|
6
|
+
export { AIChatProvider, useAIChatContext, useAIChatContextOptional } from './AIChatContext';
|
|
7
|
+
export type { AIChatContextState, AIChatContextActions, AIChatContextValue, AIChatProviderProps } from './AIChatContext';
|