2026-06-08 14:24:25 +08:00
|
|
|
<script setup lang="ts">
|
|
|
|
|
import { onMounted, ref, watch } from 'vue'
|
|
|
|
|
import {
|
|
|
|
|
NCard, NSpace, NButton, NInput, NInputNumber, NSwitch, NAlert, useMessage, NSpin, NDivider, NText, NCode,
|
|
|
|
|
} from 'naive-ui'
|
|
|
|
|
import { adminApi, type LlmSetting } from '@/api/articles'
|
|
|
|
|
|
|
|
|
|
const message = useMessage()
|
|
|
|
|
const loading = ref(false)
|
|
|
|
|
const saving = ref(false)
|
|
|
|
|
const testing = ref(false)
|
|
|
|
|
const setting = ref<LlmSetting>({
|
|
|
|
|
format_prompt: '',
|
|
|
|
|
classify_prompt: '',
|
|
|
|
|
commentary_prompt: '',
|
|
|
|
|
image_prompt_template: '',
|
2026-06-08 20:53:21 +08:00
|
|
|
image_size: '768x512',
|
2026-06-08 14:24:25 +08:00
|
|
|
chat_model: 'agnes-2.0-flash',
|
|
|
|
|
image_model: 'agnes-image-2.1-flash',
|
|
|
|
|
interval_sec: 2.0,
|
|
|
|
|
enabled: true,
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
const testResult = ref<{ ok: boolean; detail: string; configured: boolean } | null>(null)
|
|
|
|
|
|
|
|
|
|
async function load() {
|
|
|
|
|
loading.value = true
|
|
|
|
|
try {
|
|
|
|
|
setting.value = await adminApi.getLlmSettings()
|
|
|
|
|
} catch (e: any) {
|
|
|
|
|
message.error(e?.response?.data?.title || '加载失败')
|
|
|
|
|
} finally {
|
|
|
|
|
loading.value = false
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function save() {
|
|
|
|
|
saving.value = true
|
|
|
|
|
try {
|
|
|
|
|
const updated = await adminApi.updateLlmSettings(setting.value)
|
|
|
|
|
setting.value = updated
|
|
|
|
|
message.success('已保存')
|
|
|
|
|
} catch (e: any) {
|
|
|
|
|
message.error(e?.response?.data?.title || '保存失败')
|
|
|
|
|
} finally {
|
|
|
|
|
saving.value = false
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function reset() {
|
|
|
|
|
if (!confirm('恢复所有提示词为默认值?(模型名 / 限速 / enabled 保留)')) return
|
|
|
|
|
try {
|
|
|
|
|
const out = await adminApi.resetLlmSettings()
|
|
|
|
|
message.success(out.detail || '已重置')
|
|
|
|
|
await load()
|
|
|
|
|
} catch (e: any) {
|
|
|
|
|
message.error(e?.response?.data?.title || '重置失败')
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function test() {
|
|
|
|
|
testing.value = true
|
|
|
|
|
testResult.value = null
|
|
|
|
|
try {
|
|
|
|
|
testResult.value = await adminApi.testLlmConnection()
|
|
|
|
|
if (testResult.value.ok) message.success('连接 OK')
|
|
|
|
|
else message.warning('连接失败')
|
|
|
|
|
} catch (e: any) {
|
|
|
|
|
testResult.value = { ok: false, detail: e?.message || '请求失败', configured: true }
|
|
|
|
|
message.error('测试失败')
|
|
|
|
|
} finally {
|
|
|
|
|
testing.value = false
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
onMounted(load)
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
<template>
|
|
|
|
|
<NSpace vertical>
|
|
|
|
|
<NSpace justify="space-between" align="center">
|
|
|
|
|
<div>
|
|
|
|
|
<h2 style="margin: 0">🤖 LLM 智能增强设置</h2>
|
|
|
|
|
<NText depth="3" style="font-size: 12px">
|
|
|
|
|
翻译完成后,自动调用 Agnes LLM 跑 4 项任务:排版 / 分类 / 插图 / 点评
|
|
|
|
|
</NText>
|
|
|
|
|
</div>
|
|
|
|
|
<NSpace>
|
|
|
|
|
<NButton :loading="testing" @click="test">测连接</NButton>
|
|
|
|
|
<NButton @click="reset">重置默认提示词</NButton>
|
|
|
|
|
<NButton type="primary" :loading="saving" :disabled="loading" @click="save">保存</NButton>
|
|
|
|
|
</NSpace>
|
|
|
|
|
</NSpace>
|
|
|
|
|
|
|
|
|
|
<NAlert v-if="testResult" :type="testResult.ok ? 'success' : 'warning'" :show-icon="false">
|
|
|
|
|
<NText strong>测连接结果:</NText>
|
|
|
|
|
<NText>{{ testResult.detail }}</NText>
|
|
|
|
|
</NAlert>
|
|
|
|
|
|
|
|
|
|
<NSpin :show="loading">
|
|
|
|
|
<NCard title="总开关 + 模型">
|
|
|
|
|
<NSpace vertical>
|
|
|
|
|
<NSpace align="center">
|
|
|
|
|
<NText>启用 LLM 增强:</NText>
|
|
|
|
|
<NSwitch v-model:value="setting.enabled" />
|
|
|
|
|
<NText v-if="!setting.enabled" depth="3" style="font-size: 12px">(关闭后翻译后不再调 LLM)</NText>
|
|
|
|
|
</NSpace>
|
|
|
|
|
<NSpace>
|
|
|
|
|
<NText>文生文模型:</NText>
|
|
|
|
|
<NInput v-model:value="setting.chat_model" placeholder="agnes-2.0-flash" style="width: 240px" />
|
|
|
|
|
</NSpace>
|
|
|
|
|
<NSpace>
|
|
|
|
|
<NText>文生图模型:</NText>
|
|
|
|
|
<NInput v-model:value="setting.image_model" placeholder="agnes-image-2.1-flash" style="width: 240px" />
|
|
|
|
|
</NSpace>
|
|
|
|
|
<NSpace>
|
|
|
|
|
<NText>插图尺寸:</NText>
|
2026-06-08 20:53:21 +08:00
|
|
|
<NInput v-model:value="setting.image_size" placeholder="768x512" style="width: 160px" />
|
|
|
|
|
<NText depth="3" style="font-size: 12px">(格式: WIDTHxHEIGHT,如 768x512;后端默认固定用 768x512)</NText>
|
2026-06-08 14:24:25 +08:00
|
|
|
</NSpace>
|
|
|
|
|
<NSpace>
|
|
|
|
|
<NText>LLM 调用间隔(秒):</NText>
|
|
|
|
|
<NInputNumber v-model:value="setting.interval_sec" :min="0" :max="60" :step="0.5" />
|
|
|
|
|
<NText depth="3" style="font-size: 12px">(chat + image 各 1 个串行,每次调用后等这么久)</NText>
|
|
|
|
|
</NSpace>
|
|
|
|
|
</NSpace>
|
|
|
|
|
</NCard>
|
|
|
|
|
|
|
|
|
|
<NCard title="排版提示词" style="margin-top: 16px">
|
|
|
|
|
<NText depth="3" style="font-size: 12px">
|
|
|
|
|
模板变量: <NCode>{body}</NCode> = 译文正文
|
|
|
|
|
</NText>
|
|
|
|
|
<NInput
|
|
|
|
|
v-model:value="setting.format_prompt"
|
|
|
|
|
type="textarea"
|
|
|
|
|
:autosize="{ minRows: 6, maxRows: 20 }"
|
|
|
|
|
placeholder="留空用默认"
|
|
|
|
|
style="margin-top: 8px"
|
|
|
|
|
/>
|
|
|
|
|
</NCard>
|
|
|
|
|
|
|
|
|
|
<NCard title="分类提示词" style="margin-top: 16px">
|
|
|
|
|
<NText depth="3" style="font-size: 12px">
|
2026-06-08 20:53:21 +08:00
|
|
|
模板变量: <NCode>{title}</NCode> = 译后标题, <NCode>{summary}</NCode> = 摘要, <NCode>{body}</NCode> = 正文(节选)。<br />
|
|
|
|
|
期望返回 JSON(多标签,2-5 个),形如 <NCode>{`{"categories": ["时政", "国际", "经济"]}`}</NCode>
|
2026-06-08 14:24:25 +08:00
|
|
|
</NText>
|
|
|
|
|
<NInput
|
|
|
|
|
v-model:value="setting.classify_prompt"
|
|
|
|
|
type="textarea"
|
|
|
|
|
:autosize="{ minRows: 4, maxRows: 12 }"
|
|
|
|
|
placeholder="留空用默认"
|
|
|
|
|
style="margin-top: 8px"
|
|
|
|
|
/>
|
|
|
|
|
</NCard>
|
|
|
|
|
|
|
|
|
|
<NCard title="点评提示词" style="margin-top: 16px">
|
|
|
|
|
<NText depth="3" style="font-size: 12px">
|
|
|
|
|
模板变量: <NCode>{title}</NCode> = 译后标题, <NCode>{body}</NCode> = 译文正文
|
|
|
|
|
</NText>
|
|
|
|
|
<NInput
|
|
|
|
|
v-model:value="setting.commentary_prompt"
|
|
|
|
|
type="textarea"
|
|
|
|
|
:autosize="{ minRows: 4, maxRows: 12 }"
|
|
|
|
|
placeholder="留空用默认"
|
|
|
|
|
style="margin-top: 8px"
|
|
|
|
|
/>
|
|
|
|
|
</NCard>
|
|
|
|
|
|
|
|
|
|
<NCard title="插图 prompt 模板" style="margin-top: 16px">
|
|
|
|
|
<NText depth="3" style="font-size: 12px">
|
2026-06-08 20:53:21 +08:00
|
|
|
模板变量: <NCode>{body}</NCode> = 译文正文第一段(主要), <NCode>{title}</NCode> = 标题(fallback)。<br />
|
|
|
|
|
最终 prompt 会拼成英文描述发给文生图模型
|
2026-06-08 14:24:25 +08:00
|
|
|
</NText>
|
|
|
|
|
<NInput
|
|
|
|
|
v-model:value="setting.image_prompt_template"
|
|
|
|
|
type="textarea"
|
|
|
|
|
:autosize="{ minRows: 3, maxRows: 8 }"
|
|
|
|
|
placeholder="留空用默认"
|
|
|
|
|
style="margin-top: 8px"
|
|
|
|
|
/>
|
|
|
|
|
</NCard>
|
|
|
|
|
</NSpin>
|
|
|
|
|
</NSpace>
|
|
|
|
|
</template>
|