发布版本
This commit is contained in:
+31
@@ -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 |
@@ -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("<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
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user