fix: delete agent 500 error + dynamic personality + deployment guide
- Fix delete agent 500: clean up FK records (agent_llm_logs, permissions, schedules, executions, team_members) and unbind goals/tasks before delete - Remove hardcoded personality templates in Android, replace with dynamic system prompt generation from name + description - Set promptSectionsEnabled=false to bypass PromptComposer for personality - Add Tencent Cloud Linux deployment guide (Docker Compose) - Accumulated backend service updates, frontend UI fixes, Android app changes Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
326
backend/scripts/add_template_market_data.py
Normal file
326
backend/scripts/add_template_market_data.py
Normal file
@@ -0,0 +1,326 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
为模板市场添加假数据
|
||||
"""
|
||||
import sys
|
||||
import os
|
||||
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||
|
||||
from app.core.database import SessionLocal, engine
|
||||
from app.models.workflow_template import WorkflowTemplate, TemplateRating, TemplateFavorite
|
||||
from app.models.user import User
|
||||
from sqlalchemy import text
|
||||
import uuid
|
||||
from datetime import datetime, timedelta
|
||||
import random
|
||||
|
||||
# 模板数据
|
||||
TEMPLATE_DATA = [
|
||||
{
|
||||
"name": "智能客服工作流",
|
||||
"description": "一个完整的智能客服工作流,支持自动回复、问题分类和转人工功能。",
|
||||
"category": "llm",
|
||||
"tags": ["客服", "AI", "自动化"],
|
||||
"nodes": [
|
||||
{"id": "start", "type": "start", "position": {"x": 100, "y": 100}, "data": {"label": "开始"}},
|
||||
{"id": "input", "type": "input", "position": {"x": 300, "y": 100}, "data": {"label": "接收用户消息"}},
|
||||
{"id": "llm", "type": "llm", "position": {"x": 500, "y": 100}, "data": {"label": "AI回复", "model": "gpt-3.5-turbo", "prompt": "你是一个专业的客服助手"}},
|
||||
{"id": "condition", "type": "condition", "position": {"x": 700, "y": 100}, "data": {"label": "是否需要转人工", "expression": "sentiment_score < 0.3"}},
|
||||
{"id": "output1", "type": "output", "position": {"x": 900, "y": 50}, "data": {"label": "AI回复"}},
|
||||
{"id": "output2", "type": "output", "position": {"x": 900, "y": 150}, "data": {"label": "转人工"}},
|
||||
{"id": "end", "type": "end", "position": {"x": 1100, "y": 100}, "data": {"label": "结束"}}
|
||||
],
|
||||
"edges": [
|
||||
{"id": "e1", "source": "start", "target": "input"},
|
||||
{"id": "e2", "source": "input", "target": "llm"},
|
||||
{"id": "e3", "source": "llm", "target": "condition"},
|
||||
{"id": "e4", "source": "condition", "target": "output1", "sourceHandle": "true"},
|
||||
{"id": "e5", "source": "condition", "target": "output2", "sourceHandle": "false"},
|
||||
{"id": "e6", "source": "output1", "target": "end"},
|
||||
{"id": "e7", "source": "output2", "target": "end"}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "数据清洗工作流",
|
||||
"description": "自动清洗和转换数据,支持多种数据源格式。",
|
||||
"category": "data_processing",
|
||||
"tags": ["数据处理", "ETL", "数据清洗"],
|
||||
"nodes": [
|
||||
{"id": "start", "type": "start", "position": {"x": 100, "y": 100}, "data": {"label": "开始"}},
|
||||
{"id": "input", "type": "input", "position": {"x": 300, "y": 100}, "data": {"label": "读取数据"}},
|
||||
{"id": "transform1", "type": "transform", "position": {"x": 500, "y": 100}, "data": {"label": "数据清洗", "mappings": [{"from": "name", "to": "姓名"}]}},
|
||||
{"id": "transform2", "type": "transform", "position": {"x": 700, "y": 100}, "data": {"label": "数据验证"}},
|
||||
{"id": "output", "type": "output", "position": {"x": 900, "y": 100}, "data": {"label": "输出结果"}},
|
||||
{"id": "end", "type": "end", "position": {"x": 1100, "y": 100}, "data": {"label": "结束"}}
|
||||
],
|
||||
"edges": [
|
||||
{"id": "e1", "source": "start", "target": "input"},
|
||||
{"id": "e2", "source": "input", "target": "transform1"},
|
||||
{"id": "e3", "source": "transform1", "target": "transform2"},
|
||||
{"id": "e4", "source": "transform2", "target": "output"},
|
||||
{"id": "e5", "source": "output", "target": "end"}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "定时数据同步",
|
||||
"description": "定时从数据库同步数据到另一个系统,支持增量同步。",
|
||||
"category": "automation",
|
||||
"tags": ["定时任务", "数据同步", "自动化"],
|
||||
"nodes": [
|
||||
{"id": "start", "type": "start", "position": {"x": 100, "y": 100}, "data": {"label": "开始"}},
|
||||
{"id": "schedule", "type": "schedule", "position": {"x": 300, "y": 100}, "data": {"label": "定时触发", "delay_type": "fixed", "delay_value": 3600, "delay_unit": "seconds"}},
|
||||
{"id": "db1", "type": "database", "position": {"x": 500, "y": 100}, "data": {"label": "读取源数据", "query": "SELECT * FROM source_table"}},
|
||||
{"id": "transform", "type": "transform", "position": {"x": 700, "y": 100}, "data": {"label": "数据转换"}},
|
||||
{"id": "db2", "type": "database", "position": {"x": 900, "y": 100}, "data": {"label": "写入目标", "query": "INSERT INTO target_table"}},
|
||||
{"id": "end", "type": "end", "position": {"x": 1100, "y": 100}, "data": {"label": "结束"}}
|
||||
],
|
||||
"edges": [
|
||||
{"id": "e1", "source": "start", "target": "schedule"},
|
||||
{"id": "e2", "source": "schedule", "target": "db1"},
|
||||
{"id": "e3", "source": "db1", "target": "transform"},
|
||||
{"id": "e4", "source": "transform", "target": "db2"},
|
||||
{"id": "e5", "source": "db2", "target": "end"}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "API数据聚合",
|
||||
"description": "从多个API获取数据并聚合处理,支持错误重试。",
|
||||
"category": "integration",
|
||||
"tags": ["API", "数据聚合", "集成"],
|
||||
"nodes": [
|
||||
{"id": "start", "type": "start", "position": {"x": 100, "y": 100}, "data": {"label": "开始"}},
|
||||
{"id": "http1", "type": "http", "position": {"x": 300, "y": 50}, "data": {"label": "API 1", "url": "https://api1.example.com/data", "method": "GET"}},
|
||||
{"id": "http2", "type": "http", "position": {"x": 300, "y": 150}, "data": {"label": "API 2", "url": "https://api2.example.com/data", "method": "GET"}},
|
||||
{"id": "transform", "type": "transform", "position": {"x": 500, "y": 100}, "data": {"label": "数据聚合"}},
|
||||
{"id": "output", "type": "output", "position": {"x": 700, "y": 100}, "data": {"label": "输出结果"}},
|
||||
{"id": "end", "type": "end", "position": {"x": 900, "y": 100}, "data": {"label": "结束"}}
|
||||
],
|
||||
"edges": [
|
||||
{"id": "e1", "source": "start", "target": "http1"},
|
||||
{"id": "e2", "source": "start", "target": "http2"},
|
||||
{"id": "e3", "source": "http1", "target": "transform"},
|
||||
{"id": "e4", "source": "http2", "target": "transform"},
|
||||
{"id": "e5", "source": "transform", "target": "output"},
|
||||
{"id": "e6", "source": "output", "target": "end"}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "邮件通知工作流",
|
||||
"description": "根据条件发送邮件通知,支持HTML格式和附件。",
|
||||
"category": "automation",
|
||||
"tags": ["邮件", "通知", "自动化"],
|
||||
"nodes": [
|
||||
{"id": "start", "type": "start", "position": {"x": 100, "y": 100}, "data": {"label": "开始"}},
|
||||
{"id": "input", "type": "input", "position": {"x": 300, "y": 100}, "data": {"label": "接收事件"}},
|
||||
{"id": "condition", "type": "condition", "position": {"x": 500, "y": 100}, "data": {"label": "判断是否需要通知", "expression": "event_type == 'alert'"}},
|
||||
{"id": "email", "type": "email", "position": {"x": 700, "y": 100}, "data": {"label": "发送邮件", "to_email": "admin@example.com", "subject": "系统通知"}},
|
||||
{"id": "end", "type": "end", "position": {"x": 900, "y": 100}, "data": {"label": "结束"}}
|
||||
],
|
||||
"edges": [
|
||||
{"id": "e1", "source": "start", "target": "input"},
|
||||
{"id": "e2", "source": "input", "target": "condition"},
|
||||
{"id": "e3", "source": "condition", "target": "email", "sourceHandle": "true"},
|
||||
{"id": "e4", "source": "condition", "target": "end", "sourceHandle": "false"},
|
||||
{"id": "e5", "source": "email", "target": "end"}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "内容生成工作流",
|
||||
"description": "使用AI生成文章、摘要等内容,支持多种格式输出。",
|
||||
"category": "llm",
|
||||
"tags": ["AI", "内容生成", "LLM"],
|
||||
"nodes": [
|
||||
{"id": "start", "type": "start", "position": {"x": 100, "y": 100}, "data": {"label": "开始"}},
|
||||
{"id": "input", "type": "input", "position": {"x": 300, "y": 100}, "data": {"label": "输入主题"}},
|
||||
{"id": "llm1", "type": "llm", "position": {"x": 500, "y": 100}, "data": {"label": "生成大纲", "model": "gpt-4", "prompt": "根据主题生成文章大纲"}},
|
||||
{"id": "llm2", "type": "llm", "position": {"x": 700, "y": 100}, "data": {"label": "生成内容", "model": "gpt-4", "prompt": "根据大纲生成完整文章"}},
|
||||
{"id": "transform", "type": "transform", "position": {"x": 900, "y": 100}, "data": {"label": "格式化输出"}},
|
||||
{"id": "output", "type": "output", "position": {"x": 1100, "y": 100}, "data": {"label": "输出文章"}},
|
||||
{"id": "end", "type": "end", "position": {"x": 1300, "y": 100}, "data": {"label": "结束"}}
|
||||
],
|
||||
"edges": [
|
||||
{"id": "e1", "source": "start", "target": "input"},
|
||||
{"id": "e2", "source": "input", "target": "llm1"},
|
||||
{"id": "e3", "source": "llm1", "target": "llm2"},
|
||||
{"id": "e4", "source": "llm2", "target": "transform"},
|
||||
{"id": "e5", "source": "transform", "target": "output"},
|
||||
{"id": "e6", "source": "output", "target": "end"}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "文件处理工作流",
|
||||
"description": "批量处理文件,支持上传、下载、格式转换等功能。",
|
||||
"category": "data_processing",
|
||||
"tags": ["文件处理", "批量操作"],
|
||||
"nodes": [
|
||||
{"id": "start", "type": "start", "position": {"x": 100, "y": 100}, "data": {"label": "开始"}},
|
||||
{"id": "loop", "type": "loop", "position": {"x": 300, "y": 100}, "data": {"label": "遍历文件列表"}},
|
||||
{"id": "file_read", "type": "file_operation", "position": {"x": 500, "y": 100}, "data": {"label": "读取文件", "operation": "read"}},
|
||||
{"id": "transform", "type": "transform", "position": {"x": 700, "y": 100}, "data": {"label": "处理文件"}},
|
||||
{"id": "file_write", "type": "file_operation", "position": {"x": 900, "y": 100}, "data": {"label": "保存文件", "operation": "write"}},
|
||||
{"id": "loop_end", "type": "loop_end", "position": {"x": 1100, "y": 100}, "data": {"label": "循环结束"}},
|
||||
{"id": "end", "type": "end", "position": {"x": 1300, "y": 100}, "data": {"label": "结束"}}
|
||||
],
|
||||
"edges": [
|
||||
{"id": "e1", "source": "start", "target": "loop"},
|
||||
{"id": "e2", "source": "loop", "target": "file_read"},
|
||||
{"id": "e3", "source": "file_read", "target": "transform"},
|
||||
{"id": "e4", "source": "transform", "target": "file_write"},
|
||||
{"id": "e5", "source": "file_write", "target": "loop_end"},
|
||||
{"id": "e6", "source": "loop_end", "target": "loop"},
|
||||
{"id": "e7", "source": "loop_end", "target": "end"}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Webhook触发器",
|
||||
"description": "接收外部Webhook请求并触发工作流,支持多种认证方式。",
|
||||
"category": "integration",
|
||||
"tags": ["Webhook", "触发器", "集成"],
|
||||
"nodes": [
|
||||
{"id": "start", "type": "start", "position": {"x": 100, "y": 100}, "data": {"label": "开始"}},
|
||||
{"id": "webhook", "type": "webhook", "position": {"x": 300, "y": 100}, "data": {"label": "接收Webhook", "url": "/api/v1/webhooks/trigger"}},
|
||||
{"id": "transform", "type": "transform", "position": {"x": 500, "y": 100}, "data": {"label": "处理数据"}},
|
||||
{"id": "http", "type": "http", "position": {"x": 700, "y": 100}, "data": {"label": "调用外部API", "url": "https://api.example.com/process", "method": "POST"}},
|
||||
{"id": "output", "type": "output", "position": {"x": 900, "y": 100}, "data": {"label": "返回结果"}},
|
||||
{"id": "end", "type": "end", "position": {"x": 1100, "y": 100}, "data": {"label": "结束"}}
|
||||
],
|
||||
"edges": [
|
||||
{"id": "e1", "source": "start", "target": "webhook"},
|
||||
{"id": "e2", "source": "webhook", "target": "transform"},
|
||||
{"id": "e3", "source": "transform", "target": "http"},
|
||||
{"id": "e4", "source": "http", "target": "output"},
|
||||
{"id": "e5", "source": "output", "target": "end"}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
def get_random_user(db):
|
||||
"""获取一个随机用户"""
|
||||
users = db.query(User).all()
|
||||
if users:
|
||||
return random.choice(users)
|
||||
return None
|
||||
|
||||
def add_template_data():
|
||||
"""添加模板数据"""
|
||||
db = SessionLocal()
|
||||
try:
|
||||
# 获取用户
|
||||
user = get_random_user(db)
|
||||
if not user:
|
||||
print("❌ 没有找到用户,请先创建用户")
|
||||
return
|
||||
|
||||
print(f"✅ 使用用户: {user.username}")
|
||||
print(f"📝 开始添加模板数据...\n")
|
||||
|
||||
added_count = 0
|
||||
skipped_count = 0
|
||||
|
||||
for template_info in TEMPLATE_DATA:
|
||||
# 检查模板是否已存在
|
||||
existing = db.query(WorkflowTemplate).filter(
|
||||
WorkflowTemplate.name == template_info["name"],
|
||||
WorkflowTemplate.user_id == user.id
|
||||
).first()
|
||||
|
||||
if existing:
|
||||
print(f"ℹ️ 模板已存在: {template_info['name']}")
|
||||
skipped_count += 1
|
||||
continue
|
||||
|
||||
# 创建模板
|
||||
template = WorkflowTemplate(
|
||||
id=str(uuid.uuid4()),
|
||||
name=template_info["name"],
|
||||
description=template_info["description"],
|
||||
category=template_info["category"],
|
||||
tags=template_info["tags"],
|
||||
nodes=template_info["nodes"],
|
||||
edges=template_info["edges"],
|
||||
is_public=True,
|
||||
is_featured=random.choice([True, False]), # 随机设置为精选
|
||||
view_count=random.randint(0, 1000),
|
||||
use_count=random.randint(0, 100),
|
||||
rating_count=random.randint(0, 50),
|
||||
rating_avg=round(random.uniform(3.5, 5.0), 1),
|
||||
user_id=user.id,
|
||||
created_at=datetime.now() - timedelta(days=random.randint(0, 30)),
|
||||
updated_at=datetime.now() - timedelta(days=random.randint(0, 7))
|
||||
)
|
||||
|
||||
db.add(template)
|
||||
added_count += 1
|
||||
print(f"✅ 添加模板: {template_info['name']}")
|
||||
|
||||
db.commit()
|
||||
|
||||
print(f"\n{'='*60}")
|
||||
print(f"✅ 数据添加完成!")
|
||||
print(f" 新增: {added_count} 个模板")
|
||||
print(f" 跳过: {skipped_count} 个模板(已存在)")
|
||||
print(f"{'='*60}")
|
||||
|
||||
# 添加一些评分和收藏数据
|
||||
print(f"\n📝 添加评分和收藏数据...")
|
||||
|
||||
templates = db.query(WorkflowTemplate).filter(
|
||||
WorkflowTemplate.is_public == True
|
||||
).all()
|
||||
|
||||
if templates and user:
|
||||
# 为部分模板添加评分
|
||||
for template in random.sample(templates, min(5, len(templates))):
|
||||
existing_rating = db.query(TemplateRating).filter(
|
||||
TemplateRating.template_id == template.id,
|
||||
TemplateRating.user_id == user.id
|
||||
).first()
|
||||
|
||||
if not existing_rating:
|
||||
rating = TemplateRating(
|
||||
id=str(uuid.uuid4()),
|
||||
template_id=template.id,
|
||||
user_id=user.id,
|
||||
rating=random.randint(4, 5),
|
||||
comment=random.choice([
|
||||
"非常好用的模板!",
|
||||
"简单易用,推荐!",
|
||||
"功能完整,值得收藏。",
|
||||
"帮我节省了很多时间。",
|
||||
"模板设计得很专业。"
|
||||
])
|
||||
)
|
||||
db.add(rating)
|
||||
|
||||
# 为部分模板添加收藏
|
||||
for template in random.sample(templates, min(3, len(templates))):
|
||||
existing_favorite = db.query(TemplateFavorite).filter(
|
||||
TemplateFavorite.template_id == template.id,
|
||||
TemplateFavorite.user_id == user.id
|
||||
).first()
|
||||
|
||||
if not existing_favorite:
|
||||
favorite = TemplateFavorite(
|
||||
id=str(uuid.uuid4()),
|
||||
template_id=template.id,
|
||||
user_id=user.id
|
||||
)
|
||||
db.add(favorite)
|
||||
|
||||
db.commit()
|
||||
print(f"✅ 已添加评分和收藏数据")
|
||||
|
||||
except Exception as e:
|
||||
db.rollback()
|
||||
print(f"❌ 添加数据失败: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
if __name__ == "__main__":
|
||||
print("="*60)
|
||||
print("模板市场假数据生成工具")
|
||||
print("="*60)
|
||||
print()
|
||||
add_template_data()
|
||||
11
backend/scripts/bubble_sort.py
Normal file
11
backend/scripts/bubble_sort.py
Normal file
@@ -0,0 +1,11 @@
|
||||
def bubble_sort(arr):
|
||||
n = len(arr)
|
||||
for i in range(n):
|
||||
swapped = False
|
||||
for j in range(0, n - i - 1):
|
||||
if arr[j] > arr[j + 1]:
|
||||
arr[j], arr[j + 1] = arr[j + 1], arr[j]
|
||||
swapped = True
|
||||
if not swapped:
|
||||
break
|
||||
return arr
|
||||
97
backend/scripts/count_extensions.sh
Normal file
97
backend/scripts/count_extensions.sh
Normal file
@@ -0,0 +1,97 @@
|
||||
#!/bin/bash
|
||||
# count_extensions.sh - 统计当前目录下每种文件扩展名的数量
|
||||
# 用法: ./count_extensions.sh [目录路径] [-r|--recursive]
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# 默认值
|
||||
TARGET_DIR="."
|
||||
RECURSIVE=false
|
||||
|
||||
# 解析参数
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
-r|--recursive)
|
||||
RECURSIVE=true
|
||||
shift
|
||||
;;
|
||||
-h|--help)
|
||||
echo "用法: $0 [目录路径] [-r|--recursive]"
|
||||
echo ""
|
||||
echo "选项:"
|
||||
echo " 目录路径 要统计的目录(默认当前目录)"
|
||||
echo " -r, --recursive 递归统计子目录"
|
||||
echo " -h, --help 显示帮助信息"
|
||||
exit 0
|
||||
;;
|
||||
-*)
|
||||
echo "错误: 未知选项 $1" >&2
|
||||
exit 1
|
||||
;;
|
||||
*)
|
||||
TARGET_DIR="$1"
|
||||
shift
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [ ! -d "$TARGET_DIR" ]; then
|
||||
echo "错误: 目录 '$TARGET_DIR' 不存在" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "正在统计: $TARGET_DIR"
|
||||
echo "-------------------------"
|
||||
|
||||
# 统计文件扩展名
|
||||
if [ "$RECURSIVE" = true ]; then
|
||||
# 递归方式 - 包含子目录
|
||||
result=$(find "$TARGET_DIR" -type f | awk -F. '
|
||||
NF > 1 {
|
||||
ext = tolower($NF)
|
||||
count[ext]++
|
||||
}
|
||||
NF == 1 {
|
||||
count["(无扩展名)"]++
|
||||
}
|
||||
END {
|
||||
for (ext in count) {
|
||||
printf "%-20s %d\n", ext, count[ext]
|
||||
}
|
||||
}
|
||||
')
|
||||
else
|
||||
# 仅当前目录 - 不进入子目录(使用 -maxdepth 1 兼容性更好的写法)
|
||||
result=$(find "$TARGET_DIR" -maxdepth 1 -type f | awk -F. '
|
||||
NF > 1 {
|
||||
ext = tolower($NF)
|
||||
count[ext]++
|
||||
}
|
||||
NF == 1 {
|
||||
count["(无扩展名)"]++
|
||||
}
|
||||
END {
|
||||
for (ext in count) {
|
||||
printf "%-20s %d\n", ext, count[ext]
|
||||
}
|
||||
}
|
||||
')
|
||||
fi
|
||||
|
||||
# 输出结果
|
||||
if [ -z "$result" ]; then
|
||||
echo "(没有找到文件)"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "$result" | sort -k2 -nr | awk '
|
||||
BEGIN { total = 0 }
|
||||
{
|
||||
printf " %-20s %s\n", $1, $2
|
||||
total += $2
|
||||
}
|
||||
END {
|
||||
print "-------------------------"
|
||||
printf " 合计: %d 个文件\n", total
|
||||
}
|
||||
'
|
||||
90
backend/scripts/fix_agent_permission.py
Normal file
90
backend/scripts/fix_agent_permission.py
Normal file
@@ -0,0 +1,90 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
修复 Agent 权限级别: 将目标 Agent 的 permission_level 改为 acceptEdits
|
||||
避免每次文件写入都需要弹窗确认
|
||||
"""
|
||||
import sys
|
||||
import os
|
||||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
|
||||
from app.core.database import SessionLocal
|
||||
from app.models.agent import Agent
|
||||
import json
|
||||
|
||||
|
||||
def fix_agent_permission(agent_name: str = None):
|
||||
"""将指定 Agent 的 permission_level 设为 acceptEdits"""
|
||||
db = SessionLocal()
|
||||
|
||||
try:
|
||||
agents = db.query(Agent).all()
|
||||
|
||||
if not agents:
|
||||
print("[ERROR] No agents found")
|
||||
return
|
||||
|
||||
print(f"[INFO] Found {len(agents)} agents:\n")
|
||||
for a in agents:
|
||||
wc = a.workflow_config or {}
|
||||
nodes = wc.get("nodes", [])
|
||||
perm = "default"
|
||||
for n in nodes:
|
||||
if n.get("type") in ("agent", "llm", "template"):
|
||||
perm = n.get("data", {}).get("permission_level", "default")
|
||||
break
|
||||
print(f" [{a.status}] {a.name} (id={a.id}) -- permission_level={perm}")
|
||||
|
||||
if agent_name:
|
||||
target = db.query(Agent).filter(Agent.name == agent_name).first()
|
||||
if not target:
|
||||
target = db.query(Agent).filter(Agent.name.like(f"%{agent_name}%")).first()
|
||||
if not target:
|
||||
print(f"\n[ERROR] No agent matching '{agent_name}' found")
|
||||
return
|
||||
agents_to_fix = [target]
|
||||
else:
|
||||
agents_to_fix = agents
|
||||
|
||||
print(f"\n[INFO] Fixing permission_level...")
|
||||
fixed = 0
|
||||
for agent in agents_to_fix:
|
||||
wc = agent.workflow_config or {}
|
||||
nodes = wc.get("nodes", [])
|
||||
|
||||
updated = False
|
||||
for node in nodes:
|
||||
if node.get("type") in ("agent", "llm", "template"):
|
||||
data = node.get("data") or {}
|
||||
old_perm = data.get("permission_level", "default")
|
||||
if old_perm != "acceptEdits":
|
||||
data["permission_level"] = "acceptEdits"
|
||||
node["data"] = data
|
||||
updated = True
|
||||
print(f" [FIXED] [{agent.name}] permission_level: {old_perm} -> acceptEdits")
|
||||
|
||||
if updated:
|
||||
agent.workflow_config = wc
|
||||
fixed += 1
|
||||
|
||||
if fixed > 0:
|
||||
db.commit()
|
||||
print(f"\n[DONE] Fixed {fixed} agent(s), permission_level set to acceptEdits")
|
||||
print(f" File write/edit operations will no longer require confirmation")
|
||||
else:
|
||||
print(f"\n[INFO] No agents need fixing (already acceptEdits)")
|
||||
|
||||
except Exception as e:
|
||||
db.rollback()
|
||||
print(f"[ERROR] Fix failed: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import argparse
|
||||
parser = argparse.ArgumentParser(description="Fix Agent permission level")
|
||||
parser.add_argument("-n", "--name", type=str, help="Agent name (fuzzy match)")
|
||||
args = parser.parse_args()
|
||||
fix_agent_permission(agent_name=args.name)
|
||||
201
backend/scripts/insert_template_data_simple.py
Normal file
201
backend/scripts/insert_template_data_simple.py
Normal file
@@ -0,0 +1,201 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
简单的模板数据插入脚本
|
||||
直接使用数据库连接,不依赖app模块
|
||||
"""
|
||||
import pymysql
|
||||
import json
|
||||
import uuid
|
||||
from datetime import datetime, timedelta
|
||||
import random
|
||||
|
||||
# 数据库配置(从config.py中获取)
|
||||
DB_CONFIG = {
|
||||
'host': 'gz-cynosdbmysql-grp-d26pzce5.sql.tencentcdb.com',
|
||||
'port': 24936,
|
||||
'user': 'root',
|
||||
'password': '!Rjb12191',
|
||||
'database': 'agent_db',
|
||||
'charset': 'utf8mb4'
|
||||
}
|
||||
|
||||
# 模板数据
|
||||
TEMPLATES = [
|
||||
{
|
||||
"name": "智能客服工作流",
|
||||
"description": "一个完整的智能客服工作流,支持自动回复、问题分类和转人工功能。",
|
||||
"category": "llm",
|
||||
"tags": ["客服", "AI", "自动化"],
|
||||
"is_featured": True
|
||||
},
|
||||
{
|
||||
"name": "数据清洗工作流",
|
||||
"description": "自动清洗和转换数据,支持多种数据源格式。",
|
||||
"category": "data_processing",
|
||||
"tags": ["数据处理", "ETL", "数据清洗"],
|
||||
"is_featured": False
|
||||
},
|
||||
{
|
||||
"name": "定时数据同步",
|
||||
"description": "定时从数据库同步数据到另一个系统,支持增量同步。",
|
||||
"category": "automation",
|
||||
"tags": ["定时任务", "数据同步", "自动化"],
|
||||
"is_featured": True
|
||||
},
|
||||
{
|
||||
"name": "API数据聚合",
|
||||
"description": "从多个API获取数据并聚合处理,支持错误重试。",
|
||||
"category": "integration",
|
||||
"tags": ["API", "数据聚合", "集成"],
|
||||
"is_featured": False
|
||||
},
|
||||
{
|
||||
"name": "邮件通知工作流",
|
||||
"description": "根据条件发送邮件通知,支持HTML格式和附件。",
|
||||
"category": "automation",
|
||||
"tags": ["邮件", "通知", "自动化"],
|
||||
"is_featured": True
|
||||
},
|
||||
{
|
||||
"name": "内容生成工作流",
|
||||
"description": "使用AI生成文章、摘要等内容,支持多种格式输出。",
|
||||
"category": "llm",
|
||||
"tags": ["AI", "内容生成", "LLM"],
|
||||
"is_featured": False
|
||||
},
|
||||
{
|
||||
"name": "文件处理工作流",
|
||||
"description": "批量处理文件,支持上传、下载、格式转换等功能。",
|
||||
"category": "data_processing",
|
||||
"tags": ["文件处理", "批量操作"],
|
||||
"is_featured": False
|
||||
},
|
||||
{
|
||||
"name": "Webhook触发器",
|
||||
"description": "接收外部Webhook请求并触发工作流,支持多种认证方式。",
|
||||
"category": "integration",
|
||||
"tags": ["Webhook", "触发器", "集成"],
|
||||
"is_featured": True
|
||||
}
|
||||
]
|
||||
|
||||
# 简单的节点和边配置
|
||||
SIMPLE_NODES = [
|
||||
{"id": "start", "type": "start", "position": {"x": 100, "y": 100}, "data": {"label": "开始"}},
|
||||
{"id": "input", "type": "input", "position": {"x": 300, "y": 100}, "data": {"label": "输入"}},
|
||||
{"id": "process", "type": "transform", "position": {"x": 500, "y": 100}, "data": {"label": "处理"}},
|
||||
{"id": "output", "type": "output", "position": {"x": 700, "y": 100}, "data": {"label": "输出"}},
|
||||
{"id": "end", "type": "end", "position": {"x": 900, "y": 100}, "data": {"label": "结束"}}
|
||||
]
|
||||
|
||||
SIMPLE_EDGES = [
|
||||
{"id": "e1", "source": "start", "target": "input"},
|
||||
{"id": "e2", "source": "input", "target": "process"},
|
||||
{"id": "e3", "source": "process", "target": "output"},
|
||||
{"id": "e4", "source": "output", "target": "end"}
|
||||
]
|
||||
|
||||
def insert_templates():
|
||||
"""插入模板数据"""
|
||||
try:
|
||||
# 连接数据库
|
||||
connection = pymysql.connect(**DB_CONFIG)
|
||||
|
||||
try:
|
||||
with connection.cursor() as cursor:
|
||||
# 获取第一个用户ID
|
||||
cursor.execute("SELECT id FROM users LIMIT 1")
|
||||
user_result = cursor.fetchone()
|
||||
|
||||
if not user_result:
|
||||
print("❌ 没有找到用户,请先创建用户")
|
||||
return
|
||||
|
||||
user_id = user_result[0]
|
||||
print(f"✅ 使用用户ID: {user_id}")
|
||||
print()
|
||||
|
||||
# 检查模板是否已存在
|
||||
cursor.execute("SELECT COUNT(*) FROM workflow_templates WHERE user_id = %s", (user_id,))
|
||||
existing_count = cursor.fetchone()[0]
|
||||
|
||||
if existing_count > 0:
|
||||
print(f"ℹ️ 已存在 {existing_count} 个模板,跳过插入")
|
||||
return
|
||||
|
||||
# 插入模板
|
||||
added_count = 0
|
||||
for template_info in TEMPLATES:
|
||||
template_id = str(uuid.uuid4())
|
||||
nodes_json = json.dumps(SIMPLE_NODES, ensure_ascii=False)
|
||||
edges_json = json.dumps(SIMPLE_EDGES, ensure_ascii=False)
|
||||
tags_json = json.dumps(template_info["tags"], ensure_ascii=False)
|
||||
|
||||
view_count = random.randint(0, 1000)
|
||||
use_count = random.randint(0, 100)
|
||||
rating_count = random.randint(0, 50)
|
||||
rating_avg = round(random.uniform(3.5, 5.0), 1)
|
||||
days_ago = random.randint(0, 30)
|
||||
created_at = datetime.now() - timedelta(days=days_ago)
|
||||
updated_at = datetime.now() - timedelta(days=random.randint(0, 7))
|
||||
|
||||
sql = """
|
||||
INSERT INTO workflow_templates (
|
||||
id, name, description, category, tags, nodes, edges,
|
||||
is_public, is_featured, view_count, use_count, rating_count, rating_avg,
|
||||
user_id, created_at, updated_at
|
||||
) VALUES (
|
||||
%s, %s, %s, %s, %s, %s, %s,
|
||||
%s, %s, %s, %s, %s, %s,
|
||||
%s, %s, %s
|
||||
)
|
||||
"""
|
||||
|
||||
cursor.execute(sql, (
|
||||
template_id,
|
||||
template_info["name"],
|
||||
template_info["description"],
|
||||
template_info["category"],
|
||||
tags_json,
|
||||
nodes_json,
|
||||
edges_json,
|
||||
True,
|
||||
template_info["is_featured"],
|
||||
view_count,
|
||||
use_count,
|
||||
rating_count,
|
||||
rating_avg,
|
||||
user_id,
|
||||
created_at,
|
||||
updated_at
|
||||
))
|
||||
added_count += 1
|
||||
print(f"✅ 添加模板: {template_info['name']}")
|
||||
|
||||
connection.commit()
|
||||
|
||||
print()
|
||||
print("=" * 60)
|
||||
print(f"✅ 数据添加完成!")
|
||||
print(f" 新增: {added_count} 个模板")
|
||||
print("=" * 60)
|
||||
|
||||
# 查询总数
|
||||
cursor.execute("SELECT COUNT(*) FROM workflow_templates WHERE user_id = %s", (user_id,))
|
||||
total_count = cursor.fetchone()[0]
|
||||
print(f" 当前模板总数: {total_count} 个")
|
||||
|
||||
finally:
|
||||
connection.close()
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ 执行失败: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
|
||||
if __name__ == "__main__":
|
||||
print("=" * 60)
|
||||
print("模板市场假数据生成工具")
|
||||
print("=" * 60)
|
||||
print()
|
||||
insert_templates()
|
||||
114
backend/scripts/migrate_add_workspace.py
Normal file
114
backend/scripts/migrate_add_workspace.py
Normal file
@@ -0,0 +1,114 @@
|
||||
"""
|
||||
数据迁移脚本:创建默认工作区,将所有现有数据归入默认工作区。
|
||||
|
||||
运行方式: cd backend && ./venv/Scripts/python scripts/migrate_add_workspace.py
|
||||
"""
|
||||
import sys
|
||||
import os
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..'))
|
||||
|
||||
import uuid
|
||||
from datetime import datetime
|
||||
from app.core.database import SessionLocal, engine
|
||||
from app.core.config import settings
|
||||
|
||||
def migrate():
|
||||
db = SessionLocal()
|
||||
try:
|
||||
# 1. 创建默认工作区
|
||||
from app.models.workspace import Workspace, WorkspaceMembership
|
||||
from app.models.user import User
|
||||
|
||||
# 检查是否已有默认工作区
|
||||
existing = db.query(Workspace).filter(Workspace.is_default == 1).first()
|
||||
if existing:
|
||||
print(f"默认工作区已存在: {existing.id} ({existing.name})")
|
||||
default_ws_id = existing.id
|
||||
default_ws = existing
|
||||
else:
|
||||
# 找到第一个 admin 用户作为 owner
|
||||
admin_user = db.query(User).filter(User.role == "admin").first()
|
||||
if not admin_user:
|
||||
admin_user = db.query(User).first()
|
||||
if not admin_user:
|
||||
print("错误: 数据库中没有用户,请先创建管理员账户")
|
||||
return
|
||||
|
||||
default_ws = Workspace(
|
||||
id=str(uuid.uuid4()),
|
||||
name="默认工作区",
|
||||
description="系统自动创建的默认工作区,包含所有历史数据",
|
||||
is_default=1,
|
||||
owner_id=admin_user.id,
|
||||
max_members=200,
|
||||
status="active",
|
||||
created_at=datetime.utcnow(),
|
||||
updated_at=datetime.utcnow(),
|
||||
)
|
||||
db.add(default_ws)
|
||||
db.flush()
|
||||
default_ws_id = default_ws.id
|
||||
print(f"已创建默认工作区: {default_ws_id} ({default_ws.name})")
|
||||
|
||||
# 2. 将所有现有用户加入默认工作区
|
||||
all_users = db.query(User).all()
|
||||
added_count = 0
|
||||
for user in all_users:
|
||||
existing_membership = db.query(WorkspaceMembership).filter(
|
||||
WorkspaceMembership.workspace_id == default_ws_id,
|
||||
WorkspaceMembership.user_id == user.id,
|
||||
).first()
|
||||
if not existing_membership:
|
||||
membership = WorkspaceMembership(
|
||||
id=str(uuid.uuid4()),
|
||||
workspace_id=default_ws_id,
|
||||
user_id=user.id,
|
||||
role="admin", # 现有用户默认给admin角色
|
||||
joined_at=datetime.utcnow(),
|
||||
)
|
||||
db.add(membership)
|
||||
added_count += 1
|
||||
|
||||
db.flush()
|
||||
print(f"已将 {added_count} 个用户加入默认工作区")
|
||||
|
||||
# 3. 回填所有 Tier 1 表的 workspace_id
|
||||
tables_to_backfill = [
|
||||
("agents", "user_id"),
|
||||
("workflows", "user_id"),
|
||||
("goals", "creator_id"),
|
||||
("knowledge_bases", "user_id"),
|
||||
("data_sources", "user_id"),
|
||||
("model_configs", "user_id"),
|
||||
("tools", "user_id"),
|
||||
("agent_schedules", "user_id"),
|
||||
("workflow_templates", "user_id"),
|
||||
("orchestration_templates", "user_id"),
|
||||
("executions", None), # No user_id, backfill all
|
||||
("tasks", None),
|
||||
]
|
||||
|
||||
from sqlalchemy import text
|
||||
for table, _user_col in tables_to_backfill:
|
||||
result = db.execute(
|
||||
text(f"UPDATE {table} SET workspace_id = :ws_id WHERE workspace_id IS NULL"),
|
||||
{"ws_id": default_ws_id}
|
||||
)
|
||||
print(f" {table}: 已回填 {result.rowcount} 行")
|
||||
|
||||
db.commit()
|
||||
print("\n迁移完成!")
|
||||
print(f" 默认工作区: {default_ws_id}")
|
||||
print(f" 成员数: {len(all_users)}")
|
||||
print(f" 所有现有数据已归入默认工作区")
|
||||
|
||||
except Exception as e:
|
||||
db.rollback()
|
||||
print(f"迁移失败: {e}")
|
||||
raise
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
migrate()
|
||||
31
backend/scripts/start_backend.sh
Normal file
31
backend/scripts/start_backend.sh
Normal file
@@ -0,0 +1,31 @@
|
||||
#!/bin/bash
|
||||
|
||||
# 启动后端服务脚本
|
||||
|
||||
cd "$(dirname "$0")"
|
||||
|
||||
echo "🚀 启动后端服务..."
|
||||
echo ""
|
||||
|
||||
# 检查Python环境
|
||||
if ! command -v python3 &> /dev/null; then
|
||||
echo "❌ Python3 未安装"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 安装依赖(如果需要)
|
||||
echo "📦 检查依赖..."
|
||||
python3 -c "import fastapi, uvicorn" 2>/dev/null || {
|
||||
echo "正在安装依赖..."
|
||||
pip3 install fastapi uvicorn[standard] python-multipart pymysql sqlalchemy python-jose[cryptography] passlib[bcrypt] pydantic pydantic-settings -q
|
||||
}
|
||||
|
||||
# 启动服务
|
||||
echo "🌐 启动后端服务在端口 8037..."
|
||||
echo ""
|
||||
|
||||
python3 -m uvicorn app.main:app \
|
||||
--host 0.0.0.0 \
|
||||
--port 8037 \
|
||||
--reload \
|
||||
--log-level info
|
||||
Reference in New Issue
Block a user