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 { Card, CardContent, CardHeader, CardTitle, CardDescription, CardFooter } from "@/components/ui/card"; import { Save, Loader2, Database, RefreshCw, Pencil, Trash2, FileText, Plus } from "lucide-react"; import { api } from "@/lib/api"; import { useAuthStore } from "@/store/authStore"; import { useProjectStore } from "@/store/projectStore"; import { Switch } from "@/components/ui/switch"; import { Textarea } from "@/components/ui/textarea"; interface KnowledgeBase { id: string; name: string; description?: string; project_id?: number | null; embedding_model?: string | null; chunk_size: number; chunk_overlap: number; top_k: number; is_active: boolean; updated_at: string; documents?: Array<{ id: string }>; } interface KnowledgeBaseForm { name: string; description: string; embedding_model: string; chunk_size: number; chunk_overlap: number; top_k: number; is_active: boolean; } interface KnowledgeDocument { id: string; title: string; content: string; metadata?: Record; created_at: string; updated_at: string; } interface KnowledgeGlobalConfig { api_base?: string | null; api_key?: string | null; api_key_masked?: string | null; has_api_key: boolean; default_embedding_model?: string | null; } interface KnowledgeConnectionTestResult { success: boolean; message: string; model_name?: string | null; embedding_dimension?: number | null; resolved_api_base?: string | null; available_models?: string[]; } const defaultKnowledgeBaseForm: KnowledgeBaseForm = { name: '', description: '', embedding_model: '', chunk_size: 512, chunk_overlap: 50, top_k: 3, is_active: true, }; const defaultKnowledgeDocumentForm = { title: '', content: '', metadata: '', }; export function Settings() { const { t } = useTranslation(); const { user, updateUser } = useAuthStore(); const { currentProject } = useProjectStore(); const [email, setEmail] = useState(''); const [password, setPassword] = useState(''); const [confirmPassword, setConfirmPassword] = useState(''); const [isSaving, setIsSaving] = useState(false); const [isLoadingKnowledgeBases, setIsLoadingKnowledgeBases] = useState(false); const [isSavingKnowledgeBase, setIsSavingKnowledgeBase] = useState(false); const [deletingKnowledgeBaseId, setDeletingKnowledgeBaseId] = useState(''); const [reindexingKnowledgeBaseId, setReindexingKnowledgeBaseId] = useState(''); const [knowledgeBases, setKnowledgeBases] = useState([]); const [editingKnowledgeBaseId, setEditingKnowledgeBaseId] = useState(''); const [knowledgeBaseForm, setKnowledgeBaseForm] = useState(defaultKnowledgeBaseForm); const [knowledgeGlobalConfig, setKnowledgeGlobalConfig] = useState({ api_base: '', api_key: null, api_key_masked: null, has_api_key: false, default_embedding_model: '', }); const [knowledgeGlobalForm, setKnowledgeGlobalForm] = useState({ api_base: '', api_key: '', default_embedding_model: '', }); const [isLoadingKnowledgeGlobalConfig, setIsLoadingKnowledgeGlobalConfig] = useState(false); const [isSavingKnowledgeGlobalConfig, setIsSavingKnowledgeGlobalConfig] = useState(false); const [isTestingKnowledgeGlobalConnection, setIsTestingKnowledgeGlobalConnection] = useState(false); const [knowledgeConnectionTestResult, setKnowledgeConnectionTestResult] = useState(null); const [selectedKnowledgeBaseId, setSelectedKnowledgeBaseId] = useState(''); const [knowledgeDocuments, setKnowledgeDocuments] = useState([]); const [isLoadingKnowledgeDocuments, setIsLoadingKnowledgeDocuments] = useState(false); const [isSavingKnowledgeDocument, setIsSavingKnowledgeDocument] = useState(false); const [deletingKnowledgeDocumentId, setDeletingKnowledgeDocumentId] = useState(''); const [editingKnowledgeDocumentId, setEditingKnowledgeDocumentId] = useState(''); const [knowledgeDocumentForm, setKnowledgeDocumentForm] = useState(defaultKnowledgeDocumentForm); const [uploadingKnowledgeDocuments, setUploadingKnowledgeDocuments] = useState(false); const [knowledgeUploadFiles, setKnowledgeUploadFiles] = useState([]); const [error, setError] = useState(''); const [success, setSuccess] = useState(''); useEffect(() => { if (user) { setEmail(user.email || ''); } }, [user]); useEffect(() => { void fetchKnowledgeBases(); }, [currentProject?.id]); useEffect(() => { void fetchKnowledgeGlobalConfig(); }, []); const isPasswordMismatch = password !== '' && confirmPassword !== '' && password !== confirmPassword; const fetchKnowledgeGlobalConfig = async () => { setIsLoadingKnowledgeGlobalConfig(true); try { const data = await api.get('/api/v1/knowledge-bases/global-config'); setKnowledgeGlobalConfig(data); setKnowledgeGlobalForm({ api_base: data.api_base || '', api_key: '', default_embedding_model: data.default_embedding_model || '', }); } catch (err: any) { setError(err.message || t('knowledgeGlobalConfigLoadFailed')); } finally { setIsLoadingKnowledgeGlobalConfig(false); } }; const validateKnowledgeGlobalConfig = () => { const normalizedApiBase = knowledgeGlobalForm.api_base.trim(); if (!normalizedApiBase) { return ''; } if (!(normalizedApiBase.startsWith('http://') || normalizedApiBase.startsWith('https://'))) { return t('knowledgeGlobalConfigApiBaseInvalid'); } if (normalizedApiBase.toLowerCase().endsWith('/embeddings')) { return t('knowledgeGlobalConfigApiBaseShouldBeBaseUrl'); } return ''; }; const validateKnowledgeGlobalModelName = () => { const normalizedModelName = knowledgeGlobalForm.default_embedding_model.trim(); if (!normalizedModelName) { return ''; } if (normalizedModelName.length > 200) { return t('knowledgeGlobalModelNameTooLong'); } return ''; }; const handleSaveKnowledgeGlobalConfig = async () => { setError(''); setSuccess(''); const validationMessage = validateKnowledgeGlobalConfig(); if (validationMessage) { setError(validationMessage); return; } const modelValidationMessage = validateKnowledgeGlobalModelName(); if (modelValidationMessage) { setError(modelValidationMessage); return; } setIsSavingKnowledgeGlobalConfig(true); try { const payload: Record = { api_base: knowledgeGlobalForm.api_base.trim() || null, default_embedding_model: knowledgeGlobalForm.default_embedding_model.trim() || null, }; const normalizedApiKey = knowledgeGlobalForm.api_key.trim(); if (normalizedApiKey) { payload.api_key = normalizedApiKey; } const data = await api.put('/api/v1/knowledge-bases/global-config', payload); setKnowledgeGlobalConfig(data); setKnowledgeGlobalForm({ api_base: data.api_base || '', api_key: '', default_embedding_model: data.default_embedding_model || '', }); setKnowledgeConnectionTestResult(null); setSuccess(t('knowledgeGlobalConfigSaved')); } catch (err: any) { setError(err.message || t('knowledgeGlobalConfigSaveFailed')); } finally { setIsSavingKnowledgeGlobalConfig(false); } }; const handleTestKnowledgeGlobalConnection = async () => { setError(''); setSuccess(''); setKnowledgeConnectionTestResult(null); const validationMessage = validateKnowledgeGlobalConfig(); if (validationMessage) { setError(validationMessage); return; } const modelValidationMessage = validateKnowledgeGlobalModelName(); if (modelValidationMessage) { setError(modelValidationMessage); return; } const normalizedModelName = knowledgeGlobalForm.default_embedding_model.trim(); if (!normalizedModelName) { setError(t('knowledgeGlobalModelNameRequiredForTest')); return; } setIsTestingKnowledgeGlobalConnection(true); try { const payload: Record = {}; const normalizedApiBase = knowledgeGlobalForm.api_base.trim(); const normalizedApiKey = knowledgeGlobalForm.api_key.trim(); if (normalizedApiBase) payload.api_base = normalizedApiBase; if (normalizedApiKey) payload.api_key = normalizedApiKey; if (normalizedModelName) payload.model_name = normalizedModelName; const result = await api.post('/api/v1/knowledge-bases/global-config/test-connection', payload); setKnowledgeConnectionTestResult(result); setSuccess(t('knowledgeGlobalConnectionTestPassed')); } catch (err: any) { setError(err.message || t('knowledgeGlobalConnectionTestFailed')); } finally { setIsTestingKnowledgeGlobalConnection(false); } }; const fetchKnowledgeBases = async () => { if (!currentProject) { setKnowledgeBases([]); return; } setIsLoadingKnowledgeBases(true); try { const data = await api.get(`/api/v1/knowledge-bases?project_id=${currentProject.id}`); setKnowledgeBases(data); if (editingKnowledgeBaseId && !data.find((item) => item.id === editingKnowledgeBaseId)) { setEditingKnowledgeBaseId(''); setKnowledgeBaseForm(defaultKnowledgeBaseForm); } if (selectedKnowledgeBaseId && !data.find((item) => item.id === selectedKnowledgeBaseId)) { setSelectedKnowledgeBaseId(''); setKnowledgeDocuments([]); setEditingKnowledgeDocumentId(''); setKnowledgeDocumentForm(defaultKnowledgeDocumentForm); } } catch (err: any) { setError(err.message || t('knowledgeBaseLoadFailed')); } finally { setIsLoadingKnowledgeBases(false); } }; const resetKnowledgeBaseForm = () => { setEditingKnowledgeBaseId(''); setKnowledgeBaseForm(defaultKnowledgeBaseForm); }; const validateKnowledgeBaseForm = () => { if (!currentProject) { return t('selectProjectBeforeManageKnowledgeBase'); } if (!knowledgeBaseForm.name.trim()) { return t('knowledgeBaseNameRequired'); } if (knowledgeBaseForm.chunk_size < 64 || knowledgeBaseForm.chunk_size > 4096) { return t('knowledgeBaseChunkSizeRange'); } if (knowledgeBaseForm.chunk_overlap < 0 || knowledgeBaseForm.chunk_overlap > 512) { return t('knowledgeBaseChunkOverlapRange'); } if (knowledgeBaseForm.chunk_overlap >= knowledgeBaseForm.chunk_size) { return t('knowledgeBaseChunkOverlapTooLarge'); } if (knowledgeBaseForm.top_k < 1 || knowledgeBaseForm.top_k > 20) { return t('knowledgeBaseTopKRange'); } return ''; }; const handleSaveKnowledgeBase = async () => { setError(''); setSuccess(''); const validationMessage = validateKnowledgeBaseForm(); if (validationMessage) { setError(validationMessage); return; } if (!currentProject) return; setIsSavingKnowledgeBase(true); try { const payload = { name: knowledgeBaseForm.name.trim(), description: knowledgeBaseForm.description.trim() || null, embedding_model: knowledgeBaseForm.embedding_model.trim() || null, chunk_size: knowledgeBaseForm.chunk_size, chunk_overlap: knowledgeBaseForm.chunk_overlap, top_k: knowledgeBaseForm.top_k, is_active: knowledgeBaseForm.is_active, project_id: currentProject.id, }; if (editingKnowledgeBaseId) { await api.put(`/api/v1/knowledge-bases/${editingKnowledgeBaseId}`, payload); setSuccess(t('knowledgeBaseUpdated')); } else { await api.post('/api/v1/knowledge-bases', payload); setSuccess(t('knowledgeBaseCreated')); } await fetchKnowledgeBases(); resetKnowledgeBaseForm(); } catch (err: any) { setError(err.message || t('knowledgeBaseSaveFailed')); } finally { setIsSavingKnowledgeBase(false); } }; const handleEditKnowledgeBase = (item: KnowledgeBase) => { setEditingKnowledgeBaseId(item.id); setKnowledgeBaseForm({ name: item.name || '', description: item.description || '', embedding_model: item.embedding_model || '', chunk_size: item.chunk_size, chunk_overlap: item.chunk_overlap, top_k: item.top_k, is_active: item.is_active, }); }; const handleDeleteKnowledgeBase = async (id: string) => { if (!window.confirm(t('confirmDeleteKnowledgeBase'))) { return; } setError(''); setSuccess(''); setDeletingKnowledgeBaseId(id); try { await api.delete(`/api/v1/knowledge-bases/${id}`); setSuccess(t('knowledgeBaseDeleted')); if (editingKnowledgeBaseId === id) { resetKnowledgeBaseForm(); } if (selectedKnowledgeBaseId === id) { setSelectedKnowledgeBaseId(''); setKnowledgeDocuments([]); setEditingKnowledgeDocumentId(''); setKnowledgeDocumentForm(defaultKnowledgeDocumentForm); } await fetchKnowledgeBases(); } catch (err: any) { setError(err.message || t('knowledgeBaseDeleteFailed')); } finally { setDeletingKnowledgeBaseId(''); } }; const handleReindexKnowledgeBase = async (id: string) => { setError(''); setSuccess(''); setReindexingKnowledgeBaseId(id); try { await api.post(`/api/v1/knowledge-bases/${id}/reindex`, {}); setSuccess(t('knowledgeBaseReindexSuccess')); } catch (err: any) { setError(err.message || t('knowledgeBaseReindexFailed')); } finally { setReindexingKnowledgeBaseId(''); } }; const resetKnowledgeDocumentForm = () => { setEditingKnowledgeDocumentId(''); setKnowledgeDocumentForm(defaultKnowledgeDocumentForm); }; const fetchKnowledgeDocuments = async (kbId: string) => { if (!kbId) { setKnowledgeDocuments([]); return; } setIsLoadingKnowledgeDocuments(true); try { const data = await api.get(`/api/v1/knowledge-bases/${kbId}/documents`); setKnowledgeDocuments(data); if (editingKnowledgeDocumentId && !data.find((item) => item.id === editingKnowledgeDocumentId)) { resetKnowledgeDocumentForm(); } } catch (err: any) { setError(err.message || t('knowledgeDocumentLoadFailed')); } finally { setIsLoadingKnowledgeDocuments(false); } }; const handleOpenKnowledgeDocuments = async (kbId: string) => { if (selectedKnowledgeBaseId === kbId) { setSelectedKnowledgeBaseId(''); setKnowledgeDocuments([]); resetKnowledgeDocumentForm(); return; } setSelectedKnowledgeBaseId(kbId); resetKnowledgeDocumentForm(); await fetchKnowledgeDocuments(kbId); }; const validateKnowledgeDocumentForm = () => { if (!selectedKnowledgeBaseId) { return t('selectKnowledgeBaseToManageDocuments'); } if (!knowledgeDocumentForm.title.trim()) { return t('knowledgeDocumentTitleRequired'); } if (!knowledgeDocumentForm.content.trim()) { return t('knowledgeDocumentContentRequired'); } const metadataText = knowledgeDocumentForm.metadata.trim(); if (!metadataText) { return ''; } try { JSON.parse(metadataText); return ''; } catch { return t('knowledgeDocumentMetadataInvalid'); } }; const handleSaveKnowledgeDocument = async () => { setError(''); setSuccess(''); const validationMessage = validateKnowledgeDocumentForm(); if (validationMessage) { setError(validationMessage); return; } if (!selectedKnowledgeBaseId) return; setIsSavingKnowledgeDocument(true); try { const metadataText = knowledgeDocumentForm.metadata.trim(); const payload = { title: knowledgeDocumentForm.title.trim(), content: knowledgeDocumentForm.content.trim(), metadata: metadataText ? JSON.parse(metadataText) : {}, }; if (editingKnowledgeDocumentId) { await api.put(`/api/v1/knowledge-bases/${selectedKnowledgeBaseId}/documents/${editingKnowledgeDocumentId}`, payload); setSuccess(t('knowledgeDocumentUpdated')); } else { await api.post(`/api/v1/knowledge-bases/${selectedKnowledgeBaseId}/documents`, payload); setSuccess(t('knowledgeDocumentCreated')); } await fetchKnowledgeDocuments(selectedKnowledgeBaseId); await fetchKnowledgeBases(); resetKnowledgeDocumentForm(); } catch (err: any) { setError(err.message || t('knowledgeDocumentSaveFailed')); } finally { setIsSavingKnowledgeDocument(false); } }; const handleEditKnowledgeDocument = (item: KnowledgeDocument) => { setEditingKnowledgeDocumentId(item.id); setKnowledgeDocumentForm({ title: item.title || '', content: item.content || '', metadata: item.metadata && Object.keys(item.metadata).length > 0 ? JSON.stringify(item.metadata, null, 2) : '', }); }; const handleDeleteKnowledgeDocument = async (docId: string) => { if (!selectedKnowledgeBaseId) return; if (!window.confirm(t('confirmDeleteKnowledgeDocument'))) { return; } setError(''); setSuccess(''); setDeletingKnowledgeDocumentId(docId); try { await api.delete(`/api/v1/knowledge-bases/${selectedKnowledgeBaseId}/documents/${docId}`); if (editingKnowledgeDocumentId === docId) { resetKnowledgeDocumentForm(); } setSuccess(t('knowledgeDocumentDeleted')); await fetchKnowledgeDocuments(selectedKnowledgeBaseId); await fetchKnowledgeBases(); } catch (err: any) { setError(err.message || t('knowledgeDocumentDeleteFailed')); } finally { setDeletingKnowledgeDocumentId(''); } }; const handleUploadKnowledgeDocuments = async () => { setError(''); setSuccess(''); if (!selectedKnowledgeBaseId) { setError(t('selectKnowledgeBaseToManageDocuments')); return; } if (knowledgeUploadFiles.length === 0) { setError(t('knowledgeDocumentUploadEmpty')); return; } setUploadingKnowledgeDocuments(true); try { const formData = new FormData(); knowledgeUploadFiles.forEach((file) => formData.append('files', file)); await api.post(`/api/v1/knowledge-bases/${selectedKnowledgeBaseId}/documents/upload`, formData); setSuccess(t('knowledgeDocumentUploadSuccess', { count: knowledgeUploadFiles.length })); setKnowledgeUploadFiles([]); await fetchKnowledgeDocuments(selectedKnowledgeBaseId); await fetchKnowledgeBases(); } catch (err: any) { setError(err.message || t('knowledgeDocumentUploadFailed')); } finally { setUploadingKnowledgeDocuments(false); } }; const handleSave = async () => { setError(''); setSuccess(''); if (isPasswordMismatch) { setError(t('passwordsDoNotMatch')); return; } setIsSaving(true); try { const updateData: any = { email: email }; if (password) { updateData.password = password; } if (user && user.id) { const response = await api.put(`/api/v1/users/${user.id}`, updateData); let successMsg = t('personalSettingsSaved'); if (password) { successMsg = t('personalSettingsAndPasswordSaved'); } setSuccess(successMsg); setPassword(''); setConfirmPassword(''); // Update global state with new email updateUser({ email: response.email }); } } catch (error: any) { console.error("Failed to save settings", error); setError(error.message || t('failedToSaveSettings')); } finally { setIsSaving(false); } }; const selectedKnowledgeBase = knowledgeBases.find((item) => item.id === selectedKnowledgeBaseId) || null; return (
{t('personalSettings')}
{error &&
{error}
} {success &&
{success}
} {t('accountInfo')} {t('modifyLoginEmailAndPassword')}

{t('usernameCannotBeModified')}

setEmail(e.target.value)} />
{ setPassword(e.target.value); setError(''); }} />
{ setConfirmPassword(e.target.value); setError(''); }} /> {isPasswordMismatch &&

{t('passwordsDoNotMatch')}

}
{t('knowledgeBaseSettings')} {t('knowledgeBaseSettingsDesc')}
{t('knowledgeGlobalConfigTitle')}
{t('knowledgeGlobalConfigDesc')}
setKnowledgeGlobalForm((prev) => ({ ...prev, api_base: e.target.value }))} disabled={isLoadingKnowledgeGlobalConfig || isSavingKnowledgeGlobalConfig} />
setKnowledgeGlobalForm((prev) => ({ ...prev, api_key: e.target.value }))} disabled={isLoadingKnowledgeGlobalConfig || isSavingKnowledgeGlobalConfig} />
{knowledgeGlobalConfig.has_api_key ? t('knowledgeGlobalApiKeyMasked', { masked: knowledgeGlobalConfig.api_key_masked || '******' }) : t('knowledgeGlobalApiKeyEmpty')}
setKnowledgeGlobalForm((prev) => ({ ...prev, default_embedding_model: e.target.value }))} disabled={isLoadingKnowledgeGlobalConfig || isSavingKnowledgeGlobalConfig || isTestingKnowledgeGlobalConnection} />
{t('knowledgeGlobalModelNameHint')}
{knowledgeConnectionTestResult ? (
{knowledgeConnectionTestResult.message}
{knowledgeConnectionTestResult.model_name ? (
{t('knowledgeGlobalConnectionModelResult', { model: knowledgeConnectionTestResult.model_name })}
) : null} {typeof knowledgeConnectionTestResult.embedding_dimension === 'number' ? (
{t('knowledgeGlobalConnectionDimensionResult', { dim: knowledgeConnectionTestResult.embedding_dimension })}
) : null} {knowledgeConnectionTestResult.available_models && knowledgeConnectionTestResult.available_models.length > 0 ? (
{t('knowledgeGlobalConnectionAvailableModelsResult', { models: knowledgeConnectionTestResult.available_models.slice(0, 5).join(', ') })}
) : null}
) : null}
{!currentProject ? (
{t('selectProjectBeforeManageKnowledgeBase')}
) : null}
setKnowledgeBaseForm((prev) => ({ ...prev, name: e.target.value }))} disabled={!currentProject} />
setKnowledgeBaseForm((prev) => ({ ...prev, description: e.target.value }))} disabled={!currentProject} />
setKnowledgeBaseForm((prev) => ({ ...prev, embedding_model: e.target.value }))} disabled={!currentProject} />
setKnowledgeBaseForm((prev) => ({ ...prev, chunk_size: Number(e.target.value) || 0 }))} disabled={!currentProject} />
setKnowledgeBaseForm((prev) => ({ ...prev, chunk_overlap: Number(e.target.value) || 0 }))} disabled={!currentProject} />
setKnowledgeBaseForm((prev) => ({ ...prev, top_k: Number(e.target.value) || 0 }))} disabled={!currentProject} />
setKnowledgeBaseForm((prev) => ({ ...prev, is_active: checked }))} disabled={!currentProject} />
{editingKnowledgeBaseId ? ( ) : null}
{t('knowledgeBaseList')}
{isLoadingKnowledgeBases ? (
) : knowledgeBases.length === 0 ? (
{t('noKnowledgeBases')}
) : (
{knowledgeBases.map((item) => (
{item.name}
{t('knowledgeBaseMeta', { count: item.documents?.length || 0, updatedAt: new Date(item.updated_at).toLocaleString(), })}
{item.description ? (
{item.description}
) : null}
))}
)}
{selectedKnowledgeBase ? t('knowledgeDocumentManagerTitle', { name: selectedKnowledgeBase.name }) : t('knowledgeDocumentManagerTitleEmpty')}
{!selectedKnowledgeBase ? (
{t('selectKnowledgeBaseToManageDocuments')}
) : (
{t('knowledgeDocumentUploadTitle')}
setKnowledgeUploadFiles(Array.from(e.target.files || []))} disabled={uploadingKnowledgeDocuments} />
{t('knowledgeDocumentUploadHint')}
{knowledgeUploadFiles.length > 0 ? t('knowledgeDocumentUploadSelected', { count: knowledgeUploadFiles.length }) : t('knowledgeDocumentUploadNone')}
setKnowledgeDocumentForm((prev) => ({ ...prev, title: e.target.value }))} disabled={isSavingKnowledgeDocument} />