fix: qwen-mt translate (#9627)

* feat(translate): 优化翻译助手类型定义和服务逻辑

重构翻译助手类型定义,将内容字段和模型校验逻辑分离
修改翻译服务以使用助手中的模型而非全局配置
为QwenMT模型添加特殊处理逻辑

* docs(i18n): 添加Qwen MT模型在对话中不可用的错误提示

* refactor(openai): 优化翻译选项处理逻辑

移除不必要的的TranslateAssistant类型断言并重构翻译选项的生成方式

* fix(types): 修复isTranslateAssistant类型检查逻辑

确保assistant.content为字符串类型时才返回true
This commit is contained in:
Phantom 2025-08-28 17:06:38 +08:00 committed by GitHub
parent 8240493685
commit 626a5ed4f1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 62 additions and 31 deletions

View File

@ -46,6 +46,7 @@ import {
EFFORT_RATIO,
FileTypes,
isSystemProvider,
isTranslateAssistant,
MCPCallToolResponse,
MCPTool,
MCPToolResponse,
@ -54,7 +55,6 @@ import {
Provider,
SystemProviderIds,
ToolCallResponse,
TranslateAssistant,
WebSearchSource
} from '@renderer/types'
import { ChunkType, TextStartChunk, ThinkingStartChunk } from '@renderer/types/chunk'
@ -569,13 +569,18 @@ export class OpenAIAPIClient extends OpenAIBaseClient<
const extra_body: Record<string, any> = {}
if (isQwenMTModel(model)) {
const targetLanguage = (assistant as TranslateAssistant).targetLanguage
extra_body.translation_options = {
source_lang: 'auto',
target_lang: mapLanguageToQwenMTModel(targetLanguage!)
}
if (!extra_body.translation_options.target_lang) {
throw new Error(t('translate.error.not_supported', { language: targetLanguage?.value }))
if (isTranslateAssistant(assistant)) {
const targetLanguage = assistant.targetLanguage
const translationOptions = {
source_lang: 'auto',
target_lang: mapLanguageToQwenMTModel(targetLanguage)
} as const
if (!translationOptions.target_lang) {
throw new Error(t('translate.error.not_supported', { language: targetLanguage.value }))
}
extra_body.translation_options = translationOptions
} else {
throw new Error(t('translate.error.chat_qwen_mt'))
}
}

View File

@ -3785,6 +3785,7 @@
},
"empty": "Translation content is empty",
"error": {
"chat_qwen_mt": "Qwen MT model cannot be used in chat. Please go to the translation page.",
"detect": {
"qwen_mt": "QwenMT model cannot be used for language detection",
"unknown": "Unknown language detected",

View File

@ -3785,6 +3785,7 @@
},
"empty": "翻訳内容が空です",
"error": {
"chat_qwen_mt": "Qwen MT モデルは対話で使用できません。翻訳ページに移動してください",
"detect": {
"qwen_mt": "QwenMTモデルは言語検出に使用できません",
"unknown": "検出された言語は不明です",

View File

@ -3785,6 +3785,7 @@
},
"empty": "Содержимое перевода пусто",
"error": {
"chat_qwen_mt": "Модель Qwen MT недоступна для использования в диалоге, перейдите на страницу перевода",
"detect": {
"qwen_mt": "Модель QwenMT не может использоваться для определения языка",
"unknown": "Обнаружен неизвестный язык",

View File

@ -3785,6 +3785,7 @@
},
"empty": "翻译内容为空",
"error": {
"chat_qwen_mt": "Qwen MT 模型不可在对话中使用,请转至翻译页面",
"detect": {
"qwen_mt": "QwenMT模型不能用于语言检测",
"unknown": "检测到未知语言",

View File

@ -3785,6 +3785,7 @@
},
"empty": "翻譯內容為空",
"error": {
"chat_qwen_mt": "Qwen MT 模型不可在对话中使用,請轉至翻譯頁面",
"detect": {
"qwen_mt": "QwenMT模型不能用於語言檢測",
"unknown": "檢測到未知語言",

View File

@ -3785,6 +3785,7 @@
},
"empty": "Το μεταφρασμένο κείμενο είναι κενό",
"error": {
"chat_qwen_mt": "Τα μοντέλα Qwen MT δεν είναι διαθέσιμα για χρήση σε διαλόγους, παρακαλώ μεταβείτε στη σελίδα μετάφρασης",
"detect": {
"qwen_mt": "Το μοντέλο QwenMT δεν μπορεί να χρησιμοποιηθεί για εντοπισμό γλώσσας",
"unknown": "Ανιχνεύθηκε άγνωστη γλώσσα",

View File

@ -3785,6 +3785,7 @@
},
"empty": "El contenido de traducción está vacío",
"error": {
"chat_qwen_mt": "El modelo Qwen MT no está disponible para uso en conversaciones, por favor vaya a la página de traducción.",
"detect": {
"qwen_mt": "El modelo QwenMT no se puede utilizar para la detección de idiomas",
"unknown": "Se detectó un idioma desconocido",

View File

@ -3785,6 +3785,7 @@
},
"empty": "Le contenu à traduire est vide",
"error": {
"chat_qwen_mt": "Les modèles Qwen MT ne peuvent pas être utilisés dans les conversations, veuillez vous rendre sur la page de traduction.",
"detect": {
"qwen_mt": "Le modèle QwenMT ne peut pas être utilisé pour la détection de langues",
"unknown": "Langue inconnue détectée",

View File

@ -3785,6 +3785,7 @@
},
"empty": "O conteúdo de tradução está vazio",
"error": {
"chat_qwen_mt": "Modelos Qwen MT não estão disponíveis para uso em conversas. Por favor, vá para a página de tradução.",
"detect": {
"qwen_mt": "O modelo QwenMT não pode ser usado para detecção de idioma",
"unknown": "Idioma desconhecido detectado",

View File

@ -6,6 +6,7 @@ import {
MAX_CONTEXT_COUNT,
UNLIMITED_CONTEXT_COUNT
} from '@renderer/config/constant'
import { isQwenMTModel } from '@renderer/config/models'
import { UNKNOWN } from '@renderer/config/translate'
import i18n from '@renderer/i18n'
import store from '@renderer/store'
@ -52,11 +53,10 @@ export function getDefaultAssistant(): Assistant {
}
export function getDefaultTranslateAssistant(targetLanguage: TranslateLanguage, text: string): TranslateAssistant {
const translateModel = getTranslateModel()
const model = getTranslateModel()
const assistant: Assistant = getDefaultAssistant()
assistant.model = translateModel
if (!assistant.model) {
if (!model) {
logger.error('No translate model')
throw new Error(i18n.t('translate.error.not_configured'))
}
@ -66,15 +66,32 @@ export function getDefaultTranslateAssistant(targetLanguage: TranslateLanguage,
throw new Error('Unknown target language')
}
assistant.settings = {
const settings = {
temperature: 0.7
}
assistant.prompt = store
.getState()
.settings.translateModelPrompt.replaceAll('{{target_language}}', targetLanguage.value)
.replaceAll('{{text}}', text)
return { ...assistant, targetLanguage }
let prompt: string
let content: string
if (isQwenMTModel(model)) {
content = text
prompt = ''
} else {
content = 'follow system instruction'
prompt = store
.getState()
.settings.translateModelPrompt.replaceAll('{{target_language}}', targetLanguage.value)
.replaceAll('{{text}}', text)
}
const translateAssistant = {
...assistant,
model,
settings,
prompt,
targetLanguage,
content
} satisfies TranslateAssistant
return translateAssistant
}
export function getDefaultAssistantSettings() {

View File

@ -15,12 +15,7 @@ import { formatErrorMessage, isAbortError } from '@renderer/utils/error'
import { t } from 'i18next'
import { hasApiKey } from './ApiService'
import {
getDefaultModel,
getDefaultTranslateAssistant,
getProviderByModel,
getTranslateModel
} from './AssistantService'
import { getDefaultTranslateAssistant, getProviderByModel } from './AssistantService'
const logger = loggerService.withContext('TranslateService')
interface FetchTranslateProps {
@ -30,11 +25,7 @@ interface FetchTranslateProps {
}
async function fetchTranslate({ assistant, onResponse, abortKey }: FetchTranslateProps) {
const model = getTranslateModel() || assistant.model || getDefaultModel()
if (!model) {
throw new Error(t('translate.error.not_configured'))
}
const model = assistant.model
const provider = getProviderByModel(model)
@ -58,8 +49,8 @@ async function fetchTranslate({ assistant, onResponse, abortKey }: FetchTranslat
const params: CompletionsParams = {
callType: 'translate',
messages: 'do',
assistant: { ...assistant, model },
messages: assistant.content,
assistant,
streamOutput: stream,
enableReasoning,
onResponse,

View File

@ -35,10 +35,19 @@ export type Assistant = {
regularPhrases?: QuickPhrase[] // Added for regular phrase
tags?: string[] // 助手标签
enableMemory?: boolean
// for translate. 更好的做法是定义base assistant把 Assistant 作为多种不同定义 assistant 的联合类型,但重构代价太大
content?: string
targetLanguage?: TranslateLanguage
}
export type TranslateAssistant = Assistant & {
targetLanguage?: TranslateLanguage
model: Model
content: string
targetLanguage: TranslateLanguage
}
export const isTranslateAssistant = (assistant: Assistant): assistant is TranslateAssistant => {
return (assistant.model && assistant.targetLanguage && typeof assistant.content === 'string') !== undefined
}
export type AssistantsSortType = 'tags' | 'list'