增加智能体市场菜单
This commit is contained in:
parent
7b77cbf0df
commit
b22df1b48b
@ -145,6 +145,16 @@ const router = createRouter({
|
||||
name: 'NodeDetail',
|
||||
component: () => import('../../views/dev/NodeDetail.vue')
|
||||
},
|
||||
{
|
||||
path: 'agent-marketplace',
|
||||
name: 'AgentMarketplace',
|
||||
component: () => import('../../views/dev/AgentMarketplace.vue')
|
||||
},
|
||||
{
|
||||
path: 'agent-marketplace/:name',
|
||||
name: 'AgentDetail',
|
||||
component: () => import('../../views/dev/AgentDetail.vue')
|
||||
},
|
||||
{
|
||||
path: 'app-marketplace/:name',
|
||||
name: 'AppDetail',
|
||||
|
||||
@ -64,6 +64,7 @@ function getDefaultMenus(): AppMenuItem[] {
|
||||
{ id: 'app-marketplace', key: 'AppMarketplace', label: 'App Marketplace', icon: 'tabler:shopping-cart', type: 'route', routeName: 'AppMarketplace', parentId: 'dev-group', order: 7 },
|
||||
{ id: 'my-published-apps', key: 'MyPublishedApps', label: 'My Published Apps', icon: 'tabler:cloud-upload', type: 'route', routeName: 'MyPublishedApps', parentId: 'dev-group', order: 7.5 },
|
||||
{ id: 'node-marketplace', key: 'NodeMarketplace', label: 'Node Marketplace', icon: 'carbon:add-child-node', type: 'route', routeName: 'NodeMarketplace', parentId: 'dev-group', order: 8 },
|
||||
{ id: 'agent-marketplace', key: 'AgentMarketplace', label: 'Agent Marketplace', icon: 'hugeicons:robotic', type: 'route', routeName: 'AgentMarketplace', parentId: 'dev-group', order: 8.5 },
|
||||
{ id: 'menuManager', key: 'MenuManager', label: 'Menu Management', icon: 'tabler:menu-2', type: 'route', routeName: 'MenuManager', order: 10 },
|
||||
{ id: 'settings', key: 'Settings', label: 'Settings', icon: 'tabler:settings', routeName: 'Settings', order: 11, type: 'route' }
|
||||
]
|
||||
|
||||
602
apps/jingrow/frontend/src/views/dev/AgentDetail.vue
Normal file
602
apps/jingrow/frontend/src/views/dev/AgentDetail.vue
Normal file
@ -0,0 +1,602 @@
|
||||
<template>
|
||||
<div class="agent-detail">
|
||||
<div class="page-header">
|
||||
<div class="header-content">
|
||||
<div class="header-text">
|
||||
<h1>{{ agent?.title || agent?.agent_name || t('Agent Details') }}</h1>
|
||||
</div>
|
||||
<div class="header-actions">
|
||||
<n-button @click="goBack" size="medium">
|
||||
<template #icon>
|
||||
<n-icon><Icon icon="tabler:arrow-left" /></n-icon>
|
||||
</template>
|
||||
{{ t('Back') }}
|
||||
</n-button>
|
||||
<n-button
|
||||
:type="isCurrentAgentInstalled ? 'warning' : 'primary'"
|
||||
@click="installAgent"
|
||||
size="medium"
|
||||
>
|
||||
<template #icon>
|
||||
<n-icon>
|
||||
<Icon :icon="isCurrentAgentInstalled ? 'tabler:check' : 'tabler:download'" />
|
||||
</n-icon>
|
||||
</template>
|
||||
{{ isCurrentAgentInstalled ? t('Installed') : t('Install') }}
|
||||
</n-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="loading" class="loading-container">
|
||||
<n-spin size="large">
|
||||
<template #description>
|
||||
{{ t('Loading agent details...') }}
|
||||
</template>
|
||||
</n-spin>
|
||||
</div>
|
||||
|
||||
<div v-else-if="error" class="error-container">
|
||||
<n-empty :description="error">
|
||||
<template #icon>
|
||||
<n-icon><Icon icon="tabler:alert-circle" /></n-icon>
|
||||
</template>
|
||||
</n-empty>
|
||||
</div>
|
||||
|
||||
<div v-else-if="agent" class="agent-content">
|
||||
<!-- 整体卡片布局 -->
|
||||
<div class="agent-card">
|
||||
<!-- 上部分:智能体信息 -->
|
||||
<div class="agent-info-section">
|
||||
<div class="agent-content-layout">
|
||||
<!-- 左侧:智能体图标 -->
|
||||
<div class="agent-image-section">
|
||||
<div class="agent-image">
|
||||
<div v-if="agent.icon" class="agent-icon-container">
|
||||
<Icon
|
||||
:icon="agent.icon"
|
||||
:width="120"
|
||||
:height="120"
|
||||
:style="{ color: agent.color || '#6b7280' }"
|
||||
/>
|
||||
</div>
|
||||
<div v-else class="placeholder-image">
|
||||
<n-icon size="80"><Icon icon="hugeicons:robotic" /></n-icon>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 右侧:智能体信息 -->
|
||||
<div class="agent-info-content">
|
||||
<div class="agent-header">
|
||||
<h2 class="agent-title">{{ agent.title || agent.agent_name || t('Untitled Agent') }}</h2>
|
||||
<div v-if="agent.subtitle" class="agent-subtitle">{{ agent.subtitle }}</div>
|
||||
</div>
|
||||
|
||||
<div class="info-list">
|
||||
<div v-if="agent.agent_name" class="info-item">
|
||||
<span class="label">{{ t('Agent Name') }}:</span>
|
||||
<span class="value">{{ agent.agent_name }}</span>
|
||||
</div>
|
||||
|
||||
<div v-if="agent.status" class="info-item">
|
||||
<span class="label">{{ t('Status') }}:</span>
|
||||
<span class="value">{{ agent.status }}</span>
|
||||
</div>
|
||||
|
||||
<div v-if="agent.trigger_mode" class="info-item">
|
||||
<span class="label">{{ t('Trigger Mode') }}:</span>
|
||||
<span class="value">{{ agent.trigger_mode }}</span>
|
||||
</div>
|
||||
|
||||
<div v-if="agent.creation" class="info-item">
|
||||
<span class="label">{{ t('Created') }}:</span>
|
||||
<span class="value">{{ formatDate(agent.creation) }}</span>
|
||||
</div>
|
||||
|
||||
<div v-if="agent.modified" class="info-item">
|
||||
<span class="label">{{ t('Last Updated') }}:</span>
|
||||
<span class="value">{{ formatDate(agent.modified) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 下部分:描述内容 -->
|
||||
<div v-if="agent.description" class="description-section">
|
||||
<h3>{{ t('Description') }}</h3>
|
||||
<div class="description-content" v-html="agent.description"></div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 安装进度弹窗 -->
|
||||
<InstallProgressModal
|
||||
v-model="showProgressModal"
|
||||
:progress="installProgress"
|
||||
:message="installMessage"
|
||||
:status="installStatus"
|
||||
:installing="installing"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, computed } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { get_session_api_headers } from '@/shared/api/auth'
|
||||
import { NButton, NIcon, NSpin, NEmpty, useMessage, useDialog } from 'naive-ui'
|
||||
import { Icon } from '@iconify/vue'
|
||||
import axios from 'axios'
|
||||
import { t } from '@/shared/i18n'
|
||||
import InstallProgressModal from './InstallProgressModal.vue'
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const message = useMessage()
|
||||
const dialog = useDialog()
|
||||
|
||||
const loading = ref(true)
|
||||
const error = ref('')
|
||||
const agent = ref<any>(null)
|
||||
|
||||
// 安装相关状态
|
||||
const installing = ref(false)
|
||||
const installProgress = ref(0)
|
||||
const installMessage = ref('')
|
||||
const installStatus = ref<'success' | 'error' | 'info'>('info')
|
||||
const showProgressModal = ref(false)
|
||||
|
||||
// 已安装智能体集合
|
||||
const installedAgentNames = ref<Set<string>>(new Set())
|
||||
|
||||
const agentName = computed(() => route.params.name as string)
|
||||
|
||||
// 检查当前智能体是否已安装
|
||||
const isCurrentAgentInstalled = computed(() => {
|
||||
if (!agent.value) return false
|
||||
return isAgentInstalled(agent.value.agent_name || agent.value.name || '')
|
||||
})
|
||||
|
||||
async function loadAgentDetail() {
|
||||
loading.value = true
|
||||
error.value = ''
|
||||
|
||||
try {
|
||||
const response = await axios.get(`/jingrow/agent-marketplace/${agentName.value}`)
|
||||
agent.value = response.data
|
||||
} catch (err: any) {
|
||||
console.error('Failed to load agent detail:', err)
|
||||
error.value = err.response?.data?.detail || t('Failed to load agent details')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function formatDate(dateString: string): string {
|
||||
if (!dateString) return ''
|
||||
const date = new Date(dateString)
|
||||
const year = date.getFullYear()
|
||||
const month = String(date.getMonth() + 1).padStart(2, '0')
|
||||
const day = String(date.getDate()).padStart(2, '0')
|
||||
return `${year}-${month}-${day}`
|
||||
}
|
||||
|
||||
function goBack() {
|
||||
// 从查询参数获取返回路径
|
||||
const returnTo = route.query.returnTo as string
|
||||
if (returnTo) {
|
||||
router.push(returnTo)
|
||||
return
|
||||
}
|
||||
|
||||
// 检查路由历史
|
||||
if (window.history.length > 1) {
|
||||
router.back()
|
||||
} else {
|
||||
// 默认返回智能体市场
|
||||
router.push('/agent-marketplace')
|
||||
}
|
||||
}
|
||||
|
||||
async function installAgent() {
|
||||
if (!agent.value?.agent_flow && !agent.value?.agent_name) {
|
||||
message.error(t('智能体流程数据或名称不存在'))
|
||||
return
|
||||
}
|
||||
|
||||
// 先检查智能体是否已存在
|
||||
try {
|
||||
const agentNameValue = agent.value.agent_name || agent.value.name
|
||||
if (agentNameValue) {
|
||||
const checkResponse = await axios.get(`/jingrow/check-agent/${agentNameValue}`)
|
||||
|
||||
if (checkResponse.data.exists) {
|
||||
// 显示确认对话框
|
||||
dialog.warning({
|
||||
title: t('智能体已存在'),
|
||||
content: t('智能体 "{0}" 已安装,是否覆盖安装?').replace('{0}', agentNameValue),
|
||||
positiveText: t('确认覆盖'),
|
||||
negativeText: t('取消'),
|
||||
onPositiveClick: () => {
|
||||
performInstall()
|
||||
}
|
||||
})
|
||||
return
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Check agent exists error:', error)
|
||||
}
|
||||
|
||||
performInstall()
|
||||
}
|
||||
|
||||
async function performInstall() {
|
||||
try {
|
||||
installing.value = true
|
||||
installProgress.value = 0
|
||||
installMessage.value = t('正在准备安装...')
|
||||
installStatus.value = 'info'
|
||||
showProgressModal.value = true
|
||||
|
||||
// 获取流程数据和名称
|
||||
let agentFlow = agent.value.agent_flow
|
||||
let agentNameValue = agent.value.agent_name || agent.value.name
|
||||
|
||||
if (!agentFlow) {
|
||||
throw new Error(t('智能体流程数据不存在'))
|
||||
}
|
||||
|
||||
installProgress.value = 30
|
||||
installMessage.value = t('正在安装智能体...')
|
||||
|
||||
// 如果 agent_flow 是字符串,尝试解析为 JSON
|
||||
if (typeof agentFlow === 'string') {
|
||||
try {
|
||||
agentFlow = JSON.parse(agentFlow)
|
||||
} catch (e) {
|
||||
// 如果解析失败,保持原样
|
||||
}
|
||||
}
|
||||
|
||||
const response = await axios.post('/jingrow/install-agent', {
|
||||
agent_name: agentNameValue,
|
||||
agent_flow: agentFlow
|
||||
}, {
|
||||
headers: {
|
||||
...get_session_api_headers(),
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
})
|
||||
|
||||
// 更新进度到安装完成
|
||||
installProgress.value = 100
|
||||
|
||||
if (response.data.success) {
|
||||
// 所有步骤完成后才显示成功
|
||||
installing.value = false
|
||||
installStatus.value = 'success'
|
||||
installMessage.value = t('智能体安装成功!')
|
||||
message.success(t('智能体安装成功'))
|
||||
|
||||
// 刷新已安装智能体列表
|
||||
loadInstalledAgents()
|
||||
|
||||
setTimeout(() => {
|
||||
showProgressModal.value = false
|
||||
}, 2000)
|
||||
} else {
|
||||
throw new Error(response.data.error || t('安装失败'))
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error('Install agent error:', error)
|
||||
installing.value = false
|
||||
installStatus.value = 'error'
|
||||
installMessage.value = error.response?.data?.detail || error.message || t('安装失败')
|
||||
message.error(error.response?.data?.detail || t('安装失败'))
|
||||
|
||||
setTimeout(() => {
|
||||
showProgressModal.value = false
|
||||
}, 3000)
|
||||
}
|
||||
}
|
||||
|
||||
// 加载已安装智能体列表
|
||||
async function loadInstalledAgents() {
|
||||
try {
|
||||
const response = await axios.get('/jingrow/installed-agent-names')
|
||||
if (response.data.success) {
|
||||
const agents = response.data.agents || []
|
||||
installedAgentNames.value = new Set(agents.map((a: string) => a.toLowerCase()))
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Load installed agents error:', error)
|
||||
}
|
||||
}
|
||||
|
||||
// 检查智能体是否已安装
|
||||
function isAgentInstalled(agentNameValue: string): boolean {
|
||||
if (!agentNameValue) return false
|
||||
return installedAgentNames.value.has(agentNameValue.toLowerCase())
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
loadAgentDetail()
|
||||
loadInstalledAgents()
|
||||
|
||||
// 监听全局事件
|
||||
window.addEventListener('installedAgentsUpdated', () => {
|
||||
loadInstalledAgents()
|
||||
})
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.agent-detail {
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
.page-header {
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
|
||||
.header-content {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.header-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 30px;
|
||||
}
|
||||
|
||||
.header-text h1 {
|
||||
margin: 0 0 8px 0;
|
||||
font-size: 28px;
|
||||
font-weight: 700;
|
||||
color: #1a1a1a;
|
||||
}
|
||||
|
||||
.header-text p {
|
||||
margin: 0;
|
||||
color: #666;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.loading-container,
|
||||
.error-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
min-height: 400px;
|
||||
}
|
||||
|
||||
.agent-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 32px;
|
||||
}
|
||||
|
||||
/* 整体卡片布局 */
|
||||
.agent-card {
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
padding: 24px;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 24px;
|
||||
}
|
||||
|
||||
/* 上部分:智能体信息 */
|
||||
|
||||
.agent-content-layout {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 2fr;
|
||||
gap: 50px;
|
||||
align-items: start;
|
||||
}
|
||||
|
||||
.agent-image-section {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.agent-image {
|
||||
width: 100%;
|
||||
min-height: 300px;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.agent-icon-container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.placeholder-image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #9ca3af;
|
||||
}
|
||||
|
||||
.agent-header {
|
||||
margin-bottom: 20px;
|
||||
padding-bottom: 16px;
|
||||
border-bottom: 1px solid #e5e7eb;
|
||||
}
|
||||
|
||||
.agent-title {
|
||||
margin: 0;
|
||||
font-size: 22px;
|
||||
font-weight: 600;
|
||||
color: #1a1a1a;
|
||||
}
|
||||
|
||||
.agent-subtitle {
|
||||
margin: 8px 0 0 0;
|
||||
font-size: 14px;
|
||||
color: #6b7280;
|
||||
}
|
||||
|
||||
.info-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.info-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.info-item .label {
|
||||
font-weight: 500;
|
||||
color: #374151;
|
||||
font-size: 14px;
|
||||
min-width: 80px;
|
||||
}
|
||||
|
||||
.info-item .value {
|
||||
color: #6b7280;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.link {
|
||||
color: #2563eb;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.link:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.text-muted {
|
||||
color: #9ca3af;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
/* 下部分:描述内容 */
|
||||
.description-section {
|
||||
padding-top: 24px;
|
||||
border-top: 1px solid #e5e7eb;
|
||||
}
|
||||
|
||||
.description-section h3 {
|
||||
margin: 0 0 20px 0;
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
color: #1f2937;
|
||||
}
|
||||
|
||||
.description-content {
|
||||
color: #374151;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.description-content :deep(h1),
|
||||
.description-content :deep(h2),
|
||||
.description-content :deep(h3),
|
||||
.description-content :deep(h4),
|
||||
.description-content :deep(h5),
|
||||
.description-content :deep(h6) {
|
||||
margin: 16px 0 8px 0;
|
||||
color: #1f2937;
|
||||
}
|
||||
|
||||
.description-content :deep(p) {
|
||||
margin: 8px 0;
|
||||
}
|
||||
|
||||
.description-content :deep(ul),
|
||||
.description-content :deep(ol) {
|
||||
margin: 8px 0;
|
||||
padding-left: 20px;
|
||||
}
|
||||
|
||||
.description-content :deep(li) {
|
||||
margin: 4px 0;
|
||||
}
|
||||
|
||||
.description-content :deep(code) {
|
||||
background: #f3f4f6;
|
||||
padding: 2px 6px;
|
||||
border-radius: 4px;
|
||||
font-family: 'Courier New', monospace;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.description-content :deep(pre) {
|
||||
background: #f3f4f6;
|
||||
padding: 16px;
|
||||
border-radius: 8px;
|
||||
overflow-x: auto;
|
||||
margin: 16px 0;
|
||||
}
|
||||
|
||||
.description-content :deep(blockquote) {
|
||||
border-left: 4px solid #e5e7eb;
|
||||
padding-left: 16px;
|
||||
margin: 16px 0;
|
||||
color: #6b7280;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.agent-card {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.agent-content-layout {
|
||||
grid-template-columns: 1fr;
|
||||
gap: 30px;
|
||||
}
|
||||
|
||||
.agent-image-section {
|
||||
order: 2;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.agent-image {
|
||||
max-width: 150px;
|
||||
}
|
||||
|
||||
.agent-title {
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.info-item {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.info-item .label {
|
||||
min-width: auto;
|
||||
}
|
||||
|
||||
.header-content {
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
629
apps/jingrow/frontend/src/views/dev/AgentMarketplace.vue
Normal file
629
apps/jingrow/frontend/src/views/dev/AgentMarketplace.vue
Normal file
@ -0,0 +1,629 @@
|
||||
<template>
|
||||
<div class="agent-marketplace">
|
||||
<div class="page-header">
|
||||
<div class="header-content">
|
||||
<div class="header-text">
|
||||
<h1>{{ t('Agent Marketplace') }}</h1>
|
||||
<p>{{ t('Browse and install agents from Jingrow Agent Marketplace') }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="content">
|
||||
<div class="search-container">
|
||||
<div class="search-bar">
|
||||
<n-input
|
||||
v-model:value="searchQuery"
|
||||
:placeholder="t('Search agents...')"
|
||||
clearable
|
||||
size="large"
|
||||
@keyup.enter="loadAgents"
|
||||
class="search-input"
|
||||
>
|
||||
<template #prefix>
|
||||
<n-icon><Icon icon="tabler:search" /></n-icon>
|
||||
</template>
|
||||
</n-input>
|
||||
<n-button type="primary" size="large" @click="loadAgents" class="search-button">
|
||||
<template #icon>
|
||||
<n-icon><Icon icon="tabler:search" /></n-icon>
|
||||
</template>
|
||||
{{ t('Search') }}
|
||||
</n-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="agents-section" v-if="!loading && agents.length > 0">
|
||||
<!-- 排序控件 -->
|
||||
<div class="agents-header">
|
||||
<div class="agents-title">
|
||||
</div>
|
||||
<div class="sort-controls">
|
||||
<n-select
|
||||
v-model:value="sortBy"
|
||||
:options="sortOptions"
|
||||
:placeholder="t('Sort by')"
|
||||
style="width: 150px"
|
||||
@update:value="loadAgents"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="agents-grid">
|
||||
<div v-for="agent in agents" :key="agent.name" class="agent-card">
|
||||
<!-- 智能体图标 -->
|
||||
<div class="agent-icon" @click="viewAgentDetail(agent)">
|
||||
<Icon
|
||||
v-if="agent.icon"
|
||||
:icon="agent.icon"
|
||||
:width="48"
|
||||
:height="48"
|
||||
:style="{ color: agent.color || '#6b7280' }"
|
||||
/>
|
||||
<div v-else class="agent-icon-placeholder">
|
||||
<n-icon size="48"><Icon icon="hugeicons:robotic" /></n-icon>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 智能体信息 -->
|
||||
<div class="agent-content">
|
||||
<div class="agent-header">
|
||||
<div class="agent-title-section">
|
||||
<h3 @click="viewAgentDetail(agent)" class="clickable-title">{{ agent.title || agent.agent_name || agent.name }}</h3>
|
||||
<div class="agent-name" v-if="agent.agent_name">
|
||||
{{ agent.agent_name }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="agent-description" v-if="agent.description">
|
||||
{{ truncateText(agent.description, 80) }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="agent-actions">
|
||||
<n-button type="default" @click="viewAgentDetail(agent)">
|
||||
{{ t('View Details') }}
|
||||
</n-button>
|
||||
<n-button
|
||||
v-if="isAgentInstalled(agent.agent_name || agent.name)"
|
||||
type="warning"
|
||||
@click="installAgent(agent)"
|
||||
>
|
||||
{{ t('Installed') }}
|
||||
</n-button>
|
||||
<n-button
|
||||
v-else
|
||||
type="primary"
|
||||
@click="installAgent(agent)"
|
||||
>
|
||||
{{ t('Install') }}
|
||||
</n-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 分页 -->
|
||||
<div class="pagination-container">
|
||||
<n-pagination
|
||||
v-model:page="page"
|
||||
:page-count="pageCount"
|
||||
size="large"
|
||||
show-size-picker
|
||||
:page-sizes="[20, 50, 100]"
|
||||
:page-size="pageSize"
|
||||
@update:page="loadAgents"
|
||||
@update:page-size="handlePageSizeChange"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="loading" class="loading">
|
||||
<n-spin size="large">
|
||||
<template #description>{{ t('Loading agents...') }}</template>
|
||||
</n-spin>
|
||||
</div>
|
||||
|
||||
<div v-if="!loading && agents.length === 0" class="empty">
|
||||
<n-empty :description="t('No agents found')">
|
||||
<template #icon>
|
||||
<n-icon><Icon icon="hugeicons:robotic" /></n-icon>
|
||||
</template>
|
||||
</n-empty>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 安装进度弹窗 -->
|
||||
<InstallProgressModal
|
||||
v-model="showProgressModal"
|
||||
:progress="installProgress"
|
||||
:message="installMessage"
|
||||
:status="installStatus"
|
||||
:installing="installing"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, computed, watch } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { NInput, NButton, NIcon, NSpin, NEmpty, NSelect, NPagination, useMessage, useDialog } from 'naive-ui'
|
||||
import { Icon } from '@iconify/vue'
|
||||
import axios from 'axios'
|
||||
import { t } from '@/shared/i18n'
|
||||
import { get_session_api_headers } from '@/shared/api/auth'
|
||||
import InstallProgressModal from './InstallProgressModal.vue'
|
||||
|
||||
const message = useMessage()
|
||||
const dialog = useDialog()
|
||||
const router = useRouter()
|
||||
|
||||
const searchQuery = ref('')
|
||||
const loading = ref(false)
|
||||
const agents = ref<any[]>([])
|
||||
const total = ref(0)
|
||||
const page = ref(1)
|
||||
const pageSize = ref(parseInt(localStorage.getItem('itemsPerPage') || '20'))
|
||||
const sortBy = ref('creation desc')
|
||||
|
||||
// 安装相关状态
|
||||
const installing = ref(false)
|
||||
const installProgress = ref(0)
|
||||
const installMessage = ref('')
|
||||
const installStatus = ref<'success' | 'error' | 'info'>('info')
|
||||
const showProgressModal = ref(false)
|
||||
|
||||
// 已安装智能体集合
|
||||
const installedAgentNames = ref<Set<string>>(new Set())
|
||||
|
||||
// 排序选项
|
||||
const sortOptions = computed(() => [
|
||||
{ label: t('Latest'), value: 'creation desc' },
|
||||
{ label: t('Oldest'), value: 'creation asc' },
|
||||
{ label: t('Name A-Z'), value: 'agent_name asc' },
|
||||
{ label: t('Name Z-A'), value: 'agent_name desc' },
|
||||
{ label: t('Most Popular'), value: 'modified desc' }
|
||||
])
|
||||
|
||||
// 计算总页数
|
||||
const pageCount = computed(() => Math.max(1, Math.ceil(total.value / pageSize.value)))
|
||||
|
||||
async function loadAgents() {
|
||||
loading.value = true
|
||||
try {
|
||||
const params = new URLSearchParams({
|
||||
page: page.value.toString(),
|
||||
page_size: pageSize.value.toString(),
|
||||
search: searchQuery.value,
|
||||
sort_by: sortBy.value
|
||||
})
|
||||
|
||||
const response = await axios.get(`/jingrow/agent-marketplace?${params}`)
|
||||
const data = response.data
|
||||
|
||||
// 如果API返回分页数据
|
||||
if (data.items) {
|
||||
agents.value = data.items
|
||||
total.value = data.total || 0
|
||||
} else {
|
||||
// 兼容旧API格式
|
||||
agents.value = data || []
|
||||
total.value = agents.value.length
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to load agents:', error)
|
||||
message.error(t('Failed to load agents'))
|
||||
agents.value = []
|
||||
total.value = 0
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function handlePageSizeChange(newPageSize: number) {
|
||||
pageSize.value = newPageSize
|
||||
page.value = 1
|
||||
localStorage.setItem('itemsPerPage', newPageSize.toString())
|
||||
loadAgents()
|
||||
}
|
||||
|
||||
function viewAgentDetail(agent: any) {
|
||||
// 跳转到智能体详情页面,传递返回路径
|
||||
router.push({
|
||||
path: `/agent-marketplace/${agent.name}`,
|
||||
query: { returnTo: '/agent-marketplace' }
|
||||
})
|
||||
}
|
||||
|
||||
async function installAgent(agent: any) {
|
||||
if (!agent.agent_flow && !agent.agent_name) {
|
||||
message.error(t('智能体流程数据或名称不存在'))
|
||||
return
|
||||
}
|
||||
|
||||
// 先检查智能体是否已存在
|
||||
try {
|
||||
const agentName = agent.agent_name || agent.name
|
||||
if (agentName) {
|
||||
const checkResponse = await axios.get(`/jingrow/check-agent/${agentName}`)
|
||||
|
||||
if (checkResponse.data.exists) {
|
||||
// 显示确认对话框
|
||||
dialog.warning({
|
||||
title: t('智能体已存在'),
|
||||
content: t('智能体 "{0}" 已安装,是否覆盖安装?').replace('{0}', agentName),
|
||||
positiveText: t('确认覆盖'),
|
||||
negativeText: t('取消'),
|
||||
onPositiveClick: () => {
|
||||
performInstall(agent)
|
||||
}
|
||||
})
|
||||
return
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Check agent exists error:', error)
|
||||
}
|
||||
|
||||
performInstall(agent)
|
||||
}
|
||||
|
||||
async function performInstall(agent: any) {
|
||||
try {
|
||||
installing.value = true
|
||||
installProgress.value = 0
|
||||
installMessage.value = t('正在准备安装...')
|
||||
installStatus.value = 'info'
|
||||
showProgressModal.value = true
|
||||
|
||||
// 如果没有流程数据,先获取详情
|
||||
let agentFlow = agent.agent_flow
|
||||
let agentName = agent.agent_name || agent.name
|
||||
|
||||
if (!agentFlow) {
|
||||
installMessage.value = t('正在获取智能体详情...')
|
||||
installProgress.value = 20
|
||||
|
||||
try {
|
||||
const detailResponse = await axios.get(`/jingrow/agent-marketplace/${agent.name}`)
|
||||
const detail = detailResponse.data
|
||||
agentFlow = detail.agent_flow
|
||||
agentName = detail.agent_name || agentName
|
||||
} catch (error) {
|
||||
throw new Error(t('获取智能体详情失败'))
|
||||
}
|
||||
}
|
||||
|
||||
if (!agentFlow) {
|
||||
throw new Error(t('智能体流程数据不存在'))
|
||||
}
|
||||
|
||||
installProgress.value = 40
|
||||
installMessage.value = t('正在安装智能体...')
|
||||
|
||||
// 如果 agent_flow 是字符串,尝试解析为 JSON
|
||||
if (typeof agentFlow === 'string') {
|
||||
try {
|
||||
agentFlow = JSON.parse(agentFlow)
|
||||
} catch (e) {
|
||||
// 如果解析失败,保持原样
|
||||
}
|
||||
}
|
||||
|
||||
const response = await axios.post('/jingrow/install-agent', {
|
||||
agent_name: agentName,
|
||||
agent_flow: agentFlow
|
||||
}, {
|
||||
headers: {
|
||||
...get_session_api_headers(),
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
})
|
||||
|
||||
// 更新进度到安装完成
|
||||
installProgress.value = 100
|
||||
|
||||
if (response.data.success) {
|
||||
// 所有步骤完成后才显示成功
|
||||
installing.value = false
|
||||
installStatus.value = 'success'
|
||||
installMessage.value = t('智能体安装成功!')
|
||||
message.success(t('智能体安装成功'))
|
||||
|
||||
// 刷新已安装智能体列表
|
||||
loadInstalledAgents()
|
||||
|
||||
setTimeout(() => {
|
||||
showProgressModal.value = false
|
||||
}, 2000)
|
||||
} else {
|
||||
throw new Error(response.data.error || t('安装失败'))
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error('Install agent error:', error)
|
||||
installing.value = false
|
||||
installStatus.value = 'error'
|
||||
installMessage.value = error.response?.data?.detail || error.message || t('安装失败')
|
||||
message.error(error.response?.data?.detail || t('安装失败'))
|
||||
|
||||
setTimeout(() => {
|
||||
showProgressModal.value = false
|
||||
}, 3000)
|
||||
}
|
||||
}
|
||||
|
||||
function truncateText(text: string, maxLength: number): string {
|
||||
if (!text) return ''
|
||||
if (text.length <= maxLength) return text
|
||||
return text.substring(0, maxLength) + '...'
|
||||
}
|
||||
|
||||
// 加载已安装智能体列表
|
||||
async function loadInstalledAgents() {
|
||||
try {
|
||||
const response = await axios.get('/jingrow/installed-agent-names')
|
||||
if (response.data.success) {
|
||||
const agents = response.data.agents || []
|
||||
installedAgentNames.value = new Set(agents.map((a: string) => a.toLowerCase()))
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Load installed agents error:', error)
|
||||
}
|
||||
}
|
||||
|
||||
// 检查智能体是否已安装
|
||||
function isAgentInstalled(agentName: string): boolean {
|
||||
if (!agentName) return false
|
||||
return installedAgentNames.value.has(agentName.toLowerCase())
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
loadAgents()
|
||||
loadInstalledAgents()
|
||||
|
||||
// 监听全局事件
|
||||
window.addEventListener('installedAgentsUpdated', () => {
|
||||
loadInstalledAgents()
|
||||
})
|
||||
})
|
||||
|
||||
// 监听搜索和排序变化
|
||||
watch([searchQuery, sortBy], () => {
|
||||
page.value = 1 // 重置到第一页
|
||||
loadAgents()
|
||||
}, { deep: true })
|
||||
|
||||
// 监听分页变化
|
||||
watch([page], () => {
|
||||
loadAgents()
|
||||
})
|
||||
|
||||
// 监听每页数量变化(从系统设置)
|
||||
watch(() => localStorage.getItem('itemsPerPage'), (newValue) => {
|
||||
if (newValue) {
|
||||
pageSize.value = parseInt(newValue)
|
||||
page.value = 1 // 重置到第一页
|
||||
loadAgents()
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.agent-marketplace {
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
.page-header {
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
|
||||
.header-content {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.header-text h1 {
|
||||
margin: 0 0 8px 0;
|
||||
font-size: 28px;
|
||||
font-weight: 700;
|
||||
color: #1a1a1a;
|
||||
}
|
||||
|
||||
.header-text p {
|
||||
margin: 0;
|
||||
color: #666;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.search-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
|
||||
.agents-section {
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
|
||||
.agents-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.sort-controls {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.pagination-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-top: 32px;
|
||||
padding: 20px 0;
|
||||
}
|
||||
|
||||
.search-bar {
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
align-items: center;
|
||||
max-width: 600px;
|
||||
width: 100%;
|
||||
padding: 20px;
|
||||
background: linear-gradient(135deg, #f8fafc 0%, #f1f5f9 100%);
|
||||
border-radius: 20px;
|
||||
border: 1px solid #e2e8f0;
|
||||
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
.search-input {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.search-input .n-input {
|
||||
border-radius: 12px;
|
||||
border: 1px solid #d1d5db;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.search-input .n-input:focus-within {
|
||||
border-color: #3b82f6;
|
||||
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
|
||||
}
|
||||
|
||||
.search-button {
|
||||
border-radius: 12px;
|
||||
font-weight: 600;
|
||||
padding: 0 24px;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.search-button:hover {
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 4px 12px rgba(59, 130, 246, 0.3);
|
||||
}
|
||||
|
||||
.agents-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
|
||||
gap: 24px;
|
||||
}
|
||||
|
||||
.agent-card {
|
||||
border: 1px solid #e5e7eb;
|
||||
border-radius: 16px;
|
||||
background: white;
|
||||
overflow: hidden;
|
||||
transition: all 0.3s ease;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.agent-card:hover {
|
||||
transform: translateY(-4px);
|
||||
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.15);
|
||||
border-color: #d1d5db;
|
||||
}
|
||||
|
||||
.agent-icon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
height: 120px;
|
||||
background: linear-gradient(135deg, #f3f4f6 0%, #e5e7eb 100%);
|
||||
cursor: pointer;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.agent-icon-placeholder {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
color: #9ca3af;
|
||||
}
|
||||
|
||||
.agent-content {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.agent-header {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.agent-title-section {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.agent-title-section h3 {
|
||||
margin: 0;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: #1f2937;
|
||||
line-height: 1.2;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.clickable-title {
|
||||
cursor: pointer;
|
||||
transition: color 0.2s ease;
|
||||
}
|
||||
|
||||
.clickable-title:hover {
|
||||
color: #10b981;
|
||||
}
|
||||
|
||||
.agent-name {
|
||||
color: #6b7280;
|
||||
font-size: 11px;
|
||||
font-weight: 500;
|
||||
font-family: 'SF Mono', 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
|
||||
background: #f3f4f6;
|
||||
border: 1px solid #d1d5db;
|
||||
border-radius: 8px;
|
||||
padding: 4px 10px;
|
||||
display: inline-block;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.agent-description {
|
||||
color: #6b7280;
|
||||
font-size: 14px;
|
||||
line-height: 1.5;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.agent-actions {
|
||||
padding: 0 20px 20px;
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.agent-actions .n-button {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.loading, .empty {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
min-height: 300px;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.agents-grid {
|
||||
grid-template-columns: 1fr;
|
||||
gap: 16px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -732,3 +732,179 @@ async def publish_node_to_marketplace(
|
||||
logger.error(f"发布节点失败: {str(e)}")
|
||||
logger.error(f"Traceback: {traceback.format_exc()}")
|
||||
raise HTTPException(status_code=500, detail=f"发布节点失败: {str(e)}")
|
||||
|
||||
|
||||
# ==================== 智能体市场 API ====================
|
||||
|
||||
@router.get("/jingrow/agent-marketplace")
|
||||
async def get_agent_marketplace(
|
||||
search: Optional[str] = None,
|
||||
page: int = 1,
|
||||
page_size: int = 20,
|
||||
sort_by: Optional[str] = None
|
||||
):
|
||||
"""获取智能体市场数据,支持搜索、分页和排序"""
|
||||
try:
|
||||
url = f"{get_jingrow_cloud_url()}/api/action/jcloud.api.jlocal.get_local_agent_list"
|
||||
|
||||
# 构建过滤条件
|
||||
filters = {"public": 1}
|
||||
if search:
|
||||
filters["agent_name"] = ["like", f"%{search}%"]
|
||||
filters["title"] = ["like", f"%{search}%"]
|
||||
|
||||
# 1. 先获取总数(不分页)
|
||||
total_params = {
|
||||
'filters': json.dumps(filters, ensure_ascii=False),
|
||||
'limit_start': 0,
|
||||
'limit_page_length': 0
|
||||
}
|
||||
|
||||
headers = get_jingrow_cloud_api_headers()
|
||||
total_response = requests.get(url, params=total_params, headers=headers, timeout=20)
|
||||
|
||||
total_count = 0
|
||||
if total_response.status_code == 200:
|
||||
total_data = total_response.json()
|
||||
total_count = len(total_data.get('message', []))
|
||||
|
||||
# 2. 获取分页数据
|
||||
params = {
|
||||
'filters': json.dumps(filters, ensure_ascii=False)
|
||||
}
|
||||
|
||||
# 排序参数
|
||||
if sort_by:
|
||||
params['order_by'] = sort_by
|
||||
|
||||
# 分页参数
|
||||
limit_start = (page - 1) * page_size
|
||||
params['limit_start'] = limit_start
|
||||
params['limit_page_length'] = page_size
|
||||
|
||||
response = requests.get(url, params=params, headers=headers, timeout=20)
|
||||
|
||||
if response.status_code == 200:
|
||||
data = response.json()
|
||||
agents = data.get('message', [])
|
||||
|
||||
return {
|
||||
"items": agents,
|
||||
"total": total_count,
|
||||
"page": page,
|
||||
"page_size": page_size
|
||||
}
|
||||
else:
|
||||
raise HTTPException(status_code=response.status_code, detail="获取智能体市场数据失败")
|
||||
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=f"获取智能体市场数据失败: {str(e)}")
|
||||
|
||||
|
||||
@router.get("/jingrow/agent-marketplace/{name}")
|
||||
async def get_agent_detail(name: str):
|
||||
"""获取智能体详情"""
|
||||
try:
|
||||
url = f"{get_jingrow_cloud_url()}/api/action/jcloud.api.jlocal.get_local_agent"
|
||||
params = {"name": name}
|
||||
|
||||
headers = get_jingrow_cloud_api_headers()
|
||||
response = requests.get(url, params=params, headers=headers, timeout=20)
|
||||
|
||||
if response.status_code == 200:
|
||||
data = response.json()
|
||||
return data.get('message')
|
||||
else:
|
||||
raise HTTPException(status_code=404, detail="智能体不存在")
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=f"获取智能体详情失败: {str(e)}")
|
||||
|
||||
|
||||
@router.get("/jingrow/check-agent/{agent_name}")
|
||||
async def check_agent_exists(agent_name: str):
|
||||
"""检查智能体是否已安装"""
|
||||
try:
|
||||
exists_res = get_record_id(
|
||||
pagetype="Local Ai Agent",
|
||||
field="agent_name",
|
||||
value=agent_name,
|
||||
)
|
||||
|
||||
return {"exists": exists_res.get("success", False)}
|
||||
except Exception as e:
|
||||
logger.error(f"检查智能体是否存在失败: {str(e)}")
|
||||
return {"exists": False}
|
||||
|
||||
|
||||
@router.post("/jingrow/install-agent")
|
||||
async def install_agent(payload: Dict[str, Any]):
|
||||
"""安装智能体 - 只需要流程数据和智能体名称"""
|
||||
try:
|
||||
agent_name = payload.get("agent_name")
|
||||
agent_flow = payload.get("agent_flow")
|
||||
|
||||
if not agent_name:
|
||||
raise HTTPException(status_code=400, detail="智能体名称不能为空")
|
||||
|
||||
if not agent_flow:
|
||||
raise HTTPException(status_code=400, detail="流程数据不能为空")
|
||||
|
||||
# 检查是否已存在
|
||||
exists_res = get_record_id(
|
||||
pagetype="Local Ai Agent",
|
||||
field="agent_name",
|
||||
value=agent_name,
|
||||
)
|
||||
|
||||
# 准备数据
|
||||
agent_data = {
|
||||
"agent_name": agent_name,
|
||||
"agent_flow": json.dumps(agent_flow, ensure_ascii=False) if isinstance(agent_flow, dict) else agent_flow,
|
||||
"status": "Active",
|
||||
"enabled": 1,
|
||||
"trigger_mode": "Manual Trigger"
|
||||
}
|
||||
|
||||
if exists_res.get("success"):
|
||||
# 更新现有记录
|
||||
record_name = exists_res.get('name')
|
||||
if not record_name:
|
||||
return {'success': False, 'error': '获取记录名称失败'}
|
||||
res = update_record("Local Ai Agent", record_name, agent_data)
|
||||
else:
|
||||
# 创建新记录
|
||||
res = create_record("Local Ai Agent", agent_data)
|
||||
|
||||
if res.get("success"):
|
||||
return {'success': True, 'agent_name': agent_name, 'message': f'智能体 {agent_name} 安装成功'}
|
||||
else:
|
||||
return {'success': False, 'error': res.get('error', '导入数据库失败')}
|
||||
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.error(f"安装智能体失败: {str(e)}")
|
||||
raise HTTPException(status_code=500, detail=f"安装智能体失败: {str(e)}")
|
||||
|
||||
|
||||
@router.get("/jingrow/installed-agent-names")
|
||||
async def get_installed_agent_names():
|
||||
"""获取已安装的智能体名称列表"""
|
||||
try:
|
||||
agents = get_record_list(
|
||||
pagetype="Local Ai Agent",
|
||||
fields=["agent_name"],
|
||||
limit_start=0,
|
||||
limit_page_length=1000
|
||||
)
|
||||
|
||||
agent_names = []
|
||||
if agents:
|
||||
for agent in agents:
|
||||
if agent.get("agent_name"):
|
||||
agent_names.append(agent["agent_name"])
|
||||
|
||||
return {"success": True, "agents": agent_names}
|
||||
except Exception as e:
|
||||
logger.error(f"获取已安装智能体列表失败: {str(e)}")
|
||||
return {"success": False, "agents": []}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user