mirror of https://github.com/Mai-with-u/MaiBot.git
添加表达方式的最后修改来源字段,并在AI检查和LLM判断时进行标记
parent
ff9c135c5f
commit
ce674f3422
|
|
@ -184,6 +184,7 @@ class ExpressionAutoCheckTask(AsyncTask):
|
||||||
try:
|
try:
|
||||||
expression.checked = True
|
expression.checked = True
|
||||||
expression.rejected = not suitable # 通过则rejected=0,不通过则rejected=1
|
expression.rejected = not suitable # 通过则rejected=0,不通过则rejected=1
|
||||||
|
expression.modified_by = 'ai' # 标记为AI检查
|
||||||
expression.save()
|
expression.save()
|
||||||
|
|
||||||
status = "通过" if suitable else "不通过"
|
status = "通过" if suitable else "不通过"
|
||||||
|
|
|
||||||
|
|
@ -134,12 +134,14 @@ class ReflectTracker:
|
||||||
if judgment == "Approve":
|
if judgment == "Approve":
|
||||||
self.expression.checked = True
|
self.expression.checked = True
|
||||||
self.expression.rejected = False
|
self.expression.rejected = False
|
||||||
|
self.expression.modified_by = 'ai' # 通过LLM判断也标记为ai
|
||||||
self.expression.save()
|
self.expression.save()
|
||||||
logger.info(f"Expression {self.expression.id} approved by operator.")
|
logger.info(f"Expression {self.expression.id} approved by operator.")
|
||||||
return True
|
return True
|
||||||
|
|
||||||
elif judgment == "Reject":
|
elif judgment == "Reject":
|
||||||
self.expression.checked = True
|
self.expression.checked = True
|
||||||
|
self.expression.modified_by = 'ai' # 通过LLM判断也标记为ai
|
||||||
corrected_situation = json_obj.get("corrected_situation")
|
corrected_situation = json_obj.get("corrected_situation")
|
||||||
corrected_style = json_obj.get("corrected_style")
|
corrected_style = json_obj.get("corrected_style")
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -328,6 +328,7 @@ class Expression(BaseModel):
|
||||||
create_date = FloatField(null=True) # 创建日期,允许为空以兼容老数据
|
create_date = FloatField(null=True) # 创建日期,允许为空以兼容老数据
|
||||||
checked = BooleanField(default=False) # 是否已检查
|
checked = BooleanField(default=False) # 是否已检查
|
||||||
rejected = BooleanField(default=False) # 是否被拒绝但未更新
|
rejected = BooleanField(default=False) # 是否被拒绝但未更新
|
||||||
|
modified_by = TextField(null=True) # 最后修改来源:'ai' 或 'user',为空表示未检查
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
table_name = "expression"
|
table_name = "expression"
|
||||||
|
|
|
||||||
|
|
@ -56,7 +56,6 @@ from .apis import (
|
||||||
person_api,
|
person_api,
|
||||||
plugin_manage_api,
|
plugin_manage_api,
|
||||||
send_api,
|
send_api,
|
||||||
auto_talk_api,
|
|
||||||
register_plugin,
|
register_plugin,
|
||||||
get_logger,
|
get_logger,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,7 @@ class ExpressionResponse(BaseModel):
|
||||||
create_date: Optional[float]
|
create_date: Optional[float]
|
||||||
checked: bool
|
checked: bool
|
||||||
rejected: bool
|
rejected: bool
|
||||||
|
modified_by: Optional[str] = None # 'ai' 或 'user' 或 None
|
||||||
|
|
||||||
|
|
||||||
class ExpressionListResponse(BaseModel):
|
class ExpressionListResponse(BaseModel):
|
||||||
|
|
@ -60,6 +61,7 @@ class ExpressionUpdateRequest(BaseModel):
|
||||||
chat_id: Optional[str] = None
|
chat_id: Optional[str] = None
|
||||||
checked: Optional[bool] = None
|
checked: Optional[bool] = None
|
||||||
rejected: Optional[bool] = None
|
rejected: Optional[bool] = None
|
||||||
|
require_unchecked: Optional[bool] = False # 用于人工审核时的冲突检测
|
||||||
|
|
||||||
|
|
||||||
class ExpressionUpdateResponse(BaseModel):
|
class ExpressionUpdateResponse(BaseModel):
|
||||||
|
|
@ -104,6 +106,7 @@ def expression_to_response(expression: Expression) -> ExpressionResponse:
|
||||||
create_date=expression.create_date,
|
create_date=expression.create_date,
|
||||||
checked=expression.checked,
|
checked=expression.checked,
|
||||||
rejected=expression.rejected,
|
rejected=expression.rejected,
|
||||||
|
modified_by=expression.modified_by,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -356,12 +359,26 @@ async def update_expression(
|
||||||
if not expression:
|
if not expression:
|
||||||
raise HTTPException(status_code=404, detail=f"未找到 ID 为 {expression_id} 的表达方式")
|
raise HTTPException(status_code=404, detail=f"未找到 ID 为 {expression_id} 的表达方式")
|
||||||
|
|
||||||
|
# 冲突检测:如果要求未检查状态,但已经被检查了
|
||||||
|
if request.require_unchecked and expression.checked:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=409,
|
||||||
|
detail=f"此表达方式已被{'AI自动' if expression.modified_by == 'ai' else '人工'}检查,请刷新列表"
|
||||||
|
)
|
||||||
|
|
||||||
# 只更新提供的字段
|
# 只更新提供的字段
|
||||||
update_data = request.model_dump(exclude_unset=True)
|
update_data = request.model_dump(exclude_unset=True)
|
||||||
|
|
||||||
|
# 移除 require_unchecked,它不是数据库字段
|
||||||
|
update_data.pop('require_unchecked', None)
|
||||||
|
|
||||||
if not update_data:
|
if not update_data:
|
||||||
raise HTTPException(status_code=400, detail="未提供任何需要更新的字段")
|
raise HTTPException(status_code=400, detail="未提供任何需要更新的字段")
|
||||||
|
|
||||||
|
# 如果更新了 checked 或 rejected,标记为用户修改
|
||||||
|
if 'checked' in update_data or 'rejected' in update_data:
|
||||||
|
update_data['modified_by'] = 'user'
|
||||||
|
|
||||||
# 更新最后活跃时间
|
# 更新最后活跃时间
|
||||||
update_data["last_active_time"] = time.time()
|
update_data["last_active_time"] = time.time()
|
||||||
|
|
||||||
|
|
@ -521,3 +538,253 @@ async def get_expression_stats(
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.exception(f"获取统计数据失败: {e}")
|
logger.exception(f"获取统计数据失败: {e}")
|
||||||
raise HTTPException(status_code=500, detail=f"获取统计数据失败: {str(e)}") from e
|
raise HTTPException(status_code=500, detail=f"获取统计数据失败: {str(e)}") from e
|
||||||
|
|
||||||
|
|
||||||
|
# ============ 审核相关接口 ============
|
||||||
|
|
||||||
|
class ReviewStatsResponse(BaseModel):
|
||||||
|
"""审核统计响应"""
|
||||||
|
total: int
|
||||||
|
unchecked: int
|
||||||
|
passed: int
|
||||||
|
rejected: int
|
||||||
|
ai_checked: int
|
||||||
|
user_checked: int
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/review/stats", response_model=ReviewStatsResponse)
|
||||||
|
async def get_review_stats(
|
||||||
|
maibot_session: Optional[str] = Cookie(None),
|
||||||
|
authorization: Optional[str] = Header(None)
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
获取审核统计数据
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
审核统计数据
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
verify_auth_token(maibot_session, authorization)
|
||||||
|
|
||||||
|
total = Expression.select().count()
|
||||||
|
unchecked = Expression.select().where(Expression.checked == False).count()
|
||||||
|
passed = Expression.select().where(
|
||||||
|
(Expression.checked == True) & (Expression.rejected == False)
|
||||||
|
).count()
|
||||||
|
rejected = Expression.select().where(
|
||||||
|
(Expression.checked == True) & (Expression.rejected == True)
|
||||||
|
).count()
|
||||||
|
ai_checked = Expression.select().where(Expression.modified_by == 'ai').count()
|
||||||
|
user_checked = Expression.select().where(Expression.modified_by == 'user').count()
|
||||||
|
|
||||||
|
return ReviewStatsResponse(
|
||||||
|
total=total,
|
||||||
|
unchecked=unchecked,
|
||||||
|
passed=passed,
|
||||||
|
rejected=rejected,
|
||||||
|
ai_checked=ai_checked,
|
||||||
|
user_checked=user_checked
|
||||||
|
)
|
||||||
|
|
||||||
|
except HTTPException:
|
||||||
|
raise
|
||||||
|
except Exception as e:
|
||||||
|
logger.exception(f"获取审核统计失败: {e}")
|
||||||
|
raise HTTPException(status_code=500, detail=f"获取审核统计失败: {str(e)}") from e
|
||||||
|
|
||||||
|
|
||||||
|
class ReviewListResponse(BaseModel):
|
||||||
|
"""审核列表响应"""
|
||||||
|
success: bool
|
||||||
|
total: int
|
||||||
|
page: int
|
||||||
|
page_size: int
|
||||||
|
data: List[ExpressionResponse]
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/review/list", response_model=ReviewListResponse)
|
||||||
|
async def get_review_list(
|
||||||
|
page: int = Query(1, ge=1, description="页码"),
|
||||||
|
page_size: int = Query(20, ge=1, le=100, description="每页数量"),
|
||||||
|
filter_type: str = Query("unchecked", description="筛选类型: unchecked/passed/rejected/all"),
|
||||||
|
search: Optional[str] = Query(None, description="搜索关键词"),
|
||||||
|
chat_id: Optional[str] = Query(None, description="聊天ID筛选"),
|
||||||
|
maibot_session: Optional[str] = Cookie(None),
|
||||||
|
authorization: Optional[str] = Header(None),
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
获取待审核/已审核的表达方式列表
|
||||||
|
|
||||||
|
Args:
|
||||||
|
page: 页码
|
||||||
|
page_size: 每页数量
|
||||||
|
filter_type: 筛选类型 (unchecked/passed/rejected/all)
|
||||||
|
search: 搜索关键词
|
||||||
|
chat_id: 聊天ID筛选
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
表达方式列表
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
verify_auth_token(maibot_session, authorization)
|
||||||
|
|
||||||
|
query = Expression.select()
|
||||||
|
|
||||||
|
# 根据筛选类型过滤
|
||||||
|
if filter_type == "unchecked":
|
||||||
|
query = query.where(Expression.checked == False)
|
||||||
|
elif filter_type == "passed":
|
||||||
|
query = query.where((Expression.checked == True) & (Expression.rejected == False))
|
||||||
|
elif filter_type == "rejected":
|
||||||
|
query = query.where((Expression.checked == True) & (Expression.rejected == True))
|
||||||
|
# all 不需要额外过滤
|
||||||
|
|
||||||
|
# 搜索过滤
|
||||||
|
if search:
|
||||||
|
query = query.where(
|
||||||
|
(Expression.situation.contains(search)) | (Expression.style.contains(search))
|
||||||
|
)
|
||||||
|
|
||||||
|
# 聊天ID过滤
|
||||||
|
if chat_id:
|
||||||
|
query = query.where(Expression.chat_id == chat_id)
|
||||||
|
|
||||||
|
# 排序:创建时间倒序
|
||||||
|
from peewee import Case
|
||||||
|
query = query.order_by(
|
||||||
|
Case(None, [(Expression.create_date.is_null(), 1)], 0),
|
||||||
|
Expression.create_date.desc()
|
||||||
|
)
|
||||||
|
|
||||||
|
total = query.count()
|
||||||
|
offset = (page - 1) * page_size
|
||||||
|
expressions = query.offset(offset).limit(page_size)
|
||||||
|
|
||||||
|
return ReviewListResponse(
|
||||||
|
success=True,
|
||||||
|
total=total,
|
||||||
|
page=page,
|
||||||
|
page_size=page_size,
|
||||||
|
data=[expression_to_response(expr) for expr in expressions]
|
||||||
|
)
|
||||||
|
|
||||||
|
except HTTPException:
|
||||||
|
raise
|
||||||
|
except Exception as e:
|
||||||
|
logger.exception(f"获取审核列表失败: {e}")
|
||||||
|
raise HTTPException(status_code=500, detail=f"获取审核列表失败: {str(e)}") from e
|
||||||
|
|
||||||
|
|
||||||
|
class BatchReviewItem(BaseModel):
|
||||||
|
"""批量审核项"""
|
||||||
|
id: int
|
||||||
|
rejected: bool
|
||||||
|
require_unchecked: bool = True # 默认要求未检查状态
|
||||||
|
|
||||||
|
|
||||||
|
class BatchReviewRequest(BaseModel):
|
||||||
|
"""批量审核请求"""
|
||||||
|
items: List[BatchReviewItem]
|
||||||
|
|
||||||
|
|
||||||
|
class BatchReviewResultItem(BaseModel):
|
||||||
|
"""批量审核结果项"""
|
||||||
|
id: int
|
||||||
|
success: bool
|
||||||
|
message: str
|
||||||
|
|
||||||
|
|
||||||
|
class BatchReviewResponse(BaseModel):
|
||||||
|
"""批量审核响应"""
|
||||||
|
success: bool
|
||||||
|
total: int
|
||||||
|
succeeded: int
|
||||||
|
failed: int
|
||||||
|
results: List[BatchReviewResultItem]
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/review/batch", response_model=BatchReviewResponse)
|
||||||
|
async def batch_review_expressions(
|
||||||
|
request: BatchReviewRequest,
|
||||||
|
maibot_session: Optional[str] = Cookie(None),
|
||||||
|
authorization: Optional[str] = Header(None),
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
批量审核表达方式
|
||||||
|
|
||||||
|
Args:
|
||||||
|
request: 批量审核请求
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
批量审核结果
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
verify_auth_token(maibot_session, authorization)
|
||||||
|
|
||||||
|
if not request.items:
|
||||||
|
raise HTTPException(status_code=400, detail="未提供要审核的表达方式")
|
||||||
|
|
||||||
|
results = []
|
||||||
|
succeeded = 0
|
||||||
|
failed = 0
|
||||||
|
|
||||||
|
for item in request.items:
|
||||||
|
try:
|
||||||
|
expression = Expression.get_or_none(Expression.id == item.id)
|
||||||
|
|
||||||
|
if not expression:
|
||||||
|
results.append(BatchReviewResultItem(
|
||||||
|
id=item.id,
|
||||||
|
success=False,
|
||||||
|
message=f"未找到 ID 为 {item.id} 的表达方式"
|
||||||
|
))
|
||||||
|
failed += 1
|
||||||
|
continue
|
||||||
|
|
||||||
|
# 冲突检测
|
||||||
|
if item.require_unchecked and expression.checked:
|
||||||
|
results.append(BatchReviewResultItem(
|
||||||
|
id=item.id,
|
||||||
|
success=False,
|
||||||
|
message=f"已被{'AI自动' if expression.modified_by == 'ai' else '人工'}检查"
|
||||||
|
))
|
||||||
|
failed += 1
|
||||||
|
continue
|
||||||
|
|
||||||
|
# 更新状态
|
||||||
|
expression.checked = True
|
||||||
|
expression.rejected = item.rejected
|
||||||
|
expression.modified_by = 'user'
|
||||||
|
expression.last_active_time = time.time()
|
||||||
|
expression.save()
|
||||||
|
|
||||||
|
results.append(BatchReviewResultItem(
|
||||||
|
id=item.id,
|
||||||
|
success=True,
|
||||||
|
message="通过" if not item.rejected else "拒绝"
|
||||||
|
))
|
||||||
|
succeeded += 1
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
results.append(BatchReviewResultItem(
|
||||||
|
id=item.id,
|
||||||
|
success=False,
|
||||||
|
message=str(e)
|
||||||
|
))
|
||||||
|
failed += 1
|
||||||
|
|
||||||
|
logger.info(f"批量审核完成: 成功 {succeeded}, 失败 {failed}")
|
||||||
|
|
||||||
|
return BatchReviewResponse(
|
||||||
|
success=True,
|
||||||
|
total=len(request.items),
|
||||||
|
succeeded=succeeded,
|
||||||
|
failed=failed,
|
||||||
|
results=results
|
||||||
|
)
|
||||||
|
|
||||||
|
except HTTPException:
|
||||||
|
raise
|
||||||
|
except Exception as e:
|
||||||
|
logger.exception(f"批量审核失败: {e}")
|
||||||
|
raise HTTPException(status_code=500, detail=f"批量审核失败: {str(e)}") from e
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue