@djangocfg/layouts 2.1.9 → 2.1.14
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/README.md +53 -161
- package/package.json +6 -6
- package/src/components/RedirectPage/RedirectPage.tsx +1 -1
- package/src/index.ts +0 -6
- package/src/layouts/AppLayout/AppLayout.tsx +1 -1
- package/src/layouts/AppLayout/BaseApp.tsx +1 -1
- package/src/layouts/AuthLayout/AuthContext.tsx +1 -1
- package/src/layouts/AuthLayout/OAuthCallback.tsx +1 -1
- package/src/layouts/AuthLayout/OAuthProviders.tsx +1 -1
- package/src/layouts/PrivateLayout/PrivateLayout.tsx +1 -1
- package/src/layouts/PrivateLayout/components/PrivateHeader.tsx +1 -1
- package/src/layouts/ProfileLayout/components/AvatarSection.tsx +2 -2
- package/src/layouts/PublicLayout/components/PublicMobileDrawer.tsx +1 -1
- package/src/layouts/PublicLayout/components/PublicNavigation.tsx +1 -1
- package/src/layouts/_components/UserMenu.tsx +1 -1
- package/src/layouts/index.ts +0 -2
- package/src/snippets/Analytics/useAnalytics.ts +1 -1
- package/src/snippets/McpChat/hooks/useAIChat.ts +16 -3
- package/src/snippets/index.ts +0 -3
- package/src/auth/README.md +0 -962
- package/src/auth/context/AccountsContext.tsx +0 -240
- package/src/auth/context/AuthContext.tsx +0 -604
- package/src/auth/context/index.ts +0 -4
- package/src/auth/context/types.ts +0 -68
- package/src/auth/hooks/index.ts +0 -17
- package/src/auth/hooks/useAuthForm.ts +0 -332
- package/src/auth/hooks/useAuthGuard.ts +0 -25
- package/src/auth/hooks/useAuthRedirect.ts +0 -51
- package/src/auth/hooks/useAutoAuth.ts +0 -49
- package/src/auth/hooks/useGithubAuth.ts +0 -184
- package/src/auth/hooks/useLocalStorage.ts +0 -214
- package/src/auth/hooks/useProfileCache.ts +0 -146
- package/src/auth/hooks/useSessionStorage.ts +0 -189
- package/src/auth/index.ts +0 -10
- package/src/auth/middlewares/index.ts +0 -1
- package/src/auth/middlewares/proxy.ts +0 -32
- package/src/auth/server.ts +0 -6
- package/src/auth/utils/errors.ts +0 -34
- package/src/auth/utils/index.ts +0 -2
- package/src/auth/utils/validation.ts +0 -14
- package/src/contexts/LeadsContext.tsx +0 -156
- package/src/contexts/NewsletterContext.tsx +0 -263
- package/src/contexts/SupportContext.tsx +0 -256
- package/src/contexts/index.ts +0 -59
- package/src/contexts/knowbase/ChatContext.tsx +0 -174
- package/src/contexts/knowbase/DocumentsContext.tsx +0 -304
- package/src/contexts/knowbase/SessionsContext.tsx +0 -174
- package/src/contexts/knowbase/index.ts +0 -61
- package/src/contexts/payments/BalancesContext.tsx +0 -65
- package/src/contexts/payments/CurrenciesContext.tsx +0 -66
- package/src/contexts/payments/OverviewContext.tsx +0 -174
- package/src/contexts/payments/PaymentsContext.tsx +0 -132
- package/src/contexts/payments/README.md +0 -201
- package/src/contexts/payments/RootPaymentsContext.tsx +0 -68
- package/src/contexts/payments/index.ts +0 -50
- package/src/layouts/PaymentsLayout/PaymentsLayout.tsx +0 -92
- package/src/layouts/PaymentsLayout/components/CreatePaymentDialog.tsx +0 -291
- package/src/layouts/PaymentsLayout/components/PaymentDetailsDialog.tsx +0 -290
- package/src/layouts/PaymentsLayout/components/index.ts +0 -2
- package/src/layouts/PaymentsLayout/events.ts +0 -47
- package/src/layouts/PaymentsLayout/index.ts +0 -16
- package/src/layouts/PaymentsLayout/types.ts +0 -6
- package/src/layouts/PaymentsLayout/views/overview/components/BalanceCard.tsx +0 -128
- package/src/layouts/PaymentsLayout/views/overview/components/RecentPayments.tsx +0 -142
- package/src/layouts/PaymentsLayout/views/overview/components/index.ts +0 -2
- package/src/layouts/PaymentsLayout/views/overview/index.tsx +0 -20
- package/src/layouts/PaymentsLayout/views/payments/components/PaymentsList.tsx +0 -276
- package/src/layouts/PaymentsLayout/views/payments/components/index.ts +0 -1
- package/src/layouts/PaymentsLayout/views/payments/index.tsx +0 -17
- package/src/layouts/PaymentsLayout/views/transactions/components/TransactionsList.tsx +0 -273
- package/src/layouts/PaymentsLayout/views/transactions/components/index.ts +0 -1
- package/src/layouts/PaymentsLayout/views/transactions/index.tsx +0 -17
- package/src/layouts/SupportLayout/README.md +0 -91
- package/src/layouts/SupportLayout/SupportLayout.tsx +0 -179
- package/src/layouts/SupportLayout/components/CreateTicketDialog.tsx +0 -155
- package/src/layouts/SupportLayout/components/MessageInput.tsx +0 -92
- package/src/layouts/SupportLayout/components/MessageList.tsx +0 -314
- package/src/layouts/SupportLayout/components/TicketCard.tsx +0 -96
- package/src/layouts/SupportLayout/components/TicketList.tsx +0 -153
- package/src/layouts/SupportLayout/components/index.ts +0 -6
- package/src/layouts/SupportLayout/context/SupportLayoutContext.tsx +0 -263
- package/src/layouts/SupportLayout/context/index.ts +0 -2
- package/src/layouts/SupportLayout/events.ts +0 -33
- package/src/layouts/SupportLayout/hooks/index.ts +0 -2
- package/src/layouts/SupportLayout/hooks/useInfiniteMessages.ts +0 -119
- package/src/layouts/SupportLayout/hooks/useInfiniteTickets.ts +0 -92
- package/src/layouts/SupportLayout/index.ts +0 -8
- package/src/layouts/SupportLayout/types.ts +0 -21
- package/src/snippets/Chat/ChatUIContext.tsx +0 -110
- package/src/snippets/Chat/ChatWidget.tsx +0 -476
- package/src/snippets/Chat/README.md +0 -122
- package/src/snippets/Chat/components/MessageInput.tsx +0 -124
- package/src/snippets/Chat/components/MessageList.tsx +0 -169
- package/src/snippets/Chat/components/SessionList.tsx +0 -192
- package/src/snippets/Chat/components/index.ts +0 -9
- package/src/snippets/Chat/hooks/index.ts +0 -6
- package/src/snippets/Chat/hooks/useInfiniteSessions.ts +0 -82
- package/src/snippets/Chat/index.tsx +0 -45
- package/src/snippets/Chat/types.ts +0 -80
- package/src/snippets/ContactForm/ContactForm.tsx +0 -346
- package/src/snippets/ContactForm/ContactFormProvider.tsx +0 -153
- package/src/snippets/ContactForm/ContactInfo.tsx +0 -114
- package/src/snippets/ContactForm/ContactPage.tsx +0 -131
- package/src/snippets/ContactForm/dynamic.tsx +0 -55
- package/src/snippets/ContactForm/index.ts +0 -34
- package/src/snippets/ContactForm/types.ts +0 -110
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Support Layout Types
|
|
3
|
-
* Types for SupportLayout - combines API types with UI state
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
// Note: Ticket and Message types are exported from @djangocfg/layouts/contexts
|
|
7
|
-
// Import them directly: import type { Ticket, Message } from '@djangocfg/layouts/contexts';
|
|
8
|
-
|
|
9
|
-
// UI State
|
|
10
|
-
export interface SupportUIState {
|
|
11
|
-
selectedTicketUuid: string | null;
|
|
12
|
-
isCreateDialogOpen: boolean;
|
|
13
|
-
viewMode: 'list' | 'grid';
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
// Form types
|
|
17
|
-
export interface TicketFormData {
|
|
18
|
-
subject: string;
|
|
19
|
-
message: string;
|
|
20
|
-
}
|
|
21
|
-
|
|
@@ -1,110 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Chat UI Context
|
|
3
|
-
* Manages UI state for chat widget (toggle, expand, minimize)
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
'use client';
|
|
7
|
-
|
|
8
|
-
import React, { createContext, useContext, useState, useCallback, type ReactNode } from 'react';
|
|
9
|
-
import type { ChatUIState } from './types';
|
|
10
|
-
|
|
11
|
-
// ─────────────────────────────────────────────────────────────────────────
|
|
12
|
-
// Context Type
|
|
13
|
-
// ─────────────────────────────────────────────────────────────────────────
|
|
14
|
-
|
|
15
|
-
export interface ChatUIContextValue {
|
|
16
|
-
uiState: ChatUIState;
|
|
17
|
-
toggleChat: () => void;
|
|
18
|
-
openChat: () => void;
|
|
19
|
-
closeChat: () => void;
|
|
20
|
-
expandChat: () => void;
|
|
21
|
-
collapseChat: () => void;
|
|
22
|
-
minimizeChat: () => void;
|
|
23
|
-
toggleSources: () => void;
|
|
24
|
-
toggleTimestamps: () => void;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
// ─────────────────────────────────────────────────────────────────────────
|
|
28
|
-
// Context
|
|
29
|
-
// ─────────────────────────────────────────────────────────────────────────
|
|
30
|
-
|
|
31
|
-
const ChatUIContext = createContext<ChatUIContextValue | undefined>(undefined);
|
|
32
|
-
|
|
33
|
-
// ─────────────────────────────────────────────────────────────────────────
|
|
34
|
-
// Provider
|
|
35
|
-
// ─────────────────────────────────────────────────────────────────────────
|
|
36
|
-
|
|
37
|
-
export interface ChatUIProviderProps {
|
|
38
|
-
children: ReactNode;
|
|
39
|
-
initialState?: Partial<ChatUIState>;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
export function ChatUIProvider({ children, initialState }: ChatUIProviderProps) {
|
|
43
|
-
const [uiState, setUIState] = useState<ChatUIState>({
|
|
44
|
-
isOpen: false,
|
|
45
|
-
isExpanded: false,
|
|
46
|
-
isMinimized: false,
|
|
47
|
-
showSources: true,
|
|
48
|
-
showTimestamps: false,
|
|
49
|
-
...initialState,
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
const toggleChat = useCallback(() => {
|
|
53
|
-
setUIState((prev) => ({ ...prev, isOpen: !prev.isOpen }));
|
|
54
|
-
}, []);
|
|
55
|
-
|
|
56
|
-
const openChat = useCallback(() => {
|
|
57
|
-
setUIState((prev) => ({ ...prev, isOpen: true }));
|
|
58
|
-
}, []);
|
|
59
|
-
|
|
60
|
-
const closeChat = useCallback(() => {
|
|
61
|
-
setUIState((prev) => ({ ...prev, isOpen: false }));
|
|
62
|
-
}, []);
|
|
63
|
-
|
|
64
|
-
const expandChat = useCallback(() => {
|
|
65
|
-
setUIState((prev) => ({ ...prev, isExpanded: true, isMinimized: false }));
|
|
66
|
-
}, []);
|
|
67
|
-
|
|
68
|
-
const collapseChat = useCallback(() => {
|
|
69
|
-
setUIState((prev) => ({ ...prev, isExpanded: false }));
|
|
70
|
-
}, []);
|
|
71
|
-
|
|
72
|
-
const minimizeChat = useCallback(() => {
|
|
73
|
-
setUIState((prev) => ({ ...prev, isMinimized: true, isExpanded: false }));
|
|
74
|
-
}, []);
|
|
75
|
-
|
|
76
|
-
const toggleSources = useCallback(() => {
|
|
77
|
-
setUIState((prev) => ({ ...prev, showSources: !prev.showSources }));
|
|
78
|
-
}, []);
|
|
79
|
-
|
|
80
|
-
const toggleTimestamps = useCallback(() => {
|
|
81
|
-
setUIState((prev) => ({ ...prev, showTimestamps: !prev.showTimestamps }));
|
|
82
|
-
}, []);
|
|
83
|
-
|
|
84
|
-
const value: ChatUIContextValue = {
|
|
85
|
-
uiState,
|
|
86
|
-
toggleChat,
|
|
87
|
-
openChat,
|
|
88
|
-
closeChat,
|
|
89
|
-
expandChat,
|
|
90
|
-
collapseChat,
|
|
91
|
-
minimizeChat,
|
|
92
|
-
toggleSources,
|
|
93
|
-
toggleTimestamps,
|
|
94
|
-
};
|
|
95
|
-
|
|
96
|
-
return <ChatUIContext.Provider value={value}>{children}</ChatUIContext.Provider>;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
// ─────────────────────────────────────────────────────────────────────────
|
|
100
|
-
// Hook
|
|
101
|
-
// ─────────────────────────────────────────────────────────────────────────
|
|
102
|
-
|
|
103
|
-
export function useChatUI(): ChatUIContextValue {
|
|
104
|
-
const context = useContext(ChatUIContext);
|
|
105
|
-
if (!context) {
|
|
106
|
-
throw new Error('useChatUI must be used within ChatUIProvider');
|
|
107
|
-
}
|
|
108
|
-
return context;
|
|
109
|
-
}
|
|
110
|
-
|
|
@@ -1,476 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Chat Widget Component
|
|
3
|
-
* RAG-powered chat interface with session management
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
'use client';
|
|
7
|
-
|
|
8
|
-
import React, { useCallback, useEffect, useState } from 'react';
|
|
9
|
-
import { createPortal } from 'react-dom';
|
|
10
|
-
import { usePathname } from 'next/navigation';
|
|
11
|
-
import { Card, CardContent, CardHeader, Button, useIsMobile, useLocalStorage } from '@djangocfg/ui-nextjs';
|
|
12
|
-
import {
|
|
13
|
-
MessageSquare,
|
|
14
|
-
X,
|
|
15
|
-
Maximize2,
|
|
16
|
-
Minimize2,
|
|
17
|
-
Plus,
|
|
18
|
-
List,
|
|
19
|
-
} from 'lucide-react';
|
|
20
|
-
import { useKnowbaseChatContext, useKnowbaseSessionsContext, type ChatSource } from '@djangocfg/layouts/contexts';
|
|
21
|
-
import { Enums } from '@djangocfg/api/cfg/generated';
|
|
22
|
-
import { chatLogger } from '../../utils/logger';
|
|
23
|
-
import { useChatUI } from './ChatUIContext';
|
|
24
|
-
import { MessageList, MessageInput, SessionList } from './components';
|
|
25
|
-
import type { ChatWidgetProps, ChatMessageWithSources } from './types';
|
|
26
|
-
|
|
27
|
-
const WIDGET_MAX_WIDTH = 800;
|
|
28
|
-
const WIDGET_MAX_HEIGHT = 900;
|
|
29
|
-
|
|
30
|
-
// ─────────────────────────────────────────────────────────────────────────
|
|
31
|
-
// Chat Widget Component
|
|
32
|
-
// ─────────────────────────────────────────────────────────────────────────
|
|
33
|
-
|
|
34
|
-
export const ChatWidget: React.FC<ChatWidgetProps> = ({
|
|
35
|
-
autoOpen = false,
|
|
36
|
-
persistent = true,
|
|
37
|
-
className = '',
|
|
38
|
-
onToggle,
|
|
39
|
-
onMessage,
|
|
40
|
-
}) => {
|
|
41
|
-
const { sendQuery, getChatHistory } = useKnowbaseChatContext();
|
|
42
|
-
const { createSession, getSession } = useKnowbaseSessionsContext();
|
|
43
|
-
const {
|
|
44
|
-
uiState,
|
|
45
|
-
toggleChat,
|
|
46
|
-
expandChat,
|
|
47
|
-
collapseChat,
|
|
48
|
-
toggleSources,
|
|
49
|
-
toggleTimestamps,
|
|
50
|
-
} = useChatUI();
|
|
51
|
-
|
|
52
|
-
const pathname = usePathname();
|
|
53
|
-
const isMobile = useIsMobile();
|
|
54
|
-
|
|
55
|
-
const [isLoading, setIsLoading] = useState(false);
|
|
56
|
-
const [showSessions, setShowSessions] = useState(false);
|
|
57
|
-
const [mounted, setMounted] = useState(false);
|
|
58
|
-
|
|
59
|
-
// Persist session ID and messages in localStorage
|
|
60
|
-
const [currentSessionId, setCurrentSessionId] = useLocalStorage<string | null>(
|
|
61
|
-
'chat_session_id',
|
|
62
|
-
null
|
|
63
|
-
);
|
|
64
|
-
const [messages, setMessages] = useLocalStorage<ChatMessageWithSources[]>(
|
|
65
|
-
'chat_messages',
|
|
66
|
-
[]
|
|
67
|
-
);
|
|
68
|
-
|
|
69
|
-
const isSupport = pathname.startsWith('/private/support');
|
|
70
|
-
const isContact = pathname.startsWith('/private/contact');
|
|
71
|
-
|
|
72
|
-
// Mount portal target
|
|
73
|
-
useEffect(() => {
|
|
74
|
-
setMounted(true);
|
|
75
|
-
}, []);
|
|
76
|
-
|
|
77
|
-
// if isSupport or isContact, don't render
|
|
78
|
-
useEffect(() => {
|
|
79
|
-
if (isSupport || isContact) {
|
|
80
|
-
setMounted(false);
|
|
81
|
-
} else {
|
|
82
|
-
setMounted(true);
|
|
83
|
-
}
|
|
84
|
-
}, [isSupport, isContact]);
|
|
85
|
-
|
|
86
|
-
// Auto-open on mount if specified
|
|
87
|
-
useEffect(() => {
|
|
88
|
-
if (autoOpen && !uiState.isOpen) {
|
|
89
|
-
toggleChat();
|
|
90
|
-
}
|
|
91
|
-
}, [autoOpen, uiState.isOpen, toggleChat]);
|
|
92
|
-
|
|
93
|
-
// Notify parent of toggle changes
|
|
94
|
-
useEffect(() => {
|
|
95
|
-
if (onToggle) {
|
|
96
|
-
onToggle(uiState.isOpen);
|
|
97
|
-
}
|
|
98
|
-
}, [uiState.isOpen, onToggle]);
|
|
99
|
-
|
|
100
|
-
// Load chat history when session changes (only if it's different from what we had)
|
|
101
|
-
useEffect(() => {
|
|
102
|
-
if (currentSessionId) {
|
|
103
|
-
// Only load from server if we don't have messages locally or session changed
|
|
104
|
-
if (messages.length === 0) {
|
|
105
|
-
loadChatHistory(currentSessionId);
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
}, [currentSessionId]);
|
|
109
|
-
|
|
110
|
-
// Load chat history
|
|
111
|
-
const loadChatHistory = async (sessionId: string) => {
|
|
112
|
-
try {
|
|
113
|
-
const history = await getChatHistory(sessionId);
|
|
114
|
-
if (history?.messages) {
|
|
115
|
-
// Convert ChatMessage[] to ChatMessageWithSources[]
|
|
116
|
-
const messagesWithSources: ChatMessageWithSources[] = history.messages.map(msg => ({
|
|
117
|
-
...msg,
|
|
118
|
-
sources: undefined, // Sources come from ChatResponse, not ChatMessage
|
|
119
|
-
}));
|
|
120
|
-
setMessages(messagesWithSources);
|
|
121
|
-
}
|
|
122
|
-
} catch (error) {
|
|
123
|
-
chatLogger.error('Failed to load chat history:', error);
|
|
124
|
-
// If we can't load the session, clear it
|
|
125
|
-
setCurrentSessionId(null);
|
|
126
|
-
setMessages([]);
|
|
127
|
-
}
|
|
128
|
-
};
|
|
129
|
-
|
|
130
|
-
// Handle message sending
|
|
131
|
-
const handleSendMessage = useCallback(
|
|
132
|
-
async (message: string) => {
|
|
133
|
-
try {
|
|
134
|
-
setIsLoading(true);
|
|
135
|
-
|
|
136
|
-
// Create session if we don't have one
|
|
137
|
-
let sessionId = currentSessionId;
|
|
138
|
-
if (!sessionId) {
|
|
139
|
-
try {
|
|
140
|
-
const newSession = await createSession({
|
|
141
|
-
title: message.substring(0, 50), // Use first 50 chars as title
|
|
142
|
-
});
|
|
143
|
-
sessionId = newSession.id;
|
|
144
|
-
setCurrentSessionId(sessionId);
|
|
145
|
-
chatLogger.info('Created new chat session:', sessionId);
|
|
146
|
-
} catch (error) {
|
|
147
|
-
chatLogger.error('Failed to create session:', error);
|
|
148
|
-
// Continue without session if creation fails
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
// Add user message to UI
|
|
153
|
-
const userMessage: ChatMessageWithSources = {
|
|
154
|
-
id: `temp-${Date.now()}`,
|
|
155
|
-
role: Enums.ChatMessageRole.USER,
|
|
156
|
-
content: message,
|
|
157
|
-
cost_usd: 0,
|
|
158
|
-
created_at: new Date().toISOString(),
|
|
159
|
-
};
|
|
160
|
-
// Don't use functional form with useLocalStorage - use direct value
|
|
161
|
-
const updatedMessages = [...messages, userMessage];
|
|
162
|
-
setMessages(updatedMessages);
|
|
163
|
-
|
|
164
|
-
// Send query to backend with session ID
|
|
165
|
-
const response = await sendQuery({
|
|
166
|
-
query: message,
|
|
167
|
-
session_id: sessionId || undefined,
|
|
168
|
-
});
|
|
169
|
-
|
|
170
|
-
// Add assistant response to UI
|
|
171
|
-
if (response) {
|
|
172
|
-
const assistantMessage: ChatMessageWithSources = {
|
|
173
|
-
id: response.message_id,
|
|
174
|
-
role: Enums.ChatMessageRole.ASSISTANT,
|
|
175
|
-
content: response.content,
|
|
176
|
-
cost_usd: response.cost_usd,
|
|
177
|
-
tokens_used: response.tokens_used,
|
|
178
|
-
processing_time_ms: response.processing_time_ms,
|
|
179
|
-
created_at: new Date().toISOString(),
|
|
180
|
-
sources: response.sources || undefined,
|
|
181
|
-
};
|
|
182
|
-
// Don't use functional form with useLocalStorage - use direct value
|
|
183
|
-
const finalMessages = [...updatedMessages, assistantMessage];
|
|
184
|
-
setMessages(finalMessages);
|
|
185
|
-
|
|
186
|
-
if (onMessage) {
|
|
187
|
-
onMessage(assistantMessage);
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
} catch (error) {
|
|
191
|
-
chatLogger.error('Failed to send message:', error);
|
|
192
|
-
throw error;
|
|
193
|
-
} finally {
|
|
194
|
-
setIsLoading(false);
|
|
195
|
-
}
|
|
196
|
-
},
|
|
197
|
-
[sendQuery, currentSessionId, createSession, setCurrentSessionId, messages, setMessages, onMessage]
|
|
198
|
-
);
|
|
199
|
-
|
|
200
|
-
// Handle new chat creation
|
|
201
|
-
const handleNewChat = useCallback(() => {
|
|
202
|
-
setMessages([]);
|
|
203
|
-
setCurrentSessionId(null);
|
|
204
|
-
}, [setMessages, setCurrentSessionId]);
|
|
205
|
-
|
|
206
|
-
// Handle session selection
|
|
207
|
-
const handleSelectSession = useCallback((sessionId: string) => {
|
|
208
|
-
// Clear current messages before loading new session
|
|
209
|
-
setMessages([]);
|
|
210
|
-
setCurrentSessionId(sessionId);
|
|
211
|
-
}, [setMessages, setCurrentSessionId]);
|
|
212
|
-
|
|
213
|
-
// Don't render if not mounted (SSR safety)
|
|
214
|
-
if (!mounted) {
|
|
215
|
-
return null;
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
// Don't render if not open and not persistent
|
|
219
|
-
if (!uiState.isOpen && !persistent) {
|
|
220
|
-
return null;
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
// Mobile layout
|
|
224
|
-
if (isMobile) {
|
|
225
|
-
const mobileContent = (
|
|
226
|
-
<>
|
|
227
|
-
<div
|
|
228
|
-
className={`fixed inset-0 z-9999 bg-background ${
|
|
229
|
-
uiState.isOpen ? 'block' : 'hidden'
|
|
230
|
-
} ${className}`}
|
|
231
|
-
>
|
|
232
|
-
<div className="flex flex-col h-full">
|
|
233
|
-
{/* Mobile header */}
|
|
234
|
-
<div className="flex items-center justify-between p-4 border-b bg-background shadow-sm">
|
|
235
|
-
<div className="flex items-center gap-2">
|
|
236
|
-
<MessageSquare className="h-5 w-5 text-primary" />
|
|
237
|
-
<h2 className="font-semibold text-foreground">Knowledge Assistant</h2>
|
|
238
|
-
</div>
|
|
239
|
-
|
|
240
|
-
<div className="flex items-center gap-2">
|
|
241
|
-
<Button
|
|
242
|
-
variant="ghost"
|
|
243
|
-
size="sm"
|
|
244
|
-
onClick={() => setShowSessions(true)}
|
|
245
|
-
className="text-muted-foreground hover:text-foreground"
|
|
246
|
-
title="Sessions"
|
|
247
|
-
>
|
|
248
|
-
<List className="h-5 w-5" />
|
|
249
|
-
</Button>
|
|
250
|
-
|
|
251
|
-
<Button
|
|
252
|
-
variant="ghost"
|
|
253
|
-
size="sm"
|
|
254
|
-
onClick={handleNewChat}
|
|
255
|
-
className="text-muted-foreground hover:text-foreground"
|
|
256
|
-
title="New Chat"
|
|
257
|
-
>
|
|
258
|
-
<Plus className="h-5 w-5" />
|
|
259
|
-
</Button>
|
|
260
|
-
|
|
261
|
-
<Button
|
|
262
|
-
variant="ghost"
|
|
263
|
-
size="sm"
|
|
264
|
-
onClick={toggleChat}
|
|
265
|
-
className="text-muted-foreground hover:text-foreground"
|
|
266
|
-
>
|
|
267
|
-
<X className="h-5 w-5" />
|
|
268
|
-
</Button>
|
|
269
|
-
</div>
|
|
270
|
-
</div>
|
|
271
|
-
|
|
272
|
-
{/* Chat area */}
|
|
273
|
-
<div className="flex-1 flex flex-col overflow-hidden">
|
|
274
|
-
<MessageList
|
|
275
|
-
messages={messages}
|
|
276
|
-
isLoading={isLoading}
|
|
277
|
-
showSources={uiState.showSources}
|
|
278
|
-
showTimestamps={uiState.showTimestamps}
|
|
279
|
-
autoScroll={true}
|
|
280
|
-
className="flex-1"
|
|
281
|
-
/>
|
|
282
|
-
|
|
283
|
-
<MessageInput
|
|
284
|
-
onSend={handleSendMessage}
|
|
285
|
-
isLoading={isLoading}
|
|
286
|
-
placeholder="Ask me anything..."
|
|
287
|
-
/>
|
|
288
|
-
</div>
|
|
289
|
-
</div>
|
|
290
|
-
</div>
|
|
291
|
-
|
|
292
|
-
{/* Session List Drawer */}
|
|
293
|
-
<SessionList
|
|
294
|
-
isOpen={showSessions}
|
|
295
|
-
onClose={() => setShowSessions(false)}
|
|
296
|
-
onSelectSession={handleSelectSession}
|
|
297
|
-
/>
|
|
298
|
-
</>
|
|
299
|
-
);
|
|
300
|
-
|
|
301
|
-
return createPortal(mobileContent, document.body);
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
// Desktop layout
|
|
305
|
-
const widgetWidth = uiState.isExpanded ? WIDGET_MAX_WIDTH : 400;
|
|
306
|
-
const widgetHeight = uiState.isExpanded ? WIDGET_MAX_HEIGHT : 600;
|
|
307
|
-
|
|
308
|
-
// When expanded, add top margin; when collapsed, stick to bottom
|
|
309
|
-
const widgetStyle = uiState.isExpanded
|
|
310
|
-
? {
|
|
311
|
-
position: 'fixed' as const,
|
|
312
|
-
top: '24px',
|
|
313
|
-
bottom: '24px',
|
|
314
|
-
right: '24px',
|
|
315
|
-
left: 'auto',
|
|
316
|
-
width: widgetWidth,
|
|
317
|
-
height: 'auto',
|
|
318
|
-
zIndex: 40,
|
|
319
|
-
transformOrigin: 'bottom right' as const,
|
|
320
|
-
}
|
|
321
|
-
: {
|
|
322
|
-
position: 'fixed' as const,
|
|
323
|
-
bottom: '24px',
|
|
324
|
-
right: '24px',
|
|
325
|
-
width: widgetWidth,
|
|
326
|
-
height: widgetHeight,
|
|
327
|
-
zIndex: 40,
|
|
328
|
-
transformOrigin: 'bottom right' as const,
|
|
329
|
-
};
|
|
330
|
-
|
|
331
|
-
const content = (
|
|
332
|
-
<>
|
|
333
|
-
{/* Chat Window */}
|
|
334
|
-
{uiState.isOpen && (
|
|
335
|
-
<div
|
|
336
|
-
className="fixed z-40
|
|
337
|
-
animate-in fade-in zoom-in-95 slide-in-from-bottom-8
|
|
338
|
-
duration-500 ease-out"
|
|
339
|
-
style={widgetStyle}
|
|
340
|
-
>
|
|
341
|
-
<Card className="h-full flex flex-col shadow-2xl border-2 border-primary/20">
|
|
342
|
-
{/* Header */}
|
|
343
|
-
<CardHeader className="flex-row items-center justify-between space-y-0 pb-3 border-b">
|
|
344
|
-
<div className="flex items-center gap-2">
|
|
345
|
-
<MessageSquare className="h-5 w-5 text-primary" />
|
|
346
|
-
<h3 className="font-semibold text-foreground">Support</h3>
|
|
347
|
-
</div>
|
|
348
|
-
|
|
349
|
-
<div className="flex items-center gap-1">
|
|
350
|
-
<Button
|
|
351
|
-
variant="ghost"
|
|
352
|
-
size="sm"
|
|
353
|
-
onClick={() => setShowSessions(true)}
|
|
354
|
-
className="h-8 w-8 p-0"
|
|
355
|
-
title="Sessions"
|
|
356
|
-
>
|
|
357
|
-
<List className="h-4 w-4" />
|
|
358
|
-
</Button>
|
|
359
|
-
|
|
360
|
-
<Button
|
|
361
|
-
variant="ghost"
|
|
362
|
-
size="sm"
|
|
363
|
-
onClick={handleNewChat}
|
|
364
|
-
className="h-8 w-8 p-0"
|
|
365
|
-
title="New Chat"
|
|
366
|
-
>
|
|
367
|
-
<Plus className="h-4 w-4" />
|
|
368
|
-
</Button>
|
|
369
|
-
|
|
370
|
-
<Button
|
|
371
|
-
variant="ghost"
|
|
372
|
-
size="sm"
|
|
373
|
-
onClick={uiState.isExpanded ? collapseChat : expandChat}
|
|
374
|
-
className="h-8 w-8 p-0"
|
|
375
|
-
title={uiState.isExpanded ? 'Collapse' : 'Expand'}
|
|
376
|
-
>
|
|
377
|
-
{uiState.isExpanded ? (
|
|
378
|
-
<Minimize2 className="h-4 w-4" />
|
|
379
|
-
) : (
|
|
380
|
-
<Maximize2 className="h-4 w-4" />
|
|
381
|
-
)}
|
|
382
|
-
</Button>
|
|
383
|
-
|
|
384
|
-
<Button
|
|
385
|
-
variant="ghost"
|
|
386
|
-
size="sm"
|
|
387
|
-
onClick={toggleChat}
|
|
388
|
-
className="h-8 w-8 p-0"
|
|
389
|
-
title="Close"
|
|
390
|
-
>
|
|
391
|
-
<X className="h-4 w-4" />
|
|
392
|
-
</Button>
|
|
393
|
-
</div>
|
|
394
|
-
</CardHeader>
|
|
395
|
-
|
|
396
|
-
{/* Content */}
|
|
397
|
-
<CardContent className="flex-1 flex flex-col p-0 overflow-hidden">
|
|
398
|
-
<MessageList
|
|
399
|
-
messages={messages}
|
|
400
|
-
isLoading={isLoading}
|
|
401
|
-
showSources={uiState.showSources}
|
|
402
|
-
showTimestamps={uiState.showTimestamps}
|
|
403
|
-
autoScroll={true}
|
|
404
|
-
className="flex-1"
|
|
405
|
-
/>
|
|
406
|
-
|
|
407
|
-
<MessageInput
|
|
408
|
-
onSend={handleSendMessage}
|
|
409
|
-
isLoading={isLoading}
|
|
410
|
-
placeholder="Ask me anything..."
|
|
411
|
-
/>
|
|
412
|
-
</CardContent>
|
|
413
|
-
</Card>
|
|
414
|
-
</div>
|
|
415
|
-
)}
|
|
416
|
-
|
|
417
|
-
{/* Floating Action Button (when closed) */}
|
|
418
|
-
{!uiState.isOpen && persistent && (
|
|
419
|
-
<div
|
|
420
|
-
className="fixed z-40 animate-in fade-in zoom-in-95 slide-in-from-bottom-8 duration-500"
|
|
421
|
-
style={{
|
|
422
|
-
bottom: '24px',
|
|
423
|
-
right: '24px',
|
|
424
|
-
zIndex: 40,
|
|
425
|
-
width: '64px',
|
|
426
|
-
height: '64px',
|
|
427
|
-
borderRadius: '50%',
|
|
428
|
-
animation: 'pulse-shadow 2s cubic-bezier(0.4, 0, 0.6, 1) infinite',
|
|
429
|
-
}}
|
|
430
|
-
>
|
|
431
|
-
<style>{`
|
|
432
|
-
@keyframes pulse-shadow {
|
|
433
|
-
0%, 100% {
|
|
434
|
-
box-shadow: 0 0 0 0 hsl(var(--primary) / 0.7);
|
|
435
|
-
}
|
|
436
|
-
50% {
|
|
437
|
-
box-shadow: 0 0 0 20px hsl(var(--primary) / 0);
|
|
438
|
-
}
|
|
439
|
-
}
|
|
440
|
-
`}</style>
|
|
441
|
-
<Button
|
|
442
|
-
onClick={toggleChat}
|
|
443
|
-
className="w-full h-full rounded-full shadow-2xl
|
|
444
|
-
hover:scale-110 hover:rotate-12 active:scale-95
|
|
445
|
-
transition-all duration-300 ease-out
|
|
446
|
-
flex items-center justify-center
|
|
447
|
-
group"
|
|
448
|
-
style={{
|
|
449
|
-
backgroundColor: 'hsl(var(--primary))',
|
|
450
|
-
borderRadius: '50%',
|
|
451
|
-
padding: 0,
|
|
452
|
-
}}
|
|
453
|
-
title="Open Support Chat"
|
|
454
|
-
aria-label="Open Support Chat"
|
|
455
|
-
>
|
|
456
|
-
<MessageSquare
|
|
457
|
-
className="h-7 w-7 text-primary-foreground group-hover:scale-110 transition-transform duration-300"
|
|
458
|
-
strokeWidth={2}
|
|
459
|
-
/>
|
|
460
|
-
</Button>
|
|
461
|
-
</div>
|
|
462
|
-
)}
|
|
463
|
-
|
|
464
|
-
{/* Session List Drawer */}
|
|
465
|
-
<SessionList
|
|
466
|
-
isOpen={showSessions}
|
|
467
|
-
onClose={() => setShowSessions(false)}
|
|
468
|
-
onSelectSession={handleSelectSession}
|
|
469
|
-
/>
|
|
470
|
-
</>
|
|
471
|
-
);
|
|
472
|
-
|
|
473
|
-
// Render to portal
|
|
474
|
-
return createPortal(content, document.body);
|
|
475
|
-
};
|
|
476
|
-
|