diff --git a/src/App.vue b/src/App.vue index 0ca7dcc..3b89309 100644 --- a/src/App.vue +++ b/src/App.vue @@ -25,6 +25,7 @@ import { useSettingsStore, LANGUAGES, DEFAULT_TEMPLATE, + DEFAULT_EVALUATION_TEMPLATE, SPEAKER_IDENTITY_OPTIONS, TONE_REGISTER_OPTIONS, type ApiProfile @@ -134,6 +135,15 @@ const targetText = ref(''); const isTranslating = ref(false); const showCopyFeedback = ref(false); +interface EvaluationResult { + score: number; + analysis: string; + improvements?: string; +} + +const evaluationResult = ref(null); +const isEvaluating = ref(false); + let unlisten: (() => void) | null = null; onMounted(async () => { @@ -185,6 +195,7 @@ const swapLanguages = () => { const clearSource = () => { sourceText.value = ''; targetText.value = ''; + evaluationResult.value = null; }; const copyTarget = async () => { @@ -199,11 +210,61 @@ const copyTarget = async () => { } }; +const evaluateTranslation = async () => { + if (!settings.enableEvaluation || !targetText.value) return; + + isEvaluating.value = true; + evaluationResult.value = null; + + const evaluationPrompt = settings.evaluationPromptTemplate + .replace(/{SOURCE_LANG}/g, sourceLang.value.englishName) + .replace(/{TARGET_LANG}/g, targetLang.value.englishName) + .replace(/{SPEAKER_IDENTITY}/g, settings.speakerIdentity) + .replace(/{TONE_REGISTER}/g, settings.toneRegister) + .replace(/{CONTEXT}/g, context.value || 'None') + .replace(/{SOURCE_TEXT}/g, sourceText.value) + .replace(/{TRANSLATED_TEXT}/g, targetText.value); + + const requestBody = { + model: settings.modelName, + messages: [ + { role: "system", content: "You are a professional translation auditor. You must respond in valid JSON format." }, + { role: "user", content: evaluationPrompt } + ], + stream: false // Non-streaming for evaluation to parse JSON + }; + + settings.addLog('request', { type: 'evaluation', ...requestBody }); + + try { + const response = await invoke('translate', { + apiAddress: settings.apiBaseUrl, + apiKey: settings.apiKey, + payload: requestBody + }); + + try { + // Try to extract JSON if the model wrapped it in code blocks + const jsonStr = response.replace(/```json\s?|\s?```/g, '').trim(); + evaluationResult.value = JSON.parse(jsonStr); + settings.addLog('response', { type: 'evaluation', content: evaluationResult.value }); + } catch (parseErr) { + console.error('Failed to parse evaluation result:', response); + settings.addLog('error', `Evaluation parsing error: ${response}`); + } + } catch (err: any) { + settings.addLog('error', `Evaluation error: ${String(err)}`); + } finally { + isEvaluating.value = false; + } +}; + const translate = async () => { if (!sourceText.value.trim() || isTranslating.value) return; isTranslating.value = true; targetText.value = ''; + evaluationResult.value = null; const systemMessage = settings.systemPromptTemplate .replace(/{SOURCE_LANG}/g, sourceLang.value.englishName) @@ -240,6 +301,11 @@ const translate = async () => { targetText.value = response; } settings.addLog('response', 'Translation completed'); + + // Trigger evaluation if enabled + if (settings.enableEvaluation) { + await evaluateTranslation(); + } } catch (err: any) { const errorMsg = String(err); settings.addLog('error', errorMsg); @@ -520,6 +586,47 @@ const translate = async () => { {{ targetText }} 翻译结果将在此显示... + + +
+
+
+
+

翻译质量审计

+
+
+ {{ evaluationResult.score }} / 100 +
+
+ + 正在审计... +
+
+ +
+
+

+ {{ evaluationResult.analysis }} +

+
+ +
+ +
+ 建议优化 +

+ {{ evaluationResult.improvements }} +

+
+
+
+
@@ -647,26 +754,61 @@ const translate = async () => { )"> + +
+
+ +

翻译完成后自动评估准确度

+
+ +

提示词工程

-
+
- +
+
+ +
+
+ + +
+
- {{ tag }} + {{ tag }}
+ +
+ {{ tag }} +
diff --git a/src/stores/settings.ts b/src/stores/settings.ts index cd434ff..675ea34 100644 --- a/src/stores/settings.ts +++ b/src/stores/settings.ts @@ -51,6 +51,37 @@ export const DEFAULT_TEMPLATE = `You are a professional {SOURCE_LANG} ({SOURCE_C 3. Produce ONLY the {TARGET_LANG} translation, without any additional explanations, notes, or commentary. 4. If [Context] is provided, use it strictly to disambiguate polysemous words. DO NOT add any factual information or descriptive details from the [Context] that are not present in the [Text to Translate].`; +export const DEFAULT_EVALUATION_TEMPLATE = `You are an expert translation auditor proficient in {SOURCE_LANG} and {TARGET_LANG}. +Your task is to critically evaluate the accuracy and quality of a translation. + +[Context Info] +- Source Language: {SOURCE_LANG} +- Target Language: {TARGET_LANG} +- Speaker Identity: {SPEAKER_IDENTITY} +- Intended Tone/Register: {TONE_REGISTER} +- Context: {CONTEXT} + +[Input] +- Source Text: {SOURCE_TEXT} +- Translated Text: {TRANSLATED_TEXT} + +[Instructions] +1. Compare the [Source Text] and [Translated Text] meticulously. +2. Check if the translation respects the [Context Info] and [Speaker Identity]. +3. Assign an "Accuracy Score" from 0 to 100. + - Give 0 if there are fatal semantic errors, complete hallucinations, or if the meaning is reversed. + - Deduct points for minor inaccuracies, unnatural phrasing, or tone mismatches. +4. Provide a concise "Analysis" of why you gave that score. +5. (Optional) Provide "Improvements" for a more accurate/natural translation. + +[Output Format] +You MUST respond in JSON format with the following keys. The values for "analysis" and "improvements" MUST be written in Simplified Chinese (简体中文), except when quoting the source or target text: +{ + "score": number, + "analysis": "string", + "improvements": "string" +}`; + export interface ApiProfile { id: string; name: string; @@ -68,6 +99,9 @@ export const useSettingsStore = defineStore('settings', () => { const enableStreaming = useLocalStorage('enable-streaming', true); const systemPromptTemplate = useLocalStorage('system-prompt-template', DEFAULT_TEMPLATE); + const enableEvaluation = useLocalStorage('enable-evaluation', true); + const evaluationPromptTemplate = useLocalStorage('evaluation-prompt-template', DEFAULT_EVALUATION_TEMPLATE); + // 存储整个对象以保持一致性 const sourceLang = useLocalStorage('source-lang-v2', LANGUAGES[0]); const targetLang = useLocalStorage('target-lang-v2', LANGUAGES[4]); @@ -97,6 +131,8 @@ export const useSettingsStore = defineStore('settings', () => { profiles, enableStreaming, systemPromptTemplate, + enableEvaluation, + evaluationPromptTemplate, sourceLang, targetLang, speakerIdentity,