@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.
Files changed (104) hide show
  1. package/README.md +177 -0
  2. package/dist/chunk-GNRC54ON.js +4308 -0
  3. package/dist/hooks.cjs +6003 -0
  4. package/dist/hooks.d.cts +610 -0
  5. package/dist/hooks.d.ts +610 -0
  6. package/dist/hooks.js +1486 -0
  7. package/dist/index.cjs +4450 -0
  8. package/dist/index.d.cts +4398 -0
  9. package/dist/index.d.ts +4398 -0
  10. package/dist/index.js +1 -0
  11. package/package.json +81 -0
  12. package/src/api/generated/ext_knowbase/_utils/fetchers/ext_knowbase__knowbase.ts +2983 -0
  13. package/src/api/generated/ext_knowbase/_utils/fetchers/index.ts +28 -0
  14. package/src/api/generated/ext_knowbase/_utils/hooks/ext_knowbase__knowbase.ts +999 -0
  15. package/src/api/generated/ext_knowbase/_utils/hooks/index.ts +28 -0
  16. package/src/api/generated/ext_knowbase/_utils/schemas/ArchiveItem.schema.ts +33 -0
  17. package/src/api/generated/ext_knowbase/_utils/schemas/ArchiveItemChunk.schema.ts +29 -0
  18. package/src/api/generated/ext_knowbase/_utils/schemas/ArchiveItemChunkDetail.schema.ts +30 -0
  19. package/src/api/generated/ext_knowbase/_utils/schemas/ArchiveItemChunkRequest.schema.ts +22 -0
  20. package/src/api/generated/ext_knowbase/_utils/schemas/ArchiveItemDetail.schema.ts +35 -0
  21. package/src/api/generated/ext_knowbase/_utils/schemas/ArchiveItemRequest.schema.ts +22 -0
  22. package/src/api/generated/ext_knowbase/_utils/schemas/ArchiveProcessingResult.schema.ts +26 -0
  23. package/src/api/generated/ext_knowbase/_utils/schemas/ArchiveSearchRequestRequest.schema.ts +25 -0
  24. package/src/api/generated/ext_knowbase/_utils/schemas/ArchiveSearchResult.schema.ts +24 -0
  25. package/src/api/generated/ext_knowbase/_utils/schemas/ArchiveStatistics.schema.ts +28 -0
  26. package/src/api/generated/ext_knowbase/_utils/schemas/ChatHistory.schema.ts +22 -0
  27. package/src/api/generated/ext_knowbase/_utils/schemas/ChatMessage.schema.ts +27 -0
  28. package/src/api/generated/ext_knowbase/_utils/schemas/ChatQueryRequest.schema.ts +22 -0
  29. package/src/api/generated/ext_knowbase/_utils/schemas/ChatResponse.schema.ts +26 -0
  30. package/src/api/generated/ext_knowbase/_utils/schemas/ChatResponseRequest.schema.ts +26 -0
  31. package/src/api/generated/ext_knowbase/_utils/schemas/ChatSession.schema.ts +29 -0
  32. package/src/api/generated/ext_knowbase/_utils/schemas/ChatSessionCreateRequest.schema.ts +22 -0
  33. package/src/api/generated/ext_knowbase/_utils/schemas/ChatSessionRequest.schema.ts +25 -0
  34. package/src/api/generated/ext_knowbase/_utils/schemas/ChatSource.schema.ts +21 -0
  35. package/src/api/generated/ext_knowbase/_utils/schemas/ChatSourceRequest.schema.ts +21 -0
  36. package/src/api/generated/ext_knowbase/_utils/schemas/ChunkRevectorizationRequestRequest.schema.ts +20 -0
  37. package/src/api/generated/ext_knowbase/_utils/schemas/Document.schema.ts +32 -0
  38. package/src/api/generated/ext_knowbase/_utils/schemas/DocumentArchive.schema.ts +44 -0
  39. package/src/api/generated/ext_knowbase/_utils/schemas/DocumentArchiveDetail.schema.ts +48 -0
  40. package/src/api/generated/ext_knowbase/_utils/schemas/DocumentArchiveList.schema.ts +35 -0
  41. package/src/api/generated/ext_knowbase/_utils/schemas/DocumentArchiveRequest.schema.ts +21 -0
  42. package/src/api/generated/ext_knowbase/_utils/schemas/DocumentCategory.schema.ts +23 -0
  43. package/src/api/generated/ext_knowbase/_utils/schemas/DocumentCategoryRequest.schema.ts +21 -0
  44. package/src/api/generated/ext_knowbase/_utils/schemas/DocumentCreateRequest.schema.ts +22 -0
  45. package/src/api/generated/ext_knowbase/_utils/schemas/DocumentProcessingStatus.schema.ts +23 -0
  46. package/src/api/generated/ext_knowbase/_utils/schemas/DocumentRequest.schema.ts +22 -0
  47. package/src/api/generated/ext_knowbase/_utils/schemas/DocumentStats.schema.ts +25 -0
  48. package/src/api/generated/ext_knowbase/_utils/schemas/PaginatedArchiveItemChunkList.schema.ts +24 -0
  49. package/src/api/generated/ext_knowbase/_utils/schemas/PaginatedArchiveItemList.schema.ts +24 -0
  50. package/src/api/generated/ext_knowbase/_utils/schemas/PaginatedArchiveSearchResultList.schema.ts +24 -0
  51. package/src/api/generated/ext_knowbase/_utils/schemas/PaginatedChatResponseList.schema.ts +24 -0
  52. package/src/api/generated/ext_knowbase/_utils/schemas/PaginatedChatSessionList.schema.ts +24 -0
  53. package/src/api/generated/ext_knowbase/_utils/schemas/PaginatedDocumentArchiveListList.schema.ts +24 -0
  54. package/src/api/generated/ext_knowbase/_utils/schemas/PaginatedDocumentList.schema.ts +24 -0
  55. package/src/api/generated/ext_knowbase/_utils/schemas/PaginatedPublicCategoryList.schema.ts +24 -0
  56. package/src/api/generated/ext_knowbase/_utils/schemas/PaginatedPublicDocumentListList.schema.ts +24 -0
  57. package/src/api/generated/ext_knowbase/_utils/schemas/PatchedArchiveItemChunkRequest.schema.ts +22 -0
  58. package/src/api/generated/ext_knowbase/_utils/schemas/PatchedArchiveItemRequest.schema.ts +22 -0
  59. package/src/api/generated/ext_knowbase/_utils/schemas/PatchedChatResponseRequest.schema.ts +26 -0
  60. package/src/api/generated/ext_knowbase/_utils/schemas/PatchedChatSessionRequest.schema.ts +25 -0
  61. package/src/api/generated/ext_knowbase/_utils/schemas/PatchedDocumentArchiveRequest.schema.ts +21 -0
  62. package/src/api/generated/ext_knowbase/_utils/schemas/PatchedDocumentRequest.schema.ts +22 -0
  63. package/src/api/generated/ext_knowbase/_utils/schemas/PublicCategory.schema.ts +21 -0
  64. package/src/api/generated/ext_knowbase/_utils/schemas/PublicDocument.schema.ts +25 -0
  65. package/src/api/generated/ext_knowbase/_utils/schemas/PublicDocumentList.schema.ts +24 -0
  66. package/src/api/generated/ext_knowbase/_utils/schemas/VectorizationResult.schema.ts +24 -0
  67. package/src/api/generated/ext_knowbase/_utils/schemas/VectorizationStatistics.schema.ts +26 -0
  68. package/src/api/generated/ext_knowbase/_utils/schemas/index.ts +70 -0
  69. package/src/api/generated/ext_knowbase/api-instance.ts +131 -0
  70. package/src/api/generated/ext_knowbase/client.ts +301 -0
  71. package/src/api/generated/ext_knowbase/enums.ts +241 -0
  72. package/src/api/generated/ext_knowbase/errors.ts +116 -0
  73. package/src/api/generated/ext_knowbase/ext_knowbase__knowbase/client.ts +666 -0
  74. package/src/api/generated/ext_knowbase/ext_knowbase__knowbase/index.ts +2 -0
  75. package/src/api/generated/ext_knowbase/ext_knowbase__knowbase/models.ts +1120 -0
  76. package/src/api/generated/ext_knowbase/http.ts +103 -0
  77. package/src/api/generated/ext_knowbase/index.ts +273 -0
  78. package/src/api/generated/ext_knowbase/logger.ts +259 -0
  79. package/src/api/generated/ext_knowbase/retry.ts +175 -0
  80. package/src/api/generated/ext_knowbase/schema.json +5865 -0
  81. package/src/api/generated/ext_knowbase/storage.ts +161 -0
  82. package/src/api/generated/ext_knowbase/validation-events.ts +133 -0
  83. package/src/api/index.ts +9 -0
  84. package/src/components/Chat/ChatUIContext.tsx +110 -0
  85. package/src/components/Chat/ChatWidget.tsx +476 -0
  86. package/src/components/Chat/README.md +122 -0
  87. package/src/components/Chat/components/MessageInput.tsx +124 -0
  88. package/src/components/Chat/components/MessageList.tsx +169 -0
  89. package/src/components/Chat/components/SessionList.tsx +192 -0
  90. package/src/components/Chat/components/index.ts +9 -0
  91. package/src/components/Chat/hooks/index.ts +6 -0
  92. package/src/components/Chat/hooks/useInfiniteSessions.ts +81 -0
  93. package/src/components/Chat/index.tsx +45 -0
  94. package/src/components/Chat/types.ts +81 -0
  95. package/src/config.ts +20 -0
  96. package/src/contexts/knowbase/ChatContext.tsx +173 -0
  97. package/src/contexts/knowbase/DocumentsContext.tsx +306 -0
  98. package/src/contexts/knowbase/KnowbaseProvider.tsx +47 -0
  99. package/src/contexts/knowbase/SessionsContext.tsx +175 -0
  100. package/src/contexts/knowbase/index.ts +63 -0
  101. package/src/contexts/knowbase/types.ts +69 -0
  102. package/src/hooks/index.ts +28 -0
  103. package/src/index.ts +22 -0
  104. package/src/utils/logger.ts +10 -0
@@ -0,0 +1,124 @@
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
+
@@ -0,0 +1,169 @@
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 { useAuth } from '@djangocfg/api/auth';
12
+ import type { MessageListProps } from '../types';
13
+ import { Enums } 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
+
@@ -0,0 +1,192 @@
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 '../../../contexts/knowbase';
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
+
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Chat Components
3
+ * Re-exports for all chat components
4
+ */
5
+
6
+ export { MessageList } from './MessageList';
7
+ export { MessageInput } from './MessageInput';
8
+ export { SessionList } from './SessionList';
9
+
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Chat hooks
3
+ */
4
+
5
+ export { useInfiniteSessions } from './useInfiniteSessions';
6
+
@@ -0,0 +1,81 @@
1
+ /**
2
+ * Hook for infinite scroll chat sessions
3
+ * Uses SWR Infinite for pagination
4
+ */
5
+
6
+ import useSWRInfinite from 'swr/infinite';
7
+ import { apiKnowbase, ExtKnowbaseTypes, ExtKnowbaseFetchers } from '@djangocfg/api';
8
+
9
+ type PaginatedChatSessionList = ExtKnowbaseTypes.PaginatedChatSessionList;
10
+ type ChatSession = ExtKnowbaseTypes.ChatSession;
11
+
12
+ const PAGE_SIZE = 20;
13
+
14
+ export function useInfiniteSessions() {
15
+ const getKey = (pageIndex: number, previousPageData: PaginatedChatSessionList | null) => {
16
+ // Reached the end
17
+ if (previousPageData && !previousPageData.has_next) return null;
18
+
19
+ // First page, no previous data
20
+ if (pageIndex === 0) return ['cfg-knowbase-admin-sessions-infinite', 1, PAGE_SIZE];
21
+
22
+ // Add the page number to the SWR key
23
+ return ['cfg-knowbase-admin-sessions-infinite', pageIndex + 1, PAGE_SIZE];
24
+ };
25
+
26
+ const fetcher = async ([, page, pageSize]: [string, number, number]) => {
27
+ return ExtKnowbaseFetchers.getKnowbaseAdminSessionsList(
28
+ { page, page_size: pageSize },
29
+ apiKnowbase
30
+ );
31
+ };
32
+
33
+ const {
34
+ data,
35
+ error,
36
+ isLoading,
37
+ isValidating,
38
+ size,
39
+ setSize,
40
+ mutate,
41
+ } = useSWRInfinite<PaginatedChatSessionList>(getKey, fetcher, {
42
+ revalidateFirstPage: false,
43
+ parallel: false,
44
+ });
45
+
46
+ // Flatten all pages into single array
47
+ const sessions: ChatSession[] = data ? data.flatMap((page) => page.results) : [];
48
+
49
+ // Check if there are more pages
50
+ const hasMore = data && data[data.length - 1]?.has_next;
51
+
52
+ // Total count from last page
53
+ const totalCount = data && data[data.length - 1]?.count;
54
+
55
+ // Loading more state
56
+ const isLoadingMore = isValidating && data && typeof data[size - 1] !== 'undefined';
57
+
58
+ // Function to load next page
59
+ const loadMore = () => {
60
+ if (hasMore && !isLoadingMore) {
61
+ setSize(size + 1);
62
+ }
63
+ };
64
+
65
+ // Refresh all pages
66
+ const refresh = async () => {
67
+ await mutate();
68
+ };
69
+
70
+ return {
71
+ sessions,
72
+ isLoading,
73
+ isLoadingMore: isLoadingMore || false,
74
+ error,
75
+ hasMore: hasMore || false,
76
+ totalCount: totalCount || 0,
77
+ loadMore,
78
+ refresh,
79
+ };
80
+ }
81
+
@@ -0,0 +1,45 @@
1
+ // @ts-nocheck
2
+ /**
3
+ * Knowledge Chat Module
4
+ * Complete RAG-powered chat widget with context providers
5
+ */
6
+
7
+ 'use client';
8
+
9
+ import React from 'react';
10
+ import { KnowbaseChatProvider, KnowbaseSessionsProvider } from '../../contexts/knowbase';
11
+ import { ChatUIProvider } from './ChatUIContext';
12
+ import { ChatWidget } from './ChatWidget';
13
+ import type { ChatWidgetProps } from './types';
14
+
15
+ // ─────────────────────────────────────────────────────────────────────────
16
+ // Main Component with Providers
17
+ // ─────────────────────────────────────────────────────────────────────────
18
+
19
+ export const KnowledgeChat: React.FC<ChatWidgetProps> = (props) => {
20
+ return (
21
+ <KnowbaseChatProvider>
22
+ <KnowbaseSessionsProvider>
23
+ <ChatUIProvider initialState={{ isOpen: false }}>
24
+ <ChatWidget {...props} />
25
+ </ChatUIProvider>
26
+ </KnowbaseSessionsProvider>
27
+ </KnowbaseChatProvider>
28
+ );
29
+ };
30
+
31
+ // ─────────────────────────────────────────────────────────────────────────
32
+ // Named Exports
33
+ // ─────────────────────────────────────────────────────────────────────────
34
+
35
+ export { ChatWidget } from './ChatWidget';
36
+ export { ChatUIProvider, useChatUI } from './ChatUIContext';
37
+ export { MessageList, MessageInput, SessionList } from './components';
38
+ export * from './types';
39
+
40
+ // ─────────────────────────────────────────────────────────────────────────
41
+ // Default Export
42
+ // ─────────────────────────────────────────────────────────────────────────
43
+
44
+ export default KnowledgeChat;
45
+