发布版本

This commit is contained in:
2025-10-19 13:51:48 +08:00
parent a5c964516a
commit cfa13d5e4e
6 changed files with 72 additions and 114 deletions
+31
View File
@@ -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"]
Binary file not shown.

Before

Width:  |  Height:  |  Size: 709 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 695 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 574 KiB

+32 -92
View File
@@ -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}"
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("<h1 class='main-title'> 自动控制理AI+数智平台</h1>")
gr.HTML("<h1 class='main-title'> 自动控制理AI+数智平台</h1>")
gr.HTML("<p class='subtitle'>✨ 交互式控制系统分析与设计工具 | 时域·频域·根轨迹·AI问答 ✨</p>")
# 项目信息横幅 - 优化对比度和可读性
@@ -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的临时分享
+6 -19
View File
@@ -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