Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions components/Main.vue
Original file line number Diff line number Diff line change
Expand Up @@ -398,6 +398,25 @@
</el-col>
</el-row>

<!-- DeepSeek API 格式 -->
<el-row v-show="compute.showDeepseekApiType" class="margin-bottom margin-left-2em">
<el-col :span="12" class="lightblue rounded-corner">
<el-tooltip class="box-item" effect="dark"
content="自动:deepseek-v4-flash 走 Responses API,其余模型走 Chat Completion;如使用仅支持 OpenAI 格式的第三方代理,请选择 Chat Completion"
placement="top-start" :show-after="500">
<span class="popup-text popup-vertical-left">API 格式<el-icon class="icon-margin">
<ChatDotRound />
</el-icon></span>
</el-tooltip>
</el-col>
<el-col :span="12">
<el-select v-model="config.deepseekApiType" placeholder="请选择 API 格式">
<el-option class="select-left" v-for="item in options.deepseekApiType" :key="item.value" :label="item.label"
:value="item.value" />
</el-select>
</el-col>
</el-row>

<!-- 高级选项-->
<el-collapse class="margin-left-2em margin-bottom">
<el-collapse-item title="高级选项">
Expand Down Expand Up @@ -760,6 +779,8 @@ let compute = ref({
showNewAPI: computed(() => servicesType.isNewApi(config.value.service)),
// 14、是否显示Azure OpenAI端点配置
showAzureOpenaiEndpoint: computed(() => servicesType.isAzureOpenai(config.value.service)),
// 15、是否显示 DeepSeek API 格式配置
showDeepseekApiType: computed(() => config.value.service === 'deepseek'),
})

// 监听主题变化
Expand Down
2 changes: 1 addition & 1 deletion docs/config/translation-engines.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@
2. 点击设置图标,进入设置页面
3. 选择 "翻译引擎" 选项卡
4. 在引擎列表中选择 "DeepSeek"
5. 选择 `deepseek-chat` 模型,并填入配置信息
5. 选择 `deepseek-v4-flash` 模型,并填入配置信息

<img src="/click-fluent_read.png" alt="点击流畅阅读图标" style="width: 80%; max-width: 100%;border: 1px solid #eee;border-radius: 4px;box-shadow: 0 1px 2px rgba(0,0,0,0.05);" />

Expand Down
54 changes: 49 additions & 5 deletions entrypoints/service/deepseek.ts
Original file line number Diff line number Diff line change
@@ -1,33 +1,77 @@
import { method, urls } from "../utils/constant";
import { deepseekMsgTemplate } from "../utils/template";
import { deepseekMsgTemplate, deepseekResponsesMsgTemplate, getCurrentModel } from "../utils/template";
import { config } from "@/entrypoints/utils/config";
import { contentPostHandler } from "@/entrypoints/utils/check";

// deepseek-v4-* 系列模型走官方的 Responses API(POST /responses)
// 其他模型(含自定义模型)走 OpenAI 兼容的 chat completions 接口
// API 格式由设置中的 deepseekApiType 控制:'responses' 强制 Responses API,'chat' 强制 Chat Completion,'auto' 按模型支持情况自动选择
function useResponsesApi(model: string) {
const apiType = config.deepseekApiType;
if (apiType === 'responses') return true;
if (apiType === 'chat') return false;
// auto: v4-flash 官方原生支持 Responses API;v4-pro 支持尚在灰度,走 chat completions 最稳
return model === 'deepseek-v4-flash';
}

async function deepseek(message: any) {
try {
const headers = new Headers({
'Content-Type': 'application/json',
'Authorization': `Bearer ${config.token[config.service]}`
});

const url = config.proxy[config.service] || urls[config.service];
const model = getCurrentModel();
const endpoint = config.proxy[config.service] || urls[config.service];

// 去掉可能存在的 /chat/completions 后缀得到 base URL,再按所选 API 格式拼接端点。
// 这样无论配置的是完整端点(.../chat/completions)还是 base URL(.../v1),
// payload 与端点都由同一个 isResponses 决定,不会出现格式错配。
const isResponses = useResponsesApi(model);
// 去掉 /chat/completions 后缀和多余的尾斜杠,得到干净的 base URL
const baseUrl = endpoint.replace(/\/chat\/completions\/?$/, '').replace(/\/+$/, '');
const url = isResponses ? `${baseUrl}/responses` : `${baseUrl}/chat/completions`;

const resp = await fetch(url, {
method: method.POST,
headers,
body: deepseekMsgTemplate(message.origin)
body: isResponses
? deepseekResponsesMsgTemplate(message.origin)
: deepseekMsgTemplate(message.origin)
});

if (!resp.ok) {
throw new Error(`翻译失败: ${resp.status} ${resp.statusText} body: ${await resp.text()}`);
}

const result = await resp.json();
return contentPostHandler(result.choices[0].message.content);

// Responses API: output 数组中的 message 类型内容;chat completions: choices[0].message.content
if (isResponses) {
if (typeof result.output_text === 'string' && result.output_text) {
return contentPostHandler(result.output_text);
}
const output = result.output || [];
const text = output
.filter((item: any) => item.type === 'message' && item.content)
.flatMap((item: any) => item.content)
.filter((part: any) => part.type === 'output_text')
.map((part: any) => part.text)
.join('');
if (text) {
return contentPostHandler(text);
}
throw new Error('翻译失败: 上游未返回内容');
}

if (result.choices && result.choices.length > 0) {
return contentPostHandler(result.choices[0].message.content);
}
throw new Error('翻译失败: 上游未返回内容');
} catch (error) {
console.error('API调用失败:', error);
throw error;
}
}

export default deepseek;
export default deepseek;
2 changes: 1 addition & 1 deletion entrypoints/service/newapi.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { method, urls } from "../utils/constant";
import {commonMsgTemplate, deepseekMsgTemplate} from "../utils/template";
import {commonMsgTemplate} from "../utils/template";
import { config } from "@/entrypoints/utils/config";
import { contentPostHandler } from "@/entrypoints/utils/check";

Expand Down
2 changes: 2 additions & 0 deletions entrypoints/utils/model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ export class Config {
translationStatus: boolean; // 是否启用全文翻译进度面板
inputBoxTranslationTrigger: string; // 输入框翻译触发方式
inputBoxTranslationTarget: string; // 输入框翻译目标语言
deepseekApiType: string; // DeepSeek API 格式: 'auto' | 'responses' | 'chat'

constructor() {
this.on = true;
Expand Down Expand Up @@ -98,6 +99,7 @@ export class Config {
this.translationStatus = true; // 默认启用翻译进度面板
this.inputBoxTranslationTrigger = 'disabled'; // 默认关闭输入框翻译
this.inputBoxTranslationTarget = 'en'; // 默认翻译成英文
this.deepseekApiType = 'auto'; // DeepSeek 默认自动选择 API 格式
}
}

Expand Down
8 changes: 7 additions & 1 deletion entrypoints/utils/option.ts
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,7 @@ export const models = new Map<string, Array<string>>([
[services.infini, ["llama-2-13b-chat", "llama-3.3-70b-instruct", "qwen2.5-14b-instruct", "gemma-2-27b-it", "glm-4-9b-chat", customModelString]],
[services.baichuan, ["Baichuan4-Air", "Baichuan4-Turbo", "Baichuan4", customModelString]],
[services.lingyi, ["yi-lightning", customModelString]],
[services.deepseek, ["deepseek-chat", "deepseek-reasoner", customModelString]],
[services.deepseek, ["deepseek-v4-flash", "deepseek-v4-pro", customModelString]],
[services.minimax, ["chatcompletion_v2"]],
[services.jieyue, ["step-1-8k", customModelString]],
[services.huanYuan, ["hunyuan-turbos-latest", "hunyuan-t1-latest", "hunyuan-a13b", "hunyuan-lite", "hunyuan-standard", customModelString]],
Expand Down Expand Up @@ -220,6 +220,12 @@ export const options = {
{value: false, label: "关闭"},
],
form: [{value: "auto", label: "自动检测"}],
// DeepSeek API 格式(仅 DeepSeek 服务显示)
deepseekApiType: [
{value: "auto", label: "自动(推荐)"},
{value: "responses", label: "Responses API"},
{value: "chat", label: "Chat Completion"},
],
to: [
{value: "zh-Hans", label: "中文"},
{value: "en", label: "英语"},
Expand Down
43 changes: 38 additions & 5 deletions entrypoints/utils/template.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,45 @@ export function commonMsgTemplate(origin: string) {
}

// deepseek
export function deepseekMsgTemplate(origin: string) {
export function getCurrentModel() {
// 检测是否使用自定义模型
let model = config.model[config.service] === customModelString ? config.customModel[config.service] : config.model[config.service]

// 删除模型名称中的中文括号及其内容,如"gpt-4(推荐)" -> "gpt-4"
model = model.replace(/(.*)/g, "");
return model.replace(/(.*)/g, "");
}

// DeepSeek 温度参数:thinking 模式(deepseek-reasoner)下 temperature 无效,不传;其余模型统一 0.7
// 两种 API 格式共用同一逻辑,保证行为一致
function deepseekTemperature(model: string): number | undefined {
return model === 'deepseek-reasoner' ? undefined : 0.7;
}

// DeepSeek Responses API 格式(POST /responses),仅 deepseek-v4-* 系列模型支持
// 参考: https://api-docs.deepseek.com/guides/responses_api/
export function deepseekResponsesMsgTemplate(origin: string) {
const model = getCurrentModel();
let system = config.system_role[config.service] || defaultOption.system_role;
let user = (config.user_role[config.service] || defaultOption.user_role)
.replace('{{to}}', config.to).replace('{{origin}}', origin);

const payload: any = {
'model': model,
'instructions': system,
'input': user,
};

const temperature = deepseekTemperature(model);
if (temperature !== undefined) {
payload.temperature = temperature;
}

return JSON.stringify(payload);
}

// DeepSeek chat completions 格式(POST /chat/completions),兼容自定义模型与第三方 OpenAI 兼容端点
export function deepseekMsgTemplate(origin: string) {
const model = getCurrentModel();

let system = config.system_role[config.service] || defaultOption.system_role;
let user = (config.user_role[config.service] || defaultOption.user_role)
Expand All @@ -44,9 +77,9 @@ export function deepseekMsgTemplate(origin: string) {
]
};

// 如果不是 deepseek-reasoner 模型,则添加 temperature
if (model !== 'deepseek-reasoner') {
payload.temperature = 0.7;
const temperature = deepseekTemperature(model);
if (temperature !== undefined) {
payload.temperature = temperature;
}

return JSON.stringify(payload);
Expand Down