Skip to content

Commit 6745dbf

Browse files
committed
feat: move KB UI into AI Assistant UI
1 parent 8726700 commit 6745dbf

File tree

4 files changed

+175
-19
lines changed

4 files changed

+175
-19
lines changed

admin/inertia/components/chat/ChatSidebar.tsx

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ import StyledButton from '../StyledButton'
33
import { router } from '@inertiajs/react'
44
import { ChatSession } from '../../../types/chat'
55
import { IconMessage } from '@tabler/icons-react'
6+
import { useState } from 'react'
7+
import KnowledgeBaseModal from './KnowledgeBaseModal'
68

79
interface ChatSidebarProps {
810
sessions: ChatSession[]
@@ -21,6 +23,8 @@ export default function ChatSidebar({
2123
onClearHistory,
2224
isInModal = false,
2325
}: ChatSidebarProps) {
26+
const [isKnowledgeBaseModalOpen, setIsKnowledgeBaseModalOpen] = useState(false)
27+
2428
return (
2529
<div className="w-64 bg-gray-50 border-r border-gray-200 flex flex-col h-full">
2630
<div className="p-4 border-b border-gray-200 h-[75px] flex items-center justify-center">
@@ -48,7 +52,7 @@ export default function ChatSidebar({
4852
<div className="flex items-start gap-2">
4953
<IconMessage
5054
className={classNames(
51-
'h-5 w-5 mt-0.5 flex-shrink-0',
55+
'h-5 w-5 mt-0.5 shrink-0',
5256
activeSessionId === session.id ? 'text-white' : 'text-gray-400'
5357
)}
5458
/>
@@ -101,7 +105,7 @@ export default function ChatSidebar({
101105
</StyledButton>
102106
<StyledButton
103107
onClick={() => {
104-
router.visit('/knowledge-base')
108+
setIsKnowledgeBaseModalOpen(true)
105109
}}
106110
icon="IconBrain"
107111
variant="primary"
@@ -122,6 +126,9 @@ export default function ChatSidebar({
122126
</StyledButton>
123127
)}
124128
</div>
129+
{isKnowledgeBaseModalOpen && (
130+
<KnowledgeBaseModal onClose={() => setIsKnowledgeBaseModalOpen(false)} />
131+
)}
125132
</div>
126133
)
127134
}
Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
import { useMutation, useQuery } from '@tanstack/react-query'
2+
import { useRef, useState } from 'react'
3+
import FileUploader from '~/components/file-uploader'
4+
import StyledButton from '~/components/StyledButton'
5+
import StyledSectionHeader from '~/components/StyledSectionHeader'
6+
import StyledTable from '~/components/StyledTable'
7+
import { useNotifications } from '~/context/NotificationContext'
8+
import api from '~/lib/api'
9+
import { IconX } from '@tabler/icons-react'
10+
11+
interface KnowledgeBaseModalProps {
12+
onClose: () => void
13+
}
14+
15+
export default function KnowledgeBaseModal({ onClose }: KnowledgeBaseModalProps) {
16+
const { addNotification } = useNotifications()
17+
const [files, setFiles] = useState<File[]>([])
18+
const fileUploaderRef = useRef<React.ComponentRef<typeof FileUploader>>(null)
19+
20+
const { data: storedFiles = [], isLoading: isLoadingFiles } = useQuery({
21+
queryKey: ['storedFiles'],
22+
queryFn: () => api.getStoredRAGFiles(),
23+
select: (data) => data || [],
24+
})
25+
26+
const uploadMutation = useMutation({
27+
mutationFn: (file: File) => api.uploadDocument(file),
28+
onSuccess: (data) => {
29+
addNotification({
30+
type: 'success',
31+
message: data?.message || 'Document uploaded and queued for processing',
32+
})
33+
setFiles([])
34+
if (fileUploaderRef.current) {
35+
fileUploaderRef.current.clear()
36+
}
37+
},
38+
onError: (error: any) => {
39+
addNotification({
40+
type: 'error',
41+
message: error?.message || 'Failed to upload document',
42+
})
43+
},
44+
})
45+
46+
const handleUpload = () => {
47+
if (files.length > 0) {
48+
uploadMutation.mutate(files[0])
49+
}
50+
}
51+
52+
return (
53+
<div className="fixed inset-0 z-50 flex items-center justify-center p-4 bg-black/30 backdrop-blur-sm transition-opacity">
54+
<div className="bg-white rounded-lg shadow-xl max-w-4xl w-full max-h-[90vh] overflow-hidden flex flex-col">
55+
<div className="flex items-center justify-between p-6 border-b border-gray-200 shrink-0">
56+
<h2 className="text-2xl font-semibold text-gray-800">Knowledge Base</h2>
57+
<button
58+
onClick={onClose}
59+
className="p-2 hover:bg-gray-100 rounded-lg transition-colors"
60+
>
61+
<IconX className="h-6 w-6 text-gray-500" />
62+
</button>
63+
</div>
64+
<div className="overflow-y-auto flex-1 p-6">
65+
<div className="bg-white rounded-lg border shadow-md overflow-hidden">
66+
<div className="p-6">
67+
<FileUploader
68+
ref={fileUploaderRef}
69+
minFiles={1}
70+
maxFiles={1}
71+
onUpload={(uploadedFiles) => {
72+
setFiles(Array.from(uploadedFiles))
73+
}}
74+
/>
75+
<div className="flex justify-center gap-4 my-6">
76+
<StyledButton
77+
variant="primary"
78+
size="lg"
79+
icon="IconUpload"
80+
onClick={handleUpload}
81+
disabled={files.length === 0 || uploadMutation.isPending}
82+
loading={uploadMutation.isPending}
83+
>
84+
Upload
85+
</StyledButton>
86+
</div>
87+
</div>
88+
<div className="border-t bg-white p-6">
89+
<h3 className="text-lg font-semibold text-desert-green mb-4">
90+
Why upload documents to your Knowledge Base?
91+
</h3>
92+
<div className="space-y-3">
93+
<div className="flex items-start gap-3">
94+
<div className="shrink-0 w-6 h-6 rounded-full bg-desert-green text-white flex items-center justify-center text-sm font-bold">
95+
1
96+
</div>
97+
<div>
98+
<p className="font-medium text-desert-stone-dark">
99+
AI Assistant Knowledge Base Integration
100+
</p>
101+
<p className="text-sm text-desert-stone">
102+
When you upload documents to your Knowledge Base, NOMAD processes and embeds
103+
the content, making it directly accessible to the AI Assistant. This allows
104+
the AI Assistant to reference your specific documents during conversations,
105+
providing more accurate and personalized responses based on your uploaded
106+
data.
107+
</p>
108+
</div>
109+
</div>
110+
<div className="flex items-start gap-3">
111+
<div className="shrink-0 w-6 h-6 rounded-full bg-desert-green text-white flex items-center justify-center text-sm font-bold">
112+
2
113+
</div>
114+
<div>
115+
<p className="font-medium text-desert-stone-dark">
116+
Enhanced Document Processing with OCR
117+
</p>
118+
<p className="text-sm text-desert-stone">
119+
NOMAD includes built-in Optical Character Recognition (OCR) capabilities,
120+
allowing it to extract text from image-based documents such as scanned PDFs or
121+
photos. This means that even if your documents are not in a standard text
122+
format, NOMAD can still process and embed their content for AI access.
123+
</p>
124+
</div>
125+
</div>
126+
<div className="flex items-start gap-3">
127+
<div className="shrink-0 w-6 h-6 rounded-full bg-desert-green text-white flex items-center justify-center text-sm font-bold">
128+
3
129+
</div>
130+
<div>
131+
<p className="font-medium text-desert-stone-dark">
132+
Information Library Integration
133+
</p>
134+
<p className="text-sm text-desert-stone">
135+
NOMAD will automatically discover and extract any content you save to your
136+
Information Library (if installed), making it instantly available to the AI
137+
Assistant without any extra steps.
138+
</p>
139+
</div>
140+
</div>
141+
</div>
142+
</div>
143+
</div>
144+
<div className="my-12">
145+
<StyledSectionHeader title="Stored Knowledge Base Files" />
146+
<StyledTable<{ source: string }>
147+
className="font-semibold"
148+
rowLines={true}
149+
columns={[
150+
{
151+
accessor: 'source',
152+
title: 'File Name',
153+
render(record) {
154+
return <span className="text-gray-700">{record.source}</span>
155+
},
156+
},
157+
]}
158+
data={storedFiles.map((source) => ({ source }))}
159+
loading={isLoadingFiles}
160+
/>
161+
</div>
162+
</div>
163+
</div>
164+
</div>
165+
)
166+
}

admin/inertia/pages/home.tsx

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import {
22
IconBolt,
3-
IconBrain,
43
IconHelp,
54
IconMapRoute,
65
IconPlus,
@@ -12,7 +11,6 @@ import AppLayout from '~/layouts/AppLayout'
1211
import { getServiceLink } from '~/lib/navigation'
1312
import { ServiceSlim } from '../../types/services'
1413
import DynamicIcon, { DynamicIconName } from '~/components/DynamicIcon'
15-
import { SERVICE_NAMES } from '../../constants/service_names'
1614
import { useUpdateAvailable } from '~/hooks/useUpdateAvailable'
1715
import Alert from '~/components/Alert'
1816

@@ -84,17 +82,6 @@ interface DashboardItem {
8482
poweredBy: string | null
8583
}
8684

87-
const KNOWLEDGE_BASE_ITEM: DashboardItem = {
88-
label: 'Knowledge Base',
89-
to: '/knowledge-base',
90-
target: '',
91-
description: 'Upload documents to your personal knowledge base for AI access',
92-
icon: <IconBrain size={48} />,
93-
installed: true,
94-
displayOrder: 5,
95-
poweredBy: null,
96-
}
97-
9885
export default function Home(props: {
9986
system: {
10087
services: ServiceSlim[]
@@ -130,9 +117,6 @@ export default function Home(props: {
130117

131118
// Add system items
132119
items.push(...SYSTEM_ITEMS)
133-
if (props.system.services.find((s) => s.service_name === SERVICE_NAMES.OLLAMA && s.installed)) {
134-
items.push(KNOWLEDGE_BASE_ITEM)
135-
}
136120

137121
// Sort all items by display order
138122
items.sort((a, b) => a.displayOrder - b.displayOrder)

admin/start/routes.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@ router.get('/', [HomeController, 'index'])
2727
router.get('/home', [HomeController, 'home'])
2828
router.on('/about').renderInertia('about')
2929
router.get('/chat', [ChatsController, 'inertia'])
30-
router.on('/knowledge-base').renderInertia('knowledge-base')
3130
router.get('/maps', [MapsController, 'index'])
3231

3332
router.get('/easy-setup', [EasySetupController, 'index'])

0 commit comments

Comments
 (0)