Files
akmon/uni_modules/ak-ai-news/services/ai-translation-service.uts
2026-01-20 08:04:15 +08:00

563 lines
15 KiB
Plaintext
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// AI翻译服务
// filepath: h:\blews\akmon\uni_modules\ak-ai-news\services\ai-translation-service.uts
import { AkReq } from '@/uni_modules/ak-req/index.uts'
import type {
TranslationResult,
TranslationOptions,
AIProvider,
AIResponse,
AIServiceConfig
} from '../types/ai-types.uts'
export class AITranslationService {
private config: AIServiceConfig
private req: AkReq
private cache: Map<string, TranslationResult> = new Map()
constructor(config: AIServiceConfig) {
this.config = config
this.req = new AkReq()
}
/**
* 翻译文本 - 智能选择最佳AI服务
*/
async translateText(
text: string,
targetLang: string,
sourceLang?: string,
options?: TranslationOptions
): Promise<AIResponse<TranslationResult>> {
try {
// 检查缓存
const cacheKey = this.generateCacheKey(text, targetLang, sourceLang)
const cached = this.cache.get(cacheKey)
if (cached && this.isCacheValid(cached)) {
return {
success: true,
data: cached,
processingTimeMs: 0,
costUSD: 0
}
}
// 智能选择提供商
const provider = this.selectOptimalProvider(text, targetLang, options)
let result: TranslationResult
const startTime = Date.now()
switch (provider) {
case 'openai':
result = await this.translateWithOpenAI(text, targetLang, sourceLang, options)
break
case 'google':
result = await this.translateWithGoogle(text, targetLang, sourceLang, options)
break
case 'baidu':
result = await this.translateWithBaidu(text, targetLang, sourceLang, options)
break
default:
throw new Error(`不支持的AI提供商: ${provider}`)
}
result.processingTimeMs = Date.now() - startTime
// 质量检查
if (result.qualityScore < (options?.qualityThreshold ?? 0.7)) {
// 尝试使用备用提供商
const fallbackProvider = this.getFallbackProvider(provider)
if (fallbackProvider) {
const fallbackResult = await this.translateWithProvider(
text, targetLang, sourceLang, fallbackProvider, options
)
if (fallbackResult.qualityScore > result.qualityScore) {
result = fallbackResult
}
}
}
// 缓存结果
this.cache.set(cacheKey, result)
// 记录使用统计
await this.recordUsage(result)
return {
success: true,
data: result,
processingTimeMs: result.processingTimeMs,
costUSD: result.costUSD,
provider: result.provider
}
} catch (error) {
console.error('翻译失败:', error)
return {
success: false,
error: error instanceof Error ? error.message : '翻译服务异常',
errorCode: 'TRANSLATION_FAILED'
}
}
}
/**
* 批量翻译
*/
async batchTranslate(
texts: string[],
targetLang: string,
sourceLang?: string,
options?: TranslationOptions
): Promise<AIResponse<TranslationResult[]>> {
try {
const results: TranslationResult[] = []
const batchSize = 10 // 批处理大小
for (let i = 0; i < texts.length; i += batchSize) {
const batch = texts.slice(i, i + batchSize)
const batchPromises = batch.map(text =>
this.translateText(text, targetLang, sourceLang, options)
)
const batchResults = await Promise.all(batchPromises)
for (const result of batchResults) {
if (result.success && result.data) {
results.push(result.data)
}
}
// 避免API限流
if (i + batchSize < texts.length) {
await this.delay(100)
}
}
return {
success: true,
data: results,
processingTimeMs: results.reduce((sum, r) => sum + r.processingTimeMs, 0),
costUSD: results.reduce((sum, r) => sum + r.costUSD, 0)
}
} catch (error) {
return {
success: false,
error: error instanceof Error ? error.message : '批量翻译失败',
errorCode: 'BATCH_TRANSLATION_FAILED'
}
}
}
/**
* OpenAI翻译实现
*/
private async translateWithOpenAI(
text: string,
targetLang: string,
sourceLang?: string,
options?: TranslationOptions
): Promise<TranslationResult> {
const openaiConfig = this.config.openai
if (!openaiConfig) {
throw new Error('OpenAI配置未找到')
}
const prompt = this.buildOpenAIPrompt(text, targetLang, sourceLang, options)
const response = await this.req.post<any>({
url: `${openaiConfig.baseURL || 'https://api.openai.com'}/v1/chat/completions`,
headers: {
'Authorization': `Bearer ${openaiConfig.apiKey}`,
'Content-Type': 'application/json'
},
data: {
model: options?.model || openaiConfig.model,
messages: [
{
role: 'system',
content: '你是一个专业的翻译助手,能够提供高质量的多语言翻译服务。'
},
{
role: 'user',
content: prompt
}
],
max_tokens: options?.maxTokens || openaiConfig.maxTokens,
temperature: options?.temperature || openaiConfig.temperature
}
})
if (!response.success || !response.data) {
throw new Error('OpenAI API调用失败')
}
const choice = response.data.choices?.[0]
if (!choice) {
throw new Error('OpenAI响应格式错误')
}
return this.parseOpenAIResponse(
choice.message.content,
text,
targetLang,
sourceLang || 'auto',
response.data.usage
)
}
/**
* Google翻译实现
*/
private async translateWithGoogle(
text: string,
targetLang: string,
sourceLang?: string,
options?: TranslationOptions
): Promise<TranslationResult> {
const googleConfig = this.config.google
if (!googleConfig) {
throw new Error('Google翻译配置未找到')
}
const startTime = Date.now()
const response = await this.req.post<any>({
url: 'https://translation.googleapis.com/language/translate/v2',
headers: {
'Content-Type': 'application/json'
},
data: {
key: googleConfig.apiKey,
q: text,
target: this.convertLanguageCode(targetLang, 'google'),
source: sourceLang ? this.convertLanguageCode(sourceLang, 'google') : undefined,
format: 'text'
}
})
if (!response.success || !response.data) {
throw new Error('Google翻译API调用失败')
}
const translation = response.data.data?.translations?.[0]
if (!translation) {
throw new Error('Google翻译响应格式错误')
}
return {
translatedText: translation.translatedText,
originalText: text,
sourceLang: translation.detectedSourceLanguage || sourceLang || 'auto',
targetLang,
confidence: 0.9, // Google翻译通常质量较高
qualityScore: 0.85,
provider: 'google',
tokensUsed: Math.ceil(text.length / 4), // 估算token数
processingTimeMs: Date.now() - startTime,
costUSD: this.calculateGoogleCost(text.length)
}
}
/**
* 百度翻译实现
*/
private async translateWithBaidu(
text: string,
targetLang: string,
sourceLang?: string,
options?: TranslationOptions
): Promise<TranslationResult> {
const baiduConfig = this.config.baidu
if (!baiduConfig) {
throw new Error('百度翻译配置未找到')
}
const startTime = Date.now()
const salt = Date.now().toString()
const sign = this.generateBaiduSign(text, salt, baiduConfig.apiKey, baiduConfig.secretKey)
const response = await this.req.post<any>({
url: 'https://fanyi-api.baidu.com/api/trans/vip/translate',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
data: {
q: text,
from: sourceLang ? this.convertLanguageCode(sourceLang, 'baidu') : 'auto',
to: this.convertLanguageCode(targetLang, 'baidu'),
appid: baiduConfig.apiKey,
salt: salt,
sign: sign
}
})
if (!response.success || !response.data) {
throw new Error('百度翻译API调用失败')
}
const result = response.data.trans_result?.[0]
if (!result) {
throw new Error('百度翻译响应格式错误')
}
return {
translatedText: result.dst,
originalText: text,
sourceLang: response.data.from || sourceLang || 'auto',
targetLang,
confidence: 0.85,
qualityScore: 0.8,
provider: 'baidu',
tokensUsed: Math.ceil(text.length / 4),
processingTimeMs: Date.now() - startTime,
costUSD: this.calculateBaiduCost(text.length)
}
}
/**
* 选择最优提供商
*/
private selectOptimalProvider(
text: string,
targetLang: string,
options?: TranslationOptions
): AIProvider {
if (options?.provider) {
return options.provider
}
// 根据文本长度和语言选择最佳提供商
const textLength = text.length
const isChineseTarget = targetLang.startsWith('zh')
const isChineseSource = /[\u4e00-\u9fff]/.test(text)
// 中文相关翻译优先使用百度
if (isChineseTarget || isChineseSource) {
return 'baidu'
}
// 长文本使用OpenAI更好的上下文理解
if (textLength > 1000) {
return 'openai'
}
// 短文本使用Google速度快成本低
return 'google'
}
/**
* 获取备用提供商
*/
private getFallbackProvider(primary: AIProvider): AIProvider | null {
switch (primary) {
case 'openai':
return 'google'
case 'google':
return 'baidu'
case 'baidu':
return 'google'
default:
return null
}
}
/**
* 生成缓存键
*/
private generateCacheKey(text: string, targetLang: string, sourceLang?: string): string {
const source = sourceLang || 'auto'
const hash = this.simpleHash(text)
return `${source}-${targetLang}-${hash}`
}
/**
* 检查缓存是否有效
*/
private isCacheValid(result: TranslationResult): boolean {
// 简单的缓存有效性检查,可以根据需要扩展
return result.qualityScore > 0.7
}
/**
* 构建OpenAI提示词
*/
private buildOpenAIPrompt(
text: string,
targetLang: string,
sourceLang?: string,
options?: TranslationOptions
): string {
let prompt = `请将以下文本翻译成${this.getLanguageName(targetLang)}\n\n${text}\n\n`
if (options?.culturalAdaptation) {
prompt += '请考虑文化差异,进行适当的本地化调整。\n'
}
if (options?.preserveFormatting) {
prompt += '请保持原文的格式和结构。\n'
}
prompt += '只返回翻译结果,不要包含其他解释。'
return prompt
}
/**
* 解析OpenAI响应
*/
private parseOpenAIResponse(
content: string,
originalText: string,
targetLang: string,
sourceLang: string,
usage: any
): TranslationResult {
return {
translatedText: content.trim(),
originalText,
sourceLang,
targetLang,
confidence: 0.9,
qualityScore: 0.9,
provider: 'openai',
tokensUsed: usage?.total_tokens || 0,
processingTimeMs: 0,
costUSD: this.calculateOpenAICost(usage?.total_tokens || 0)
}
}
/**
* 转换语言代码
*/
private convertLanguageCode(code: string, provider: AIProvider): string {
const codeMap: Record<AIProvider, Record<string, string>> = {
openai: {
'zh-CN': 'Chinese',
'en-US': 'English',
'ja-JP': 'Japanese',
'ko-KR': 'Korean'
},
google: {
'zh-CN': 'zh',
'en-US': 'en',
'ja-JP': 'ja',
'ko-KR': 'ko'
},
baidu: {
'zh-CN': 'zh',
'en-US': 'en',
'ja-JP': 'jp',
'ko-KR': 'kor'
},
custom: {}
}
return codeMap[provider]?.[code] || code
}
/**
* 获取语言名称
*/
private getLanguageName(code: string): string {
const nameMap: Record<string, string> = {
'zh-CN': '中文',
'en-US': 'English',
'ja-JP': '日语',
'ko-KR': '韩语',
'es-ES': 'Spanish',
'fr-FR': 'French',
'de-DE': 'German'
}
return nameMap[code] || code
}
/**
* 计算成本
*/
private calculateOpenAICost(tokens: number): number {
// GPT-4 pricing: $0.03 per 1K tokens (input + output)
return (tokens / 1000) * 0.03
}
private calculateGoogleCost(textLength: number): number {
// Google Translate: $20 per 1M characters
return (textLength / 1000000) * 20
}
private calculateBaiduCost(textLength: number): number {
// 百度翻译:较低成本,假设$5 per 1M characters
return (textLength / 1000000) * 5
}
/**
* 生成百度签名
*/
private generateBaiduSign(text: string, salt: string, appid: string, key: string): string {
// 简化的签名生成实际应用中需要使用MD5
const str = appid + text + salt + key
return this.simpleHash(str)
}
/**
* 简单哈希函数
*/
private simpleHash(str: string): string {
let hash = 0
for (let i = 0; i < str.length; i++) {
const char = str.charCodeAt(i)
hash = ((hash << 5) - hash) + char
hash = hash & hash // Convert to 32-bit integer
}
return Math.abs(hash).toString(36)
}
/**
* 延迟函数
*/
private delay(ms: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, ms))
}
/**
* 记录使用统计
*/
private async recordUsage(result: TranslationResult): Promise<void> {
try {
// 这里可以将使用统计发送到数据库
console.log('翻译使用统计:', {
provider: result.provider,
tokensUsed: result.tokensUsed,
costUSD: result.costUSD,
processingTimeMs: result.processingTimeMs
})
} catch (error) {
console.error('记录使用统计失败:', error)
}
}
/**
* 根据提供商翻译
*/
private async translateWithProvider(
text: string,
targetLang: string,
sourceLang: string | undefined,
provider: AIProvider,
options?: TranslationOptions
): Promise<TranslationResult> {
switch (provider) {
case 'openai':
return await this.translateWithOpenAI(text, targetLang, sourceLang, options)
case 'google':
return await this.translateWithGoogle(text, targetLang, sourceLang, options)
case 'baidu':
return await this.translateWithBaidu(text, targetLang, sourceLang, options)
default:
throw new Error(`不支持的提供商: ${provider}`)
}
}
}