diff --git a/TangledUp-AI_dataBase_fastAPI-main/.gitignore b/TangledUp-AI_dataBase_fastAPI-main/.gitignore new file mode 100644 index 0000000..95847a1 --- /dev/null +++ b/TangledUp-AI_dataBase_fastAPI-main/.gitignore @@ -0,0 +1,29 @@ +# Large files +*.tar +*.tar.gz +*.zip +*.rar +*.7z +*.iso +*.bin +*.exe +*.dll +*.so +*.dylib + +# Python +__pycache__/ +*.pyc +*.pyo +*.pyd +.Python +env/ +venv/ +.env + +# OS +.DS_Store +Thumbs.db + +# Project specific +uploads/ diff --git a/TangledUp-AI_dataBase_fastAPI-main/.trae/documents/Implement Dynamic Agent API with Video and Knowledge Base Support.md b/TangledUp-AI_dataBase_fastAPI-main/.trae/documents/Implement Dynamic Agent API with Video and Knowledge Base Support.md new file mode 100644 index 0000000..e9a94c9 --- /dev/null +++ b/TangledUp-AI_dataBase_fastAPI-main/.trae/documents/Implement Dynamic Agent API with Video and Knowledge Base Support.md @@ -0,0 +1,47 @@ +# Add Dynamic Agent API (Type=1) with Database Integration + +I will implement the Dynamic Agent feature by integrating it with the existing `agent_cards` system. Dynamic Agents will be distinguished by `type=1` and will store their extended data (videos, knowledge base) in a new linked table. + +## 1. Database Schema Extension +I will create a new table `dynamic_agent_details` to store the specialized data for dynamic agents. +* **Table Name**: `dynamic_agent_details` +* **Columns**: + * `id`: `SERIAL PRIMARY KEY` + * `card_id`: `INTEGER` (Foreign Key linking to `agent_cards.card_id`) + * `videos`: `JSONB` (Stores list of `{url, emotion}`, min 1, max 7) + * `kb_id`: `VARCHAR` (Knowledge Base ID) + * `kb_config`: `JSONB` (Additional Knowledge Base fields) + * `created_at`: `TIMESTAMP` + +## 2. Code Implementation (`main.py`) + +### Pydantic Models +I will define: +* `VideoItem`: `{url: str, emotion: str}` +* `DynamicAgentCreate`: Inherits/Includes fields from `AgentCard` (name, avatar, etc.) PLUS: + * `videos`: List[VideoItem] (validated length 1-7) + * `kb_id`: str + * `kb_config`: dict + +### API Endpoints +1. **Create Dynamic Agent** + * `POST /dynamic_agent/` + * **Logic**: + 1. Insert the base agent info into `agent_cards` with `type=1` and `RETURNING card_id`. + 2. Insert the extended info (videos, kb details) into `dynamic_agent_details` using the returned `card_id`. + 3. Return the new `card_id`. + +2. **Get Dynamic Agent Details** + * `GET /dynamic_agent/{card_id}` + * **Logic**: + 1. Fetch base info from `agent_cards`. + 2. Fetch extended info from `dynamic_agent_details`. + 3. Return combined result. + +### Helper Functions +* `init_dynamic_agent_db()`: Checks/Creates the `dynamic_agent_details` table on startup. + +## 3. Verification +* Create a dynamic agent via the new API. +* Verify it appears in the `agent_cards` table (as type 1). +* Verify extended data is in `dynamic_agent_details`. diff --git a/TangledUp-AI_dataBase_fastAPI-main/.trae/documents/Implement Tool Management System for Dynamic Agents.md b/TangledUp-AI_dataBase_fastAPI-main/.trae/documents/Implement Tool Management System for Dynamic Agents.md new file mode 100644 index 0000000..afa7923 --- /dev/null +++ b/TangledUp-AI_dataBase_fastAPI-main/.trae/documents/Implement Tool Management System for Dynamic Agents.md @@ -0,0 +1,43 @@ +# Implement Tool Management for Dynamic Agents + +I will implement a system to manage "Tools" (Function Calls) and associate them with Dynamic Agents, allowing the frontend to easily configure which tools an agent can use. + +## 1. Database Schema Expansion +I will create two new tables in PostgreSQL: +* `tools`: Stores reusable tool definitions (templates). + * `tool_id`: `SERIAL PRIMARY KEY` + * `name`: `VARCHAR` (Unique, e.g., "get_weather") + * `description`: `TEXT` (For LLM understanding) + * `parameters`: `JSONB` (JSON Schema for arguments) + * `created_at`: `TIMESTAMP` +* `agent_tools`: Maps agents to tools (Many-to-Many). + * `agent_card_id`: `INTEGER` (FK) + * `tool_id`: `INTEGER` (FK) + * `config`: `JSONB` (Optional, for agent-specific tool config if needed) + +## 2. API Implementation (`main.py`) + +### Pydantic Models +* `ToolCreate`: Validation for creating new tool definitions. +* `ToolResponse`: API response format. +* `AgentToolAssignment`: For linking tools to agents. + +### New Endpoints +* **Tool Management (CRUD)**: + * `POST /tools/`: Create a new tool definition. + * `GET /tools/`: List all available tools. + * `PUT /tools/{tool_id}`: Update a tool. + * `DELETE /tools/{tool_id}`: Delete a tool. +* **Agent Tool Association**: + * `POST /agent/{card_id}/tools`: Assign a list of tools to an agent. + * `GET /agent/{card_id}/tools`: Get all tools assigned to an agent. + * `DELETE /agent/{card_id}/tools/{tool_id}`: Remove a tool from an agent. + +### Update Existing Logic +* **Modify `GET /dynamic_agent/{card_id}`**: + * Update the query to automatically fetch and include the list of associated tools in the agent details response. This ensures the frontend gets everything in one call. + +## 3. Verification +* Create a "Weather Query" tool definition. +* Assign it to a Dynamic Agent. +* Verify the agent details endpoint returns the tool info correctly. diff --git a/TangledUp-AI_dataBase_fastAPI-main/Dockerfile b/TangledUp-AI_dataBase_fastAPI-main/Dockerfile new file mode 100644 index 0000000..de8f60b --- /dev/null +++ b/TangledUp-AI_dataBase_fastAPI-main/Dockerfile @@ -0,0 +1,30 @@ +FROM python:3.12 + +WORKDIR /app + +COPY requirements.txt . +COPY .env . + +RUN pip install -r requirements.txt -i https://mirrors.aliyun.com/pypi/simple + +COPY . . + +ARG API_KEY +ARG DB_HOST +ARG DB_PORT +ARG DB_NAME +ARG DB_USER +ARG DB_PASSWORD +ARG DB_SSLMODE + +ENV API_KEY=$API_KEY +ENV DB_HOST=$DB_HOST +ENV DB_PORT=$DB_PORT +ENV DB_NAME=$DB_NAME +ENV DB_USER=$DB_USER +ENV DB_PASSWORD=$DB_PASSWORD +ENV DB_SSLMODE=$DB_SSLMODE + +CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "9090"] + +EXPOSE 9090 \ No newline at end of file diff --git a/TangledUp-AI_dataBase_fastAPI-main/README.md b/TangledUp-AI_dataBase_fastAPI-main/README.md new file mode 100644 index 0000000..1b06662 --- /dev/null +++ b/TangledUp-AI_dataBase_fastAPI-main/README.md @@ -0,0 +1,216 @@ +# TangledupAI API 数据库服务 + +这是一个基于 FastAPI 构建的后端 API 服务,为 TangledupAI 提供用户管理、Agent 卡片管理、对话管理和文件存储等功能。 + +## 功能特性 + +- **用户管理**: 用户注册、查询、更新 +- **Agent 管理**: 创建、查询、更新、删除 Agent 卡片 +- **对话管理**: 创建对话、保存消息 +- **短信服务**: 发送验证码短信 +- **文件存储**: 基于阿里云 OSS 的文件上传、删除、查询 +- **API 安全**: 基于 API Key 的身份验证 + +## 技术栈 + +- **后端框架**: FastAPI +- **数据库**: PostgreSQL +- **对象存储**: 阿里云 OSS +- **短信服务**: 阿里云短信服务 +- **部署**: Docker & Docker Compose + +## 项目结构 + +``` +API_database/ +├── main.py # 主应用程序入口 +├── config.py # 配置管理 +├── models.py # 数据模型 +├── oss_service.py # 阿里云 OSS 服务 +├── sms.py # 短信服务 +├── utils.py # 工具函数 +├── requirements.txt # Python 依赖 +├── Dockerfile # Docker 镜像构建文件 +├── docker-compose.yml # Docker Compose 配置 +└── .env # 环境变量配置 +``` + +## 安装与运行 + +### 环境要求 + +- Python 3.12+ +- PostgreSQL 数据库 +- 阿里云 OSS 账号 +- 阿里云短信服务账号 + +### 本地开发 + +1. 克隆仓库 +```bash +git clone +cd API_database +``` + +2. 安装依赖 +```bash +pip install -r requirements.txt -i https://mirrors.aliyun.com/pypi/simple +``` + +3. 配置环境变量 +创建 `.env` 文件并配置以下变量: +```env +API_KEY=your_api_key +DB_HOST=your_db_host +DB_PORT=your_db_port +DB_NAME=your_db_name +DB_USER=your_db_user +DB_PASSWORD=your_db_password +DB_SSLMODE=disable + +# 阿里云 OSS 配置 +OSS_ACCESS_KEY_ID=your_oss_access_key_id +OSS_ACCESS_KEY_SECRET=your_oss_access_key_secret +OSS_ENDPOINT=https://oss-cn-shanghai.aliyuncs.com +OSS_BUCKET_NAME=your_bucket_name +``` + +4. 启动应用 +```bash +uvicorn main:app --reload --host 0.0.0.0 --port 9090 +``` + +### Docker 部署 + +1. 使用 Docker Compose +```bash +# 设置环境变量 +export API_KEY=your_api_key +export DB_HOST=your_db_host +export DB_PORT=your_db_port +export DB_NAME=your_db_name +export DB_USER=your_db_user +export DB_PASSWORD=your_db_password +export DB_SSLMODE=disable + +# 启动服务 +docker-compose up -d +``` + +2. 或者直接使用 Docker +```bash +docker build -t tangledup-api . +docker run -d -p 9090:9090 \ + -e API_KEY=your_api_key \ + -e DB_HOST=your_db_host \ + -e DB_PORT=your_db_port \ + -e DB_NAME=your_db_name \ + -e DB_USER=your_db_user \ + -e DB_PASSWORD=your_db_password \ + -e DB_SSLMODE=disable \ + tangledup-api +``` + +## API 文档 + +启动服务后,可以通过以下地址访问 API 文档: + +- Swagger UI: http://localhost:9090/docs +- ReDoc: http://localhost:9090/redoc + +## API 端点 + +### 用户管理 + +- `POST /register/` - 用户注册 +- `GET /user/{phone}` - 获取用户信息 +- `PUT /user/{phone}` - 更新用户信息 + +### Agent 管理 + +- `POST /new_agent/` - 创建 Agent 卡片 +- `GET /agents/{phone}` - 获取用户的所有 Agent 卡片 +- `GET /agent_id/{agent_id}` - 根据 ID 获取 Agent 卡片 +- `PUT /agent/{phone_number}/{agent_name}` - 更新 Agent 卡片 +- `PUT /agent_id/{agent_id}` - 根据 ID 更新 Agent 卡片 +- `DELETE /agent/{phone_number}/{agent_name}` - 删除 Agent 卡片 +- `DELETE /agent_id/{agent_id}` - 根据 ID 删除 Agent 卡片 + +### 对话管理 + +- `POST /conversations/` - 创建新对话 +- `POST /messages/` - 保存消息 + +### 文件上传 + +- `POST /upload_image/` - 上传图片 +- `POST /upload` - 上传文件到 OSS + +### 短信服务 + +- `POST /api/send-sms` - 发送短信 +- `GET /api/sms-records` - 获取短信记录 + +### OSS 服务 + +- `POST /test/` - 测试 OSS 连接 + +## 身份验证 + +所有 API 端点都需要在请求头中包含有效的 API Key: + +``` +x-api-key: 123tangledup-ai +``` + +## 数据库表结构 + +### users 表 +- phone_number: 手机号码 (主键) +- user_name: 用户名 +- user_details: 用户详情 +- avatar: 头像URL +- email: 邮箱 +- points: 积分 +- create_date: 创建时间 + +### agent_cards 表 +- card_id: 卡片ID (主键) +- phone_number: 用户手机号码 +- card_info: 卡片信息 +- agent_avatar_url: Agent头像URL +- agent_prompt: Agent提示 +- agent_name: Agent名称 +- is_publish: 是否发布 +- create_date: 创建时间 +- voice_type: 语音类型 +- temperature: 温度参数 + +### conversations 表 +- conversation_id: 对话ID (主键) +- user_phone: 用户手机号码 +- visitor_key: 访客密钥 +- agent_card_id: Agent卡片ID + +### messages 表 +- message_id: 消息ID (主键) +- conversation_id: 对话ID +- sender: 发送者 (user/agent) +- content: 消息内容 +- order: 消息顺序 + +## 贡献指南 + +1. Fork 本仓库 +2. 创建您的特性分支 (`git checkout -b feature/AmazingFeature`) +3. 提交您的更改 (`git commit -m 'Add some AmazingFeature'`) +4. 推送到分支 (`git push origin feature/AmazingFeature`) +5. 打开一个 Pull Request + +## 许可证 + +本项目采用 MIT 许可证 - 查看 [LICENSE](LICENSE) 文件了解详情。 + +## 联系方式 + +如有问题或建议,请联系项目维护者。 \ No newline at end of file diff --git a/TangledUp-AI_dataBase_fastAPI-main/config.py b/TangledUp-AI_dataBase_fastAPI-main/config.py new file mode 100644 index 0000000..83e9097 --- /dev/null +++ b/TangledUp-AI_dataBase_fastAPI-main/config.py @@ -0,0 +1,45 @@ +import os +from typing import Optional +from pydantic_settings import BaseSettings + + +class Settings(BaseSettings): + """应用配置类""" + + # API密钥配置 + api_key: str = "123tangledup-ai" + + # SMS配置 + sms_template_code: str = "SMS_465420786" + sms_sign_name: str = "叠加态科技" + + # 数据库配置 + db_host: str = "121.43.104.161" + db_port: str = "6432" + db_name: str = "call_data" + db_user: str = "tangledup-ai" + db_password: str = "123tangledup-ai" + db_sslmode: str = "disable" + + # 阿里云OSS配置 + oss_access_key_id: str = "LTAI5tE62GW8MKyoEaotzxXk" + oss_access_key_secret: str = "Zdzqo1fgj57DxxioXOotNKhJdSfVQW" + oss_endpoint: str = "https://oss-cn-shanghai.aliyuncs.com" + oss_bucket_name: str = "tangledup-ai-staging" + oss_internal_endpoint: str = "https://oss-cn-shanghai-internal.aliyuncs.com" + + # FastAPI配置 + app_host: str = os.getenv("APP_HOST", "0.0.0.0") + app_port: int = int(os.getenv("APP_PORT", "8000")) + debug: bool = os.getenv("DEBUG", "True").lower() == "true" + + # 文件上传配置 + max_file_size: int = 1024 * 1024 * 1024 # 1GB + allowed_extensions: list = ['.jpg', '.jpeg', '.png', '.gif', '.pdf', '.txt', '.doc', '.docx', '.mp3', '.mp4', '.wav', '.webm'] + + class Config: + env_file = ".env" + + +# 全局设置实例 +settings = Settings() \ No newline at end of file diff --git a/TangledUp-AI_dataBase_fastAPI-main/docker-compose.yml b/TangledUp-AI_dataBase_fastAPI-main/docker-compose.yml new file mode 100644 index 0000000..fa0efb9 --- /dev/null +++ b/TangledUp-AI_dataBase_fastAPI-main/docker-compose.yml @@ -0,0 +1,29 @@ +version: '3.8' +services: + api: + build: + context: . + dockerfile: Dockerfile + args: + - API_KEY + - DB_HOST + - DB_PORT + - DB_NAME + - DB_USER + - DB_PASSWORD + - DB_SSLMODE + environment: + - API_KEY=${API_KEY} + - DB_HOST=${DB_HOST} + - DB_PORT=${DB_PORT} + - DB_NAME=${DB_NAME} + - DB_USER=${DB_USER} + - DB_PASSWORD=${DB_PASSWORD} + - DB_SSLMODE=${DB_SSLMODE} + ports: + - "9090:9090" + volumes: + - .:/app + # - /mnt/server/userImage/call_avator:/mnt/server/userImage/call_avator + command: ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "9090"] + restart: always \ No newline at end of file diff --git a/TangledUp-AI_dataBase_fastAPI-main/docker_deply.sh b/TangledUp-AI_dataBase_fastAPI-main/docker_deply.sh new file mode 100755 index 0000000..c8f5d29 --- /dev/null +++ b/TangledUp-AI_dataBase_fastAPI-main/docker_deply.sh @@ -0,0 +1,301 @@ +#!/bin/bash + +# ============================================================================= +# Docker 镜像构建和部署自动化脚本 +# # chmod u+x docker_deply.sh +# 用法: +# ./docker_deply.sh # 完整构建和部署流程 (默认AMD64架构) +# ./docker_deply.sh -amd # 构建和部署AMD64架构 +# ./docker_deply.sh -arm # 构建和部署ARM64架构 +# ./docker_depla.sh -upload # 仅上传已存在的tar文件并部署 +# ./docker_deply.sh -upload -amd # 仅上传已存在的AMD64架构tar文件并部署 +# ./docker_deply.sh -upload -arm # 仅上传已存在的ARM64架构tar文件并部署 +# ============================================================================= + +# 配置变量 - 请根据实际情况修改 +SERVER_HOST="121.43.104.161" # 服务器IP地址 +SERVER_USER="root" # 服务器用户名 +SERVER_PASSWORD="123quant-speed" # 服务器密码 +SERVER_PORT="22" # SSH端口,默认22 +IMAGE_NAME="api_database-api" # Docker镜像名称 +IMAGE_TAG="latest" # Docker镜像标签 +CONTAINER_NAME="api_database-api" # 容器名称 +LOCAL_PORT="9090" # 本地端口 +CONTAINER_PORT="9090" # 容器端口 +TAR_FILE="${IMAGE_NAME}-${IMAGE_TAG}.tar" # 压缩包文件名 + +# 架构相关变量 +PLATFORM="linux/amd64" # 默认架构 +ARCH_SUFFIX="" # 架构后缀,用于区分不同架构的tar文件 + +# 颜色输出 +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +# 日志函数 +log_info() { + echo -e "${BLUE}[INFO]${NC} $1" +} + +log_success() { + echo -e "${GREEN}[SUCCESS]${NC} $1" +} + +log_warning() { + echo -e "${YELLOW}[WARNING]${NC} $1" +} + +log_error() { + echo -e "${RED}[ERROR]${NC} $1" +} + +# 检查依赖 +check_dependencies() { + log_info "检查依赖..." + + if ! command -v docker &> /dev/null; then + log_error "Docker 未安装,请先安装 Docker" + exit 1 + fi + + if ! command -v sshpass &> /dev/null; then + log_error "sshpass 未安装,请先安装 sshpass" + log_info "macOS: brew install sshpass" + log_info "Ubuntu: sudo apt-get install sshpass" + exit 1 + fi + + log_success "依赖检查完成" +} + +# 解析命令行参数 +parse_arguments() { + while [[ $# -gt 0 ]]; do + case $1 in + -amd) + PLATFORM="linux/amd64" + ARCH_SUFFIX="-amd64" + log_info "设置目标架构为 AMD64" + shift + ;; + -arm) + PLATFORM="linux/arm64" + ARCH_SUFFIX="-arm64" + log_info "设置目标架构为 ARM64" + shift + ;; + -upload) + UPLOAD_ONLY=true + log_info "设置为仅上传模式" + shift + ;; + *) + log_error "未知参数: $1" + log_info "支持的参数: -amd, -arm, -upload" + exit 1 + ;; + esac + done + + # 更新TAR_FILE名,包含架构后缀 + TAR_FILE="${IMAGE_NAME}-${IMAGE_TAG}${ARCH_SUFFIX}.tar" + log_info "镜像文件名: ${TAR_FILE}" +} + +# 构建Docker镜像 +build_image() { + log_info "开始构建 Docker 镜像..." + + # 检查是否存在旧的tar文件 + if [ -f "$TAR_FILE" ]; then + log_warning "发现旧的tar文件,正在删除..." + rm -f "$TAR_FILE" + fi + + # 构建镜像并导出为tar文件 + docker buildx build --platform $PLATFORM -t "${IMAGE_NAME}:${IMAGE_TAG}" --output type=docker,dest="./${TAR_FILE}" . + + if [ $? -eq 0 ]; then + log_success "Docker 镜像构建完成: ${TAR_FILE}" + else + log_error "Docker 镜像构建失败" + exit 1 + fi +} + +# 上传文件到服务器 +upload_to_server() { + log_info "上传文件到服务器..." + + sshpass -p "$SERVER_PASSWORD" scp -P "$SERVER_PORT" -o StrictHostKeyChecking=no "$TAR_FILE" "${SERVER_USER}@${SERVER_HOST}:/tmp/" + + if [ $? -eq 0 ]; then + log_success "文件上传成功" + else + log_error "文件上传失败" + exit 1 + fi +} + +# 在服务器上部署 +deploy_on_server() { + log_info "在服务器上部署..." + + sshpass -p "$SERVER_PASSWORD" ssh -p "$SERVER_PORT" -o StrictHostKeyChecking=no "${SERVER_USER}@${SERVER_HOST}" << EOF + set -e + + echo "[INFO] 开始服务器端部署..." + + # 检查并停止现有容器 + if docker ps -a --format 'table {{.Names}}' | grep -q "^${CONTAINER_NAME}$"; then + echo "[INFO] 发现现有容器 ${CONTAINER_NAME},正在停止并删除..." + docker stop ${CONTAINER_NAME} || true + docker rm ${CONTAINER_NAME} || true + fi + + # 检查是否有其他容器占用目标端口(处理容器名称不一致但端口冲突的情况) + # 注意:这里使用 \$ 转义,确保命令在服务器端执行而不是本地 + CONFLICT_CONTAINER_ID=\$(docker ps --format "{{.ID}} {{.Ports}}" | grep ":${LOCAL_PORT}->" | awk '{print \$1}') + if [ ! -z "\$CONFLICT_CONTAINER_ID" ]; then + echo "[WARNING] 发现端口 ${LOCAL_PORT} 被其他容器 (\$CONFLICT_CONTAINER_ID) 占用,正在强制停止并删除..." + docker stop \$CONFLICT_CONTAINER_ID || true + docker rm \$CONFLICT_CONTAINER_ID || true + fi + + # 检查并删除现有镜像 + if sudo docker images --format 'table {{.Repository}}:{{.Tag}}' | grep -q "^${IMAGE_NAME}:${IMAGE_TAG}$"; then + echo "[INFO] 发现现有镜像 ${IMAGE_NAME}:${IMAGE_TAG},正在删除..." + sudo docker rmi ${IMAGE_NAME}:${IMAGE_TAG} || true + fi + + # 加载新镜像 + echo "[INFO] 加载新镜像..." + sudo docker load -i /tmp/${TAR_FILE} + + # 验证镜像是否加载成功 + if sudo docker images | grep -q "${IMAGE_NAME}"; then + echo "[SUCCESS] 镜像加载成功" + else + echo "[ERROR] 镜像加载失败" + exit 1 + fi + + # 运行新容器 + echo "[INFO] 启动新容器..." + sudo docker run -d -p ${LOCAL_PORT}:${CONTAINER_PORT} --name ${CONTAINER_NAME} ${IMAGE_NAME}:${IMAGE_TAG} + + # 验证容器是否启动成功 + if sudo docker ps | grep -q "${CONTAINER_NAME}"; then + echo "[SUCCESS] 容器启动成功" + echo "[INFO] 容器状态:" + sudo docker ps | grep "${CONTAINER_NAME}" + else + echo "[ERROR] 容器启动失败" + echo "[INFO] 查看容器日志:" + sudo docker logs ${CONTAINER_NAME} + exit 1 + fi + + # 清理临时文件 + echo "[INFO] 清理临时文件..." + rm -f /tmp/${TAR_FILE} + + echo "[SUCCESS] 部署完成!" + echo "[INFO] 应用访问地址: http://${SERVER_HOST}:${LOCAL_PORT}" +EOF + + if [ $? -eq 0 ]; then + log_success "服务器部署完成" + else + log_error "服务器部署失败" + exit 1 + fi +} + +# 清理本地文件 +cleanup_local() { + log_info "清理本地临时文件..." + + if [ -f "$TAR_FILE" ]; then + rm -f "$TAR_FILE" + log_success "本地临时文件已清理" + fi +} + +# 显示部署信息 +show_deployment_info() { + log_success "部署完成!" + echo "" + echo "==========================================" + echo "部署信息:" + echo "==========================================" + echo "服务器地址: ${SERVER_HOST}" + echo "应用端口: ${LOCAL_PORT}" + echo "访问地址: http://${SERVER_HOST}:${LOCAL_PORT}" + echo "容器名称: ${CONTAINER_NAME}" + echo "镜像名称: ${IMAGE_NAME}:${IMAGE_TAG}" + echo "目标架构: ${PLATFORM}" + echo "镜像文件: ${TAR_FILE}" + echo "==========================================" + echo "" + log_info "如需查看容器日志,请在服务器上运行: sudo docker logs ${CONTAINER_NAME}" + log_info "如需停止容器,请在服务器上运行: sudo docker stop ${CONTAINER_NAME}" +} + +# 主函数 +main() { + echo "==========================================" + echo "Docker 镜像构建和部署自动化脚本" + echo "==========================================" + echo "" + + # 解析命令行参数 + UPLOAD_ONLY=false + parse_arguments "$@" + + # 检查配置 + if [ "$SERVER_HOST" = "your-server-ip" ] || [ "$SERVER_PASSWORD" = "your-password" ]; then + log_error "请先配置脚本顶部的服务器信息" + log_info "需要修改的变量:" + log_info " - SERVER_HOST: 服务器IP地址" + log_info " - SERVER_USER: 服务器用户名" + log_info " - SERVER_PASSWORD: 服务器密码" + exit 1 + fi + + # 检查是否是上传模式 + if [ "$UPLOAD_ONLY" = true ]; then + log_info "检测到 -upload 参数,跳过构建步骤" + + # 检查tar文件是否存在 + if [ ! -f "$TAR_FILE" ]; then + log_error "未找到tar文件: $TAR_FILE" + log_info "请先运行脚本构建镜像,或确保tar文件存在" + exit 1 + fi + + log_success "找到tar文件: $TAR_FILE" + + # 执行上传和部署流程 + upload_to_server + deploy_on_server + cleanup_local + show_deployment_info + else + # 执行完整的部署流程 + check_dependencies + build_image + upload_to_server + deploy_on_server + cleanup_local + show_deployment_info + fi +} + +# 脚本入口 +if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then + main "$@" +fi \ No newline at end of file diff --git a/TangledUp-AI_dataBase_fastAPI-main/ins.txt b/TangledUp-AI_dataBase_fastAPI-main/ins.txt new file mode 100644 index 0000000..7facd98 --- /dev/null +++ b/TangledUp-AI_dataBase_fastAPI-main/ins.txt @@ -0,0 +1,6 @@ +nohup uvicorn main:app --reload --port 9090 > output.log 2>&1 & + + +uvicorn main:app --host 0.0.0.0 --port 9090 + +uvicorn main:app --host 0.0.0.0 --port 9090 --reload \ No newline at end of file diff --git a/TangledUp-AI_dataBase_fastAPI-main/main.py b/TangledUp-AI_dataBase_fastAPI-main/main.py new file mode 100644 index 0000000..1df5b30 --- /dev/null +++ b/TangledUp-AI_dataBase_fastAPI-main/main.py @@ -0,0 +1,1137 @@ +# 修正拼写错误,将 qyditic 改为 pydantic +from fastapi import FastAPI, HTTPException, Depends, Request, UploadFile, File, Form +from pydantic import BaseModel, validator +import json +import psycopg2.pool +from fastapi.middleware.cors import CORSMiddleware +from fastapi.security import APIKeyHeader +import uuid +import os +from typing import Optional, List +from pydantic import BaseModel +from sms import SMS +from oss_service import oss_service +from config import settings + +class SMSRequest(BaseModel): + phone_number: str + code: str + template_code: Optional[str] = settings.sms_template_code + sign_name: Optional[str] = settings.sms_sign_name + + + +import os +API_KEY = "123tangledup-ai" +api_key_header = APIKeyHeader(name="x-api-key") + +def get_current_user(api_key: str = Depends(api_key_header)): + if api_key != API_KEY: + raise HTTPException(status_code=401, detail="Invalid API Key") + return {"api_key": api_key} + +app = FastAPI() + +app.add_middleware( + CORSMiddleware, +# 原代码存在语法错误,* 不能单独作为列表元素,这里假设要允许所有来源,使用 ['*'] + allow_origins=['http://localhost:5173', 'https://data.tangledup-ai.com', 'https://sms.tangledup-ai.com' , '*'], + allow_credentials=True, + allow_methods=['*'], + allow_headers=['*'], +) + +# 数据库连接池配置 +pool = psycopg2.pool.SimpleConnectionPool( + minconn=1, + maxconn=10, + host="47.101.218.42", + port="5432", + database="jeremygan2021", + user="jeremygan2021", + password="qweasdzxc1", + sslmode="disable" +) + +class User(BaseModel): + phone: str + name: str + user_details: str + avatar: str + email: str + points: int + +class UserUpdate(BaseModel): + name: str | None = None + user_details: str | None = None + avatar: str | None = None + email: str | None = None + points: int | None = None + + +@app.post('/register/', tags=["用户管理"]) +def register(user: User, current_user: dict = Depends(get_current_user)): + conn = pool.getconn() + try: + with conn.cursor() as cur: + cur.execute('SELECT * FROM users WHERE phone_number = %s', (user.phone,)) + if cur.fetchone(): + raise HTTPException(status_code=400, detail='User already exists') + cur.execute('INSERT INTO users (phone_number, user_name, user_details, avatar, email, points, create_date) VALUES (%s, %s, %s, %s, %s, %s, NOW())', (user.phone, user.name, user.user_details, user.avatar, user.email, user.points)) + conn.commit() + return {'message': 'User registered successfully'} + finally: + pool.putconn(conn) + +@app.get('/user/{phone}', tags=["用户管理"]) +def get_user(phone: str, current_user: dict = Depends(get_current_user)): + conn = pool.getconn() + try: + with conn.cursor() as cur: + cur.execute('SELECT * FROM users WHERE phone_number = %s', (phone,)) + user = cur.fetchone() + if not user: + raise HTTPException(status_code=404, detail='User not found') + # 直接返回完整的用户数据作为 JSON + columns = [desc[0] for desc in cur.description] + user_dict = dict(zip(columns, user)) + return user_dict + finally: + pool.putconn(conn) + + +@app.get('/agents/{phone}', tags=["用户管理"]) +def get_user(phone: str, current_user: dict = Depends(get_current_user)): + conn = pool.getconn() + try: + with conn.cursor() as cur: + cur.execute('SELECT * FROM agent_cards WHERE phone_number = %s', (phone,)) + users = cur.fetchall() + if not users: + raise HTTPException(status_code=404, detail='No agents found for this user') + # 返回所有agent数据作为JSON数组 + columns = [desc[0] for desc in cur.description] + return [dict(zip(columns, user)) for user in users] + finally: + pool.putconn(conn) + + + + +class AgentCard(BaseModel): + phone_number: str + card_info: str + agent_avatar_url: str + agent_prompt: str + agent_name: str + is_publish: bool + create_date: str + voice_type: str + temperature: float + type: int = 0 + + +class AgentCardUpdate(BaseModel): + card_info: str | None = None + agent_avatar_url: str | None = None + agent_prompt: str | None = None + agent_name: str | None = None + is_publish: bool | None = None + create_date: str | None = None + voice_type: str | None = None + temperature: float | None = None + type: int | None = None + + +class VideoItem(BaseModel): + url: str + emotion: str + +class DynamicAgentCreate(AgentCard): + videos: List[VideoItem] + kb_id: str + kb_config: dict + + # Force type to 1 for dynamic agents + type: int = 1 + + @validator('videos') + def validate_videos(cls, v): + if not (1 <= len(v) <= 7): + raise ValueError('Must provide between 1 and 7 videos') + return v + +class DynamicAgentUpdate(BaseModel): + card_info: str | None = None + agent_avatar_url: str | None = None + agent_prompt: str | None = None + agent_name: str | None = None + is_publish: bool | None = None + voice_type: str | None = None + temperature: float | None = None + videos: List[VideoItem] | None = None + kb_id: str | None = None + kb_config: dict | None = None + + @validator('videos') + def validate_videos(cls, v): + if v is not None and not (1 <= len(v) <= 7): + raise ValueError('Must provide between 1 and 7 videos') + return v + +@app.post('/new_agent/', tags=["Agent管理"]) +def create_agent_card(agent_card: AgentCard, current_user: dict = Depends(get_current_user)): + conn = pool.getconn() + try: + with conn.cursor() as cur: +# 原代码存在拼写错误,修正为正确的变量名和 SQL 语句 + cur.execute('SELECT * FROM users WHERE phone_number = %s', (agent_card.phone_number,)) + if not cur.fetchone(): + raise HTTPException(status_code=400, detail='Phone number not registered') + cur.execute('SELECT * FROM agent_cards WHERE phone_number = %s AND agent_name = %s', (agent_card.phone_number, agent_card.agent_name)) + if cur.fetchone(): + raise HTTPException(status_code=400, detail='Agent card already exists') + cur.execute('INSERT INTO agent_cards (phone_number, card_info, agent_avatar_url, agent_prompt, agent_name, is_publish, create_date, voice_type, temperature, type) VALUES (%s, %s, %s, %s, %s, %s, NOW(), %s, %s, %s)', (agent_card.phone_number, agent_card.card_info, agent_card.agent_avatar_url, agent_card.agent_prompt, agent_card.agent_name, agent_card.is_publish, agent_card.voice_type, agent_card.temperature, agent_card.type)) + conn.commit() + return {'message': 'Agent card created successfully'} + finally: + pool.putconn(conn) + + +class ToolCreate(BaseModel): + name: str + description: str | None = None + parameters: dict + +class ToolUpdate(BaseModel): + description: str | None = None + parameters: dict | None = None + +class ToolResponse(ToolCreate): + tool_id: int + created_at: str | None = None + +class AgentToolAssignment(BaseModel): + tool_ids: List[int] + +@app.post('/tools/', tags=["工具管理"]) +def create_tool(tool: ToolCreate, current_user: dict = Depends(get_current_user)): + conn = pool.getconn() + try: + with conn.cursor() as cur: + # Check if tool exists + cur.execute('SELECT * FROM tools WHERE name = %s', (tool.name,)) + if cur.fetchone(): + raise HTTPException(status_code=400, detail='Tool with this name already exists') + + cur.execute(''' + INSERT INTO tools (name, description, parameters) + VALUES (%s, %s, %s) + RETURNING tool_id + ''', (tool.name, tool.description, json.dumps(tool.parameters))) + + tool_id = cur.fetchone()[0] + conn.commit() + return {'message': 'Tool created successfully', 'tool_id': tool_id} + finally: + pool.putconn(conn) + +@app.get('/tools/', tags=["工具管理"]) +def list_tools(current_user: dict = Depends(get_current_user)): + conn = pool.getconn() + try: + with conn.cursor() as cur: + cur.execute('SELECT * FROM tools ORDER BY tool_id') + tools = cur.fetchall() + + columns = [desc[0] for desc in cur.description] + result = [] + for t in tools: + d = dict(zip(columns, t)) + # Ensure parameters is a dict (psycopg2 might return it as dict if jsonb, but let's be safe) + if isinstance(d['parameters'], str): + d['parameters'] = json.loads(d['parameters']) + # Convert datetime to str + d['created_at'] = str(d['created_at']) + result.append(d) + return result + finally: + pool.putconn(conn) + +@app.get('/tools/{tool_id}', tags=["工具管理"]) +def get_tool(tool_id: int, current_user: dict = Depends(get_current_user)): + conn = pool.getconn() + try: + with conn.cursor() as cur: + cur.execute('SELECT * FROM tools WHERE tool_id = %s', (tool_id,)) + tool = cur.fetchone() + if not tool: + raise HTTPException(status_code=404, detail='Tool not found') + + columns = [desc[0] for desc in cur.description] + d = dict(zip(columns, tool)) + if isinstance(d['parameters'], str): + d['parameters'] = json.loads(d['parameters']) + d['created_at'] = str(d['created_at']) + return d + finally: + pool.putconn(conn) + +@app.put('/tools/{tool_id}', tags=["工具管理"]) +def update_tool(tool_id: int, tool_update: ToolUpdate, current_user: dict = Depends(get_current_user)): + conn = pool.getconn() + try: + with conn.cursor() as cur: + cur.execute('SELECT * FROM tools WHERE tool_id = %s', (tool_id,)) + if not cur.fetchone(): + raise HTTPException(status_code=404, detail='Tool not found') + + update_fields = [] + params = [] + if tool_update.description is not None: + update_fields.append('description = %s') + params.append(tool_update.description) + if tool_update.parameters is not None: + update_fields.append('parameters = %s') + params.append(json.dumps(tool_update.parameters)) + + if not update_fields: + raise HTTPException(status_code=400, detail='No fields to update') + + params.append(tool_id) + query = f"UPDATE tools SET {', '.join(update_fields)} WHERE tool_id = %s" + + cur.execute(query, params) + conn.commit() + return {'message': 'Tool updated successfully'} + finally: + pool.putconn(conn) + +@app.delete('/tools/{tool_id}', tags=["工具管理"]) +def delete_tool(tool_id: int, current_user: dict = Depends(get_current_user)): + conn = pool.getconn() + try: + with conn.cursor() as cur: + cur.execute('DELETE FROM tools WHERE tool_id = %s', (tool_id,)) + if cur.rowcount == 0: + raise HTTPException(status_code=404, detail='Tool not found') + conn.commit() + return {'message': 'Tool deleted successfully'} + finally: + pool.putconn(conn) + +@app.post('/agent/{card_id}/tools', tags=["Agent工具关联"]) +def assign_tools_to_agent(card_id: int, assignment: AgentToolAssignment, current_user: dict = Depends(get_current_user)): + conn = pool.getconn() + try: + with conn.cursor() as cur: + # Check agent exists + cur.execute('SELECT * FROM agent_cards WHERE card_id = %s', (card_id,)) + if not cur.fetchone(): + raise HTTPException(status_code=404, detail='Agent not found') + + # Verify tools exist + if assignment.tool_ids: + cur.execute('SELECT count(*) FROM tools WHERE tool_id = ANY(%s)', (assignment.tool_ids,)) + count = cur.fetchone()[0] + if count != len(set(assignment.tool_ids)): + raise HTTPException(status_code=400, detail='One or more tool IDs are invalid') + + # We can choose to replace all or add. Let's implement "replace all" for simplicity and consistency with a "save configuration" model. + # First, delete existing associations + cur.execute('DELETE FROM agent_tools WHERE agent_card_id = %s', (card_id,)) + + # Insert new ones + if assignment.tool_ids: + values = [(card_id, tid) for tid in assignment.tool_ids] + args_str = ','.join(cur.mogrify("(%s,%s)", x).decode('utf-8') for x in values) + cur.execute("INSERT INTO agent_tools (agent_card_id, tool_id) VALUES " + args_str) + + conn.commit() + return {'message': 'Tools assigned successfully'} + finally: + pool.putconn(conn) + +@app.get('/agent/{card_id}/tools', tags=["Agent工具关联"]) +def get_agent_tools(card_id: int, current_user: dict = Depends(get_current_user)): + conn = pool.getconn() + try: + with conn.cursor() as cur: + query = """ + SELECT t.* + FROM tools t + JOIN agent_tools at ON t.tool_id = at.tool_id + WHERE at.agent_card_id = %s + """ + cur.execute(query, (card_id,)) + tools = cur.fetchall() + + columns = [desc[0] for desc in cur.description] + result = [] + for t in tools: + d = dict(zip(columns, t)) + if isinstance(d['parameters'], str): + d['parameters'] = json.loads(d['parameters']) + d['created_at'] = str(d['created_at']) + result.append(d) + return result + finally: + pool.putconn(conn) + +@app.post('/dynamic_agent/', tags=["Dynamic agent 管理"]) +def create_dynamic_agent(agent_data: DynamicAgentCreate, current_user: dict = Depends(get_current_user)): + conn = pool.getconn() + try: + with conn.cursor() as cur: + # 1. Check user exists + cur.execute('SELECT * FROM users WHERE phone_number = %s', (agent_data.phone_number,)) + if not cur.fetchone(): + raise HTTPException(status_code=400, detail='Phone number not registered') + + # 2. Check agent exists + cur.execute('SELECT * FROM agent_cards WHERE phone_number = %s AND agent_name = %s', (agent_data.phone_number, agent_data.agent_name)) + if cur.fetchone(): + raise HTTPException(status_code=400, detail='Agent card already exists') + + # 3. Insert into agent_cards (Force type=1) + # Note: We use RETURNING card_id to get the new ID + cur.execute(''' + INSERT INTO agent_cards + (phone_number, card_info, agent_avatar_url, agent_prompt, agent_name, is_publish, create_date, voice_type, temperature, type) + VALUES (%s, %s, %s, %s, %s, %s, NOW(), %s, %s, 1) + RETURNING card_id + ''', (agent_data.phone_number, agent_data.card_info, agent_data.agent_avatar_url, agent_data.agent_prompt, agent_data.agent_name, agent_data.is_publish, agent_data.voice_type, agent_data.temperature)) + + card_id = cur.fetchone()[0] + + # 4. Insert into dynamic_agent_details + # Convert videos list to JSON string/object + videos_json = json.dumps([v.dict() for v in agent_data.videos]) + kb_config_json = json.dumps(agent_data.kb_config) + + cur.execute(''' + INSERT INTO dynamic_agent_details (card_id, videos, kb_id, kb_config) + VALUES (%s, %s, %s, %s) + ''', (card_id, videos_json, agent_data.kb_id, kb_config_json)) + + conn.commit() + return {'message': 'Dynamic agent created successfully', 'card_id': card_id} + finally: + pool.putconn(conn) + +@app.get('/dynamic_agent/{card_id}', tags=["Dynamic agent 管理"]) +def get_dynamic_agent(card_id: int, current_user: dict = Depends(get_current_user)): + conn = pool.getconn() + try: + with conn.cursor() as cur: + # Join with dynamic_agent_details + cur.execute(''' + SELECT a.*, d.videos, d.kb_id, d.kb_config + FROM agent_cards a + LEFT JOIN dynamic_agent_details d ON a.card_id = d.card_id + WHERE a.card_id = %s + ''', (card_id,)) + row = cur.fetchone() + if not row: + raise HTTPException(status_code=404, detail='Agent not found') + + columns = [desc[0] for desc in cur.description] + result = dict(zip(columns, row)) + + # Fetch associated tools + cur.execute(''' + SELECT t.* + FROM tools t + JOIN agent_tools at ON t.tool_id = at.tool_id + WHERE at.agent_card_id = %s + ''', (card_id,)) + tools = cur.fetchall() + + tool_list = [] + tool_columns = [desc[0] for desc in cur.description] + for t in tools: + td = dict(zip(tool_columns, t)) + if isinstance(td['parameters'], str): + td['parameters'] = json.loads(td['parameters']) + td['created_at'] = str(td['created_at']) + tool_list.append(td) + + result['tools'] = tool_list + + return result + finally: + pool.putconn(conn) + +@app.delete('/agent/{phone_number}/{agent_name}', tags=["Agent管理"]) +def delete_agent_card(phone_number: str, agent_name: str, current_user: dict = Depends(get_current_user)): + conn = pool.getconn() + try: + with conn.cursor() as cur: + cur.execute('DELETE FROM agent_cards WHERE phone_number = %s AND agent_name = %s', (phone_number, agent_name)) + if cur.rowcount == 0: + raise HTTPException(status_code=404, detail='Agent card not found') + conn.commit() + return {'message': 'Agent card deleted successfully'} + finally: + pool.putconn(conn) + +@app.put('/agent/{phone_number}/{agent_name}', tags=["Agent管理"]) +def update_agent_card(phone_number: str, agent_name: str, agent_card: AgentCardUpdate, current_user: dict = Depends(get_current_user)): + conn = pool.getconn() + try: + with conn.cursor() as cur: + cur.execute('SELECT * FROM agent_cards WHERE phone_number = %s AND agent_name = %s', (phone_number, agent_name)) + if not cur.fetchone(): + raise HTTPException(status_code=404, detail='Agent card not found') + + # 构建动态更新的SQL语句和参数 + update_fields = [] + params = [] + + if agent_card.card_info is not None: + update_fields.append('card_info = %s') + params.append(agent_card.card_info) + if agent_card.agent_avatar_url is not None: + update_fields.append('agent_avatar_url = %s') + params.append(agent_card.agent_avatar_url) + if agent_card.agent_prompt is not None: + update_fields.append('agent_prompt = %s') + params.append(agent_card.agent_prompt) + if agent_card.agent_name is not None: + update_fields.append('agent_name = %s') + params.append(agent_card.agent_name) + if agent_card.is_publish is not None: + update_fields.append('is_publish = %s') + params.append(agent_card.is_publish) + if agent_card.voice_type is not None: + update_fields.append('voice_type = %s') + params.append(agent_card.voice_type) + if agent_card.temperature is not None: + update_fields.append('temperature = %s') + params.append(agent_card.temperature) + if agent_card.type is not None: + update_fields.append('type = %s') + params.append(agent_card.type) + + if not update_fields: + raise HTTPException(status_code=400, detail='No fields to update') + + params.extend([phone_number, agent_name]) + + update_query = f''' + UPDATE agent_cards SET + {', '.join(update_fields)} + WHERE phone_number = %s AND agent_name = %s + ''' + + cur.execute(update_query, params) + conn.commit() + return {'message': 'Agent card updated successfully'} + finally: + pool.putconn(conn) + + + + +@app.put('/dynamic_agent/{card_id}', tags=["Dynamic agent 管理"]) +def update_dynamic_agent(card_id: int, agent_update: DynamicAgentUpdate, current_user: dict = Depends(get_current_user)): + conn = pool.getconn() + try: + with conn.cursor() as cur: + # 1. Verify existence + cur.execute('SELECT * FROM agent_cards WHERE card_id = %s', (card_id,)) + if not cur.fetchone(): + raise HTTPException(status_code=404, detail='Agent not found') + + # 2. Update agent_cards table (base fields) + update_fields = [] + params = [] + + if agent_update.card_info is not None: + update_fields.append('card_info = %s') + params.append(agent_update.card_info) + if agent_update.agent_avatar_url is not None: + update_fields.append('agent_avatar_url = %s') + params.append(agent_update.agent_avatar_url) + if agent_update.agent_prompt is not None: + update_fields.append('agent_prompt = %s') + params.append(agent_update.agent_prompt) + if agent_update.agent_name is not None: + update_fields.append('agent_name = %s') + params.append(agent_update.agent_name) + if agent_update.is_publish is not None: + update_fields.append('is_publish = %s') + params.append(agent_update.is_publish) + if agent_update.voice_type is not None: + update_fields.append('voice_type = %s') + params.append(agent_update.voice_type) + if agent_update.temperature is not None: + update_fields.append('temperature = %s') + params.append(agent_update.temperature) + + if update_fields: + params.append(card_id) + cur.execute(f"UPDATE agent_cards SET {', '.join(update_fields)} WHERE card_id = %s", params) + + # 3. Update dynamic_agent_details table + # Check if details record exists (it should for dynamic agents, but create if missing to be safe?) + # Assuming it exists. + + dynamic_fields = [] + dynamic_params = [] + + if agent_update.videos is not None: + dynamic_fields.append('videos = %s') + dynamic_params.append(json.dumps([v.dict() for v in agent_update.videos])) + if agent_update.kb_id is not None: + dynamic_fields.append('kb_id = %s') + dynamic_params.append(agent_update.kb_id) + if agent_update.kb_config is not None: + dynamic_fields.append('kb_config = %s') + dynamic_params.append(json.dumps(agent_update.kb_config)) + + if dynamic_fields: + dynamic_params.append(card_id) + cur.execute(f"UPDATE dynamic_agent_details SET {', '.join(dynamic_fields)} WHERE card_id = %s", dynamic_params) + + if not update_fields and not dynamic_fields: + raise HTTPException(status_code=400, detail='No fields to update') + + conn.commit() + return {'message': 'Dynamic agent updated successfully'} + finally: + pool.putconn(conn) + +@app.delete('/dynamic_agent/{card_id}', tags=["Dynamic agent 管理"]) +def delete_dynamic_agent(card_id: int, current_user: dict = Depends(get_current_user)): + conn = pool.getconn() + try: + with conn.cursor() as cur: + cur.execute('DELETE FROM agent_cards WHERE card_id = %s', (card_id,)) + if cur.rowcount == 0: + raise HTTPException(status_code=404, detail='Agent not found') + # Cascading delete will handle dynamic_agent_details and agent_tools + conn.commit() + return {'message': 'Dynamic agent deleted successfully'} + finally: + pool.putconn(conn) + +@app.get('/dynamic_agents/{phone_number}', tags=["Dynamic agent 管理"]) +def list_dynamic_agents(phone_number: str, current_user: dict = Depends(get_current_user)): + conn = pool.getconn() + try: + with conn.cursor() as cur: + # Join tables to get full details for all agents of type 1 (or all if desired, but user asked for dynamic agent management) + # Let's filter by type=1 for "Dynamic Agents" list, or just return all with left join. + # Usually specific management implies specific type. + query = ''' + SELECT a.*, d.videos, d.kb_id, d.kb_config + FROM agent_cards a + LEFT JOIN dynamic_agent_details d ON a.card_id = d.card_id + WHERE a.phone_number = %s AND a.type = 1 + ORDER BY a.card_id DESC + ''' + cur.execute(query, (phone_number,)) + agents = cur.fetchall() + + result = [] + columns = [desc[0] for desc in cur.description] + + for row in agents: + agent_dict = dict(zip(columns, row)) + + # Fetch tools for each agent + cur.execute(''' + SELECT t.* + FROM tools t + JOIN agent_tools at ON t.tool_id = at.tool_id + WHERE at.agent_card_id = %s + ''', (agent_dict['card_id'],)) + tools = cur.fetchall() + + tool_list = [] + tool_columns = [desc[0] for desc in cur.description] + for t in tools: + td = dict(zip(tool_columns, t)) + if isinstance(td['parameters'], str): + td['parameters'] = json.loads(td['parameters']) + td['created_at'] = str(td['created_at']) + tool_list.append(td) + + agent_dict['tools'] = tool_list + result.append(agent_dict) + + return result + finally: + pool.putconn(conn) + +@app.get('/agent_id/{agent_id}', tags=["Agent管理"]) +def get_agent(agent_id: str, current_user: dict = Depends(get_current_user)): + conn = pool.getconn() + try: + with conn.cursor() as cur: + cur.execute('SELECT * FROM agent_cards WHERE card_id = %s', (agent_id,)) + agent = cur.fetchone() + if not agent: + raise HTTPException(status_code=404, detail='Agent card not found') + + columns = [desc[0] for desc in cur.description] + return dict(zip(columns, agent)) + finally: + pool.putconn(conn) + + +@app.put('/agent_id/{agent_id}', tags=["Agent管理"]) +def update_agent_card_by_id(agent_id: str, agent_card: AgentCardUpdate, current_user: dict = Depends(get_current_user)): + conn = pool.getconn() + try: + with conn.cursor() as cur: + cur.execute('SELECT * FROM agent_cards WHERE card_id = %s', (agent_id,)) + if not cur.fetchone(): + raise HTTPException(status_code=404, detail='Agent card not found') + + # 构建动态更新的SQL语句和参数 + update_fields = [] + params = [] + + if agent_card.card_info is not None: + update_fields.append('card_info = %s') + params.append(agent_card.card_info) + if agent_card.agent_avatar_url is not None: + update_fields.append('agent_avatar_url = %s') + params.append(agent_card.agent_avatar_url) + if agent_card.agent_prompt is not None: + update_fields.append('agent_prompt = %s') + params.append(agent_card.agent_prompt) + if agent_card.agent_name is not None: + update_fields.append('agent_name = %s') + params.append(agent_card.agent_name) + if agent_card.is_publish is not None: + update_fields.append('is_publish = %s') + params.append(agent_card.is_publish) + if agent_card.voice_type is not None: + update_fields.append('voice_type = %s') + params.append(agent_card.voice_type) + if agent_card.temperature is not None: + update_fields.append('temperature = %s') + params.append(agent_card.temperature) + if agent_card.type is not None: + update_fields.append('type = %s') + params.append(agent_card.type) + + if not update_fields: + raise HTTPException(status_code=400, detail='No fields to update') + + params.append(agent_id) + + update_query = f''' + UPDATE agent_cards SET + {', '.join(update_fields)} + WHERE card_id = %s + ''' + + cur.execute(update_query, params) + conn.commit() + return {'message': 'Agent card updated successfully'} + finally: + pool.putconn(conn) + + + + +@app.delete('/agent_id/{agent_id}', tags=["Agent管理"]) +def delete_agent_card_by_id(agent_id: str, current_user: dict = Depends(get_current_user)): + conn = pool.getconn() + try: + with conn.cursor() as cur: + cur.execute('SELECT * FROM agent_cards WHERE card_id = %s', (agent_id,)) + if not cur.fetchone(): + raise HTTPException(status_code=404, detail='Agent card not found') + + cur.execute('DELETE FROM agent_cards WHERE card_id = %s', (agent_id,)) + if cur.rowcount == 0: + raise HTTPException(status_code=404, detail='Agent card not found') + conn.commit() + return {'message': 'Agent card deleted successfully'} + finally: + pool.putconn(conn) + + +# 图片上传目录配置 +#UPLOAD_DIR = "/mnt/server/userImage/call_avator" +UPLOAD_DIR = "./" +if not os.path.exists(UPLOAD_DIR): + os.makedirs(UPLOAD_DIR) + +# 允许的图片类型 +ALLOWED_IMAGE_TYPES = ["image/jpeg", "image/png", "image/gif"] + +@app.post('/upload_image/', tags=["文件上传"]) +async def upload_image(file: UploadFile = File(...), current_user: dict = Depends(get_current_user)): + # 验证文件类型 + if file.content_type not in ALLOWED_IMAGE_TYPES: + raise HTTPException(status_code=400, detail="Only image files are allowed") + + try: + # 生成唯一文件名 + file_ext = os.path.splitext(file.filename)[1] + unique_id = str(uuid.uuid4()) + filename = f"{unique_id}{file_ext}" + file_path = os.path.join(UPLOAD_DIR, filename) + + # 保存文件 + with open(file_path, "wb") as buffer: + buffer.write(await file.read()) + + return {"message": "Image uploaded successfully", "file_id": unique_id, "file_path": file_path} + except Exception as e: + raise HTTPException(status_code=500, detail=f"Error uploading file: {str(e)}") + +@app.put('/user/{phone}', tags=["用户管理"]) +def update_user(phone: str, user_update: UserUpdate, current_user: dict = Depends(get_current_user)): + conn = pool.getconn() + try: + with conn.cursor() as cur: + cur.execute('SELECT * FROM users WHERE phone_number = %s', (phone,)) + if not cur.fetchone(): + raise HTTPException(status_code=404, detail='User not found') + + # 构建动态更新的SQL语句和参数 + update_fields = [] + params = [] + + if user_update.name is not None: + update_fields.append('user_name = %s') + params.append(user_update.name) + if user_update.user_details is not None: + update_fields.append('user_details = %s') + params.append(user_update.user_details) + if user_update.avatar is not None: + update_fields.append('avatar = %s') + params.append(user_update.avatar) + if user_update.email is not None: + update_fields.append('email = %s') + params.append(user_update.email) + if user_update.points is not None: + update_fields.append('points = %s') + params.append(user_update.points) + + if not update_fields: + raise HTTPException(status_code=400, detail='No fields to update') + + params.append(phone) + + update_query = f''' + UPDATE users SET + {', '.join(update_fields)} + WHERE phone_number = %s + ''' + + cur.execute(update_query, params) + conn.commit() + return {'message': 'User updated successfully'} + finally: + pool.putconn(conn) + + +@app.post("/api/send-sms", tags=["短信服务"]) +async def send_sms(sms_request: SMSRequest): + """发送短信API""" + sms = SMS() + print(f"Sending SMS with template_code: {sms_request.template_code}, sign_name: {sms_request.sign_name}") + result = sms.main(self=sms, phone_number=sms_request.phone_number, template_param=sms_request.code, template_code=sms_request.template_code, sign_name=sms_request.sign_name) + + # 保存发送记录到数据库 + conn = pool.getconn() + try: + with conn.cursor() as cur: + status = 'success' if result and result.get('success') else 'failed' + biz_id = result.get('biz_id') if result else None + error_message = result.get('error_message') if result else 'Unknown error' + + cur.execute(""" + INSERT INTO sms_records (phone_number, template_code, template_param, sign_name, status, biz_id, error_message) + VALUES (%s, %s, %s, %s, %s, %s, %s) + """, (sms_request.phone_number, sms_request.template_code, sms_request.code, sms_request.sign_name, status, biz_id, error_message)) + conn.commit() + except Exception as e: + print(f"Error saving SMS record: {e}") + finally: + pool.putconn(conn) + + if not result or not result.get('success'): + return {"status": "failed", "message": result.get('error_message') if result else "发送失败"} + + return {"status": "success", "message": "短信发送请求已处理", "data": result} + +class ConversationCreate(BaseModel): + user_phone: str | None = None + visitor_key: str | None = None + agent_card_id: int + +class MessageCreate(BaseModel): + conversation_id: int + sender: str + content: str + order: int + +@app.post('/conversations/', tags=["对话管理"]) +def create_conversation(conversation: ConversationCreate, current_user: dict = Depends(get_current_user)): + conn = pool.getconn() + try: + with conn.cursor() as cur: + # 验证用户或访客只能存在一个 + if conversation.user_phone and conversation.visitor_key: + raise HTTPException(status_code=400, detail='Cannot specify both user_phone and visitor_key') + if not conversation.user_phone and not conversation.visitor_key: + raise HTTPException(status_code=400, detail='Must specify either user_phone or visitor_key') + + # 验证agent_card_id是否存在 + cur.execute('SELECT * FROM agent_cards WHERE card_id = %s', (conversation.agent_card_id,)) + if not cur.fetchone(): + raise HTTPException(status_code=404, detail='Agent card not found') + + # 验证visitor_key格式是否为有效的UUID + if conversation.visitor_key: + try: + uuid.UUID(conversation.visitor_key) + except ValueError: + raise HTTPException(status_code=400, detail='visitor_key must be a valid UUID format') + + # 创建新对话 + cur.execute(''' + INSERT INTO conversations (user_phone, visitor_key, agent_card_id) + VALUES (%s, %s, %s) + RETURNING conversation_id + ''', (conversation.user_phone, conversation.visitor_key, conversation.agent_card_id)) + + conversation_id = cur.fetchone()[0] + conn.commit() + return {'conversation_id': conversation_id} + finally: + pool.putconn(conn) + +@app.post('/messages/', tags=["对话管理"]) +def create_message(message: MessageCreate, current_user: dict = Depends(get_current_user)): + conn = pool.getconn() + try: + with conn.cursor() as cur: + # 验证对话是否存在 + cur.execute('SELECT * FROM conversations WHERE conversation_id = %s', (message.conversation_id,)) + if not cur.fetchone(): + raise HTTPException(status_code=404, detail='Conversation not found') + + # 验证sender只能是user或agent + if message.sender not in ['user', 'agent']: + raise HTTPException(status_code=400, detail="Sender must be either 'user' or 'agent'") + + # 保存消息 + cur.execute(''' + INSERT INTO messages (conversation_id, sender, content, "order") + VALUES (%s, %s, %s, %s) + RETURNING message_id + ''',(message.conversation_id, message.sender, message.content, message.order)) + + message_id = cur.fetchone()[0] + conn.commit() + return {'message_id': message_id} + finally: + pool.putconn(conn) + +@app.get("/api/sms-records", tags=["短信服务"]) +async def get_all_sms_records(): + """获取所有短信发送记录""" + conn = pool.getconn() + try: + with conn.cursor() as cur: + cur.execute('SELECT * FROM sms_records ORDER BY created_at DESC') + records = cur.fetchall() + + columns = [desc[0] for desc in cur.description] + result = [] + for row in records: + d = dict(zip(columns, row)) + # Convert datetime to str + if 'created_at' in d: + d['created_at'] = str(d['created_at']) + result.append(d) + return {"status": "success", "data": result} + finally: + pool.putconn(conn) + + +# 阿里云OSS相关API +@app.post("/test/", tags=["阿里云OSS"]) +async def test_oss(): + """测试OSS连接""" + return "string" + + +@app.post("/upload", tags=["阿里云OSS"]) +async def upload_file( + file: UploadFile = File(...), + folder: str = "uploads" +): + """ + 上传单个文件到OSS + + Args: + file: 上传的文件 + folder: 存储文件夹(可选) + + Returns: + 文件上传结果 + """ + try: + # 读取文件内容 + file_content = await file.read() + + # 生成对象键 + object_key = oss_service.generate_object_key(file.filename, folder) + + # 上传文件 + result = oss_service.upload_file( + file_content=file_content, + object_key=object_key, + content_type=file.content_type + ) + + return result + except Exception as e: + # 返回错误信息 + return { + "success": False, + "message": f"上传失败: {str(e)}", + "object_key": None, + "file_url": None, + "etag": None, + "request_id": None, + "error_code": "InternalError" + } + + +@app.post("/upload/multiple", tags=["阿里云OSS"]) +async def upload_multiple_files( + files: List[UploadFile] = File(...), + folder: str = "uploads" +): + """ + 上传多个文件到OSS + + Args: + files: 上传的文件列表 + folder: 存储文件夹(可选) + + Returns: + 文件上传结果列表 + """ + results = [] + + for file in files: + try: + # 读取文件内容 + file_content = await file.read() + + # 生成对象键 + object_key = oss_service.generate_object_key(file.filename, folder) + + # 上传文件 + result = oss_service.upload_file( + file_content=file_content, + object_key=object_key, + content_type=file.content_type + ) + + results.append(result) + except Exception as e: + # 添加错误信息 + results.append({ + "success": False, + "message": f"上传失败: {str(e)}", + "object_key": None, + "file_url": None, + "etag": None, + "request_id": None, + "error_code": "InternalError" + }) + + return results + + +@app.get("/files", tags=["阿里云OSS"]) +async def list_files( + prefix: str = "recordings/", + max_keys: int = 100 +): + """ + 列出OSS中的文件 + + Args: + prefix: 对象键前缀(可选) + max_keys: 最大返回数量 + + Returns: + 文件列表 + """ + # 验证max_keys参数 + if max_keys > 1000: + return { + "success": False, + "message": "max_keys不能超过1000", + "files": [], + "count": 0 + } + + # 获取文件列表 + result = oss_service.list_files(prefix=prefix, max_keys=max_keys) + + # 确保返回格式包含message字段 + if "message" not in result: + result["message"] = "获取文件列表成功" + + return result + + +@app.get("/files/{object_key:path}/info", tags=["阿里云OSS"]) +async def get_file_info(object_key: str): + """ + 获取文件信息 + + Args: + object_key: OSS对象键 + + Returns: + 文件信息 + """ + # 获取文件信息 + result = oss_service.get_file_info(object_key) + + # 确保返回格式包含message字段 + if "message" not in result: + if result["success"]: + result["message"] = "获取文件信息成功" + else: + result["message"] = "获取文件信息失败" + + return result + + +@app.delete("/files/{object_key:path}", tags=["阿里云OSS"]) +async def delete_file(object_key: str): + """ + 删除OSS文件 + + Args: + object_key: OSS对象键 + + Returns: + 删除结果 + """ + # 删除文件 + result = oss_service.delete_file(object_key) + + # 确保返回格式包含所有必要字段 + if "error_code" not in result: + result["error_code"] = None + + return result \ No newline at end of file diff --git a/TangledUp-AI_dataBase_fastAPI-main/models.py b/TangledUp-AI_dataBase_fastAPI-main/models.py new file mode 100644 index 0000000..a3d18b1 --- /dev/null +++ b/TangledUp-AI_dataBase_fastAPI-main/models.py @@ -0,0 +1,57 @@ +from pydantic import BaseModel +from typing import Optional, List +from datetime import datetime + + +class FileUploadResponse(BaseModel): + """文件上传响应模型""" + success: bool + message: str + object_key: Optional[str] = None + file_url: Optional[str] = None + etag: Optional[str] = None + request_id: Optional[str] = None + error_code: Optional[str] = None + + +class FileInfo(BaseModel): + """文件信息模型""" + success: bool + object_key: Optional[str] = None + size: Optional[int] = None + last_modified: Optional[datetime] = None + content_type: Optional[str] = None + etag: Optional[str] = None + message: Optional[str] = None + + +class FileListItem(BaseModel): + """文件列表项模型""" + key: str + size: int + last_modified: Optional[str] = None + etag: str + + +class FileListResponse(BaseModel): + """文件列表响应模型""" + success: bool + files: Optional[List[FileListItem]] = None + count: Optional[int] = None + message: Optional[str] = None + + +class DeleteFileResponse(BaseModel): + """删除文件响应模型""" + success: bool + message: str + object_key: Optional[str] = None + request_id: Optional[str] = None + error_code: Optional[str] = None + + +class HealthCheck(BaseModel): + """健康检查响应模型""" + status: str + message: str + timestamp: datetime \ No newline at end of file diff --git a/TangledUp-AI_dataBase_fastAPI-main/oss_service.py b/TangledUp-AI_dataBase_fastAPI-main/oss_service.py new file mode 100644 index 0000000..3df33d7 --- /dev/null +++ b/TangledUp-AI_dataBase_fastAPI-main/oss_service.py @@ -0,0 +1,198 @@ +import os +import uuid +from datetime import datetime +from typing import Optional, BinaryIO +import oss2 +from oss2.exceptions import OssError, NoSuchBucket, NoSuchKey +from config import settings + + +class OSSService: + """阿里云OSS服务类""" + + def __init__(self): + """初始化OSS客户端""" + if not all([settings.oss_access_key_id, settings.oss_access_key_secret, settings.oss_bucket_name]): + raise ValueError("OSS配置不完整,请检查环境变量") + + # 创建认证对象 + self.auth = oss2.Auth(settings.oss_access_key_id, settings.oss_access_key_secret) + + # 创建Bucket对象 + self.bucket = oss2.Bucket(self.auth, settings.oss_endpoint, settings.oss_bucket_name) + + # 验证bucket是否存在 + try: + self.bucket.get_bucket_info() + except NoSuchBucket: + raise ValueError(f"Bucket '{settings.oss_bucket_name}' 不存在") + + def generate_object_key(self, original_filename: str, folder: str = "uploads") -> str: + """生成OSS对象键名""" + # 获取文件扩展名 + _, ext = os.path.splitext(original_filename) + + # 生成唯一文件名 + timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") + unique_id = str(uuid.uuid4())[:8] + filename = f"{timestamp}_{unique_id}{ext}" + + # 返回完整的对象键 + return f"{folder}/{filename}" + + def upload_file(self, file_content: bytes, object_key: str, content_type: str = None) -> dict: + """ + 上传文件到OSS + + Args: + file_content: 文件内容 + object_key: OSS对象键 + content_type: 文件MIME类型 + + Returns: + 包含上传结果的字典 + """ + try: + # 设置文件头信息 + headers = {} + if content_type: + headers['Content-Type'] = content_type + + # 上传文件 + result = self.bucket.put_object(object_key, file_content, headers=headers) + + # 构造文件URL + file_url = f"https://{settings.oss_bucket_name}.{settings.oss_endpoint.replace('http://', '').replace('https://', '')}/{object_key}" + + return { + "success": True, + "message": "文件上传成功", + "object_key": object_key, + "file_url": file_url, + "etag": result.etag, + "request_id": result.request_id + } + + except OssError as e: + return { + "success": False, + "message": f"上传失败: {e}", + "error_code": e.code if hasattr(e, 'code') else 'Unknown' + } + + def delete_file(self, object_key: str) -> dict: + """ + 删除OSS文件 + + Args: + object_key: OSS对象键 + + Returns: + 删除结果字典 + """ + try: + # 首先检查文件是否存在 + try: + self.bucket.head_object(object_key) + except NoSuchKey: + return { + "success": False, + "message": f"文件不存在: {object_key}", + "error_code": "NoSuchKey" + } + + # 文件存在,执行删除操作 + result = self.bucket.delete_object(object_key) + return { + "success": True, + "message": "文件删除成功", + "object_key": object_key, + "request_id": result.request_id + } + except OssError as e: + return { + "success": False, + "message": f"删除失败: {e}", + "error_code": e.code if hasattr(e, 'code') else 'Unknown' + } + + def get_file_info(self, object_key: str) -> dict: + """ + 获取文件信息 + + Args: + object_key: OSS对象键 + + Returns: + 文件信息字典 + """ + try: + head_result = self.bucket.head_object(object_key) + + return { + "success": True, + "object_key": object_key, + "size": head_result.content_length, + "last_modified": head_result.last_modified, + "content_type": head_result.content_type, + "etag": head_result.etag + } + except NoSuchKey: + return { + "success": False, + "message": "文件不存在" + } + except OssError as e: + return { + "success": False, + "message": f"获取文件信息失败: {e}" + } + + def list_files(self, prefix: str = "", max_keys: int = 100) -> dict: + """ + 列出文件 + + Args: + prefix: 对象键前缀 + max_keys: 最大返回数量 + + Returns: + 文件列表字典 + """ + try: + files = [] + for obj in oss2.ObjectIterator(self.bucket, prefix=prefix, max_keys=max_keys): + # 安全地处理 last_modified 字段 + last_modified_str = None + if obj.last_modified: + if hasattr(obj.last_modified, 'isoformat'): + # 如果是 datetime 对象 + last_modified_str = obj.last_modified.isoformat() + elif isinstance(obj.last_modified, (int, float)): + # 如果是时间戳,转换为 datetime 然后格式化 + last_modified_str = datetime.fromtimestamp(obj.last_modified).isoformat() + else: + # 其他情况,尝试转换为字符串 + last_modified_str = str(obj.last_modified) + + files.append({ + "key": obj.key, + "size": obj.size, + "last_modified": last_modified_str, + "etag": obj.etag + }) + + return { + "success": True, + "files": files, + "count": len(files) + } + except OssError as e: + return { + "success": False, + "message": f"列出文件失败: {e}" + } + + +# 全局OSS服务实例 +oss_service = OSSService() \ No newline at end of file diff --git a/TangledUp-AI_dataBase_fastAPI-main/requirements.txt b/TangledUp-AI_dataBase_fastAPI-main/requirements.txt new file mode 100644 index 0000000..af98d9e --- /dev/null +++ b/TangledUp-AI_dataBase_fastAPI-main/requirements.txt @@ -0,0 +1,15 @@ +fastapi +uvicorn +pydantic +psycopg2-binary +python-multipart +alibabacloud_dysmsapi20170525 +alibabacloud_tea_openapi +alibabacloud_darabonba_env +alibabacloud_tea_util +alibabacloud_tea_console +alibabacloud_darabonba_string +alibabacloud_darabonba_time +oss2==2.18.3 +pydantic-settings==2.9.1 +aiofiles==23.2.0 \ No newline at end of file diff --git a/TangledUp-AI_dataBase_fastAPI-main/save_voice.py b/TangledUp-AI_dataBase_fastAPI-main/save_voice.py new file mode 100644 index 0000000..e361d0d --- /dev/null +++ b/TangledUp-AI_dataBase_fastAPI-main/save_voice.py @@ -0,0 +1,41 @@ +#!/usr/bin/env python3 +""" +阿里云OSS文件上传API服务启动脚本 +""" + +import os +import sys +import uvicorn +from main import app +from config import settings + + +def main(): + """启动应用""" + print("🚀 启动阿里云OSS文件上传API服务...") + print(f"📍 服务地址: http://{settings.app_host}:{settings.app_port}") + print(f"📚 API文档: http://{settings.app_host}:{settings.app_port}/docs") + print(f"🔧 OSS端点: {settings.oss_endpoint}") + print(f"🪣 Bucket: {settings.oss_bucket_name}") + print("-" * 50) + + # 检查必要的环境变量 + if not settings.oss_access_key_id or not settings.oss_access_key_secret: + print("❌ 错误: 请设置OSS访问密钥环境变量:") + print(" export OSS_ACCESS_KEY_ID=your_access_key_id") + print(" export OSS_ACCESS_KEY_SECRET=your_access_key_secret") + print(" export OSS_BUCKET_NAME=your_bucket_name") + sys.exit(1) + + # 启动服务 + uvicorn.run( + app, + host=settings.app_host, + port=settings.app_port, + reload=settings.debug, + log_level="info" + ) + + +if __name__ == "__main__": + main() diff --git a/TangledUp-AI_dataBase_fastAPI-main/sms.py b/TangledUp-AI_dataBase_fastAPI-main/sms.py new file mode 100644 index 0000000..28448ca --- /dev/null +++ b/TangledUp-AI_dataBase_fastAPI-main/sms.py @@ -0,0 +1,221 @@ +# -*- coding: utf-8 -*- +# This file is auto-generated, don't edit it. Thanks. +import sys +import os +import json + +from typing import List + +from alibabacloud_dysmsapi20170525.client import Client as DysmsapiClient +from alibabacloud_tea_openapi import models as open_api_models +from alibabacloud_darabonba_env.client import Client as EnvClient +from alibabacloud_dysmsapi20170525 import models as dysmsapi_models +from alibabacloud_tea_util.client import Client as UtilClient +from alibabacloud_tea_console.client import Client as ConsoleClient +from alibabacloud_darabonba_string.client import Client as StringClient +from alibabacloud_darabonba_time.client import Client as TimeClient +from config import settings + + + + +class SMS: + def __init__(self): + self.AccessKey_ID = "LTAI5tEWmH3robZUGHiWYVnP" + self.AccessKey_Secret = "7LAVepzjXcnEobqlx7WPrvHaFYYEU3" + + @staticmethod + def create_client( + access_key_id: str, + access_key_secret: str, + ) -> DysmsapiClient: + """ + 使用AK&SK初始化账号Client + """ + config = open_api_models.Config() + config.access_key_id = access_key_id + config.access_key_secret = access_key_secret + return DysmsapiClient(config) + + @staticmethod + def main(self, + phone_number: str, + template_param: str, + template_code: str = settings.sms_template_code, + sign_name: str = settings.sms_sign_name, + ) -> dict: + try: + + if self.AccessKey_ID is None or self.AccessKey_Secret is None: + ConsoleClient.log("警告:使用默认的访问密钥,建议通过环境变量设置您自己的访问密钥") + + client = SMS.create_client(self.AccessKey_ID, self.AccessKey_Secret) + + # 设置默认值 + phone_number = phone_number + # sign_name is passed as argument + # template_code is passed as argument + param_code = f"{{\"code\":\"{template_param}\"}}" + + ConsoleClient.log(f"正在尝试发送短信... 模板代码: {template_code}, 签名: {sign_name}") + # 1.发送短信 + send_req = dysmsapi_models.SendSmsRequest( + phone_numbers=phone_number, + sign_name=sign_name, + template_code=template_code, + template_param=param_code + ) + try: + send_resp = client.send_sms(send_req) + code = send_resp.body.code + if not UtilClient.equal_string(code, 'OK'): + error_msg = f'发送失败,错误代码: {code}, 错误信息: {send_resp.body.message}' + ConsoleClient.log(error_msg) + return {"success": False, "error_message": error_msg} + + ConsoleClient.log(f'短信发送成功,业务ID: {send_resp.body.biz_id}') + biz_id = send_resp.body.biz_id + + # 2. 等待 10 秒后查询结果 + ConsoleClient.log("等待10秒后查询发送状态...") + UtilClient.sleep(10000) + + # 3.查询结果 + phone_nums = StringClient.split(phone_number, ',', -1) + details = [] + for phone_num in phone_nums: + query_req = dysmsapi_models.QuerySendDetailsRequest( + phone_number=UtilClient.assert_as_string(phone_num), + biz_id=biz_id, + send_date=TimeClient.format('yyyyMMdd'), + page_size=10, + current_page=1 + ) + query_resp = client.query_send_details(query_req) + dtos = query_resp.body.sms_send_detail_dtos.sms_send_detail_dto + # 打印结果 + for dto in dtos: + detail = { + "phone_num": dto.phone_num, + "send_status": dto.send_status, + "receive_date": dto.receive_date, + "err_code": dto.err_code + } + details.append(detail) + if UtilClient.equal_string(f'{dto.send_status}', '3'): + ConsoleClient.log(f'{dto.phone_num} 发送成功,接收时间: {dto.receive_date}') + elif UtilClient.equal_string(f'{dto.send_status}', '2'): + ConsoleClient.log(f'{dto.phone_num} 发送失败') + else: + ConsoleClient.log(f'{dto.phone_num} 正在发送中...') + + return {"success": True, "biz_id": biz_id, "details": details} + + except Exception as e: + error_msg = str(e) + if "NoPermission" in error_msg or "403" in error_msg: + ConsoleClient.log("权限错误:当前账号没有发送短信的权限") + # ... (keep existing log messages if possible, but for brevity I might shorten) + elif "InvalidAccessKeyId" in error_msg: + ConsoleClient.log("错误:AccessKeyId无效") + elif "SignatureDoesNotMatch" in error_msg: + ConsoleClient.log("错误:签名不匹配") + else: + ConsoleClient.log(f"发生错误: {error_msg}") + return {"success": False, "error_message": error_msg} + except Exception as e: + ConsoleClient.log(f"程序运行错误: {str(e)}") + return {"success": False, "error_message": str(e)} + + @staticmethod + async def main_async(self, + phone_number: str, + template_param: str, + template_code: str = settings.sms_template_code, + sign_name: str = settings.sms_sign_name, + ) -> None: + try: + + + client = SMS.create_client(self.AccessKey_ID, self.AccessKey_Secret) + + # 设置默认值 + phone_number = phone_number + # sign_name is passed as argument + # template_code is passed as argument + template_param = f"{{\"code\":\"{template_param}\"}}" + + # 如果提供了命令行参数,则使用命令行参数 + + + + ConsoleClient.log(f"正在尝试发送短信... 模板代码: {template_code}, 签名: {sign_name}") + # 1.发送短信 + send_req = dysmsapi_models.SendSmsRequest( + phone_numbers=phone_number, + sign_name=sign_name, + template_code=template_code, + template_param=template_param + ) + try: + send_resp = await client.send_sms_async(send_req) + code = send_resp.body.code + if not UtilClient.equal_string(code, 'OK'): + ConsoleClient.log(f'发送失败,错误代码: {code}, 错误信息: {send_resp.body.message}') + return + + ConsoleClient.log(f'短信发送成功,业务ID: {send_resp.body.biz_id}') + biz_id = send_resp.body.biz_id + + # 2. 等待 10 秒后查询结果 + ConsoleClient.log("等待10秒后查询发送状态...") + await UtilClient.sleep_async(10000) + + # 3.查询结果 + phone_nums = StringClient.split(phone_number, ',', -1) + for phone_num in phone_nums: + query_req = dysmsapi_models.QuerySendDetailsRequest( + phone_number=UtilClient.assert_as_string(phone_num), + biz_id=biz_id, + send_date=TimeClient.format('yyyyMMdd'), + page_size=10, + current_page=1 + ) + query_resp = await client.query_send_details_async(query_req) + dtos = query_resp.body.sms_send_detail_dtos.sms_send_detail_dto + # 打印结果 + for dto in dtos: + if UtilClient.equal_string(f'{dto.send_status}', '3'): + ConsoleClient.log(f'{dto.phone_num} 发送成功,接收时间: {dto.receive_date}') + elif UtilClient.equal_string(f'{dto.send_status}', '2'): + ConsoleClient.log(f'{dto.phone_num} 发送失败') + else: + ConsoleClient.log(f'{dto.phone_num} 正在发送中...') + except Exception as e: + error_msg = str(e) + if "NoPermission" in error_msg or "403" in error_msg: + ConsoleClient.log("权限错误:当前账号没有发送短信的权限") + ConsoleClient.log("可能的原因:") + ConsoleClient.log("1. 访问密钥无效或已过期") + ConsoleClient.log("2. 账号未开通短信服务或权限不足") + ConsoleClient.log("3. 如果使用子账号,需要主账号授予相应权限") + ConsoleClient.log("解决方法:") + ConsoleClient.log("1. 登录阿里云控制台,检查账号短信服务开通状态") + ConsoleClient.log("2. 确认访问密钥是否有效") + ConsoleClient.log("3. 如需帮助,请联系阿里云客服") + elif "InvalidAccessKeyId" in error_msg: + ConsoleClient.log("错误:AccessKeyId无效") + ConsoleClient.log("请检查您的AccessKeyId是否正确") + elif "SignatureDoesNotMatch" in error_msg: + ConsoleClient.log("错误:签名不匹配") + ConsoleClient.log("请检查您的AccessKeySecret是否正确") + else: + ConsoleClient.log(f"发生错误: {error_msg}") + except Exception as e: + ConsoleClient.log(f"程序运行错误: {str(e)}") + + +if __name__ == '__main__': + SMS = SMS() + SMS.main(self=SMS, phone_number="18585164448", template_param="6666") + diff --git a/TangledUp-AI_dataBase_fastAPI-main/utils.py b/TangledUp-AI_dataBase_fastAPI-main/utils.py new file mode 100644 index 0000000..18f9832 --- /dev/null +++ b/TangledUp-AI_dataBase_fastAPI-main/utils.py @@ -0,0 +1,105 @@ +import os +import mimetypes +from typing import Tuple +from fastapi import UploadFile, HTTPException +from config import settings + + +def validate_file(file: UploadFile) -> Tuple[bool, str]: + """ + 验证上传的文件 + + Args: + file: FastAPI上传文件对象 + + Returns: + (是否通过验证, 错误信息) + """ + # 检查文件名 + if not file.filename: + return False, "文件名不能为空" + + # 检查文件大小 + if hasattr(file, 'size') and file.size > 1024 * 1024 * 1024: # 1GB = 1024^3 bytes + return False, f"文件大小超过限制 (1GB)" + + + # 检查文件扩展名 + _, ext = os.path.splitext(file.filename.lower()) + if ext not in [e.lower() for e in settings.allowed_extensions]: + return False, f"不支持的文件类型。支持的类型: {', '.join(settings.allowed_extensions)}" + + return True, "" + + +def get_content_type(filename: str) -> str: + """ + 根据文件名获取MIME类型 + + Args: + filename: 文件名 + + Returns: + MIME类型字符串 + """ + content_type, _ = mimetypes.guess_type(filename) + return content_type or "application/octet-stream" + + +def format_file_size(size_bytes: int) -> str: + """ + 格式化文件大小 + + Args: + size_bytes: 文件大小(字节) + + Returns: + 格式化后的文件大小字符串 + """ + if size_bytes == 0: + return "0B" + + size_names = ["B", "KB", "MB", "GB", "TB"] + i = 0 + size = float(size_bytes) + + while size >= 1024.0 and i < len(size_names) - 1: + size /= 1024.0 + i += 1 + + return f"{size:.2f}{size_names[i]}" + + +def extract_object_key_from_url(file_url: str) -> str: + """ + 从文件URL中提取对象键 + + Args: + file_url: 文件URL + + Returns: + 对象键 + """ + # 假设URL格式为: https://bucket.endpoint.com/object_key + try: + return file_url.split('/')[-1] + except: + return "" + + +def sanitize_filename(filename: str) -> str: + """ + 清理文件名,移除特殊字符 + + Args: + filename: 原始文件名 + + Returns: + 清理后的文件名 + """ + # 移除或替换特殊字符 + invalid_chars = '<>:"/\\|?*' + for char in invalid_chars: + filename = filename.replace(char, '_') + + return filename.strip() \ No newline at end of file