Files
nl-tcm-agent-admin/src/views/config/index.vue
2026-08-15 12:52:39 +08:00

129 lines
7.5 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<script setup>
// 配置总览只读LLM / 知识库 / Agent 行为 / 面板会话 四组玻璃卡
// 面板不改配置(此前约定:开关写入口统一在 PHP 后台,避免双写),只提供缓存失效
import { ref, computed, onMounted } from 'vue'
import { message } from 'ant-design-vue'
import { ReloadOutlined, ClearOutlined } from '@ant-design/icons-vue'
import { useUserStore } from '@/stores/user'
import { getAgentConfig } from '@/api/agent'
import { getActiveConfig, invalidateModelCache } from '@/api/models'
import { fmtTimeFull } from '@/utils/format'
import PageHeader from '@/components/PageHeader.vue'
import GlassCard from '@/components/GlassCard.vue'
const user = useUserStore()
const cfg = ref(null)
const llm = ref(null)
const loading = ref(false)
async function loadAll() {
loading.value = true
try {
// 两个接口并行拉,任一失败不影响另一个展示
const [cfgRes, llmRes] = await Promise.allSettled([
getAgentConfig({ silent: true }),
getActiveConfig({ silent: true })
])
if (cfgRes.status === 'fulfilled') cfg.value = cfgRes.value.data
if (llmRes.status === 'fulfilled') llm.value = llmRes.value.data
} finally { loading.value = false }
}
async function doInvalidate() {
await invalidateModelCache()
message.success('LLM 配置缓存已失效')
loadAll()
}
// JWT 过期时间(面板会话卡展示)
const expireText = computed(() => {
const exp = Number(localStorage.getItem('agent_admin_expire') || 0)
return exp ? fmtTimeFull(exp) : '-'
})
const onoff = v => v ? '开启' : '关闭'
onMounted(loadAll)
</script>
<template>
<div>
<PageHeader title="配置总览" desc="全部只读;开关修改统一走 PHP 管理后台xk_system_config避免双写冲突">
<template #actions>
<a-tag color="default" :bordered="false">只读</a-tag>
<a-button :loading="loading" @click="loadAll"><template #icon><ReloadOutlined /></template>刷新</a-button>
<a-popconfirm title="确认失效 LLM 配置缓存?" ok-text="失效" cancel-text="取消" @confirm="doInvalidate">
<a-button danger><template #icon><ClearOutlined /></template>失效缓存</a-button>
</a-popconfirm>
</template>
</PageHeader>
<div class="grid grid-cols-1 lg:grid-cols-2 gap-4">
<!-- LLM -->
<GlassCard title="LLM当前生效脱敏">
<a-skeleton v-if="loading && !llm" active :paragraph="{ rows: 4 }" :title="false" />
<a-descriptions v-else-if="llm" :column="1" size="small" bordered>
<a-descriptions-item label="Provider"><span class="mono" style="color: var(--primary)">{{ llm.provider }}</span></a-descriptions-item>
<a-descriptions-item label="Model"><span class="mono">{{ llm.model }}</span></a-descriptions-item>
<a-descriptions-item label="API 地址"><span class="mono text-xs">{{ llm.api_url || '-' }}</span></a-descriptions-item>
<a-descriptions-item label="API Key"><span class="mono">{{ llm.api_key_tail ? '****' + llm.api_key_tail : '-' }}#{{ llm.api_key_id }}</span></a-descriptions-item>
<a-descriptions-item label="配置来源"><a-tag :bordered="false" :color="llm.source === 'db_active' ? 'processing' : 'default'">{{ llm.source }}</a-tag></a-descriptions-item>
</a-descriptions>
<div v-else class="text-xs" style="color: var(--text-3)">读取失败数据库不可达时不影响其他卡片</div>
</GlassCard>
<!-- 知识库 -->
<GlassCard title="知识库">
<a-skeleton v-if="loading && !cfg" active :paragraph="{ rows: 4 }" :title="false" />
<a-descriptions v-else-if="cfg" :column="1" size="small" bordered>
<a-descriptions-item label="检索来源"><span class="mono">{{ cfg.kb?.source }}</span></a-descriptions-item>
<a-descriptions-item label="检索模式"><span class="mono">{{ cfg.kb?.search_mode || '-' }}</span></a-descriptions-item>
<a-descriptions-item label="TopK 默认值">{{ cfg.kb?.top_k }}</a-descriptions-item>
<a-descriptions-item label="Embedding"><span class="mono">{{ cfg.kb?.embedding_provider || '-' }}</span></a-descriptions-item>
</a-descriptions>
</GlassCard>
<!-- Agent 行为 -->
<GlassCard title="Agent 行为xk_system_config 实时值30s 缓存)">
<a-skeleton v-if="loading && !cfg" active :paragraph="{ rows: 6 }" :title="false" />
<template v-else-if="cfg">
<a-descriptions :column="1" size="small" bordered class="mb-3">
<a-descriptions-item label="ReAct 引擎">{{ onoff(cfg.react?.enabled) }}最多 {{ cfg.react?.max_iterations }} </a-descriptions-item>
<a-descriptions-item label="Planning / Reflection">{{ onoff(cfg.react?.planning_enabled) }} / {{ onoff(cfg.react?.reflection_enabled) }}</a-descriptions-item>
<a-descriptions-item label="JSON 修复">{{ onoff(cfg.react?.json_repair_enabled) }}</a-descriptions-item>
<a-descriptions-item label="Token 预算">
{{ onoff(cfg.token_budget?.enabled) }}
<span v-if="cfg.token_budget?.enabled" class="mono text-xs ml-1">单请求 {{ cfg.token_budget?.per_request }} / 单次 {{ cfg.token_budget?.max_tokens_per_call }}</span>
</a-descriptions-item>
<a-descriptions-item label="Debug 日志">
<span :style="{ color: cfg.debug?.log_request_body ? 'var(--warn)' : '' }">{{ cfg.debug?.log_request_body ? '开(含请求体,排查完记得关)' : '关' }}</span>
</a-descriptions-item>
</a-descriptions>
<div class="text-xs mb-1.5" style="color: var(--text-3)">医疗守卫 {{ onoff(cfg.medical_guard?.enabled) }} · 白名单 {{ (cfg.medical_guard?.whitelist || []).length }} · 黑名单 {{ (cfg.medical_guard?.blacklist || []).length }} </div>
<a-collapse ghost size="small">
<a-collapse-panel key="words" header="展开守卫词表">
<div class="mb-2">
<div class="text-xs mb-1" style="color: var(--text-3)">白名单命中即放行</div>
<a-tag v-for="w in cfg.medical_guard?.whitelist || []" :key="w" color="success" :bordered="false" class="!mb-1">{{ w }}</a-tag>
</div>
<div>
<div class="text-xs mb-1" style="color: var(--text-3)">黑名单命中即拦截</div>
<a-tag v-for="b in cfg.medical_guard?.blacklist || []" :key="b" color="error" :bordered="false" class="!mb-1">{{ b }}</a-tag>
</div>
</a-collapse-panel>
</a-collapse>
</template>
</GlassCard>
<!-- 面板会话 -->
<GlassCard title="面板会话">
<a-descriptions :column="1" size="small" bordered>
<a-descriptions-item label="登录账号">{{ user.userInfo?.nick_name || user.userInfo?.username || '-' }}</a-descriptions-item>
<a-descriptions-item label="角色"><a-tag :bordered="false" color="processing">{{ user.userInfo?.role_name || 'panel_admin' }}</a-tag></a-descriptions-item>
<a-descriptions-item label="会话过期"><span class="mono text-xs">{{ expireText }}</span></a-descriptions-item>
<a-descriptions-item label="续签策略">剩余 &lt; 24h 时自动静默续签</a-descriptions-item>
<a-descriptions-item label="旧面板">/ /kb/view 口令鉴权不受本面板影响</a-descriptions-item>
</a-descriptions>
</GlassCard>
</div>
</div>
</template>