# -*- coding: utf-8 -*- """ TSP助手预警管理Web应用 提供预警系统的Web界面和API接口 """ import sys import os import json import logging import pandas as pd # 配置日志 logger = logging.getLogger(__name__) from datetime import datetime, timedelta from openpyxl import Workbook from openpyxl.styles import Font from flask import Flask, render_template, request, jsonify, redirect, url_for, send_from_directory, send_file from flask_cors import CORS from werkzeug.utils import secure_filename # 添加项目根目录到Python路径 sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) from src.main import TSPAssistant from src.agent_assistant import TSPAgentAssistant from src.analytics.alert_system import AlertRule, AlertLevel, AlertType from src.dialogue.realtime_chat import RealtimeChatManager from src.vehicle.vehicle_data_manager import VehicleDataManager from src.core.database import db_manager from src.core.models import WorkOrder, Alert, Conversation, KnowledgeEntry, WorkOrderSuggestion, VehicleData from src.core.backup_manager import backup_manager app = Flask(__name__) CORS(app) # 抑制 /api/health 的访问日志 werkzeug_logger = logging.getLogger('werkzeug') class HealthLogFilter(logging.Filter): def filter(self, record): try: msg = record.getMessage() return '/api/health' not in msg except Exception: return True werkzeug_logger.addFilter(HealthLogFilter()) # 配置上传文件夹 UPLOAD_FOLDER = 'uploads' app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER app.config['MAX_CONTENT_LENGTH'] = 16 * 1024 * 1024 # 16MB max file size # 初始化TSP助手和Agent助手 assistant = TSPAssistant() agent_assistant = TSPAgentAssistant() chat_manager = RealtimeChatManager() vehicle_manager = VehicleDataManager() # 工具函数:确保工单模板文件存在 def _ensure_workorder_template_file() -> str: """返回已有的模板xlsx路径;不做动态生成,避免运行时依赖问题""" template_path = os.path.join(app.config['UPLOAD_FOLDER'], 'workorder_template.xlsx') # 确保目录存在 os.makedirs(app.config['UPLOAD_FOLDER'], exist_ok=True) if not os.path.exists(template_path): # 如果运行目录不存在模板,尝试从项目根相对路径拷贝一次 repo_template = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), '..', 'uploads', 'workorder_template.xlsx') repo_template = os.path.abspath(repo_template) try: if os.path.exists(repo_template): import shutil shutil.copyfile(repo_template, template_path) else: raise FileNotFoundError('模板文件缺失:uploads/workorder_template.xlsx') except Exception as copy_err: raise copy_err return template_path @app.route('/') def index(): """主页 - 综合管理平台""" return render_template('dashboard.html') @app.route('/alerts') def alerts(): """预警管理页面""" return render_template('index.html') @app.route('/api/health') def get_health(): """获取系统健康状态(附加近1小时业务指标)""" try: base = assistant.get_system_health() or {} # 追加数据库近1小时指标 from datetime import datetime, timedelta with db_manager.get_session() as session: since = datetime.now() - timedelta(hours=1) conv_count = session.query(Conversation).filter(Conversation.timestamp >= since).count() resp_times = [c.response_time for c in session.query(Conversation).filter(Conversation.timestamp >= since).all() if c.response_time] avg_resp = round(sum(resp_times)/len(resp_times), 2) if resp_times else 0 open_wos = session.query(WorkOrder).filter(WorkOrder.status == 'open').count() levels = session.query(Alert.level).filter(Alert.is_active == True).all() level_map = {} for (lvl,) in levels: level_map[lvl] = level_map.get(lvl, 0) + 1 base.update({ "throughput_1h": conv_count, "avg_response_time_1h": avg_resp, "open_workorders": open_wos, "active_alerts_by_level": level_map }) return jsonify(base) except Exception as e: return jsonify({"error": str(e)}), 500 @app.route('/api/alerts') def get_alerts(): """获取预警列表""" try: alerts = assistant.get_active_alerts() return jsonify(alerts) except Exception as e: return jsonify({"error": str(e)}), 500 @app.route('/api/alerts', methods=['POST']) def create_alert(): """创建预警""" try: data = request.get_json() alert = assistant.create_alert( alert_type=data.get('alert_type', 'manual'), title=data.get('title', '手动预警'), description=data.get('description', ''), level=data.get('level', 'medium') ) return jsonify({"success": True, "alert": alert}) except Exception as e: return jsonify({"error": str(e)}), 500 @app.route('/api/alerts/statistics') def get_alert_statistics(): """获取预警统计""" try: stats = assistant.get_alert_statistics() return jsonify(stats) except Exception as e: return jsonify({"error": str(e)}), 500 @app.route('/api/alerts//resolve', methods=['POST']) def resolve_alert(alert_id): """解决预警""" try: success = assistant.resolve_alert(alert_id) if success: return jsonify({"success": True, "message": "预警已解决"}) else: return jsonify({"success": False, "message": "解决预警失败"}), 400 except Exception as e: return jsonify({"error": str(e)}), 500 @app.route('/api/rules') def get_rules(): """获取预警规则列表""" try: rules = assistant.alert_system.rules rules_data = [] for name, rule in rules.items(): rules_data.append({ "name": rule.name, "description": rule.description, "alert_type": rule.alert_type.value, "level": rule.level.value, "threshold": rule.threshold, "condition": rule.condition, "enabled": rule.enabled, "check_interval": rule.check_interval, "cooldown": rule.cooldown }) return jsonify(rules_data) except Exception as e: return jsonify({"error": str(e)}), 500 @app.route('/api/rules', methods=['POST']) def create_rule(): """创建预警规则""" try: data = request.get_json() rule = AlertRule( name=data['name'], description=data['description'], alert_type=AlertType(data['alert_type']), level=AlertLevel(data['level']), threshold=float(data['threshold']), condition=data['condition'], enabled=data.get('enabled', True), check_interval=int(data.get('check_interval', 300)), cooldown=int(data.get('cooldown', 3600)) ) success = assistant.alert_system.add_custom_rule(rule) if success: return jsonify({"success": True, "message": "规则创建成功"}) else: return jsonify({"success": False, "message": "规则创建失败"}), 400 except Exception as e: return jsonify({"error": str(e)}), 500 @app.route('/api/rules/', methods=['PUT']) def update_rule(rule_name): """更新预警规则""" try: data = request.get_json() success = assistant.alert_system.update_rule(rule_name, **data) if success: return jsonify({"success": True, "message": "规则更新成功"}) else: return jsonify({"success": False, "message": "规则更新失败"}), 400 except Exception as e: return jsonify({"error": str(e)}), 500 @app.route('/api/rules/', methods=['DELETE']) def delete_rule(rule_name): """删除预警规则""" try: success = assistant.alert_system.delete_rule(rule_name) if success: return jsonify({"success": True, "message": "规则删除成功"}) else: return jsonify({"success": False, "message": "规则删除失败"}), 400 except Exception as e: return jsonify({"error": str(e)}), 500 @app.route('/api/monitor/start', methods=['POST']) def start_monitoring(): """启动监控服务""" try: success = assistant.start_monitoring() if success: return jsonify({"success": True, "message": "监控服务已启动"}) else: return jsonify({"success": False, "message": "启动监控服务失败"}), 400 except Exception as e: return jsonify({"error": str(e)}), 500 @app.route('/api/monitor/stop', methods=['POST']) def stop_monitoring(): """停止监控服务""" try: success = assistant.stop_monitoring() if success: return jsonify({"success": True, "message": "监控服务已停止"}) else: return jsonify({"success": False, "message": "停止监控服务失败"}), 400 except Exception as e: return jsonify({"error": str(e)}), 500 @app.route('/api/monitor/status') def get_monitor_status(): """获取监控服务状态""" try: health = assistant.get_system_health() return jsonify({ "monitor_status": health.get("monitor_status", "unknown"), "health_score": health.get("health_score", 0), "active_alerts": health.get("active_alerts", 0) }) except Exception as e: return jsonify({"error": str(e)}), 500 @app.route('/api/check-alerts', methods=['POST']) def check_alerts(): """手动检查预警""" try: alerts = assistant.check_alerts() return jsonify({ "success": True, "alerts": alerts, "count": len(alerts) }) except Exception as e: return jsonify({"error": str(e)}), 500 # 实时对话相关路由 @app.route('/chat') def chat(): """实时对话页面 (WebSocket版本)""" return render_template('chat.html') @app.route('/chat-http') def chat_http(): """实时对话页面 (HTTP版本)""" return render_template('chat_http.html') @app.route('/api/chat/session', methods=['POST']) def create_chat_session(): """创建对话会话""" try: data = request.get_json() user_id = data.get('user_id', 'anonymous') work_order_id = data.get('work_order_id') session_id = chat_manager.create_session(user_id, work_order_id) return jsonify({ "success": True, "session_id": session_id, "message": "会话创建成功" }) except Exception as e: return jsonify({"error": str(e)}), 500 @app.route('/api/chat/message', methods=['POST']) def send_chat_message(): """发送聊天消息""" try: data = request.get_json() session_id = data.get('session_id') message = data.get('message') if not session_id or not message: return jsonify({"error": "缺少必要参数"}), 400 result = chat_manager.process_message(session_id, message) return jsonify(result) except Exception as e: return jsonify({"error": str(e)}), 500 @app.route('/api/chat/history/') def get_chat_history(session_id): """获取对话历史""" try: history = chat_manager.get_session_history(session_id) return jsonify({ "success": True, "history": history }) except Exception as e: return jsonify({"error": str(e)}), 500 @app.route('/api/chat/work-order', methods=['POST']) def create_work_order(): """创建工单""" try: data = request.get_json() session_id = data.get('session_id') title = data.get('title') description = data.get('description') category = data.get('category', '技术问题') priority = data.get('priority', 'medium') if not session_id or not title or not description: return jsonify({"error": "缺少必要参数"}), 400 result = chat_manager.create_work_order(session_id, title, description, category, priority) return jsonify(result) except Exception as e: return jsonify({"error": str(e)}), 500 @app.route('/api/chat/work-order/') def get_work_order_status(work_order_id): """获取工单状态""" try: result = chat_manager.get_work_order_status(work_order_id) return jsonify(result) except Exception as e: return jsonify({"error": str(e)}), 500 @app.route('/api/chat/session/', methods=['DELETE']) def end_chat_session(session_id): """结束对话会话""" try: success = chat_manager.end_session(session_id) return jsonify({ "success": success, "message": "会话已结束" if success else "结束会话失败" }) except Exception as e: return jsonify({"error": str(e)}), 500 @app.route('/api/chat/sessions') def get_active_sessions(): """获取活跃会话列表""" try: sessions = chat_manager.get_active_sessions() return jsonify({ "success": True, "sessions": sessions }) except Exception as e: return jsonify({"error": str(e)}), 500 # Agent相关API @app.route('/api/agent/status') def get_agent_status(): """获取Agent状态""" try: status = agent_assistant.get_agent_status() return jsonify(status) except Exception as e: return jsonify({"error": str(e)}), 500 @app.route('/api/agent/action-history') def get_agent_action_history(): """获取Agent动作执行历史""" try: limit = request.args.get('limit', 50, type=int) history = agent_assistant.get_action_history(limit) return jsonify({ "success": True, "history": history, "count": len(history) }) except Exception as e: return jsonify({"error": str(e)}), 500 @app.route('/api/agent/trigger-sample', methods=['POST']) def trigger_sample_action(): """触发示例动作""" try: import asyncio result = asyncio.run(agent_assistant.trigger_sample_actions()) return jsonify(result) except Exception as e: return jsonify({"error": str(e)}), 500 @app.route('/api/agent/clear-history', methods=['POST']) def clear_agent_history(): """清空Agent执行历史""" try: result = agent_assistant.clear_execution_history() return jsonify(result) except Exception as e: return jsonify({"error": str(e)}), 500 @app.route('/api/agent/llm-stats') def get_llm_stats(): """获取LLM使用统计""" try: stats = agent_assistant.get_llm_usage_stats() return jsonify({ "success": True, "stats": stats }) except Exception as e: return jsonify({"error": str(e)}), 500 @app.route('/api/agent/toggle', methods=['POST']) def toggle_agent_mode(): """切换Agent模式""" try: data = request.get_json() enabled = data.get('enabled', True) success = agent_assistant.toggle_agent_mode(enabled) return jsonify({ "success": success, "message": f"Agent模式已{'启用' if enabled else '禁用'}" }) except Exception as e: return jsonify({"error": str(e)}), 500 @app.route('/api/agent/monitoring/start', methods=['POST']) def start_agent_monitoring(): """启动Agent监控""" try: success = agent_assistant.start_proactive_monitoring() return jsonify({ "success": success, "message": "Agent监控已启动" if success else "启动失败" }) except Exception as e: return jsonify({"error": str(e)}), 500 @app.route('/api/agent/monitoring/stop', methods=['POST']) def stop_agent_monitoring(): """停止Agent监控""" try: success = agent_assistant.stop_proactive_monitoring() return jsonify({ "success": success, "message": "Agent监控已停止" if success else "停止失败" }) except Exception as e: return jsonify({"error": str(e)}), 500 @app.route('/api/agent/proactive-monitoring', methods=['POST']) def proactive_monitoring(): """主动监控检查""" try: result = agent_assistant.run_proactive_monitoring() return jsonify(result) except Exception as e: return jsonify({"error": str(e)}), 500 @app.route('/api/agent/intelligent-analysis', methods=['POST']) def intelligent_analysis(): """智能分析""" try: analysis = agent_assistant.run_intelligent_analysis() return jsonify({"success": True, "analysis": analysis}) except Exception as e: return jsonify({"error": str(e)}), 500 # 知识库相关API @app.route('/api/knowledge') def get_knowledge(): """获取知识库列表""" try: # 获取分页参数 page = request.args.get('page', 1, type=int) per_page = request.args.get('per_page', 10, type=int) # 从数据库获取知识库数据 knowledge_entries = assistant.knowledge_manager.get_knowledge_entries( page=page, per_page=per_page ) return jsonify(knowledge_entries) except Exception as e: return jsonify({"error": str(e)}), 500 @app.route('/api/knowledge/search') def search_knowledge(): """搜索知识库""" try: query = request.args.get('q', '') # 这里应该调用知识库管理器的搜索方法 results = assistant.search_knowledge(query, top_k=5) return jsonify(results.get('results', [])) except Exception as e: return jsonify({"error": str(e)}), 500 @app.route('/api/knowledge', methods=['POST']) def add_knowledge(): """添加知识库条目""" try: data = request.get_json() success = assistant.knowledge_manager.add_knowledge_entry( question=data['question'], answer=data['answer'], category=data['category'], confidence_score=data['confidence_score'] ) return jsonify({"success": success, "message": "知识添加成功" if success else "添加失败"}) except Exception as e: return jsonify({"error": str(e)}), 500 @app.route('/api/knowledge/stats') def get_knowledge_stats(): """获取知识库统计""" try: stats = assistant.knowledge_manager.get_knowledge_stats() return jsonify(stats) except Exception as e: return jsonify({"error": str(e)}), 500 @app.route('/api/knowledge/upload', methods=['POST']) def upload_knowledge_file(): """上传文件并生成知识库""" try: if 'file' not in request.files: return jsonify({"error": "没有上传文件"}), 400 file = request.files['file'] if file.filename == '': return jsonify({"error": "没有选择文件"}), 400 # 保存文件到临时目录 import tempfile import os import uuid # 创建唯一的临时文件名 temp_filename = f"upload_{uuid.uuid4()}{os.path.splitext(file.filename)[1]}" temp_path = os.path.join(tempfile.gettempdir(), temp_filename) try: # 保存文件 file.save(temp_path) # 使用Agent助手处理文件 result = agent_assistant.process_file_to_knowledge(temp_path, file.filename) return jsonify(result) finally: # 确保删除临时文件 try: if os.path.exists(temp_path): os.unlink(temp_path) except Exception as cleanup_error: logger.warning(f"清理临时文件失败: {cleanup_error}") except Exception as e: logger.error(f"文件上传处理失败: {e}") return jsonify({"error": str(e)}), 500 @app.route('/api/knowledge/delete/', methods=['DELETE']) def delete_knowledge(knowledge_id): """删除知识库条目""" try: success = assistant.knowledge_manager.delete_knowledge_entry(knowledge_id) return jsonify({"success": success, "message": "删除成功" if success else "删除失败"}) except Exception as e: return jsonify({"error": str(e)}), 500 @app.route('/api/knowledge/verify/', methods=['POST']) def verify_knowledge(knowledge_id): """验证知识库条目""" try: data = request.get_json() or {} verified_by = data.get('verified_by', 'admin') success = assistant.knowledge_manager.verify_knowledge_entry(knowledge_id, verified_by) return jsonify({"success": success, "message": "验证成功" if success else "验证失败"}) except Exception as e: return jsonify({"error": str(e)}), 500 @app.route('/api/knowledge/unverify/', methods=['POST']) def unverify_knowledge(knowledge_id): """取消验证知识库条目""" try: success = assistant.knowledge_manager.unverify_knowledge_entry(knowledge_id) return jsonify({"success": success, "message": "取消验证成功" if success else "取消验证失败"}) except Exception as e: return jsonify({"error": str(e)}), 500 # 工单相关API @app.route('/api/workorders') def get_workorders(): """获取工单列表(来自数据库)""" try: status_filter = request.args.get('status') priority_filter = request.args.get('priority') with db_manager.get_session() as session: q = session.query(WorkOrder) if status_filter and status_filter != 'all': q = q.filter(WorkOrder.status == status_filter) if priority_filter and priority_filter != 'all': q = q.filter(WorkOrder.priority == priority_filter) q = q.order_by(WorkOrder.created_at.desc()) rows = q.all() result = [] for w in rows: result.append({ "id": w.id, "order_id": w.order_id, "title": w.title, "description": w.description, "category": w.category, "priority": w.priority, "status": w.status, "created_at": w.created_at.isoformat() if w.created_at else None, "updated_at": w.updated_at.isoformat() if w.updated_at else None, "resolution": w.resolution, "satisfaction_score": w.satisfaction_score }) return jsonify(result) except Exception as e: return jsonify({"error": str(e)}), 500 @app.route('/api/workorders', methods=['POST']) def create_workorder(): """创建工单""" try: data = request.get_json() result = assistant.create_work_order( title=data['title'], description=data['description'], category=data['category'], priority=data['priority'] ) return jsonify({"success": True, "workorder": result}) except Exception as e: return jsonify({"error": str(e)}), 500 @app.route('/api/workorders/') def get_workorder_details(workorder_id): """获取工单详情(含数据库对话记录)""" try: with db_manager.get_session() as session: w = session.query(WorkOrder).filter(WorkOrder.id == workorder_id).first() if not w: return jsonify({"error": "工单不存在"}), 404 convs = session.query(Conversation).filter(Conversation.work_order_id == w.id).order_by(Conversation.timestamp.asc()).all() conv_list = [] for c in convs: conv_list.append({ "id": c.id, "user_message": c.user_message, "assistant_response": c.assistant_response, "timestamp": c.timestamp.isoformat() if c.timestamp else None }) # 在会话内构建工单数据 workorder = { "id": w.id, "order_id": w.order_id, "title": w.title, "description": w.description, "category": w.category, "priority": w.priority, "status": w.status, "created_at": w.created_at.isoformat() if w.created_at else None, "updated_at": w.updated_at.isoformat() if w.updated_at else None, "resolution": w.resolution, "satisfaction_score": w.satisfaction_score, "conversations": conv_list } return jsonify(workorder) except Exception as e: return jsonify({"error": str(e)}), 500 @app.route('/api/workorders/', methods=['PUT']) def update_workorder(workorder_id): """更新工单(写入数据库)""" try: data = request.get_json() if not data.get('title') or not data.get('description'): return jsonify({"error": "标题和描述不能为空"}), 400 with db_manager.get_session() as session: w = session.query(WorkOrder).filter(WorkOrder.id == workorder_id).first() if not w: return jsonify({"error": "工单不存在"}), 404 w.title = data.get('title', w.title) w.description = data.get('description', w.description) w.category = data.get('category', w.category) w.priority = data.get('priority', w.priority) w.status = data.get('status', w.status) w.resolution = data.get('resolution', w.resolution) w.satisfaction_score = data.get('satisfaction_score', w.satisfaction_score) w.updated_at = datetime.now() session.commit() updated = { "id": w.id, "title": w.title, "description": w.description, "category": w.category, "priority": w.priority, "status": w.status, "resolution": w.resolution, "satisfaction_score": w.satisfaction_score, "updated_at": w.updated_at.isoformat() if w.updated_at else None } return jsonify({"success": True, "message": "工单更新成功", "workorder": updated}) except Exception as e: return jsonify({"error": str(e)}), 500 # 工单AI建议:生成、保存人工描述、审批入库 @app.route('/api/workorders//ai-suggestion', methods=['POST']) def generate_workorder_ai_suggestion(workorder_id): """根据工单描述与知识库生成AI建议草稿""" try: with db_manager.get_session() as session: w = session.query(WorkOrder).filter(WorkOrder.id == workorder_id).first() if not w: return jsonify({"error": "工单不存在"}), 404 # 调用知识库搜索与LLM生成 query = f"{w.title} {w.description}" kb_results = assistant.search_knowledge(query, top_k=3) kb_list = kb_results.get('results', []) if isinstance(kb_results, dict) else [] # 组装提示词 context = "\n".join([f"Q: {k.get('question','')}\nA: {k.get('answer','')}" for k in kb_list]) from src.core.llm_client import QwenClient llm = QwenClient() prompt = f"请基于以下工单描述与知识库片段,给出简洁、可执行的处理建议。\n工单描述:\n{w.description}\n\n知识库片段:\n{context}\n\n请直接输出建议文本:" llm_resp = llm.chat_completion(messages=[{"role":"user","content":prompt}], temperature=0.3, max_tokens=800) suggestion = "" if llm_resp and 'choices' in llm_resp: suggestion = llm_resp['choices'][0]['message']['content'] # 保存/更新草稿记录 rec = session.query(WorkOrderSuggestion).filter(WorkOrderSuggestion.work_order_id == w.id).first() if not rec: rec = WorkOrderSuggestion(work_order_id=w.id, ai_suggestion=suggestion) session.add(rec) else: rec.ai_suggestion = suggestion rec.updated_at = datetime.now() session.commit() return jsonify({"success": True, "ai_suggestion": suggestion}) except Exception as e: return jsonify({"error": str(e)}), 500 @app.route('/api/workorders//human-resolution', methods=['POST']) def save_workorder_human_resolution(workorder_id): """保存人工描述,并计算与AI建议相似度;若≥95%可自动审批入库""" try: data = request.get_json() or {} human_text = data.get('human_resolution','').strip() if not human_text: return jsonify({"error":"人工描述不能为空"}), 400 with db_manager.get_session() as session: w = session.query(WorkOrder).filter(WorkOrder.id == workorder_id).first() if not w: return jsonify({"error": "工单不存在"}), 404 rec = session.query(WorkOrderSuggestion).filter(WorkOrderSuggestion.work_order_id == w.id).first() if not rec: rec = WorkOrderSuggestion(work_order_id=w.id) session.add(rec) rec.human_resolution = human_text # 计算相似度(使用简单cosine TF-IDF,避免外部服务依赖) try: from sklearn.feature_extraction.text import TfidfVectorizer from sklearn.metrics.pairwise import cosine_similarity texts = [rec.ai_suggestion or "", human_text] vec = TfidfVectorizer(max_features=1000) mat = vec.fit_transform(texts) sim = float(cosine_similarity(mat[0:1], mat[1:2])[0][0]) except Exception: sim = 0.0 rec.ai_similarity = sim # 自动审批条件≥0.95 approved = sim >= 0.95 rec.approved = approved session.commit() return jsonify({"success": True, "similarity": sim, "approved": approved}) except Exception as e: return jsonify({"error": str(e)}), 500 @app.route('/api/workorders//approve-to-knowledge', methods=['POST']) def approve_workorder_to_knowledge(workorder_id): """将已审批的AI建议入库为知识条目""" try: with db_manager.get_session() as session: w = session.query(WorkOrder).filter(WorkOrder.id == workorder_id).first() if not w: return jsonify({"error": "工单不存在"}), 404 rec = session.query(WorkOrderSuggestion).filter(WorkOrderSuggestion.work_order_id == w.id).first() if not rec or not rec.approved or not rec.ai_suggestion: return jsonify({"error": "未找到可入库的已审批AI建议"}), 400 # 入库为知识条目(问=工单标题;答=AI建议;类目用工单分类) entry = KnowledgeEntry( question=w.title or (w.description[:20] if w.description else '工单问题'), answer=rec.ai_suggestion, category=w.category or '其他', confidence_score=0.95, is_active=True, is_verified=True, verified_by='auto_approve', verified_at=datetime.now() ) session.add(entry) session.commit() return jsonify({"success": True, "knowledge_id": entry.id}) except Exception as e: return jsonify({"error": str(e)}), 500 # 分析相关API @app.route('/api/analytics') def get_analytics(): """获取分析数据""" try: # 支持多种参数名 time_range = request.args.get('timeRange', request.args.get('days', '30')) dimension = request.args.get('dimension', 'workorders') analytics = generate_db_analytics(int(time_range), dimension) return jsonify(analytics) except Exception as e: return jsonify({"error": str(e)}), 500 def generate_db_analytics(days: int, dimension: str) -> dict: """基于数据库生成真实分析数据""" from collections import defaultdict, Counter end_time = datetime.now() start_time = end_time - timedelta(days=days-1) with db_manager.get_session() as session: # 拉取数据 workorders = session.query(WorkOrder).filter(WorkOrder.created_at >= start_time).all() alerts = session.query(Alert).filter(Alert.created_at >= start_time).all() conversations = session.query(Conversation).filter(Conversation.timestamp >= start_time).all() knowledge_entries = session.query(KnowledgeEntry).all() # 趋势数据(按天) day_keys = [(start_time + timedelta(days=i)).strftime('%Y-%m-%d') for i in range(days)] wo_by_day = Counter([(wo.created_at.strftime('%Y-%m-%d') if wo.created_at else end_time.strftime('%Y-%m-%d')) for wo in workorders]) alert_by_day = Counter([(al.created_at.strftime('%Y-%m-%d') if al.created_at else end_time.strftime('%Y-%m-%d')) for al in alerts]) trend = [{ 'date': d, 'workorders': int(wo_by_day.get(d, 0)), 'alerts': int(alert_by_day.get(d, 0)) } for d in day_keys] # 工单统计 total = len(workorders) status_counts = Counter([wo.status for wo in workorders]) category_counts = Counter([wo.category for wo in workorders]) priority_counts = Counter([wo.priority for wo in workorders]) resolved_count = status_counts.get('resolved', 0) workorders_stats = { 'total': total, 'open': status_counts.get('open', 0), 'in_progress': status_counts.get('in_progress', 0), 'resolved': resolved_count, 'closed': status_counts.get('closed', 0), 'by_category': dict(category_counts), 'by_priority': dict(priority_counts) } # 满意度 scores = [] for wo in workorders: if wo.satisfaction_score not in (None, ''): try: score = float(wo.satisfaction_score) scores.append(score) except (ValueError, TypeError): continue avg_satisfaction = round(sum(scores)/len(scores), 1) if scores else 0 dist = Counter([str(int(round(s))) for s in scores]) if scores else {} satisfaction_stats = { 'average': avg_satisfaction, 'distribution': {k: int(v) for k, v in dist.items()} } # 预警统计 level_counts = Counter([al.level for al in alerts]) active_alerts = len([al for al in alerts if al.is_active]) resolved_alerts = len([al for al in alerts if not al.is_active and al.resolved_at]) alerts_stats = { 'total': len(alerts), 'active': active_alerts, 'resolved': resolved_alerts, 'by_level': {k: int(v) for k, v in level_counts.items()} } # 性能指标(基于对话响应时间粗略估计) resp_times = [] for c in conversations: if c.response_time not in (None, ''): try: resp_time = float(c.response_time) resp_times.append(resp_time) except (ValueError, TypeError): continue avg_resp = round(sum(resp_times)/len(resp_times), 2) if resp_times else 0 throughput = len(conversations) # 期间内的对话数量 # 错误率:用严重预警比例粗估 critical = level_counts.get('critical', 0) error_rate = round((critical / alerts_stats['total']) * 100, 2) if alerts_stats['total'] > 0 else 0 performance_stats = { 'response_time': avg_resp, 'uptime': 99.0, # 可接入真实监控后更新 'error_rate': error_rate, 'throughput': throughput } return { 'trend': trend, 'workorders': workorders_stats, 'satisfaction': satisfaction_stats, 'alerts': alerts_stats, 'performance': performance_stats, 'summary': { 'total_workorders': total, 'resolution_rate': round((resolved_count/total)*100, 1) if total > 0 else 0, 'avg_satisfaction': avg_satisfaction, 'active_alerts': active_alerts } } @app.route('/api/analytics/export') def export_analytics(): """导出分析报告""" try: # 生成Excel报告(使用数据库真实数据) analytics = generate_db_analytics(30, 'workorders') # 创建工作簿 wb = Workbook() ws = wb.active ws.title = "分析报告" # 添加标题 ws['A1'] = 'TSP智能助手分析报告' ws['A1'].font = Font(size=16, bold=True) # 添加工单统计 ws['A3'] = '工单统计' ws['A3'].font = Font(bold=True) ws['A4'] = '总工单数' ws['B4'] = analytics['workorders']['total'] ws['A5'] = '待处理' ws['B5'] = analytics['workorders']['open'] ws['A6'] = '已解决' ws['B6'] = analytics['workorders']['resolved'] # 保存文件 report_path = 'uploads/analytics_report.xlsx' os.makedirs('uploads', exist_ok=True) wb.save(report_path) return send_file(report_path, as_attachment=True, download_name='analytics_report.xlsx') except Exception as e: return jsonify({"error": str(e)}), 500 # Agent 工具统计与自定义工具 @app.route('/api/agent/tools/stats') def get_agent_tools_stats(): try: tools = agent_assistant.agent_core.tool_manager.get_available_tools() performance = agent_assistant.agent_core.tool_manager.get_tool_performance_report() return jsonify({ "success": True, "tools": tools, "performance": performance }) except Exception as e: return jsonify({"error": str(e)}), 500 @app.route('/api/agent/tools/register', methods=['POST']) def register_custom_tool(): """注册自定义工具(仅登记元数据,函数为占位)""" try: data = request.get_json() or {} name = data.get('name') description = data.get('description', '') if not name: return jsonify({"error": "缺少工具名称"}), 400 def _placeholder_tool(**kwargs): return {"message": f"自定义工具 {name} 已登记(占位),当前不可执行", "params": kwargs} agent_assistant.agent_core.tool_manager.register_tool( name, _placeholder_tool, metadata={"description": description, "custom": True} ) return jsonify({"success": True, "message": "工具已注册"}) except Exception as e: return jsonify({"error": str(e)}), 500 @app.route('/api/agent/tools/unregister/', methods=['DELETE']) def unregister_custom_tool(name): try: success = agent_assistant.agent_core.tool_manager.unregister_tool(name) return jsonify({"success": success}) except Exception as e: return jsonify({"error": str(e)}), 500 # 工单导入相关API @app.route('/api/workorders/import', methods=['POST']) def import_workorders(): """导入Excel工单文件""" try: # 检查是否有文件上传 if 'file' not in request.files: return jsonify({"error": "没有上传文件"}), 400 file = request.files['file'] if file.filename == '': return jsonify({"error": "没有选择文件"}), 400 if not file.filename.endswith(('.xlsx', '.xls')): return jsonify({"error": "只支持Excel文件(.xlsx, .xls)"}), 400 # 保存上传的文件 filename = secure_filename(file.filename) upload_path = os.path.join('uploads', filename) os.makedirs('uploads', exist_ok=True) file.save(upload_path) # 解析Excel文件 try: df = pd.read_excel(upload_path) imported_workorders = [] # 处理每一行数据 for index, row in df.iterrows(): # 根据Excel列名映射到工单字段 workorder = { "id": len(assistant.work_orders) + index + 1, # 生成新ID "order_id": f"WO{len(assistant.work_orders) + index + 1:06d}", "title": str(row.get('标题', row.get('title', f'导入工单 {index + 1}'))), "description": str(row.get('描述', row.get('description', ''))), "category": str(row.get('分类', row.get('category', '技术问题'))), "priority": str(row.get('优先级', row.get('priority', 'medium'))), "status": str(row.get('状态', row.get('status', 'open'))), "created_at": datetime.now().isoformat(), "updated_at": datetime.now().isoformat(), "resolution": str(row.get('解决方案', row.get('resolution', ''))) if pd.notna(row.get('解决方案', row.get('resolution'))) else None, "satisfaction_score": int(row.get('满意度', row.get('satisfaction_score', 0))) if pd.notna(row.get('满意度', row.get('satisfaction_score'))) else None } # 添加到工单列表(这里应该保存到数据库) assistant.work_orders.append(workorder) imported_workorders.append(workorder) # 清理上传的文件 os.remove(upload_path) return jsonify({ "success": True, "message": f"成功导入 {len(imported_workorders)} 个工单", "imported_count": len(imported_workorders), "workorders": imported_workorders }) except Exception as e: # 清理上传的文件 if os.path.exists(upload_path): os.remove(upload_path) return jsonify({"error": f"解析Excel文件失败: {str(e)}"}), 400 except Exception as e: return jsonify({"error": str(e)}), 500 @app.route('/api/workorders/import/template') def download_import_template(): """下载工单导入模板""" try: template_path = _ensure_workorder_template_file() return jsonify({ "success": True, "template_url": f"/uploads/workorder_template.xlsx" }) except Exception as e: return jsonify({"error": str(e)}), 500 @app.route('/api/workorders/import/template/file') def download_import_template_file(): """直接返回工单导入模板文件(下载)""" try: template_path = _ensure_workorder_template_file() return send_file(template_path, as_attachment=True, download_name='工单导入模板.xlsx') except Exception as e: return jsonify({"error": str(e)}), 500 @app.route('/uploads/') def uploaded_file(filename): """提供上传文件的下载服务""" return send_from_directory(app.config['UPLOAD_FOLDER'], filename) # 系统设置相关API @app.route('/api/settings') def get_settings(): """获取系统设置""" try: import json settings_path = os.path.join('data', 'system_settings.json') os.makedirs('data', exist_ok=True) if os.path.exists(settings_path): with open(settings_path, 'r', encoding='utf-8') as f: settings = json.load(f) # 掩码API Key if settings.get('api_key'): settings['api_key'] = '******' settings['api_key_masked'] = True else: settings = { "api_timeout": 30, "max_history": 10, "refresh_interval": 10, "auto_monitoring": True, "agent_mode": True, # LLM与API配置(仅持久化,不直接热更新LLM客户端) "api_provider": "openai", "api_base_url": "", "api_key": "", "model_name": "qwen-turbo", "model_temperature": 0.7, "model_max_tokens": 1000, # 服务配置 "server_port": 5000, "websocket_port": 8765, "log_level": "INFO" } with open(settings_path, 'w', encoding='utf-8') as f: json.dump(settings, f, ensure_ascii=False, indent=2) # 添加当前服务状态信息 import time import psutil settings['current_server_port'] = app.config.get('SERVER_PORT', 5000) settings['current_websocket_port'] = app.config.get('WEBSOCKET_PORT', 8765) settings['uptime_seconds'] = int(time.time() - app.config.get('START_TIME', time.time())) settings['memory_usage_percent'] = psutil.virtual_memory().percent settings['cpu_usage_percent'] = psutil.cpu_percent() return jsonify(settings) except Exception as e: return jsonify({"error": str(e)}), 500 @app.route('/api/settings', methods=['POST']) def save_settings(): """保存系统设置""" try: data = request.get_json() import json os.makedirs('data', exist_ok=True) settings_path = os.path.join('data', 'system_settings.json') # 读取旧值,处理api_key掩码 old = {} if os.path.exists(settings_path): try: with open(settings_path, 'r', encoding='utf-8') as f: old = json.load(f) except Exception: old = {} # 如果前端传回掩码或空,则保留旧的api_key if 'api_key' in data: if not data['api_key'] or data['api_key'] == '******': data['api_key'] = old.get('api_key', '') # 移除mask标志 if 'api_key_masked' in data: data.pop('api_key_masked') with open(settings_path, 'w', encoding='utf-8') as f: json.dump(data, f, ensure_ascii=False, indent=2) return jsonify({"success": True, "message": "设置保存成功"}) except Exception as e: return jsonify({"error": str(e)}), 500 @app.route('/api/system/info') def get_system_info(): """获取系统信息""" try: import sys import platform info = { "version": "1.0.0", "python_version": sys.version, "database": "SQLite", "uptime": "2天3小时", "memory_usage": 128 } return jsonify(info) except Exception as e: return jsonify({"error": str(e)}), 500 # 车辆数据相关API @app.route('/api/vehicle/data') def get_vehicle_data(): """获取车辆数据""" try: vehicle_id = request.args.get('vehicle_id') vehicle_vin = request.args.get('vehicle_vin') data_type = request.args.get('data_type') limit = request.args.get('limit', 10, type=int) if vehicle_vin: data = vehicle_manager.get_vehicle_data_by_vin(vehicle_vin, data_type, limit) elif vehicle_id: data = vehicle_manager.get_vehicle_data(vehicle_id, data_type, limit) else: data = vehicle_manager.search_vehicle_data(limit=limit) return jsonify(data) except Exception as e: return jsonify({"error": str(e)}), 500 @app.route('/api/vehicle/data/vin//latest') def get_latest_vehicle_data_by_vin(vehicle_vin): """按VIN获取车辆最新数据""" try: data = vehicle_manager.get_latest_vehicle_data_by_vin(vehicle_vin) return jsonify(data) except Exception as e: return jsonify({"error": str(e)}), 500 @app.route('/api/vehicle/data//latest') def get_latest_vehicle_data(vehicle_id): """获取车辆最新数据""" try: data = vehicle_manager.get_latest_vehicle_data(vehicle_id) return jsonify(data) except Exception as e: return jsonify({"error": str(e)}), 500 @app.route('/api/vehicle/data//summary') def get_vehicle_summary(vehicle_id): """获取车辆数据摘要""" try: summary = vehicle_manager.get_vehicle_summary(vehicle_id) return jsonify(summary) except Exception as e: return jsonify({"error": str(e)}), 500 @app.route('/api/vehicle/data', methods=['POST']) def add_vehicle_data(): """添加车辆数据""" try: data = request.get_json() success = vehicle_manager.add_vehicle_data( vehicle_id=data['vehicle_id'], data_type=data['data_type'], data_value=data['data_value'], vehicle_vin=data.get('vehicle_vin') ) return jsonify({"success": success, "message": "数据添加成功" if success else "添加失败"}) except Exception as e: return jsonify({"error": str(e)}), 500 @app.route('/api/vehicle/init-sample-data', methods=['POST']) def init_sample_vehicle_data(): """初始化示例车辆数据""" try: success = vehicle_manager.add_sample_vehicle_data() return jsonify({"success": success, "message": "示例数据初始化成功" if success else "初始化失败"}) except Exception as e: return jsonify({"error": str(e)}), 500 # API测试相关接口 @app.route('/api/test/connection', methods=['POST']) def test_api_connection(): """测试API连接""" try: data = request.get_json() api_provider = data.get('api_provider', 'openai') api_base_url = data.get('api_base_url', '') api_key = data.get('api_key', '') model_name = data.get('model_name', 'qwen-turbo') # 这里可以调用LLM客户端进行连接测试 # 暂时返回模拟结果 return jsonify({ "success": True, "message": f"API连接测试成功 - {api_provider}", "response_time": "150ms", "model_status": "可用" }) except Exception as e: return jsonify({"success": False, "error": str(e)}), 500 @app.route('/api/test/model', methods=['POST']) def test_model_response(): """测试模型回答""" try: data = request.get_json() test_message = data.get('test_message', '你好,请简单介绍一下你自己') # 这里可以调用LLM客户端进行回答测试 # 暂时返回模拟结果 return jsonify({ "success": True, "test_message": test_message, "response": "你好!我是TSP智能助手,基于大语言模型构建的智能客服系统。我可以帮助您解决车辆相关问题,提供技术支持和服务。", "response_time": "1.2s", "tokens_used": 45 }) except Exception as e: return jsonify({"success": False, "error": str(e)}), 500 # 数据库备份管理API @app.route('/api/backup/info') def get_backup_info(): """获取备份信息""" try: info = backup_manager.get_backup_info() return jsonify({ "success": True, "backup_info": info }) except Exception as e: return jsonify({"error": str(e)}), 500 @app.route('/api/backup/create', methods=['POST']) def create_backup(): """创建数据备份""" try: result = backup_manager.backup_all_data() return jsonify({ "success": result["success"], "message": "备份创建成功" if result["success"] else "备份创建失败", "backup_result": result }) except Exception as e: return jsonify({"error": str(e)}), 500 @app.route('/api/backup/restore', methods=['POST']) def restore_backup(): """从备份恢复数据""" try: data = request.get_json() or {} table_name = data.get('table_name') # 可选:指定恢复特定表 result = backup_manager.restore_from_backup(table_name) return jsonify({ "success": result["success"], "message": "数据恢复成功" if result["success"] else "数据恢复失败", "restore_result": result }) except Exception as e: return jsonify({"error": str(e)}), 500 @app.route('/api/database/status') def get_database_status(): """获取数据库状态信息""" try: # MySQL数据库状态 mysql_status = { "type": "MySQL", "url": str(db_manager.engine.url).replace(db_manager.engine.url.password, "******") if db_manager.engine.url.password else str(db_manager.engine.url), "connected": db_manager.test_connection() } # 统计MySQL数据 with db_manager.get_session() as session: mysql_status["table_counts"] = { "work_orders": session.query(WorkOrder).count(), "conversations": session.query(Conversation).count(), "knowledge_entries": session.query(KnowledgeEntry).count(), "vehicle_data": session.query(VehicleData).count(), "alerts": session.query(Alert).count() } # SQLite备份状态 backup_info = backup_manager.get_backup_info() return jsonify({ "success": True, "mysql": mysql_status, "sqlite_backup": backup_info }) except Exception as e: return jsonify({"error": str(e)}), 500 if __name__ == '__main__': import time app.config['START_TIME'] = time.time() app.config['SERVER_PORT'] = 5000 app.config['WEBSOCKET_PORT'] = 8765 app.run(debug=True, host='0.0.0.0', port=5000)