import React, { useState, useEffect } from 'react'; import { ModelConfig, ModelType, AppSettings, KnowledgeGroup } from '../../types'; import { useLanguage } from '../../contexts/LanguageContext'; import { X, Plus, Trash2, Edit2, Save, Cpu, Box, Loader2, User, Shield, Key, LogOut, Globe, Settings as SettingsIcon, ToggleLeft, ToggleRight, Database, Sparkles, ChevronRight, Lock, Building2, BookOpen, UserCircle, HardDrive, LayoutGrid } from 'lucide-react'; import { motion, AnimatePresence } from 'framer-motion'; import { userService } from '../../services/userService'; // import { settingsService } from '../../services/settingsService'; import { userSettingService } from '../../services/userSettingService'; import { knowledgeGroupService } from '../../services/knowledgeGroupService'; import { useConfirm } from '../../contexts/ConfirmContext'; import { useToast } from '../../contexts/ToastContext'; interface SettingsViewProps { // Model Props models: ModelConfig[]; authToken: string | null; onUpdateModels: (action: 'create' | 'update' | 'delete', model: ModelConfig) => Promise; isAdmin?: boolean; // Added isAdmin prop currentUser?: any; // Added current user prop initialTab?: TabType; } type TabType = 'general' | 'user' | 'model' | 'tenants' | 'knowledge_base'; export const SettingsView: React.FC = ({ models, authToken, onUpdateModels, isAdmin = false, currentUser, initialTab = 'general', }) => { const { t, language, setLanguage } = useLanguage(); const { confirm } = useConfirm(); const { showError, showSuccess } = useToast(); const [activeTab, setActiveTab] = useState('general'); const [isLoading, setIsLoading] = useState(false); const [error, setError] = useState(null); // --- Model Manager State --- const [editingId, setEditingId] = useState(null); const [modelFormData, setModelFormData] = useState>({ type: ModelType.LLM, baseUrl: 'http://localhost:11434/v1', modelId: 'llama3', name: '', dimensions: 1536, apiKey: '', maxInputTokens: 8191, maxBatchSize: 2048 }); // --- User Management State --- interface UserType { id: string; username: string; isAdmin: boolean; role?: string; tenantId?: string; createdAt: string; tenantMembers?: Array<{ tenantId: string; role: string; tenant?: { id: string; name: string } }>; } const [users, setUsers] = useState([]); const [isUserLoading, setIsUserLoading] = useState(false); const [showAddUser, setShowAddUser] = useState(false); const [newUser, setNewUser] = useState({ username: '', password: '', role: 'USER' }); const [userSuccess, setUserSuccess] = useState(''); // --- Change Password State --- const [passwordForm, setPasswordForm] = useState({ current: '', new: '', confirm: '' }); const [passwordSuccess, setPasswordSuccess] = useState(''); // --- App Settings State --- const [appSettings, setAppSettings] = useState(null); const [knowledgeGroups, setKnowledgeGroups] = useState([]); const [isSettingsLoading, setIsSettingsLoading] = useState(false); const [enabledModelIds, setEnabledModelIds] = useState([]); // --- Tenant Admin Binding Search State --- const [bindingTenantId, setBindingTenantId] = useState(null); const [userSearchQuery, setUserSearchQuery] = useState(''); // --- Manage Members Modal State --- const [managingMembersTenantId, setManagingMembersTenantId] = useState(null); const [tenantMembers, setTenantMembers] = useState([]); const [memberUserSearch, setMemberUserSearch] = useState(''); const [isMembersLoading, setIsMembersLoading] = useState(false); useEffect(() => { if (initialTab) { setActiveTab(initialTab); } }, [initialTab]); // ユーザー一覧の取得(ユーザータブがアクティブな場合) useEffect(() => { if (activeTab === 'user') { fetchUsers(); } else if (activeTab === 'general') { fetchSettingsAndGroups(); } else if (activeTab === 'model' && (isAdmin || currentUser?.role === 'SUPER_ADMIN')) { // Model tab initialization } else if (activeTab === 'tenants' && currentUser?.role === 'SUPER_ADMIN') { fetchTenantsData(); fetchUsers(); // Ensure users are loaded for admin binding } else if (activeTab === 'knowledge_base' && (currentUser?.role === 'TENANT_ADMIN' || currentUser?.role === 'SUPER_ADMIN')) { fetchKnowledgeBaseSettings(); } }, [activeTab, currentUser]); const [kbSettings, setKbSettings] = useState(null); const [localKbSettings, setLocalKbSettings] = useState(null); const [isSavingKbSettings, setIsSavingKbSettings] = useState(false); const fetchKnowledgeBaseSettings = async () => { if (!authToken) return; setIsLoading(true); try { const res = await fetch('/api/v1/admin/settings', { headers: { 'Authorization': `Bearer ${authToken}` } }); if (res.ok) { const data = await res.json(); setKbSettings(data); setLocalKbSettings(data); } } catch (error) { console.error(error); } finally { setIsLoading(false); } }; const handleUpdateKbSettings = (key: string, value: any) => { setLocalKbSettings((prev: any) => ({ ...prev, [key]: value })); }; const handleSaveKbSettings = async () => { if (!authToken || !localKbSettings) return; setIsSavingKbSettings(true); try { const res = await fetch('/api/v1/admin/settings', { method: 'PUT', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${authToken}` }, body: JSON.stringify(localKbSettings) }); if (res.ok) { setKbSettings(localKbSettings); showSuccess('Knowledge Base settings saved'); } else { showError('Failed to save settings'); } } catch (error) { showError('Error saving settings'); } finally { setIsSavingKbSettings(false); } }; const handleCancelKbSettings = () => { setLocalKbSettings(kbSettings); }; const fetchSettingsAndGroups = async () => { if (!authToken) return; setIsSettingsLoading(true); try { const [settings, groups] = await Promise.all([ userSettingService.get(authToken), knowledgeGroupService.getGroups() ]); setAppSettings(settings); setKnowledgeGroups(groups); } catch (error) { console.error('Failed to fetch settings or groups:', error); } finally { setIsSettingsLoading(false); } }; // --- 一般タブのハンドラー --- const handleChangePassword = async (e: React.FormEvent) => { e.preventDefault(); setError(null); setPasswordSuccess(''); if (passwordForm.new !== passwordForm.confirm) { setError(t('passwordMismatch')); return; } if (passwordForm.new.length < 6) { setError(t('newPasswordMinLength')); return; } setIsLoading(true); try { await userService.changePassword(passwordForm.current, passwordForm.new); setPasswordSuccess(t('passwordChangeSuccess')); setPasswordForm({ current: '', new: '', confirm: '' }); } catch (err: any) { setError(err.message || t('passwordChangeFailed')); } finally { setIsLoading(false); } }; // --- ユーザータブのハンドラー --- const fetchUsers = async () => { setIsUserLoading(true); try { const userList = await userService.getUsers(); setUsers(userList); } catch (error: any) { setError(error.message || t('getUserListFailed')); } finally { setIsUserLoading(false); } }; const handleCreateUser = async (e: React.FormEvent) => { e.preventDefault(); setError(''); setUserSuccess(''); if (newUser.password.length < 6) { setError(t('passwordMinLength')); return; } try { await userService.createUser(newUser.username, newUser.password, newUser.role); setUserSuccess(t('userCreatedSuccess')); setNewUser({ username: '', password: '', role: 'USER' }); setShowAddUser(false); fetchUsers(); } catch (error: any) { setError(error.message || t('createUserFailed')); } }; const [passwordChangeUserData, setPasswordChangeUserData] = useState<{ userId: string, newPassword: string } | null>(null); // --- Edit Role State --- const [roleChangeUserData, setRoleChangeUserData] = useState<{ userId: string, newRole: string } | null>(null); const handleToggleUserAdmin = async (userId: string, newAdminStatus: boolean) => { try { await userService.updateUser(userId, newAdminStatus); // ユーザーリストを再取得 fetchUsers(); setUserSuccess(newAdminStatus ? t('userPromotedToAdmin') : t('userDemotedFromAdmin')); } catch (error: any) { setError(error.message || t('updateUserFailed')); } }; const handleUserPasswordChange = async () => { if (!passwordChangeUserData || !passwordChangeUserData.newPassword) return; try { // Update user password await userService.updateUserInfo(passwordChangeUserData.userId, { password: passwordChangeUserData.newPassword }); setUserSuccess(t('passwordChangeSuccess')); setPasswordChangeUserData(null); fetchUsers(); // Refresh the user list } catch (error: any) { setError(error.message || t('passwordChangeFailed')); } }; // --- Tenants Management State (Migrated) --- const [stats, setStats] = useState({ tenants: 0, users: 0 }); const [tenants, setTenants] = useState([]); const [tenantAdmins, setTenantAdmins] = useState([]); const [isTenantsLoading, setIsTenantsLoading] = useState(false); const [showCreateTenant, setShowCreateTenant] = useState(false); const [editingTenant, setEditingTenant] = useState(null); const [newTenant, setNewTenant] = useState({ name: '', domain: '', adminUserId: '' }); const fetchTenantMembers = async (tenantId: string) => { setIsMembersLoading(true); try { const headers = { 'Authorization': `Bearer ${authToken}`, 'x-api-key': authToken || '' }; const res = await fetch(`/api/v1/tenants/${tenantId}/members`, { headers }); if (res.ok) { setTenantMembers(await res.json()); } } catch (e) { console.error(e); } finally { setIsMembersLoading(false); } }; const handleAddMember = async (tenantId: string, userId: string) => { try { const headers = { 'Content-Type': 'application/json', 'Authorization': `Bearer ${authToken}`, 'x-api-key': authToken || '' }; const res = await fetch(`/api/v1/tenants/${tenantId}/members`, { method: 'POST', headers, body: JSON.stringify({ userId, role: 'USER' }), }); if (res.ok) { showSuccess('User added to organization'); fetchTenantMembers(tenantId); fetchTenantsData(); fetchUsers(); } else { const errData = await res.json().catch(() => ({})); showError(errData.message || 'Failed to add member'); } } catch (e) { showError('Error adding member'); } }; const handleRemoveMember = async (tenantId: string, userId: string) => { try { const headers = { 'Authorization': `Bearer ${authToken}`, 'x-api-key': authToken || '' }; const res = await fetch(`/api/v1/tenants/${tenantId}/members/${userId}`, { method: 'DELETE', headers, }); if (res.ok || res.status === 204) { showSuccess('User removed from organization'); fetchTenantMembers(tenantId); fetchTenantsData(); fetchUsers(); } else { showError('Failed to remove member'); } } catch (e) { showError('Error removing member'); } }; const fetchTenantsData = async () => { setIsTenantsLoading(true); try { const headers = { 'Authorization': `Bearer ${authToken}`, 'x-api-key': authToken || '' }; const [tenRes, admRes] = await Promise.all([ fetch('/api/v1/tenants', { headers }), fetch('/api/v1/admin/users', { headers }) ]); if (tenRes.ok) { const data: any[] = await tenRes.json(); // Fetch settings for each tenant in parallel and merge as settings_obj const withSettings = await Promise.all( data.map(async (t) => { try { const sRes = await fetch(`/api/tenants/${t.id}/settings`, { headers }); const settings_obj = sRes.ok ? await sRes.json() : null; return { ...t, settings_obj }; } catch { return { ...t, settings_obj: null }; } }) ); setTenants(withSettings); setStats(s => ({ ...s, tenants: withSettings.length })); } if (admRes.ok) { const data = await admRes.json(); setTenantAdmins(data.filter((u: any) => u.role === 'TENANT_ADMIN' || u.role === 'SUPER_ADMIN')); setStats(s => ({ ...s, users: data.length })); } } catch (e) { console.error(e); } finally { setIsTenantsLoading(false); } }; const handleCreateTenant = async (e: React.FormEvent) => { e.preventDefault(); try { if (editingTenant) { const res = await fetch(`/api/v1/tenants/${editingTenant.id}`, { method: 'PUT', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${authToken}`, 'x-api-key': authToken || '' }, body: JSON.stringify({ name: newTenant.name, domain: newTenant.domain }) }); if (res.ok) { setEditingTenant(null); setNewTenant({ name: '', domain: '', adminUserId: '' }); fetchTenantsData(); showSuccess('Tenant updated successfully'); } else { showError('Failed to update tenant'); } } else { const res = await fetch('/api/v1/tenants', { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${authToken}`, 'x-api-key': authToken || '' }, body: JSON.stringify(newTenant) }); if (res.ok) { setShowCreateTenant(false); setNewTenant({ name: '', domain: '', adminUserId: '' }); fetchTenantsData(); showSuccess('Tenant created successfully'); } else { showError('Failed to create tenant'); } } } catch (e) { showError('Error processing tenant'); } }; const handleDeleteTenant = async (tenantId: string) => { if (!(await confirm('Are you sure you want to delete this tenant? All associated data will be removed.'))) return; try { const res = await fetch(`/api/v1/tenants/${tenantId}`, { method: 'DELETE', headers: { 'Authorization': `Bearer ${authToken}`, 'x-api-key': authToken || '' } }); if (res.ok) { showSuccess('Tenant deleted successfully'); fetchTenantsData(); } else { showError('Failed to delete tenant'); } } catch (e) { showError('Error deleting tenant'); } }; const handleBindAdmin = async (tenantId: string, userId: string): Promise => { if (!userId) return false; try { const res = await fetch(`/api/v1/tenants/${tenantId}/admin`, { method: 'PUT', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${authToken}`, 'x-api-key': authToken || '' }, body: JSON.stringify({ userId }) }); if (res.ok) { showSuccess('Admin bound successfully'); // Refresh tenants data and users to ensure all states are in sync await Promise.all([ fetchTenantsData(), fetchUsers() ]); return true; } else { showError('Failed to bind admin'); return false; } } catch (e) { showError('Error binding admin'); return false; } }; const handleToggleNotebookFeature = async (tenantId: string, currentEnabled: boolean) => { try { const res = await fetch(`/api/tenants/${tenantId}/settings`, { method: 'PUT', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${authToken}`, 'x-api-key': authToken || '' }, body: JSON.stringify({ isNotebookEnabled: !currentEnabled }) }); if (res.ok) { showSuccess('Feature updated successfully'); fetchTenantsData(); } else { showError('Failed to update feature'); } } catch (e) { showError('Failed to update feature'); } }; const handleUserRoleChange = async () => { if (!roleChangeUserData) return; try { await userService.updateUserInfo(roleChangeUserData.userId, { role: roleChangeUserData.newRole }); showSuccess('Role updated successfully.'); setRoleChangeUserData(null); fetchUsers(); } catch (error: any) { showError(error.response?.data?.message || 'Failed to update user role.'); } }; const handleDeleteUser = async (userId: string) => { if (!confirm(t('confirmDeleteUser') || "Are you sure you want to delete this user?")) return; try { await userService.deleteUser(userId); showSuccess(t('userDeletedSuccessfully')); fetchUsers(); } catch (error: any) { showError(error.message || t('deleteUserFailed')); } }; // --- モデルタブのハンドラー --- const handleSaveModel = async () => { if (!authToken) { setError(t('mmErrorNotAuthenticated')); return; } setError(null); if (!modelFormData.name?.trim()) { setError(t('mmErrorNameRequired')); return; } if (!modelFormData.modelId?.trim()) { setError(t('mmErrorModelIdRequired')); return; } if (!modelFormData.baseUrl?.trim()) { setError(t('mmErrorBaseUrlRequired')); return; } setIsLoading(true); try { const saveData = { ...modelFormData } as ModelConfig; await onUpdateModels(editingId === 'new' ? 'create' : 'update', saveData); setEditingId(null); } catch (err: any) { setError(err.message || t('errorGeneric')); } finally { setIsLoading(false); } }; const handleToggleModel = async (model: ModelConfig) => { if (currentUser?.role === 'TENANT_ADMIN') { const newEnabledIds = enabledModelIds.includes(model.id) ? enabledModelIds.filter(id => id !== model.id) : [...enabledModelIds, model.id]; try { await fetch('/api/v1/admin/settings', { method: 'PUT', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${authToken}` }, body: JSON.stringify({ enabledModelIds: newEnabledIds }) }); setEnabledModelIds(newEnabledIds); showSuccess('Settings updated successfully'); } catch (error) { console.error(error); showError(t('errorGeneric')); } return; } try { await onUpdateModels('update', { ...model, isEnabled: !model.isEnabled }); } catch (error) { console.error('Failed to toggle model:', error); } }; const handleDeleteModel = async (id: string) => { if (await confirm(t('confirmClear'))) { await onUpdateModels('delete', { id } as ModelConfig); } }; const getTypeLabel = (type: ModelType) => { switch (type) { case ModelType.LLM: return t('typeLLM'); case ModelType.EMBEDDING: return t('typeEmbedding'); case ModelType.RERANK: return t('typeRerank'); } }; // --- レンダリング関数 --- const renderGeneralTab = () => (
{/* パスワード変更セクション */}

{t('changePassword')}

setPasswordForm({ ...passwordForm, current: e.target.value })} className="w-full px-3 py-2 text-sm border border-slate-300 rounded-md focus:ring-2 focus:ring-blue-500 outline-none" required />
setPasswordForm({ ...passwordForm, new: e.target.value })} className="w-full px-3 py-2 text-sm border border-slate-300 rounded-md focus:ring-2 focus:ring-blue-500 outline-none" required />
setPasswordForm({ ...passwordForm, confirm: e.target.value })} className="w-full px-3 py-2 text-sm border border-slate-300 rounded-md focus:ring-2 focus:ring-blue-500 outline-none" required />
{passwordSuccess &&

{passwordSuccess}

}
{/* 语言设置セクション */}

{t('languageSettings')}

); const renderUserTab = () => (

{t('userList')}

{t('sidebarDesc')}

{currentUser?.role === 'SUPER_ADMIN' && ( )}
{showAddUser && (
setNewUser({ ...newUser, username: e.target.value })} className="w-full px-4 py-3 bg-slate-50 border border-slate-200 rounded-2xl text-sm font-medium focus:ring-4 focus:ring-indigo-500/10 focus:border-indigo-500/50 outline-none transition-all" required /> setNewUser({ ...newUser, password: e.target.value })} className="w-full px-4 py-3 bg-slate-50 border border-slate-200 rounded-2xl text-sm font-medium focus:ring-4 focus:ring-indigo-500/10 focus:border-indigo-500/50 outline-none transition-all" required />
{currentUser?.role === 'SUPER_ADMIN' ? ( ) : ( {t('creatingRegularUser')} )}
)} {passwordChangeUserData && (

{t('changeUserPassword')}

{ e.preventDefault(); handleUserPasswordChange(); }} className="space-y-6">
setPasswordChangeUserData({ ...passwordChangeUserData, newPassword: e.target.value })} className="w-full px-4 py-3.5 bg-slate-50 border border-slate-200 rounded-2xl text-[14px] font-medium transition-all focus:outline-none focus:ring-4 focus:ring-indigo-500/10 focus:border-indigo-500/50" placeholder={t('enterNewPassword')} required />
)} {/* Edit Role Modal */} {roleChangeUserData && (

{t('editUserRole')}

{ e.preventDefault(); handleUserRoleChange(); }} className="space-y-6">
)}
{users.map((user, index) => { let IconComponent = User; let iconColors = 'bg-slate-50 text-slate-400'; if (user.role === 'SUPER_ADMIN') { IconComponent = Shield; iconColors = 'bg-red-50 text-red-600'; } else if (user.isAdmin || user.role === 'TENANT_ADMIN') { IconComponent = Shield; iconColors = 'bg-indigo-50 text-indigo-600'; } return (

{user.username}

{(user.role === 'SUPER_ADMIN' || user.isAdmin) && {user.role === 'SUPER_ADMIN' ? 'SUPER' : 'ADMIN'}}

{t('createdAt')}: {new Date(user.createdAt).toLocaleDateString()}

{user.tenantMembers && user.tenantMembers.length > 0 && (
{user.tenantMembers .filter((m: any) => m.tenant?.name !== 'Default') .map((m: any) => ( {m.tenant?.name || m.tenantId} ))}
)}
{user.username !== 'admin' && ( <> {currentUser?.role === 'SUPER_ADMIN' && user.role !== 'SUPER_ADMIN' && ( )} {user.id !== currentUser?.id && ( )} )}
); })}
); const renderTenantsTab = () => (
Total Tenants

{stats.tenants}

System Users

{stats.users}

System Health

Operational

Organization Management

Global tenant list and control

{tenants.map(t => ( ))}
Name Domain Admin Members Features Created Actions
{t.name} {t.domain || '-'} {(() => { // Search admin in tenant members - the source of truth for multi-tenancy const adminMember = t.members?.find((m: any) => m.role === 'TENANT_ADMIN' || m.role === 'SUPER_ADMIN'); const adminUser = adminMember?.user; if (adminUser) { return (
{adminUser.username} {t.name !== 'Default' && ( )}
); } else { return (
{t.name !== 'Default' ? ( ) : ( System Restricted )} None
); } })()}
{/* Members count + manage button */}
{(t.members || []).filter((m: any) => m.role !== 'SUPER_ADMIN').length} users {t.name !== 'Default' && ( )}
{t.name !== 'Default' ? ( ) : (
)}
{new Date(t.createdAt).toLocaleDateString()} {t.name !== 'Default' && (
)}
{(showCreateTenant || editingTenant) && (

{editingTenant ? 'Edit Organization' : 'Create New Tenant'}

setNewTenant({ ...newTenant, name: e.target.value })} required /> setNewTenant({ ...newTenant, domain: e.target.value })} /> {!editingTenant && (

Selecting a user will promote them to Tenant Admin for this organization.

)}
)} {/* Bind Admin Search Modal */} {bindingTenantId && (

Bind Tenant Admin

Search and select a user to manage {tenants.find(t => t.id === bindingTenantId)?.name}

setUserSearchQuery(e.target.value)} />
{users .filter(u => (u.role === 'TENANT_ADMIN' || u.isAdmin) && u.role !== 'SUPER_ADMIN' && u.username.toLowerCase().includes(userSearchQuery.toLowerCase()) ) .map(u => ( ))} {users.filter(u => (u.role === 'TENANT_ADMIN' || u.isAdmin) && u.role !== 'SUPER_ADMIN' && u.username.toLowerCase().includes(userSearchQuery.toLowerCase()) ).length === 0 && (

No unassigned users found

)}
)}
{/* Manage Members Modal */} {managingMembersTenantId && (() => { const tenant = tenants.find(t => t.id === managingMembersTenantId); const memberUserIds = new Set(tenantMembers.map((m: any) => m.userId)); const availableUsers = users.filter(u => !memberUserIds.has(u.id) && u.role !== 'SUPER_ADMIN' && u.username.toLowerCase().includes(memberUserSearch.toLowerCase()) ); return (

Manage Members

Organization: {tenant?.name}

{/* Current Members Section */}

Current Members ({tenantMembers.length})

{isMembersLoading ? (
) : tenantMembers.length === 0 ? (

No members yet

) : (
{tenantMembers.map((m: any) => (

{m.user?.username || m.userId}

{m.role}

))}
)}
{/* Add Users Section */}

Add Users

setMemberUserSearch(e.target.value)} />
{availableUsers.length === 0 ? (

{memberUserSearch ? 'No matching users' : 'All users already added'}

) : (
{availableUsers.map(u => ( ))}
)}
); })()}
); const renderKnowledgeBaseTab = () => (
{localKbSettings && ( <> {/* Save/Cancel Bar */}
{/* Model Configuration */}
Model Configuration
{/* Indexing & Chunking Configuration */}
Indexing & Chunking Configuration
{localKbSettings.chunkSize || 1000}
handleUpdateKbSettings('chunkSize', parseInt(e.target.value))} className="w-full h-2 bg-slate-100 rounded-lg appearance-none cursor-pointer accent-indigo-600" />
{localKbSettings.chunkOverlap || 100}
handleUpdateKbSettings('chunkOverlap', parseInt(e.target.value))} className="w-full h-2 bg-slate-100 rounded-lg appearance-none cursor-pointer accent-indigo-600" />
{/* Chat Hyperparameters */}
Chat Hyperparameters
{localKbSettings.temperature}
handleUpdateKbSettings('temperature', parseFloat(e.target.value))} className="w-full h-2 bg-slate-100 rounded-lg appearance-none cursor-pointer accent-indigo-600" />
Precise Creative
handleUpdateKbSettings('maxTokens', parseInt(e.target.value))} className="w-full px-4 py-3.5 bg-slate-50 border border-slate-200 rounded-2xl text-sm font-medium outline-none focus:ring-4 focus:ring-indigo-500/10 transition-all" />
{/* Retrieval & Search Settings */}
Retrieval & Search Settings
{localKbSettings.topK}
handleUpdateKbSettings('topK', parseInt(e.target.value))} className="w-full h-2 bg-slate-100 rounded-lg appearance-none cursor-pointer accent-indigo-600" />
{localKbSettings.similarityThreshold}
handleUpdateKbSettings('similarityThreshold', parseFloat(e.target.value))} className="w-full h-2 bg-slate-100 rounded-lg appearance-none cursor-pointer accent-indigo-600" />
Enable Hybrid Search
Combine vector and full-text search results.
{localKbSettings.enableFullTextSearch && (
{localKbSettings.hybridVectorWeight || 0.5}
handleUpdateKbSettings('hybridVectorWeight', parseFloat(e.target.value))} className="w-full h-2 bg-indigo-100 rounded-lg appearance-none cursor-pointer accent-indigo-600" />
Pure Text Pure Vector
)}
Enable Query Expansion
Rewrites query for better recall.
Enable HyDE
Hypothetical Document Embeddings.
Enable Reranking
Re-score search results for higher accuracy.
{localKbSettings.enableRerank && (
{localKbSettings.rerankSimilarityThreshold || 0.5}
handleUpdateKbSettings('rerankSimilarityThreshold', parseFloat(e.target.value))} className="w-full h-2 bg-indigo-100 rounded-lg appearance-none cursor-pointer accent-indigo-600" />
Broad Strict
)}
)}
); const renderModelTab = () => (

{t('mmTitle')}

{t('sidebarDesc')}

{!editingId && currentUser?.role === 'SUPER_ADMIN' && ( )}
{editingId ? (

{editingId === 'new' ? t('mmAddBtn') : t('mmEdit')}

setModelFormData({ ...modelFormData, name: e.target.value })} disabled={isLoading} />
setModelFormData({ ...modelFormData, modelId: e.target.value })} disabled={isLoading} />
setModelFormData({ ...modelFormData, baseUrl: e.target.value })} disabled={isLoading} />
setModelFormData({ ...modelFormData, apiKey: e.target.value })} disabled={isLoading} placeholder={t('mmFormApiKeyPlaceholder')} />
{modelFormData.type === ModelType.EMBEDDING && (
setModelFormData({ ...modelFormData, maxInputTokens: parseInt(e.target.value) })} />
setModelFormData({ ...modelFormData, dimensions: parseInt(e.target.value) })} />
)}
) : (
{models.map((model, index) => ( {/* Subtle background pattern/glow */}

{model.name}

{getTypeLabel(model.type)} {model.isDefault && ( Default )}

{model.modelId}

{/* Additional info grid */}
{model.type === ModelType.EMBEDDING && ( <>
Dims {model.dimensions || '-'}
Ctx {model.maxInputTokens || '-'}
)} {model.type === ModelType.LLM && (
Base API {model.baseUrl}
)}
Configured
{currentUser?.role === 'SUPER_ADMIN' && ( <> )}
))} {models.length === 0 && (

{t('mmEmpty')}

)}
)}
); return (
{/* Settings Sidebar */}

{t('tabSettings')}

{(currentUser?.role === 'TENANT_ADMIN' || currentUser?.role === 'SUPER_ADMIN' || isAdmin) && ( <> )} {currentUser?.role === 'SUPER_ADMIN' && ( )}
{/* Content Area */}

{activeTab === 'general' ? t('generalSettings') : activeTab === 'user' ? t('userManagement') : activeTab === 'model' ? t('modelManagement') : activeTab === 'knowledge_base' ? t('sidebarTitle') : t('navTenants')}

{activeTab === 'general' ? 'Manage your application preferences.' : activeTab === 'user' ? 'Manage access and accounts.' : activeTab === 'model' ? 'Configure global AI models.' : activeTab === 'knowledge_base' ? 'Technical configuration for indexing and chat parameters.' : 'Global system overview.'}

{error && (
{t('errorLabel')} {error}
)} {activeTab === 'general' && renderGeneralTab()} {activeTab === 'user' && renderUserTab()} {activeTab === 'model' && (isAdmin || currentUser?.role === 'SUPER_ADMIN') && renderModelTab()} {activeTab === 'knowledge_base' && (isAdmin || currentUser?.role === 'SUPER_ADMIN') && renderKnowledgeBaseTab()} {activeTab === 'tenants' && currentUser?.role === 'SUPER_ADMIN' && renderTenantsTab()}
); };