feat: rebuild support workspace frontend
This commit is contained in:
@@ -1,15 +1,19 @@
|
||||
import * as React from 'react'
|
||||
import { Plus, Search } from 'lucide-react'
|
||||
import { BookOpen, Grid2X2, List, Plus, Search } from 'lucide-react'
|
||||
|
||||
import { AppCreateDialog } from '@/components/app/AppCreateDialog'
|
||||
import { AppDataTable, type AppDataTableColumn } from '@/components/app/AppDataTable'
|
||||
import { AppEmptyState } from '@/components/app/AppEmptyState'
|
||||
import { AppField } from '@/components/app/AppField'
|
||||
import { AppPage } from '@/components/app/AppPage'
|
||||
import { AppPageHeader } from '@/components/app/AppPageHeader'
|
||||
import { AppSection } from '@/components/app/AppSection'
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from '@/components/ui/card'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { InputGroup, InputGroupAddon, InputGroupInput } from '@/components/ui/input-group'
|
||||
import { Textarea } from '@/components/ui/textarea'
|
||||
import { useAgentPageContext } from '@/features/agent-workspace/AgentWorkspaceProvider'
|
||||
import { formatDateTime } from '@/lib/format'
|
||||
|
||||
import { createKnowledgeBase, listKnowledgeBases } from './api'
|
||||
@@ -64,7 +68,18 @@ const columns: AppDataTableColumn<KnowledgeBase>[] = [
|
||||
export function KnowledgeBaseListPage() {
|
||||
const [bases, setBases] = React.useState<KnowledgeBase[]>([])
|
||||
const [form, setForm] = React.useState<KnowledgeBaseForm>(emptyForm)
|
||||
const [createOpen, setCreateOpen] = React.useState(false)
|
||||
const [creating, setCreating] = React.useState(false)
|
||||
const [createError, setCreateError] = React.useState('')
|
||||
const [error, setError] = React.useState('')
|
||||
const [query, setQuery] = React.useState('')
|
||||
const [viewMode, setViewMode] = React.useState<'grid' | 'list'>('grid')
|
||||
const visibleBases = React.useMemo(() => {
|
||||
const keyword = query.trim().toLowerCase()
|
||||
return keyword ? bases.filter((base) => `${base.name} ${base.description} ${base.scope}`.toLowerCase().includes(keyword)) : bases
|
||||
}, [bases, query])
|
||||
const agentContext = React.useMemo(() => ({ routeKey: 'knowledge', entityType: 'knowledge-base-list', title: `知识库 · ${bases.length} 个`, status: error ? '加载异常' : '已同步页面数据', metadata: { 启用: String(bases.filter((base) => base.enabled).length), 当前显示: String(visibleBases.length) } }), [bases, error, visibleBases.length])
|
||||
useAgentPageContext(agentContext)
|
||||
|
||||
async function load() {
|
||||
try {
|
||||
@@ -81,34 +96,66 @@ export function KnowledgeBaseListPage() {
|
||||
|
||||
async function handleCreate(event: React.FormEvent<HTMLFormElement>) {
|
||||
event.preventDefault()
|
||||
await createKnowledgeBase(form)
|
||||
setForm(emptyForm)
|
||||
await load()
|
||||
setCreating(true)
|
||||
setCreateError('')
|
||||
try {
|
||||
await createKnowledgeBase(form)
|
||||
setForm(emptyForm)
|
||||
setCreateOpen(false)
|
||||
await load()
|
||||
} catch (reason) {
|
||||
setCreateError(reason instanceof Error ? reason.message : '创建知识库失败')
|
||||
} finally {
|
||||
setCreating(false)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<AppPage contentClassName="gap-5">
|
||||
<AppPageHeader
|
||||
<AppPage
|
||||
contentClassName="gap-5"
|
||||
header={<AppPageHeader
|
||||
title="知识库"
|
||||
description="集中管理支持知识、文档范围与检索入口"
|
||||
actions={
|
||||
<div className="flex gap-2">
|
||||
<Button variant="outline" onClick={() => (window.location.hash = '/knowledge/search')}>
|
||||
<Search data-icon="inline-start" />
|
||||
知识检索
|
||||
</Button>
|
||||
<Button onClick={() => (window.location.hash = '/knowledge/items/new')}>
|
||||
<Button variant="outline" onClick={() => (window.location.hash = '/knowledge/items/new')}>
|
||||
<Plus data-icon="inline-start" />
|
||||
新建知识条目
|
||||
</Button>
|
||||
<Button onClick={() => setCreateOpen(true)}>
|
||||
<Plus data-icon="inline-start" />
|
||||
新增知识库
|
||||
</Button>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
/>}
|
||||
>
|
||||
|
||||
<AppSection title="新建知识库">
|
||||
<form className="grid gap-4 md:grid-cols-[1fr_1fr_1fr_auto] md:items-end" onSubmit={handleCreate}>
|
||||
<div className="grid grid-cols-2 gap-3 lg:grid-cols-3">
|
||||
{[['知识库总数', bases.length], ['已启用', bases.filter((base) => base.enabled).length], ['当前结果', visibleBases.length]].map(([label, value], index) => <Card key={String(label)} className={index === 2 ? 'hidden lg:flex' : ''}><CardContent className="flex items-center gap-3 px-4"><span className="flex size-9 items-center justify-center rounded-lg bg-primary/10 text-primary"><BookOpen className="size-4" /></span><span><span className="block text-xs text-muted-foreground">{label}</span><strong className="mt-1 block text-2xl font-semibold tabular-nums">{value}</strong></span></CardContent></Card>)}
|
||||
</div>
|
||||
|
||||
<Card className="py-0"><CardContent className="flex flex-col gap-3 p-3 sm:flex-row sm:items-center"><InputGroup className="flex-1"><InputGroupAddon><Search /></InputGroupAddon><InputGroupInput value={query} onChange={(event) => setQuery(event.target.value)} placeholder="搜索知识库、范围或描述" /></InputGroup><div className="flex rounded-lg border bg-muted/30 p-0.5"><Button variant={viewMode === 'grid' ? 'secondary' : 'ghost'} size="icon-sm" aria-label="卡片视图" onClick={() => setViewMode('grid')}><Grid2X2 /></Button><Button variant={viewMode === 'list' ? 'secondary' : 'ghost'} size="icon-sm" aria-label="列表视图" onClick={() => setViewMode('list')}><List /></Button></div></CardContent></Card>
|
||||
|
||||
<AppCreateDialog
|
||||
open={createOpen}
|
||||
onOpenChange={(open) => { setCreateOpen(open); if (!open) setCreateError('') }}
|
||||
title="新增知识库"
|
||||
description="按业务领域建立知识边界,后续可持续维护文档和知识条目。"
|
||||
submitLabel="创建知识库"
|
||||
onSubmit={handleCreate}
|
||||
pending={creating}
|
||||
error={createError}
|
||||
>
|
||||
<div className="grid gap-5">
|
||||
<AppField label="名称" htmlFor="knowledge-base-name" required>
|
||||
<Input
|
||||
id="knowledge-base-name"
|
||||
autoFocus
|
||||
required
|
||||
value={form.name}
|
||||
onChange={(event) => setForm((current) => ({ ...current, name: event.target.value }))}
|
||||
@@ -123,29 +170,30 @@ export function KnowledgeBaseListPage() {
|
||||
placeholder="例如:数据采集"
|
||||
/>
|
||||
</AppField>
|
||||
<AppField label="描述" htmlFor="knowledge-base-description">
|
||||
<Input
|
||||
<AppField label="描述" htmlFor="knowledge-base-description" description="简要说明适用场景,方便支持人员判断是否使用。">
|
||||
<Textarea
|
||||
id="knowledge-base-description"
|
||||
value={form.description}
|
||||
onChange={(event) => setForm((current) => ({ ...current, description: event.target.value }))}
|
||||
placeholder="知识库用途说明"
|
||||
/>
|
||||
</AppField>
|
||||
<Button type="submit">创建知识库</Button>
|
||||
</form>
|
||||
</AppSection>
|
||||
</div>
|
||||
</AppCreateDialog>
|
||||
|
||||
{error ? <AppEmptyState title="无法加载知识库" description={error} /> : null}
|
||||
{!error ? (
|
||||
{!error && viewMode === 'list' ? (
|
||||
<AppDataTable
|
||||
columns={columns}
|
||||
rows={bases}
|
||||
rows={visibleBases}
|
||||
getRowKey={(base) => base.id}
|
||||
emptyTitle="暂无知识库"
|
||||
emptyDescription="创建知识库后,可继续维护文档、知识条目和检索内容。"
|
||||
footer={<p className="text-sm text-muted-foreground">共 {bases.length} 个知识库</p>}
|
||||
footer={<p className="text-sm text-muted-foreground">当前显示 {visibleBases.length} 个知识库</p>}
|
||||
/>
|
||||
) : null}
|
||||
{!error && viewMode === 'grid' ? <div className="grid gap-3 md:grid-cols-2 xl:grid-cols-3">{visibleBases.map((base) => <Card key={base.id} className="app-card-hover"><CardHeader><div className="flex items-start justify-between gap-3"><span className="flex size-9 items-center justify-center rounded-lg bg-primary/10 text-primary"><BookOpen className="size-4" /></span><Badge variant={base.enabled ? 'success' : 'secondary'}>{base.enabled ? '已启用' : '已停用'}</Badge></div><CardTitle className="mt-2">{base.name}</CardTitle><CardDescription className="line-clamp-2 min-h-10">{base.description || '暂无描述'}</CardDescription></CardHeader><CardContent><Badge variant="outline">{base.scope || '未设置范围'}</Badge></CardContent><CardFooter className="justify-between text-xs text-muted-foreground"><span>{formatDateTime(base.updated_at)}</span><Button variant="ghost" size="sm" onClick={() => (window.location.hash = '/knowledge/search')}>检索<Search data-icon="inline-end" /></Button></CardFooter></Card>)}</div> : null}
|
||||
{!error && visibleBases.length === 0 ? <AppEmptyState title="未找到知识库" description="请调整搜索关键词,或创建新的知识库。" /> : null}
|
||||
</AppPage>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user