1345 lines
51 KiB
Python
1345 lines
51 KiB
Python
|
||
# -*- coding: utf-8 -*-
|
||
"""
|
||
TSP助手预警管理Web应用
|
||
提供预警系统的Web界面和API接口
|
||
"""
|
||
|
||
import sys
|
||
import os
|
||
import json
|
||
import logging
|
||
import pandas as pd
|
||
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
|
||
|
||
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/<int:alert_id>/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/<rule_name>', 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/<rule_name>', 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/<session_id>')
|
||
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/<int:work_order_id>')
|
||
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/<session_id>', 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/<int:knowledge_id>', 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/<int:knowledge_id>', 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/<int:knowledge_id>', 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/<int:workorder_id>')
|
||
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/<int:workorder_id>', 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/<int:workorder_id>/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/<int:workorder_id>/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/<int:workorder_id>/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/<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/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/<filename>')
|
||
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/<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)
|