增加智能体广场
This commit is contained in:
parent
144739fb4a
commit
aa654fe024
2
.playwright-mcp/console-2026-02-21T06-43-52-154Z.log
Normal file
2
.playwright-mcp/console-2026-02-21T06-43-52-154Z.log
Normal 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
|
||||
@ -22,12 +22,16 @@ CREATE TABLE IF NOT EXISTS agent_bots (
|
||||
name VARCHAR(255) NOT NULL,
|
||||
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,
|
||||
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(),
|
||||
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
|
||||
);
|
||||
|
||||
-- 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_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 表
|
||||
CREATE TABLE IF NOT EXISTS agent_user_tokens (
|
||||
|
||||
@ -651,6 +651,7 @@
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 20px;
|
||||
font-size: 13px;
|
||||
color: var(--text);
|
||||
cursor: pointer;
|
||||
transition: all 0.15s ease;
|
||||
}
|
||||
@ -660,6 +661,15 @@
|
||||
color: var(--primary);
|
||||
}
|
||||
|
||||
.dark .suggestion-chip {
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
.dark .suggestion-chip:hover {
|
||||
border-color: var(--primary);
|
||||
color: var(--primary);
|
||||
}
|
||||
|
||||
/* ===== Messages ===== */
|
||||
.message {
|
||||
display: flex;
|
||||
|
||||
@ -7,6 +7,8 @@ import logging
|
||||
import uuid
|
||||
import hashlib
|
||||
import secrets
|
||||
import os
|
||||
import shutil
|
||||
from datetime import datetime, timedelta
|
||||
from typing import Optional, List
|
||||
from fastapi import APIRouter, HTTPException, Header
|
||||
@ -19,6 +21,39 @@ logger = logging.getLogger('app')
|
||||
|
||||
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_USERNAME = "admin"
|
||||
ADMIN_PASSWORD = "Admin123" # 生产环境应使用环境变量
|
||||
@ -370,6 +405,8 @@ class BotResponse(BaseModel):
|
||||
bot_id: str
|
||||
is_owner: bool = False
|
||||
is_shared: bool = False
|
||||
is_published: bool = False # 是否发布到广场
|
||||
copied_from: Optional[str] = None # 复制来源的bot id
|
||||
owner: Optional[dict] = None # {id, username}
|
||||
role: Optional[str] = None # 'viewer', 'editor', None for owner
|
||||
shared_at: Optional[str] = None
|
||||
@ -394,6 +431,7 @@ class BotSettingsUpdate(BaseModel):
|
||||
enable_thinking: Optional[bool] = None
|
||||
tool_response: Optional[bool] = None
|
||||
skills: Optional[str] = None
|
||||
is_published: Optional[bool] = None # 是否发布到广场
|
||||
|
||||
|
||||
class ModelInfo(BaseModel):
|
||||
@ -421,9 +459,31 @@ class BotSettingsResponse(BaseModel):
|
||||
enable_thinking: bool
|
||||
tool_response: bool
|
||||
skills: Optional[str]
|
||||
is_published: bool = False # 是否发布到广场
|
||||
copied_from: Optional[str] = None # 复制来源的bot id
|
||||
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):
|
||||
"""创建会话请求"""
|
||||
@ -802,6 +862,58 @@ async def migrate_bot_settings_to_jsonb():
|
||||
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():
|
||||
"""
|
||||
初始化 Bot Manager 相关的所有数据库表
|
||||
@ -813,6 +925,8 @@ async def init_bot_manager_tables():
|
||||
await migrate_bot_settings_to_jsonb()
|
||||
# 2. User 和 shares 迁移
|
||||
await migrate_bot_owner_and_shares()
|
||||
# 3. Marketplace 字段迁移
|
||||
await migrate_add_marketplace_fields()
|
||||
|
||||
# SQL 表创建语句
|
||||
tables_sql = [
|
||||
@ -1205,7 +1319,8 @@ async def get_bots(authorization: Optional[str] = Header(None)):
|
||||
# 管理员可以看到所有 Bot
|
||||
await cursor.execute("""
|
||||
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
|
||||
LEFT JOIN agent_user u ON b.owner_id = u.id
|
||||
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
|
||||
is_owner=True,
|
||||
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,
|
||||
role=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("""
|
||||
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,
|
||||
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
|
||||
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
|
||||
@ -1253,6 +1371,8 @@ async def get_bots(authorization: Optional[str] = Header(None)):
|
||||
bot_id=str(row[0]), # bot_id 也指向主键 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_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,
|
||||
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,
|
||||
@ -1486,7 +1606,7 @@ async def get_bot_settings(bot_uuid: str, authorization: Optional[str] = Header(
|
||||
async with pool.connection() as conn:
|
||||
async with conn.cursor() as cursor:
|
||||
await cursor.execute("""
|
||||
SELECT id, settings, updated_at
|
||||
SELECT id, settings, updated_at, is_published, copied_from
|
||||
FROM agent_bots
|
||||
WHERE id = %s
|
||||
""", (bot_uuid,))
|
||||
@ -1495,7 +1615,7 @@ async def get_bot_settings(bot_uuid: str, authorization: Optional[str] = Header(
|
||||
if not row:
|
||||
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 {}
|
||||
|
||||
# 获取关联的模型信息
|
||||
@ -1538,6 +1658,8 @@ async def get_bot_settings(bot_uuid: str, authorization: Optional[str] = Header(
|
||||
enable_thinking=settings.get('enable_thinking', False),
|
||||
tool_response=settings.get('tool_response', False),
|
||||
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)
|
||||
)
|
||||
|
||||
@ -1617,7 +1739,10 @@ async def update_bot_settings(
|
||||
if request.skills is not None:
|
||||
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")
|
||||
|
||||
async with pool.connection() as conn:
|
||||
@ -1632,7 +1757,14 @@ async def update_bot_settings(
|
||||
existing_settings = row[1] if row[1] else {}
|
||||
existing_settings.update(update_json)
|
||||
|
||||
# 更新设置
|
||||
# 更新设置和is_published字段
|
||||
if need_update_published:
|
||||
await cursor.execute("""
|
||||
UPDATE agent_bots
|
||||
SET settings = %s, is_published = %s, updated_at = NOW()
|
||||
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()
|
||||
@ -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
BIN
suggestion-chips-dark.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 56 KiB |
BIN
suggestion-chips-light.png
Normal file
BIN
suggestion-chips-light.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 55 KiB |
Loading…
Reference in New Issue
Block a user