feat: 2026-07-11 全量更新 - 代办集成+会议室预定+知识迭代修复+UI统一+Bug修复

== 已部署上线 (9项) ==
- 代办事项真实数据源集成 (企微审批API 8bug修复链)
- H5/坐席端 Logo样式统一+绿色背景
- 视频引导页修复 (localStorage key v2)
- 坐席端 v9 Vue版本修复 (ElMessage._context)
- 截图按钮 v10 修复 (getDisplayMedia user gesture)
- 扫码样式恢复+H5扫码登录跳转修复
- H5截图快捷键提示

== 代码完成待部署 (3项) ==
- 知识迭代3Bug修复 (#8 POST端点/#7 MERGE幂等/#6 过期检查)
- 会议室预定-小鱼易联终端 (40文件, 40/40测试通过)
- IT资产升级审批推送 (asset_service.py)

== 需求文档 (2项) ==
- 坐席端AI辅助消息框-PRD (4项新功能确认)
- 坐席端布局优化建议 v2.0 (7天计划)

== 新增文档 ==
- 日报-2026-07-11.md
- 知识迭代Bug修复报告-20260711.md
- 会议室预定-部署指南.md
- CHANGELOG.md 更新

== 测试 ==
- test_todo_integration.py: 40/40
- test_meetingroom.py: 40/40
- test_bugfix_ki_suggestions.py: 21/21
This commit is contained in:
Simon
2026-07-11 23:13:10 +08:00
parent 3d152fc8eb
commit bea288e414
928 changed files with 85169 additions and 54205 deletions
+340
View File
@@ -55,14 +55,35 @@ from app.schemas.automation import (
AutoMetricsResponse,
serialize_action,
serialize_approval,
serialize_information_item,
serialize_session,
PauseRequest,
ResumeRequest,
CorrectRequest,
SupplementRequest,
AgentResumeRequest,
AgentCloseRequest,
BatchCorrectRequest,
BatchCorrectResponse,
UndoCorrectionResponse,
CorrectionHistoryResponse,
VersionChainResponse,
VersionDiffResponse,
CompressionLogItem,
CompressionLogListResponse,
VersionDiffRequest,
)
from app.services.automation import (
ActionExecutor,
AutoSessionService,
AutomationException,
InformationItemService,
to_app_exception,
)
from app.services.automation.correction_service import CorrectionService
from app.services.automation.snapshot_service import SnapshotService
from app.services.automation.context_compressor import ContextCompressor
from app.models.automation import ContextCompression
from app.services.automation.progress_publisher import (
register_ws,
set_parties,
@@ -133,6 +154,56 @@ async def list_sessions(
return success_response([_session_min(s) for s in sessions])
# --- 复杂场景重构:暂停会话相关端点(必须在 /sessions/{session_id} 之前注册)---
@router.get("/sessions/paused", tags=["自动化闭环"])
async def list_paused_sessions(
employee_id: str = Query(..., description="员工ID"),
db: AsyncSession = Depends(get_db),
_agent: Agent = Depends(get_current_agent),
):
"""获取员工的暂停会话列表。"""
svc = AutoSessionService(db)
paused_list = await svc.list_paused_sessions(employee_id)
return success_response(paused_list)
@router.post("/sessions/resume", tags=["自动化闭环"])
async def resume_session_select(
req: ResumeRequest,
db: AsyncSession = Depends(get_db),
employee_id: str = Depends(get_current_employee_id),
):
"""恢复暂停会话(员工端,支持多任务选择)。
若未指定 session_id 且存在多个暂停会话,返回列表供前端选择。
"""
svc = AutoSessionService(db)
try:
result = await svc.resume_session(employee_id, req.session_id)
except AutomationException as e:
raise to_app_exception(e)
await db.commit()
# 多任务选择场景
if result.get("need_select"):
return success_response({
"need_select": True,
"paused_list": result["paused_list"],
})
session = result["session"]
if session is None:
return success_response({"need_select": False, "session": None, "message": "没有暂停的任务"})
return success_response({
"need_select": False,
"session": serialize_session(session).__dict__,
"resume_point": result.get("resume_point"),
"pending_items": result.get("pending_items", []),
})
@router.get("/sessions/{session_id}", tags=["自动化闭环"])
async def get_session(
session_id: str,
@@ -285,6 +356,274 @@ async def feedback_session(
return success_response(serialize_session(session).__dict__)
# --- 复杂场景重构:员工端暂停/恢复/更正/补充/信息项端点 ---
@router.post("/sessions/{session_id}/pause", tags=["自动化闭环"])
async def pause_session(
session_id: str,
req: PauseRequest,
db: AsyncSession = Depends(get_db),
employee_id: str = Depends(get_current_employee_id),
):
"""暂停会话(员工/坐席均可)。"""
svc = AutoSessionService(db)
try:
session = await svc.pause_session(session_id, reason=req.reason)
except AutomationException as e:
raise to_app_exception(e)
await db.commit()
return success_response(serialize_session(session).__dict__)
@router.post("/sessions/{session_id}/resume", tags=["自动化闭环"])
async def resume_session_by_id(
session_id: str,
db: AsyncSession = Depends(get_db),
employee_id: str = Depends(get_current_employee_id),
):
"""恢复指定会话(员工端,选中某个暂停会话后调用)。"""
svc = AutoSessionService(db)
try:
result = await svc.resume_session(employee_id, session_id)
except AutomationException as e:
raise to_app_exception(e)
await db.commit()
session = result["session"]
return success_response({
"session": serialize_session(session).__dict__ if session else None,
"resume_point": result.get("resume_point"),
"pending_items": result.get("pending_items", []),
})
@router.post("/sessions/{session_id}/correct", tags=["自动化闭环"])
async def correct_info(
session_id: str,
req: CorrectRequest,
db: AsyncSession = Depends(get_db),
employee_id: str = Depends(get_current_employee_id),
):
"""信息更正。"""
svc = AutoSessionService(db)
try:
item = await svc.correct_info(
session_id, req.field, req.new_value, req.old_value
)
except AutomationException as e:
raise to_app_exception(e)
await db.commit()
return success_response(serialize_information_item(item).__dict__)
@router.post("/sessions/{session_id}/supplement", tags=["自动化闭环"])
async def supplement_info(
session_id: str,
req: SupplementRequest,
db: AsyncSession = Depends(get_db),
employee_id: str = Depends(get_current_employee_id),
):
"""信息补充。"""
svc = AutoSessionService(db)
try:
item = await svc.supplement_info(session_id, req.field, req.value)
except AutomationException as e:
raise to_app_exception(e)
await db.commit()
return success_response(serialize_information_item(item).__dict__)
@router.get("/sessions/{session_id}/info-items", tags=["自动化闭环"])
async def get_info_items(
session_id: str,
db: AsyncSession = Depends(get_db),
_agent: Agent = Depends(get_current_agent),
):
"""获取会话信息项列表。"""
info_svc = InformationItemService(db)
items = await info_svc.get_items(session_id)
return success_response([serialize_information_item(item).__dict__ for item in items])
# --- 复杂场景重构:坐席端代恢复/关闭端点 ---
@router.post("/sessions/{session_id}/agent-resume", tags=["自动化闭环"])
async def agent_resume_session(
session_id: str,
req: AgentResumeRequest,
db: AsyncSession = Depends(get_db),
current_agent: Agent = Depends(get_current_agent),
):
"""坐席代恢复暂停会话。"""
svc = AutoSessionService(db)
try:
session = await svc.agent_resume(
session_id, current_agent.user_id, req.note
)
except AutomationException as e:
raise to_app_exception(e)
await db.commit()
return success_response(serialize_session(session).__dict__)
@router.post("/sessions/{session_id}/agent-close", tags=["自动化闭环"])
async def agent_close_session(
session_id: str,
req: AgentCloseRequest,
db: AsyncSession = Depends(get_db),
current_agent: Agent = Depends(get_current_agent),
):
"""坐席关闭暂停会话。"""
svc = AutoSessionService(db)
try:
session = await svc.agent_close(
session_id, current_agent.user_id, req.note
)
except AutomationException as e:
raise to_app_exception(e)
await db.commit()
return success_response(serialize_session(session).__dict__)
# --- 复杂场景重构第二阶段:P2/P3 端点 ---
@router.post("/sessions/{session_id}/batch-correct", tags=["自动化闭环"])
async def batch_correct_info(
session_id: str,
req: BatchCorrectRequest,
db: AsyncSession = Depends(get_db),
employee_id: str = Depends(get_current_employee_id),
):
"""批量更正信息项(原子事务,失败整体回滚)。"""
svc = CorrectionService(db)
try:
result = await svc.batch_correct(
session_id, req.corrections, req.reason
)
except AutomationException:
raise to_app_exception(AutomationException(4019, "批量更正失败,已回滚"))
except Exception as e:
logger.error(f"批量更正失败: {e}")
raise to_app_exception(AutomationException(4019, f"批量更正失败: {e}"))
await db.commit()
return success_response(result.to_dict())
@router.post("/sessions/{session_id}/undo-correction", tags=["自动化闭环"])
async def undo_correction(
session_id: str,
db: AsyncSession = Depends(get_db),
employee_id: str = Depends(get_current_employee_id),
):
"""撤销最近一次更正。"""
svc = CorrectionService(db)
try:
result = await svc.undo_correction(session_id)
except ValueError as e:
if "超限" in str(e):
raise to_app_exception(AutomationException(4018, str(e)))
raise AppException(4004, str(e))
await db.commit()
# 计算剩余撤销次数:查询当前已撤销的快照数量
from app.config import settings
from sqlalchemy import func, select as _select
from app.models.automation import InformationSnapshot
undone_stmt = (
_select(func.count(InformationSnapshot.id))
.where(
InformationSnapshot.session_id == session_id,
InformationSnapshot.is_undone == True, # noqa: E712
)
)
undone_count = (await db.execute(undone_stmt)).scalar() or 0
remaining = settings.max_undo_count - undone_count
result["remaining_undo_count"] = max(0, remaining)
return success_response(result)
@router.get("/sessions/{session_id}/correction-history", tags=["自动化闭环"])
async def get_correction_history(
session_id: str,
db: AsyncSession = Depends(get_db),
_agent: Agent = Depends(get_current_agent),
):
"""获取更正历史。"""
svc = CorrectionService(db)
history = await svc.get_correction_history(session_id)
return success_response({"history": history})
@router.get("/sessions/{session_id}/version-chain/{item_name}", tags=["自动化闭环"])
async def get_version_chain(
session_id: str,
item_name: str,
db: AsyncSession = Depends(get_db),
_agent: Agent = Depends(get_current_agent),
):
"""获取信息项版本链。"""
svc = CorrectionService(db)
chain = await svc.get_version_chain(session_id, item_name)
return success_response({"item_key": item_name, "chain": chain})
@router.post("/sessions/{session_id}/version-diff/{item_name}", tags=["自动化闭环"])
async def get_version_diff(
session_id: str,
item_name: str,
req: VersionDiffRequest,
db: AsyncSession = Depends(get_db),
_agent: Agent = Depends(get_current_agent),
):
"""版本对比。"""
svc = CorrectionService(db)
diff = await svc.get_version_diff(session_id, item_name, req.v1, req.v2)
return success_response(diff)
@router.get("/sessions/{session_id}/compression-logs", tags=["自动化闭环"])
async def get_compression_logs(
session_id: str,
page: int = Query(1, ge=1),
page_size: int = Query(20, ge=1, le=100),
db: AsyncSession = Depends(get_db),
_agent: Agent = Depends(get_current_agent),
):
"""获取上下文压缩日志。"""
from sqlalchemy import func, select
stmt = (
select(ContextCompression)
.where(ContextCompression.session_id == session_id)
.order_by(ContextCompression.created_at.desc())
.offset((page - 1) * page_size)
.limit(page_size)
)
result = await db.execute(stmt)
logs = result.scalars().all()
count_stmt = select(func.count()).select_from(ContextCompression).where(
ContextCompression.session_id == session_id
)
total = (await db.execute(count_stmt)).scalar() or 0
return success_response({
"logs": [
{
"id": log.id,
"session_id": log.session_id,
"tokens_before": log.tokens_before,
"tokens_after": log.tokens_after,
"compression_ratio": float(log.compression_ratio),
"task_node": log.task_node,
"duration_ms": log.duration_ms,
"compression_level": log.compression_level,
"summary": log.summary,
"created_at": log.created_at.isoformat() if log.created_at else None,
}
for log in logs
],
"total": total,
})
# ==========================================================================
# 管理端接口(配置写需 OTP
# ==========================================================================
@@ -405,6 +744,7 @@ def _session_min(session) -> dict:
"mode": session.mode,
"confidence": session.confidence,
"title": session.title,
"paused_at": session.paused_at.isoformat() if session.paused_at else None,
"created_at": session.created_at.isoformat() if session.created_at else None,
"updated_at": session.updated_at.isoformat() if session.updated_at else None,
}