@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,139 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import React, { useState, useRef, useCallback, useEffect } from 'react';
|
|
4
|
+
import { Button } from '@djangocfg/ui';
|
|
5
|
+
import { Send, Loader2 } from 'lucide-react';
|
|
6
|
+
|
|
7
|
+
export interface AIMessageInputProps {
|
|
8
|
+
onSend: (message: string) => void;
|
|
9
|
+
disabled?: boolean;
|
|
10
|
+
isLoading?: boolean;
|
|
11
|
+
placeholder?: string;
|
|
12
|
+
maxRows?: number;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export const AIMessageInput = React.memo<AIMessageInputProps>(
|
|
16
|
+
({
|
|
17
|
+
onSend,
|
|
18
|
+
disabled = false,
|
|
19
|
+
isLoading = false,
|
|
20
|
+
placeholder = 'Ask about DjangoCFG...',
|
|
21
|
+
maxRows = 5,
|
|
22
|
+
}) => {
|
|
23
|
+
const [value, setValue] = useState('');
|
|
24
|
+
const textareaRef = useRef<HTMLTextAreaElement>(null);
|
|
25
|
+
|
|
26
|
+
// Auto-resize textarea
|
|
27
|
+
const adjustHeight = useCallback(() => {
|
|
28
|
+
const textarea = textareaRef.current;
|
|
29
|
+
if (!textarea) return;
|
|
30
|
+
|
|
31
|
+
// Reset height to auto to get the correct scrollHeight
|
|
32
|
+
textarea.style.height = 'auto';
|
|
33
|
+
|
|
34
|
+
// Calculate line height and max height
|
|
35
|
+
const lineHeight = 24; // ~1.5rem
|
|
36
|
+
const minHeight = 44; // Single line height with padding
|
|
37
|
+
const maxHeight = lineHeight * maxRows + 20; // padding
|
|
38
|
+
|
|
39
|
+
// Set new height
|
|
40
|
+
const newHeight = Math.min(Math.max(textarea.scrollHeight, minHeight), maxHeight);
|
|
41
|
+
textarea.style.height = `${newHeight}px`;
|
|
42
|
+
}, [maxRows]);
|
|
43
|
+
|
|
44
|
+
// Adjust height when value changes
|
|
45
|
+
useEffect(() => {
|
|
46
|
+
adjustHeight();
|
|
47
|
+
}, [value, adjustHeight]);
|
|
48
|
+
|
|
49
|
+
const handleSubmit = useCallback(
|
|
50
|
+
(e?: React.FormEvent) => {
|
|
51
|
+
e?.preventDefault();
|
|
52
|
+
|
|
53
|
+
const trimmed = value.trim();
|
|
54
|
+
if (!trimmed || disabled || isLoading) return;
|
|
55
|
+
|
|
56
|
+
onSend(trimmed);
|
|
57
|
+
setValue('');
|
|
58
|
+
|
|
59
|
+
// Reset height after sending
|
|
60
|
+
if (textareaRef.current) {
|
|
61
|
+
textareaRef.current.style.height = 'auto';
|
|
62
|
+
}
|
|
63
|
+
textareaRef.current?.focus();
|
|
64
|
+
},
|
|
65
|
+
[value, disabled, isLoading, onSend]
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
const handleKeyDown = useCallback(
|
|
69
|
+
(e: React.KeyboardEvent) => {
|
|
70
|
+
if (e.key === 'Enter' && !e.shiftKey) {
|
|
71
|
+
e.preventDefault();
|
|
72
|
+
handleSubmit();
|
|
73
|
+
}
|
|
74
|
+
},
|
|
75
|
+
[handleSubmit]
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
const canSend = value.trim().length > 0 && !disabled && !isLoading;
|
|
79
|
+
|
|
80
|
+
return (
|
|
81
|
+
<form onSubmit={handleSubmit} className="w-full">
|
|
82
|
+
<div
|
|
83
|
+
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"
|
|
84
|
+
style={{ minHeight: '44px' }}
|
|
85
|
+
>
|
|
86
|
+
{/* Textarea */}
|
|
87
|
+
<textarea
|
|
88
|
+
ref={textareaRef}
|
|
89
|
+
value={value}
|
|
90
|
+
onChange={(e) => setValue(e.target.value)}
|
|
91
|
+
onKeyDown={handleKeyDown}
|
|
92
|
+
placeholder={placeholder}
|
|
93
|
+
disabled={disabled || isLoading}
|
|
94
|
+
rows={1}
|
|
95
|
+
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"
|
|
96
|
+
style={{
|
|
97
|
+
minHeight: '44px',
|
|
98
|
+
maxHeight: `${24 * maxRows + 20}px`,
|
|
99
|
+
lineHeight: '1.5rem',
|
|
100
|
+
}}
|
|
101
|
+
autoComplete="off"
|
|
102
|
+
/>
|
|
103
|
+
|
|
104
|
+
{/* Send Button - positioned inside, bottom right */}
|
|
105
|
+
<div
|
|
106
|
+
className="absolute flex items-center justify-center"
|
|
107
|
+
style={{
|
|
108
|
+
right: '6px',
|
|
109
|
+
bottom: '6px',
|
|
110
|
+
}}
|
|
111
|
+
>
|
|
112
|
+
<Button
|
|
113
|
+
type="submit"
|
|
114
|
+
size="icon"
|
|
115
|
+
disabled={!canSend}
|
|
116
|
+
className="h-8 w-8 rounded-full transition-all"
|
|
117
|
+
style={{
|
|
118
|
+
opacity: canSend ? 1 : 0.5,
|
|
119
|
+
}}
|
|
120
|
+
>
|
|
121
|
+
{isLoading ? (
|
|
122
|
+
<Loader2 className="h-4 w-4 animate-spin" />
|
|
123
|
+
) : (
|
|
124
|
+
<Send className="h-4 w-4" />
|
|
125
|
+
)}
|
|
126
|
+
</Button>
|
|
127
|
+
</div>
|
|
128
|
+
</div>
|
|
129
|
+
|
|
130
|
+
{/* Helper text */}
|
|
131
|
+
<p className="mt-1.5 text-xs text-muted-foreground text-center">
|
|
132
|
+
Press Enter to send, Shift+Enter for new line
|
|
133
|
+
</p>
|
|
134
|
+
</form>
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
);
|
|
138
|
+
|
|
139
|
+
AIMessageInput.displayName = 'AIMessageInput';
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
export { ChatWidget } from './ChatWidget';
|
|
4
|
+
export type { ChatWidgetProps } from './ChatWidget';
|
|
5
|
+
|
|
6
|
+
export { AIChatWidget } from './AIChatWidget';
|
|
7
|
+
export type { AIChatWidgetProps } from './AIChatWidget';
|
|
8
|
+
|
|
9
|
+
export { ChatPanel } from './ChatPanel';
|
|
10
|
+
export type { ChatPanelProps } from './ChatPanel';
|
|
11
|
+
|
|
12
|
+
export { ChatMessages } from './ChatMessages';
|
|
13
|
+
export type { ChatMessagesProps, ChatMessagesHandle } from './ChatMessages';
|
|
14
|
+
|
|
15
|
+
export { MessageBubble } from './MessageBubble';
|
|
16
|
+
export type { MessageBubbleProps } from './MessageBubble';
|
|
17
|
+
|
|
18
|
+
export { AIMessageInput } from './MessageInput';
|
|
19
|
+
export type { AIMessageInputProps } from './MessageInput';
|
|
20
|
+
|
|
21
|
+
export { ChatSidebar } from './ChatSidebar';
|
|
22
|
+
export type { ChatSidebarProps } from './ChatSidebar';
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* McpChat configuration
|
|
3
|
+
* Environment-aware configuration for MCP server endpoints
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// Hosts
|
|
7
|
+
const PROD_HOST = 'https://mcp.djangocfg.com';
|
|
8
|
+
const DEV_HOST = 'http://localhost:3002';
|
|
9
|
+
|
|
10
|
+
// Current host based on environment
|
|
11
|
+
const HOST = process.env.NODE_ENV === 'development' ? DEV_HOST : PROD_HOST;
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* MCP Server endpoints
|
|
15
|
+
*/
|
|
16
|
+
export const mcpEndpoints = {
|
|
17
|
+
/** Base URL */
|
|
18
|
+
baseUrl: HOST,
|
|
19
|
+
/** Chat API endpoint */
|
|
20
|
+
chat: `${HOST}/api/chat`,
|
|
21
|
+
/** Search API endpoint */
|
|
22
|
+
search: `${HOST}/api/search`,
|
|
23
|
+
/** Conversations API endpoint */
|
|
24
|
+
conversations: `${HOST}/api/conversations`,
|
|
25
|
+
/** Health check endpoint */
|
|
26
|
+
health: `${HOST}/health`,
|
|
27
|
+
/** MCP protocol endpoint (Streamable HTTP) */
|
|
28
|
+
mcp: `${HOST}/mcp`,
|
|
29
|
+
/** SSE endpoint for legacy clients */
|
|
30
|
+
sse: `${HOST}/mcp/sse`,
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
// Export defaults for backwards compatibility
|
|
34
|
+
export const DEFAULT_CHAT_API_ENDPOINT = PROD_HOST + '/api/chat';
|
|
35
|
+
export const DEFAULT_MCP_BASE_URL = PROD_HOST;
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import React, { createContext, useContext, useState, useCallback, useMemo, useEffect, type ReactNode } from 'react';
|
|
4
|
+
import { useLocalStorage, useIsMobile } from '@djangocfg/ui/hooks';
|
|
5
|
+
import { useAIChat } from '../hooks/useAIChat';
|
|
6
|
+
import { mcpEndpoints, type AIChatMessage, type ChatWidgetConfig, type ChatDisplayMode } from '../types';
|
|
7
|
+
|
|
8
|
+
const STORAGE_KEY_MODE = 'djangocfg-ai-chat-mode';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* AI Chat context state
|
|
12
|
+
*/
|
|
13
|
+
export interface AIChatContextState {
|
|
14
|
+
/** All chat messages */
|
|
15
|
+
messages: AIChatMessage[];
|
|
16
|
+
/** Whether a request is in progress */
|
|
17
|
+
isLoading: boolean;
|
|
18
|
+
/** Last error if any */
|
|
19
|
+
error: Error | null;
|
|
20
|
+
/** Whether chat panel is open */
|
|
21
|
+
isOpen: boolean;
|
|
22
|
+
/** Whether chat is minimized */
|
|
23
|
+
isMinimized: boolean;
|
|
24
|
+
/** Configuration */
|
|
25
|
+
config: ChatWidgetConfig;
|
|
26
|
+
/** Current display mode */
|
|
27
|
+
displayMode: ChatDisplayMode;
|
|
28
|
+
/** Is on mobile device */
|
|
29
|
+
isMobile: boolean;
|
|
30
|
+
/** Thread ID for conversation */
|
|
31
|
+
threadId: string;
|
|
32
|
+
/** User ID for conversation */
|
|
33
|
+
userId: string;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* AI Chat context actions
|
|
38
|
+
*/
|
|
39
|
+
export interface AIChatContextActions {
|
|
40
|
+
/** Send a message */
|
|
41
|
+
sendMessage: (content: string) => Promise<void>;
|
|
42
|
+
/** Clear all messages */
|
|
43
|
+
clearMessages: () => void;
|
|
44
|
+
/** Open chat panel */
|
|
45
|
+
openChat: () => void;
|
|
46
|
+
/** Close chat panel */
|
|
47
|
+
closeChat: () => void;
|
|
48
|
+
/** Toggle chat panel */
|
|
49
|
+
toggleChat: () => void;
|
|
50
|
+
/** Minimize/restore chat */
|
|
51
|
+
toggleMinimize: () => void;
|
|
52
|
+
/** Set display mode */
|
|
53
|
+
setDisplayMode: (mode: ChatDisplayMode) => void;
|
|
54
|
+
/** Stop streaming response */
|
|
55
|
+
stopStreaming: () => void;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export type AIChatContextValue = AIChatContextState & AIChatContextActions;
|
|
59
|
+
|
|
60
|
+
const AIChatContext = createContext<AIChatContextValue | null>(null);
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* AI Chat provider props
|
|
64
|
+
*/
|
|
65
|
+
export interface AIChatProviderProps {
|
|
66
|
+
children: ReactNode;
|
|
67
|
+
/** API endpoint for AI chat (default: /api/ai/chat) */
|
|
68
|
+
apiEndpoint?: string;
|
|
69
|
+
/** Widget configuration */
|
|
70
|
+
config?: Partial<ChatWidgetConfig>;
|
|
71
|
+
/** Callback on error */
|
|
72
|
+
onError?: (error: Error) => void;
|
|
73
|
+
/** Enable streaming (default: true) */
|
|
74
|
+
enableStreaming?: boolean;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* AI Chat provider component
|
|
79
|
+
* Uses useAIChat hook with server-side persistence
|
|
80
|
+
*/
|
|
81
|
+
export function AIChatProvider({
|
|
82
|
+
children,
|
|
83
|
+
apiEndpoint = mcpEndpoints.chat,
|
|
84
|
+
config: userConfig = {},
|
|
85
|
+
onError,
|
|
86
|
+
enableStreaming = true,
|
|
87
|
+
}: AIChatProviderProps) {
|
|
88
|
+
// Use AI chat hook
|
|
89
|
+
const {
|
|
90
|
+
messages,
|
|
91
|
+
isLoading,
|
|
92
|
+
error,
|
|
93
|
+
threadId,
|
|
94
|
+
userId,
|
|
95
|
+
sendMessage: sendAIMessage,
|
|
96
|
+
clearMessages: clearAIMessages,
|
|
97
|
+
stopStreaming,
|
|
98
|
+
} = useAIChat({
|
|
99
|
+
apiEndpoint,
|
|
100
|
+
onError,
|
|
101
|
+
enableStreaming,
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
const [isMinimized, setIsMinimized] = useState(false);
|
|
105
|
+
|
|
106
|
+
// Display mode with localStorage persistence
|
|
107
|
+
const [storedMode, setStoredMode] = useLocalStorage<ChatDisplayMode>(STORAGE_KEY_MODE, 'closed');
|
|
108
|
+
|
|
109
|
+
const isMobile = useIsMobile();
|
|
110
|
+
|
|
111
|
+
// On mobile, sidebar mode is not available - fallback to floating
|
|
112
|
+
const displayMode: ChatDisplayMode = useMemo(() => {
|
|
113
|
+
if (isMobile && storedMode === 'sidebar') {
|
|
114
|
+
return 'floating';
|
|
115
|
+
}
|
|
116
|
+
return storedMode;
|
|
117
|
+
}, [isMobile, storedMode]);
|
|
118
|
+
|
|
119
|
+
// Derived state: isOpen is true when not in 'closed' mode
|
|
120
|
+
const isOpen = displayMode !== 'closed';
|
|
121
|
+
|
|
122
|
+
const config: ChatWidgetConfig = useMemo(
|
|
123
|
+
() => ({
|
|
124
|
+
apiEndpoint,
|
|
125
|
+
title: 'DjangoCFG AI Assistant',
|
|
126
|
+
placeholder: 'Ask about DjangoCFG...',
|
|
127
|
+
greeting:
|
|
128
|
+
"Hi! I'm your DjangoCFG AI assistant powered by GPT. Ask me anything about configuration, features, or how to use the library.",
|
|
129
|
+
position: 'bottom-right',
|
|
130
|
+
variant: 'default',
|
|
131
|
+
...userConfig,
|
|
132
|
+
}),
|
|
133
|
+
[apiEndpoint, userConfig]
|
|
134
|
+
);
|
|
135
|
+
|
|
136
|
+
const sendMessage = useCallback(
|
|
137
|
+
async (content: string) => {
|
|
138
|
+
await sendAIMessage(content);
|
|
139
|
+
},
|
|
140
|
+
[sendAIMessage]
|
|
141
|
+
);
|
|
142
|
+
|
|
143
|
+
const clearMessages = useCallback(() => {
|
|
144
|
+
clearAIMessages();
|
|
145
|
+
}, [clearAIMessages]);
|
|
146
|
+
|
|
147
|
+
const openChat = useCallback(() => {
|
|
148
|
+
setStoredMode('floating');
|
|
149
|
+
setIsMinimized(false);
|
|
150
|
+
}, [setStoredMode]);
|
|
151
|
+
|
|
152
|
+
const closeChat = useCallback(() => {
|
|
153
|
+
setStoredMode('closed');
|
|
154
|
+
setIsMinimized(false);
|
|
155
|
+
}, [setStoredMode]);
|
|
156
|
+
|
|
157
|
+
const toggleChat = useCallback(() => {
|
|
158
|
+
if (displayMode === 'closed') {
|
|
159
|
+
setStoredMode('floating');
|
|
160
|
+
setIsMinimized(false);
|
|
161
|
+
} else {
|
|
162
|
+
setStoredMode('closed');
|
|
163
|
+
}
|
|
164
|
+
}, [displayMode, setStoredMode]);
|
|
165
|
+
|
|
166
|
+
const toggleMinimize = useCallback(() => {
|
|
167
|
+
setIsMinimized((prev) => !prev);
|
|
168
|
+
}, []);
|
|
169
|
+
|
|
170
|
+
const setDisplayMode = useCallback(
|
|
171
|
+
(mode: ChatDisplayMode) => {
|
|
172
|
+
// On mobile, sidebar is not available
|
|
173
|
+
if (isMobile && mode === 'sidebar') {
|
|
174
|
+
setStoredMode('floating');
|
|
175
|
+
} else {
|
|
176
|
+
setStoredMode(mode);
|
|
177
|
+
}
|
|
178
|
+
setIsMinimized(false);
|
|
179
|
+
},
|
|
180
|
+
[isMobile, setStoredMode]
|
|
181
|
+
);
|
|
182
|
+
|
|
183
|
+
const value = useMemo<AIChatContextValue>(
|
|
184
|
+
() => ({
|
|
185
|
+
messages,
|
|
186
|
+
isLoading,
|
|
187
|
+
error,
|
|
188
|
+
isOpen,
|
|
189
|
+
isMinimized,
|
|
190
|
+
config,
|
|
191
|
+
displayMode,
|
|
192
|
+
isMobile,
|
|
193
|
+
threadId,
|
|
194
|
+
userId,
|
|
195
|
+
sendMessage,
|
|
196
|
+
clearMessages,
|
|
197
|
+
openChat,
|
|
198
|
+
closeChat,
|
|
199
|
+
toggleChat,
|
|
200
|
+
toggleMinimize,
|
|
201
|
+
setDisplayMode,
|
|
202
|
+
stopStreaming,
|
|
203
|
+
}),
|
|
204
|
+
[
|
|
205
|
+
messages,
|
|
206
|
+
isLoading,
|
|
207
|
+
error,
|
|
208
|
+
isOpen,
|
|
209
|
+
isMinimized,
|
|
210
|
+
config,
|
|
211
|
+
displayMode,
|
|
212
|
+
isMobile,
|
|
213
|
+
threadId,
|
|
214
|
+
userId,
|
|
215
|
+
sendMessage,
|
|
216
|
+
clearMessages,
|
|
217
|
+
openChat,
|
|
218
|
+
closeChat,
|
|
219
|
+
toggleChat,
|
|
220
|
+
toggleMinimize,
|
|
221
|
+
setDisplayMode,
|
|
222
|
+
stopStreaming,
|
|
223
|
+
]
|
|
224
|
+
);
|
|
225
|
+
|
|
226
|
+
return <AIChatContext.Provider value={value}>{children}</AIChatContext.Provider>;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Hook to access AI chat context
|
|
231
|
+
*/
|
|
232
|
+
export function useAIChatContext(): AIChatContextValue {
|
|
233
|
+
const context = useContext(AIChatContext);
|
|
234
|
+
if (!context) {
|
|
235
|
+
throw new Error('useAIChatContext must be used within an AIChatProvider');
|
|
236
|
+
}
|
|
237
|
+
return context;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Hook to check if AI chat context is available
|
|
242
|
+
*/
|
|
243
|
+
export function useAIChatContextOptional(): AIChatContextValue | null {
|
|
244
|
+
return useContext(AIChatContext);
|
|
245
|
+
}
|