Newer
Older
cortex-hub / frontend / src / features / settings / components / SettingsPageContent.js
import React from 'react';
import VoicesModal from './components/VoicesModal';

export default function SettingsPageContent({ context }) {
  const {
    config,
    effective,
    loading,
    saving,
    message,
    activeConfigTab,
    setActiveConfigTab,
    activeAdminTab,
    setActiveAdminTab,
    userSearch,
    setUserSearch,
    expandedProvider,
    setExpandedProvider,
    selectedNewProvider,
    setSelectedNewProvider,
    verifying,
    setVerifying,
    fetchedModels,
    setFetchedModels,
    providerLists,
    providerStatuses,
    voiceList,
    showVoicesModal,
    setShowVoicesModal,
    voicesLoading,
    allUsers,
    usersLoading,
    loadUsers,
    allGroups,
    groupsLoading,
    editingGroup,
    setEditingGroup,
    addingSection,
    setAddingSection,
    addForm,
    setAddForm,
    allNodes,
    nodesLoading,
    allSkills,
    skillsLoading,
    accessibleNodes,
    nodePrefs,
    fileInputRef,
    handleViewVoices,
    handleRoleToggle,
    handleGroupChange,
    handleNodePrefChange,
    toggleDefaultNode,
    handleSaveGroup,
    handleDeleteGroup,
    handleSave,
    handleImport,
    handleExport,
    inputClass,
    labelClass,
    sectionClass,
    filteredUsers,
    sortedGroups,
    renderProviderSection
  } = context;

  return (
    <>
      <div className="h-full overflow-y-auto py-10 px-4 sm:px-6 lg:px-8 font-sans">
      <div className="max-w-3xl mx-auto">
        <div className="flex justify-between items-end mb-2">
          <h1 className="text-3xl font-extrabold text-gray-900 dark:text-white tracking-tight">Configuration</h1>
          <div className="flex gap-2">
            <input
              type="file"
              ref={fileInputRef}
              style={{ display: 'none' }}
              accept=".yaml,.yml"
              onChange={handleImport}
            />
            <button
              type="button"
              onClick={() => fileInputRef.current?.click()}
              className="text-sm font-semibold px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-800 text-gray-700 dark:text-gray-300 transition-colors shadow-sm flex items-center gap-2"
            >
              <svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M4 16v1a2 2 0 002 2h12a2 2 0 002-2v-1m-4-8l-4-4m0 0L8 8m4-4v12" /></svg>
              Import
            </button>
            <button
              type="button"
              onClick={handleExport}
              className="text-sm font-semibold px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-800 text-gray-700 dark:text-gray-300 transition-colors shadow-sm flex items-center gap-2"
            >
              <svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M4 16v1a2 2 0 002 2h12a2 2 0 002-2v-1m-4-4l-4 4m0 0l-4-4m4 4V4" /></svg>
              Export
            </button>
          </div>
        </div>
        <p className="text-gray-600 dark:text-gray-400 mb-8">
          Customize your AI models, backend API tokens, and providers. These settings override system defaults.
        </p>

        {message.text && (
          <div className={`p-4 rounded-xl mb-6 shadow-sm border ${message.type === 'error' ? 'bg-red-50 dark:bg-red-900/30 text-red-700 dark:text-red-400 border-red-200 dark:border-red-800' : 'bg-emerald-50 dark:bg-emerald-900/30 text-emerald-700 dark:text-emerald-400 border-emerald-200 dark:border-emerald-800'}`}>
            {message.text}
          </div>
        )}

        <div className="space-y-12 pb-20">
          {/* Card 1: AI Provider Configuration */}
          <div className="bg-white dark:bg-gray-800 rounded-3xl shadow-xl overflow-hidden border border-gray-100 dark:border-gray-700 backdrop-blur-sm">
            <div className="p-6 border-b border-gray-100 dark:border-gray-700 bg-gray-50/30 dark:bg-gray-800/50">
              <h2 className="text-xl font-black text-gray-900 dark:text-white flex items-center gap-3">
                <svg className="w-6 h-6 text-indigo-500" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2.5} d="M19.428 15.428a2 2 0 00-1.022-.547l-2.387-.477a2 2 0 00-1.96 1.414l-.503 1.508a10 10 0 01-4.322-4.322l1.508-.503a2 2 0 001.414-1.96l-.477-2.387a2 2 0 00-.547-1.022L7.707 4.707a1 1 0 00-1.414 0l-1.586 1.586a1 1 0 00-.233 1.03 10.001 10.001 0 0011.95 11.95 1 1 0 001.03-.233l1.586-1.586a1 1 0 000-1.414l-1.586-1.586z" /></svg>
                AI Resource Configuration
              </h2>
              <p className="text-xs text-gray-400 mt-1 uppercase font-bold tracking-widest">Manage your providers, models, and API keys</p>
            </div>

            {/* Config Tabs */}
            <div className="flex border-b border-gray-200 dark:border-gray-700 bg-gray-50/50 dark:bg-gray-800/50 overflow-x-auto no-scrollbar">
              {['llm', 'tts', 'stt'].map((tab) => (
                <button
                  key={tab}
                  type="button"
                  onClick={() => setActiveConfigTab(tab)}
                  className={`flex-1 min-w-[100px] py-4 text-[10px] font-black uppercase tracking-widest transition-all duration-200 focus:outline-none ${activeConfigTab === tab
                    ? 'text-indigo-600 dark:text-indigo-400 border-b-2 border-indigo-600 dark:border-indigo-400 bg-white dark:bg-gray-800'
                    : 'text-gray-400 dark:text-gray-500 hover:text-gray-600 dark:hover:text-gray-300 hover:bg-gray-100/50 dark:hover:bg-gray-700/30'
                    }`}
                >
                  {tab === 'llm' ? 'Models' : tab === 'tts' ? 'TTS' : 'STT'}
                </button>
              ))}
            </div>

            <form onSubmit={handleSave} className="p-6 sm:p-8 space-y-6">
              {/* LLM Settings */}
              {activeConfigTab === 'llm' && (
                <div className={sectionClass}>
                  {renderProviderSection('llm', providerLists.llm, false)}
                </div>
              )}

              {/* TTS Settings */}
              {activeConfigTab === 'tts' && (
                <div className={sectionClass}>
                  {renderProviderSection('tts', providerLists.tts, true)}
                </div>
              )}

              {/* STT Settings */}
              {activeConfigTab === 'stt' && (
                <div className={sectionClass}>
                  {renderProviderSection('stt', providerLists.stt, false)}
                </div>
              )}

              <div className="pt-6 mt-6 border-t border-gray-100 dark:border-gray-700 flex items-center justify-end">
                <button
                  type="submit"
                  disabled={saving}
                  className="px-6 py-3 bg-indigo-600 hover:bg-indigo-700 text-white font-bold rounded-xl shadow-lg shadow-indigo-200 dark:shadow-indigo-900/50 transform transition duration-200 hover:scale-[1.02] focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 disabled:opacity-75 disabled:cursor-not-allowed disabled:transform-none flex items-center gap-2"
                >
                  {saving && (
                    <svg className="animate-spin -ml-1 mr-2 h-5 w-5 text-white" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
                      <circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle>
                      <path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
                    </svg>
                  )}
                  <span>Save Configuration</span>
                </button>
              </div>
            </form>
          </div>

          {/* Card 2: Team & Access Management */}
          <div className="bg-white dark:bg-gray-800 rounded-3xl shadow-xl overflow-hidden border border-gray-100 dark:border-gray-700 backdrop-blur-sm">
            <div className="p-6 border-b border-gray-100 dark:border-gray-700 bg-gray-50/30 dark:bg-gray-800/50">
              <h2 className="text-xl font-black text-gray-900 dark:text-white flex items-center gap-3">
                <svg className="w-6 h-6 text-indigo-500" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2.5} d="M12 4.354a4 4 0 110 5.292M15 21H3v-1a6 6 0 0112 0v1zm0 0h6v-1a6 6 0 00-9-5.197M13 7a4 4 0 11-8 0 4 4 0 018 0z" /></svg>
                Identity & Access Governance
              </h2>
              <p className="text-xs text-gray-400 mt-1 uppercase font-bold tracking-widest">Define groups, policies, and manage members</p>
            </div>

            {/* Admin Tabs */}
            <div className="flex border-b border-gray-200 dark:border-gray-700 bg-gray-50/50 dark:bg-gray-800/50 overflow-x-auto no-scrollbar">
              {['groups', 'users', 'personal'].map((tab) => (
                <button
                  key={tab}
                  type="button"
                  onClick={() => setActiveAdminTab(tab)}
                  className={`flex-1 min-w-[100px] py-4 text-[10px] font-black uppercase tracking-widest transition-all duration-200 focus:outline-none ${activeAdminTab === tab
                    ? 'text-indigo-600 dark:text-indigo-400 border-b-2 border-indigo-600 dark:border-indigo-400 bg-white dark:bg-gray-800'
                    : 'text-gray-400 dark:text-gray-500 hover:text-gray-600 dark:hover:text-gray-300 hover:bg-gray-100/50 dark:hover:bg-gray-700/30'
                    }`}
                >
                  {tab === 'groups' ? 'Groups' : tab === 'users' ? 'Users' : 'My Profile'}
                </button>
              ))}
            </div>

            <div className="p-6 sm:p-8">
              {/* Groups Management */}
              {activeAdminTab === 'groups' && (
                <div className={sectionClass}>
                  {!editingGroup ? (
                    <div className="space-y-6">
                      <div className="flex items-center justify-between">
                        <h3 className="text-lg font-black flex items-center gap-3 text-gray-900 dark:text-white">
                          Registered Groups
                        </h3>
                        <button
                          type="button"
                          onClick={() => setEditingGroup({ id: 'new', name: '', description: '', policy: { llm: [], tts: [], stt: [], nodes: [], skills: [] } })}
                          className="px-4 py-2 bg-indigo-600 hover:bg-indigo-700 text-white text-[10px] font-black uppercase tracking-widest rounded-lg shadow-md transition-all flex items-center gap-2"
                        >
                          <svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={3} d="M12 4v16m8-8H4" /></svg>
                          Add Group
                        </button>
                      </div>

                      <div className="grid grid-cols-1 gap-4">
                        {sortedGroups.map((g) => (
                          <div key={g.id} className={`p-4 border ${g.id === 'ungrouped' ? 'border-indigo-300 dark:border-indigo-700 bg-indigo-50/30 dark:bg-indigo-900/10' : 'border-gray-100 dark:border-gray-700 bg-gray-50/50 dark:bg-gray-900/30'} rounded-2xl flex justify-between items-center group/card hover:bg-white dark:hover:bg-gray-800 transition-all hover:shadow-md`}>
                            <div>
                              <h4 className="font-bold text-gray-900 dark:text-white flex items-center gap-2">
                                {g.id === 'ungrouped' ? 'Standard / Guest Policy' : g.name}
                                {g.id === 'ungrouped' && <span className="text-[10px] font-black uppercase tracking-widest text-indigo-600 dark:text-indigo-400 py-0.5 px-2 bg-indigo-100/50 dark:bg-indigo-900/40 rounded-full">Global Fallback</span>}
                              </h4>
                              <p className="text-xs text-gray-500 dark:text-gray-400 mt-1">
                                {g.id === 'ungrouped' ? 'Baseline access for all unassigned members.' : (g.description || 'No description')}
                              </p>
                              <div className="flex gap-2 mt-3 overflow-x-auto no-scrollbar pb-1">
                                {['llm', 'tts', 'stt', 'nodes', 'skills'].map(section => (
                                  <div key={section} className="flex flex-col items-center">
                                    <span className="text-[10px] font-black uppercase tracking-tight text-gray-400 mb-1">{section === 'nodes' ? 'Accessible Nodes' : `${section} Access`}</span>
                                    <div className="flex -space-x-2">
                                      {g.policy?.[section]?.length > 0 ? (
                                        g.policy?.[section].slice(0, 3).map(p => (
                                          <div key={p} className={`w-5 h-5 rounded-full ${section === 'nodes' ? 'bg-emerald-100 dark:bg-emerald-900 text-emerald-600 dark:text-emerald-400' : (section === 'skills' ? 'bg-amber-100 dark:bg-amber-900 text-amber-600 dark:text-amber-400' : 'bg-indigo-100 dark:bg-indigo-900 text-indigo-600 dark:text-indigo-400')} border-2 border-white dark:border-gray-800 flex items-center justify-center text-[8px] font-bold`} title={p}>
                                            {p[0].toUpperCase()}
                                          </div>
                                        ))
                                      ) : (
                                        <span className="text-[10px] text-gray-400 italic">None</span>
                                      )}
                                      {g.policy?.[section]?.length > 3 && (
                                        <div className="w-5 h-5 rounded-full bg-gray-100 dark:bg-gray-700 border-2 border-white dark:border-gray-800 flex items-center justify-center text-[8px] font-bold text-gray-500">
                                          +{g.policy?.[section].length - 3}
                                        </div>
                                      )}
                                    </div>
                                  </div>
                                ))}
                              </div>
                            </div>
                            <div className="flex gap-2">
                              <button
                                type="button"
                                onClick={() => setEditingGroup(g)}
                                className="p-2 text-indigo-600 hover:bg-indigo-50 dark:hover:bg-indigo-900/30 rounded-lg transition-colors"
                              >
                                <svg className="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15.232 5.232l3.536 3.536m-2.036-5.036a2.5 2.5 0 113.536 3.536L6.5 21.036H3v-3.572L16.732 3.732z" /></svg>
                              </button>
                              {g.id !== 'ungrouped' && (
                                <button
                                  type="button"
                                  onClick={() => handleDeleteGroup(g.id)}
                                  className="p-2 text-red-500 hover:bg-red-50 dark:hover:bg-red-900/30 rounded-lg transition-colors"
                                >
                                  <svg className="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" /></svg>
                                </button>
                              )}
                            </div>
                          </div>
                        ))}
                      </div>
                    </div>
                  ) : (
                    <div className="animate-fade-in space-y-6">
                      {/* (Group editing form - unchanged logic, just cleaner container) */}
                      <div className="flex items-center gap-4">
                        <button type="button" onClick={() => setEditingGroup(null)} className="p-2 text-gray-400 hover:text-gray-600 dark:hover:text-gray-200">
                          <svg className="w-6 h-6" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M10 19l-7-7m0 0l7-7m-7 7h18" /></svg>
                        </button>
                        <h3 className="text-xl font-black text-gray-900 dark:text-white">
                          {editingGroup.id === 'new' ? 'New Group Policy' : `Edit: ${editingGroup.id === 'ungrouped' ? 'Standard / Guest Policy' : editingGroup.name}`}
                        </h3>
                        {editingGroup.id === 'ungrouped' && (
                          <span className="text-[10px] font-black uppercase tracking-widest text-amber-700 dark:text-amber-400 py-1 px-2.5 bg-amber-100/60 dark:bg-amber-900/30 border border-amber-200 dark:border-amber-800 rounded-full flex items-center gap-1">
                            <svg className="w-3 h-3" fill="currentColor" viewBox="0 0 20 20"><path fillRule="evenodd" d="M5 9V7a5 5 0 0110 0v2a2 2 0 012 2v5a2 2 0 01-2 2H5a2 2 0 01-2-2v-5a2 2 0 012-2zm8-2v2H7V7a3 3 0 016 0z" clipRule="evenodd" /></svg>
                            System Group
                          </span>
                        )}
                      </div>

                      <div className="bg-gray-50/50 dark:bg-gray-900/30 p-6 rounded-3xl border border-gray-100 dark:border-gray-700 space-y-6">
                        <div className="grid grid-cols-1 sm:grid-cols-2 gap-6">
                          <div>
                            <label className={labelClass}>Group Name</label>
                            <input
                              value={editingGroup.id === 'ungrouped' ? 'Ungrouped (System Default)' : editingGroup.name}
                              onChange={e => editingGroup.id !== 'ungrouped' && setEditingGroup({ ...editingGroup, name: e.target.value })}
                              readOnly={editingGroup.id === 'ungrouped'}
                              placeholder="Engineering, Designers, etc."
                              className={`${inputClass} ${editingGroup.id === 'ungrouped'
                                ? 'opacity-60 cursor-not-allowed bg-gray-100 dark:bg-gray-700 text-gray-500 dark:text-gray-400'
                                : (editingGroup.name.trim() &&
                                  allGroups.some(g => g.id !== editingGroup.id && g.name.toLowerCase() === editingGroup.name.trim().toLowerCase())
                                  ? '!border-red-400 dark:!border-red-600 !ring-red-300'
                                  : '')
                                }`}
                            />
                            {editingGroup.id === 'ungrouped' ? (
                              <p className="mt-1.5 text-xs text-amber-600 dark:text-amber-400 font-medium flex items-center gap-1">
                                <svg className="w-3.5 h-3.5" fill="currentColor" viewBox="0 0 20 20"><path fillRule="evenodd" d="M5 9V7a5 5 0 0110 0v2a2 2 0 012 2v5a2 2 0 01-2 2H5a2 2 0 01-2-2v-5a2 2 0 012-2zm8-2v2H7V7a3 3 0 016 0z" clipRule="evenodd" /></svg>
                                System group name is locked. Only the access policy can be changed.
                              </p>
                            ) : editingGroup.name.trim() &&
                              allGroups.some(g => g.id !== editingGroup.id && g.name.toLowerCase() === editingGroup.name.trim().toLowerCase()) && (
                              <p className="mt-1.5 text-xs font-semibold text-red-500 flex items-center gap-1">
                                <svg className="w-3.5 h-3.5" fill="currentColor" viewBox="0 0 20 20"><path fillRule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7 4a1 1 0 11-2 0 1 1 0 012 0zm-1-9a1 1 0 00-1 1v4a1 1 0 102 0V6a1 1 0 00-1-1z" clipRule="evenodd" /></svg>
                                A group with this name already exists
                              </p>
                            )}
                          </div>
                          <div>
                            <label className={labelClass}>Description</label>
                            <input
                              value={editingGroup.description || ''}
                              onChange={e => setEditingGroup({ ...editingGroup, description: e.target.value })}
                              placeholder="Short description of this group..."
                              className={inputClass}
                            />
                          </div>
                        </div>

                        <div className="border-t border-gray-200 dark:border-gray-700 pt-6">
                          <label className="text-sm font-black uppercase tracking-widest text-gray-500 mb-4 block">Provider Access Policy (Whitelists)</label>

                          <div className="grid grid-cols-1 gap-8">
                            {['llm', 'tts', 'stt', 'nodes', 'skills'].map(section => (
                              <div key={section} className="space-y-3">
                                <div className="flex items-center justify-between mb-2">
                                  <span className="text-[10px] font-black uppercase tracking-widest text-gray-400">{section === 'nodes' ? 'Accessible Nodes' : `${section} Access`}</span>
                                  <div className="flex gap-2">
                                    <button type="button" onClick={() => {
                                      let availableIds = [];
                                      if (section === 'nodes') {
                                        availableIds = allNodes.map(n => n.node_id);
                                      } else if (section === 'skills') {
                                        availableIds = allSkills.filter(s => !s.is_system).map(s => s.name);
                                      } else {
                                        availableIds = effective[section]?.providers ? Object.keys(effective[section].providers) : [];
                                      }
                                      setEditingGroup({ ...editingGroup, policy: { ...editingGroup.policy, [section]: availableIds } })
                                    }} className="text-[10px] font-bold text-indigo-600 dark:text-indigo-400 hover:underline">Select All</button>
                                    <button type="button" onClick={() => setEditingGroup({ ...editingGroup, policy: { ...editingGroup.policy, [section]: [] } })} className="text-[10px] font-bold text-red-600 dark:text-red-400 hover:underline">Clear</button>
                                  </div>
                                </div>
                                <div className="grid grid-cols-2 sm:grid-cols-3 gap-2">
                                  {(section === 'nodes' ? allNodes.map(n => ({ id: n.node_id, label: n.display_name })) :
                                    (section === 'skills' ? allSkills.filter(s => !s.is_system).map(s => ({ id: s.name, label: s.name })) :
                                      (effective[section]?.providers ? Object.keys(effective[section].providers) : []).map(pId => {
                                        const baseType = pId.split('_')[0];
                                        const baseDef = providerLists[section].find(ld => ld.id === baseType || ld.id === pId);
                                        return { id: pId, label: baseDef ? (pId.includes('_') ? `${baseDef.label} (${pId.split('_').slice(1).join('_')})` : baseDef.label) : pId };
                                      }))).map(item => {
                                        const isChecked = (editingGroup.policy?.[section] || []).includes(item.id);
                                        return (
                                          <label key={item.id} className={`flex items-center gap-2 px-3 py-2 rounded-xl border cursor-pointer transition-all ${isChecked ? 'bg-indigo-50 dark:bg-indigo-900/30 border-indigo-200 dark:border-indigo-800' : 'border-gray-100 dark:border-gray-700 hover:bg-gray-50 dark:hover:bg-gray-800/50'}`}>
                                            <input
                                              type="checkbox"
                                              checked={isChecked}
                                              onChange={(e) => {
                                                const current = editingGroup.policy?.[section] || [];
                                                const next = e.target.checked ? [...current, item.id] : current.filter(x => x !== item.id);
                                                setEditingGroup({ ...editingGroup, policy: { ...editingGroup.policy, [section]: next } });
                                              }}
                                              className="rounded border-gray-300 dark:border-gray-600 text-indigo-600 focus:ring-indigo-500"
                                            />
                                            <span className="text-[11px] font-bold text-gray-700 dark:text-gray-300 truncate" title={item.label}>{item.label}</span>
                                          </label>
                                        );
                                      })}
                                </div>
                                {section === 'nodes' && allNodes.length === 0 && (
                                  <p className="text-[10px] text-gray-400 italic">No agent nodes registered yet.</p>
                                )}
                              </div>
                            ))}
                          </div>

                          <div className="flex justify-end gap-3 pt-4">
                            <button type="button" onClick={() => setEditingGroup(null)} className="px-6 py-2 rounded-xl text-sm font-bold text-gray-500 hover:bg-gray-100 dark:hover:bg-gray-800 transition-colors">Cancel</button>
                            <button type="button" onClick={handleSaveGroup} disabled={saving || !editingGroup.name || allGroups.some(g => g.id !== editingGroup.id && g.name.toLowerCase() === editingGroup.name.trim().toLowerCase())} className="px-8 py-2 bg-indigo-600 text-white rounded-xl text-sm font-black uppercase tracking-widest shadow-lg shadow-indigo-200 dark:shadow-indigo-900/50 hover:bg-indigo-700 disabled:opacity-50 transition-all">
                              {saving ? 'Saving...' : 'Save Group'}
                            </button>
                          </div>
                        </div>
                      </div>
                    </div>
                  )}
                </div>
              )}

              {/* Users Management */}
              {activeAdminTab === 'users' && (
                <div className={sectionClass}>
                  <div className="space-y-6">
                    <div className="flex flex-col sm:flex-row items-center justify-between gap-4">
                      <h3 className="text-lg font-black flex items-center gap-3 text-gray-900 dark:text-white">
                        Active Roster
                        <span className="text-[10px] py-0.5 px-2 bg-gray-100 dark:bg-gray-700 text-gray-400 rounded-full">{filteredUsers.length}</span>
                      </h3>
                      <div className="flex items-center gap-3 w-full sm:w-auto">
                        <div className="relative flex-1 sm:min-w-[250px]">
                          <input
                            type="text"
                            value={userSearch}
                            onChange={e => setUserSearch(e.target.value)}
                            placeholder="Search by name, email..."
                            className="w-full text-xs p-2.5 pl-9 bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-xl focus:ring-2 focus:ring-indigo-500 outline-none transition-all"
                          />
                          <svg className="w-4 h-4 text-gray-400 absolute left-3 top-1/2 -translate-y-1/2" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2.5} d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" /></svg>
                        </div>
                        <button onClick={loadUsers} className="p-2.5 text-indigo-600 hover:bg-indigo-50 dark:hover:bg-indigo-900/30 border border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800 rounded-xl transition-colors">
                          <svg className={`w-4 h-4 ${usersLoading ? 'animate-spin' : ''}`} fill="none" viewBox="0 0 24 24" stroke="currentColor"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" /></svg>
                        </button>
                      </div>
                    </div>
                    <div className="overflow-hidden border border-gray-100 dark:border-gray-700 rounded-2xl shadow-sm bg-gray-50/50 dark:bg-gray-900/30">
                      <table className="w-full text-left border-collapse">
                        <thead className="bg-white dark:bg-gray-800">
                          <tr>
                            <th className="px-6 py-4 text-[9px] font-black uppercase tracking-widest text-gray-500">Member</th>
                            <th className="px-6 py-4 text-[9px] font-black uppercase tracking-widest text-gray-500">Policy Group</th>
                            <th className="px-6 py-4 text-[9px] font-black uppercase tracking-widest text-gray-500">Activity Auditing</th>
                            <th className="px-6 py-4 text-[9px] font-black uppercase tracking-widest text-gray-500 text-right">Actions</th>
                          </tr>
                        </thead>
                        <tbody className="divide-y divide-gray-100 dark:divide-gray-800">
                          {filteredUsers.map((u) => (
                            <tr key={u.id} className="hover:bg-white dark:hover:bg-gray-800/50 transition-colors">
                              <td className="px-6 py-4">
                                <div className="flex items-center gap-3">
                                  <div className={`w-8 h-8 rounded-full flex items-center justify-center text-[10px] font-black ${u.role === 'admin' ? 'bg-indigo-100 text-indigo-700 dark:bg-indigo-900/40 dark:text-indigo-300' : 'bg-gray-100 text-gray-700 dark:bg-gray-700 dark:text-gray-300'}`}>
                                    {(u.username || u.email || '?')[0].toUpperCase()}
                                  </div>
                                  <div>
                                    <p className="text-xs font-black text-gray-900 dark:text-white truncate max-w-[150px]">{u.username || u.email}</p>
                                    <p className="text-[10px] text-gray-400 font-bold uppercase tracking-tight">{u.role}</p>
                                  </div>
                                </div>
                              </td>
                              <td className="px-6 py-4">
                                <select
                                  value={u.group_id || 'ungrouped'}
                                  onChange={(e) => handleGroupChange(u.id, e.target.value)}
                                  className="text-xs p-1.5 bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg outline-none focus:ring-1 focus:ring-indigo-500 transition-all font-bold text-gray-600 dark:text-gray-300 min-w-[150px]"
                                >
                                  {sortedGroups.map(g => (
                                    <option key={g.id} value={g.id}>
                                      {g.id === 'ungrouped' ? 'Standard / Guest' : g.name}
                                    </option>
                                  ))}
                                </select>
                              </td>
                              <td className="px-6 py-4">
                                <div className="space-y-1">
                                  <div className="flex items-center gap-1.5">
                                    <span className="text-[9px] font-black uppercase text-gray-400">Join:</span>
                                    <span className="text-[10px] font-bold text-gray-600 dark:text-gray-300">{new Date(u.created_at).toLocaleDateString()}</span>
                                  </div>
                                  <div className="flex items-center gap-1.5">
                                    <span className="text-[9px] font-black uppercase text-gray-400">Last:</span>
                                    <span className="text-[10px] font-bold text-indigo-600 dark:text-indigo-400">
                                      {u.last_login_at ? new Date(u.last_login_at).toLocaleDateString() : 'Never'}
                                    </span>
                                  </div>
                                </div>
                              </td>
                              <td className="px-6 py-4 text-right">
                                <button
                                  type="button"
                                  onClick={(e) => { e.preventDefault(); handleRoleToggle(u); }}
                                  className={`text-[9px] font-black uppercase tracking-widest transition-all ${u.role === 'admin'
                                    ? 'text-red-600 hover:text-red-700'
                                    : 'text-indigo-600 hover:text-indigo-700'
                                    }`}
                                >
                                  {u.role === 'admin' ? 'Demote' : 'Promote'}
                                </button>
                              </td>
                            </tr>
                          ))}
                        </tbody>
                      </table>
                      {allUsers.length === 0 && !usersLoading && (
                        <div className="p-12 text-center text-gray-400 italic text-sm">No other users found.</div>
                      )}
                    </div>
                  </div>
                </div>
              )}

              {/* Personal Settings */}
              {activeAdminTab === 'personal' && (
                <div className={sectionClass}>
                  <div className="space-y-8">
                    <div className="flex items-center gap-3">
                      <div className="p-2 bg-indigo-50 dark:bg-indigo-900/30 rounded-lg text-indigo-600 dark:text-indigo-400">
                        <svg className="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2.5} d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z" /></svg>
                      </div>
                      <div>
                        <h3 className="text-lg font-black text-gray-900 dark:text-white uppercase tracking-wider">My Preferences</h3>
                        <p className="text-[10px] text-gray-500 dark:text-gray-400 font-bold uppercase tracking-widest">Customize your individual experience</p>
                      </div>
                    </div>

                    <div className="bg-gray-50/50 dark:bg-gray-900/30 p-6 rounded-3xl border border-gray-100 dark:border-gray-700 space-y-8">
                      {accessibleNodes.length > 0 ? (
                        <div className="space-y-4">
                          <label className="text-sm font-black uppercase tracking-widest text-gray-500 block">Default Node Attachment</label>
                          <p className="text-xs text-gray-400 mb-4">Auto-attach these nodes to new sessions:</p>
                          <div className="flex flex-wrap gap-2">
                            {accessibleNodes.map(node => {
                              const isActive = (nodePrefs.default_node_ids || []).includes(node.node_id);
                              return (
                                <button
                                  key={node.node_id}
                                  type="button"
                                  onClick={() => toggleDefaultNode(node.node_id)}
                                  className={`px-4 py-2 rounded-xl text-[10px] font-black uppercase tracking-widest border-2 transition-all ${isActive
                                    ? 'bg-emerald-600 border-emerald-600 text-white shadow-lg shadow-emerald-200 dark:shadow-none translate-y-[-1px]'
                                    : 'bg-white dark:bg-gray-800 border-gray-200 dark:border-gray-700 text-gray-400 hover:border-emerald-300'
                                    }`}
                                >
                                  {node.display_name}
                                </button>
                              );
                            })}
                          </div>
                        </div>
                      ) : (
                        <div className="p-4 bg-gray-100/50 dark:bg-gray-800/50 rounded-2xl border border-dashed border-gray-200 dark:border-gray-700">
                          <p className="text-xs text-gray-500 italic">No agent nodes are currently assigned to your group.</p>
                        </div>
                      )}

                      <div className="space-y-4 pt-4 border-t border-gray-100 dark:border-gray-700">
                        <label className="text-sm font-black uppercase tracking-widest text-gray-500 block">Default Sync Workspace Directory</label>
                        <div className="flex flex-col sm:flex-row gap-3">
                          <select
                            value={nodePrefs.data_source?.source || 'empty'}
                            onChange={(e) => handleNodePrefChange({ data_source: { ...nodePrefs.data_source, source: e.target.value } })}
                            className="bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-xl px-4 py-3 text-xs font-bold text-gray-700 dark:text-gray-300 focus:outline-none focus:ring-2 focus:ring-indigo-500 shadow-sm"
                          >
                            <option value="empty">Empty Workspace</option>
                            <option value="node_local">Node Local Path</option>
                          </select>
                          {nodePrefs.data_source?.source === 'node_local' && (
                            <input
                              type="text"
                              value={nodePrefs.data_source?.path || ''}
                              onChange={(e) => handleNodePrefChange({ data_source: { ...nodePrefs.data_source, path: e.target.value } })}
                              placeholder="/home/user/workspace"
                              className="flex-1 bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-xl px-4 py-3 text-xs font-mono text-indigo-600 dark:text-indigo-400 focus:outline-none focus:ring-2 focus:ring-indigo-500 shadow-sm"
                            />
                          )}
                        </div>
                        <p className="text-[10px] text-gray-400 italic font-medium">
                          Determines where the agent should look for files on the node when starting a chat.
                        </p>
                      </div>
                    </div>
                  </div>
                </div>
              )}
            </div>
            {showVoicesModal && (
              <div className="fixed inset-0 z-[100] flex items-center justify-center bg-black/50 p-4 transition-opacity" onClick={() => setShowVoicesModal(false)}>
                <div className="bg-white dark:bg-gray-800 rounded-xl max-w-lg w-full p-6 shadow-2xl relative max-h-[85vh] flex flex-col" onClick={e => e.stopPropagation()}>
                  <div className="flex justify-between items-center mb-4 border-b border-gray-100 dark:border-gray-700 pb-3">
                    <div>
                      <h3 className="text-xl font-bold text-gray-900 dark:text-gray-100">Available Cloud Voices</h3>
                      <p className="text-xs text-gray-500 mt-1">Found {voiceList.length} voices to choose from.</p>
                      <p className="text-xs text-indigo-500 font-medium mt-1">Highlighted voices (Chirp, Journey, Studio) use advanced AI for highest quality.</p>
                    </div>
                    <button onClick={() => setShowVoicesModal(false)} className="text-gray-400 hover:text-gray-600 dark:hover:text-gray-300 text-3xl font-bold focus:outline-none">&times;</button>
                  </div>
                  <div className="overflow-y-auto flex-1 bg-gray-50 dark:bg-gray-900/50 border border-gray-100 dark:border-gray-700 p-2 rounded-lg">
                    {voicesLoading ? (
                      <div className="flex h-32 items-center justify-center">
                        <div className="animate-spin rounded-full h-8 w-8 border-b-2 border-indigo-600 dark:border-indigo-400"></div>
                      </div>
                    ) : voiceList.length > 0 ? (
                      <ul className="space-y-[2px]">
                        {voiceList.map((v, i) => {
                          let highlight = v.toLowerCase().includes('chirp') || v.toLowerCase().includes('journey') || v.toLowerCase().includes('studio');
                          return (
                            <li key={i} className={`text-sm cursor-text font-mono select-all p-2 hover:bg-white dark:hover:bg-gray-800 rounded transition-colors border border-transparent hover:border-gray-200 dark:hover:border-gray-600 ${highlight ? 'text-indigo-600 dark:text-indigo-400 font-semibold' : 'text-gray-600 dark:text-gray-400'}`}>
                              {v}
                            </li>
                          );
                        })}
                      </ul>
                    ) : (
                      <p className="text-center text-gray-500 mt-8">No voices found. Make sure your API key is configured and valid.</p>
                    )}
                  </div>
                  <div className="mt-4 text-xs text-gray-500 dark:text-gray-400 flex justify-between items-center">
                    <span>Double-click a name to select it, then paste it into the field.</span>
                    <button onClick={() => setShowVoicesModal(false)} className="px-4 py-2 bg-gray-100 hover:bg-gray-200 dark:bg-gray-700 dark:hover:bg-gray-600 text-gray-700 dark:text-gray-300 rounded-lg transition-colors font-medium">Close</button>
                  </div>
                </div>
              </div>
            )}
          </div>
          </div>
        </div>
      </div>
    </>
  );
}