|
|
@@ -17,6 +17,8 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
|
For commercial licensing, please contact [email protected]
|
|
|
*/
|
|
|
|
|
|
+import React, { useEffect, useState, useRef, useMemo } from 'react';
|
|
|
+import { useTranslation } from 'react-i18next';
|
|
|
import {
|
|
|
API,
|
|
|
showError,
|
|
|
@@ -24,42 +26,38 @@ import {
|
|
|
showSuccess,
|
|
|
verifyJSON,
|
|
|
} from '../../../../helpers';
|
|
|
+import { useIsMobile } from '../../../../hooks/common/useIsMobile.js';
|
|
|
+import { CHANNEL_OPTIONS } from '../../../../constants';
|
|
|
import {
|
|
|
- Avatar,
|
|
|
- Banner,
|
|
|
- Button,
|
|
|
- Card,
|
|
|
- Checkbox,
|
|
|
- Col,
|
|
|
- Form,
|
|
|
- Highlight,
|
|
|
- ImagePreview,
|
|
|
- Input,
|
|
|
- Modal,
|
|
|
- Row,
|
|
|
SideSheet,
|
|
|
Space,
|
|
|
Spin,
|
|
|
- Tag,
|
|
|
+ Button,
|
|
|
Typography,
|
|
|
+ Checkbox,
|
|
|
+ Banner,
|
|
|
+ Modal,
|
|
|
+ ImagePreview,
|
|
|
+ Card,
|
|
|
+ Tag,
|
|
|
+ Avatar,
|
|
|
+ Form,
|
|
|
+ Row,
|
|
|
+ Col,
|
|
|
+ Highlight,
|
|
|
} from '@douyinfe/semi-ui';
|
|
|
import { getChannelModels, copy, getChannelIcon, getModelCategories, selectFilter } from '../../../../helpers';
|
|
|
import ModelSelectModal from './ModelSelectModal';
|
|
|
import JSONEditor from '../../../common/JSONEditor';
|
|
|
-import { CHANNEL_OPTIONS, CLAUDE_CODE_DEFAULT_SYSTEM_PROMPT } from '../../../../constants';
|
|
|
import {
|
|
|
- IconBolt,
|
|
|
- IconClose,
|
|
|
- IconCode,
|
|
|
- IconGlobe,
|
|
|
IconSave,
|
|
|
+ IconClose,
|
|
|
IconServer,
|
|
|
IconSetting,
|
|
|
+ IconCode,
|
|
|
+ IconGlobe,
|
|
|
+ IconBolt,
|
|
|
} from '@douyinfe/semi-icons';
|
|
|
-import React, { useEffect, useMemo, useRef, useState } from 'react';
|
|
|
-
|
|
|
-import { useIsMobile } from '../../../../hooks/common/useIsMobile.js';
|
|
|
-import { useTranslation } from 'react-i18next';
|
|
|
|
|
|
const { Text, Title } = Typography;
|
|
|
|
|
|
@@ -95,8 +93,6 @@ function type2secretPrompt(type) {
|
|
|
return '按照如下格式输入: AccessKey|SecretKey, 如果上游是New API,则直接输ApiKey';
|
|
|
case 51:
|
|
|
return '按照如下格式输入: Access Key ID|Secret Access Key';
|
|
|
- case 53:
|
|
|
- return '按照如下格式输入:AccessToken|RefreshToken';
|
|
|
default:
|
|
|
return '请输入渠道对应的鉴权密钥';
|
|
|
}
|
|
|
@@ -149,10 +145,6 @@ const EditChannelModal = (props) => {
|
|
|
const [customModel, setCustomModel] = useState('');
|
|
|
const [modalImageUrl, setModalImageUrl] = useState('');
|
|
|
const [isModalOpenurl, setIsModalOpenurl] = useState(false);
|
|
|
- const [showOAuthModal, setShowOAuthModal] = useState(false);
|
|
|
- const [authorizationCode, setAuthorizationCode] = useState('');
|
|
|
- const [oauthParams, setOauthParams] = useState(null);
|
|
|
- const [isExchangingCode, setIsExchangingCode] = useState(false);
|
|
|
const [modelModalVisible, setModelModalVisible] = useState(false);
|
|
|
const [fetchedModels, setFetchedModels] = useState([]);
|
|
|
const formApiRef = useRef(null);
|
|
|
@@ -361,24 +353,6 @@ const EditChannelModal = (props) => {
|
|
|
data.system_prompt = '';
|
|
|
}
|
|
|
|
|
|
- // 特殊处理Claude Code渠道的密钥拆分和系统提示词
|
|
|
- if (data.type === 53) {
|
|
|
- // 拆分密钥
|
|
|
- if (data.key) {
|
|
|
- const keyParts = data.key.split('|');
|
|
|
- if (keyParts.length === 2) {
|
|
|
- data.access_token = keyParts[0];
|
|
|
- data.refresh_token = keyParts[1];
|
|
|
- } else {
|
|
|
- // 如果没有 | 分隔符,表示只有access token
|
|
|
- data.access_token = data.key;
|
|
|
- data.refresh_token = '';
|
|
|
- }
|
|
|
- }
|
|
|
- // 强制设置固定系统提示词
|
|
|
- data.system_prompt = CLAUDE_CODE_DEFAULT_SYSTEM_PROMPT;
|
|
|
- }
|
|
|
-
|
|
|
setInputs(data);
|
|
|
if (formApiRef.current) {
|
|
|
formApiRef.current.setValues(data);
|
|
|
@@ -502,72 +476,6 @@ const EditChannelModal = (props) => {
|
|
|
}
|
|
|
};
|
|
|
|
|
|
- // 生成OAuth授权URL
|
|
|
- const handleGenerateOAuth = async () => {
|
|
|
- try {
|
|
|
- setLoading(true);
|
|
|
- const res = await API.get('/api/channel/claude/oauth/url');
|
|
|
- if (res.data.success) {
|
|
|
- setOauthParams(res.data.data);
|
|
|
- setShowOAuthModal(true);
|
|
|
- showSuccess(t('OAuth授权URL生成成功'));
|
|
|
- } else {
|
|
|
- showError(res.data.message || t('生成OAuth授权URL失败'));
|
|
|
- }
|
|
|
- } catch (error) {
|
|
|
- showError(t('生成OAuth授权URL失败:') + error.message);
|
|
|
- } finally {
|
|
|
- setLoading(false);
|
|
|
- }
|
|
|
- };
|
|
|
-
|
|
|
- // 交换授权码
|
|
|
- const handleExchangeCode = async () => {
|
|
|
- if (!authorizationCode.trim()) {
|
|
|
- showError(t('请输入授权码'));
|
|
|
- return;
|
|
|
- }
|
|
|
-
|
|
|
- if (!oauthParams) {
|
|
|
- showError(t('OAuth参数丢失,请重新生成'));
|
|
|
- return;
|
|
|
- }
|
|
|
-
|
|
|
- try {
|
|
|
- setIsExchangingCode(true);
|
|
|
- const res = await API.post('/api/channel/claude/oauth/exchange', {
|
|
|
- authorization_code: authorizationCode,
|
|
|
- code_verifier: oauthParams.code_verifier,
|
|
|
- state: oauthParams.state,
|
|
|
- });
|
|
|
-
|
|
|
- if (res.data.success) {
|
|
|
- const tokenData = res.data.data;
|
|
|
- // 自动填充access token和refresh token
|
|
|
- handleInputChange('access_token', tokenData.access_token);
|
|
|
- handleInputChange('refresh_token', tokenData.refresh_token);
|
|
|
- handleInputChange('key', `${tokenData.access_token}|${tokenData.refresh_token}`);
|
|
|
-
|
|
|
- // 更新表单字段
|
|
|
- if (formApiRef.current) {
|
|
|
- formApiRef.current.setValue('access_token', tokenData.access_token);
|
|
|
- formApiRef.current.setValue('refresh_token', tokenData.refresh_token);
|
|
|
- }
|
|
|
-
|
|
|
- setShowOAuthModal(false);
|
|
|
- setAuthorizationCode('');
|
|
|
- setOauthParams(null);
|
|
|
- showSuccess(t('授权码交换成功,已自动填充tokens'));
|
|
|
- } else {
|
|
|
- showError(res.data.message || t('授权码交换失败'));
|
|
|
- }
|
|
|
- } catch (error) {
|
|
|
- showError(t('授权码交换失败:') + error.message);
|
|
|
- } finally {
|
|
|
- setIsExchangingCode(false);
|
|
|
- }
|
|
|
- };
|
|
|
-
|
|
|
useEffect(() => {
|
|
|
const modelMap = new Map();
|
|
|
|
|
|
@@ -880,7 +788,7 @@ const EditChannelModal = (props) => {
|
|
|
const batchExtra = batchAllowed ? (
|
|
|
<Space>
|
|
|
<Checkbox
|
|
|
- disabled={isEdit || inputs.type === 53}
|
|
|
+ disabled={isEdit}
|
|
|
checked={batch}
|
|
|
onChange={(e) => {
|
|
|
const checked = e.target.checked;
|
|
|
@@ -1216,49 +1124,6 @@ const EditChannelModal = (props) => {
|
|
|
/>
|
|
|
)}
|
|
|
</>
|
|
|
- ) : inputs.type === 53 ? (
|
|
|
- <>
|
|
|
- <Form.Input
|
|
|
- field='access_token'
|
|
|
- label={isEdit ? t('Access Token(编辑模式下,保存的密钥不会显示)') : t('Access Token')}
|
|
|
- placeholder={t('sk-ant-xxx')}
|
|
|
- rules={isEdit ? [] : [{ required: true, message: t('请输入Access Token') }]}
|
|
|
- autoComplete='new-password'
|
|
|
- onChange={(value) => {
|
|
|
- handleInputChange('access_token', value);
|
|
|
- // 同时更新key字段,格式为access_token|refresh_token
|
|
|
- const refreshToken = inputs.refresh_token || '';
|
|
|
- handleInputChange('key', `${value}|${refreshToken}`);
|
|
|
- }}
|
|
|
- suffix={
|
|
|
- <Button
|
|
|
- size="small"
|
|
|
- type="primary"
|
|
|
- theme="light"
|
|
|
- onClick={handleGenerateOAuth}
|
|
|
- >
|
|
|
- {t('生成OAuth授权码')}
|
|
|
- </Button>
|
|
|
- }
|
|
|
- extraText={batchExtra}
|
|
|
- showClear
|
|
|
- />
|
|
|
- <Form.Input
|
|
|
- field='refresh_token'
|
|
|
- label={isEdit ? t('Refresh Token(编辑模式下,保存的密钥不会显示)') : t('Refresh Token')}
|
|
|
- placeholder={t('sk-ant-xxx(可选)')}
|
|
|
- rules={[]}
|
|
|
- autoComplete='new-password'
|
|
|
- onChange={(value) => {
|
|
|
- handleInputChange('refresh_token', value);
|
|
|
- // 同时更新key字段,格式为access_token|refresh_token
|
|
|
- const accessToken = inputs.access_token || '';
|
|
|
- handleInputChange('key', `${accessToken}|${value}`);
|
|
|
- }}
|
|
|
- extraText={batchExtra}
|
|
|
- showClear
|
|
|
- />
|
|
|
- </>
|
|
|
) : (
|
|
|
<Form.Input
|
|
|
field='key'
|
|
|
@@ -1767,19 +1632,11 @@ const EditChannelModal = (props) => {
|
|
|
<Form.TextArea
|
|
|
field='system_prompt'
|
|
|
label={t('系统提示词')}
|
|
|
- placeholder={inputs.type === 53 ? CLAUDE_CODE_DEFAULT_SYSTEM_PROMPT : t('输入系统提示词,用户的系统提示词将优先于此设置')}
|
|
|
- onChange={(value) => {
|
|
|
- if (inputs.type === 53) {
|
|
|
- // Claude Code渠道系统提示词固定,不允许修改
|
|
|
- return;
|
|
|
- }
|
|
|
- handleChannelSettingsChange('system_prompt', value);
|
|
|
- }}
|
|
|
- disabled={inputs.type === 53}
|
|
|
- value={inputs.type === 53 ? CLAUDE_CODE_DEFAULT_SYSTEM_PROMPT : undefined}
|
|
|
+ placeholder={t('输入系统提示词,用户的系统提示词将优先于此设置')}
|
|
|
+ onChange={(value) => handleChannelSettingsChange('system_prompt', value)}
|
|
|
autosize
|
|
|
- showClear={inputs.type !== 53}
|
|
|
- extraText={inputs.type === 53 ? t('Claude Code渠道系统提示词固定为官方CLI身份,不可修改') : t('用户优先:如果用户在请求中指定了系统提示词,将优先使用用户的设置')}
|
|
|
+ showClear
|
|
|
+ extraText={t('用户优先:如果用户在请求中指定了系统提示词,将优先使用用户的设置')}
|
|
|
/>
|
|
|
</Card>
|
|
|
</div>
|
|
|
@@ -1803,70 +1660,8 @@ const EditChannelModal = (props) => {
|
|
|
}}
|
|
|
onCancel={() => setModelModalVisible(false)}
|
|
|
/>
|
|
|
-
|
|
|
- {/* OAuth Authorization Modal */}
|
|
|
- <Modal
|
|
|
- title={t('生成Claude Code OAuth授权码')}
|
|
|
- visible={showOAuthModal}
|
|
|
- onCancel={() => {
|
|
|
- setShowOAuthModal(false);
|
|
|
- setAuthorizationCode('');
|
|
|
- setOauthParams(null);
|
|
|
- }}
|
|
|
- onOk={handleExchangeCode}
|
|
|
- okText={isExchangingCode ? t('交换中...') : t('确认')}
|
|
|
- cancelText={t('取消')}
|
|
|
- confirmLoading={isExchangingCode}
|
|
|
- width={600}
|
|
|
- >
|
|
|
- <div className="space-y-4">
|
|
|
- <div>
|
|
|
- <Text className="text-sm font-medium mb-2 block">{t('请访问以下授权地址:')}</Text>
|
|
|
- <div className="p-3 bg-gray-50 rounded-lg border">
|
|
|
- <Text
|
|
|
- link
|
|
|
- underline
|
|
|
- className="text-sm font-mono break-all cursor-pointer text-blue-600 hover:text-blue-800"
|
|
|
- onClick={() => {
|
|
|
- if (oauthParams?.auth_url) {
|
|
|
- window.open(oauthParams.auth_url, '_blank');
|
|
|
- }
|
|
|
- }}
|
|
|
- >
|
|
|
- {oauthParams?.auth_url || t('正在生成授权地址...')}
|
|
|
- </Text>
|
|
|
- <div className="mt-2">
|
|
|
- <Text
|
|
|
- copyable={{ content: oauthParams?.auth_url }}
|
|
|
- type="tertiary"
|
|
|
- size="small"
|
|
|
- >
|
|
|
- {t('复制链接')}
|
|
|
- </Text>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
-
|
|
|
- <div>
|
|
|
- <Text className="text-sm font-medium mb-2 block">{t('授权后,请将获得的授权码粘贴到下方:')}</Text>
|
|
|
- <Input
|
|
|
- value={authorizationCode}
|
|
|
- onChange={setAuthorizationCode}
|
|
|
- placeholder={t('请输入授权码')}
|
|
|
- showClear
|
|
|
- style={{ width: '100%' }}
|
|
|
- />
|
|
|
- </div>
|
|
|
-
|
|
|
- <Banner
|
|
|
- type="info"
|
|
|
- description={t('获得授权码后,系统将自动换取access token和refresh token并填充到表单中。')}
|
|
|
- className="!rounded-lg"
|
|
|
- />
|
|
|
- </div>
|
|
|
- </Modal>
|
|
|
</>
|
|
|
);
|
|
|
};
|
|
|
|
|
|
-export default EditChannelModal;
|
|
|
+export default EditChannelModal;
|