Merge pull request #2238 from seefs001/feature/doubao-coding-plan

feat: support doubao coding plan
This commit is contained in:
Seefs
2025-11-16 23:49:35 +08:00
committed by GitHub
parent 68777bf05f
commit 7598753f4e
4 changed files with 55 additions and 5 deletions

1
.gitignore vendored
View File

@@ -17,6 +17,7 @@ tiktoken_cache
.eslintcache .eslintcache
.gocache .gocache
.cache .cache
web/bun.lock
electron/node_modules electron/node_modules
electron/dist electron/dist

View File

@@ -11,6 +11,7 @@ import (
"github.com/QuantumNous/new-api/constant" "github.com/QuantumNous/new-api/constant"
"github.com/QuantumNous/new-api/dto" "github.com/QuantumNous/new-api/dto"
"github.com/QuantumNous/new-api/model" "github.com/QuantumNous/new-api/model"
"github.com/QuantumNous/new-api/relay/channel/volcengine"
"github.com/QuantumNous/new-api/service" "github.com/QuantumNous/new-api/service"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
@@ -192,6 +193,12 @@ func FetchUpstreamModels(c *gin.Context) {
url = fmt.Sprintf("%s/compatible-mode/v1/models", baseURL) url = fmt.Sprintf("%s/compatible-mode/v1/models", baseURL)
case constant.ChannelTypeZhipu_v4: case constant.ChannelTypeZhipu_v4:
url = fmt.Sprintf("%s/api/paas/v4/models", baseURL) url = fmt.Sprintf("%s/api/paas/v4/models", baseURL)
case constant.ChannelTypeVolcEngine:
if baseURL == volcengine.DoubaoCodingPlan {
url = fmt.Sprintf("%s/v1/models", volcengine.DoubaoCodingPlanOpenAIBaseURL)
} else {
url = fmt.Sprintf("%s/v1/models", baseURL)
}
default: default:
url = fmt.Sprintf("%s/v1/models", baseURL) url = fmt.Sprintf("%s/v1/models", baseURL)
} }

View File

@@ -25,6 +25,9 @@ import (
const ( const (
contextKeyTTSRequest = "volcengine_tts_request" contextKeyTTSRequest = "volcengine_tts_request"
contextKeyResponseFormat = "response_format" contextKeyResponseFormat = "response_format"
DoubaoCodingPlan = "doubao-coding-plan"
DoubaoCodingPlanClaudeBaseURL = "https://ark.cn-beijing.volces.com/api/coding"
DoubaoCodingPlanOpenAIBaseURL = "https://ark.cn-beijing.volces.com/api/coding/v3"
) )
type Adaptor struct { type Adaptor struct {
@@ -238,6 +241,9 @@ func (a *Adaptor) GetRequestURL(info *relaycommon.RelayInfo) (string, error) {
switch info.RelayFormat { switch info.RelayFormat {
case types.RelayFormatClaude: case types.RelayFormatClaude:
if baseUrl == DoubaoCodingPlan {
return fmt.Sprintf("%s/v1/messages", DoubaoCodingPlanClaudeBaseURL), nil
}
if strings.HasPrefix(info.UpstreamModelName, "bot") { if strings.HasPrefix(info.UpstreamModelName, "bot") {
return fmt.Sprintf("%s/api/v3/bots/chat/completions", baseUrl), nil return fmt.Sprintf("%s/api/v3/bots/chat/completions", baseUrl), nil
} }
@@ -245,6 +251,9 @@ func (a *Adaptor) GetRequestURL(info *relaycommon.RelayInfo) (string, error) {
default: default:
switch info.RelayMode { switch info.RelayMode {
case constant.RelayModeChatCompletions: case constant.RelayModeChatCompletions:
if baseUrl == DoubaoCodingPlan {
return fmt.Sprintf("%s/chat/completions", DoubaoCodingPlanOpenAIBaseURL), nil
}
if strings.HasPrefix(info.UpstreamModelName, "bot") { if strings.HasPrefix(info.UpstreamModelName, "bot") {
return fmt.Sprintf("%s/api/v3/bots/chat/completions", baseUrl), nil return fmt.Sprintf("%s/api/v3/bots/chat/completions", baseUrl), nil
} }

View File

@@ -189,6 +189,7 @@ const EditChannelModal = (props) => {
const [useManualInput, setUseManualInput] = useState(false); // 是否使用手动输入模式 const [useManualInput, setUseManualInput] = useState(false); // 是否使用手动输入模式
const [keyMode, setKeyMode] = useState('append'); // 密钥模式replace覆盖或 append追加 const [keyMode, setKeyMode] = useState('append'); // 密钥模式replace覆盖或 append追加
const [isEnterpriseAccount, setIsEnterpriseAccount] = useState(false); // 是否为企业账户 const [isEnterpriseAccount, setIsEnterpriseAccount] = useState(false); // 是否为企业账户
const [doubaoApiEditUnlocked, setDoubaoApiEditUnlocked] = useState(false); // 豆包渠道自定义 API 地址隐藏入口
// 密钥显示状态 // 密钥显示状态
const [keyDisplayState, setKeyDisplayState] = useState({ const [keyDisplayState, setKeyDisplayState] = useState({
@@ -218,6 +219,7 @@ const EditChannelModal = (props) => {
'channelExtraSettings', 'channelExtraSettings',
]; ];
const formContainerRef = useRef(null); const formContainerRef = useRef(null);
const doubaoApiClickCountRef = useRef(0);
// 2FA状态更新辅助函数 // 2FA状态更新辅助函数
const updateTwoFAState = (updates) => { const updateTwoFAState = (updates) => {
@@ -306,6 +308,20 @@ const EditChannelModal = (props) => {
scrollToSection(availableSections[newIndex]); scrollToSection(availableSections[newIndex]);
}; };
const handleApiConfigSecretClick = () => {
if (inputs.type !== 45) return;
const next = doubaoApiClickCountRef.current + 1;
doubaoApiClickCountRef.current = next;
if (next >= 10) {
setDoubaoApiEditUnlocked((unlocked) => {
if (!unlocked) {
showInfo(t('已解锁豆包自定义 API 地址编辑'));
}
return true;
});
}
};
// 渠道额外设置状态 // 渠道额外设置状态
const [channelSettings, setChannelSettings] = useState({ const [channelSettings, setChannelSettings] = useState({
force_format: false, force_format: false,
@@ -724,6 +740,13 @@ const EditChannelModal = (props) => {
} }
}; };
useEffect(() => {
if (inputs.type !== 45) {
doubaoApiClickCountRef.current = 0;
setDoubaoApiEditUnlocked(false);
}
}, [inputs.type]);
useEffect(() => { useEffect(() => {
const modelMap = new Map(); const modelMap = new Map();
@@ -823,6 +846,9 @@ const EditChannelModal = (props) => {
setKeyMode('append'); setKeyMode('append');
// 重置企业账户状态 // 重置企业账户状态
setIsEnterpriseAccount(false); setIsEnterpriseAccount(false);
// 重置豆包隐藏入口状态
setDoubaoApiEditUnlocked(false);
doubaoApiClickCountRef.current = 0;
// 清空表单中的key_mode字段 // 清空表单中的key_mode字段
if (formApiRef.current) { if (formApiRef.current) {
formApiRef.current.setValue('key_mode', undefined); formApiRef.current.setValue('key_mode', undefined);
@@ -1959,7 +1985,10 @@ const EditChannelModal = (props) => {
<div ref={(el) => (formSectionRefs.current.apiConfig = el)}> <div ref={(el) => (formSectionRefs.current.apiConfig = el)}>
<Card className='!rounded-2xl shadow-sm border-0 mb-6'> <Card className='!rounded-2xl shadow-sm border-0 mb-6'>
{/* Header: API Config */} {/* Header: API Config */}
<div className='flex items-center mb-2'> <div
className='flex items-center mb-2'
onClick={handleApiConfigSecretClick}
>
<Avatar <Avatar
size='small' size='small'
color='green' color='green'
@@ -2094,7 +2123,7 @@ const EditChannelModal = (props) => {
inputs.type !== 8 && inputs.type !== 8 &&
inputs.type !== 22 && inputs.type !== 22 &&
inputs.type !== 36 && inputs.type !== 36 &&
inputs.type !== 45 && ( (inputs.type !== 45 || doubaoApiEditUnlocked) && (
<div> <div>
<Form.Input <Form.Input
field='base_url' field='base_url'
@@ -2147,7 +2176,7 @@ const EditChannelModal = (props) => {
</div> </div>
)} )}
{inputs.type === 45 && ( {inputs.type === 45 && !doubaoApiEditUnlocked && (
<div> <div>
<Form.Select <Form.Select
field='base_url' field='base_url'
@@ -2167,6 +2196,10 @@ const EditChannelModal = (props) => {
label: label:
'https://ark.ap-southeast.bytepluses.com', 'https://ark.ap-southeast.bytepluses.com',
}, },
{
value: 'doubao-coding-plan',
label: 'Doubao Coding Plan',
},
]} ]}
defaultValue='https://ark.cn-beijing.volces.com' defaultValue='https://ark.cn-beijing.volces.com'
/> />