feat(agent-ui): 添加智能体选择弹窗并优化默认标记样式

- 在AgentSingleView中添加智能体选择弹窗功能
- 重构默认智能体标记的样式和位置
- 仅管理员显示系统设置菜单项
This commit is contained in:
Wenjie Zhang 2025-12-22 04:32:45 +08:00
parent 5f1f6be7c4
commit abf4414a0f
3 changed files with 259 additions and 25 deletions

View File

@ -29,8 +29,8 @@
<component :is="themeStore.isDark ? Sun : Moon" size="16"/>
<span class="menu-text">{{ themeStore.isDark ? '切换到浅色模式' : '切换到深色模式 (Beta)' }}</span>
</a-menu-item>
<a-menu-divider />
<a-menu-item key="setting" @click="goToSetting">
<a-menu-divider v-if="userStore.isAdmin"/>
<a-menu-item v-if="userStore.isAdmin" key="setting" @click="goToSetting">
<Settings size="16"/>
<span class="menu-text">系统设置</span>
</a-menu-item>

View File

@ -1,7 +1,47 @@
<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" />
@ -14,19 +54,66 @@
</template>
<script setup>
import { computed, ref } from 'vue';
import { computed, ref, onMounted } from 'vue';
import { message } from 'ant-design-vue';
import { useRoute } from 'vue-router';
import { Share2 } from 'lucide-vue-next';
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?.();
@ -54,6 +141,17 @@ const handleShareChat = async () => {
handleChatError(error, 'export');
}
};
// store
onMounted(async () => {
if (!agentStore.isInitialized) {
try {
await agentStore.initialize();
} catch (error) {
console.error('初始化智能体 store 失败:', error);
}
}
});
</script>
<style lang="less" scoped>
@ -73,6 +171,115 @@ const handleShareChat = async () => {
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;
@ -129,3 +336,38 @@ const handleShareChat = async () => {
}
</style>
<style lang="less">
.agent-nav-btn {
display: flex;
gap: 10px;
padding: 6px 14px;
justify-content: center;
align-items: center;
border-radius: 12px;
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

@ -22,9 +22,9 @@
<div class="agent-card-header">
<div class="agent-card-title">
<span class="agent-card-name">{{ agent.name || 'Unknown' }}</span>
<StarFilled v-if="agent.id === defaultAgentId" class="default-icon" />
<StarOutlined v-else @click.prevent="setAsDefaultAgent(agent.id)" class="default-icon" />
</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">
@ -428,12 +428,6 @@ const handlePreview = () => {
white-space: pre-wrap;
}
}
.default-icon {
color: var(--color-warning-500);
font-size: 14px;
margin-left: 4px;
}
}
//
.tools-selector {
@ -754,11 +748,6 @@ const handlePreview = () => {
color: var(--gray-900);
font-weight: 500;
}
.default-icon {
color: var(--color-warning-500);
font-size: 14px;
}
}
}
@ -814,9 +803,6 @@ const handlePreview = () => {
margin-bottom: 12px;
.agent-card-title {
display: flex;
align-items: center;
gap: 8px;
flex: 1;
.agent-card-name {
@ -825,11 +811,17 @@ const handlePreview = () => {
color: var(--gray-900);
line-height: 1.4;
}
}
.default-icon {
color: var(--color-warning-500);
font-size: 16px;
flex-shrink: 0;
.default-icon {
color: var(--color-warning-500);
font-size: 16px;
flex-shrink: 0;
margin-left: 8px;
cursor: pointer;
&:hover {
color: var(--color-warning-600);
}
}
}