ConfigPanel.tsx 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290
  1. import React from 'react';
  2. import { AppSettings, ModelConfig, ModelType } from '../types';
  3. import { useLanguage } from '../contexts/LanguageContext';
  4. import { Settings, Database, Sliders, Layers, Cpu, ChevronRight } from 'lucide-react';
  5. import VisionModelSelector from './VisionModelSelector';
  6. interface ConfigPanelProps {
  7. settings: AppSettings;
  8. models: ModelConfig[];
  9. onSettingsChange: (newSettings: AppSettings) => void;
  10. onOpenSettings: () => void;
  11. mode?: 'chat' | 'kb' | 'all';
  12. }
  13. const ConfigPanel: React.FC<ConfigPanelProps> = ({ settings, models, onSettingsChange, onOpenSettings, mode = 'all' }) => {
  14. const { t } = useLanguage();
  15. const handleChange = (key: keyof AppSettings, value: any) => {
  16. onSettingsChange({
  17. ...settings,
  18. [key]: value,
  19. });
  20. };
  21. const llmModels = models.filter(m => m.type === ModelType.LLM && m.isEnabled !== false && !m.supportsVision);
  22. const embeddingModels = models.filter(m => m.type === ModelType.EMBEDDING && m.isEnabled !== false);
  23. const rerankModels = models.filter(m => m.type === ModelType.RERANK && m.isEnabled !== false);
  24. const showChatSettings = mode === 'chat' || mode === 'all';
  25. const showKbSettings = mode === 'kb' || mode === 'all';
  26. return (
  27. <div className="flex-1 overflow-y-auto p-4 space-y-6 bg-slate-50">
  28. {/* Model Selection (LLM) - Chat Mode Only */}
  29. {showChatSettings && (
  30. <div className="bg-white p-4 rounded-xl border border-slate-200 shadow-sm">
  31. <div className="flex items-center justify-between mb-4 border-b border-slate-100 pb-2">
  32. <div className="flex items-center gap-2 text-slate-800 font-semibold">
  33. <Cpu className="w-4 h-4 text-blue-600" />
  34. {t('headerModelSelection')}
  35. </div>
  36. </div>
  37. <div className="space-y-4">
  38. <div>
  39. <label className="block text-xs font-medium text-slate-500 mb-1.5">{t('selectLLMModel')}</label>
  40. <select
  41. value={settings.selectedLLMId}
  42. onChange={(e) => handleChange('selectedLLMId', e.target.value)}
  43. 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"
  44. >
  45. <option value="">{t('selectLLMModel')}</option>
  46. {llmModels.map(m => (
  47. <option key={m.id} value={m.id}>
  48. {m.name} ({m.modelId})
  49. </option>
  50. ))}
  51. </select>
  52. </div>
  53. </div>
  54. </div>
  55. )}
  56. {/* Embedding Model Selection - KB Mode Only */}
  57. {showKbSettings && (
  58. <div className="bg-white p-4 rounded-xl border border-slate-200 shadow-sm">
  59. <div className="flex items-center justify-between mb-4 border-b border-slate-100 pb-2">
  60. <div className="flex items-center gap-2 text-slate-800 font-semibold">
  61. <Cpu className="w-4 h-4 text-blue-600" />
  62. {t('lblEmbedding')}
  63. </div>
  64. </div>
  65. <div className="space-y-4">
  66. <div>
  67. <label className="block text-xs font-medium text-slate-500 mb-1.5">{t('lblEmbedding')}</label>
  68. <select
  69. value={settings.selectedEmbeddingId}
  70. onChange={(e) => {
  71. const newId = e.target.value;
  72. if (newId !== settings.selectedEmbeddingId && settings.selectedEmbeddingId) {
  73. if (confirm(t('confirmChangeEmbeddingModel') || "WARNING: Changing the embedding model will require re-indexing all existing files. Are you sure?")) {
  74. handleChange('selectedEmbeddingId', newId);
  75. }
  76. } else {
  77. handleChange('selectedEmbeddingId', newId);
  78. }
  79. }}
  80. 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"
  81. >
  82. <option value="">--- {t('selectEmbeddingModel')} ---</option>
  83. {embeddingModels.map(m => (
  84. <option key={m.id} value={m.id}>{m.name}</option>
  85. ))}
  86. </select>
  87. <p className="text-[10px] text-slate-400 mt-1">{t('defaultForUploads')}</p>
  88. <p className="text-[10px] text-orange-500 mt-1">{t('embeddingModelWarning') || "Changing this setting may require clearing and re-importing your knowledge base."}</p>
  89. </div>
  90. </div>
  91. </div>
  92. )}
  93. {/* Hyperparameters - Chat Mode Only */}
  94. {showChatSettings && (
  95. <div className="bg-white p-4 rounded-xl border border-slate-200 shadow-sm">
  96. <div className="flex items-center gap-2 mb-4 text-slate-800 font-semibold border-b border-slate-100 pb-2">
  97. <Sliders className="w-4 h-4 text-pink-500" />
  98. {t('headerHyperparams')}
  99. </div>
  100. <div className="space-y-4">
  101. <div>
  102. <div className="flex justify-between mb-1.5">
  103. <label className="text-xs font-medium text-slate-500">{t('lblTemperature')}</label>
  104. <span className="text-xs text-blue-600 font-bold">{settings.temperature}</span>
  105. </div>
  106. <input
  107. type="range"
  108. min="0"
  109. max="2"
  110. step="0.1"
  111. value={settings.temperature}
  112. onChange={(e) => handleChange('temperature', parseFloat(e.target.value))}
  113. className="w-full h-2 bg-slate-200 rounded-lg appearance-none cursor-pointer accent-blue-600"
  114. />
  115. </div>
  116. <div>
  117. <label className="block text-xs font-medium text-slate-500 mb-1.5">{t('lblMaxTokens')}</label>
  118. <input
  119. type="number"
  120. value={settings.maxTokens}
  121. onChange={(e) => handleChange('maxTokens', parseInt(e.target.value))}
  122. 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"
  123. />
  124. </div>
  125. </div>
  126. </div>
  127. )}
  128. {/* Vision Model Settings - Chat Mode Only? Or both? Assuming Chat */}
  129. {/* Vision Model Settings - KB Only */}
  130. {showKbSettings && <VisionModelSelector />}
  131. {/* Retrieval Settings - KB Mode Only */}
  132. {showKbSettings && (
  133. <div className="bg-white p-4 rounded-xl border border-slate-200 shadow-sm">
  134. <div className="flex items-center gap-2 mb-4 text-slate-800 font-semibold border-b border-slate-100 pb-2">
  135. <Database className="w-4 h-4 text-green-600" />
  136. {t('headerRetrieval')}
  137. </div>
  138. <div className="space-y-4">
  139. <div>
  140. <div className="flex justify-between mb-1.5">
  141. <label className="text-xs font-medium text-slate-500">{t('lblTopK')}</label>
  142. <span className="text-xs text-blue-600 font-bold">{settings.topK}</span>
  143. </div>
  144. <input
  145. type="range"
  146. min="1"
  147. max="20"
  148. step="1"
  149. value={settings.topK}
  150. onChange={(e) => handleChange('topK', parseInt(e.target.value))}
  151. className="w-full h-2 bg-slate-200 rounded-lg appearance-none cursor-pointer accent-blue-600"
  152. />
  153. </div>
  154. <div>
  155. <label className="block text-xs font-medium text-slate-500 mb-1.5">{t('lblRerankRef')}</label>
  156. <select
  157. value={settings.selectedRerankId}
  158. onChange={(e) => handleChange('selectedRerankId', e.target.value)}
  159. disabled={!settings.enableRerank}
  160. 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"
  161. >
  162. <option value="">--- {t('noRerankModel')} ---</option>
  163. {rerankModels.map(m => (
  164. <option key={m.id} value={m.id}>{m.name}</option>
  165. ))}
  166. </select>
  167. </div>
  168. <div>
  169. <div className="flex justify-between mb-1.5">
  170. <label className="text-xs font-medium text-slate-500">{t('similarityThreshold')}</label>
  171. <span className="text-xs text-blue-600 font-bold">{settings.similarityThreshold}</span>
  172. </div>
  173. <input
  174. type="range"
  175. min="0.1"
  176. max="1.0"
  177. step="0.05"
  178. value={settings.similarityThreshold}
  179. onChange={(e) => handleChange('similarityThreshold', parseFloat(e.target.value))}
  180. className="w-full h-2 bg-slate-200 rounded-lg appearance-none cursor-pointer accent-blue-600"
  181. />
  182. <p className="text-[10px] text-slate-400 mt-1">{t('filterLowResults')}</p>
  183. </div>
  184. {settings.enableRerank && (
  185. <div>
  186. <div className="flex justify-between mb-1.5">
  187. <label className="text-xs font-medium text-slate-500">{t('scoreThreshold')}</label>
  188. <span className="text-xs text-blue-600 font-bold">{settings.scoreThreshold}</span>
  189. </div>
  190. <input
  191. type="range"
  192. min="0"
  193. max="1.0"
  194. step="0.05"
  195. value={settings.scoreThreshold}
  196. onChange={(e) => handleChange('scoreThreshold', parseFloat(e.target.value))}
  197. className="w-full h-2 bg-slate-200 rounded-lg appearance-none cursor-pointer accent-blue-600"
  198. />
  199. <p className="text-[10px] text-slate-400 mt-1">{t('filterLowResults')}</p>
  200. </div>
  201. )}
  202. <div className="flex items-center justify-between pt-2">
  203. <label className="text-sm text-slate-700">{t('lblRerank')}</label>
  204. <button
  205. onClick={() => handleChange('enableRerank', !settings.enableRerank)}
  206. 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'
  207. }`}
  208. >
  209. <span
  210. className={`${settings.enableRerank ? 'translate-x-6' : 'translate-x-1'
  211. } inline-block h-4 w-4 transform rounded-full bg-white transition-transform`}
  212. />
  213. </button>
  214. </div>
  215. <div className="flex items-center justify-between pt-2">
  216. <label className="text-sm text-slate-700">{t('fullTextSearch')}</label>
  217. <button
  218. onClick={() => handleChange('enableFullTextSearch', !settings.enableFullTextSearch)}
  219. 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'
  220. }`}
  221. >
  222. <span
  223. className={`${settings.enableFullTextSearch ? 'translate-x-6' : 'translate-x-1'
  224. } inline-block h-4 w-4 transform rounded-full bg-white transition-transform`}
  225. />
  226. </button>
  227. </div>
  228. <div className="pt-2">
  229. <div className="flex items-center justify-between mb-1">
  230. <label className="text-sm text-slate-700">{t('queryExpansion')}</label>
  231. <button
  232. onClick={() => handleChange('enableQueryExpansion', !settings.enableQueryExpansion)}
  233. className={`relative inline-flex h-6 w-11 items-center rounded-full transition-colors focus:outline-none ${settings.enableQueryExpansion ? 'bg-blue-600' : 'bg-slate-300'
  234. }`}
  235. >
  236. <span
  237. className={`${settings.enableQueryExpansion ? 'translate-x-6' : 'translate-x-1'
  238. } inline-block h-4 w-4 transform rounded-full bg-white transition-transform`}
  239. />
  240. </button>
  241. </div>
  242. <p className="text-[10px] text-slate-400">{t('queryExpansionDesc')}</p>
  243. </div>
  244. <div className="pt-2">
  245. <div className="flex items-center justify-between mb-1">
  246. <label className="text-sm text-slate-700">{t('hyde')}</label>
  247. <button
  248. onClick={() => handleChange('enableHyDE', !settings.enableHyDE)}
  249. className={`relative inline-flex h-6 w-11 items-center rounded-full transition-colors focus:outline-none ${settings.enableHyDE ? 'bg-blue-600' : 'bg-slate-300'
  250. }`}
  251. >
  252. <span
  253. className={`${settings.enableHyDE ? 'translate-x-6' : 'translate-x-1'
  254. } inline-block h-4 w-4 transform rounded-full bg-white transition-transform`}
  255. />
  256. </button>
  257. </div>
  258. <p className="text-[10px] text-slate-400">{t('hydeDesc')}</p>
  259. </div>
  260. </div>
  261. </div>
  262. )}
  263. </div>
  264. );
  265. };
  266. export default ConfigPanel;