refactor: 移除冗余文件并优化代码结构
- 删除多个不再使用的脚本和配置文件,包括 `auto_push.bat`, `check_and_fix_users.py`, `init.sql` 等。 - 新增 `git_push.bat` 和 `git_push.sh` 脚本以简化 Git 推送流程。 - 更新 `README.md` 以反映最新的功能和结构变化。 - 优化前端代码,添加新的页面和组件,提升用户体验。 此提交旨在清理项目结构并增强代码可维护性。
This commit is contained in:
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -167,6 +167,17 @@ class AlertSystem:
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"检查规则 {rule_name} 失败: {e}")
|
||||
# 如果规则检查失败,也可以考虑生成一个系统级别的预警
|
||||
system_alert = {
|
||||
"rule_name": f"系统预警 - 规则检查失败: {rule_name}",
|
||||
"alert_type": AlertType.SYSTEM.value,
|
||||
"level": AlertLevel.ERROR.value,
|
||||
"message": f"规则 \'{rule_name}\' 检查失败: {e}",
|
||||
"data": {"error": str(e)},
|
||||
"timestamp": datetime.now().isoformat(),
|
||||
"rule_id": "system_rule_check_failure"
|
||||
}
|
||||
self._save_alert(system_alert) # 保存系统预警
|
||||
|
||||
return triggered_alerts
|
||||
|
||||
@@ -238,6 +249,8 @@ class AlertSystem:
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"获取规则数据失败: {e}")
|
||||
# 重新抛出异常,让上层调用者决定如何处理
|
||||
raise
|
||||
|
||||
return data
|
||||
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -11,6 +11,7 @@ class Config:
|
||||
|
||||
# 数据库配置
|
||||
DATABASE_URL = "mysql+pymysql://tsp_assistant:123456@jeason.online/tsp_assistant?charset=utf8mb4"
|
||||
# DATABASE_URL = "sqlite:///local_test.db" # 本地测试数据库
|
||||
|
||||
# 知识库配置
|
||||
KNOWLEDGE_BASE_PATH = "data/knowledge_base"
|
||||
|
||||
@@ -27,10 +27,10 @@ class DatabaseConfig:
|
||||
@dataclass
|
||||
class LLMConfig:
|
||||
"""LLM配置"""
|
||||
provider: str = "openai"
|
||||
api_key: str = ""
|
||||
provider: str = "qwen"
|
||||
api_key: str = "sk-c0dbefa1718d46eaa897199135066f00"
|
||||
base_url: str = "https://dashscope.aliyuncs.com/compatible-mode/v1"
|
||||
model: str = "qwen-turbo"
|
||||
model: str = "qwen-plus-latest"
|
||||
temperature: float = 0.7
|
||||
max_tokens: int = 2000
|
||||
timeout: int = 30
|
||||
|
||||
Binary file not shown.
BIN
src/core/__pycache__/auth_manager.cpython-311.pyc
Normal file
BIN
src/core/__pycache__/auth_manager.cpython-311.pyc
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
144
src/core/auth_manager.py
Normal file
144
src/core/auth_manager.py
Normal file
@@ -0,0 +1,144 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
认证管理器
|
||||
处理用户登录、注册、会话管理等功能
|
||||
"""
|
||||
|
||||
import jwt
|
||||
import hashlib
|
||||
from datetime import datetime, timedelta
|
||||
from typing import Optional, Dict, Any
|
||||
from flask import current_app
|
||||
|
||||
from src.core.database import db_manager
|
||||
from src.core.models import User
|
||||
|
||||
|
||||
class AuthManager:
|
||||
"""认证管理器"""
|
||||
|
||||
def __init__(self):
|
||||
self.secret_key = "your-secret-key-change-this-in-production" # 应该从配置中读取
|
||||
self.token_expiry = timedelta(hours=24)
|
||||
|
||||
def hash_password(self, password: str) -> str:
|
||||
"""密码哈希"""
|
||||
return hashlib.sha256(password.encode()).hexdigest()
|
||||
|
||||
def verify_password(self, password: str, password_hash: str) -> bool:
|
||||
"""验证密码"""
|
||||
return self.hash_password(password) == password_hash
|
||||
|
||||
def generate_token(self, user_data: dict) -> str:
|
||||
"""生成JWT token"""
|
||||
payload = {
|
||||
'user_id': user_data['id'],
|
||||
'username': user_data['username'],
|
||||
'exp': datetime.utcnow() + self.token_expiry,
|
||||
'iat': datetime.utcnow()
|
||||
}
|
||||
return jwt.encode(payload, self.secret_key, algorithm='HS256')
|
||||
|
||||
def verify_token(self, token: str) -> Optional[Dict[str, Any]]:
|
||||
"""验证JWT token"""
|
||||
try:
|
||||
payload = jwt.decode(token, self.secret_key, algorithms=['HS256'])
|
||||
return payload
|
||||
except jwt.ExpiredSignatureError:
|
||||
return None
|
||||
except jwt.InvalidTokenError:
|
||||
return None
|
||||
|
||||
def authenticate_user(self, username: str, password: str) -> Optional[dict]:
|
||||
"""用户认证"""
|
||||
print(f"[DEBUG] 开始认证用户: {username}")
|
||||
with db_manager.get_session() as session:
|
||||
user = session.query(User).filter_by(username=username).first()
|
||||
print(f"[DEBUG] 找到用户: {user is not None}")
|
||||
|
||||
if user and user.is_active and self.verify_password(password, user.password_hash):
|
||||
print(f"[DEBUG] 用户认证成功: {user.username}")
|
||||
|
||||
# 立即访问所有需要的属性,确保在会话内
|
||||
user_id = user.id
|
||||
username_val = user.username
|
||||
password_hash_val = user.password_hash
|
||||
email_val = user.email
|
||||
name_val = user.name
|
||||
role_val = user.role
|
||||
is_active_val = user.is_active
|
||||
created_at_val = user.created_at
|
||||
last_login_val = datetime.now()
|
||||
|
||||
print(f"[DEBUG] 访问用户属性成功: name={name_val}, role={role_val}")
|
||||
|
||||
# 更新最后登录时间
|
||||
user.last_login = last_login_val
|
||||
session.commit()
|
||||
print("[DEBUG] 数据库更新成功")
|
||||
|
||||
# 返回用户数据字典,避免SQLAlchemy会话绑定问题
|
||||
user_dict = {
|
||||
'id': user_id,
|
||||
'username': username_val,
|
||||
'password_hash': password_hash_val,
|
||||
'email': email_val,
|
||||
'name': name_val,
|
||||
'role': role_val,
|
||||
'is_active': is_active_val,
|
||||
'created_at': created_at_val,
|
||||
'last_login': last_login_val
|
||||
}
|
||||
print(f"[DEBUG] 返回用户字典: {user_dict['username']}")
|
||||
return user_dict
|
||||
else:
|
||||
print("[DEBUG] 用户认证失败")
|
||||
return None
|
||||
|
||||
def get_user_by_id(self, user_id: int) -> Optional[User]:
|
||||
"""根据ID获取用户"""
|
||||
with db_manager.get_session() as session:
|
||||
return session.query(User).filter_by(id=user_id).first()
|
||||
|
||||
def get_user_by_token(self, token: str) -> Optional[User]:
|
||||
"""根据token获取用户"""
|
||||
payload = self.verify_token(token)
|
||||
if payload:
|
||||
return self.get_user_by_id(payload['user_id'])
|
||||
return None
|
||||
|
||||
def create_user(self, username: str, password: str, name: str, email: str = None, role: str = 'user') -> Optional[User]:
|
||||
"""创建新用户"""
|
||||
try:
|
||||
with db_manager.get_session() as session:
|
||||
# 检查用户名是否已存在
|
||||
existing_user = session.query(User).filter_by(username=username).first()
|
||||
if existing_user:
|
||||
return None
|
||||
|
||||
user = User(
|
||||
username=username,
|
||||
name=name,
|
||||
email=email,
|
||||
role=role,
|
||||
is_active=True
|
||||
)
|
||||
user.set_password(password)
|
||||
|
||||
session.add(user)
|
||||
session.commit()
|
||||
return user
|
||||
except Exception as e:
|
||||
print(f"创建用户失败: {e}")
|
||||
return None
|
||||
|
||||
def create_default_admin(self):
|
||||
"""创建默认管理员用户"""
|
||||
admin = self.create_user('admin', 'admin123', '系统管理员', 'admin@example.com', 'admin')
|
||||
if admin:
|
||||
print("默认管理员用户已创建: admin/admin123")
|
||||
return admin
|
||||
|
||||
|
||||
# 全局认证管理器实例
|
||||
auth_manager = AuthManager()
|
||||
@@ -26,18 +26,22 @@ class DatabaseManager:
|
||||
|
||||
# 根据数据库类型选择不同的连接参数
|
||||
if "mysql" in db_config["url"]:
|
||||
# MySQL配置 - 优化连接池
|
||||
# MySQL配置 - 优化连接池和重连机制
|
||||
self.engine = create_engine(
|
||||
db_config["url"],
|
||||
echo=db_config["echo"],
|
||||
pool_size=20, # 增加连接池大小
|
||||
max_overflow=30, # 增加溢出连接数
|
||||
pool_pre_ping=True,
|
||||
pool_recycle=1800, # 减少回收时间
|
||||
pool_timeout=30, # 连接池超时(秒)
|
||||
pool_size=10, # 连接池大小
|
||||
max_overflow=20, # 溢出连接数
|
||||
pool_pre_ping=True, # 连接前检查连接是否有效
|
||||
pool_recycle=3600, # 1小时后回收连接
|
||||
pool_timeout=60, # 连接池超时(秒)
|
||||
connect_args={
|
||||
"charset": "utf8mb4",
|
||||
"autocommit": False,
|
||||
"connect_timeout": 30, # 连接超时
|
||||
"read_timeout": 60, # 读取超时
|
||||
"write_timeout": 60, # 写入超时
|
||||
"max_allowed_packet": 64*1024*1024, # 64MB
|
||||
"connect_timeout": 30, # 连接超时(秒)- 适用于网络延迟较大的情况
|
||||
"read_timeout": 30, # 读取超时(秒)
|
||||
"write_timeout": 30, # 写入超时(秒)
|
||||
@@ -82,7 +86,32 @@ class DatabaseManager:
|
||||
logger.error(f"数据库操作失败: {e}")
|
||||
raise
|
||||
finally:
|
||||
session.close()
|
||||
try:
|
||||
session.close()
|
||||
except Exception as close_error:
|
||||
logger.warning(f"关闭数据库会话时出错: {close_error}")
|
||||
|
||||
def check_connection(self) -> bool:
|
||||
"""检查数据库连接是否正常"""
|
||||
try:
|
||||
with self.get_session() as session:
|
||||
session.execute(text("SELECT 1"))
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.error(f"数据库连接检查失败: {e}")
|
||||
return False
|
||||
|
||||
def reconnect(self) -> bool:
|
||||
"""重新连接数据库"""
|
||||
try:
|
||||
if self.engine:
|
||||
self.engine.dispose()
|
||||
self._initialize_database()
|
||||
logger.info("数据库重新连接成功")
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.error(f"数据库重新连接失败: {e}")
|
||||
return False
|
||||
|
||||
def get_session_direct(self) -> Session:
|
||||
"""直接获取数据库会话"""
|
||||
|
||||
@@ -2,6 +2,7 @@ from sqlalchemy import Column, Integer, String, Text, DateTime, Float, Boolean,
|
||||
from sqlalchemy.ext.declarative import declarative_base
|
||||
from sqlalchemy.orm import relationship
|
||||
from datetime import datetime
|
||||
import hashlib
|
||||
|
||||
Base = declarative_base()
|
||||
|
||||
@@ -173,3 +174,39 @@ class WorkOrderProcessHistory(Base):
|
||||
|
||||
# 关联工单
|
||||
work_order = relationship("WorkOrder", back_populates="process_history")
|
||||
|
||||
|
||||
class User(Base):
|
||||
"""用户模型"""
|
||||
__tablename__ = "users"
|
||||
|
||||
id = Column(Integer, primary_key=True)
|
||||
username = Column(String(50), unique=True, nullable=False)
|
||||
password_hash = Column(String(128), nullable=False)
|
||||
email = Column(String(120), unique=True, nullable=True)
|
||||
name = Column(String(100), nullable=True)
|
||||
role = Column(String(20), default='user') # admin, user, operator
|
||||
is_active = Column(Boolean, default=True)
|
||||
created_at = Column(DateTime, default=datetime.now)
|
||||
last_login = Column(DateTime)
|
||||
|
||||
def set_password(self, password):
|
||||
"""设置密码哈希"""
|
||||
self.password_hash = hashlib.sha256(password.encode()).hexdigest()
|
||||
|
||||
def check_password(self, password):
|
||||
"""验证密码"""
|
||||
return self.password_hash == hashlib.sha256(password.encode()).hexdigest()
|
||||
|
||||
def to_dict(self):
|
||||
"""转换为字典格式(用于API响应)"""
|
||||
return {
|
||||
'id': self.id,
|
||||
'username': self.username,
|
||||
'email': self.email,
|
||||
'name': self.name,
|
||||
'role': self.role,
|
||||
'is_active': self.is_active,
|
||||
'created_at': self.created_at.isoformat() if self.created_at else None,
|
||||
'last_login': self.last_login.isoformat() if self.last_login else None
|
||||
}
|
||||
|
||||
@@ -292,7 +292,7 @@ class QueryOptimizer:
|
||||
# 处理状态映射(支持中英文状态)
|
||||
status_mapping = {
|
||||
'open': ['open', '待处理', '新建', 'new'],
|
||||
'in_progress': ['in_progress', '处理中', '进行中', 'progress', 'processing'],
|
||||
'in_progress': ['in_progress', '处理中', '进行中', 'progress', 'processing', 'analysising', 'analyzing'],
|
||||
'resolved': ['resolved', '已解决', '已完成'],
|
||||
'closed': ['closed', '已关闭', '关闭']
|
||||
}
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -141,7 +141,13 @@ class RealtimeChatManager:
|
||||
|
||||
# 保存到数据库(每轮一条,带会话标记)
|
||||
self._save_conversation(session_id, user_msg, assistant_msg)
|
||||
|
||||
|
||||
# 更新知识库使用次数
|
||||
if knowledge_results:
|
||||
used_entry_ids = [result["id"] for result in knowledge_results if result.get("id")]
|
||||
if used_entry_ids:
|
||||
self.knowledge_manager.update_usage_count(used_entry_ids)
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"response": assistant_response["content"], # 修改为response字段
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -23,16 +23,17 @@ class AISuggestionService:
|
||||
self.llm_config = get_config().llm
|
||||
logger.info(f"使用LLM配置: {self.llm_config.provider} - {self.llm_config.model}")
|
||||
|
||||
def generate_suggestion(self, tr_description: str, process_history: Optional[str] = None, vin: Optional[str] = None, existing_ai_suggestion: Optional[str] = None) -> str:
|
||||
def generate_suggestion(self, tr_description: str, process_history: Optional[str] = None, vin: Optional[str] = None, existing_ai_suggestion: Optional[str] = None, context: str = "realtime_chat") -> str:
|
||||
"""
|
||||
生成AI建议 - 参考处理过程记录生成建议
|
||||
|
||||
生成AI建议 - 根据不同上下文使用不同的提示词
|
||||
|
||||
Args:
|
||||
tr_description: TR描述
|
||||
process_history: 处理过程记录(可选,用于了解当前问题状态)
|
||||
vin: 车架号(可选)
|
||||
existing_ai_suggestion: 现有的AI建议(可选,用于判断是否是首次建议)
|
||||
|
||||
context: 调用上下文,"realtime_chat" 或 "feishu_sync"
|
||||
|
||||
Returns:
|
||||
AI建议文本
|
||||
"""
|
||||
@@ -70,16 +71,11 @@ class AISuggestionService:
|
||||
2. 如果远程操作都无法解决,可以考虑更深入的诊断方案
|
||||
3. 语言简洁精炼,用逗号连接,不要用序号或分行"""
|
||||
|
||||
# 构建用户消息 - 要求生成简洁的简短建议
|
||||
user_message = f"""请为以下问题提供精炼的技术支持操作建议:
|
||||
|
||||
格式要求:
|
||||
1. 现状+步骤,语言精炼
|
||||
2. 总长度控制在150字以内
|
||||
|
||||
{suggestion_instruction}
|
||||
|
||||
问题描述:{tr_description}{context_info}"""
|
||||
# 根据上下文选择不同的提示词构建方法
|
||||
if context == "feishu_sync":
|
||||
user_message = self._build_feishu_sync_prompt(tr_description, process_history, vin, existing_ai_suggestion, is_first_suggestion)
|
||||
else:
|
||||
user_message = self._build_realtime_chat_prompt(tr_description, process_history, is_first_suggestion)
|
||||
|
||||
# 创建会话
|
||||
session_id = chat_manager.create_session("ai_suggestion_service")
|
||||
@@ -248,7 +244,127 @@ class AISuggestionService:
|
||||
logger.info(f"未找到需要替换的内容: {content[:100] if len(content) > 100 else content}")
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def _build_feishu_sync_prompt(self, tr_description: str, process_history: str = None, vin: str = None, existing_ai_suggestion: str = None, is_first_suggestion: bool = True) -> str:
|
||||
"""
|
||||
构建飞书同步专用的AI建议提示词
|
||||
|
||||
Args:
|
||||
tr_description: TR描述
|
||||
process_history: 处理过程记录
|
||||
vin: 车架号
|
||||
existing_ai_suggestion: 现有的AI建议
|
||||
is_first_suggestion: 是否是首次建议
|
||||
|
||||
Returns:
|
||||
构建的提示词
|
||||
"""
|
||||
prompt = f"""请作为专业的汽车技术支持工程师,为以下工单问题提供详细的技术分析和处理建议。
|
||||
|
||||
问题描述:
|
||||
{tr_description}
|
||||
|
||||
"""
|
||||
|
||||
# 添加处理过程记录
|
||||
if process_history and process_history.strip():
|
||||
prompt += f"""
|
||||
当前处理进度:
|
||||
{process_history}
|
||||
|
||||
"""
|
||||
|
||||
# 添加VIN信息
|
||||
if vin:
|
||||
prompt += f"""
|
||||
车辆VIN:{vin}
|
||||
|
||||
"""
|
||||
|
||||
# 添加现有AI建议历史
|
||||
if existing_ai_suggestion and existing_ai_suggestion.strip():
|
||||
prompt += f"""
|
||||
历史AI建议记录:
|
||||
{existing_ai_suggestion}
|
||||
|
||||
"""
|
||||
|
||||
# 根据是否首次建议设置不同的要求
|
||||
if is_first_suggestion:
|
||||
prompt += """
|
||||
要求:
|
||||
1. 详细分析问题描述,识别可能的根本原因
|
||||
2. 基于当前处理进度,判断问题处于哪个阶段
|
||||
3. 提供具体的排查步骤和技术指导
|
||||
4. 建议需要收集哪些技术信息(如日志、配置、版本等)
|
||||
5. 如果需要,可以建议进站处理的具体项目
|
||||
6. 语言专业,包含技术细节,方便技术人员理解
|
||||
7. 建议格式要清晰,便于执行和跟踪
|
||||
|
||||
请提供完整的分析和建议:"""
|
||||
else:
|
||||
prompt += """
|
||||
要求:
|
||||
1. 基于已有处理记录和当前进度,分析问题进展情况
|
||||
2. 判断之前的处理步骤是否有效,找出可能的遗漏点
|
||||
3. 根据问题发展阶段提供更深入的技术解决方案
|
||||
4. 如果远程处理无效,明确说明需要哪些线下技术支持
|
||||
5. 详细说明进站后需要执行的具体诊断和修复步骤
|
||||
6. 包含技术参数、工具要求和注意事项
|
||||
7. 便于技术人员快速理解问题状态和下一步行动
|
||||
|
||||
请提供针对性的深入分析和处理建议:"""
|
||||
|
||||
return prompt
|
||||
|
||||
def _build_realtime_chat_prompt(self, tr_description: str, process_history: str = None, is_first_suggestion: bool = True) -> str:
|
||||
"""
|
||||
构建实时对话专用的AI建议提示词(保持原有风格)
|
||||
|
||||
Args:
|
||||
tr_description: TR描述
|
||||
process_history: 处理过程记录
|
||||
is_first_suggestion: 是否是首次建议
|
||||
|
||||
Returns:
|
||||
构建的提示词
|
||||
"""
|
||||
# 构建上下文信息
|
||||
context_info = ""
|
||||
if process_history and process_history.strip():
|
||||
context_info = f"""
|
||||
|
||||
已处理的步骤:
|
||||
{process_history}"""
|
||||
|
||||
# 根据是否为首次建议,设置不同的提示词
|
||||
if is_first_suggestion:
|
||||
# 首次建议:只给出一般性的排查步骤,不要提进站抓取日志
|
||||
suggestion_instruction = """要求:
|
||||
1. 首次给客户建议,只提供远程可操作的一般性排查步骤
|
||||
2. 如检查网络、重启系统、确认配置等常见操作
|
||||
3. 绝对不要提到"进站"、"抓取日志"等需要线下操作的内容
|
||||
4. 语言简洁精炼,用逗号连接,不要用序号或分行"""
|
||||
else:
|
||||
# 后续建议:如果已有处理记录但未解决,可以考虑更深入的方案
|
||||
suggestion_instruction = """要求:
|
||||
1. 基于已有处理步骤,给出下一步的排查建议
|
||||
2. 如果远程操作都无法解决,可以考虑更深入的诊断方案
|
||||
3. 语言简洁精炼,用逗号连接,不要用序号或分行"""
|
||||
|
||||
# 构建用户消息 - 要求生成简洁的简短建议
|
||||
user_message = f"""请为以下问题提供精炼的技术支持操作建议:
|
||||
|
||||
格式要求:
|
||||
1. 现状+步骤,语言精炼
|
||||
2. 总长度控制在150字以内
|
||||
|
||||
{suggestion_instruction}
|
||||
|
||||
问题描述:{tr_description}{context_info}"""
|
||||
|
||||
return user_message
|
||||
|
||||
def _clean_and_validate_response(self, content: str) -> str:
|
||||
"""
|
||||
清理和校验响应内容
|
||||
@@ -346,14 +462,15 @@ class AISuggestionService:
|
||||
logger.error(f"清理响应内容失败: {e}")
|
||||
return content
|
||||
|
||||
def batch_generate_suggestions(self, records: List[Dict[str, Any]], limit: int = 10) -> List[Dict[str, Any]]:
|
||||
def batch_generate_suggestions(self, records: List[Dict[str, Any]], limit: int = 10, context: str = "feishu_sync") -> List[Dict[str, Any]]:
|
||||
"""
|
||||
批量生成AI建议
|
||||
|
||||
|
||||
Args:
|
||||
records: 记录列表
|
||||
limit: 处理数量限制
|
||||
|
||||
context: 调用上下文,"realtime_chat" 或 "feishu_sync"
|
||||
|
||||
Returns:
|
||||
处理后的记录列表
|
||||
"""
|
||||
@@ -377,7 +494,7 @@ class AISuggestionService:
|
||||
logger.info(f"记录 {record.get('record_id', i)} - 现有AI建议前100字符: {existing_ai_suggestion[:100]}")
|
||||
|
||||
if tr_description:
|
||||
ai_suggestion = self.generate_suggestion(tr_description, process_history, vin, existing_ai_suggestion)
|
||||
ai_suggestion = self.generate_suggestion(tr_description, process_history, vin, existing_ai_suggestion, context)
|
||||
# 处理同一天多次更新的情况
|
||||
new_suggestion = self._format_ai_suggestion_with_numbering(
|
||||
time_str, ai_suggestion, existing_ai_suggestion
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -417,3 +417,23 @@ class KnowledgeManager:
|
||||
except Exception as e:
|
||||
logger.error(f"获取知识库统计失败: {e}")
|
||||
return {}
|
||||
|
||||
def update_usage_count(self, entry_ids: List[int]) -> bool:
|
||||
"""更新知识库条目的使用次数"""
|
||||
try:
|
||||
with db_manager.get_session() as session:
|
||||
# 批量更新使用次数
|
||||
session.query(KnowledgeEntry).filter(
|
||||
KnowledgeEntry.id.in_(entry_ids)
|
||||
).update({
|
||||
"usage_count": KnowledgeEntry.usage_count + 1,
|
||||
"updated_at": datetime.now()
|
||||
})
|
||||
session.commit()
|
||||
|
||||
logger.info(f"成功更新 {len(entry_ids)} 个知识库条目的使用次数")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"更新知识库使用次数失败: {e}")
|
||||
return False
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
src/web/__pycache__/app.cpython-310.pyc
Normal file
BIN
src/web/__pycache__/app.cpython-310.pyc
Normal file
Binary file not shown.
Binary file not shown.
BIN
src/web/__pycache__/app.cpython-312.pyc
Normal file
BIN
src/web/__pycache__/app.cpython-312.pyc
Normal file
Binary file not shown.
BIN
src/web/__pycache__/error_handlers.cpython-310.pyc
Normal file
BIN
src/web/__pycache__/error_handlers.cpython-310.pyc
Normal file
Binary file not shown.
Binary file not shown.
BIN
src/web/__pycache__/service_manager.cpython-310.pyc
Normal file
BIN
src/web/__pycache__/service_manager.cpython-310.pyc
Normal file
Binary file not shown.
Binary file not shown.
BIN
src/web/__pycache__/websocket_server.cpython-310.pyc
Normal file
BIN
src/web/__pycache__/websocket_server.cpython-310.pyc
Normal file
Binary file not shown.
Binary file not shown.
413
src/web/app.py
413
src/web/app.py
@@ -32,6 +32,11 @@ 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
|
||||
from src.web.blueprints.auth import auth_bp
|
||||
from src.web.blueprints.agent import agent_bp
|
||||
from src.web.blueprints.vehicle import vehicle_bp
|
||||
from src.web.blueprints.analytics import analytics_bp
|
||||
from src.web.blueprints.test import test_bp
|
||||
|
||||
# 配置日志
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -69,6 +74,11 @@ app.register_blueprint(monitoring_bp)
|
||||
app.register_blueprint(system_bp)
|
||||
app.register_blueprint(feishu_sync_bp)
|
||||
app.register_blueprint(core_bp)
|
||||
app.register_blueprint(auth_bp)
|
||||
app.register_blueprint(agent_bp)
|
||||
app.register_blueprint(vehicle_bp)
|
||||
app.register_blueprint(analytics_bp)
|
||||
app.register_blueprint(test_bp)
|
||||
|
||||
# 页面路由
|
||||
@app.route('/')
|
||||
@@ -100,10 +110,12 @@ def uploaded_file(filename):
|
||||
return send_from_directory(app.config['UPLOAD_FOLDER'], filename)
|
||||
|
||||
# ============================================================================
|
||||
# 核心API路由 - 已迁移到蓝图
|
||||
# 核心API路由
|
||||
# ============================================================================
|
||||
# 健康检查、预警规则、监控状态等核心功能已迁移到 core 蓝图
|
||||
# 分析数据相关功能也已迁移到 core 蓝图
|
||||
# 以下路由因功能特殊性保留在主应用中:
|
||||
# - Chat相关路由:使用RealtimeChatManager进行实时对话
|
||||
# - 健康检查、预警规则、监控状态等核心功能已迁移到 core 蓝图
|
||||
# - 分析数据相关功能已迁移到 analytics 蓝图
|
||||
|
||||
# ============================================================================
|
||||
# 实时对话相关路由
|
||||
@@ -115,9 +127,9 @@ def create_chat_session():
|
||||
data = request.get_json()
|
||||
user_id = data.get('user_id', 'anonymous')
|
||||
work_order_id = data.get('work_order_id')
|
||||
|
||||
|
||||
session_id = service_manager.get_chat_manager().create_session(user_id, work_order_id)
|
||||
|
||||
|
||||
return jsonify({
|
||||
"success": True,
|
||||
"session_id": session_id,
|
||||
@@ -133,10 +145,10 @@ def send_chat_message():
|
||||
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 = service_manager.get_chat_manager().process_message(session_id, message)
|
||||
return jsonify(result)
|
||||
except Exception as e:
|
||||
@@ -164,10 +176,10 @@ def create_work_order():
|
||||
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 = service_manager.get_chat_manager().create_work_order(session_id, title, description, category, priority)
|
||||
return jsonify(result)
|
||||
except Exception as e:
|
||||
@@ -209,388 +221,13 @@ 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 = service_manager.get_agent_assistant().get_agent_status()
|
||||
return jsonify({"success": True, **status})
|
||||
except Exception as e:
|
||||
# 返回默认状态,避免500错误
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"status": "inactive",
|
||||
"active_goals": 0,
|
||||
"available_tools": 0,
|
||||
"error": "Agent服务暂时不可用"
|
||||
})
|
||||
# Agent相关路由已移动到 agent_bp 蓝图
|
||||
|
||||
@app.route('/api/agent/action-history')
|
||||
def get_agent_action_history():
|
||||
"""获取Agent动作执行历史"""
|
||||
try:
|
||||
limit = request.args.get('limit', 50, type=int)
|
||||
history = service_manager.get_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
|
||||
# 分析相关路由已移动到 analytics_bp 蓝图
|
||||
|
||||
@app.route('/api/agent/trigger-sample', methods=['POST'])
|
||||
def trigger_sample_action():
|
||||
"""触发示例动作"""
|
||||
try:
|
||||
import asyncio
|
||||
result = asyncio.run(service_manager.get_agent_assistant().trigger_sample_actions())
|
||||
return jsonify(result)
|
||||
except Exception as e:
|
||||
return jsonify({"error": str(e)}), 500
|
||||
# 车辆数据相关路由已移动到 vehicle_bp 蓝图
|
||||
|
||||
@app.route('/api/agent/clear-history', methods=['POST'])
|
||||
def clear_agent_history():
|
||||
"""清空Agent执行历史"""
|
||||
try:
|
||||
result = service_manager.get_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 = service_manager.get_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 = service_manager.get_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 = service_manager.get_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 = service_manager.get_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 = service_manager.get_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 = service_manager.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 = service_manager.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:
|
||||
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,
|
||||
"performance": performance
|
||||
})
|
||||
except Exception as e:
|
||||
# 返回默认工具列表,避免500错误
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"tools": [],
|
||||
"performance": {},
|
||||
"error": "工具统计暂时不可用"
|
||||
})
|
||||
|
||||
@app.route('/api/agent/tools/execute', methods=['POST'])
|
||||
def execute_agent_tool():
|
||||
"""执行指定的Agent工具"""
|
||||
try:
|
||||
data = request.get_json() or {}
|
||||
tool_name = data.get('tool') or data.get('name')
|
||||
parameters = data.get('parameters') or {}
|
||||
if not tool_name:
|
||||
return jsonify({"error": "缺少工具名称tool"}), 400
|
||||
|
||||
import asyncio
|
||||
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
|
||||
|
||||
@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}
|
||||
|
||||
service_manager.get_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 = 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 - 已迁移到 core 蓝图
|
||||
# ============================================================================
|
||||
|
||||
@app.route('/api/analytics/export')
|
||||
def export_analytics():
|
||||
"""导出分析报告"""
|
||||
try:
|
||||
# 生成Excel报告(使用数据库真实数据)
|
||||
analytics = query_optimizer.get_analytics_optimized(30)
|
||||
|
||||
# 创建工作簿
|
||||
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)
|
||||
|
||||
vehicle_mgr = service_manager.get_vehicle_manager()
|
||||
if vehicle_vin:
|
||||
data = vehicle_mgr.get_vehicle_data_by_vin(vehicle_vin, data_type, limit)
|
||||
elif vehicle_id:
|
||||
data = vehicle_mgr.get_vehicle_data(vehicle_id, data_type, limit)
|
||||
else:
|
||||
data = vehicle_mgr.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 = 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
|
||||
|
||||
@app.route('/api/vehicle/data/<vehicle_id>/latest')
|
||||
def get_latest_vehicle_data(vehicle_id):
|
||||
"""获取车辆最新数据"""
|
||||
try:
|
||||
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
|
||||
|
||||
@app.route('/api/vehicle/data/<vehicle_id>/summary')
|
||||
def get_vehicle_summary(vehicle_id):
|
||||
"""获取车辆数据摘要"""
|
||||
try:
|
||||
summary = service_manager.get_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 = service_manager.get_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 = 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连接"""
|
||||
try:
|
||||
data = request.get_json()
|
||||
api_provider = data.get('api_provider', 'openai')
|
||||
api_base_url = data.get('api_base_url', '')
|
||||
api_key = data.get('api_key', '')
|
||||
model_name = data.get('model_name', 'qwen-turbo')
|
||||
|
||||
# 这里可以调用LLM客户端进行连接测试
|
||||
# 暂时返回模拟结果
|
||||
|
||||
return jsonify({
|
||||
"success": True,
|
||||
"message": f"API连接测试成功 - {api_provider}",
|
||||
"response_time": "150ms",
|
||||
"model_status": "可用"
|
||||
})
|
||||
except Exception as e:
|
||||
return jsonify({"success": False, "error": str(e)}), 500
|
||||
|
||||
@app.route('/api/test/model', methods=['POST'])
|
||||
def test_model_response():
|
||||
"""测试模型回答"""
|
||||
try:
|
||||
data = request.get_json()
|
||||
test_message = data.get('test_message', '你好,请简单介绍一下你自己')
|
||||
|
||||
# 这里可以调用LLM客户端进行回答测试
|
||||
# 暂时返回模拟结果
|
||||
return jsonify({
|
||||
"success": True,
|
||||
"test_message": test_message,
|
||||
"response": "你好!我是TSP智能助手,基于大语言模型构建的智能客服系统。我可以帮助您解决车辆相关问题,提供技术支持和服务。",
|
||||
"response_time": "1.2s",
|
||||
"tokens_used": 45
|
||||
})
|
||||
except Exception as e:
|
||||
return jsonify({"success": False, "error": str(e)}), 500
|
||||
# API测试相关路由已移动到 test_bp 蓝图
|
||||
|
||||
# ============================================================================
|
||||
# 应用启动配置
|
||||
|
||||
BIN
src/web/blueprints/__pycache__/__init__.cpython-310.pyc
Normal file
BIN
src/web/blueprints/__pycache__/__init__.cpython-310.pyc
Normal file
Binary file not shown.
Binary file not shown.
BIN
src/web/blueprints/__pycache__/agent.cpython-311.pyc
Normal file
BIN
src/web/blueprints/__pycache__/agent.cpython-311.pyc
Normal file
Binary file not shown.
BIN
src/web/blueprints/__pycache__/alerts.cpython-310.pyc
Normal file
BIN
src/web/blueprints/__pycache__/alerts.cpython-310.pyc
Normal file
Binary file not shown.
Binary file not shown.
BIN
src/web/blueprints/__pycache__/analytics.cpython-311.pyc
Normal file
BIN
src/web/blueprints/__pycache__/analytics.cpython-311.pyc
Normal file
Binary file not shown.
Binary file not shown.
BIN
src/web/blueprints/__pycache__/conversations.cpython-310.pyc
Normal file
BIN
src/web/blueprints/__pycache__/conversations.cpython-310.pyc
Normal file
Binary file not shown.
Binary file not shown.
BIN
src/web/blueprints/__pycache__/core.cpython-310.pyc
Normal file
BIN
src/web/blueprints/__pycache__/core.cpython-310.pyc
Normal file
Binary file not shown.
Binary file not shown.
BIN
src/web/blueprints/__pycache__/feishu_sync.cpython-310.pyc
Normal file
BIN
src/web/blueprints/__pycache__/feishu_sync.cpython-310.pyc
Normal file
Binary file not shown.
Binary file not shown.
BIN
src/web/blueprints/__pycache__/knowledge.cpython-310.pyc
Normal file
BIN
src/web/blueprints/__pycache__/knowledge.cpython-310.pyc
Normal file
Binary file not shown.
Binary file not shown.
BIN
src/web/blueprints/__pycache__/monitoring.cpython-310.pyc
Normal file
BIN
src/web/blueprints/__pycache__/monitoring.cpython-310.pyc
Normal file
Binary file not shown.
Binary file not shown.
BIN
src/web/blueprints/__pycache__/system.cpython-310.pyc
Normal file
BIN
src/web/blueprints/__pycache__/system.cpython-310.pyc
Normal file
Binary file not shown.
Binary file not shown.
BIN
src/web/blueprints/__pycache__/test.cpython-311.pyc
Normal file
BIN
src/web/blueprints/__pycache__/test.cpython-311.pyc
Normal file
Binary file not shown.
BIN
src/web/blueprints/__pycache__/vehicle.cpython-311.pyc
Normal file
BIN
src/web/blueprints/__pycache__/vehicle.cpython-311.pyc
Normal file
Binary file not shown.
BIN
src/web/blueprints/__pycache__/workorders.cpython-310.pyc
Normal file
BIN
src/web/blueprints/__pycache__/workorders.cpython-310.pyc
Normal file
Binary file not shown.
Binary file not shown.
257
src/web/blueprints/agent.py
Normal file
257
src/web/blueprints/agent.py
Normal file
@@ -0,0 +1,257 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Agent相关API蓝图
|
||||
处理智能代理、工具执行、监控等功能
|
||||
"""
|
||||
|
||||
from flask import Blueprint, request, jsonify
|
||||
import asyncio
|
||||
|
||||
agent_bp = Blueprint('agent', __name__, url_prefix='/api/agent')
|
||||
|
||||
|
||||
@agent_bp.route('/status')
|
||||
def get_agent_status():
|
||||
"""获取Agent状态"""
|
||||
try:
|
||||
from src.web.service_manager import service_manager
|
||||
status = service_manager.get_agent_assistant().get_agent_status()
|
||||
return jsonify({"success": True, **status})
|
||||
except Exception as e:
|
||||
# 返回默认状态,避免500错误
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"status": "inactive",
|
||||
"active_goals": 0,
|
||||
"available_tools": 0,
|
||||
"error": "Agent服务暂时不可用"
|
||||
})
|
||||
|
||||
|
||||
@agent_bp.route('/action-history')
|
||||
def get_agent_action_history():
|
||||
"""获取Agent动作执行历史"""
|
||||
try:
|
||||
from src.web.service_manager import service_manager
|
||||
limit = request.args.get('limit', 50, type=int)
|
||||
history = service_manager.get_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
|
||||
|
||||
|
||||
@agent_bp.route('/trigger-sample', methods=['POST'])
|
||||
def trigger_sample_action():
|
||||
"""触发示例动作"""
|
||||
try:
|
||||
from src.web.service_manager import service_manager
|
||||
import asyncio
|
||||
result = asyncio.run(service_manager.get_agent_assistant().trigger_sample_actions())
|
||||
return jsonify(result)
|
||||
except Exception as e:
|
||||
return jsonify({"error": str(e)}), 500
|
||||
|
||||
|
||||
@agent_bp.route('/clear-history', methods=['POST'])
|
||||
def clear_agent_history():
|
||||
"""清空Agent执行历史"""
|
||||
try:
|
||||
from src.web.service_manager import service_manager
|
||||
result = service_manager.get_agent_assistant().clear_execution_history()
|
||||
return jsonify(result)
|
||||
except Exception as e:
|
||||
return jsonify({"error": str(e)}), 500
|
||||
|
||||
|
||||
@agent_bp.route('/llm-stats')
|
||||
def get_llm_stats():
|
||||
"""获取LLM使用统计"""
|
||||
try:
|
||||
from src.web.service_manager import service_manager
|
||||
stats = service_manager.get_agent_assistant().get_llm_usage_stats()
|
||||
return jsonify({
|
||||
"success": True,
|
||||
"stats": stats
|
||||
})
|
||||
except Exception as e:
|
||||
return jsonify({"error": str(e)}), 500
|
||||
|
||||
|
||||
@agent_bp.route('/toggle', methods=['POST'])
|
||||
def toggle_agent_mode():
|
||||
"""切换Agent模式"""
|
||||
try:
|
||||
from src.web.service_manager import service_manager
|
||||
data = request.get_json()
|
||||
enabled = data.get('enabled', True)
|
||||
success = service_manager.get_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
|
||||
|
||||
|
||||
@agent_bp.route('/monitoring/start', methods=['POST'])
|
||||
def start_agent_monitoring():
|
||||
"""启动Agent监控"""
|
||||
try:
|
||||
from src.web.service_manager import service_manager
|
||||
success = service_manager.get_agent_assistant().start_proactive_monitoring()
|
||||
return jsonify({
|
||||
"success": success,
|
||||
"message": "Agent监控已启动" if success else "启动失败"
|
||||
})
|
||||
except Exception as e:
|
||||
return jsonify({"error": str(e)}), 500
|
||||
|
||||
|
||||
@agent_bp.route('/monitoring/stop', methods=['POST'])
|
||||
def stop_agent_monitoring():
|
||||
"""停止Agent监控"""
|
||||
try:
|
||||
from src.web.service_manager import service_manager
|
||||
success = service_manager.get_agent_assistant().stop_proactive_monitoring()
|
||||
return jsonify({
|
||||
"success": success,
|
||||
"message": "Agent监控已停止" if success else "停止失败"
|
||||
})
|
||||
except Exception as e:
|
||||
return jsonify({"error": str(e)}), 500
|
||||
|
||||
|
||||
@agent_bp.route('/proactive-monitoring', methods=['POST'])
|
||||
def proactive_monitoring():
|
||||
"""主动监控检查"""
|
||||
try:
|
||||
from src.web.service_manager import service_manager
|
||||
result = service_manager.get_agent_assistant().run_proactive_monitoring()
|
||||
return jsonify(result)
|
||||
except Exception as e:
|
||||
return jsonify({"error": str(e)}), 500
|
||||
|
||||
|
||||
@agent_bp.route('/intelligent-analysis', methods=['POST'])
|
||||
def intelligent_analysis():
|
||||
"""智能分析"""
|
||||
try:
|
||||
from src.web.service_manager import service_manager
|
||||
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
|
||||
|
||||
|
||||
@agent_bp.route('/chat', methods=['POST'])
|
||||
def agent_chat():
|
||||
"""Agent对话接口"""
|
||||
try:
|
||||
from src.web.service_manager import service_manager
|
||||
data = request.get_json()
|
||||
message = data.get('message', '')
|
||||
context = data.get('context', {})
|
||||
|
||||
if not message:
|
||||
return jsonify({"error": "消息不能为空"}), 400
|
||||
|
||||
# 使用Agent助手处理消息
|
||||
agent_assistant = service_manager.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_bp.route('/tools/stats')
|
||||
def get_agent_tools_stats():
|
||||
"""获取Agent工具统计"""
|
||||
try:
|
||||
from src.web.service_manager import service_manager
|
||||
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,
|
||||
"performance": performance
|
||||
})
|
||||
except Exception as e:
|
||||
# 返回默认工具列表,避免500错误
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"tools": [],
|
||||
"performance": {},
|
||||
"error": "工具统计暂时不可用"
|
||||
})
|
||||
|
||||
|
||||
@agent_bp.route('/tools/execute', methods=['POST'])
|
||||
def execute_agent_tool():
|
||||
"""执行指定的Agent工具"""
|
||||
try:
|
||||
from src.web.service_manager import service_manager
|
||||
data = request.get_json() or {}
|
||||
tool_name = data.get('tool') or data.get('name')
|
||||
parameters = data.get('parameters') or {}
|
||||
if not tool_name:
|
||||
return jsonify({"error": "缺少工具名称tool"}), 400
|
||||
|
||||
import asyncio
|
||||
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
|
||||
|
||||
|
||||
@agent_bp.route('/tools/register', methods=['POST'])
|
||||
def register_custom_tool():
|
||||
"""注册自定义工具(仅登记元数据,函数为占位符)"""
|
||||
try:
|
||||
from src.web.service_manager import service_manager
|
||||
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}
|
||||
|
||||
service_manager.get_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
|
||||
|
||||
|
||||
@agent_bp.route('/tools/unregister/<name>', methods=['DELETE'])
|
||||
def unregister_custom_tool(name):
|
||||
"""注销自定义工具"""
|
||||
try:
|
||||
from src.web.service_manager import service_manager
|
||||
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
|
||||
52
src/web/blueprints/analytics.py
Normal file
52
src/web/blueprints/analytics.py
Normal file
@@ -0,0 +1,52 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
分析相关API蓝图
|
||||
处理数据分析、报告生成等功能
|
||||
"""
|
||||
|
||||
from flask import Blueprint, request, jsonify, send_file
|
||||
import os
|
||||
|
||||
analytics_bp = Blueprint('analytics', __name__, url_prefix='/api/analytics')
|
||||
|
||||
|
||||
@analytics_bp.route('/export')
|
||||
def export_analytics():
|
||||
"""导出分析报告"""
|
||||
try:
|
||||
from src.web.service_manager import service_manager
|
||||
from src.core.query_optimizer import query_optimizer
|
||||
from openpyxl import Workbook
|
||||
from openpyxl.styles import Font
|
||||
|
||||
# 生成Excel报告(使用数据库真实数据)
|
||||
analytics = query_optimizer.get_analytics_optimized(30)
|
||||
|
||||
# 创建工作簿
|
||||
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
|
||||
150
src/web/blueprints/auth.py
Normal file
150
src/web/blueprints/auth.py
Normal file
@@ -0,0 +1,150 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
认证蓝图
|
||||
处理用户登录、注册、注销等功能
|
||||
"""
|
||||
|
||||
from flask import Blueprint, request, jsonify, session, redirect, url_for
|
||||
from src.core.auth_manager import auth_manager
|
||||
from src.web.error_handlers import handle_api_errors, create_error_response, create_success_response
|
||||
|
||||
auth_bp = Blueprint('auth', __name__, url_prefix='/api/auth')
|
||||
|
||||
|
||||
@auth_bp.route('/login', methods=['POST'])
|
||||
@handle_api_errors
|
||||
def login():
|
||||
"""用户登录"""
|
||||
data = request.get_json()
|
||||
|
||||
if not data or not data.get('username') or not data.get('password'):
|
||||
return jsonify({"success": False, "message": "用户名和密码不能为空"}), 400
|
||||
|
||||
username = data['username']
|
||||
password = data['password']
|
||||
remember = data.get('remember', False)
|
||||
|
||||
# 认证用户
|
||||
user_data = auth_manager.authenticate_user(username, password)
|
||||
|
||||
if user_data:
|
||||
# 生成token
|
||||
token = auth_manager.generate_token(user_data)
|
||||
|
||||
# 存储到session
|
||||
session['user_id'] = user_data['id']
|
||||
session['username'] = user_data['username']
|
||||
session['user_info'] = user_data
|
||||
session['token'] = token
|
||||
|
||||
if remember:
|
||||
session.permanent = True
|
||||
|
||||
# 构建响应
|
||||
response_data = {
|
||||
"success": True,
|
||||
"message": "登录成功",
|
||||
"user": {
|
||||
"id": user_data['id'],
|
||||
"username": user_data['username'],
|
||||
"name": user_data['name'],
|
||||
"email": user_data['email'],
|
||||
"role": user_data['role']
|
||||
},
|
||||
"token": token
|
||||
}
|
||||
|
||||
return jsonify(response_data)
|
||||
else:
|
||||
return jsonify({"success": False, "message": "用户名或密码错误"}), 401
|
||||
|
||||
|
||||
@auth_bp.route('/logout', methods=['POST'])
|
||||
@handle_api_errors
|
||||
def logout():
|
||||
"""用户注销"""
|
||||
# 清除session
|
||||
session.clear()
|
||||
|
||||
return jsonify(create_success_response(message="注销成功"))
|
||||
|
||||
|
||||
@auth_bp.route('/status')
|
||||
@handle_api_errors
|
||||
def get_auth_status():
|
||||
"""获取认证状态"""
|
||||
if 'user_id' in session and 'user_info' in session:
|
||||
return jsonify({
|
||||
"authenticated": True,
|
||||
"user": session['user_info'],
|
||||
"token": session.get('token')
|
||||
})
|
||||
else:
|
||||
return jsonify({
|
||||
"authenticated": False,
|
||||
"user": None,
|
||||
"token": None
|
||||
})
|
||||
|
||||
|
||||
@auth_bp.route('/register', methods=['POST'])
|
||||
@handle_api_errors
|
||||
def register():
|
||||
"""用户注册(仅管理员可用)"""
|
||||
# 检查当前用户是否为管理员
|
||||
if not session.get('user_info') or session['user_info'].get('role') != 'admin':
|
||||
return create_error_response("权限不足", 403)
|
||||
|
||||
data = request.get_json()
|
||||
|
||||
required_fields = ['username', 'password', 'name']
|
||||
if not all(data.get(field) for field in required_fields):
|
||||
return create_error_response("缺少必要字段", 400)
|
||||
|
||||
user = auth_manager.create_user(
|
||||
username=data['username'],
|
||||
password=data['password'],
|
||||
name=data['name'],
|
||||
email=data.get('email'),
|
||||
role=data.get('role', 'user')
|
||||
)
|
||||
|
||||
if not user:
|
||||
return create_error_response("用户创建失败,用户名可能已存在", 400)
|
||||
|
||||
return jsonify(create_success_response(
|
||||
data={
|
||||
"id": user.id,
|
||||
"username": user.username,
|
||||
"name": user.name,
|
||||
"email": user.email,
|
||||
"role": user.role
|
||||
},
|
||||
message="用户创建成功"
|
||||
))
|
||||
|
||||
|
||||
@auth_bp.route('/user/profile')
|
||||
@handle_api_errors
|
||||
def get_user_profile():
|
||||
"""获取用户信息"""
|
||||
if 'user_info' not in session:
|
||||
return create_error_response("未登录", 401)
|
||||
|
||||
return jsonify(create_success_response(data=session['user_info']))
|
||||
|
||||
|
||||
@auth_bp.route('/user/profile', methods=['PUT'])
|
||||
@handle_api_errors
|
||||
def update_user_profile():
|
||||
"""更新用户信息"""
|
||||
if 'user_info' not in session:
|
||||
return create_error_response("未登录", 401)
|
||||
|
||||
data = request.get_json()
|
||||
user_id = session['user_info']['id']
|
||||
|
||||
# 这里应该实现用户信息的更新逻辑
|
||||
# 暂时只返回成功响应
|
||||
|
||||
return jsonify(create_success_response(message="用户信息更新成功"))
|
||||
@@ -14,10 +14,9 @@ from src.web.error_handlers import handle_api_errors, create_error_response, cre
|
||||
knowledge_bp = Blueprint('knowledge', __name__, url_prefix='/api/knowledge')
|
||||
|
||||
def get_agent_assistant():
|
||||
"""获取Agent助手实例()"""
|
||||
"""获取Agent助手实例(懒加载)"""
|
||||
global _agent_assistant
|
||||
if '_agent_assistant' not in globals():
|
||||
from src.agent_assistant import TSPAgentAssistant
|
||||
_agent_assistant = TSPAgentAssistant()
|
||||
return _agent_assistant
|
||||
|
||||
|
||||
52
src/web/blueprints/test.py
Normal file
52
src/web/blueprints/test.py
Normal file
@@ -0,0 +1,52 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
API测试相关蓝图
|
||||
处理API连接测试、模型测试等功能
|
||||
"""
|
||||
|
||||
from flask import Blueprint, request, jsonify
|
||||
|
||||
test_bp = Blueprint('test', __name__, url_prefix='/api/test')
|
||||
|
||||
|
||||
@test_bp.route('/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
|
||||
|
||||
|
||||
@test_bp.route('/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
|
||||
93
src/web/blueprints/vehicle.py
Normal file
93
src/web/blueprints/vehicle.py
Normal file
@@ -0,0 +1,93 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
车辆数据相关API蓝图
|
||||
处理车辆数据查询、添加、监控等功能
|
||||
"""
|
||||
|
||||
from flask import Blueprint, request, jsonify
|
||||
|
||||
vehicle_bp = Blueprint('vehicle', __name__, url_prefix='/api/vehicle')
|
||||
|
||||
|
||||
@vehicle_bp.route('/data')
|
||||
def get_vehicle_data():
|
||||
"""获取车辆数据"""
|
||||
try:
|
||||
from src.web.service_manager import service_manager
|
||||
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)
|
||||
|
||||
vehicle_mgr = service_manager.get_vehicle_manager()
|
||||
if vehicle_vin:
|
||||
data = vehicle_mgr.get_vehicle_data_by_vin(vehicle_vin, data_type, limit)
|
||||
elif vehicle_id:
|
||||
data = vehicle_mgr.get_vehicle_data(vehicle_id, data_type, limit)
|
||||
else:
|
||||
data = vehicle_mgr.search_vehicle_data(limit=limit)
|
||||
|
||||
return jsonify(data)
|
||||
except Exception as e:
|
||||
return jsonify({"error": str(e)}), 500
|
||||
|
||||
|
||||
@vehicle_bp.route('/data/vin/<vehicle_vin>/latest')
|
||||
def get_latest_vehicle_data_by_vin(vehicle_vin):
|
||||
"""按VIN获取车辆最新数据"""
|
||||
try:
|
||||
from src.web.service_manager import service_manager
|
||||
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
|
||||
|
||||
|
||||
@vehicle_bp.route('/data/<vehicle_id>/latest')
|
||||
def get_latest_vehicle_data(vehicle_id):
|
||||
"""获取车辆最新数据"""
|
||||
try:
|
||||
from src.web.service_manager import service_manager
|
||||
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
|
||||
|
||||
|
||||
@vehicle_bp.route('/data/<vehicle_id>/summary')
|
||||
def get_vehicle_summary(vehicle_id):
|
||||
"""获取车辆数据摘要"""
|
||||
try:
|
||||
from src.web.service_manager import service_manager
|
||||
summary = service_manager.get_vehicle_manager().get_vehicle_summary(vehicle_id)
|
||||
return jsonify(summary)
|
||||
except Exception as e:
|
||||
return jsonify({"error": str(e)}), 500
|
||||
|
||||
|
||||
@vehicle_bp.route('/data', methods=['POST'])
|
||||
def add_vehicle_data():
|
||||
"""添加车辆数据"""
|
||||
try:
|
||||
from src.web.service_manager import service_manager
|
||||
data = request.get_json()
|
||||
success = service_manager.get_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
|
||||
|
||||
|
||||
@vehicle_bp.route('/init-sample-data', methods=['POST'])
|
||||
def init_sample_vehicle_data():
|
||||
"""初始化示例车辆数据"""
|
||||
try:
|
||||
from src.web.service_manager import service_manager
|
||||
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
|
||||
@@ -10,7 +10,6 @@ import logging
|
||||
import uuid
|
||||
import time
|
||||
from datetime import datetime
|
||||
from typing import Optional
|
||||
from flask import Blueprint, request, jsonify, send_file
|
||||
from werkzeug.utils import secure_filename
|
||||
from sqlalchemy import text
|
||||
@@ -26,13 +25,13 @@ class SimpleAIAccuracyConfig:
|
||||
self.manual_review_threshold = 0.80
|
||||
self.ai_suggestion_confidence = 0.95
|
||||
self.human_resolution_confidence = 0.90
|
||||
|
||||
|
||||
def should_auto_approve(self, similarity: float) -> bool:
|
||||
return similarity >= self.auto_approve_threshold
|
||||
|
||||
|
||||
def should_use_human_resolution(self, similarity: float) -> bool:
|
||||
return similarity < self.use_human_resolution_threshold
|
||||
|
||||
|
||||
def get_confidence_score(self, similarity: float, use_human: bool = False) -> float:
|
||||
if use_human:
|
||||
return self.human_resolution_confidence
|
||||
@@ -41,83 +40,12 @@ class SimpleAIAccuracyConfig:
|
||||
|
||||
from src.main import TSPAssistant
|
||||
from src.core.database import db_manager
|
||||
from src.core.models import WorkOrder, Conversation, WorkOrderSuggestion, KnowledgeEntry, WorkOrderProcessHistory
|
||||
from src.core.models import WorkOrder, Conversation, WorkOrderSuggestion, KnowledgeEntry
|
||||
from src.core.query_optimizer import query_optimizer
|
||||
from src.web.service_manager import service_manager
|
||||
from src.core.workorder_permissions import (
|
||||
WorkOrderPermissionManager, WorkOrderDispatchManager,
|
||||
UserRole, WorkOrderModule
|
||||
)
|
||||
|
||||
workorders_bp = Blueprint('workorders', __name__, url_prefix='/api/workorders')
|
||||
|
||||
def get_current_user_role() -> UserRole:
|
||||
"""获取当前用户角色(临时实现,实际需要集成认证系统)"""
|
||||
# TODO: 从session或token中获取用户信息
|
||||
# 在没有认证系统之前,默认返回ADMIN以便可以查看所有工单
|
||||
# 实际实现时需要从认证系统获取真实角色
|
||||
role_str = request.headers.get('X-User-Role', 'admin') # 临时改为admin,避免VIEWER无法查看数据
|
||||
try:
|
||||
return UserRole(role_str)
|
||||
except ValueError:
|
||||
return UserRole.ADMIN # 临时返回ADMIN,避免VIEWER无法查看数据
|
||||
|
||||
def get_current_user_name() -> str:
|
||||
"""获取当前用户名(临时实现,实际需要集成认证系统)"""
|
||||
# TODO: 从session或token中获取用户信息
|
||||
return request.headers.get('X-User-Name', 'anonymous')
|
||||
|
||||
def add_process_history(
|
||||
workorder_id: int,
|
||||
processor_name: str,
|
||||
process_content: str,
|
||||
action_type: str,
|
||||
processor_role: Optional[str] = None,
|
||||
processor_region: Optional[str] = None,
|
||||
previous_status: Optional[str] = None,
|
||||
new_status: Optional[str] = None,
|
||||
assigned_module: Optional[str] = None
|
||||
) -> WorkOrderProcessHistory:
|
||||
"""
|
||||
添加工单处理过程记录
|
||||
|
||||
Args:
|
||||
workorder_id: 工单ID
|
||||
processor_name: 处理人员姓名
|
||||
process_content: 处理内容
|
||||
action_type: 操作类型(dispatch、process、close、reassign等)
|
||||
processor_role: 处理人员角色
|
||||
processor_region: 处理人员区域
|
||||
previous_status: 处理前的状态
|
||||
new_status: 处理后的状态
|
||||
assigned_module: 分配的模块
|
||||
|
||||
Returns:
|
||||
创建的处理记录对象
|
||||
"""
|
||||
try:
|
||||
with db_manager.get_session() as session:
|
||||
history = WorkOrderProcessHistory(
|
||||
work_order_id=workorder_id,
|
||||
processor_name=processor_name,
|
||||
processor_role=processor_role,
|
||||
processor_region=processor_region,
|
||||
process_content=process_content,
|
||||
action_type=action_type,
|
||||
previous_status=previous_status,
|
||||
new_status=new_status,
|
||||
assigned_module=assigned_module,
|
||||
process_time=datetime.now()
|
||||
)
|
||||
session.add(history)
|
||||
session.commit()
|
||||
session.refresh(history)
|
||||
logger.info(f"工单 {workorder_id} 添加处理记录: {action_type} by {processor_name}")
|
||||
return history
|
||||
except Exception as e:
|
||||
logger.error(f"添加处理记录失败: {e}")
|
||||
raise
|
||||
|
||||
# 移除get_assistant函数,使用service_manager
|
||||
|
||||
def _ensure_workorder_template_file() -> str:
|
||||
@@ -125,14 +53,14 @@ def _ensure_workorder_template_file() -> str:
|
||||
# 获取项目根目录
|
||||
current_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
project_root = os.path.abspath(os.path.join(current_dir, '..', '..', '..'))
|
||||
|
||||
|
||||
# 模板文件路径(项目根目录下的uploads)
|
||||
template_path = os.path.join(project_root, 'uploads', 'workorder_template.xlsx')
|
||||
|
||||
|
||||
# 确保目录存在
|
||||
uploads_dir = os.path.join(project_root, 'uploads')
|
||||
os.makedirs(uploads_dir, exist_ok=True)
|
||||
|
||||
|
||||
if not os.path.exists(template_path):
|
||||
# 尝试从其他可能的位置复制模板
|
||||
possible_locations = [
|
||||
@@ -140,7 +68,7 @@ def _ensure_workorder_template_file() -> str:
|
||||
os.path.join(current_dir, 'uploads', 'workorder_template.xlsx'),
|
||||
os.path.join(os.getcwd(), 'uploads', 'workorder_template.xlsx')
|
||||
]
|
||||
|
||||
|
||||
source_found = False
|
||||
for source_path in possible_locations:
|
||||
if os.path.exists(source_path):
|
||||
@@ -151,7 +79,7 @@ def _ensure_workorder_template_file() -> str:
|
||||
break
|
||||
except Exception as e:
|
||||
logger.warning(f"复制模板文件失败: {e}")
|
||||
|
||||
|
||||
if not source_found:
|
||||
# 自动生成一个最小可用模板
|
||||
try:
|
||||
@@ -163,66 +91,42 @@ def _ensure_workorder_template_file() -> str:
|
||||
logger.info(f"自动生成模板文件: {template_path}")
|
||||
except Exception as gen_err:
|
||||
raise FileNotFoundError('模板文件缺失且自动生成失败,请检查依赖:openpyxl/pandas') from gen_err
|
||||
|
||||
|
||||
return template_path
|
||||
|
||||
@workorders_bp.route('')
|
||||
def get_workorders():
|
||||
"""获取工单列表(分页,带权限过滤)"""
|
||||
"""获取工单列表(分页)"""
|
||||
try:
|
||||
# 获取当前用户角色和权限
|
||||
current_role = get_current_user_role()
|
||||
|
||||
# 获取分页参数
|
||||
page = request.args.get('page', 1, type=int)
|
||||
per_page = request.args.get('per_page', 10, type=int)
|
||||
status_filter = request.args.get('status', '')
|
||||
priority_filter = request.args.get('priority', '')
|
||||
module_filter = request.args.get('module', '') # 模块过滤
|
||||
|
||||
|
||||
# 从数据库获取分页数据
|
||||
from src.core.database import db_manager
|
||||
from src.core.models import WorkOrder
|
||||
|
||||
|
||||
with db_manager.get_session() as session:
|
||||
# 构建查询
|
||||
query = session.query(WorkOrder)
|
||||
|
||||
# 权限过滤:业务方只能看到自己模块的工单
|
||||
if not WorkOrderPermissionManager.can_view_all_workorders(current_role):
|
||||
# 获取用户可访问的模块
|
||||
accessible_modules = WorkOrderPermissionManager.get_accessible_modules(current_role)
|
||||
if accessible_modules:
|
||||
# 构建模块列表过滤条件
|
||||
module_names = [m.value for m in accessible_modules]
|
||||
query = query.filter(WorkOrder.assigned_module.in_(module_names))
|
||||
else:
|
||||
# 如果没有可访问的模块,返回空列表
|
||||
return jsonify({
|
||||
"workorders": [],
|
||||
"total": 0,
|
||||
"page": page,
|
||||
"per_page": per_page,
|
||||
"total_pages": 0 # 统一使用total_pages字段
|
||||
})
|
||||
|
||||
|
||||
# 应用过滤器
|
||||
if status_filter:
|
||||
query = query.filter(WorkOrder.status == status_filter)
|
||||
if priority_filter:
|
||||
query = query.filter(WorkOrder.priority == priority_filter)
|
||||
if module_filter:
|
||||
query = query.filter(WorkOrder.assigned_module == module_filter)
|
||||
|
||||
|
||||
# 按创建时间倒序排列
|
||||
query = query.order_by(WorkOrder.created_at.desc())
|
||||
|
||||
|
||||
# 计算总数
|
||||
total = query.count()
|
||||
|
||||
|
||||
# 分页查询
|
||||
workorders = query.offset((page - 1) * per_page).limit(per_page).all()
|
||||
|
||||
|
||||
# 转换为字典
|
||||
workorders_data = []
|
||||
for workorder in workorders:
|
||||
@@ -231,11 +135,6 @@ def get_workorders():
|
||||
'order_id': workorder.order_id,
|
||||
'title': workorder.title,
|
||||
'description': workorder.description,
|
||||
'assigned_module': workorder.assigned_module,
|
||||
'module_owner': workorder.module_owner,
|
||||
'dispatcher': workorder.dispatcher,
|
||||
'dispatch_time': workorder.dispatch_time.isoformat() if workorder.dispatch_time else None,
|
||||
'region': workorder.region,
|
||||
'category': workorder.category,
|
||||
'priority': workorder.priority,
|
||||
'status': workorder.status,
|
||||
@@ -247,10 +146,10 @@ def get_workorders():
|
||||
'updated_at': workorder.updated_at.isoformat() if workorder.updated_at else None,
|
||||
'date_of_close': workorder.date_of_close.isoformat() if workorder.date_of_close else None
|
||||
})
|
||||
|
||||
|
||||
# 计算分页信息
|
||||
total_pages = (total + per_page - 1) // per_page
|
||||
|
||||
|
||||
return jsonify({
|
||||
'workorders': workorders_data,
|
||||
'page': page,
|
||||
@@ -258,13 +157,13 @@ def get_workorders():
|
||||
'total': total,
|
||||
'total_pages': total_pages
|
||||
})
|
||||
|
||||
|
||||
except Exception as e:
|
||||
return jsonify({"error": str(e)}), 500
|
||||
|
||||
@workorders_bp.route('', methods=['POST'])
|
||||
def create_workorder():
|
||||
"""创建工单(初始状态为待分发)"""
|
||||
"""创建工单"""
|
||||
try:
|
||||
data = request.get_json()
|
||||
result = service_manager.get_assistant().create_work_order(
|
||||
@@ -273,72 +172,23 @@ def create_workorder():
|
||||
category=data['category'],
|
||||
priority=data['priority']
|
||||
)
|
||||
|
||||
# 获取当前用户信息(用于记录创建人)
|
||||
current_user = get_current_user_name()
|
||||
current_role = get_current_user_role()
|
||||
|
||||
# 创建工单后,设置为待分发状态(未分配模块)
|
||||
if result and 'id' in result:
|
||||
workorder_id = result.get('id')
|
||||
with db_manager.get_session() as session:
|
||||
workorder = session.query(WorkOrder).filter(WorkOrder.id == workorder_id).first()
|
||||
if workorder:
|
||||
# 初始状态为待分发
|
||||
workorder.assigned_module = WorkOrderModule.UNASSIGNED.value
|
||||
workorder.status = "pending" # 待处理/待分发
|
||||
workorder.created_by = current_user # 记录创建人
|
||||
session.commit()
|
||||
|
||||
# 记录创建工单的处理历史
|
||||
processor_region = "overseas" if current_role == UserRole.OVERSEAS_OPS else "domestic"
|
||||
add_process_history(
|
||||
workorder_id=workorder_id,
|
||||
processor_name=current_user,
|
||||
process_content=f"工单已创建:{data.get('title', '')[:50]}",
|
||||
action_type="create",
|
||||
processor_role=current_role.value,
|
||||
processor_region=processor_region,
|
||||
previous_status=None,
|
||||
new_status="pending"
|
||||
)
|
||||
|
||||
|
||||
# 清除工单相关缓存
|
||||
from src.core.cache_manager import cache_manager
|
||||
cache_manager.clear() # 清除所有缓存
|
||||
|
||||
|
||||
return jsonify({"success": True, "workorder": result})
|
||||
except Exception as e:
|
||||
return jsonify({"error": str(e)}), 500
|
||||
|
||||
@workorders_bp.route('/<int:workorder_id>')
|
||||
def get_workorder_details(workorder_id):
|
||||
"""获取工单详情(含数据库对话记录,带权限检查)"""
|
||||
"""获取工单详情(含数据库对话记录)"""
|
||||
try:
|
||||
# 获取当前用户角色和权限
|
||||
current_role = get_current_user_role()
|
||||
|
||||
with db_manager.get_session() as session:
|
||||
w = session.query(WorkOrder).filter(WorkOrder.id == workorder_id).first()
|
||||
if not w:
|
||||
return jsonify({"error": "工单不存在"}), 404
|
||||
|
||||
# 权限检查:业务方只能访问自己模块的工单
|
||||
if not WorkOrderPermissionManager.can_view_all_workorders(current_role):
|
||||
# 检查是否有权限访问该工单
|
||||
assigned_module_str = w.assigned_module
|
||||
if not assigned_module_str or assigned_module_str == WorkOrderModule.UNASSIGNED.value:
|
||||
# 未分配的工单,业务方不能访问
|
||||
return jsonify({"error": "无权访问该工单"}), 403
|
||||
|
||||
try:
|
||||
assigned_module = WorkOrderModule(assigned_module_str)
|
||||
accessible_modules = WorkOrderPermissionManager.get_accessible_modules(current_role)
|
||||
if assigned_module not in accessible_modules:
|
||||
return jsonify({"error": "无权访问该工单"}), 403
|
||||
except ValueError:
|
||||
# 如果模块值无效,业务方不能访问
|
||||
return jsonify({"error": "无权访问该工单"}), 403
|
||||
convs = session.query(Conversation).filter(Conversation.work_order_id == w.id).order_by(Conversation.timestamp.asc()).all()
|
||||
conv_list = []
|
||||
for c in convs:
|
||||
@@ -348,27 +198,6 @@ def get_workorder_details(workorder_id):
|
||||
"assistant_response": c.assistant_response,
|
||||
"timestamp": c.timestamp.isoformat() if c.timestamp else None
|
||||
})
|
||||
|
||||
# 获取处理过程记录
|
||||
process_history_list = session.query(WorkOrderProcessHistory).filter(
|
||||
WorkOrderProcessHistory.work_order_id == w.id
|
||||
).order_by(WorkOrderProcessHistory.process_time.asc()).all()
|
||||
|
||||
process_history_data = []
|
||||
for ph in process_history_list:
|
||||
process_history_data.append({
|
||||
"id": ph.id,
|
||||
"processor_name": ph.processor_name,
|
||||
"processor_role": ph.processor_role,
|
||||
"processor_region": ph.processor_region,
|
||||
"process_content": ph.process_content,
|
||||
"action_type": ph.action_type,
|
||||
"previous_status": ph.previous_status,
|
||||
"new_status": ph.new_status,
|
||||
"assigned_module": ph.assigned_module,
|
||||
"process_time": ph.process_time.isoformat() if ph.process_time else None
|
||||
})
|
||||
|
||||
# 在会话内构建工单数据
|
||||
workorder = {
|
||||
"id": w.id,
|
||||
@@ -382,13 +211,7 @@ def get_workorder_details(workorder_id):
|
||||
"updated_at": w.updated_at.isoformat() if w.updated_at else None,
|
||||
"resolution": w.resolution,
|
||||
"satisfaction_score": w.satisfaction_score,
|
||||
"assigned_module": w.assigned_module,
|
||||
"module_owner": w.module_owner,
|
||||
"dispatcher": w.dispatcher,
|
||||
"dispatch_time": w.dispatch_time.isoformat() if w.dispatch_time else None,
|
||||
"region": w.region,
|
||||
"conversations": conv_list,
|
||||
"process_history": process_history_data # 处理过程记录
|
||||
"conversations": conv_list
|
||||
}
|
||||
return jsonify(workorder)
|
||||
except Exception as e:
|
||||
@@ -396,72 +219,29 @@ def get_workorder_details(workorder_id):
|
||||
|
||||
@workorders_bp.route('/<int:workorder_id>', methods=['PUT'])
|
||||
def update_workorder(workorder_id):
|
||||
"""更新工单(写入数据库,自动记录处理历史)"""
|
||||
"""更新工单(写入数据库)"""
|
||||
try:
|
||||
# 获取当前用户信息
|
||||
current_user = get_current_user_name()
|
||||
current_role = get_current_user_role()
|
||||
|
||||
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
|
||||
|
||||
# 记录更新前的状态
|
||||
previous_status = w.status
|
||||
previous_priority = w.priority
|
||||
|
||||
# 更新工单信息
|
||||
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)
|
||||
new_status = data.get('status', w.status)
|
||||
w.status = new_status
|
||||
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()
|
||||
|
||||
# 如果状态或优先级发生变化,记录处理历史
|
||||
has_status_change = previous_status != new_status
|
||||
has_priority_change = previous_priority != data.get('priority', w.priority)
|
||||
|
||||
if has_status_change or has_priority_change:
|
||||
# 构建处理内容
|
||||
change_items = []
|
||||
if has_status_change:
|
||||
change_items.append(f"状态变更:{previous_status} → {new_status}")
|
||||
if has_priority_change:
|
||||
change_items.append(f"优先级变更:{previous_priority} → {data.get('priority', w.priority)}")
|
||||
|
||||
process_content = ";".join(change_items)
|
||||
if data.get('resolution'):
|
||||
process_content += f";解决方案:{data.get('resolution', '')[:100]}"
|
||||
|
||||
# 判断区域
|
||||
processor_region = "overseas" if current_role == UserRole.OVERSEAS_OPS else "domestic"
|
||||
|
||||
add_process_history(
|
||||
workorder_id=workorder_id,
|
||||
processor_name=current_user,
|
||||
process_content=process_content or "更新工单信息",
|
||||
action_type="update",
|
||||
processor_role=current_role.value,
|
||||
processor_region=processor_region,
|
||||
previous_status=previous_status,
|
||||
new_status=new_status
|
||||
)
|
||||
|
||||
|
||||
# 清除工单相关缓存
|
||||
from src.core.cache_manager import cache_manager
|
||||
cache_manager.clear() # 清除所有缓存
|
||||
|
||||
|
||||
updated = {
|
||||
"id": w.id,
|
||||
"title": w.title,
|
||||
@@ -485,25 +265,25 @@ def delete_workorder(workorder_id):
|
||||
workorder = session.query(WorkOrder).filter(WorkOrder.id == workorder_id).first()
|
||||
if not workorder:
|
||||
return jsonify({"error": "工单不存在"}), 404
|
||||
|
||||
|
||||
# 先删除所有相关的子记录(按外键依赖顺序)
|
||||
# 1. 删除工单建议记录
|
||||
try:
|
||||
session.execute(text("DELETE FROM work_order_suggestions WHERE work_order_id = :id"), {"id": workorder_id})
|
||||
except Exception as e:
|
||||
print(f"删除工单建议记录失败: {e}")
|
||||
|
||||
|
||||
# 2. 删除对话记录
|
||||
session.query(Conversation).filter(Conversation.work_order_id == workorder_id).delete()
|
||||
|
||||
|
||||
# 3. 删除工单
|
||||
session.delete(workorder)
|
||||
session.commit()
|
||||
|
||||
|
||||
# 清除工单相关缓存
|
||||
from src.core.cache_manager import cache_manager
|
||||
cache_manager.clear() # 清除所有缓存
|
||||
|
||||
|
||||
return jsonify({
|
||||
"success": True,
|
||||
"message": "工单删除成功"
|
||||
@@ -511,6 +291,39 @@ def delete_workorder(workorder_id):
|
||||
except Exception as e:
|
||||
return jsonify({"error": str(e)}), 500
|
||||
|
||||
@workorders_bp.route('/generate-ai-suggestion', methods=['POST'])
|
||||
def generate_ai_suggestion():
|
||||
"""通用AI建议生成API - 不需要先创建工单"""
|
||||
try:
|
||||
data = request.get_json()
|
||||
if not data or 'tr_description' not in data:
|
||||
return jsonify({"error": "缺少tr_description参数"}), 400
|
||||
|
||||
tr_description = data['tr_description']
|
||||
vin = data.get('vin')
|
||||
process_history = data.get('process_history')
|
||||
|
||||
# 使用AI建议服务生成建议
|
||||
from src.integrations.ai_suggestion_service import AISuggestionService
|
||||
ai_service = AISuggestionService()
|
||||
|
||||
suggestion = ai_service.generate_suggestion(
|
||||
tr_description=tr_description,
|
||||
process_history=process_history,
|
||||
vin=vin
|
||||
)
|
||||
|
||||
return jsonify({
|
||||
"success": True,
|
||||
"suggestion": suggestion,
|
||||
"message": "AI建议生成成功"
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"AI建议生成失败: {e}")
|
||||
return jsonify({"error": f"AI建议生成失败: {str(e)}"}), 500
|
||||
|
||||
|
||||
@workorders_bp.route('/<int:workorder_id>/ai-suggestion', methods=['POST'])
|
||||
def generate_workorder_ai_suggestion(workorder_id):
|
||||
"""根据工单描述与知识库生成AI建议草稿"""
|
||||
@@ -582,22 +395,22 @@ def save_workorder_human_resolution(workorder_id):
|
||||
except Exception:
|
||||
sim = 0.0
|
||||
rec.ai_similarity = sim
|
||||
|
||||
|
||||
# 使用简化的配置
|
||||
config = SimpleAIAccuracyConfig()
|
||||
|
||||
|
||||
# 自动审批条件
|
||||
approved = config.should_auto_approve(sim)
|
||||
rec.approved = approved
|
||||
|
||||
|
||||
# 记录使用人工描述入库的标记(当AI准确率低于阈值时)
|
||||
use_human_resolution = config.should_use_human_resolution(sim)
|
||||
rec.use_human_resolution = use_human_resolution
|
||||
|
||||
|
||||
session.commit()
|
||||
return jsonify({
|
||||
"success": True,
|
||||
"similarity": sim,
|
||||
"success": True,
|
||||
"similarity": sim,
|
||||
"approved": approved,
|
||||
"use_human_resolution": use_human_resolution
|
||||
})
|
||||
@@ -612,14 +425,14 @@ def approve_workorder_to_knowledge(workorder_id):
|
||||
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:
|
||||
return jsonify({"error": "未找到工单建议记录"}), 400
|
||||
|
||||
|
||||
# 使用简化的配置
|
||||
config = SimpleAIAccuracyConfig()
|
||||
|
||||
|
||||
# 确定使用哪个内容入库
|
||||
if rec.use_human_resolution and rec.human_resolution:
|
||||
# AI准确率低于阈值,使用人工描述入库
|
||||
@@ -635,7 +448,7 @@ def approve_workorder_to_knowledge(workorder_id):
|
||||
logger.info(f"工单 {workorder_id} 使用AI建议入库,相似度: {rec.ai_similarity:.4f}")
|
||||
else:
|
||||
return jsonify({"error": "未找到可入库的内容"}), 400
|
||||
|
||||
|
||||
# 入库为知识条目
|
||||
entry = KnowledgeEntry(
|
||||
question=w.title or (w.description[:20] if w.description else '工单问题'),
|
||||
@@ -649,9 +462,9 @@ def approve_workorder_to_knowledge(workorder_id):
|
||||
)
|
||||
session.add(entry)
|
||||
session.commit()
|
||||
|
||||
|
||||
return jsonify({
|
||||
"success": True,
|
||||
"success": True,
|
||||
"knowledge_id": entry.id,
|
||||
"used_content": "human_resolution" if rec.use_human_resolution else "ai_suggestion",
|
||||
"confidence_score": confidence_score
|
||||
@@ -667,25 +480,25 @@ def import_workorders():
|
||||
# 检查是否有文件上传
|
||||
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列名映射到工单字段
|
||||
@@ -694,16 +507,16 @@ def import_workorders():
|
||||
category = str(row.get('分类', row.get('category', '技术问题')))
|
||||
priority = str(row.get('优先级', row.get('priority', 'medium')))
|
||||
status = str(row.get('状态', row.get('status', 'open')))
|
||||
|
||||
|
||||
# 验证必填字段
|
||||
if not title or title.strip() == '':
|
||||
continue
|
||||
|
||||
|
||||
# 生成唯一的工单ID
|
||||
timestamp = int(time.time())
|
||||
unique_id = str(uuid.uuid4())[:8]
|
||||
order_id = f"IMP_{timestamp}_{unique_id}"
|
||||
|
||||
|
||||
# 创建工单到数据库
|
||||
try:
|
||||
with db_manager.get_session() as session:
|
||||
@@ -717,26 +530,26 @@ def import_workorders():
|
||||
created_at=datetime.now(),
|
||||
updated_at=datetime.now()
|
||||
)
|
||||
|
||||
|
||||
# 处理可选字段
|
||||
if pd.notna(row.get('解决方案', row.get('resolution'))):
|
||||
workorder.resolution = str(row.get('解决方案', row.get('resolution')))
|
||||
|
||||
|
||||
if pd.notna(row.get('满意度', row.get('satisfaction_score'))):
|
||||
try:
|
||||
workorder.satisfaction_score = int(row.get('满意度', row.get('satisfaction_score')))
|
||||
except (ValueError, TypeError):
|
||||
workorder.satisfaction_score = None
|
||||
|
||||
|
||||
session.add(workorder)
|
||||
session.commit()
|
||||
|
||||
|
||||
logger.info(f"成功导入工单: {order_id} - {title}")
|
||||
|
||||
|
||||
except Exception as db_error:
|
||||
logger.error(f"导入工单到数据库失败: {db_error}")
|
||||
continue
|
||||
|
||||
|
||||
# 添加到返回列表
|
||||
imported_workorders.append({
|
||||
"id": workorder.id,
|
||||
@@ -751,23 +564,23 @@ def import_workorders():
|
||||
"resolution": workorder.resolution,
|
||||
"satisfaction_score": workorder.satisfaction_score
|
||||
})
|
||||
|
||||
|
||||
# 清理上传的文件
|
||||
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
|
||||
|
||||
@@ -780,7 +593,7 @@ def download_import_template():
|
||||
"success": True,
|
||||
"template_url": f"/uploads/workorder_template.xlsx"
|
||||
})
|
||||
|
||||
|
||||
except Exception as e:
|
||||
return jsonify({"error": str(e)}), 500
|
||||
|
||||
@@ -789,256 +602,27 @@ def download_import_template_file():
|
||||
"""直接返回工单导入模板文件(下载)"""
|
||||
try:
|
||||
template_path = _ensure_workorder_template_file()
|
||||
|
||||
|
||||
# 检查文件是否存在
|
||||
if not os.path.exists(template_path):
|
||||
logger.error(f"模板文件不存在: {template_path}")
|
||||
return jsonify({"error": "模板文件不存在"}), 404
|
||||
|
||||
|
||||
# 检查文件大小
|
||||
file_size = os.path.getsize(template_path)
|
||||
if file_size == 0:
|
||||
logger.error(f"模板文件为空: {template_path}")
|
||||
return jsonify({"error": "模板文件为空"}), 500
|
||||
|
||||
|
||||
logger.info(f"准备下载模板文件: {template_path}, 大小: {file_size} bytes")
|
||||
|
||||
|
||||
try:
|
||||
# Flask>=2 使用 download_name
|
||||
return send_file(template_path, as_attachment=True, download_name='工单导入模板.xlsx', mimetype='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet')
|
||||
except TypeError:
|
||||
# 兼容 Flask<2 的 attachment_filename
|
||||
return send_file(template_path, as_attachment=True, attachment_filename='工单导入模板.xlsx', mimetype='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet')
|
||||
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"下载模板文件失败: {e}")
|
||||
return jsonify({"error": f"下载失败: {str(e)}"}), 500
|
||||
|
||||
@workorders_bp.route('/<int:workorder_id>/dispatch', methods=['POST'])
|
||||
def dispatch_workorder(workorder_id):
|
||||
"""工单分发:运维将工单分配给业务模块"""
|
||||
try:
|
||||
# 获取当前用户角色和权限
|
||||
current_role = get_current_user_role()
|
||||
current_user = get_current_user_name()
|
||||
|
||||
# 检查分发权限
|
||||
if not WorkOrderPermissionManager.can_dispatch_workorder(current_role):
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"error": "无权进行工单分发,只有属地运维和管理员可以分发工单"
|
||||
}), 403
|
||||
|
||||
# 获取请求数据
|
||||
data = request.get_json() or {}
|
||||
target_module_str = data.get('target_module', '')
|
||||
|
||||
if not target_module_str:
|
||||
return jsonify({"success": False, "error": "请指定目标模块"}), 400
|
||||
|
||||
# 验证模块
|
||||
try:
|
||||
target_module = WorkOrderModule(target_module_str)
|
||||
except ValueError:
|
||||
return jsonify({"success": False, "error": f"无效的模块: {target_module_str}"}), 400
|
||||
|
||||
# 获取工单
|
||||
with db_manager.get_session() as session:
|
||||
workorder = session.query(WorkOrder).filter(WorkOrder.id == workorder_id).first()
|
||||
if not workorder:
|
||||
return jsonify({"success": False, "error": "工单不存在"}), 404
|
||||
|
||||
# 执行分发
|
||||
module_owner = WorkOrderDispatchManager.get_module_owner(target_module)
|
||||
|
||||
# 记录分发前的状态
|
||||
previous_status = workorder.status
|
||||
|
||||
# 更新工单信息
|
||||
workorder.assigned_module = target_module.value
|
||||
workorder.module_owner = module_owner
|
||||
workorder.dispatcher = current_user
|
||||
workorder.dispatch_time = datetime.now()
|
||||
workorder.status = "assigned" # 更新状态为已分配
|
||||
|
||||
# 根据区域自动设置(可以从工单source或其他字段判断)
|
||||
# 这里简化处理,可以根据实际需求调整
|
||||
if not workorder.region:
|
||||
# 如果source包含特定关键词,可以判断区域
|
||||
source = workorder.source or ""
|
||||
if any(keyword in source.lower() for keyword in ["overseas", "abroad", "海外"]):
|
||||
workorder.region = "overseas"
|
||||
else:
|
||||
workorder.region = "domestic"
|
||||
|
||||
session.commit()
|
||||
|
||||
# 记录处理历史:工单分发
|
||||
processor_region = "overseas" if current_role == UserRole.OVERSEAS_OPS else "domestic"
|
||||
add_process_history(
|
||||
workorder_id=workorder_id,
|
||||
processor_name=current_user,
|
||||
process_content=f"工单已分发到{target_module.value}模块,业务接口人:{module_owner}",
|
||||
action_type="dispatch",
|
||||
processor_role=current_role.value,
|
||||
processor_region=processor_region,
|
||||
previous_status=previous_status,
|
||||
new_status="assigned",
|
||||
assigned_module=target_module.value
|
||||
)
|
||||
|
||||
logger.info(f"工单 {workorder_id} 已分发到 {target_module.value} 模块,分发人: {current_user}")
|
||||
|
||||
return jsonify({
|
||||
"success": True,
|
||||
"message": f"工单已成功分发到{target_module.value}模块",
|
||||
"workorder": {
|
||||
"id": workorder.id,
|
||||
"assigned_module": workorder.assigned_module,
|
||||
"module_owner": workorder.module_owner,
|
||||
"dispatcher": workorder.dispatcher,
|
||||
"dispatch_time": workorder.dispatch_time.isoformat() if workorder.dispatch_time else None,
|
||||
"status": workorder.status
|
||||
}
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"工单分发失败: {e}")
|
||||
return jsonify({"success": False, "error": str(e)}), 500
|
||||
|
||||
@workorders_bp.route('/<int:workorder_id>/suggest-module', methods=['POST'])
|
||||
def suggest_workorder_module(workorder_id):
|
||||
"""AI建议工单应该分配的模块"""
|
||||
try:
|
||||
with db_manager.get_session() as session:
|
||||
workorder = session.query(WorkOrder).filter(WorkOrder.id == workorder_id).first()
|
||||
if not workorder:
|
||||
return jsonify({"success": False, "error": "工单不存在"}), 404
|
||||
|
||||
# 使用AI分析建议模块
|
||||
suggested_module = WorkOrderDispatchManager.suggest_module(
|
||||
description=workorder.description or "",
|
||||
title=workorder.title or ""
|
||||
)
|
||||
|
||||
return jsonify({
|
||||
"success": True,
|
||||
"suggested_module": suggested_module.value if suggested_module else None,
|
||||
"module_owner": WorkOrderDispatchManager.get_module_owner(suggested_module) if suggested_module else None
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"模块建议失败: {e}")
|
||||
return jsonify({"success": False, "error": str(e)}), 500
|
||||
|
||||
@workorders_bp.route('/modules', methods=['GET'])
|
||||
def get_available_modules():
|
||||
"""获取所有可用的模块列表"""
|
||||
try:
|
||||
modules = [
|
||||
{"value": m.value, "name": m.name, "owner": WorkOrderDispatchManager.get_module_owner(m)}
|
||||
for m in WorkOrderModule
|
||||
if m != WorkOrderModule.UNASSIGNED
|
||||
]
|
||||
return jsonify({"success": True, "modules": modules})
|
||||
except Exception as e:
|
||||
return jsonify({"success": False, "error": str(e)}), 500
|
||||
|
||||
@workorders_bp.route('/<int:workorder_id>/process-history', methods=['GET'])
|
||||
def get_workorder_process_history(workorder_id):
|
||||
"""获取工单处理过程记录"""
|
||||
try:
|
||||
with db_manager.get_session() as session:
|
||||
workorder = session.query(WorkOrder).filter(WorkOrder.id == workorder_id).first()
|
||||
if not workorder:
|
||||
return jsonify({"error": "工单不存在"}), 404
|
||||
|
||||
# 获取处理历史
|
||||
history_list = session.query(WorkOrderProcessHistory).filter(
|
||||
WorkOrderProcessHistory.work_order_id == workorder_id
|
||||
).order_by(WorkOrderProcessHistory.process_time.asc()).all()
|
||||
|
||||
history_data = []
|
||||
for ph in history_list:
|
||||
history_data.append({
|
||||
"id": ph.id,
|
||||
"processor_name": ph.processor_name,
|
||||
"processor_role": ph.processor_role,
|
||||
"processor_region": ph.processor_region,
|
||||
"process_content": ph.process_content,
|
||||
"action_type": ph.action_type,
|
||||
"previous_status": ph.previous_status,
|
||||
"new_status": ph.new_status,
|
||||
"assigned_module": ph.assigned_module,
|
||||
"process_time": ph.process_time.isoformat() if ph.process_time else None
|
||||
})
|
||||
|
||||
return jsonify({
|
||||
"success": True,
|
||||
"workorder_id": workorder_id,
|
||||
"process_history": history_data,
|
||||
"total": len(history_data)
|
||||
})
|
||||
except Exception as e:
|
||||
logger.error(f"获取处理历史失败: {e}")
|
||||
return jsonify({"success": False, "error": str(e)}), 500
|
||||
|
||||
@workorders_bp.route('/<int:workorder_id>/process-history', methods=['POST'])
|
||||
def add_workorder_process_history(workorder_id):
|
||||
"""手动添加工单处理过程记录"""
|
||||
try:
|
||||
# 获取当前用户信息
|
||||
current_user = get_current_user_name()
|
||||
current_role = get_current_user_role()
|
||||
|
||||
data = request.get_json() or {}
|
||||
process_content = data.get('process_content', '').strip()
|
||||
|
||||
if not process_content:
|
||||
return jsonify({"success": False, "error": "处理内容不能为空"}), 400
|
||||
|
||||
with db_manager.get_session() as session:
|
||||
workorder = session.query(WorkOrder).filter(WorkOrder.id == workorder_id).first()
|
||||
if not workorder:
|
||||
return jsonify({"success": False, "error": "工单不存在"}), 404
|
||||
|
||||
# 获取可选参数
|
||||
action_type = data.get('action_type', 'process') # 默认操作类型为process
|
||||
processor_role = data.get('processor_role', current_role.value)
|
||||
processor_region = data.get('processor_region')
|
||||
if not processor_region:
|
||||
# 根据角色自动判断区域
|
||||
processor_region = "overseas" if current_role == UserRole.OVERSEAS_OPS else "domestic"
|
||||
|
||||
previous_status = data.get('previous_status', workorder.status)
|
||||
new_status = data.get('new_status', workorder.status)
|
||||
assigned_module = data.get('assigned_module', workorder.assigned_module)
|
||||
|
||||
# 添加处理记录
|
||||
history = add_process_history(
|
||||
workorder_id=workorder_id,
|
||||
processor_name=data.get('processor_name', current_user),
|
||||
process_content=process_content,
|
||||
action_type=action_type,
|
||||
processor_role=processor_role,
|
||||
processor_region=processor_region,
|
||||
previous_status=previous_status,
|
||||
new_status=new_status,
|
||||
assigned_module=assigned_module
|
||||
)
|
||||
|
||||
return jsonify({
|
||||
"success": True,
|
||||
"message": "处理记录已添加",
|
||||
"history": {
|
||||
"id": history.id,
|
||||
"processor_name": history.processor_name,
|
||||
"processor_role": history.processor_role,
|
||||
"process_content": history.process_content,
|
||||
"action_type": history.action_type,
|
||||
"process_time": history.process_time.isoformat() if history.process_time else None
|
||||
}
|
||||
})
|
||||
except Exception as e:
|
||||
logger.error(f"添加处理记录失败: {e}")
|
||||
return jsonify({"success": False, "error": str(e)}), 500
|
||||
|
||||
79
src/web/decorators.py
Normal file
79
src/web/decorators.py
Normal file
@@ -0,0 +1,79 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
通用装饰器
|
||||
提供统一的错误处理、服务管理等装饰器
|
||||
"""
|
||||
|
||||
from functools import wraps
|
||||
from flask import jsonify
|
||||
from src.web.service_manager import service_manager
|
||||
|
||||
|
||||
def handle_errors(default_response=None):
|
||||
"""统一错误处理装饰器"""
|
||||
def decorator(f):
|
||||
@wraps(f)
|
||||
def decorated_function(*args, **kwargs):
|
||||
try:
|
||||
return f(*args, **kwargs)
|
||||
except Exception as e:
|
||||
if default_response:
|
||||
return default_response
|
||||
return jsonify({"error": str(e)}), 500
|
||||
return decorated_function
|
||||
return decorator
|
||||
|
||||
|
||||
def with_service(service_name):
|
||||
"""服务注入装饰器"""
|
||||
def decorator(f):
|
||||
@wraps(f)
|
||||
def decorated_function(*args, **kwargs):
|
||||
# 将服务管理器注入到函数参数中
|
||||
kwargs['service_manager'] = service_manager
|
||||
return f(*args, **kwargs)
|
||||
return decorated_function
|
||||
return decorator
|
||||
|
||||
|
||||
def require_json(required_fields=None):
|
||||
"""JSON请求验证装饰器"""
|
||||
def decorator(f):
|
||||
@wraps(f)
|
||||
def decorated_function(*args, **kwargs):
|
||||
from flask import request
|
||||
try:
|
||||
data = request.get_json()
|
||||
if data is None:
|
||||
return jsonify({"error": "请求必须是JSON格式"}), 400
|
||||
|
||||
if required_fields:
|
||||
missing_fields = [field for field in required_fields if field not in data]
|
||||
if missing_fields:
|
||||
return jsonify({"error": f"缺少必要字段: {', '.join(missing_fields)}"}), 400
|
||||
|
||||
kwargs['data'] = data
|
||||
return f(*args, **kwargs)
|
||||
except Exception as e:
|
||||
return jsonify({"error": "JSON格式错误"}), 400
|
||||
return decorated_function
|
||||
return decorator
|
||||
|
||||
|
||||
def cache_response(timeout=300):
|
||||
"""响应缓存装饰器"""
|
||||
def decorator(f):
|
||||
@wraps(f)
|
||||
def decorated_function(*args, **kwargs):
|
||||
from flask import make_response
|
||||
response = f(*args, **kwargs)
|
||||
if isinstance(response, tuple):
|
||||
response, status = response
|
||||
else:
|
||||
status = 200
|
||||
|
||||
if hasattr(response, 'headers'):
|
||||
response.headers['Cache-Control'] = f'public, max-age={timeout}'
|
||||
return response
|
||||
return decorated_function
|
||||
return decorator
|
||||
@@ -41,7 +41,7 @@ def handle_database_errors(func: Callable) -> Callable:
|
||||
return func(*args, **kwargs)
|
||||
except Exception as e:
|
||||
logger.error(f"数据库错误 {func.__name__}: {e}")
|
||||
return jsonify({"error": "数据库操作失败"}), 500
|
||||
return jsonify({"error": f"数据库操作失败: {str(e)}"}), 500
|
||||
return wrapper
|
||||
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user