feat(ui): 实现普通用户和管理员差异化权限显示

- 知识库API区分:管理员调用getDatabases,普通用户调用getAccessibleDatabases
- UI权限控制:隐藏普通用户的顶部导航栏、知识库管理、任务中心等管理员功能
- 路由调整:普通用户可访问/agent页面,移除requiresAdmin限制
- 代码优化:删除AgentSingleView.vue,合并到AgentView.vue统一处理
- 错误处理增强:AppLayout中增加配置和知识库加载的try-catch
This commit is contained in:
Wenjie Zhang 2026-03-17 00:58:56 +08:00
parent 3eac205c0e
commit 1af5b33ccc
11 changed files with 248 additions and 481 deletions

View File

@ -1,4 +1,4 @@
import { apiAdminGet, apiAdminPost, apiAdminPut, apiAdminDelete, apiRequest } from './base'
import { apiGet, apiAdminGet, apiAdminPost, apiAdminPut, apiAdminDelete, apiRequest } from './base'
/**
* 知识库管理API模块
@ -75,7 +75,7 @@ export const databaseApi = {
* @returns {Promise} - 可访问的知识库列表
*/
getAccessibleDatabases: async () => {
return apiAdminGet('/api/knowledge/databases/accessible')
return apiGet('/api/knowledge/databases/accessible')
}
}

View File

@ -27,10 +27,22 @@
<div class="chat-header">
<div class="header__left">
<slot name="header-left" class="nav-btn"></slot>
<div
v-if="!chatUIStore.isSidebarOpen && !userStore.isAdmin"
type="button"
class="sidebar-logo-toggle"
@click="toggleSidebar"
>
<img v-if="sidebarLogo" :src="sidebarLogo" alt="logo" class="sidebar-logo-image" />
<PanelLeftOpen v-else class="sidebar-logo-fallback" size="18" />
<div class="sidebar-expand-overlay">
<PanelLeftOpen class="nav-btn-icon" size="18" />
</div>
</div>
<div
type="button"
class="agent-nav-btn"
v-if="!chatUIStore.isSidebarOpen"
v-if="!chatUIStore.isSidebarOpen && userStore.isAdmin"
@click="toggleSidebar"
>
<PanelLeftOpen class="nav-btn-icon" size="18" />
@ -52,6 +64,7 @@
</div>
</div>
<div class="header__right">
<UserInfoComponent v-if="!userStore.isAdmin" />
<!-- AgentState 显示按钮已移动到输入框底部 -->
<slot name="header-right"></slot>
</div>
@ -219,6 +232,8 @@ import { ScrollController } from '@/utils/scrollController'
import { AgentValidator } from '@/utils/agentValidator'
import { useAgentStore } from '@/stores/agent'
import { useChatUIStore } from '@/stores/chatUI'
import { useInfoStore } from '@/stores/info'
import { useUserStore } from '@/stores/user'
import { storeToRefs } from 'pinia'
import { MessageProcessor } from '@/utils/messageProcessor'
import { agentApi, threadApi } from '@/apis'
@ -226,6 +241,7 @@ import HumanApprovalModal from '@/components/HumanApprovalModal.vue'
import { useApproval } from '@/composables/useApproval'
import { useAgentStreamHandler } from '@/composables/useAgentStreamHandler'
import AgentPanel from '@/components/AgentPanel.vue'
import UserInfoComponent from '@/components/UserInfoComponent.vue'
// ==================== PROPS & EMITS ====================
const props = defineProps({
@ -236,6 +252,8 @@ const props = defineProps({
// ==================== STORE MANAGEMENT ====================
const agentStore = useAgentStore()
const chatUIStore = useChatUIStore()
const infoStore = useInfoStore()
const userStore = useUserStore()
const {
agents,
selectedAgentId,
@ -247,9 +265,11 @@ const {
availableMcps,
availableSkills
} = storeToRefs(agentStore)
const { organization } = storeToRefs(infoStore)
// ==================== LOCAL CHAT & UI STATE ====================
const userInput = ref('')
const sidebarLogo = computed(() => organization.value?.logo || organization.value?.avatar || '')
const useRunsApi =
import.meta.env.VITE_USE_RUNS_API === 'true' &&
localStorage.getItem('force_legacy_stream') !== 'true'
@ -1910,6 +1930,7 @@ watch(
.header__right {
display: flex;
align-items: center;
gap: 8px;
}
.switch-icon {
@ -1920,6 +1941,48 @@ watch(
.agent-nav-btn:hover .switch-icon {
color: var(--main-500);
}
.sidebar-logo-toggle {
position: relative;
width: 32px;
height: 32px;
border-radius: 8px;
border: 1px solid var(--gray-150);
background: var(--gray-0);
display: flex;
align-items: center;
justify-content: center;
overflow: hidden;
cursor: pointer;
flex-shrink: 0;
}
.sidebar-logo-image {
width: 24px;
height: 24px;
border-radius: 6px;
object-fit: cover;
}
.sidebar-logo-fallback {
color: var(--gray-700);
}
.sidebar-expand-overlay {
position: absolute;
inset: 0;
display: flex;
align-items: center;
justify-content: center;
background: var(--gray-100);
color: var(--gray-900);
opacity: 0;
transition: opacity 0.2s ease;
}
.sidebar-logo-toggle:hover .sidebar-expand-overlay {
opacity: 1;
}
}
}

View File

@ -5,7 +5,15 @@
>
<div class="sidebar-content">
<div class="sidebar-header">
<div class="header-title">{{ branding.name }}</div>
<div class="header-brand">
<img
v-if="!userStore.isAdmin && (organization.logo || organization.avatar)"
:src="organization.logo || organization.avatar"
alt="logo"
class="brand-logo"
/>
<div class="header-title">{{ branding.name || organization.name || 'Yuxi-Know' }}</div>
</div>
<div class="header-actions">
<div
class="toggle-sidebar nav-btn"
@ -96,7 +104,7 @@
</template>
<script setup>
import { computed, h, ref } from 'vue'
import { computed, h } from 'vue'
import { message, Modal } from 'ant-design-vue'
import {
PanelLeftClose,
@ -111,13 +119,15 @@ import {
import dayjs, { parseToShanghai } from '@/utils/time'
import { useChatUIStore } from '@/stores/chatUI'
import { useInfoStore } from '@/stores/info'
import { useUserStore } from '@/stores/user'
import { storeToRefs } from 'pinia'
// 使 chatUI store
const chatUIStore = useChatUIStore()
const infoStore = useInfoStore()
const userStore = useUserStore()
const { branding } = storeToRefs(infoStore)
const { branding, organization } = storeToRefs(infoStore)
const props = defineProps({
currentAgentId: {
@ -300,6 +310,24 @@ const togglePin = (chatId) => {
border-bottom: 1px solid var(--gray-50);
flex-shrink: 0;
.header-brand {
display: flex;
align-items: center;
gap: 8px;
min-width: 0;
flex: 1;
}
.brand-logo {
width: 26px;
height: 26px;
border-radius: 6px;
object-fit: cover;
flex-shrink: 0;
border: 1px solid var(--gray-100);
background: var(--gray-0);
}
.header-title {
font-weight: 600;
font-size: 16px;

View File

@ -47,12 +47,21 @@ const handleDebugModalClose = () => {
showDebugModal.value = false
}
const getRemoteConfig = () => {
configStore.refreshConfig()
const getRemoteConfig = async () => {
if (!userStore.isAdmin) return
try {
await configStore.refreshConfig()
} catch (error) {
console.warn('加载系统配置失败:', error)
}
}
const getRemoteDatabase = () => {
databaseStore.loadDatabases()
const getRemoteDatabase = async () => {
try {
await databaseStore.loadDatabases()
} catch (error) {
console.warn('加载知识库列表失败:', error)
}
}
// Fetch GitHub stars count
@ -73,12 +82,14 @@ const fetchGithubStars = async () => {
onMounted(async () => {
//
await infoStore.loadInfoConfig()
//
getRemoteConfig()
getRemoteDatabase()
fetchGithubStars() // Fetch GitHub stars on mount
//
taskerStore.loadTasks()
// 访
await getRemoteDatabase()
//
if (userStore.isAdmin) {
await getRemoteConfig()
taskerStore.loadTasks()
fetchGithubStars() // Fetch GitHub stars on mount
}
})
// 使 vue3 setup composition API
@ -95,37 +106,42 @@ const mainList = computed(() => {
path: '/agent',
icon: Bot,
activeIcon: Bot
},
{
name: '图谱',
path: '/graph',
icon: Waypoints,
activeIcon: Waypoints
},
{
name: '知识库',
path: '/database',
icon: LibraryBig,
activeIcon: LibraryBig
}
]
if (userStore.isSuperAdmin) {
if (userStore.isAdmin) {
items.push(
{
name: '图谱',
path: '/graph',
icon: Waypoints,
activeIcon: Waypoints
},
{
name: '知识库',
path: '/database',
icon: LibraryBig,
activeIcon: LibraryBig
}
)
if (userStore.isSuperAdmin) {
items.push({
name: '扩展管理',
path: '/extensions',
icon: Blocks,
activeIcon: Blocks
})
}
items.push({
name: '扩展管理',
path: '/extensions',
icon: Blocks,
activeIcon: Blocks
name: 'Dashboard',
path: '/dashboard',
icon: BarChart3,
activeIcon: BarChart3
})
}
items.push({
name: 'Dashboard',
path: '/dashboard',
icon: BarChart3,
activeIcon: BarChart3
})
return items
})
@ -137,7 +153,7 @@ provide('settingsModal', {
<template>
<div class="app-layout" :class="{ 'use-top-bar': layoutSettings.useTopBar }">
<div class="header" :class="{ 'top-bar': layoutSettings.useTopBar }">
<div v-if="userStore.isAdmin" class="header" :class="{ 'top-bar': layoutSettings.useTopBar }">
<div class="logo circle">
<router-link to="/">
<img :src="infoStore.organization.avatar" />
@ -163,6 +179,7 @@ provide('settingsModal', {
</a-tooltip>
</RouterLink>
<div
v-if="userStore.isAdmin"
class="nav-item task-center"
:class="{ active: isDrawerOpen }"
@click="taskerStore.openDrawer()"
@ -217,7 +234,7 @@ provide('settingsModal', {
>
<DebugComponent />
</a-modal>
<TaskCenterDrawer />
<TaskCenterDrawer v-if="userStore.isAdmin" />
<SettingsModal v-model:visible="showSettingsModal" @close="() => (showSettingsModal = false)" />
</div>
</template>

View File

@ -35,16 +35,16 @@ const router = createRouter({
path: '',
name: 'AgentComp',
component: () => import('../views/AgentView.vue'),
meta: { keepAlive: true, requiresAuth: true, requiresAdmin: true }
meta: { keepAlive: true, requiresAuth: true }
},
{
path: ':agent_id',
name: 'AgentCompWithId',
component: () => import('../views/AgentView.vue'),
meta: { keepAlive: true, requiresAuth: true }
}
]
},
{
path: '/agent/:agent_id',
name: 'AgentSinglePage',
component: () => import('../views/AgentSingleView.vue'),
meta: { requiresAuth: true }
},
{
path: '/graph',
name: 'graph',
@ -173,13 +173,13 @@ router.beforeEach(async (to, from, next) => {
if (agentIds.length > 0) {
next(`/agent/${agentIds[0]}`)
} else {
// 没有可用的智能体,跳转到
next('/')
// 没有可用的智能体,跳转到聊天
next('/agent')
}
}
} catch (error) {
console.error('获取智能体信息失败:', error)
next('/')
next('/agent')
}
return
}
@ -195,11 +195,11 @@ router.beforeEach(async (to, from, next) => {
if (defaultAgent && defaultAgent.id) {
next(`/agent/${defaultAgent.id}`)
} else {
next('/')
next('/agent')
}
} catch (error) {
console.error('获取智能体信息失败:', error)
next('/')
next('/agent')
}
return
}

View File

@ -39,11 +39,11 @@ export const useConfigStore = defineStore('config', () => {
})
}
function refreshConfig() {
configApi.getConfig().then((data) => {
console.log('config', data)
setConfig(data)
})
async function refreshConfig() {
const data = await configApi.getConfig()
console.log('config', data)
setConfig(data)
return data
}
return { config, setConfig, setConfigValue, refreshConfig, setConfigValues }

View File

@ -3,12 +3,14 @@ import { ref, reactive } from 'vue'
import { message, Modal } from 'ant-design-vue'
import { databaseApi, documentApi, queryApi } from '@/apis/knowledge_api'
import { useTaskerStore } from '@/stores/tasker'
import { useUserStore } from '@/stores/user'
import { useRouter } from 'vue-router'
import { parseToShanghai } from '@/utils/time'
export const useDatabaseStore = defineStore('database', () => {
const router = useRouter()
const taskerStore = useTaskerStore()
const userStore = useUserStore()
// State
const databases = ref([])
@ -41,11 +43,15 @@ export const useDatabaseStore = defineStore('database', () => {
let autoRefreshManualOverride = false // Indicates user explicitly disabled auto-refresh
// Actions
// 管理员获取所有知识库,普通用户获取有权限访问的知识库
async function loadDatabases() {
state.listLoading = true
try {
const data = await databaseApi.getDatabases()
databases.value = data.databases.sort((a, b) => {
const data = userStore.isAdmin
? await databaseApi.getDatabases()
: await databaseApi.getAccessibleDatabases()
const list = data?.databases || []
databases.value = list.sort((a, b) => {
const timeA = parseToShanghai(a.created_at)
const timeB = parseToShanghai(b.created_at)
if (!timeA && !timeB) return 0

View File

@ -2,6 +2,7 @@ import { defineStore } from 'pinia'
import { computed, ref } from 'vue'
import { message } from 'ant-design-vue'
import { taskerApi } from '@/apis/tasker'
import { useUserStore } from '@/stores/user'
import { parseToShanghai } from '@/utils/time'
const ACTIVE_STATUSES = new Set(['pending', 'running', 'queued'])
@ -32,6 +33,7 @@ const toTask = (raw = {}) => ({
})
export const useTaskerStore = defineStore('tasker', () => {
const userStore = useUserStore()
const tasks = ref([])
const loading = ref(false)
const lastError = ref(null)
@ -80,6 +82,13 @@ export const useTaskerStore = defineStore('tasker', () => {
}
async function loadTasks(params = {}) {
if (!userStore.isAdmin) {
tasks.value = []
summary.value = createDefaultSummary()
lastError.value = null
return
}
loading.value = true
lastError.value = null
try {

View File

@ -1,376 +0,0 @@
<template>
<div class="agent-single-view">
<!-- 智能体选择弹窗 -->
<a-modal
v-model:open="agentModalOpen"
title="选择智能体"
:width="800"
:footer="null"
:maskClosable="true"
class="agent-modal"
>
<div class="agent-modal-content">
<div class="agents-grid">
<div
v-for="agent in agents"
:key="agent.id"
class="agent-card"
:class="{ selected: agent.id === agentId }"
@click="selectAgentFromModal(agent.id)"
>
<div class="agent-card-header">
<div class="agent-card-title">
<span class="agent-card-name">{{ agent.name || 'Unknown' }}</span>
</div>
<StarFilled v-if="agent.id === defaultAgentId" class="default-icon" />
<StarOutlined
v-else
@click.prevent="setAsDefaultAgent(agent.id)"
class="default-icon"
/>
</div>
<div class="agent-card-description">
{{ agent.description || '' }}
</div>
</div>
</div>
</div>
</a-modal>
<!-- 智能体聊天界面 -->
<AgentChatComponent ref="chatComponentRef" :agent-id="agentId" :single-mode="true">
<template #header-left>
<div type="button" class="agent-nav-btn" @click="openAgentModal">
<span class="text">{{ currentAgentName || '选择智能体' }}</span>
<ChevronDown size="16" class="switch-icon" />
</div>
</template>
<template #header-right>
<div type="button" class="agent-nav-btn" @click="handleShareChat">
<Share2 size="18" class="nav-btn-icon" />
<span class="text">分享</span>
</div>
<UserInfoComponent />
</template>
</AgentChatComponent>
</div>
</template>
<script setup>
import { computed, ref, onMounted } from 'vue'
import { message } from 'ant-design-vue'
import { useRoute, useRouter } from 'vue-router'
import { Share2, ChevronDown } from 'lucide-vue-next'
import { StarFilled, StarOutlined } from '@ant-design/icons-vue'
import AgentChatComponent from '@/components/AgentChatComponent.vue'
import UserInfoComponent from '@/components/UserInfoComponent.vue'
import { ChatExporter } from '@/utils/chatExporter'
import { handleChatError } from '@/utils/errorHandler'
import { useAgentStore } from '@/stores/agent'
import { storeToRefs } from 'pinia'
const route = useRoute()
const router = useRouter()
const agentStore = useAgentStore()
const agentId = computed(() => route.params.agent_id)
const chatComponentRef = ref(null)
//
const agentModalOpen = ref(false)
// store
const { agents, defaultAgentId } = storeToRefs(agentStore)
//
const currentAgentName = computed(() => {
if (!agentId.value || !agents.value?.length) return '智能体加载中……'
const agent = agents.value.find((a) => a.id === agentId.value)
return agent ? agent.name : '未知智能体'
})
//
const openAgentModal = () => {
agentModalOpen.value = true
}
// -
const selectAgentFromModal = (newAgentId) => {
if (newAgentId === agentId.value) {
//
agentModalOpen.value = false
return
}
//
router.push(`/agent/${newAgentId}`)
agentModalOpen.value = false
}
//
const setAsDefaultAgent = async (agentIdToSet) => {
try {
await agentStore.setDefaultAgent(agentIdToSet)
message.success('已设置为默认智能体')
} catch (error) {
handleChatError(error, 'save')
}
}
const handleShareChat = async () => {
try {
const exportData = chatComponentRef.value?.getExportPayload?.()
if (!exportData) {
message.warning('当前没有可导出的对话内容')
return
}
const hasMessages = Boolean(exportData.messages?.length)
const hasOngoingMessages = Boolean(exportData.onGoingMessages?.length)
if (!hasMessages && !hasOngoingMessages) {
message.warning('当前对话暂无内容可导出,请先进行对话')
return
}
const result = await ChatExporter.exportToHTML(exportData)
message.success(`对话已导出为HTML文件: ${result.filename}`)
} catch (error) {
if (error?.message?.includes('没有可导出的对话内容')) {
message.warning('当前对话暂无内容可导出,请先进行对话')
return
}
handleChatError(error, 'export')
}
}
// store
onMounted(async () => {
if (!agentStore.isInitialized) {
try {
await agentStore.initialize()
} catch (error) {
console.error('初始化智能体 store 失败:', error)
}
}
})
</script>
<style lang="less" scoped>
.agent-single-view {
width: 100%;
height: 100vh;
overflow: hidden;
position: relative;
display: flex;
flex-direction: row;
}
.user-info-wrapper {
position: absolute;
top: 10px;
right: 20px;
z-index: 10;
}
//
.agent-modal {
:deep(.ant-modal-content) {
border-radius: 8px;
overflow: hidden;
}
:deep(.ant-modal-header) {
background: var(--gray-0);
border-bottom: 1px solid var(--gray-200);
padding: 16px 20px;
.ant-modal-title {
font-weight: 600;
color: var(--gray-900);
}
}
:deep(.ant-modal-body) {
padding: 20px;
background: var(--gray-0);
}
.agent-modal-content {
.agents-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
gap: 12px;
max-height: 500px;
overflow-y: auto;
}
.agent-card {
border: 1px solid var(--gray-200);
border-radius: 8px;
padding: 16px;
cursor: pointer;
transition: all 0.2s ease;
background: var(--gray-0);
&:hover {
border-color: var(--main-color);
}
.agent-card-header {
display: flex;
justify-content: space-between;
align-items: flex-start;
margin-bottom: 12px;
.agent-card-title {
flex: 1;
.agent-card-name {
font-size: 16px;
font-weight: 600;
color: var(--gray-900);
line-height: 1.4;
}
}
.default-icon {
color: var(--color-warning-500);
font-size: 16px;
flex-shrink: 0;
margin-left: 8px;
cursor: pointer;
&:hover {
color: var(--color-warning-600);
}
}
}
.agent-card-description {
font-size: 14px;
color: var(--gray-700);
line-height: 1.5;
word-break: break-word;
white-space: pre-wrap;
}
&.selected {
border-color: var(--main-color);
background: var(--main-20);
.agent-card-header .agent-card-title .agent-card-name {
color: var(--main-color);
}
.agent-card-description {
color: var(--gray-900);
}
}
}
}
}
//
@media (max-width: 768px) {
.agent-modal {
.agent-modal-content {
.agents-grid {
grid-template-columns: 1fr;
}
}
}
}
//
.sidebar {
// position: absolute;
left: 0;
top: 0;
height: 100%;
width: 240px;
background-color: var(--gray-50);
transition: all 0.3s ease;
z-index: 20;
display: flex;
&.collapsed {
width: 60px;
}
.sidebar-content {
flex: 1;
padding: 20px 10px;
overflow-y: auto;
}
.user-icon {
cursor: pointer;
margin-bottom: 20px;
padding-left: 4px 8px;
img {
width: 32px;
height: 32px;
}
}
.toggle-button {
position: absolute;
right: -15px;
top: 50%;
transform: translateY(-50%);
width: 30px;
height: 30px;
background-color: var(--gray-0);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
img {
width: 16px;
height: 16px;
}
}
}
</style>
<style lang="less">
.agent-nav-btn {
display: flex;
gap: 6px;
padding: 6px 8px;
justify-content: center;
align-items: center;
border-radius: 6px;
color: var(--gray-900);
cursor: pointer;
width: auto;
font-size: 15px;
transition: background-color 0.3s;
border: none;
background: transparent;
&:hover {
background-color: var(--gray-50);
}
.nav-btn-icon {
height: 24px;
}
.switch-icon {
color: var(--gray-500);
transition: all 0.2s ease;
}
&:hover .switch-icon {
color: var(--main-500);
}
}
</style>

View File

@ -82,7 +82,7 @@
</a-dropdown>
</template>
<template #header-right>
<template #header-right v-if="userStore.isAdmin">
<div
v-if="selectedAgentId"
ref="moreButtonRef"
@ -103,13 +103,13 @@
/>
<!-- 反馈模态框 -->
<FeedbackModalComponent ref="feedbackModal" :agent-id="selectedAgentId" />
<FeedbackModalComponent v-if="userStore.isAdmin" ref="feedbackModal" :agent-id="selectedAgentId" />
<!-- 自定义更多菜单 -->
<Teleport to="body">
<Transition name="menu-fade">
<div
v-if="chatUIStore.moreMenuOpen"
v-if="userStore.isAdmin && chatUIStore.moreMenuOpen"
ref="moreMenuRef"
class="more-popup-menu"
:style="{
@ -125,10 +125,6 @@
<MessageOutlined class="menu-icon" />
<span class="menu-text">查看反馈</span>
</div>
<div class="menu-item" @click="handlePreview">
<EyeOutlined class="menu-icon" />
<span class="menu-text">预览页面</span>
</div>
</div>
</Transition>
</Teleport>
@ -137,10 +133,11 @@
</template>
<script setup>
import { ref } from 'vue'
import { MessageOutlined, ShareAltOutlined, EyeOutlined } from '@ant-design/icons-vue'
import { ref, watch } from 'vue'
import { MessageOutlined, ShareAltOutlined } from '@ant-design/icons-vue'
import { message } from 'ant-design-vue'
import { Settings2, Ellipsis, ChevronDown, Star, CirclePlus, SquarePen } from 'lucide-vue-next'
import { useRoute, useRouter } from 'vue-router'
import AgentChatComponent from '@/components/AgentChatComponent.vue'
import AgentConfigSidebar from '@/components/AgentConfigSidebar.vue'
import FeedbackModalComponent from '@/components/dashboard/FeedbackModalComponent.vue'
@ -162,12 +159,65 @@ const configDropdownOpen = ref(false)
const userStore = useUserStore()
const agentStore = useAgentStore()
const chatUIStore = useChatUIStore()
const route = useRoute()
const router = useRouter()
// agentStore
const { selectedAgentId, agentConfigs, selectedAgentConfigId, selectedConfigSummary } =
const { agents, selectedAgentId, agentConfigs, selectedAgentConfigId, selectedConfigSummary } =
storeToRefs(agentStore)
const syncingRouteAgent = ref(false)
const getRouteAgentId = () => {
const value = route.params.agent_id
return typeof value === 'string' ? value : ''
}
const syncSelectedAgentFromRoute = async () => {
const routeAgentId = getRouteAgentId()
if (!routeAgentId) return
syncingRouteAgent.value = true
try {
if (!agentStore.isInitialized) {
await agentStore.initialize()
}
const routeAgentExists = (agents.value || []).some((agent) => agent.id === routeAgentId)
if (!routeAgentExists) {
if (selectedAgentId.value) {
await router.replace({ name: 'AgentCompWithId', params: { agent_id: selectedAgentId.value } })
}
return
}
if (selectedAgentId.value !== routeAgentId) {
await agentStore.selectAgent(routeAgentId)
}
} catch (error) {
handleChatError(error, 'load')
} finally {
syncingRouteAgent.value = false
}
}
watch(
() => route.params.agent_id,
() => {
syncSelectedAgentFromRoute()
},
{ immediate: true }
)
watch(selectedAgentId, (newAgentId) => {
if (!newAgentId || syncingRouteAgent.value) return
const routeAgentId = getRouteAgentId()
if (routeAgentId === newAgentId) return
router.replace({ name: 'AgentCompWithId', params: { agent_id: newAgentId } })
})
const openConfigSidebar = () => {
configDropdownOpen.value = false
chatUIStore.isConfigSidebarOpen = true
}
@ -176,6 +226,7 @@ const createConfigLoading = ref(false)
const createConfigName = ref('')
const openCreateConfigModal = () => {
configDropdownOpen.value = false
createConfigName.value = ''
createConfigModalOpen.value = true
}
@ -288,13 +339,6 @@ const handleFeedback = () => {
closeMoreMenu()
feedbackModal.value?.show()
}
const handlePreview = () => {
closeMoreMenu()
if (selectedAgentId.value) {
window.open(`/agent/${selectedAgentId.value}`, '_blank')
}
}
</script>
<style lang="less" scoped>

View File

@ -414,37 +414,13 @@ const handleLogin = async () => {
//
if (redirectPath === '/') {
// /chat
if (userStore.isAdmin) {
//
try {
await agentStore.initialize()
router.push('/agent')
return
}
//
try {
// agentStore
await agentStore.initialize()
//
if (agentStore.defaultAgentId) {
//
router.push(`/agent/${agentStore.defaultAgentId}`)
return
}
//
const agentIds = Object.keys(agentStore.agents)
if (agentIds.length > 0) {
router.push(`/agent/${agentIds[0]}`)
return
}
// 退
router.push('/')
} catch (error) {
console.error('获取智能体信息失败:', error)
router.push('/')
router.push('/agent')
}
} else {
//