EditChannel.js 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846
  1. import React, { useEffect, useRef, useState } from 'react';
  2. import { useNavigate, useParams } from 'react-router-dom';
  3. import {
  4. API,
  5. isMobile,
  6. showError,
  7. showInfo,
  8. showSuccess,
  9. verifyJSON,
  10. } from '../../helpers';
  11. import { CHANNEL_OPTIONS } from '../../constants';
  12. import Title from '@douyinfe/semi-ui/lib/es/typography/title';
  13. import {
  14. SideSheet,
  15. Space,
  16. Spin,
  17. Button,
  18. Tooltip,
  19. Input,
  20. Typography,
  21. Select,
  22. TextArea,
  23. Checkbox,
  24. Banner,
  25. } from '@douyinfe/semi-ui';
  26. import { Divider } from 'semantic-ui-react';
  27. import { getChannelModels, loadChannelModels } from '../../components/utils.js';
  28. import axios from 'axios';
  29. const MODEL_MAPPING_EXAMPLE = {
  30. 'gpt-3.5-turbo-0301': 'gpt-3.5-turbo',
  31. 'gpt-4-0314': 'gpt-4',
  32. 'gpt-4-32k-0314': 'gpt-4-32k',
  33. };
  34. const STATUS_CODE_MAPPING_EXAMPLE = {
  35. 400: '500',
  36. };
  37. const fetchButtonTips = "1. 新建渠道时,请求通过当前浏览器发出;2. 编辑已有渠道,请求通过后端服务器发出"
  38. function type2secretPrompt(type) {
  39. // inputs.type === 15 ? '按照如下格式输入:APIKey|SecretKey' : (inputs.type === 18 ? '按照如下格式输入:APPID|APISecret|APIKey' : '请输入渠道对应的鉴权密钥')
  40. switch (type) {
  41. case 15:
  42. return '按照如下格式输入:APIKey|SecretKey';
  43. case 18:
  44. return '按照如下格式输入:APPID|APISecret|APIKey';
  45. case 22:
  46. return '按照如下格式输入:APIKey-AppId,例如:fastgpt-0sp2gtvfdgyi4k30jwlgwf1i-64f335d84283f05518e9e041';
  47. case 23:
  48. return '按照如下格式输入:AppId|SecretId|SecretKey';
  49. case 33:
  50. return '按照如下格式输入:Ak|Sk|Region';
  51. default:
  52. return '请输入渠道对应的鉴权密钥';
  53. }
  54. }
  55. const EditChannel = (props) => {
  56. const navigate = useNavigate();
  57. const channelId = props.editingChannel.id;
  58. const isEdit = channelId !== undefined;
  59. const [loading, setLoading] = useState(isEdit);
  60. const handleCancel = () => {
  61. props.handleClose();
  62. };
  63. const originInputs = {
  64. name: '',
  65. type: 1,
  66. key: '',
  67. openai_organization: '',
  68. max_input_tokens: 0,
  69. base_url: '',
  70. other: '',
  71. model_mapping: '',
  72. status_code_mapping: '',
  73. models: [],
  74. auto_ban: 1,
  75. test_model: '',
  76. groups: ['default'],
  77. };
  78. const [batch, setBatch] = useState(false);
  79. const [autoBan, setAutoBan] = useState(true);
  80. // const [autoBan, setAutoBan] = useState(true);
  81. const [inputs, setInputs] = useState(originInputs);
  82. const [originModelOptions, setOriginModelOptions] = useState([]);
  83. const [modelOptions, setModelOptions] = useState([]);
  84. const [groupOptions, setGroupOptions] = useState([]);
  85. const [basicModels, setBasicModels] = useState([]);
  86. const [fullModels, setFullModels] = useState([]);
  87. const [customModel, setCustomModel] = useState('');
  88. const handleInputChange = (name, value) => {
  89. setInputs((inputs) => ({ ...inputs, [name]: value }));
  90. if (name === 'type') {
  91. let localModels = [];
  92. switch (value) {
  93. case 2:
  94. localModels = [
  95. 'mj_imagine',
  96. 'mj_variation',
  97. 'mj_reroll',
  98. 'mj_blend',
  99. 'mj_upscale',
  100. 'mj_describe',
  101. 'mj_uploads',
  102. ];
  103. break;
  104. case 5:
  105. localModels = [
  106. 'swap_face',
  107. 'mj_imagine',
  108. 'mj_variation',
  109. 'mj_reroll',
  110. 'mj_blend',
  111. 'mj_upscale',
  112. 'mj_describe',
  113. 'mj_zoom',
  114. 'mj_shorten',
  115. 'mj_modal',
  116. 'mj_inpaint',
  117. 'mj_custom_zoom',
  118. 'mj_high_variation',
  119. 'mj_low_variation',
  120. 'mj_pan',
  121. 'mj_uploads',
  122. ];
  123. break;
  124. default:
  125. localModels = getChannelModels(value);
  126. break;
  127. }
  128. if (inputs.models.length === 0) {
  129. setInputs((inputs) => ({ ...inputs, models: localModels }));
  130. }
  131. setBasicModels(localModels);
  132. }
  133. //setAutoBan
  134. };
  135. const loadChannel = async () => {
  136. setLoading(true);
  137. let res = await API.get(`/api/channel/${channelId}`);
  138. if (res === undefined) {
  139. return;
  140. }
  141. const { success, message, data } = res.data;
  142. if (success) {
  143. if (data.models === '') {
  144. data.models = [];
  145. } else {
  146. data.models = data.models.split(',');
  147. }
  148. if (data.group === '') {
  149. data.groups = [];
  150. } else {
  151. data.groups = data.group.split(',');
  152. }
  153. if (data.model_mapping !== '') {
  154. data.model_mapping = JSON.stringify(
  155. JSON.parse(data.model_mapping),
  156. null,
  157. 2,
  158. );
  159. }
  160. setInputs(data);
  161. if (data.auto_ban === 0) {
  162. setAutoBan(false);
  163. } else {
  164. setAutoBan(true);
  165. }
  166. setBasicModels(getChannelModels(data.type));
  167. // console.log(data);
  168. } else {
  169. showError(message);
  170. }
  171. setLoading(false);
  172. };
  173. const fetchUpstreamModelList = async (name) => {
  174. if (inputs["type"] !== 1) {
  175. showError("仅支持 OpenAI 接口格式")
  176. return;
  177. }
  178. setLoading(true)
  179. const models = inputs["models"] || []
  180. let err = false;
  181. if (isEdit) {
  182. const res = await API.get("/api/channel/fetch_models/" + channelId)
  183. if (res.data && res.data?.success) {
  184. models.push(...res.data.data)
  185. } else {
  186. err = true
  187. }
  188. } else {
  189. if (!inputs?.["key"]) {
  190. showError("请填写密钥")
  191. err = true
  192. } else {
  193. try {
  194. const host = new URL((inputs["base_url"] || "https://api.openai.com"))
  195. const url = `https://${host.hostname}/v1/models`;
  196. const key = inputs["key"];
  197. const res = await axios.get(url, {
  198. headers: {
  199. 'Authorization': `Bearer ${key}`
  200. }
  201. })
  202. if (res.data && res.data?.success) {
  203. models.push(...res.data.data.map((model) => model.id))
  204. } else {
  205. err = true
  206. }
  207. }
  208. catch (error) {
  209. err = true
  210. }
  211. }
  212. }
  213. if (!err) {
  214. handleInputChange(name, Array.from(new Set(models)));
  215. showSuccess("获取模型列表成功");
  216. } else {
  217. showError('获取模型列表失败');
  218. }
  219. setLoading(false);
  220. }
  221. const fetchModels = async () => {
  222. try {
  223. let res = await API.get(`/api/channel/models`);
  224. let localModelOptions = res.data.data.map((model) => ({
  225. label: model.id,
  226. value: model.id,
  227. }));
  228. setOriginModelOptions(localModelOptions);
  229. setFullModels(res.data.data.map((model) => model.id));
  230. setBasicModels(
  231. res.data.data
  232. .filter((model) => {
  233. return model.id.startsWith('gpt-3') || model.id.startsWith('text-');
  234. })
  235. .map((model) => model.id),
  236. );
  237. } catch (error) {
  238. showError(error.message);
  239. }
  240. };
  241. const fetchGroups = async () => {
  242. try {
  243. let res = await API.get(`/api/group/`);
  244. if (res === undefined) {
  245. return;
  246. }
  247. setGroupOptions(
  248. res.data.data.map((group) => ({
  249. label: group,
  250. value: group,
  251. })),
  252. );
  253. } catch (error) {
  254. showError(error.message);
  255. }
  256. };
  257. useEffect(() => {
  258. let localModelOptions = [...originModelOptions];
  259. inputs.models.forEach((model) => {
  260. if (!localModelOptions.find((option) => option.key === model)) {
  261. localModelOptions.push({
  262. label: model,
  263. value: model,
  264. });
  265. }
  266. });
  267. setModelOptions(localModelOptions);
  268. }, [originModelOptions, inputs.models]);
  269. useEffect(() => {
  270. fetchModels().then();
  271. fetchGroups().then();
  272. if (isEdit) {
  273. loadChannel().then(() => {});
  274. } else {
  275. setInputs(originInputs);
  276. let localModels = getChannelModels(inputs.type);
  277. setBasicModels(localModels);
  278. setInputs((inputs) => ({ ...inputs, models: localModels }));
  279. }
  280. }, [props.editingChannel.id]);
  281. const submit = async () => {
  282. if (!isEdit && (inputs.name === '' || inputs.key === '')) {
  283. showInfo('请填写渠道名称和渠道密钥!');
  284. return;
  285. }
  286. if (inputs.models.length === 0) {
  287. showInfo('请至少选择一个模型!');
  288. return;
  289. }
  290. if (inputs.model_mapping !== '' && !verifyJSON(inputs.model_mapping)) {
  291. showInfo('模型映射必须是合法的 JSON 格式!');
  292. return;
  293. }
  294. let localInputs = { ...inputs };
  295. if (localInputs.base_url && localInputs.base_url.endsWith('/')) {
  296. localInputs.base_url = localInputs.base_url.slice(
  297. 0,
  298. localInputs.base_url.length - 1,
  299. );
  300. }
  301. if (localInputs.type === 3 && localInputs.other === '') {
  302. localInputs.other = '2023-06-01-preview';
  303. }
  304. if (localInputs.type === 18 && localInputs.other === '') {
  305. localInputs.other = 'v2.1';
  306. }
  307. let res;
  308. if (!Array.isArray(localInputs.models)) {
  309. showError('提交失败,请勿重复提交!');
  310. handleCancel();
  311. return;
  312. }
  313. localInputs.auto_ban = autoBan ? 1 : 0;
  314. localInputs.models = localInputs.models.join(',');
  315. localInputs.group = localInputs.groups.join(',');
  316. if (isEdit) {
  317. res = await API.put(`/api/channel/`, {
  318. ...localInputs,
  319. id: parseInt(channelId),
  320. });
  321. } else {
  322. res = await API.post(`/api/channel/`, localInputs);
  323. }
  324. const { success, message } = res.data;
  325. if (success) {
  326. if (isEdit) {
  327. showSuccess('渠道更新成功!');
  328. } else {
  329. showSuccess('渠道创建成功!');
  330. setInputs(originInputs);
  331. }
  332. props.refresh();
  333. props.handleClose();
  334. } else {
  335. showError(message);
  336. }
  337. };
  338. const addCustomModels = () => {
  339. if (customModel.trim() === '') return;
  340. // 使用逗号分隔字符串,然后去除每个模型名称前后的空格
  341. const modelArray = customModel.split(',').map((model) => model.trim());
  342. let localModels = [...inputs.models];
  343. let localModelOptions = [...modelOptions];
  344. let hasError = false;
  345. modelArray.forEach((model) => {
  346. // 检查模型是否已存在,且模型名称非空
  347. if (model && !localModels.includes(model)) {
  348. localModels.push(model); // 添加到模型列表
  349. localModelOptions.push({
  350. // 添加到下拉选项
  351. key: model,
  352. text: model,
  353. value: model,
  354. });
  355. } else if (model) {
  356. showError('某些模型已存在!');
  357. hasError = true;
  358. }
  359. });
  360. if (hasError) return; // 如果有错误则终止操作
  361. // 更新状态值
  362. setModelOptions(localModelOptions);
  363. setCustomModel('');
  364. handleInputChange('models', localModels);
  365. };
  366. return (
  367. <>
  368. <SideSheet
  369. maskClosable={false}
  370. placement={isEdit ? 'right' : 'left'}
  371. title={
  372. <Title level={3}>{isEdit ? '更新渠道信息' : '创建新的渠道'}</Title>
  373. }
  374. headerStyle={{ borderBottom: '1px solid var(--semi-color-border)' }}
  375. bodyStyle={{ borderBottom: '1px solid var(--semi-color-border)' }}
  376. visible={props.visible}
  377. footer={
  378. <div style={{ display: 'flex', justifyContent: 'flex-end' }}>
  379. <Space>
  380. <Button theme='solid' size={'large'} onClick={submit}>
  381. 提交
  382. </Button>
  383. <Button
  384. theme='solid'
  385. size={'large'}
  386. type={'tertiary'}
  387. onClick={handleCancel}
  388. >
  389. 取消
  390. </Button>
  391. </Space>
  392. </div>
  393. }
  394. closeIcon={null}
  395. onCancel={() => handleCancel()}
  396. width={isMobile() ? '100%' : 600}
  397. >
  398. <Spin spinning={loading}>
  399. <div style={{ marginTop: 10 }}>
  400. <Typography.Text strong>类型:</Typography.Text>
  401. </div>
  402. <Select
  403. name='type'
  404. required
  405. optionList={CHANNEL_OPTIONS}
  406. value={inputs.type}
  407. onChange={(value) => handleInputChange('type', value)}
  408. style={{ width: '50%' }}
  409. />
  410. {inputs.type === 3 && (
  411. <>
  412. <div style={{ marginTop: 10 }}>
  413. <Banner
  414. type={'warning'}
  415. description={
  416. <>
  417. 注意,<strong>模型部署名称必须和模型名称保持一致</strong>
  418. ,因为 One API 会把请求体中的 model
  419. 参数替换为你的部署名称(模型名称中的点会被剔除),
  420. <a
  421. target='_blank'
  422. href='https://github.com/songquanpeng/one-api/issues/133?notification_referrer_id=NT_kwDOAmJSYrM2NjIwMzI3NDgyOjM5OTk4MDUw#issuecomment-1571602271'
  423. >
  424. 图片演示
  425. </a>
  426. </>
  427. }
  428. ></Banner>
  429. </div>
  430. <div style={{ marginTop: 10 }}>
  431. <Typography.Text strong>
  432. AZURE_OPENAI_ENDPOINT:
  433. </Typography.Text>
  434. </div>
  435. <Input
  436. label='AZURE_OPENAI_ENDPOINT'
  437. name='azure_base_url'
  438. placeholder={
  439. '请输入 AZURE_OPENAI_ENDPOINT,例如:https://docs-test-001.openai.azure.com'
  440. }
  441. onChange={(value) => {
  442. handleInputChange('base_url', value);
  443. }}
  444. value={inputs.base_url}
  445. autoComplete='new-password'
  446. />
  447. <div style={{ marginTop: 10 }}>
  448. <Typography.Text strong>默认 API 版本:</Typography.Text>
  449. </div>
  450. <Input
  451. label='默认 API 版本'
  452. name='azure_other'
  453. placeholder={
  454. '请输入默认 API 版本,例如:2023-06-01-preview,该配置可以被实际的请求查询参数所覆盖'
  455. }
  456. onChange={(value) => {
  457. handleInputChange('other', value);
  458. }}
  459. value={inputs.other}
  460. autoComplete='new-password'
  461. />
  462. </>
  463. )}
  464. {inputs.type === 8 && (
  465. <>
  466. <div style={{ marginTop: 10 }}>
  467. <Banner
  468. type={'warning'}
  469. description={
  470. <>
  471. 如果你对接的是上游One API或者New API等转发项目,请使用OpenAI类型,不要使用此类型,除非你知道你在做什么。
  472. </>
  473. }
  474. ></Banner>
  475. </div>
  476. <div style={{ marginTop: 10 }}>
  477. <Typography.Text strong>
  478. 完整的 Base URL,支持变量{'{model}'}:
  479. </Typography.Text>
  480. </div>
  481. <Input
  482. name='base_url'
  483. placeholder={
  484. '请输入完整的URL,例如:https://api.openai.com/v1/chat/completions'
  485. }
  486. onChange={(value) => {
  487. handleInputChange('base_url', value);
  488. }}
  489. value={inputs.base_url}
  490. autoComplete='new-password'
  491. />
  492. </>
  493. )}
  494. <div style={{ marginTop: 10 }}>
  495. <Typography.Text strong>名称:</Typography.Text>
  496. </div>
  497. <Input
  498. required
  499. name='name'
  500. placeholder={'请为渠道命名'}
  501. onChange={(value) => {
  502. handleInputChange('name', value);
  503. }}
  504. value={inputs.name}
  505. autoComplete='new-password'
  506. />
  507. <div style={{ marginTop: 10 }}>
  508. <Typography.Text strong>分组:</Typography.Text>
  509. </div>
  510. <Select
  511. placeholder={'请选择可以使用该渠道的分组'}
  512. name='groups'
  513. required
  514. multiple
  515. selection
  516. allowAdditions
  517. additionLabel={'请在系统设置页面编辑分组倍率以添加新的分组:'}
  518. onChange={(value) => {
  519. handleInputChange('groups', value);
  520. }}
  521. value={inputs.groups}
  522. autoComplete='new-password'
  523. optionList={groupOptions}
  524. />
  525. {inputs.type === 18 && (
  526. <>
  527. <div style={{ marginTop: 10 }}>
  528. <Typography.Text strong>模型版本:</Typography.Text>
  529. </div>
  530. <Input
  531. name='other'
  532. placeholder={
  533. '请输入星火大模型版本,注意是接口地址中的版本号,例如:v2.1'
  534. }
  535. onChange={(value) => {
  536. handleInputChange('other', value);
  537. }}
  538. value={inputs.other}
  539. autoComplete='new-password'
  540. />
  541. </>
  542. )}
  543. {inputs.type === 21 && (
  544. <>
  545. <div style={{ marginTop: 10 }}>
  546. <Typography.Text strong>知识库 ID:</Typography.Text>
  547. </div>
  548. <Input
  549. label='知识库 ID'
  550. name='other'
  551. placeholder={'请输入知识库 ID,例如:123456'}
  552. onChange={(value) => {
  553. handleInputChange('other', value);
  554. }}
  555. value={inputs.other}
  556. autoComplete='new-password'
  557. />
  558. </>
  559. )}
  560. <div style={{ marginTop: 10 }}>
  561. <Typography.Text strong>模型:</Typography.Text>
  562. </div>
  563. <Select
  564. placeholder={'请选择该渠道所支持的模型'}
  565. name='models'
  566. required
  567. multiple
  568. selection
  569. onChange={(value) => {
  570. handleInputChange('models', value);
  571. }}
  572. value={inputs.models}
  573. autoComplete='new-password'
  574. optionList={modelOptions}
  575. />
  576. <div style={{ lineHeight: '40px', marginBottom: '12px' }}>
  577. <Space>
  578. <Button
  579. type='primary'
  580. onClick={() => {
  581. handleInputChange('models', basicModels);
  582. }}
  583. >
  584. 填入相关模型
  585. </Button>
  586. <Button
  587. type='secondary'
  588. onClick={() => {
  589. handleInputChange('models', fullModels);
  590. }}
  591. >
  592. 填入所有模型
  593. </Button>
  594. <Tooltip content={fetchButtonTips}>
  595. <Button
  596. type='tertiary'
  597. onClick={() => {
  598. fetchUpstreamModelList('models');
  599. }}
  600. >
  601. 获取模型列表
  602. </Button>
  603. </Tooltip>
  604. <Button
  605. type='warning'
  606. onClick={() => {
  607. handleInputChange('models', []);
  608. }}
  609. >
  610. 清除所有模型
  611. </Button>
  612. </Space>
  613. <Input
  614. addonAfter={
  615. <Button type='primary' onClick={addCustomModels}>
  616. 填入
  617. </Button>
  618. }
  619. placeholder='输入自定义模型名称'
  620. value={customModel}
  621. onChange={(value) => {
  622. setCustomModel(value.trim());
  623. }}
  624. />
  625. </div>
  626. <div style={{ marginTop: 10 }}>
  627. <Typography.Text strong>模型重定向:</Typography.Text>
  628. </div>
  629. <TextArea
  630. placeholder={`此项可选,用于修改请求体中的模型名称,为一个 JSON 字符串,键为请求中模型名称,值为要替换的模型名称,例如:\n${JSON.stringify(MODEL_MAPPING_EXAMPLE, null, 2)}`}
  631. name='model_mapping'
  632. onChange={(value) => {
  633. handleInputChange('model_mapping', value);
  634. }}
  635. autosize
  636. value={inputs.model_mapping}
  637. autoComplete='new-password'
  638. />
  639. <Typography.Text
  640. style={{
  641. color: 'rgba(var(--semi-blue-5), 1)',
  642. userSelect: 'none',
  643. cursor: 'pointer',
  644. }}
  645. onClick={() => {
  646. handleInputChange(
  647. 'model_mapping',
  648. JSON.stringify(MODEL_MAPPING_EXAMPLE, null, 2),
  649. );
  650. }}
  651. >
  652. 填入模板
  653. </Typography.Text>
  654. <div style={{ marginTop: 10 }}>
  655. <Typography.Text strong>密钥:</Typography.Text>
  656. </div>
  657. {batch ? (
  658. <TextArea
  659. label='密钥'
  660. name='key'
  661. required
  662. placeholder={'请输入密钥,一行一个'}
  663. onChange={(value) => {
  664. handleInputChange('key', value);
  665. }}
  666. value={inputs.key}
  667. style={{ minHeight: 150, fontFamily: 'JetBrains Mono, Consolas' }}
  668. autoComplete='new-password'
  669. />
  670. ) : (
  671. <Input
  672. label='密钥'
  673. name='key'
  674. required
  675. placeholder={type2secretPrompt(inputs.type)}
  676. onChange={(value) => {
  677. handleInputChange('key', value);
  678. }}
  679. value={inputs.key}
  680. autoComplete='new-password'
  681. />
  682. )}
  683. {inputs.type === 1 && (
  684. <>
  685. <div style={{ marginTop: 10 }}>
  686. <Typography.Text strong>组织:</Typography.Text>
  687. </div>
  688. <Input
  689. label='组织,可选,不填则为默认组织'
  690. name='openai_organization'
  691. placeholder='请输入组织org-xxx'
  692. onChange={(value) => {
  693. handleInputChange('openai_organization', value);
  694. }}
  695. value={inputs.openai_organization}
  696. />
  697. </>
  698. )}
  699. <div style={{ marginTop: 10 }}>
  700. <Typography.Text strong>默认测试模型:</Typography.Text>
  701. </div>
  702. <Input
  703. name='test_model'
  704. placeholder='不填则为模型列表第一个'
  705. onChange={(value) => {
  706. handleInputChange('test_model', value);
  707. }}
  708. value={inputs.test_model}
  709. />
  710. <div style={{ marginTop: 10, display: 'flex' }}>
  711. <Space>
  712. <Checkbox
  713. name='auto_ban'
  714. checked={autoBan}
  715. onChange={() => {
  716. setAutoBan(!autoBan);
  717. }}
  718. // onChange={handleInputChange}
  719. />
  720. <Typography.Text strong>
  721. 是否自动禁用(仅当自动禁用开启时有效),关闭后不会自动禁用该渠道:
  722. </Typography.Text>
  723. </Space>
  724. </div>
  725. {!isEdit && (
  726. <div style={{ marginTop: 10, display: 'flex' }}>
  727. <Space>
  728. <Checkbox
  729. checked={batch}
  730. label='批量创建'
  731. name='batch'
  732. onChange={() => setBatch(!batch)}
  733. />
  734. <Typography.Text strong>批量创建</Typography.Text>
  735. </Space>
  736. </div>
  737. )}
  738. {inputs.type !== 3 && inputs.type !== 8 && inputs.type !== 22 && (
  739. <>
  740. <div style={{ marginTop: 10 }}>
  741. <Typography.Text strong>代理:</Typography.Text>
  742. </div>
  743. <Input
  744. label='代理'
  745. name='base_url'
  746. placeholder={'此项可选,用于通过代理站来进行 API 调用'}
  747. onChange={(value) => {
  748. handleInputChange('base_url', value);
  749. }}
  750. value={inputs.base_url}
  751. autoComplete='new-password'
  752. />
  753. </>
  754. )}
  755. {inputs.type === 22 && (
  756. <>
  757. <div style={{ marginTop: 10 }}>
  758. <Typography.Text strong>私有部署地址:</Typography.Text>
  759. </div>
  760. <Input
  761. name='base_url'
  762. placeholder={
  763. '请输入私有部署地址,格式为:https://fastgpt.run/api/openapi'
  764. }
  765. onChange={(value) => {
  766. handleInputChange('base_url', value);
  767. }}
  768. value={inputs.base_url}
  769. autoComplete='new-password'
  770. />
  771. </>
  772. )}
  773. <div style={{ marginTop: 10 }}>
  774. <Typography.Text strong>
  775. 状态码复写(仅影响本地判断,不修改返回到上游的状态码):
  776. </Typography.Text>
  777. </div>
  778. <TextArea
  779. placeholder={`此项可选,用于复写返回的状态码,比如将claude渠道的400错误复写为500(用于重试),请勿滥用该功能,例如:\n${JSON.stringify(STATUS_CODE_MAPPING_EXAMPLE, null, 2)}`}
  780. name='status_code_mapping'
  781. onChange={(value) => {
  782. handleInputChange('status_code_mapping', value);
  783. }}
  784. autosize
  785. value={inputs.status_code_mapping}
  786. autoComplete='new-password'
  787. />
  788. <Typography.Text
  789. style={{
  790. color: 'rgba(var(--semi-blue-5), 1)',
  791. userSelect: 'none',
  792. cursor: 'pointer',
  793. }}
  794. onClick={() => {
  795. handleInputChange(
  796. 'status_code_mapping',
  797. JSON.stringify(STATUS_CODE_MAPPING_EXAMPLE, null, 2),
  798. );
  799. }}
  800. >
  801. 填入模板
  802. </Typography.Text>
  803. {/*<div style={{ marginTop: 10 }}>*/}
  804. {/* <Typography.Text strong>*/}
  805. {/* 最大请求token(0表示不限制):*/}
  806. {/* </Typography.Text>*/}
  807. {/*</div>*/}
  808. {/*<Input*/}
  809. {/* label='最大请求token'*/}
  810. {/* name='max_input_tokens'*/}
  811. {/* placeholder='默认为0,表示不限制'*/}
  812. {/* onChange={(value) => {*/}
  813. {/* handleInputChange('max_input_tokens', value);*/}
  814. {/* }}*/}
  815. {/* value={inputs.max_input_tokens}*/}
  816. {/*/>*/}
  817. </Spin>
  818. </SideSheet>
  819. </>
  820. );
  821. };
  822. export default EditChannel;