Newer
Older
cortex-hub / frontend / src / features / settings / components / cards / AIConfigurationCard.js
import React from 'react';
import ProviderPanel from '../shared/ProviderPanel';

const AIConfigurationCard = ({ context }) => {
  const {
      config,
      providerLists,
      providerStatuses,
      collapsedSections,
      setCollapsedSections,
      activeConfigTab,
      setActiveConfigTab,
      addingSection,
      setAddingSection,
      addForm,
      setAddForm,
      handleConfigChange,
      handleAddInstance,
      handleSaveConfig,
      saving,
      labelClass,
      inputClass,
      sectionClass,
      fetchedModels,
      expandedProvider,
      setExpandedProvider,
      verifying,
      handleVerifyProvider,
      handleDeleteProvider
  } = context;

  const renderConfigSection = (sectionKey, title, description) => {
    const sectionConfig = config[sectionKey] || {};
    const providers = sectionConfig.providers || {};
    const availableTypes = providerLists[sectionKey] || [];

    return (
      <div className="space-y-6 animate-fade-in">
        <div className="flex flex-col sm:flex-row sm:items-center justify-between gap-4 border-b border-gray-100 dark:border-gray-700 pb-4">
          <div>
            <h3 className="text-xl font-black text-gray-900 dark:text-white">{title} Resources</h3>
            <p className="text-xs text-gray-500 font-bold mt-1">{description}</p>
          </div>
        </div>

        <div className="grid grid-cols-1 gap-4">
          {Object.entries(providers).map(([id, prefs]) => (
            <ProviderPanel
              key={id}
              id={id}
              prefs={prefs}
              sectionKey={sectionKey}
              status={providerStatuses[`${sectionKey}_${id}`]}
              providers={providers}
              expandedProvider={expandedProvider}
              setExpandedProvider={setExpandedProvider}
              verifying={verifying}
              handleVerifyProvider={handleVerifyProvider}
              handleDeleteProvider={handleDeleteProvider}
              handleConfigChange={handleConfigChange}
              labelClass={labelClass}
              inputClass={inputClass}
              fetchedModels={fetchedModels}
            />
          ))}

          {addingSection === sectionKey ? (
            <div className="p-6 bg-indigo-50/50 dark:bg-indigo-900/20 border-2 border-dashed border-indigo-200 dark:border-indigo-800 rounded-3xl animate-fade-in">
              <div className="grid grid-cols-1 sm:grid-cols-2 gap-6">
                <div className="space-y-1.5">
                  <label className={labelClass}>Provider Type</label>
                  <select
                    value={addForm.type}
                    onChange={e => setAddForm({ ...addForm, type: e.target.value, model: '' })}
                    className={inputClass}
                  >
                    <option value="">Select reaching...</option>
                    {availableTypes.map(t => <option key={t.id} value={t.id}>{t.label}</option>)}
                  </select>
                </div>
                <div className="space-y-1.5">
                  <label className={labelClass}>Instance Suffix (Optional)</label>
                  <input
                    type="text"
                    id={`suffix-${sectionKey}`}
                    name={`suffix-${sectionKey}`}
                    placeholder="e.g. dev, prod, testing"
                    value={addForm.suffix}
                    onChange={e => setAddForm({ ...addForm, suffix: e.target.value })}
                    className={inputClass}
                    autoComplete="off"
                  />
                </div>
                {addForm.type && (
                  <div className="sm:col-span-2 grid grid-cols-1 sm:grid-cols-2 gap-4 animate-fade-in">
                    <div className="space-y-1.5">
                      <label className={labelClass}>{sectionKey === 'tts' && addForm.type === 'gcloud_tts' ? 'Voice ID / Language' : 'Primary Model'}</label>
                    <input
                      type="text"
                      id={`model-search-${sectionKey}`}
                      name={`model-search-${sectionKey}`}
                      list={`models-${sectionKey}-${addForm.type}`}
                      value={typeof addForm.model === 'object' ? (addForm.model.model_name || addForm.model.id || '') : (addForm.model || '')}
                      onChange={e => setAddForm({ ...addForm, model: e.target.value })}
                      placeholder="Search or enter model..."
                      className={inputClass}
                      autoComplete="off"
                    />
                    <datalist id={`models-${sectionKey}-${addForm.type}`}>
                      {(fetchedModels[`${sectionKey}_${addForm.type}`] || []).map(m => (
                        <option key={typeof m === 'string' ? m : m.model_name} value={typeof m === 'string' ? m : m.model_name} />
                      ))}
                    </datalist>
                    </div>
                  </div>
                )}
              </div>
              <div className="flex justify-end gap-3 mt-6">
                <button onClick={() => setAddingSection(null)} className="px-6 py-2 text-sm font-bold text-gray-500 hover:text-gray-700">Cancel</button>
                <button
                  onClick={() => handleAddInstance(sectionKey)}
                  disabled={!addForm.type}
                  className="px-8 py-2 bg-indigo-600 text-white rounded-xl text-xs font-black shadow-lg hover:bg-indigo-700 disabled:opacity-50 transition-all"
                >
                  Create Instance
                </button>
              </div>
            </div>
          ) : (
            <button
              onClick={() => { setAddingSection(sectionKey); setAddForm({ type: '', suffix: '', model: '' }); }}
              className="w-full p-6 border-2 border-dashed border-gray-200 dark:border-gray-700 rounded-3xl text-gray-400 hover:text-indigo-500 hover:border-indigo-300 dark:hover:border-indigo-800 transition-all flex flex-col items-center gap-3"
            >
              <div className="w-10 h-10 rounded-full bg-gray-50 dark:bg-gray-800 flex items-center justify-center">
                <svg className="w-6 h-6" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2.5} d="M12 4v16m8-8H4" /></svg>
              </div>
              <span className="text-xs font-black text-gray-500 dark:text-gray-400">Add New {title} Provider</span>
            </button>
          )}
        </div>

        
      </div>
    );
  };

  return (
    <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 transition-all duration-300">
      <div 
        onClick={() => setCollapsedSections(prev => ({ ...prev, ai: !prev.ai }))}
        className="p-6 border-b border-gray-100 dark:border-gray-700 bg-gray-50/30 dark:bg-gray-800/50 cursor-pointer hover:bg-gray-100/50 dark:hover:bg-gray-700/50 transition-colors flex items-center justify-between group"
      >
          <div className="flex items-center gap-3">
              <svg className={`w-6 h-6 text-emerald-500 transition-transform duration-300 ${collapsedSections.ai ? '-rotate-90' : 'rotate-0'}`} fill="none" viewBox="0 0 24 24" stroke="currentColor"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2.5} d="M13 10V3L4 14h7v7l9-11h-7z" /></svg>
              <div>
                <h2 className="text-xl font-black text-gray-900 dark:text-white flex items-center gap-3">
                    AI Resource Management
                </h2>
                <p className="text-[10px] text-gray-400 mt-1 font-bold leading-none">Global AI providers, model endpoints, and synthesis engines</p>
              </div>
          </div>
          <div className="bg-emerald-50 dark:bg-emerald-950/30 p-2 rounded-xl border border-emerald-100 dark:border-emerald-900/50 group-hover:scale-110 transition-transform duration-300">
            <svg className={`w-4 h-4 text-emerald-600 dark:text-emerald-400 transition-transform duration-300 ${collapsedSections.ai ? 'rotate-180' : 'rotate-0'}`} fill="none" viewBox="0 0 24 24" stroke="currentColor"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={3} d="M19 9l-7 7-7-7" /></svg>
          </div>
      </div>
      {!collapsedSections.ai && (
        <div className="animate-fade-in-down">
          <div className="flex border-b border-gray-200 dark:border-gray-700 bg-gray-100/30 dark:bg-gray-800/80 overflow-x-auto no-scrollbar">
              {[
                  { id: 'llm', label: 'Large Models' },
                  { id: 'tts', label: 'Speech Synthesis' },
                  { id: 'stt', label: 'Transcription' }
              ].map((tab) => (
                  <button
                      key={tab.id}
                      type="button"
                      onClick={() => setActiveConfigTab(tab.id)}
                      className={`flex-1 min-w-[120px] py-4 text-[10px] font-black transition-all duration-200 focus:outline-none ${activeConfigTab === tab.id
                          ? 'text-emerald-600 dark:text-emerald-400 border-b-2 border-emerald-600 dark:border-emerald-400 bg-white dark:bg-gray-800 shadow-sm z-10'
                          : '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.label}
                  </button>
              ))}
          </div>
          <div className="p-6 sm:p-10 bg-gradient-to-br from-white to-gray-50/50 dark:from-gray-800 dark:to-gray-900/50">
              {activeConfigTab === 'llm' && (
                  <div className={sectionClass}>
                      {renderConfigSection('llm', 'Large Language Model', 'Manage global AI providers, specialized models, and API endpoints.')}
                  </div>
              )}
              {activeConfigTab === 'tts' && (
                  <div className={sectionClass}>
                      {renderConfigSection('tts', 'Text-to-Speech', 'Configure voice synthesis engines and region-specific endpoints.')}
                  </div>
              )}
              {activeConfigTab === 'stt' && (
                  <div className={sectionClass}>
                      {renderConfigSection('stt', 'Speech-to-Text', 'Set up transcription services and language model defaults.')}
                  </div>
              )}
          </div>
          <div className="px-6 pb-6 flex justify-end">
            <button
              onClick={handleSaveConfig}
              disabled={saving}
              className="px-8 py-3 bg-emerald-600 hover:bg-emerald-700 text-white rounded-2xl text-xs font-black shadow-lg shadow-emerald-500/20 active:scale-95 transition-all flex items-center gap-2"
            >
              {saving ? 'Saving...' : 'Save AI Configuration'}
              <svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2.5} d="M5 13l4 4L19 7" /></svg>
            </button>
          </div>
        </div>
      )}
    </div>
  );
};

export default AIConfigurationCard;