1
0
mirror of https://github.com/zclzone/vue-naive-admin.git synced 2026-06-21 22:04:07 +08:00

feat: 添加AI翻译功能

This commit is contained in:
zclzone
2026-06-17 17:02:39 +08:00
parent a1c64f29f7
commit 23eb592269
2 changed files with 557 additions and 0 deletions

View File

@@ -0,0 +1,122 @@
export const STORE_KEY = 'demo-translate-settings'
export const API_BASE_URL_OPTIONS = [
'https://runapi.co/v1',
]
export const MODEL_OPTIONS = [
'gpt-5.4-mini',
'gpt-5.2',
'gpt-5.4',
'gpt-5.5',
'deepseek-v4-flash',
'deepseek-v4-pro',
]
export const DEFAULT_SETTINGS = {
apiKey: 'sk-17UVTOjYEB3M9Erg32yGdEhMVFKEaALuIkjy1CkGrCUdiQqJ',
apiBaseUrl: API_BASE_URL_OPTIONS[0],
model: MODEL_OPTIONS[0],
}
export const EXPERIENCE_KEY_MAX_TEXT_LENGTH = 100
const TRANSLATION_RULES = 'Translate directly. Return only the translation. Preserve formatting, markdown, code, URLs, numbers, placeholders, and line breaks.'
export function isSettingsReady(target) {
return Boolean(target.apiKey?.trim() && target.apiBaseUrl?.trim() && target.model?.trim())
}
export function isExperienceApiKey(apiKey) {
return apiKey?.trim() === DEFAULT_SETTINGS.apiKey
}
export function validateTranslateText(text, settings) {
if (isExperienceApiKey(settings.apiKey) && text.length > EXPERIENCE_KEY_MAX_TEXT_LENGTH)
throw new Error(`体验 Key 最多支持 ${EXPERIENCE_KEY_MAX_TEXT_LENGTH} 字符`)
}
function countMatches(text, pattern) {
return text.match(pattern)?.length || 0
}
function detectSourceLanguage(text) {
const cjkCount = countMatches(text, /[\u3400-\u9FFF]/g)
const latinCount = countMatches(text, /[A-Z]/gi)
if (!cjkCount && !latinCount)
return 'auto'
if (!cjkCount)
return 'en'
if (!latinCount)
return 'zh'
const cjkRatio = cjkCount / (cjkCount + latinCount)
if (cjkRatio >= 0.2)
return 'zh'
if (cjkRatio <= 0.08)
return 'en'
const firstScript = text.match(/[\u3400-\u9FFFA-Z]/i)?.[0]
return firstScript && /[\u3400-\u9FFF]/.test(firstScript) ? 'zh' : 'en'
}
function createSystemPrompt(sourceLanguage) {
if (sourceLanguage === 'zh')
return `${TRANSLATION_RULES} Target: natural English.`
if (sourceLanguage === 'en')
return `${TRANSLATION_RULES} Target: natural Simplified Chinese.`
return `${TRANSLATION_RULES} Target: Simplified Chinese.`
}
function createFastModelOptions(model) {
const normalized = model.trim().toLowerCase()
if (!normalized.startsWith('gpt-5'))
return {}
return {
reasoning_effort: /^gpt-5\.[1-9]\d*/.test(normalized) ? 'none' : 'low',
verbosity: 'low',
}
}
export async function translateText(text, settings) {
validateTranslateText(text, settings)
const baseUrl = settings.apiBaseUrl.trim().replace(/\/+$/, '')
const model = settings.model.trim() || DEFAULT_SETTINGS.model
const sourceLanguage = detectSourceLanguage(text)
const response = await fetch(`${baseUrl}/chat/completions`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${settings.apiKey.trim()}`,
},
body: JSON.stringify({
model,
temperature: 0,
...createFastModelOptions(model),
messages: [
{
role: 'system',
content: createSystemPrompt(sourceLanguage),
},
{
role: 'user',
content: text,
},
],
}),
})
const data = await response.json().catch(() => ({}))
if (!response.ok)
throw new Error(data?.error?.message || `请求失败:${response.status}`)
const result = data?.choices?.[0]?.message?.content?.trim() || ''
if (!result)
throw new Error('未收到翻译结果')
return result
}