mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2026-01-10 23:59:45 +08:00
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:
parent
8240493685
commit
626a5ed4f1
@ -46,6 +46,7 @@ import {
|
|||||||
EFFORT_RATIO,
|
EFFORT_RATIO,
|
||||||
FileTypes,
|
FileTypes,
|
||||||
isSystemProvider,
|
isSystemProvider,
|
||||||
|
isTranslateAssistant,
|
||||||
MCPCallToolResponse,
|
MCPCallToolResponse,
|
||||||
MCPTool,
|
MCPTool,
|
||||||
MCPToolResponse,
|
MCPToolResponse,
|
||||||
@ -54,7 +55,6 @@ import {
|
|||||||
Provider,
|
Provider,
|
||||||
SystemProviderIds,
|
SystemProviderIds,
|
||||||
ToolCallResponse,
|
ToolCallResponse,
|
||||||
TranslateAssistant,
|
|
||||||
WebSearchSource
|
WebSearchSource
|
||||||
} from '@renderer/types'
|
} from '@renderer/types'
|
||||||
import { ChunkType, TextStartChunk, ThinkingStartChunk } from '@renderer/types/chunk'
|
import { ChunkType, TextStartChunk, ThinkingStartChunk } from '@renderer/types/chunk'
|
||||||
@ -569,13 +569,18 @@ export class OpenAIAPIClient extends OpenAIBaseClient<
|
|||||||
const extra_body: Record<string, any> = {}
|
const extra_body: Record<string, any> = {}
|
||||||
|
|
||||||
if (isQwenMTModel(model)) {
|
if (isQwenMTModel(model)) {
|
||||||
const targetLanguage = (assistant as TranslateAssistant).targetLanguage
|
if (isTranslateAssistant(assistant)) {
|
||||||
extra_body.translation_options = {
|
const targetLanguage = assistant.targetLanguage
|
||||||
source_lang: 'auto',
|
const translationOptions = {
|
||||||
target_lang: mapLanguageToQwenMTModel(targetLanguage!)
|
source_lang: 'auto',
|
||||||
}
|
target_lang: mapLanguageToQwenMTModel(targetLanguage)
|
||||||
if (!extra_body.translation_options.target_lang) {
|
} as const
|
||||||
throw new Error(t('translate.error.not_supported', { language: targetLanguage?.value }))
|
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'))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -3785,6 +3785,7 @@
|
|||||||
},
|
},
|
||||||
"empty": "Translation content is empty",
|
"empty": "Translation content is empty",
|
||||||
"error": {
|
"error": {
|
||||||
|
"chat_qwen_mt": "Qwen MT model cannot be used in chat. Please go to the translation page.",
|
||||||
"detect": {
|
"detect": {
|
||||||
"qwen_mt": "QwenMT model cannot be used for language detection",
|
"qwen_mt": "QwenMT model cannot be used for language detection",
|
||||||
"unknown": "Unknown language detected",
|
"unknown": "Unknown language detected",
|
||||||
|
|||||||
@ -3785,6 +3785,7 @@
|
|||||||
},
|
},
|
||||||
"empty": "翻訳内容が空です",
|
"empty": "翻訳内容が空です",
|
||||||
"error": {
|
"error": {
|
||||||
|
"chat_qwen_mt": "Qwen MT モデルは対話で使用できません。翻訳ページに移動してください",
|
||||||
"detect": {
|
"detect": {
|
||||||
"qwen_mt": "QwenMTモデルは言語検出に使用できません",
|
"qwen_mt": "QwenMTモデルは言語検出に使用できません",
|
||||||
"unknown": "検出された言語は不明です",
|
"unknown": "検出された言語は不明です",
|
||||||
|
|||||||
@ -3785,6 +3785,7 @@
|
|||||||
},
|
},
|
||||||
"empty": "Содержимое перевода пусто",
|
"empty": "Содержимое перевода пусто",
|
||||||
"error": {
|
"error": {
|
||||||
|
"chat_qwen_mt": "Модель Qwen MT недоступна для использования в диалоге, перейдите на страницу перевода",
|
||||||
"detect": {
|
"detect": {
|
||||||
"qwen_mt": "Модель QwenMT не может использоваться для определения языка",
|
"qwen_mt": "Модель QwenMT не может использоваться для определения языка",
|
||||||
"unknown": "Обнаружен неизвестный язык",
|
"unknown": "Обнаружен неизвестный язык",
|
||||||
|
|||||||
@ -3785,6 +3785,7 @@
|
|||||||
},
|
},
|
||||||
"empty": "翻译内容为空",
|
"empty": "翻译内容为空",
|
||||||
"error": {
|
"error": {
|
||||||
|
"chat_qwen_mt": "Qwen MT 模型不可在对话中使用,请转至翻译页面",
|
||||||
"detect": {
|
"detect": {
|
||||||
"qwen_mt": "QwenMT模型不能用于语言检测",
|
"qwen_mt": "QwenMT模型不能用于语言检测",
|
||||||
"unknown": "检测到未知语言",
|
"unknown": "检测到未知语言",
|
||||||
|
|||||||
@ -3785,6 +3785,7 @@
|
|||||||
},
|
},
|
||||||
"empty": "翻譯內容為空",
|
"empty": "翻譯內容為空",
|
||||||
"error": {
|
"error": {
|
||||||
|
"chat_qwen_mt": "Qwen MT 模型不可在对话中使用,請轉至翻譯頁面",
|
||||||
"detect": {
|
"detect": {
|
||||||
"qwen_mt": "QwenMT模型不能用於語言檢測",
|
"qwen_mt": "QwenMT模型不能用於語言檢測",
|
||||||
"unknown": "檢測到未知語言",
|
"unknown": "檢測到未知語言",
|
||||||
|
|||||||
@ -3785,6 +3785,7 @@
|
|||||||
},
|
},
|
||||||
"empty": "Το μεταφρασμένο κείμενο είναι κενό",
|
"empty": "Το μεταφρασμένο κείμενο είναι κενό",
|
||||||
"error": {
|
"error": {
|
||||||
|
"chat_qwen_mt": "Τα μοντέλα Qwen MT δεν είναι διαθέσιμα για χρήση σε διαλόγους, παρακαλώ μεταβείτε στη σελίδα μετάφρασης",
|
||||||
"detect": {
|
"detect": {
|
||||||
"qwen_mt": "Το μοντέλο QwenMT δεν μπορεί να χρησιμοποιηθεί για εντοπισμό γλώσσας",
|
"qwen_mt": "Το μοντέλο QwenMT δεν μπορεί να χρησιμοποιηθεί για εντοπισμό γλώσσας",
|
||||||
"unknown": "Ανιχνεύθηκε άγνωστη γλώσσα",
|
"unknown": "Ανιχνεύθηκε άγνωστη γλώσσα",
|
||||||
|
|||||||
@ -3785,6 +3785,7 @@
|
|||||||
},
|
},
|
||||||
"empty": "El contenido de traducción está vacío",
|
"empty": "El contenido de traducción está vacío",
|
||||||
"error": {
|
"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": {
|
"detect": {
|
||||||
"qwen_mt": "El modelo QwenMT no se puede utilizar para la detección de idiomas",
|
"qwen_mt": "El modelo QwenMT no se puede utilizar para la detección de idiomas",
|
||||||
"unknown": "Se detectó un idioma desconocido",
|
"unknown": "Se detectó un idioma desconocido",
|
||||||
|
|||||||
@ -3785,6 +3785,7 @@
|
|||||||
},
|
},
|
||||||
"empty": "Le contenu à traduire est vide",
|
"empty": "Le contenu à traduire est vide",
|
||||||
"error": {
|
"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": {
|
"detect": {
|
||||||
"qwen_mt": "Le modèle QwenMT ne peut pas être utilisé pour la détection de langues",
|
"qwen_mt": "Le modèle QwenMT ne peut pas être utilisé pour la détection de langues",
|
||||||
"unknown": "Langue inconnue détectée",
|
"unknown": "Langue inconnue détectée",
|
||||||
|
|||||||
@ -3785,6 +3785,7 @@
|
|||||||
},
|
},
|
||||||
"empty": "O conteúdo de tradução está vazio",
|
"empty": "O conteúdo de tradução está vazio",
|
||||||
"error": {
|
"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": {
|
"detect": {
|
||||||
"qwen_mt": "O modelo QwenMT não pode ser usado para detecção de idioma",
|
"qwen_mt": "O modelo QwenMT não pode ser usado para detecção de idioma",
|
||||||
"unknown": "Idioma desconhecido detectado",
|
"unknown": "Idioma desconhecido detectado",
|
||||||
|
|||||||
@ -6,6 +6,7 @@ import {
|
|||||||
MAX_CONTEXT_COUNT,
|
MAX_CONTEXT_COUNT,
|
||||||
UNLIMITED_CONTEXT_COUNT
|
UNLIMITED_CONTEXT_COUNT
|
||||||
} from '@renderer/config/constant'
|
} from '@renderer/config/constant'
|
||||||
|
import { isQwenMTModel } from '@renderer/config/models'
|
||||||
import { UNKNOWN } from '@renderer/config/translate'
|
import { UNKNOWN } from '@renderer/config/translate'
|
||||||
import i18n from '@renderer/i18n'
|
import i18n from '@renderer/i18n'
|
||||||
import store from '@renderer/store'
|
import store from '@renderer/store'
|
||||||
@ -52,11 +53,10 @@ export function getDefaultAssistant(): Assistant {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function getDefaultTranslateAssistant(targetLanguage: TranslateLanguage, text: string): TranslateAssistant {
|
export function getDefaultTranslateAssistant(targetLanguage: TranslateLanguage, text: string): TranslateAssistant {
|
||||||
const translateModel = getTranslateModel()
|
const model = getTranslateModel()
|
||||||
const assistant: Assistant = getDefaultAssistant()
|
const assistant: Assistant = getDefaultAssistant()
|
||||||
assistant.model = translateModel
|
|
||||||
|
|
||||||
if (!assistant.model) {
|
if (!model) {
|
||||||
logger.error('No translate model')
|
logger.error('No translate model')
|
||||||
throw new Error(i18n.t('translate.error.not_configured'))
|
throw new Error(i18n.t('translate.error.not_configured'))
|
||||||
}
|
}
|
||||||
@ -66,15 +66,32 @@ export function getDefaultTranslateAssistant(targetLanguage: TranslateLanguage,
|
|||||||
throw new Error('Unknown target language')
|
throw new Error('Unknown target language')
|
||||||
}
|
}
|
||||||
|
|
||||||
assistant.settings = {
|
const settings = {
|
||||||
temperature: 0.7
|
temperature: 0.7
|
||||||
}
|
}
|
||||||
|
|
||||||
assistant.prompt = store
|
let prompt: string
|
||||||
.getState()
|
let content: string
|
||||||
.settings.translateModelPrompt.replaceAll('{{target_language}}', targetLanguage.value)
|
if (isQwenMTModel(model)) {
|
||||||
.replaceAll('{{text}}', text)
|
content = text
|
||||||
return { ...assistant, targetLanguage }
|
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() {
|
export function getDefaultAssistantSettings() {
|
||||||
|
|||||||
@ -15,12 +15,7 @@ import { formatErrorMessage, isAbortError } from '@renderer/utils/error'
|
|||||||
import { t } from 'i18next'
|
import { t } from 'i18next'
|
||||||
|
|
||||||
import { hasApiKey } from './ApiService'
|
import { hasApiKey } from './ApiService'
|
||||||
import {
|
import { getDefaultTranslateAssistant, getProviderByModel } from './AssistantService'
|
||||||
getDefaultModel,
|
|
||||||
getDefaultTranslateAssistant,
|
|
||||||
getProviderByModel,
|
|
||||||
getTranslateModel
|
|
||||||
} from './AssistantService'
|
|
||||||
|
|
||||||
const logger = loggerService.withContext('TranslateService')
|
const logger = loggerService.withContext('TranslateService')
|
||||||
interface FetchTranslateProps {
|
interface FetchTranslateProps {
|
||||||
@ -30,11 +25,7 @@ interface FetchTranslateProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function fetchTranslate({ assistant, onResponse, abortKey }: FetchTranslateProps) {
|
async function fetchTranslate({ assistant, onResponse, abortKey }: FetchTranslateProps) {
|
||||||
const model = getTranslateModel() || assistant.model || getDefaultModel()
|
const model = assistant.model
|
||||||
|
|
||||||
if (!model) {
|
|
||||||
throw new Error(t('translate.error.not_configured'))
|
|
||||||
}
|
|
||||||
|
|
||||||
const provider = getProviderByModel(model)
|
const provider = getProviderByModel(model)
|
||||||
|
|
||||||
@ -58,8 +49,8 @@ async function fetchTranslate({ assistant, onResponse, abortKey }: FetchTranslat
|
|||||||
|
|
||||||
const params: CompletionsParams = {
|
const params: CompletionsParams = {
|
||||||
callType: 'translate',
|
callType: 'translate',
|
||||||
messages: 'do',
|
messages: assistant.content,
|
||||||
assistant: { ...assistant, model },
|
assistant,
|
||||||
streamOutput: stream,
|
streamOutput: stream,
|
||||||
enableReasoning,
|
enableReasoning,
|
||||||
onResponse,
|
onResponse,
|
||||||
|
|||||||
@ -35,10 +35,19 @@ export type Assistant = {
|
|||||||
regularPhrases?: QuickPhrase[] // Added for regular phrase
|
regularPhrases?: QuickPhrase[] // Added for regular phrase
|
||||||
tags?: string[] // 助手标签
|
tags?: string[] // 助手标签
|
||||||
enableMemory?: boolean
|
enableMemory?: boolean
|
||||||
|
// for translate. 更好的做法是定义base assistant,把 Assistant 作为多种不同定义 assistant 的联合类型,但重构代价太大
|
||||||
|
content?: string
|
||||||
|
targetLanguage?: TranslateLanguage
|
||||||
}
|
}
|
||||||
|
|
||||||
export type TranslateAssistant = Assistant & {
|
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'
|
export type AssistantsSortType = 'tags' | 'list'
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user