Merge pull request #124 from lemon07r/main

refactor: directly import and use `setLanguage` from i18n module in l…
This commit is contained in:
何夕2077 2025-12-23 10:55:51 +08:00 committed by GitHub
commit db08148137
8 changed files with 105 additions and 7 deletions

5
.gitignore vendored
View file

@ -5,4 +5,7 @@ CLAUDE.md
config.json config.json
provider_pools.json provider_pools.json
fetch_system_prompt.txt fetch_system_prompt.txt
input_system_prompt.txt input_system_prompt.txt
token-store.json
usage-cache.json
*_oauth_creds.json

0
install-and-run.bat Normal file → Executable file
View file

0
install-and-run.sh Normal file → Executable file
View file

View file

@ -1,7 +1,7 @@
export default { export default {
testEnvironment: 'node', testEnvironment: 'node',
transform: { transform: {
'^.+\\.js$': 'babel-jest', '^.+\\.(js|mjs)$': 'babel-jest',
}, },
transformIgnorePatterns: [ transformIgnorePatterns: [
'/node_modules/(?!(uuid)/)', // uuid is an ESM module that needs to be transformed '/node_modules/(?!(uuid)/)', // uuid is an ESM module that needs to be transformed
@ -15,8 +15,7 @@ export default {
'^(\\.{1,2}/.*)\\.js$': '$1' '^(\\.{1,2}/.*)\\.js$': '$1'
}, },
testMatch: [ testMatch: [
'**/tests/api-server.test.js', '**/tests/**/*.test.js'
'**/tests/api-integration.test.js'
], ],
collectCoverageFrom: [ collectCoverageFrom: [
'src/**/*.js', 'src/**/*.js',

2
package-lock.json generated
View file

@ -1,5 +1,5 @@
{ {
"name": "AIClient2API", "name": "AIClient-2-API",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {

View file

@ -209,6 +209,7 @@ export class GeminiConverter extends BaseConverter {
} }
if (part.functionCall) { if (part.functionCall) {
toolCalls.push({ toolCalls.push({
index: toolCalls.length,
id: part.functionCall.id || `call_${uuidv4()}`, id: part.functionCall.id || `call_${uuidv4()}`,
type: 'function', type: 'function',
function: { function: {
@ -231,6 +232,11 @@ export class GeminiConverter extends BaseConverter {
candidate.finishReason.toLowerCase(); candidate.finishReason.toLowerCase();
} }
// 如果包含工具调用,且完成原因为 stop则将完成原因修改为 tool_calls
if (toolCalls.length > 0 && finishReason === 'stop') {
finishReason = 'tool_calls';
}
// 构建delta对象 // 构建delta对象
const delta = {}; const delta = {};
if (content) delta.content = content; if (content) delta.content = content;

View file

@ -219,7 +219,7 @@
</div> </div>
<script type="module"> <script type="module">
import { initI18n, t } from './app/i18n.js'; import { initI18n, t, setLanguage } from './app/i18n.js';
import { initLanguageSwitcher } from './app/language-switcher.js'; import { initLanguageSwitcher } from './app/language-switcher.js';
// 初始化多语言 // 初始化多语言
@ -246,7 +246,7 @@
option.addEventListener('click', (e) => { option.addEventListener('click', (e) => {
e.stopPropagation(); e.stopPropagation();
const lang = option.getAttribute('data-lang'); const lang = option.getAttribute('data-lang');
module.setLanguage(lang); setLanguage(lang);
switcher.querySelector('.current-lang').textContent = lang === 'zh-CN' ? '中文' : 'EN'; switcher.querySelector('.current-lang').textContent = lang === 'zh-CN' ? '中文' : 'EN';
languageOptions.forEach(opt => opt.classList.remove('active')); languageOptions.forEach(opt => opt.classList.remove('active'));
option.classList.add('active'); option.classList.add('active');

View file

@ -0,0 +1,90 @@
import GeminiConverter from '../src/converters/strategies/GeminiConverter.js';
import { jest } from '@jest/globals';
describe('GeminiConverter', () => {
let converter;
beforeEach(() => {
converter = new GeminiConverter();
});
test('toOpenAIStreamChunk adds index to tool_calls', () => {
const geminiChunk = {
candidates: [{
content: {
parts: [{
functionCall: {
name: 'test_tool',
args: { param: 'value' }
}
}]
}
}]
};
const result = converter.toOpenAIStreamChunk(geminiChunk, 'gemini-pro');
expect(result).not.toBeNull();
expect(result.choices[0].delta).toHaveProperty('tool_calls');
expect(result.choices[0].delta.tool_calls).toHaveLength(1);
expect(result.choices[0].delta.tool_calls[0]).toHaveProperty('index');
expect(result.choices[0].delta.tool_calls[0].index).toBe(0);
expect(result.choices[0].delta.tool_calls[0].function.name).toBe('test_tool');
});
test('toOpenAIStreamChunk handles multiple tool_calls with correct indices', () => {
const geminiChunk = {
candidates: [{
content: {
parts: [
{
functionCall: {
name: 'tool_one',
args: {}
}
},
{
functionCall: {
name: 'tool_two',
args: {}
}
}
]
}
}]
};
const result = converter.toOpenAIStreamChunk(geminiChunk, 'gemini-pro');
expect(result).not.toBeNull();
expect(result.choices[0].delta.tool_calls).toHaveLength(2);
expect(result.choices[0].delta.tool_calls[0].index).toBe(0);
expect(result.choices[0].delta.tool_calls[0].function.name).toBe('tool_one');
expect(result.choices[0].delta.tool_calls[1].index).toBe(1);
expect(result.choices[0].delta.tool_calls[1].function.name).toBe('tool_two');
});
test('toOpenAIStreamChunk sets finish_reason to tool_calls when tool calls are present and finishReason is STOP', () => {
const geminiChunk = {
candidates: [{
finishReason: 'STOP',
content: {
parts: [{
functionCall: {
name: 'test_tool',
args: {}
}
}]
}
}]
};
const result = converter.toOpenAIStreamChunk(geminiChunk, 'gemini-pro');
expect(result).not.toBeNull();
expect(result.choices[0].finish_reason).toBe('tool_calls');
});
});