diff --git a/.gitignore b/.gitignore index b403469bb..a86135f87 100644 --- a/.gitignore +++ b/.gitignore @@ -17,6 +17,7 @@ tiktoken_cache .eslintcache .gocache .cache +web/bun.lock electron/node_modules electron/dist diff --git a/controller/channel.go b/controller/channel.go index 7bdaf437e..cf1a4010f 100644 --- a/controller/channel.go +++ b/controller/channel.go @@ -11,6 +11,7 @@ import ( "github.com/QuantumNous/new-api/constant" "github.com/QuantumNous/new-api/dto" "github.com/QuantumNous/new-api/model" + "github.com/QuantumNous/new-api/relay/channel/volcengine" "github.com/QuantumNous/new-api/service" "github.com/gin-gonic/gin" @@ -192,6 +193,12 @@ func FetchUpstreamModels(c *gin.Context) { url = fmt.Sprintf("%s/compatible-mode/v1/models", baseURL) case constant.ChannelTypeZhipu_v4: 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: url = fmt.Sprintf("%s/v1/models", baseURL) } diff --git a/relay/channel/volcengine/adaptor.go b/relay/channel/volcengine/adaptor.go index 6e2590591..1391131f2 100644 --- a/relay/channel/volcengine/adaptor.go +++ b/relay/channel/volcengine/adaptor.go @@ -23,8 +23,11 @@ import ( ) const ( - contextKeyTTSRequest = "volcengine_tts_request" - contextKeyResponseFormat = "response_format" + contextKeyTTSRequest = "volcengine_tts_request" + 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 { @@ -238,6 +241,9 @@ func (a *Adaptor) GetRequestURL(info *relaycommon.RelayInfo) (string, error) { switch info.RelayFormat { case types.RelayFormatClaude: + if baseUrl == DoubaoCodingPlan { + return fmt.Sprintf("%s/v1/messages", DoubaoCodingPlanClaudeBaseURL), nil + } if strings.HasPrefix(info.UpstreamModelName, "bot") { 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: switch info.RelayMode { case constant.RelayModeChatCompletions: + if baseUrl == DoubaoCodingPlan { + return fmt.Sprintf("%s/chat/completions", DoubaoCodingPlanOpenAIBaseURL), nil + } if strings.HasPrefix(info.UpstreamModelName, "bot") { return fmt.Sprintf("%s/api/v3/bots/chat/completions", baseUrl), nil } diff --git a/web/src/components/table/channels/modals/EditChannelModal.jsx b/web/src/components/table/channels/modals/EditChannelModal.jsx index c9c284abc..6d9388bef 100644 --- a/web/src/components/table/channels/modals/EditChannelModal.jsx +++ b/web/src/components/table/channels/modals/EditChannelModal.jsx @@ -189,6 +189,7 @@ const EditChannelModal = (props) => { const [useManualInput, setUseManualInput] = useState(false); // 是否使用手动输入模式 const [keyMode, setKeyMode] = useState('append'); // 密钥模式:replace(覆盖)或 append(追加) const [isEnterpriseAccount, setIsEnterpriseAccount] = useState(false); // 是否为企业账户 + const [doubaoApiEditUnlocked, setDoubaoApiEditUnlocked] = useState(false); // 豆包渠道自定义 API 地址隐藏入口 // 密钥显示状态 const [keyDisplayState, setKeyDisplayState] = useState({ @@ -218,6 +219,7 @@ const EditChannelModal = (props) => { 'channelExtraSettings', ]; const formContainerRef = useRef(null); + const doubaoApiClickCountRef = useRef(0); // 2FA状态更新辅助函数 const updateTwoFAState = (updates) => { @@ -306,6 +308,20 @@ const EditChannelModal = (props) => { 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({ force_format: false, @@ -724,6 +740,13 @@ const EditChannelModal = (props) => { } }; + useEffect(() => { + if (inputs.type !== 45) { + doubaoApiClickCountRef.current = 0; + setDoubaoApiEditUnlocked(false); + } + }, [inputs.type]); + useEffect(() => { const modelMap = new Map(); @@ -823,6 +846,9 @@ const EditChannelModal = (props) => { setKeyMode('append'); // 重置企业账户状态 setIsEnterpriseAccount(false); + // 重置豆包隐藏入口状态 + setDoubaoApiEditUnlocked(false); + doubaoApiClickCountRef.current = 0; // 清空表单中的key_mode字段 if (formApiRef.current) { formApiRef.current.setValue('key_mode', undefined); @@ -1959,7 +1985,10 @@ const EditChannelModal = (props) => {