agent-Specialization/static/src/components/panels/LeftPanel.vue

177 lines
7.0 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<aside class="sidebar left-sidebar" :style="panelStyle">
<div class="sidebar-status">
<div class="compact-status-card">
<div class="status-line">
<div class="status-brand">
<span class="icon icon-lg status-logo" :style="iconStyle('bot')" aria-hidden="true"></span>
<div class="brand-text">
<span class="brand-name">AI Agent</span>
<span class="agent-version" v-if="agentVersion">{{ agentVersion }}</span>
</div>
</div>
<div class="status-indicators">
<span class="mode-indicator" :class="{ thinking: thinkingMode, fast: !thinkingMode }">
<transition name="mode-icon" mode="out-in">
<span
class="icon icon-sm"
:style="iconStyle(thinkingMode ? 'brain' : 'zap')"
:key="thinkingMode ? 'brain' : 'zap'"
aria-hidden="true"
></span>
</transition>
</span>
<span class="connection-dot" :class="{ active: isConnected }" :title="isConnected ? '已连接' : '未连接'"></span>
</div>
</div>
</div>
</div>
<div class="sidebar-panel-card-wrapper">
<div class="sidebar-panel-card">
<div class="sidebar-header">
<div class="panel-menu-wrapper" ref="panelMenuWrapper">
<button class="sidebar-view-toggle" @click.stop="$emit('toggle-panel-menu')" title="切换侧边栏">
<span class="icon icon-md" :style="iconStyle('menu')" aria-hidden="true"></span>
</button>
<transition name="fade">
<div class="panel-menu" v-if="panelMenuOpen">
<button
type="button"
:class="{ active: panelMode === 'files' }"
@click.stop="$emit('select-panel', 'files')"
title="项目文件"
>
<span class="icon icon-md" :style="iconStyle('folder')" aria-hidden="true"></span>
</button>
<button
type="button"
:class="{ active: panelMode === 'todo' }"
@click.stop="$emit('select-panel', 'todo')"
title="待办列表"
>
<span class="icon icon-md" :style="iconStyle('stickyNote')" aria-hidden="true"></span>
</button>
<button
type="button"
:class="{ active: panelMode === 'subAgents' }"
@click.stop="$emit('select-panel', 'subAgents')"
title="子智能体"
>
<span class="icon icon-md" :style="iconStyle('bot')" aria-hidden="true"></span>
</button>
</div>
</transition>
</div>
<button class="sidebar-manage-btn" @click="$emit('open-file-manager')" title="打开桌面式文件管理器">管理</button>
<h3>
<span v-if="panelMode === 'files'" class="icon-label">
<span class="icon icon-sm" :style="iconStyle('folder')" aria-hidden="true"></span>
<span>项目文件</span>
</span>
<span v-else-if="panelMode === 'todo'" class="icon-label">
<span class="icon icon-sm" :style="iconStyle('stickyNote')" aria-hidden="true"></span>
<span>待办列表</span>
</span>
<span v-else class="icon-label">
<span class="icon icon-sm" :style="iconStyle('bot')" aria-hidden="true"></span>
<span>子智能体</span>
</span>
</h3>
</div>
<div class="sidebar-panel-content">
<div v-if="panelMode === 'todo'" class="todo-panel">
<div v-if="!todoList" class="todo-empty">暂无待办列表</div>
<div v-else>
<div class="todo-task" v-for="task in todoList.tasks || []" :key="task.index" :class="{ done: task.status === 'done' }">
<span class="todo-task-title">task{{ task.index }}{{ task.title }}</span>
<span class="todo-task-status icon-label">
<span class="icon icon-sm" :style="iconStyle(task.status === 'done' ? 'check' : 'checkbox')" aria-hidden="true"></span>
<span>{{ task.status === 'done' ? '完成' : '未完成' }}</span>
</span>
</div>
<div class="todo-instruction">{{ todoList.instruction }}</div>
</div>
</div>
<div v-else-if="panelMode === 'subAgents'" class="sub-agent-panel">
<div v-if="!subAgents.length" class="sub-agent-empty">暂无运行中的子智能体</div>
<div v-else class="sub-agent-cards">
<div class="sub-agent-card" v-for="agent in subAgents" :key="agent.task_id" @click="openSubAgent(agent)">
<div class="sub-agent-header">
<span class="sub-agent-id">#{{ agent.agent_id }}</span>
<span class="sub-agent-status" :class="agent.status">{{ agent.status }}</span>
</div>
<div class="sub-agent-summary">{{ agent.summary }}</div>
<div class="sub-agent-tool" v-if="agent.last_tool">当前:{{ agent.last_tool }}</div>
</div>
</div>
</div>
<div v-else class="file-tree" @contextmenu.prevent>
<FileNode
v-for="node in fileTree"
:key="node.path"
:node="node"
:level="0"
:expanded-folders="expandedFolders"
:icon-style="iconStyle"
/>
</div>
</div>
</div>
</div>
</aside>
</template>
<script setup lang="ts">
import { computed, ref } from 'vue';
import { storeToRefs } from 'pinia';
import FileNode from '@/components/files/FileNode.vue';
import { useFileStore } from '@/stores/file';
import { useSubAgentStore } from '@/stores/subAgent';
defineOptions({ name: 'LeftPanel' });
const props = defineProps<{
width: number;
collapsed?: boolean;
iconStyle: (key: string) => Record<string, string>;
agentVersion: string | null;
thinkingMode: boolean;
isConnected: boolean;
panelMenuOpen: boolean;
panelMode: 'files' | 'todo' | 'subAgents';
}>();
defineEmits<{
(event: 'toggle-panel-menu'): void;
(event: 'select-panel', mode: 'files' | 'todo' | 'subAgents'): void;
(event: 'open-file-manager'): void;
}>();
const panelMenuWrapper = ref<HTMLElement | null>(null);
const panelStyle = computed(() => {
if (props.collapsed) {
return {
width: '0px',
minWidth: '0px'
};
}
const px = `${props.width}px`;
return {
width: px,
minWidth: px
};
});
const fileStore = useFileStore();
const subAgentStore = useSubAgentStore();
const { fileTree, expandedFolders, todoList } = storeToRefs(fileStore);
const { subAgents } = storeToRefs(subAgentStore);
const openSubAgent = (agent: any) => {
subAgentStore.openSubAgent(agent);
};
defineExpose({
panelMenuWrapper
});
</script>