diff --git a/src/app/api/chat/route.ts b/src/app/api/chat/route.ts index ec3bfaf..fca4d4e 100644 --- a/src/app/api/chat/route.ts +++ b/src/app/api/chat/route.ts @@ -2042,6 +2042,28 @@ function buildClaudeToolDefinitions(toolIds: string[]) { required: ['url'], }, }, + youdao_translate: { + name: 'youdao_translate', + description: '有道智云高质量多语言翻译。当用户需要翻译文本、句子、段落或询问某个词/短语的翻译时,请使用此工具。支持100+种语言互译,包括中英日韩法德西俄阿拉伯语等。', + input_schema: { + type: 'object', + properties: { + text: { + type: 'string', + description: '待翻译的文本内容', + }, + from: { + type: 'string', + description: '源语言代码,如 auto(自动检测)/en(英语)/zh-CHS(简体中文)/ja(日语)/ko(韩语)/fr(法语)/de(德语) 等,默认auto', + }, + to: { + type: 'string', + description: '目标语言代码,如 zh-CHS(简体中文)/en(英语)/ja(日语)/ko(韩语)/fr(法语)/de(德语) 等,默认zh-CHS', + }, + }, + required: ['text'], + }, + }, }; return toolIds @@ -2129,6 +2151,31 @@ function buildOpenAIToolDefinitions(toolIds: string[]) { }, }, }, + youdao_translate: { + type: 'function', + function: { + name: 'youdao_translate', + description: '有道智云高质量多语言翻译。当用户需要翻译文本、句子、段落或询问某个词/短语的翻译时,请使用此工具。支持100+种语言互译,包括中英日韩法德西俄阿拉伯语等。', + parameters: { + type: 'object', + properties: { + text: { + type: 'string', + description: '待翻译的文本内容', + }, + from: { + type: 'string', + description: '源语言代码,如 auto(自动检测)/en(英语)/zh-CHS(简体中文)/ja(日语)/ko(韩语)/fr(法语)/de(德语) 等,默认auto', + }, + to: { + type: 'string', + description: '目标语言代码,如 zh-CHS(简体中文)/en(英语)/ja(日语)/ko(韩语)/fr(法语)/de(德语) 等,默认zh-CHS', + }, + }, + required: ['text'], + }, + }, + }, }; return toolIds @@ -2208,6 +2255,29 @@ function buildCodexToolDefinitions(toolIds: string[]) { required: ['url'], }, }, + youdao_translate: { + type: 'function', + name: 'youdao_translate', + description: '有道智云高质量多语言翻译。当用户需要翻译文本、句子、段落或询问某个词/短语的翻译时,请使用此工具。支持100+种语言互译,包括中英日韩法德西俄阿拉伯语等。', + parameters: { + type: 'object', + properties: { + text: { + type: 'string', + description: '待翻译的文本内容', + }, + from: { + type: 'string', + description: '源语言代码,如 auto(自动检测)/en(英语)/zh-CHS(简体中文)/ja(日语)/ko(韩语)/fr(法语)/de(德语) 等,默认auto', + }, + to: { + type: 'string', + description: '目标语言代码,如 zh-CHS(简体中文)/en(英语)/ja(日语)/ko(韩语)/fr(法语)/de(德语) 等,默认zh-CHS', + }, + }, + required: ['text'], + }, + }, }; return toolIds diff --git a/src/services/tools/executor.ts b/src/services/tools/executor.ts index 54edcc4..73a784b 100644 --- a/src/services/tools/executor.ts +++ b/src/services/tools/executor.ts @@ -42,6 +42,13 @@ import { type MetasoReaderInput, type MetasoReaderResponse, } from './metasoReader'; +import { + translate, + formatTranslateResult, + formatTranslateResultShort, + type TranslateInput, + type TranslateResponse, +} from './translate'; import { shouldUsePyodide, analyzeCode, type LoadingCallback } from './codeAnalyzer'; // 导出代码分析函数供外部使用 @@ -208,6 +215,22 @@ export async function executeTool( }; } + case 'youdao_translate': { + const text = String(input.text || ''); + const from = input.from ? String(input.from) : 'auto'; + const to = input.to ? String(input.to) : 'zh-CHS'; + + const translateInput: TranslateInput = { text, from, to }; + const response: TranslateResponse = await translate(translateInput); + + return { + success: response.success, + fullResult: formatTranslateResult(response, text), + displayResult: formatTranslateResultShort(response, text), + rawData: response, + }; + } + default: return { success: false, @@ -255,5 +278,11 @@ export function getAvailableTools() { description: '秘塔AI网页读取,返回Markdown格式', icon: '📄', }, + { + id: 'youdao_translate', + name: '翻译', + description: '有道智云高质量多语言翻译,支持100+种语言', + icon: '🌐', + }, ]; } diff --git a/src/services/tools/translate.ts b/src/services/tools/translate.ts new file mode 100644 index 0000000..4635735 --- /dev/null +++ b/src/services/tools/translate.ts @@ -0,0 +1,314 @@ +/** + * 有道翻译工具服务 + * 使用有道智云API实现高质量多语言翻译 + * + * API文档: https://ai.youdao.com/DOCSIRMA/html/trans/api/wbfy/index.html + */ + +import crypto from 'crypto'; + +// ============== 类型定义 ============== + +export interface TranslateInput { + /** 待翻译文本 */ + text: string; + /** 源语言代码,默认auto自动检测 */ + from?: string; + /** 目标语言代码,默认zh-CHS简体中文 */ + to?: string; +} + +export interface TranslateResponse { + /** 是否成功 */ + success: boolean; + /** 翻译结果 */ + translation?: string; + /** 检测到的源语言 */ + from?: string; + /** 目标语言 */ + to?: string; + /** 错误信息 */ + error?: string; + /** 原始API响应 */ + rawResponse?: YoudaoApiResponse; +} + +/** 有道API原始响应 */ +interface YoudaoApiResponse { + errorCode: string; + query?: string; + translation?: string[]; + l?: string; + dict?: { url: string }; + webdict?: { url: string }; + tSpeakUrl?: string; + speakUrl?: string; +} + +// ============== 有道API配置 ============== + +const YOUDAO_APP_KEY = process.env.YOUDAO_APP_KEY || ''; +const YOUDAO_APP_SECRET = process.env.YOUDAO_APP_SECRET || ''; +const YOUDAO_API_URL = 'https://openapi.youdao.com/api'; + +// ============== 错误码映射 ============== + +const ERROR_CODE_MAP: Record = { + '101': '缺少必填参数', + '102': '不支持的语言类型', + '103': '翻译文本过长(最大5000字符)', + '108': '应用ID无效', + '110': '无相关服务的有效应用', + '111': '开发者账号无效', + '112': '请求服务无效', + '113': '翻译文本不能为空', + '202': '签名检验失败', + '203': '访问IP地址不在可访问IP列表', + '206': '时间戳无效导致签名校验失败', + '207': '重放请求', + '301': '辞典查询失败', + '302': '翻译查询失败', + '303': '服务端异常', + '401': '账户已欠费', + '411': '访问频率受限', + '412': '长请求过于频繁', +}; + +// ============== 工具函数 ============== + +/** + * 生成SHA256签名 + */ +function generateSign( + appKey: string, + input: string, + salt: string, + curtime: string, + appSecret: string +): string { + const signStr = appKey + input + salt + curtime + appSecret; + return crypto.createHash('sha256').update(signStr).digest('hex'); +} + +/** + * 计算input参数(处理长文本截断) + * 规则:文本长度>20时,取前10字符+长度+后10字符 + */ +function truncate(q: string): string { + const len = q.length; + if (len <= 20) return q; + return q.substring(0, 10) + len + q.substring(len - 10, len); +} + +/** + * 获取错误信息 + */ +function getErrorMessage(errorCode: string): string { + return ERROR_CODE_MAP[errorCode] || `未知错误 (错误码: ${errorCode})`; +} + +// ============== 语言代码映射 ============== + +/** 常用语言代码对照表 */ +export const LANGUAGE_MAP: Record = { + // 自动检测 + 'auto': '自动检测', + // 中文 + 'zh-CHS': '简体中文', + 'zh-CHT': '繁体中文', + // 主要语言 + 'en': '英语', + 'ja': '日语', + 'ko': '韩语', + 'fr': '法语', + 'de': '德语', + 'es': '西班牙语', + 'pt': '葡萄牙语', + 'it': '意大利语', + 'ru': '俄语', + 'ar': '阿拉伯语', + // 其他常用语言 + 'vi': '越南语', + 'th': '泰语', + 'id': '印度尼西亚语', + 'ms': '马来语', + 'hi': '印地语', + 'nl': '荷兰语', + 'pl': '波兰语', + 'tr': '土耳其语', + 'uk': '乌克兰语', + 'sv': '瑞典语', + 'da': '丹麦语', + 'no': '挪威语', + 'fi': '芬兰语', + 'el': '希腊语', + 'cs': '捷克语', + 'hu': '匈牙利语', + 'ro': '罗马尼亚语', + 'bg': '保加利亚语', + 'he': '希伯来语', + 'yue': '粤语', +}; + +/** + * 获取语言名称 + */ +export function getLanguageName(code: string): string { + return LANGUAGE_MAP[code] || code; +} + +// ============== 主翻译函数 ============== + +/** + * 执行翻译 + * @param input 翻译输入参数 + * @returns 翻译响应 + */ +export async function translate(input: TranslateInput): Promise { + const { text, from = 'auto', to = 'zh-CHS' } = input; + + // API Key 验证 + if (!YOUDAO_APP_KEY || !YOUDAO_APP_SECRET) { + return { + success: false, + error: '有道翻译 API 未配置,请在环境变量中设置 YOUDAO_APP_KEY 和 YOUDAO_APP_SECRET', + }; + } + + // 参数验证 + if (!text || text.trim().length === 0) { + return { + success: false, + error: '翻译文本不能为空', + }; + } + + if (text.length > 5000) { + return { + success: false, + error: '翻译文本过长,最大支持5000字符', + }; + } + + try { + // 生成请求参数 + const salt = crypto.randomUUID(); + const curtime = Math.round(Date.now() / 1000).toString(); + const sign = generateSign( + YOUDAO_APP_KEY, + truncate(text), + salt, + curtime, + YOUDAO_APP_SECRET + ); + + // 构建请求体 + const params = new URLSearchParams({ + q: text, + from, + to, + appKey: YOUDAO_APP_KEY, + salt, + sign, + signType: 'v3', + curtime, + }); + + // 发送请求 + const response = await fetch(YOUDAO_API_URL, { + method: 'POST', + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + }, + body: params.toString(), + }); + + if (!response.ok) { + return { + success: false, + error: `HTTP请求失败: ${response.status} ${response.statusText}`, + }; + } + + const data: YoudaoApiResponse = await response.json(); + + // 检查API返回结果 + if (data.errorCode === '0') { + // 解析语言方向 (如 "en2zh-CHS") + const langParts = data.l?.split('2') || [from, to]; + const detectedFrom = langParts[0] || from; + const detectedTo = langParts[1] || to; + + return { + success: true, + translation: data.translation?.join('\n') || '', + from: detectedFrom, + to: detectedTo, + rawResponse: data, + }; + } else { + return { + success: false, + error: getErrorMessage(data.errorCode), + rawResponse: data, + }; + } + } catch (error) { + console.error('Translate API error:', error); + return { + success: false, + error: error instanceof Error ? error.message : '翻译请求失败', + }; + } +} + +// ============== 格式化函数 ============== + +/** + * 格式化翻译结果(完整版 - 发送给AI) + */ +export function formatTranslateResult( + response: TranslateResponse, + originalText: string +): string { + if (!response.success) { + return `翻译失败: ${response.error}`; + } + + const fromLang = getLanguageName(response.from || 'auto'); + const toLang = getLanguageName(response.to || 'zh-CHS'); + + let result = `## 翻译结果\n\n`; + result += `**源语言**: ${fromLang}\n`; + result += `**目标语言**: ${toLang}\n\n`; + result += `### 原文\n${originalText}\n\n`; + result += `### 译文\n${response.translation}`; + + return result; +} + +/** + * 格式化翻译结果(简短版 - 显示给用户) + */ +export function formatTranslateResultShort( + response: TranslateResponse, + originalText: string +): string { + if (!response.success) { + return `翻译失败: ${response.error}`; + } + + const fromLang = getLanguageName(response.from || 'auto'); + const toLang = getLanguageName(response.to || 'zh-CHS'); + + // 截取原文预览(最多30字符) + const textPreview = originalText.length > 30 + ? originalText.substring(0, 30) + '...' + : originalText; + + let result = `> 🌐 已翻译「${textPreview}」\n`; + result += `> **${fromLang}** → **${toLang}**\n\n`; + result += `${response.translation}`; + + return result; +}