Files
pit-router/frontend/src/views/HomeView.vue
feifei.xu 021ce8b50b feat: 功能2 - 绑定/解绑 Agent (v0.9.7)
- 添加 BotSettingsModal.vue 机器人设置模态框
- 显示在线 Agent 列表
- 实现 Agent 绑定功能
- 实现 Agent 解绑功能
- HomeView 添加设置按钮(hover 显示)
- Store 添加 bindBotAgent 方法
- 显示机器人绑定状态
2026-03-15 11:59:04 +08:00

403 lines
8.9 KiB
Vue
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { useRouter } from 'vue-router'
import { useAuthStore } from '@/stores/auth'
import { useChatStore, type Bot } from '@/stores/chat'
import CreateBotModal from '@/components/chat/CreateBotModal.vue'
import BotSettingsModal from '@/components/chat/BotSettingsModal.vue'
const router = useRouter()
const authStore = useAuthStore()
const chatStore = useChatStore()
const showCreateBot = ref(false)
const showBotSettings = ref(false)
const selectedBot = ref<Bot | null>(null)
onMounted(async () => {
await chatStore.fetchBots()
await chatStore.fetchSessions()
chatStore.setupSocket()
})
function startChat(botId: string) {
router.push({ name: 'chat', params: {} })
chatStore.createSession(botId).then(session => {
router.push({ name: 'chat', params: { sessionId: session.id } })
})
}
function resumeChat(sessionId: string) {
router.push({ name: 'chat', params: { sessionId } })
}
function handleBotCreated(bot: Bot) {
chatStore.bots.push(bot)
}
function openBotSettings(bot: Bot, event: Event) {
event.stopPropagation()
selectedBot.value = bot
showBotSettings.value = true
}
function handleBotUpdated(bot: Bot) {
const index = chatStore.bots.findIndex(b => b.id === bot.id)
if (index !== -1) {
chatStore.bots[index] = bot
}
selectedBot.value = bot
}
</script>
<template>
<div class="home-page">
<header class="header">
<div class="header-content">
<h1>🐕 智队中枢</h1>
<div class="user-actions">
<button class="new-bot-btn" @click="showCreateBot = true">
+ 新建机器人
</button>
<div class="user-info">
<span>{{ authStore.user?.nickname || authStore.user?.username }}</span>
<button class="logout-btn" @click="authStore.logout">退出</button>
</div>
</div>
</div>
</header>
<main class="main-content">
<!-- Bot List -->
<section class="section">
<div class="section-header">
<h2 class="section-title">选择机器人开始聊天</h2>
<span class="bot-count">{{ chatStore.bots.length }} 个机器人</span>
</div>
<div class="bot-grid">
<div
v-for="bot in chatStore.bots"
:key="bot.id"
class="bot-card"
@click="startChat(bot.id)"
>
<div class="bot-avatar">
{{ bot.avatar || '🤖' }}
</div>
<div class="bot-info">
<h3 class="bot-name">{{ bot.display_name || bot.name }}</h3>
<p class="bot-status" :class="bot.status">
{{ bot.status === 'online' ? '🟢 在线' : '⚪ 离线' }}
</p>
</div>
<button
class="settings-btn"
@click="openBotSettings(bot, $event)"
>
</button>
</div>
</div>
</section>
<!-- Recent Sessions -->
<section class="section" v-if="chatStore.sessions.length > 0">
<h2 class="section-title">最近会话</h2>
<div class="session-list">
<div
v-for="session in chatStore.sessions.slice(0, 10)"
:key="session.id"
class="session-item"
@click="resumeChat(session.id)"
>
<div class="session-avatar">
{{ session.bot?.avatar || '🤖' }}
</div>
<div class="session-info">
<div class="session-header">
<h4 class="session-title">{{ session.title }}</h4>
<span class="session-time">{{ new Date(session.last_active_at || session.created_at).toLocaleDateString() }}</span>
</div>
<p class="session-preview" v-if="session.last_message">
{{ session.last_message.content.slice(0, 50) }}...
</p>
<p class="session-preview" v-else>暂无消息</p>
</div>
<div class="session-badge" v-if="session.unread_count > 0">
{{ session.unread_count }}
</div>
</div>
</div>
</section>
</main>
<CreateBotModal
v-if="showCreateBot"
@close="showCreateBot = false"
@created="handleBotCreated"
/>
<BotSettingsModal
v-if="showBotSettings && selectedBot"
:bot="selectedBot"
@close="showBotSettings = false"
@updated="handleBotUpdated"
/>
</div>
</template>
<style scoped>
.home-page {
min-height: 100vh;
background: var(--bg-secondary);
}
.header {
background: var(--bg-primary);
border-bottom: 1px solid var(--border-color);
padding: var(--spacing-md) var(--spacing-lg);
}
.header-content {
max-width: 1200px;
margin: 0 auto;
display: flex;
justify-content: space-between;
align-items: center;
}
.header h1 {
font-size: var(--font-size-lg);
color: var(--primary-color);
}
.user-actions {
display: flex;
align-items: center;
gap: var(--spacing-md);
}
.new-bot-btn {
padding: var(--spacing-xs) var(--spacing-md);
background: var(--primary-color);
color: white;
border: none;
border-radius: var(--border-radius);
font-size: var(--font-size-sm);
font-weight: 500;
cursor: pointer;
transition: background 0.2s;
}
.new-bot-btn:hover {
background: var(--primary-dark);
}
.user-info {
display: flex;
align-items: center;
gap: var(--spacing-md);
color: var(--text-secondary);
}
.logout-btn {
padding: var(--spacing-xs) var(--spacing-sm);
background: transparent;
border: 1px solid var(--border-color);
border-radius: var(--border-radius);
color: var(--text-secondary);
cursor: pointer;
font-size: var(--font-size-sm);
}
.logout-btn:hover {
border-color: var(--error-color);
color: var(--error-color);
}
.main-content {
max-width: 1200px;
margin: 0 auto;
padding: var(--spacing-lg);
}
.section {
margin-bottom: var(--spacing-xl);
}
.section-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: var(--spacing-md);
}
.section-title {
font-size: var(--font-size-md);
font-weight: 600;
color: var(--text-primary);
}
.bot-count {
font-size: var(--font-size-sm);
color: var(--text-tertiary);
}
.bot-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
gap: var(--spacing-md);
}
.bot-card {
background: var(--bg-primary);
border-radius: var(--border-radius-lg);
padding: var(--spacing-md);
display: flex;
align-items: center;
gap: var(--spacing-md);
cursor: pointer;
transition: transform 0.2s, box-shadow 0.2s;
position: relative;
}
.bot-card:hover {
transform: translateY(-2px);
box-shadow: var(--shadow-md);
}
.settings-btn {
position: absolute;
top: 8px;
right: 8px;
width: 28px;
height: 28px;
border: none;
background: var(--bg-tertiary);
border-radius: 50%;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
font-size: 12px;
opacity: 0;
transition: opacity 0.2s;
}
.bot-card:hover .settings-btn {
opacity: 1;
}
.settings-btn:hover {
background: var(--bg-primary);
}
.bot-avatar {
width: 48px;
height: 48px;
border-radius: 50%;
background: var(--bg-secondary);
display: flex;
align-items: center;
justify-content: center;
font-size: 24px;
}
.bot-info {
flex: 1;
}
.bot-name {
font-size: var(--font-size-md);
font-weight: 600;
margin-bottom: var(--spacing-xs);
}
.bot-status {
font-size: var(--font-size-sm);
color: var(--text-secondary);
}
.bot-status.online {
color: var(--success-color);
}
.session-list {
background: var(--bg-primary);
border-radius: var(--border-radius-lg);
overflow: hidden;
}
.session-item {
display: flex;
align-items: center;
gap: var(--spacing-md);
padding: var(--spacing-md);
cursor: pointer;
border-bottom: 1px solid var(--border-color);
transition: background 0.2s;
}
.session-item:last-child {
border-bottom: none;
}
.session-item:hover {
background: var(--bg-secondary);
}
.session-avatar {
width: 40px;
height: 40px;
border-radius: 50%;
background: var(--bg-secondary);
display: flex;
align-items: center;
justify-content: center;
font-size: 20px;
}
.session-info {
flex: 1;
min-width: 0;
}
.session-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: var(--spacing-xs);
}
.session-title {
font-weight: 500;
color: var(--text-primary);
}
.session-time {
font-size: var(--font-size-xs);
color: var(--text-tertiary);
}
.session-preview {
font-size: var(--font-size-sm);
color: var(--text-secondary);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.session-badge {
min-width: 20px;
height: 20px;
padding: 0 6px;
background: var(--error-color);
color: white;
border-radius: 10px;
font-size: var(--font-size-xs);
display: flex;
align-items: center;
justify-content: center;
}
</style>