refactor(debug): 更新调试组件,增强系统配置和用户信息打印功能

- 修改 DebugComponent.vue,重命名打印配置函数为 printSystemConfig,并新增打印用户信息、知识库信息和智能体配置的功能。
- 更新日志级别选择的实现方式,采用多选卡片样式,提升用户体验。
- 调整样式以优化界面布局,确保在不同屏幕尺寸下的适配性。
- 在 AppLayout.vue 中移除调试面板的浮动按钮,改为使用模态框展示调试组件。
This commit is contained in:
Wenjie Zhang 2025-06-27 01:53:30 +08:00
parent 9bc5b086f9
commit 105ade5273
2 changed files with 315 additions and 61 deletions

View File

@ -10,9 +10,21 @@
<template #icon><ClearOutlined /></template>
清空
</a-button>
<a-button @click="printConfig">
<a-button @click="printSystemConfig">
<template #icon><SettingOutlined /></template>
打印配置
系统配置
</a-button>
<a-button @click="printUserInfo">
<template #icon><UserOutlined /></template>
用户信息
</a-button>
<a-button @click="printDatabaseInfo">
<template #icon><DatabaseOutlined /></template>
知识库信息
</a-button>
<a-button @click="printAgentConfig">
<template #icon><RobotOutlined /></template>
智能体配置
</a-button>
<a-button @click="toggleFullscreen">
<template #icon>
@ -24,6 +36,7 @@
<a-tooltip :title="state.autoRefresh ? '点击停止自动刷新' : '点击开启自动刷新'">
<a-button
:type="state.autoRefresh ? 'primary' : 'default'"
:class="{ 'auto-refresh-button': state.autoRefresh }"
@click="toggleAutoRefresh(!state.autoRefresh)"
>
<template #icon>
@ -38,16 +51,31 @@
<a-input-search
v-model:value="state.searchText"
placeholder="搜索日志..."
style="width: 200px"
style="width: 200px; height: 32px;"
@search="onSearch"
/>
<a-select
v-model:value="state.selectedLevels"
mode="multiple"
placeholder="选择日志级别"
:options="logLevels"
@change="filterLogs"
/>
<div class="log-level-selector">
<div class="multi-select-cards">
<div
v-for="level in logLevels"
:key="level.value"
class="option-card"
:class="{
'selected': isLogLevelSelected(level.value),
'unselected': !isLogLevelSelected(level.value)
}"
@click="toggleLogLevel(level.value)"
>
<div class="option-content">
<span class="option-text">{{ level.label }}</span>
<div class="option-indicator">
<CheckCircleOutlined v-if="isLogLevelSelected(level.value)" />
<PlusCircleOutlined v-else />
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div ref="logContainer" class="log-container">
@ -73,6 +101,7 @@
import { ref, onMounted, onActivated, onUnmounted, nextTick, reactive, computed } from 'vue';
import { useConfigStore } from '@/stores/config';
import { useUserStore } from '@/stores/user';
import { useDatabaseStore } from '@/stores/database';
import { useThrottleFn } from '@vueuse/core';
import { message } from 'ant-design-vue';
import {
@ -81,13 +110,20 @@ import {
ReloadOutlined,
ClearOutlined,
SettingOutlined,
SyncOutlined
SyncOutlined,
CheckCircleOutlined,
PlusCircleOutlined,
UserOutlined,
DatabaseOutlined,
RobotOutlined
} from '@ant-design/icons-vue';
import dayjs from 'dayjs';
import { logApi } from '@/apis/admin_api';
import { logApi, systemConfigApi } from '@/apis/admin_api';
import { chatApi } from '@/apis/auth_api';
const configStore = useConfigStore()
const userStore = useUserStore();
const databaseStore = useDatabaseStore();
const config = configStore.config;
//
@ -126,7 +162,8 @@ let autoRefreshInterval = null;
//
const parseLogLine = (line) => {
const match = line.match(/^(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2},\d{3}) - (\w+) - ([^-]+) - (.+)$/);
//
const match = line.match(/^(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}(?:,\d{3})?)\s*-\s*(\w+)\s*-\s*([^-]+?)\s*-\s*(.+)$/);
if (match) {
return {
timestamp: match[1],
@ -142,8 +179,14 @@ const parseLogLine = (line) => {
//
const formatTimestamp = (timestamp) => {
try {
// "2025-03-10 08:26:37,269" "2025-03-10 08:26:37.269"
const normalizedTimestamp = timestamp.replace(',', '.');
// "2025-03-10 08:26:37,269" "2025-03-10 08:26:37.269"
let normalizedTimestamp = timestamp.replace(',', '.');
// .000
if (!/\.\d{3}$/.test(normalizedTimestamp)) {
normalizedTimestamp += '.000';
}
const date = dayjs(normalizedTimestamp);
return date.isValid() ? date.format('HH:mm:ss.SSS') : timestamp;
} catch (err) {
@ -204,6 +247,24 @@ const filterLogs = () => {
// computed
};
//
const isLogLevelSelected = (level) => {
return state.selectedLevels.includes(level);
};
const toggleLogLevel = (level) => {
const currentLevels = [...state.selectedLevels];
const index = currentLevels.indexOf(level);
if (index > -1) {
currentLevels.splice(index, 1);
} else {
currentLevels.push(level);
}
state.selectedLevels = currentLevels;
};
//
const toggleAutoRefresh = (value) => {
if (!checkAdminPermission()) return;
@ -285,16 +346,80 @@ onUnmounted(() => {
document.removeEventListener('msfullscreenchange', handleFullscreenChange);
});
const printConfig = () => {
//
const printSystemConfig = () => {
if (!checkAdminPermission()) return;
console.log('Current config:', config);
console.log('=== 系统配置 ===');
console.log(config);
};
//
const printUserInfo = () => {
if (!checkAdminPermission()) return;
console.log('=== 用户信息 ===');
const userInfo = {
token: userStore.token ? '*** (已隐藏)' : null,
userId: userStore.userId,
username: userStore.username,
userRole: userStore.userRole,
isLoggedIn: userStore.isLoggedIn,
isAdmin: userStore.isAdmin,
isSuperAdmin: userStore.isSuperAdmin
};
console.log(JSON.stringify(userInfo, null, 2));
};
//
const printDatabaseInfo = async () => {
if (!checkAdminPermission()) return;
try {
console.log('=== 知识库信息 ===');
// API
await databaseStore.refreshDatabase();
} catch (error) {
console.error('获取知识库信息失败:', error);
message.error('获取知识库信息失败: ' + error.message);
}
};
//
const printAgentConfig = async () => {
if (!checkAdminPermission()) return;
try {
console.log('=== 智能体配置 ===');
//
const agentsData = await chatApi.getAgents();
console.log('智能体列表:', JSON.stringify(agentsData.agents, null, 2));
//
const defaultAgent = await chatApi.getDefaultAgent();
console.log('默认智能体:', JSON.stringify(defaultAgent, null, 2));
//
for (const agent of agentsData.agents) {
try {
const agentConfig = await systemConfigApi.getAgentConfig(agent.name);
console.log(`智能体 "${agent.name}" 配置:`, JSON.stringify(agentConfig, null, 2));
} catch (err) {
console.log(`智能体 "${agent.name}" 配置获取失败:`, err.message);
}
}
} catch (error) {
console.error('获取智能体配置失败:', error);
message.error('获取智能体配置失败: ' + error.message);
}
};
</script>
<style scoped>
.log-viewer {
background: white;
height: 100%;
}
.log-viewer.fullscreen {
@ -303,24 +428,68 @@ const printConfig = () => {
.control-panel {
margin-bottom: 16px;
padding: 16px;
background: var(--gray-50);
border-radius: 8px;
border: 1px solid var(--gray-200);
}
.button-group {
display: flex;
gap: 10px;
gap: 8px;
margin-bottom: 10px;
flex-wrap: wrap;
.ant-btn {
min-width: 80px;
height: 32px;
padding: 4px 12px;
font-size: 13px;
border-color: var(--gray-300);
color: var(--gray-700);
&:hover {
border-color: var(--main-color);
color: var(--main-color);
}
&.ant-btn-primary {
background-color: var(--main-color);
border-color: var(--main-color);
&:hover {
background-color: var(--main-600);
border-color: var(--main-600);
}
}
.anticon {
font-size: 14px;
}
}
.refresh-interval {
font-size: 12px;
opacity: 0.8;
margin-left: 2px;
}
.auto-refresh-button {
color:white;
}
}
.filter-group {
display: flex;
gap: 10px;
margin-bottom: 10px;
gap: 16px;
align-items: flex-start;
flex-wrap: wrap;
height: 32px;
@media (max-width: 768px) {
flex-direction: column;
gap: 12px;
}
}
.error {
@ -328,7 +497,7 @@ const printConfig = () => {
}
.log-container {
height: calc(100vh - 200px);
height: calc(80vh - 200px);
overflow-y: auto;
background: #0C0C0C;
color: #D1D1D1;
@ -402,15 +571,95 @@ const printConfig = () => {
}
:fullscreen .log-container {
height: calc(100vh - 120px);
height: calc(100vh - 160PX);
}
:-webkit-full-screen .log-container {
height: calc(100vh - 120px);
height: calc(100vh - 160PX);
}
:-ms-fullscreen .log-container {
height: calc(100vh - 120px);
height: calc(100vh - 160PX);
}
.multi-select-cards {
display: flex;
flex-direction: row;
gap: 10px;
.option-card {
border: 1px solid var(--gray-300);
border-radius: 6px;
padding: 0px 10px;
cursor: pointer;
transition: all 0.2s ease;
background: var(--gray-0);
user-select: none;
height: 32px;
display: flex;
align-items: center;
&:hover {
border-color: var(--main-color);
background: var(--main-5);
}
&.selected {
border-color: var(--main-color);
background: var(--main-10);
.option-indicator {
color: var(--main-color);
}
.option-text {
color: var(--main-color);
font-weight: 500;
}
}
&.unselected {
.option-indicator {
color: var(--gray-400);
}
.option-text {
color: var(--gray-700);
}
}
.option-content {
display: flex;
justify-content: space-between;
align-items: center;
gap: 6px;
width: 100%;
}
.option-text {
flex: 1;
font-size: 12px;
text-align: center;
}
.option-indicator {
flex-shrink: 0;
font-size: 14px;
transition: color 0.2s ease;
}
}
}
/* 响应式适配 */
@media (max-width: 768px) {
.log-level-selector {
min-width: 280px;
}
.multi-select-cards .options-grid {
grid-template-columns: repeat(2, 1fr);
}
}
</style>

View File

@ -1,18 +1,18 @@
<script setup>
import { ref, reactive, KeepAlive, onMounted, computed } from 'vue'
import { ref, reactive, onMounted, useTemplateRef, shallowRef } from 'vue'
import { RouterLink, RouterView, useRoute } from 'vue-router'
import {
GithubOutlined,
BugOutlined,
ExclamationCircleOutlined,
} from '@ant-design/icons-vue'
import { Bot, Waypoints, LibraryBig, MessageSquareMore, Settings } from 'lucide-vue-next';
import { Bot, Waypoints, LibraryBig, Settings } from 'lucide-vue-next';
import { onLongPress } from '@vueuse/core'
import { useConfigStore } from '@/stores/config'
import { useDatabaseStore } from '@/stores/database'
import { useInfoStore } from '@/stores/info'
import DebugComponent from '@/components/DebugComponent.vue'
import UserInfoComponent from '@/components/UserInfoComponent.vue'
import DebugComponent from '@/components/DebugComponent.vue'
const configStore = useConfigStore()
const databaseStore = useDatabaseStore()
@ -109,28 +109,6 @@ const mainList = [{
<template>
<div class="app-layout" :class="{ 'use-top-bar': layoutSettings.useTopBar }">
<div class="debug-panel" >
<a-float-button
@click="layoutSettings.showDebug = !layoutSettings.showDebug"
tooltip="调试面板"
:style="{
right: '12px',
}"
>
<template #icon>
<BugOutlined />
</template>
</a-float-button>
<a-drawer
v-model:open="layoutSettings.showDebug"
title="调试面板"
width="800"
:contentWrapperStyle="{ maxWidth: '100%'}"
placement="right"
>
<DebugComponent />
</a-drawer>
</div>
<div class="header" :class="{ 'top-bar': layoutSettings.useTopBar }">
<div class="logo circle">
<router-link to="/">
@ -159,7 +137,10 @@ const mainList = [{
</div>
</a-tooltip>
</div>
<div class="fill" style="flex-grow: 1;"></div>
<div
ref="htmlRefHook"
class="fill debug-trigger"
></div>
<div class="github nav-item">
@ -208,6 +189,20 @@ const mainList = [{
</keep-alive>
<component :is="Component" v-else />
</router-view>
<!-- Debug Modal -->
<a-modal
v-model:open="showDebugModal"
title="调试面板"
width="90%"
:footer="null"
@cancel="handleDebugModalClose"
:maskClosable="true"
:destroyOnClose="true"
class="debug-modal"
>
<DebugComponent />
</a-modal>
</div>
</template>
@ -261,6 +256,25 @@ div.header, #app-router-view {
width: var(--header-width);
border-right: 1px solid var(--gray-300);
.nav {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
position: relative;
// height: 45px;
gap: 16px;
}
// debug
.debug-trigger {
position: relative;
height: 100%;
width: 100%;
min-height: 20px;
flex-grow: 1;
}
.logo {
width: 40px;
height: 40px;
@ -373,15 +387,6 @@ div.header, #app-router-view {
}
}
.header .nav {
display: flex;
flex-direction: column;
justify-content: space-between;
align-items: center;
position: relative;
height: 45px;
gap: 16px;
}
@media (max-width: 520px) {
.app-layout {