From 485b07c12f6b584e563f9040860a9e1d19eec2a6 Mon Sep 17 00:00:00 2001 From: MT-Fire <798521692@qq.com> Date: Tue, 3 Mar 2026 13:58:13 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=96=B0=E5=A2=9EPython=E6=9C=8D?= =?UTF-8?q?=E5=8A=A1=E5=99=A8=E3=80=82=E5=87=86=E5=A4=87=E9=80=90=E6=AD=A5?= =?UTF-8?q?=E6=9B=BF=E6=8D=A2node=E6=9C=8D=E5=8A=A1=E5=99=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 2 + server_python/README.md | 106 ++++++++ server_python/app.py | 434 +++++++++++++++++++++++++++++++++ server_python/main.py | 343 ++++++++++++++++++++++++++ server_python/requirements.txt | 8 + start_python_server.sh | 27 ++ stop_python_server.sh | 13 + vite.config.ts | 2 +- 8 files changed, 934 insertions(+), 1 deletion(-) create mode 100644 server_python/README.md create mode 100644 server_python/app.py create mode 100644 server_python/main.py create mode 100644 server_python/requirements.txt create mode 100644 start_python_server.sh create mode 100644 stop_python_server.sh diff --git a/.gitignore b/.gitignore index 2ee3483..6801002 100644 --- a/.gitignore +++ b/.gitignore @@ -13,6 +13,8 @@ dist-ssr *.local uploads .env +.venv +__pycache__ # Editor directories and files .vscode/* diff --git a/server_python/README.md b/server_python/README.md new file mode 100644 index 0000000..f2c7671 --- /dev/null +++ b/server_python/README.md @@ -0,0 +1,106 @@ +# Python AI Chat Server + +这是原有Node.js服务器的Python替代版本,使用FastAPI和DashScope Python SDK连接阿里云百炼平台API。 + +## 特性 + +- 基于FastAPI的高性能异步服务器 +- 支持流式和非流式对话 +- 完全兼容前端API端点 +- 支持多模态输入(文本+图像) +- 集成阿里云百炼API +- 文件上传功能 +- 对话历史管理 + +## 安装要求 + +- Python 3.8+ +- pip包管理器 + +## 安装步骤 + +1. **克隆或复制代码** + +2. **安装Python依赖**: + ```bash + cd server_python + pip install -r requirements.txt + ``` + +3. **配置环境变量**: + ```bash + cp .env.example .env + ``` + + 编辑`.env`文件,填入您的阿里云百炼API密钥: + ``` + ALIYUN_API_KEY=your_actual_api_key_here + ``` + +## 启动服务器 + +### 方法一:直接运行 +```bash +python run_server.py +``` + +### 方法二:使用uvicorn +```bash +uvicorn app:app --host 0.0.0.0 --port 8000 --reload +``` + +## API端点 + +服务器提供了与原Node.js版本完全相同的API端点: + +- `POST /api/chat-ui/chat` - 聊天接口(支持流式和非流式) +- `GET /api/chat-ui/models` - 获取模型列表 +- `GET /api/chat-ui/conversations` - 获取所有对话 +- `GET /api/chat-ui/conversations/{id}` - 获取特定对话 +- `POST /api/chat-ui/conversations` - 保存/更新对话 +- `DELETE /api/chat-ui/conversations/{id}` - 删除对话 +- `POST /api/chat-ui/upload` - 文件上传 +- `POST /api/chat-ui/stop` - 停止生成 +- `POST /api/chat-ui/stop/{id}` - 按ID停止生成 +- `GET /health` - 健康检查 + +## 前端配置 + +修改Vite配置(`vite.config.ts`)中的代理目标: + +```typescript +server: { + proxy: { + "/api/chat-ui": { + target: "http://localhost:8000", // 修改为Python服务器端口 + changeOrigin: true, + }, + }, +}, +``` + +## 环境变量 + +- `ALIYUN_API_KEY`: 阿里云百炼API密钥(必填) +- `PORT`: 服务器端口(默认8000) + +## 依赖说明 + +- `fastapi`: 现代高性能web框架 +- `uvicorn`: ASGI服务器 +- `dashscope`: 阿里云百炼SDK +- `python-multipart`: 处理文件上传 +- `python-dotenv`: 环境变量管理 + +## 错误排查 + +1. **API密钥错误**: 确保在`.env`文件中正确设置了`ALIYUN_API_KEY` +2. **端口冲突**: 检查8000端口是否被占用,可以修改`.env`中的`PORT`变量 +3. **依赖问题**: 确保已正确安装所有依赖 + +## 注意事项 + +- Python服务器提供了与Node.js服务器相同的功能和API接口 +- 保留了原有的日志记录机制 +- 对话数据仍存储在内存中,生产环境建议使用数据库 +- 支持与原前端应用无缝集成 \ No newline at end of file diff --git a/server_python/app.py b/server_python/app.py new file mode 100644 index 0000000..715d70f --- /dev/null +++ b/server_python/app.py @@ -0,0 +1,434 @@ +""" +改进版Python FastAPI服务器实现,使用DashScope Python SDK连接阿里云百炼平台API +""" + +import os +import json +import uuid +import asyncio +from datetime import datetime +from typing import Dict, List, Optional, Any +from pathlib import Path +from enum import Enum + +import dashscope +from dashscope import Generation +from dotenv import load_dotenv +from fastapi import FastAPI, HTTPException, File, UploadFile, Request +from fastapi.responses import JSONResponse, StreamingResponse +from pydantic import BaseModel, Field +import uvicorn + +# 加载环境变量 +load_dotenv() + +# 设置 DashScope API 密钥 +api_key = os.getenv("ALIYUN_API_KEY") +if not api_key: + raise ValueError("请在环境变量中设置 ALIYUN_API_KEY") + +dashscope.api_key = api_key + +# 创建 FastAPI 应用 +app = FastAPI(title="AI Chat API Server (Python)", version="2.0.0") + +# 数据模型定义 +class ChatMessage(BaseModel): + role: str + content: str + images: Optional[List[str]] = None + files: Optional[List[str]] = None + +class ChatRequest(BaseModel): + model: str = "qwen-plus" + messages: List[Dict[str, Any]] + stream: bool = True + temperature: Optional[float] = 0.7 + max_tokens: Optional[int] = 2000 + +class ModelInfo(BaseModel): + id: str + name: str + description: str + maxTokens: int + provider: str + +# 模拟数据库 - 实际应用中应使用持久化存储 +conversations_db: Dict[str, dict] = {} + +# 配置上传目录 +upload_dir = Path("uploads") +upload_dir.mkdir(exist_ok=True) + +@app.middleware("http") +async def logging_middleware(request: Request, call_next): + """中间件:记录请求日志""" + start_time = datetime.utcnow() + + # 记录请求信息 + print(f"[INFO] {datetime.now().strftime('%Y-%m-%d %H:%M:%S')} - " + f"HTTP {request.method} {request.url.path} - " + f"IP: {request.client.host if request.client else 'unknown'}") + + response = await call_next(request) + + # 计算处理时间 + process_time = (datetime.utcnow() - start_time).total_seconds() * 1000 + + # 记录响应信息 + print(f"[INFO] {datetime.now().strftime('%Y-%m-%d %H:%M:%S')} - " + f"Response {response.status_code}, Process Time: {process_time:.2f}ms") + + # 在响应头中添加处理时间 + response.headers["X-Process-Time"] = f"{process_time:.2f}ms" + + return response + +@app.get("/health") +async def health_check(): + """健康检查端点""" + return {"status": "healthy", "timestamp": datetime.utcnow().isoformat()} + +@app.post("/api/chat-ui/chat") +async def chat_endpoint(request: Request): + """ + 聊天接口 - 与阿里云百炼API兼容的接口 + 这个端点会接收前端的聊天请求并转发到阿里云百炼API + """ + try: + # 获取请求体数据 + body = await request.json() + + # 检查请求格式并适配 + # 如果是OpenAI兼容格式 (来自streamChat) + if 'messages' in body: + messages = body.get('messages', []) + model = body.get('model', 'qwen-plus') + stream = body.get('stream', True) + temperature = body.get('temperature', 0.7) + max_tokens = body.get('max_tokens', 2000) + else: + # 否则是前端简化格式 (来自chat函数) + # 需要将其转换为OpenAI兼容格式 + message_text = body.get('message', '') + + # 检查message是否已经是格式化的列表(带图片的情况) + if isinstance(message_text, list): + user_content = message_text + else: + # 如果是字符串,转换为标准格式 + user_content = [{"type": "text", "text": message_text}] + + messages = [ + {"role": "system", "content": body.get('systemPrompt', '你是一个支持视觉理解的助手。')}, + {"role": "user", "content": user_content} + ] + model = body.get('model', 'qwen-plus') + stream = body.get('stream', False) # 默认为非流式 + temperature = body.get('temperature', 0.7) + max_tokens = body.get('maxTokens', 2000) + + if stream: + # 流式响应 + async def event_generator(): + try: + responses = Generation.call( + model=model, + messages=messages, + stream=True, + max_tokens=max_tokens, + temperature=temperature + ) + + for idx, response in enumerate(responses): + if response.status_code == 200: + # 检查响应是否包含预期的内容 + # DashScope API的响应结构可能是 output.choices 或 output.text + content = None + + # 尝试从 output.choices 获取内容 + if (hasattr(response, 'output') and + response.output and + hasattr(response.output, 'choices') and + response.output.choices is not None and + len(response.output.choices) > 0 and + 'message' in response.output.choices[0] and + 'content' in response.output.choices[0]['message']): + + content = response.output.choices[0]['message']['content'] + # 否则尝试从 output.text 获取内容(DashScope特定格式) + elif (hasattr(response, 'output') and + response.output and + 'text' in response.output): + + content = response.output.get('text') + + if content: + # 构建 SSE 数据块 + data = { + "id": f"chatcmpl-{uuid.uuid4()}", + "object": "chat.completion.chunk", + "created": int(datetime.utcnow().timestamp()), + "model": model, + "choices": [ + { + "index": 0, + "delta": {"content": content} if content else {}, + "finish_reason": None + } + ] + } + + yield f"data: {json.dumps(data)}\n\n" + else: + # 如果响应中没有内容,跳过 + continue + else: + # 错误处理 + error_data = { + "error": { + "message": f"API Error: {response.code} - {response.message}", + "type": "api_error", + "param": None, + "code": response.code + } + } + yield f"data: {json.dumps(error_data)}\n\n" + break + + # 发送结束信号 + finish_data = { + "id": f"chatcmpl-{uuid.uuid4()}", + "object": "chat.completion.chunk", + "created": int(datetime.utcnow().timestamp()), + "model": model, + "choices": [ + { + "index": 0, + "delta": {}, + "finish_reason": "stop" + } + ] + } + yield f"data: {json.dumps(finish_data)}\n\n" + yield "data: [DONE]\n\n" + + except Exception as e: + error_data = { + "error": { + "message": str(e), + "type": "server_error" + } + } + yield f"data: {json.dumps(error_data)}\n\n" + + return StreamingResponse(event_generator(), media_type="text/event-stream") + else: + # 非流式响应 + response = Generation.call( + model=model, + messages=messages, + stream=False, + max_tokens=max_tokens, + temperature=temperature + ) + + if response.status_code == 200: + # 检查响应是否包含预期的内容 + # DashScope API的响应结构可能是 output.choices 或 output.text + content = None + + # 尝试从 output.choices 获取内容 + if (hasattr(response, 'output') and + response.output and + hasattr(response.output, 'choices') and + response.output.choices is not None and + len(response.output.choices) > 0 and + 'message' in response.output.choices[0] and + 'content' in response.output.choices[0]['message']): + + content = response.output.choices[0]['message']['content'] + # 否则尝试从 output.text 获取内容(DashScope特定格式) + elif (hasattr(response, 'output') and + response.output and + 'text' in response.output): + + content = response.output.get('text') + + if content: + # 构建前端期望的响应格式 + chat_response = { + "id": str(uuid.uuid4()), + "conversationId": body.get('conversationId', str(uuid.uuid4())), + "content": content, + "model": model, + "createdAt": int(datetime.utcnow().timestamp()) + } + + if hasattr(response, 'usage') and response.usage: + chat_response["usage"] = { + "promptTokens": response.usage.input_tokens, + "completionTokens": response.usage.output_tokens, + "totalTokens": response.usage.total_tokens + } + + return JSONResponse(content=chat_response) + else: + raise HTTPException( + status_code=500, + detail="API Response does not contain expected content" + ) + else: + raise HTTPException( + status_code=500, + detail=f"API Error: {response.code} - {response.message}" + ) + + except Exception as e: + print(f"[ERROR] Error in chat endpoint: {str(e)}") + raise HTTPException(status_code=500, detail=str(e)) + +@app.get("/api/chat-ui/models") +async def get_models(): + """获取模型列表""" + models = [ + ModelInfo( + id="qwen-max", + name="通义千问 Max", + description="最强大的模型", + maxTokens=8192, + provider="Aliyun" + ), + ModelInfo( + id="qwen-plus", + name="通义千问 Plus", + description="能力均衡", + maxTokens=8192, + provider="Aliyun" + ), + ModelInfo( + id="qwen-turbo", + name="通义千问 Turbo", + description="速度更快、成本更低", + maxTokens=8192, + provider="Aliyun" + ) + ] + return [model.dict() for model in models] + +@app.get("/api/chat-ui/conversations") +async def get_conversations(): + """获取所有对话""" + return list(conversations_db.values()) + +@app.get("/api/chat-ui/conversations/{conversation_id}") +async def get_conversation(conversation_id: str): + """获取特定对话""" + conversation = conversations_db.get(conversation_id) + if not conversation: + raise HTTPException(status_code=404, detail="对话不存在") + return conversation + +@app.post("/api/chat-ui/conversations") +async def save_conversation(request: Request): + """保存或更新对话""" + try: + data = await request.json() + conversation_id = data.get('id') or str(uuid.uuid4()) + + conversation = { + "id": conversation_id, + "title": data.get('title', '新对话'), + "messages": data.get('messages', []), + "updatedAt": datetime.utcnow().isoformat(), + "createdAt": data.get('createdAt', datetime.utcnow().isoformat()) + } + + conversations_db[conversation_id] = conversation + return conversation + + except Exception as e: + print(f"[ERROR] Error saving conversation: {str(e)}") + raise HTTPException(status_code=500, detail=str(e)) + +@app.delete("/api/chat-ui/conversations/{conversation_id}") +async def delete_conversation(conversation_id: str): + """删除对话""" + if conversation_id in conversations_db: + del conversations_db[conversation_id] + return {"success": True, "message": "删除成功"} + else: + raise HTTPException(status_code=404, detail="对话不存在") + +@app.post("/api/chat-ui/upload") +async def upload_file(file: UploadFile = File(...)): + """文件上传接口""" + try: + # 检查文件类型 + allowed_types = ['image/jpeg', 'image/png', 'image/gif', 'image/webp', 'text/plain', 'application/pdf'] + if file.content_type not in allowed_types: + raise HTTPException(status_code=400, detail=f"不支持的文件类型: {file.content_type}") + + # 生成唯一文件名 + file_extension = Path(file.filename).suffix.lower() + unique_filename = f"{int(datetime.utcnow().timestamp())}_{uuid.uuid4()}{file_extension}" + file_path = upload_dir / unique_filename + + # 保存文件 + with open(file_path, "wb") as f: + content = await file.read() + f.write(content) + + # 返回文件信息 + file_url = f"http://localhost:8000/uploads/{unique_filename}" + result = { + "url": file_url, + "name": file.filename, + "size": len(content), + "mimeType": file.content_type + } + + print(f"[INFO] File uploaded: {result}") + return result + + except Exception as e: + print(f"[ERROR] Upload error: {str(e)}") + raise HTTPException(status_code=500, detail=f"上传失败: {str(e)}") + +@app.get("/uploads/{filename}") +async def serve_upload(filename: str): + """提供上传文件的访问""" + file_path = upload_dir / filename + if not file_path.exists(): + raise HTTPException(status_code=404, detail="文件不存在") + + from fastapi.responses import FileResponse + return FileResponse(str(file_path)) + +@app.post("/api/chat-ui/stop") +async def stop_generation(): + """停止生成接口""" + # 在实际实现中,这里可能需要维护正在运行的任务ID列表 + # 目前只是返回成功消息 + return {"success": True, "message": "已发出停止指令"} + +@app.post("/api/chat-ui/stop/{message_id}") +async def stop_generation_by_id(message_id: str): + """根据消息ID停止生成""" + return {"success": True, "message": "已发出停止指令,消息ID: " + message_id} + +if __name__ == "__main__": + port = int(os.getenv("PORT", 8000)) + print("="*50) + print(f"Python AI Chat Server 启动中...") + print(f"监听端口: {port}") + print(f"API Key 状态: {'已配置' if api_key else '未配置'}") + print("="*50) + + if not api_key: + print("警告: 未在环境变量中检测到 ALIYUN_API_KEY!") + print("请在 .env 文件中添加您的百炼 API Key。") + else: + print("API Key 已检测到。") + + uvicorn.run(app, host="0.0.0.0", port=port) \ No newline at end of file diff --git a/server_python/main.py b/server_python/main.py new file mode 100644 index 0000000..ce10e63 --- /dev/null +++ b/server_python/main.py @@ -0,0 +1,343 @@ +""" +Python Flask/FastAPI 服务器实现,用于替代 Node.js 服务器 +使用 DashScope Python SDK 连接阿里云百炼平台 API +""" +import os +import json +import uuid +import asyncio +from datetime import datetime +from typing import Dict, List, Optional +from pathlib import Path + +import dashscope +from dashscope import Generation +from dotenv import load_dotenv +from fastapi import FastAPI, HTTPException, File, UploadFile, Form +from fastapi.responses import JSONResponse, StreamingResponse +from pydantic import BaseModel, Field +import uvicorn + +# 加载环境变量 +load_dotenv() + +# 设置 DashScope API 密钥 +dashscope.api_key = os.getenv("ALIYUN_API_KEY") + +# 创建 FastAPI 应用 +app = FastAPI(title="AI Chat API Server", version="1.0.0") + +# 数据模型定义 +class ChatMessage(BaseModel): + role: str + content: str + images: Optional[List[str]] = None + files: Optional[List[str]] = None + +class ChatRequest(BaseModel): + conversationId: Optional[str] = None + message: str + images: Optional[List[str]] = None + files: Optional[List[str]] = None + model: Optional[str] = "qwen-plus" + temperature: Optional[float] = 0.7 + maxTokens: Optional[int] = 2000 + systemPrompt: Optional[str] = "你是一个支持视觉理解的助手。" + stream: Optional[bool] = True + # 扩展选项 + deepSearch: Optional[bool] = False + webSearch: Optional[bool] = False + deepThinking: Optional[bool] = False + +class ModelInfo(BaseModel): + id: str + name: str + description: str + maxTokens: int + provider: str + +# 模拟数据库 - 实际应用中应使用持久化存储 +conversations_db: Dict[str, dict] = {} + +# 配置上传目录 +upload_dir = Path("uploads") +upload_dir.mkdir(exist_ok=True) + +@app.middleware("http") +async def add_process_time_header(request, call_next): + """中间件:记录请求处理时间""" + start_time = datetime.utcnow() + response = await call_next(request) + + # 计算处理时间 + process_time = (datetime.utcnow() - start_time).total_seconds() * 1000 + + # 在响应头中添加处理时间 + response.headers["X-Process-Time"] = f"{process_time:.2f}ms" + + # 记录请求信息 + print(f"HTTP {request.method} {request.url.path} {response.status_code} {process_time:.2f}ms") + + return response + +@app.get("/health") +async def health_check(): + """健康检查端点""" + return {"status": "healthy", "timestamp": datetime.utcnow().isoformat()} + +@app.post("/api/chat-ui/chat") +async def chat_endpoint(request: ChatRequest): + """聊天接口 - 处理普通请求""" + try: + # 构建消息数组,考虑是否包含图片 + user_content = [] + + # 添加用户消息文本 + user_content.append({"type": "text", "text": request.message}) + + # 如果有图片,则添加到内容中 + if request.images and len(request.images) > 0: + for image_url in request.images: + user_content.append({ + "type": "image_url", + "image_url": image_url + }) + + # 构建请求给百炼的消息列表 + messages = [ + {"role": "system", "content": request.systemPrompt}, + {"role": "user", "content": user_content} + ] + + # 调用 DashScope API + response = Generation.call( + model=request.model, + messages=messages, + stream=False, # 非流式响应 + max_tokens=request.maxTokens, + temperature=request.temperature + ) + + if response.status_code == 200: + content = response.output.choices[0]['message']['content'] + + # 构建响应 + result = { + "id": str(uuid.uuid4()), + "conversationId": request.conversationId or str(uuid.uuid4()), + "content": content, + "model": request.model, + "createdAt": int(datetime.utcnow().timestamp()) + } + + if hasattr(response, 'usage'): + result["usage"] = { + "promptTokens": response.usage.input_tokens, + "completionTokens": response.usage.output_tokens, + "totalTokens": response.usage.total_tokens + } + + return JSONResponse(content=result) + else: + raise HTTPException(status_code=500, detail=f"API Error: {response.code} - {response.message}") + + except Exception as e: + print(f"Error in chat endpoint: {str(e)}") + raise HTTPException(status_code=500, detail=str(e)) + +@app.post("/api/chat-ui/chat/stream") +async def chat_stream_endpoint(request: ChatRequest): + """流式聊天接口 - 处理流式请求""" + async def event_generator(): + try: + # 构建消息数组,考虑是否包含图片 + user_content = [] + + # 添加用户消息文本 + user_content.append({"type": "text", "text": request.message}) + + # 如果有图片,则添加到内容中 + if request.images and len(request.images) > 0: + for image_url in request.images: + user_content.append({ + "type": "image_url", + "image_url": image_url + }) + + # 构建请求给百炼的消息列表 + messages = [ + {"role": "system", "content": request.systemPrompt}, + {"role": "user", "content": user_content} + ] + + # 调用 DashScope API(流式) + responses = Generation.call( + model=request.model, + messages=messages, + stream=True, # 流式响应 + max_tokens=request.maxTokens, + temperature=request.temperature + ) + + for response in responses: + if response.status_code == 200: + content = response.output.choices[0]['message']['content'] + + if content: + # 发送流式数据 + data = { + "choices": [ + { + "delta": {"content": content}, + "index": 0, + "finish_reason": None + } + ] + } + + yield f"data: {json.dumps(data)}\n\n" + else: + error_data = { + "error": { + "message": f"API Error: {response.code} - {response.message}", + "type": "api_error", + "param": None, + "code": response.code + } + } + yield f"data: {json.dumps(error_data)}\n\n" + break + + # 发送结束信号 + yield "data: [DONE]\n\n" + + except Exception as e: + error_data = { + "error": { + "message": str(e), + "type": "server_error" + } + } + yield f"data: {json.dumps(error_data)}\n\n" + + return StreamingResponse(event_generator(), media_type="text/event-stream") + +@app.get("/api/chat-ui/models") +async def get_models(): + """获取模型列表""" + models = [ + ModelInfo( + id="qwen-max", + name="通义千问 Max", + description="最强大的模型", + maxTokens=8192, + provider="Aliyun" + ), + ModelInfo( + id="qwen-plus", + name="通义千问 Plus", + description="能力均衡", + maxTokens=8192, + provider="Aliyun" + ) + ] + return [model.dict() for model in models] + +@app.get("/api/chat-ui/conversations") +async def get_conversations(): + """获取所有对话""" + return list(conversations_db.values()) + +@app.get("/api/chat-ui/conversations/{conversation_id}") +async def get_conversation(conversation_id: str): + """获取特定对话""" + conversation = conversations_db.get(conversation_id) + if not conversation: + raise HTTPException(status_code=404, detail="对话不存在") + return conversation + +@app.post("/api/chat-ui/conversations") +async def save_conversation( + id: str = Form(None), + title: str = Form(...), + messages: str = Form(...) +): + """保存或更新对话""" + # 解析 messages JSON 字符串 + try: + parsed_messages = json.loads(messages) + except json.JSONDecodeError: + raise HTTPException(status_code=400, detail="Invalid messages JSON") + + conversation_id = id or str(uuid.uuid4()) + conversation = { + "id": conversation_id, + "title": title, + "messages": parsed_messages, + "updatedAt": datetime.utcnow().isoformat() + } + + conversations_db[conversation_id] = conversation + return conversation + +@app.delete("/api/chat-ui/conversations/{conversation_id}") +async def delete_conversation(conversation_id: str): + """删除对话""" + if conversation_id in conversations_db: + del conversations_db[conversation_id] + return {"success": True, "message": "删除成功"} + else: + raise HTTPException(status_code=404, detail="对话不存在") + +@app.post("/api/chat-ui/upload") +async def upload_file(file: UploadFile = File(...)): + """文件上传接口""" + try: + # 生成唯一文件名 + file_extension = Path(file.filename).suffix + unique_filename = f"{int(datetime.utcnow().timestamp())}-{uuid.uuid4()}{file_extension}" + file_path = upload_dir / unique_filename + + # 保存文件 + with open(file_path, "wb") as f: + content = await file.read() + f.write(content) + + # 返回文件信息 + file_url = f"http://localhost:8000/uploads/{unique_filename}" + return { + "url": file_url, + "name": file.filename, + "size": len(content), + "mimeType": file.content_type + } + + except Exception as e: + print(f"Upload error: {str(e)}") + raise HTTPException(status_code=500, detail=f"上传失败: {str(e)}") + +@app.get("/uploads/{filename}") +async def serve_upload(filename: str): + """提供上传文件的访问""" + file_path = upload_dir / filename + if not file_path.exists(): + raise HTTPException(status_code=404, detail="文件不存在") + + from fastapi.responses import FileResponse + return FileResponse(file_path) + +@app.post("/api/chat-ui/stop") +async def stop_generation(): + """停止生成接口""" + # 在实际实现中,这里可能需要维护正在运行的任务ID列表 + # 目前只是返回成功消息 + return {"success": True, "message": "已发出停止指令"} + +@app.post("/api/chat-ui/stop/{message_id}") +async def stop_generation_by_id(message_id: str): + """根据消息ID停止生成""" + return {"success": True, "message": "已发出停止指令"} + +if __name__ == "__main__": + port = int(os.getenv("PORT", 8000)) + uvicorn.run(app, host="0.0.0.0", port=port) \ No newline at end of file diff --git a/server_python/requirements.txt b/server_python/requirements.txt new file mode 100644 index 0000000..d311ae3 --- /dev/null +++ b/server_python/requirements.txt @@ -0,0 +1,8 @@ +fastapi==0.115.4 +uvicorn==0.32.0 +dashscope==1.20.12 +python-multipart==0.0.18 +python-dotenv==1.0.1 +aiofiles==24.1.0 +pydantic==2.9.2 +typing-extensions==4.12.2 \ No newline at end of file diff --git a/start_python_server.sh b/start_python_server.sh new file mode 100644 index 0000000..ba180b0 --- /dev/null +++ b/start_python_server.sh @@ -0,0 +1,27 @@ +#!/bin/bash +# 启动Python服务器的增强脚本 + +echo "启动Python AI Chat服务器..." + +# 检查是否有服务器已经在8000端口运行 +if lsof -Pi :8000 -sTCP:LISTEN -t >/dev/null; then + echo "错误: 端口8000已被占用。请先停止占用该端口的进程。" + exit 1 +fi + +# 切换到服务器目录 +cd /home/mt/project/ai-chat-ui/server_python + +# 检查虚拟环境是否存在 +if [ ! -d ".venv" ]; then + echo "错误: 虚拟环境不存在。请先创建虚拟环境:" + echo "python3 -m venv .venv" + echo "source .venv/bin/activate" + echo "pip install -r requirements.txt" + exit 1 +fi + +echo "虚拟环境已找到,正在激活..." + +# 激活虚拟环境并启动服务器 +source .venv/bin/activate && python3 app.py \ No newline at end of file diff --git a/stop_python_server.sh b/stop_python_server.sh new file mode 100644 index 0000000..be33f9f --- /dev/null +++ b/stop_python_server.sh @@ -0,0 +1,13 @@ +#!/bin/bash +# 停止Python服务器的脚本 + +echo "正在停止Python AI Chat服务器..." + +# 查找并终止在8000端口运行的Python进程 +PID=$(lsof -t -i:8000) +if [ -n "$PID" ]; then + kill $PID + echo "服务器进程 (PID: $PID) 已停止" +else + echo "在端口8000上没有找到运行的服务器" +fi \ No newline at end of file diff --git a/vite.config.ts b/vite.config.ts index 3139cd7..af95ead 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -18,7 +18,7 @@ export default defineConfig({ host: "0.0.0.0", proxy: { "/api/chat-ui": { - target: "http://localhost:3000", + target: "http://localhost:8000", // Python服务器端口 changeOrigin: true, }, },