improve evaluation
This commit is contained in:
108
src/App.vue
108
src/App.vue
@@ -90,20 +90,23 @@ const sourceDropdownOpen = ref(false);
|
|||||||
const targetDropdownOpen = ref(false);
|
const targetDropdownOpen = ref(false);
|
||||||
const speakerDropdownOpen = ref(false);
|
const speakerDropdownOpen = ref(false);
|
||||||
const toneDropdownOpen = ref(false);
|
const toneDropdownOpen = ref(false);
|
||||||
|
const evaluationProfileDropdownOpen = ref(false);
|
||||||
|
|
||||||
const closeAllDropdowns = () => {
|
const closeAllDropdowns = () => {
|
||||||
sourceDropdownOpen.value = false;
|
sourceDropdownOpen.value = false;
|
||||||
targetDropdownOpen.value = false;
|
targetDropdownOpen.value = false;
|
||||||
speakerDropdownOpen.value = false;
|
speakerDropdownOpen.value = false;
|
||||||
toneDropdownOpen.value = false;
|
toneDropdownOpen.value = false;
|
||||||
|
evaluationProfileDropdownOpen.value = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
const toggleDropdown = (type: 'source' | 'target' | 'speaker' | 'tone') => {
|
const toggleDropdown = (type: 'source' | 'target' | 'speaker' | 'tone' | 'evaluationProfile') => {
|
||||||
const states = {
|
const states = {
|
||||||
source: sourceDropdownOpen,
|
source: sourceDropdownOpen,
|
||||||
target: targetDropdownOpen,
|
target: targetDropdownOpen,
|
||||||
speaker: speakerDropdownOpen,
|
speaker: speakerDropdownOpen,
|
||||||
tone: toneDropdownOpen
|
tone: toneDropdownOpen,
|
||||||
|
evaluationProfile: evaluationProfileDropdownOpen
|
||||||
};
|
};
|
||||||
|
|
||||||
const targetState = states[type];
|
const targetState = states[type];
|
||||||
@@ -186,6 +189,12 @@ const currentToneLabel = computed(() => {
|
|||||||
return TONE_REGISTER_OPTIONS.find(opt => opt.value === settings.toneRegister)?.label || '正式专业';
|
return TONE_REGISTER_OPTIONS.find(opt => opt.value === settings.toneRegister)?.label || '正式专业';
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const currentEvaluationProfileLabel = computed(() => {
|
||||||
|
if (!settings.evaluationProfileId) return '使用主翻译配置(默认)';
|
||||||
|
const profile = settings.profiles.find(p => p.id === settings.evaluationProfileId);
|
||||||
|
return profile ? `${profile.name} — ${profile.modelName}` : '使用主翻译配置(默认)';
|
||||||
|
});
|
||||||
|
|
||||||
const swapLanguages = () => {
|
const swapLanguages = () => {
|
||||||
const temp = { ...settings.sourceLang };
|
const temp = { ...settings.sourceLang };
|
||||||
settings.sourceLang = { ...settings.targetLang };
|
settings.sourceLang = { ...settings.targetLang };
|
||||||
@@ -216,6 +225,20 @@ const evaluateTranslation = async () => {
|
|||||||
isEvaluating.value = true;
|
isEvaluating.value = true;
|
||||||
evaluationResult.value = null;
|
evaluationResult.value = null;
|
||||||
|
|
||||||
|
// Determine which API config to use for evaluation
|
||||||
|
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 evaluationPrompt = settings.evaluationPromptTemplate
|
const evaluationPrompt = settings.evaluationPromptTemplate
|
||||||
.replace(/{SOURCE_LANG}/g, sourceLang.value.englishName)
|
.replace(/{SOURCE_LANG}/g, sourceLang.value.englishName)
|
||||||
.replace(/{TARGET_LANG}/g, targetLang.value.englishName)
|
.replace(/{TARGET_LANG}/g, targetLang.value.englishName)
|
||||||
@@ -226,7 +249,7 @@ const evaluateTranslation = async () => {
|
|||||||
.replace(/{TRANSLATED_TEXT}/g, targetText.value);
|
.replace(/{TRANSLATED_TEXT}/g, targetText.value);
|
||||||
|
|
||||||
const requestBody = {
|
const requestBody = {
|
||||||
model: settings.modelName,
|
model: modelName,
|
||||||
messages: [
|
messages: [
|
||||||
{ role: "system", content: "You are a professional translation auditor. You must respond in valid JSON format." },
|
{ role: "system", content: "You are a professional translation auditor. You must respond in valid JSON format." },
|
||||||
{ role: "user", content: evaluationPrompt }
|
{ role: "user", content: evaluationPrompt }
|
||||||
@@ -238,8 +261,8 @@ const evaluateTranslation = async () => {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await invoke<string>('translate', {
|
const response = await invoke<string>('translate', {
|
||||||
apiAddress: settings.apiBaseUrl,
|
apiAddress: apiBaseUrl,
|
||||||
apiKey: settings.apiKey,
|
apiKey: apiKey,
|
||||||
payload: requestBody
|
payload: requestBody
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -616,10 +639,10 @@ const translate = async () => {
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="evaluationResult.improvements" class="flex gap-2 p-3 bg-blue-50/50 dark:bg-blue-900/10 rounded-lg border border-blue-100/50 dark:border-blue-900/20">
|
<div v-if="evaluationResult.improvements && evaluationResult.score < 90" class="flex gap-2 p-3 bg-blue-50/50 dark:bg-blue-900/10 rounded-lg border border-blue-100/50 dark:border-blue-900/20">
|
||||||
<Check class="w-4 h-4 text-blue-500 shrink-0 mt-0.5" />
|
<Check class="w-4 h-4 text-blue-500 shrink-0 mt-0.5" />
|
||||||
<div class="space-y-1">
|
<div class="space-y-1">
|
||||||
<span class="text-[10px] font-bold text-blue-600/70 dark:text-blue-400/70 uppercase">建议优化</span>
|
<span class="text-[11px] font-bold text-blue-600/70 dark:text-blue-400/70 uppercase">建议优化</span>
|
||||||
<p class="text-xs text-slate-600 dark:text-slate-300 leading-relaxed">
|
<p class="text-xs text-slate-600 dark:text-slate-300 leading-relaxed">
|
||||||
{{ evaluationResult.improvements }}
|
{{ evaluationResult.improvements }}
|
||||||
</p>
|
</p>
|
||||||
@@ -773,6 +796,69 @@ const translate = async () => {
|
|||||||
)"></div>
|
)"></div>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div v-if="settings.enableEvaluation" class="space-y-3 pt-4 border-t border-dashed dark:border-slate-800 animate-in fade-in slide-in-from-top-2 duration-300">
|
||||||
|
<div class="flex items-center gap-2 mb-1">
|
||||||
|
<Settings class="w-3.5 h-3.5 text-blue-500" />
|
||||||
|
<label class="text-sm font-semibold text-slate-700 dark:text-slate-300">审计模型</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Custom Evaluation Profile Dropdown -->
|
||||||
|
<div class="relative lang-dropdown">
|
||||||
|
<button
|
||||||
|
@click.stop="toggleDropdown('evaluationProfile')"
|
||||||
|
class="flex items-center justify-between w-full px-4 py-2.5 border dark:border-slate-700 rounded-xl bg-slate-50/50 dark:bg-slate-800/30 hover:bg-slate-100/50 dark:hover:bg-slate-800/50 transition-all text-sm text-slate-700 dark:text-slate-200 group"
|
||||||
|
>
|
||||||
|
<span class="truncate">{{ currentEvaluationProfileLabel }}</span>
|
||||||
|
<ChevronDown :class="cn('w-4 h-4 text-slate-400 transition-transform duration-200 group-hover:text-blue-500', evaluationProfileDropdownOpen && 'rotate-180')" />
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<transition
|
||||||
|
enter-active-class="transition duration-100 ease-out"
|
||||||
|
enter-from-class="transform scale-95 opacity-0"
|
||||||
|
enter-to-class="transform scale-100 opacity-100"
|
||||||
|
leave-active-class="transition duration-75 ease-in"
|
||||||
|
leave-from-class="transform scale-100 opacity-100"
|
||||||
|
leave-to-class="transform scale-95 opacity-0"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
v-if="evaluationProfileDropdownOpen"
|
||||||
|
class="absolute left-0 mt-2 w-full max-h-60 overflow-y-auto bg-white dark:bg-slate-800 rounded-xl shadow-xl border border-slate-200 dark:border-slate-700 z-50 py-2 flex flex-col custom-scrollbar"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
@click="settings.evaluationProfileId = null; evaluationProfileDropdownOpen = false"
|
||||||
|
:class="cn(
|
||||||
|
'px-4 py-2.5 text-sm text-left transition-colors flex items-center justify-between',
|
||||||
|
settings.evaluationProfileId === null ? 'bg-blue-50 text-blue-600 dark:bg-blue-900/30 dark:text-blue-400 font-bold' : 'text-slate-500 hover:bg-slate-50 dark:hover:bg-slate-700/50'
|
||||||
|
)"
|
||||||
|
>
|
||||||
|
使用主翻译配置(默认)
|
||||||
|
<Check v-if="settings.evaluationProfileId === null" class="w-3.5 h-3.5" />
|
||||||
|
</button>
|
||||||
|
<div class="h-px bg-slate-100 dark:bg-slate-700 my-1 mx-2"></div>
|
||||||
|
<button
|
||||||
|
v-for="profile in settings.profiles"
|
||||||
|
:key="profile.id"
|
||||||
|
@click="settings.evaluationProfileId = profile.id; evaluationProfileDropdownOpen = false"
|
||||||
|
:class="cn(
|
||||||
|
'px-4 py-2.5 text-sm text-left transition-colors flex items-center justify-between',
|
||||||
|
settings.evaluationProfileId === profile.id ? 'bg-blue-50 text-blue-600 dark:bg-blue-900/30 dark:text-blue-400 font-bold' : 'text-slate-600 dark:text-slate-300 hover:bg-slate-50 dark:hover:bg-slate-700/50'
|
||||||
|
)"
|
||||||
|
>
|
||||||
|
<div class="flex flex-col min-w-0">
|
||||||
|
<span class="truncate">{{ profile.name }}</span>
|
||||||
|
<span class="text-[10px] opacity-60 font-mono">{{ profile.modelName }}</span>
|
||||||
|
</div>
|
||||||
|
<Check v-if="settings.evaluationProfileId === profile.id" class="w-3.5 h-3.5 shrink-0" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</transition>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p class="text-[11px] text-slate-500 dark:text-slate-500 pl-1">
|
||||||
|
提示:建议为审计选择更强大的模型以获得更精准的反馈
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
@@ -789,6 +875,9 @@ const translate = async () => {
|
|||||||
rows="6"
|
rows="6"
|
||||||
class="w-full px-4 py-3 border dark:border-slate-700 rounded-lg bg-transparent focus:ring-2 focus:ring-blue-500/20 focus:border-blue-500 outline-none transition-all font-mono text-xs leading-relaxed text-slate-900 dark:text-slate-100"
|
class="w-full px-4 py-3 border dark:border-slate-700 rounded-lg bg-transparent focus:ring-2 focus:ring-blue-500/20 focus:border-blue-500 outline-none transition-all font-mono text-xs leading-relaxed text-slate-900 dark:text-slate-100"
|
||||||
></textarea>
|
></textarea>
|
||||||
|
<div class="flex flex-wrap gap-2 mt-2">
|
||||||
|
<span v-for="tag in ['{SOURCE_CODE}', '{TARGET_CODE}', '{SOURCE_LANG}', '{TARGET_LANG}', '{SPEAKER_IDENTITY}', '{TONE_REGISTER}']" :key="tag" class="px-2 py-1 bg-slate-100 dark:bg-slate-800 text-[10px] font-mono rounded border dark:border-slate-700 text-slate-600 dark:text-slate-400">{{ tag }}</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="settings.enableEvaluation" class="space-y-2 border-t dark:border-slate-800 pt-6">
|
<div v-if="settings.enableEvaluation" class="space-y-2 border-t dark:border-slate-800 pt-6">
|
||||||
@@ -802,13 +891,10 @@ const translate = async () => {
|
|||||||
class="w-full px-4 py-3 border dark:border-slate-700 rounded-lg bg-transparent focus:ring-2 focus:ring-blue-500/20 focus:border-blue-500 outline-none transition-all font-mono text-xs leading-relaxed text-slate-900 dark:text-slate-100"
|
class="w-full px-4 py-3 border dark:border-slate-700 rounded-lg bg-transparent focus:ring-2 focus:ring-blue-500/20 focus:border-blue-500 outline-none transition-all font-mono text-xs leading-relaxed text-slate-900 dark:text-slate-100"
|
||||||
></textarea>
|
></textarea>
|
||||||
<div class="flex flex-wrap gap-2 mt-2">
|
<div class="flex flex-wrap gap-2 mt-2">
|
||||||
<span v-for="tag in ['{SOURCE_TEXT}', '{TRANSLATED_TEXT}', '{CONTEXT}']" :key="tag" class="px-2 py-1 bg-slate-100 dark:bg-slate-800 text-[10px] font-mono rounded border dark:border-slate-700 text-slate-600 dark:text-slate-400">{{ tag }}</span>
|
<span v-for="tag in ['{SOURCE_LANG}', '{TARGET_LANG}', '{SPEAKER_IDENTITY}', '{TONE_REGISTER}', '{CONTEXT}', '{SOURCE_TEXT}', '{TRANSLATED_TEXT}']" :key="tag" class="px-2 py-1 bg-slate-100 dark:bg-slate-800 text-[10px] font-mono rounded border dark:border-slate-700 text-slate-600 dark:text-slate-400">{{ tag }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex flex-wrap gap-2 mt-2">
|
|
||||||
<span v-for="tag in ['{SOURCE_LANG}', '{TARGET_LANG}', '{SPEAKER_IDENTITY}', '{TONE_REGISTER}']" :key="tag" class="px-2 py-1 bg-slate-100 dark:bg-slate-800 text-[10px] font-mono rounded border dark:border-slate-700 text-slate-600 dark:text-slate-400">{{ tag }}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ Your task is to critically evaluate the accuracy and quality of a translation.
|
|||||||
|
|
||||||
[Instructions]
|
[Instructions]
|
||||||
1. Compare the [Source Text] and [Translated Text] meticulously.
|
1. Compare the [Source Text] and [Translated Text] meticulously.
|
||||||
2. Check if the translation respects the [Context Info] and [Speaker Identity].
|
2. Check if the translation respects the [Context Info].
|
||||||
3. Assign an "Accuracy Score" from 0 to 100.
|
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.
|
- 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.
|
- Deduct points for minor inaccuracies, unnatural phrasing, or tone mismatches.
|
||||||
@@ -101,6 +101,7 @@ export const useSettingsStore = defineStore('settings', () => {
|
|||||||
|
|
||||||
const enableEvaluation = useLocalStorage('enable-evaluation', true);
|
const enableEvaluation = useLocalStorage('enable-evaluation', true);
|
||||||
const evaluationPromptTemplate = useLocalStorage('evaluation-prompt-template', DEFAULT_EVALUATION_TEMPLATE);
|
const evaluationPromptTemplate = useLocalStorage('evaluation-prompt-template', DEFAULT_EVALUATION_TEMPLATE);
|
||||||
|
const evaluationProfileId = useLocalStorage<string | null>('evaluation-profile-id', null);
|
||||||
|
|
||||||
// 存储整个对象以保持一致性
|
// 存储整个对象以保持一致性
|
||||||
const sourceLang = useLocalStorage<Language>('source-lang-v2', LANGUAGES[0]);
|
const sourceLang = useLocalStorage<Language>('source-lang-v2', LANGUAGES[0]);
|
||||||
@@ -133,6 +134,7 @@ export const useSettingsStore = defineStore('settings', () => {
|
|||||||
systemPromptTemplate,
|
systemPromptTemplate,
|
||||||
enableEvaluation,
|
enableEvaluation,
|
||||||
evaluationPromptTemplate,
|
evaluationPromptTemplate,
|
||||||
|
evaluationProfileId,
|
||||||
sourceLang,
|
sourceLang,
|
||||||
targetLang,
|
targetLang,
|
||||||
speakerIdentity,
|
speakerIdentity,
|
||||||
|
|||||||
Reference in New Issue
Block a user