Spaces:
Sleeping
Sleeping
| import os | |
| import json | |
| import sqlite3 | |
| import requests | |
| import random | |
| import time | |
| from datetime import datetime | |
| from flask import Flask, render_template, request, jsonify, g | |
| from werkzeug.exceptions import HTTPException | |
| app = Flask(__name__, template_folder='templates') | |
| app.config['SECRET_KEY'] = os.environ.get('SECRET_KEY', 'dev_key_bio_synthesis') | |
| app.config['MAX_CONTENT_LENGTH'] = 100 * 1024 * 1024 # 100MB | |
| app.config['UPLOAD_FOLDER'] = os.path.join(app.instance_path, 'uploads') | |
| os.makedirs(app.config['UPLOAD_FOLDER'], exist_ok=True) | |
| # Database Setup | |
| def get_db(): | |
| if 'db' not in g: | |
| db_path = os.path.join(app.instance_path, 'bio_synthesis.db') | |
| os.makedirs(app.instance_path, exist_ok=True) | |
| g.db = sqlite3.connect(db_path) | |
| g.db.row_factory = sqlite3.Row | |
| return g.db | |
| def close_db(error): | |
| if hasattr(g, 'db'): | |
| g.db.close() | |
| def init_db(): | |
| with app.app_context(): | |
| db = get_db() | |
| # Experiments Table: Stores the full lifecycle of a bio-experiment | |
| db.execute(''' | |
| CREATE TABLE IF NOT EXISTS experiments ( | |
| id INTEGER PRIMARY KEY AUTOINCREMENT, | |
| name TEXT NOT NULL, | |
| status TEXT DEFAULT 'planned', -- planned, running, completed, failed | |
| target_molecule TEXT, | |
| protocol_json TEXT, -- JSON: Steps, Reagents, Conditions | |
| sensor_log_json TEXT, -- JSON: Time-series data (Temp, pH, OD600) | |
| ai_analysis_json TEXT, -- JSON: AI optimization suggestions | |
| created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP | |
| ) | |
| ''') | |
| # Knowledge Base: Stored protocols/assets | |
| db.execute(''' | |
| CREATE TABLE IF NOT EXISTS assets ( | |
| id INTEGER PRIMARY KEY AUTOINCREMENT, | |
| category TEXT, -- plasmid, strain, reagent | |
| name TEXT NOT NULL, | |
| properties_json TEXT, | |
| stock_level INTEGER DEFAULT 0 | |
| ) | |
| ''') | |
| db.commit() | |
| # SiliconFlow API Configuration | |
| SILICONFLOW_API_KEY = "sk-vimuseiptfbomzegyuvmebjzooncsqbyjtlddrfodzcdskgi" | |
| SILICONFLOW_API_URL = "https://api.siliconflow.cn/v1/chat/completions" | |
| MODEL_NAME = "Qwen/Qwen2.5-7B-Instruct" | |
| def call_ai_agent(system_prompt, user_message, json_mode=False): | |
| """ | |
| Calls SiliconFlow API with fallback to Mock Mode. | |
| """ | |
| headers = { | |
| "Authorization": f"Bearer {SILICONFLOW_API_KEY}", | |
| "Content-Type": "application/json" | |
| } | |
| messages = [ | |
| {"role": "system", "content": system_prompt}, | |
| {"role": "user", "content": user_message} | |
| ] | |
| payload = { | |
| "model": MODEL_NAME, | |
| "messages": messages, | |
| "temperature": 0.7, | |
| "max_tokens": 1024 | |
| } | |
| if json_mode: | |
| payload["response_format"] = {"type": "json_object"} | |
| try: | |
| # Short timeout for responsive UI | |
| response = requests.post(SILICONFLOW_API_URL, json=payload, headers=headers, timeout=8) | |
| response.raise_for_status() | |
| data = response.json() | |
| content = data['choices'][0]['message']['content'] | |
| return content | |
| except Exception as e: | |
| print(f"AI API Error (Using Mock): {e}") | |
| return mock_ai_response(user_message, json_mode) | |
| def mock_ai_response(query, json_mode): | |
| """ | |
| Provides context-aware mock responses for demonstration/offline mode. | |
| """ | |
| query_lower = query.lower() | |
| if json_mode: | |
| if "protocol" in query_lower or "设计" in query_lower: | |
| return json.dumps({ | |
| "title": "GFP 绿色荧光蛋白优化表达方案", | |
| "steps": [ | |
| {"step": 1, "action": "接种 (Inoculation)", "params": "37°C, 200rpm, 16小时"}, | |
| {"step": 2, "action": "诱导 (Induction)", "params": "OD600=0.6 时添加 0.5mM IPTG"}, | |
| {"step": 3, "action": "收获 (Harvest)", "params": "5000g 离心, 10分钟"} | |
| ], | |
| "risk_assessment": "诱导期间如果温度超过 30°C,极易形成包涵体。" | |
| }, ensure_ascii=False) | |
| elif "analyze" in query_lower or "分析" in query_lower: | |
| return json.dumps({ | |
| "diagnosis": "检测到发酵液溶氧 (DO) 水平异常波动。", | |
| "anomaly_detected": True, | |
| "yield_prediction": "1.2 g/L (预计低于目标 1.5 g/L)", | |
| "metabolic_bottleneck": "氧传递速率 (OTR) 受限,导致菌体代谢转向副产物生成。", | |
| "recommendation": "建议立即将搅拌转速提高至 250rpm,并补充纯氧通气。同时检查消泡剂添加量。" | |
| }, ensure_ascii=False) | |
| elif "anomaly" in query_lower or "异常" in query_lower: | |
| return json.dumps({ | |
| "anomaly_detected": True, | |
| "diagnosis": "pH 值在过去 1 小时内意外下降。", | |
| "recommendation": "建议添加缓冲液或检查补料速率。" | |
| }, ensure_ascii=False) | |
| return "系统正在模拟模式下运行。AI建议:当前发酵参数偏离最优值,建议将温度降低至30°C以提高蛋白溶解度。请检查DO(溶解氧)探头读数。" | |
| # Routes | |
| def index(): | |
| return render_template('index.html') | |
| def handle_experiments(): | |
| db = get_db() | |
| if request.method == 'POST': | |
| data = request.json | |
| name = data.get('name', 'New Experiment') | |
| target = data.get('target', 'Unknown') | |
| # Initial Protocol Generation (AI) | |
| prompt = f"Design a bio-synthesis protocol for {target}. Return valid JSON with 'steps' array." | |
| protocol = call_ai_agent( | |
| "You are a Senior Bio-Process Engineer. Output JSON only.", | |
| prompt, | |
| json_mode=True | |
| ) | |
| db.execute( | |
| 'INSERT INTO experiments (name, target_molecule, protocol_json, status) VALUES (?, ?, ?, ?)', | |
| (name, target, protocol, 'planned') | |
| ) | |
| db.commit() | |
| return jsonify({"status": "created", "protocol": protocol}) | |
| else: | |
| rows = db.execute('SELECT * FROM experiments ORDER BY created_at DESC').fetchall() | |
| return jsonify([dict(row) for row in rows]) | |
| def bioreactor_stream(): | |
| """ | |
| Simulates real-time sensor data from a bioreactor. | |
| Returns simulated values for: Temp, pH, DO (Dissolved Oxygen), OD600 (Cell Density). | |
| """ | |
| # Simulate a growth curve based on time of day or random walk | |
| now = time.time() | |
| # Simple sine wave + noise for demo | |
| temp = 37.0 + (random.random() - 0.5) * 0.5 | |
| ph = 7.0 + (random.random() - 0.5) * 0.2 | |
| do = 40.0 + (random.random() - 0.5) * 5.0 # % saturation | |
| # Sigmoidal growth simulation for OD600 | |
| # Just random walk for now to show activity | |
| od600 = 0.5 + (random.random() * 0.1) | |
| return jsonify({ | |
| "timestamp": datetime.now().isoformat(), | |
| "temperature": round(temp, 2), | |
| "ph": round(ph, 2), | |
| "dissolved_oxygen": round(do, 1), | |
| "od600": round(od600, 3), | |
| "status": "active" | |
| }) | |
| def analyze_run(): | |
| """ | |
| Analyzes current experiment data using AI. | |
| """ | |
| data = request.json | |
| sensor_data = data.get('sensor_data', {}) | |
| prompt = f""" | |
| Analyze the following bioreactor data: | |
| Temperature: {sensor_data.get('temperature')} C | |
| pH: {sensor_data.get('ph')} | |
| DO: {sensor_data.get('dissolved_oxygen')}% | |
| Identify any anomalies and suggest process control adjustments. Return JSON. | |
| """ | |
| analysis = call_ai_agent( | |
| "You are a Bioreactor Data Analyst. Return JSON with keys: anomaly_detected (bool), diagnosis, recommendation.", | |
| prompt, | |
| json_mode=True | |
| ) | |
| return jsonify({"analysis": analysis}) | |
| def request_entity_too_large(error): | |
| return jsonify({"error": "文件过大,请上传小于 100MB 的文件"}), 413 | |
| def upload_file(): | |
| if 'file' not in request.files: | |
| return jsonify({"error": "没有文件部分"}), 400 | |
| file = request.files['file'] | |
| if file.filename == '': | |
| return jsonify({"error": "未选择文件"}), 400 | |
| if file: | |
| from werkzeug.utils import secure_filename | |
| filename = secure_filename(file.filename) | |
| # Handle non-ASCII filenames preservation if needed, or just use secure_filename | |
| # For simplicity and safety in demo: | |
| save_path = os.path.join(app.config['UPLOAD_FOLDER'], filename) | |
| file.save(save_path) | |
| return jsonify({"status": "success", "filename": filename, "path": save_path}) | |
| def not_found(e): | |
| return jsonify({"error": "Resource not found"}), 404 | |
| def server_error(e): | |
| return jsonify({"error": "Internal server error"}), 500 | |
| # Initialize DB on import | |
| init_db() | |
| if __name__ == '__main__': | |
| app.run(host='0.0.0.0', port=7860, debug=True) | |