This commit is contained in:
2026-04-20 13:08:51 -04:00
parent f50a176252
commit 17b91d6ac8
3 changed files with 170 additions and 70 deletions

View File

@@ -18,6 +18,12 @@ import { useLogsStore } from '../stores/logs';
import { cn } from '../lib/utils';
import { listen } from '@tauri-apps/api/event';
import { useClipboard } from '../composables/useClipboard';
import {
buildConversationEvaluationUserPrompt,
buildConversationRefinementUserPrompt,
buildConversationSystemPrompt,
buildConversationTranslationUserPrompt,
} from '../lib/prompt-builders';
import {
executeTranslationRequest,
extractAssistantContent,
@@ -196,24 +202,21 @@ const translateMessage = async (sender: 'me' | 'partner', retranslateId?: string
: 'Auto-detect';
const senderName = sender === 'me' ? activeSession.value.me.name : activeSession.value.partner.name;
const systemPrompt = settings.chatSystemPromptTemplate
.replace(/{ME_NAME}/g, activeSession.value.me.name)
.replace(/{ME_GENDER}/g, activeSession.value.me.gender)
.replace(/{ME_LANG}/g, activeSession.value.me.language.englishName)
.replace(/{PART_NAME}/g, activeSession.value.partner.name)
.replace(/{PART_GENDER}/g, activeSession.value.partner.gender)
.replace(/{PART_LANG}/g, activeSession.value.partner.language.englishName)
.replace(/{HISTORY_BLOCK}/g, historyBlock || 'None (This is the start of conversation)')
.replace(/{SENDER_NAME}/g, senderName)
.replace(/{FROM_LANG}/g, fromLang.englishName)
.replace(/{TO_LANG}/g, toLang.englishName)
.replace(/{TARGET_TONE}/g, targetTone);
const systemPrompt = buildConversationSystemPrompt(settings.chatSystemPromptTemplate, {
me: activeSession.value.me,
partner: activeSession.value.partner,
historyBlock,
senderName,
fromLang,
toLang,
targetTone,
}, 'None (This is the start of conversation)');
const requestBody: TranslationPayload = {
model: settings.modelName,
messages: [
{ role: "system", content: systemPrompt },
{ role: "user", content: `[Text to Translate]\n${text}` }
{ role: "user", content: buildConversationTranslationUserPrompt(text) }
],
stream: settings.enableStreaming
};
@@ -285,22 +288,17 @@ const evaluateMessage = async (messageId: string, force = false) => {
? (TONE_REGISTER_OPTIONS.find(o => o.value === activeSession.value!.me.tone)?.value || 'Polite & Conversational')
: 'Auto-detect';
const systemPrompt = settings.chatEvaluationPromptTemplate
.replace(/{ME_NAME}/g, activeSession.value.me.name)
.replace(/{ME_GENDER}/g, activeSession.value.me.gender)
.replace(/{ME_LANG}/g, activeSession.value.me.language.englishName)
.replace(/{PART_NAME}/g, activeSession.value.partner.name)
.replace(/{PART_GENDER}/g, activeSession.value.partner.gender)
.replace(/{PART_LANG}/g, activeSession.value.partner.language.englishName)
.replace(/{HISTORY_BLOCK}/g, historyBlock || 'None')
.replace(/{TARGET_TONE}/g, targetTone)
.replace(/{SENDER_NAME}/g, senderName)
.replace(/{FROM_LANG}/g, fromLang.englishName)
.replace(/{TO_LANG}/g, toLang.englishName);
// .replace(/{ORIGINAL_TEXT}/g, msg.original)
// .replace(/{CURRENT_TRANSLATION}/g, msg.translated);
const systemPrompt = buildConversationSystemPrompt(settings.chatEvaluationPromptTemplate, {
me: activeSession.value.me,
partner: activeSession.value.partner,
historyBlock,
senderName,
fromLang,
toLang,
targetTone,
}, 'None');
const userPrompt = `[Source Text]\n${msg.original}\n\n[Current Translation]\n${msg.translated}`;
const userPrompt = buildConversationEvaluationUserPrompt(msg.original, msg.translated);
const modelConfig = resolveModelConfig({
apiBaseUrl: settings.apiBaseUrl,
@@ -350,7 +348,6 @@ const refineMessage = async (messageId: string) => {
const selectedSuggestions = evalData.suggestions.filter((s: any) => selectedSuggestionIds.value.includes(s.id));
if (selectedSuggestions.length === 0) return;
const suggestionsText = selectedSuggestions.map((s: any) => `- ${s.text}`).join('\n');
const currentTranslation = msg.translated;
isAuditModalOpen.value = false; // 关闭弹窗开始润色
@@ -375,21 +372,15 @@ const refineMessage = async (messageId: string) => {
const toLang = msg.sender === 'me' ? activeSession.value.partner.language : activeSession.value.me.language;
const senderName = msg.sender === 'me' ? activeSession.value.me.name : activeSession.value.partner.name;
const systemPrompt = settings.chatRefinementPromptTemplate
.replace(/{ME_NAME}/g, activeSession.value.me.name)
.replace(/{ME_GENDER}/g, activeSession.value.me.gender)
.replace(/{ME_LANG}/g, activeSession.value.me.language.englishName)
.replace(/{PART_NAME}/g, activeSession.value.partner.name)
.replace(/{PART_GENDER}/g, activeSession.value.partner.gender)
.replace(/{PART_LANG}/g, activeSession.value.partner.language.englishName)
.replace(/{HISTORY_BLOCK}/g, historyBlock || 'None')
// .replace(/{ORIGINAL_TEXT}/g, msg.original)
// .replace(/{CURRENT_TRANSLATION}/g, msg.translated)
// .replace(/{SUGGESTIONS}/g, suggestionsText)
.replace(/{TARGET_TONE}/g, targetTone)
.replace(/{SENDER_NAME}/g, senderName)
.replace(/{FROM_LANG}/g, fromLang.englishName)
.replace(/{TO_LANG}/g, toLang.englishName);
const systemPrompt = buildConversationSystemPrompt(settings.chatRefinementPromptTemplate, {
me: activeSession.value.me,
partner: activeSession.value.partner,
historyBlock,
senderName,
fromLang,
toLang,
targetTone,
}, 'None');
const modelConfig = resolveModelConfig({
apiBaseUrl: settings.apiBaseUrl,
@@ -401,7 +392,7 @@ const refineMessage = async (messageId: string) => {
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}` }
{ role: "user", content: buildConversationRefinementUserPrompt(msg.original, currentTranslation, selectedSuggestions.map((s: any) => s.text)) }
],
stream: settings.enableStreaming
};

View File

@@ -9,6 +9,14 @@ import { useLogsStore } from '../stores/logs';
import { useTranslationWorkspaceStore } from '../stores/translation-workspace';
import { cn } from '../lib/utils';
import { useClipboard } from '../composables/useClipboard';
import {
buildSingleEvaluationSystemPrompt,
buildSingleEvaluationUserPrompt,
buildSingleRefinementSystemPrompt,
buildSingleRefinementUserPrompt,
buildSingleTranslationSystemPrompt,
buildSingleTranslationUserPrompt,
} from '../lib/prompt-builders';
import {
executeTranslationRequest,
extractAssistantContent,
@@ -123,14 +131,15 @@ const evaluateTranslation = async () => {
modelName: settings.modelName,
}, settings.profiles, settings.evaluationProfileId);
const evaluationSystemPrompt = 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');
const evaluationSystemPrompt = buildSingleEvaluationSystemPrompt(settings.evaluationPromptTemplate, {
sourceLang: sourceLang.value,
targetLang: targetLang.value,
speakerIdentity: settings.speakerIdentity,
toneRegister: settings.toneRegister,
context: context.value,
});
const evaluationUserPrompt = `[Source Text]\n${sourceText.value}\n\n[Translated Text]\n${targetText.value}`;
const evaluationUserPrompt = buildSingleEvaluationUserPrompt(sourceText.value, targetText.value);
const requestBody: TranslationPayload = {
model: modelConfig.modelName,
@@ -173,15 +182,15 @@ const refineTranslation = async () => {
modelName: settings.modelName,
}, settings.profiles, settings.evaluationProfileId);
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 refinementSystemPrompt = buildSingleRefinementSystemPrompt(settings.refinementPromptTemplate, {
sourceLang: sourceLang.value,
targetLang: targetLang.value,
speakerIdentity: settings.speakerIdentity,
toneRegister: settings.toneRegister,
context: context.value,
});
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 refinementUserPrompt = buildSingleRefinementUserPrompt(sourceText.value, originalTranslation, selectedTexts);
const requestBody: TranslationPayload = {
model: modelConfig.modelName,
@@ -229,17 +238,14 @@ const translate = async () => {
targetText.value = '';
evaluationResult.value = null;
const systemMessage = settings.systemPromptTemplate
.replace(/{SOURCE_LANG}/g, sourceLang.value.englishName)
.replace(/{SOURCE_CODE}/g, sourceLang.value.code)
.replace(/{TARGET_LANG}/g, targetLang.value.englishName)
.replace(/{TARGET_CODE}/g, targetLang.value.code)
.replace(/{SPEAKER_IDENTITY}/g, settings.speakerIdentity)
.replace(/{TONE_REGISTER}/g, settings.toneRegister);
const systemMessage = buildSingleTranslationSystemPrompt(settings.systemPromptTemplate, {
sourceLang: sourceLang.value,
targetLang: targetLang.value,
speakerIdentity: settings.speakerIdentity,
toneRegister: settings.toneRegister,
});
const userMessage = context.value
? `[Context]\n${context.value}\n\n[Text to Translate]\n${sourceText.value}`
: `[Text to Translate]\n${sourceText.value}`;
const userMessage = buildSingleTranslationUserPrompt(sourceText.value, context.value);
const requestBody: TranslationPayload = {
model: settings.modelName,

103
src/lib/prompt-builders.ts Normal file
View File

@@ -0,0 +1,103 @@
import type { Language, Participant } from '../stores/settings';
interface SingleTranslationPromptContext {
sourceLang: Language;
targetLang: Language;
speakerIdentity: string;
toneRegister: string;
context: string;
}
interface ConversationPromptContext {
me: Participant;
partner: Participant;
historyBlock: string;
senderName: string;
fromLang: Language;
toLang: Language;
targetTone: string;
}
function replaceTokens(template: string, values: Record<string, string>) {
return Object.entries(values).reduce(
(result, [key, value]) => result.replace(new RegExp(`\\{${key}\\}`, 'g'), value),
template,
);
}
export function buildSingleTranslationSystemPrompt(
template: string,
context: Omit<SingleTranslationPromptContext, 'context'>,
) {
return replaceTokens(template, {
SOURCE_LANG: context.sourceLang.englishName,
SOURCE_CODE: context.sourceLang.code,
TARGET_LANG: context.targetLang.englishName,
TARGET_CODE: context.targetLang.code,
SPEAKER_IDENTITY: context.speakerIdentity,
TONE_REGISTER: context.toneRegister,
});
}
export function buildSingleEvaluationSystemPrompt(
template: string,
context: SingleTranslationPromptContext,
) {
return replaceTokens(template, {
SOURCE_LANG: context.sourceLang.englishName,
TARGET_LANG: context.targetLang.englishName,
SPEAKER_IDENTITY: context.speakerIdentity,
TONE_REGISTER: context.toneRegister,
CONTEXT: context.context || 'None',
});
}
export function buildSingleRefinementSystemPrompt(
template: string,
context: SingleTranslationPromptContext,
) {
return buildSingleEvaluationSystemPrompt(template, context);
}
export function buildSingleTranslationUserPrompt(sourceText: string, context: string) {
return context
? `[Context]\n${context}\n\n[Text to Translate]\n${sourceText}`
: `[Text to Translate]\n${sourceText}`;
}
export function buildSingleEvaluationUserPrompt(sourceText: string, targetText: string) {
return `[Source Text]\n${sourceText}\n\n[Translated Text]\n${targetText}`;
}
export function buildSingleRefinementUserPrompt(sourceText: string, currentTranslation: string, suggestions: string[]) {
const formattedSuggestions = suggestions.map((text, index) => `${index + 1}. ${text}`).join('\n');
return `[Source Text]\n${sourceText}\n\n[Current Translation]\n${currentTranslation}\n\n[User Feedback]\n${formattedSuggestions}`;
}
export function buildConversationSystemPrompt(template: string, context: ConversationPromptContext, emptyHistoryFallback: string) {
return replaceTokens(template, {
ME_NAME: context.me.name,
ME_GENDER: context.me.gender,
ME_LANG: context.me.language.englishName,
PART_NAME: context.partner.name,
PART_GENDER: context.partner.gender,
PART_LANG: context.partner.language.englishName,
HISTORY_BLOCK: context.historyBlock || emptyHistoryFallback,
SENDER_NAME: context.senderName,
FROM_LANG: context.fromLang.englishName,
TO_LANG: context.toLang.englishName,
TARGET_TONE: context.targetTone,
});
}
export function buildConversationTranslationUserPrompt(text: string) {
return `[Text to Translate]\n${text}`;
}
export function buildConversationEvaluationUserPrompt(sourceText: string, currentTranslation: string) {
return `[Source Text]\n${sourceText}\n\n[Current Translation]\n${currentTranslation}`;
}
export function buildConversationRefinementUserPrompt(sourceText: string, currentTranslation: string, suggestions: string[]) {
return `[Source Text]\n${sourceText}\n\n[Current Translation]\n${currentTranslation}\n\n[Suggestions]\n${suggestions.map((text) => `- ${text}`).join('\n')}`;
}