rebuild settings ui
This commit is contained in:
564
src/App.vue
564
src/App.vue
@@ -57,6 +57,7 @@ const toggleTheme = () => {
|
||||
};
|
||||
|
||||
const view = ref<'translate' | 'settings' | 'logs' | 'history'>('translate');
|
||||
const settingsCategory = ref<'api' | 'general' | 'prompts'>('api');
|
||||
|
||||
// History Management
|
||||
const searchQuery = ref('');
|
||||
@@ -1058,267 +1059,370 @@ const translate = async () => {
|
||||
</div>
|
||||
|
||||
<!-- Settings View -->
|
||||
<div v-else-if="view === 'settings'" class="flex-1 overflow-y-auto bg-slate-100/50 dark:bg-slate-950 p-6 md:p-10 min-h-0">
|
||||
<div class="max-w-2xl mx-auto space-y-8">
|
||||
<section>
|
||||
<h2 class="text-sm font-semibold text-slate-500 dark:text-slate-400 uppercase tracking-wider mb-4">API 配置预设</h2>
|
||||
<div class="bg-white/60 dark:bg-slate-900 rounded-xl shadow-sm border dark:border-slate-800 p-1.5 space-y-1">
|
||||
<div v-if="settings.profiles.length === 0" class="p-8 text-center text-sm text-slate-400 dark:text-slate-600 italic">
|
||||
暂无预设配置
|
||||
<div v-else-if="view === 'settings'" class="flex-1 flex overflow-hidden bg-slate-100/50 dark:bg-slate-950">
|
||||
<!-- Settings Sidebar (Master) -->
|
||||
<div class="w-64 border-r dark:border-slate-800 flex flex-col bg-white/60 dark:bg-slate-900/40">
|
||||
<div class="p-4 border-b dark:border-slate-800 shrink-0">
|
||||
<h2 class="text-sm font-semibold text-slate-500 dark:text-slate-400 uppercase tracking-wider">系统设置</h2>
|
||||
</div>
|
||||
<nav class="flex-1 overflow-y-auto p-3 space-y-1">
|
||||
<button
|
||||
@click="settingsCategory = 'api'"
|
||||
:class="cn(
|
||||
'w-full flex items-center gap-3 px-3 py-2.5 rounded-lg text-sm font-medium transition-colors',
|
||||
settingsCategory === 'api' ? 'bg-blue-50 text-blue-600 dark:bg-blue-900/30 dark:text-blue-400' : 'text-slate-600 hover:bg-slate-100 dark:text-slate-300 dark:hover:bg-slate-800/50'
|
||||
)"
|
||||
>
|
||||
<div :class="cn('p-1.5 rounded-md', settingsCategory === 'api' ? 'bg-blue-100 dark:bg-blue-900/50' : 'bg-slate-100 dark:bg-slate-800')">
|
||||
<Play class="w-4 h-4" />
|
||||
</div>
|
||||
<div
|
||||
v-for="profile in settings.profiles"
|
||||
:key="profile.id"
|
||||
class="p-3 flex items-center justify-between group hover:bg-slate-100/50 dark:hover:bg-slate-800/50 transition-colors rounded-lg"
|
||||
>
|
||||
<div class="flex flex-col gap-0.5 min-w-0">
|
||||
<span class="text-sm font-semibold text-slate-700 dark:text-slate-200 truncate">{{ profile.name }}</span>
|
||||
<div class="flex items-center gap-2 text-[10px] text-slate-400 dark:text-slate-500 font-mono">
|
||||
<span class="truncate max-w-30">{{ profile.modelName }}</span>
|
||||
<span>•</span>
|
||||
<span class="truncate">{{ profile.apiBaseUrl }}</span>
|
||||
API 与模型
|
||||
</button>
|
||||
<button
|
||||
@click="settingsCategory = 'general'"
|
||||
:class="cn(
|
||||
'w-full flex items-center gap-3 px-3 py-2.5 rounded-lg text-sm font-medium transition-colors',
|
||||
settingsCategory === 'general' ? 'bg-blue-50 text-blue-600 dark:bg-blue-900/30 dark:text-blue-400' : 'text-slate-600 hover:bg-slate-100 dark:text-slate-300 dark:hover:bg-slate-800/50'
|
||||
)"
|
||||
>
|
||||
<div :class="cn('p-1.5 rounded-md', settingsCategory === 'general' ? 'bg-blue-100 dark:bg-blue-900/50' : 'bg-slate-100 dark:bg-slate-800')">
|
||||
<Settings class="w-4 h-4" />
|
||||
</div>
|
||||
常规设置
|
||||
</button>
|
||||
<button
|
||||
@click="settingsCategory = 'prompts'"
|
||||
:class="cn(
|
||||
'w-full flex items-center gap-3 px-3 py-2.5 rounded-lg text-sm font-medium transition-colors',
|
||||
settingsCategory === 'prompts' ? 'bg-blue-50 text-blue-600 dark:bg-blue-900/30 dark:text-blue-400' : 'text-slate-600 hover:bg-slate-100 dark:text-slate-300 dark:hover:bg-slate-800/50'
|
||||
)"
|
||||
>
|
||||
<div :class="cn('p-1.5 rounded-md', settingsCategory === 'prompts' ? 'bg-blue-100 dark:bg-blue-900/50' : 'bg-slate-100 dark:bg-slate-800')">
|
||||
<Type class="w-4 h-4" />
|
||||
</div>
|
||||
提示词工程
|
||||
</button>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<!-- Settings Content (Detail) -->
|
||||
<div class="flex-1 overflow-y-auto p-6 md:p-10 custom-scrollbar bg-slate-50/30 dark:bg-transparent">
|
||||
<div class="max-w-3xl mx-auto space-y-8 pb-20">
|
||||
|
||||
<!-- API & Models -->
|
||||
<template v-if="settingsCategory === 'api'">
|
||||
<div class="mb-6 border-b dark:border-slate-800 pb-4">
|
||||
<h1 class="text-2xl font-bold text-slate-800 dark:text-slate-100">API 与模型</h1>
|
||||
<p class="text-sm text-slate-500 dark:text-slate-400 mt-1">配置与大语言模型通信的接口地址和凭证。</p>
|
||||
</div>
|
||||
|
||||
<section>
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<h2 class="text-sm font-semibold text-slate-500 dark:text-slate-400 uppercase tracking-wider">当前接口配置</h2>
|
||||
<div v-if="!isSavingProfile" class="flex items-center gap-2">
|
||||
<button
|
||||
@click="isSavingProfile = true"
|
||||
class="text-xs flex items-center gap-1.5 text-blue-600 dark:text-blue-400 hover:underline px-2 py-1 rounded"
|
||||
>
|
||||
<Save class="w-3.5 h-3.5" />
|
||||
保存为预设
|
||||
</button>
|
||||
</div>
|
||||
<div v-else class="flex items-center gap-2 bg-white dark:bg-slate-800 p-1 rounded-lg border dark:border-slate-700 shadow-sm animate-in fade-in zoom-in duration-200">
|
||||
<input
|
||||
v-model="newProfileName"
|
||||
type="text"
|
||||
placeholder="输入预设名称..."
|
||||
class="text-xs px-2 py-1 bg-transparent outline-none w-32 dark:text-slate-200"
|
||||
@keyup.enter="saveCurrentAsProfile"
|
||||
/>
|
||||
<button
|
||||
@click="saveCurrentAsProfile"
|
||||
:disabled="!newProfileName.trim()"
|
||||
class="p-1 text-green-600 hover:bg-green-50 dark:hover:bg-green-900/30 rounded disabled:opacity-50"
|
||||
>
|
||||
<Check class="w-3.5 h-3.5" />
|
||||
</button>
|
||||
<button
|
||||
@click="isSavingProfile = false; newProfileName = ''"
|
||||
class="p-1 text-slate-400 hover:bg-slate-100 dark:hover:bg-slate-700 rounded"
|
||||
>
|
||||
<Plus class="w-3.5 h-3.5 rotate-45" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center gap-1 opacity-0 group-hover:opacity-100 transition-opacity">
|
||||
<button
|
||||
@click="applyProfile(profile)"
|
||||
class="p-2 text-blue-600 hover:bg-blue-50 dark:hover:bg-blue-900/30 rounded-lg transition-colors"
|
||||
title="应用此配置"
|
||||
>
|
||||
<Play class="w-4 h-4 fill-current" />
|
||||
</button>
|
||||
<button
|
||||
@click="deleteProfile(profile.id)"
|
||||
class="p-2 text-red-600 hover:bg-red-50 dark:hover:bg-red-900/30 rounded-lg transition-colors"
|
||||
title="删除"
|
||||
>
|
||||
<Trash2 class="w-4 h-4" />
|
||||
</button>
|
||||
<div class="bg-white/80 dark:bg-slate-900 rounded-2xl shadow-sm border dark:border-slate-800 p-6 space-y-4">
|
||||
<div class="space-y-2">
|
||||
<label class="text-sm font-medium text-slate-700 dark:text-slate-300">API Base URL</label>
|
||||
<input
|
||||
v-model="settings.apiBaseUrl"
|
||||
type="text"
|
||||
class="w-full px-4 py-2.5 border dark:border-slate-700 rounded-xl bg-slate-50/50 dark:bg-slate-950 focus:ring-2 focus:ring-blue-500/20 focus:border-blue-500 outline-none transition-all font-mono text-sm text-slate-900 dark:text-slate-100"
|
||||
placeholder="https://api.openai.com/v1"
|
||||
/>
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<label class="text-sm font-medium text-slate-700 dark:text-slate-300">API Key</label>
|
||||
<input
|
||||
v-model="settings.apiKey"
|
||||
type="password"
|
||||
class="w-full px-4 py-2.5 border dark:border-slate-700 rounded-xl bg-slate-50/50 dark:bg-slate-950 focus:ring-2 focus:ring-blue-500/20 focus:border-blue-500 outline-none transition-all font-mono text-sm text-slate-900 dark:text-slate-100"
|
||||
placeholder="sk-..."
|
||||
/>
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<label class="text-sm font-medium text-slate-700 dark:text-slate-300">Model Name</label>
|
||||
<input
|
||||
v-model="settings.modelName"
|
||||
type="text"
|
||||
class="w-full px-4 py-2.5 border dark:border-slate-700 rounded-xl bg-slate-50/50 dark:bg-slate-950 focus:ring-2 focus:ring-blue-500/20 focus:border-blue-500 outline-none transition-all font-mono text-sm text-slate-900 dark:text-slate-100"
|
||||
placeholder="gpt-3.5-turbo"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<h2 class="text-sm font-semibold text-slate-500 dark:text-slate-400 uppercase tracking-wider">模型接口配置</h2>
|
||||
<div v-if="!isSavingProfile" class="flex items-center gap-2">
|
||||
<button
|
||||
@click="isSavingProfile = true"
|
||||
class="text-xs flex items-center gap-1.5 text-blue-600 dark:text-blue-400 hover:underline px-2 py-1 rounded"
|
||||
>
|
||||
<Save class="w-3.5 h-3.5" />
|
||||
保存为预设
|
||||
</button>
|
||||
</div>
|
||||
<div v-else class="flex items-center gap-2 bg-white dark:bg-slate-800 p-1 rounded-lg border dark:border-slate-700 shadow-sm animate-in fade-in zoom-in duration-200">
|
||||
<input
|
||||
v-model="newProfileName"
|
||||
type="text"
|
||||
placeholder="输入预设名称..."
|
||||
class="text-xs px-2 py-1 bg-transparent outline-none w-32 dark:text-slate-200"
|
||||
@keyup.enter="saveCurrentAsProfile"
|
||||
/>
|
||||
<button
|
||||
@click="saveCurrentAsProfile"
|
||||
:disabled="!newProfileName.trim()"
|
||||
class="p-1 text-green-600 hover:bg-green-50 dark:hover:bg-green-900/30 rounded disabled:opacity-50"
|
||||
>
|
||||
<Check class="w-3.5 h-3.5" />
|
||||
</button>
|
||||
<button
|
||||
@click="isSavingProfile = false; newProfileName = ''"
|
||||
class="p-1 text-slate-400 hover:bg-slate-100 dark:hover:bg-slate-700 rounded"
|
||||
>
|
||||
<Plus class="w-3.5 h-3.5 rotate-45" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bg-slate-200/20 dark:bg-slate-900 rounded-xl shadow-sm/5 border dark:border-slate-800 p-6 space-y-4">
|
||||
<div class="space-y-2">
|
||||
<label class="text-sm font-medium text-slate-700 dark:text-slate-300">API Base URL</label>
|
||||
<input
|
||||
v-model="settings.apiBaseUrl"
|
||||
type="text"
|
||||
class="w-full px-4 py-2 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-slate-900 dark:text-slate-100"
|
||||
placeholder="https://api.openai.com/v1"
|
||||
/>
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<label class="text-sm font-medium text-slate-700 dark:text-slate-300">API Key</label>
|
||||
<input
|
||||
v-model="settings.apiKey"
|
||||
type="password"
|
||||
class="w-full px-4 py-2 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-sm text-slate-900 dark:text-slate-100"
|
||||
placeholder="sk-..."
|
||||
/>
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<label class="text-sm font-medium text-slate-700 dark:text-slate-300">Model</label>
|
||||
<input
|
||||
v-model="settings.modelName"
|
||||
type="text"
|
||||
class="w-full px-4 py-2 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-sm text-slate-900 dark:text-slate-100"
|
||||
placeholder="gpt-3.5-turbo"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<label class="text-sm font-medium text-slate-700 dark:text-slate-300">流式输出</label>
|
||||
<p class="text-xs text-slate-500 dark:text-slate-500">在生成时即时渲染文本</p>
|
||||
</div>
|
||||
<button
|
||||
@click="settings.enableStreaming = !settings.enableStreaming"
|
||||
:class="cn(
|
||||
'w-12 h-6 rounded-full transition-colors relative',
|
||||
settings.enableStreaming ? 'bg-blue-600' : 'bg-slate-300 dark:bg-slate-700'
|
||||
)"
|
||||
>
|
||||
<div :class="cn(
|
||||
'absolute top-1 left-1 w-4 h-4 bg-white rounded-full transition-transform',
|
||||
settings.enableStreaming ? 'translate-x-6' : 'translate-x-0'
|
||||
)"></div>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<label class="text-sm font-medium text-slate-700 dark:text-slate-300">自动质量审计</label>
|
||||
<p class="text-xs text-slate-500 dark:text-slate-500">翻译完成后自动评估准确度</p>
|
||||
</div>
|
||||
<button
|
||||
@click="settings.enableEvaluation = !settings.enableEvaluation"
|
||||
:class="cn(
|
||||
'w-12 h-6 rounded-full transition-colors relative',
|
||||
settings.enableEvaluation ? 'bg-blue-600' : 'bg-slate-300 dark:bg-slate-700'
|
||||
)"
|
||||
>
|
||||
<div :class="cn(
|
||||
'absolute top-1 left-1 w-4 h-4 bg-white rounded-full transition-transform',
|
||||
settings.enableEvaluation ? 'translate-x-6' : 'translate-x-0'
|
||||
)"></div>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div 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"
|
||||
<section>
|
||||
<h2 class="text-sm font-semibold text-slate-500 dark:text-slate-400 uppercase tracking-wider mb-4">API 配置预设库</h2>
|
||||
<div class="bg-slate-200/20 dark:bg-slate-900 rounded-xl shadow-sm border dark:border-slate-800 p-2 space-y-1">
|
||||
<div v-if="settings.profiles.length === 0" class="p-8 text-center text-sm text-slate-400 dark:text-slate-600 italic">
|
||||
暂无预设配置。修改上方配置并点击“保存为预设”。
|
||||
</div>
|
||||
<div
|
||||
v-for="profile in settings.profiles"
|
||||
:key="profile.id"
|
||||
class="p-3 flex items-center justify-between group hover:bg-white dark:hover:bg-slate-800 transition-colors rounded-lg border border-transparent hover:border-slate-200 dark:hover:border-slate-700"
|
||||
>
|
||||
<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"
|
||||
>
|
||||
<div class="flex flex-col gap-1 min-w-0">
|
||||
<span class="text-sm font-bold text-slate-700 dark:text-slate-200 truncate">{{ profile.name }}</span>
|
||||
<div class="flex items-center gap-2 text-[10px] text-slate-500 dark:text-slate-400 font-mono bg-slate-100 dark:bg-slate-950 px-2 py-0.5 rounded w-fit">
|
||||
<span class="truncate max-w-32">{{ profile.modelName }}</span>
|
||||
<span class="opacity-30">•</span>
|
||||
<span class="truncate max-w-48">{{ profile.apiBaseUrl }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center gap-2 opacity-0 group-hover:opacity-100 transition-opacity">
|
||||
<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'
|
||||
)"
|
||||
@click="applyProfile(profile)"
|
||||
class="flex items-center gap-1.5 px-3 py-1.5 bg-blue-50 text-blue-600 hover:bg-blue-100 dark:bg-blue-900/30 dark:text-blue-400 dark:hover:bg-blue-900/50 rounded-lg transition-colors text-xs font-medium shadow-sm"
|
||||
>
|
||||
使用主翻译配置(默认)
|
||||
<Check v-if="settings.evaluationProfileId === null" class="w-3.5 h-3.5" />
|
||||
<Play class="w-3 h-3 fill-current" />
|
||||
应用
|
||||
</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'
|
||||
)"
|
||||
@click="deleteProfile(profile.id)"
|
||||
class="p-1.5 text-slate-400 hover:text-red-600 hover:bg-red-50 dark:hover:bg-red-900/30 rounded-lg transition-colors"
|
||||
title="删除"
|
||||
>
|
||||
<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" />
|
||||
<Trash2 class="w-4 h-4" />
|
||||
</button>
|
||||
</div>
|
||||
</transition>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<p class="text-[11px] text-slate-500 dark:text-slate-500 pl-1">
|
||||
提示:建议为审计选择更强大的模型以获得更精准的反馈
|
||||
</p>
|
||||
<!-- General Settings -->
|
||||
<template v-if="settingsCategory === 'general'">
|
||||
<div class="mb-6 border-b dark:border-slate-800 pb-4">
|
||||
<h1 class="text-2xl font-bold text-slate-800 dark:text-slate-100">常规设置</h1>
|
||||
<p class="text-sm text-slate-500 dark:text-slate-400 mt-1">调整软件的翻译行为和质量审计规则。</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2 class="text-sm font-semibold text-slate-500 dark:text-slate-400 uppercase tracking-wider mb-4">提示词工程</h2>
|
||||
<div class="bg-slate-200/20 dark:bg-slate-900 rounded-xl shadow-sm/5 border dark:border-slate-800 p-6 space-y-6">
|
||||
<div class="space-y-2">
|
||||
<div class="bg-white/80 dark:bg-slate-900 rounded-2xl shadow-sm border dark:border-slate-800 p-6 space-y-6">
|
||||
<!-- Streaming -->
|
||||
<div class="flex items-center justify-between">
|
||||
<label class="text-sm font-medium text-slate-700 dark:text-slate-300">翻译系统提示词模板</label>
|
||||
<button @click="settings.systemPromptTemplate = DEFAULT_TEMPLATE" class="text-xs text-blue-600 dark:text-blue-400 hover:underline">恢复默认值</button>
|
||||
<div class="space-y-0.5">
|
||||
<label class="text-sm font-semibold text-slate-800 dark:text-slate-200">流式输出 (Streaming)</label>
|
||||
<p class="text-xs text-slate-500 dark:text-slate-400">在模型生成文本时逐字渲染,提升响应速度体验。</p>
|
||||
</div>
|
||||
<button
|
||||
@click="settings.enableStreaming = !settings.enableStreaming"
|
||||
:class="cn(
|
||||
'w-12 h-6 rounded-full transition-colors relative focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 shrink-0',
|
||||
settings.enableStreaming ? 'bg-blue-600' : 'bg-slate-300 dark:bg-slate-700'
|
||||
)"
|
||||
>
|
||||
<div :class="cn(
|
||||
'absolute top-1 left-1 w-4 h-4 bg-white rounded-full transition-transform shadow-sm',
|
||||
settings.enableStreaming ? 'translate-x-6' : 'translate-x-0'
|
||||
)"></div>
|
||||
</button>
|
||||
</div>
|
||||
<textarea
|
||||
v-model="settings.systemPromptTemplate"
|
||||
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"
|
||||
></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 class="space-y-2 border-t dark:border-slate-800 pt-6">
|
||||
<div class="h-px bg-slate-100 dark:bg-slate-800"></div>
|
||||
|
||||
<!-- Auto Evaluation -->
|
||||
<div class="flex items-center justify-between">
|
||||
<label class="text-sm font-medium text-slate-700 dark:text-slate-300">审计系统提示词模板</label>
|
||||
<button @click="settings.evaluationPromptTemplate = DEFAULT_EVALUATION_TEMPLATE" class="text-xs text-blue-600 dark:text-blue-400 hover:underline">恢复默认值</button>
|
||||
<div class="space-y-0.5">
|
||||
<label class="text-sm font-semibold text-slate-800 dark:text-slate-200">自动质量审计 (Auto Evaluation)</label>
|
||||
<p class="text-xs text-slate-500 dark:text-slate-400">翻译完成后,自动启动一个次级请求来评估翻译的准确度和语气,并提供改进建议。</p>
|
||||
</div>
|
||||
<button
|
||||
@click="settings.enableEvaluation = !settings.enableEvaluation"
|
||||
:class="cn(
|
||||
'w-12 h-6 rounded-full transition-colors relative focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 shrink-0',
|
||||
settings.enableEvaluation ? 'bg-blue-600' : 'bg-slate-300 dark:bg-slate-700'
|
||||
)"
|
||||
>
|
||||
<div :class="cn(
|
||||
'absolute top-1 left-1 w-4 h-4 bg-white rounded-full transition-transform shadow-sm',
|
||||
settings.enableEvaluation ? 'translate-x-6' : 'translate-x-0'
|
||||
)"></div>
|
||||
</button>
|
||||
</div>
|
||||
<textarea
|
||||
v-model="settings.evaluationPromptTemplate"
|
||||
rows="8"
|
||||
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>
|
||||
<div class="flex flex-wrap gap-2 mt-2">
|
||||
<span v-for="tag in ['{SOURCE_LANG}', '{TARGET_LANG}', '{SPEAKER_IDENTITY}', '{TONE_REGISTER}', '{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>
|
||||
|
||||
<div class="h-px bg-slate-100 dark:bg-slate-800"></div>
|
||||
|
||||
<!-- Audit Profile Selector -->
|
||||
<div class="space-y-3">
|
||||
<div class="space-y-0.5">
|
||||
<label class="text-sm font-semibold text-slate-800 dark:text-slate-200 flex items-center gap-1.5">
|
||||
<Settings class="w-4 h-4 text-blue-500" />
|
||||
独立审计模型配置
|
||||
</label>
|
||||
<p class="text-xs text-slate-500 dark:text-slate-400">无论自动或手动审计,都将使用此配置。强烈建议选择更强大的模型以获得精准反馈。</p>
|
||||
</div>
|
||||
|
||||
<!-- Custom Evaluation Profile Dropdown -->
|
||||
<div class="relative lang-dropdown w-full max-w-md">
|
||||
<button
|
||||
@click.stop="toggleDropdown('evaluationProfile')"
|
||||
class="flex items-center justify-between w-full px-4 py-2 border dark:border-slate-700 rounded-xl bg-slate-50 dark:bg-slate-950 hover:bg-slate-100 dark:hover:bg-slate-800 transition-all text-sm text-slate-700 dark:text-slate-200 group shadow-sm"
|
||||
>
|
||||
<span class="truncate font-medium">{{ 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-3 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-600 hover:bg-slate-50 dark:text-slate-300 dark:hover:bg-slate-700/50'
|
||||
)"
|
||||
>
|
||||
<span class="font-semibold">使用当前主翻译配置(不推荐)</span>
|
||||
<Check v-if="settings.evaluationProfileId === null" class="w-4 h-4" />
|
||||
</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-3 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 gap-0.5 min-w-0">
|
||||
<span class="truncate font-semibold">{{ profile.name }}</span>
|
||||
<span class="text-[10px] opacity-60 font-mono">{{ profile.modelName }}</span>
|
||||
</div>
|
||||
<Check v-if="settings.evaluationProfileId === profile.id" class="w-4 h-4 shrink-0" />
|
||||
</button>
|
||||
</div>
|
||||
</transition>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- Prompt Engineering -->
|
||||
<template v-if="settingsCategory === 'prompts'">
|
||||
<div class="mb-6 border-b dark:border-slate-800 pb-4">
|
||||
<h1 class="text-2xl font-bold text-slate-800 dark:text-slate-100">提示词工程</h1>
|
||||
<p class="text-sm text-slate-500 dark:text-slate-400 mt-1">深度定制 AI 的系统指令,修改变量占位符将改变其核心逻辑。</p>
|
||||
</div>
|
||||
|
||||
<div class="space-y-2 border-t dark:border-slate-800 pt-6">
|
||||
<div class="flex items-center justify-between">
|
||||
<label class="text-sm font-medium text-slate-700 dark:text-slate-300">润色系统提示词模板</label>
|
||||
<button @click="settings.refinementPromptTemplate = DEFAULT_REFINEMENT_TEMPLATE" class="text-xs text-blue-600 dark:text-blue-400 hover:underline">恢复默认值</button>
|
||||
<div class="space-y-8">
|
||||
<!-- Translation Prompt -->
|
||||
<div class="bg-white/80 dark:bg-slate-900 rounded-2xl shadow-sm border dark:border-slate-800 overflow-hidden flex flex-col">
|
||||
<div class="px-5 py-3 border-b dark:border-slate-800 bg-slate-50 dark:bg-slate-950 flex items-center justify-between">
|
||||
<div class="flex items-center gap-2">
|
||||
<div class="w-2 h-2 rounded-full bg-blue-500"></div>
|
||||
<h3 class="text-sm font-bold text-slate-700 dark:text-slate-200">主翻译系统指令</h3>
|
||||
</div>
|
||||
<button @click="settings.systemPromptTemplate = DEFAULT_TEMPLATE" class="text-xs text-blue-600 dark:text-blue-400 hover:underline font-medium">恢复默认</button>
|
||||
</div>
|
||||
<textarea
|
||||
v-model="settings.systemPromptTemplate"
|
||||
rows="8"
|
||||
class="w-full p-5 bg-transparent outline-none font-mono text-xs leading-relaxed text-slate-800 dark:text-slate-300 resize-y"
|
||||
spellcheck="false"
|
||||
></textarea>
|
||||
<div class="px-5 py-3 bg-slate-50 dark:bg-slate-950 border-t dark:border-slate-800">
|
||||
<p class="text-[10px] font-semibold text-slate-400 uppercase mb-2">可用变量 (Variables)</p>
|
||||
<div class="flex flex-wrap gap-1.5">
|
||||
<span v-for="tag in ['{SOURCE_CODE}', '{TARGET_CODE}', '{SOURCE_LANG}', '{TARGET_LANG}', '{SPEAKER_IDENTITY}', '{TONE_REGISTER}']" :key="tag" class="px-2 py-0.5 bg-white dark:bg-slate-800 text-[10px] font-mono rounded-md border dark:border-slate-700 text-slate-500 shadow-sm">{{ tag }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<textarea
|
||||
v-model="settings.refinementPromptTemplate"
|
||||
rows="8"
|
||||
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>
|
||||
<div class="flex flex-wrap gap-2 mt-2">
|
||||
<span v-for="tag in ['{SOURCE_LANG}', '{TARGET_LANG}', '{SPEAKER_IDENTITY}', '{TONE_REGISTER}', '{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>
|
||||
|
||||
<!-- Evaluation Prompt -->
|
||||
<div class="bg-white/80 dark:bg-slate-900 rounded-2xl shadow-sm border dark:border-slate-800 overflow-hidden flex flex-col">
|
||||
<div class="px-5 py-3 border-b dark:border-slate-800 bg-slate-50 dark:bg-slate-950 flex items-center justify-between">
|
||||
<div class="flex items-center gap-2">
|
||||
<div class="w-2 h-2 rounded-full bg-amber-500"></div>
|
||||
<h3 class="text-sm font-bold text-slate-700 dark:text-slate-200">质量审计指令 (JSON 输出)</h3>
|
||||
</div>
|
||||
<button @click="settings.evaluationPromptTemplate = DEFAULT_EVALUATION_TEMPLATE" class="text-xs text-blue-600 dark:text-blue-400 hover:underline font-medium">恢复默认</button>
|
||||
</div>
|
||||
<textarea
|
||||
v-model="settings.evaluationPromptTemplate"
|
||||
rows="12"
|
||||
class="w-full p-5 bg-transparent outline-none font-mono text-xs leading-relaxed text-slate-800 dark:text-slate-300 resize-y"
|
||||
spellcheck="false"
|
||||
></textarea>
|
||||
<div class="px-5 py-3 bg-slate-50 dark:bg-slate-950 border-t dark:border-slate-800">
|
||||
<p class="text-[10px] font-semibold text-slate-400 uppercase mb-2">可用变量 (Variables)</p>
|
||||
<div class="flex flex-wrap gap-1.5">
|
||||
<span v-for="tag in ['{SOURCE_LANG}', '{TARGET_LANG}', '{SPEAKER_IDENTITY}', '{TONE_REGISTER}', '{CONTEXT}']" :key="tag" class="px-2 py-0.5 bg-white dark:bg-slate-800 text-[10px] font-mono rounded-md border dark:border-slate-700 text-slate-500 shadow-sm">{{ tag }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Refinement Prompt -->
|
||||
<div class="bg-white/80 dark:bg-slate-900 rounded-2xl shadow-sm border dark:border-slate-800 overflow-hidden flex flex-col">
|
||||
<div class="px-5 py-3 border-b dark:border-slate-800 bg-slate-50 dark:bg-slate-950 flex items-center justify-between">
|
||||
<div class="flex items-center gap-2">
|
||||
<div class="w-2 h-2 rounded-full bg-green-500"></div>
|
||||
<h3 class="text-sm font-bold text-slate-700 dark:text-slate-200">定向润色指令</h3>
|
||||
</div>
|
||||
<button @click="settings.refinementPromptTemplate = DEFAULT_REFINEMENT_TEMPLATE" class="text-xs text-blue-600 dark:text-blue-400 hover:underline font-medium">恢复默认</button>
|
||||
</div>
|
||||
<textarea
|
||||
v-model="settings.refinementPromptTemplate"
|
||||
rows="8"
|
||||
class="w-full p-5 bg-transparent outline-none font-mono text-xs leading-relaxed text-slate-800 dark:text-slate-300 resize-y"
|
||||
spellcheck="false"
|
||||
></textarea>
|
||||
<div class="px-5 py-3 bg-slate-50 dark:bg-slate-950 border-t dark:border-slate-800">
|
||||
<p class="text-[10px] font-semibold text-slate-400 uppercase mb-2">可用变量 (Variables)</p>
|
||||
<div class="flex flex-wrap gap-1.5">
|
||||
<span v-for="tag in ['{SOURCE_LANG}', '{TARGET_LANG}', '{SPEAKER_IDENTITY}', '{TONE_REGISTER}', '{CONTEXT}']" :key="tag" class="px-2 py-0.5 bg-white dark:bg-slate-800 text-[10px] font-mono rounded-md border dark:border-slate-700 text-slate-500 shadow-sm">{{ tag }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Logs View -->
|
||||
<!-- Logs View -->
|
||||
<div v-else-if="view === 'logs'" class="flex-1 flex overflow-hidden bg-slate-100/50 dark:bg-slate-950">
|
||||
<!-- Logs List (Master) -->
|
||||
<div class="w-80 md:w-96 border-r dark:border-slate-800 flex flex-col bg-white/60 dark:bg-slate-900/40">
|
||||
|
||||
Reference in New Issue
Block a user