feat(ui): 实现普通用户和管理员差异化权限显示
- 知识库API区分:管理员调用getDatabases,普通用户调用getAccessibleDatabases - UI权限控制:隐藏普通用户的顶部导航栏、知识库管理、任务中心等管理员功能 - 路由调整:普通用户可访问/agent页面,移除requiresAdmin限制 - 代码优化:删除AgentSingleView.vue,合并到AgentView.vue统一处理 - 错误处理增强:AppLayout中增加配置和知识库加载的try-catch
This commit is contained in:
parent
3eac205c0e
commit
1af5b33ccc
@ -1,4 +1,4 @@
|
|||||||
import { apiAdminGet, apiAdminPost, apiAdminPut, apiAdminDelete, apiRequest } from './base'
|
import { apiGet, apiAdminGet, apiAdminPost, apiAdminPut, apiAdminDelete, apiRequest } from './base'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 知识库管理API模块
|
* 知识库管理API模块
|
||||||
@ -75,7 +75,7 @@ export const databaseApi = {
|
|||||||
* @returns {Promise} - 可访问的知识库列表
|
* @returns {Promise} - 可访问的知识库列表
|
||||||
*/
|
*/
|
||||||
getAccessibleDatabases: async () => {
|
getAccessibleDatabases: async () => {
|
||||||
return apiAdminGet('/api/knowledge/databases/accessible')
|
return apiGet('/api/knowledge/databases/accessible')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -27,10 +27,22 @@
|
|||||||
<div class="chat-header">
|
<div class="chat-header">
|
||||||
<div class="header__left">
|
<div class="header__left">
|
||||||
<slot name="header-left" class="nav-btn"></slot>
|
<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
|
<div
|
||||||
type="button"
|
type="button"
|
||||||
class="agent-nav-btn"
|
class="agent-nav-btn"
|
||||||
v-if="!chatUIStore.isSidebarOpen"
|
v-if="!chatUIStore.isSidebarOpen && userStore.isAdmin"
|
||||||
@click="toggleSidebar"
|
@click="toggleSidebar"
|
||||||
>
|
>
|
||||||
<PanelLeftOpen class="nav-btn-icon" size="18" />
|
<PanelLeftOpen class="nav-btn-icon" size="18" />
|
||||||
@ -52,6 +64,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="header__right">
|
<div class="header__right">
|
||||||
|
<UserInfoComponent v-if="!userStore.isAdmin" />
|
||||||
<!-- AgentState 显示按钮已移动到输入框底部 -->
|
<!-- AgentState 显示按钮已移动到输入框底部 -->
|
||||||
<slot name="header-right"></slot>
|
<slot name="header-right"></slot>
|
||||||
</div>
|
</div>
|
||||||
@ -219,6 +232,8 @@ import { ScrollController } from '@/utils/scrollController'
|
|||||||
import { AgentValidator } from '@/utils/agentValidator'
|
import { AgentValidator } from '@/utils/agentValidator'
|
||||||
import { useAgentStore } from '@/stores/agent'
|
import { useAgentStore } from '@/stores/agent'
|
||||||
import { useChatUIStore } from '@/stores/chatUI'
|
import { useChatUIStore } from '@/stores/chatUI'
|
||||||
|
import { useInfoStore } from '@/stores/info'
|
||||||
|
import { useUserStore } from '@/stores/user'
|
||||||
import { storeToRefs } from 'pinia'
|
import { storeToRefs } from 'pinia'
|
||||||
import { MessageProcessor } from '@/utils/messageProcessor'
|
import { MessageProcessor } from '@/utils/messageProcessor'
|
||||||
import { agentApi, threadApi } from '@/apis'
|
import { agentApi, threadApi } from '@/apis'
|
||||||
@ -226,6 +241,7 @@ import HumanApprovalModal from '@/components/HumanApprovalModal.vue'
|
|||||||
import { useApproval } from '@/composables/useApproval'
|
import { useApproval } from '@/composables/useApproval'
|
||||||
import { useAgentStreamHandler } from '@/composables/useAgentStreamHandler'
|
import { useAgentStreamHandler } from '@/composables/useAgentStreamHandler'
|
||||||
import AgentPanel from '@/components/AgentPanel.vue'
|
import AgentPanel from '@/components/AgentPanel.vue'
|
||||||
|
import UserInfoComponent from '@/components/UserInfoComponent.vue'
|
||||||
|
|
||||||
// ==================== PROPS & EMITS ====================
|
// ==================== PROPS & EMITS ====================
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
@ -236,6 +252,8 @@ const props = defineProps({
|
|||||||
// ==================== STORE MANAGEMENT ====================
|
// ==================== STORE MANAGEMENT ====================
|
||||||
const agentStore = useAgentStore()
|
const agentStore = useAgentStore()
|
||||||
const chatUIStore = useChatUIStore()
|
const chatUIStore = useChatUIStore()
|
||||||
|
const infoStore = useInfoStore()
|
||||||
|
const userStore = useUserStore()
|
||||||
const {
|
const {
|
||||||
agents,
|
agents,
|
||||||
selectedAgentId,
|
selectedAgentId,
|
||||||
@ -247,9 +265,11 @@ const {
|
|||||||
availableMcps,
|
availableMcps,
|
||||||
availableSkills
|
availableSkills
|
||||||
} = storeToRefs(agentStore)
|
} = storeToRefs(agentStore)
|
||||||
|
const { organization } = storeToRefs(infoStore)
|
||||||
|
|
||||||
// ==================== LOCAL CHAT & UI STATE ====================
|
// ==================== LOCAL CHAT & UI STATE ====================
|
||||||
const userInput = ref('')
|
const userInput = ref('')
|
||||||
|
const sidebarLogo = computed(() => organization.value?.logo || organization.value?.avatar || '')
|
||||||
const useRunsApi =
|
const useRunsApi =
|
||||||
import.meta.env.VITE_USE_RUNS_API === 'true' &&
|
import.meta.env.VITE_USE_RUNS_API === 'true' &&
|
||||||
localStorage.getItem('force_legacy_stream') !== 'true'
|
localStorage.getItem('force_legacy_stream') !== 'true'
|
||||||
@ -1910,6 +1930,7 @@ watch(
|
|||||||
.header__right {
|
.header__right {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.switch-icon {
|
.switch-icon {
|
||||||
@ -1920,6 +1941,48 @@ watch(
|
|||||||
.agent-nav-btn:hover .switch-icon {
|
.agent-nav-btn:hover .switch-icon {
|
||||||
color: var(--main-500);
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -5,7 +5,15 @@
|
|||||||
>
|
>
|
||||||
<div class="sidebar-content">
|
<div class="sidebar-content">
|
||||||
<div class="sidebar-header">
|
<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="header-actions">
|
||||||
<div
|
<div
|
||||||
class="toggle-sidebar nav-btn"
|
class="toggle-sidebar nav-btn"
|
||||||
@ -96,7 +104,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { computed, h, ref } from 'vue'
|
import { computed, h } from 'vue'
|
||||||
import { message, Modal } from 'ant-design-vue'
|
import { message, Modal } from 'ant-design-vue'
|
||||||
import {
|
import {
|
||||||
PanelLeftClose,
|
PanelLeftClose,
|
||||||
@ -111,13 +119,15 @@ import {
|
|||||||
import dayjs, { parseToShanghai } from '@/utils/time'
|
import dayjs, { parseToShanghai } from '@/utils/time'
|
||||||
import { useChatUIStore } from '@/stores/chatUI'
|
import { useChatUIStore } from '@/stores/chatUI'
|
||||||
import { useInfoStore } from '@/stores/info'
|
import { useInfoStore } from '@/stores/info'
|
||||||
|
import { useUserStore } from '@/stores/user'
|
||||||
import { storeToRefs } from 'pinia'
|
import { storeToRefs } from 'pinia'
|
||||||
|
|
||||||
// 使用 chatUI store
|
// 使用 chatUI store
|
||||||
const chatUIStore = useChatUIStore()
|
const chatUIStore = useChatUIStore()
|
||||||
const infoStore = useInfoStore()
|
const infoStore = useInfoStore()
|
||||||
|
const userStore = useUserStore()
|
||||||
|
|
||||||
const { branding } = storeToRefs(infoStore)
|
const { branding, organization } = storeToRefs(infoStore)
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
currentAgentId: {
|
currentAgentId: {
|
||||||
@ -300,6 +310,24 @@ const togglePin = (chatId) => {
|
|||||||
border-bottom: 1px solid var(--gray-50);
|
border-bottom: 1px solid var(--gray-50);
|
||||||
flex-shrink: 0;
|
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 {
|
.header-title {
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
|
|||||||
@ -47,12 +47,21 @@ const handleDebugModalClose = () => {
|
|||||||
showDebugModal.value = false
|
showDebugModal.value = false
|
||||||
}
|
}
|
||||||
|
|
||||||
const getRemoteConfig = () => {
|
const getRemoteConfig = async () => {
|
||||||
configStore.refreshConfig()
|
if (!userStore.isAdmin) return
|
||||||
|
try {
|
||||||
|
await configStore.refreshConfig()
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('加载系统配置失败:', error)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const getRemoteDatabase = () => {
|
const getRemoteDatabase = async () => {
|
||||||
databaseStore.loadDatabases()
|
try {
|
||||||
|
await databaseStore.loadDatabases()
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('加载知识库列表失败:', error)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fetch GitHub stars count
|
// Fetch GitHub stars count
|
||||||
@ -73,12 +82,14 @@ const fetchGithubStars = async () => {
|
|||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
// 加载信息配置
|
// 加载信息配置
|
||||||
await infoStore.loadInfoConfig()
|
await infoStore.loadInfoConfig()
|
||||||
// 加载其他配置
|
// 加载知识库数据(普通用户加载可访问知识库)
|
||||||
getRemoteConfig()
|
await getRemoteDatabase()
|
||||||
getRemoteDatabase()
|
// 仅管理员加载系统配置和任务中心数据
|
||||||
fetchGithubStars() // Fetch GitHub stars on mount
|
if (userStore.isAdmin) {
|
||||||
// 预加载任务数据,确保任务中心打开时有内容
|
await getRemoteConfig()
|
||||||
taskerStore.loadTasks()
|
taskerStore.loadTasks()
|
||||||
|
fetchGithubStars() // Fetch GitHub stars on mount
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
// 打印当前页面的路由信息,使用 vue3 的 setup composition API
|
// 打印当前页面的路由信息,使用 vue3 的 setup composition API
|
||||||
@ -95,37 +106,42 @@ const mainList = computed(() => {
|
|||||||
path: '/agent',
|
path: '/agent',
|
||||||
icon: Bot,
|
icon: Bot,
|
||||||
activeIcon: 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({
|
items.push({
|
||||||
name: '扩展管理',
|
name: 'Dashboard',
|
||||||
path: '/extensions',
|
path: '/dashboard',
|
||||||
icon: Blocks,
|
icon: BarChart3,
|
||||||
activeIcon: Blocks
|
activeIcon: BarChart3
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
items.push({
|
|
||||||
name: 'Dashboard',
|
|
||||||
path: '/dashboard',
|
|
||||||
icon: BarChart3,
|
|
||||||
activeIcon: BarChart3
|
|
||||||
})
|
|
||||||
|
|
||||||
return items
|
return items
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -137,7 +153,7 @@ provide('settingsModal', {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="app-layout" :class="{ 'use-top-bar': layoutSettings.useTopBar }">
|
<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">
|
<div class="logo circle">
|
||||||
<router-link to="/">
|
<router-link to="/">
|
||||||
<img :src="infoStore.organization.avatar" />
|
<img :src="infoStore.organization.avatar" />
|
||||||
@ -163,6 +179,7 @@ provide('settingsModal', {
|
|||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</RouterLink>
|
</RouterLink>
|
||||||
<div
|
<div
|
||||||
|
v-if="userStore.isAdmin"
|
||||||
class="nav-item task-center"
|
class="nav-item task-center"
|
||||||
:class="{ active: isDrawerOpen }"
|
:class="{ active: isDrawerOpen }"
|
||||||
@click="taskerStore.openDrawer()"
|
@click="taskerStore.openDrawer()"
|
||||||
@ -217,7 +234,7 @@ provide('settingsModal', {
|
|||||||
>
|
>
|
||||||
<DebugComponent />
|
<DebugComponent />
|
||||||
</a-modal>
|
</a-modal>
|
||||||
<TaskCenterDrawer />
|
<TaskCenterDrawer v-if="userStore.isAdmin" />
|
||||||
<SettingsModal v-model:visible="showSettingsModal" @close="() => (showSettingsModal = false)" />
|
<SettingsModal v-model:visible="showSettingsModal" @close="() => (showSettingsModal = false)" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@ -35,16 +35,16 @@ const router = createRouter({
|
|||||||
path: '',
|
path: '',
|
||||||
name: 'AgentComp',
|
name: 'AgentComp',
|
||||||
component: () => import('../views/AgentView.vue'),
|
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',
|
path: '/graph',
|
||||||
name: 'graph',
|
name: 'graph',
|
||||||
@ -173,13 +173,13 @@ router.beforeEach(async (to, from, next) => {
|
|||||||
if (agentIds.length > 0) {
|
if (agentIds.length > 0) {
|
||||||
next(`/agent/${agentIds[0]}`)
|
next(`/agent/${agentIds[0]}`)
|
||||||
} else {
|
} else {
|
||||||
// 没有可用的智能体,跳转到首页
|
// 没有可用的智能体,跳转到聊天页
|
||||||
next('/')
|
next('/agent')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('获取智能体信息失败:', error)
|
console.error('获取智能体信息失败:', error)
|
||||||
next('/')
|
next('/agent')
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -195,11 +195,11 @@ router.beforeEach(async (to, from, next) => {
|
|||||||
if (defaultAgent && defaultAgent.id) {
|
if (defaultAgent && defaultAgent.id) {
|
||||||
next(`/agent/${defaultAgent.id}`)
|
next(`/agent/${defaultAgent.id}`)
|
||||||
} else {
|
} else {
|
||||||
next('/')
|
next('/agent')
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('获取智能体信息失败:', error)
|
console.error('获取智能体信息失败:', error)
|
||||||
next('/')
|
next('/agent')
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@ -39,11 +39,11 @@ export const useConfigStore = defineStore('config', () => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function refreshConfig() {
|
async function refreshConfig() {
|
||||||
configApi.getConfig().then((data) => {
|
const data = await configApi.getConfig()
|
||||||
console.log('config', data)
|
console.log('config', data)
|
||||||
setConfig(data)
|
setConfig(data)
|
||||||
})
|
return data
|
||||||
}
|
}
|
||||||
|
|
||||||
return { config, setConfig, setConfigValue, refreshConfig, setConfigValues }
|
return { config, setConfig, setConfigValue, refreshConfig, setConfigValues }
|
||||||
|
|||||||
@ -3,12 +3,14 @@ import { ref, reactive } from 'vue'
|
|||||||
import { message, Modal } from 'ant-design-vue'
|
import { message, Modal } from 'ant-design-vue'
|
||||||
import { databaseApi, documentApi, queryApi } from '@/apis/knowledge_api'
|
import { databaseApi, documentApi, queryApi } from '@/apis/knowledge_api'
|
||||||
import { useTaskerStore } from '@/stores/tasker'
|
import { useTaskerStore } from '@/stores/tasker'
|
||||||
|
import { useUserStore } from '@/stores/user'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
import { parseToShanghai } from '@/utils/time'
|
import { parseToShanghai } from '@/utils/time'
|
||||||
|
|
||||||
export const useDatabaseStore = defineStore('database', () => {
|
export const useDatabaseStore = defineStore('database', () => {
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const taskerStore = useTaskerStore()
|
const taskerStore = useTaskerStore()
|
||||||
|
const userStore = useUserStore()
|
||||||
|
|
||||||
// State
|
// State
|
||||||
const databases = ref([])
|
const databases = ref([])
|
||||||
@ -41,11 +43,15 @@ export const useDatabaseStore = defineStore('database', () => {
|
|||||||
let autoRefreshManualOverride = false // Indicates user explicitly disabled auto-refresh
|
let autoRefreshManualOverride = false // Indicates user explicitly disabled auto-refresh
|
||||||
|
|
||||||
// Actions
|
// Actions
|
||||||
|
// 管理员获取所有知识库,普通用户获取有权限访问的知识库
|
||||||
async function loadDatabases() {
|
async function loadDatabases() {
|
||||||
state.listLoading = true
|
state.listLoading = true
|
||||||
try {
|
try {
|
||||||
const data = await databaseApi.getDatabases()
|
const data = userStore.isAdmin
|
||||||
databases.value = data.databases.sort((a, b) => {
|
? await databaseApi.getDatabases()
|
||||||
|
: await databaseApi.getAccessibleDatabases()
|
||||||
|
const list = data?.databases || []
|
||||||
|
databases.value = list.sort((a, b) => {
|
||||||
const timeA = parseToShanghai(a.created_at)
|
const timeA = parseToShanghai(a.created_at)
|
||||||
const timeB = parseToShanghai(b.created_at)
|
const timeB = parseToShanghai(b.created_at)
|
||||||
if (!timeA && !timeB) return 0
|
if (!timeA && !timeB) return 0
|
||||||
|
|||||||
@ -2,6 +2,7 @@ import { defineStore } from 'pinia'
|
|||||||
import { computed, ref } from 'vue'
|
import { computed, ref } from 'vue'
|
||||||
import { message } from 'ant-design-vue'
|
import { message } from 'ant-design-vue'
|
||||||
import { taskerApi } from '@/apis/tasker'
|
import { taskerApi } from '@/apis/tasker'
|
||||||
|
import { useUserStore } from '@/stores/user'
|
||||||
import { parseToShanghai } from '@/utils/time'
|
import { parseToShanghai } from '@/utils/time'
|
||||||
|
|
||||||
const ACTIVE_STATUSES = new Set(['pending', 'running', 'queued'])
|
const ACTIVE_STATUSES = new Set(['pending', 'running', 'queued'])
|
||||||
@ -32,6 +33,7 @@ const toTask = (raw = {}) => ({
|
|||||||
})
|
})
|
||||||
|
|
||||||
export const useTaskerStore = defineStore('tasker', () => {
|
export const useTaskerStore = defineStore('tasker', () => {
|
||||||
|
const userStore = useUserStore()
|
||||||
const tasks = ref([])
|
const tasks = ref([])
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
const lastError = ref(null)
|
const lastError = ref(null)
|
||||||
@ -80,6 +82,13 @@ export const useTaskerStore = defineStore('tasker', () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function loadTasks(params = {}) {
|
async function loadTasks(params = {}) {
|
||||||
|
if (!userStore.isAdmin) {
|
||||||
|
tasks.value = []
|
||||||
|
summary.value = createDefaultSummary()
|
||||||
|
lastError.value = null
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
loading.value = true
|
loading.value = true
|
||||||
lastError.value = null
|
lastError.value = null
|
||||||
try {
|
try {
|
||||||
|
|||||||
@ -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>
|
|
||||||
@ -82,7 +82,7 @@
|
|||||||
</a-dropdown>
|
</a-dropdown>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template #header-right>
|
<template #header-right v-if="userStore.isAdmin">
|
||||||
<div
|
<div
|
||||||
v-if="selectedAgentId"
|
v-if="selectedAgentId"
|
||||||
ref="moreButtonRef"
|
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">
|
<Teleport to="body">
|
||||||
<Transition name="menu-fade">
|
<Transition name="menu-fade">
|
||||||
<div
|
<div
|
||||||
v-if="chatUIStore.moreMenuOpen"
|
v-if="userStore.isAdmin && chatUIStore.moreMenuOpen"
|
||||||
ref="moreMenuRef"
|
ref="moreMenuRef"
|
||||||
class="more-popup-menu"
|
class="more-popup-menu"
|
||||||
:style="{
|
:style="{
|
||||||
@ -125,10 +125,6 @@
|
|||||||
<MessageOutlined class="menu-icon" />
|
<MessageOutlined class="menu-icon" />
|
||||||
<span class="menu-text">查看反馈</span>
|
<span class="menu-text">查看反馈</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="menu-item" @click="handlePreview">
|
|
||||||
<EyeOutlined class="menu-icon" />
|
|
||||||
<span class="menu-text">预览页面</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</Transition>
|
</Transition>
|
||||||
</Teleport>
|
</Teleport>
|
||||||
@ -137,10 +133,11 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref } from 'vue'
|
import { ref, watch } from 'vue'
|
||||||
import { MessageOutlined, ShareAltOutlined, EyeOutlined } from '@ant-design/icons-vue'
|
import { MessageOutlined, ShareAltOutlined } from '@ant-design/icons-vue'
|
||||||
import { message } from 'ant-design-vue'
|
import { message } from 'ant-design-vue'
|
||||||
import { Settings2, Ellipsis, ChevronDown, Star, CirclePlus, SquarePen } from 'lucide-vue-next'
|
import { Settings2, Ellipsis, ChevronDown, Star, CirclePlus, SquarePen } from 'lucide-vue-next'
|
||||||
|
import { useRoute, useRouter } from 'vue-router'
|
||||||
import AgentChatComponent from '@/components/AgentChatComponent.vue'
|
import AgentChatComponent from '@/components/AgentChatComponent.vue'
|
||||||
import AgentConfigSidebar from '@/components/AgentConfigSidebar.vue'
|
import AgentConfigSidebar from '@/components/AgentConfigSidebar.vue'
|
||||||
import FeedbackModalComponent from '@/components/dashboard/FeedbackModalComponent.vue'
|
import FeedbackModalComponent from '@/components/dashboard/FeedbackModalComponent.vue'
|
||||||
@ -162,12 +159,65 @@ const configDropdownOpen = ref(false)
|
|||||||
const userStore = useUserStore()
|
const userStore = useUserStore()
|
||||||
const agentStore = useAgentStore()
|
const agentStore = useAgentStore()
|
||||||
const chatUIStore = useChatUIStore()
|
const chatUIStore = useChatUIStore()
|
||||||
|
const route = useRoute()
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
// 从 agentStore 中获取响应式状态
|
// 从 agentStore 中获取响应式状态
|
||||||
const { selectedAgentId, agentConfigs, selectedAgentConfigId, selectedConfigSummary } =
|
const { agents, selectedAgentId, agentConfigs, selectedAgentConfigId, selectedConfigSummary } =
|
||||||
storeToRefs(agentStore)
|
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 = () => {
|
const openConfigSidebar = () => {
|
||||||
|
configDropdownOpen.value = false
|
||||||
chatUIStore.isConfigSidebarOpen = true
|
chatUIStore.isConfigSidebarOpen = true
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -176,6 +226,7 @@ const createConfigLoading = ref(false)
|
|||||||
const createConfigName = ref('')
|
const createConfigName = ref('')
|
||||||
|
|
||||||
const openCreateConfigModal = () => {
|
const openCreateConfigModal = () => {
|
||||||
|
configDropdownOpen.value = false
|
||||||
createConfigName.value = ''
|
createConfigName.value = ''
|
||||||
createConfigModalOpen.value = true
|
createConfigModalOpen.value = true
|
||||||
}
|
}
|
||||||
@ -288,13 +339,6 @@ const handleFeedback = () => {
|
|||||||
closeMoreMenu()
|
closeMoreMenu()
|
||||||
feedbackModal.value?.show()
|
feedbackModal.value?.show()
|
||||||
}
|
}
|
||||||
|
|
||||||
const handlePreview = () => {
|
|
||||||
closeMoreMenu()
|
|
||||||
if (selectedAgentId.value) {
|
|
||||||
window.open(`/agent/${selectedAgentId.value}`, '_blank')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
|
|||||||
@ -414,37 +414,13 @@ const handleLogin = async () => {
|
|||||||
|
|
||||||
// 根据用户角色决定重定向目标
|
// 根据用户角色决定重定向目标
|
||||||
if (redirectPath === '/') {
|
if (redirectPath === '/') {
|
||||||
// 如果是管理员,直接跳转到/chat页面
|
// 统一跳转到聊天页面(管理员与普通用户共享同一聊天界面)
|
||||||
if (userStore.isAdmin) {
|
try {
|
||||||
await agentStore.initialize()
|
await agentStore.initialize()
|
||||||
router.push('/agent')
|
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) {
|
} catch (error) {
|
||||||
console.error('获取智能体信息失败:', error)
|
console.error('获取智能体信息失败:', error)
|
||||||
router.push('/')
|
router.push('/agent')
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// 跳转到其他预设的路径
|
// 跳转到其他预设的路径
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user