EditChannel.js 25 KB

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