feat: move KB UI into AI Assistant UI

This commit is contained in:
Jake Turner 2026-02-08 13:05:47 -08:00 committed by Jake Turner
parent 8726700a0a
commit 6745dbf3d1
4 changed files with 175 additions and 19 deletions

View File

@ -3,6 +3,8 @@ import StyledButton from '../StyledButton'
import { router } from '@inertiajs/react'
import { ChatSession } from '../../../types/chat'
import { IconMessage } from '@tabler/icons-react'
import { useState } from 'react'
import KnowledgeBaseModal from './KnowledgeBaseModal'
interface ChatSidebarProps {
sessions: ChatSession[]
@ -21,6 +23,8 @@ export default function ChatSidebar({
onClearHistory,
isInModal = false,
}: ChatSidebarProps) {
const [isKnowledgeBaseModalOpen, setIsKnowledgeBaseModalOpen] = useState(false)
return (
<div className="w-64 bg-gray-50 border-r border-gray-200 flex flex-col h-full">
<div className="p-4 border-b border-gray-200 h-[75px] flex items-center justify-center">
@ -48,7 +52,7 @@ export default function ChatSidebar({
<div className="flex items-start gap-2">
<IconMessage
className={classNames(
'h-5 w-5 mt-0.5 flex-shrink-0',
'h-5 w-5 mt-0.5 shrink-0',
activeSessionId === session.id ? 'text-white' : 'text-gray-400'
)}
/>
@ -101,7 +105,7 @@ export default function ChatSidebar({
</StyledButton>
<StyledButton
onClick={() => {
router.visit('/knowledge-base')
setIsKnowledgeBaseModalOpen(true)
}}
icon="IconBrain"
variant="primary"
@ -122,6 +126,9 @@ export default function ChatSidebar({
</StyledButton>
)}
</div>
{isKnowledgeBaseModalOpen && (
<KnowledgeBaseModal onClose={() => setIsKnowledgeBaseModalOpen(false)} />
)}
</div>
)
}

View File

@ -0,0 +1,166 @@
import { useMutation, useQuery } from '@tanstack/react-query'
import { useRef, useState } from 'react'
import FileUploader from '~/components/file-uploader'
import StyledButton from '~/components/StyledButton'
import StyledSectionHeader from '~/components/StyledSectionHeader'
import StyledTable from '~/components/StyledTable'
import { useNotifications } from '~/context/NotificationContext'
import api from '~/lib/api'
import { IconX } from '@tabler/icons-react'
interface KnowledgeBaseModalProps {
onClose: () => void
}
export default function KnowledgeBaseModal({ onClose }: KnowledgeBaseModalProps) {
const { addNotification } = useNotifications()
const [files, setFiles] = useState<File[]>([])
const fileUploaderRef = useRef<React.ComponentRef<typeof FileUploader>>(null)
const { data: storedFiles = [], isLoading: isLoadingFiles } = useQuery({
queryKey: ['storedFiles'],
queryFn: () => api.getStoredRAGFiles(),
select: (data) => data || [],
})
const uploadMutation = useMutation({
mutationFn: (file: File) => api.uploadDocument(file),
onSuccess: (data) => {
addNotification({
type: 'success',
message: data?.message || 'Document uploaded and queued for processing',
})
setFiles([])
if (fileUploaderRef.current) {
fileUploaderRef.current.clear()
}
},
onError: (error: any) => {
addNotification({
type: 'error',
message: error?.message || 'Failed to upload document',
})
},
})
const handleUpload = () => {
if (files.length > 0) {
uploadMutation.mutate(files[0])
}
}
return (
<div className="fixed inset-0 z-50 flex items-center justify-center p-4 bg-black/30 backdrop-blur-sm transition-opacity">
<div className="bg-white rounded-lg shadow-xl max-w-4xl w-full max-h-[90vh] overflow-hidden flex flex-col">
<div className="flex items-center justify-between p-6 border-b border-gray-200 shrink-0">
<h2 className="text-2xl font-semibold text-gray-800">Knowledge Base</h2>
<button
onClick={onClose}
className="p-2 hover:bg-gray-100 rounded-lg transition-colors"
>
<IconX className="h-6 w-6 text-gray-500" />
</button>
</div>
<div className="overflow-y-auto flex-1 p-6">
<div className="bg-white rounded-lg border shadow-md overflow-hidden">
<div className="p-6">
<FileUploader
ref={fileUploaderRef}
minFiles={1}
maxFiles={1}
onUpload={(uploadedFiles) => {
setFiles(Array.from(uploadedFiles))
}}
/>
<div className="flex justify-center gap-4 my-6">
<StyledButton
variant="primary"
size="lg"
icon="IconUpload"
onClick={handleUpload}
disabled={files.length === 0 || uploadMutation.isPending}
loading={uploadMutation.isPending}
>
Upload
</StyledButton>
</div>
</div>
<div className="border-t bg-white p-6">
<h3 className="text-lg font-semibold text-desert-green mb-4">
Why upload documents to your Knowledge Base?
</h3>
<div className="space-y-3">
<div className="flex items-start gap-3">
<div className="shrink-0 w-6 h-6 rounded-full bg-desert-green text-white flex items-center justify-center text-sm font-bold">
1
</div>
<div>
<p className="font-medium text-desert-stone-dark">
AI Assistant Knowledge Base Integration
</p>
<p className="text-sm text-desert-stone">
When you upload documents to your Knowledge Base, NOMAD processes and embeds
the content, making it directly accessible to the AI Assistant. This allows
the AI Assistant to reference your specific documents during conversations,
providing more accurate and personalized responses based on your uploaded
data.
</p>
</div>
</div>
<div className="flex items-start gap-3">
<div className="shrink-0 w-6 h-6 rounded-full bg-desert-green text-white flex items-center justify-center text-sm font-bold">
2
</div>
<div>
<p className="font-medium text-desert-stone-dark">
Enhanced Document Processing with OCR
</p>
<p className="text-sm text-desert-stone">
NOMAD includes built-in Optical Character Recognition (OCR) capabilities,
allowing it to extract text from image-based documents such as scanned PDFs or
photos. This means that even if your documents are not in a standard text
format, NOMAD can still process and embed their content for AI access.
</p>
</div>
</div>
<div className="flex items-start gap-3">
<div className="shrink-0 w-6 h-6 rounded-full bg-desert-green text-white flex items-center justify-center text-sm font-bold">
3
</div>
<div>
<p className="font-medium text-desert-stone-dark">
Information Library Integration
</p>
<p className="text-sm text-desert-stone">
NOMAD will automatically discover and extract any content you save to your
Information Library (if installed), making it instantly available to the AI
Assistant without any extra steps.
</p>
</div>
</div>
</div>
</div>
</div>
<div className="my-12">
<StyledSectionHeader title="Stored Knowledge Base Files" />
<StyledTable<{ source: string }>
className="font-semibold"
rowLines={true}
columns={[
{
accessor: 'source',
title: 'File Name',
render(record) {
return <span className="text-gray-700">{record.source}</span>
},
},
]}
data={storedFiles.map((source) => ({ source }))}
loading={isLoadingFiles}
/>
</div>
</div>
</div>
</div>
)
}

View File

@ -1,6 +1,5 @@
import {
IconBolt,
IconBrain,
IconHelp,
IconMapRoute,
IconPlus,
@ -12,7 +11,6 @@ import AppLayout from '~/layouts/AppLayout'
import { getServiceLink } from '~/lib/navigation'
import { ServiceSlim } from '../../types/services'
import DynamicIcon, { DynamicIconName } from '~/components/DynamicIcon'
import { SERVICE_NAMES } from '../../constants/service_names'
import { useUpdateAvailable } from '~/hooks/useUpdateAvailable'
import Alert from '~/components/Alert'
@ -84,17 +82,6 @@ interface DashboardItem {
poweredBy: string | null
}
const KNOWLEDGE_BASE_ITEM: DashboardItem = {
label: 'Knowledge Base',
to: '/knowledge-base',
target: '',
description: 'Upload documents to your personal knowledge base for AI access',
icon: <IconBrain size={48} />,
installed: true,
displayOrder: 5,
poweredBy: null,
}
export default function Home(props: {
system: {
services: ServiceSlim[]
@ -130,9 +117,6 @@ export default function Home(props: {
// Add system items
items.push(...SYSTEM_ITEMS)
if (props.system.services.find((s) => s.service_name === SERVICE_NAMES.OLLAMA && s.installed)) {
items.push(KNOWLEDGE_BASE_ITEM)
}
// Sort all items by display order
items.sort((a, b) => a.displayOrder - b.displayOrder)

View File

@ -27,7 +27,6 @@ router.get('/', [HomeController, 'index'])
router.get('/home', [HomeController, 'home'])
router.on('/about').renderInertia('about')
router.get('/chat', [ChatsController, 'inertia'])
router.on('/knowledge-base').renderInertia('knowledge-base')
router.get('/maps', [MapsController, 'index'])
router.get('/easy-setup', [EasySetupController, 'index'])