Files
Xtranslate/test/providers.test.js
hopechen067andClaude Opus 5.5 4c436830be feat: 大模型模型改为下拉框,加入各家免费模型并自动关闭思考
- 新增硅基流动、Gemini、Groq、Cerebras 预设,智谱改用 glm-4.7-flash,默认服务商改为智谱(免费、国内直连)
- 模型下拉框:精选模型标注免费/推荐,OpenRouter 再联网补充最新免费模型,末项可自定义
- 按模型附带关闭思考的参数(智谱、DeepSeek、Qwen3、Groq/Cerebras、OpenRouter),deepseek-flash 从约 7 秒降到 1 秒内
- OpenRouter 免费模型带两个备用模型,被限流时自动重试一次,仍失败给出明确提示
- 移除已停用的 deepseek-chat 预设

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 05:31:20 -04:00

96 lines
4.2 KiB
JavaScript

'use strict';
const test = require('node:test');
const assert = require('node:assert');
const { PROVIDERS, getProvider, modelExtra } = require('../src/main/providers');
const { translateLLM } = require('../src/main/engines/llm');
const { pickFreeModels } = require('../src/main/models');
test('每个预设的默认模型都在自己的模型列表里,免费标记只用 free / quota', () => {
for (const p of PROVIDERS) {
if (!p.models.length) continue;
assert.ok(p.models.some((m) => m.id === p.model), p.id);
for (const m of p.models) assert.ok(m.free === undefined || m.free === 'free' || m.free === 'quota', m.id);
}
assert.equal(getProvider('nope').id, 'custom');
});
test('预设模型的额外参数会进请求体,自定义模型名不带', async () => {
assert.deepEqual(modelExtra('zhipu', 'glm-4.7-flash'), { thinking: { type: 'disabled' } });
assert.deepEqual(modelExtra('zhipu', 'my-own-model'), {});
let body;
await translateLLM({
text: '你好',
direction: 'zh2en',
llm: { provider: 'deepseek', baseUrl: 'https://example.com', model: 'deepseek-flash', apiKey: 'k' },
fetchImpl: async (_url, options) => {
body = JSON.parse(options.body);
return {
ok: true,
status: 200,
body: new ReadableStream({ start(c) { c.enqueue(Buffer.from('data: {"choices":[{"delta":{"content":"Hi"}}]}\n\ndata: [DONE]\n\n')); c.close(); } }),
};
},
});
assert.deepEqual(body.thinking, { type: 'disabled' });
assert.equal(body.model, 'deepseek-flash');
assert.equal(body.stream, true);
});
test('OpenRouter 免费模型:只留 :free、去掉已推荐的和不适合翻译的,按上下文排序截断', () => {
const data = [
{ id: 'google/gemma-4-31b-it:free', name: 'Gemma 4 31B (free)', context_length: 262144 },
{ id: 'foo/bar:free', name: 'Bar (free)', context_length: 8000 },
{ id: 'foo/big:free', name: 'Big (free)', context_length: 128000 },
{ id: 'nvidia/nemotron-3.5-content-safety:free', name: 'Safety', context_length: 128000 },
{ id: 'cohere/north-mini-code:free', name: 'Code', context_length: 256000 },
{ id: 'paid/model', name: 'Paid', context_length: 999999 },
];
const out = pickFreeModels(data, ['google/gemma-4-31b-it:free'], 10);
assert.deepEqual(out.map((m) => m.id), ['foo/big:free', 'foo/bar:free']);
assert.equal(out[0].free, 'free');
assert.equal(out[0].extra.reasoning.enabled, false);
assert.equal(pickFreeModels(data, [], 1).length, 1);
});
function okStream(text) {
return {
ok: true,
status: 200,
body: new ReadableStream({ start(c) { c.enqueue(Buffer.from(`data: {"choices":[{"delta":{"content":"${text}"}}]}\n\ndata: [DONE]\n\n`)); c.close(); } }),
};
}
const busy = () => ({ ok: false, status: 429, text: async () => '' });
const orLLM = { provider: 'openrouter', baseUrl: 'https://openrouter.ai/api/v1', model: 'google/gemma-4-31b-it:free', apiKey: 'k' };
test('OpenRouter 免费模型带上最多 3 个备用模型并关闭思考;付费模型不带', () => {
const extra = modelExtra('openrouter', 'google/gemma-4-31b-it:free');
assert.equal(extra.models.length, 3);
assert.equal(extra.models[0], 'google/gemma-4-31b-it:free');
assert.equal(new Set(extra.models).size, 3);
assert.deepEqual(extra.reasoning, { enabled: false });
assert.equal(modelExtra('openrouter', 'some/new-model:free').models[0], 'some/new-model:free');
assert.deepEqual(modelExtra('openrouter', 'deepseek/deepseek-chat'), {});
});
test('免费模型 429 时自动重试一次,成功就返回', async () => {
let calls = 0;
const text = await translateLLM({
text: '你好', direction: 'zh2en', llm: orLLM, retryDelayMs: 1,
fetchImpl: async () => (++calls === 1 ? busy() : okStream('Hi')),
});
assert.equal(text, 'Hi');
assert.equal(calls, 2);
});
test('免费模型重试后仍 429,报"免费模型太忙"', async () => {
let calls = 0;
await assert.rejects(
translateLLM({ text: '你好', direction: 'zh2en', llm: orLLM, retryDelayMs: 1, fetchImpl: async () => { calls++; return busy(); } }),
(err) => err.code === 'FREE_BUSY',
);
assert.equal(calls, 2);
const { userMessage } = require('../src/main/errors');
assert.match(userMessage({ code: 'FREE_BUSY' }), /免费模型这会儿太忙/);
});