feat: 租户管理体系建设 CRUD + 各业务模块接入 tenant_id

1. 新增 Tenant 模型(tenants 表),支持租户创建、重命名、删除
2. 新增 /api/tenants CRUD 蓝图,default 租户不可删除
3. 数据库初始化时自动创建默认租户记录
4. Dashboard 新增租户管理标签页(创建/编辑/删除租户)
5. 各业务模块写入数据时正确传递 tenant_id:
   - realtime_chat: create_session 和 _save_conversation 支持 tenant_id
   - dialogue_manager: _save_conversation 和 create_work_order 支持 tenant_id
   - conversation_history: save_conversation 支持 tenant_id
   - workorder_sync: sync_from_feishu 支持 tenant_id
   - websocket_server: create_session 传递 tenant_id
   - HTTP chat API: create_session 传递 tenant_id
   - feishu_sync API: 同步时传递 tenant_id
   - workorders API: 创建工单时传递 tenant_id
6. 网页对话入口添加租户选择器
7. 知识库搜索按租户隔离(realtime_chat 中 _search_knowledge 传递 tenant_id)
8. 初始化时自动加载租户列表填充选择器
This commit is contained in:
2026-04-02 09:33:16 +08:00
parent 7013e9db70
commit edb0616f7f
14 changed files with 465 additions and 15 deletions

Binary file not shown.

View File

@@ -68,10 +68,35 @@ class DatabaseManager:
Base.metadata.create_all(bind=self.engine)
logger.info("数据库初始化成功")
# 确保默认租户存在
self._ensure_default_tenant()
except Exception as e:
logger.error(f"数据库初始化失败: {e}")
raise
def _ensure_default_tenant(self):
"""确保默认租户记录存在"""
try:
from .models import Tenant, DEFAULT_TENANT
session = self.SessionLocal()
try:
existing = session.query(Tenant).filter(Tenant.tenant_id == DEFAULT_TENANT).first()
if not existing:
session.add(Tenant(
tenant_id=DEFAULT_TENANT,
name="默认租户",
description="系统默认租户"
))
session.commit()
logger.info("默认租户已创建")
except Exception:
session.rollback()
finally:
session.close()
except Exception as e:
logger.warning(f"确保默认租户失败(不影响启动): {e}")
@contextmanager
def get_session(self) -> Generator[Session, None, None]:
"""获取数据库会话的上下文管理器"""

View File

@@ -9,6 +9,35 @@ Base = declarative_base()
# 默认租户ID单租户部署时使用
DEFAULT_TENANT = "default"
class Tenant(Base):
"""租户模型 — 管理多租户(市场)"""
__tablename__ = "tenants"
id = Column(Integer, primary_key=True)
tenant_id = Column(String(50), unique=True, nullable=False) # 唯一标识,如 market_a
name = Column(String(100), nullable=False) # 显示名称,如 "市场A"
description = Column(Text, nullable=True)
is_active = Column(Boolean, default=True)
created_at = Column(DateTime, default=datetime.now)
updated_at = Column(DateTime, default=datetime.now, onupdate=datetime.now)
# 租户级配置:飞书 app 凭证、LLM 参数等JSON 格式)
config = Column(Text, nullable=True)
def to_dict(self):
import json
return {
'id': self.id,
'tenant_id': self.tenant_id,
'name': self.name,
'description': self.description,
'is_active': self.is_active,
'config': json.loads(self.config) if self.config else {},
'created_at': self.created_at.isoformat() if self.created_at else None,
'updated_at': self.updated_at.isoformat() if self.updated_at else None,
}
class WorkOrder(Base):
"""工单模型"""
__tablename__ = "work_orders"

View File

@@ -46,16 +46,19 @@ class ConversationHistoryManager:
knowledge_used: Optional[List[int]] = None,
ip_address: Optional[str] = None,
invocation_method: Optional[str] = None,
session_id: Optional[str] = None
session_id: Optional[str] = None,
tenant_id: Optional[str] = None
) -> int:
"""保存对话记录到数据库和Redis"""
conversation_id = 0
try:
from src.core.models import DEFAULT_TENANT
# 保存到数据库
with db_manager.get_session() as session:
conversation = Conversation(
session_id=session_id,
tenant_id=tenant_id or DEFAULT_TENANT,
work_order_id=work_order_id,
user_message=user_message,
assistant_response=assistant_response,

View File

@@ -273,13 +273,16 @@ class DialogueManager:
work_order_id: Optional[int],
user_message: str,
assistant_response: str,
knowledge_used: str
knowledge_used: str,
tenant_id: Optional[str] = None
) -> int:
"""保存对话记录"""
try:
from src.core.models import DEFAULT_TENANT
with db_manager.get_session() as session:
conversation = Conversation(
work_order_id=work_order_id,
tenant_id=tenant_id or DEFAULT_TENANT,
user_message=user_message,
assistant_response=assistant_response,
knowledge_used=knowledge_used,
@@ -310,10 +313,12 @@ class DialogueManager:
title: str,
description: str,
category: str,
priority: str = "medium"
priority: str = "medium",
tenant_id: Optional[str] = None
) -> Dict[str, Any]:
"""创建工单"""
try:
from src.core.models import DEFAULT_TENANT
with db_manager.get_session() as session:
work_order = WorkOrder(
order_id=f"WO{datetime.now().strftime('%Y%m%d%H%M%S')}",
@@ -322,6 +327,7 @@ class DialogueManager:
category=category,
priority=priority,
status="open",
tenant_id=tenant_id or DEFAULT_TENANT,
created_at=datetime.now()
)
session.add(work_order)

View File

@@ -39,13 +39,14 @@ class RealtimeChatManager:
self.active_sessions = {} # 存储活跃的对话会话
self.message_history = {} # 存储消息历史
def create_session(self, user_id: str, work_order_id: Optional[int] = None) -> str:
def create_session(self, user_id: str, work_order_id: Optional[int] = None, tenant_id: Optional[str] = None) -> str:
"""创建新的对话会话"""
session_id = f"session_{user_id}_{int(time.time())}"
session_data = {
"user_id": user_id,
"work_order_id": work_order_id,
"tenant_id": tenant_id,
"created_at": datetime.now(),
"last_activity": datetime.now(),
"message_count": 0,
@@ -57,11 +58,14 @@ class RealtimeChatManager:
# 持久化会话到数据库
try:
from src.core.models import DEFAULT_TENANT
effective_tenant = tenant_id or DEFAULT_TENANT
with db_manager.get_session() as db_session:
chat_session = ChatSession(
session_id=session_id,
user_id=user_id,
work_order_id=work_order_id,
tenant_id=effective_tenant,
status="active",
message_count=0,
)
@@ -96,8 +100,9 @@ class RealtimeChatManager:
# 添加到消息历史
self.message_history[session_id].append(user_msg)
# 搜索相关知识
knowledge_results = self._search_knowledge(user_message)
# 搜索相关知识(按租户隔离)
session_tenant = session.get("tenant_id")
knowledge_results = self._search_knowledge(user_message, tenant_id=session_tenant)
# 识别VIN并查询实时数据注入上下文
vin = self._extract_vin(user_message)
@@ -180,10 +185,10 @@ class RealtimeChatManager:
logger.error(f"处理消息失败: {e}")
return {"error": f"处理消息失败: {str(e)}"}
def _search_knowledge(self, query: str, top_k: int = 3) -> List[Dict[str, Any]]:
def _search_knowledge(self, query: str, top_k: int = 3, tenant_id: str = None) -> List[Dict[str, Any]]:
"""搜索相关知识"""
try:
results = self.knowledge_manager.search_knowledge(query, top_k)
results = self.knowledge_manager.search_knowledge(query, top_k, tenant_id=tenant_id)
return results
except Exception as e:
logger.error(f"搜索知识库失败: {e}")
@@ -474,8 +479,17 @@ class RealtimeChatManager:
# 保存知识库使用记录(不再塞 session_marker
knowledge_data = assistant_msg.knowledge_used or []
# 获取会话的 tenant_id
session_tenant = None
if session_id in self.active_sessions:
session_tenant = self.active_sessions[session_id].get('tenant_id')
if not session_tenant:
from src.core.models import DEFAULT_TENANT
session_tenant = DEFAULT_TENANT
conversation = Conversation(
session_id=session_id,
tenant_id=session_tenant,
work_order_id=assistant_msg.work_order_id or user_msg.work_order_id,
user_message=user_msg.content or "",
assistant_response=assistant_msg.content or "",

View File

@@ -124,7 +124,7 @@ class WorkOrderSyncService:
"""获取映射状态"""
return self.field_mapper.get_mapping_status()
def sync_from_feishu(self, generate_ai_suggestions: bool = True, limit: int = 10) -> Dict[str, Any]:
def sync_from_feishu(self, generate_ai_suggestions: bool = True, limit: int = 10, tenant_id: str = None) -> Dict[str, Any]:
"""
从飞书同步工单数据到本地系统
@@ -215,6 +215,8 @@ class WorkOrderSyncService:
# 创建新记录
valid_fields["created_at"] = datetime.now()
valid_fields["updated_at"] = datetime.now()
if tenant_id:
valid_fields["tenant_id"] = tenant_id
new_workorder = WorkOrder(**valid_fields)
session.add(new_workorder)
created_count += 1

View File

@@ -38,6 +38,7 @@ from src.web.blueprints.vehicle import vehicle_bp
from src.web.blueprints.analytics import analytics_bp
from src.web.blueprints.test import test_bp
from src.web.blueprints.feishu_bot import feishu_bot_bp
from src.web.blueprints.tenants import tenants_bp
# 配置日志
@@ -126,6 +127,7 @@ app.register_blueprint(vehicle_bp)
app.register_blueprint(analytics_bp)
app.register_blueprint(test_bp)
app.register_blueprint(feishu_bot_bp)
app.register_blueprint(tenants_bp)
# 页面路由
@@ -180,8 +182,9 @@ def create_chat_session():
data = request.get_json()
user_id = data.get('user_id', 'anonymous')
work_order_id = data.get('work_order_id')
tenant_id = data.get('tenant_id')
session_id = service_manager.get_chat_manager().create_session(user_id, work_order_id)
session_id = service_manager.get_chat_manager().create_session(user_id, work_order_id, tenant_id=tenant_id)
return jsonify({
"success": True,

View File

@@ -92,9 +92,10 @@ def sync_from_feishu():
data = request.get_json() or {}
generate_ai = data.get('generate_ai_suggestions', True)
limit = data.get('limit', 10)
tenant_id = data.get('tenant_id')
sync_service = get_sync_service()
result = sync_service.sync_from_feishu(generate_ai_suggestions=generate_ai, limit=limit)
result = sync_service.sync_from_feishu(generate_ai_suggestions=generate_ai, limit=limit, tenant_id=tenant_id)
if result.get("success"):
message = f"同步完成:创建 {result['created_count']} 条,更新 {result['updated_count']}"

View File

@@ -0,0 +1,132 @@
# -*- coding: utf-8 -*-
"""
租户管理蓝图
处理租户 CRUD 的 API 路由
"""
import json
import logging
from flask import Blueprint, request, jsonify
from src.core.database import db_manager
from src.core.models import Tenant, DEFAULT_TENANT
logger = logging.getLogger(__name__)
tenants_bp = Blueprint('tenants', __name__, url_prefix='/api/tenants')
def _ensure_default_tenant():
"""确保 default 租户存在"""
try:
with db_manager.get_session() as session:
existing = session.query(Tenant).filter(Tenant.tenant_id == DEFAULT_TENANT).first()
if not existing:
session.add(Tenant(
tenant_id=DEFAULT_TENANT,
name="默认租户",
description="系统默认租户"
))
session.commit()
except Exception as e:
logger.error(f"确保默认租户失败: {e}")
@tenants_bp.route('', methods=['GET'])
def list_tenants():
"""获取所有租户列表"""
try:
with db_manager.get_session() as session:
tenants = session.query(Tenant).order_by(Tenant.created_at).all()
return jsonify([t.to_dict() for t in tenants])
except Exception as e:
logger.error(f"获取租户列表失败: {e}")
return jsonify({"error": str(e)}), 500
@tenants_bp.route('', methods=['POST'])
def create_tenant():
"""创建新租户"""
try:
data = request.get_json()
if not data or not data.get('tenant_id') or not data.get('name'):
return jsonify({"error": "tenant_id 和 name 为必填项"}), 400
tenant_id = data['tenant_id'].strip()
name = data['name'].strip()
if not tenant_id or not name:
return jsonify({"error": "tenant_id 和 name 不能为空"}), 400
with db_manager.get_session() as session:
existing = session.query(Tenant).filter(Tenant.tenant_id == tenant_id).first()
if existing:
return jsonify({"error": f"租户 '{tenant_id}' 已存在"}), 409
tenant = Tenant(
tenant_id=tenant_id,
name=name,
description=data.get('description', ''),
config=json.dumps(data.get('config', {}))
)
session.add(tenant)
session.commit()
# 重新查询以获取完整数据
tenant = session.query(Tenant).filter(Tenant.tenant_id == tenant_id).first()
return jsonify({"success": True, "tenant": tenant.to_dict()}), 201
except Exception as e:
logger.error(f"创建租户失败: {e}")
return jsonify({"error": str(e)}), 500
@tenants_bp.route('/<tenant_id>', methods=['PUT'])
def update_tenant(tenant_id):
"""更新租户信息(重命名等)"""
try:
data = request.get_json()
if not data:
return jsonify({"error": "请求体不能为空"}), 400
with db_manager.get_session() as session:
tenant = session.query(Tenant).filter(Tenant.tenant_id == tenant_id).first()
if not tenant:
return jsonify({"error": f"租户 '{tenant_id}' 不存在"}), 404
if 'name' in data:
tenant.name = data['name'].strip()
if 'description' in data:
tenant.description = data['description']
if 'config' in data:
tenant.config = json.dumps(data['config'])
if 'is_active' in data:
tenant.is_active = data['is_active']
session.commit()
tenant = session.query(Tenant).filter(Tenant.tenant_id == tenant_id).first()
return jsonify({"success": True, "tenant": tenant.to_dict()})
except Exception as e:
logger.error(f"更新租户失败: {e}")
return jsonify({"error": str(e)}), 500
@tenants_bp.route('/<tenant_id>', methods=['DELETE'])
def delete_tenant(tenant_id):
"""删除租户(不允许删除 default"""
try:
if tenant_id == DEFAULT_TENANT:
return jsonify({"error": "不能删除默认租户"}), 403
with db_manager.get_session() as session:
tenant = session.query(Tenant).filter(Tenant.tenant_id == tenant_id).first()
if not tenant:
return jsonify({"error": f"租户 '{tenant_id}' 不存在"}), 404
session.delete(tenant)
session.commit()
return jsonify({"success": True, "message": f"租户 '{tenant_id}' 已删除"})
except Exception as e:
logger.error(f"删除租户失败: {e}")
return jsonify({"error": str(e)}), 500

View File

@@ -151,7 +151,8 @@ def create_workorder():
title=data['title'],
description=data['description'],
category=data['category'],
priority=data['priority']
priority=data['priority'],
tenant_id=data.get('tenant_id')
)
# 清除工单相关缓存

View File

@@ -373,12 +373,31 @@ class TSPDashboard {
}
init() {
this.bindEvents();
this.populateTenantSelectors();
// 优化:并行加载初始数据,提高响应速度
this.loadInitialDataAsync();
this.startAutoRefresh();
this.initCharts();
}
async populateTenantSelectors() {
try {
const response = await fetch('/api/tenants');
const tenants = await response.json();
if (!Array.isArray(tenants)) return;
const selectors = document.querySelectorAll('#chat-tenant-id');
selectors.forEach(select => {
const currentVal = select.value;
select.innerHTML = tenants.map(t =>
`<option value="${t.tenant_id}"${t.tenant_id === currentVal ? ' selected' : ''}>${t.name} (${t.tenant_id})</option>`
).join('');
});
} catch (e) {
console.warn('加载租户列表失败:', e);
}
}
async loadInitialDataAsync() {
// 并行加载多个数据源
try {
@@ -722,7 +741,7 @@ class TSPDashboard {
this.loadWorkOrders();
break;
case 'conversation-history':
this.loadConversationHistory();
this.loadConversationTenantList();
break;
case 'token-monitor':
this.loadTokenMonitor();
@@ -739,6 +758,9 @@ class TSPDashboard {
case 'settings':
this.loadSettings();
break;
case 'tenant-management':
this.loadTenantList();
break;
}
}
@@ -1290,6 +1312,7 @@ class TSPDashboard {
try {
const userId = document.getElementById('user-id').value;
const workOrderId = document.getElementById('work-order-id').value;
const tenantId = document.getElementById('chat-tenant-id')?.value || 'default';
const response = await fetch('/api/chat/session', {
method: 'POST',
@@ -1298,7 +1321,8 @@ class TSPDashboard {
},
body: JSON.stringify({
user_id: userId,
work_order_id: workOrderId ? parseInt(workOrderId) : null
work_order_id: workOrderId ? parseInt(workOrderId) : null,
tenant_id: tenantId
})
});
@@ -6858,6 +6882,134 @@ class TSPDashboard {
this.showNotification('清空Agent历史失败: ' + error.message, 'error');
}
}
// ==================== 租户管理 ====================
async loadTenantList() {
const container = document.getElementById('tenant-list');
if (!container) return;
container.innerHTML = '<div class="text-center py-4"><i class="fas fa-spinner fa-spin fa-2x"></i></div>';
try {
const response = await fetch('/api/tenants');
const tenants = await response.json();
if (!Array.isArray(tenants) || tenants.length === 0) {
container.innerHTML = '<div class="text-center py-4 text-muted">暂无租户,请点击"新建租户"创建</div>';
return;
}
container.innerHTML = tenants.map(t => `
<div class="card mb-2">
<div class="card-body d-flex justify-content-between align-items-center py-2">
<div>
<strong>${t.name}</strong>
<span class="text-muted ms-2">(${t.tenant_id})</span>
${t.description ? `<br><small class="text-muted">${t.description}</small>` : ''}
${!t.is_active ? '<span class="badge bg-secondary ms-2">已禁用</span>' : ''}
</div>
<div class="btn-group btn-group-sm">
<button class="btn btn-outline-primary" onclick="dashboard.showEditTenantModal('${t.tenant_id}', '${(t.name || '').replace(/'/g, "\\'")}', '${(t.description || '').replace(/'/g, "\\'")}')">
<i class="fas fa-edit"></i>
</button>
${t.tenant_id !== 'default' ? `
<button class="btn btn-outline-danger" onclick="dashboard.deleteTenant('${t.tenant_id}')">
<i class="fas fa-trash"></i>
</button>` : ''}
</div>
</div>
</div>
`).join('');
} catch (error) {
console.error('加载租户列表失败:', error);
container.innerHTML = '<div class="text-center py-4 text-danger">加载失败</div>';
}
}
showCreateTenantModal() {
document.getElementById('tenantModalTitle').textContent = '新建租户';
document.getElementById('tenant-edit-id').value = '';
document.getElementById('tenant-id-input').value = '';
document.getElementById('tenant-id-input').disabled = false;
document.getElementById('tenant-id-group').style.display = '';
document.getElementById('tenant-name-input').value = '';
document.getElementById('tenant-desc-input').value = '';
new bootstrap.Modal(document.getElementById('tenantModal')).show();
}
showEditTenantModal(tenantId, name, description) {
document.getElementById('tenantModalTitle').textContent = '编辑租户';
document.getElementById('tenant-edit-id').value = tenantId;
document.getElementById('tenant-id-input').value = tenantId;
document.getElementById('tenant-id-input').disabled = true;
document.getElementById('tenant-name-input').value = name;
document.getElementById('tenant-desc-input').value = description;
new bootstrap.Modal(document.getElementById('tenantModal')).show();
}
async saveTenant() {
const editId = document.getElementById('tenant-edit-id').value;
const tenantId = document.getElementById('tenant-id-input').value.trim();
const name = document.getElementById('tenant-name-input').value.trim();
const description = document.getElementById('tenant-desc-input').value.trim();
if (!name) {
this.showNotification('租户名称不能为空', 'error');
return;
}
try {
let response;
if (editId) {
// 编辑
response = await fetch(`/api/tenants/${encodeURIComponent(editId)}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ name, description })
});
} else {
// 新建
if (!tenantId) {
this.showNotification('租户标识不能为空', 'error');
return;
}
response = await fetch('/api/tenants', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ tenant_id: tenantId, name, description })
});
}
const data = await response.json();
if (data.success) {
bootstrap.Modal.getInstance(document.getElementById('tenantModal')).hide();
this.showNotification(editId ? '租户已更新' : '租户已创建', 'success');
this.loadTenantList();
} else {
this.showNotification(data.error || '操作失败', 'error');
}
} catch (error) {
console.error('保存租户失败:', error);
this.showNotification('保存租户失败: ' + error.message, 'error');
}
}
async deleteTenant(tenantId) {
if (!confirm(`确定要删除租户 "${tenantId}" 吗?该操作不会删除关联数据。`)) return;
try {
const response = await fetch(`/api/tenants/${encodeURIComponent(tenantId)}`, { method: 'DELETE' });
const data = await response.json();
if (data.success) {
this.showNotification('租户已删除', 'success');
this.loadTenantList();
} else {
this.showNotification(data.error || '删除失败', 'error');
}
} catch (error) {
console.error('删除租户失败:', error);
this.showNotification('删除租户失败: ' + error.message, 'error');
}
}
}
// 飞书同步管理器

View File

@@ -439,6 +439,10 @@
<i class="fas fa-sliders-h"></i>
系统设置
</a>
<a class="nav-link" href="#tenant-management" data-tab="tenant-management">
<i class="fas fa-building"></i>
租户管理
</a>
</nav>
</div>
</div>
@@ -568,6 +572,13 @@
<h5><i class="fas fa-cog me-2"></i>对话控制</h5>
</div>
<div class="card-body">
<div class="mb-3">
<label class="form-label">租户</label>
<select class="form-select" id="chat-tenant-id">
<option value="default">默认租户</option>
</select>
</div>
<div class="mb-3">
<label class="form-label">用户ID</label>
<input type="text" class="form-control" id="user-id" value="user_001">
@@ -2187,6 +2198,76 @@
</div>
<!-- 模态框 -->
<!-- 租户管理标签页 -->
<div id="tenant-management-tab" class="tab-content" style="display: none;">
<div class="row mb-4">
<div class="col-md-8">
<div class="card">
<div class="card-header d-flex justify-content-between align-items-center">
<h5><i class="fas fa-building me-2"></i>租户管理</h5>
<button class="btn btn-primary btn-sm" onclick="dashboard.showCreateTenantModal()">
<i class="fas fa-plus me-1"></i>新建租户
</button>
</div>
<div class="card-body">
<div id="tenant-list">
<div class="loading-spinner">
<i class="fas fa-spinner fa-spin"></i>
</div>
</div>
</div>
</div>
</div>
<div class="col-md-4">
<div class="card">
<div class="card-header">
<h5><i class="fas fa-info-circle me-2"></i>说明</h5>
</div>
<div class="card-body">
<p class="text-muted">租户代表不同的市场或业务单元。每个租户拥有独立的知识库、对话历史和工单数据。</p>
<ul class="text-muted small">
<li>创建租户后,可在各业务模块中选择租户</li>
<li>默认租户不可删除</li>
<li>可为每个租户配置独立的飞书应用和机器人</li>
</ul>
</div>
</div>
</div>
</div>
</div>
<!-- 创建/编辑租户模态框 -->
<div class="modal fade" id="tenantModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="tenantModalTitle">新建租户</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<input type="hidden" id="tenant-edit-id">
<div class="mb-3" id="tenant-id-group">
<label class="form-label">租户标识 (tenant_id)</label>
<input type="text" class="form-control" id="tenant-id-input" placeholder="如 market_a创建后不可修改">
<div class="form-text">唯一标识,建议使用英文和下划线</div>
</div>
<div class="mb-3">
<label class="form-label">租户名称</label>
<input type="text" class="form-control" id="tenant-name-input" placeholder="如 市场A">
</div>
<div class="mb-3">
<label class="form-label">描述</label>
<textarea class="form-control" id="tenant-desc-input" rows="2" placeholder="可选"></textarea>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">取消</button>
<button type="button" class="btn btn-primary" onclick="dashboard.saveTenant()">保存</button>
</div>
</div>
</div>
</div>
<!-- 创建工单模态框 -->
<div class="modal fade" id="createWorkOrderModal" tabindex="-1">
<div class="modal-dialog">

View File

@@ -67,8 +67,9 @@ class WebSocketServer:
"""处理创建会话请求"""
user_id = data.get("user_id", "anonymous")
work_order_id = data.get("work_order_id")
tenant_id = data.get("tenant_id")
session_id = self.chat_manager.create_session(user_id, work_order_id)
session_id = self.chat_manager.create_session(user_id, work_order_id, tenant_id=tenant_id)
response = {
"type": "session_created",