UI: kb manag optimize
This commit is contained in:
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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}}",
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user