增加智能体广场

This commit is contained in:
朱潮 2026-02-21 21:22:21 +08:00
parent 144739fb4a
commit aa654fe024
6 changed files with 567 additions and 11 deletions

View File

@ -0,0 +1,2 @@
[ 200ms] [ERROR] Failed to load resource: the server responded with a status of 404 (Not Found) @ http://localhost:8001/manage/chat/:0
[ 250ms] [ERROR] Failed to load resource: the server responded with a status of 404 (Not Found) @ http://localhost:8001/favicon.ico:0

View File

@ -22,12 +22,16 @@ CREATE TABLE IF NOT EXISTS agent_bots (
name VARCHAR(255) NOT NULL, name VARCHAR(255) NOT NULL,
settings JSONB DEFAULT '{"language": "zh", "enable_memori": false, "enable_thinking": false, "tool_response": false}'::jsonb, settings JSONB DEFAULT '{"language": "zh", "enable_memori": false, "enable_thinking": false, "tool_response": false}'::jsonb,
owner_id UUID NOT NULL REFERENCES agent_user(id) ON DELETE RESTRICT, owner_id UUID NOT NULL REFERENCES agent_user(id) ON DELETE RESTRICT,
is_published BOOLEAN DEFAULT FALSE, -- 是否发布到智能体广场
copied_from UUID REFERENCES agent_bots(id) ON DELETE SET NULL, -- 复制来源的bot id
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
); );
-- agent_bots 索引 -- agent_bots 索引
CREATE INDEX IF NOT EXISTS idx_agent_bots_owner_id ON agent_bots(owner_id); CREATE INDEX IF NOT EXISTS idx_agent_bots_owner_id ON agent_bots(owner_id);
CREATE INDEX IF NOT EXISTS idx_agent_bots_is_published ON agent_bots(is_published) WHERE is_published = TRUE;
CREATE INDEX IF NOT EXISTS idx_agent_bots_copied_from ON agent_bots(copied_from);
-- 3. 创建 agent_user_tokens 表 -- 3. 创建 agent_user_tokens 表
CREATE TABLE IF NOT EXISTS agent_user_tokens ( CREATE TABLE IF NOT EXISTS agent_user_tokens (

View File

@ -651,6 +651,7 @@
border: 1px solid var(--border); border: 1px solid var(--border);
border-radius: 20px; border-radius: 20px;
font-size: 13px; font-size: 13px;
color: var(--text);
cursor: pointer; cursor: pointer;
transition: all 0.15s ease; transition: all 0.15s ease;
} }
@ -660,6 +661,15 @@
color: var(--primary); color: var(--primary);
} }
.dark .suggestion-chip {
color: var(--text);
}
.dark .suggestion-chip:hover {
border-color: var(--primary);
color: var(--primary);
}
/* ===== Messages ===== */ /* ===== Messages ===== */
.message { .message {
display: flex; display: flex;

View File

@ -7,6 +7,8 @@ import logging
import uuid import uuid
import hashlib import hashlib
import secrets import secrets
import os
import shutil
from datetime import datetime, timedelta from datetime import datetime, timedelta
from typing import Optional, List from typing import Optional, List
from fastapi import APIRouter, HTTPException, Header from fastapi import APIRouter, HTTPException, Header
@ -19,6 +21,39 @@ logger = logging.getLogger('app')
router = APIRouter() router = APIRouter()
# ============== 辅助函数 ==============
def copy_skills_folder(source_bot_id: str, target_bot_id: str) -> bool:
"""
复制智能体的 skills 文件夹
Args:
source_bot_id: 源智能体 ID
target_bot_id: 目标智能体 ID
Returns:
bool: 是否成功复制
"""
try:
source_skills_path = os.path.join('projects', 'uploads', source_bot_id, 'skills')
target_skills_path = os.path.join('projects', 'uploads', target_bot_id, 'skills')
if os.path.exists(source_skills_path):
# 如果目标目录已存在,先删除
if os.path.exists(target_skills_path):
shutil.rmtree(target_skills_path)
# 复制整个 skills 文件夹
shutil.copytree(source_skills_path, target_skills_path)
logger.info(f"Copied skills folder from {source_bot_id} to {target_bot_id}")
return True
else:
logger.info(f"Source skills folder not found: {source_skills_path}")
return False
except Exception as e:
logger.error(f"Failed to copy skills folder: {e}")
return False
# ============== Admin 配置 ============== # ============== Admin 配置 ==============
ADMIN_USERNAME = "admin" ADMIN_USERNAME = "admin"
ADMIN_PASSWORD = "Admin123" # 生产环境应使用环境变量 ADMIN_PASSWORD = "Admin123" # 生产环境应使用环境变量
@ -370,6 +405,8 @@ class BotResponse(BaseModel):
bot_id: str bot_id: str
is_owner: bool = False is_owner: bool = False
is_shared: bool = False is_shared: bool = False
is_published: bool = False # 是否发布到广场
copied_from: Optional[str] = None # 复制来源的bot id
owner: Optional[dict] = None # {id, username} owner: Optional[dict] = None # {id, username}
role: Optional[str] = None # 'viewer', 'editor', None for owner role: Optional[str] = None # 'viewer', 'editor', None for owner
shared_at: Optional[str] = None shared_at: Optional[str] = None
@ -394,6 +431,7 @@ class BotSettingsUpdate(BaseModel):
enable_thinking: Optional[bool] = None enable_thinking: Optional[bool] = None
tool_response: Optional[bool] = None tool_response: Optional[bool] = None
skills: Optional[str] = None skills: Optional[str] = None
is_published: Optional[bool] = None # 是否发布到广场
class ModelInfo(BaseModel): class ModelInfo(BaseModel):
@ -421,9 +459,31 @@ class BotSettingsResponse(BaseModel):
enable_thinking: bool enable_thinking: bool
tool_response: bool tool_response: bool
skills: Optional[str] skills: Optional[str]
is_published: bool = False # 是否发布到广场
copied_from: Optional[str] = None # 复制来源的bot id
updated_at: str updated_at: str
# --- 广场相关 ---
class MarketplaceBotResponse(BaseModel):
"""广场 Bot 响应(公开信息)"""
id: str
name: str
description: Optional[str] = None
avatar_url: Optional[str] = None
owner_name: Optional[str] = None
suggestions: Optional[List[str]] = None
copy_count: int = 0 # 被复制次数
created_at: str
updated_at: str
class MarketplaceListResponse(BaseModel):
"""广场列表响应"""
bots: List[MarketplaceBotResponse]
total: int
# --- 会话相关 --- # --- 会话相关 ---
class SessionCreate(BaseModel): class SessionCreate(BaseModel):
"""创建会话请求""" """创建会话请求"""
@ -802,6 +862,58 @@ async def migrate_bot_settings_to_jsonb():
logger.info("Settings column already exists, skipping migration") logger.info("Settings column already exists, skipping migration")
async def migrate_add_marketplace_fields():
"""
添加智能体广场相关字段到 agent_bots
"""
pool = get_db_pool_manager().pool
async with pool.connection() as conn:
async with conn.cursor() as cursor:
# 1. 添加 is_published 字段
await cursor.execute("""
SELECT column_name
FROM information_schema.columns
WHERE table_name = 'agent_bots' AND column_name = 'is_published'
""")
has_is_published = await cursor.fetchone()
if not has_is_published:
logger.info("Adding is_published column to agent_bots table")
await cursor.execute("""
ALTER TABLE agent_bots
ADD COLUMN is_published BOOLEAN DEFAULT FALSE
""")
# 创建部分索引,只索引发布的 bots
await cursor.execute("""
CREATE INDEX idx_agent_bots_is_published
ON agent_bots(is_published) WHERE is_published = TRUE
""")
logger.info("is_published column added successfully")
# 2. 添加 copied_from 字段
await cursor.execute("""
SELECT column_name
FROM information_schema.columns
WHERE table_name = 'agent_bots' AND column_name = 'copied_from'
""")
has_copied_from = await cursor.fetchone()
if not has_copied_from:
logger.info("Adding copied_from column to agent_bots table")
await cursor.execute("""
ALTER TABLE agent_bots
ADD COLUMN copied_from UUID REFERENCES agent_bots(id) ON DELETE SET NULL
""")
await cursor.execute("""
CREATE INDEX idx_agent_bots_copied_from ON agent_bots(copied_from)
""")
logger.info("copied_from column added successfully")
await conn.commit()
logger.info("Marketplace fields migration completed")
async def init_bot_manager_tables(): async def init_bot_manager_tables():
""" """
初始化 Bot Manager 相关的所有数据库表 初始化 Bot Manager 相关的所有数据库表
@ -813,6 +925,8 @@ async def init_bot_manager_tables():
await migrate_bot_settings_to_jsonb() await migrate_bot_settings_to_jsonb()
# 2. User 和 shares 迁移 # 2. User 和 shares 迁移
await migrate_bot_owner_and_shares() await migrate_bot_owner_and_shares()
# 3. Marketplace 字段迁移
await migrate_add_marketplace_fields()
# SQL 表创建语句 # SQL 表创建语句
tables_sql = [ tables_sql = [
@ -1205,7 +1319,8 @@ async def get_bots(authorization: Optional[str] = Header(None)):
# 管理员可以看到所有 Bot # 管理员可以看到所有 Bot
await cursor.execute(""" await cursor.execute("""
SELECT b.id, b.name, b.bot_id, b.created_at, b.updated_at, b.settings, SELECT b.id, b.name, b.bot_id, b.created_at, b.updated_at, b.settings,
u.id as owner_id, u.username as owner_username u.id as owner_id, u.username as owner_username,
b.is_published, b.copied_from
FROM agent_bots b FROM agent_bots b
LEFT JOIN agent_user u ON b.owner_id = u.id LEFT JOIN agent_user u ON b.owner_id = u.id
ORDER BY b.created_at DESC ORDER BY b.created_at DESC
@ -1219,6 +1334,8 @@ async def get_bots(authorization: Optional[str] = Header(None)):
bot_id=str(row[0]), # bot_id 也指向主键 id bot_id=str(row[0]), # bot_id 也指向主键 id
is_owner=True, is_owner=True,
is_shared=False, is_shared=False,
is_published=row[8] if row[8] else False,
copied_from=str(row[9]) if row[9] else None,
owner={"id": str(row[6]), "username": row[7]} if row[6] else None, owner={"id": str(row[6]), "username": row[7]} if row[6] else None,
role=None, role=None,
description=row[5].get('description') if row[5] else None, description=row[5].get('description') if row[5] else None,
@ -1234,7 +1351,8 @@ async def get_bots(authorization: Optional[str] = Header(None)):
await cursor.execute(""" await cursor.execute("""
SELECT DISTINCT b.id, b.name, b.bot_id, b.created_at, b.updated_at, b.settings, SELECT DISTINCT b.id, b.name, b.bot_id, b.created_at, b.updated_at, b.settings,
u.id as owner_id, u.username as owner_username, u.id as owner_id, u.username as owner_username,
s.role, s.shared_at, s.expires_at s.role, s.shared_at, s.expires_at,
b.is_published, b.copied_from
FROM agent_bots b FROM agent_bots b
LEFT JOIN agent_user u ON b.owner_id = u.id LEFT JOIN agent_user u ON b.owner_id = u.id
LEFT JOIN bot_shares s ON b.id = s.bot_id AND s.user_id = %s LEFT JOIN bot_shares s ON b.id = s.bot_id AND s.user_id = %s
@ -1253,6 +1371,8 @@ async def get_bots(authorization: Optional[str] = Header(None)):
bot_id=str(row[0]), # bot_id 也指向主键 id bot_id=str(row[0]), # bot_id 也指向主键 id
is_owner=(row[6] is not None and str(row[6]) == user_id), is_owner=(row[6] is not None and str(row[6]) == user_id),
is_shared=(row[6] is not None and str(row[6]) != user_id and row[8] is not None), is_shared=(row[6] is not None and str(row[6]) != user_id and row[8] is not None),
is_published=row[11] if row[11] else False,
copied_from=str(row[12]) if row[12] else None,
owner={"id": str(row[6]), "username": row[7]} if row[6] is not None else None, owner={"id": str(row[6]), "username": row[7]} if row[6] is not None else None,
role=row[8] if row[8] is not None else None, role=row[8] if row[8] is not None else None,
shared_at=datetime_to_str(row[9]) if row[9] is not None else None, shared_at=datetime_to_str(row[9]) if row[9] is not None else None,
@ -1486,7 +1606,7 @@ async def get_bot_settings(bot_uuid: str, authorization: Optional[str] = Header(
async with pool.connection() as conn: async with pool.connection() as conn:
async with conn.cursor() as cursor: async with conn.cursor() as cursor:
await cursor.execute(""" await cursor.execute("""
SELECT id, settings, updated_at SELECT id, settings, updated_at, is_published, copied_from
FROM agent_bots FROM agent_bots
WHERE id = %s WHERE id = %s
""", (bot_uuid,)) """, (bot_uuid,))
@ -1495,7 +1615,7 @@ async def get_bot_settings(bot_uuid: str, authorization: Optional[str] = Header(
if not row: if not row:
raise HTTPException(status_code=404, detail="Bot not found") raise HTTPException(status_code=404, detail="Bot not found")
bot_id, settings_json, updated_at = row bot_id, settings_json, updated_at, is_published, copied_from = row
settings = settings_json if settings_json else {} settings = settings_json if settings_json else {}
# 获取关联的模型信息 # 获取关联的模型信息
@ -1538,6 +1658,8 @@ async def get_bot_settings(bot_uuid: str, authorization: Optional[str] = Header(
enable_thinking=settings.get('enable_thinking', False), enable_thinking=settings.get('enable_thinking', False),
tool_response=settings.get('tool_response', False), tool_response=settings.get('tool_response', False),
skills=settings.get('skills'), skills=settings.get('skills'),
is_published=is_published if is_published else False,
copied_from=str(copied_from) if copied_from else None,
updated_at=datetime_to_str(updated_at) updated_at=datetime_to_str(updated_at)
) )
@ -1617,7 +1739,10 @@ async def update_bot_settings(
if request.skills is not None: if request.skills is not None:
update_json['skills'] = request.skills update_json['skills'] = request.skills
if not update_json: # is_published 是表字段,不在 settings JSON 中
need_update_published = request.is_published is not None
if not update_json and not need_update_published:
raise HTTPException(status_code=400, detail="No fields to update") raise HTTPException(status_code=400, detail="No fields to update")
async with pool.connection() as conn: async with pool.connection() as conn:
@ -1632,12 +1757,19 @@ async def update_bot_settings(
existing_settings = row[1] if row[1] else {} existing_settings = row[1] if row[1] else {}
existing_settings.update(update_json) existing_settings.update(update_json)
# 更新设置 # 更新设置和is_published字段
await cursor.execute(""" if need_update_published:
UPDATE agent_bots await cursor.execute("""
SET settings = %s, updated_at = NOW() UPDATE agent_bots
WHERE id = %s SET settings = %s, is_published = %s, updated_at = NOW()
""", (json.dumps(existing_settings), bot_uuid)) WHERE id = %s
""", (json.dumps(existing_settings), request.is_published, bot_uuid))
else:
await cursor.execute("""
UPDATE agent_bots
SET settings = %s, updated_at = NOW()
WHERE id = %s
""", (json.dumps(existing_settings), bot_uuid))
await conn.commit() await conn.commit()
@ -2985,3 +3117,411 @@ async def remove_bot_share(
) )
# ============== 智能体广场 API ==============
@router.get("/api/v1/marketplace/bots", response_model=MarketplaceListResponse)
async def get_marketplace_bots(
page: int = 1,
page_size: int = 20,
search: str = "",
authorization: Optional[str] = Header(None)
):
"""
获取广场智能体列表
Args:
page: 页码从1开始
page_size: 每页数量
search: 搜索关键词名称/描述
authorization: Bearer token可选用于判断是否已登录
Returns:
MarketplaceListResponse: 广场智能体列表
"""
# 不强制要求登录,但如果有 token 则验证
user_valid, _, _ = await verify_user_auth(authorization)
pool = get_db_pool_manager().pool
offset = (page - 1) * page_size
async with pool.connection() as conn:
async with conn.cursor() as cursor:
# 构建搜索条件
search_condition = ""
params = []
if search:
search_condition = "AND (b.name ILIKE %s OR b.settings->>'description' ILIKE %s)"
search_param = f"%{search}%"
params.extend([search_param, search_param])
# 获取总数
count_query = f"""
SELECT COUNT(*)
FROM agent_bots b
WHERE b.is_published = TRUE
{search_condition}
"""
await cursor.execute(count_query, params)
total = (await cursor.fetchone())[0]
# 获取列表
list_query = f"""
SELECT b.id, b.name, b.settings, b.created_at, b.updated_at,
u.username as owner_name
FROM agent_bots b
LEFT JOIN agent_user u ON b.owner_id = u.id
WHERE b.is_published = TRUE
{search_condition}
ORDER BY b.updated_at DESC
LIMIT %s OFFSET %s
"""
params.extend([page_size, offset])
await cursor.execute(list_query, params)
rows = await cursor.fetchall()
bots = []
for row in rows:
settings = row[2] if row[2] else {}
# 计算被复制次数
await cursor.execute("""
SELECT COUNT(*) FROM agent_bots WHERE copied_from = %s
""", (row[0],))
copy_count = (await cursor.fetchone())[0]
bots.append(MarketplaceBotResponse(
id=str(row[0]),
name=row[1],
description=settings.get('description'),
avatar_url=settings.get('avatar_url'),
owner_name=row[5],
suggestions=settings.get('suggestions'),
copy_count=copy_count,
created_at=datetime_to_str(row[3]),
updated_at=datetime_to_str(row[4])
))
return MarketplaceListResponse(bots=bots, total=total)
@router.get("/api/v1/marketplace/bots/{bot_uuid}", response_model=MarketplaceBotResponse)
async def get_marketplace_bot_detail(
bot_uuid: str,
authorization: Optional[str] = Header(None)
):
"""
获取广场智能体详情
Args:
bot_uuid: Bot UUID
authorization: Bearer token可选
Returns:
MarketplaceBotResponse: 智能体公开信息
"""
pool = get_db_pool_manager().pool
async with pool.connection() as conn:
async with conn.cursor() as cursor:
await cursor.execute("""
SELECT b.id, b.name, b.settings, b.created_at, b.updated_at,
u.username as owner_name, b.is_published
FROM agent_bots b
LEFT JOIN agent_user u ON b.owner_id = u.id
WHERE b.id = %s
""", (bot_uuid,))
row = await cursor.fetchone()
if not row:
raise HTTPException(status_code=404, detail="Bot not found")
if not row[6]: # is_published
raise HTTPException(status_code=404, detail="Bot not found in marketplace")
settings = row[2] if row[2] else {}
# 计算被复制次数
await cursor.execute("""
SELECT COUNT(*) FROM agent_bots WHERE copied_from = %s
""", (bot_uuid,))
copy_count = (await cursor.fetchone())[0]
return MarketplaceBotResponse(
id=str(row[0]),
name=row[1],
description=settings.get('description'),
avatar_url=settings.get('avatar_url'),
owner_name=row[5],
suggestions=settings.get('suggestions'),
copy_count=copy_count,
created_at=datetime_to_str(row[3]),
updated_at=datetime_to_str(row[4])
)
@router.post("/api/v1/marketplace/bots/{bot_uuid}/copy", response_model=BotResponse)
async def copy_marketplace_bot(
bot_uuid: str,
authorization: Optional[str] = Header(None)
):
"""
复制广场智能体到个人管理
Args:
bot_uuid: 要复制的 Bot UUID
authorization: Bearer token
Returns:
BotResponse: 新创建的 Bot 信息
"""
user_valid, user_id, user_username = await verify_user_auth(authorization)
if not user_valid:
raise HTTPException(
status_code=401,
detail="Unauthorized"
)
pool = get_db_pool_manager().pool
async with pool.connection() as conn:
async with conn.cursor() as cursor:
# 获取原始 Bot 信息
await cursor.execute("""
SELECT id, name, settings, is_published
FROM agent_bots
WHERE id = %s AND is_published = TRUE
""", (bot_uuid,))
original = await cursor.fetchone()
if not original:
raise HTTPException(status_code=404, detail="Bot not found in marketplace")
original_id, original_name, original_settings, _ = original
settings = original_settings if original_settings else {}
# 创建新 Bot名称加"副本"后缀)
new_name = f"{original_name} (副本)"
new_bot_id = str(uuid.uuid4())
# 只复制部分设置(不复制 system_prompt, MCP配置等
new_settings = {
'language': settings.get('language', 'zh'),
'avatar_url': settings.get('avatar_url'),
'description': settings.get('description'),
'suggestions': settings.get('suggestions'),
'dataset_ids': settings.get('dataset_ids'),
# 不复制的设置:
# 'model_id', 'system_prompt', 'enable_memori', 'enable_thinking', 'tool_response', 'skills'
'enable_memori': False,
'enable_thinking': False,
'tool_response': False,
}
# 插入新 Bot
await cursor.execute("""
INSERT INTO agent_bots (name, bot_id, owner_id, settings, copied_from)
VALUES (%s, %s, %s, %s, %s)
RETURNING id, created_at, updated_at
""", (new_name, new_bot_id, user_id, json.dumps(new_settings), original_id))
new_row = await cursor.fetchone()
new_id, created_at, updated_at = new_row
await conn.commit()
# 复制 skills 文件夹
copy_skills_folder(str(original_id), str(new_id))
return BotResponse(
id=str(new_id),
name=new_name,
bot_id=new_bot_id,
is_owner=True,
is_shared=False,
is_published=False,
copied_from=str(original_id),
owner={"id": str(user_id), "username": user_username},
role=None,
description=new_settings.get('description'),
avatar_url=new_settings.get('avatar_url'),
created_at=datetime_to_str(created_at),
updated_at=datetime_to_str(updated_at)
)
@router.patch("/api/v1/bots/{bot_uuid}/publish", response_model=SuccessResponse)
async def toggle_bot_publication(
bot_uuid: str,
authorization: Optional[str] = Header(None)
):
"""
切换智能体发布状态仅所有者可操作
Args:
bot_uuid: Bot UUID
authorization: Bearer token
Returns:
SuccessResponse: 操作结果
"""
user_valid, user_id, user_username = await verify_user_auth(authorization)
if not user_valid:
raise HTTPException(
status_code=401,
detail="Unauthorized"
)
pool = get_db_pool_manager().pool
async with pool.connection() as conn:
async with conn.cursor() as cursor:
# 检查是否是所有者
await cursor.execute("""
SELECT id, is_published FROM agent_bots WHERE id = %s AND owner_id = %s
""", (bot_uuid, user_id))
row = await cursor.fetchone()
if not row:
raise HTTPException(
status_code=403,
detail="Only bot owner can toggle publication status"
)
current_status = row[1] if row[1] else False
new_status = not current_status
# 更新状态
await cursor.execute("""
UPDATE agent_bots
SET is_published = %s, updated_at = NOW()
WHERE id = %s
""", (new_status, bot_uuid))
await conn.commit()
action = "发布到" if new_status else "取消发布"
return SuccessResponse(
success=True,
message=f"Bot {action} marketplace successfully"
)
@router.post("/api/v1/bots/{bot_uuid}/sync-from-source", response_model=SuccessResponse)
async def sync_bot_from_source(
bot_uuid: str,
authorization: Optional[str] = Header(None)
):
"""
从原始智能体同步配置仅限从广场复制的智能体
同步以下配置
- 系统提示词
- MCP 服务器配置
- 技能配置
- skills 文件夹
Args:
bot_uuid: Bot UUID
authorization: Bearer token
Returns:
SuccessResponse: 操作结果
"""
user_valid, user_id, user_username = await verify_user_auth(authorization)
if not user_valid:
raise HTTPException(
status_code=401,
detail="Unauthorized"
)
pool = get_db_pool_manager().pool
async with pool.connection() as conn:
async with conn.cursor() as cursor:
# 获取当前 Bot 信息
await cursor.execute("""
SELECT id, copied_from, settings, owner_id
FROM agent_bots
WHERE id = %s
""", (bot_uuid,))
current_bot = await cursor.fetchone()
if not current_bot:
raise HTTPException(status_code=404, detail="Bot not found")
current_id, copied_from, current_settings, owner_id = current_bot
# 检查是否是从广场复制的
if not copied_from:
raise HTTPException(
status_code=400,
detail="This bot is not copied from marketplace"
)
# 检查是否是所有者
if str(owner_id) != str(user_id):
raise HTTPException(
status_code=403,
detail="Only bot owner can sync from source"
)
# 获取原始 Bot 信息
await cursor.execute("""
SELECT id, settings
FROM agent_bots
WHERE id = %s AND is_published = TRUE
""", (copied_from,))
source_bot = await cursor.fetchone()
if not source_bot:
raise HTTPException(
status_code=404,
detail="Source bot not found or not published"
)
source_id, source_settings = source_bot
source_settings = source_settings if source_settings else {}
current_settings = current_settings if current_settings else {}
# 同步配置系统提示词、MCP、skill
current_settings['system_prompt'] = source_settings.get('system_prompt')
current_settings['skills'] = source_settings.get('skills')
# 更新当前 Bot 的设置
await cursor.execute("""
UPDATE agent_bots
SET settings = %s, updated_at = NOW()
WHERE id = %s
""", (json.dumps(current_settings), bot_uuid))
# 同步 MCP 服务器配置
await cursor.execute("""
DELETE FROM agent_mcp_servers WHERE bot_id = %s
""", (bot_uuid,))
await cursor.execute("""
SELECT name, type, config, enabled
FROM agent_mcp_servers
WHERE bot_id = %s
""", (copied_from,))
source_mcp_servers = await cursor.fetchall()
for server in source_mcp_servers:
server_name, server_type, server_config, server_enabled = server
await cursor.execute("""
INSERT INTO agent_mcp_servers (bot_id, name, type, config, enabled)
VALUES (%s, %s, %s, %s, %s)
""", (bot_uuid, server_name, server_type, json.dumps(server_config), server_enabled))
await conn.commit()
# 复制 skills 文件夹
copy_skills_folder(str(copied_from), str(bot_uuid))
return SuccessResponse(
success=True,
message="Bot synced from source successfully"
)

BIN
suggestion-chips-dark.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

BIN
suggestion-chips-light.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB