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 } from 'lucide-react'; 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 } type TabType = 'general' | 'user' | 'model'; export const SettingsView: React.FC = ({ models, authToken, onUpdateModels, isAdmin = false, currentUser, }) => { const { t } = 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, maxInputTokens: 8191, maxBatchSize: 2048 }); // --- User Management State --- interface UserType { id: string; username: string; isAdmin: boolean; createdAt: string; } const [users, setUsers] = useState([]); const [isUserLoading, setIsUserLoading] = useState(false); const [showAddUser, setShowAddUser] = useState(false); const [newUser, setNewUser] = useState({ username: '', password: '', isAdmin: false }); 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); // ユーザー一覧の取得(ユーザータブがアクティブな場合) useEffect(() => { if (activeTab === 'user') { fetchUsers(); } else if (activeTab === 'general') { fetchSettingsAndGroups(); } }, [activeTab]); // activeTab のみに依存し、currentUser は不要 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.isAdmin); setUserSuccess(t('userCreatedSuccess')); setNewUser({ username: '', password: '', isAdmin: false }); setShowAddUser(false); fetchUsers(); } catch (error: any) { setError(error.message || t('createUserFailed')); } }; const [passwordChangeUserData, setPasswordChangeUserData] = useState<{ userId: string, newPassword: 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 handleDeleteUser = async (userId: string) => { if (await confirm(t('confirmDeleteUser'))) { try { await userService.deleteUser(userId); // ユーザーリストを再取得 fetchUsers(); showSuccess(t('userDeletedSuccessfully')); } 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) => { 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}

}
); const renderUserTab = () => (

{t('userList')}

{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 />
setNewUser({ ...newUser, isAdmin: e.target.checked })} className="w-4 h-4 text-blue-600 rounded border border-slate-300" />
)} {/* Password Change Modal */} {passwordChangeUserData && (

{t('changeUserPassword')}

{ e.preventDefault(); handleUserPasswordChange(); }} className="space-y-4">
setPasswordChangeUserData({ ...passwordChangeUserData, newPassword: e.target.value })} className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500" placeholder={t('enterNewPassword')} required />

{t('passwordMinLength')}

)} {userSuccess &&

{userSuccess}

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

{user.username}

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

{user.isAdmin ? t('admin') : t('user')} {user.username !== 'admin' && ( // ビルトイン管理者は読み取り専用、操作ボタンなし <> {currentUser?.username === 'admin' && ( // ビルトイン管理者のみがロールを変更可能 )} {user.id !== currentUser?.id && ( // 自身の削除は許可しない )} )}
))}
); 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}

{getTypeLabel(model.type)} {model.modelId}
))}
)}
); return (
{/* サイドバー */}

{t('settings')}

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

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

{error && (
Error: {error}
)} {activeTab === 'general' && renderGeneralTab()} {activeTab === 'user' && renderUserTab()} {activeTab === 'model' && isAdmin && renderModelTab()} {/* Only render model tab if user is admin */}
); };