feat: 实现完整的人工审核功能与子图模块

- 新增三个核心子图:人工审核、意图理解、格式化输出
- 实现完整的审核 API 端点(/api/review/*)
- 前端添加审核确认界面(右下角固定框)
- 为每个子图创建分步测试代码
- 添加功能实现文档
This commit is contained in:
2026-04-25 13:24:50 +08:00
parent 851d52ed8d
commit bc26b81f08
9 changed files with 1981 additions and 4 deletions

View File

@@ -185,6 +185,199 @@ class APIClient:
"type": "error",
"message": f"请求失败: {str(e)}"
}
# ==================== 审核接口 ====================
def get_pending_reviews(self, limit: int = 100) -> List[Dict[str, Any]]:
"""
获取待审核列表
Args:
limit: 返回数量限制
Returns:
审核列表
"""
try:
resp = requests.get(
f"{self.base_url}/reviews/pending",
params={"limit": limit},
timeout=10
)
if resp.status_code == 200:
return resp.json()
else:
warning(f"获取待审核列表失败: HTTP {resp.status_code}")
return []
except Exception as e:
error(f"获取待审核列表异常: {e}")
return []
def get_review(self, review_id: str) -> Dict[str, Any]:
"""
获取审核详情
Args:
review_id: 审核 ID
Returns:
审核详情
"""
try:
resp = requests.get(
f"{self.base_url}/reviews/{review_id}",
timeout=10
)
if resp.status_code == 200:
return resp.json()
else:
warning(f"获取审核详情失败: HTTP {resp.status_code}")
return {}
except Exception as e:
error(f"获取审核详情异常: {e}")
return {}
def approve_review(self, review_id: str, reviewer: str, comment: str = "") -> bool:
"""
审核通过
Args:
review_id: 审核 ID
reviewer: 审核人
comment: 审核意见
Returns:
是否成功
"""
try:
payload = {
"review_id": review_id,
"reviewer": reviewer,
"comment": comment
}
resp = requests.post(
f"{self.base_url}/reviews/{review_id}/approve",
json=payload,
timeout=10
)
if resp.status_code == 200:
return True
else:
warning(f"审核通过失败: HTTP {resp.status_code}")
return False
except Exception as e:
error(f"审核通过异常: {e}")
return False
def reject_review(self, review_id: str, reviewer: str, comment: str = "") -> bool:
"""
审核拒绝
Args:
review_id: 审核 ID
reviewer: 审核人
comment: 审核意见
Returns:
是否成功
"""
try:
payload = {
"review_id": review_id,
"reviewer": reviewer,
"comment": comment
}
resp = requests.post(
f"{self.base_url}/reviews/{review_id}/reject",
json=payload,
timeout=10
)
if resp.status_code == 200:
return True
else:
warning(f"审核拒绝失败: HTTP {resp.status_code}")
return False
except Exception as e:
error(f"审核拒绝异常: {e}")
return False
def modify_review(self, review_id: str, reviewer: str, modified_content: str, comment: str = "") -> bool:
"""
审核修改
Args:
review_id: 审核 ID
reviewer: 审核人
modified_content: 修改后的内容
comment: 审核意见
Returns:
是否成功
"""
try:
payload = {
"review_id": review_id,
"reviewer": reviewer,
"modified_content": modified_content,
"comment": comment
}
resp = requests.post(
f"{self.base_url}/reviews/{review_id}/modify",
json=payload,
timeout=10
)
if resp.status_code == 200:
return True
else:
warning(f"审核修改失败: HTTP {resp.status_code}")
return False
except Exception as e:
error(f"审核修改异常: {e}")
return False
def request_review(self, thread_id: str, user_id: str, content: str) -> str:
"""
请求审核(测试用)
Args:
thread_id: 线程ID
user_id: 用户ID
content: 待审核内容
Returns:
审核ID
"""
try:
# 后端使用查询参数传递数据
resp = requests.post(
f"{self.base_url}/reviews/request",
params={
"thread_id": thread_id,
"user_id": user_id,
"content": content
},
timeout=10
)
if resp.status_code == 200:
return resp.json().get("review_id", "")
else:
warning(f"请求审核失败: HTTP {resp.status_code}")
return ""
except Exception as e:
error(f"请求审核异常: {e}")
return ""
# 全局 API 客户端实例(单例模式)

View File

@@ -22,6 +22,9 @@ def render_chat_area():
# 渲染历史消息
_render_chat_history()
# 检查并渲染审核确认界面
_render_review_confirmation()
# 输入框和流式响应处理
_render_input_and_response()
@@ -344,3 +347,201 @@ def _show_completion_stats(event: dict):
if token_usage:
total_tokens = token_usage.get("total_tokens", 0)
st.caption(f"📊 消耗 {total_tokens} tokens | ⏱️ {elapsed:.2f}s")
def _render_review_confirmation():
"""渲染审核确认界面 - 类似编程工具的右下角确认交互"""
# 获取当前线程的待审核内容
thread_id = AppState.get_current_thread_id()
user_id = AppState.get_user_id()
# 初始化会话状态
if 'pending_review' not in st.session_state:
st.session_state.pending_review = None
if 'show_review_modify' not in st.session_state:
st.session_state.show_review_modify = False
if 'review_error' not in st.session_state:
st.session_state.review_error = None
# 如果有待审核内容,先尝试从后端获取最新状态
if st.session_state.pending_review:
review_id = st.session_state.pending_review.get("review_id")
if review_id:
try:
latest_review = api_client.get_review(review_id)
if latest_review and latest_review.get("status") != "PENDING":
# 审核已处理,清除待审核状态
st.session_state.pending_review = None
st.session_state.show_review_modify = False
except Exception as e:
pass
# 如果没有待审核内容,检查是否有新的待审核内容
if not st.session_state.pending_review:
try:
pending_reviews = api_client.get_pending_reviews(limit=10)
# 查找当前线程的待审核内容
for review in pending_reviews:
if review.get("thread_id") == thread_id and review.get("status") == "PENDING":
st.session_state.pending_review = {
"review_id": review.get("review_id"),
"content_to_review": review.get("content_to_review"),
"created_at": review.get("created_at"),
"user_id": review.get("user_id")
}
break
except Exception as e:
st.session_state.review_error = str(e)
# 测试按钮 - 用于演示审核功能
with st.container():
st.markdown("<div style='height: 20px;'></div>", unsafe_allow_html=True)
col_test, col_info = st.columns([1, 3])
with col_test:
if st.button("🔧 测试审核", key="test_review_chat"):
# 创建一个测试审核请求
test_content = "这是一条待审核的测试内容。\n\n您可以选择:\n✅ 确定 - 批准此内容\n✏️ 修改 - 修改后批准\n❌ 拒绝 - 拒绝此内容"
review_id = api_client.request_review(thread_id, user_id, test_content)
if review_id:
st.session_state.pending_review = {
"review_id": review_id,
"content_to_review": test_content,
"created_at": "2024-01-01T12:00:00",
"user_id": user_id
}
st.success("✅ 已创建测试审核")
st.rerun()
else:
st.error("❌ 创建测试审核失败")
with col_info:
if st.session_state.get("review_error"):
st.warning(f"⚠️ {st.session_state.review_error}")
elif st.session_state.pending_review:
st.info("📋 有待审核内容")
# 显示审核确认界面
if st.session_state.pending_review:
review = st.session_state.pending_review
# 使用右下角的固定样式显示通过CSS实现
st.markdown("""
<style>
.review-container {
position: fixed;
bottom: 20px;
right: 20px;
width: 400px;
background: white;
border-radius: 12px;
box-shadow: 0 4px 20px rgba(0,0,0,0.15);
z-index: 1000;
padding: 20px;
border: 1px solid #e0e0e0;
}
.review-header {
font-weight: 600;
font-size: 16px;
margin-bottom: 12px;
color: #333;
display: flex;
justify-content: space-between;
align-items: center;
}
.review-content {
background: #f8f9fa;
padding: 12px;
border-radius: 8px;
margin-bottom: 16px;
max-height: 150px;
overflow-y: auto;
font-size: 14px;
line-height: 1.5;
white-space: pre-wrap;
}
.review-buttons {
display: flex;
gap: 8px;
justify-content: flex-end;
}
</style>
""", unsafe_allow_html=True)
# 渲染审核确认框
with st.container():
st.markdown('<div class="review-container">', unsafe_allow_html=True)
# 标题和关闭按钮
col_title, col_close = st.columns([4, 1])
with col_title:
st.markdown('<div class="review-header">📋 待审核内容</div>', unsafe_allow_html=True)
with col_close:
if st.button("", key="close_review"):
st.session_state.pending_review = None
st.rerun()
# 内容区域 - 转义 HTML
safe_content = review["content_to_review"].replace("<", "&lt;").replace(">", "&gt;")
st.markdown(f'<div class="review-content">{safe_content}</div>', unsafe_allow_html=True)
# 如果是修改模式,显示文本编辑框
if st.session_state.show_review_modify:
modified_content = st.text_area(
"修改内容",
value=review["content_to_review"],
key="modify_text_area",
height=100
)
col_cancel, col_submit = st.columns([1, 1])
with col_cancel:
if st.button("取消", key="cancel_modify", use_container_width=True):
st.session_state.show_review_modify = False
st.rerun()
with col_submit:
if st.button("提交修改", key="submit_modify", type="primary", use_container_width=True):
# 调用API提交修改
reviewer = user_id
success = api_client.modify_review(
review["review_id"],
reviewer,
modified_content
)
if success:
st.success("✅ 修改已提交")
st.session_state.pending_review = None
st.session_state.show_review_modify = False
st.rerun()
else:
st.error("❌ 提交失败")
else:
# 按钮区域
col_approve, col_modify, col_reject = st.columns([1, 1, 1])
with col_approve:
if st.button("✅ 确定", key="approve_btn", use_container_width=True, type="primary"):
# 调用审核通过API
reviewer = user_id
success = api_client.approve_review(review["review_id"], reviewer, "已批准")
if success:
st.success("✅ 已批准")
st.session_state.pending_review = None
st.rerun()
else:
st.error("❌ 操作失败")
with col_modify:
if st.button("✏️ 修改", key="modify_btn", use_container_width=True):
st.session_state.show_review_modify = True
st.rerun()
with col_reject:
if st.button("❌ 拒绝", key="reject_btn", use_container_width=True):
# 调用审核拒绝API
reviewer = user_id
success = api_client.reject_review(review["review_id"], reviewer, "已拒绝")
if success:
st.success("✅ 已拒绝")
st.session_state.pending_review = None
st.rerun()
else:
st.error("❌ 操作失败")
st.markdown('</div>', unsafe_allow_html=True)

View File

@@ -117,14 +117,14 @@ def main():
# 顶部标题(可选,也可以不放,让界面更像对话框)
st.markdown("<h3 style='text-align: center; font-weight: 400; color: #555; margin-bottom: 2rem;'>个人助手</h3>", unsafe_allow_html=True)
# 左侧边栏:合并用户登录、模型选择和历史对话
with st.sidebar:
render_sidebar()
# 将原本右侧的信息面板简化并移入侧边栏底部
st.divider()
render_info_panel()
# 中间主区域:全宽的聊天区域
render_chat_area()