import React, { useState, useEffect } from 'react'; import { ModelConfig, ModelType } from '../types'; import { useLanguage } from '../contexts/LanguageContext'; import { X, Plus, Trash2, Edit2, Save, Cpu, Box, Loader2, User, Shield, Key, LogOut, Globe, Settings as SettingsIcon, Star } from 'lucide-react'; import { userService } from '../services/userService'; import { settingsService } from '../services/settingsService'; import { userSettingService } from '../services/userSettingService'; import { knowledgeGroupService } from '../services/knowledgeGroupService'; import { modelConfigService } from '../services/modelConfigService'; import { useConfirm } from '../contexts/ConfirmContext'; import { AppSettings, KnowledgeGroup } from '../types'; interface SettingsModalProps { isOpen: boolean; onClose: () => void; // Model Props models: ModelConfig[]; authToken: string | null; onUpdateModels: (action: 'create' | 'update' | 'delete', model: ModelConfig) => Promise; onLogout: () => void; } type TabType = 'general' | 'user' | 'model'; export const SettingsModal: React.FC = ({ isOpen, onClose, models, authToken, onUpdateModels, onLogout }) => { const { t, language, setLanguage } = useLanguage(); const { confirm } = useConfirm(); 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, maxInputTokens: 8191, maxBatchSize: 2048 }); // --- User Management State --- interface UserType { id: string; username: string; isAdmin: boolean; role?: string; createdAt: string; } const [users, setUsers] = useState([]); const [isUserLoading, setIsUserLoading] = useState(false); const [showAddUser, setShowAddUser] = useState(false); const [newUser, setNewUser] = useState({ username: '', password: '' }); 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 [currentUser, setCurrentUser] = useState(null); // Reset state on open useEffect(() => { if (isOpen) { setActiveTab('general'); setError(null); setEditingId(null); } }, [isOpen]); // Fetch Users when User tab is active useEffect(() => { if (isOpen) { if (activeTab === 'user') { fetchUsers(); } else if (activeTab === 'general') { fetchSettingsAndGroups(); } } }, [isOpen, activeTab]); const fetchSettingsAndGroups = async () => { if (!authToken) return; setIsSettingsLoading(true); try { const [settings, groups, users, personal] = await Promise.all([ userSettingService.get(authToken), knowledgeGroupService.getGroups(), userService.getUsers().catch(() => []), // Regular users might fail this userSettingService.getPersonal(authToken).catch(() => null) ]); setAppSettings(settings); setKnowledgeGroups(groups); if (personal?.language && personal.language !== language) { setLanguage(personal.language as any); } // Temporary way to get current user details since we lack a /me endpoint hook here const tokenPayload = JSON.parse(atob(authToken.split('.')[1])); const me = users.find(u => u.id === tokenPayload.sub) || { isAdmin: tokenPayload.role === 'SUPER_ADMIN' || tokenPayload.role === 'TENANT_ADMIN', role: tokenPayload.role }; setCurrentUser(me as any); } catch (error) { console.error('Failed to fetch settings or groups:', error); } finally { setIsSettingsLoading(false); } }; if (!isOpen) return null; // --- General Tab Handlers --- const handleLanguageChange = async (newLanguage: string) => { setIsLoading(true); try { await settingsService.updateLanguage(newLanguage); setLanguage(newLanguage as any); } catch (error) { console.error('Failed to update language:', error); } finally { setIsLoading(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); } }; // --- User Tab Handlers --- 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); setUserSuccess(t('userCreatedSuccess')); setNewUser({ username: '', password: '' }); setShowAddUser(false); fetchUsers(); } catch (error: any) { setError(error.message || t('createUserFailed')); } }; // --- Model Tab Handlers --- 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 handleDeleteModel = async (id: string) => { if (await confirm(t('confirmClear'))) { await onUpdateModels('delete', { id } as ModelConfig); } }; const handleSetDefault = async (id: string) => { if (!authToken) { setError(t('mmErrorNotAuthenticated')); return; } setIsLoading(true); try { await modelConfigService.setDefault(authToken, id); // モデル一覧を再取得するためにページをリロード window.location.reload(); } catch (err: any) { setError(err.message || t('defaultSettingFailed')); } finally { setIsLoading(false); } }; const getTypeLabel = (type: ModelType) => { switch (type) { case ModelType.LLM: return t('typeLLM'); case ModelType.EMBEDDING: return t('typeEmbedding'); case ModelType.RERANK: return t('typeRerank'); } }; // --- Render Functions --- const renderGeneralTab = () => (
{/* 言語セクション */}

{t('languageSettings')}

{(['zh', 'en', 'ja'] as const).map((lang) => ( ))}
{/* Change Password Section */}

{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}

}
{/* Logout Section */}
); const renderUserTab = () => (

{t('userList')}

{currentUser?.role === 'SUPER_ADMIN' && ( )}
{showAddUser && (
setNewUser({ ...newUser, username: e.target.value })} className="w-full px-3 py-2 text-sm border border-slate-300 rounded-md" required /> setNewUser({ ...newUser, password: e.target.value })} className="w-full px-3 py-2 text-sm border border-slate-300 rounded-md" required />
)} {userSuccess &&

{userSuccess}

}
{users.map(user => (
{user.isAdmin ? : }

{user.username}

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

{user.isAdmin ? t('admin') : t('user')}
))}
); const renderModelTab = () => (
{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} autoComplete="off" />
{modelFormData.type === ModelType.EMBEDDING && (
setModelFormData({ ...modelFormData, maxInputTokens: parseInt(e.target.value) })} />
setModelFormData({ ...modelFormData, dimensions: parseInt(e.target.value) })} />
)}
) : (
{models.map(model => (

{model.name}

{model.isDefault && ( {t('defaultBadge')} )}
{getTypeLabel(model.type)} {model.modelId}
))}
)}
); return (
{/* サイドバー */}

{t('settings')}

{/* コンテンツエリア */}

{activeTab === 'general' ? t('generalSettings') : activeTab === 'user' ? t('userManagement') : t('modelManagement')}

{error && (
Error: {error}
)} {activeTab === 'general' && renderGeneralTab()} {activeTab === 'user' && currentUser?.role === 'SUPER_ADMIN' && renderUserTab()} {activeTab === 'model' && currentUser?.role !== 'USER' && renderModelTab()}
); };