@djangocfg/ext-knowbase 1.0.0
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 +177 -0
- package/dist/chunk-GNRC54ON.js +4308 -0
- package/dist/hooks.cjs +6003 -0
- package/dist/hooks.d.cts +610 -0
- package/dist/hooks.d.ts +610 -0
- package/dist/hooks.js +1486 -0
- package/dist/index.cjs +4450 -0
- package/dist/index.d.cts +4398 -0
- package/dist/index.d.ts +4398 -0
- package/dist/index.js +1 -0
- package/package.json +81 -0
- package/src/api/generated/ext_knowbase/_utils/fetchers/ext_knowbase__knowbase.ts +2983 -0
- package/src/api/generated/ext_knowbase/_utils/fetchers/index.ts +28 -0
- package/src/api/generated/ext_knowbase/_utils/hooks/ext_knowbase__knowbase.ts +999 -0
- package/src/api/generated/ext_knowbase/_utils/hooks/index.ts +28 -0
- package/src/api/generated/ext_knowbase/_utils/schemas/ArchiveItem.schema.ts +33 -0
- package/src/api/generated/ext_knowbase/_utils/schemas/ArchiveItemChunk.schema.ts +29 -0
- package/src/api/generated/ext_knowbase/_utils/schemas/ArchiveItemChunkDetail.schema.ts +30 -0
- package/src/api/generated/ext_knowbase/_utils/schemas/ArchiveItemChunkRequest.schema.ts +22 -0
- package/src/api/generated/ext_knowbase/_utils/schemas/ArchiveItemDetail.schema.ts +35 -0
- package/src/api/generated/ext_knowbase/_utils/schemas/ArchiveItemRequest.schema.ts +22 -0
- package/src/api/generated/ext_knowbase/_utils/schemas/ArchiveProcessingResult.schema.ts +26 -0
- package/src/api/generated/ext_knowbase/_utils/schemas/ArchiveSearchRequestRequest.schema.ts +25 -0
- package/src/api/generated/ext_knowbase/_utils/schemas/ArchiveSearchResult.schema.ts +24 -0
- package/src/api/generated/ext_knowbase/_utils/schemas/ArchiveStatistics.schema.ts +28 -0
- package/src/api/generated/ext_knowbase/_utils/schemas/ChatHistory.schema.ts +22 -0
- package/src/api/generated/ext_knowbase/_utils/schemas/ChatMessage.schema.ts +27 -0
- package/src/api/generated/ext_knowbase/_utils/schemas/ChatQueryRequest.schema.ts +22 -0
- package/src/api/generated/ext_knowbase/_utils/schemas/ChatResponse.schema.ts +26 -0
- package/src/api/generated/ext_knowbase/_utils/schemas/ChatResponseRequest.schema.ts +26 -0
- package/src/api/generated/ext_knowbase/_utils/schemas/ChatSession.schema.ts +29 -0
- package/src/api/generated/ext_knowbase/_utils/schemas/ChatSessionCreateRequest.schema.ts +22 -0
- package/src/api/generated/ext_knowbase/_utils/schemas/ChatSessionRequest.schema.ts +25 -0
- package/src/api/generated/ext_knowbase/_utils/schemas/ChatSource.schema.ts +21 -0
- package/src/api/generated/ext_knowbase/_utils/schemas/ChatSourceRequest.schema.ts +21 -0
- package/src/api/generated/ext_knowbase/_utils/schemas/ChunkRevectorizationRequestRequest.schema.ts +20 -0
- package/src/api/generated/ext_knowbase/_utils/schemas/Document.schema.ts +32 -0
- package/src/api/generated/ext_knowbase/_utils/schemas/DocumentArchive.schema.ts +44 -0
- package/src/api/generated/ext_knowbase/_utils/schemas/DocumentArchiveDetail.schema.ts +48 -0
- package/src/api/generated/ext_knowbase/_utils/schemas/DocumentArchiveList.schema.ts +35 -0
- package/src/api/generated/ext_knowbase/_utils/schemas/DocumentArchiveRequest.schema.ts +21 -0
- package/src/api/generated/ext_knowbase/_utils/schemas/DocumentCategory.schema.ts +23 -0
- package/src/api/generated/ext_knowbase/_utils/schemas/DocumentCategoryRequest.schema.ts +21 -0
- package/src/api/generated/ext_knowbase/_utils/schemas/DocumentCreateRequest.schema.ts +22 -0
- package/src/api/generated/ext_knowbase/_utils/schemas/DocumentProcessingStatus.schema.ts +23 -0
- package/src/api/generated/ext_knowbase/_utils/schemas/DocumentRequest.schema.ts +22 -0
- package/src/api/generated/ext_knowbase/_utils/schemas/DocumentStats.schema.ts +25 -0
- package/src/api/generated/ext_knowbase/_utils/schemas/PaginatedArchiveItemChunkList.schema.ts +24 -0
- package/src/api/generated/ext_knowbase/_utils/schemas/PaginatedArchiveItemList.schema.ts +24 -0
- package/src/api/generated/ext_knowbase/_utils/schemas/PaginatedArchiveSearchResultList.schema.ts +24 -0
- package/src/api/generated/ext_knowbase/_utils/schemas/PaginatedChatResponseList.schema.ts +24 -0
- package/src/api/generated/ext_knowbase/_utils/schemas/PaginatedChatSessionList.schema.ts +24 -0
- package/src/api/generated/ext_knowbase/_utils/schemas/PaginatedDocumentArchiveListList.schema.ts +24 -0
- package/src/api/generated/ext_knowbase/_utils/schemas/PaginatedDocumentList.schema.ts +24 -0
- package/src/api/generated/ext_knowbase/_utils/schemas/PaginatedPublicCategoryList.schema.ts +24 -0
- package/src/api/generated/ext_knowbase/_utils/schemas/PaginatedPublicDocumentListList.schema.ts +24 -0
- package/src/api/generated/ext_knowbase/_utils/schemas/PatchedArchiveItemChunkRequest.schema.ts +22 -0
- package/src/api/generated/ext_knowbase/_utils/schemas/PatchedArchiveItemRequest.schema.ts +22 -0
- package/src/api/generated/ext_knowbase/_utils/schemas/PatchedChatResponseRequest.schema.ts +26 -0
- package/src/api/generated/ext_knowbase/_utils/schemas/PatchedChatSessionRequest.schema.ts +25 -0
- package/src/api/generated/ext_knowbase/_utils/schemas/PatchedDocumentArchiveRequest.schema.ts +21 -0
- package/src/api/generated/ext_knowbase/_utils/schemas/PatchedDocumentRequest.schema.ts +22 -0
- package/src/api/generated/ext_knowbase/_utils/schemas/PublicCategory.schema.ts +21 -0
- package/src/api/generated/ext_knowbase/_utils/schemas/PublicDocument.schema.ts +25 -0
- package/src/api/generated/ext_knowbase/_utils/schemas/PublicDocumentList.schema.ts +24 -0
- package/src/api/generated/ext_knowbase/_utils/schemas/VectorizationResult.schema.ts +24 -0
- package/src/api/generated/ext_knowbase/_utils/schemas/VectorizationStatistics.schema.ts +26 -0
- package/src/api/generated/ext_knowbase/_utils/schemas/index.ts +70 -0
- package/src/api/generated/ext_knowbase/api-instance.ts +131 -0
- package/src/api/generated/ext_knowbase/client.ts +301 -0
- package/src/api/generated/ext_knowbase/enums.ts +241 -0
- package/src/api/generated/ext_knowbase/errors.ts +116 -0
- package/src/api/generated/ext_knowbase/ext_knowbase__knowbase/client.ts +666 -0
- package/src/api/generated/ext_knowbase/ext_knowbase__knowbase/index.ts +2 -0
- package/src/api/generated/ext_knowbase/ext_knowbase__knowbase/models.ts +1120 -0
- package/src/api/generated/ext_knowbase/http.ts +103 -0
- package/src/api/generated/ext_knowbase/index.ts +273 -0
- package/src/api/generated/ext_knowbase/logger.ts +259 -0
- package/src/api/generated/ext_knowbase/retry.ts +175 -0
- package/src/api/generated/ext_knowbase/schema.json +5865 -0
- package/src/api/generated/ext_knowbase/storage.ts +161 -0
- package/src/api/generated/ext_knowbase/validation-events.ts +133 -0
- package/src/api/index.ts +9 -0
- package/src/components/Chat/ChatUIContext.tsx +110 -0
- package/src/components/Chat/ChatWidget.tsx +476 -0
- package/src/components/Chat/README.md +122 -0
- package/src/components/Chat/components/MessageInput.tsx +124 -0
- package/src/components/Chat/components/MessageList.tsx +169 -0
- package/src/components/Chat/components/SessionList.tsx +192 -0
- package/src/components/Chat/components/index.ts +9 -0
- package/src/components/Chat/hooks/index.ts +6 -0
- package/src/components/Chat/hooks/useInfiniteSessions.ts +81 -0
- package/src/components/Chat/index.tsx +45 -0
- package/src/components/Chat/types.ts +81 -0
- package/src/config.ts +20 -0
- package/src/contexts/knowbase/ChatContext.tsx +173 -0
- package/src/contexts/knowbase/DocumentsContext.tsx +306 -0
- package/src/contexts/knowbase/KnowbaseProvider.tsx +47 -0
- package/src/contexts/knowbase/SessionsContext.tsx +175 -0
- package/src/contexts/knowbase/index.ts +63 -0
- package/src/contexts/knowbase/types.ts +69 -0
- package/src/hooks/index.ts +28 -0
- package/src/index.ts +22 -0
- package/src/utils/logger.ts +10 -0
|
@@ -0,0 +1,476 @@
|
|
|
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 } from '../../contexts/knowbase';
|
|
21
|
+
import { Enums } from './types';
|
|
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
|
+
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
# Knowledge Chat Widget
|
|
2
|
+
|
|
3
|
+
RAG-powered chat widget with beautiful animations powered by `tailwindcss-animate`.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- 🎨 **Beautiful Animations**: Smooth transitions and micro-interactions
|
|
8
|
+
- 💬 **Real-time Chat**: RAG-powered responses with sources
|
|
9
|
+
- 📱 **Responsive**: Works on desktop and mobile
|
|
10
|
+
- 🗂️ **Session Management**: Create and manage chat sessions
|
|
11
|
+
- 🎯 **Context-aware**: Uses SWR hooks for data management
|
|
12
|
+
|
|
13
|
+
## Animations
|
|
14
|
+
|
|
15
|
+
### Widget Animations
|
|
16
|
+
- **Entry**: Fade in + slide from bottom with scale
|
|
17
|
+
- **Exit**: Fade out + slide to bottom with scale reduction
|
|
18
|
+
- **FAB Button**: Fade in + slide from bottom, hover scale, active scale
|
|
19
|
+
|
|
20
|
+
### Message Animations
|
|
21
|
+
- **Message Entry**: Staggered fade in + slide from bottom (50ms delay between messages)
|
|
22
|
+
- **Message Bubble**: Hover shadow effect with smooth transition
|
|
23
|
+
- **Loading State**: Pulsing avatar with bouncing bot icon
|
|
24
|
+
|
|
25
|
+
### Sources Animations
|
|
26
|
+
- **Container**: Fade in + slide from left with 100ms delay
|
|
27
|
+
- **Individual Badges**: Staggered zoom in (100ms per badge)
|
|
28
|
+
- **Badge Hover**: Scale up + active scale down
|
|
29
|
+
|
|
30
|
+
### Session List Animations
|
|
31
|
+
- **Session Items**: Staggered fade in + slide from left (50ms delay)
|
|
32
|
+
- **Hover State**: Border color + shadow transition
|
|
33
|
+
- **Action Buttons**:
|
|
34
|
+
- Slide in from right on hover
|
|
35
|
+
- Scale up on hover, scale down on active
|
|
36
|
+
|
|
37
|
+
### Input Animations
|
|
38
|
+
- **Textarea**: Focus ring animation
|
|
39
|
+
- **Send Button**: Scale up on hover, scale down on active
|
|
40
|
+
- **Send Icon**: Smooth transitions
|
|
41
|
+
|
|
42
|
+
## Usage
|
|
43
|
+
|
|
44
|
+
```tsx
|
|
45
|
+
import { KnowledgeChat } from '@djangocfg/layouts/snippets';
|
|
46
|
+
|
|
47
|
+
// Simple usage
|
|
48
|
+
<KnowledgeChat />
|
|
49
|
+
|
|
50
|
+
// With props
|
|
51
|
+
<KnowledgeChat
|
|
52
|
+
autoOpen={false}
|
|
53
|
+
persistent={true}
|
|
54
|
+
onToggle={(isOpen) => console.log('Chat toggled:', isOpen)}
|
|
55
|
+
onMessage={(message) => console.log('Message sent:', message)}
|
|
56
|
+
/>
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Components
|
|
60
|
+
|
|
61
|
+
### KnowledgeChat
|
|
62
|
+
Main component with all providers integrated.
|
|
63
|
+
|
|
64
|
+
### ChatWidget
|
|
65
|
+
Core chat interface (use with providers manually if needed).
|
|
66
|
+
|
|
67
|
+
### ChatUIProvider
|
|
68
|
+
Manages UI state (open/closed, expanded, sources visibility).
|
|
69
|
+
|
|
70
|
+
### MessageList
|
|
71
|
+
Displays chat messages with sources and animations.
|
|
72
|
+
|
|
73
|
+
### MessageInput
|
|
74
|
+
Input field with auto-resize and keyboard shortcuts.
|
|
75
|
+
|
|
76
|
+
### SessionList
|
|
77
|
+
Drawer with chat sessions list.
|
|
78
|
+
|
|
79
|
+
## Animation Timing
|
|
80
|
+
|
|
81
|
+
- **Fast**: 200ms (buttons, badges)
|
|
82
|
+
- **Normal**: 300ms (widget, messages, drawer)
|
|
83
|
+
- **Stagger Delay**: 50-100ms (lists)
|
|
84
|
+
|
|
85
|
+
## Tailwind Classes Used
|
|
86
|
+
|
|
87
|
+
### tailwindcss-animate
|
|
88
|
+
- `animate-in`
|
|
89
|
+
- `fade-in`
|
|
90
|
+
- `slide-in-from-bottom-{n}`
|
|
91
|
+
- `slide-in-from-left-{n}`
|
|
92
|
+
- `zoom-in-95`
|
|
93
|
+
- `duration-{n}`
|
|
94
|
+
- `delay-{n}`
|
|
95
|
+
|
|
96
|
+
### Tailwind Core
|
|
97
|
+
- `transition-all`
|
|
98
|
+
- `transition-transform`
|
|
99
|
+
- `transition-colors`
|
|
100
|
+
- `duration-{n}`
|
|
101
|
+
- `ease-out`
|
|
102
|
+
- `animate-spin`
|
|
103
|
+
- `animate-pulse`
|
|
104
|
+
- `animate-bounce`
|
|
105
|
+
- `hover:scale-{n}`
|
|
106
|
+
- `active:scale-{n}`
|
|
107
|
+
|
|
108
|
+
## Performance
|
|
109
|
+
|
|
110
|
+
All animations use CSS transforms and opacity for optimal performance:
|
|
111
|
+
- Hardware-accelerated transforms (translate, scale)
|
|
112
|
+
- Efficient opacity transitions
|
|
113
|
+
- No layout thrashing
|
|
114
|
+
- Minimal reflows
|
|
115
|
+
|
|
116
|
+
## Accessibility
|
|
117
|
+
|
|
118
|
+
- Respects `prefers-reduced-motion`
|
|
119
|
+
- Keyboard navigation support
|
|
120
|
+
- ARIA labels
|
|
121
|
+
- Screen reader friendly
|
|
122
|
+
|