SettingsPanel.js 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253
  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 {
  17. Card,
  18. Select,
  19. Typography,
  20. Button,
  21. Switch,
  22. } from '@douyinfe/semi-ui';
  23. import {
  24. Sparkles,
  25. Users,
  26. ToggleLeft,
  27. X,
  28. Settings,
  29. } from 'lucide-react';
  30. import { useTranslation } from 'react-i18next';
  31. import { renderGroupOption, modelSelectFilter } from '../../helpers';
  32. import ParameterControl from './ParameterControl';
  33. import ImageUrlInput from './ImageUrlInput';
  34. import ConfigManager from './ConfigManager';
  35. import CustomRequestEditor from './CustomRequestEditor';
  36. const SettingsPanel = ({
  37. inputs,
  38. parameterEnabled,
  39. models,
  40. groups,
  41. styleState,
  42. showDebugPanel,
  43. customRequestMode,
  44. customRequestBody,
  45. onInputChange,
  46. onParameterToggle,
  47. onCloseSettings,
  48. onConfigImport,
  49. onConfigReset,
  50. onCustomRequestModeChange,
  51. onCustomRequestBodyChange,
  52. previewPayload,
  53. messages,
  54. }) => {
  55. const { t } = useTranslation();
  56. const currentConfig = {
  57. inputs,
  58. parameterEnabled,
  59. showDebugPanel,
  60. customRequestMode,
  61. customRequestBody,
  62. };
  63. return (
  64. <Card
  65. className="h-full flex flex-col"
  66. bordered={false}
  67. bodyStyle={{
  68. padding: styleState.isMobile ? '16px' : '24px',
  69. height: '100%',
  70. display: 'flex',
  71. flexDirection: 'column'
  72. }}
  73. >
  74. {/* 标题区域 - 与调试面板保持一致 */}
  75. <div className="flex items-center justify-between mb-6 flex-shrink-0">
  76. <div className="flex items-center">
  77. <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">
  78. <Settings size={20} className="text-white" />
  79. </div>
  80. <Typography.Title heading={5} className="mb-0">
  81. {t('模型配置')}
  82. </Typography.Title>
  83. </div>
  84. {styleState.isMobile && onCloseSettings && (
  85. <Button
  86. icon={<X size={16} />}
  87. onClick={onCloseSettings}
  88. theme="borderless"
  89. type="tertiary"
  90. size="small"
  91. className="!rounded-lg"
  92. />
  93. )}
  94. </div>
  95. {/* 移动端配置管理 */}
  96. {styleState.isMobile && (
  97. <div className="mb-4 flex-shrink-0">
  98. <ConfigManager
  99. currentConfig={currentConfig}
  100. onConfigImport={onConfigImport}
  101. onConfigReset={onConfigReset}
  102. styleState={{ ...styleState, isMobile: false }}
  103. messages={messages}
  104. />
  105. </div>
  106. )}
  107. <div className="space-y-6 overflow-y-auto flex-1 pr-2 model-settings-scroll">
  108. {/* 自定义请求体编辑器 */}
  109. <CustomRequestEditor
  110. customRequestMode={customRequestMode}
  111. customRequestBody={customRequestBody}
  112. onCustomRequestModeChange={onCustomRequestModeChange}
  113. onCustomRequestBodyChange={onCustomRequestBodyChange}
  114. defaultPayload={previewPayload}
  115. />
  116. {/* 分组选择 */}
  117. <div className={customRequestMode ? 'opacity-50' : ''}>
  118. <div className="flex items-center gap-2 mb-2">
  119. <Users size={16} className="text-gray-500" />
  120. <Typography.Text strong className="text-sm">
  121. {t('分组')}
  122. </Typography.Text>
  123. {customRequestMode && (
  124. <Typography.Text className="text-xs text-orange-600">
  125. (已在自定义模式中忽略)
  126. </Typography.Text>
  127. )}
  128. </div>
  129. <Select
  130. placeholder={t('请选择分组')}
  131. name='group'
  132. required
  133. selection
  134. onChange={(value) => onInputChange('group', value)}
  135. value={inputs.group}
  136. autoComplete='new-password'
  137. optionList={groups}
  138. renderOptionItem={renderGroupOption}
  139. style={{ width: '100%' }}
  140. dropdownStyle={{ width: '100%', maxWidth: '100%' }}
  141. className="!rounded-lg"
  142. disabled={customRequestMode}
  143. />
  144. </div>
  145. {/* 模型选择 */}
  146. <div className={customRequestMode ? 'opacity-50' : ''}>
  147. <div className="flex items-center gap-2 mb-2">
  148. <Sparkles size={16} className="text-gray-500" />
  149. <Typography.Text strong className="text-sm">
  150. {t('模型')}
  151. </Typography.Text>
  152. {customRequestMode && (
  153. <Typography.Text className="text-xs text-orange-600">
  154. (已在自定义模式中忽略)
  155. </Typography.Text>
  156. )}
  157. </div>
  158. <Select
  159. placeholder={t('请选择模型')}
  160. name='model'
  161. required
  162. selection
  163. filter={modelSelectFilter}
  164. autoClearSearchValue={false}
  165. onChange={(value) => onInputChange('model', value)}
  166. value={inputs.model}
  167. autoComplete='new-password'
  168. optionList={models}
  169. style={{ width: '100%' }}
  170. dropdownStyle={{ width: '100%', maxWidth: '100%' }}
  171. className="!rounded-lg"
  172. disabled={customRequestMode}
  173. />
  174. </div>
  175. {/* 图片URL输入 */}
  176. <div className={customRequestMode ? 'opacity-50' : ''}>
  177. <ImageUrlInput
  178. imageUrls={inputs.imageUrls}
  179. imageEnabled={inputs.imageEnabled}
  180. onImageUrlsChange={(urls) => onInputChange('imageUrls', urls)}
  181. onImageEnabledChange={(enabled) => onInputChange('imageEnabled', enabled)}
  182. disabled={customRequestMode}
  183. />
  184. </div>
  185. {/* 参数控制组件 */}
  186. <div className={customRequestMode ? 'opacity-50' : ''}>
  187. <ParameterControl
  188. inputs={inputs}
  189. parameterEnabled={parameterEnabled}
  190. onInputChange={onInputChange}
  191. onParameterToggle={onParameterToggle}
  192. disabled={customRequestMode}
  193. />
  194. </div>
  195. {/* 流式输出开关 */}
  196. <div className={customRequestMode ? 'opacity-50' : ''}>
  197. <div className="flex items-center justify-between">
  198. <div className="flex items-center gap-2">
  199. <ToggleLeft size={16} className="text-gray-500" />
  200. <Typography.Text strong className="text-sm">
  201. 流式输出
  202. </Typography.Text>
  203. {customRequestMode && (
  204. <Typography.Text className="text-xs text-orange-600">
  205. (已在自定义模式中忽略)
  206. </Typography.Text>
  207. )}
  208. </div>
  209. <Switch
  210. checked={inputs.stream}
  211. onChange={(checked) => onInputChange('stream', checked)}
  212. checkedText="开"
  213. uncheckedText="关"
  214. size="small"
  215. disabled={customRequestMode}
  216. />
  217. </div>
  218. </div>
  219. </div>
  220. {/* 桌面端的配置管理放在底部 */}
  221. {!styleState.isMobile && (
  222. <div className="flex-shrink-0 pt-3">
  223. <ConfigManager
  224. currentConfig={currentConfig}
  225. onConfigImport={onConfigImport}
  226. onConfigReset={onConfigReset}
  227. styleState={styleState}
  228. messages={messages}
  229. />
  230. </div>
  231. )}
  232. </Card>
  233. );
  234. };
  235. export default SettingsPanel;