diff --git a/src/components/ConversationView.vue b/src/components/ConversationView.vue index e5c5c15..53252f5 100644 --- a/src/components/ConversationView.vue +++ b/src/components/ConversationView.vue @@ -14,13 +14,19 @@ import { type Participant } from '../stores/settings'; import { cn } from '../lib/utils'; -import { invoke } from '@tauri-apps/api/core'; import { listen } from '@tauri-apps/api/event'; import { useClipboard } from '../composables/useClipboard'; +import { + executeTranslationRequest, + extractAssistantContent, + resolveModelConfig, + tryParseEvaluationResult, + type TranslationChunkEvent, + type TranslationPayload, +} from '../lib/translation-service'; const settings = useSettingsStore(); const { activeCopyId, copyWithFeedback } = useClipboard(); -interface TranslationChunkEvent { request_id: string; chunk: string; } // UI States const searchQuery = ref(''); @@ -137,12 +143,6 @@ onMounted(async () => { }); onUnmounted(() => { if (unlisten) unlisten(); }); -const generateCurl = (baseUrl: string, key: string, body: any) => { - const fullUrl = `${baseUrl.replace(/\/$/, '')}/chat/completions`; - const maskedKey = key ? `${key.slice(0, 6)}...${key.slice(-4)}` : 'YOUR_API_KEY'; - return `curl "${fullUrl}" \\\n -H "Content-Type: application/json" \\\n -H "Authorization: Bearer ${maskedKey}" \\\n -d '${JSON.stringify(body, null, 2)}'`; -}; - const translateMessage = async (sender: 'me' | 'partner', retranslateId?: string) => { if (!activeSession.value || isTranslating.value) return; @@ -205,7 +205,7 @@ const translateMessage = async (sender: 'me' | 'partner', retranslateId?: string .replace(/{TO_LANG}/g, toLang.englishName) .replace(/{TARGET_TONE}/g, targetTone); - const requestBody = { + const requestBody: TranslationPayload = { model: settings.modelName, messages: [ { role: "system", content: systemPrompt }, @@ -213,31 +213,24 @@ const translateMessage = async (sender: 'me' | 'partner', retranslateId?: string ], stream: settings.enableStreaming }; - const requestId = crypto.randomUUID(); - - settings.addLog('request', { type: retranslateId ? 'conversation-retranslate' : 'conversation', ...requestBody }, generateCurl(settings.apiBaseUrl, settings.apiKey, requestBody)); try { - if (settings.enableStreaming) activeStreamRequestId.value = requestId; - const response = await invoke('translate', { - apiAddress: settings.apiBaseUrl, - apiKey: settings.apiKey, + const response = await executeTranslationRequest({ + apiAddress: settings.apiBaseUrl, + apiKey: settings.apiKey, payload: requestBody, - requestId + logger: settings, + logType: retranslateId ? 'conversation-retranslate' : 'conversation', + onStreamStart: (requestId) => { + activeStreamRequestId.value = requestId; + }, }); if (!settings.enableStreaming) { - const fullResponseJson = JSON.parse(response); - settings.addLog('response', fullResponseJson); - const translatedText = fullResponseJson.choices?.[0]?.message?.content || response; - settings.updateChatMessage(activeSession.value.id, messageId, { translated: translatedText }); - } else { - settings.addLog('response', response); + settings.updateChatMessage(activeSession.value.id, messageId, { translated: extractAssistantContent(response) }); } } catch (err: any) { - const errorMsg = String(err); - settings.addLog('error', errorMsg); - settings.updateChatMessage(activeSession.value.id, messageId, { translated: `Error: ${errorMsg}` }); + settings.updateChatMessage(activeSession.value.id, messageId, { translated: `Error: ${String(err)}` }); } finally { isTranslating.value = false; currentStreamingMessageId.value = null; @@ -305,45 +298,31 @@ const evaluateMessage = async (messageId: string, force = false) => { const userPrompt = `[Source Text]\n${msg.original}\n\n[Current Translation]\n${msg.translated}`; - // 使用审计专用配置 - let evalApiBaseUrl = settings.apiBaseUrl; - let evalApiKey = settings.apiKey; - let evalModelName = settings.modelName; + const modelConfig = resolveModelConfig({ + apiBaseUrl: settings.apiBaseUrl, + apiKey: settings.apiKey, + modelName: settings.modelName, + }, settings.profiles, settings.evaluationProfileId); - if (settings.evaluationProfileId) { - const profile = settings.profiles.find(p => p.id === settings.evaluationProfileId); - if (profile) { - evalApiBaseUrl = profile.apiBaseUrl; - evalApiKey = profile.apiKey; - evalModelName = profile.modelName; - } - } - - const requestBody = { - model: evalModelName, + const requestBody: TranslationPayload = { + model: modelConfig.modelName, messages: [ { role: "system", content: systemPrompt }, { role: "user", content: userPrompt } ], stream: false }; - const requestId = crypto.randomUUID(); - - settings.addLog('request', { type: 'conversation-eval', ...requestBody }, generateCurl(evalApiBaseUrl, evalApiKey, requestBody)); try { - const response = await invoke('translate', { - apiAddress: evalApiBaseUrl, - apiKey: evalApiKey, + const response = await executeTranslationRequest({ + apiAddress: modelConfig.apiBaseUrl, + apiKey: modelConfig.apiKey, payload: requestBody, - requestId + logger: settings, + logType: 'conversation-eval', }); - const fullResponseJson = JSON.parse(response); - settings.addLog('response', fullResponseJson); - const evaluationJson = fullResponseJson.choices?.[0]?.message?.content || response; - settings.updateChatMessage(activeSession.value.id, messageId, { evaluation: evaluationJson }); - } catch (err: any) { - settings.addLog('error', String(err)); + settings.updateChatMessage(activeSession.value.id, messageId, { evaluation: extractAssistantContent(response) }); + } catch { } finally { settings.updateChatMessage(activeSession.value.id, messageId, { isEvaluating: false }); } @@ -360,10 +339,8 @@ const refineMessage = async (messageId: string) => { const msg = activeSession.value.messages.find(m => m.id === messageId); if (!msg || !msg.evaluation || msg.isRefining) return; - let evalData; - try { - evalData = parseEvaluation(msg.evaluation); - } catch (e) { return; } + const parsedEvaluation = tryParseEvaluationResult(msg.evaluation); + const evalData = parsedEvaluation.result; // 仅使用选中的建议 const selectedSuggestions = evalData.suggestions.filter((s: any) => selectedSuggestionIds.value.includes(s.id)); @@ -410,51 +387,37 @@ const refineMessage = async (messageId: string) => { .replace(/{FROM_LANG}/g, fromLang.englishName) .replace(/{TO_LANG}/g, toLang.englishName); - // 使用审计专用配置 - let refineApiBaseUrl = settings.apiBaseUrl; - let refineApiKey = settings.apiKey; - let refineModelName = settings.modelName; + const modelConfig = resolveModelConfig({ + apiBaseUrl: settings.apiBaseUrl, + apiKey: settings.apiKey, + modelName: settings.modelName, + }, settings.profiles, settings.evaluationProfileId); - if (settings.evaluationProfileId) { - const profile = settings.profiles.find(p => p.id === settings.evaluationProfileId); - if (profile) { - refineApiBaseUrl = profile.apiBaseUrl; - refineApiKey = profile.apiKey; - refineModelName = profile.modelName; - } - } - - const requestBody = { - model: refineModelName, + const requestBody: TranslationPayload = { + model: modelConfig.modelName, messages: [ { role: "system", content: systemPrompt }, { role: "user", content: `[Source Text]\n${msg.original}\n\n[Current Translation]\n${currentTranslation}\n\n[Suggestions]\n${suggestionsText}` } ], stream: settings.enableStreaming }; - const requestId = crypto.randomUUID(); - - settings.addLog('request', { type: 'conversation-refine', ...requestBody }, generateCurl(refineApiBaseUrl, refineApiKey, requestBody)); try { - if (settings.enableStreaming) activeStreamRequestId.value = requestId; - const response = await invoke('translate', { - apiAddress: refineApiBaseUrl, - apiKey: refineApiKey, + const response = await executeTranslationRequest({ + apiAddress: modelConfig.apiBaseUrl, + apiKey: modelConfig.apiKey, payload: requestBody, - requestId + logger: settings, + logType: 'conversation-refine', + onStreamStart: (requestId) => { + activeStreamRequestId.value = requestId; + }, }); if (!settings.enableStreaming) { - const fullResponseJson = JSON.parse(response); - settings.addLog('response', fullResponseJson); - const refinedText = fullResponseJson.choices?.[0]?.message?.content || response; - settings.updateChatMessage(activeSession.value.id, messageId, { translated: refinedText }); - } else { - settings.addLog('response', response); + settings.updateChatMessage(activeSession.value.id, messageId, { translated: extractAssistantContent(response) }); } - } catch (err: any) { - settings.addLog('error', String(err)); + } catch { } finally { settings.updateChatMessage(activeSession.value.id, messageId, { isRefining: false, evaluation: undefined }); currentStreamingMessageId.value = null; @@ -468,20 +431,7 @@ const refineMessage = async (messageId: string) => { } }; -const parseEvaluation = (evalStr?: string) => { - if (!evalStr) return null; - try { - // 1. 清洗数据:剔除 Markdown 代码块标记 (如 ```json ... ``` 或 ``` ...) - let cleanStr = evalStr.trim(); - if (cleanStr.startsWith('```')) { - cleanStr = cleanStr.replace(/^```[a-zA-Z]*\n?/, '').replace(/\n?```$/, '').trim(); - } - return JSON.parse(cleanStr); - } catch (e) { - console.error('Failed to parse evaluation JSON:', e, 'Raw string:', evalStr); - return { score: 0, analysis: '无法解析审计结果,请查看日志', suggestions: [] }; - } -}; +const parseEvaluation = (evalStr?: string) => tryParseEvaluationResult(evalStr).result; const handleGlobalClick = () => { myToneDropdownOpen.value = false; diff --git a/src/components/TranslationView.vue b/src/components/TranslationView.vue index fbbe7d9..e2091a1 100644 --- a/src/components/TranslationView.vue +++ b/src/components/TranslationView.vue @@ -1,11 +1,19 @@