@@ -0,0 +1,734 @@
< script setup lang = "ts" >
import type {
AgentListItem ,
KnowledgeBaseListItem ,
WorkflowListItem ,
WorkflowRunListItem ,
} from '#/api/ai-platform/ai-platform' ;
import { computed , onMounted , reactive , ref } from 'vue' ;
import { useRouter } from 'vue-router' ;
import { Page } from '@vben/common-ui' ;
import {
Bot ,
BookOpen ,
CircleCheckBig ,
Clock3 ,
GitBranch ,
MessageSquare ,
Play ,
RefreshCw ,
Route ,
Settings ,
Shield ,
Sparkles ,
Users ,
Workflow ,
} from '@vben/icons' ;
import {
ElButton ,
ElCard ,
ElEmpty ,
ElSkeleton ,
ElTag ,
ElTimeline ,
ElTimelineItem ,
} from 'element-plus' ;
import {
getAgentListApi ,
getAllWorkflowRunsApi ,
getKnowledgeBaseListApi ,
getWorkflowListApi ,
} from '#/api/ai-platform/ai-platform' ;
defineOptions ( { name : 'ControlCenter' } ) ;
const router = useRouter ( ) ;
const loading = ref ( false ) ;
const loaded = ref ( false ) ;
const agents = ref < AgentListItem [ ] > ( [ ] ) ;
const workflows = ref < WorkflowListItem [ ] > ( [ ] ) ;
const knowledgeBases = ref < KnowledgeBaseListItem [ ] > ( [ ] ) ;
const runs = ref < WorkflowRunListItem [ ] > ( [ ] ) ;
const stats = reactive ( {
agentTotal : 0 ,
workflowTotal : 0 ,
knowledgeTotal : 0 ,
runTotal : 0 ,
} ) ;
const publishedAgents = computed (
( ) => agents . value . filter ( ( item ) => item . status === 'published' ) . length ,
) ;
const publishedWorkflows = computed (
( ) => workflows . value . filter ( ( item ) => item . status === 'published' ) . length ,
) ;
const successfulRuns = computed (
( ) => runs . value . filter ( ( item ) => item . status === 'success' ) . length ,
) ;
const failedRuns = computed (
( ) => runs . value . filter ( ( item ) => item . status === 'failed' ) . length ,
) ;
const quickEntries = [
{
desc : '沉淀角色能力、提示词和工具调用策略' ,
icon : Bot ,
path : '/ai-platform/agent' ,
title : '智能体' ,
} ,
{
desc : '编排多 Agent 协作、人工确认和系统调用' ,
icon : Workflow ,
path : '/ai-platform/workflow' ,
title : '流程编排' ,
} ,
{
desc : '管理文档、分段、向量索引和检索日志' ,
icon : BookOpen ,
path : '/ai-platform/knowledge-base' ,
title : '知识库' ,
} ,
{
desc : '进入 Codex 协作入口,串联开发任务' ,
icon : MessageSquare ,
path : '/agent-chat/codex' ,
title : 'Codex' ,
} ,
] ;
const adminEntries = [
{ icon : Users , path : '/system/user' , title : '用户管理' } ,
{ icon : Shield , path : '/system/role' , title : '角色权限' } ,
{ icon : Route , path : '/system/menu' , title : '菜单管理' } ,
{ icon : Settings , path : '/system-config' , title : '系统配置' } ,
] ;
function go ( path : string ) {
router . push ( path ) ;
}
function formatTime ( value ? : string ) {
if ( ! value ) return '-' ;
return value . replace ( 'T' , ' ' ) . slice ( 0 , 19 ) ;
}
async function loadDashboard ( ) {
loading . value = true ;
try {
const [ agentRes , workflowRes , knowledgeRes , runRes ] = await Promise . all ( [
getAgentListApi ( { page : 1 , pageSize : 6 } ) ,
getWorkflowListApi ( { page : 1 , pageSize : 6 } ) ,
getKnowledgeBaseListApi ( { page : 1 , pageSize : 6 } ) ,
getAllWorkflowRunsApi ( { page : 1 , pageSize : 6 } ) ,
] ) ;
agents . value = agentRes . items || [ ] ;
workflows . value = workflowRes . items || [ ] ;
knowledgeBases . value = knowledgeRes . items || [ ] ;
runs . value = runRes . items || [ ] ;
stats . agentTotal = agentRes . total || agents . value . length ;
stats . workflowTotal = workflowRes . total || workflows . value . length ;
stats . knowledgeTotal = knowledgeRes . total || knowledgeBases . value . length ;
stats . runTotal = runRes . total || runs . value . length ;
loaded . value = true ;
} finally {
loading . value = false ;
}
}
onMounted ( loadDashboard ) ;
< / script >
< template >
< Page auto -content -height title = "控制中心" >
< div class = "control-center" >
< section class = "hero-panel" >
< div >
< div class = "eyebrow" >
< Sparkles :size = "16" / >
ZQ - AI Agent Admin
< / div >
< h1 > 控制中心 < / h1 >
< p >
{ { stats . agentTotal } } 个智能体 、 { { stats . workflowTotal } } 条流程 、 { { stats . runTotal } } 次执行记录已汇总 。
< / p >
< / div >
< div class = "hero-actions" >
< ElButton :icon = "RefreshCw" :loading = "loading" @click ="loadDashboard" >
刷新
< / ElButton >
< ElButton type = "primary" :icon = "Bot" @click ="go('/ai-platform/agent')" >
创建智能体
< / ElButton >
< / div >
< / section >
< section class = "metric-grid" >
< ElCard shadow = "never" class = "metric-card" >
< div class = "metric-icon agent" > < Bot :size = "22" / > < / div >
< div >
< span > 智能体 < / span >
< strong > { { stats . agentTotal } } < / strong >
< small > 已发布 { { publishedAgents } } < / small >
< / div >
< / ElCard >
< ElCard shadow = "never" class = "metric-card" >
< div class = "metric-icon workflow" > < Workflow :size = "22" / > < / div >
< div >
< span > 流程 < / span >
< strong > { { stats . workflowTotal } } < / strong >
< small > 已发布 { { publishedWorkflows } } < / small >
< / div >
< / ElCard >
< ElCard shadow = "never" class = "metric-card" >
< div class = "metric-icon knowledge" > < BookOpen :size = "22" / > < / div >
< div >
< span > 知识库 < / span >
< strong > { { stats . knowledgeTotal } } < / strong >
< small > { { knowledgeBases . length } } 个最近维护 < / small >
< / div >
< / ElCard >
< ElCard shadow = "never" class = "metric-card" >
< div class = "metric-icon run" > < Play :size = "22" / > < / div >
< div >
< span > 执行记录 < / span >
< strong > { { stats . runTotal } } < / strong >
< small > 成功 { { successfulRuns } } / 失败 { { failedRuns } } < / small >
< / div >
< / ElCard >
< / section >
< section class = "content-grid" >
< ElCard shadow = "never" class = "panel-card" >
< template # header >
< div class = "card-header" >
< span > AI 工作入口 < / span >
< ElButton link type = "primary" @click ="go('/ai-platform/agent')" >
查看全部
< / ElButton >
< / div >
< / template >
< div class = "quick-grid" >
< button
v-for = "entry in quickEntries"
:key = "entry.path"
class = "quick-entry"
type = "button"
@click ="go(entry.path)"
>
< component :is = "entry.icon" :size = "22" / >
< span > { { entry . title } } < / span >
< small > { { entry . desc } } < / small >
< / button >
< / div >
< / ElCard >
< ElCard shadow = "never" class = "panel-card" >
< template # header >
< div class = "card-header" >
< span > 基础后台 < / span >
< ElButton link type = "primary" @click ="go('/system/user')" >
用户权限
< / ElButton >
< / div >
< / template >
< div class = "admin-grid" >
< button
v-for = "entry in adminEntries"
:key = "entry.path"
class = "admin-entry"
type = "button"
@click ="go(entry.path)"
>
< component :is = "entry.icon" :size = "18" / >
< span > { { entry . title } } < / span >
< / button >
< / div >
< / ElCard >
< / section >
< section class = "content-grid lower" >
< ElCard shadow = "never" class = "panel-card" >
< template # header >
< div class = "card-header" >
< span > 智能体协作 < / span >
< ElButton link type = "primary" @click ="go('/ai-platform/agent')" >
管理智能体
< / ElButton >
< / div >
< / template >
< ElSkeleton v-if = "loading && !loaded" :rows="5" animated / >
< div v-else-if = "agents.length" class="list-stack" >
< button
v-for = "agent in agents"
:key = "agent.id"
class = "list-row"
type = "button"
@click ="go(`/ai-platform/agent/editor/${agent.id}`)"
>
< span >
< strong > { { agent . name } } < / strong >
< small > { { agent . description || agent . code } } < / small >
< / span >
< ElTag : type = "agent.status === 'published' ? 'success' : 'info'" >
{ { agent . status === 'published' ? '已发布' : '草稿' } }
< / ElTag >
< / button >
< / div >
< ElEmpty v-else description = "暂无智能体" / >
< / ElCard >
< ElCard shadow = "never" class = "panel-card" >
< template # header >
< div class = "card-header" >
< span > 最近执行 < / span >
< ElButton
link
type = "primary"
@click ="go('/ai-platform/workflow-runs')"
>
执行历史
< / ElButton >
< / div >
< / template >
< ElTimeline v-if = "runs.length" class="run-timeline" >
< ElTimelineItem
v-for = "run in runs"
:key = "run.id"
:timestamp = "formatTime(run.started_at)"
: type = "run.status === 'success' ? 'success' : run.status === 'failed' ? 'danger' : 'primary'"
>
< button
class = "run-item"
type = "button"
@click ="go('/ai-platform/workflow-runs')"
>
< span >
< strong > { { run . workflow _name } } < / strong >
< small >
{ { run . total _steps } } 步 · { { run . total _tokens } } tokens
< / small >
< / span >
< ElTag size = "small" >
{ { run . status } }
< / ElTag >
< / button >
< / ElTimelineItem >
< / ElTimeline >
< ElEmpty v-else description = "暂无执行记录" / >
< / ElCard >
< / section >
< section class = "content-grid lower" >
< ElCard shadow = "never" class = "panel-card" >
< template # header >
< div class = "card-header" >
< span > 流程编排 < / span >
< ElButton link type = "primary" @click ="go('/ai-platform/workflow')" >
查看流程
< / ElButton >
< / div >
< / template >
< div v-if = "workflows.length" class="list-stack compact" >
< button
v-for = "workflow in workflows"
:key = "workflow.id"
class = "list-row"
type = "button"
@click ="go(`/ai-platform/workflow/editor/${workflow.id}`)"
>
< span >
< strong > { { workflow . name } } < / strong >
< small > { { workflow . description || workflow . code } } < / small >
< / span >
< ElTag type = "info" > { { workflow . workflow _type } } < / ElTag >
< / button >
< / div >
< ElEmpty v-else description = "暂无流程" / >
< / ElCard >
< ElCard shadow = "never" class = "panel-card" >
< template # header >
< div class = "card-header" >
< span > 知识资产 < / span >
< ElButton
link
type = "primary"
@click ="go('/ai-platform/knowledge-base')"
>
查看知识库
< / ElButton >
< / div >
< / template >
< div v-if = "knowledgeBases.length" class="knowledge-strip" >
< button
v-for = "kb in knowledgeBases"
:key = "kb.id"
class = "knowledge-item"
type = "button"
@click ="go(`/ai-platform/knowledge/detail/${kb.id}`)"
>
< BookOpen :size = "18" / >
< span >
< strong > { { kb . name } } < / strong >
< small >
{ { kb . document _count } } 文档 · { { kb . segment _count } } 分段
< / small >
< / span >
< / button >
< / div >
< ElEmpty v-else description = "暂无知识库" / >
< / ElCard >
< / section >
< section class = "health-band" >
< div >
< CircleCheckBig :size = "18" / >
< span > 基础后台状态正常 , 用户 、 角色 、 菜单和公告入口可用 。 < / span >
< / div >
< div >
< GitBranch :size = "18" / >
< span > AI 协作资源已加载 , 智能体 、 流程和 Codex 入口可用 。 < / span >
< / div >
< div >
< Clock3 :size = "18" / >
< span > 最近执行记录已同步 , 刷新后可查看最新运行状态 。 < / span >
< / div >
< / section >
< / div >
< / Page >
< / template >
< style scoped >
. control - center {
display : flex ;
flex - direction : column ;
gap : 16 px ;
}
. hero - panel {
align - items : center ;
background :
linear - gradient ( 135 deg , rgb ( 15 23 42 / 92 % ) , rgb ( 32 41 64 / 88 % ) ) ,
radial - gradient ( circle at 72 % 24 % , rgb ( 20 184 166 / 38 % ) , transparent 30 % ) ;
border - radius : 8 px ;
color : # fff ;
display : flex ;
justify - content : space - between ;
min - height : 178 px ;
overflow : hidden ;
padding : 28 px 32 px ;
}
. eyebrow ,
. hero - actions ,
. card - header ,
. health - band div {
align - items : center ;
display : flex ;
}
. eyebrow {
color : rgb ( 204 251 241 ) ;
font - size : 13 px ;
gap : 8 px ;
margin - bottom : 14 px ;
}
. hero - panel h1 {
font - size : 28 px ;
font - weight : 650 ;
line - height : 1.25 ;
margin : 0 0 10 px ;
}
. hero - panel p {
color : rgb ( 226 232 240 ) ;
line - height : 1.7 ;
margin : 0 ;
max - width : 680 px ;
}
. hero - actions {
flex - shrink : 0 ;
gap : 10 px ;
}
. metric - grid {
display : grid ;
gap : 12 px ;
grid - template - columns : repeat ( 4 , minmax ( 0 , 1 fr ) ) ;
}
. metric - card : deep ( . el - card _ _body ) {
align - items : center ;
display : flex ;
gap : 14 px ;
padding : 18 px ;
}
. metric - icon {
align - items : center ;
border - radius : 8 px ;
display : flex ;
height : 46 px ;
justify - content : center ;
width : 46 px ;
}
. metric - icon . agent {
background : rgb ( 219 234 254 ) ;
color : rgb ( 37 99 235 ) ;
}
. metric - icon . workflow {
background : rgb ( 204 251 241 ) ;
color : rgb ( 13 148 136 ) ;
}
. metric - icon . knowledge {
background : rgb ( 254 243 199 ) ;
color : rgb ( 180 83 9 ) ;
}
. metric - icon . run {
background : rgb ( 220 252 231 ) ;
color : rgb ( 22 163 74 ) ;
}
. metric - card span ,
. list - row small ,
. knowledge - item small {
color : hsl ( var ( -- muted - foreground ) ) ;
}
. metric - card strong {
display : block ;
font - size : 28 px ;
line - height : 1.1 ;
margin : 4 px 0 ;
}
. metric - card small {
color : hsl ( var ( -- muted - foreground ) ) ;
}
. content - grid {
display : grid ;
gap : 16 px ;
grid - template - columns : minmax ( 0 , 1.35 fr ) minmax ( 360 px , 0.65 fr ) ;
}
. content - grid . lower {
grid - template - columns : repeat ( 2 , minmax ( 0 , 1 fr ) ) ;
}
. panel - card {
border - radius : 8 px ;
}
. card - header {
justify - content : space - between ;
}
. card - header span {
font - weight : 650 ;
}
. quick - grid {
display : grid ;
gap : 12 px ;
grid - template - columns : repeat ( 2 , minmax ( 0 , 1 fr ) ) ;
}
. quick - entry ,
. admin - entry ,
. list - row ,
. run - item ,
. knowledge - item {
background : hsl ( var ( -- background ) ) ;
border : 1 px solid hsl ( var ( -- border ) ) ;
border - radius : 8 px ;
color : inherit ;
cursor : pointer ;
text - align : left ;
transition :
border - color 0.18 s ease ,
box - shadow 0.18 s ease ,
transform 0.18 s ease ;
}
. quick - entry : hover ,
. admin - entry : hover ,
. list - row : hover ,
. run - item : hover ,
. knowledge - item : hover {
border - color : hsl ( var ( -- primary ) / 55 % ) ;
box - shadow : 0 8 px 24 px rgb ( 15 23 42 / 8 % ) ;
transform : translateY ( - 1 px ) ;
}
. quick - entry {
display : grid ;
gap : 8 px ;
min - height : 126 px ;
padding : 16 px ;
}
. quick - entry svg {
color : hsl ( var ( -- primary ) ) ;
}
. quick - entry span ,
. admin - entry span ,
. knowledge - item strong {
font - weight : 650 ;
}
. quick - entry small {
color : hsl ( var ( -- muted - foreground ) ) ;
line - height : 1.6 ;
}
. admin - grid {
display : grid ;
gap : 10 px ;
}
. admin - entry {
align - items : center ;
display : flex ;
gap : 10 px ;
height : 48 px ;
padding : 0 14 px ;
}
. admin - entry svg {
color : hsl ( var ( -- primary ) ) ;
}
. list - stack {
display : grid ;
gap : 10 px ;
}
. list - stack . compact {
gap : 8 px ;
}
. list - row ,
. run - item {
align - items : center ;
display : flex ;
justify - content : space - between ;
min - height : 62 px ;
padding : 10 px 12 px ;
width : 100 % ;
}
. list - row span ,
. run - item span ,
. knowledge - item span {
display : grid ;
gap : 4 px ;
min - width : 0 ;
}
. list - row strong ,
. run - item strong ,
. knowledge - item strong {
overflow : hidden ;
text - overflow : ellipsis ;
white - space : nowrap ;
}
. run - timeline {
padding - top : 4 px ;
}
. run - item {
border : 0 ;
min - height : 50 px ;
padding : 4 px 0 ;
}
. knowledge - strip {
display : grid ;
gap : 10 px ;
grid - template - columns : repeat ( 2 , minmax ( 0 , 1 fr ) ) ;
}
. knowledge - item {
align - items : center ;
display : flex ;
gap : 10 px ;
min - height : 72 px ;
padding : 12 px ;
}
. knowledge - item svg {
color : hsl ( var ( -- primary ) ) ;
flex - shrink : 0 ;
}
. health - band {
background : hsl ( var ( -- card ) ) ;
border : 1 px solid hsl ( var ( -- border ) ) ;
border - radius : 8 px ;
display : grid ;
gap : 10 px ;
grid - template - columns : repeat ( 3 , minmax ( 0 , 1 fr ) ) ;
padding : 14 px 16 px ;
}
. health - band div {
color : hsl ( var ( -- muted - foreground ) ) ;
gap : 8 px ;
min - width : 0 ;
}
. health - band svg {
color : hsl ( var ( -- primary ) ) ;
flex - shrink : 0 ;
}
@ media ( max - width : 1180 px ) {
. metric - grid ,
. content - grid ,
. content - grid . lower ,
. health - band {
grid - template - columns : repeat ( 2 , minmax ( 0 , 1 fr ) ) ;
}
}
@ media ( max - width : 720 px ) {
. hero - panel {
align - items : flex - start ;
flex - direction : column ;
gap : 18 px ;
padding : 22 px ;
}
. hero - panel h1 {
font - size : 24 px ;
}
. hero - actions ,
. hero - actions . el - button {
width : 100 % ;
}
. metric - grid ,
. content - grid ,
. content - grid . lower ,
. quick - grid ,
. knowledge - strip ,
. health - band {
grid - template - columns : 1 fr ;
}
}
< / style >