@@ -542,6 +542,9 @@ def save():
542542 elif action == "clear_history" :
543543 self .plugin .recent_incidents .clear ()
544544 message = "已清空拦截记录"
545+ elif action == "clear_logs" :
546+ self .plugin .analysis_logs .clear ()
547+ message = "已清空分析日志"
545548 else :
546549 message = "未知操作"
547550 success = False
@@ -551,6 +554,7 @@ def _render_dashboard(self, token: str, notice: str, success: bool) -> str:
551554 config = self .plugin .config
552555 stats = self .plugin .stats
553556 incidents = list (self .plugin .recent_incidents )
557+ analysis_logs = list (self .plugin .analysis_logs )
554558 whitelist = config .get ("whitelist" , [])
555559 blacklist = config .get ("blacklist" , {})
556560 defense_mode = config .get ("defense_mode" , "sentry" )
@@ -649,6 +653,11 @@ def _render_dashboard(self, token: str, notice: str, success: bool) -> str:
649653 f"<input type='hidden' name='action' value='clear_history'/>"
650654 f"<button class='btn danger' type='submit'>清空拦截记录</button></form>"
651655 )
656+ html_parts .append (
657+ f"<form class='inline-form' method='get' action='/'>{ token_input } "
658+ f"<input type='hidden' name='action' value='clear_logs'/>"
659+ f"<button class='btn danger' type='submit'>清空分析日志</button></form>"
660+ )
652661 html_parts .append ("</div></div>" )
653662
654663 html_parts .append ("</div>" )
@@ -733,6 +742,31 @@ def _render_dashboard(self, token: str, notice: str, success: bool) -> str:
733742 html_parts .append ("<div class='small'>尚未记录拦截事件。</div>" )
734743 html_parts .append ("</section>" )
735744
745+ html_parts .append ("<section class='card'><h3>分析日志</h3>" )
746+ if analysis_logs :
747+ html_parts .append ("<table><thead><tr><th>时间</th><th>来源</th><th>结果</th><th>严重级别</th><th>得分</th><th>触发</th><th>原因</th><th>内容预览</th></tr></thead><tbody>" )
748+ for item in analysis_logs [:50 ]:
749+ timestamp = datetime .fromtimestamp (item ["time" ]).strftime ("%Y-%m-%d %H:%M:%S" )
750+ source = item ["sender_id" ]
751+ if item .get ("group_id" ):
752+ source = f"{ source } @ { item ['group_id' ]} "
753+ html_parts .append (
754+ "<tr>"
755+ f"<td>{ escape (timestamp )} </td>"
756+ f"<td>{ escape (str (source ))} </td>"
757+ f"<td>{ escape (item .get ('result' , '' ))} </td>"
758+ f"<td>{ escape (item .get ('severity' , '' ))} </td>"
759+ f"<td>{ escape (str (item .get ('score' , 0 )))} </td>"
760+ f"<td>{ escape (item .get ('trigger' , '' ))} </td>"
761+ f"<td>{ escape (item .get ('reason' , '' ))} </td>"
762+ f"<td>{ escape (item .get ('prompt_preview' , '' ))} </td>"
763+ "</tr>"
764+ )
765+ html_parts .append ("</tbody></table>" )
766+ else :
767+ html_parts .append ("<div class='small'>暂无分析日志,可等待消息经过后查看。</div>" )
768+ html_parts .append ("</section>" )
769+
736770 html_parts .append ("</div></body></html>" )
737771 return "\n " .join (html_parts )
738772
@@ -798,6 +832,7 @@ def __init__(self, context: Context, config: AstrBotConfig = None):
798832 self .detector = PromptThreatDetector ()
799833 history_size = max (10 , int (self .config .get ("incident_history_size" , 100 )))
800834 self .recent_incidents : deque = deque (maxlen = history_size )
835+ self .analysis_logs : deque = deque (maxlen = 200 )
801836 self .stats : Dict [str , int ] = {
802837 "total_intercepts" : 0 ,
803838 "regex_hits" : 0 ,
@@ -853,6 +888,20 @@ def _record_incident(self, event: AstrMessageEvent, analysis: Dict[str, Any], de
853888 else :
854889 self .stats ["heuristic_hits" ] += 1
855890
891+ def _append_analysis_log (self , event : AstrMessageEvent , analysis : Dict [str , Any ], intercepted : bool ):
892+ entry = {
893+ "time" : time .time (),
894+ "sender_id" : event .get_sender_id (),
895+ "group_id" : event .get_group_id (),
896+ "severity" : analysis .get ("severity" , "none" ),
897+ "score" : analysis .get ("score" , 0 ),
898+ "trigger" : analysis .get ("trigger" , "scan" ),
899+ "result" : "拦截" if intercepted else "放行" ,
900+ "reason" : analysis .get ("reason" ) or ("未检测到明显风险" if not intercepted else "检测到风险" ),
901+ "prompt_preview" : self ._make_prompt_preview (analysis .get ("prompt" , "" )),
902+ }
903+ self .analysis_logs .appendleft (entry )
904+
856905 def _build_stats_summary (self ) -> str :
857906 return (
858907 "🛡️ 反注入防护统计:\n "
@@ -1044,6 +1093,7 @@ async def intercept_llm_request(self, event: AstrMessageEvent, req: ProviderRequ
10441093 "trigger" : "blacklist" ,
10451094 }
10461095 self ._record_incident (event , analysis , self .config .get ("defense_mode" , "sentry" ), "blacklist" )
1096+ self ._append_analysis_log (event , analysis , True )
10471097 event .stop_event ()
10481098 return
10491099 del blacklist [sender_id ]
@@ -1067,7 +1117,17 @@ async def intercept_llm_request(self, event: AstrMessageEvent, req: ProviderRequ
10671117 await self ._apply_scorch_defense (req )
10681118 event .stop_event ()
10691119
1120+ analysis ["reason" ] = reason
10701121 self ._record_incident (event , analysis , defense_mode , defense_mode )
1122+ self ._append_analysis_log (event , analysis , True )
1123+ else :
1124+ if not analysis .get ("reason" ):
1125+ analysis ["reason" ] = "未检测到明显风险"
1126+ if not analysis .get ("severity" ):
1127+ analysis ["severity" ] = "none"
1128+ if not analysis .get ("trigger" ):
1129+ analysis ["trigger" ] = "scan"
1130+ self ._append_analysis_log (event , analysis , False )
10711131 except Exception as exc :
10721132 logger .error (f"⚠️ [拦截] 注入分析时发生错误: { exc } " )
10731133 await self ._apply_scorch_defense (req )
0 commit comments