UI: kb manag optimize

This commit is contained in:
qixinbo
2026-03-29 15:04:39 +08:00
parent 4d623f798d
commit 2832ca91f3
4 changed files with 583 additions and 603 deletions
@@ -0,0 +1,216 @@
import { useState, useEffect } from 'react';
import { useTranslation } from 'react-i18next';
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Save, Loader2 } from "lucide-react";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import { Switch } from "@/components/ui/switch";
import { useProjectStore } from "@/store/projectStore";
import { api } from "@/lib/api";
export interface KnowledgeBaseFormValues {
name: string;
description: string;
embedding_model: string;
chunk_size: number;
chunk_overlap: number;
top_k: number;
is_active: boolean;
}
export interface KnowledgeBaseFormProps {
initialData?: KnowledgeBaseFormValues | null;
onSubmit: (data: KnowledgeBaseFormValues) => Promise<void>;
onCancel: () => void;
isSubmitting?: boolean;
}
interface EmbeddingModelConfig {
id: string;
name?: string;
provider: string;
model: string;
}
const defaultFormValues: KnowledgeBaseFormValues = {
name: '',
description: '',
embedding_model: '',
chunk_size: 512,
chunk_overlap: 50,
top_k: 3,
is_active: true,
};
export function KnowledgeBaseForm({ initialData, onSubmit, onCancel, isSubmitting = false }: KnowledgeBaseFormProps) {
const { t } = useTranslation();
const { currentProject } = useProjectStore();
const [form, setForm] = useState<KnowledgeBaseFormValues>(initialData || defaultFormValues);
const [embeddingModels, setEmbeddingModels] = useState<EmbeddingModelConfig[]>([]);
const [error, setError] = useState('');
useEffect(() => {
void fetchEmbeddingModels();
}, []);
const fetchEmbeddingModels = async () => {
try {
const data = await api.get<EmbeddingModelConfig[]>('/api/v1/embedding-models');
setEmbeddingModels(data);
} catch (err) {
console.error('Failed to fetch embedding models', err);
}
};
const validate = () => {
if (!currentProject) {
return t('selectProjectBeforeManageKnowledgeBase', 'Please select a project before managing knowledge bases');
}
if (!form.name.trim()) {
return t('knowledgeBaseNameRequired', 'Knowledge base name is required');
}
if (form.chunk_size < 64 || form.chunk_size > 4096) {
return t('knowledgeBaseChunkSizeRange', 'Chunk size must be between 64 and 4096');
}
if (form.chunk_overlap < 0 || form.chunk_overlap > 512) {
return t('knowledgeBaseChunkOverlapRange', 'Chunk overlap must be between 0 and 512');
}
if (form.chunk_overlap >= form.chunk_size) {
return t('knowledgeBaseChunkOverlapTooLarge', 'Chunk overlap must be less than chunk size');
}
if (form.top_k < 1 || form.top_k > 20) {
return t('knowledgeBaseTopKRange', 'Top K must be between 1 and 20');
}
return '';
};
const handleSubmit = async () => {
setError('');
const validationMessage = validate();
if (validationMessage) {
setError(validationMessage);
return;
}
try {
await onSubmit(form);
} catch (err: any) {
setError(err.message || t('knowledgeBaseSaveFailed', 'Failed to save knowledge base'));
}
};
const selectedEmbeddingModel = embeddingModels.find(m => m.id === form.embedding_model);
return (
<div className="space-y-6">
{error && <div className="text-sm text-red-600 bg-red-50 border border-red-100 rounded-md p-3">{error}</div>}
{!currentProject ? (
<div className="text-sm text-amber-700 bg-amber-50 border border-amber-200 rounded-md p-3">
{t('selectProjectBeforeManageKnowledgeBase')}
</div>
) : null}
<div className="grid gap-4 md:grid-cols-2">
<div className="space-y-2 md:col-span-2">
<Label htmlFor="knowledge-base-name">{t('knowledgeBaseName')}</Label>
<Input
id="knowledge-base-name"
value={form.name}
placeholder={t('knowledgeBaseNamePlaceholder')}
onChange={(e) => setForm((prev) => ({ ...prev, name: e.target.value }))}
disabled={!currentProject || isSubmitting}
/>
</div>
<div className="space-y-2 md:col-span-2">
<Label htmlFor="knowledge-base-description">{t('description')}</Label>
<Input
id="knowledge-base-description"
value={form.description}
placeholder={t('knowledgeBaseDescriptionPlaceholder')}
onChange={(e) => setForm((prev) => ({ ...prev, description: e.target.value }))}
disabled={!currentProject || isSubmitting}
/>
</div>
<div className="space-y-2 md:col-span-2">
<Label htmlFor="knowledge-base-embedding-model">{t('knowledgeBaseEmbeddingModel')}</Label>
<Select
value={form.embedding_model}
onValueChange={(val) => setForm((prev) => ({ ...prev, embedding_model: val || '' }))}
disabled={!currentProject || isSubmitting}
>
<SelectTrigger id="knowledge-base-embedding-model">
<SelectValue placeholder={t('knowledgeBaseEmbeddingModelPlaceholder')}>
{selectedEmbeddingModel
? `${selectedEmbeddingModel.name || selectedEmbeddingModel.model} (${selectedEmbeddingModel.provider})`
: form.embedding_model || undefined}
</SelectValue>
</SelectTrigger>
<SelectContent>
{embeddingModels.map((model) => (
<SelectItem key={model.id} value={model.id}>
{model.name || model.model} ({model.provider})
</SelectItem>
))}
</SelectContent>
</Select>
</div>
<div className="space-y-2">
<Label htmlFor="knowledge-base-chunk-size">{t('knowledgeBaseChunkSize')}</Label>
<Input
id="knowledge-base-chunk-size"
type="number"
value={form.chunk_size}
onChange={(e) => setForm((prev) => ({ ...prev, chunk_size: Number(e.target.value) || 0 }))}
disabled={!currentProject || isSubmitting}
/>
</div>
<div className="space-y-2">
<Label htmlFor="knowledge-base-chunk-overlap">{t('knowledgeBaseChunkOverlap')}</Label>
<Input
id="knowledge-base-chunk-overlap"
type="number"
value={form.chunk_overlap}
onChange={(e) => setForm((prev) => ({ ...prev, chunk_overlap: Number(e.target.value) || 0 }))}
disabled={!currentProject || isSubmitting}
/>
</div>
<div className="space-y-2">
<Label htmlFor="knowledge-base-top-k">{t('knowledgeBaseTopK')}</Label>
<Input
id="knowledge-base-top-k"
type="number"
value={form.top_k}
onChange={(e) => setForm((prev) => ({ ...prev, top_k: Number(e.target.value) || 0 }))}
disabled={!currentProject || isSubmitting}
/>
</div>
<div className="flex items-center justify-between rounded-lg border border-border px-3 py-2 mt-7 md:col-span-2">
<Label htmlFor="knowledge-base-active">{t('activeStatus')}</Label>
<Switch
id="knowledge-base-active"
checked={form.is_active}
onCheckedChange={(checked) => setForm((prev) => ({ ...prev, is_active: checked }))}
disabled={!currentProject || isSubmitting}
/>
</div>
</div>
<div className="flex items-center justify-end gap-2 pt-4 border-t border-border">
<Button variant="outline" onClick={onCancel} disabled={isSubmitting}>
{t('cancel')}
</Button>
<Button onClick={handleSubmit} disabled={!currentProject || isSubmitting} className="bg-indigo-600 hover:bg-indigo-700 text-primary-foreground">
{isSubmitting ? <Loader2 className="h-4 w-4 mr-2 animate-spin" /> : <Save className="h-4 w-4 mr-2" />}
{t('save')}
</Button>
</div>
</div>
);
}
+5 -5
View File
@@ -141,7 +141,7 @@
"saveSettings": "Save Settings",
"knowledgeBase": "Knowledge Base",
"knowledgeBaseSettings": "Knowledge Base Configuration",
"knowledgeBaseSettingsDesc": "Create, edit, reindex, and manage knowledge bases in the current project.",
"knowledgeBaseSettingsDesc": "Manage knowledge bases and their documents",
"knowledgeGlobalConfigTitle": "Knowledge Global Configuration",
"knowledgeGlobalConfigDesc": "Configure global API base and key for knowledge service shared across projects.",
"knowledgeGlobalApiBase": "API Base",
@@ -169,15 +169,15 @@
"knowledgeGlobalArkModelRequiredForTest": "For Volcengine Ark, model name is required for connection testing (Model ID or Endpoint ID)",
"saveKnowledgeGlobalConfig": "Save Global Configuration",
"refresh": "Refresh",
"knowledgeBaseName": "Knowledge Base Name",
"knowledgeBaseNamePlaceholder": "Enter knowledge base name",
"knowledgeBaseDescriptionPlaceholder": "Enter knowledge base description (optional)",
"knowledgeBaseName": "Name",
"knowledgeBaseNamePlaceholder": "e.g. Sales Q&A",
"knowledgeBaseDescriptionPlaceholder": "Optional description",
"knowledgeBaseEmbeddingModel": "Embedding Model",
"knowledgeBaseEmbeddingModelPlaceholder": "Select an embedding model",
"knowledgeBaseChunkSize": "Chunk Size",
"knowledgeBaseChunkOverlap": "Chunk Overlap",
"knowledgeBaseTopK": "Top K",
"createKnowledgeBase": "Create Knowledge Base",
"createKnowledgeBase": "New Knowledge Base",
"updateKnowledgeBase": "Update Knowledge Base",
"knowledgeBaseList": "Knowledge Base List",
"knowledgeBaseMeta": "{{count}} docs · Updated {{updatedAt}}",
+4 -4
View File
@@ -154,7 +154,7 @@
"saveSettings": "保存设置",
"knowledgeBase": "知识库",
"knowledgeBaseSettings": "知识库配置与建库管理",
"knowledgeBaseSettingsDesc": "在当前项目下创建、编辑、重建索引并维护知识库配置。",
"knowledgeBaseSettingsDesc": "管理知识库及其文档内容",
"knowledgeGlobalConfigTitle": "知识库全局配置",
"knowledgeGlobalConfigDesc": "配置知识库服务的全局 API 地址与密钥,所有项目共享。",
"knowledgeGlobalApiBase": "API Base",
@@ -183,14 +183,14 @@
"saveKnowledgeGlobalConfig": "保存全局配置",
"refresh": "刷新",
"knowledgeBaseName": "知识库名称",
"knowledgeBaseNamePlaceholder": "请输入知识库名称",
"knowledgeBaseDescriptionPlaceholder": "请输入知识库描述(可选)",
"knowledgeBaseNamePlaceholder": "例如:销售问答库",
"knowledgeBaseDescriptionPlaceholder": "选填,知识库描述",
"knowledgeBaseEmbeddingModel": "Embedding 模型",
"knowledgeBaseEmbeddingModelPlaceholder": "请选择一个嵌入模型",
"knowledgeBaseChunkSize": "Chunk Size",
"knowledgeBaseChunkOverlap": "Chunk Overlap",
"knowledgeBaseTopK": "Top K",
"createKnowledgeBase": "建知识库",
"createKnowledgeBase": "建知识库",
"updateKnowledgeBase": "更新知识库",
"knowledgeBaseList": "知识库列表",
"knowledgeBaseMeta": "文档 {{count}} 个 · 更新时间 {{updatedAt}}",
File diff suppressed because it is too large Load Diff