129 lines
7.5 KiB
Vue
129 lines
7.5 KiB
Vue
<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="续签策略">剩余 < 24h 时自动静默续签</a-descriptions-item>
|
||
<a-descriptions-item label="旧面板">/ 与 /kb/view 口令鉴权,不受本面板影响</a-descriptions-item>
|
||
</a-descriptions>
|
||
</GlassCard>
|
||
</div>
|
||
</div>
|
||
</template>
|