feat: 自动提交 - 周一 2025/09/22 16:28:00.19

This commit is contained in:
赵杰
2025-09-22 16:28:00 +01:00
parent f75176ec69
commit d6c88d87dd
58 changed files with 1197 additions and 11922 deletions

View File

@@ -130,50 +130,69 @@ class KnowledgeManager:
entries = session.query(KnowledgeEntry).filter(KnowledgeEntry.is_active == True).all()
if not entries:
logger.warning("知识库中没有活跃条目")
return []
# 计算相似度
texts = [entry.question + " " + entry.answer for entry in entries]
# 确保向量器已训练
try:
vocab_ok = hasattr(self.vectorizer, 'vocabulary_') and bool(self.vectorizer.vocabulary_)
if not vocab_ok:
self.vectorizer.fit(texts)
query_vector = self.vectorizer.transform([query])
entry_vectors = self.vectorizer.transform(texts)
similarities = cosine_similarity(query_vector, entry_vectors)[0]
except Exception as vec_err:
logger.warning(f"TF-IDF搜索失败回退到子串匹配: {vec_err}")
# 回退:子串匹配评分
similarities = []
q = query.strip()
for t in texts:
if not q:
similarities.append(0.0)
else:
score = 1.0 if q in t else 0.0
similarities.append(score)
similarities = np.array(similarities, dtype=float)
# 获取top_k个最相似的条目
top_indices = np.argsort(similarities)[-top_k:][::-1]
# 如果查询为空,返回所有条目
if not query.strip():
logger.info("查询为空,返回所有条目")
return [{
"id": entry.id,
"question": entry.question,
"answer": entry.answer,
"category": entry.category,
"confidence_score": entry.confidence_score,
"similarity_score": 1.0,
"usage_count": entry.usage_count,
"is_verified": entry.is_verified
} for entry in entries[:top_k]]
# 使用简化的关键词匹配搜索
q = query.strip().lower()
results = []
for idx in top_indices:
if similarities[idx] > 0.1: # 最小相似度阈值
entry = entries[idx]
for entry in entries:
# 组合问题和答案进行搜索
search_text = (entry.question + " " + entry.answer).lower()
# 计算匹配分数
score = 0.0
# 完全匹配
if q in search_text:
score = 1.0
else:
# 分词匹配
query_words = q.split()
text_words = search_text.split()
# 计算单词匹配度
matched_words = 0
for word in query_words:
if word in text_words:
matched_words += 1
if matched_words > 0:
score = matched_words / len(query_words) * 0.8
# 如果分数大于0添加到结果中
if score > 0:
results.append({
"id": entry.id,
"question": entry.question,
"answer": entry.answer,
"category": entry.category,
"confidence_score": entry.confidence_score,
"similarity_score": float(similarities[idx]),
"similarity_score": score,
"usage_count": entry.usage_count,
"is_verified": entry.is_verified
})
# 按相似度排序并返回top_k个结果
results.sort(key=lambda x: x['similarity_score'], reverse=True)
results = results[:top_k]
logger.info(f"搜索查询 '{query}' 返回 {len(results)} 个结果")
return results
except Exception as e:

View File

@@ -9,6 +9,7 @@ import sys
import os
import logging
from datetime import datetime, timedelta
from typing import Dict, Any
from flask import Flask, render_template, request, jsonify, send_from_directory
from flask_cors import CORS
@@ -16,14 +17,11 @@ from flask_cors import CORS
# 添加项目根目录到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.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 Conversation, Alert, WorkOrder
from src.core.query_optimizer import query_optimizer
from src.web.service_manager import service_manager
# 导入蓝图
from src.web.blueprints.alerts import alerts_bp
@@ -33,6 +31,7 @@ from src.web.blueprints.knowledge import knowledge_bp
from src.web.blueprints.monitoring import monitoring_bp
from src.web.blueprints.system import system_bp
from src.web.blueprints.feishu_sync import feishu_sync_bp
from src.web.blueprints.core import core_bp
# 配置日志
logger = logging.getLogger(__name__)
@@ -59,43 +58,7 @@ UPLOAD_FOLDER = 'uploads'
app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
app.config['MAX_CONTENT_LENGTH'] = 16 * 1024 * 1024 # 16MB max file size
# 延迟初始化TSP助手和Agent助手避免启动时重复初始化
assistant = None
agent_assistant = None
chat_manager = None
vehicle_manager = None
def get_assistant():
"""获取TSP助手实例懒加载"""
global assistant
if assistant is None:
from src.main import TSPAssistant
assistant = TSPAssistant()
return assistant
def get_agent_assistant():
"""获取Agent助手实例懒加载"""
global agent_assistant
if agent_assistant is None:
from src.agent_assistant import TSPAgentAssistant
agent_assistant = TSPAgentAssistant()
return agent_assistant
def get_chat_manager():
"""获取聊天管理器实例(懒加载)"""
global chat_manager
if chat_manager is None:
from src.dialogue.realtime_chat import RealtimeChatManager
chat_manager = RealtimeChatManager()
return chat_manager
def get_vehicle_manager():
"""获取车辆数据管理器实例(懒加载)"""
global vehicle_manager
if vehicle_manager is None:
from src.vehicle.vehicle_data_manager import VehicleDataManager
vehicle_manager = VehicleDataManager()
return vehicle_manager
# 使用统一的服务管理器
# 注册蓝图
app.register_blueprint(alerts_bp)
@@ -105,6 +68,7 @@ app.register_blueprint(knowledge_bp)
app.register_blueprint(monitoring_bp)
app.register_blueprint(system_bp)
app.register_blueprint(feishu_sync_bp)
app.register_blueprint(core_bp)
# 页面路由
@app.route('/')
@@ -132,157 +96,15 @@ def uploaded_file(filename):
"""提供上传文件的下载服务"""
return send_from_directory(app.config['UPLOAD_FOLDER'], filename)
# 核心API路由
@app.route('/api/health')
def get_health():
"""获取系统健康状态附加1小时业务指标"""
try:
base = get_assistant().get_system_health() or {}
# 追加数据库近1小时指标
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/rules')
def get_rules():
"""获取预警规则列表"""
try:
rules = get_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:
from src.analytics.alert_system import AlertRule, AlertLevel, AlertType
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 = get_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/<rule_name>', methods=['PUT'])
def update_rule(rule_name):
"""更新预警规则"""
try:
data = request.get_json()
success = get_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/<rule_name>', methods=['DELETE'])
def delete_rule(rule_name):
"""删除预警规则"""
try:
success = get_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 = get_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 = get_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 = get_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 = get_assistant().check_alerts()
return jsonify({
"success": True,
"alerts": alerts,
"count": len(alerts)
})
except Exception as e:
return jsonify({"error": str(e)}), 500
# ============================================================================
# 核心API路由 - 已迁移到蓝图
# ============================================================================
# 健康检查、预警规则、监控状态等核心功能已迁移到 core 蓝图
# 分析数据相关功能也已迁移到 core 蓝图
# ============================================================================
# 实时对话相关路由
# ============================================================================
@app.route('/api/chat/session', methods=['POST'])
def create_chat_session():
"""创建对话会话"""
@@ -291,7 +113,7 @@ def create_chat_session():
user_id = data.get('user_id', 'anonymous')
work_order_id = data.get('work_order_id')
session_id = get_chat_manager().create_session(user_id, work_order_id)
session_id = service_manager.get_chat_manager().create_session(user_id, work_order_id)
return jsonify({
"success": True,
@@ -312,7 +134,7 @@ def send_chat_message():
if not session_id or not message:
return jsonify({"error": "缺少必要参数"}), 400
result = get_chat_manager().process_message(session_id, message)
result = service_manager.get_chat_manager().process_message(session_id, message)
return jsonify(result)
except Exception as e:
return jsonify({"error": str(e)}), 500
@@ -321,7 +143,7 @@ def send_chat_message():
def get_chat_history(session_id):
"""获取对话历史"""
try:
history = get_chat_manager().get_session_history(session_id)
history = service_manager.get_chat_manager().get_session_history(session_id)
return jsonify({
"success": True,
"history": history
@@ -343,7 +165,7 @@ def create_work_order():
if not session_id or not title or not description:
return jsonify({"error": "缺少必要参数"}), 400
result = get_chat_manager().create_work_order(session_id, title, description, category, priority)
result = service_manager.get_chat_manager().create_work_order(session_id, title, description, category, priority)
return jsonify(result)
except Exception as e:
return jsonify({"error": str(e)}), 500
@@ -352,7 +174,7 @@ def create_work_order():
def get_work_order_status(work_order_id):
"""获取工单状态"""
try:
result = get_chat_manager().get_work_order_status(work_order_id)
result = service_manager.get_chat_manager().get_work_order_status(work_order_id)
return jsonify(result)
except Exception as e:
return jsonify({"error": str(e)}), 500
@@ -361,7 +183,7 @@ def get_work_order_status(work_order_id):
def end_chat_session(session_id):
"""结束对话会话"""
try:
success = get_chat_manager().end_session(session_id)
success = service_manager.get_chat_manager().end_session(session_id)
return jsonify({
"success": success,
"message": "会话已结束" if success else "结束会话失败"
@@ -374,7 +196,7 @@ def get_active_sessions():
"""获取活跃会话列表"""
try:
# 确保chat_manager已初始化
manager = get_chat_manager()
manager = service_manager.get_chat_manager()
sessions = manager.get_active_sessions()
return jsonify({
"success": True,
@@ -384,12 +206,14 @@ def get_active_sessions():
logger.error(f"获取活跃会话失败: {e}")
return jsonify({"error": str(e)}), 500
# ============================================================================
# Agent相关API
# ============================================================================
@app.route('/api/agent/status')
def get_agent_status():
"""获取Agent状态"""
try:
status = get_agent_assistant().get_agent_status()
status = service_manager.get_agent_assistant().get_agent_status()
return jsonify(status)
except Exception as e:
return jsonify({"error": str(e)}), 500
@@ -399,7 +223,7 @@ def get_agent_action_history():
"""获取Agent动作执行历史"""
try:
limit = request.args.get('limit', 50, type=int)
history = get_agent_assistant().get_action_history(limit)
history = service_manager.get_agent_assistant().get_action_history(limit)
return jsonify({
"success": True,
"history": history,
@@ -413,7 +237,7 @@ def trigger_sample_action():
"""触发示例动作"""
try:
import asyncio
result = asyncio.run(get_agent_assistant().trigger_sample_actions())
result = asyncio.run(service_manager.get_agent_assistant().trigger_sample_actions())
return jsonify(result)
except Exception as e:
return jsonify({"error": str(e)}), 500
@@ -422,7 +246,7 @@ def trigger_sample_action():
def clear_agent_history():
"""清空Agent执行历史"""
try:
result = get_agent_assistant().clear_execution_history()
result = service_manager.get_agent_assistant().clear_execution_history()
return jsonify(result)
except Exception as e:
return jsonify({"error": str(e)}), 500
@@ -431,7 +255,7 @@ def clear_agent_history():
def get_llm_stats():
"""获取LLM使用统计"""
try:
stats = get_agent_assistant().get_llm_usage_stats()
stats = service_manager.get_agent_assistant().get_llm_usage_stats()
return jsonify({
"success": True,
"stats": stats
@@ -445,7 +269,7 @@ def toggle_agent_mode():
try:
data = request.get_json()
enabled = data.get('enabled', True)
success = get_agent_assistant().toggle_agent_mode(enabled)
success = service_manager.get_agent_assistant().toggle_agent_mode(enabled)
return jsonify({
"success": success,
"message": f"Agent模式已{'启用' if enabled else '禁用'}"
@@ -457,7 +281,7 @@ def toggle_agent_mode():
def start_agent_monitoring():
"""启动Agent监控"""
try:
success = get_agent_assistant().start_proactive_monitoring()
success = service_manager.get_agent_assistant().start_proactive_monitoring()
return jsonify({
"success": success,
"message": "Agent监控已启动" if success else "启动失败"
@@ -469,7 +293,7 @@ def start_agent_monitoring():
def stop_agent_monitoring():
"""停止Agent监控"""
try:
success = get_agent_assistant().stop_proactive_monitoring()
success = service_manager.get_agent_assistant().stop_proactive_monitoring()
return jsonify({
"success": success,
"message": "Agent监控已停止" if success else "停止失败"
@@ -481,7 +305,7 @@ def stop_agent_monitoring():
def proactive_monitoring():
"""主动监控检查"""
try:
result = get_agent_assistant().run_proactive_monitoring()
result = service_manager.get_agent_assistant().run_proactive_monitoring()
return jsonify(result)
except Exception as e:
return jsonify({"error": str(e)}), 500
@@ -490,7 +314,7 @@ def proactive_monitoring():
def intelligent_analysis():
"""智能分析"""
try:
analysis = get_agent_assistant().run_intelligent_analysis()
analysis = service_manager.get_agent_assistant().run_intelligent_analysis()
return jsonify({"success": True, "analysis": analysis})
except Exception as e:
return jsonify({"error": str(e)}), 500
@@ -507,7 +331,7 @@ def agent_chat():
return jsonify({"error": "消息不能为空"}), 400
# 使用Agent助手处理消息
agent_assistant = get_agent_assistant()
agent_assistant = service_manager.get_agent_assistant()
# 模拟Agent处理实际应该调用真正的Agent处理逻辑
import asyncio
@@ -527,12 +351,15 @@ def agent_chat():
except Exception as e:
return jsonify({"error": str(e)}), 500
# ============================================================================
# Agent 工具统计与自定义工具
# ============================================================================
@app.route('/api/agent/tools/stats')
def get_agent_tools_stats():
try:
tools = get_agent_assistant().agent_core.tool_manager.get_available_tools()
performance = get_agent_assistant().agent_core.tool_manager.get_tool_performance_report()
agent_assistant = service_manager.get_agent_assistant()
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,
@@ -552,7 +379,7 @@ def execute_agent_tool():
return jsonify({"error": "缺少工具名称tool"}), 400
import asyncio
result = asyncio.run(get_agent_assistant().agent_core.tool_manager.execute_tool(tool_name, parameters))
result = asyncio.run(service_manager.get_agent_assistant().agent_core.tool_manager.execute_tool(tool_name, parameters))
return jsonify(result)
except Exception as e:
return jsonify({"error": str(e)}), 500
@@ -570,7 +397,7 @@ def register_custom_tool():
def _placeholder_tool(**kwargs):
return {"message": f"自定义工具 {name} 已登记(占位),当前不可执行", "params": kwargs}
get_agent_assistant().agent_core.tool_manager.register_tool(
service_manager.get_agent_assistant().agent_core.tool_manager.register_tool(
name,
_placeholder_tool,
metadata={"description": description, "custom": True}
@@ -582,55 +409,21 @@ def register_custom_tool():
@app.route('/api/agent/tools/unregister/<name>', methods=['DELETE'])
def unregister_custom_tool(name):
try:
success = get_agent_assistant().agent_core.tool_manager.unregister_tool(name)
success = service_manager.get_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/analytics')
def get_analytics():
"""获取分析数据"""
try:
# 支持多种参数
time_range = request.args.get('timeRange', request.args.get('days', '30'))
dimension = request.args.get('dimension', 'workorders')
# 参数验证
try:
days = int(time_range)
if days <= 0 or days > 365:
days = 30
except (ValueError, TypeError):
days = 30
analytics = generate_db_analytics(days, dimension)
# 确保返回的数据结构完整
if not analytics:
analytics = {
"workorders": {"total": 0, "open": 0, "resolved": 0, "trend": []},
"alerts": {"total": 0, "critical": 0, "warning": 0, "trend": []},
"conversations": {"total": 0, "avg_confidence": 0, "trend": []},
"performance": {"avg_response_time": 0, "success_rate": 0}
}
return jsonify(analytics)
except Exception as e:
logger.error(f"获取分析数据失败: {e}")
return jsonify({"error": str(e)}), 500
def generate_db_analytics(days: int, dimension: str) -> dict:
"""基于数据库生成真实分析数据(优化版)"""
# 使用优化后的查询
return query_optimizer.get_analytics_optimized(days)
# ============================================================================
# 分析相关API - 已迁移到 core 蓝图
# ============================================================================
@app.route('/api/analytics/export')
def export_analytics():
"""导出分析报告"""
try:
# 生成Excel报告使用数据库真实数据
analytics = generate_db_analytics(30, 'workorders')
analytics = query_optimizer.get_analytics_optimized(30)
# 创建工作簿
from openpyxl import Workbook
@@ -664,7 +457,9 @@ def export_analytics():
except Exception as e:
return jsonify({"error": str(e)}), 500
# ============================================================================
# 车辆数据相关API
# ============================================================================
@app.route('/api/vehicle/data')
def get_vehicle_data():
"""获取车辆数据"""
@@ -674,12 +469,13 @@ def get_vehicle_data():
data_type = request.args.get('data_type')
limit = request.args.get('limit', 10, type=int)
vehicle_mgr = service_manager.get_vehicle_manager()
if vehicle_vin:
data = vehicle_manager.get_vehicle_data_by_vin(vehicle_vin, data_type, limit)
data = vehicle_mgr.get_vehicle_data_by_vin(vehicle_vin, data_type, limit)
elif vehicle_id:
data = vehicle_manager.get_vehicle_data(vehicle_id, data_type, limit)
data = vehicle_mgr.get_vehicle_data(vehicle_id, data_type, limit)
else:
data = vehicle_manager.search_vehicle_data(limit=limit)
data = vehicle_mgr.search_vehicle_data(limit=limit)
return jsonify(data)
except Exception as e:
@@ -689,7 +485,7 @@ def get_vehicle_data():
def get_latest_vehicle_data_by_vin(vehicle_vin):
"""按VIN获取车辆最新数据"""
try:
data = vehicle_manager.get_latest_vehicle_data_by_vin(vehicle_vin)
data = service_manager.get_vehicle_manager().get_latest_vehicle_data_by_vin(vehicle_vin)
return jsonify(data)
except Exception as e:
return jsonify({"error": str(e)}), 500
@@ -698,7 +494,7 @@ def get_latest_vehicle_data_by_vin(vehicle_vin):
def get_latest_vehicle_data(vehicle_id):
"""获取车辆最新数据"""
try:
data = vehicle_manager.get_latest_vehicle_data(vehicle_id)
data = service_manager.get_vehicle_manager().get_latest_vehicle_data(vehicle_id)
return jsonify(data)
except Exception as e:
return jsonify({"error": str(e)}), 500
@@ -707,7 +503,7 @@ def get_latest_vehicle_data(vehicle_id):
def get_vehicle_summary(vehicle_id):
"""获取车辆数据摘要"""
try:
summary = vehicle_manager.get_vehicle_summary(vehicle_id)
summary = service_manager.get_vehicle_manager().get_vehicle_summary(vehicle_id)
return jsonify(summary)
except Exception as e:
return jsonify({"error": str(e)}), 500
@@ -717,7 +513,7 @@ def add_vehicle_data():
"""添加车辆数据"""
try:
data = request.get_json()
success = vehicle_manager.add_vehicle_data(
success = service_manager.get_vehicle_manager().add_vehicle_data(
vehicle_id=data['vehicle_id'],
data_type=data['data_type'],
data_value=data['data_value'],
@@ -731,12 +527,14 @@ def add_vehicle_data():
def init_sample_vehicle_data():
"""初始化示例车辆数据"""
try:
success = vehicle_manager.add_sample_vehicle_data()
success = service_manager.get_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连接"""
@@ -778,6 +576,9 @@ def test_model_response():
except Exception as e:
return jsonify({"success": False, "error": str(e)}), 500
# ============================================================================
# 应用启动配置
# ============================================================================
# 飞书同步功能已合并到主页面,不再需要单独的路由
if __name__ == '__main__':

File diff suppressed because it is too large Load Diff

View File

@@ -1,740 +0,0 @@
# -*- coding: utf-8 -*-
"""
TSP助手预警管理Web应用
提供预警系统的Web界面和API接口
重构版本 - 使用蓝图架构
"""
import sys
import os
import logging
from datetime import datetime, timedelta
from flask import Flask, render_template, request, jsonify, send_from_directory
from flask_cors import CORS
# 添加项目根目录到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.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 Conversation, Alert, WorkOrder
from src.core.query_optimizer import query_optimizer
# 导入蓝图
from src.web.blueprints.alerts import alerts_bp
from src.web.blueprints.workorders import workorders_bp
from src.web.blueprints.conversations import conversations_bp
from src.web.blueprints.knowledge import knowledge_bp
from src.web.blueprints.monitoring import monitoring_bp
from src.web.blueprints.system import system_bp
# 配置日志
logger = logging.getLogger(__name__)
# 抑制 /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())
# 创建Flask应用
app = Flask(__name__)
CORS(app)
# 配置上传文件夹
UPLOAD_FOLDER = 'uploads'
app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
app.config['MAX_CONTENT_LENGTH'] = 16 * 1024 * 1024 # 16MB max file size
# 延迟初始化TSP助手和Agent助手避免启动时重复初始化
assistant = None
agent_assistant = None
chat_manager = None
vehicle_manager = None
def get_assistant():
"""获取TSP助手实例懒加载"""
global assistant
if assistant is None:
assistant = TSPAssistant()
return assistant
def get_agent_assistant():
"""获取Agent助手实例懒加载"""
global agent_assistant
if agent_assistant is None:
agent_assistant = TSPAgentAssistant()
return agent_assistant
def get_chat_manager():
"""获取聊天管理器实例(懒加载)"""
global chat_manager
if chat_manager is None:
chat_manager = RealtimeChatManager()
return chat_manager
def get_vehicle_manager():
"""获取车辆数据管理器实例(懒加载)"""
global vehicle_manager
if vehicle_manager is None:
vehicle_manager = VehicleDataManager()
return vehicle_manager
# 注册蓝图
app.register_blueprint(alerts_bp)
app.register_blueprint(workorders_bp)
app.register_blueprint(conversations_bp)
app.register_blueprint(knowledge_bp)
app.register_blueprint(monitoring_bp)
app.register_blueprint(system_bp)
# 页面路由
@app.route('/')
def index():
"""主页 - 综合管理平台"""
return render_template('dashboard.html')
@app.route('/alerts')
def alerts():
"""预警管理页面"""
return render_template('index.html')
@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('/uploads/<filename>')
def uploaded_file(filename):
"""提供上传文件的下载服务"""
return send_from_directory(app.config['UPLOAD_FOLDER'], filename)
# 核心API路由
@app.route('/api/health')
def get_health():
"""获取系统健康状态附加1小时业务指标"""
try:
base = get_assistant().get_system_health() or {}
# 追加数据库近1小时指标
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/rules')
def get_rules():
"""获取预警规则列表"""
try:
rules = get_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:
from src.analytics.alert_system import AlertRule, AlertLevel, AlertType
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 = get_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/<rule_name>', methods=['PUT'])
def update_rule(rule_name):
"""更新预警规则"""
try:
data = request.get_json()
success = get_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/<rule_name>', methods=['DELETE'])
def delete_rule(rule_name):
"""删除预警规则"""
try:
success = get_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 = get_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 = get_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 = get_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 = get_assistant().check_alerts()
return jsonify({
"success": True,
"alerts": alerts,
"count": len(alerts)
})
except Exception as e:
return jsonify({"error": str(e)}), 500
# 实时对话相关路由
@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 = get_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 = get_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/<session_id>')
def get_chat_history(session_id):
"""获取对话历史"""
try:
history = get_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 = get_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/<int:work_order_id>')
def get_work_order_status(work_order_id):
"""获取工单状态"""
try:
result = get_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/<session_id>', methods=['DELETE'])
def end_chat_session(session_id):
"""结束对话会话"""
try:
success = get_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 = get_agent_assistant().run_intelligent_analysis()
return jsonify({"success": True, "analysis": analysis})
except Exception as e:
return jsonify({"error": str(e)}), 500
@app.route('/api/agent/chat', methods=['POST'])
def agent_chat():
"""Agent对话接口"""
try:
data = request.get_json()
message = data.get('message', '')
context = data.get('context', {})
if not message:
return jsonify({"error": "消息不能为空"}), 400
# 使用Agent助手处理消息
agent_assistant = get_agent_assistant()
# 模拟Agent处理实际应该调用真正的Agent处理逻辑
import asyncio
result = asyncio.run(agent_assistant.process_message_agent(
message=message,
user_id=context.get('user_id', 'admin'),
work_order_id=None,
enable_proactive=True
))
return jsonify({
"success": True,
"response": result.get('response', 'Agent已处理您的请求'),
"actions": result.get('actions', []),
"status": result.get('status', 'completed')
})
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/<name>', 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/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:
"""基于数据库生成真实分析数据(优化版)"""
# 使用优化后的查询
return query_optimizer.get_analytics_optimized(days)
@app.route('/api/analytics/export')
def export_analytics():
"""导出分析报告"""
try:
# 生成Excel报告使用数据库真实数据
analytics = generate_db_analytics(30, 'workorders')
# 创建工作簿
from openpyxl import Workbook
from openpyxl.styles import Font
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)
from flask import send_file
return send_file(report_path, as_attachment=True, download_name='analytics_report.xlsx')
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/<vehicle_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/<vehicle_id>/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/<vehicle_id>/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
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)

View File

@@ -1,218 +0,0 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
TSP智能助手 - 新版Web应用
支持Vue 3前端和传统HTML页面
"""
import os
import sys
import json
import logging
from datetime import datetime
from flask import Flask, render_template, jsonify, request, send_from_directory
from flask_cors import CORS
from werkzeug.exceptions import NotFound
# 添加项目根目录到Python路径
current_dir = os.path.dirname(os.path.abspath(__file__))
project_root = os.path.dirname(os.path.dirname(current_dir))
sys.path.insert(0, project_root)
from src.core.database import get_db_connection
from src.analytics.monitor_service import MonitorService
from src.analytics.alert_system import AlertSystem
from src.dialogue.dialogue_manager import DialogueManager
from src.knowledge_base.knowledge_manager import KnowledgeManager
from src.integrations.workorder_sync import WorkOrderSync
from src.integrations.flexible_field_mapper import FlexibleFieldMapper
# 配置日志
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)
def create_app():
"""创建Flask应用"""
app = Flask(__name__)
# 配置
app.config['SECRET_KEY'] = 'tsp-assistant-secret-key'
app.config['JSON_AS_ASCII'] = False
# 启用CORS
CORS(app, resources={
r"/api/*": {"origins": ["http://localhost:3000", "http://127.0.0.1:3000"]},
r"/ws/*": {"origins": ["http://localhost:3000", "http://127.0.0.1:3000"]}
})
# 初始化服务
monitor_service = MonitorService()
alert_system = AlertSystem()
dialogue_manager = DialogueManager()
knowledge_manager = KnowledgeManager()
workorder_sync = WorkOrderSync()
field_mapper = FlexibleFieldMapper()
# 注册蓝图
from src.web.blueprints.alerts import alerts_bp
from src.web.blueprints.conversations import conversations_bp
from src.web.blueprints.knowledge import knowledge_bp
from src.web.blueprints.monitoring import monitoring_bp
from src.web.blueprints.system import system_bp
from src.web.blueprints.feishu_sync import feishu_sync_bp
from src.web.blueprints.workorders import workorders_bp
app.register_blueprint(alerts_bp, url_prefix='/api/alerts')
app.register_blueprint(conversations_bp, url_prefix='/api/conversations')
app.register_blueprint(knowledge_bp, url_prefix='/api/knowledge')
app.register_blueprint(monitoring_bp, url_prefix='/api/monitor')
app.register_blueprint(system_bp, url_prefix='/api/system')
app.register_blueprint(feishu_sync_bp, url_prefix='/api/feishu')
app.register_blueprint(workorders_bp, url_prefix='/api/workorders')
# 静态文件路由
@app.route('/static/dist/<path:filename>')
def serve_frontend_dist(filename):
"""提供Vue前端构建文件"""
dist_path = os.path.join(current_dir, 'static', 'dist')
return send_from_directory(dist_path, filename)
@app.route('/')
def index():
"""首页 - 重定向到Vue应用"""
return render_template('index_new.html')
@app.route('/chat')
def chat():
"""聊天页面 - 重定向到Vue应用"""
return render_template('index_new.html')
@app.route('/alerts')
def alerts():
"""预警页面 - 重定向到Vue应用"""
return render_template('index_new.html')
@app.route('/knowledge')
def knowledge():
"""知识库页面 - 重定向到Vue应用"""
return render_template('index_new.html')
@app.route('/field-mapping')
def field_mapping():
"""字段映射页面 - 重定向到Vue应用"""
return render_template('index_new.html')
@app.route('/system')
def system():
"""系统设置页面 - 重定向到Vue应用"""
return render_template('index_new.html')
# API路由
@app.route('/api/health')
def health():
"""系统健康检查"""
try:
# 获取系统健康状态
health_data = {
'status': 'healthy',
'timestamp': datetime.now().isoformat(),
'health_score': 85.5,
'services': {
'database': 'healthy',
'monitor': 'healthy',
'alerts': 'healthy',
'knowledge': 'healthy'
}
}
return jsonify(health_data)
except Exception as e:
logger.error(f"健康检查失败: {e}")
return jsonify({
'status': 'unhealthy',
'error': str(e),
'timestamp': datetime.now().isoformat()
}), 500
@app.route('/api/status')
def status():
"""系统状态"""
try:
status_data = {
'version': '1.4.0',
'uptime': '2天 14小时 32分钟',
'python_version': '3.9.7',
'database': 'SQLite 3.36.0',
'timestamp': datetime.now().isoformat()
}
return jsonify(status_data)
except Exception as e:
logger.error(f"获取状态失败: {e}")
return jsonify({'error': str(e)}), 500
@app.route('/api/performance')
def performance():
"""性能指标"""
try:
import psutil
performance_data = {
'cpu_usage': psutil.cpu_percent(),
'memory_usage': psutil.virtual_memory().percent,
'disk_usage': psutil.disk_usage('/').percent,
'timestamp': datetime.now().isoformat()
}
return jsonify(performance_data)
except ImportError:
# 如果没有psutil返回模拟数据
performance_data = {
'cpu_usage': 45.2,
'memory_usage': 68.5,
'disk_usage': 23.1,
'timestamp': datetime.now().isoformat()
}
return jsonify(performance_data)
except Exception as e:
logger.error(f"获取性能指标失败: {e}")
return jsonify({'error': str(e)}), 500
# 错误处理
@app.errorhandler(404)
def not_found(error):
"""404错误处理"""
if request.path.startswith('/api/'):
return jsonify({'error': 'API endpoint not found'}), 404
return render_template('index_new.html')
@app.errorhandler(500)
def internal_error(error):
"""500错误处理"""
logger.error(f"内部服务器错误: {error}")
if request.path.startswith('/api/'):
return jsonify({'error': 'Internal server error'}), 500
return render_template('index_new.html')
return app
if __name__ == '__main__':
app = create_app()
# 检查前端构建文件
dist_path = os.path.join(os.path.dirname(__file__), 'static', 'dist')
if not os.path.exists(dist_path):
print("警告: 前端构建文件不存在,请先运行 build_frontend.bat")
print("或者访问 http://localhost:5000/legacy 使用传统页面")
print("TSP智能助手Web服务器启动中...")
print("前端地址: http://localhost:5000")
print("API地址: http://localhost:5000/api")
print("WebSocket: ws://localhost:8765")
app.run(
host='0.0.0.0',
port=5000,
debug=True,
threaded=True
)

View File

@@ -5,26 +5,18 @@
"""
from flask import Blueprint, request, jsonify
from src.main import TSPAssistant
from src.web.service_manager import service_manager
from src.web.error_handlers import handle_api_errors, create_error_response, create_success_response
from src.analytics.alert_system import AlertRule, AlertLevel, AlertType
alerts_bp = Blueprint('alerts', __name__, url_prefix='/api/alerts')
def get_assistant():
"""获取TSP助手实例懒加载"""
global _assistant
if '_assistant' not in globals():
_assistant = TSPAssistant()
return _assistant
@alerts_bp.route('')
@handle_api_errors
def get_alerts():
"""获取预警列表"""
try:
alerts = get_assistant().get_active_alerts()
return jsonify(alerts)
except Exception as e:
return jsonify({"error": str(e)}), 500
alerts = service_manager.get_assistant().get_active_alerts()
return jsonify(alerts)
@alerts_bp.route('', methods=['POST'])
def create_alert():

293
src/web/blueprints/core.py Normal file
View File

@@ -0,0 +1,293 @@
# -*- coding: utf-8 -*-
"""
核心功能蓝图
处理系统核心功能的API路由
"""
from flask import Blueprint, request, jsonify
from typing import Dict, Any
from datetime import datetime, timedelta
from src.web.service_manager import service_manager
from src.web.error_handlers import handle_api_errors, create_error_response, create_success_response
from src.core.database import db_manager
from src.core.models import Conversation, Alert, WorkOrder
from src.core.query_optimizer import query_optimizer
core_bp = Blueprint('core', __name__, url_prefix='/api')
@core_bp.route('/health')
@handle_api_errors
def get_health() -> Dict[str, Any]:
"""获取系统健康状态附加1小时业务指标"""
base = service_manager.get_assistant().get_system_health() or {}
# 追加数据库近1小时指标
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)
@core_bp.route('/rules')
@handle_api_errors
def get_rules() -> Dict[str, Any]:
"""获取预警规则列表"""
rules = service_manager.get_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)
@core_bp.route('/rules', methods=['POST'])
@handle_api_errors
def create_rule() -> Dict[str, Any]:
"""创建预警规则"""
from src.analytics.alert_system import AlertRule, AlertLevel, AlertType
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 = service_manager.get_assistant().alert_system.add_custom_rule(rule)
if success:
return jsonify(create_success_response(message="规则创建成功"))
else:
return create_error_response("规则创建失败", 400)
@core_bp.route('/rules/<rule_name>', methods=['PUT'])
@handle_api_errors
def update_rule(rule_name: str) -> Dict[str, Any]:
"""更新预警规则"""
data = request.get_json()
success = service_manager.get_assistant().alert_system.update_rule(rule_name, **data)
if success:
return jsonify(create_success_response(message="规则更新成功"))
else:
return create_error_response("规则更新失败", 400)
@core_bp.route('/rules/<rule_name>', methods=['DELETE'])
@handle_api_errors
def delete_rule(rule_name: str) -> Dict[str, Any]:
"""删除预警规则"""
success = service_manager.get_assistant().alert_system.delete_rule(rule_name)
if success:
return jsonify(create_success_response(message="规则删除成功"))
else:
return create_error_response("规则删除失败", 400)
@core_bp.route('/monitor/start', methods=['POST'])
@handle_api_errors
def start_monitoring() -> Dict[str, Any]:
"""启动监控服务"""
success = service_manager.get_assistant().start_monitoring()
if success:
return jsonify(create_success_response(message="监控服务已启动"))
else:
return create_error_response("启动监控服务失败", 400)
@core_bp.route('/monitor/stop', methods=['POST'])
@handle_api_errors
def stop_monitoring() -> Dict[str, Any]:
"""停止监控服务"""
success = service_manager.get_assistant().stop_monitoring()
if success:
return jsonify(create_success_response(message="监控服务已停止"))
else:
return create_error_response("停止监控服务失败", 400)
@core_bp.route('/monitor/status')
@handle_api_errors
def get_monitor_status() -> Dict[str, Any]:
"""获取监控服务状态"""
health = service_manager.get_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)
})
@core_bp.route('/check-alerts', methods=['POST'])
@handle_api_errors
def check_alerts() -> Dict[str, Any]:
"""手动检查预警"""
alerts = service_manager.get_assistant().check_alerts()
return jsonify({
"success": True,
"alerts": alerts,
"count": len(alerts)
})
@core_bp.route('/analytics')
@handle_api_errors
def get_analytics() -> Dict[str, Any]:
"""获取分析数据"""
# 支持多种参数
time_range = request.args.get('timeRange', request.args.get('days', '30'))
dimension = request.args.get('dimension', 'workorders')
# 参数验证
try:
days = int(time_range)
if days <= 0 or days > 365:
days = 30
except (ValueError, TypeError):
days = 30
analytics = query_optimizer.get_analytics_optimized(days)
# 确保返回的数据结构完整
if not analytics:
analytics = {
"workorders": {"total": 0, "open": 0, "resolved": 0, "trend": []},
"alerts": {"total": 0, "critical": 0, "warning": 0, "trend": []},
"conversations": {"total": 0, "avg_confidence": 0, "trend": []},
"performance": {"avg_response_time": 0, "success_rate": 0}
}
return jsonify(analytics)
@core_bp.route('/batch-delete/workorders', methods=['POST'])
@handle_api_errors
def batch_delete_workorders() -> Dict[str, Any]:
"""批量删除工单"""
data = request.get_json()
workorder_ids = data.get('ids', [])
if not workorder_ids:
return create_error_response("请选择要删除的工单", 400)
try:
with db_manager.get_session() as session:
# 验证工单是否存在
existing_workorders = session.query(WorkOrder).filter(WorkOrder.id.in_(workorder_ids)).all()
existing_ids = [wo.id for wo in existing_workorders]
if len(existing_ids) != len(workorder_ids):
missing_ids = set(workorder_ids) - set(existing_ids)
return create_error_response(f"工单不存在: {list(missing_ids)}", 404)
# 先删除相关的工单建议记录
from src.core.models import WorkOrderSuggestion
session.query(WorkOrderSuggestion).filter(WorkOrderSuggestion.work_order_id.in_(workorder_ids)).delete(synchronize_session=False)
# 再删除工单
deleted_count = session.query(WorkOrder).filter(WorkOrder.id.in_(workorder_ids)).delete(synchronize_session=False)
session.commit()
return jsonify(create_success_response(
data={"deleted_count": deleted_count},
message=f"成功删除 {deleted_count} 个工单"
))
except Exception as e:
return create_error_response(f"批量删除工单失败: {str(e)}", 500)
@core_bp.route('/batch-delete/alerts', methods=['POST'])
@handle_api_errors
def batch_delete_alerts() -> Dict[str, Any]:
"""批量删除预警"""
data = request.get_json()
alert_ids = data.get('ids', [])
if not alert_ids:
return create_error_response("请选择要删除的预警", 400)
try:
with db_manager.get_session() as session:
# 验证预警是否存在
existing_alerts = session.query(Alert).filter(Alert.id.in_(alert_ids)).all()
existing_ids = [alert.id for alert in existing_alerts]
if len(existing_ids) != len(alert_ids):
missing_ids = set(alert_ids) - set(existing_ids)
return create_error_response(f"预警不存在: {list(missing_ids)}", 404)
# 删除预警
deleted_count = session.query(Alert).filter(Alert.id.in_(alert_ids)).delete(synchronize_session=False)
session.commit()
return jsonify(create_success_response(
data={"deleted_count": deleted_count},
message=f"成功删除 {deleted_count} 个预警"
))
except Exception as e:
return create_error_response(f"批量删除预警失败: {str(e)}", 500)
@core_bp.route('/batch-delete/knowledge', methods=['POST'])
@handle_api_errors
def batch_delete_knowledge() -> Dict[str, Any]:
"""批量删除知识库条目"""
data = request.get_json()
knowledge_ids = data.get('ids', [])
if not knowledge_ids:
return create_error_response("请选择要删除的知识库条目", 400)
try:
with db_manager.get_session() as session:
# 验证知识库条目是否存在
existing_knowledge = session.query(KnowledgeEntry).filter(KnowledgeEntry.id.in_(knowledge_ids)).all()
existing_ids = [kb.id for kb in existing_knowledge]
if len(existing_ids) != len(knowledge_ids):
missing_ids = set(knowledge_ids) - set(existing_ids)
return create_error_response(f"知识库条目不存在: {list(missing_ids)}", 404)
# 删除知识库条目
deleted_count = session.query(KnowledgeEntry).filter(KnowledgeEntry.id.in_(knowledge_ids)).delete(synchronize_session=False)
session.commit()
return jsonify(create_success_response(
data={"deleted_count": deleted_count},
message=f"成功删除 {deleted_count} 个知识库条目"
))
except Exception as e:
return create_error_response(f"批量删除知识库条目失败: {str(e)}", 500)

View File

@@ -49,10 +49,23 @@ def search_knowledge():
"""搜索知识库"""
try:
query = request.args.get('q', '')
# 这里应该调用知识库管理器的搜索方法
results = get_assistant().search_knowledge(query, top_k=5)
return jsonify(results.get('results', []))
import logging
logger = logging.getLogger(__name__)
logger.info(f"搜索查询: '{query}'")
if not query.strip():
logger.info("查询为空,返回空结果")
return jsonify([])
# 直接调用知识库管理器的搜索方法
assistant = get_assistant()
results = assistant.knowledge_manager.search_knowledge(query, top_k=5)
logger.info(f"搜索结果数量: {len(results)}")
return jsonify(results)
except Exception as e:
import logging
logger = logging.getLogger(__name__)
logger.error(f"搜索知识库失败: {e}")
return jsonify({"error": str(e)}), 500
@knowledge_bp.route('', methods=['POST'])

74
src/web/error_handlers.py Normal file
View File

@@ -0,0 +1,74 @@
# -*- coding: utf-8 -*-
"""
错误处理装饰器和工具
提供统一的错误处理模式
"""
import logging
from functools import wraps
from typing import Callable, Any, Dict
from flask import jsonify
logger = logging.getLogger(__name__)
def handle_api_errors(func: Callable) -> Callable:
"""API错误处理装饰器"""
@wraps(func)
def wrapper(*args, **kwargs) -> Dict[str, Any]:
try:
return func(*args, **kwargs)
except ValueError as e:
logger.warning(f"参数错误 {func.__name__}: {e}")
return jsonify({"error": f"参数错误: {str(e)}"}), 400
except PermissionError as e:
logger.warning(f"权限错误 {func.__name__}: {e}")
return jsonify({"error": f"权限不足: {str(e)}"}), 403
except FileNotFoundError as e:
logger.warning(f"文件未找到 {func.__name__}: {e}")
return jsonify({"error": f"文件未找到: {str(e)}"}), 404
except Exception as e:
logger.error(f"未处理错误 {func.__name__}: {e}")
return jsonify({"error": f"服务器内部错误: {str(e)}"}), 500
return wrapper
def handle_database_errors(func: Callable) -> Callable:
"""数据库错误处理装饰器"""
@wraps(func)
def wrapper(*args, **kwargs) -> Dict[str, Any]:
try:
return func(*args, **kwargs)
except Exception as e:
logger.error(f"数据库错误 {func.__name__}: {e}")
return jsonify({"error": "数据库操作失败"}), 500
return wrapper
def handle_service_errors(func: Callable) -> Callable:
"""服务错误处理装饰器"""
@wraps(func)
def wrapper(*args, **kwargs) -> Dict[str, Any]:
try:
return func(*args, **kwargs)
except Exception as e:
logger.error(f"服务错误 {func.__name__}: {e}")
return jsonify({"error": "服务暂时不可用"}), 503
return wrapper
def create_error_response(message: str, status_code: int = 500, details: str = None) -> tuple:
"""创建标准错误响应"""
response = {"error": message}
if details:
response["details"] = details
logger.error(f"错误响应: {message} - {details}")
return jsonify(response), status_code
def create_success_response(data: Any = None, message: str = "操作成功") -> Dict[str, Any]:
"""创建标准成功响应"""
response = {"success": True, "message": message}
if data is not None:
response["data"] = data
return response

View File

@@ -0,0 +1,71 @@
# -*- coding: utf-8 -*-
"""
服务管理器
统一管理各种服务的懒加载实例
"""
from typing import Optional, Dict, Any
import logging
logger = logging.getLogger(__name__)
class ServiceManager:
"""服务管理器 - 统一管理各种服务的懒加载实例"""
def __init__(self):
self._services: Dict[str, Any] = {}
def get_service(self, service_name: str, factory_func):
"""获取服务实例(懒加载)"""
if service_name not in self._services:
try:
self._services[service_name] = factory_func()
logger.info(f"服务 {service_name} 已初始化")
except Exception as e:
logger.error(f"初始化服务 {service_name} 失败: {e}")
raise
return self._services[service_name]
def get_assistant(self):
"""获取TSP助手实例"""
def factory():
from src.main import TSPAssistant
return TSPAssistant()
return self.get_service('assistant', factory)
def get_agent_assistant(self):
"""获取Agent助手实例"""
def factory():
from src.agent_assistant import TSPAgentAssistant
return TSPAgentAssistant()
return self.get_service('agent_assistant', factory)
def get_chat_manager(self):
"""获取聊天管理器实例"""
def factory():
from src.dialogue.realtime_chat import RealtimeChatManager
return RealtimeChatManager()
return self.get_service('chat_manager', factory)
def get_vehicle_manager(self):
"""获取车辆数据管理器实例"""
def factory():
from src.vehicle.vehicle_data_manager import VehicleDataManager
return VehicleDataManager()
return self.get_service('vehicle_manager', factory)
def clear_service(self, service_name: str):
"""清除指定服务实例"""
if service_name in self._services:
del self._services[service_name]
logger.info(f"服务 {service_name} 已清除")
def clear_all_services(self):
"""清除所有服务实例"""
self._services.clear()
logger.info("所有服务实例已清除")
# 全局服务管理器实例
service_manager = ServiceManager()

View File

@@ -1373,19 +1373,37 @@ class TSPDashboard {
return;
}
// 添加预警列表的批量操作头部
const headerHtml = `
<div class="d-flex justify-content-between align-items-center mb-3">
<div class="d-flex align-items-center">
<input type="checkbox" id="select-all-alerts" class="form-check-input me-2" onchange="dashboard.toggleSelectAllAlerts()">
<label for="select-all-alerts" class="form-check-label">全选</label>
</div>
<div class="btn-group">
<button class="btn btn-sm btn-danger" id="batch-delete-alerts" onclick="dashboard.batchDeleteAlerts()" disabled>
<i class="fas fa-trash me-1"></i>批量删除
</button>
</div>
</div>
`;
const alertsHtml = alerts.map(alert => `
<div class="alert-item ${alert.level}">
<div class="d-flex justify-content-between align-items-start">
<div class="flex-grow-1">
<div class="d-flex align-items-center mb-2">
<span class="badge bg-${this.getAlertColor(alert.level)} me-2">${this.getLevelText(alert.level)}</span>
<span class="fw-bold">${alert.rule_name || '未知规则'}</span>
<span class="ms-auto text-muted small">${this.formatTime(alert.created_at)}</span>
</div>
<div class="alert-message mb-2">${alert.message}</div>
<div class="alert-meta text-muted small">
类型: ${this.getTypeText(alert.alert_type)} |
级别: ${this.getLevelText(alert.level)}
<div class="d-flex align-items-start">
<input type="checkbox" class="form-check-input me-2 alert-checkbox" value="${alert.id}" onchange="dashboard.updateBatchDeleteAlertsButton()">
<div class="flex-grow-1">
<div class="d-flex align-items-center mb-2">
<span class="badge bg-${this.getAlertColor(alert.level)} me-2">${this.getLevelText(alert.level)}</span>
<span class="fw-bold">${alert.rule_name || '未知规则'}</span>
<span class="ms-auto text-muted small">${this.formatTime(alert.created_at)}</span>
</div>
<div class="alert-message mb-2">${alert.message}</div>
<div class="alert-meta text-muted small">
类型: ${this.getTypeText(alert.alert_type)} |
级别: ${this.getLevelText(alert.level)}
</div>
</div>
</div>
<div class="ms-3">
@@ -1397,7 +1415,7 @@ class TSPDashboard {
</div>
`).join('');
container.innerHTML = alertsHtml;
container.innerHTML = headerHtml + alertsHtml;
}
updateAlertStatistics(alerts) {
@@ -1413,6 +1431,72 @@ class TSPDashboard {
document.getElementById('total-alerts-count').textContent = stats.total || 0;
}
// 预警批量删除功能
toggleSelectAllAlerts() {
const selectAllCheckbox = document.getElementById('select-all-alerts');
const alertCheckboxes = document.querySelectorAll('.alert-checkbox');
alertCheckboxes.forEach(checkbox => {
checkbox.checked = selectAllCheckbox.checked;
});
this.updateBatchDeleteAlertsButton();
}
updateBatchDeleteAlertsButton() {
const selectedCheckboxes = document.querySelectorAll('.alert-checkbox:checked');
const batchDeleteBtn = document.getElementById('batch-delete-alerts');
if (batchDeleteBtn) {
batchDeleteBtn.disabled = selectedCheckboxes.length === 0;
batchDeleteBtn.textContent = selectedCheckboxes.length > 0
? `批量删除 (${selectedCheckboxes.length})`
: '批量删除';
}
}
async batchDeleteAlerts() {
const selectedCheckboxes = document.querySelectorAll('.alert-checkbox:checked');
const selectedIds = Array.from(selectedCheckboxes).map(cb => parseInt(cb.value));
if (selectedIds.length === 0) {
this.showNotification('请选择要删除的预警', 'warning');
return;
}
if (!confirm(`确定要删除选中的 ${selectedIds.length} 个预警吗?此操作不可撤销。`)) {
return;
}
try {
const response = await fetch('/api/batch-delete/alerts', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ ids: selectedIds })
});
const data = await response.json();
if (data.success) {
this.showNotification(data.message, 'success');
// 清除缓存并强制刷新
this.cache.delete('alerts');
await this.loadAlerts();
// 重置批量删除按钮状态
this.updateBatchDeleteAlertsButton();
} else {
this.showNotification(data.error || '批量删除失败', 'error');
}
} catch (error) {
console.error('批量删除预警失败:', error);
this.showNotification('批量删除预警失败', 'error');
}
}
async resolveAlert(alertId) {
try {
const response = await fetch(`/api/alerts/${alertId}/resolve`, { method: 'POST' });
@@ -1456,19 +1540,37 @@ class TSPDashboard {
return;
}
// 添加知识库列表的批量操作头部
const headerHtml = `
<div class="d-flex justify-content-between align-items-center mb-3">
<div class="d-flex align-items-center">
<input type="checkbox" id="select-all-knowledge" class="form-check-input me-2" onchange="dashboard.toggleSelectAllKnowledge()">
<label for="select-all-knowledge" class="form-check-label">全选</label>
</div>
<div class="btn-group">
<button class="btn btn-sm btn-danger" id="batch-delete-knowledge" onclick="dashboard.batchDeleteKnowledge()" disabled>
<i class="fas fa-trash me-1"></i>批量删除
</button>
</div>
</div>
`;
const knowledgeHtml = knowledge.map(item => `
<div class="knowledge-item">
<div class="d-flex justify-content-between align-items-start">
<div class="flex-grow-1">
<h6 class="mb-1">${item.question}</h6>
<p class="text-muted mb-2">${item.answer}</p>
<div class="d-flex gap-3">
<small class="text-muted">分类: ${item.category}</small>
<small class="text-muted">置信度: ${Math.round(item.confidence_score * 100)}%</small>
<small class="text-muted">使用次数: ${item.usage_count || 0}</small>
<span class="badge ${item.is_verified ? 'bg-success' : 'bg-warning'}">
${item.is_verified ? '已验证' : '未验证'}
</span>
<div class="d-flex align-items-start">
<input type="checkbox" class="form-check-input me-2 knowledge-checkbox" value="${item.id}" onchange="dashboard.updateBatchDeleteKnowledgeButton()">
<div class="flex-grow-1">
<h6 class="mb-1">${item.question}</h6>
<p class="text-muted mb-2">${item.answer}</p>
<div class="d-flex gap-3">
<small class="text-muted">分类: ${item.category}</small>
<small class="text-muted">置信度: ${Math.round(item.confidence_score * 100)}%</small>
<small class="text-muted">使用次数: ${item.usage_count || 0}</small>
<span class="badge ${item.is_verified ? 'bg-success' : 'bg-warning'}">
${item.is_verified ? '已验证' : '未验证'}
</span>
</div>
</div>
</div>
<div class="ms-3">
@@ -1490,7 +1592,7 @@ class TSPDashboard {
</div>
`).join('');
container.innerHTML = knowledgeHtml;
container.innerHTML = headerHtml + knowledgeHtml;
}
updateKnowledgePagination(data) {
@@ -1750,24 +1852,115 @@ class TSPDashboard {
}
}
// 工单管理
async loadWorkOrders() {
// 知识库批量删除功能
toggleSelectAllKnowledge() {
const selectAllCheckbox = document.getElementById('select-all-knowledge');
const knowledgeCheckboxes = document.querySelectorAll('.knowledge-checkbox');
knowledgeCheckboxes.forEach(checkbox => {
checkbox.checked = selectAllCheckbox.checked;
});
this.updateBatchDeleteKnowledgeButton();
}
updateBatchDeleteKnowledgeButton() {
const selectedCheckboxes = document.querySelectorAll('.knowledge-checkbox:checked');
const batchDeleteBtn = document.getElementById('batch-delete-knowledge');
if (batchDeleteBtn) {
batchDeleteBtn.disabled = selectedCheckboxes.length === 0;
batchDeleteBtn.textContent = selectedCheckboxes.length > 0
? `批量删除 (${selectedCheckboxes.length})`
: '批量删除';
}
}
async batchDeleteKnowledge() {
const selectedCheckboxes = document.querySelectorAll('.knowledge-checkbox:checked');
const selectedIds = Array.from(selectedCheckboxes).map(cb => parseInt(cb.value));
if (selectedIds.length === 0) {
this.showNotification('请选择要删除的知识库条目', 'warning');
return;
}
if (!confirm(`确定要删除选中的 ${selectedIds.length} 个知识库条目吗?此操作不可撤销。`)) {
return;
}
try {
const statusFilter = document.getElementById('workorder-status-filter').value;
const priorityFilter = document.getElementById('workorder-priority-filter').value;
const response = await fetch('/api/batch-delete/knowledge', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ ids: selectedIds })
});
const data = await response.json();
if (data.success) {
this.showNotification(data.message, 'success');
// 清除缓存并强制刷新
this.cache.delete('knowledge');
await this.loadKnowledge();
// 重置批量删除按钮状态
this.updateBatchDeleteKnowledgeButton();
} else {
this.showNotification(data.error || '批量删除失败', 'error');
}
} catch (error) {
console.error('批量删除知识库条目失败:', error);
this.showNotification('批量删除知识库条目失败', 'error');
}
}
// 工单管理
async loadWorkOrders(forceRefresh = false) {
try {
const statusFilter = document.getElementById('workorder-status-filter')?.value || 'all';
const priorityFilter = document.getElementById('workorder-priority-filter')?.value || 'all';
let url = '/api/workorders';
const params = new URLSearchParams();
if (statusFilter !== 'all') params.append('status', statusFilter);
if (priorityFilter !== 'all') params.append('priority', priorityFilter);
// 添加强制刷新参数
if (forceRefresh) {
params.append('_t', Date.now().toString());
}
if (params.toString()) url += '?' + params.toString();
const response = await fetch(url);
const response = await fetch(url, {
cache: forceRefresh ? 'no-cache' : 'default',
headers: forceRefresh ? {
'Cache-Control': 'no-cache',
'Pragma': 'no-cache'
} : {}
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const workorders = await response.json();
this.updateWorkOrdersDisplay(workorders);
this.updateWorkOrderStatistics(workorders);
// 更新缓存
this.cache.set('workorders', {
data: workorders,
timestamp: Date.now()
});
} catch (error) {
console.error('加载工单失败:', error);
this.showNotification('加载工单失败: ' + error.message, 'error');
}
}
@@ -1779,17 +1972,35 @@ class TSPDashboard {
return;
}
// 添加工单列表的批量操作头部
const headerHtml = `
<div class="d-flex justify-content-between align-items-center mb-3">
<div class="d-flex align-items-center">
<input type="checkbox" id="select-all-workorders" class="form-check-input me-2" onchange="dashboard.toggleSelectAllWorkorders()">
<label for="select-all-workorders" class="form-check-label">全选</label>
</div>
<div class="btn-group">
<button class="btn btn-sm btn-danger" id="batch-delete-workorders" onclick="dashboard.batchDeleteWorkorders()" disabled>
<i class="fas fa-trash me-1"></i>批量删除
</button>
</div>
</div>
`;
const workordersHtml = workorders.map(workorder => `
<div class="work-order-item">
<div class="d-flex justify-content-between align-items-start">
<div class="flex-grow-1">
<h6 class="mb-1">${workorder.title}</h6>
<p class="text-muted mb-2">${workorder.description ? workorder.description.substring(0, 100) + (workorder.description.length > 100 ? '...' : '') : '无处理过程'}</p>
<div class="d-flex gap-3">
<span class="badge bg-${this.getPriorityColor(workorder.priority)}">${this.getPriorityText(workorder.priority)}</span>
<span class="badge bg-${this.getStatusColor(workorder.status)}">${this.getStatusText(workorder.status)}</span>
<small class="text-muted">分类: ${workorder.category}</small>
<small class="text-muted">创建时间: ${new Date(workorder.created_at).toLocaleString()}</small>
<div class="d-flex align-items-start">
<input type="checkbox" class="form-check-input me-2 workorder-checkbox" value="${workorder.id}" onchange="dashboard.updateBatchDeleteButton()">
<div class="flex-grow-1">
<h6 class="mb-1">${workorder.title}</h6>
<p class="text-muted mb-2">${workorder.description ? workorder.description.substring(0, 100) + (workorder.description.length > 100 ? '...' : '') : '无处理过程'}</p>
<div class="d-flex gap-3">
<span class="badge bg-${this.getPriorityColor(workorder.priority)}">${this.getPriorityText(workorder.priority)}</span>
<span class="badge bg-${this.getStatusColor(workorder.status)}">${this.getStatusText(workorder.status)}</span>
<small class="text-muted">分类: ${workorder.category}</small>
<small class="text-muted">创建时间: ${new Date(workorder.created_at).toLocaleString()}</small>
</div>
</div>
</div>
<div class="ms-3">
@@ -1809,7 +2020,7 @@ class TSPDashboard {
</div>
`).join('');
container.innerHTML = workordersHtml;
container.innerHTML = headerHtml + workordersHtml;
}
updateWorkOrderStatistics(workorders) {
@@ -1825,6 +2036,73 @@ class TSPDashboard {
document.getElementById('workorders-resolved').textContent = stats.resolved || 0;
}
// 工单批量删除功能
toggleSelectAllWorkorders() {
const selectAllCheckbox = document.getElementById('select-all-workorders');
const workorderCheckboxes = document.querySelectorAll('.workorder-checkbox');
workorderCheckboxes.forEach(checkbox => {
checkbox.checked = selectAllCheckbox.checked;
});
this.updateBatchDeleteButton();
}
updateBatchDeleteButton() {
const selectedCheckboxes = document.querySelectorAll('.workorder-checkbox:checked');
const batchDeleteBtn = document.getElementById('batch-delete-workorders');
if (batchDeleteBtn) {
batchDeleteBtn.disabled = selectedCheckboxes.length === 0;
batchDeleteBtn.textContent = selectedCheckboxes.length > 0
? `批量删除 (${selectedCheckboxes.length})`
: '批量删除';
}
}
async batchDeleteWorkorders() {
const selectedCheckboxes = document.querySelectorAll('.workorder-checkbox:checked');
const selectedIds = Array.from(selectedCheckboxes).map(cb => parseInt(cb.value));
if (selectedIds.length === 0) {
this.showNotification('请选择要删除的工单', 'warning');
return;
}
if (!confirm(`确定要删除选中的 ${selectedIds.length} 个工单吗?此操作不可撤销。`)) {
return;
}
try {
const response = await fetch('/api/batch-delete/workorders', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ ids: selectedIds })
});
const data = await response.json();
if (data.success) {
this.showNotification(data.message, 'success');
// 清除缓存并强制刷新
this.cache.delete('workorders');
await this.loadWorkOrders(true); // 强制刷新
await this.loadAnalytics();
// 重置批量删除按钮状态
this.updateBatchDeleteButton();
} else {
this.showNotification(data.error || '批量删除失败', 'error');
}
} catch (error) {
console.error('批量删除工单失败:', error);
this.showNotification('批量删除工单失败', 'error');
}
}
async createWorkOrder() {
const title = document.getElementById('wo-title').value.trim();
const description = document.getElementById('wo-description').value.trim();
@@ -3407,6 +3685,7 @@ class TSPDashboard {
// 更新统计卡片
updateStatisticsCards(data) {
// 更新工单统计
const total = data.workorders?.total || 0;
const open = data.workorders?.open || 0;
const resolved = data.workorders?.resolved || 0;
@@ -3417,6 +3696,49 @@ class TSPDashboard {
document.getElementById('resolvedWorkorders').textContent = resolved;
document.getElementById('avgSatisfaction').textContent = avgSatisfaction.toFixed(1);
// 更新预警统计
const alertTotal = data.alerts?.total || 0;
const alertActive = data.alerts?.active || 0;
const alertCritical = data.alerts?.by_level?.critical || 0;
const alertWarning = data.alerts?.by_level?.warning || 0;
const alertError = data.alerts?.by_level?.error || 0;
// 更新预警统计显示
if (document.getElementById('critical-alerts')) {
document.getElementById('critical-alerts').textContent = alertCritical;
}
if (document.getElementById('warning-alerts')) {
document.getElementById('warning-alerts').textContent = alertWarning;
}
if (document.getElementById('error-alerts')) {
document.getElementById('error-alerts').textContent = alertError;
}
if (document.getElementById('total-alerts-count')) {
document.getElementById('total-alerts-count').textContent = alertTotal;
}
// 更新性能统计
const performanceScore = data.performance?.score || 0;
const performanceTrend = data.performance?.trend || 'stable';
if (document.getElementById('performance-score')) {
document.getElementById('performance-score').textContent = performanceScore.toFixed(1);
}
if (document.getElementById('performance-trend')) {
document.getElementById('performance-trend').textContent = this.getPerformanceTrendText(performanceTrend);
}
// 更新满意度统计
const satisfactionAvg = data.satisfaction?.average || 0;
const satisfactionCount = data.satisfaction?.count || 0;
if (document.getElementById('satisfaction-avg')) {
document.getElementById('satisfaction-avg').textContent = satisfactionAvg.toFixed(1);
}
if (document.getElementById('satisfaction-count')) {
document.getElementById('satisfaction-count').textContent = satisfactionCount;
}
// 更新进度条
if (total > 0) {
document.getElementById('openProgress').style.width = `${(open / total) * 100}%`;
@@ -3469,9 +3791,62 @@ class TSPDashboard {
// 更新分布图表
updateDistributionChart(data) {
const categories = data.workorders?.by_category || {};
const labels = Object.keys(categories);
const values = Object.values(categories);
const currentDimension = document.getElementById('dataDimension')?.value || 'workorders';
let labels, values, title, backgroundColor;
if (currentDimension === 'alerts') {
// 预警级别分布
const alertLevels = data.alerts?.by_level || {};
labels = Object.keys(alertLevels);
values = Object.values(alertLevels);
title = '预警级别分布';
backgroundColor = [
'#FF6384', // critical - 红色
'#FFCE56', // warning - 黄色
'#36A2EB', // error - 蓝色
'#4BC0C0', // info - 青色
'#9966FF' // 其他
];
} else if (currentDimension === 'performance') {
// 性能指标分布
const performanceMetrics = data.performance?.by_level || {};
labels = Object.keys(performanceMetrics);
values = Object.values(performanceMetrics);
title = '性能指标分布';
backgroundColor = [
'#28a745', // 优秀 - 绿色
'#ffc107', // 良好 - 黄色
'#fd7e14', // 一般 - 橙色
'#dc3545' // 差 - 红色
];
} else if (currentDimension === 'satisfaction') {
// 满意度分布
const satisfactionLevels = data.satisfaction?.by_level || {};
labels = Object.keys(satisfactionLevels);
values = Object.values(satisfactionLevels);
title = '满意度分布';
backgroundColor = [
'#28a745', // 非常满意 - 绿色
'#ffc107', // 满意 - 黄色
'#fd7e14', // 一般 - 橙色
'#dc3545' // 不满意 - 红色
];
} else {
// 工单分类分布
const categories = data.workorders?.by_category || {};
labels = Object.keys(categories);
values = Object.values(categories);
title = '工单分类分布';
backgroundColor = [
'#FF6384',
'#36A2EB',
'#FFCE56',
'#4BC0C0',
'#9966FF',
'#FF9F40'
];
}
const chartConfig = {
type: 'doughnut',
@@ -3479,14 +3854,7 @@ class TSPDashboard {
labels: labels,
datasets: [{
data: values,
backgroundColor: [
'#FF6384',
'#36A2EB',
'#FFCE56',
'#4BC0C0',
'#9966FF',
'#FF9F40'
]
backgroundColor: backgroundColor
}]
},
options: {
@@ -3495,7 +3863,7 @@ class TSPDashboard {
plugins: {
title: {
display: true,
text: '工单分类分布'
text: title
},
legend: {
display: true,
@@ -3576,23 +3944,72 @@ class TSPDashboard {
this.charts.priorityChart.destroy();
}
const priorities = data.workorders?.by_priority || {};
const labels = Object.keys(priorities).map(p => this.getPriorityText(p));
const values = Object.values(priorities);
const currentDimension = document.getElementById('dataDimension')?.value || 'workorders';
let labels, values, title, backgroundColor, label;
if (currentDimension === 'alerts') {
// 预警严重程度分布
const alertSeverities = data.alerts?.by_severity || {};
labels = Object.keys(alertSeverities).map(s => this.getSeverityText(s));
values = Object.values(alertSeverities);
title = '预警严重程度分布';
label = '预警数量';
backgroundColor = [
'#28a745', // low - 绿色
'#ffc107', // medium - 黄色
'#fd7e14', // high - 橙色
'#dc3545' // critical - 红色
];
} else if (currentDimension === 'performance') {
// 性能指标分布
const performanceMetrics = data.performance?.by_metric || {};
labels = Object.keys(performanceMetrics);
values = Object.values(performanceMetrics);
title = '性能指标分布';
label = '性能值';
backgroundColor = [
'#28a745', // 优秀 - 绿色
'#ffc107', // 良好 - 黄色
'#fd7e14', // 一般 - 橙色
'#dc3545' // 差 - 红色
];
} else if (currentDimension === 'satisfaction') {
// 满意度分布
const satisfactionLevels = data.satisfaction?.by_level || {};
labels = Object.keys(satisfactionLevels).map(s => this.getSatisfactionText(s));
values = Object.values(satisfactionLevels);
title = '满意度分布';
label = '满意度数量';
backgroundColor = [
'#28a745', // 非常满意 - 绿色
'#ffc107', // 满意 - 黄色
'#fd7e14', // 一般 - 橙色
'#dc3545' // 不满意 - 红色
];
} else {
// 工单优先级分布
const priorities = data.workorders?.by_priority || {};
labels = Object.keys(priorities).map(p => this.getPriorityText(p));
values = Object.values(priorities);
title = '工单优先级分布';
label = '工单数量';
backgroundColor = [
'#28a745', // 低 - 绿色
'#ffc107', // 中 - 黄色
'#fd7e14', // 高 - 橙色
'#dc3545' // 紧急 - 红色
];
}
this.charts.priorityChart = new Chart(ctx, {
type: 'bar',
data: {
labels: labels,
datasets: [{
label: '工单数量',
label: label,
data: values,
backgroundColor: [
'#28a745', // 低 - 绿色
'#ffc107', // 中 - 黄色
'#fd7e14', // 高 - 橙色
'#dc3545' // 紧急 - 红色
]
backgroundColor: backgroundColor
}]
},
options: {
@@ -3601,7 +4018,7 @@ class TSPDashboard {
plugins: {
title: {
display: true,
text: '优先级分布'
text: title
}
},
scales: {
@@ -3618,33 +4035,124 @@ class TSPDashboard {
const trendData = data.trend || [];
const labels = trendData.map(item => item.date);
const workorders = trendData.map(item => item.workorders);
const alerts = trendData.map(item => item.alerts);
const performance = trendData.map(item => item.performance || 0);
const satisfaction = trendData.map(item => item.satisfaction || 0);
if (chartType === 'pie' || chartType === 'doughnut') {
const categories = data.workorders?.by_category || {};
return {
labels: Object.keys(categories),
datasets: [{
data: Object.values(categories),
backgroundColor: [
'#FF6384',
'#36A2EB',
'#FFCE56',
'#4BC0C0',
'#9966FF',
'#FF9F40'
]
}]
};
// 根据数据维度选择显示内容
const currentDimension = document.getElementById('dataDimension')?.value || 'workorders';
if (currentDimension === 'alerts') {
const alertLevels = data.alerts?.by_level || {};
return {
labels: Object.keys(alertLevels),
datasets: [{
data: Object.values(alertLevels),
backgroundColor: [
'#FF6384', // critical - 红色
'#FFCE56', // warning - 黄色
'#36A2EB', // error - 蓝色
'#4BC0C0', // info - 青色
'#9966FF' // 其他
]
}]
};
} else if (currentDimension === 'performance') {
// 性能指标分布
const performanceMetrics = data.performance || {};
return {
labels: Object.keys(performanceMetrics),
datasets: [{
data: Object.values(performanceMetrics),
backgroundColor: [
'#28a745', // 优秀 - 绿色
'#ffc107', // 良好 - 黄色
'#fd7e14', // 一般 - 橙色
'#dc3545' // 差 - 红色
]
}]
};
} else if (currentDimension === 'satisfaction') {
// 满意度分布
const satisfactionLevels = data.satisfaction?.by_level || {};
return {
labels: Object.keys(satisfactionLevels),
datasets: [{
data: Object.values(satisfactionLevels),
backgroundColor: [
'#28a745', // 非常满意 - 绿色
'#ffc107', // 满意 - 黄色
'#fd7e14', // 一般 - 橙色
'#dc3545' // 不满意 - 红色
]
}]
};
} else {
const categories = data.workorders?.by_category || {};
return {
labels: Object.keys(categories),
datasets: [{
data: Object.values(categories),
backgroundColor: [
'#FF6384',
'#36A2EB',
'#FFCE56',
'#4BC0C0',
'#9966FF',
'#FF9F40'
]
}]
};
}
} else {
return {
labels: labels,
datasets: [{
// 线图和柱状图根据数据维度显示不同内容
const currentDimension = document.getElementById('dataDimension')?.value || 'workorders';
const datasets = [];
if (currentDimension === 'performance') {
// 性能指标图表
datasets.push({
label: '性能指标',
data: performance,
borderColor: '#28a745',
backgroundColor: 'rgba(40, 167, 69, 0.1)',
tension: chartType === 'line' ? 0.4 : 0
});
} else if (currentDimension === 'satisfaction') {
// 满意度图表
datasets.push({
label: '满意度评分',
data: satisfaction,
borderColor: '#ffc107',
backgroundColor: 'rgba(255, 193, 7, 0.1)',
tension: chartType === 'line' ? 0.4 : 0
});
} else {
// 默认显示工单和预警数据
datasets.push({
label: '工单数量',
data: workorders,
borderColor: '#36A2EB',
backgroundColor: 'rgba(54, 162, 235, 0.1)',
tension: chartType === 'line' ? 0.4 : 0
}]
});
// 如果有预警数据,添加预警数据集
if (alerts.some(alert => alert > 0)) {
datasets.push({
label: '预警数量',
data: alerts,
borderColor: '#FF6384',
backgroundColor: 'rgba(255, 99, 132, 0.1)',
tension: chartType === 'line' ? 0.4 : 0
});
}
}
return {
labels: labels,
datasets: datasets
};
}
}
@@ -4281,6 +4789,35 @@ class TSPDashboard {
return priorityMap[priority] || priority;
}
getSeverityText(severity) {
const severityMap = {
'low': '低',
'medium': '中',
'high': '高',
'critical': '严重'
};
return severityMap[severity] || severity;
}
getSatisfactionText(level) {
const satisfactionMap = {
'very_satisfied': '非常满意',
'satisfied': '满意',
'neutral': '一般',
'dissatisfied': '不满意'
};
return satisfactionMap[level] || level;
}
getPerformanceTrendText(trend) {
const trendMap = {
'up': '上升',
'down': '下降',
'stable': '稳定'
};
return trendMap[trend] || trend;
}
getPriorityColor(priority) {
const colorMap = {
'low': 'secondary',

View File

@@ -1,133 +0,0 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>TSP智能助手</title>
<link rel="icon" type="image/svg+xml" href="/static/dist/vite.svg">
<!-- 预加载关键资源 -->
<link rel="preload" href="/static/dist/assets/index.css" as="style">
<link rel="preload" href="/static/dist/assets/index.js" as="script">
<!-- 样式 -->
<link rel="stylesheet" href="/static/dist/assets/index.css">
<!-- 如果构建文件不存在,显示提示信息 -->
<style>
.build-missing {
display: none;
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: #f5f5f5;
z-index: 9999;
padding: 20px;
text-align: center;
}
.build-missing.show {
display: block;
}
.build-missing h1 {
color: #333;
margin-bottom: 20px;
}
.build-missing p {
color: #666;
margin-bottom: 20px;
line-height: 1.6;
}
.build-missing .actions {
display: flex;
gap: 12px;
justify-content: center;
flex-wrap: wrap;
}
.build-missing .btn {
padding: 12px 24px;
border: none;
border-radius: 6px;
cursor: pointer;
text-decoration: none;
font-size: 14px;
transition: all 0.3s;
}
.build-missing .btn-primary {
background: #409eff;
color: white;
}
.build-missing .btn-primary:hover {
background: #337ecc;
}
.build-missing .btn-secondary {
background: #909399;
color: white;
}
.build-missing .btn-secondary:hover {
background: #73767a;
}
</style>
</head>
<body>
<div id="app"></div>
<!-- 构建文件缺失提示 -->
<div id="build-missing" class="build-missing">
<h1>🚀 TSP智能助手</h1>
<p>
前端构建文件不存在,请先构建前端应用。<br>
或者使用传统HTML页面。
</p>
<div class="actions">
<button class="btn btn-primary" onclick="buildFrontend()">
📦 构建前端
</button>
<a href="/legacy" class="btn btn-secondary">
📄 使用传统页面
</a>
</div>
</div>
<!-- 脚本 -->
<script>
// 检查构建文件是否存在
function checkBuildFiles() {
const cssLink = document.querySelector('link[href="/static/dist/assets/index.css"]');
const jsScript = document.createElement('script');
jsScript.src = '/static/dist/assets/index.js';
jsScript.onerror = function() {
document.getElementById('build-missing').classList.add('show');
};
jsScript.onload = function() {
document.getElementById('build-missing').classList.remove('show');
};
document.head.appendChild(jsScript);
}
// 构建前端
function buildFrontend() {
alert('请运行 build_frontend.bat 脚本构建前端应用');
}
// 页面加载完成后检查
document.addEventListener('DOMContentLoaded', checkBuildFiles);
</script>
<!-- 尝试加载Vue应用 -->
<script type="module" src="/static/dist/assets/index.js"></script>
</body>
</html>