Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.
 
 
 

282 строки
9.3 KiB

  1. /*
  2. Copyright (C) 2025 QuantumNous
  3. This program is free software: you can redistribute it and/or modify
  4. it under the terms of the GNU Affero General Public License as
  5. published by the Free Software Foundation, either version 3 of the
  6. License, or (at your option) any later version.
  7. This program is distributed in the hope that it will be useful,
  8. but WITHOUT ANY WARRANTY; without even the implied warranty of
  9. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  10. GNU Affero General Public License for more details.
  11. You should have received a copy of the GNU Affero General Public License
  12. along with this program. If not, see <https://www.gnu.org/licenses/>.
  13. For commercial licensing, please contact support@quantumnous.com
  14. */
  15. import React from 'react';
  16. import { Card, Select, Typography, Button, Switch } from '@douyinfe/semi-ui';
  17. import { Sparkles, Users, ToggleLeft, X, Settings } from 'lucide-react';
  18. import { useTranslation } from 'react-i18next';
  19. import { renderGroupOption, selectFilter } from '../../helpers';
  20. import ParameterControl from './ParameterControl';
  21. import ImageUrlInput from './ImageUrlInput';
  22. import ConfigManager from './ConfigManager';
  23. import CustomRequestEditor from './CustomRequestEditor';
  24. const SettingsPanel = ({
  25. inputs,
  26. parameterEnabled,
  27. models,
  28. groups,
  29. styleState,
  30. showDebugPanel,
  31. customRequestMode,
  32. customRequestBody,
  33. mutualExclusive = false,
  34. onInputChange,
  35. onParameterToggle,
  36. onCloseSettings,
  37. onConfigImport,
  38. onConfigReset,
  39. onCustomRequestModeChange,
  40. onCustomRequestBodyChange,
  41. previewPayload,
  42. messages,
  43. channels = [],
  44. }) => {
  45. const { t } = useTranslation();
  46. const currentConfig = {
  47. inputs,
  48. parameterEnabled,
  49. showDebugPanel,
  50. customRequestMode,
  51. customRequestBody,
  52. };
  53. return (
  54. <Card
  55. className='h-full flex flex-col'
  56. bordered={false}
  57. bodyStyle={{
  58. padding: styleState.isMobile ? '16px' : '24px',
  59. height: '100%',
  60. display: 'flex',
  61. flexDirection: 'column',
  62. }}
  63. >
  64. {/* 标题区域 - 与调试面板保持一致 */}
  65. <div className='flex items-center justify-between mb-6 flex-shrink-0'>
  66. <div className='flex items-center'>
  67. <div className='w-10 h-10 rounded-full bg-gradient-to-r from-purple-500 to-pink-500 flex items-center justify-center mr-3'>
  68. <Settings size={20} className='text-white' />
  69. </div>
  70. <Typography.Title heading={5} className='mb-0'>
  71. {t('模型配置')}
  72. </Typography.Title>
  73. </div>
  74. {styleState.isMobile && onCloseSettings && (
  75. <Button
  76. icon={<X size={16} />}
  77. onClick={onCloseSettings}
  78. theme='borderless'
  79. type='tertiary'
  80. size='small'
  81. className='!rounded-lg'
  82. />
  83. )}
  84. </div>
  85. {/* 移动端配置管理 */}
  86. {styleState.isMobile && (
  87. <div className='mb-4 flex-shrink-0'>
  88. <ConfigManager
  89. currentConfig={currentConfig}
  90. onConfigImport={onConfigImport}
  91. onConfigReset={onConfigReset}
  92. styleState={{ ...styleState, isMobile: false }}
  93. messages={messages}
  94. />
  95. </div>
  96. )}
  97. <div className='space-y-6 overflow-y-auto flex-1 pr-2 model-settings-scroll'>
  98. {/* 自定义请求体编辑器 */}
  99. <CustomRequestEditor
  100. customRequestMode={customRequestMode}
  101. customRequestBody={customRequestBody}
  102. onCustomRequestModeChange={onCustomRequestModeChange}
  103. onCustomRequestBodyChange={onCustomRequestBodyChange}
  104. defaultPayload={previewPayload}
  105. />
  106. {/* 分组选择 - 已隐藏
  107. <div className={customRequestMode ? 'opacity-50' : ''}>
  108. <div className='flex items-center gap-2 mb-2'>
  109. <Users size={16} className='text-gray-500' />
  110. <Typography.Text strong className='text-sm'>
  111. {t('分组')}
  112. </Typography.Text>
  113. {customRequestMode && (
  114. <Typography.Text className='text-xs text-orange-600'>
  115. ({t('已在自定义模式中忽略')})
  116. </Typography.Text>
  117. )}
  118. </div>
  119. <Select
  120. placeholder={t('请选择分组')}
  121. name='group'
  122. required
  123. selection
  124. filter={selectFilter}
  125. autoClearSearchValue={false}
  126. onChange={(value) => onInputChange('group', value)}
  127. value={inputs.group}
  128. autoComplete='new-password'
  129. optionList={groups}
  130. renderOptionItem={renderGroupOption}
  131. style={{ width: '100%' }}
  132. dropdownStyle={{ width: '100%', maxWidth: '100%' }}
  133. className='!rounded-lg'
  134. disabled={customRequestMode}
  135. />
  136. </div>
  137. */}
  138. {/* 模型选择 */}
  139. <div className={customRequestMode ? 'opacity-50' : ''}>
  140. <div className='flex items-center gap-2 mb-2'>
  141. <Sparkles size={16} className='text-gray-500' />
  142. <Typography.Text strong className='text-sm'>
  143. {t('模型')}
  144. </Typography.Text>
  145. {customRequestMode && (
  146. <Typography.Text className='text-xs text-orange-600'>
  147. ({t('已在自定义模式中忽略')})
  148. </Typography.Text>
  149. )}
  150. </div>
  151. <Select
  152. placeholder={t('请选择模型')}
  153. name='model'
  154. required
  155. selection
  156. filter={selectFilter}
  157. autoClearSearchValue={false}
  158. onChange={(value) => onInputChange('model', value)}
  159. value={inputs.model}
  160. autoComplete='new-password'
  161. optionList={models}
  162. style={{ width: '100%' }}
  163. dropdownStyle={{ width: '100%', maxWidth: '100%' }}
  164. className='!rounded-lg'
  165. disabled={customRequestMode}
  166. />
  167. </div>
  168. {/* 通道选择 */}
  169. <div className={customRequestMode ? 'opacity-50' : ''}>
  170. <div className='flex items-center gap-2 mb-2'>
  171. <Typography.Text strong className='text-sm'>
  172. {t('通道')}
  173. </Typography.Text>
  174. {customRequestMode && (
  175. <Typography.Text className='text-xs text-orange-600'>
  176. ({t('已在自定义模式中忽略')})
  177. </Typography.Text>
  178. )}
  179. </div>
  180. <Select
  181. placeholder={t('请选择通道')}
  182. name='channelId'
  183. selection
  184. filter={selectFilter}
  185. autoClearSearchValue={false}
  186. onChange={(value) => onInputChange('channelId', value)}
  187. value={inputs.channelId}
  188. autoComplete='new-password'
  189. optionList={channels.map((ch) => ({
  190. value: ch.id,
  191. label: ch.public_name || ch.name,
  192. }))}
  193. style={{ width: '100%' }}
  194. dropdownStyle={{ width: '100%', maxWidth: '100%' }}
  195. className='!rounded-lg'
  196. disabled={customRequestMode || channels.length === 0}
  197. />
  198. </div>
  199. {/* 图片URL输入 */}
  200. <div className={customRequestMode ? 'opacity-50' : ''}>
  201. <ImageUrlInput
  202. imageUrls={inputs.imageUrls}
  203. imageEnabled={inputs.imageEnabled}
  204. onImageUrlsChange={(urls) => onInputChange('imageUrls', urls)}
  205. onImageEnabledChange={(enabled) =>
  206. onInputChange('imageEnabled', enabled)
  207. }
  208. disabled={customRequestMode}
  209. />
  210. </div>
  211. {/* 参数控制组件 */}
  212. <div className={customRequestMode ? 'opacity-50' : ''}>
  213. <ParameterControl
  214. inputs={inputs}
  215. parameterEnabled={parameterEnabled}
  216. onInputChange={onInputChange}
  217. onParameterToggle={onParameterToggle}
  218. disabled={customRequestMode}
  219. mutualExclusive={mutualExclusive}
  220. />
  221. </div>
  222. {/* 流式输出开关 */}
  223. <div className={customRequestMode ? 'opacity-50' : ''}>
  224. <div className='flex items-center justify-between'>
  225. <div className='flex items-center gap-2'>
  226. <ToggleLeft size={16} className='text-gray-500' />
  227. <Typography.Text strong className='text-sm'>
  228. {t('流式输出')}
  229. </Typography.Text>
  230. {customRequestMode && (
  231. <Typography.Text className='text-xs text-orange-600'>
  232. ({t('已在自定义模式中忽略')})
  233. </Typography.Text>
  234. )}
  235. </div>
  236. <Switch
  237. checked={inputs.stream}
  238. onChange={(checked) => onInputChange('stream', checked)}
  239. checkedText={t('开')}
  240. uncheckedText={t('关')}
  241. size='small'
  242. disabled={customRequestMode}
  243. />
  244. </div>
  245. </div>
  246. </div>
  247. {/* 桌面端的配置管理放在底部 */}
  248. {!styleState.isMobile && (
  249. <div className='flex-shrink-0 pt-3'>
  250. <ConfigManager
  251. currentConfig={currentConfig}
  252. onConfigImport={onConfigImport}
  253. onConfigReset={onConfigReset}
  254. styleState={styleState}
  255. messages={messages}
  256. />
  257. </div>
  258. )}
  259. </Card>
  260. );
  261. };
  262. export default SettingsPanel;