diff --git a/src/App.vue b/src/App.vue index e9bf43a..b4ffb73 100644 --- a/src/App.vue +++ b/src/App.vue @@ -26,6 +26,7 @@ import { LANGUAGES, DEFAULT_TEMPLATE, DEFAULT_EVALUATION_TEMPLATE, + DEFAULT_REFINEMENT_TEMPLATE, SPEAKER_IDENTITY_OPTIONS, TONE_REGISTER_OPTIONS, type ApiProfile @@ -138,20 +139,38 @@ const targetText = ref(''); const isTranslating = ref(false); const showCopyFeedback = ref(false); +interface Suggestion { + id: number; + text: string; + importance: number; +} + interface EvaluationResult { score: number; analysis: string; - improvements?: string; + suggestions?: Suggestion[]; } const evaluationResult = ref(null); const isEvaluating = ref(false); +const isRefining = ref(false); +const selectedSuggestionIds = ref([]); + +const toggleSuggestion = (id: number) => { + if (!selectedSuggestionIds.value) selectedSuggestionIds.value = []; + const index = selectedSuggestionIds.value.indexOf(id); + if (index > -1) { + selectedSuggestionIds.value.splice(index, 1); + } else { + selectedSuggestionIds.value.push(id); + } +}; let unlisten: (() => void) | null = null; onMounted(async () => { unlisten = await listen('translation-chunk', (event) => { - if (isTranslating.value) { + if (isTranslating.value || isRefining.value) { targetText.value += event.payload; } }); @@ -224,6 +243,7 @@ const evaluateTranslation = async () => { isEvaluating.value = true; evaluationResult.value = null; + selectedSuggestionIds.value = []; // Determine which API config to use for evaluation let apiBaseUrl = settings.apiBaseUrl; @@ -282,6 +302,76 @@ const evaluateTranslation = async () => { } }; +const refineTranslation = async () => { + if (!targetText.value || isRefining.value) return; + + const selectedTexts = evaluationResult.value?.suggestions + ?.filter(s => selectedSuggestionIds.value?.includes(s.id)) + .map(s => s.text); + + if (!selectedTexts || selectedTexts.length === 0) return; + + isRefining.value = true; + const originalTranslation = targetText.value; + targetText.value = ''; + // Keep evaluationResult and selectedSuggestionIds to allow the user to see what was changed + + // Determine which API config to use (same as audit) + let apiBaseUrl = settings.apiBaseUrl; + let apiKey = settings.apiKey; + let modelName = settings.modelName; + + if (settings.evaluationProfileId) { + const profile = settings.profiles.find(p => p.id === settings.evaluationProfileId); + if (profile) { + apiBaseUrl = profile.apiBaseUrl; + apiKey = profile.apiKey; + modelName = profile.modelName; + } + } + + const refinementSystemPrompt = settings.refinementPromptTemplate + .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'); + + const formattedSuggestions = selectedTexts.map((text, i) => `${i + 1}. ${text}`).join('\n'); + + const refinementUserPrompt = `[Source Text]\n${sourceText.value}\n\n[Current Translation]\n${originalTranslation}\n\n[User Feedback]\n${formattedSuggestions}`; + + const requestBody = { + model: modelName, + messages: [ + { role: "system", content: refinementSystemPrompt }, + { role: "user", content: refinementUserPrompt } + ], + stream: settings.enableStreaming + }; + + settings.addLog('request', { type: 'refinement', ...requestBody }); + + try { + const response = await invoke('translate', { + apiAddress: apiBaseUrl, + apiKey: apiKey, + payload: requestBody + }); + + if (!settings.enableStreaming) { + targetText.value = response; + } + settings.addLog('response', 'Refinement completed'); + } catch (err: any) { + const errorMsg = String(err); + settings.addLog('error', errorMsg); + targetText.value = `Error: ${errorMsg}`; + } finally { + isRefining.value = false; + } +}; + const translate = async () => { if (!sourceText.value.trim() || isTranslating.value) return; @@ -458,7 +548,7 @@ const translate = async () => {
-
+
-

建议优化

+

修改建议

-
-

- {{ evaluationResult.improvements }} -

+
+
+
+ +
+
+

{{ sug.text }}

+
+
+
+
+ {{ sug.importance }}% +
+
+
-
+
+
+
+
+ + +
+ +
+ {{ tag }} +
+
+ diff --git a/src/stores/settings.ts b/src/stores/settings.ts index c33e2e6..6e5bbee 100644 --- a/src/stores/settings.ts +++ b/src/stores/settings.ts @@ -77,16 +77,34 @@ Only penalize and provide improvements if the translation meets one of these cri - **75-89**: Accurate meaning, but suffers from "stiff" phrasing or minor flow issues that need adjustment. - **Below 75**: Contains semantic errors, severe grammar issues, or tone mismatches. 3. **Analysis**: Provide a concise explanation in Simplified Chinese. Focus on *why* the error matters (e.g., "meaning is reversed" or "too awkward to read"). -4. **Improvements**: Provide suggestions in Simplified Chinese. If the score is 95+, this field can be an empty string "". +4. **Suggestions**: Provide a list of specific, actionable suggestions. For each, assign an "importance" from 0 to 100 (0 = unnecessary/optional, 100 = critical error). # Output Format -Respond ONLY in JSON format. The "analysis" and "improvements" MUST be in Simplified Chinese: +Respond ONLY in JSON format. The "analysis" and "text" within "suggestions" MUST be in Simplified Chinese: { "score": number, "analysis": "string", - "improvements": "string" + "suggestions": [ + { "id": 1, "text": "suggestion text", "importance": number } + ] }`; +export const DEFAULT_REFINEMENT_TEMPLATE = `You are a senior translation editor. Your task is to refine the [Current Translation] based on specific [User Feedback], while strictly maintaining the original meaning of the [Source Text] and adhering to the established context. + +[Context Info] +- Source Language: {SOURCE_LANG} +- Target Language: {TARGET_LANG} +- Speaker Identity: {SPEAKER_IDENTITY} +- Intended Tone/Register: {TONE_REGISTER} +- Context: {CONTEXT} + +[Instructions] +1. Carefully review the [User Feedback] and apply the requested improvements to the [Current Translation]. +2. Ensure that the refined translation remains semantically identical to the [Source Text]. +3. Maintain the [Speaker Identity] and [Intended Tone/Register] as specified. +4. If a piece of feedback contradicts the [Source Text], prioritize accuracy and provide a balanced refinement. +5. Produce ONLY the refined {TARGET_LANG} translation, without any additional explanations, notes, or commentary.`; + export interface ApiProfile { id: string; name: string; @@ -107,6 +125,7 @@ export const useSettingsStore = defineStore('settings', () => { const enableEvaluation = useLocalStorage('enable-evaluation', true); const evaluationPromptTemplate = useLocalStorage('evaluation-prompt-template', DEFAULT_EVALUATION_TEMPLATE); const evaluationProfileId = useLocalStorage('evaluation-profile-id', null); + const refinementPromptTemplate = useLocalStorage('refinement-prompt-template', DEFAULT_REFINEMENT_TEMPLATE); // 存储整个对象以保持一致性 const sourceLang = useLocalStorage('source-lang-v2', LANGUAGES[0]); @@ -140,6 +159,7 @@ export const useSettingsStore = defineStore('settings', () => { enableEvaluation, evaluationPromptTemplate, evaluationProfileId, + refinementPromptTemplate, sourceLang, targetLang, speakerIdentity,