发布版本

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

+35 -95
View File
@@ -36,6 +36,8 @@
import gradio as gr import gradio as gr
import numpy as np import numpy as np
import control as ct import control as ct
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt import matplotlib.pyplot as plt
import re import re
@@ -262,53 +264,44 @@ def root_locus_analysis(num_str, den_str, log_k):
return None, f"错误: {e}", 10**log_k return None, f"错误: {e}", 10**log_k
# --- [新增] 功能函数5: AI 智能问答 (支持 DeepSeek 和 Gemini) --- # --- [新增] 功能函数5: AI 智能问答 (支持 DeepSeek 和 Gemini) ---
# 注意: API 配置已移至文件开头的配置区域,方便统一管理和修改 # [已修复] 兼容新版 Gradio 的 Chatbot 格式
# 如果您在本地运行并设置了环境变量,可以在配置区域使用:
# API_KEY = os.environ.get("DEEPSEEK_API_KEY", "")
# 异步函数以处理流式响应
async def chat_with_ai(message, history): async def chat_with_ai(message, history):
""" """
与 AI 模型进行流式对话。支持 DeepSeek 和 Gemini API。 与 AI 模型进行流式对话。支持 DeepSeek 和 Gemini API。
使用新版 Gradio 的 'messages' 格式。
""" """
# 系统指令,设定AI的角色和回答风格 # 系统指令
system_prompt = "你是一位精通自动控制原理的专家教授。请用清晰、准确、专业的中文来回答有关自动控制课程内容的问题。在适当的时候,可以使用公式和示例来辅助解释。" system_prompt = "你是一位精通自动控制原理的专家教授。请用清晰、准确、专业的中文来回答有关自动控制课程内容的问题。在适当的时候,可以使用公式和示例来辅助解释。"
# 检查 API_KEY 是否配置 # 检查 API_KEY
if not API_KEY or API_KEY.strip() == "": 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 yield history
return return
# 初始化机器人回复 # 将用户的新消息添加到历史记录中
history.append({"role": "user", "content": message})
# 添加一个临时的 "正在思考" 消息
history.append({"role": "assistant", "content": "正在思考..."})
yield history
bot_response = "" bot_response = ""
history.append([message, "正在思考..."])
yield history # 立即显示用户消息
try: try:
import aiohttp import aiohttp
if API_TYPE == "deepseek": if API_TYPE == "deepseek":
# DeepSeek API (OpenAI 兼容格式)
api_url = f"{API_BASE_URL}/chat/completions" api_url = f"{API_BASE_URL}/chat/completions"
# 构造消息历史 # 构造发送到 API 的消息 (不包括我们临时的 '正在思考' 消息)
messages = [{"role": "system", "content": system_prompt}] messages_for_api = [{"role": "system", "content": system_prompt}] + history[:-1]
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})
payload = { payload = {
"model": API_MODEL, "model": API_MODEL,
"messages": messages, "messages": messages_for_api,
"stream": True, "stream": True, "temperature": 0.7, "max_tokens": 2048
"temperature": 0.7,
"max_tokens": 2048
} }
headers = { headers = {
"Content-Type": "application/json", "Content-Type": "application/json",
"Authorization": f"Bearer {API_KEY}" "Authorization": f"Bearer {API_KEY}"
@@ -317,13 +310,11 @@ async def chat_with_ai(message, history):
async with aiohttp.ClientSession() as session: async with aiohttp.ClientSession() as session:
async with session.post(api_url, json=payload, headers=headers, timeout=aiohttp.ClientTimeout(total=60)) as response: async with session.post(api_url, json=payload, headers=headers, timeout=aiohttp.ClientTimeout(total=60)) as response:
if response.status == 200: if response.status == 200:
# 处理流式响应
async for line in response.content: async for line in response.content:
# (此处省略了流式处理的细节,和您原代码一致,但更新了history的修改方式)
line = line.decode('utf-8').strip() line = line.decode('utf-8').strip()
if not line or line == "data: [DONE]": if not line or line == "data: [DONE]": continue
continue if line.startswith("data: "): line = line[6:]
if line.startswith("data: "):
line = line[6:]
try: try:
data = json.loads(line) data = json.loads(line)
if "choices" in data and len(data["choices"]) > 0: if "choices" in data and len(data["choices"]) > 0:
@@ -331,79 +322,27 @@ async def chat_with_ai(message, history):
content = delta.get("content", "") content = delta.get("content", "")
if content: if content:
bot_response += content bot_response += content
history[-1][1] = bot_response history[-1]["content"] = bot_response # 更新最后一条消息
yield history yield history
except json.JSONDecodeError: except json.JSONDecodeError: pass
pass
if not bot_response: if not bot_response:
history[-1][1] = "⚠️ API 返回了空响应,请稍后重试。" history[-1]["content"] = "⚠️ API 返回了空响应,请稍后重试。"
yield history yield history
else: else:
error_text = await response.text() 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 yield history
else: # Gemini API else: # Gemini API
api_url = f"{API_BASE_URL}/models/{API_MODEL}:streamGenerateContent?key={API_KEY}" history[-1]["content"] = "❌ Gemini API 的逻辑当前未在此修复中实现。"
yield history
# 构造 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
except aiohttp.ClientError as e: except aiohttp.ClientError as e:
history[-1][1] = f"❌ 网络连接错误: {e}\n请检查网络连接或 API_BASE_URL 配置。" history[-1]["content"] = f"❌ 网络连接错误: {e}"
yield history yield history
except Exception as e: except Exception as e:
history[-1][1] = f"❌ 发生错误: {type(e).__name__}: {e}" history[-1]["content"] = f"❌ 发生错误: {type(e).__name__}: {e}"
yield history 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>") gr.HTML("<p class='subtitle'>✨ 交互式控制系统分析与设计工具 | 时域·频域·根轨迹·AI问答 ✨</p>")
# 项目信息横幅 - 优化对比度和可读性 # 项目信息横幅 - 优化对比度和可读性
@@ -2026,6 +1965,7 @@ with gr.Blocks(title="自动控制原理学习网站 - AI+数智平台", css=cus
with gr.Column(scale=1): with gr.Column(scale=1):
chatbot = gr.Chatbot( chatbot = gr.Chatbot(
label="🎓 自控原理AI助教", label="🎓 自控原理AI助教",
type="messages",
bubble_full_width=False, bubble_full_width=False,
avatar_images=( avatar_images=(
"https://img.icons8.com/fluency/96/user-male-circle.png", "https://img.icons8.com/fluency/96/user-male-circle.png",
@@ -2168,7 +2108,7 @@ with gr.Blocks(title="自动控制原理学习网站 - AI+数智平台", css=cus
if __name__ == "__main__": if __name__ == "__main__":
# 需要安装 aiohttp: pip install aiohttp # 需要安装 aiohttp: pip install aiohttp
demo.launch( demo.queue().launch(
server_name="0.0.0.0", # 监听所有网络接口 server_name="0.0.0.0", # 监听所有网络接口
server_port=7860, # 指定一个端口 server_port=7860, # 指定一个端口
share=False # 关闭Gradio的临时分享 share=False # 关闭Gradio的临时分享
+6 -19
View File
@@ -1,20 +1,7 @@
# 自动控制原理AI+数智平台 - 依赖包列表 # requirements.txt
# Python 版本要求: Python 3.8+
# Web 界面框架 gradio
gradio>=4.0.0 numpy
control
# 科学计算库 matplotlib
numpy>=1.21.0 aiohttp
# 控制系统分析库
control>=0.9.0
# 绘图库
matplotlib>=3.5.0
# 异步 HTTP 客户端(用于 AI 问答)
aiohttp>=3.8.0
# 可选:数据处理
scipy>=1.7.0