Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
160 changes: 147 additions & 13 deletions frontend/app/page.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,76 @@
'use client'

import { useState } from 'react'
import { askQuestion, listDocuments, listMessages } from '@/lib/api'
import { useEffect, useState } from 'react'
import {
askQuestion,
getDocument,
listDocuments,
type Document,
type DocumentDetail,
} from '@/lib/api'

function formatDate(value?: string | null) {
if (!value) return ''
const date = new Date(value)
if (Number.isNaN(date.getTime())) return ''
return date.toLocaleDateString('en-AU', {
day: 'numeric',
month: 'short',
year: 'numeric',
})
}

export default function Home() {
const [question, setQuestion] = useState('')
const [answer, setAnswer] = useState('')
const [loading, setLoading] = useState(false)
const [activeTab, setActiveTab] = useState<'ask' | 'documents' | 'messages'>('ask')
const [documents, setDocuments] = useState<Document[]>([])
const [documentsTotal, setDocumentsTotal] = useState(0)
const [documentsLoading, setDocumentsLoading] = useState(false)
const [documentsError, setDocumentsError] = useState('')
const [selectedDocument, setSelectedDocument] = useState<DocumentDetail | null>(null)
const [detailLoading, setDetailLoading] = useState(false)
const [detailError, setDetailError] = useState('')

useEffect(() => {
if (activeTab !== 'documents') return
let cancelled = false
setDocumentsLoading(true)
setDocumentsError('')
listDocuments()
.then((data) => {
if (cancelled) return
setDocuments(data.items)
setDocumentsTotal(data.total)
})
.catch(() => {
if (cancelled) return
setDocuments([])
setDocumentsTotal(0)
setDocumentsError('Could not load documents. Check the backend is running and try again.')
})
.finally(() => {
if (!cancelled) setDocumentsLoading(false)
})
return () => {
cancelled = true
}
}, [activeTab])

const handleSelectDocument = async (doc: Document) => {
setDetailLoading(true)
setDetailError('')
try {
const detail = await getDocument(doc.id)
setSelectedDocument(detail)
} catch {
setSelectedDocument(null)
setDetailError('Could not load this document. Try again.')
} finally {
setDetailLoading(false)
}
}

const handleAsk = async () => {
if (!question.trim()) return
Expand Down Expand Up @@ -135,20 +198,91 @@ export default function Home() {
{activeTab === 'documents' && (
<div className="max-w-3xl mx-auto">
<div className="bg-white rounded-xl shadow-lg p-8">
<h2 className="text-2xl font-semibold text-gray-800 mb-6">
<h2 className="text-2xl font-semibold text-gray-800 mb-2">
Knowledge Base Documents
</h2>
<p className="text-gray-600">
Documents are indexed for retrieval-augmented generation.
Use the API to add new documents to the knowledge base.
<p className="text-gray-600 mb-6">
Indexed documents used for retrieval-augmented generation. Select one to read its full content.
</p>
<div className="mt-6">
<code className="bg-gray-100 p-4 rounded-lg block text-sm text-gray-800">
POST /api/documents
<br />
{`{ "title": "...", "content": "...", "source": "..." }`}
</code>
</div>

{documentsLoading && (
<p className="text-gray-600">Loading documents...</p>
)}

{!documentsLoading && documentsError && (
<div className="p-4 bg-red-50 rounded-lg">
<p className="text-red-700">{documentsError}</p>
</div>
)}

{!documentsLoading && !documentsError && documents.length === 0 && (
<p className="text-gray-600">
No documents yet. Add one with POST /api/documents and it will appear here.
</p>
)}

{!documentsLoading && !documentsError && documents.length > 0 && (
<>
<p className="text-sm text-gray-500 mb-4">
{documentsTotal} {documentsTotal === 1 ? 'document' : 'documents'}
</p>
<ul className="divide-y divide-gray-100">
{documents.map((doc) => (
<li key={doc.id}>
<button
onClick={() => handleSelectDocument(doc)}
className={`w-full text-left py-4 px-3 rounded-lg transition-colors ${
selectedDocument?.id === doc.id
? 'bg-blue-50'
: 'hover:bg-gray-50'
}`}
>
<span className="flex items-center justify-between gap-4">
<span className="font-medium text-gray-800">{doc.title}</span>
<span className="shrink-0 text-xs bg-gray-100 text-gray-600 px-2 py-1 rounded-full">
{doc.source}
</span>
</span>
{formatDate(doc.created_at) && (
<span className="block text-sm text-gray-500 mt-1">
{formatDate(doc.created_at)}
</span>
)}
{doc.content_preview && (
<span className="block text-sm text-gray-600 mt-1 line-clamp-2">
{doc.content_preview}
</span>
)}
</button>
</li>
))}
</ul>
</>
)}

{detailLoading && (
<p className="text-gray-600 mt-6">Loading document...</p>
)}

{!detailLoading && detailError && (
<div className="mt-6 p-4 bg-red-50 rounded-lg">
<p className="text-red-700">{detailError}</p>
</div>
)}

{!detailLoading && !detailError && selectedDocument && (
<div className="mt-6 p-6 bg-blue-50 rounded-lg">
<h3 className="text-lg font-semibold text-gray-800 mb-1">
{selectedDocument.title}
</h3>
<p className="text-sm text-gray-500 mb-3">
{selectedDocument.source}
{formatDate(selectedDocument.created_at) &&
` - ${formatDate(selectedDocument.created_at)}`}
</p>
<p className="text-gray-700 whitespace-pre-wrap">{selectedDocument.content}</p>
</div>
)}
</div>
</div>
)}
Expand Down
Loading