| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294 |
- import React from 'react';
- import { AppSettings, ModelConfig, ModelType } from '../types';
- import { useLanguage } from '../contexts/LanguageContext';
- import { useConfirm } from '../contexts/ConfirmContext';
- import { Settings, Database, Sliders, Layers, Cpu, ChevronRight } from 'lucide-react';
- import VisionModelSelector from './VisionModelSelector';
- interface ConfigPanelProps {
- settings: AppSettings;
- models: ModelConfig[];
- onSettingsChange: (newSettings: AppSettings) => void;
- onOpenSettings: () => void;
- mode?: 'chat' | 'kb' | 'all';
- isAdmin?: boolean;
- }
- const ConfigPanel: React.FC<ConfigPanelProps> = ({ settings, models, onSettingsChange, onOpenSettings, mode = 'all', isAdmin = false }) => {
- const { t } = useLanguage();
- const { confirm } = useConfirm();
- const handleChange = (key: keyof AppSettings, value: any) => {
- onSettingsChange({
- ...settings,
- [key]: value,
- });
- };
- const llmModels = models.filter(m => m.type === ModelType.LLM && m.isEnabled !== false && !m.supportsVision);
- const embeddingModels = models.filter(m => m.type === ModelType.EMBEDDING && m.isEnabled !== false);
- const rerankModels = models.filter(m => m.type === ModelType.RERANK && m.isEnabled !== false);
- const showChatSettings = mode === 'chat' || mode === 'all';
- const showKbSettings = mode === 'kb' || mode === 'all';
- return (
- <div className="flex-1 overflow-y-auto p-4 space-y-6 bg-slate-50">
- {!isAdmin && (
- <div className="bg-orange-50 border border-orange-200 p-3 rounded-lg mb-4">
- <p className="text-xs text-orange-700 flex items-center gap-2">
- <Sliders className="w-3 h-3" />
- {t('onlyAdminCanModify') || "Only administrators can modify system settings."}
- </p>
- </div>
- )}
- {/* Model Selection (LLM) - Chat Mode Only */}
- {showChatSettings && (
- <div className="bg-white p-4 rounded-xl border border-slate-200 shadow-sm">
- <div className="flex items-center justify-between mb-4 border-b border-slate-100 pb-2">
- <div className="flex items-center gap-2 text-slate-800 font-semibold">
- <Cpu className="w-4 h-4 text-blue-600" />
- {t('headerModelSelection')}
- </div>
- </div>
- <div className="space-y-4">
- <div>
- <label className="block text-xs font-medium text-slate-500 mb-1.5">{t('selectLLMModel')}</label>
- <select
- value={settings.selectedLLMId}
- onChange={(e) => handleChange('selectedLLMId', e.target.value)}
- disabled={!isAdmin}
- className="w-full text-sm bg-slate-50 border border-slate-200 rounded-lg px-3 py-2 text-slate-700 focus:outline-none focus:border-blue-500 disabled:opacity-50 disabled:cursor-not-allowed"
- >
- <option value="">--- {t('selectLLMModel')} ---</option>
- {llmModels.map(m => (
- <option key={m.id} value={m.id}>
- {m.name} ({m.modelId})
- </option>
- ))}
- </select>
- </div>
- </div>
- </div>
- )}
- {/* Embedding Model Selection - KB Mode Only */}
- {showKbSettings && (
- <div className="bg-white p-4 rounded-xl border border-slate-200 shadow-sm">
- <div className="flex items-center justify-between mb-4 border-b border-slate-100 pb-2">
- <div className="flex items-center gap-2 text-slate-800 font-semibold">
- <Cpu className="w-4 h-4 text-blue-600" />
- {t('lblEmbedding')}
- </div>
- </div>
- <div className="space-y-4">
- <div>
- <label className="block text-xs font-medium text-slate-500 mb-1.5">{t('lblEmbedding')}</label>
- <select
- value={settings.selectedEmbeddingId}
- onChange={async (e) => {
- const newId = e.target.value;
- if (newId !== settings.selectedEmbeddingId && settings.selectedEmbeddingId) {
- if (await confirm(t('confirmChangeEmbeddingModel') || "WARNING: Changing the embedding model will require re-indexing all existing files. Are you sure?")) {
- handleChange('selectedEmbeddingId', newId);
- }
- } else {
- handleChange('selectedEmbeddingId', newId);
- }
- }}
- disabled={!isAdmin}
- className="w-full text-sm bg-slate-50 border border-slate-200 rounded-lg px-3 py-2 text-slate-700 focus:outline-none focus:border-blue-500 disabled:opacity-50 disabled:cursor-not-allowed"
- >
- <option value="">--- {t('selectEmbeddingModel')} ---</option>
- {embeddingModels.map(m => (
- <option key={m.id} value={m.id}>{m.name}</option>
- ))}
- </select>
- <p className="text-[10px] text-slate-400 mt-1">{t('defaultForUploads')}</p>
- <p className="text-[10px] text-orange-500 mt-1">{t('embeddingModelWarning') || "Changing this setting may require clearing and re-importing your knowledge base."}</p>
- </div>
- </div>
- </div>
- )}
- {/* Hyperparameters - Chat Mode Only */}
- {showChatSettings && (
- <div className="bg-white p-4 rounded-xl border border-slate-200 shadow-sm">
- <div className="flex items-center gap-2 mb-4 text-slate-800 font-semibold border-b border-slate-100 pb-2">
- <Sliders className="w-4 h-4 text-pink-500" />
- {t('headerHyperparams')}
- </div>
- <div className="space-y-4">
- <div>
- <div className="flex justify-between mb-1.5">
- <label className="text-xs font-medium text-slate-500">{t('lblTemperature')}</label>
- <span className="text-xs text-blue-600 font-bold">{settings.temperature}</span>
- </div>
- <input
- type="range"
- min="0"
- max="1"
- step="0.1"
- value={settings.temperature}
- onChange={(e) => handleChange('temperature', parseFloat(e.target.value))}
- disabled={!isAdmin}
- className={`w-full h-2 rounded-lg appearance-none cursor-pointer accent-blue-600 ${!isAdmin ? 'bg-slate-100' : 'bg-slate-200'}`}
- />
- </div>
- <div>
- <label className="block text-xs font-medium text-slate-500 mb-1.5">{t('lblMaxTokens')}</label>
- <input
- type="number"
- value={settings.maxTokens}
- onChange={(e) => handleChange('maxTokens', parseInt(e.target.value))}
- disabled={!isAdmin}
- className="w-full text-sm bg-slate-50 border border-slate-200 rounded-lg px-3 py-2 text-slate-700 focus:outline-none focus:border-blue-500 disabled:opacity-50 disabled:cursor-not-allowed"
- />
- </div>
- </div>
- </div>
- )}
- {/* Vision Model Settings - Chat Mode Only? Or both? Assuming Chat */}
- {/* Vision Model Settings - KB Only */}
- {showKbSettings && <VisionModelSelector isAdmin={isAdmin} />}
- {/* Retrieval Settings - KB Mode Only */}
- {showKbSettings && (
- <div className="bg-white p-4 rounded-xl border border-slate-200 shadow-sm">
- <div className="flex items-center gap-2 mb-4 text-slate-800 font-semibold border-b border-slate-100 pb-2">
- <Database className="w-4 h-4 text-green-600" />
- {t('headerRetrieval')}
- </div>
- <div className="space-y-4">
- <div>
- <div className="flex justify-between mb-1.5">
- <label className="text-xs font-medium text-slate-500">{t('lblTopK')}</label>
- <span className="text-xs text-blue-600 font-bold">{settings.topK}</span>
- </div>
- <input
- type="range"
- min="1"
- max="20"
- step="1"
- value={settings.topK}
- onChange={(e) => handleChange('topK', parseInt(e.target.value))}
- disabled={!isAdmin}
- className={`w-full h-2 rounded-lg appearance-none cursor-pointer accent-blue-600 ${!isAdmin ? 'bg-slate-100' : 'bg-slate-200'}`}
- />
- </div>
- <div>
- <label className="block text-xs font-medium text-slate-500 mb-1.5">{t('lblRerankRef')}</label>
- <select
- value={settings.selectedRerankId}
- onChange={(e) => handleChange('selectedRerankId', e.target.value)}
- disabled={!settings.enableRerank || !isAdmin}
- className="w-full text-sm bg-slate-50 border border-slate-200 rounded-lg px-3 py-2 text-slate-700 focus:outline-none focus:border-blue-500 disabled:opacity-50 disabled:cursor-not-allowed"
- >
- <option value="">--- {t('selectRerankModel')} ---</option>
- {rerankModels.map(m => (
- <option key={m.id} value={m.id}>{m.name}</option>
- ))}
- </select>
- </div>
- <div>
- <div className="flex justify-between mb-1.5">
- <label className="text-xs font-medium text-slate-500">{t('vectorSimilarityThreshold')}</label>
- <span className="text-xs text-blue-600 font-bold">{settings.similarityThreshold}</span>
- </div>
- <input
- type="range"
- min="0.0"
- max="1.0"
- step="0.05"
- value={settings.similarityThreshold}
- onChange={(e) => handleChange('similarityThreshold', parseFloat(e.target.value))}
- disabled={!isAdmin}
- className={`w-full h-2 rounded-lg appearance-none cursor-pointer accent-blue-600 ${!isAdmin ? 'bg-slate-100' : 'bg-slate-200'}`}
- />
- <p className="text-[10px] text-slate-400 mt-1">{t('filterLowResults')}</p>
- </div>
- {settings.enableRerank && (
- <div>
- <div className="flex justify-between mb-1.5">
- <label className="text-xs font-medium text-slate-500">{t('rerankSimilarityThreshold')}</label>
- <span className="text-xs text-blue-600 font-bold">{settings.rerankSimilarityThreshold}</span>
- </div>
- <input
- type="range"
- min="0.0"
- max="1.0"
- step="0.05"
- value={settings.rerankSimilarityThreshold}
- onChange={(e) => handleChange('rerankSimilarityThreshold', parseFloat(e.target.value))}
- className="w-full h-2 bg-slate-200 rounded-lg appearance-none cursor-pointer accent-pink-600"
- />
- </div>
- )}
- <div className="flex items-center justify-between pt-2">
- <label className="text-sm text-slate-700">{t('lblRerank')}</label>
- <button
- onClick={() => isAdmin && handleChange('enableRerank', !settings.enableRerank)}
- disabled={!isAdmin}
- className={`relative inline-flex h-6 w-11 items-center rounded-full transition-colors focus:outline-none ${settings.enableRerank ? 'bg-blue-600' : 'bg-slate-300'
- } ${!isAdmin ? 'cursor-not-allowed opacity-50' : ''}`}
- >
- <span
- className={`${settings.enableRerank ? 'translate-x-6' : 'translate-x-1'
- } inline-block h-4 w-4 transform rounded-full bg-white transition-transform`}
- />
- </button>
- </div>
- <div className="flex items-center justify-between pt-2">
- <label className="text-sm text-slate-700">{t('fullTextSearch')}</label>
- <button
- onClick={() => isAdmin && handleChange('enableFullTextSearch', !settings.enableFullTextSearch)}
- disabled={!isAdmin}
- className={`relative inline-flex h-6 w-11 items-center rounded-full transition-colors focus:outline-none ${settings.enableFullTextSearch ? 'bg-blue-600' : 'bg-slate-300'
- } ${!isAdmin ? 'cursor-not-allowed opacity-50' : ''}`}
- >
- <span
- className={`${settings.enableFullTextSearch ? 'translate-x-6' : 'translate-x-1'
- } inline-block h-4 w-4 transform rounded-full bg-white transition-transform`}
- />
- </button>
- </div>
- {settings.enableFullTextSearch && (
- <div className="pt-2 animate-in fade-in slide-in-from-top-1 duration-200">
- <div className="flex justify-between mb-1.5">
- <label className="text-xs font-medium text-slate-500">{t('hybridVectorWeight')}</label>
- <span className="text-xs text-blue-600 font-bold">{settings.hybridVectorWeight}</span>
- </div>
- <input
- type="range"
- min="0.0"
- max="1.0"
- step="0.05"
- value={settings.hybridVectorWeight}
- onChange={(e) => handleChange('hybridVectorWeight', parseFloat(e.target.value))}
- disabled={!isAdmin}
- className={`w-full h-2 rounded-lg appearance-none cursor-pointer accent-blue-600 ${!isAdmin ? 'bg-slate-100' : 'bg-slate-200'}`}
- />
- <p className="text-[10px] text-slate-400 mt-1">{t('hybridVectorWeightDesc')}</p>
- </div>
- )}
- </div>
- </div>
- )}
- </div>
- );
- };
- export default ConfigPanel;
|