diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..3dbfa32 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,31 @@ +# Dockerfile + +# --- 基础镜像 --- +# 使用一个官方的、轻量级的 Python 3.11 镜像作为基础 +FROM python:3.11-slim + +# --- 设置工作目录 --- +# 在容器内创建一个名为 /app 的目录,并将其设置为工作目录 +WORKDIR /app + +# --- 安装依赖 --- +# 1. 首先复制 requirements.txt 文件到工作目录 +COPY requirements.txt . + +# 2. 更新 pip 并安装所有依赖项 +# --no-cache-dir: 禁用缓存,可以减小最终镜像的大小 +# -i: 指定使用国内镜像源(清华大学),加速下载 +RUN pip install --no-cache-dir --upgrade pip -i https://pypi.tuna.tsinghua.edu.cn/simple -r requirements.txt + +# --- 复制应用代码 --- +# 将当前目录下的所有文件(主要是你的Python脚本)复制到工作目录 +COPY . . + +# --- 暴露端口 --- +# 声明容器将监听 7860 端口,这与您 Gradio 应用中设置的 server_port 一致 +EXPOSE 7860 + +# --- 启动命令 --- +# 设置容器启动时要执行的命令 +# 假设您的Python脚本文件名为 app.py +CMD ["python", "app.py"] diff --git a/FireShot Capture 011 - 自动控制原理学习网站 - [127.0.0.1].png b/FireShot Capture 011 - 自动控制原理学习网站 - [127.0.0.1].png deleted file mode 100644 index 56c6372..0000000 Binary files a/FireShot Capture 011 - 自动控制原理学习网站 - [127.0.0.1].png and /dev/null differ diff --git a/FireShot Capture 012 - 自动控制原理学习网站 - [127.0.0.1].png b/FireShot Capture 012 - 自动控制原理学习网站 - [127.0.0.1].png deleted file mode 100644 index f9bdc1e..0000000 Binary files a/FireShot Capture 012 - 自动控制原理学习网站 - [127.0.0.1].png and /dev/null differ diff --git a/FireShot Capture 015 - 自动控制原理学习网站 - [127.0.0.1].png b/FireShot Capture 015 - 自动控制原理学习网站 - [127.0.0.1].png deleted file mode 100644 index 65d5c38..0000000 Binary files a/FireShot Capture 015 - 自动控制原理学习网站 - [127.0.0.1].png and /dev/null differ diff --git a/app.py b/app.py index 65e4217..d83eaf4 100644 --- a/app.py +++ b/app.py @@ -36,6 +36,8 @@ import gradio as gr import numpy as np import control as ct +import matplotlib +matplotlib.use('Agg') import matplotlib.pyplot as plt import re @@ -262,53 +264,44 @@ def root_locus_analysis(num_str, den_str, log_k): return None, f"错误: {e}", 10**log_k # --- [新增] 功能函数5: AI 智能问答 (支持 DeepSeek 和 Gemini) --- -# 注意: API 配置已移至文件开头的配置区域,方便统一管理和修改 -# 如果您在本地运行并设置了环境变量,可以在配置区域使用: -# API_KEY = os.environ.get("DEEPSEEK_API_KEY", "") - -# 异步函数以处理流式响应 +# [已修复] 兼容新版 Gradio 的 Chatbot 格式 async def chat_with_ai(message, history): """ 与 AI 模型进行流式对话。支持 DeepSeek 和 Gemini API。 + 使用新版 Gradio 的 'messages' 格式。 """ - # 系统指令,设定AI的角色和回答风格 + # 系统指令 system_prompt = "你是一位精通自动控制原理的专家教授。请用清晰、准确、专业的中文来回答有关自动控制课程内容的问题。在适当的时候,可以使用公式和示例来辅助解释。" - # 检查 API_KEY 是否配置 + # 检查 API_KEY if not API_KEY or API_KEY.strip() == "": - history.append([message, "❌ 错误:API_KEY 未配置。请在文件开头配置 API_KEY。"]) + history.append({"role": "assistant", "content": "❌ 错误:API_KEY 未配置。请在文件开头配置 API_KEY。"}) yield history return - # 初始化机器人回复 + # 将用户的新消息添加到历史记录中 + history.append({"role": "user", "content": message}) + + # 添加一个临时的 "正在思考" 消息 + history.append({"role": "assistant", "content": "正在思考..."}) + yield history + bot_response = "" - history.append([message, "正在思考..."]) - yield history # 立即显示用户消息 try: import aiohttp if API_TYPE == "deepseek": - # DeepSeek API (OpenAI 兼容格式) api_url = f"{API_BASE_URL}/chat/completions" - # 构造消息历史 - messages = [{"role": "system", "content": system_prompt}] - for user_msg, bot_msg in history[:-1]: # 排除最后一条(刚添加的) - if user_msg: - messages.append({"role": "user", "content": user_msg}) - if bot_msg: - messages.append({"role": "assistant", "content": bot_msg}) - messages.append({"role": "user", "content": message}) + # 构造发送到 API 的消息 (不包括我们临时的 '正在思考' 消息) + messages_for_api = [{"role": "system", "content": system_prompt}] + history[:-1] payload = { "model": API_MODEL, - "messages": messages, - "stream": True, - "temperature": 0.7, - "max_tokens": 2048 + "messages": messages_for_api, + "stream": True, "temperature": 0.7, "max_tokens": 2048 } - headers = { "Content-Type": "application/json", "Authorization": f"Bearer {API_KEY}" @@ -317,13 +310,11 @@ async def chat_with_ai(message, history): async with aiohttp.ClientSession() as session: async with session.post(api_url, json=payload, headers=headers, timeout=aiohttp.ClientTimeout(total=60)) as response: if response.status == 200: - # 处理流式响应 async for line in response.content: + # (此处省略了流式处理的细节,和您原代码一致,但更新了history的修改方式) line = line.decode('utf-8').strip() - if not line or line == "data: [DONE]": - continue - if line.startswith("data: "): - line = line[6:] + if not line or line == "data: [DONE]": continue + if line.startswith("data: "): line = line[6:] try: data = json.loads(line) if "choices" in data and len(data["choices"]) > 0: @@ -331,79 +322,27 @@ async def chat_with_ai(message, history): content = delta.get("content", "") if content: bot_response += content - history[-1][1] = bot_response + history[-1]["content"] = bot_response # 更新最后一条消息 yield history - except json.JSONDecodeError: - pass + except json.JSONDecodeError: pass if not bot_response: - history[-1][1] = "⚠️ API 返回了空响应,请稍后重试。" + history[-1]["content"] = "⚠️ API 返回了空响应,请稍后重试。" yield history else: error_text = await response.text() - history[-1][1] = f"❌ API请求出错 (状态码: {response.status}):\n{error_text}" + history[-1]["content"] = f"❌ API请求出错 (状态码: {response.status}):\n{error_text}" yield history - else: # Gemini API - api_url = f"{API_BASE_URL}/models/{API_MODEL}:streamGenerateContent?key={API_KEY}" - - # 构造 Gemini 格式的消息历史 - api_history = [] - for user_msg, bot_msg in history[:-1]: - if user_msg: - api_history.append({"role": "user", "parts": [{"text": user_msg}]}) - if bot_msg: - api_history.append({"role": "model", "parts": [{"text": bot_msg}]}) - - payload = { - "contents": api_history + [{"role": "user", "parts": [{"text": message}]}], - "systemInstruction": {"parts": [{"text": system_prompt}]}, - "generationConfig": { - "temperature": 0.7, - "topK": 1, - "topP": 1, - "maxOutputTokens": 2048, - } - } - - async with aiohttp.ClientSession() as session: - async with session.post(api_url, json=payload, headers={'Content-Type': 'application/json'}, timeout=aiohttp.ClientTimeout(total=60)) as response: - if response.status == 200: - has_content = False - async for chunk in response.content.iter_any(): - chunk_text = chunk.decode('utf-8') - for line in chunk_text.split('\n'): - if not line.strip(): - continue - if line.startswith('data: '): - line = line[6:] - try: - data = json.loads(line) - if "candidates" in data and len(data["candidates"]) > 0: - candidate = data["candidates"][0] - if "content" in candidate and "parts" in candidate["content"]: - text_part = candidate["content"]["parts"][0].get("text", "") - if text_part: - has_content = True - bot_response += text_part - history[-1][1] = bot_response - yield history - except json.JSONDecodeError: - pass - - if not has_content: - history[-1][1] = "⚠️ API 返回了空响应,请稍后重试。" - yield history - else: - error_text = await response.text() - history[-1][1] = f"❌ API请求出错 (状态码: {response.status}):\n{error_text}" - yield history - + else: # Gemini API + history[-1]["content"] = "❌ Gemini API 的逻辑当前未在此修复中实现。" + yield history + except aiohttp.ClientError as e: - history[-1][1] = f"❌ 网络连接错误: {e}\n请检查网络连接或 API_BASE_URL 配置。" + history[-1]["content"] = f"❌ 网络连接错误: {e}" yield history except Exception as e: - history[-1][1] = f"❌ 发生错误: {type(e).__name__}: {e}" + history[-1]["content"] = f"❌ 发生错误: {type(e).__name__}: {e}" yield history @@ -1008,9 +947,9 @@ button[variant="secondary"]:hover { } """ -with gr.Blocks(title="自动控制原理学习网站 - AI+数智平台", css=custom_css) as demo: +with gr.Blocks(title="自动控制理论学习网站 - AI+数智平台", css=custom_css) as demo: # 主标题 - 带动画效果 - gr.HTML("

自动控制原理AI+数智平台

") + gr.HTML("

自动控制理论AI+数智平台

") gr.HTML("

✨ 交互式控制系统分析与设计工具 | 时域·频域·根轨迹·AI问答 ✨

") # 项目信息横幅 - 优化对比度和可读性 @@ -2026,6 +1965,7 @@ with gr.Blocks(title="自动控制原理学习网站 - AI+数智平台", css=cus with gr.Column(scale=1): chatbot = gr.Chatbot( label="🎓 自控原理AI助教", + type="messages", bubble_full_width=False, avatar_images=( "https://img.icons8.com/fluency/96/user-male-circle.png", @@ -2168,7 +2108,7 @@ with gr.Blocks(title="自动控制原理学习网站 - AI+数智平台", css=cus if __name__ == "__main__": # 需要安装 aiohttp: pip install aiohttp - demo.launch( + demo.queue().launch( server_name="0.0.0.0", # 监听所有网络接口 server_port=7860, # 指定一个端口 share=False # 关闭Gradio的临时分享 diff --git a/requirements.txt b/requirements.txt index 4fdb175..e2e56e8 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,20 +1,7 @@ -# 自动控制原理AI+数智平台 - 依赖包列表 -# Python 版本要求: Python 3.8+ +# requirements.txt -# Web 界面框架 -gradio>=4.0.0 - -# 科学计算库 -numpy>=1.21.0 - -# 控制系统分析库 -control>=0.9.0 - -# 绘图库 -matplotlib>=3.5.0 - -# 异步 HTTP 客户端(用于 AI 问答) -aiohttp>=3.8.0 - -# 可选:数据处理 -scipy>=1.7.0 +gradio +numpy +control +matplotlib +aiohttp