2026-03-05 10:49:46 +08:00
< script setup lang = "ts" >
2026-03-12 15:23:13 +08:00
import { ref , computed , onMounted , onUnmounted } from 'vue'
const API _BASE = 'http://localhost:8082'
// Skills 接口
interface Skill {
id : string
skill _name : string
skill _type : string
skill _desc : string
path : string
status : string
}
2026-03-05 10:49:46 +08:00
2026-03-11 16:26:10 +08:00
// Agents 数据
const agents = ref ( [
{ id : 1 , name : 'Claude Agent' , avatar : '🧠' , description : 'General purpose AI assistant' , accentColor : '#f97316' , gradient : 'from-orange-500/20 to-amber-500/20' , status : 'running' as const , framework : 'Google ADK' , model : 'gemini-2.0-flash' , mcpServers : 2 , createdAt : '2025-04-10' } ,
{ id : 2 , name : 'Code Assistant' , avatar : '💻' , description : 'Specialized in code generation' , accentColor : '#3b82f6' , gradient : 'from-blue-500/20 to-cyan-500/20' , status : 'running' as const , framework : 'OpenAI' , model : 'gpt-4o' , mcpServers : 1 , createdAt : '2025-04-08' } ,
{ id : 3 , name : 'Data Analyst' , avatar : '📊' , description : 'Data analysis and visualization' , accentColor : '#10b981' , gradient : 'from-emerald-500/20 to-green-500/20' , status : 'stopped' as const , framework : 'PydanticAI' , model : 'gpt-4o-mini' , mcpServers : 3 , createdAt : '2025-04-05' } ,
{ id : 4 , name : 'Research Bot' , avatar : '🔬' , description : 'Academic research assistant' , accentColor : '#8b5cf6' , gradient : 'from-violet-500/20 to-purple-500/20' , status : 'running' as const , framework : 'LangChain' , model : 'claude-3-5-sonnet' , mcpServers : 2 , createdAt : '2025-04-12' } ,
{ id : 5 , name : '客服助手' , avatar : '🎧' , description : 'Customer support agent' , accentColor : '#ec4899' , gradient : 'from-pink-500/20 to-rose-500/20' , status : 'running' as const , framework : 'Google ADK' , model : 'gemini-1.5-pro' , mcpServers : 4 , createdAt : '2025-04-11' } ,
] )
2026-03-11 14:26:25 +08:00
// 创建智能体弹窗状态
const showCreateModal = ref ( false )
const isCreating = ref ( false )
const newAgent = ref ( {
name : '' ,
description : '' ,
2026-03-12 15:23:13 +08:00
skillsMode : 'all' as 'all' | 'include' | 'exclude' ,
selectedSkills : [ ] as string [ ] ,
2026-03-11 14:26:25 +08:00
knowledge : '' ,
prompt : '' ,
2026-03-12 10:49:44 +08:00
avatar : '🤖' ,
2026-03-11 14:26:25 +08:00
} )
2026-03-12 15:23:13 +08:00
// Skills 选择器状态
const showSkillsDropdown = ref ( false )
const showSubSkillsDropdown = ref ( false )
// Skills 模式选项
const skillsModeOptions = [
{ value : 'all' , label : 'All Skills' , desc : 'Use all available skills' } ,
{ value : 'include' , label : 'Include Skills' , desc : 'Select specific skills to use' } ,
{ value : 'exclude' , label : 'Exclude Skills' , desc : 'Select skills to exclude' } ,
]
// 切换技能模式下拉框
const toggleSkillsMode = ( ) => {
showSkillsDropdown . value = ! showSkillsDropdown . value
showSubSkillsDropdown . value = false
}
// 选择技能模式
const selectSkillsMode = ( mode : 'all' | 'include' | 'exclude' ) => {
newAgent . value . skillsMode = mode
if ( mode === 'all' ) {
newAgent . value . selectedSkills = [ ]
}
showSkillsDropdown . value = false
if ( mode === 'include' || mode === 'exclude' ) {
showSubSkillsDropdown . value = true
}
}
// 处理技能模式点击(用于模板)
const handleSkillsModeClick = ( mode : 'all' | 'include' | 'exclude' ) => {
selectSkillsMode ( mode )
}
// 切换子下拉框
const toggleSubSkillsDropdown = ( ) => {
showSubSkillsDropdown . value = ! showSubSkillsDropdown . value
}
// 关闭所有下拉框
const closeAllDropdowns = ( ) => {
showSkillsDropdown . value = false
showSubSkillsDropdown . value = false
}
// 获取显示文本
const getSkillsDisplayText = ( ) => {
if ( newAgent . value . skillsMode === 'all' ) {
return 'All Skills'
}
const count = newAgent . value . selectedSkills . length
if ( count === 0 ) {
return newAgent . value . skillsMode === 'include' ? 'Select skills to include...' : 'Select skills to exclude...'
}
return ` ${ count } skill ${ count > 1 ? 's' : '' } ${ newAgent . value . skillsMode === 'include' ? 'included' : 'excluded' } `
}
// 切换子下拉框中的技能选择
const toggleSkillSelection = ( skillId : string ) => {
const index = newAgent . value . selectedSkills . indexOf ( skillId )
if ( index > - 1 ) {
newAgent . value . selectedSkills . splice ( index , 1 )
} else {
newAgent . value . selectedSkills . push ( skillId )
}
}
2026-03-12 10:49:44 +08:00
// 头像选项
const avatarOptions = [
'🤖' , '🧠' , '💻' , '📊' , '🔬' , '🎧' , '✨' , '💬' , '🔮' , '🌙' ,
'🐉' , '☁️' , '🎨' , '🎯' , '🚀' , '⚡' , '🔥' , '💡' , '🎭' , '🎪'
]
2026-03-12 15:23:13 +08:00
// Skills 列表
const skillsList = ref < Skill [ ] > ( [ ] )
const skillsLoading = ref ( false )
// 获取 skills 列表
const fetchSkills = async ( ) => {
skillsLoading . value = true
try {
const response = await fetch ( ` ${ API _BASE } /skill/list ` )
const result = await response . json ( )
if ( result . list ) {
skillsList . value = result . list
}
} catch ( error ) {
console . error ( 'Failed to fetch skills:' , error )
} finally {
skillsLoading . value = false
}
}
// Skills 选项(从真实列表生成)
const skillsOptions = computed ( ( ) => {
return skillsList . value . map ( skill => ( {
value : skill . id ,
label : skill . skill _name ,
desc : skill . skill _desc ,
} ) )
} )
2026-03-11 14:26:25 +08:00
// Knowledge 选项
const knowledgeOptions = [
{ value : 'general' , label : 'General Knowledge' } ,
{ value : 'codebase' , label : 'Codebase' } ,
{ value : 'docs' , label : 'Documentation' } ,
{ value : 'api' , label : 'API Reference' } ,
]
2026-03-12 15:23:13 +08:00
// Skills 选择器状态
const skillsSearch = ref ( '' )
// 获取 skill 标签
const getSkillLabel = ( id : string ) => {
return skillsOptions . value . find ( s => s . value === id ) ? . label || id
}
// 过滤后的 skills
const filteredSkills = computed ( ( ) => {
if ( ! skillsSearch . value ) return skillsOptions . value
const search = skillsSearch . value . toLowerCase ( )
return skillsOptions . value . filter ( s =>
s . label . toLowerCase ( ) . includes ( search ) ||
( s . desc && s . desc . toLowerCase ( ) . includes ( search ) )
)
} )
// 是否全选
const isAllSelected = computed ( ( ) => {
return skillsOptions . value . length > 0 && newAgent . value . selectedSkills . length === skillsOptions . value . length
} )
// 是否半选
const isIndeterminate = computed ( ( ) => {
return newAgent . value . selectedSkills . length > 0 && newAgent . value . selectedSkills . length < skillsOptions . value . length
} )
// 切换下拉框
const toggleSkillsDropdown = ( ) => {
showSkillsDropdown . value = ! showSkillsDropdown . value
if ( ! showSkillsDropdown . value ) {
skillsSearch . value = ''
}
}
// 关闭下拉框
const closeSkillsDropdown = ( ) => {
showSkillsDropdown . value = false
skillsSearch . value = ''
}
// 全选 skills
const selectAllSkills = ( ) => {
newAgent . value . selectedSkills = skillsOptions . value . map ( s => s . value )
}
// 清空 skills
const clearSkills = ( ) => {
newAgent . value . selectedSkills = [ ]
}
// 切换全选
const toggleSelectAll = ( ) => {
if ( isAllSelected . value ) {
newAgent . value . selectedSkills = [ ]
} else {
newAgent . value . selectedSkills = skillsOptions . value . map ( s => s . value )
}
}
// 点击外部关闭下拉框
const handleClickOutside = ( e : MouseEvent ) => {
const target = e . target as HTMLElement
if ( ! target . closest ( '.skills-selector' ) ) {
closeSkillsDropdown ( )
}
}
// 页面加载时获取 skills
onMounted ( ( ) => {
fetchSkills ( )
document . addEventListener ( 'click' , handleClickOutside )
} )
onUnmounted ( ( ) => {
document . removeEventListener ( 'click' , handleClickOutside )
} )
2026-03-11 14:26:25 +08:00
// 打开创建弹窗
const openCreateModal = ( ) => {
2026-03-12 15:23:13 +08:00
newAgent . value = {
name : '' ,
description : '' ,
skillsMode : 'all' as const ,
selectedSkills : [ ] ,
knowledge : '' ,
prompt : '' ,
avatar : '🤖'
}
2026-03-11 14:26:25 +08:00
showCreateModal . value = true
}
// 创建智能体
const createAgent = async ( ) => {
2026-03-12 15:23:13 +08:00
if ( ! newAgent . value . name || ( newAgent . value . skillsMode !== 'all' && newAgent . value . selectedSkills . length === 0 ) || ! newAgent . value . knowledge ) {
2026-03-11 14:26:25 +08:00
return
}
isCreating . value = true
try {
2026-03-12 15:23:13 +08:00
// 处理 skills 标签
const skillsLabels = newAgent . value . selectedSkills . map ( ( id : string ) => getSkillLabel ( id ) ) . join ( ', ' )
2026-03-11 14:26:25 +08:00
// 模拟创建
const newId = Math . max ( ... agents . value . map ( a => a . id ) ) + 1
agents . value . unshift ( {
id : newId ,
name : newAgent . value . name ,
2026-03-12 10:49:44 +08:00
avatar : newAgent . value . avatar ,
2026-03-11 14:26:25 +08:00
description : newAgent . value . description ,
accentColor : '#f97316' ,
gradient : 'from-orange-500/20 to-amber-500/20' ,
status : 'stopped' ,
2026-03-12 15:23:13 +08:00
framework : skillsLabels || 'None' ,
2026-03-11 14:26:25 +08:00
model : knowledgeOptions . find ( k => k . value === newAgent . value . knowledge ) ? . label || newAgent . value . knowledge ,
mcpServers : 0 ,
createdAt : new Date ( ) . toISOString ( ) . split ( 'T' ) [ 0 ] ,
} )
showCreateModal . value = false
} finally {
isCreating . value = false
}
}
2026-03-10 16:09:09 +08:00
const searchQuery = ref ( '' )
const filterStatus = ref ( 'all' )
// 过滤后的 agents
const filteredAgents = computed ( ( ) => {
return agents . value . filter ( agent => {
const matchSearch = agent . name . toLowerCase ( ) . includes ( searchQuery . value . toLowerCase ( ) ) ||
agent . framework . toLowerCase ( ) . includes ( searchQuery . value . toLowerCase ( ) )
const matchStatus = filterStatus . value === 'all' || agent . status === filterStatus . value
return matchSearch && matchStatus
} )
} )
// 统计数据
const stats = computed ( ( ) => ( {
total : agents . value . length ,
running : agents . value . filter ( a => a . status === 'running' ) . length ,
stopped : agents . value . filter ( a => a . status === 'stopped' ) . length ,
} ) )
// 状态颜色
const statusClass = ( status : string ) => {
switch ( status ) {
case 'running' : return 'bg-primary-success'
case 'stopped' : return 'bg-gray-500'
default : return 'bg-gray-500'
2026-03-05 10:49:46 +08:00
}
}
2026-03-10 15:42:21 +08:00
2026-03-10 16:09:09 +08:00
// 切换状态
2026-03-11 16:26:10 +08:00
const toggleStatus = ( agent : any ) => {
2026-03-10 16:09:09 +08:00
agent . status = agent . status === 'running' ? 'stopped' : 'running'
2026-03-10 15:42:21 +08:00
}
2026-03-10 16:09:09 +08:00
// 删除 Agent
const deleteAgent = ( id : number ) => {
agents . value = agents . value . filter ( a => a . id !== id )
2026-03-10 15:42:21 +08:00
}
2026-03-05 10:49:46 +08:00
< / script >
< template >
2026-03-10 16:09:09 +08:00
<!-- 主内容区域 -- >
< div class = "p-6 min-h-screen" >
<!-- 顶部导航 -- >
< div class = "flex justify-between items-center mb-6" >
< div class = "flex items-center gap-2" >
2026-03-10 17:39:10 +08:00
< i class = "fa-solid fa-robot text-orange-500" > < / i >
2026-03-10 16:09:09 +08:00
< span class = "font-medium" > Agents < / span >
< / div >
2026-03-11 14:26:25 +08:00
< button @click ="openCreateModal" class = "btn-primary" >
2026-03-10 16:09:09 +08:00
< i class = "fa-solid fa-plus" > < / i >
New Agent
< / button >
< / div >
<!-- 搜索和筛选 -- >
< div class = "flex gap-4 mb-6" >
< div class = "flex-1 relative" >
< i class = "fa-solid fa-search absolute left-3 top-1/2 -translate-y-1/2 text-gray-400" > < / i >
< input
v - model = "searchQuery"
type = "text"
placeholder = "Search agents by name or framework..."
class = "search-input w-full"
>
< / div >
< el-select v-model = "filterStatus" placeholder="Select" class="w-40" size="large" >
< el-option label = "All Status" value = "all" / >
< el-option label = "Running" value = "running" / >
< el-option label = "Stopped" value = "stopped" / >
< / el-select >
< / div >
2026-03-05 10:49:46 +08:00
2026-03-10 16:09:09 +08:00
<!-- Agents 列表 -- >
< div class = "bg-dark-700 rounded-xl overflow-hidden" >
< table class = "w-full" >
< thead class = "bg-dark-600" >
< tr >
< th class = "text-left px-5 py-3 text-sm font-medium text-gray-400" > Agent Name < / th >
< th class = "text-left px-5 py-3 text-sm font-medium text-gray-400" > Framework < / th >
< th class = "text-left px-5 py-3 text-sm font-medium text-gray-400" > Model < / th >
< th class = "text-center px-5 py-3 text-sm font-medium text-gray-400" > MCP < / th >
< th class = "text-left px-5 py-3 text-sm font-medium text-gray-400" > Status < / th >
< th class = "text-left px-5 py-3 text-sm font-medium text-gray-400" > Created < / th >
< th class = "text-right px-5 py-3 text-sm font-medium text-gray-400" > Actions < / th >
< / tr >
< / thead >
< tbody >
< tr v-for = "agent in filteredAgents" :key="agent.id" class="table-row" >
< td class = "px-5 py-4" >
2026-03-10 15:42:21 +08:00
< div class = "flex items-center gap-3" >
2026-03-05 10:49:46 +08:00
< div
2026-03-10 16:09:09 +08:00
class = "w-10 h-10 rounded-lg flex items-center justify-center text-lg"
2026-03-10 15:42:21 +08:00
: style = "{ backgroundColor: agent.accentColor + '20', color: agent.accentColor }"
2026-03-05 10:49:46 +08:00
>
2026-03-10 15:42:21 +08:00
{ { agent . avatar } }
2026-03-05 10:49:46 +08:00
< / div >
2026-03-10 16:09:09 +08:00
< div >
< div class = "font-medium text-white" > { { agent . name } } < / div >
< div class = "text-xs text-gray-500" > { { agent . description } } < / div >
2026-03-05 10:49:46 +08:00
< / div >
< / div >
2026-03-10 16:09:09 +08:00
< / td >
< td class = "px-5 py-4" >
< span class = "bg-dark-500 px-2 py-1 rounded text-sm text-gray-300" > { { agent . framework } } < / span >
< / td >
< td class = "px-5 py-4 text-gray-300" > { { agent . model } } < / td >
< td class = "px-5 py-4 text-center" >
< span class = "text-primary-cyan" > { { agent . mcpServers } } < / span >
< / td >
< td class = "px-5 py-4" >
< div class = "flex items-center gap-2" >
< span class = "w-2 h-2 rounded-full" :class = "statusClass(agent.status)" > < / span >
< span class = "capitalize text-sm text-gray-300" > { { agent . status } } < / span >
< / div >
< / td >
< td class = "px-5 py-4 text-gray-400 text-sm" > { { agent . createdAt } } < / td >
< td class = "px-5 py-4" >
< div class = "flex items-center justify-end gap-2" >
< button
@ click = "toggleStatus(agent)"
class = "btn-icon"
: title = "agent.status === 'running' ? 'Stop' : 'Start'"
>
< i : class = "['fa-solid', agent.status === 'running' ? 'fa-stop' : 'fa-play', 'text-gray-400']" > < / i >
< / button >
< button class = "btn-icon" title = "Edit" >
< i class = "fa-solid fa-pen text-gray-400" > < / i >
< / button >
< button class = "btn-icon" title = "Settings" >
< i class = "fa-solid fa-gear text-gray-400" > < / i >
< / button >
< button
@ click = "deleteAgent(agent.id)"
class = "btn-icon"
title = "Delete"
>
< i class = "fa-solid fa-trash text-gray-400 hover:text-primary-danger" > < / i >
< / button >
< / div >
< / td >
< / tr >
< / tbody >
< / table >
<!-- 空状态 -- >
< div v-if = "filteredAgents.length === 0" class="py-12 text-center text-gray-500" >
< i class = "fa-solid fa-robot text-4xl mb-3" > < / i >
< p > No agents found < / p >
2026-03-05 10:49:46 +08:00
< / div >
2026-03-10 16:09:09 +08:00
< / div >
2026-03-05 10:49:46 +08:00
< / div >
2026-03-11 14:26:25 +08:00
<!-- 创建智能体弹窗 -- >
< Teleport to = "body" >
< div v-if = "showCreateModal" class="fixed inset-0 bg-black/60 flex items-center justify-center z-50" >
< div class = "bg-dark-700 rounded-2xl w-full max-w-lg border border-dark-500 shadow-2xl" >
< div class = "flex items-center justify-between p-5 border-b border-dark-500" >
< h3 class = "text-lg font-semibold" > Create New Agent < / h3 >
< button @click ="showCreateModal = false" class = "text-gray-400 hover:text-white transition-colors" >
< i class = "fa-solid fa-xmark text-xl" > < / i >
< / button >
< / div >
< div class = "p-5 space-y-4" >
< div >
< label class = "block text-sm font-medium text-gray-300 mb-2" > Agent Name * < / label >
< input
v - model = "newAgent.name"
type = "text"
placeholder = "Enter agent name..."
class = "w-full bg-dark-600 border border-dark-500 rounded-lg px-4 py-2.5 text-white placeholder-gray-500 focus:outline-none focus:border-primary-orange"
>
< / div >
< div >
< label class = "block text-sm font-medium text-gray-300 mb-2" > Description < / label >
< textarea
v - model = "newAgent.description"
rows = "3"
placeholder = "Describe what this agent does..."
class = "w-full bg-dark-600 border border-dark-500 rounded-lg px-4 py-2.5 text-white placeholder-gray-500 focus:outline-none focus:border-primary-orange resize-none"
> < / textarea >
< / div >
2026-03-12 10:49:44 +08:00
< div >
< label class = "block text-sm font-medium text-gray-300 mb-2" > Avatar < / label >
< div class = "flex flex-wrap gap-2" >
< button
v - for = "avatar in avatarOptions"
: key = "avatar"
type = "button"
@ click = "newAgent.avatar = avatar"
class = "w-10 h-10 rounded-lg flex items-center justify-center text-lg transition-all"
: class = "newAgent.avatar === avatar ? 'bg-primary-orange text-white ring-2 ring-orange-400' : 'bg-dark-600 text-gray-300 hover:bg-dark-500'"
>
{ { avatar } }
< / button >
< / div >
< / div >
2026-03-11 14:26:25 +08:00
< div >
< label class = "block text-sm font-medium text-gray-300 mb-2" > Skills * < / label >
2026-03-12 15:23:13 +08:00
<!-- 技能模式选择 - 两级下拉框 -- >
< div class = "skills-selector" >
<!-- 已选 tags 显示区域 -- >
< div class = "selected-tags" @click ="toggleSkillsDropdown" >
< div v-if = "newAgent.skillsMode === 'all'" class="placeholder" >
All Skills
< / div >
< div v-else class = "tags-container" >
< span
v - for = "skillId in newAgent.selectedSkills.slice(0, 3)"
: key = "skillId"
class = "selected-tag"
>
{ { getSkillLabel ( skillId ) } }
< / span >
< span v-if = "newAgent.selectedSkills.length > 3" class="more-tag" >
+ { { newAgent . selectedSkills . length - 3 } }
< / span >
< / div >
< svg class = "dropdown-icon" : class = "{ 'rotate-180': showSkillsDropdown }" xmlns = "http://www.w3.org/2000/svg" width = "16" height = "16" viewBox = "0 0 24 24" fill = "none" stroke = "currentColor" stroke -width = " 2 " stroke -linecap = " round " stroke -linejoin = " round " >
< path d = "m6 9 6 6 6-6" / >
< / svg >
< / div >
<!-- 第一级下拉面板 : 模式选择 -- >
< Transition name = "dropdown" >
< div v-if = "showSkillsDropdown" class="dropdown-panel" >
< div class = "skills-mode-options" >
< div
v - for = "option in skillsModeOptions"
: key = "option.value"
class = "skills-mode-item"
: class = "{ 'active': newAgent.skillsMode === option.value }"
@ click = "handleSkillsModeClick(option.value as 'all' | 'include' | 'exclude')"
>
< div class = "mode-radio" >
< div v-if = "newAgent.skillsMode === option.value" class="radio-dot" > < / div >
< / div >
< div class = "mode-content" >
< span class = "mode-label" > { { option . label } } < / span >
< span class = "mode-desc" > { { option . desc } } < / span >
< / div >
<!-- 显示展开图标 -- >
< svg v-if = "option.value !== 'all'" class="sub-arrow" xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" >
< path d = "m9 18 6-6-6-6" / >
< / svg >
< / div >
< / div >
< / div >
< / Transition >
<!-- 第二级下拉面板 : 技能列表 ( 悬浮窗形式 ) -- >
< Transition name = "dropdown" >
< div v-if = "showSubSkillsDropdown" class="sub-dropdown-panel" @click.stop >
<!-- 标题 -- >
< div class = "sub-dropdown-header" >
< span class = "sub-dropdown-title" >
{ { newAgent . skillsMode === 'include' ? 'Select skills to include' : 'Select skills to exclude' } }
< / span >
< button @click ="showSubSkillsDropdown = false" class = "sub-dropdown-close" >
< svg xmlns = "http://www.w3.org/2000/svg" width = "16" height = "16" viewBox = "0 0 24 24" fill = "none" stroke = "currentColor" stroke -width = " 2 " stroke -linecap = " round " stroke -linejoin = " round " >
< path d = "M18 6 6 18" / > < path d = "m6 6 12 12" / >
< / svg >
< / button >
< / div >
<!-- 搜索框 -- >
< div class = "search-box" >
< svg xmlns = "http://www.w3.org/2000/svg" width = "14" height = "14" viewBox = "0 0 24 24" fill = "none" stroke = "currentColor" stroke -width = " 2 " stroke -linecap = " round " stroke -linejoin = " round " class = "search-icon" >
< circle cx = "11" cy = "11" r = "8" / > < path d = "m21 21-4.3-4.3" / >
< / svg >
< input
v - model = "skillsSearch"
type = "text"
placeholder = "Search skills..."
class = "search-input"
>
< / div >
<!-- 操作按钮 -- >
< div class = "action-bar" >
< label class = "checkbox-label cursor-pointer" >
< input
type = "checkbox"
: checked = "isAllSelected"
: indeterminate = "isIndeterminate"
@ change = "toggleSelectAll"
class = "checkbox"
>
< span class = "checkbox-text" > Select All < / span >
< / label >
< button
v - if = "newAgent.selectedSkills.length > 0"
type = "button"
@ click = "clearSkills"
class = "clear-btn"
>
Clear
< / button >
< / div >
<!-- 技能列表 -- >
< div class = "options-list" >
< template v-if = "filteredSkills.length > 0" >
< label
v - for = "skill in filteredSkills"
: key = "skill.value"
class = "option-item"
>
< input
type = "checkbox"
: value = "skill.value"
v - model = "newAgent.selectedSkills"
class = "checkbox"
>
< div class = "option-content" >
< span class = "option-label" > { { skill . label } } < / span >
< span v-if = "skill.desc" class="option-desc" > {{ skill.desc }} < / span >
< / div >
< / label >
< / template >
< div v-else class = "no-results" >
< svg xmlns = "http://www.w3.org/2000/svg" width = "24" height = "24" viewBox = "0 0 24 24" fill = "none" stroke = "currentColor" stroke -width = " 2 " stroke -linecap = " round " stroke -linejoin = " round " class = "no-results-icon" >
< path d = "M21 12a9 9 0 1 1-9-9c2.52 0 4.93 1 6.74 2.74L21 8" / >
< path d = "M21 3v5h-5" / >
< / svg >
< span > No skills available < / span >
< / div >
< / div >
< / div >
< / Transition >
< / div >
2026-03-11 14:26:25 +08:00
< / div >
< div >
< label class = "block text-sm font-medium text-gray-300 mb-2" > Knowledge * < / label >
< el-select v-model = "newAgent.knowledge" placeholder="Select knowledge" class="w-full" size="large" popper-class="dark-select-dropdown" >
< el-option v-for = "k in knowledgeOptions" :key="k.value" :label="k.label" :value="k.value" / >
< / el-select >
< / div >
< div >
< label class = "block text-sm font-medium text-gray-300 mb-2" > Custom Prompt < / label >
< textarea
v - model = "newAgent.prompt"
rows = "4"
placeholder = "Define the agent's behavior and instructions..."
class = "w-full bg-dark-600 border border-dark-500 rounded-lg px-4 py-2.5 text-white placeholder-gray-500 focus:outline-none focus:border-primary-orange resize-none"
> < / textarea >
< / div >
< / div >
< div class = "flex items-center justify-end gap-3 p-5 border-t border-dark-500" >
< button
@ click = "showCreateModal = false"
class = "px-4 py-2 rounded-lg bg-dark-600 text-gray-300 hover:bg-dark-500 transition-colors"
>
Cancel
< / button >
< button
@ click = "createAgent"
2026-03-12 15:23:13 +08:00
: disabled = "isCreating || !newAgent.name || (newAgent.skillsMode !== 'all' && newAgent.selectedSkills.length === 0) || !newAgent.knowledge"
2026-03-11 14:26:25 +08:00
class = "px-4 py-2 rounded-lg bg-gradient-to-r from-primary-orange to-red-500 text-white hover:from-orange-500 hover:to-red-600 transition-all disabled:opacity-50 disabled:cursor-not-allowed flex items-center gap-2"
>
< i v-if = "isCreating" class="fa-solid fa-circle-notch fa-spin" > < / i >
{ { isCreating ? 'Creating...' : 'Create Agent' } }
< / button >
< / div >
< / div >
< / div >
< / Teleport >
2026-03-05 10:49:46 +08:00
< / template >
2026-03-12 15:23:13 +08:00
< style scoped >
/* Skills Selector Styles */
. skills - selector {
position : relative ;
}
/* Skills Mode Options */
. skills - mode - options {
padding : 8 px ;
border - bottom : 1 px solid # 4 b5563 ;
}
. skills - mode - item {
display : flex ;
align - items : flex - start ;
gap : 12 px ;
padding : 12 px ;
border - radius : 8 px ;
cursor : pointer ;
transition : all 0.2 s ease ;
}
. skills - mode - item : hover {
background : # 374151 ;
}
. skills - mode - item . active {
background : rgba ( 249 , 115 , 22 , 0.1 ) ;
}
. mode - radio {
width : 18 px ;
height : 18 px ;
border : 2 px solid # 6 b7280 ;
border - radius : 50 % ;
display : flex ;
align - items : center ;
justify - content : center ;
flex - shrink : 0 ;
margin - top : 2 px ;
}
. skills - mode - item . active . mode - radio {
border - color : # f97316 ;
}
. radio - dot {
width : 10 px ;
height : 10 px ;
background : # f97316 ;
border - radius : 50 % ;
}
. mode - content {
display : flex ;
flex - direction : column ;
gap : 2 px ;
}
. mode - label {
font - size : 14 px ;
font - weight : 500 ;
color : # f3f4f6 ;
}
. mode - desc {
font - size : 12 px ;
color : # 9 ca3af ;
}
/* 子下拉框箭头 */
. sub - arrow {
margin - left : auto ;
color : # 9 ca3af ;
transition : transform 0.2 s ease ;
}
. skills - mode - item : hover . sub - arrow {
color : # f97316 ;
}
/* 子下拉面板 */
. sub - dropdown - panel {
position : absolute ;
top : 0 ;
left : 100 % ;
margin - left : 8 px ;
width : 280 px ;
background : # 374151 ;
border : 1 px solid # 4 b5563 ;
border - radius : 12 px ;
box - shadow : 0 10 px 40 px rgba ( 0 , 0 , 0 , 0.4 ) ;
z - index : 100 ;
overflow : hidden ;
}
. sub - dropdown - header {
display : flex ;
align - items : center ;
justify - content : space - between ;
padding : 12 px 16 px ;
border - bottom : 1 px solid # 4 b5563 ;
background : # 4 b5563 ;
}
. sub - dropdown - title {
font - size : 13 px ;
font - weight : 500 ;
color : # f3f4f6 ;
}
. sub - dropdown - close {
display : flex ;
align - items : center ;
justify - content : center ;
width : 24 px ;
height : 24 px ;
border - radius : 6 px ;
color : # 9 ca3af ;
transition : all 0.2 s ease ;
}
. sub - dropdown - close : hover {
background : # 374151 ;
color : # f3f4f6 ;
}
/* 无结果样式 */
. no - results {
display : flex ;
flex - direction : column ;
align - items : center ;
justify - content : center ;
padding : 24 px ;
color : # 6 b7280 ;
gap : 8 px ;
}
. no - results - icon {
opacity : 0.5 ;
}
. selected - tags {
display : flex ;
align - items : center ;
justify - content : space - between ;
min - height : 44 px ;
padding : 0 12 px ;
background : # 374151 ;
border : 1 px solid # 4 b5563 ;
border - radius : 8 px ;
cursor : pointer ;
transition : all 0.2 s ease ;
}
. selected - tags : hover {
border - color : # f97316 ;
}
. selected - tags . placeholder {
color : # 9 ca3af ;
font - size : 14 px ;
}
. tags - container {
display : flex ;
flex - wrap : wrap ;
gap : 6 px ;
flex : 1 ;
overflow : hidden ;
}
. selected - tag {
display : inline - flex ;
align - items : center ;
padding : 2 px 8 px ;
background : linear - gradient ( 135 deg , # f97316 0 % , # ea580c 100 % ) ;
color : white ;
font - size : 12 px ;
font - weight : 500 ;
border - radius : 4 px ;
white - space : nowrap ;
}
. more - tag {
display : inline - flex ;
align - items : center ;
padding : 2 px 8 px ;
background : # 374151 ;
color : # d1d5db ;
font - size : 12 px ;
border - radius : 4 px ;
}
. dropdown - icon {
color : # 9 ca3af ;
transition : transform 0.2 s ease ;
flex - shrink : 0 ;
}
. dropdown - icon . rotate - 180 {
transform : rotate ( 180 deg ) ;
}
/* Dropdown Panel */
. dropdown - panel {
position : absolute ;
top : calc ( 100 % + 8 px ) ;
left : 0 ;
right : 0 ;
background : # 374151 ;
border : 1 px solid # 4 b5563 ;
border - radius : 12 px ;
box - shadow : 0 20 px 40 px rgba ( 0 , 0 , 0 , 0.4 ) ;
z - index : 100 ;
overflow : hidden ;
}
/* Search Box */
. search - box {
position : relative ;
padding : 12 px ;
border - bottom : 1 px solid # 4 b5563 ;
}
. search - icon {
position : absolute ;
left : 24 px ;
top : 50 % ;
transform : translateY ( - 50 % ) ;
color : # 9 ca3af ;
}
. search - input {
width : 100 % ;
padding : 8 px 12 px 8 px 36 px ;
background : # 111827 ;
border : 1 px solid # 374151 ;
border - radius : 6 px ;
color : white ;
font - size : 14 px ;
outline : none ;
transition : border - color 0.2 s ease ;
}
. search - input : focus {
border - color : # f97316 ;
}
. search - input : : placeholder {
color : # 6 b7280 ;
}
/* Action Bar */
. action - bar {
display : flex ;
align - items : center ;
justify - content : space - between ;
padding : 10 px 12 px ;
background : # 111827 ;
border - bottom : 1 px solid # 374151 ;
}
. checkbox - label {
display : flex ;
align - items : center ;
gap : 8 px ;
cursor : pointer ;
}
. checkbox {
width : 16 px ;
height : 16 px ;
accent - color : # f97316 ;
cursor : pointer ;
}
. checkbox - text {
font - size : 13 px ;
font - weight : 500 ;
color : # f97316 ;
}
. clear - btn {
padding : 4 px 10 px ;
background : transparent ;
border : 1 px solid # 4 b5563 ;
border - radius : 4 px ;
color : # 9 ca3af ;
font - size : 12 px ;
cursor : pointer ;
transition : all 0.2 s ease ;
}
. clear - btn : hover {
background : # 374151 ;
color : white ;
}
/* Options List */
. options - list {
max - height : 240 px ;
overflow - y : auto ;
padding : 8 px ;
}
. options - list : : - webkit - scrollbar {
width : 6 px ;
}
. options - list : : - webkit - scrollbar - track {
background : # 374151 ;
}
. options - list : : - webkit - scrollbar - thumb {
background : # 4 b5563 ;
border - radius : 3 px ;
}
. option - item {
display : flex ;
align - items : flex - start ;
gap : 10 px ;
padding : 10 px 12 px ;
border - radius : 8 px ;
cursor : pointer ;
transition : background 0.15 s ease ;
}
. option - item : hover {
background : # 374151 ;
}
. option - item . checkbox {
margin - top : 2 px ;
flex - shrink : 0 ;
}
. option - content {
display : flex ;
flex - direction : column ;
gap : 2 px ;
min - width : 0 ;
flex : 1 ;
}
. option - label {
font - size : 14 px ;
font - weight : 500 ;
color : white ;
}
. option - desc {
font - size : 12 px ;
color : # 9 ca3af ;
overflow : hidden ;
text - overflow : ellipsis ;
white - space : nowrap ;
}
. no - results {
padding : 20 px ;
text - align : center ;
color : # 6 b7280 ;
font - size : 14 px ;
}
/* Dropdown Transition */
. dropdown - enter - active ,
. dropdown - leave - active {
transition : all 0.2 s ease ;
}
. dropdown - enter - from ,
. dropdown - leave - to {
opacity : 0 ;
transform : translateY ( - 8 px ) ;
}
/* Indeterminate checkbox */
input [ type = "checkbox" ] : indeterminate {
accent - color : # f97316 ;
}
< / style >