app.routes.chat
Chat route handlers for the AIFT Flask application.
Handles interactive chat sessions about completed forensic analysis results, including message submission, SSE streaming, and history management.
Attributes:
- chat_bp: Flask Blueprint for chat routes.
1"""Chat route handlers for the AIFT Flask application. 2 3Handles interactive chat sessions about completed forensic analysis results, 4including message submission, SSE streaming, and history management. 5 6Attributes: 7 chat_bp: Flask Blueprint for chat routes. 8""" 9 10from __future__ import annotations 11 12import copy 13import threading 14 15from flask import Blueprint, Response, current_app, request 16 17from ..chat import ChatManager 18 19from .state import ( 20 STATE_LOCK, 21 CHAT_PROGRESS, 22 error_response, 23 success_response, 24 get_case, 25 new_progress, 26 stream_sse, 27) 28from .tasks import ( 29 run_task_with_case_log_context, 30 run_chat, 31 load_case_analysis_results, 32) 33 34__all__ = ["chat_bp"] 35 36chat_bp = Blueprint("chat", __name__) 37 38 39@chat_bp.post("/api/cases/<case_id>/chat") 40def chat_with_case(case_id: str) -> Response | tuple[Response, int]: 41 """Initiate a chat interaction about completed analysis results. 42 43 Args: 44 case_id: UUID of the case. 45 46 Returns: 47 ``(Response, 202)`` confirming start, or error. 48 """ 49 case = get_case(case_id) 50 if case is None: 51 return error_response(f"Case not found: {case_id}", 404) 52 53 payload = request.get_json(silent=True) 54 if not isinstance(payload, dict): 55 return error_response("Chat payload must be a JSON object.", 400) 56 57 message = str(payload.get("message", "")).strip() 58 if not message: 59 return error_response("`message` is required.", 400) 60 61 with STATE_LOCK: 62 case_snapshot_for_check = dict(case) 63 if not load_case_analysis_results(case_snapshot_for_check): 64 return error_response("No analysis results available for this case. Run analysis first.", 400) 65 66 config = current_app.config.get("AIFT_CONFIG", {}) 67 if not isinstance(config, dict): 68 return error_response("Invalid in-memory configuration state.", 500) 69 70 with STATE_LOCK: 71 chat_state = CHAT_PROGRESS.setdefault(case_id, new_progress()) 72 if chat_state.get("status") == "running": 73 return error_response("Chat is already running for this case.", 409) 74 CHAT_PROGRESS[case_id] = new_progress(status="running") 75 76 config_snapshot = copy.deepcopy(config) 77 threading.Thread( 78 target=run_task_with_case_log_context, 79 args=(case_id, run_chat, case_id, message, config_snapshot), 80 daemon=True, 81 ).start() 82 return success_response({"status": "processing"}, 202) 83 84 85@chat_bp.get("/api/cases/<case_id>/chat/stream") 86def stream_chat_progress(case_id: str) -> Response | tuple[Response, int]: 87 """Stream chat response tokens via SSE. 88 89 Args: 90 case_id: UUID of the case. 91 92 Returns: 93 SSE Response, or 404 error. 94 """ 95 if get_case(case_id) is None: 96 return error_response(f"Case not found: {case_id}", 404) 97 return stream_sse(CHAT_PROGRESS, case_id) 98 99 100@chat_bp.get("/api/cases/<case_id>/chat/history") 101def get_case_chat_history(case_id: str) -> Response | tuple[Response, int]: 102 """Retrieve the full chat message history for a case. 103 104 Args: 105 case_id: UUID of the case. 106 107 Returns: 108 JSON response with chat messages, or 404 error. 109 """ 110 case = get_case(case_id) 111 if case is None: 112 return error_response(f"Case not found: {case_id}", 404) 113 with STATE_LOCK: 114 case_dir = case["case_dir"] 115 manager = ChatManager(case_dir) 116 return success_response({"messages": manager.get_history()}) 117 118 119@chat_bp.delete("/api/cases/<case_id>/chat/history") 120def clear_case_chat_history(case_id: str) -> Response | tuple[Response, int]: 121 """Clear the chat history for a case. 122 123 Args: 124 case_id: UUID of the case. 125 126 Returns: 127 JSON confirmation, or 404 error. 128 """ 129 case = get_case(case_id) 130 if case is None: 131 return error_response(f"Case not found: {case_id}", 404) 132 with STATE_LOCK: 133 case_dir = case["case_dir"] 134 audit_logger = case["audit"] 135 manager = ChatManager(case_dir) 136 manager.clear() 137 audit_logger.log("chat_history_cleared", {"case_id": case_id}) 138 return success_response({"status": "cleared", "case_id": case_id})
chat_bp =
<Blueprint 'chat'>