|
|
@@ -58,6 +58,7 @@ export const SettingsView: React.FC<SettingsViewProps> = ({
|
|
|
role?: string;
|
|
|
tenantId?: string;
|
|
|
createdAt: string;
|
|
|
+ tenantMembers?: Array<{ tenantId: string; role: string; tenant?: { id: string; name: string } }>;
|
|
|
}
|
|
|
const [users, setUsers] = useState<UserType[]>([]);
|
|
|
const [isUserLoading, setIsUserLoading] = useState(false);
|
|
|
@@ -79,6 +80,12 @@ export const SettingsView: React.FC<SettingsViewProps> = ({
|
|
|
const [bindingTenantId, setBindingTenantId] = useState<string | null>(null);
|
|
|
const [userSearchQuery, setUserSearchQuery] = useState('');
|
|
|
|
|
|
+ // --- Manage Members Modal State ---
|
|
|
+ const [managingMembersTenantId, setManagingMembersTenantId] = useState<string | null>(null);
|
|
|
+ const [tenantMembers, setTenantMembers] = useState<any[]>([]);
|
|
|
+ const [memberUserSearch, setMemberUserSearch] = useState('');
|
|
|
+ const [isMembersLoading, setIsMembersLoading] = useState(false);
|
|
|
+
|
|
|
useEffect(() => {
|
|
|
if (initialTab) {
|
|
|
setActiveTab(initialTab);
|
|
|
@@ -273,6 +280,63 @@ export const SettingsView: React.FC<SettingsViewProps> = ({
|
|
|
const [editingTenant, setEditingTenant] = useState<any | null>(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 {
|
|
|
@@ -732,15 +796,31 @@ export const SettingsView: React.FC<SettingsViewProps> = ({
|
|
|
transition={{ delay: index * 0.05 }}
|
|
|
className="flex items-center justify-between p-5 bg-white/70 backdrop-blur-md border border-slate-200/50 rounded-2xl shadow-sm hover:shadow-md hover:border-indigo-200/50 transition-all group"
|
|
|
>
|
|
|
- <div className="flex items-center gap-4">
|
|
|
- <div className={`w-12 h-12 rounded-xl flex items-center justify-center transition-all ${iconColors}`}>
|
|
|
+ <div className="flex items-center gap-4 flex-1 min-w-0">
|
|
|
+ <div className={`w-12 h-12 rounded-xl flex items-center justify-center transition-all flex-shrink-0 ${iconColors}`}>
|
|
|
<IconComponent size={22} />
|
|
|
</div>
|
|
|
- <div>
|
|
|
- <div className="flex items-center gap-2">
|
|
|
+ <div className="flex-1 min-w-0">
|
|
|
+ <div className="flex items-center gap-2 flex-wrap">
|
|
|
<p className="font-black text-slate-900">{user.username}</p>
|
|
|
- {(user.role === 'SUPER_ADMIN' || user.isAdmin) && <span className={`text-[9px] font-black px-1.5 py-0.5 rounded-md uppercase tracking-wider ${user.role === 'SUPER_ADMIN' ? 'bg-red-100 text-red-700' : 'bg-indigo-100 text-indigo-700'}`}>{user.role === 'SUPER_ADMIN' ? 'SUPER' : 'ADMIN'}</span>} </div>
|
|
|
+ {(user.role === 'SUPER_ADMIN' || user.isAdmin) && <span className={`text-[9px] font-black px-1.5 py-0.5 rounded-md uppercase tracking-wider ${user.role === 'SUPER_ADMIN' ? 'bg-red-100 text-red-700' : 'bg-indigo-100 text-indigo-700'}`}>{user.role === 'SUPER_ADMIN' ? 'SUPER' : 'ADMIN'}</span>}
|
|
|
+ </div>
|
|
|
<p className="text-[10px] font-bold text-slate-400 mt-0.5 uppercase tracking-widest">{t('createdAt')}: {new Date(user.createdAt).toLocaleDateString()}</p>
|
|
|
+ {user.tenantMembers && user.tenantMembers.length > 0 && (
|
|
|
+ <div className="flex flex-wrap gap-1 mt-1.5">
|
|
|
+ {user.tenantMembers
|
|
|
+ .filter((m: any) => m.tenant?.name !== 'Default')
|
|
|
+ .map((m: any) => (
|
|
|
+ <span
|
|
|
+ key={m.tenantId}
|
|
|
+ className="inline-flex items-center gap-1 px-2 py-0.5 bg-emerald-50 text-emerald-700 text-[9px] font-black rounded-md uppercase tracking-wider border border-emerald-100"
|
|
|
+ >
|
|
|
+ <Building2 size={8} />
|
|
|
+ {m.tenant?.name || m.tenantId}
|
|
|
+ </span>
|
|
|
+ ))}
|
|
|
+ </div>
|
|
|
+ )}
|
|
|
</div>
|
|
|
</div>
|
|
|
<div className="flex items-center gap-2 opacity-0 group-hover:opacity-100 transition-all">
|
|
|
@@ -829,6 +909,7 @@ export const SettingsView: React.FC<SettingsViewProps> = ({
|
|
|
<th className="px-6 py-4">Name</th>
|
|
|
<th className="px-6 py-4">Domain</th>
|
|
|
<th className="px-6 py-4">Admin</th>
|
|
|
+ <th className="px-6 py-4">Members</th>
|
|
|
<th className="px-6 py-4">Features</th>
|
|
|
<th className="px-6 py-4">Created</th>
|
|
|
<th className="px-6 py-4 text-right">Actions</th>
|
|
|
@@ -852,32 +933,38 @@ export const SettingsView: React.FC<SettingsViewProps> = ({
|
|
|
<User className="w-3 h-3 text-indigo-500" />
|
|
|
</div>
|
|
|
{adminUser.username}
|
|
|
- <button
|
|
|
- onClick={() => {
|
|
|
- setBindingTenantId(t.id);
|
|
|
- setUserSearchQuery('');
|
|
|
- fetchUsers();
|
|
|
- }}
|
|
|
- className="ml-2 p-1 hover:bg-indigo-50 text-slate-400 hover:text-indigo-600 rounded transition-all"
|
|
|
- title="Change Admin"
|
|
|
- >
|
|
|
- <Edit2 size={12} />
|
|
|
- </button>
|
|
|
+ {t.name !== 'Default' && (
|
|
|
+ <button
|
|
|
+ onClick={() => {
|
|
|
+ setBindingTenantId(t.id);
|
|
|
+ setUserSearchQuery('');
|
|
|
+ fetchUsers();
|
|
|
+ }}
|
|
|
+ className="ml-2 p-1 hover:bg-indigo-50 text-slate-400 hover:text-indigo-600 rounded transition-all"
|
|
|
+ title="Change Admin"
|
|
|
+ >
|
|
|
+ <Edit2 size={12} />
|
|
|
+ </button>
|
|
|
+ )}
|
|
|
</div>
|
|
|
);
|
|
|
} else {
|
|
|
return (
|
|
|
<div className="flex items-center gap-2">
|
|
|
- <button
|
|
|
- onClick={() => {
|
|
|
- setBindingTenantId(t.id);
|
|
|
- setUserSearchQuery('');
|
|
|
- fetchUsers();
|
|
|
- }}
|
|
|
- className="px-3 py-1.5 bg-indigo-50 text-indigo-600 hover:bg-indigo-100 rounded-xl text-[10px] font-black uppercase tracking-widest transition-all"
|
|
|
- >
|
|
|
- Bind Admin
|
|
|
- </button>
|
|
|
+ {t.name !== 'Default' ? (
|
|
|
+ <button
|
|
|
+ onClick={() => {
|
|
|
+ setBindingTenantId(t.id);
|
|
|
+ setUserSearchQuery('');
|
|
|
+ fetchUsers();
|
|
|
+ }}
|
|
|
+ className="px-3 py-1.5 bg-indigo-50 text-indigo-600 hover:bg-indigo-100 rounded-xl text-[10px] font-black uppercase tracking-widest transition-all"
|
|
|
+ >
|
|
|
+ Bind Admin
|
|
|
+ </button>
|
|
|
+ ) : (
|
|
|
+ <span className="text-[10px] text-slate-400 italic">System Restricted</span>
|
|
|
+ )}
|
|
|
<span className="text-[10px] text-slate-400 italic">None</span>
|
|
|
</div>
|
|
|
);
|
|
|
@@ -889,36 +976,64 @@ export const SettingsView: React.FC<SettingsViewProps> = ({
|
|
|
<div className={`p-1.5 rounded-lg ${t.settings_obj?.isNotebookEnabled ? 'bg-indigo-50 text-indigo-600' : 'bg-slate-50 text-slate-400'}`} title="Notebook Feature">
|
|
|
<BookOpen size={14} />
|
|
|
</div>
|
|
|
- <button
|
|
|
- onClick={() => handleToggleNotebookFeature(t.id, t.settings_obj?.isNotebookEnabled !== false)}
|
|
|
- className="text-[10px] font-black uppercase tracking-widest text-indigo-600 hover:underline"
|
|
|
- >
|
|
|
- {t.settings_obj?.isNotebookEnabled !== false ? 'Enabled' : 'Disabled'}
|
|
|
- </button>
|
|
|
+ {t.name !== 'Default' ? (
|
|
|
+ <button
|
|
|
+ onClick={() => handleToggleNotebookFeature(t.id, t.settings_obj?.isNotebookEnabled !== false)}
|
|
|
+ className="text-[10px] font-black uppercase tracking-widest text-indigo-600 hover:underline"
|
|
|
+ >
|
|
|
+ {t.settings_obj?.isNotebookEnabled !== false ? 'Enabled' : 'Disabled'}
|
|
|
+ </button>
|
|
|
+ ) : (
|
|
|
+ <span className="text-[10px] font-black uppercase tracking-widest text-slate-400">Fixed</span>
|
|
|
+ )}
|
|
|
</div>
|
|
|
</td>
|
|
|
<td className="px-6 py-4 text-xs text-slate-400">{new Date(t.createdAt).toLocaleDateString()}</td>
|
|
|
- <td className="px-6 py-4 text-right">
|
|
|
- <div className="flex justify-end gap-2 opacity-0 group-hover:opacity-100 transition-all">
|
|
|
- <button
|
|
|
- onClick={() => {
|
|
|
- setEditingTenant(t);
|
|
|
- setNewTenant({ name: t.name, domain: t.domain || '', adminUserId: '' });
|
|
|
- }}
|
|
|
- className="p-1.5 hover:bg-slate-100 text-slate-400 hover:text-indigo-600 rounded-lg transition-all"
|
|
|
- title="Edit Tenant"
|
|
|
- >
|
|
|
- <Edit2 size={14} />
|
|
|
- </button>
|
|
|
- <button
|
|
|
- onClick={() => handleDeleteTenant(t.id)}
|
|
|
- className="p-1.5 hover:bg-red-50 text-slate-400 hover:text-red-500 rounded-lg transition-all"
|
|
|
- title="Delete Tenant"
|
|
|
- >
|
|
|
- <Trash2 size={14} />
|
|
|
- </button>
|
|
|
+ <td className="px-6 py-4 text-xs">
|
|
|
+ {/* Members count + manage button */}
|
|
|
+ <div className="flex items-center gap-2">
|
|
|
+ <span className="font-bold text-slate-700">
|
|
|
+ {(t.members || []).filter((m: any) => m.role !== 'SUPER_ADMIN').length}
|
|
|
+ </span>
|
|
|
+ <span className="text-slate-400">users</span>
|
|
|
+ {t.name !== 'Default' && (
|
|
|
+ <button
|
|
|
+ onClick={() => {
|
|
|
+ setManagingMembersTenantId(t.id);
|
|
|
+ setMemberUserSearch('');
|
|
|
+ fetchTenantMembers(t.id);
|
|
|
+ fetchUsers();
|
|
|
+ }}
|
|
|
+ className="ml-1 px-2.5 py-1 bg-emerald-50 text-emerald-700 hover:bg-emerald-100 rounded-lg text-[10px] font-black uppercase tracking-widest transition-all"
|
|
|
+ >
|
|
|
+ Manage
|
|
|
+ </button>
|
|
|
+ )}
|
|
|
</div>
|
|
|
</td>
|
|
|
+ <td className="px-6 py-4 text-right">
|
|
|
+ {t.name !== 'Default' && (
|
|
|
+ <div className="flex justify-end gap-2 opacity-0 group-hover:opacity-100 transition-all">
|
|
|
+ <button
|
|
|
+ onClick={() => {
|
|
|
+ setEditingTenant(t);
|
|
|
+ setNewTenant({ name: t.name, domain: t.domain || '', adminUserId: '' });
|
|
|
+ }}
|
|
|
+ className="p-1.5 hover:bg-slate-100 text-slate-400 hover:text-indigo-600 rounded-lg transition-all"
|
|
|
+ title="Edit Tenant"
|
|
|
+ >
|
|
|
+ <Edit2 size={14} />
|
|
|
+ </button>
|
|
|
+ <button
|
|
|
+ onClick={() => handleDeleteTenant(t.id)}
|
|
|
+ className="p-1.5 hover:bg-red-50 text-slate-400 hover:text-red-500 rounded-lg transition-all"
|
|
|
+ title="Delete Tenant"
|
|
|
+ >
|
|
|
+ <Trash2 size={14} />
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ )}
|
|
|
+ </td>
|
|
|
</tr>
|
|
|
))}
|
|
|
</tbody>
|
|
|
@@ -1044,6 +1159,142 @@ export const SettingsView: React.FC<SettingsViewProps> = ({
|
|
|
</div>
|
|
|
)}
|
|
|
</AnimatePresence>
|
|
|
+
|
|
|
+ {/* Manage Members Modal */}
|
|
|
+ <AnimatePresence>
|
|
|
+ {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 (
|
|
|
+ <div className="fixed inset-0 z-[140] bg-slate-900/60 backdrop-blur-md flex items-center justify-center p-4">
|
|
|
+ <motion.div
|
|
|
+ initial={{ scale: 0.9, opacity: 0 }}
|
|
|
+ animate={{ scale: 1, opacity: 1 }}
|
|
|
+ exit={{ scale: 0.9, opacity: 0 }}
|
|
|
+ className="bg-white rounded-[2.5rem] p-8 w-full max-w-2xl shadow-2xl border border-white/20 overflow-hidden relative max-h-[90vh] flex flex-col"
|
|
|
+ >
|
|
|
+ <div className="flex items-start justify-between mb-6">
|
|
|
+ <div>
|
|
|
+ <h3 className="text-2xl font-black text-slate-900 tracking-tight mb-1">Manage Members</h3>
|
|
|
+ <p className="text-sm text-slate-500 font-medium">
|
|
|
+ Organization: <span className="text-emerald-600 font-bold">{tenant?.name}</span>
|
|
|
+ </p>
|
|
|
+ </div>
|
|
|
+ <button
|
|
|
+ onClick={() => { setManagingMembersTenantId(null); setTenantMembers([]); }}
|
|
|
+ className="p-2 hover:bg-slate-100 rounded-2xl transition-all"
|
|
|
+ >
|
|
|
+ <X size={20} className="text-slate-400" />
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div className="flex-1 overflow-y-auto flex flex-col gap-6 pr-1 scrollbar-hide">
|
|
|
+ {/* Current Members Section */}
|
|
|
+ <div>
|
|
|
+ <h4 className="text-[10px] font-black text-slate-400 uppercase tracking-widest mb-3">
|
|
|
+ Current Members ({tenantMembers.length})
|
|
|
+ </h4>
|
|
|
+ {isMembersLoading ? (
|
|
|
+ <div className="flex items-center justify-center py-8 opacity-40">
|
|
|
+ <Loader2 size={24} className="animate-spin" />
|
|
|
+ </div>
|
|
|
+ ) : tenantMembers.length === 0 ? (
|
|
|
+ <div className="py-6 text-center bg-slate-50 rounded-2xl opacity-50">
|
|
|
+ <User size={28} className="mx-auto mb-2 text-slate-400" />
|
|
|
+ <p className="text-xs font-bold uppercase tracking-widest text-slate-400">No members yet</p>
|
|
|
+ </div>
|
|
|
+ ) : (
|
|
|
+ <div className="space-y-2">
|
|
|
+ {tenantMembers.map((m: any) => (
|
|
|
+ <div key={m.id || m.userId} className="flex items-center justify-between p-4 bg-slate-50 rounded-2xl border border-slate-200/60">
|
|
|
+ <div className="flex items-center gap-3">
|
|
|
+ <div className="w-9 h-9 rounded-xl bg-white border border-slate-200 flex items-center justify-center">
|
|
|
+ <User size={16} className="text-slate-400" />
|
|
|
+ </div>
|
|
|
+ <div>
|
|
|
+ <p className="text-sm font-black text-slate-900">{m.user?.username || m.userId}</p>
|
|
|
+ <p className="text-[10px] font-bold text-slate-400 uppercase tracking-widest">{m.role}</p>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <button
|
|
|
+ onClick={() => handleRemoveMember(managingMembersTenantId, m.userId)}
|
|
|
+ className="p-2 hover:bg-red-50 text-slate-400 hover:text-red-500 rounded-xl transition-all"
|
|
|
+ title="Remove member"
|
|
|
+ >
|
|
|
+ <Trash2 size={14} />
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ ))}
|
|
|
+ </div>
|
|
|
+ )}
|
|
|
+ </div>
|
|
|
+
|
|
|
+ {/* Add Users Section */}
|
|
|
+ <div>
|
|
|
+ <h4 className="text-[10px] font-black text-slate-400 uppercase tracking-widest mb-3">Add Users</h4>
|
|
|
+ <div className="relative mb-3">
|
|
|
+ <div className="absolute left-4 top-1/2 -translate-y-1/2 text-slate-400">
|
|
|
+ <User size={16} />
|
|
|
+ </div>
|
|
|
+ <input
|
|
|
+ className="w-full pl-10 pr-4 py-3 bg-slate-50 border border-slate-200 rounded-2xl text-sm font-medium focus:ring-4 focus:ring-emerald-500/10 focus:border-emerald-500/50 outline-none transition-all"
|
|
|
+ placeholder="Search users by name..."
|
|
|
+ value={memberUserSearch}
|
|
|
+ onChange={e => setMemberUserSearch(e.target.value)}
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ {availableUsers.length === 0 ? (
|
|
|
+ <div className="py-6 text-center bg-slate-50 rounded-2xl opacity-50">
|
|
|
+ <p className="text-xs font-bold uppercase tracking-widest text-slate-400">
|
|
|
+ {memberUserSearch ? 'No matching users' : 'All users already added'}
|
|
|
+ </p>
|
|
|
+ </div>
|
|
|
+ ) : (
|
|
|
+ <div className="space-y-2 max-h-[220px] overflow-y-auto scrollbar-hide">
|
|
|
+ {availableUsers.map(u => (
|
|
|
+ <button
|
|
|
+ key={u.id}
|
|
|
+ onClick={() => handleAddMember(managingMembersTenantId, u.id)}
|
|
|
+ className="w-full flex items-center justify-between p-4 bg-slate-50/50 hover:bg-emerald-50 border border-slate-200/50 hover:border-emerald-200 rounded-2xl transition-all group"
|
|
|
+ >
|
|
|
+ <div className="flex items-center gap-3">
|
|
|
+ <div className="w-9 h-9 rounded-xl bg-white border border-slate-200 flex items-center justify-center group-hover:text-emerald-600 transition-all">
|
|
|
+ <User size={16} className="text-slate-400 group-hover:text-emerald-600" />
|
|
|
+ </div>
|
|
|
+ <div className="text-left">
|
|
|
+ <p className="text-sm font-black text-slate-900">{u.username}</p>
|
|
|
+ <p className="text-[10px] font-bold text-slate-400 uppercase tracking-widest">{u.role || 'User'}</p>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div className="flex items-center gap-1.5 px-3 py-1 bg-emerald-50 group-hover:bg-emerald-100 text-emerald-700 rounded-xl text-[10px] font-black uppercase tracking-widest transition-all">
|
|
|
+ <Plus size={12} />
|
|
|
+ Add
|
|
|
+ </div>
|
|
|
+ </button>
|
|
|
+ ))}
|
|
|
+ </div>
|
|
|
+ )}
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div className="pt-6 border-t border-slate-100 flex justify-end mt-4">
|
|
|
+ <button
|
|
|
+ onClick={() => { setManagingMembersTenantId(null); setTenantMembers([]); }}
|
|
|
+ className="px-6 py-3 text-sm font-bold text-slate-500 hover:text-slate-700 transition-all"
|
|
|
+ >
|
|
|
+ Done
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ </motion.div>
|
|
|
+ </div>
|
|
|
+ );
|
|
|
+ })()}
|
|
|
+ </AnimatePresence>
|
|
|
</div>
|
|
|
);
|
|
|
const renderKnowledgeBaseTab = () => (
|
|
|
@@ -1122,6 +1373,48 @@ export const SettingsView: React.FC<SettingsViewProps> = ({
|
|
|
</div>
|
|
|
</section>
|
|
|
|
|
|
+ {/* Indexing & Chunking Configuration */}
|
|
|
+ <section className="bg-white/80 backdrop-blur-md p-8 rounded-3xl border border-slate-200/50 shadow-sm space-y-6">
|
|
|
+ <div className="flex items-center gap-3 text-slate-900 font-black uppercase tracking-widest text-[11px] border-b border-slate-100 pb-4">
|
|
|
+ <div className="w-8 h-8 rounded-xl bg-orange-50 flex items-center justify-center text-orange-600">
|
|
|
+ <BookOpen size={16} />
|
|
|
+ </div>
|
|
|
+ Indexing & Chunking Configuration
|
|
|
+ </div>
|
|
|
+ <div className="grid grid-cols-1 md:grid-cols-2 gap-8">
|
|
|
+ <div>
|
|
|
+ <div className="flex justify-between mb-3 px-1">
|
|
|
+ <label className="text-[10px] font-black text-slate-400 uppercase tracking-widest">Chunk Size (Tokens)</label>
|
|
|
+ <span className="text-sm font-black text-indigo-600 bg-indigo-50 px-2 py-0.5 rounded-lg">{localKbSettings.chunkSize || 1000}</span>
|
|
|
+ </div>
|
|
|
+ <input
|
|
|
+ type="range"
|
|
|
+ min="100"
|
|
|
+ max="8192"
|
|
|
+ step="100"
|
|
|
+ value={localKbSettings.chunkSize || 1000}
|
|
|
+ onChange={(e) => handleUpdateKbSettings('chunkSize', parseInt(e.target.value))}
|
|
|
+ className="w-full h-2 bg-slate-100 rounded-lg appearance-none cursor-pointer accent-indigo-600"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ <div>
|
|
|
+ <div className="flex justify-between mb-3 px-1">
|
|
|
+ <label className="text-[10px] font-black text-slate-400 uppercase tracking-widest">Chunk Overlap</label>
|
|
|
+ <span className="text-sm font-black text-indigo-600 bg-indigo-50 px-2 py-0.5 rounded-lg">{localKbSettings.chunkOverlap || 100}</span>
|
|
|
+ </div>
|
|
|
+ <input
|
|
|
+ type="range"
|
|
|
+ min="0"
|
|
|
+ max="2048"
|
|
|
+ step="10"
|
|
|
+ value={localKbSettings.chunkOverlap || 100}
|
|
|
+ onChange={(e) => handleUpdateKbSettings('chunkOverlap', parseInt(e.target.value))}
|
|
|
+ className="w-full h-2 bg-slate-100 rounded-lg appearance-none cursor-pointer accent-indigo-600"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </section>
|
|
|
+
|
|
|
{/* Chat Hyperparameters */}
|
|
|
<section className="bg-white/80 backdrop-blur-md p-8 rounded-3xl border border-slate-200/50 shadow-sm space-y-6">
|
|
|
<div className="flex items-center gap-3 text-slate-900 font-black uppercase tracking-widest text-[11px] border-b border-slate-100 pb-4">
|