feat(knowledge-base): Knowledge Base UI — browse/edit, modal, project links
- Knowledge Bases list page + sidebar entry + "+ New" create modal - Detail page with a recursive structure tree: add/edit/delete topics, categories and facts inline, including name + description editing - Create/metadata-edit modal (title, description) - Project edit modal gains a link/remove knowledge base section - Types and routes for /knowledge-bases and /knowledge-bases/:id - Scoped .kb-* styles (contained panel, topic cards, hierarchy guides) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
104
src/components/KnowledgeBaseFormModal.tsx
Normal file
104
src/components/KnowledgeBaseFormModal.tsx
Normal file
@@ -0,0 +1,104 @@
|
||||
import { useEffect, useState } from 'react'
|
||||
import api from '@/services/api'
|
||||
import type { KnowledgeBase } from '@/types'
|
||||
|
||||
type Props = {
|
||||
isOpen: boolean
|
||||
onClose: () => void
|
||||
onSaved?: (kb: KnowledgeBase) => void | Promise<void>
|
||||
knowledgeBase?: KnowledgeBase | null
|
||||
}
|
||||
|
||||
type FormState = {
|
||||
title: string
|
||||
description: string
|
||||
}
|
||||
|
||||
const emptyForm: FormState = { title: '', description: '' }
|
||||
|
||||
export default function KnowledgeBaseFormModal({ isOpen, onClose, onSaved, knowledgeBase }: Props) {
|
||||
const [saving, setSaving] = useState(false)
|
||||
const [error, setError] = useState('')
|
||||
const [form, setForm] = useState<FormState>(emptyForm)
|
||||
|
||||
useEffect(() => {
|
||||
if (!isOpen) return
|
||||
setError('')
|
||||
if (knowledgeBase) {
|
||||
setForm({ title: knowledgeBase.title, description: knowledgeBase.description || '' })
|
||||
} else {
|
||||
setForm(emptyForm)
|
||||
}
|
||||
}, [isOpen, knowledgeBase])
|
||||
|
||||
const submit = async (e: React.FormEvent) => {
|
||||
e.preventDefault()
|
||||
setSaving(true)
|
||||
setError('')
|
||||
try {
|
||||
if (knowledgeBase) {
|
||||
const { data } = await api.patch<KnowledgeBase>(`/knowledge-bases/${knowledgeBase.id}`, {
|
||||
title: form.title,
|
||||
description: form.description || null,
|
||||
})
|
||||
await onSaved?.(data)
|
||||
} else {
|
||||
const { data } = await api.post<KnowledgeBase>('/knowledge-bases', {
|
||||
title: form.title,
|
||||
description: form.description || null,
|
||||
})
|
||||
await onSaved?.(data)
|
||||
}
|
||||
onClose()
|
||||
} catch (err: any) {
|
||||
setError(err?.response?.data?.detail || 'Failed to save knowledge base')
|
||||
} finally {
|
||||
setSaving(false)
|
||||
}
|
||||
}
|
||||
|
||||
if (!isOpen) return null
|
||||
|
||||
return (
|
||||
<div className="modal-overlay" onClick={onClose}>
|
||||
<div className="modal" onClick={(e) => e.stopPropagation()}>
|
||||
<div className="modal-header">
|
||||
<h3>{knowledgeBase ? 'Edit Knowledge Base' : 'Create Knowledge Base'}</h3>
|
||||
<button type="button" className="btn-secondary" onClick={onClose}>✕</button>
|
||||
</div>
|
||||
|
||||
<form className="task-create-form" onSubmit={submit}>
|
||||
<label>
|
||||
Title
|
||||
<input
|
||||
data-testid="kb-title-input"
|
||||
required
|
||||
placeholder="Knowledge base title"
|
||||
value={form.title}
|
||||
onChange={(e) => setForm((f) => ({ ...f, title: e.target.value }))}
|
||||
/>
|
||||
</label>
|
||||
|
||||
<label>
|
||||
Description
|
||||
<textarea
|
||||
data-testid="kb-description-input"
|
||||
placeholder="Description (optional)"
|
||||
value={form.description}
|
||||
onChange={(e) => setForm((f) => ({ ...f, description: e.target.value }))}
|
||||
/>
|
||||
</label>
|
||||
|
||||
{error && <p className="error-text" style={{ color: 'var(--danger, #e5534b)' }}>{error}</p>}
|
||||
|
||||
<div className="modal-actions">
|
||||
<button type="submit" className="btn-primary" disabled={saving}>
|
||||
{saving ? 'Saving...' : (knowledgeBase ? 'Save' : 'Create')}
|
||||
</button>
|
||||
<button type="button" className="btn-secondary" onClick={onClose} disabled={saving}>Cancel</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user