184 lines
6.2 KiB
Vue
184 lines
6.2 KiB
Vue
|
|
<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: '',
|
||
|
|
image_size: '1024x768',
|
||
|
|
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>
|
||
|
|
<NInput v-model:value="setting.image_size" placeholder="1024x768" style="width: 160px" />
|
||
|
|
<NText depth="3" style="font-size: 12px">(格式: WIDTHxHEIGHT,如 1024x768)</NText>
|
||
|
|
</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">
|
||
|
|
模板变量: <NCode>{title}</NCode> = 译后标题, <NCode>{summary}</NCode> = 摘要。<br />
|
||
|
|
期望返回 JSON,形如 <NCode>{`{"categories": ["时政", "国际"]}`}</NCode>
|
||
|
|
</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">
|
||
|
|
模板变量: <NCode>{title}</NCode> = 标题(优先译后)。最终 prompt 会拼成英文描述发给文生图模型
|
||
|
|
</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>
|