import React, { useState, useEffect } from 'react'; import { ModelConfig, ModelType, AppSettings, KnowledgeGroup, Tenant, TenantMember, DEFAULT_SETTINGS } from '../../types'; import { useLanguage } from '../../contexts/LanguageContext'; import { ChevronLeft, ChevronRight, Plus, Search, KeyRound, Trash2, Edit, UserPlus, Globe, PlusCircle, Clock, ExternalLink, Download, Upload, Building, Settings as SettingsIcon, Shield, User, MoreVertical, Check, ChevronDown, ChevronUp, Filter, RefreshCcw, LayoutDashboard, Users, Database, UserCircle, HardDrive, LayoutGrid, X, Key, Loader2, Edit2, Save, Cpu, BookOpen, Sparkles, ToggleRight, ToggleLeft, } 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 { apiClient } from '../../services/apiClient'; 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' | 'import_tasks'; const buildTenantTree = (tenants: Tenant[]): Tenant[] => { const map = new Map(); const roots: Tenant[] = []; tenants.forEach(t => { map.set(t.id, { ...t, children: [] }); }); tenants.forEach(t => { const node = map.get(t.id)!; if (t.parentId && map.has(t.parentId)) { const parent = map.get(t.parentId)!; parent.children = parent.children || []; parent.children.push(node); } else { roots.push(node); } }); return roots; }; // Moved outside to prevent re-mounting const Pagination: React.FC<{ current: number; total: number; pageSize: number; onChange: (page: number) => void; }> = ({ current, total, pageSize, onChange }) => { const totalPages = Math.ceil(total / pageSize); if (totalPages <= 1) return null; return (
{[...Array(totalPages)].map((_, i) => { const p = i + 1; if (totalPages > 7) { if (p !== 1 && p !== totalPages && Math.abs(p - current) > 1) { if (p === 2 || p === totalPages - 1) return ...; return null; } } return ( ); })}
); }; 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 }); const [users, setUsers] = useState([]); const [isUserLoading, setIsUserLoading] = useState(false); const [userPage, setUserPage] = useState(1); const USER_PAGE_SIZE = 20; const [showAddUser, setShowAddUser] = useState(false); const [newUser, setNewUser] = useState({ username: '', password: '', displayName: '' }); 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 [allMemberIds, setAllMemberIds] = useState>(new Set()); const [memberUserSearch, setMemberUserSearch] = useState(''); const [bindingRole, setBindingRole] = useState('USER'); const [currentMemberSearch, setCurrentMemberSearch] = useState(''); const [isMembersLoading, setIsMembersLoading] = useState(false); const [activeTenantManagementId, setActiveTenantManagementId] = useState(null); const [memberPage, setMemberPage] = useState(1); const [memberTotal, setMemberTotal] = useState(0); const MEMBER_PAGE_SIZE = 20; const [userTotal, setUserTotal] = useState(0); // --- Tenant Tree & Global Management State --- const [tenants, setTenants] = useState([]); const [selectedTenantId, setSelectedTenantId] = useState(null); const [stats, setStats] = useState({ users: 0, tenants: 0 }); const [showCreateTenant, setShowCreateTenant] = useState(false); const [editingTenant, setEditingTenant] = useState(null); const [newTenant, setNewTenant] = useState<{ name: string; domain: string; parentId: string | null }>({ name: '', domain: '', parentId: null }); useEffect(() => { if (initialTab) { setActiveTab(initialTab); } }, [initialTab]); useEffect(() => { if (activeTab === 'user' || activeTab === 'tenants') { fetchUsers(userPage); } }, [userPage]); useEffect(() => { if (selectedTenantId) { fetchTenantMembers(selectedTenantId, memberPage); fetchAllMemberIds(selectedTenantId); } else { setAllMemberIds(new Set()); } }, [selectedTenantId, memberPage]); // Data fetching on tab change useEffect(() => { // Reset pages when switching tabs to avoid bleed-over if (activeTab === 'user' || activeTab === 'tenants') { setUserPage(1); } if (activeTab === 'user') { fetchUsers(1); } else if (activeTab === 'general') { fetchSettingsAndGroups(); } else if (activeTab === 'tenants' && currentUser?.role === 'SUPER_ADMIN') { fetchTenantsData(); fetchUsers(1); // Ensure users are loaded for admin binding } // Independent check for KB/Model settings to avoid being blocked by the branches above if ((activeTab === 'knowledge_base' || activeTab === 'model') && (currentUser?.role === 'TENANT_ADMIN' || currentUser?.role === 'SUPER_ADMIN' || isAdmin)) { fetchKnowledgeBaseSettings(); } }, [activeTab, currentUser, authToken, isAdmin]); 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 data = await userSettingService.get(authToken); // If data is null, undefined, or empty object, use DEFAULT_SETTINGS const finalSettings = (data && Object.keys(data).length > 0) ? { ...DEFAULT_SETTINGS, ...data } : DEFAULT_SETTINGS; setKbSettings(finalSettings); setLocalKbSettings(finalSettings); } catch (error) { console.error(error); // Fallback to defaults on error to prevent blank page setKbSettings(DEFAULT_SETTINGS); setLocalKbSettings(DEFAULT_SETTINGS); } 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 { await userSettingService.update(authToken, localKbSettings); setKbSettings(localKbSettings); showSuccess(t('kbSettingsSaved')); } catch (error) { console.error(error); showError(t('actionFailed')); } finally { setIsSavingKbSettings(false); } }; const handleCancelKbSettings = () => { setLocalKbSettings(kbSettings); }; const fetchSettingsAndGroups = async () => { if (!authToken) return; setIsSettingsLoading(true); try { const [settings, groups, personal] = await Promise.all([ userSettingService.get(authToken), knowledgeGroupService.getGroups(), userSettingService.getPersonal(authToken) ]); setAppSettings(settings); setKnowledgeGroups(groups); // Sync local language with user settings if they differ if (personal?.language && personal.language !== language) { setLanguage(personal.language as any); } // Also update KB settings with the same data if not already set if (settings && Object.keys(settings).length > 0) { setKbSettings(settings); setLocalKbSettings(settings); } } 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 (page?: number) => { setIsUserLoading(true); const p = page || userPage; try { const result = await userService.getUsers(p, USER_PAGE_SIZE); if (result && result.data) { setUsers(result.data); setUserTotal(result.total); } else if (Array.isArray(result)) { setUsers(result); setUserTotal(result.length); } } catch (error: any) { setError(error.message || t('getUserListFailed')); } finally { setIsUserLoading(false); } }; const handleCreateUser = async (e: React.FormEvent) => { e.preventDefault(); setError(''); setUserSuccess(''); if (newUser.username && newUser.password && newUser.displayName) { setIsUserLoading(true); try { await userService.createUser( newUser.username, newUser.password, false, undefined, newUser.displayName ); showSuccess(t('userCreatedSuccess')); setNewUser({ username: '', password: '', displayName: '' }); setShowAddUser(false); fetchUsers(); } catch (error: any) { setError(error.message || t('createUserFailed')); } finally { setIsUserLoading(false); } } }; const [passwordChangeUserData, setPasswordChangeUserData] = useState<{ userId: string, newPassword: string } | null>(null); // --- Edit User State --- const [editUserData, setEditUserData] = useState<{ userId: string, username: string, displayName: 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')); } }; const fetchAllMemberIds = async (tenantId: string) => { try { const { data } = await apiClient.get(`/v1/tenants/${tenantId}/members/ids`); if (Array.isArray(data)) { setAllMemberIds(new Set(data)); } } catch (e) { console.error('Failed to fetch all member IDs:', e); } }; const fetchTenantMembers = async (tenantId: string, page?: number) => { setIsMembersLoading(true); const p = page || memberPage; try { const { data } = await apiClient.get(`/v1/tenants/${tenantId}/members?page=${p}&limit=${MEMBER_PAGE_SIZE}`); if (data && data.data) { setTenantMembers(data.data); setMemberTotal(data.total); } else if (Array.isArray(data)) { setTenantMembers(data); setMemberTotal(data.length); } } catch (e) { console.error(e); } finally { setIsMembersLoading(false); } }; const handleAddMember = async (tenantId: string, userId: string, role: string = 'USER') => { try { await apiClient.post(`/v1/tenants/${tenantId}/members`, { userId, role }); setAllMemberIds(prev => { const next = new Set(prev); next.add(userId); return next; }); showSuccess(t('confirm')); fetchTenantMembers(tenantId); fetchTenantsData(); } catch (e: any) { showError(e.message || 'Error adding member'); } }; const handleRemoveMember = async (tenantId: string, userId: string) => { try { await apiClient.delete(`/v1/tenants/${tenantId}/members/${userId}`); setAllMemberIds(prev => { const next = new Set(prev); next.delete(userId); return next; }); showSuccess('User removed from organization'); fetchTenantMembers(tenantId); fetchTenantsData(); } catch (e: any) { showError(e.message || 'Error removing member'); } }; const handleUpdateMemberRole = async (tenantId: string, userId: string, role: string) => { try { await apiClient.patch(`/v1/tenants/${tenantId}/members/${userId}`, { role }); showSuccess(t('featureUpdated')); fetchTenantMembers(tenantId); } catch (e: any) { showError(e.message || 'Error updating role'); } }; const fetchTenantsData = async () => { if (!authToken) return; setIsLoading(true); try { const [tenRes, admRes] = await Promise.all([ apiClient.get('/v1/tenants'), apiClient.get('/users?page=1&limit=1') ]); const data: Tenant[] = tenRes.data; const filteredData = data.filter(t => t.name !== 'Default'); setTenants(filteredData); setStats(s => ({ ...s, tenants: filteredData.length })); const result = admRes.data; setStats(s => ({ ...s, users: result.total ?? result.length ?? 0 })); } catch (e) { console.error(e); } finally { setIsLoading(false); } }; const handleCreateTenant = async (e: React.FormEvent) => { e.preventDefault(); try { const path = editingTenant ? `/v1/tenants/${editingTenant.id}` : '/v1/tenants'; const body = { name: newTenant.name, domain: newTenant.domain, parentId: newTenant.parentId }; if (editingTenant) { await apiClient.put(path, body); } else { await apiClient.post(path, body); } setShowCreateTenant(false); setEditingTenant(null); setNewTenant({ name: '', domain: '', parentId: null }); fetchTenantsData(); showSuccess(editingTenant ? 'Tenant updated' : 'Tenant created'); } catch (e: any) { showError(e.message || 'Action failed'); } }; const handleRemoveTenant = async (tenantId: string) => { if (!(await confirm(t('confirmDeleteTenant')))) return; try { await apiClient.delete(`/v1/tenants/${tenantId}`); setSelectedTenantId(null); fetchTenantsData(); showSuccess('Tenant deleted'); } catch (e: any) { showError(e.message || 'Delete failed'); } }; const handleUpdateUser = async () => { if (!editUserData) return; try { await userService.updateUserInfo(editUserData.userId, { username: editUserData.username, displayName: editUserData.displayName }); showSuccess(t('featureUpdated')); setEditUserData(null); fetchUsers(); } catch (error: any) { showError('Failed to update user'); } }; const handleDeleteUser = async (userId: string) => { if (!confirm(t('confirmDeleteUser') || "Are you sure?")) return; try { await userService.deleteUser(userId); showSuccess(t('userDeletedSuccessfully')); fetchUsers(); } catch (error: any) { showError(error.message || t('deleteUserFailed')); } }; const handleExportUsers = async () => { try { const blob = await userService.exportUsers(); const url = window.URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `users_${new Date().toISOString().split('T')[0]}.xlsx`; document.body.appendChild(a); a.click(); window.URL.revokeObjectURL(url); document.body.removeChild(a); } catch (error) { console.error('Export users failed', error); showError(t('exportFailed')); } }; const handleImportUsers = async (e: React.ChangeEvent) => { const file = e.target.files?.[0]; if (!file) return; try { const result = await userService.importUsers(file); showSuccess(t('importSuccess').replace('$1', (result.success || 0).toString()).replace('$2', (result.failed || 0).toString())); fetchUsers(); if (result.errors.length > 0) { console.warn('Import had errors:', result.errors); } } catch (error: any) { console.error('Import users failed', error); showError(t('importFailed') + (error.response?.data?.message ? `: ${error.response.data.message}` : '')); } finally { // Reset input e.target.value = ''; } }; const handleSaveModel = async () => { if (!authToken) return; setIsLoading(true); try { await onUpdateModels(editingId === 'new' ? 'create' : 'update', modelFormData as ModelConfig); setEditingId(null); } catch (err) { setError('Update failed'); } 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 apiClient.put('/v1/admin/settings', { enabledModelIds: newEnabledIds }); setEnabledModelIds(newEnabledIds); setLocalKbSettings((prev: any) => ({ ...prev, enabledModelIds: newEnabledIds })); showSuccess('Updated'); } catch (e: any) { showError(e.message || 'Update failed'); } return; } await onUpdateModels('update', { ...model, isEnabled: !model.isEnabled }); }; const handleDeleteModel = async (id: string) => { if (await confirm(t('confirmDeleteModel'))) { await onUpdateModels('delete', { id } as ModelConfig); } }; const TenantTreeNode: React.FC<{ tenant: Tenant; selectedTenantId: string | null; onSelect: (id: string) => void; onCreateSubtenant: (parentId: string) => void; depth?: number; }> = ({ tenant, selectedTenantId, onSelect, onCreateSubtenant, depth = 0 }) => { const [collapsed, setCollapsed] = useState(false); const hasChildren = tenant.children && tenant.children.length > 0; const isSelected = selectedTenantId === tenant.id; return (
onSelect(tenant.id)} >
{hasChildren ? ( ) : (
)} {tenant.name}
{hasChildren && !collapsed && (
{tenant.children?.map(child => ( ))}
)}
); }; const getTypeLabel = (type: ModelType) => { switch (type) { case ModelType.LLM: return t('typeLLM'); case ModelType.EMBEDDING: return t('typeEmbedding'); case ModelType.RERANK: return t('typeRerank'); case ModelType.VISION: return t('typeVision'); default: return type; } }; // --- レンダリング関数 --- 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 autoComplete="current-password" />
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 autoComplete="new-password" />
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 autoComplete="new-password" />
{passwordSuccess &&

{passwordSuccess}

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

{t('languageSettings')}

); const renderUserTab = () => (

{''}

{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 autoComplete="username" /> setNewUser({ ...newUser, displayName: 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 autoComplete="name" /> 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 autoComplete="new-password" />
)} {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 User Modal */} {editUserData && (

{t('editUser')}

{ e.preventDefault(); handleUpdateUser(); }} className="space-y-6">
setEditUserData({ ...editUserData, username: 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('usernamePlaceholder')} required />
setEditUserData({ ...editUserData, displayName: 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('displayNamePlaceholder') || t('namePlaceholder')} required />

{t('globalUserNote') || "Note"}

{t('roleManagedInOrg') || "Roles are managed within organizations."}

)}
{users.filter((u: any) => u.username !== 'admin').map((user: any, index: number) => { let IconComponent = User; let iconColors = 'bg-slate-50 text-slate-400'; if (user.isAdmin) { IconComponent = Shield; iconColors = 'bg-red-50 text-red-600'; } return ( ); })}
{t('username')} {t('displayName') || t('name')} {t('organizations')} {t('createdAt')} {t('actions')}

{user.username}

{user.displayName || '-'}

{user.tenantMembers && user.tenantMembers.length > 0 ? (
{user.tenantMembers .filter((m: any) => m.tenant?.name !== 'Default') .map((m: any) => ( {m.tenant?.name || m.tenantId} ))}
) : ( {t('noOrganization')} )}

{new Date(user.createdAt).toLocaleDateString()}

{user.username !== 'admin' && ( <> {user.id !== currentUser?.id && ( )} )}
); const renderTenantsTab = () => { const tenantTree = buildTenantTree(tenants); const activeTenant = selectedTenantId ? tenants.find(t => t.id === selectedTenantId) : null; return (
{/* Left: Organization Tree */}

{t('orgManagement')}

{t('globalTenantControl')}

{tenantTree.length > 0 ? ( tenantTree.map(t => ( { if (id !== selectedTenantId) { setSelectedTenantId(id); setMemberPage(1); setUserPage(1); } }} onCreateSubtenant={(parentId) => { setNewTenant({ name: '', domain: '', parentId }); setEditingTenant(null); setShowCreateTenant(true); }} /> )) ) : (

{t('noOrganizations')}

)}

{t('totalTenants')}

{stats.tenants}

{/* Right: User List & Management */}
{activeTenant ? (
{/* Organization Header */}

{activeTenant.name}

{t('activeOrg')}

{activeTenant.domain || t('noCustomDomain')}

{/* Main Content Split: Members vs All Users */}
{/* Current Members */}

{t('orgMembers')}

{t('membersCount').replace('$1', (memberTotal || 0).toString())}
{tenantMembers?.map((m: any) => (

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

))} {(!tenantMembers || tenantMembers.length === 0) && (

{t('noMembersAssigned')}

)}
{/* Add New Users (Right side of specific tenant view) */}

{t('addMembers')}

setUserSearchQuery(e.target.value)} />
{users .filter(u => u.username !== 'admin' && u.username.toLowerCase().includes(userSearchQuery.toLowerCase()) ) .map(u => { const isAlreadyMember = allMemberIds.has(u.id); return ( ); }) }
{/* Create Tenant Modal (Nested in Tab Content for scope) */} {showCreateTenant && (

{editingTenant ? t('editOrg') : t('newTenant')}

setNewTenant({ ...newTenant, name: e.target.value })} required />
setNewTenant({ ...newTenant, domain: e.target.value })} />
{!editingTenant ? (
{newTenant.parentId ? tenants.find(t => t.id === newTenant.parentId)?.name : t('noneRoot')}
) : ( )}
)}
) : (

{t('selectOrg')}

{t('selectOrgDesc')}

{stats.users}

{t('totalSystemUsers')}

{tenants.filter(t => t.parentId === null).length}

{t('rootOrgs')}

{/* Scope Modal for Create even when no selection */} {showCreateTenant && (

{t('newTenant')}

setNewTenant({ ...newTenant, name: e.target.value })} required />
setNewTenant({ ...newTenant, domain: e.target.value })} />
)}
)}
); }; const renderKnowledgeBaseTab = () => (
{localKbSettings ? ( <> {/* Save/Cancel Bar */}
{/* Model Configuration */}
{t('modelConfiguration')}
{/* Indexing & Chunking Configuration */}
{t('indexingChunkingConfig')}
{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 */}
{t('chatHyperparameters')}
{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" />
{t('precise')} {t('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 */}
{t('retrievalSearchSettings')}
{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" />
{t('enableHybridSearch')}
{t('hybridSearchDesc')}
{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" />
{t('pureText')} {t('pureVector')}
)}
{t('enableQueryExpansion')}
{t('queryExpansionDesc')}
{t('enableHyDE')}
{t('hydeDesc')}
{t('enableReranking')}
{t('rerankingDesc')}
{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" />
{t('broad')} {t('strict')}
)}
) : (

{t('loading')}

)}
); const renderModelTab = () => (
{!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 && ( {t('defaultBadge')} )}

{model.modelId}

{/* Additional info grid */}
{model.type === ModelType.EMBEDDING && ( <>
{t('dims')} {model.dimensions || '-'}
{t('ctx')} {model.maxInputTokens || '-'}
)} {model.type === ModelType.LLM && (
{t('baseApi')} {model.baseUrl}
)}
{t('configured')}
{currentUser?.role === 'SUPER_ADMIN' && ( <> )}
))} {models.length === 0 && (

{t('mmEmpty')}

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

{t('tabSettings')}

{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' ? t('generalSettingsSubtitle') : activeTab === 'user' ? t('userManagementSubtitle') : activeTab === 'model' ? t('modelManagementSubtitle') : activeTab === 'knowledge_base' ? t('kbSettingsSubtitle') : t('tenantsSubtitle')}

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