feat(gui): add four-quadrant Agent-First layout with top navigation (U2)
- Create AgentLayout.vue with CSS Grid four-quadrant layout - Create SplitPane.vue with draggable divider and localStorage persistence - Create TopNav.vue with logo, status indicator, and settings entry - Create QuadrantPanel.vue with tab switching and collapse support - Restructure router: /agent as main route, legacy routes redirect - App.vue now uses router-view for layout switching
This commit is contained in:
parent
2988d3768e
commit
9273612a5b
|
|
@ -1,13 +1,12 @@
|
|||
<template>
|
||||
<a-config-provider :locale="zhCN" :theme="themeConfig">
|
||||
<AppLayout />
|
||||
<router-view />
|
||||
</a-config-provider>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ConfigProvider as AConfigProvider } from 'ant-design-vue'
|
||||
import zhCN from 'ant-design-vue/es/locale/zh_CN'
|
||||
import AppLayout from './components/layout/AppLayout.vue'
|
||||
import { themeConfig } from './styles'
|
||||
</script>
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,162 @@
|
|||
<template>
|
||||
<div class="agent-layout">
|
||||
<TopNav />
|
||||
<div class="agent-layout__body">
|
||||
<SplitPane
|
||||
direction="horizontal"
|
||||
:default-ratio="0.5"
|
||||
storage-key="agent-h-split"
|
||||
>
|
||||
<template #first>
|
||||
<SplitPane
|
||||
direction="vertical"
|
||||
:default-ratio="0.6"
|
||||
storage-key="agent-left-v-split"
|
||||
>
|
||||
<template #first>
|
||||
<QuadrantPanel
|
||||
:tabs="topLeftTabs"
|
||||
default-tab="chat"
|
||||
storage-key="quadrant-tl-tab"
|
||||
>
|
||||
<template #chat>
|
||||
<ChatView />
|
||||
</template>
|
||||
</QuadrantPanel>
|
||||
</template>
|
||||
<template #second>
|
||||
<QuadrantPanel
|
||||
:tabs="bottomLeftTabs"
|
||||
default-tab="terminal"
|
||||
storage-key="quadrant-bl-tab"
|
||||
>
|
||||
<template #terminal>
|
||||
<TerminalView />
|
||||
</template>
|
||||
</QuadrantPanel>
|
||||
</template>
|
||||
</SplitPane>
|
||||
</template>
|
||||
<template #second>
|
||||
<SplitPane
|
||||
direction="vertical"
|
||||
:default-ratio="0.6"
|
||||
storage-key="agent-right-v-split"
|
||||
>
|
||||
<template #first>
|
||||
<QuadrantPanel
|
||||
:tabs="topRightTabs"
|
||||
default-tab="code"
|
||||
storage-key="quadrant-tr-tab"
|
||||
>
|
||||
<template #code>
|
||||
<div class="agent-layout__placeholder">
|
||||
<FileTextOutlined style="font-size: 32px; color: var(--text-placeholder)" />
|
||||
<p>代码预览</p>
|
||||
</div>
|
||||
</template>
|
||||
<template #workflow>
|
||||
<WorkflowView />
|
||||
</template>
|
||||
<template #knowledge>
|
||||
<KnowledgeBaseView />
|
||||
</template>
|
||||
</QuadrantPanel>
|
||||
</template>
|
||||
<template #second>
|
||||
<QuadrantPanel
|
||||
:tabs="bottomRightTabs"
|
||||
default-tab="monitor"
|
||||
storage-key="quadrant-br-tab"
|
||||
>
|
||||
<template #monitor>
|
||||
<EvolutionView />
|
||||
</template>
|
||||
<template #skills>
|
||||
<SkillsView />
|
||||
</template>
|
||||
<template #settings>
|
||||
<SettingsView />
|
||||
</template>
|
||||
</QuadrantPanel>
|
||||
</template>
|
||||
</SplitPane>
|
||||
</template>
|
||||
</SplitPane>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { type Component } from 'vue'
|
||||
import {
|
||||
MessageOutlined,
|
||||
CodeOutlined,
|
||||
FileTextOutlined,
|
||||
ApartmentOutlined,
|
||||
BookOutlined,
|
||||
DashboardOutlined,
|
||||
AppstoreOutlined,
|
||||
SettingOutlined,
|
||||
} from '@ant-design/icons-vue'
|
||||
import TopNav from './TopNav.vue'
|
||||
import SplitPane from './SplitPane.vue'
|
||||
import QuadrantPanel from './QuadrantPanel.vue'
|
||||
import type { QuadrantTab } from './QuadrantPanel.vue'
|
||||
import ChatView from '@/views/ChatView.vue'
|
||||
import TerminalView from '@/views/TerminalView.vue'
|
||||
import WorkflowView from '@/views/WorkflowView.vue'
|
||||
import KnowledgeBaseView from '@/views/KnowledgeBaseView.vue'
|
||||
import EvolutionView from '@/views/EvolutionView.vue'
|
||||
import SkillsView from '@/views/SkillsView.vue'
|
||||
import SettingsView from '@/views/SettingsView.vue'
|
||||
|
||||
const topLeftTabs: QuadrantTab[] = [
|
||||
{ key: 'chat', label: '对话', icon: MessageOutlined as Component },
|
||||
]
|
||||
|
||||
const bottomLeftTabs: QuadrantTab[] = [
|
||||
{ key: 'terminal', label: '终端', icon: CodeOutlined as Component },
|
||||
]
|
||||
|
||||
const topRightTabs: QuadrantTab[] = [
|
||||
{ key: 'code', label: '代码', icon: FileTextOutlined as Component },
|
||||
{ key: 'workflow', label: '工作流', icon: ApartmentOutlined as Component },
|
||||
{ key: 'knowledge', label: '知识库', icon: BookOutlined as Component },
|
||||
]
|
||||
|
||||
const bottomRightTabs: QuadrantTab[] = [
|
||||
{ key: 'monitor', label: '监控', icon: DashboardOutlined as Component },
|
||||
{ key: 'skills', label: '技能', icon: AppstoreOutlined as Component },
|
||||
{ key: 'settings', label: '设置', icon: SettingOutlined as Component },
|
||||
]
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.agent-layout {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100vh;
|
||||
width: 100vw;
|
||||
overflow: hidden;
|
||||
background: var(--bg-secondary);
|
||||
}
|
||||
|
||||
.agent-layout__body {
|
||||
flex: 1;
|
||||
padding: var(--space-2);
|
||||
gap: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.agent-layout__placeholder {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
gap: var(--space-3);
|
||||
color: var(--text-placeholder);
|
||||
font-size: var(--font-sm);
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,154 @@
|
|||
<template>
|
||||
<div class="quadrant-panel" :class="{ 'quadrant-panel--collapsed': collapsed }">
|
||||
<div class="quadrant-panel__header">
|
||||
<div class="quadrant-panel__tabs">
|
||||
<button
|
||||
v-for="tab in tabs"
|
||||
:key="tab.key"
|
||||
:class="['quadrant-panel__tab', { 'quadrant-panel__tab--active': activeTab === tab.key }]"
|
||||
@click="activeTab = tab.key"
|
||||
>
|
||||
<component :is="tab.icon" v-if="tab.icon" class="quadrant-panel__tab-icon" />
|
||||
<span>{{ tab.label }}</span>
|
||||
</button>
|
||||
</div>
|
||||
<button
|
||||
class="quadrant-panel__collapse-btn"
|
||||
@click="collapsed = !collapsed"
|
||||
:title="collapsed ? '展开' : '折叠'"
|
||||
>
|
||||
<MinusOutlined v-if="!collapsed" />
|
||||
<PlusOutlined v-else />
|
||||
</button>
|
||||
</div>
|
||||
<div v-show="!collapsed" class="quadrant-panel__body">
|
||||
<div v-for="tab in tabs" :key="tab.key" v-show="activeTab === tab.key" class="quadrant-panel__content">
|
||||
<slot :name="tab.key" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, type Component } from 'vue'
|
||||
import { MinusOutlined, PlusOutlined } from '@ant-design/icons-vue'
|
||||
|
||||
export interface QuadrantTab {
|
||||
key: string
|
||||
label: string
|
||||
icon?: Component
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
tabs: QuadrantTab[]
|
||||
defaultTab?: string
|
||||
storageKey?: string
|
||||
}>(), {
|
||||
defaultTab: '',
|
||||
})
|
||||
|
||||
const savedTab = props.storageKey
|
||||
? localStorage.getItem(props.storageKey) || props.defaultTab
|
||||
: props.defaultTab
|
||||
|
||||
const activeTab = ref(savedTab || (props.tabs[0]?.key ?? ''))
|
||||
const collapsed = ref(false)
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.quadrant-panel {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
background: var(--bg-primary);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: var(--radius-lg);
|
||||
}
|
||||
|
||||
.quadrant-panel--collapsed {
|
||||
height: auto !important;
|
||||
}
|
||||
|
||||
.quadrant-panel__header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
height: 36px;
|
||||
padding: 0 var(--space-2);
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
background: var(--bg-secondary);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.quadrant-panel__tabs {
|
||||
display: flex;
|
||||
gap: var(--space-1);
|
||||
overflow-x: auto;
|
||||
scrollbar-width: none;
|
||||
}
|
||||
|
||||
.quadrant-panel__tabs::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.quadrant-panel__tab {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-1);
|
||||
padding: var(--space-1) var(--space-2);
|
||||
border: none;
|
||||
background: transparent;
|
||||
color: var(--text-tertiary);
|
||||
font-size: var(--font-xs);
|
||||
cursor: pointer;
|
||||
border-radius: var(--radius-sm);
|
||||
white-space: nowrap;
|
||||
transition: all var(--transition-fast);
|
||||
}
|
||||
|
||||
.quadrant-panel__tab:hover {
|
||||
color: var(--text-secondary);
|
||||
background: var(--bg-tertiary);
|
||||
}
|
||||
|
||||
.quadrant-panel__tab--active {
|
||||
color: var(--color-primary);
|
||||
background: var(--color-primary-light);
|
||||
font-weight: var(--font-weight-medium);
|
||||
}
|
||||
|
||||
.quadrant-panel__tab-icon {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.quadrant-panel__collapse-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border: none;
|
||||
background: transparent;
|
||||
color: var(--text-tertiary);
|
||||
cursor: pointer;
|
||||
border-radius: var(--radius-sm);
|
||||
flex-shrink: 0;
|
||||
transition: all var(--transition-fast);
|
||||
}
|
||||
|
||||
.quadrant-panel__collapse-btn:hover {
|
||||
color: var(--text-secondary);
|
||||
background: var(--bg-tertiary);
|
||||
}
|
||||
|
||||
.quadrant-panel__body {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.quadrant-panel__content {
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,174 @@
|
|||
<template>
|
||||
<div
|
||||
ref="containerRef"
|
||||
:class="['split-pane', `split-pane--${direction}`, { 'split-pane--dragging': isDragging }]"
|
||||
>
|
||||
<div
|
||||
class="split-pane__first"
|
||||
:style="firstStyle"
|
||||
>
|
||||
<slot name="first" />
|
||||
</div>
|
||||
<div
|
||||
class="split-pane__handle"
|
||||
@mousedown="onMouseDown"
|
||||
>
|
||||
<div class="split-pane__handle-line" />
|
||||
</div>
|
||||
<div
|
||||
class="split-pane__second"
|
||||
:style="secondStyle"
|
||||
>
|
||||
<slot name="second" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed } from 'vue'
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
direction?: 'horizontal' | 'vertical'
|
||||
defaultRatio?: number
|
||||
minRatio?: number
|
||||
maxRatio?: number
|
||||
storageKey?: string
|
||||
}>(), {
|
||||
direction: 'horizontal',
|
||||
defaultRatio: 0.5,
|
||||
minRatio: 0.2,
|
||||
maxRatio: 0.8,
|
||||
})
|
||||
|
||||
const containerRef = ref<HTMLElement | null>(null)
|
||||
const isDragging = ref(false)
|
||||
|
||||
const savedRatio = props.storageKey
|
||||
? parseFloat(localStorage.getItem(props.storageKey) || String(props.defaultRatio))
|
||||
: props.defaultRatio
|
||||
|
||||
const ratio = ref(
|
||||
Math.min(props.maxRatio, Math.max(props.minRatio, savedRatio))
|
||||
)
|
||||
|
||||
const firstStyle = computed(() => {
|
||||
if (props.direction === 'horizontal') {
|
||||
return { width: `${ratio.value * 100}%` }
|
||||
}
|
||||
return { height: `${ratio.value * 100}%` }
|
||||
})
|
||||
|
||||
const secondStyle = computed(() => {
|
||||
if (props.direction === 'horizontal') {
|
||||
return { width: `${(1 - ratio.value) * 100}%` }
|
||||
}
|
||||
return { height: `${(1 - ratio.value) * 100}%` }
|
||||
})
|
||||
|
||||
function onMouseDown(e: MouseEvent) {
|
||||
e.preventDefault()
|
||||
isDragging.value = true
|
||||
|
||||
const startPos = props.direction === 'horizontal' ? e.clientX : e.clientY
|
||||
const containerSize = props.direction === 'horizontal'
|
||||
? containerRef.value!.offsetWidth
|
||||
: containerRef.value!.offsetHeight
|
||||
const startRatio = ratio.value
|
||||
|
||||
function onMouseMove(ev: MouseEvent) {
|
||||
const currentPos = props.direction === 'horizontal' ? ev.clientX : ev.clientY
|
||||
const delta = (currentPos - startPos) / containerSize
|
||||
const newRatio = Math.min(props.maxRatio, Math.max(props.minRatio, startRatio + delta))
|
||||
ratio.value = newRatio
|
||||
}
|
||||
|
||||
function onMouseUp() {
|
||||
isDragging.value = false
|
||||
if (props.storageKey) {
|
||||
localStorage.setItem(props.storageKey, String(ratio.value))
|
||||
}
|
||||
// Trigger resize for Vue Flow / ECharts
|
||||
window.dispatchEvent(new Event('resize'))
|
||||
document.removeEventListener('mousemove', onMouseMove)
|
||||
document.removeEventListener('mouseup', onMouseUp)
|
||||
}
|
||||
|
||||
document.addEventListener('mousemove', onMouseMove)
|
||||
document.addEventListener('mouseup', onMouseUp)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.split-pane {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.split-pane--horizontal {
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.split-pane--vertical {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.split-pane__first,
|
||||
.split-pane__second {
|
||||
overflow: hidden;
|
||||
min-width: 0;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.split-pane__handle {
|
||||
flex-shrink: 0;
|
||||
z-index: 10;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: background-color var(--transition-fast);
|
||||
}
|
||||
|
||||
.split-pane--horizontal > .split-pane__handle {
|
||||
width: 6px;
|
||||
cursor: col-resize;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.split-pane--vertical > .split-pane__handle {
|
||||
height: 6px;
|
||||
cursor: row-resize;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.split-pane__handle:hover,
|
||||
.split-pane--dragging .split-pane__handle {
|
||||
background-color: var(--color-primary);
|
||||
}
|
||||
|
||||
.split-pane__handle-line {
|
||||
background-color: var(--border-color);
|
||||
border-radius: var(--radius-full);
|
||||
transition: background-color var(--transition-fast);
|
||||
}
|
||||
|
||||
.split-pane--horizontal > .split-pane__handle > .split-pane__handle-line {
|
||||
width: 2px;
|
||||
height: 24px;
|
||||
}
|
||||
|
||||
.split-pane--vertical > .split-pane__handle > .split-pane__handle-line {
|
||||
height: 2px;
|
||||
width: 24px;
|
||||
}
|
||||
|
||||
.split-pane__handle:hover .split-pane__handle-line,
|
||||
.split-pane--dragging .split-pane__handle-line {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.split-pane--dragging {
|
||||
user-select: none;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,138 @@
|
|||
<template>
|
||||
<header class="top-nav">
|
||||
<div class="top-nav__left">
|
||||
<div class="top-nav__logo" @click="router.push('/agent')">
|
||||
<span class="top-nav__logo-text">Fischer</span>
|
||||
<span class="top-nav__logo-badge">AgentKit</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="top-nav__center">
|
||||
<a-select
|
||||
v-if="false"
|
||||
:value="'default'"
|
||||
class="top-nav__task-select"
|
||||
size="small"
|
||||
:bordered="false"
|
||||
>
|
||||
<a-select-option value="default">当前任务</a-select-option>
|
||||
</a-select>
|
||||
</div>
|
||||
|
||||
<div class="top-nav__right">
|
||||
<div class="top-nav__status">
|
||||
<a-badge
|
||||
:status="wsConnected ? 'success' : 'error'"
|
||||
:text="wsConnected ? '已连接' : '未连接'"
|
||||
/>
|
||||
</div>
|
||||
<a-tooltip title="设置">
|
||||
<button class="top-nav__icon-btn" @click="router.push('/agent/monitor?tab=settings')">
|
||||
<SettingOutlined />
|
||||
</button>
|
||||
</a-tooltip>
|
||||
</div>
|
||||
</header>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { Select as ASelect, SelectOption as ASelectOption, Badge as ABadge, Tooltip as ATooltip } from 'ant-design-vue'
|
||||
import { SettingOutlined } from '@ant-design/icons-vue'
|
||||
import { useChatStore } from '@/stores/chat'
|
||||
|
||||
const router = useRouter()
|
||||
const chatStore = useChatStore()
|
||||
const wsConnected = computed(() => chatStore.isWsConnected)
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.top-nav {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
height: var(--topnav-height);
|
||||
padding: 0 var(--space-4);
|
||||
background: var(--bg-primary);
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
flex-shrink: 0;
|
||||
z-index: var(--z-sticky);
|
||||
}
|
||||
|
||||
.top-nav__left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-3);
|
||||
}
|
||||
|
||||
.top-nav__logo {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-2);
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.top-nav__logo-text {
|
||||
font-size: var(--font-lg);
|
||||
font-weight: var(--font-weight-bold);
|
||||
background: var(--gradient-brand);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
}
|
||||
|
||||
.top-nav__logo-badge {
|
||||
font-size: var(--font-xs);
|
||||
font-weight: var(--font-weight-medium);
|
||||
color: var(--text-inverse);
|
||||
background: var(--gradient-brand);
|
||||
padding: 1px var(--space-2);
|
||||
border-radius: var(--radius-full);
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
.top-nav__center {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.top-nav__task-select {
|
||||
min-width: 200px;
|
||||
}
|
||||
|
||||
.top-nav__right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-3);
|
||||
}
|
||||
|
||||
.top-nav__status {
|
||||
font-size: var(--font-xs);
|
||||
}
|
||||
|
||||
.top-nav__status :deep(.ant-badge-status-text) {
|
||||
color: var(--text-tertiary);
|
||||
font-size: var(--font-xs);
|
||||
}
|
||||
|
||||
.top-nav__icon-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border: none;
|
||||
background: transparent;
|
||||
color: var(--text-tertiary);
|
||||
cursor: pointer;
|
||||
border-radius: var(--radius-md);
|
||||
transition: all var(--transition-fast);
|
||||
}
|
||||
|
||||
.top-nav__icon-btn:hover {
|
||||
color: var(--text-primary);
|
||||
background: var(--bg-tertiary);
|
||||
}
|
||||
</style>
|
||||
|
|
@ -2,96 +2,135 @@ import { createRouter, createWebHistory } from 'vue-router'
|
|||
import type { RouteRecordRaw } from 'vue-router'
|
||||
|
||||
const routes: RouteRecordRaw[] = [
|
||||
// Agent-First 四象限布局 (新)
|
||||
{
|
||||
path: '/agent',
|
||||
name: 'agent',
|
||||
component: () => import('@/components/layout/AgentLayout.vue'),
|
||||
meta: { title: 'AgentKit' },
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
redirect: '/agent/chat',
|
||||
},
|
||||
{
|
||||
path: 'chat',
|
||||
name: 'agent-chat',
|
||||
meta: { title: '对话', quadrant: 'tl', tab: 'chat' },
|
||||
component: () => import('@/views/ChatView.vue'),
|
||||
},
|
||||
{
|
||||
path: 'code',
|
||||
name: 'agent-code',
|
||||
meta: { title: '代码', quadrant: 'tr', tab: 'code' },
|
||||
component: () => import('@/views/ChatView.vue'),
|
||||
},
|
||||
{
|
||||
path: 'terminal',
|
||||
name: 'agent-terminal',
|
||||
meta: { title: '终端', quadrant: 'bl', tab: 'terminal' },
|
||||
component: () => import('@/views/TerminalView.vue'),
|
||||
},
|
||||
{
|
||||
path: 'monitor',
|
||||
name: 'agent-monitor',
|
||||
meta: { title: '监控', quadrant: 'br', tab: 'monitor' },
|
||||
component: () => import('@/views/EvolutionView.vue'),
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
// Default redirect to agent layout
|
||||
{
|
||||
path: '/',
|
||||
name: 'chat',
|
||||
component: () => import('@/views/ChatView.vue'),
|
||||
meta: { title: '智能对话' },
|
||||
redirect: '/agent',
|
||||
},
|
||||
|
||||
// Legacy route redirects → agent quadrant routes
|
||||
{
|
||||
path: '/workflow',
|
||||
name: 'workflow',
|
||||
component: () => import('@/views/WorkflowView.vue'),
|
||||
meta: { title: '工作流' },
|
||||
redirect: '/agent/code?tab=workflow',
|
||||
},
|
||||
{
|
||||
path: '/knowledge',
|
||||
name: 'knowledge',
|
||||
component: () => import('@/views/KnowledgeBaseView.vue'),
|
||||
meta: { title: '知识库' },
|
||||
redirect: '/agent/code?tab=knowledge',
|
||||
},
|
||||
{
|
||||
path: '/skills',
|
||||
name: 'skills',
|
||||
component: () => import('@/views/SkillsView.vue'),
|
||||
meta: { title: '技能' },
|
||||
redirect: '/agent/monitor?tab=skills',
|
||||
},
|
||||
{
|
||||
path: '/evolution',
|
||||
redirect: '/agent/monitor?tab=monitor',
|
||||
},
|
||||
{
|
||||
path: '/settings',
|
||||
redirect: '/agent/monitor?tab=settings',
|
||||
},
|
||||
{
|
||||
path: '/terminal',
|
||||
name: 'terminal',
|
||||
component: () => import('@/views/TerminalView.vue'),
|
||||
meta: { title: '终端' },
|
||||
redirect: '/agent/terminal',
|
||||
},
|
||||
|
||||
// Computer Use (保留独立路由,显示"即将推出")
|
||||
{
|
||||
path: '/computer-use',
|
||||
name: 'computer-use',
|
||||
component: () => import('@/views/ComputerUseView.vue'),
|
||||
meta: { title: 'Computer Use' },
|
||||
},
|
||||
|
||||
// Legacy layout (fallback)
|
||||
{
|
||||
path: '/evolution',
|
||||
name: 'evolution',
|
||||
component: () => import('@/views/EvolutionView.vue'),
|
||||
meta: { title: '自进化' },
|
||||
path: '/legacy',
|
||||
name: 'legacy',
|
||||
component: () => import('@/components/layout/AppLayout.vue'),
|
||||
meta: { title: 'Fischer AgentKit (Legacy)' },
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
redirect: '/evolution/overview',
|
||||
path: 'chat',
|
||||
name: 'legacy-chat',
|
||||
component: () => import('@/views/ChatView.vue'),
|
||||
meta: { title: '智能对话' },
|
||||
},
|
||||
{
|
||||
path: 'overview',
|
||||
name: 'evolution-overview',
|
||||
component: () => import('@/components/evolution/DashboardOverview.vue'),
|
||||
meta: { title: '概览 - 自进化' },
|
||||
path: 'workflow',
|
||||
name: 'legacy-workflow',
|
||||
component: () => import('@/views/WorkflowView.vue'),
|
||||
meta: { title: '工作流' },
|
||||
},
|
||||
{
|
||||
path: 'experiences',
|
||||
name: 'evolution-experiences',
|
||||
component: () => import('@/components/evolution/ExperiencePanel.vue'),
|
||||
meta: { title: '经验记录 - 自进化' },
|
||||
path: 'knowledge',
|
||||
name: 'legacy-knowledge',
|
||||
component: () => import('@/views/KnowledgeBaseView.vue'),
|
||||
meta: { title: '知识库' },
|
||||
},
|
||||
{
|
||||
path: 'metrics',
|
||||
name: 'evolution-metrics',
|
||||
component: () => import('@/components/evolution/MetricsPanel.vue'),
|
||||
meta: { title: '指标趋势 - 自进化' },
|
||||
path: 'skills',
|
||||
name: 'legacy-skills',
|
||||
component: () => import('@/views/SkillsView.vue'),
|
||||
meta: { title: '技能' },
|
||||
},
|
||||
{
|
||||
path: 'pitfalls',
|
||||
name: 'evolution-pitfalls',
|
||||
component: () => import('@/components/evolution/PitfallRoutePanel.vue'),
|
||||
meta: { title: '避坑预警 - 自进化' },
|
||||
path: 'terminal',
|
||||
name: 'legacy-terminal',
|
||||
component: () => import('@/views/TerminalView.vue'),
|
||||
meta: { title: '终端' },
|
||||
},
|
||||
{
|
||||
path: 'optimizations',
|
||||
name: 'evolution-optimizations',
|
||||
component: () => import('@/components/evolution/OptimizationPanel.vue'),
|
||||
meta: { title: '路径优化 - 自进化' },
|
||||
path: 'evolution',
|
||||
name: 'legacy-evolution',
|
||||
component: () => import('@/views/EvolutionView.vue'),
|
||||
meta: { title: '自进化' },
|
||||
},
|
||||
{
|
||||
path: 'usage',
|
||||
name: 'evolution-usage',
|
||||
component: () => import('@/components/evolution/UsagePanel.vue'),
|
||||
meta: { title: '用量统计 - 自进化' },
|
||||
path: 'settings',
|
||||
name: 'legacy-settings',
|
||||
component: () => import('@/views/SettingsView.vue'),
|
||||
meta: { title: '设置' },
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: '/settings',
|
||||
name: 'settings',
|
||||
component: () => import('@/views/SettingsView.vue'),
|
||||
meta: { title: '设置' },
|
||||
},
|
||||
]
|
||||
|
||||
const router = createRouter({
|
||||
|
|
|
|||
Loading…
Reference in New Issue