@djangocfg/layouts 2.1.10 → 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/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,122 +0,0 @@
|
|
|
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
|
-
|
|
@@ -1,124 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Message Input Component
|
|
3
|
-
* Input field for sending chat messages
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
'use client';
|
|
7
|
-
|
|
8
|
-
import React, { useState, useCallback, useRef, useEffect } from 'react';
|
|
9
|
-
import { Button, Textarea } from '@djangocfg/ui-nextjs';
|
|
10
|
-
import { Send, Loader2 } from 'lucide-react';
|
|
11
|
-
import { chatLogger } from '../../../utils/logger';
|
|
12
|
-
import type { MessageInputProps } from '../types';
|
|
13
|
-
|
|
14
|
-
// ─────────────────────────────────────────────────────────────────────────
|
|
15
|
-
// Message Input Component
|
|
16
|
-
// ─────────────────────────────────────────────────────────────────────────
|
|
17
|
-
|
|
18
|
-
export const MessageInput: React.FC<MessageInputProps> = ({
|
|
19
|
-
onSend,
|
|
20
|
-
isLoading = false,
|
|
21
|
-
disabled = false,
|
|
22
|
-
placeholder = 'Ask me anything...',
|
|
23
|
-
className = '',
|
|
24
|
-
}) => {
|
|
25
|
-
const [message, setMessage] = useState('');
|
|
26
|
-
const [rows, setRows] = useState(1);
|
|
27
|
-
const textareaRef = useRef<HTMLTextAreaElement>(null);
|
|
28
|
-
|
|
29
|
-
// Auto-resize textarea based on content
|
|
30
|
-
useEffect(() => {
|
|
31
|
-
if (textareaRef.current) {
|
|
32
|
-
const lineHeight = 24; // approximate line height
|
|
33
|
-
const maxRows = 5;
|
|
34
|
-
const minRows = 1;
|
|
35
|
-
|
|
36
|
-
// If message is empty, reset to min height
|
|
37
|
-
if (!message) {
|
|
38
|
-
textareaRef.current.style.height = 'auto';
|
|
39
|
-
setRows(minRows);
|
|
40
|
-
return;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
textareaRef.current.style.height = 'auto';
|
|
44
|
-
const scrollHeight = textareaRef.current.scrollHeight;
|
|
45
|
-
const newRows = Math.max(minRows, Math.min(maxRows, Math.floor(scrollHeight / lineHeight)));
|
|
46
|
-
setRows(newRows);
|
|
47
|
-
}
|
|
48
|
-
}, [message]);
|
|
49
|
-
|
|
50
|
-
const handleSubmit = useCallback(
|
|
51
|
-
async (e?: React.FormEvent) => {
|
|
52
|
-
e?.preventDefault();
|
|
53
|
-
|
|
54
|
-
const trimmedMessage = message.trim();
|
|
55
|
-
if (!trimmedMessage || isLoading || disabled) return;
|
|
56
|
-
|
|
57
|
-
try {
|
|
58
|
-
await onSend(trimmedMessage);
|
|
59
|
-
setMessage('');
|
|
60
|
-
setRows(1);
|
|
61
|
-
|
|
62
|
-
// Reset textarea height
|
|
63
|
-
if (textareaRef.current) {
|
|
64
|
-
textareaRef.current.style.height = 'auto';
|
|
65
|
-
}
|
|
66
|
-
} catch (error) {
|
|
67
|
-
chatLogger.error('Failed to send message from input:', error);
|
|
68
|
-
}
|
|
69
|
-
},
|
|
70
|
-
[message, isLoading, disabled, onSend]
|
|
71
|
-
);
|
|
72
|
-
|
|
73
|
-
const handleKeyDown = useCallback(
|
|
74
|
-
(e: React.KeyboardEvent<HTMLTextAreaElement>) => {
|
|
75
|
-
// Send on Enter (without Shift)
|
|
76
|
-
if (e.key === 'Enter' && !e.shiftKey) {
|
|
77
|
-
e.preventDefault();
|
|
78
|
-
handleSubmit();
|
|
79
|
-
}
|
|
80
|
-
},
|
|
81
|
-
[handleSubmit]
|
|
82
|
-
);
|
|
83
|
-
|
|
84
|
-
const isDisabled = disabled || isLoading;
|
|
85
|
-
const canSend = message.trim().length > 0 && !isDisabled;
|
|
86
|
-
|
|
87
|
-
return (
|
|
88
|
-
<form onSubmit={handleSubmit} className={`border-t p-4 ${className}`}>
|
|
89
|
-
<div className="flex items-end gap-2">
|
|
90
|
-
<Textarea
|
|
91
|
-
ref={textareaRef}
|
|
92
|
-
value={message}
|
|
93
|
-
onChange={(e) => setMessage(e.target.value)}
|
|
94
|
-
onKeyDown={handleKeyDown}
|
|
95
|
-
placeholder={placeholder}
|
|
96
|
-
disabled={isDisabled}
|
|
97
|
-
rows={rows}
|
|
98
|
-
className="resize-none min-h-[40px] max-h-[120px] transition-all duration-200
|
|
99
|
-
focus:ring-2 focus:ring-primary/20"
|
|
100
|
-
style={{ resize: 'none' }}
|
|
101
|
-
/>
|
|
102
|
-
|
|
103
|
-
<Button
|
|
104
|
-
type="submit"
|
|
105
|
-
size="icon"
|
|
106
|
-
disabled={!canSend}
|
|
107
|
-
className="shrink-0 h-10 w-10 transition-all duration-200
|
|
108
|
-
hover:scale-110 active:scale-95 disabled:scale-100"
|
|
109
|
-
>
|
|
110
|
-
{isLoading ? (
|
|
111
|
-
<Loader2 className="h-4 w-4 animate-spin" />
|
|
112
|
-
) : (
|
|
113
|
-
<Send className="h-4 w-4" />
|
|
114
|
-
)}
|
|
115
|
-
</Button>
|
|
116
|
-
</div>
|
|
117
|
-
|
|
118
|
-
<p className="text-xs text-muted-foreground mt-2">
|
|
119
|
-
Press Enter to send, Shift+Enter for new line
|
|
120
|
-
</p>
|
|
121
|
-
</form>
|
|
122
|
-
);
|
|
123
|
-
};
|
|
124
|
-
|
|
@@ -1,169 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Message List Component
|
|
3
|
-
* Displays chat messages with markdown support and sources
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
'use client';
|
|
7
|
-
|
|
8
|
-
import React, { useEffect, useRef } from 'react';
|
|
9
|
-
import { Card, CardContent, Avatar, AvatarImage, AvatarFallback, Badge, ScrollArea } from '@djangocfg/ui-nextjs';
|
|
10
|
-
import { Bot, User, ExternalLink, Loader2 } from 'lucide-react';
|
|
11
|
-
import { Enums } from '@djangocfg/api/cfg/generated';
|
|
12
|
-
import { useAuth } from '../../../auth';
|
|
13
|
-
import type { MessageListProps } from '../types';
|
|
14
|
-
|
|
15
|
-
// ─────────────────────────────────────────────────────────────────────────
|
|
16
|
-
// Message List Component
|
|
17
|
-
// ─────────────────────────────────────────────────────────────────────────
|
|
18
|
-
|
|
19
|
-
export const MessageList: React.FC<MessageListProps> = ({
|
|
20
|
-
messages,
|
|
21
|
-
isLoading = false,
|
|
22
|
-
showSources = true,
|
|
23
|
-
showTimestamps = false,
|
|
24
|
-
autoScroll = true,
|
|
25
|
-
className = '',
|
|
26
|
-
}) => {
|
|
27
|
-
const scrollRef = useRef<HTMLDivElement>(null);
|
|
28
|
-
const { user } = useAuth();
|
|
29
|
-
|
|
30
|
-
// Auto-scroll to bottom on new messages
|
|
31
|
-
useEffect(() => {
|
|
32
|
-
if (autoScroll && scrollRef.current) {
|
|
33
|
-
const scrollContainer = scrollRef.current.querySelector('[data-radix-scroll-area-viewport]');
|
|
34
|
-
if (scrollContainer) {
|
|
35
|
-
scrollContainer.scrollTop = scrollContainer.scrollHeight;
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
}, [messages, isLoading, autoScroll]);
|
|
39
|
-
|
|
40
|
-
const formatTimestamp = (timestamp: string) => {
|
|
41
|
-
const date = new Date(timestamp);
|
|
42
|
-
return date.toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit' });
|
|
43
|
-
};
|
|
44
|
-
|
|
45
|
-
return (
|
|
46
|
-
<ScrollArea className={`h-full bg-muted/50 ${className}`} viewportRef={scrollRef}>
|
|
47
|
-
<div className="space-y-4 p-4">
|
|
48
|
-
{messages.length === 0 && !isLoading ? (
|
|
49
|
-
<div className="flex flex-col items-center justify-center h-full text-center py-12">
|
|
50
|
-
<Bot className="h-12 w-12 text-muted-foreground mb-4" />
|
|
51
|
-
<h3 className="text-lg font-semibold text-foreground mb-2">
|
|
52
|
-
Start a Conversation
|
|
53
|
-
</h3>
|
|
54
|
-
<p className="text-sm text-muted-foreground max-w-md">
|
|
55
|
-
Ask me anything about the documentation, features, or get help with your project.
|
|
56
|
-
</p>
|
|
57
|
-
</div>
|
|
58
|
-
) : (
|
|
59
|
-
messages.map((message, index) => {
|
|
60
|
-
const isUser = message.role === Enums.ChatMessageRole.USER;
|
|
61
|
-
const isAssistant = message.role === Enums.ChatMessageRole.ASSISTANT;
|
|
62
|
-
|
|
63
|
-
return (
|
|
64
|
-
<div
|
|
65
|
-
key={message.id}
|
|
66
|
-
className={`flex gap-3 ${isUser ? 'justify-end' : 'justify-start'}
|
|
67
|
-
animate-in fade-in slide-in-from-bottom-2 duration-300`}
|
|
68
|
-
style={{ animationDelay: `${index * 50}ms` }}
|
|
69
|
-
>
|
|
70
|
-
{/* Avatar */}
|
|
71
|
-
{isAssistant && (
|
|
72
|
-
<Avatar className="h-8 w-8 shrink-0">
|
|
73
|
-
<div className="flex h-full w-full items-center justify-center bg-primary text-primary-foreground">
|
|
74
|
-
<Bot className="h-5 w-5" />
|
|
75
|
-
</div>
|
|
76
|
-
</Avatar>
|
|
77
|
-
)}
|
|
78
|
-
|
|
79
|
-
{/* Message Content */}
|
|
80
|
-
<div
|
|
81
|
-
className={`flex flex-col gap-2 flex-1 max-w-[80%] ${
|
|
82
|
-
isUser ? 'items-end' : 'items-start'
|
|
83
|
-
}`}
|
|
84
|
-
>
|
|
85
|
-
{/* Message Bubble */}
|
|
86
|
-
<Card
|
|
87
|
-
className={`${
|
|
88
|
-
isUser
|
|
89
|
-
? 'bg-primary text-primary-foreground'
|
|
90
|
-
: 'bg-muted'
|
|
91
|
-
} transition-all duration-200 hover:shadow-md`}
|
|
92
|
-
>
|
|
93
|
-
<CardContent className="p-3">
|
|
94
|
-
<div className="text-sm whitespace-pre-wrap break-words">
|
|
95
|
-
{message.content}
|
|
96
|
-
</div>
|
|
97
|
-
</CardContent>
|
|
98
|
-
</Card>
|
|
99
|
-
|
|
100
|
-
{/* Timestamp */}
|
|
101
|
-
{showTimestamps && message.created_at && (
|
|
102
|
-
<span className="text-xs text-muted-foreground px-1">
|
|
103
|
-
{formatTimestamp(message.created_at)}
|
|
104
|
-
</span>
|
|
105
|
-
)}
|
|
106
|
-
|
|
107
|
-
{/* Sources */}
|
|
108
|
-
{showSources &&
|
|
109
|
-
isAssistant &&
|
|
110
|
-
message.sources &&
|
|
111
|
-
message.sources.length > 0 && (
|
|
112
|
-
<div className="flex flex-wrap gap-2 px-1 animate-in fade-in slide-in-from-left-2 duration-300 delay-100">
|
|
113
|
-
{message.sources.map((source, idx) => (
|
|
114
|
-
<div key={idx}>
|
|
115
|
-
<Badge
|
|
116
|
-
variant="secondary"
|
|
117
|
-
className="text-xs flex items-center gap-1 cursor-pointer
|
|
118
|
-
hover:bg-secondary/80 hover:scale-105 active:scale-95
|
|
119
|
-
transition-all duration-200 animate-in fade-in zoom-in-95"
|
|
120
|
-
style={{ animationDelay: `${(idx + 1) * 100}ms` }}
|
|
121
|
-
>
|
|
122
|
-
{source.document_title || `Source ${idx + 1}`}
|
|
123
|
-
<ExternalLink className="h-3 w-3" />
|
|
124
|
-
</Badge>
|
|
125
|
-
</div>
|
|
126
|
-
))}
|
|
127
|
-
</div>
|
|
128
|
-
)}
|
|
129
|
-
</div>
|
|
130
|
-
|
|
131
|
-
{/* User Avatar */}
|
|
132
|
-
{isUser && (
|
|
133
|
-
<Avatar className="h-8 w-8 shrink-0">
|
|
134
|
-
{user?.avatar && <AvatarImage src={user.avatar} alt={user.display_username || user.email || 'User'} />}
|
|
135
|
-
<AvatarFallback className="bg-primary/10 text-primary font-semibold">
|
|
136
|
-
{user?.display_username?.charAt(0)?.toUpperCase() ||
|
|
137
|
-
user?.email?.charAt(0)?.toUpperCase() ||
|
|
138
|
-
<User className="h-5 w-5" />}
|
|
139
|
-
</AvatarFallback>
|
|
140
|
-
</Avatar>
|
|
141
|
-
)}
|
|
142
|
-
</div>
|
|
143
|
-
);
|
|
144
|
-
})
|
|
145
|
-
)}
|
|
146
|
-
|
|
147
|
-
{/* Loading Indicator */}
|
|
148
|
-
{isLoading && (
|
|
149
|
-
<div className="flex gap-3 justify-start animate-in fade-in slide-in-from-bottom-2 duration-300">
|
|
150
|
-
<Avatar className="h-8 w-8 shrink-0 animate-pulse">
|
|
151
|
-
<div className="flex h-full w-full items-center justify-center bg-primary text-primary-foreground">
|
|
152
|
-
<Bot className="h-5 w-5 animate-bounce" />
|
|
153
|
-
</div>
|
|
154
|
-
</Avatar>
|
|
155
|
-
<Card className="bg-muted animate-pulse">
|
|
156
|
-
<CardContent className="p-3">
|
|
157
|
-
<div className="flex items-center gap-2 text-sm text-muted-foreground">
|
|
158
|
-
<Loader2 className="h-4 w-4 animate-spin" />
|
|
159
|
-
<span className="animate-pulse">Thinking...</span>
|
|
160
|
-
</div>
|
|
161
|
-
</CardContent>
|
|
162
|
-
</Card>
|
|
163
|
-
</div>
|
|
164
|
-
)}
|
|
165
|
-
</div>
|
|
166
|
-
</ScrollArea>
|
|
167
|
-
);
|
|
168
|
-
};
|
|
169
|
-
|
|
@@ -1,192 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Session List Component
|
|
3
|
-
* Drawer with list of chat sessions
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
'use client';
|
|
7
|
-
|
|
8
|
-
import React, { useCallback, useRef, useEffect } from 'react';
|
|
9
|
-
import {
|
|
10
|
-
Sheet,
|
|
11
|
-
SheetContent,
|
|
12
|
-
SheetHeader,
|
|
13
|
-
SheetTitle,
|
|
14
|
-
SheetDescription,
|
|
15
|
-
Button,
|
|
16
|
-
ScrollArea,
|
|
17
|
-
Badge,
|
|
18
|
-
} from '@djangocfg/ui-nextjs';
|
|
19
|
-
import { MessageSquare, Clock, Archive, Trash2, Loader2 } from 'lucide-react';
|
|
20
|
-
import { useKnowbaseSessionsContext } from '@djangocfg/layouts/contexts';
|
|
21
|
-
import { useInfiniteSessions } from '../hooks/useInfiniteSessions';
|
|
22
|
-
import type { SessionListProps } from '../types';
|
|
23
|
-
|
|
24
|
-
// ─────────────────────────────────────────────────────────────────────────
|
|
25
|
-
// Session List Component
|
|
26
|
-
// ─────────────────────────────────────────────────────────────────────────
|
|
27
|
-
|
|
28
|
-
export const SessionList: React.FC<SessionListProps> = ({
|
|
29
|
-
isOpen,
|
|
30
|
-
onClose,
|
|
31
|
-
onSelectSession,
|
|
32
|
-
className = '',
|
|
33
|
-
}) => {
|
|
34
|
-
const { deleteSession, archiveSession } = useKnowbaseSessionsContext();
|
|
35
|
-
const {
|
|
36
|
-
sessions,
|
|
37
|
-
isLoading,
|
|
38
|
-
isLoadingMore,
|
|
39
|
-
hasMore,
|
|
40
|
-
loadMore,
|
|
41
|
-
} = useInfiniteSessions();
|
|
42
|
-
|
|
43
|
-
const scrollRef = useRef<HTMLDivElement>(null);
|
|
44
|
-
|
|
45
|
-
// Handle scroll to load more
|
|
46
|
-
const handleScroll = useCallback(() => {
|
|
47
|
-
if (!scrollRef.current || isLoadingMore || !hasMore) return;
|
|
48
|
-
|
|
49
|
-
const scrollContainer = scrollRef.current.querySelector('[data-radix-scroll-area-viewport]');
|
|
50
|
-
if (!scrollContainer) return;
|
|
51
|
-
|
|
52
|
-
const { scrollTop, scrollHeight, clientHeight } = scrollContainer;
|
|
53
|
-
const scrollPercentage = (scrollTop + clientHeight) / scrollHeight;
|
|
54
|
-
|
|
55
|
-
// Load more when scrolled 80% down
|
|
56
|
-
if (scrollPercentage > 0.8) {
|
|
57
|
-
loadMore();
|
|
58
|
-
}
|
|
59
|
-
}, [hasMore, isLoadingMore, loadMore]);
|
|
60
|
-
|
|
61
|
-
// Attach scroll listener
|
|
62
|
-
useEffect(() => {
|
|
63
|
-
const scrollContainer = scrollRef.current?.querySelector('[data-radix-scroll-area-viewport]');
|
|
64
|
-
if (!scrollContainer) return;
|
|
65
|
-
|
|
66
|
-
scrollContainer.addEventListener('scroll', handleScroll);
|
|
67
|
-
return () => scrollContainer.removeEventListener('scroll', handleScroll);
|
|
68
|
-
}, [handleScroll]);
|
|
69
|
-
|
|
70
|
-
const formatDate = (dateString: string) => {
|
|
71
|
-
const date = new Date(dateString);
|
|
72
|
-
return date.toLocaleDateString('en-US', { month: 'short', day: 'numeric' });
|
|
73
|
-
};
|
|
74
|
-
|
|
75
|
-
return (
|
|
76
|
-
<Sheet open={isOpen} onOpenChange={onClose}>
|
|
77
|
-
<SheetContent side="left" className={`w-[400px] sm:w-[540px] ${className}`}>
|
|
78
|
-
<SheetHeader>
|
|
79
|
-
<SheetTitle>Chat Sessions</SheetTitle>
|
|
80
|
-
<SheetDescription>
|
|
81
|
-
View and manage your chat history
|
|
82
|
-
</SheetDescription>
|
|
83
|
-
</SheetHeader>
|
|
84
|
-
|
|
85
|
-
<ScrollArea className="h-[calc(100vh-120px)] mt-6" viewportRef={scrollRef}>
|
|
86
|
-
{isLoading ? (
|
|
87
|
-
<div className="flex items-center justify-center py-12">
|
|
88
|
-
<Loader2 className="h-6 w-6 animate-spin text-muted-foreground" />
|
|
89
|
-
</div>
|
|
90
|
-
) : !sessions || sessions.length === 0 ? (
|
|
91
|
-
<div className="flex flex-col items-center justify-center py-12 text-center">
|
|
92
|
-
<MessageSquare className="h-12 w-12 text-muted-foreground mb-4" />
|
|
93
|
-
<h3 className="text-lg font-semibold text-foreground mb-2">
|
|
94
|
-
No Sessions Yet
|
|
95
|
-
</h3>
|
|
96
|
-
<p className="text-sm text-muted-foreground max-w-md">
|
|
97
|
-
Start a new conversation to create your first session.
|
|
98
|
-
</p>
|
|
99
|
-
</div>
|
|
100
|
-
) : (
|
|
101
|
-
<div className="space-y-2">
|
|
102
|
-
{sessions.map((session, index) => (
|
|
103
|
-
<div
|
|
104
|
-
key={session.id}
|
|
105
|
-
className="group relative flex items-start gap-3 p-4 border rounded-sm
|
|
106
|
-
hover:bg-muted/50 hover:border-primary/50 hover:shadow-md
|
|
107
|
-
transition-all duration-200 cursor-pointer
|
|
108
|
-
animate-in fade-in slide-in-from-left-2"
|
|
109
|
-
style={{ animationDelay: `${index * 50}ms` }}
|
|
110
|
-
onClick={() => {
|
|
111
|
-
onSelectSession(session.id);
|
|
112
|
-
onClose();
|
|
113
|
-
}}
|
|
114
|
-
>
|
|
115
|
-
{/* Session Icon */}
|
|
116
|
-
<div className="shrink-0 mt-1">
|
|
117
|
-
<MessageSquare className="h-5 w-5 text-muted-foreground" />
|
|
118
|
-
</div>
|
|
119
|
-
|
|
120
|
-
{/* Session Info */}
|
|
121
|
-
<div className="flex-1 min-w-0">
|
|
122
|
-
<div className="flex items-start justify-between gap-2 mb-1">
|
|
123
|
-
<h4 className="font-medium text-sm truncate">
|
|
124
|
-
{session.title || 'Untitled Session'}
|
|
125
|
-
</h4>
|
|
126
|
-
{session.is_active && (
|
|
127
|
-
<Badge variant="default" className="shrink-0 text-xs">
|
|
128
|
-
Active
|
|
129
|
-
</Badge>
|
|
130
|
-
)}
|
|
131
|
-
</div>
|
|
132
|
-
|
|
133
|
-
<div className="flex items-center gap-2 text-xs text-muted-foreground">
|
|
134
|
-
<Clock className="h-3 w-3" />
|
|
135
|
-
<span>{formatDate(session.created_at)}</span>
|
|
136
|
-
</div>
|
|
137
|
-
</div>
|
|
138
|
-
|
|
139
|
-
{/* Actions */}
|
|
140
|
-
<div className="shrink-0 flex items-center gap-1 opacity-0 group-hover:opacity-100
|
|
141
|
-
transition-all duration-200 group-hover:translate-x-0 translate-x-2">
|
|
142
|
-
<Button
|
|
143
|
-
variant="ghost"
|
|
144
|
-
size="sm"
|
|
145
|
-
className="h-8 w-8 p-0 hover:scale-110 active:scale-95 transition-transform duration-200"
|
|
146
|
-
onClick={(e) => {
|
|
147
|
-
e.stopPropagation();
|
|
148
|
-
archiveSession(session.id, {});
|
|
149
|
-
}}
|
|
150
|
-
title="Archive"
|
|
151
|
-
>
|
|
152
|
-
<Archive className="h-4 w-4" />
|
|
153
|
-
</Button>
|
|
154
|
-
<Button
|
|
155
|
-
variant="ghost"
|
|
156
|
-
size="sm"
|
|
157
|
-
className="h-8 w-8 p-0 text-destructive hover:text-destructive
|
|
158
|
-
hover:scale-110 active:scale-95 transition-transform duration-200"
|
|
159
|
-
onClick={(e) => {
|
|
160
|
-
e.stopPropagation();
|
|
161
|
-
deleteSession(session.id);
|
|
162
|
-
}}
|
|
163
|
-
title="Delete"
|
|
164
|
-
>
|
|
165
|
-
<Trash2 className="h-4 w-4" />
|
|
166
|
-
</Button>
|
|
167
|
-
</div>
|
|
168
|
-
</div>
|
|
169
|
-
))}
|
|
170
|
-
|
|
171
|
-
{/* Loading more indicator */}
|
|
172
|
-
{isLoadingMore && (
|
|
173
|
-
<div className="flex items-center justify-center py-6">
|
|
174
|
-
<Loader2 className="h-5 w-5 animate-spin text-muted-foreground mr-2" />
|
|
175
|
-
<span className="text-sm text-muted-foreground">Loading more...</span>
|
|
176
|
-
</div>
|
|
177
|
-
)}
|
|
178
|
-
|
|
179
|
-
{/* No more sessions indicator */}
|
|
180
|
-
{!hasMore && sessions.length > 0 && (
|
|
181
|
-
<div className="text-center py-4">
|
|
182
|
-
<span className="text-xs text-muted-foreground">No more sessions</span>
|
|
183
|
-
</div>
|
|
184
|
-
)}
|
|
185
|
-
</div>
|
|
186
|
-
)}
|
|
187
|
-
</ScrollArea>
|
|
188
|
-
</SheetContent>
|
|
189
|
-
</Sheet>
|
|
190
|
-
);
|
|
191
|
-
};
|
|
192
|
-
|