mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2026-01-11 16:39:15 +08:00
fix(translate): Fix ActionTranslate duplicate execution and getLanguageByLangcode logic (#12241)
* refactor(translate): remove default temperature setting * refactor(translate): Remove temperature setting from language detection prompt * refactor: Set default translate languages from user settings Initialize target language from user's language setting, falling back to zh-CN if unknown. Set alter language to en-US by default. Remove redundant logic that was duplicated in the effect. * fix(translate): translate action won't trigger twice * fix(translate): Fix getLanguageByLangcode to return built-in language when not loaded Previously, the function would always return UNKNOWN if the languages were not loaded, even for built-in language codes. This change ensures built-in languages are returned if found, regardless of the load state. * fix: Expose translation languages loaded state and fix initialization Add `isLoaded` flag to `useTranslate` hook to track when translation languages are available. Use this flag to prevent premature initialization in `ActionTranslate` component, ensuring language lookups succeed before creating assistants and topics. Add error logging for failed custom language loading and update fallback warning messages for better debugging. * fix: set initialized when finished
This commit is contained in:
parent
acd1ecc09c
commit
680bda3993
@ -36,18 +36,16 @@ export default function useTranslate() {
|
||||
|
||||
const getLanguageByLangcode = useCallback(
|
||||
(langCode: string) => {
|
||||
if (!isLoaded) {
|
||||
logger.verbose('Translate languages are not loaded yet. Return UNKNOWN.')
|
||||
return UNKNOWN
|
||||
}
|
||||
|
||||
const result = translateLanguages.find((item) => item.langCode === langCode)
|
||||
|
||||
if (result) {
|
||||
return result
|
||||
} else if (!isLoaded) {
|
||||
logger.verbose('Translate languages are not loaded yet. Return UNKNOWN.')
|
||||
} else {
|
||||
logger.warn(`Unknown language ${langCode}`)
|
||||
return UNKNOWN
|
||||
}
|
||||
return UNKNOWN
|
||||
},
|
||||
[isLoaded, translateLanguages]
|
||||
)
|
||||
@ -63,6 +61,7 @@ export default function useTranslate() {
|
||||
prompt,
|
||||
settings,
|
||||
translateLanguages,
|
||||
isLoaded,
|
||||
getLanguageByLangcode,
|
||||
updateSettings: handleUpdateSettings
|
||||
}
|
||||
|
||||
@ -116,7 +116,6 @@ export function getDefaultTranslateAssistant(
|
||||
// disable reasoning if it could be disabled, otherwise no configuration
|
||||
const reasoningEffort = supportedOptions?.includes('none') ? 'none' : 'default'
|
||||
const settings = {
|
||||
temperature: 0.7,
|
||||
reasoning_effort: reasoningEffort,
|
||||
..._settings
|
||||
} satisfies Partial<AssistantSettings>
|
||||
|
||||
@ -83,9 +83,7 @@ const detectLanguageByLLM = async (inputText: string): Promise<TranslateLanguage
|
||||
const assistant: Assistant = getDefaultAssistant()
|
||||
|
||||
assistant.model = model
|
||||
assistant.settings = {
|
||||
temperature: 0.7
|
||||
}
|
||||
assistant.settings = {}
|
||||
assistant.prompt = LANG_DETECT_PROMPT.replace('{{list_lang}}', listLangText).replace('{{input}}', text)
|
||||
|
||||
const onChunk: (chunk: Chunk) => void = (chunk: Chunk) => {
|
||||
@ -257,6 +255,7 @@ export const getTranslateOptions = async () => {
|
||||
}))
|
||||
return [...builtinLanguages, ...transformedCustomLangs]
|
||||
} catch (e) {
|
||||
logger.error('[getTranslateOptions] Failed to get custom languages. Fallback to builtinLanguages', e as Error)
|
||||
return builtinLanguages
|
||||
}
|
||||
}
|
||||
|
||||
@ -11,7 +11,6 @@ import MessageContent from '@renderer/pages/home/Messages/MessageContent'
|
||||
import { getDefaultTopic, getDefaultTranslateAssistant } from '@renderer/services/AssistantService'
|
||||
import type { Assistant, Topic, TranslateLanguage, TranslateLanguageCode } from '@renderer/types'
|
||||
import type { ActionItem } from '@renderer/types/selectionTypes'
|
||||
import { runAsyncFunction } from '@renderer/utils'
|
||||
import { abortCompletion } from '@renderer/utils/abortController'
|
||||
import { detectLanguage } from '@renderer/utils/translate'
|
||||
import { Tooltip } from 'antd'
|
||||
@ -32,70 +31,102 @@ const logger = loggerService.withContext('ActionTranslate')
|
||||
|
||||
const ActionTranslate: FC<Props> = ({ action, scrollToBottom }) => {
|
||||
const { t } = useTranslation()
|
||||
const { translateModelPrompt, language } = useSettings()
|
||||
const { language } = useSettings()
|
||||
const { getLanguageByLangcode, isLoaded: isLanguagesLoaded } = useTranslate()
|
||||
|
||||
const [targetLanguage, setTargetLanguage] = useState<TranslateLanguage>(LanguagesEnum.enUS)
|
||||
const [alterLanguage, setAlterLanguage] = useState<TranslateLanguage>(LanguagesEnum.zhCN)
|
||||
const [targetLanguage, setTargetLanguage] = useState<TranslateLanguage>(() => {
|
||||
const lang = getLanguageByLangcode(language)
|
||||
if (lang !== UNKNOWN) {
|
||||
return lang
|
||||
} else {
|
||||
logger.warn('[initialize targetLanguage] Unexpected UNKNOWN. Fallback to zh-CN')
|
||||
return LanguagesEnum.zhCN
|
||||
}
|
||||
})
|
||||
|
||||
const [alterLanguage, setAlterLanguage] = useState<TranslateLanguage>(LanguagesEnum.enUS)
|
||||
|
||||
const [error, setError] = useState('')
|
||||
const [showOriginal, setShowOriginal] = useState(false)
|
||||
const [isContented, setIsContented] = useState(false)
|
||||
const [isLoading, setIsLoading] = useState(true)
|
||||
const [contentToCopy, setContentToCopy] = useState('')
|
||||
const { getLanguageByLangcode } = useTranslate()
|
||||
|
||||
// Use useRef for values that shouldn't trigger re-renders
|
||||
const initialized = useRef(false)
|
||||
const assistantRef = useRef<Assistant | null>(null)
|
||||
const topicRef = useRef<Topic | null>(null)
|
||||
const askId = useRef('')
|
||||
const targetLangRef = useRef(targetLanguage)
|
||||
|
||||
useEffect(() => {
|
||||
runAsyncFunction(async () => {
|
||||
const biDirectionLangPair = await db.settings.get({ id: 'translate:bidirectional:pair' })
|
||||
// It's called only in initialization.
|
||||
// It will change target/alter language, so fetchResult will be triggered. Be careful!
|
||||
const updateLanguagePair = useCallback(async () => {
|
||||
// Only called is when languages loaded.
|
||||
// It ensure we could get right language from getLanguageByLangcode.
|
||||
if (!isLanguagesLoaded) {
|
||||
logger.silly('[updateLanguagePair] Languages are not loaded. Skip.')
|
||||
return
|
||||
}
|
||||
|
||||
let targetLang: TranslateLanguage
|
||||
let alterLang: TranslateLanguage
|
||||
|
||||
if (!biDirectionLangPair || !biDirectionLangPair.value[0]) {
|
||||
const lang = getLanguageByLangcode(language)
|
||||
if (lang !== UNKNOWN) {
|
||||
targetLang = lang
|
||||
} else {
|
||||
logger.warn('Fallback to zh-CN')
|
||||
targetLang = LanguagesEnum.zhCN
|
||||
}
|
||||
} else {
|
||||
targetLang = getLanguageByLangcode(biDirectionLangPair.value[0])
|
||||
}
|
||||
|
||||
if (!biDirectionLangPair || !biDirectionLangPair.value[1]) {
|
||||
alterLang = LanguagesEnum.enUS
|
||||
} else {
|
||||
alterLang = getLanguageByLangcode(biDirectionLangPair.value[1])
|
||||
}
|
||||
const biDirectionLangPair = await db.settings.get({ id: 'translate:bidirectional:pair' })
|
||||
|
||||
if (biDirectionLangPair && biDirectionLangPair.value[0]) {
|
||||
const targetLang = getLanguageByLangcode(biDirectionLangPair.value[0])
|
||||
setTargetLanguage(targetLang)
|
||||
setAlterLanguage(alterLang)
|
||||
})
|
||||
}, [getLanguageByLangcode, language])
|
||||
targetLangRef.current = targetLang
|
||||
}
|
||||
|
||||
// Initialize values only once when action changes
|
||||
useEffect(() => {
|
||||
if (initialized.current || !action.selectedText) return
|
||||
initialized.current = true
|
||||
if (biDirectionLangPair && biDirectionLangPair.value[1]) {
|
||||
const alterLang = getLanguageByLangcode(biDirectionLangPair.value[1])
|
||||
setAlterLanguage(alterLang)
|
||||
}
|
||||
}, [getLanguageByLangcode, isLanguagesLoaded])
|
||||
|
||||
// Initialize values only once
|
||||
const initialize = useCallback(async () => {
|
||||
if (initialized.current) {
|
||||
logger.silly('[initialize] Already initialized.')
|
||||
return
|
||||
}
|
||||
|
||||
// Only try to initialize when languages loaded, so updateLanguagePair would not fail.
|
||||
if (!isLanguagesLoaded) {
|
||||
logger.silly('[initialize] Languages not loaded. Skip initialization.')
|
||||
return
|
||||
}
|
||||
|
||||
// Edge case
|
||||
if (action.selectedText === undefined) {
|
||||
logger.error('[initialize] No selected text.')
|
||||
return
|
||||
}
|
||||
logger.silly('[initialize] Start initialization.')
|
||||
|
||||
// Initialize language pair.
|
||||
// It will update targetLangRef, so we could get latest target language in the following code
|
||||
await updateLanguagePair()
|
||||
|
||||
// Initialize assistant
|
||||
const currentAssistant = getDefaultTranslateAssistant(targetLanguage, action.selectedText)
|
||||
const currentAssistant = getDefaultTranslateAssistant(targetLangRef.current, action.selectedText)
|
||||
|
||||
assistantRef.current = currentAssistant
|
||||
|
||||
// Initialize topic
|
||||
topicRef.current = getDefaultTopic(currentAssistant.id)
|
||||
}, [action, targetLanguage, translateModelPrompt])
|
||||
initialized.current = true
|
||||
}, [action.selectedText, isLanguagesLoaded, updateLanguagePair])
|
||||
|
||||
// Try to initialize when:
|
||||
// 1. action.selectedText change (generally will not)
|
||||
// 2. isLanguagesLoaded change (only initialize when languages loaded)
|
||||
// 3. updateLanguagePair change (depend on translateLanguages and isLanguagesLoaded)
|
||||
useEffect(() => {
|
||||
initialize()
|
||||
}, [initialize])
|
||||
|
||||
const fetchResult = useCallback(async () => {
|
||||
if (!assistantRef.current || !topicRef.current || !action.selectedText) return
|
||||
if (!assistantRef.current || !topicRef.current || !action.selectedText || !initialized.current) return
|
||||
|
||||
const setAskId = (id: string) => {
|
||||
askId.current = id
|
||||
@ -141,6 +172,7 @@ const ActionTranslate: FC<Props> = ({ action, scrollToBottom }) => {
|
||||
|
||||
const assistant = getDefaultTranslateAssistant(translateLang, action.selectedText)
|
||||
assistantRef.current = assistant
|
||||
logger.debug('process once')
|
||||
processMessages(assistant, topicRef.current, assistant.content, setAskId, onStream, onFinish, onError)
|
||||
}, [action, targetLanguage, alterLanguage, scrollToBottom])
|
||||
|
||||
@ -157,7 +189,11 @@ const ActionTranslate: FC<Props> = ({ action, scrollToBottom }) => {
|
||||
}, [allMessages])
|
||||
|
||||
const handleChangeLanguage = (targetLanguage: TranslateLanguage, alterLanguage: TranslateLanguage) => {
|
||||
if (!initialized.current) {
|
||||
return
|
||||
}
|
||||
setTargetLanguage(targetLanguage)
|
||||
targetLangRef.current = targetLanguage
|
||||
setAlterLanguage(alterLanguage)
|
||||
|
||||
db.settings.put({ id: 'translate:bidirectional:pair', value: [targetLanguage.langCode, alterLanguage.langCode] })
|
||||
|
||||
Loading…
Reference in New Issue
Block a user