EditChannel.js 27 KB

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