feat(admin): Angel(Agnes) provider 凭据 DB 化 + 安全 key_set 字段
- llm_settings.agnes_api_key TEXT (DB key 优先,.env 兜底) - llm_settings.agnes_base_url_override VARCHAR (留空 = 用 .env) - alembic 0005_agnes_key 迁移 - LlmSettingOut.agnes_api_key_set (bool) 替代直接回传 key - LlmSettingUpdate 加 agnes_api_key / agnes_base_url_override(可空可清空) - providers.get_angel_client 改用 DB key 优先 - enrichment.py 改为 get_angel_client() 工厂调用(热改 key 不需重启) - /admin/llm/settings/test 走 get_angel_client(测的是 DB 里的 key) - 前端 AdminLlmSettings 在'总开关 + 模型'卡里加 Angel api_key 输入框 + base_url 覆盖 + 已配置/未配置指示灯 + 清空按钮 - 顶部'测连接'按钮复用(测的就是 Angel)
This commit is contained in:
@@ -92,6 +92,9 @@ export interface LlmSetting {
|
||||
enabled: boolean
|
||||
// 全局屏蔽分类标签;与 sources.blocklist_tags 合并后注入 classify prompt
|
||||
blocklist_tags?: string[]
|
||||
// Angel(Agnes)凭据 — DB 存,优先于 .env
|
||||
agnes_api_key_set?: boolean // 不回传 key 真值
|
||||
agnes_base_url_override?: string // 留空 = 用 .env
|
||||
// 美团大模型(LongCat,OpenAI 兼容)双 provider 评论
|
||||
meituan_api_key_set?: boolean // 不回传 key 真值
|
||||
meituan_base_url?: string
|
||||
|
||||
@@ -20,6 +20,9 @@ const setting = ref<LlmSetting>({
|
||||
interval_sec: 2.0,
|
||||
enabled: true,
|
||||
blocklist_tags: [],
|
||||
// Angel(Agnes)凭据
|
||||
agnes_api_key_set: false,
|
||||
agnes_base_url_override: '',
|
||||
// 美团大模型(LongCat,OpenAI 兼容)双 provider 评论
|
||||
meituan_api_key_set: false,
|
||||
meituan_base_url: 'https://api.longcat.chat/openai/v1',
|
||||
@@ -29,6 +32,21 @@ const setting = ref<LlmSetting>({
|
||||
meituan_commentary_prompt: '',
|
||||
})
|
||||
|
||||
// === Angel api_key 编辑(不回显真值) ===
|
||||
const agnesKeyInput = ref('')
|
||||
const agnesKeyHasValue = ref(false)
|
||||
const agnesKeyPlaceholder = computed(() => {
|
||||
if (agnesKeyHasValue.value) return '已配置(留空 = 不修改,输入新值 = 覆盖)'
|
||||
return '请输入 Agnes API Key(DB 留空 = 用 .env AGNES_API_KEY)'
|
||||
})
|
||||
|
||||
function loadAgnesKeyState() {
|
||||
agnesKeyHasValue.value = !!setting.value.agnes_api_key_set
|
||||
agnesKeyInput.value = ''
|
||||
}
|
||||
|
||||
watch(() => setting.value.agnes_api_key_set, loadAgnesKeyState)
|
||||
|
||||
// === 美团 api_key 编辑(不回显真值)===
|
||||
const meituanKeyInput = ref('')
|
||||
const meituanKeyHasValue = ref(false) // 当前 DB 是否已设置
|
||||
@@ -65,6 +83,7 @@ async function load() {
|
||||
try {
|
||||
setting.value = await adminApi.getLlmSettings()
|
||||
loadMeituanKeyState()
|
||||
loadAgnesKeyState()
|
||||
} catch (e: any) {
|
||||
message.error(e?.response?.data?.title || '加载失败')
|
||||
} finally {
|
||||
@@ -78,6 +97,8 @@ async function save() {
|
||||
// 美团 api_key:有输入才提交(否则不修改);清空用 "clear" 信号
|
||||
const body: any = { ...setting.value }
|
||||
delete body.meituan_api_key_set // 后端不需要这个字段
|
||||
delete body.agnes_api_key_set // 后端不需要这个字段
|
||||
// --- 美团 key ---
|
||||
if (meituanKeyInput.value === '__CLEAR__') {
|
||||
body.meituan_api_key = ''
|
||||
} else if (meituanKeyInput.value && meituanKeyInput.value.trim()) {
|
||||
@@ -85,10 +106,20 @@ async function save() {
|
||||
} else {
|
||||
delete body.meituan_api_key
|
||||
}
|
||||
// --- Angel key(同样的三态语义) ---
|
||||
if (agnesKeyInput.value === '__CLEAR__') {
|
||||
body.agnes_api_key = ''
|
||||
} else if (agnesKeyInput.value && agnesKeyInput.value.trim()) {
|
||||
body.agnes_api_key = agnesKeyInput.value.trim()
|
||||
} else {
|
||||
delete body.agnes_api_key
|
||||
}
|
||||
const updated = await adminApi.updateLlmSettings(body)
|
||||
setting.value = updated
|
||||
meituanKeyInput.value = '' // 重置输入
|
||||
agnesKeyInput.value = ''
|
||||
loadMeituanKeyState()
|
||||
loadAgnesKeyState()
|
||||
message.success('已保存')
|
||||
} catch (e: any) {
|
||||
message.error(e?.response?.data?.title || '保存失败')
|
||||
@@ -170,6 +201,43 @@ onMounted(load)
|
||||
<NSwitch v-model:value="setting.enabled" />
|
||||
<NText v-if="!setting.enabled" depth="3" style="font-size: 12px">(关闭后翻译后不再调 LLM)</NText>
|
||||
</NSpace>
|
||||
|
||||
<NDivider style="margin: 4px 0" />
|
||||
<NText strong>👼 Angel(Agnes)provider</NText>
|
||||
<NText depth="3" style="font-size: 12px; margin-top: -8px">
|
||||
DB key 优先,留空 = 用 .env 中的 <NCode>AGNES_API_KEY</NCode>。点击顶部"测连接"实测连通性。
|
||||
</NText>
|
||||
<NSpace align="center" :wrap="true" style="row-gap: 6px">
|
||||
<NText style="min-width: 80px">API Key:</NText>
|
||||
<NInput
|
||||
v-model:value="agnesKeyInput"
|
||||
type="password"
|
||||
show-password-on="click"
|
||||
:placeholder="agnesKeyPlaceholder"
|
||||
style="width: 360px"
|
||||
/>
|
||||
<NButton
|
||||
v-if="agnesKeyHasValue"
|
||||
size="small"
|
||||
type="warning"
|
||||
ghost
|
||||
@click="agnesKeyInput = '__CLEAR__'"
|
||||
>
|
||||
清空
|
||||
</NButton>
|
||||
<NText v-if="agnesKeyHasValue" depth="3" style="font-size: 12px; color: #18a058">● DB 已配置</NText>
|
||||
<NText v-else depth="3" style="font-size: 12px; color: #d03050">● DB 未配置(将用 .env 兜底)</NText>
|
||||
</NSpace>
|
||||
<NSpace align="center" :wrap="true" style="row-gap: 6px">
|
||||
<NText style="min-width: 80px">Base URL:</NText>
|
||||
<NInput
|
||||
v-model:value="setting.agnes_base_url_override"
|
||||
placeholder="留空 = 用 .env AGNES_BASE_URL"
|
||||
style="width: 380px"
|
||||
/>
|
||||
</NSpace>
|
||||
|
||||
<NDivider style="margin: 4px 0" />
|
||||
<NSpace>
|
||||
<NText>文生文模型:</NText>
|
||||
<NInput v-model:value="setting.chat_model" placeholder="agnes-2.0-flash" style="width: 240px" />
|
||||
@@ -194,7 +262,7 @@ onMounted(load)
|
||||
<NCard title="🐱 美团大模型(LongCat,双 provider 评论)" style="margin-top: 16px">
|
||||
<NText depth="3" style="font-size: 12px">
|
||||
与 Angel(Agnes)并列,各跑各的 prompt,结果存到 articles.commentary_meituan 等字段。
|
||||
留空 api_key = 关闭该 provider,Angel 仍正常工作。
|
||||
DB 留空 = 用 .env <NCode>MEITUAN_API_KEY</NCode>(.env 也没配 = 关闭该 provider,Angel 仍正常工作)。
|
||||
</NText>
|
||||
<NSpace vertical style="margin-top: 12px">
|
||||
<NSpace align="center">
|
||||
|
||||
Reference in New Issue
Block a user