From fe9b146375080e9df63252e2f72ac3ac8f98f339 Mon Sep 17 00:00:00 2001 From: michael Date: Wed, 27 Aug 2025 15:20:56 -0500 Subject: [PATCH] major refactor of entire application - migrate sync -> async including pymongo -> motor, flask -> quart, google-generativeai -> google-genai --- .claude/settings.local.json | 6 +- .gitignore | 1 + backend/__pycache__/run.cpython-313.pyc | Bin 3290 -> 10337 bytes backend/app/__init__.py | 55 ++- .../app/__pycache__/__init__.cpython-313.pyc | Bin 6187 -> 6276 bytes backend/app/__pycache__/db.cpython-313.pyc | Bin 3406 -> 5325 bytes .../__pycache__/extensions.cpython-313.pyc | Bin 663 -> 842 bytes backend/app/auth/__init__.py | 9 + backend/app/auth/quart_jwt.py | 204 +++++++++ backend/app/db.py | 86 +++- backend/app/extensions.py | 30 +- .../__pycache__/focus_group.cpython-313.pyc | Bin 48551 -> 53770 bytes .../models/__pycache__/folder.cpython-313.pyc | Bin 15645 -> 17322 bytes .../__pycache__/persona.cpython-313.pyc | Bin 5551 -> 6274 bytes .../models/__pycache__/user.cpython-313.pyc | Bin 5461 -> 6242 bytes backend/app/models/focus_group.py | 276 ++++++------ backend/app/models/folder.py | 91 ++-- backend/app/models/persona.py | 42 +- backend/app/models/user.py | 48 +-- .../__pycache__/ai_personas.cpython-313.pyc | Bin 43226 -> 44042 bytes .../routes/__pycache__/auth.cpython-313.pyc | Bin 8426 -> 10101 bytes .../focus_group_ai.cpython-313.pyc | Bin 53726 -> 56522 bytes .../__pycache__/focus_groups.cpython-313.pyc | Bin 74702 -> 78065 bytes .../__pycache__/folders.cpython-313.pyc | Bin 12203 -> 13519 bytes .../__pycache__/personas.cpython-313.pyc | Bin 12155 -> 13306 bytes backend/app/routes/ai_personas.py | 187 ++++---- backend/app/routes/auth.py | 59 ++- backend/app/routes/focus_group_ai.py | 192 ++++----- backend/app/routes/focus_groups.py | 245 ++++++----- backend/app/routes/folders.py | 64 +-- backend/app/routes/personas.py | 60 +-- .../ai_moderator_service.cpython-313.pyc | Bin 29545 -> 31226 bytes .../ai_persona_service.cpython-313.pyc | Bin 27383 -> 27751 bytes ...us_conversation_controller.cpython-313.pyc | Bin 53161 -> 54502 bytes ...nversation_context_service.cpython-313.pyc | Bin 31780 -> 32290 bytes ...versation_decision_service.cpython-313.pyc | Bin 15681 -> 16419 bytes ...conversation_state_manager.cpython-313.pyc | Bin 33565 -> 34047 bytes .../customer_data_service.cpython-313.pyc | Bin 7543 -> 9576 bytes ...cus_group_response_service.cpython-313.pyc | Bin 23128 -> 23412 bytes .../focus_group_service.cpython-313.pyc | Bin 23742 -> 23943 bytes .../image_description_service.cpython-313.pyc | Bin 11562 -> 11691 bytes .../key_theme_service.cpython-313.pyc | Bin 18591 -> 18882 bytes .../__pycache__/llm_service.cpython-313.pyc | Bin 30244 -> 30770 bytes .../persona_export_service.cpython-313.pyc | Bin 7171 -> 7248 bytes ...rsona_modification_service.cpython-313.pyc | Bin 10116 -> 10288 bytes backend/app/services/ai_moderator_service.py | 88 ++-- backend/app/services/ai_persona_service.py | 20 +- backend/app/services/ai_runner_service.py | 376 +++++++++++++++++ .../autonomous_conversation_controller.py | 116 +++-- .../services/conversation_context_service.py | 26 +- .../services/conversation_decision_service.py | 36 +- .../services/conversation_state_manager.py | 24 +- backend/app/services/customer_data_service.py | 37 +- .../services/focus_group_response_service.py | 14 +- backend/app/services/focus_group_service.py | 6 +- .../app/services/image_description_service.py | 6 +- backend/app/services/key_theme_service.py | 14 +- backend/app/services/llm_service.py | 287 ++++++------- .../app/services/persona_export_service.py | 4 +- .../services/persona_modification_service.py | 8 +- backend/app/websocket_manager.py | 2 +- backend/app/websocket_manager_async.py | 398 ++++++++++++++++++ backend/requirements.txt | 2 +- backend/run.py | 312 +++++++++++--- ...b0392-086a55e9378e46c1bf7531383c3a6cba.jpg | Bin 0 -> 89310 bytes ...b0392-191a0d87408546598d977f4f948aa1c0.jpg | Bin 0 -> 25348 bytes ...b0392-60f64d7a0361482999e20c8a547125d9.jpg | Bin 0 -> 104575 bytes ...b0392-b6ef459cfa31460e92020879a216e1d0.jpg | Bin 0 -> 110961 bytes src/pages/FocusGroupSession.tsx | 26 +- 69 files changed, 2379 insertions(+), 1078 deletions(-) create mode 100644 backend/app/auth/__init__.py create mode 100644 backend/app/auth/quart_jwt.py create mode 100644 backend/app/services/ai_runner_service.py create mode 100644 backend/app/websocket_manager_async.py create mode 100644 backend/uploads/focus-group-68af42ff19ed40daa02b0392/fg-68af42ff19ed40daa02b0392-086a55e9378e46c1bf7531383c3a6cba.jpg create mode 100644 backend/uploads/focus-group-68af42ff19ed40daa02b0392/fg-68af42ff19ed40daa02b0392-191a0d87408546598d977f4f948aa1c0.jpg create mode 100644 backend/uploads/focus-group-68af42ff19ed40daa02b0392/fg-68af42ff19ed40daa02b0392-60f64d7a0361482999e20c8a547125d9.jpg create mode 100644 backend/uploads/focus-group-68af42ff19ed40daa02b0392/fg-68af42ff19ed40daa02b0392-b6ef459cfa31460e92020879a216e1d0.jpg diff --git a/.claude/settings.local.json b/.claude/settings.local.json index fa21d6be..26ec7441 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -18,7 +18,11 @@ "Bash(find:*)", "Bash(npx tsc:*)", "WebFetch(domain:platform.openai.com)", - "WebFetch(domain:cookbook.openai.com)" + "WebFetch(domain:cookbook.openai.com)", + "Bash(pip uninstall:*)", + "Bash(pip install:*)", + "mcp__gpt5-bridge__call_gpt5", + "WebSearch" ], "deny": [] }, diff --git a/.gitignore b/.gitignore index d048741b..19d3a29d 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ dist/ # Ignore Python cache files __pycache__/ *.py[cod] +*pycache* diff --git a/backend/__pycache__/run.cpython-313.pyc b/backend/__pycache__/run.cpython-313.pyc index eddc996a350bbee3e3d61e2447ce65c8845bbd78..26dacc0c08af852624519dc2e92f63f67ce1161a 100644 GIT binary patch literal 10337 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 Ba#jEU diff --git a/backend/app/__init__.py b/backend/app/__init__.py index c4fe2e64..50276f30 100644 --- a/backend/app/__init__.py +++ b/backend/app/__init__.py @@ -1,10 +1,10 @@ -from flask import Flask -from flask_cors import CORS -from flask_jwt_extended import JWTManager -from flask_socketio import SocketIO +from quart import Quart +from quart_cors import cors +# No longer using Flask-JWT-Extended - replaced with Quart-compatible JWT from dotenv import load_dotenv import os import tempfile +import asyncio load_dotenv() @@ -43,10 +43,10 @@ def setup_temp_directories(): return temp_dir, upload_dir def create_app(): - # Set up temp directories BEFORE creating Flask app + # Set up temp directories BEFORE creating Quart app temp_dir, upload_dir = setup_temp_directories() - app = Flask(__name__) + app = Quart(__name__) # Setup custom logging configuration try: @@ -67,14 +67,14 @@ def create_app(): app.config['TIMEOUT'] = 300 # 5 minutes app.config['MAX_CONTENT_LENGTH'] = 16 * 1024 * 1024 # 16 MB max upload - # Configure Flask/Werkzeug file upload settings + # Configure Quart/Werkzeug file upload settings app.config['UPLOAD_FOLDER'] = upload_dir app.config['UPLOAD_EXTENSIONS'] = ['.jpg', '.jpeg', '.png'] - # Configure temp directory for Flask/Werkzeug + # Configure temp directory for Quart/Werkzeug if temp_dir and os.path.isdir(temp_dir): app.config['TEMP_FOLDER'] = temp_dir - print(f"✓ Flask configured with temp directory: {temp_dir}") + print(f"✓ Quart configured with temp directory: {temp_dir}") # Additional Werkzeug configuration for multipart form handling app.config['MAX_CONTENT_PATH'] = None # Don't limit content path @@ -83,19 +83,20 @@ def create_app(): app.config['MAX_FORM_MEMORY_SIZE'] = 16 * 1024 * 1024 # Keep small uploads in memory # Initialize extensions - CORS(app, resources={r"/api/*": {"origins": "*"}}) - jwt = JWTManager(app) + app = cors(app, allow_origin="*", allow_methods=["GET", "POST", "PUT", "DELETE", "OPTIONS"]) - # Initialize SocketIO using singleton pattern (GPT-5 fix for AI mode WebSocket issues) - from .extensions import socketio - socketio.init_app(app) # Bind the singleton SocketIO instance to this Flask app + # JWT is now handled by custom Quart-compatible auth system + # No longer using JWTManager(app) due to Flask/Quart incompatibility + + # Initialize AsyncServer WebSocket functionality + from .extensions import socketio_server # Store socketio reference on app for backward compatibility - app.socketio = socketio + app.socketio = socketio_server - # Initialize WebSocket manager with singleton SocketIO - from app.websocket_manager import init_websocket_manager - websocket_manager = init_websocket_manager() # No parameter needed - uses singleton + # Initialize async WebSocket manager + from app.websocket_manager_async import init_async_websocket_manager + websocket_manager = init_async_websocket_manager() # Debug tap removed - using simpler GPT-5 diagnostic logging instead @@ -103,8 +104,12 @@ def create_app(): import threading main_process_id = os.getpid() main_thread_id = threading.get_ident() - print(f"🔌 PROCESS DEBUG - Flask app initialized with WebSocket manager") - print(f"🔌 PROCESS DEBUG - Main Flask PID: {main_process_id}, Thread: {main_thread_id}") + print(f"🔌 PROCESS DEBUG - Quart app initialized with WebSocket manager") + print(f"🔌 PROCESS DEBUG - Main Quart PID: {main_process_id}, Thread: {main_thread_id}") + + # Initialize AI Runner service for autonomous conversations + from app.services.ai_runner_service import init_ai_runner + init_ai_runner() # Register blueprints from app.routes.auth import auth_bp @@ -126,7 +131,11 @@ def create_app(): def health_check(): return {'status': 'ok', 'message': 'Backend is running'}, 200 - # Store socketio reference on app for access in routes - app.socketio = socketio + # Create ASGI app with SocketIO integration + import socketio as socketio_pkg + asgi_app = socketio_pkg.ASGIApp(socketio_server, app) - return app \ No newline at end of file + # Store reference to the original Quart app for access in routes + asgi_app.quart_app = app + + return asgi_app \ No newline at end of file diff --git a/backend/app/__pycache__/__init__.cpython-313.pyc b/backend/app/__pycache__/__init__.cpython-313.pyc index 9c9d5a14fc59730e1cdac5d1d16b9556c459e02b..6f7028e19f2b6af646a8d6d7cfba16c304544355 100644 GIT binary patch delta 1441 zcmZWpO-vg{6rS<_58gH2#TX36U?4S6Xp}%i6rrMqzg=2`*DffjwAq-gSz%_~S(~)# z0hA)Ow>FwvPw6>Ts|vN(_Ec5eD3RJMiqal>NKa8|q)NSXW*3)KUCG~j^S*Cp-h2Dj z4_?{7$eqSw5kQ~(@E4m`llQn3ym9Zs#5@itK=H{z9CU05hu-oJfJvsLPZlWtZh*r| zfMTTT%Lkvba30$J)ux6U)bUkJY<>0p4*@uK@Nw`2!s5YJ_=%@D0hG>FsHCR7$fI;A z-EzB>K>jRPFV+Nh2745!q-9a+K!GeM2hOiWLCOF*i9$EqTqOX-aZ>k)Q?1NUxEv~8 zY?0^oUp{iPuN75#NH*57+glEEK*^LN9Ju70gf2@fgi4>>C3T}HO(J@pgu`UzwA6#z z9KT=gl`<&i_ycmEltrB5=j48A0P&7LDCeX>6nFd~c}N;Y?etVnu>b)1G}cSjIDs6- zVvha&%tknOJ#uCxE(TZ0IyZ9F>4JcSvQSi7#hlyq;-tr2A+hW!i?xv!R~E@9TpHdu z_?+XR`=-cu{L-!sNAiB#yD(R>eJcf}WP4Z2B|C6^Zh5XWX9o)_rKQ3fO5S6)X|VlgHIN;1SKE4H)7-G0vd+;L&s%0?OK%vaYUy}K$28PqAfusb)_bU;-qCNn0(DzM zT2;r*S+W*Son_OA&!2hU#yfb<-Djw{iI9%TQT&M?U!&KMuiLkxCF-2A((ou5O;&j_B_;bYvMOvhZ~l2(_xP z(^|bg{=cl=pj{bDPX`cJHRZVJadGdk=>o#eCHE zJ59k}CpWi2?61q6i@=6S=VAx@DeKPA0MiAXSvE{O#^S6B%%%~u;d84DUE4M{nl*g} yzfWWA)39Em@NCQrA$$xvAA|I7Aaer3CxAZz+^-+#q3)_)7$Wg_s#-IKX(&Vd{(5P)Fkotz7~44dL;e7dJ(90~@ya)sld}GxyHLne4<0;KQ=VRUPUd zF6BSA2Dkgk%XGz6n(k|Z?{nQJGy{Zq#m#_e+u#I#n&qS1s62!V z4221Pj2o9dhCRvWVq9Eij7@@Pxe3{8*hzkpOUORMPVq@DB@g3_ETt9gq9+blG}*x0 z27Y1ScLx4}aaCiOOy+o5DwZUe09AiKeg_b3q}_3_Xt`M4zs-1|*NC0+XDVh)C(QB{eH{+UsbZI0ufgEeWZL zEUIXMDB)wub*0`>YiroMrJ{m%7#M>^%@d5nMQth=ffux5Fm^5-Kqs*yIz_y#*c&}h zOt;>+)i$C<=ZQM}VvsfMelT@~#AG~=3dAZBSR`0WT?zCIA6P|G&-Bo8>oXhGUn=O42M<-VGo3RV6+F)J&@>u*a2`I0PnBB Tz3chY#zOXJ0bYgBYh3mpiJMV? diff --git a/backend/app/__pycache__/db.cpython-313.pyc b/backend/app/__pycache__/db.cpython-313.pyc index 5b5aaf69c55c70abbdd024d2aaf85d3b801d3c9d..81136b8de0fa760aa98cd199fc86d965e83d940e 100644 GIT binary patch literal 5325 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 delta 1642 zcma)6O>7fK6n?YowP%0r&Ej8zy-uAdsZby|=??;`(8j?@TS}Jgp{_tFIM8C8YV9DA zOHeCS4wpbzmAEyim-JG(>V-2xfgW0U)kwAil_G&E^%i6fmAG`qCXQ>c)EW8hw{PB? z_w&8=Z$rOFl)5YnK-QJ-7cV}MZYrUX0rK`87+|1jm_ioPDQ2b_i)lPri#6B?Sgs4q zkn3;idh_dekR{LhX^whksIIYQ82e{dL!3R1v>+NoO>MrL1=F%Xuf8M98m=+s#VyRM zZI(F0-Lhc~Ph;a@*5JwZ7!(O)H<4&aO?1GOcbP5?PMinGb5Ss4lQTq84k%zK1WPZl z*JK(RmPjAbt4)1U$h$KhnpqDOrrYQ-w6~eO$Mno#`!=I-*u*npqY-;{%G4nvQER$p z6noa?pZ2$f4J6rJOA{9u9TFgd4pC@^qJAXNRzgXa5$qP#+ZL5%($gTuPxH}2M)=?^ zIYR3+NKe^_DcDb$J)vEbm(!lz+n#6w;*jyt zUy|S9k-8HayEZh~*3gbj1Eci%_C!tmQd2rhj8OjHjQ#%-+k!THgZeHBooA_d+gW2w zX?fjeho1g-C19xUL;JDklADgKCEKU*2vUCe25Ssb?5ePgM0-Uv;_h>~&$IcWr4Xx4 zl-(4I*+TKGl`SmfiUpAOjIBKGFd`nVRrk1B}T-hMTKgqGBn&!WreyT#xz;>_19ebqq-dbFHeXX~A zs=QdvRrbAGIrEvLo-4VxWUVUej;xoMEi9}E%fgK}HgIGs5UK_Soxor+SDSRIRd4DDRPUVv`j(ql7swS#cG3toXiYr=}AZ@(6cRZHjnR{>?r}|)>7g8~9J;G7h zCro E7j#E3`Tzg` diff --git a/backend/app/__pycache__/extensions.cpython-313.pyc b/backend/app/__pycache__/extensions.cpython-313.pyc index 385f54cf53a984fc0a55cf17acbff517cd23b5e0..fff51628c25cdb3305042eab3b926feeada6a2ec 100644 GIT binary patch delta 594 zcmZWm-D(p-6rR~^c9TuFTep!mTIgb-kd`(VVnOe6W2s8O3?fSCvTmlErJGEdS)=Jq z^uh-y#Xk`AQg3_&AHX}SkY#*=+_mHZ^lWM%#WQf`ob#RUJDi!f+3%V;GED=4eO~(7 z;zwqw{{fQMh#-y#CdysR@%UD_(opJJ+IrEhA(FX)&ud(5sP)1{Jk5YP^M7^1a!>=; zXaC{rQ+*(4K`8Os z@Hqg(4RE*1nl!vr*ln{Wl|`R%;aXl4v1hdDG9CtDM;yPxTm5og>aN>lzU%S~P^BKR z0MJ0@0~+6hm~v@NX1OT{g`5D4c4;K_ht+y*r?xE%Q;SRy#CWqWXaw3w?pELpxTv&4 zzva#%GF()zJ*XbHCB-sfOmS4D6K?d*#!epLXSil^|CDvUm6sM@0QtYDL7)5p delta 390 zcmXv}O-sW-5Zz7EG{(k{l1sqDB3@F_i%_Z;@lvRQ9#-%oENQxJL(^>8O|=z7{0XIq z_?JBO8203~H;Z_86X^^yyqRI%y!kZWOU{qu*dVv%NB?=|c#WFEZvX=hFvQvwywtdc zz`eE)u#OE}!X_>&w@yv2_cna{>C0}$ff&pdC#3?+EqL~F+m8h5MiVMf+NbD_wlNC_ zG({IJG-Mr$A|b{UwWo+CV$7qYhk`gpqkyLZv2L*xj7DfIXn{pAr0Acbb%UZrq(KtW zV|$rP_o1}bGYMrX)E)LnJB0D5r--B-h-ng%T-AI5 z>xb9AAgK-@QHYNi*A str: + """ + Create a JWT access token. + + Args: + identity: User identifier (usually user ID) + expires_delta: Optional expiration time override + + Returns: + JWT token string + """ + if expires_delta: + expire = datetime.utcnow() + expires_delta + else: + expire = datetime.utcnow() + JWT_ACCESS_TOKEN_EXPIRES + + payload = { + 'sub': identity, # Subject (user ID) + 'exp': expire, + 'iat': datetime.utcnow(), + 'type': 'access' + } + + return jwt.encode(payload, JWT_SECRET_KEY, algorithm=JWT_ALGORITHM) + + +def decode_token(token: str) -> Dict[str, Any]: + """ + Decode and validate a JWT token. + + Args: + token: JWT token string + + Returns: + Decoded token payload + + Raises: + QuartJWTError: If token is invalid + """ + try: + payload = jwt.decode(token, JWT_SECRET_KEY, algorithms=[JWT_ALGORITHM]) + return payload + except jwt.ExpiredSignatureError: + raise QuartJWTError("Token has expired") + except jwt.InvalidTokenError as e: + raise QuartJWTError(f"Invalid token: {str(e)}") + + +def get_jwt_identity() -> Optional[str]: + """ + Get the identity (user ID) from the current JWT token. + + Returns: + User ID from token, or None if no valid token + """ + try: + return getattr(g, 'current_user_id', None) + except Exception: + return None + + +def jwt_required(optional: bool = False): + """ + Decorator to require valid JWT token for route access. + + Args: + optional: If True, allow access without token (but still decode if present) + """ + def decorator(func): + @functools.wraps(func) + async def wrapper(*args, **kwargs): + try: + # Get token from Authorization header + auth_header = request.headers.get('Authorization') + token = None + + if auth_header: + # Expected format: "Bearer " + parts = auth_header.split() + if len(parts) == 2 and parts[0].lower() == 'bearer': + token = parts[1] + + if not token: + if optional: + # No token provided but optional - allow access + g.current_user_id = None + result = await func(*args, **kwargs) + # Handle tuple returns + if isinstance(result, tuple) and len(result) == 2: + response, status_code = result + if hasattr(response, 'status_code'): + response.status_code = status_code + return response + else: + from quart import make_response + return make_response(response, status_code) + else: + return result + else: + # Token required but not provided + from quart import make_response + return make_response(jsonify({'error': 'Missing authorization token'}), 401) + + # Validate token + try: + payload = decode_token(token) + user_id = payload.get('sub') + + if not user_id: + raise QuartJWTError("No user ID in token") + + # Store user ID in request context + g.current_user_id = user_id + + # Call the actual route function and handle tuple returns + result = await func(*args, **kwargs) + + # Handle tuple returns (response, status_code) + if isinstance(result, tuple) and len(result) == 2: + response, status_code = result + if hasattr(response, 'status_code'): + response.status_code = status_code + return response + else: + # Use make_response for non-response objects + from quart import make_response + return make_response(response, status_code) + else: + return result + + except QuartJWTError as e: + if optional: + # Invalid token but optional - allow access without user ID + g.current_user_id = None + result = await func(*args, **kwargs) + # Handle tuple returns here too + if isinstance(result, tuple) and len(result) == 2: + response, status_code = result + if hasattr(response, 'status_code'): + response.status_code = status_code + return response + else: + from quart import make_response + return make_response(response, status_code) + else: + return result + else: + # Invalid token and required + from quart import make_response + return make_response(jsonify({'error': f'Invalid token: {str(e)}'}), 401) + + except Exception as e: + current_app.logger.error(f"JWT validation error: {e}") + if optional: + g.current_user_id = None + result = await func(*args, **kwargs) + # Handle tuple returns here too + if isinstance(result, tuple) and len(result) == 2: + response, status_code = result + if hasattr(response, 'status_code'): + response.status_code = status_code + return response + else: + from quart import make_response + return make_response(response, status_code) + else: + return result + else: + from quart import make_response + return make_response(jsonify({'error': 'Authentication error'}), 500) + + return wrapper + return decorator + + +# For backward compatibility, provide the same function names as Flask-JWT-Extended +def get_current_user(): + """Get current user ID (alias for get_jwt_identity).""" + return get_jwt_identity() \ No newline at end of file diff --git a/backend/app/db.py b/backend/app/db.py index d3d03e3a..a15ce925 100644 --- a/backend/app/db.py +++ b/backend/app/db.py @@ -1,9 +1,27 @@ +from motor.motor_asyncio import AsyncIOMotorClient from pymongo import MongoClient import os import logging -# MongoDB connection -def get_db(): +# Global Motor client singleton - per event loop +_motor_clients = {} # event_loop_id -> (client, database) + +async def get_db(): + """Get database connection using singleton Motor client per event loop.""" + import asyncio + + # Get current event loop to ensure Motor client affinity + try: + current_loop = asyncio.get_running_loop() + loop_id = id(current_loop) + except RuntimeError: + raise RuntimeError("get_db() must be called from within an async context") + + # Return cached database for this event loop if available + if loop_id in _motor_clients: + client, database = _motor_clients[loop_id] + return database + # Try to read environment variables for MongoDB credentials mongo_user = os.environ.get('MONGO_USER') mongo_pass = os.environ.get('MONGO_PASS') @@ -22,28 +40,33 @@ def get_db(): for creds in standard_credentials: try: uri = f"mongodb://{creds['user']}:{creds['pass']}@{mongo_host}:{mongo_port}/semblance_db?authSource={creds['db']}" - client = MongoClient(uri, serverSelectionTimeoutMS=2000) - db = client.semblance_db + motor_client = AsyncIOMotorClient(uri, serverSelectionTimeoutMS=2000) + database = motor_client.semblance_db # Test the connection with a simple command - db.command('ping') + await database.command('ping') logging.debug(f"Successfully connected to MongoDB with standard credentials ({creds['user']})") - return db + + # Cache for this event loop + _motor_clients[loop_id] = (motor_client, database) + return database except Exception as e: # Continue trying other credentials pass # Try to connect without authentication if standard credentials don't work try: - client = MongoClient(f'mongodb://{mongo_host}:{mongo_port}', serverSelectionTimeoutMS=5000) - # Simply use the database as is - MongoDB will allow this if auth is not required - db = client.semblance_db + motor_client = AsyncIOMotorClient(f'mongodb://{mongo_host}:{mongo_port}', serverSelectionTimeoutMS=5000) + database = motor_client.semblance_db # Test the connection with a simple command - db.command('ping') + await database.command('ping') # Try a write operation to verify we have proper access - test_result = db.test_collection.insert_one({"test": "auth_test"}) - db.test_collection.delete_one({"_id": test_result.inserted_id}) + test_result = await database.test_collection.insert_one({"test": "auth_test"}) + await database.test_collection.delete_one({"_id": test_result.inserted_id}) logging.debug("Successfully connected to MongoDB without authentication") - return db + + # Cache for this event loop + _motor_clients[loop_id] = (motor_client, database) + return database except Exception as e: logging.debug(f"Could not connect without auth: {e}") @@ -51,11 +74,14 @@ def get_db(): if mongo_user and mongo_pass: try: uri = f"mongodb://{mongo_user}:{mongo_pass}@{mongo_host}:{mongo_port}/semblance_db?authSource=admin" - client = MongoClient(uri, serverSelectionTimeoutMS=5000) - db = client.semblance_db - db.command('ping') # Test the connection + motor_client = AsyncIOMotorClient(uri, serverSelectionTimeoutMS=5000) + database = motor_client.semblance_db + await database.command('ping') # Test the connection logging.debug(f"Successfully connected to MongoDB with credentials for user: {mongo_user}") - return db + + # Cache for this event loop + _motor_clients[loop_id] = (motor_client, database) + return database except Exception as e: logging.warning(f"Failed to connect with environment credentials: {e}") @@ -63,5 +89,27 @@ def get_db(): logging.warning("Could not authenticate with MongoDB. If authentication is required, operations will fail.") logging.warning("To fix this: Set MONGO_USER and MONGO_PASS environment variables.") # Return a client that will likely fail when operations are performed, but the app will start - client = MongoClient(f'mongodb://{mongo_host}:{mongo_port}', serverSelectionTimeoutMS=5000) - return client.semblance_db \ No newline at end of file + motor_client = AsyncIOMotorClient(f'mongodb://{mongo_host}:{mongo_port}', serverSelectionTimeoutMS=5000) + database = motor_client.semblance_db + + # Cache for this event loop + _motor_clients[loop_id] = (motor_client, database) + return database + + +def close_db_connections(): + """Close all Motor clients and their PyMongo background threads.""" + global _motor_clients + + closed_count = 0 + for loop_id, (client, database) in _motor_clients.items(): + try: + client.close() + closed_count += 1 + except Exception as e: + logging.warning(f"Error closing Motor client for loop {loop_id}: {e}") + + if closed_count > 0: + logging.info(f"🗄️ Closed {closed_count} Motor clients - PyMongo threads should stop") + + _motor_clients.clear() \ No newline at end of file diff --git a/backend/app/extensions.py b/backend/app/extensions.py index 4fb3ff3d..d836f7e7 100644 --- a/backend/app/extensions.py +++ b/backend/app/extensions.py @@ -1,20 +1,24 @@ """ -Flask Extensions Module -Provides singleton instances of Flask extensions to ensure consistency across the application. -This fixes the WebSocket AI mode issue by ensuring all parts of the app use the same SocketIO instance. +Quart Extensions Module +Provides singleton instances of Quart extensions to ensure consistency across the application. +Uses python-socketio AsyncServer for native Quart/ASGI compatibility. """ -from flask_socketio import SocketIO +import socketio +import logging -# Create the SINGLE SocketIO instance that will be used throughout the application -# This is the singleton pattern recommended by GPT-5 to fix AI mode WebSocket issues -socketio = SocketIO( +# Set up logging for socketio +socketio_logger = logging.getLogger('socketio') +socketio_logger.setLevel(logging.WARNING) # Reduce socketio log noise + +# Create the AsyncServer instance for Quart/ASGI compatibility +socketio_server = socketio.AsyncServer( + async_mode='asgi', cors_allowed_origins="*", - async_mode="eventlet", - ping_timeout=120, # 2 minutes timeout for ping response - ping_interval=45, # Send ping every 45 seconds - logger=False, # Disable verbose socketio logging (reduces log noise) - engineio_logger=False # Disable verbose engineio logging (reduces PING/PONG spam) + ping_timeout=120, # 2 minutes timeout for ping response + ping_interval=45, # Send ping every 45 seconds + logger=False, # Disable verbose socketio logging + engineio_logger=False # Disable verbose engineio logging ) -# Note: The app will be bound to this instance using socketio.init_app(app) in create_app() \ No newline at end of file +# Note: This will be wrapped with socketio.ASGIApp in create_app() to integrate with Quart \ No newline at end of file diff --git a/backend/app/models/__pycache__/focus_group.cpython-313.pyc b/backend/app/models/__pycache__/focus_group.cpython-313.pyc index aebd971f584004d5df155d82622023c6e1dcdf63..f5181f7873378fd8e0c41b9915c8a9f5e693469a 100644 GIT binary patch delta 17582 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 X-}A+E%X_J+RcexW4ZL@fD4hFW*LGDx delta 12581 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 P`m&fbenMcMecyipZ~6eA delta 5340 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 diff --git a/backend/app/models/__pycache__/persona.cpython-313.pyc b/backend/app/models/__pycache__/persona.cpython-313.pyc index 5b67f2648716d07998df485edfa507ae7885ede5..98bb203be6fd6d2235501378410d7374e1f72f8e 100644 GIT binary patch delta 2265 zcma)7TWk|Y6rJ(z`my$U{fND`<0rP`7?X!lLK18!v6F}@k0Pq5RFMd2NP&u)vPs%X zR7y!fL~5%hodQKg0@RNnwJNFFkBI(YJ8C~`%O6IlqC(=Mh@V7&2na#%Y#s_pD>2eu zpS^Qucjw$Q$Jb)xap8d=@CZg&`04ORKMOl?{bz9ZAc+wQB0oBd`T#AB#^vAF(VOTn zgV2ZgEYrgo$RT`MT%Ws-M-5uka|W*0nwWR-F9%e8SiA^NxQ9MPcdbs+{MCN)JF~Sy za-Kgib)qcQ6s4N0M@v$oY)%jzyK8L0iHz=r3S>z>8Qug7$BijBp2R5!JJqTKn04Wl zi_KQ<1e^ox#)GS$3gsHOl&w^&UU@4hgT1H^wg!WAnfzp$eS5>!Ua(CSu&u)Z)TdS| zA!yLR0n)+kH!D1_E8J+2fqc*Pu~7acdCu)oH8G@1{Yd2CL)Y@qrHMH`zDu1KI?L|* zqPu=3JnJmP(zK`KLge)Q%S)YKcx88GAN73EwzwY_kA=tu5>n3+S99y%}B8w$g)n4jk?{@8iauK z<8W=}Q7;=pRD@BkN7lo8WUBakFJQ=ia#7c0tD{K=4JI0#G*pv+bV>0fz_p#K23Q7q)lt2wK(8#otc^{nRaqxavFfag zq1aOe`5cP@=hO_~mlV88$$?c$K%Ib>#`1Brp><~uMBIzXui9#U<-qRM5JB%Fe`2pk zRfjT6uWAEnD8u&Y$)G-n1#(93i!AY%S>}%kAyB(<<;|dW7eX1X&rJT%H(OVzrL4+Z zXz&xG!QZKq;UFwi3eQ7wEe#6S6%^Ie5TwBZA*x=QTHgjbA0cCgt#4cbNP98Q8g%x* zBoF!cymumXbHzX+MJZB}qGhSJDAiv5WOi>!+Etc1ic-go(UR0XvHPY?n&`L_h(hy( z$mAjN%;3`t$l{0Q36aYV;uB(UR0iQK<2Y4|PYEdvn!+i>e%*=zX02?BRb?gUs59UW^7!Tqwv57t6Z;)6k>WnyKSYeUcF!@__ zj5+wnh_Ub!>UkbST^2({F?9KOQLHX#s~761=0h;OYRzFP^#DjcnF`~afKx#>XRM$& zh#|ft>{V+v{J%AuHf(p7>C%Sn-ZllO7rzJU)WW8e0qWdCMDu@n@okfjd~SAAKbFa~ zS@GjX<}D^KER=mTP)h>�U30I(yvEaFWx&(NSqH%NvoF@KoeD|)R0bj$d`kUV@W zGthtJVE^!l462>6G>Xq*YAxCGmX-#$_fq0QqFfm-R>o%uL3fGGd?WETla0kQFi7Sb6u%#GmnQge?Ds1m61-r|h?t-VAd|`F! zpDx&uy#p`a6&>UUYY6mw8r~1q&r<78Q0r&$xnSCjt_!U@P*&pyn2q2x{F7E&DBiAu zitG+PEipMwJZ)ie%{Y}%=US|Q^9Bt0yudd&? z{#8vUuwkYK+P6PEwc?S(?2)AtgK-$2_k-`3fkrDaGJEJ2*!~OkoJIp;m oWV+%T+)a|US{F^o-86s)p5U7WB(Xy^v4ool=8#5Kla+V5`u}#jK)M048#!lq30BeE}rB*=Q;0t z?m6dupZC4q{7F#Q5(F0EF>k!{GV`g>Md@g~+Ttk$->J>;I`tM9R98QnRV66yZS0vr zKMUeJXJD6ZF&f2r$DE^J^eaYxn$H`3(K05u&vqRe#hR@t$%5Q7Hj4F~w%4Qf5^<QhV66v5#o>s4XJ%7zzoV9-YpXue)}5_*$dyY zPg6}maUvCfX0r(bTrD?+{iQs-$VE;)wz^Zj8&1!%HEq7@$~&7L}^VO6d-eg6v3%s`!Htj=Lc0=iG zEgG{Ulw=Yj3<&Ldk`a;&`6&WkSP(qztV~|AuD*C-`l?KRlp6>%5@;e|BG630N1y^j zKqHrOXy7WrTJE}b2b*WmrkXFaY#6OzOGP)Vj)mw_|6+eZs#m1?ywp&TLW&g1ocOvb zFU1Q|zasVDkLRV+dFykjzBRKo)$`a-qu;|0y9^>X0Xf)bHc() z4-GXq^O6r|-egY=>}=?5TS$ekgs;l&ox9U6{EF+{k?u~k(#b}>bXKd6N_5sj5iT(p zpQxonom~6vqe-f;GBjM`?eKyA0lZ=GumRnm>?MJF;B&(`RRfjALGGhswEO+~s&QiE zzx}!#xAkMy!c783*+Z-RminaZw_<6Vppk6rpz7n7TbJc zE>SQC6>~5>lzBE+-<3BXOZBXYl?BnSi2k%!5ra8CxU*deKbpN^r+X=}m?$_yiZhgX z{$3>4+LL$o&i4G|@D?16ilZ^(RUDxoOrgC^jxo0GZQGHd61ptD{@v*~zWio&v=e27 z?g08Sh}lYjjYjB|(9vj^&Nfn*XPea6o((gEM`*%3*;p05sx`)(^s1A=9R9Ejo9jrZ zEd<&K93v2i5vxPfOQ;iGv)-nhptm*H_YK5~I|;4pj$XPv{>sIPF8LG;+x%+M`~LvY C*?D~c diff --git a/backend/app/models/__pycache__/user.cpython-313.pyc b/backend/app/models/__pycache__/user.cpython-313.pyc index bfbe514f7098e551d1062a83307fd349dae98a60..138dafd86c0bc6a1d5f9f6bde8b3dff79af40d15 100644 GIT binary patch delta 1920 zcmb_cS!^3s6n)P#<2Ck-x3S~h%`}UXw53jn?bwmLP^Sq5C6*JhxwL7c3ZzI$BxIzb zpi&9{QTM5oegR(%AhDI2qfrFKagUDrAB;k$1bUGRGJT-bdQhU zHt(E!-`foQbi}Zu*J}~9V~5^98(=>&oMw)^0)KZd0CXT9x{RK}^0?dvn?L9{%Am6p zLNDOU)CAK;E~*Zb&zZIZE&XU-kyQvtjV5{_)I-!9Cf_m(%OTk2HhLM|GPje^Yq^4= z>Ttc;eYIP1^p+jHD^IT4#lA$vk(4aSf~LyaMYX-gAcO0sC0XBb!Q%6VIKmfklr9QD zKE@;TCn_LcV1U`()51g=VviaxbT-=mAm>MAX5;~KWm}H5y$cfb#`^mE^ zOVjb!;ZXZ`{IUDHJ+SNc&#F_)G1&G$+|J$@U7e{6JRyua&R`Dr=Qkf0^}*YJbft z>oOtJ!}X;D4P6O6=t^klV!R7zDTEU`x)e47U(;d0S(vrkcGH)Gq&9_l?!8+iXVCC1 zc|!9ji>4@Yf_1p|4B-sU;$iUUq(F!vv`79ohM3U0D8oeH$&_Eb?z_i2og_{+w`Xyl z4RYYyR}ddSyb3W(-eY6dmojLcibKuJp%~>wa~MX(Nu3>23{ltP+$6?nLX7!kKO9Uh zp>5i&8QQYBN&bqbkl0ote~07?m3^U#FCsZ2h4CuOzwvy9?b+h2BLC2L+|ep;m-s-L z53CGT_@KlEMJ~9lWb{H+=)5-i_NXL8%R+SZxvx9L*knbRk~~vlcDn4DzNq`X4OzTf z-j1r9ylC=9Z5KSh+Oj<;Y}`bOa54JlN+cFB9pJCM9yIV<3LS~ycv9F z%j_bbntYl+Yj$J|-1${aelYo$|5~~Mrk%&BetbcnQllbH+UQF*VDI2$JN<4Cu#y=k zopi}U%e>u}^rBK2C*5=@q5{5#!_dDL!6_xZHp~KF*UHvS^b8{CFv-rhiG1Kk+b zt`B>)DT-R};gTcNdYF+XjWE!&LCGK6P-{~GYQtnm9i%pV3~ah_kdb==6masWS-@jt n!Fw@8zd^>w`oFb0!A^0G-y0D z(F>Un#EV?KX{^D6i3gQlJa)5?_Ry0d@kUTcG;+{c5KvM{c*%V8X5O3kz4zvQKDKmJ zx2Dyq0FgHv)3K+z*SaCPj3URiD+H#&H7MC6Ili^wW@Rl7C^Yi8EFyCP{EHs#Jwi;A zU|C!8=xS1yL&ll5skSVt5KzV3y;MV{(l4SwvN*@^86`i4i1Xl1RB|XH>mZCHig4XN zjN>UfQ|T3vZx18BT!c(f!d1Aa&h3I;DF+5!#O*%?-F^{Ejf*v>GL_9D3hv<`&$i{D zRUuXXetk@&CNh<$MRew0M%gwU*%f4M_VF=M3KjWX)r0%gcDhP_Va$!E)Dh^xU)0^^ zcH+NVEmtzbPw`pKDR54FU1PxsjY;w6*Q6feyk-_Q<2%}k@=dmt!fYT8BcMx$@9PZs zr0%&nPDQ9LJ@MTmWP}beH=rMnGl$iE#5f7^(jeiNOk-G&c5|8Q5xLr=JjWQaj6+}? z8Kq+@tG@&8^xzwruxVf|Fuw`bp^3vVjKC)1(_e<)u??8w{3uK}J_JA-YiYV98EV3V zTrU z%0a0mYxW3ck7%w>cH|i2lM#`r%Cm-y(Y?rea;!efItA7_*S*NPR^_zTnX}rT#b#q! zt5>jk->N>eW_%r@_57z%!Fma=pz3nYl4TtN>sVyVH{+aGVjGvGfOSi3{f~7UKo%S^ z*r|f+@v{5 z(vd3UrP5Yi$W5i)G|_R7(FxDtgmKtbL!yq=N#Kfc$@1Rwwa diff --git a/backend/app/models/focus_group.py b/backend/app/models/focus_group.py index ee5f15e1..772bddf3 100644 --- a/backend/app/models/focus_group.py +++ b/backend/app/models/focus_group.py @@ -7,9 +7,9 @@ import os import threading import eventlet -def emit_websocket_event(event_name: str, focus_group_id: str, data: dict): - """Helper function to emit WebSocket events using queue-based emitter (GPT-5 fix).""" - from app.websocket_manager import emit_websocket_event as queue_emit +async def emit_websocket_event(event_name: str, focus_group_id: str, data: dict): + """Helper function to emit WebSocket events using async WebSocket manager.""" + from app.websocket_manager_async import emit_websocket_event as async_emit process_id = os.getpid() thread_id = threading.get_ident() @@ -38,9 +38,9 @@ def emit_websocket_event(event_name: str, focus_group_id: str, data: dict): **data } - # Emit to the specific focus group room using the queue-based system - queue_emit(event_name, event_data, focus_group_id) - print(f"🔔 Successfully queued {event_name} for focus group {focus_group_id}") + # Emit to the specific focus group room using the async system + await async_emit(event_name, event_data, focus_group_id) + print(f"🔔 Successfully emitted {event_name} for focus group {focus_group_id}") except Exception as e: print(f"🔔 ERROR emitting WebSocket event {event_name}: {e}") @@ -65,8 +65,8 @@ def emit_with_ack(event_name: str, focus_group_id: str, payload: dict): class FocusGroup: @staticmethod - def create(focus_group_data, user_id): - db = get_db() + async def create(focus_group_data, user_id): + db = await get_db() # Add metadata focus_group_data["created_at"] = datetime.utcnow() @@ -87,14 +87,14 @@ class FocusGroup: if "verbosity" not in focus_group_data: focus_group_data["verbosity"] = "medium" - result = db.focus_groups.insert_one(focus_group_data) + result = await db.focus_groups.insert_one(focus_group_data) return str(result.inserted_id) @staticmethod - def find_by_id(focus_group_id): - db = get_db() + async def find_by_id(focus_group_id): + db = await get_db() try: - focus_group = db.focus_groups.find_one({"_id": ObjectId(focus_group_id)}) + focus_group = await db.focus_groups.find_one({"_id": ObjectId(focus_group_id)}) if focus_group: focus_group["_id"] = str(focus_group["_id"]) return focus_group @@ -102,9 +102,10 @@ class FocusGroup: return None @staticmethod - def find_by_user(user_id, limit=50): - db = get_db() - focus_groups = db.focus_groups.find({"created_by": user_id}).sort("created_at", -1).limit(limit) + async def find_by_user(user_id, limit=50): + db = await get_db() + cursor = db.focus_groups.find({"created_by": user_id}).sort("created_at", -1).limit(limit) + focus_groups = await cursor.to_list(length=limit) result = [] for group in focus_groups: @@ -114,21 +115,22 @@ class FocusGroup: return result @staticmethod - def get_all(limit=50): + async def get_all(limit=50): import logging logger = logging.getLogger('app.focus_group_model') try: logger.debug(f"=== FocusGroup.get_all() called with limit={limit} ===") - db = get_db() + db = await get_db() logger.debug(f"Database connection obtained: {db}") # Check if collection exists and has data collection = db.focus_groups - total_count = collection.count_documents({}) + total_count = await collection.count_documents({}) logger.debug(f"Total focus groups in database: {total_count}") - focus_groups = list(db.focus_groups.find().sort("created_at", -1).limit(limit)) + cursor = db.focus_groups.find().sort("created_at", -1).limit(limit) + focus_groups = await cursor.to_list(length=limit) logger.debug(f"Query returned {len(focus_groups)} focus groups") result = [] @@ -147,8 +149,8 @@ class FocusGroup: return [] @staticmethod - def update(focus_group_id, data): - db = get_db() + async def update(focus_group_id, data): + db = await get_db() # Create a copy of the data to avoid modifying the original filtered_data = data.copy() @@ -177,7 +179,7 @@ class FocusGroup: except: pass - result = db.focus_groups.update_one( + result = await db.focus_groups.update_one( {"_id": ObjectId(focus_group_id)}, {"$set": filtered_data} ) @@ -186,7 +188,7 @@ class FocusGroup: if result.modified_count > 0: # Emit status change event if status was updated if 'status' in filtered_data: - emit_websocket_event('ai_status_update', focus_group_id, { + await emit_websocket_event('ai_status_update', focus_group_id, { 'status': { 'status': filtered_data['status'], # Frontend expects nested structure 'updated_at': filtered_data["updated_at"].isoformat() @@ -195,7 +197,7 @@ class FocusGroup: # Emit model change event if LLM model was updated if 'llm_model' in filtered_data: - emit_websocket_event('focus_group_update', focus_group_id, { + await emit_websocket_event('focus_group_update', focus_group_id, { 'llm_model': filtered_data['llm_model'], 'reasoning_effort': filtered_data.get('reasoning_effort'), 'verbosity': filtered_data.get('verbosity'), @@ -206,7 +208,7 @@ class FocusGroup: if 'llm_model' in filtered_data and result.modified_count > 0: try: # Re-read the document to verify the update - updated_doc = db.focus_groups.find_one({"_id": ObjectId(focus_group_id)}) + updated_doc = await db.focus_groups.find_one({"_id": ObjectId(focus_group_id)}) actual_model = updated_doc.get('llm_model') if updated_doc else None log_msg = f"🔍 [{datetime.utcnow()}] POST-UPDATE VERIFICATION: Expected '{filtered_data['llm_model']}', got '{actual_model}' for {focus_group_id}\n" with open('/tmp/focus_group_debug.log', 'a') as f: @@ -283,9 +285,9 @@ class FocusGroup: return cleaned_files, failed_files @staticmethod - def _cleanup_focus_group_collections(focus_group_id): + async def _cleanup_focus_group_collections(focus_group_id): """Clean up all related collection documents for a focus group.""" - db = get_db() + db = await get_db() cleaned_collections = [] failed_collections = [] @@ -301,7 +303,7 @@ class FocusGroup: for collection_name, field_name in collections_to_clean: try: collection = getattr(db, collection_name) - result = collection.delete_many({field_name: focus_group_id}) + result = await collection.delete_many({field_name: focus_group_id}) if result.deleted_count > 0: cleaned_collections.append(f"{collection_name}: {result.deleted_count} documents") print(f"Cleaned up {result.deleted_count} documents from {collection_name}") @@ -314,13 +316,13 @@ class FocusGroup: return cleaned_collections, failed_collections @staticmethod - def delete(focus_group_id): + async def delete(focus_group_id): """Delete a focus group and all its associated data including creative assets.""" - db = get_db() + db = await get_db() try: # First, get the focus group data to access uploaded assets - focus_group = FocusGroup.find_by_id(focus_group_id) + focus_group = await FocusGroup.find_by_id(focus_group_id) if not focus_group: print(f"Focus group {focus_group_id} not found") return False @@ -331,10 +333,10 @@ class FocusGroup: cleaned_files, failed_files = FocusGroup._cleanup_focus_group_assets(focus_group_id, uploaded_assets) # Clean up related collections - cleaned_collections, failed_collections = FocusGroup._cleanup_focus_group_collections(focus_group_id) + cleaned_collections, failed_collections = await FocusGroup._cleanup_focus_group_collections(focus_group_id) # Finally, delete the main focus group document - result = db.focus_groups.delete_one({"_id": ObjectId(focus_group_id)}) + result = await db.focus_groups.delete_one({"_id": ObjectId(focus_group_id)}) if result.deleted_count > 0: print(f"Successfully deleted focus group {focus_group_id}") @@ -357,32 +359,33 @@ class FocusGroup: return False @staticmethod - def add_participant(focus_group_id, persona_id): - db = get_db() - result = db.focus_groups.update_one( + async def add_participant(focus_group_id, persona_id): + db = await get_db() + result = await db.focus_groups.update_one( {"_id": ObjectId(focus_group_id)}, {"$addToSet": {"participants": persona_id}} ) return result.modified_count > 0 @staticmethod - def remove_participant(focus_group_id, persona_id): - db = get_db() - result = db.focus_groups.update_one( + async def remove_participant(focus_group_id, persona_id): + db = await get_db() + result = await db.focus_groups.update_one( {"_id": ObjectId(focus_group_id)}, {"$pull": {"participants": persona_id}} ) return result.modified_count > 0 @staticmethod - def get_messages(focus_group_id, limit=100): + async def get_messages(focus_group_id, limit=100): """Get all messages for a focus group.""" - db = get_db() + db = await get_db() try: # Get all messages and sort chronologically - messages = list(db.focus_group_messages.find( + cursor = db.focus_group_messages.find( {"focus_group_id": focus_group_id} - ).sort("created_at", 1)) + ).sort("created_at", 1) + messages = await cursor.to_list(length=None) # Convert ObjectId to strings for message in messages: @@ -396,12 +399,12 @@ class FocusGroup: return [] @staticmethod - def add_message(focus_group_id, message_data): + async def add_message(focus_group_id, message_data): """Add a new message to a focus group.""" - db = get_db() + db = await get_db() try: # Ensure the focus group exists - focus_group = FocusGroup.find_by_id(focus_group_id) + focus_group = await FocusGroup.find_by_id(focus_group_id) if not focus_group: return None @@ -419,7 +422,7 @@ class FocusGroup: } # Insert the message - result = db.focus_group_messages.insert_one(message) + result = await db.focus_group_messages.insert_one(message) if result.inserted_id: message_id = str(result.inserted_id) @@ -427,7 +430,7 @@ class FocusGroup: # If this message activates visual context, update the focus group's active visual context if message.get("activates_visual_context") and message.get("attached_assets"): - FocusGroup._activate_visual_assets(focus_group_id, message.get("attached_assets"), message_id) + await FocusGroup._activate_visual_assets(focus_group_id, message.get("attached_assets"), message_id) # Emit WebSocket event for new message message_for_websocket = { @@ -443,7 +446,7 @@ class FocusGroup: } print(f"🔔 EMITTING WEBSOCKET EVENT: message_update for focus group {focus_group_id}") print(f"🔔 Message data: sender={message_for_websocket['senderId']}, type={message_for_websocket['type']}") - emit_websocket_event('message_update', focus_group_id, message_for_websocket) + await emit_websocket_event('message_update', focus_group_id, message_for_websocket) return message_id else: @@ -455,17 +458,17 @@ class FocusGroup: return None @staticmethod - def update_message_highlight(focus_group_id, message_id, highlighted): + async def update_message_highlight(focus_group_id, message_id, highlighted): """Update the highlighted status of a message.""" - db = get_db() + db = await get_db() try: # Ensure the focus group exists - focus_group = FocusGroup.find_by_id(focus_group_id) + focus_group = await FocusGroup.find_by_id(focus_group_id) if not focus_group: return False # Update the message - result = db.focus_group_messages.update_one( + result = await db.focus_group_messages.update_one( {"_id": ObjectId(message_id), "focus_group_id": focus_group_id}, {"$set": {"highlighted": highlighted, "updated_at": datetime.utcnow()}} ) @@ -477,14 +480,15 @@ class FocusGroup: return False @staticmethod - def get_generated_themes(focus_group_id): + async def get_generated_themes(focus_group_id): """Get all generated themes for a focus group.""" - db = get_db() + db = await get_db() try: # Get themes associated with this focus group - themes = list(db.focus_group_themes.find( + cursor = db.focus_group_themes.find( {"focus_group_id": focus_group_id} - ).sort("created_at", -1)) + ).sort("created_at", -1) + themes = await cursor.to_list(length=None) # Convert ObjectId to strings for theme in themes: @@ -498,12 +502,12 @@ class FocusGroup: return [] @staticmethod - def add_generated_theme(focus_group_id, theme_data): + async def add_generated_theme(focus_group_id, theme_data): """Add a new generated theme to a focus group.""" - db = get_db() + db = await get_db() try: # Ensure the focus group exists - focus_group = FocusGroup.find_by_id(focus_group_id) + focus_group = await FocusGroup.find_by_id(focus_group_id) if not focus_group: return None @@ -519,13 +523,13 @@ class FocusGroup: } # Insert the theme - result = db.focus_group_themes.insert_one(theme) + result = await db.focus_group_themes.insert_one(theme) if result.inserted_id: theme["_id"] = str(result.inserted_id) # Emit WebSocket event for new theme - emit_websocket_event('theme_update', focus_group_id, { + await emit_websocket_event('theme_update', focus_group_id, { 'theme': { 'id': theme["id"], 'title': theme["title"], @@ -545,12 +549,12 @@ class FocusGroup: return None @staticmethod - def add_generated_themes(focus_group_id, themes_data): + async def add_generated_themes(focus_group_id, themes_data): """Add multiple generated themes to a focus group.""" - db = get_db() + db = await get_db() try: # Ensure the focus group exists - focus_group = FocusGroup.find_by_id(focus_group_id) + focus_group = await FocusGroup.find_by_id(focus_group_id) if not focus_group: return None @@ -574,11 +578,11 @@ class FocusGroup: # Insert the themes if themes: - result = db.focus_group_themes.insert_many(themes) + result = await db.focus_group_themes.insert_many(themes) # Emit WebSocket events for all new themes for theme in themes: - emit_websocket_event('theme_update', focus_group_id, { + await emit_websocket_event('theme_update', focus_group_id, { 'theme': { 'id': theme["id"], 'title': theme["title"], @@ -600,12 +604,12 @@ class FocusGroup: return [] @staticmethod - def delete_generated_theme(focus_group_id, theme_id): + async def delete_generated_theme(focus_group_id, theme_id): """Delete a generated theme from a focus group.""" - db = get_db() + db = await get_db() try: # Delete the theme - result = db.focus_group_themes.delete_one( + result = await db.focus_group_themes.delete_one( {"focus_group_id": focus_group_id, "id": theme_id} ) @@ -616,14 +620,15 @@ class FocusGroup: return False @staticmethod - def get_reasoning_history(focus_group_id, limit=50): + async def get_reasoning_history(focus_group_id, limit=50): """Get reasoning history for a focus group.""" - db = get_db() + db = await get_db() try: # Get reasoning entries associated with this focus group - reasoning_entries = list(db.focus_group_reasoning.find( + cursor = db.focus_group_reasoning.find( {"focus_group_id": focus_group_id} - ).sort("timestamp", -1).limit(limit)) + ).sort("timestamp", -1).limit(limit) + reasoning_entries = await cursor.to_list(length=limit) # Convert ObjectId to strings and format timestamps for entry in reasoning_entries: @@ -640,12 +645,12 @@ class FocusGroup: return [] @staticmethod - def add_reasoning_entry(focus_group_id, reasoning_data): + async def add_reasoning_entry(focus_group_id, reasoning_data): """Add a reasoning entry to a focus group.""" - db = get_db() + db = await get_db() try: # Ensure the focus group exists - focus_group = FocusGroup.find_by_id(focus_group_id) + focus_group = await FocusGroup.find_by_id(focus_group_id) if not focus_group: return None @@ -669,7 +674,7 @@ class FocusGroup: reasoning_entry["timestamp"] = datetime.utcnow() # Insert the reasoning entry - result = db.focus_group_reasoning.insert_one(reasoning_entry) + result = await db.focus_group_reasoning.insert_one(reasoning_entry) # Return the id of the new entry return str(result.inserted_id) @@ -679,12 +684,12 @@ class FocusGroup: return None @staticmethod - def update_reasoning_execution(focus_group_id, reasoning_id, execution_result): + async def update_reasoning_execution(focus_group_id, reasoning_id, execution_result): """Update the execution result of a reasoning entry.""" - db = get_db() + db = await get_db() try: # Update the reasoning entry - result = db.focus_group_reasoning.update_one( + result = await db.focus_group_reasoning.update_one( {"_id": ObjectId(reasoning_id), "focus_group_id": focus_group_id}, {"$set": { "execution_status": "success" if not execution_result.get("error") else "error", @@ -700,14 +705,15 @@ class FocusGroup: return False @staticmethod - def get_notes(focus_group_id, limit=100): + async def get_notes(focus_group_id, limit=100): """Get all notes for a focus group.""" - db = get_db() + db = await get_db() try: # Look for a notes collection associated with this focus group - notes = list(db.focus_group_notes.find( + cursor = db.focus_group_notes.find( {"focus_group_id": focus_group_id} - ).sort("created_at", -1).limit(limit)) + ).sort("created_at", -1).limit(limit) + notes = await cursor.to_list(length=limit) # Convert ObjectId to strings for note in notes: @@ -723,12 +729,12 @@ class FocusGroup: return [] @staticmethod - def add_note(focus_group_id, note_data): + async def add_note(focus_group_id, note_data): """Add a new note to a focus group.""" - db = get_db() + db = await get_db() try: # Ensure the focus group exists - focus_group = FocusGroup.find_by_id(focus_group_id) + focus_group = await FocusGroup.find_by_id(focus_group_id) if not focus_group: return None @@ -752,7 +758,7 @@ class FocusGroup: note["timestamp"] = datetime.utcnow() # Insert the note - result = db.focus_group_notes.insert_one(note) + result = await db.focus_group_notes.insert_one(note) # Return the id of the new note return str(result.inserted_id) @@ -762,12 +768,12 @@ class FocusGroup: return None @staticmethod - def delete_note(focus_group_id, note_id): + async def delete_note(focus_group_id, note_id): """Delete a note from a focus group.""" - db = get_db() + db = await get_db() try: # Delete the note - result = db.focus_group_notes.delete_one( + result = await db.focus_group_notes.delete_one( {"_id": ObjectId(note_id), "focus_group_id": focus_group_id} ) @@ -778,12 +784,12 @@ class FocusGroup: return False @staticmethod - def add_mode_event(focus_group_id, event_type, user_id=None): + async def add_mode_event(focus_group_id, event_type, user_id=None): """Add a mode switch event to a focus group.""" - db = get_db() + db = await get_db() try: # Ensure the focus group exists - focus_group = FocusGroup.find_by_id(focus_group_id) + focus_group = await FocusGroup.find_by_id(focus_group_id) if not focus_group: return None @@ -797,7 +803,7 @@ class FocusGroup: } # Insert the mode event - result = db.focus_group_mode_events.insert_one(mode_event) + result = await db.focus_group_mode_events.insert_one(mode_event) if result.inserted_id: mode_event_id = str(result.inserted_id) @@ -814,7 +820,7 @@ class FocusGroup: } print(f"🔔 EMITTING WEBSOCKET EVENT: mode_event_update for focus group {focus_group_id}") print(f"🔔 Mode event data: event_type={event_type}, timestamp={mode_event['timestamp'].isoformat()}") - emit_websocket_event('mode_event_update', focus_group_id, mode_event_for_websocket) + await emit_websocket_event('mode_event_update', focus_group_id, mode_event_for_websocket) return mode_event_id @@ -826,14 +832,15 @@ class FocusGroup: return None @staticmethod - def get_mode_events(focus_group_id, limit=100): + async def get_mode_events(focus_group_id, limit=100): """Get all mode events for a focus group.""" - db = get_db() + db = await get_db() try: # Look for mode events associated with this focus group - mode_events = list(db.focus_group_mode_events.find( + cursor = db.focus_group_mode_events.find( {"focus_group_id": focus_group_id} - ).sort("timestamp", 1).limit(limit)) + ).sort("timestamp", 1).limit(limit) + mode_events = await cursor.to_list(length=limit) # Convert ObjectId to strings for event in mode_events: @@ -849,9 +856,9 @@ class FocusGroup: return [] @staticmethod - def add_uploaded_assets(focus_group_id, assets_metadata): + async def add_uploaded_assets(focus_group_id, assets_metadata): """Add uploaded asset metadata to a focus group.""" - db = get_db() + db = await get_db() try: # Clean the metadata to remove file_path before storing in DB cleaned_assets = [] @@ -867,7 +874,7 @@ class FocusGroup: cleaned_assets.append(cleaned_asset) # Add assets to the focus group - result = db.focus_groups.update_one( + result = await db.focus_groups.update_one( {"_id": ObjectId(focus_group_id)}, { "$push": {"uploaded_assets": {"$each": cleaned_assets}}, @@ -882,12 +889,12 @@ class FocusGroup: return False @staticmethod - def remove_uploaded_asset(focus_group_id, filename): + async def remove_uploaded_asset(focus_group_id, filename): """Remove an uploaded asset metadata from a focus group.""" - db = get_db() + db = await get_db() try: # Remove asset from the focus group - result = db.focus_groups.update_one( + result = await db.focus_groups.update_one( {"_id": ObjectId(focus_group_id)}, { "$pull": {"uploaded_assets": {"filename": filename}}, @@ -902,11 +909,11 @@ class FocusGroup: return False @staticmethod - def get_uploaded_assets(focus_group_id): + async def get_uploaded_assets(focus_group_id): """Get uploaded assets for a focus group.""" - db = get_db() + db = await get_db() try: - focus_group = db.focus_groups.find_one({"_id": ObjectId(focus_group_id)}) + focus_group = await db.focus_groups.find_one({"_id": ObjectId(focus_group_id)}) if focus_group: return focus_group.get('uploaded_assets', []) return [] @@ -916,11 +923,11 @@ class FocusGroup: return [] @staticmethod - def update_asset_name(focus_group_id, filename, user_assigned_name): + async def update_asset_name(focus_group_id, filename, user_assigned_name): """Update the user assigned name for an uploaded asset.""" - db = get_db() + db = await get_db() try: - result = db.focus_groups.update_one( + result = await db.focus_groups.update_one( {"_id": ObjectId(focus_group_id), "uploaded_assets.filename": filename}, { "$set": { @@ -937,11 +944,11 @@ class FocusGroup: return False @staticmethod - def clear_uploaded_assets(focus_group_id): + async def clear_uploaded_assets(focus_group_id): """Clear all uploaded assets for a focus group from database.""" - db = get_db() + db = await get_db() try: - result = db.focus_groups.update_one( + result = await db.focus_groups.update_one( {"_id": ObjectId(focus_group_id)}, { "$unset": {"uploaded_assets": ""}, @@ -956,15 +963,15 @@ class FocusGroup: return False @staticmethod - def _activate_visual_assets(focus_group_id, asset_filenames, message_id): + async def _activate_visual_assets(focus_group_id, asset_filenames, message_id): """Internal method to activate visual assets in conversation context.""" - db = get_db() + db = await get_db() try: # Get current message count to determine sequence number - message_count = db.focus_group_messages.count_documents({"focus_group_id": focus_group_id}) + message_count = await db.focus_group_messages.count_documents({"focus_group_id": focus_group_id}) # Get existing visual context to check for duplicate assets - focus_group = db.focus_groups.find_one({"_id": ObjectId(focus_group_id)}) + focus_group = await db.focus_groups.find_one({"_id": ObjectId(focus_group_id)}) existing_context = focus_group.get("active_visual_context", []) if focus_group else [] # Track which assets are new vs existing @@ -1009,7 +1016,7 @@ class FocusGroup: # First, update existing assets to current sequence for filename in updated_filenames: - db.focus_groups.update_one( + await db.focus_groups.update_one( {"_id": ObjectId(focus_group_id), "active_visual_context.filename": filename}, { "$set": { @@ -1024,7 +1031,7 @@ class FocusGroup: # Then, add any new assets result = None if new_records: - result = db.focus_groups.update_one( + result = await db.focus_groups.update_one( {"_id": ObjectId(focus_group_id)}, { "$push": {"active_visual_context": {"$each": new_records}}, @@ -1034,7 +1041,7 @@ class FocusGroup: ) else: # If we only updated existing assets, just set the updated_at timestamp - result = db.focus_groups.update_one( + result = await db.focus_groups.update_one( {"_id": ObjectId(focus_group_id)}, {"$set": {"updated_at": datetime.utcnow()}} ) @@ -1048,11 +1055,11 @@ class FocusGroup: return False @staticmethod - def get_active_visual_context(focus_group_id): + async def get_active_visual_context(focus_group_id): """Get all images that are active in conversation context for this focus group.""" - db = get_db() + db = await get_db() try: - focus_group = db.focus_groups.find_one({"_id": ObjectId(focus_group_id)}) + focus_group = await db.focus_groups.find_one({"_id": ObjectId(focus_group_id)}) if focus_group: return focus_group.get('active_visual_context', []) return [] @@ -1062,14 +1069,15 @@ class FocusGroup: return [] @staticmethod - def get_messages_with_visual_context(focus_group_id, limit=100): + async def get_messages_with_visual_context(focus_group_id, limit=100): """Get messages with enhanced visual context information.""" - db = get_db() + db = await get_db() try: # Get all messages - messages = list(db.focus_group_messages.find( + cursor = db.focus_group_messages.find( {"focus_group_id": focus_group_id} - ).sort("created_at", 1)) + ).sort("created_at", 1) + messages = await cursor.to_list(length=None) # Convert ObjectId to strings and add sequence numbers for i, message in enumerate(messages): @@ -1078,7 +1086,7 @@ class FocusGroup: message["sequence"] = i + 1 # Add flag indicating if this message has visual context available - active_context = FocusGroup.get_active_visual_context(focus_group_id) + active_context = await FocusGroup.get_active_visual_context(focus_group_id) message["has_visual_context"] = any( asset["activated_at_sequence"] <= message["sequence"] for asset in active_context @@ -1091,11 +1099,11 @@ class FocusGroup: return [] @staticmethod - def clear_visual_context(focus_group_id): + async def clear_visual_context(focus_group_id): """Clear all active visual context for a focus group (useful for testing).""" - db = get_db() + db = await get_db() try: - result = db.focus_groups.update_one( + result = await db.focus_groups.update_one( {"_id": ObjectId(focus_group_id)}, { "$unset": {"active_visual_context": ""}, diff --git a/backend/app/models/folder.py b/backend/app/models/folder.py index b473ba1c..fb8e9e9a 100644 --- a/backend/app/models/folder.py +++ b/backend/app/models/folder.py @@ -5,9 +5,9 @@ from datetime import datetime class Folder: @staticmethod - def create(folder_data, user_id): + async def create(folder_data, user_id): """Create a new folder.""" - db = get_db() + db = await get_db() # Add metadata folder_data["created_at"] = datetime.utcnow() @@ -15,15 +15,15 @@ class Folder: # Note: No longer storing persona_ids in folders - using persona-centric storage - result = db.folders.insert_one(folder_data) + result = await db.folders.insert_one(folder_data) return str(result.inserted_id) @staticmethod - def find_by_id(folder_id): + async def find_by_id(folder_id): """Find a folder by its ID.""" - db = get_db() + db = await get_db() try: - folder = db.folders.find_one({"_id": ObjectId(folder_id)}) + folder = await db.folders.find_one({"_id": ObjectId(folder_id)}) if folder: folder["_id"] = str(folder["_id"]) return folder @@ -32,10 +32,11 @@ class Folder: return None @staticmethod - def find_by_user(user_id, limit=100): + async def find_by_user(user_id, limit=100): """Find all folders created by a specific user.""" - db = get_db() - folders = db.folders.find({"created_by": user_id}).sort("created_at", -1).limit(limit) + db = await get_db() + cursor = db.folders.find({"created_by": user_id}).sort("created_at", -1).limit(limit) + folders = await cursor.to_list(length=limit) result = [] for folder in folders: @@ -45,11 +46,12 @@ class Folder: return result @staticmethod - def get_all(limit=100): + async def get_all(limit=100): """Get all folders (for debugging/admin purposes).""" try: - db = get_db() - folders = list(db.folders.find().sort("created_at", -1).limit(limit)) + db = await get_db() + cursor = db.folders.find().sort("created_at", -1).limit(limit) + folders = await cursor.to_list(length=limit) result = [] for folder in folders: @@ -62,9 +64,9 @@ class Folder: return [] @staticmethod - def update(folder_id, data): + async def update(folder_id, data): """Update a folder.""" - db = get_db() + db = await get_db() # Create a copy of the data to avoid modifying the original filtered_data = data.copy() @@ -82,7 +84,7 @@ class Folder: # Set the updated timestamp filtered_data["updated_at"] = datetime.utcnow() - result = db.folders.update_one( + result = await db.folders.update_one( {"_id": ObjectId(folder_id)}, {"$set": filtered_data} ) @@ -90,26 +92,26 @@ class Folder: return result.modified_count > 0 @staticmethod - def delete(folder_id): + async def delete(folder_id): """Delete a folder.""" - db = get_db() + db = await get_db() try: - result = db.folders.delete_one({"_id": ObjectId(folder_id)}) + result = await db.folders.delete_one({"_id": ObjectId(folder_id)}) return result.deleted_count > 0 except Exception as e: print(f"Error in delete: {e}") return False @staticmethod - def add_persona(folder_id, persona_id): + async def add_persona(folder_id, persona_id): """Add a persona to a folder (persona-centric storage).""" - db = get_db() + db = await get_db() try: print(f"🔧 FOLDER ADD_PERSONA: folder_id={folder_id}, persona_id={persona_id}") # Check if persona exists - persona = db.personas.find_one({"_id": ObjectId(persona_id)}) + persona = await db.personas.find_one({"_id": ObjectId(persona_id)}) if not persona: print(f"❌ FOLDER ADD_PERSONA: Persona {persona_id} not found") return False @@ -118,7 +120,7 @@ class Folder: print(f"📋 FOLDER ADD_PERSONA: Current folder_ids: {persona.get('folder_ids', 'None')}") # Only update the persona's folder_ids - single source of truth - persona_result = db.personas.update_one( + persona_result = await db.personas.update_one( {"_id": ObjectId(persona_id)}, {"$addToSet": {"folder_ids": folder_id}, "$set": {"updated_at": datetime.utcnow()}} ) @@ -126,11 +128,11 @@ class Folder: print(f"📝 FOLDER ADD_PERSONA: Update result - modified_count: {persona_result.modified_count}, matched_count: {persona_result.matched_count}") # Verify the update - updated_persona = db.personas.find_one({"_id": ObjectId(persona_id)}) + updated_persona = await db.personas.find_one({"_id": ObjectId(persona_id)}) print(f"✅ FOLDER ADD_PERSONA: Updated folder_ids: {updated_persona.get('folder_ids', 'None')}") # Update folder's updated_at timestamp - db.folders.update_one( + await db.folders.update_one( {"_id": ObjectId(folder_id)}, {"$set": {"updated_at": datetime.utcnow()}} ) @@ -143,15 +145,15 @@ class Folder: return False @staticmethod - def remove_persona(folder_id, persona_id): + async def remove_persona(folder_id, persona_id): """Remove a persona from a folder (persona-centric storage).""" - db = get_db() + db = await get_db() try: print(f"🔧 FOLDER REMOVE_PERSONA: folder_id={folder_id}, persona_id={persona_id}") # Check if persona exists - persona = db.personas.find_one({"_id": ObjectId(persona_id)}) + persona = await db.personas.find_one({"_id": ObjectId(persona_id)}) if not persona: print(f"❌ FOLDER REMOVE_PERSONA: Persona {persona_id} not found") return False @@ -160,7 +162,7 @@ class Folder: print(f"📋 FOLDER REMOVE_PERSONA: Current folder_ids: {persona.get('folder_ids', 'None')}") # Only update the persona's folder_ids - single source of truth - persona_result = db.personas.update_one( + persona_result = await db.personas.update_one( {"_id": ObjectId(persona_id)}, {"$pull": {"folder_ids": folder_id}, "$set": {"updated_at": datetime.utcnow()}} ) @@ -168,11 +170,11 @@ class Folder: print(f"📝 FOLDER REMOVE_PERSONA: Update result - modified_count: {persona_result.modified_count}, matched_count: {persona_result.matched_count}") # Verify the update - updated_persona = db.personas.find_one({"_id": ObjectId(persona_id)}) + updated_persona = await db.personas.find_one({"_id": ObjectId(persona_id)}) print(f"✅ FOLDER REMOVE_PERSONA: Updated folder_ids: {updated_persona.get('folder_ids', 'None')}") # Update folder's updated_at timestamp - db.folders.update_one( + await db.folders.update_one( {"_id": ObjectId(folder_id)}, {"$set": {"updated_at": datetime.utcnow()}} ) @@ -185,9 +187,9 @@ class Folder: return False @staticmethod - def add_personas_batch(folder_id, persona_ids): + async def add_personas_batch(folder_id, persona_ids): """Add multiple personas to a folder (persona-centric storage).""" - db = get_db() + db = await get_db() try: print(f"🔧 FOLDER ADD_PERSONAS_BATCH: folder_id={folder_id}, persona_ids={persona_ids}") @@ -199,7 +201,7 @@ class Folder: print(f"🔧 FOLDER BATCH: Processing persona {persona_id}") # Check if persona exists - persona = db.personas.find_one({"_id": ObjectId(persona_id)}) + persona = await db.personas.find_one({"_id": ObjectId(persona_id)}) if not persona: print(f"❌ FOLDER BATCH: Persona {persona_id} not found") persona_results.append(False) @@ -208,7 +210,7 @@ class Folder: print(f"✅ FOLDER BATCH: Found persona {persona.get('name', 'Unknown')}") print(f"📋 FOLDER BATCH: Current folder_ids: {persona.get('folder_ids', 'None')}") - result = db.personas.update_one( + result = await db.personas.update_one( {"_id": ObjectId(persona_id)}, {"$addToSet": {"folder_ids": folder_id}, "$set": {"updated_at": datetime.utcnow()}} ) @@ -221,7 +223,7 @@ class Folder: persona_results.append(False) # Update folder's updated_at timestamp - db.folders.update_one( + await db.folders.update_one( {"_id": ObjectId(folder_id)}, {"$set": {"updated_at": datetime.utcnow()}} ) @@ -237,9 +239,9 @@ class Folder: return False @staticmethod - def remove_personas_batch(folder_id, persona_ids): + async def remove_personas_batch(folder_id, persona_ids): """Remove multiple personas from a folder (persona-centric storage).""" - db = get_db() + db = await get_db() try: print(f"🔧 FOLDER REMOVE_PERSONAS_BATCH: folder_id={folder_id}, persona_ids={persona_ids}") @@ -251,7 +253,7 @@ class Folder: print(f"🔧 FOLDER REMOVE_BATCH: Processing persona {persona_id}") # Check if persona exists - persona = db.personas.find_one({"_id": ObjectId(persona_id)}) + persona = await db.personas.find_one({"_id": ObjectId(persona_id)}) if not persona: print(f"❌ FOLDER REMOVE_BATCH: Persona {persona_id} not found") persona_results.append(False) @@ -260,7 +262,7 @@ class Folder: print(f"✅ FOLDER REMOVE_BATCH: Found persona {persona.get('name', 'Unknown')}") print(f"📋 FOLDER REMOVE_BATCH: Current folder_ids: {persona.get('folder_ids', 'None')}") - result = db.personas.update_one( + result = await db.personas.update_one( {"_id": ObjectId(persona_id)}, {"$pull": {"folder_ids": folder_id}, "$set": {"updated_at": datetime.utcnow()}} ) @@ -273,7 +275,7 @@ class Folder: persona_results.append(False) # Update folder's updated_at timestamp - db.folders.update_one( + await db.folders.update_one( {"_id": ObjectId(folder_id)}, {"$set": {"updated_at": datetime.utcnow()}} ) @@ -289,13 +291,13 @@ class Folder: return False @staticmethod - def get_folders_containing_persona(persona_id, user_id=None): + async def get_folders_containing_persona(persona_id, user_id=None): """Find all folders that contain a specific persona (persona-centric storage).""" - db = get_db() + db = await get_db() try: # Get the persona to see which folders it belongs to - persona = db.personas.find_one({"_id": ObjectId(persona_id)}) + persona = await db.personas.find_one({"_id": ObjectId(persona_id)}) if not persona or not persona.get("folder_ids"): return [] @@ -307,7 +309,8 @@ class Folder: if user_id: query["created_by"] = user_id - folders = list(db.folders.find(query)) + cursor = db.folders.find(query) + folders = await cursor.to_list(length=None) result = [] for folder in folders: diff --git a/backend/app/models/persona.py b/backend/app/models/persona.py index 432af221..185fe1d2 100644 --- a/backend/app/models/persona.py +++ b/backend/app/models/persona.py @@ -4,8 +4,8 @@ from datetime import datetime class Persona: @staticmethod - def create(persona_data, user_id=None): - db = get_db() + async def create(persona_data, user_id=None): + db = await get_db() # Add metadata persona_data["created_at"] = datetime.utcnow() @@ -15,13 +15,13 @@ class Persona: if "folder_ids" not in persona_data: persona_data["folder_ids"] = [] - result = db.personas.insert_one(persona_data) + result = await db.personas.insert_one(persona_data) print(f"✅ PERSONA CREATED: {persona_data.get('name', 'Unknown')} with folder_ids: {persona_data['folder_ids']}") return str(result.inserted_id) @staticmethod - def find_by_id(persona_id): - db = get_db() + async def find_by_id(persona_id): + db = await get_db() try: # If persona_id is already an ObjectId, use it directly if isinstance(persona_id, ObjectId): @@ -33,14 +33,14 @@ class Persona: except Exception as e: print(f"Invalid ObjectId format: {persona_id}, error: {e}") # Try lookup by string ID as fallback - persona = db.personas.find_one({"id": persona_id}) + persona = await db.personas.find_one({"id": persona_id}) if persona: persona["_id"] = str(persona["_id"]) return persona return None # Lookup by ObjectId - persona = db.personas.find_one({"_id": object_id}) + persona = await db.personas.find_one({"_id": object_id}) if persona: persona["_id"] = str(persona["_id"]) return persona @@ -49,25 +49,25 @@ class Persona: return None @staticmethod - def find_by_user(user_id, limit=100): - db = get_db() + async def find_by_user(user_id, limit=100): + db = await get_db() personas = db.personas.find({"created_by": user_id}).sort("created_at", -1).limit(limit) result = [] - for persona in personas: + async for persona in personas: persona["_id"] = str(persona["_id"]) result.append(persona) return result @staticmethod - def get_all(limit=100): + async def get_all(limit=100): try: - db = get_db() - personas = list(db.personas.find().sort("created_at", -1).limit(limit)) + db = await get_db() + personas = db.personas.find().sort("created_at", -1).limit(limit) result = [] - for persona in personas: + async for persona in personas: persona["_id"] = str(persona["_id"]) result.append(persona) @@ -77,8 +77,8 @@ class Persona: return [] @staticmethod - def update(persona_id, data): - db = get_db() + async def update(persona_id, data): + db = await get_db() # Create a copy of the data to avoid modifying the original filtered_data = data.copy() @@ -96,7 +96,7 @@ class Persona: # Set the updated timestamp filtered_data["updated_at"] = datetime.utcnow() - result = db.personas.update_one( + result = await db.personas.update_one( {"_id": ObjectId(persona_id)}, {"$set": filtered_data} ) @@ -104,8 +104,8 @@ class Persona: return result.modified_count > 0 @staticmethod - def delete(persona_id): - db = get_db() + async def delete(persona_id): + db = await get_db() try: # Convert to ObjectId if needed if isinstance(persona_id, ObjectId): @@ -119,7 +119,7 @@ class Persona: except Exception as e: print(f"Invalid ObjectId format for delete: {persona_id}, error: {e}") # Try delete by string ID as fallback - result = db.personas.delete_one({"id": persona_id}) + result = await db.personas.delete_one({"id": persona_id}) # Note: No folder cleanup needed - using persona-centric storage return result.deleted_count > 0 @@ -127,7 +127,7 @@ class Persona: # Folder membership is only stored in persona.folder_ids, which gets deleted with the persona # Delete by ObjectId - result = db.personas.delete_one({"_id": object_id}) + result = await db.personas.delete_one({"_id": object_id}) return result.deleted_count > 0 except Exception as e: print(f"Error in delete: {e}, persona_id: {persona_id}") diff --git a/backend/app/models/user.py b/backend/app/models/user.py index 4fe7097a..1a8d4239 100644 --- a/backend/app/models/user.py +++ b/backend/app/models/user.py @@ -22,33 +22,33 @@ class User: return bcrypt.checkpw(password.encode('utf-8'), password_hash.encode('utf-8')) @staticmethod - def find_by_username(username): - db = get_db() - user_data = db.users.find_one({"username": username}) + async def find_by_username(username): + db = await get_db() + user_data = await db.users.find_one({"username": username}) return user_data @staticmethod - def find_by_email(email): - db = get_db() - user_data = db.users.find_one({"email": email}) + async def find_by_email(email): + db = await get_db() + user_data = await db.users.find_one({"email": email}) return user_data @staticmethod - def find_by_id(user_id): - db = get_db() - user_data = db.users.find_one({"_id": ObjectId(user_id)}) + async def find_by_id(user_id): + db = await get_db() + user_data = await db.users.find_one({"_id": ObjectId(user_id)}) return user_data @staticmethod - def find_by_microsoft_id(microsoft_id): - db = get_db() - user_data = db.users.find_one({"microsoft_id": microsoft_id}) + async def find_by_microsoft_id(microsoft_id): + db = await get_db() + user_data = await db.users.find_one({"microsoft_id": microsoft_id}) return user_data @staticmethod - def update_microsoft_id(user_id, microsoft_id): - db = get_db() - result = db.users.update_one( + async def update_microsoft_id(user_id, microsoft_id): + db = await get_db() + result = await db.users.update_one( {"_id": ObjectId(user_id)}, {"$set": {"microsoft_id": microsoft_id, "auth_type": "microsoft"}} ) @@ -63,8 +63,8 @@ class User: "microsoft_id": self.microsoft_id } - def save(self): - db = get_db() + async def save(self): + db = await get_db() user_data = { "username": self.username, "email": self.email, @@ -73,23 +73,23 @@ class User: "auth_type": self.auth_type, "microsoft_id": self.microsoft_id } - result = db.users.insert_one(user_data) + result = await db.users.insert_one(user_data) return result.inserted_id @staticmethod - def create_default_user(): + async def create_default_user(): try: - db = get_db() + db = await get_db() # First check if users collection exists - collections = db.list_collection_names() + collections = await db.list_collection_names() if "users" not in collections: print("Creating users collection") - db.create_collection("users") + await db.create_collection("users") # Safely check if user exists, handling potential auth errors try: - user_exists = db.users.count_documents({"username": "user"}) > 0 + user_exists = await db.users.count_documents({"username": "user"}) > 0 except Exception as e: print(f"Error checking for default user: {e}") # If we can't query, assume we need to create the user @@ -102,7 +102,7 @@ class User: password_hash=User.hash_password("pass"), role="admin" ) - default_user.save() + await default_user.save() print("Default user created successfully") else: print("Default user already exists") diff --git a/backend/app/routes/__pycache__/ai_personas.cpython-313.pyc b/backend/app/routes/__pycache__/ai_personas.cpython-313.pyc index a09d39ebb86ff89cb11bec7db616f1ef3941841d..72fcafbf3b57873e5345705d6da797e3b9d37acf 100644 GIT binary patch delta 14466 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 delta 13518 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 diff --git a/backend/app/routes/__pycache__/auth.cpython-313.pyc b/backend/app/routes/__pycache__/auth.cpython-313.pyc index d65be35f23bf591b1295256d348ffacc2bb37b9e..d99c0dc3d7d6110c4d9216d36bfbc36ad8cff8bf 100644 GIT binary patch delta 3234 zcmaJ@eN0=|6~FgAKYu*@Y-9XwV{ASQ4q!tJfdGcu1TZ9_Q617Lw2D|T2^n#+XINXb zjV7DAbo-~-Rh24B)=X13rfFHG>;tL$C=k9PRU&wiBbGllt(DsCA4t+I+ahh}JV;7t zwJY6ofA^eo?|tug?>YDWE%r&T;l3n^1j@C#tH=Lddfwom2YyjrLeGEQ?#@qY!ZfMf z%_Yq)Qjbnh>hmm~3;WOSXV2ygCG2OKXD052iN18*LbTO?(FE4@ z#8~>TOc$_l*4atv_h?6>>QcM{;H-*vG^=KXTEx3(M^rW25X*zk97j9aRCD|;#LX0C zRFQ2c$JODHQ^$tWN0I2q1$RebdW{zps#&$AdOJ_o+q4&`WgGh>t?ARStDK$f7cEQ> z|0F6*z5onU`Fc$B#T)1IYWyXZF^T^zZDR9=n=0H=#lJ6#g1h0d%6 zJF_-tN0iKl6^?5V-_W7{hFFcbLBWk}faerE*GOS3(2bg?*xAOeNg9{8Z4SKbW+WG9L$ z6xAqdP}H(#%@s7vUN*Pd8&J^*BFM`D#LXyL*!yO$AqMa?)XY5)AG3d&;|7=a?eN9$ zWO>%LGiTY!_7wDK*Yk)q^6-I$T`Fil^eA%xe9sog8|gdFI7fa@<3#lbMAHrm%4uf+ zaU0?pLIKZkV!WQ8(R<@H{LBuDxKcoCjexkG2h6mVLcPLB-KWR808TL7l0QhA^szHs zDf_Qh$1Z7O(2DG~<$#q>s%vQdCV@#!9al>Fc=iTorCN5t>Zdv;TWi5f+~OsmSCXh%E7@UNUCs8{edG(BNi9ZBQs1Xe zN{F_s*Uzrn4mWNelotEMK5fzn>=0;mo2_ZHl{Q-~tFaIBV8t(Dzp|HDlpz1BA{}sm zK7l&L?${%4n%2ZLy*HCeJ4A;_lrJJf6zmt-w;e?+=I}tK$TwuTBH#&~6~4ae=nnJA z!Yxc%>$sZH!?5?8i>}_{>(H```{Ix2j zULMYj4yTVD8Ox+(xdjc)>_c}`IiBS2Ld`5c&S2^TpP#{gdAK-`D{fvXZl2ngE$+^F zy3g(ZhniUYcLO_efyR|Ub}>(L0Y_5LkfH7x|aB# zL1}%SK762Mb>0%)zt_#gUGwmPiyii!f`n;?!+)0cI3QtmmqNnqYExYf1De*Oa$1T} z=*$?fGh=K8JS!odHE@XYoe3S8-9ZzaYPPZz@C_qP2&x;VR={%xn$W1`j1ppx2K?tr z!-*O)UqBNf)x2d#Vgfp?F#?u_R-ARA4Od&_D2!MX6sb|9PE1ttiv@;6fM4_r=o1i7 zQ^_L^QJ8hHM(p*}yGzo~UJm7IdSWXSesm$@}(NkYaJ$;ux$--V>53(>`r6t<~FG`<;GaM2hsUc}L zz&R(9d_DBBq-noq13_p$NkeI_U-wv>RP}LvG$|z2A=Tp~v5}@A+FJaiUwLz(w}uRq z!HoQtu@LP2(&L%>_|5SX#6Tk4mM9dTL?IvJF;dgeZjC~?GV;cwU8!_R9vDse27Kw% zOFo4Z-_R-9H=0sn#5Xu5L#X(Ao$_28jA50BR42+G8 zDgia_If%^qHhE4B`Z8ljkESx2p|O#XSH^W;9vhHHt53W%8dS+<_z&5E0&k*K^~MTv zEo#sXfB0;4?VV z2SvtgyDDvd>=EVv<%6L62)(`!0^g5oYNN1^-nA9xY=IS9AZx40nJRL|@Z@vnRCmm_ z3l--lD&E?CC7m_bpVK^06G!D_L)IQSr~klcyJN4v2?``eg z|E%%o_b7vj?JS*$OAzZZH^h3(sk+<@2=8AB?q7=7EyhKDy37?9c)W{6eufBOoe>1Y zA`iGXXjv7M-H~~ibAwzYbJaX>`g90$ZAdX6fz8{X8qc|mVYA9E7WFmY3s8=sz)Y&p zC?^2jhe|e>*0!8HH8?hsYL#CE9mE;Qe8l{|-$mBA=O~TSwG$lSbl)mGQ@Cs_nhZ=j YUM*avJJ-2;Stxa25wwXod=bScOH`{c+ zbf}}C38G5l@qTP7Q>`?A4@`F&LZGfd!*bc7nOf6gHYbHS-$ zE`h<#+o4D{Q~3&<;RmN$GzQKb#R$EGe$kstdS^lJEb3j57V;fBLrG8yf^vL!N$@WU z{$;7=c*TNm^MbXrD0NNe7o^03F0m}=OM<;1*pII(3EoA)3q%*ynQD%XzB>Ba7;Mqq zzy3N4^02mfWNhF5+|z8aI#~GSAkR{K8@ZvG3ruaTcn9-2AtS)a3Ofk;-{Fq_FSud& z!rVgLKOw3_g<8p=1*VMcxEZb(r*Q~QnI52c)znwlO^pq7<{LPc+BLohYZ90yP?9_a z$y1bS;R)$SqfIHYp**yF8KoHqn} z{OJ6~FvSc0`zT)Yi@lBHVvD2KM`lHy`pilu#$A}|XWeCuPs4a4Vb-9SRLE>pNm|KV z6{dX7s-bw8G>>MNNU~?wYH_OQ;RxyIELUeYMlfc67Ot5m;9&KLJ77wwF=|xp48jbW z7_>5oz@6$w+yV`9w{;^^Y@!e#s-N*r23;^AyA26S57L?c6(N|E6MEV9?)GEbPbQ0U z2yV$4S_=^}mMwwXc^`E<6^yr|8OhJET}%YXIpj{%lXGs&c)f-q{9$n!$^l>C@BL2L zRnq`Rxhi;o`b6Pig)Bpp#$I7pMI;YKO8&^(2gAc(SYskD-wU?G8qlG7lrCM0sa?jld zB?%Et3p-U_yBcx4}GmEPAKvxC z!K`X!_6V$Zwc;o|<{Hwj+#i^Ch45Cm>1x0|;BwovUCgf&HoIeWJ1IRzXa0kgPSfuA zU#GqA{wk$-OG>n$L{EHJ>a0iSw01xMY7;S=Ha;F{+%dR4pNjMqp0xe|j4K{C%PeSR|U#f;bTR6p<6Fdk$+^G#x+-$Uj%D*X;}Nye04 za%d=~0@X{Jp^?4$XH?cCD(w-ZvTL(sE^GH3cy4@8_WAK_ZZJCtzpcwg*fXZGK2=$n q)qVy62JCHH(shoG4IUcFcB>E5C2Oku47}|B+VCg$AjSz?cKr{C@_sk~ diff --git a/backend/app/routes/__pycache__/focus_group_ai.cpython-313.pyc b/backend/app/routes/__pycache__/focus_group_ai.cpython-313.pyc index 4e3b2c1fffb57b93578a1095b595a240cd588b25..0067947a036b73b3d80d7b1452dbfd784dc068c3 100644 GIT binary patch delta 16444 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 Kj$9(e)%}0*HC&wl delta 13380 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 YX^EB@)hd?cu%n5ZB^m5!1_${613}r_tN;K2 diff --git a/backend/app/routes/__pycache__/folders.cpython-313.pyc b/backend/app/routes/__pycache__/folders.cpython-313.pyc index 3a2d75fd0bed48b2890bcc972ff3d9f707b38fa5..149d9bb0db970823836c0ecf4e091471a8ce5fe3 100644 GIT binary patch delta 4304 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 LYom6|T$mSkGt7`3yeOB77X z+BJU`1VQJmLmxTVlC}Ght;3LQXog_GfTCy%*~yRnDk2T=5V*?*1i{e#Sb=2N<`&D& zB^5=E2b945?h$!;dCqsgbB;bb{+oH}o+t_oUdsy?pF0x2CP@w_hd=NAiDBTco*`Ir z4Dn=yjWh6;LnSP{=ZFvv)*Py08)2)g${2i^E#Px&K1Pl)J2;hrpfd+gIg?}#zUBP3 z|9RZY>5(P zUWUEM8V;Rpapa_1CSBl_0x&4mwPjp(mbou59mY5ak!Y09i9n9Lx;&IOTW!9Wv<92ER$KtLd=&rP_~sQ zvF2qsOX5SjXlqg6YNJD--)?(@816RD%%|{yPvts_JiIQ1Ibw+Lxvx)=nsV}#`2vsL zn{!J}yW{{Il7E46`b>U#U>1GYhoc%np;BFG{C=|T39Nb4IzN7T5DSBPeue{VyCSbU$U@7vouF27jY8JnBE;Vgu z-bo|W!UYAVl`#o%)h^6%+^RP*J;|+(6KYTLh;Ygu7ECc_(8nLaO;`H==GSNbEU`g4 zpgbl#I~)q=utSZ^62s9(psBV^^t#%fn)D{!XS_2@Czehx#TH^#{_DU7-zL-W%b=!G z;wqH9HxW>%PDbil6Qgx8dg-Nxn1sIvpLR=KubuqC$xC}1(g4hcwmB+0aP1jCoJ#uP z!;sI?=bz2eF$Cf8AR#n&V8Yu4r^88jCmeypYNrilN8oli2Woc&UQnez%lk*U#2W41 zn@>$~2q=#C9bt|&+@?4>n&J?f;xOlyIR4Z96ih`vMscV;bM`OM_@<%SlhgL2`7(~` zzYwTS{QRL!I)Oj*q{PP+5)B5rFJ*4}2bYC)IkYCn>vH_^k%pXyyfSRb;=HnR`$jc~ zyPhjBQ(30Qr731}h$}IPVpf>Wa4Qpu=|OH)BWSM<@`!LU8G@5h#iH=Qqw3qri0}s48*5H8p~H<%EyaKE>6fPAJ<7Hq0w?Zc*16+XwTp z50E-V8+V)3=@=FAcT^kcJAmfTa8%z#p!|9Guam6>+>09O46n(9b$Rd#rqm>SqBV)q zdw1y@Ra#N7q9>WF6l&cP#iTGbz}1EmQyRAtA!x5?JR(joV zX8ojp4*u4EpPNgHp!G!I^MM)rL1Y5S-90#_mC*b>j%u=5#6tYx&EkA}aH}}?iByv5 zx}3hUw;@l%cw)+uF{dbc{-pVm5)e5*kNb^+^VLrV=wD?z&9S zz8>NcQJA9^?{%mk7{`}bnDh3)_fy08Z{YnD72U_F zh{w4pp9OHF^(SbLdUzs>9z2m&2Qi$RdIKgo?ahWAUQFk4E3y5IpqAF*K{~=Y4GEM? z(C}ZCFgc3jQe-OwM?JV)yx}zv*eg_ zD{uap`7!)(s6r0HgQ0PM5jU-jP)JcoQ}`-8t&fpIa9%%**|n}e<2^!e_fhat_!{iZ zj&&SG^N%>HuV5*7HLJSYx){8dePK&=vIL;lW!kFb-SE>~QvmmKyPmTI@WxcvEXS-2 zq!Cx;1jYO~;`I>;@dhXCX1E(-Vm8m+7$wxs^N7jtE&kYvh3Ypev~5*v6lk%zC7XBW zpMUZ6{L*($FO?UU7U99RMU8ghirGzQovqMfQK6!)97XsQQ`zym^H1zyZM1d#4@lxy AF8}}l diff --git a/backend/app/routes/__pycache__/personas.cpython-313.pyc b/backend/app/routes/__pycache__/personas.cpython-313.pyc index e497489b1224efa490bddbaec81986d6d4913541..c7bd2b98c552821394ebd25faecc467986d502e9 100644 GIT binary patch delta 3243 zcmai0du&tJ89&GO-q^YE`*|Jv`Vn8pjvYHr948^6B-e>k+Od#gjMy>~QYfIQiS|0bfcRltvy1tDJ)FYQ2D2@vJIxS+MERb*g7qZD_s<2MVl(LYX4x!R64rHcPWo~|lC`dEm5ysatg(J1N{ZOLAU0o!l*LZP)%nuq28IglIGj$xr5eD!C+cf&@vwNtEtB)3d z8m(mjG+M*@@E6A6AfIL-243<8ug1Vr^OM>`uuhFZl@?;)4ov%}{AG7cvBk*mYkV~o zEwab&EbFLyf2(Qj{cQX(STKp@4tz}LLZ|Qs_Oz!9U8aiP;4buAUd}KjE-a^*QVdfZ z=U}Lm;wa8AfJvJ^a4*AkD;XZvH<{Mv?7AaG-qANvdt1mq^s2pk4D&v6-(DW<75s`2 zTM%Mzc9w-U#o9)W*ga&w5$rY~Tl<}5BenY_BTdVIYIoLzkGW+lI*nyB`%{W8VbEQ6 z0V|<9q0ra z0w)nUsWlOgDZ0+htm!N3bW^5~9^Bpx&DqZE!{lAl`QUS~3(XUx!sIU-14kZTvIvSL zxL^sEEg_{QM2?%iMwjp83$MIzW^dWms#sddd9#!Umg=PnkF1$b$AhrRWJqqoKgV*C zJ(~tpa$`BgmTDaor!dGxX0Wh*54C#r(E(7s+N=5hB%_Ht8}idyUFX@L$46eZjQH0i z8;vmqCc5&q(RM@zE%9}0Q`Wp0BJxLQuR$w7YavIiN1&K5Vp|4p+YB4*gf`v-w%?<) zpBuzW4zJ<}FF3+wM?^72j1O| zQ^qqBWuZf{b{yB@_ z`W>5qf~UZ`N!*u$(&?es?3({4b&rn*ZM0r`4kY{{bH7*&_zanR2=ac?6#J`IvJ8_mlrRgVGk* z_1iRC-vOZ#y9z<3?DY%wdI+|?MKQLJZkN|+_naJfW#CL_*`8F4NwUw?++49CQySuH zYFeRmt-J}J!g42jIt_?c@J0w?jN*+LBst}B8}Bjrz7H>J9gnQYyRN5k3mF!Y>lpYW zp&qxAw}l?+)Aylus6O2i-oXRpyt^m14P5yl_37=;efol8wb!PNp1SExQ=8swEPHws zM-SOnyE$EP&|)k(AR;c6m%NTD5kR(dKpJHBXSK&hzA5c-O%D zuO5e1UqD8bH;{jOJe;wh$zvjVvu3F72fvBPh_{X2zaKygkQ3f6tRe4v{p3Hqt{RVN zWvv7Y2o}3RFN+EHeH|U!hxeh!7(d#Fq0WzrgBW66_T8xacrQd}f=MQm4P3DmaB({o z1uEzbT--^P{PjRKCh!;Yx}lqaC+mk{)@f+t)2ru&5a#-^dif_7#6($aQd~`BD0tBy zk+D+0b)kOiHCOOjAbrj0za}O?R%)su{3nP+>e7ZvGZIpjKvj^xv+O0i>c*kqUxe)^ zaV|j%K0^z>nHGEo3LbQq>H(ER3QIn@6*{w4+?!@+Z6d`fIxyReDNd_0!{=hmY`0=@LZUKIdd@s#qH5tn7QJo`YQpd*I$8Q=)@T45KCxRGhDrNAI#Jv+3~Yb zBdvso=DincWVn14xf=?q-0(}?xSEVeE)!os8K5D3fEhL9MJeIZZ(!DJfX1{1Eg6*@ z*g*a)`HjYcHgBSLC%nxZ@rFa1d*c!7eV*Eq+iC7FiZHD9AMRjThMXBzDIDe|j^!RR+YI}dIA)+(Xx z&D;*W{#uL1d^TTL5kF_S;>uiTAvwsUZTnc`%09~TG%K{)cnsaG5 zn&mF&%<6Hjjh!{fX6Vd`bkdwpwm|1f6YX4q5&)c5Wt%EH`2GxYRTJ$`F;`=l;y4F0 zuco;ER_J6H=rs5ixo;jGEgmWY(@~LLUq#BTMeFFngUQjuhYlpw;gPR@>kyF}|KO!R e4n=w&73l$9q~}7B#_}AxjV?wKgBtdE==~4rYVwf) delta 2027 zcmZuxeQZ-z6o0R`uit%bUq4>EZtuOambLq6$2P<aXMK7(KV$K+&7L-#zc$ zbMHCl{(kSw?tQq^e&24lFgPxKf2Kch-hR+%BKUSc$};etF{~3AXmy5QgWY1tup7PG zb?~-df>VMC;-)6(Hx0nFDc5uiw;W;ans_d8Ls(D{!pDt2TZP-AR21v-V%_P^g4pub z+8G1mmxm4y9xjMcxMf!K4{lgJ;Ig!YXR{KXa5UvfiA+wkbht<^6zQ;$%LW$h%Qg-X zKD3DC+$I?Y_?k#53)b2MORNL%h4rMjznq1^h*`KRhLVHHf;$X5Y*IPUo3<8ZHd{dh z)hC#9mQ;*<6-k9jPGZr_g*im{)25Vz81Mr4ngs0dCt%2)fNS;vSj&GGm@Cy_p``|j zLO3skN46D&M(FkhVWVR%*g;22bT+$y2CGXMn6n~6&S2?aNX{8awUOLnmfCF`;-GV` zunSn&$%-Us&aE)zTmUB5xys$8qGn8tLoV>jkw&LfCR2H!xGAEBze&PJG0sdwf~6mODZWhNU31x_cZMk|t5=j-w~Rz(7rjX_#?u zg3X>Iqf?${cKteB+=@>&$d*Vp$XpkD!{IABV&&}!q$HK$s1ZG34x*bV0@&aAT%8}PNPVqNKtCcOO2mb6r^Qv(U&T-6%%{2uBzcC)UI(d0xTf_ClcQPk3!`ljc6pR&% z7HTQ<9WW$b>^ZQzpEC1k7|<&HI4!6f%Hhq59<~;e!8jZWM%a4zDA*2v1ixlC!e^E3 z5f$A};FCRtP!9e-^!lort=`I^or60^o+)^j!j`JFTqy;}S0&&t!jZH8VmA)FEV6od zSE_ciD%Z#^M#|_=NsBIwI*>(TP>t}56e1?o40<_~w5aCAy0TY>+iosfxWk%ect*9Q z3C}d^=g(1?e|Xr_I+ce#atC}VKLs;#8_PnYBD*kFRwpwmpE0J4VVq%?lEk$UrNR5? z8(r&T`mE#CfW+hR7^k(Mt)(e!rm%&=^N_4whz#~s-*ozEr1u+I=;n6^-t*ETaE9)l3(4^8l>60g`8W8VU z;r&Q6c`##xA0ye+Y_<(gLjB3inS}^BPlpAkG1;=(L&jo}RUUGMXVJdmu~Q61S3AhK znSL2}aEMS?m?&$po;+?RcLcfAyGCp3h+gI3R!yix3l~iiQ`!LMqF#qN&CJt+G+1DN zw1HS#349t2ur?Tv%65C&sPY`@gW^F|(oyac-V7)lIqI?n2MfP(9@k ztQq#lRQhD4|Kxsn7?arm1Z#tYRV$#iwz?`f$8%-IoH8TNrFouH zI8ve7_nG|{`biz+{E4`i6q<@q~6`=J}=!53^W6md6;9C7w zX+zAenW#@Q1$qmbp2Bhp+acMoOW8rql5l94DZcmMtFLGWy0g97YcK8Y)i%M!hOX)i zE@7KsvOkp7;#tIPW}oO0S6O{w+$#3Ew0-UYO0+;_*l8R{NN 10: # Limit the number for performance reasons @@ -472,27 +472,22 @@ def batch_generate_personas(): 'temperature': temperature }) - # Generate personas - personas = [] - with concurrent.futures.ThreadPoolExecutor(max_workers=min(count, 4)) as executor: - # Start the generation tasks - future_to_task = { - executor.submit( - generate_persona, - task['prompt_customization'], + # Generate personas using asyncio.gather for concurrent async execution + try: + generation_coroutines = [ + generate_persona( + task['prompt_customization'], None, # No basic_persona for this endpoint task['temperature'] - ): i for i, task in enumerate(generation_tasks) - } + ) for task in generation_tasks + ] - # Process completed tasks as they finish - for future in concurrent.futures.as_completed(future_to_task): - try: - persona_data = future.result() - personas.append(persona_data) - except Exception as exc: - current_app.logger.error(f"Persona generation task failed with error: {exc}") - raise PersonaGenerationError(f"Failed to generate one of the personas: {str(exc)}") + # Execute all persona generations concurrently + personas = await asyncio.gather(*generation_coroutines) + + except Exception as exc: + current_app.logger.error(f"Persona generation task failed with error: {exc}") + raise PersonaGenerationError(f"Failed to generate one of the personas: {str(exc)}") return jsonify({ "message": f"Successfully generated {len(personas)} personas", @@ -509,7 +504,7 @@ def batch_generate_personas(): @ai_personas_bp.route('/batch-generate-and-save', methods=['POST']) @jwt_required() -def batch_generate_and_save_personas(): +async def batch_generate_and_save_personas(): """ Generate multiple synthetic personas using AI and save them to the database. @@ -519,7 +514,7 @@ def batch_generate_and_save_personas(): A JSON object containing the array of generated and saved personas with their IDs """ user_id = get_jwt_identity() - data = request.get_json() or {} + data = await request.get_json() or {} count = data.get('count', 1) if count < 1 or count > 10: # Limit for performance @@ -553,27 +548,22 @@ def batch_generate_and_save_personas(): 'temperature': temperature }) - # Generate personas - generated_personas = [] - with concurrent.futures.ThreadPoolExecutor(max_workers=min(count, 4)) as executor: - # Start the generation tasks - future_to_task = { - executor.submit( - generate_persona, - task['prompt_customization'], + # Generate personas using asyncio.gather for concurrent async execution + try: + generation_coroutines = [ + generate_persona( + task['prompt_customization'], None, # No basic_persona for this endpoint task['temperature'] - ): i for i, task in enumerate(generation_tasks) - } + ) for task in generation_tasks + ] - # Process completed tasks as they finish - for future in concurrent.futures.as_completed(future_to_task): - try: - persona_data = future.result() - generated_personas.append(persona_data) - except Exception as exc: - current_app.logger.error(f"Persona generation task failed with error: {exc}") - raise PersonaGenerationError(f"Failed to generate one of the personas: {str(exc)}") + # Execute all persona generations concurrently + generated_personas = await asyncio.gather(*generation_coroutines) + + except Exception as exc: + current_app.logger.error(f"Persona generation task failed with error: {exc}") + raise PersonaGenerationError(f"Failed to generate one of the personas: {str(exc)}") # Save all generated personas to the database personas = [] @@ -581,7 +571,7 @@ def batch_generate_and_save_personas(): for persona_data in generated_personas: # Generate AI summary for each persona try: - summary_data = generate_persona_summary( + summary_data = await generate_persona_summary( persona_data=persona_data, temperature=temperature ) @@ -603,7 +593,7 @@ def batch_generate_and_save_personas(): del persona_data['id'] # Save to database - persona_id = Persona.create(persona_data, user_id) + persona_id = await Persona.create(persona_data, user_id) # Add database ID to the response persona_data['_id'] = str(persona_id) @@ -626,7 +616,7 @@ def batch_generate_and_save_personas(): @ai_personas_bp.route('/generate-persona-summary', methods=['POST']) @jwt_required() -def generate_summary_for_persona(): +async def generate_summary_for_persona(): """ Generate an AI-synthesized summary for an existing persona. @@ -649,7 +639,7 @@ def generate_summary_for_persona(): A JSON object containing the generated summary data """ user_id = get_jwt_identity() - data = request.get_json() or {} + data = await request.get_json() or {} # Extract parameters persona_data = data.get('persona_data') @@ -671,7 +661,7 @@ def generate_summary_for_persona(): try: # Generate the summary - summary_data = generate_persona_summary( + summary_data = await generate_persona_summary( persona_data=persona_data, temperature=temperature ) @@ -691,7 +681,7 @@ def generate_summary_for_persona(): @ai_personas_bp.route('/enhance-audience-brief', methods=['POST']) @jwt_required() -def enhance_audience_brief_endpoint(): +async def enhance_audience_brief_endpoint(): """ Generate suggestions to improve an audience brief for better persona generation. @@ -710,7 +700,7 @@ def enhance_audience_brief_endpoint(): A JSON object containing separate suggestion arrays for audience_brief and research_objective """ user_id = get_jwt_identity() - data = request.get_json() or {} + data = await request.get_json() or {} # Extract parameters audience_brief = data.get('audience_brief') @@ -733,7 +723,7 @@ def enhance_audience_brief_endpoint(): try: # Generate enhancement suggestions - suggestions = enhance_audience_brief( + suggestions = await enhance_audience_brief( audience_brief=audience_brief.strip(), research_objective=research_objective.strip(), temperature=temperature @@ -755,7 +745,7 @@ def enhance_audience_brief_endpoint(): @ai_personas_bp.route('/batch-generate-summaries', methods=['POST']) @jwt_required() -def batch_generate_summaries(): +async def batch_generate_summaries(): """ Generate comprehensive markdown summaries for multiple personas for download/client review. @@ -773,7 +763,7 @@ def batch_generate_summaries(): A JSON object containing the generated summaries and any errors encountered """ user_id = get_jwt_identity() - data = request.get_json() or {} + data = await request.get_json() or {} # Extract parameters persona_ids = data.get('persona_ids', []) @@ -802,7 +792,7 @@ def batch_generate_summaries(): for persona_id in persona_ids: try: - persona = Persona.find_by_id(persona_id) + persona = await Persona.find_by_id(persona_id) if persona: personas_data.append(persona) else: @@ -822,13 +812,13 @@ def batch_generate_summaries(): successful_summaries = [] failed_summaries = [] - def process_persona_summary(persona_data): + async def process_persona_summary(persona_data): """Helper function to process a single persona summary""" try: persona_name = persona_data.get('name', 'Unknown') print(f"✅ Backend: Successfully generated summary for '{persona_name}' using model: {llm_model}") - summary = generate_persona_download_summary( + summary = await generate_persona_download_summary( persona_data=persona_data, temperature=temperature, llm_model=llm_model @@ -852,22 +842,24 @@ def batch_generate_summaries(): batch = personas_data[i:i + batch_size] current_app.logger.info(f"Processing batch {i//batch_size + 1}: {len(batch)} personas") - # Process this batch - with concurrent.futures.ThreadPoolExecutor(max_workers=batch_size) as executor: - # Submit all tasks for this batch - future_to_persona = { - executor.submit(process_persona_summary, persona): persona - for persona in batch - } - - # Collect results as they complete - for future in concurrent.futures.as_completed(future_to_persona): - result = future.result() - if result['success']: - successful_summaries.append(result) - else: - failed_summaries.append(result) - current_app.logger.error(f"Failed to generate summary for persona {result['persona_name']}: {result['error']}") + # Process this batch using asyncio + batch_tasks = [process_persona_summary(persona) for persona in batch] + batch_results = await asyncio.gather(*batch_tasks, return_exceptions=True) + + # Collect results + for result in batch_results: + if isinstance(result, Exception): + failed_summaries.append({ + 'success': False, + 'error': str(result), + 'persona_name': 'Unknown' + }) + current_app.logger.error(f"Failed to generate summary: {result}") + elif result['success']: + successful_summaries.append(result) + else: + failed_summaries.append(result) + current_app.logger.error(f"Failed to generate summary for persona {result['persona_name']}: {result['error']}") # Prepare response total_requested = len(persona_ids) @@ -916,7 +908,7 @@ def batch_generate_summaries(): @ai_personas_bp.route('/upload-customer-data', methods=['POST']) @jwt_required() -def upload_customer_data(): +async def upload_customer_data(): """ Upload customer data files and parse them using LlamaParse. @@ -930,12 +922,13 @@ def upload_customer_data(): try: current_app.logger.debug(f"=== UPLOAD CUSTOMER DATA API called for user {user_id} ===") - # Check if files were provided - if 'files' not in request.files: + # Check if files were provided (Quart async files access) + files_dict = await request.files + if 'files' not in files_dict: current_app.logger.warning("No 'files' key in request.files") return jsonify({"error": "No files provided"}), 400 - files = request.files.getlist('files') + files = files_dict.getlist('files') if not files or all(f.filename == '' for f in files): current_app.logger.warning("No files selected") return jsonify({"error": "No files selected"}), 400 @@ -943,7 +936,7 @@ def upload_customer_data(): current_app.logger.info(f"Processing {len(files)} customer data files") # Upload and parse files using customer data service - session_id = customer_data_service.upload_and_parse_files(files) + session_id = await customer_data_service.upload_and_parse_files(files) current_app.logger.info(f"Successfully processed customer data files with session_id: {session_id}") @@ -963,7 +956,7 @@ def upload_customer_data(): @ai_personas_bp.route('/cleanup-customer-data/', methods=['DELETE']) @jwt_required() -def cleanup_customer_data(session_id): +async def cleanup_customer_data(session_id): """ Clean up customer data files for a specific session. diff --git a/backend/app/routes/auth.py b/backend/app/routes/auth.py index d69b2cd8..becee7d2 100644 --- a/backend/app/routes/auth.py +++ b/backend/app/routes/auth.py @@ -1,13 +1,13 @@ -from flask import Blueprint, request, jsonify -from flask_jwt_extended import create_access_token, jwt_required, get_jwt_identity +from quart import Blueprint, request, jsonify +from app.auth.quart_jwt import create_access_token, jwt_required, get_jwt_identity from app.models.user import User from app.services.msal_service import MSALService auth_bp = Blueprint('auth', __name__) @auth_bp.route('/register', methods=['POST']) -def register(): - data = request.get_json() +async def register(): + data = await request.get_json() if not data or not data.get('username') or not data.get('email') or not data.get('password'): return jsonify({"message": "Missing required fields"}), 400 @@ -17,15 +17,15 @@ def register(): password = data.get('password') # Check if user already exists - if User.find_by_username(username): + if await User.find_by_username(username): return jsonify({"message": "Username already taken"}), 409 - if User.find_by_email(email): + if await User.find_by_email(email): return jsonify({"message": "Email already registered"}), 409 # Create new user hashed_password = User.hash_password(password) new_user = User(username=username, email=email, password_hash=hashed_password) - user_id = new_user.save() + user_id = await new_user.save() # Generate access token access_token = create_access_token(identity=str(user_id)) @@ -37,9 +37,9 @@ def register(): }), 201 @auth_bp.route('/login', methods=['POST']) -def login(): +async def login(): try: - data = request.get_json() + data = await request.get_json() if not data or not data.get('username') or not data.get('password'): return jsonify({"message": "Missing username or password"}), 400 @@ -76,7 +76,7 @@ def login(): # Try to find user in database try: # Find user by username - user_data = User.find_by_username(username) + user_data = await User.find_by_username(username) if not user_data: return jsonify({"message": "Invalid username or password"}), 401 @@ -111,7 +111,7 @@ def login(): @auth_bp.route('/me', methods=['GET']) @jwt_required() -def get_profile(): +async def get_profile(): user_id = get_jwt_identity() # Handle the default_id case specially @@ -124,7 +124,7 @@ def get_profile(): }), 200 try: - user_data = User.find_by_id(user_id) + user_data = await User.find_by_id(user_id) if not user_data: return jsonify({"message": "User not found"}), 404 @@ -144,10 +144,10 @@ def get_profile(): }), 200 @auth_bp.route('/microsoft', methods=['POST']) -def microsoft_login(): +async def microsoft_login(): """Handle Microsoft OAuth authentication.""" try: - data = request.get_json() + data = await request.get_json() if not data or not data.get('id_token'): return jsonify({"message": "Missing Microsoft ID token"}), 400 @@ -171,15 +171,15 @@ def microsoft_login(): existing_user = None try: # First try to find by Microsoft ID - existing_user = User.find_by_microsoft_id(microsoft_id) + existing_user = await User.find_by_microsoft_id(microsoft_id) # If not found by Microsoft ID, try by email if not existing_user: - existing_user = User.find_by_email(email) + existing_user = await User.find_by_email(email) # If found by email but no Microsoft ID, update the user to link Microsoft account if existing_user and not existing_user.get('microsoft_id'): - User.update_microsoft_id(existing_user['_id'], microsoft_id) + await User.update_microsoft_id(existing_user['_id'], microsoft_id) existing_user['microsoft_id'] = microsoft_id existing_user['auth_type'] = 'microsoft' @@ -192,7 +192,7 @@ def microsoft_login(): try: user_data = msal_service.create_user_data(microsoft_user_info) new_user = User(**user_data) - user_id = new_user.save() + user_id = await new_user.save() existing_user = { "_id": user_id, @@ -226,4 +226,25 @@ def microsoft_login(): except Exception as e: print(f"Unexpected error in Microsoft login route: {e}") - return jsonify({"message": "Internal server error"}), 500 \ No newline at end of file + return jsonify({"message": "Internal server error"}), 500 + + +@auth_bp.route('/refresh-token', methods=['POST']) +async def refresh_token(): + """Generate a new token for testing during JWT system migration.""" + try: + data = (await request.get_json()) or {} + user_id = data.get('user_id', 'default_user') + + # Create a new token with our Quart-JWT system + access_token = create_access_token(user_id) + + return jsonify({ + "message": "Token refreshed successfully", + "access_token": access_token, + "user_id": user_id, + "system": "quart-jwt" + }), 200 + + except Exception as e: + return jsonify({"message": f"Token refresh failed: {str(e)}"}), 500 \ No newline at end of file diff --git a/backend/app/routes/focus_group_ai.py b/backend/app/routes/focus_group_ai.py index 23edb242..0fd177dc 100644 --- a/backend/app/routes/focus_group_ai.py +++ b/backend/app/routes/focus_group_ai.py @@ -4,10 +4,11 @@ These endpoints handle AI-assisted focus group functionality, including persona and key theme generation. """ -from flask import Blueprint, request, jsonify, current_app -from flask_jwt_extended import jwt_required, get_jwt_identity +from quart import Blueprint, request, jsonify, current_app +from app.auth.quart_jwt import jwt_required, get_jwt_identity from typing import Dict, List, Any import time +import concurrent.futures from app.services.focus_group_response_service import ( generate_persona_response, @@ -23,6 +24,7 @@ from app.services.ai_moderator_service import AIModeratorService from app.services.autonomous_conversation_controller import AutonomousConversationController from app.services.conversation_decision_service import ConversationDecisionService, ConversationDecisionError from app.services.conversation_state_manager import ConversationStateManager +from app.services.ai_runner_service import get_ai_runner from app.services.image_description_service import ImageDescriptionService, ImageDescriptionError from app.models.focus_group import FocusGroup from app.models.persona import Persona @@ -32,7 +34,7 @@ focus_group_ai_bp = Blueprint('focus_group_ai', __name__) @focus_group_ai_bp.route('/generate-response', methods=['POST']) @jwt_required(optional=True) # Make JWT optional for development -def generate_ai_response(): +async def generate_ai_response(): """ Generate a response from a persona in a focus group discussion. @@ -49,7 +51,7 @@ def generate_ai_response(): A JSON object containing the generated response """ try: - data = request.get_json() or {} + data = (await request.get_json()) or {} # Validate required fields required_fields = ['focus_group_id', 'persona_id', 'current_topic'] @@ -92,7 +94,7 @@ def generate_ai_response(): current_app.logger.info(f"🤖 Generating AI response using model: {llm_model or 'default (gemini-2.5-pro)'} for focus group {focus_group_id}") # Validate persona exists - persona = Persona.find_by_id(persona_id) + persona = await Persona.find_by_id(persona_id) if not persona: return jsonify({"error": "Persona not found"}), 404 @@ -114,13 +116,13 @@ def generate_ai_response(): # This is the new approach - use persistent conversation context instead of detection print(f"🎨 Checking for active visual context in focus group {focus_group_id}") from app.services.conversation_context_service import ConversationContextService - has_visual_context = ConversationContextService.has_visual_context(focus_group_id) + has_visual_context = await ConversationContextService.has_visual_context(focus_group_id) print(f"🎨 Focus group has active visual context: {has_visual_context}") # Build multimodal conversation context try: - multimodal_context = ConversationContextService.build_multimodal_context(focus_group_id, recent_messages) + multimodal_context = await ConversationContextService.build_multimodal_context(focus_group_id, recent_messages) print(f"✅ Built multimodal context with {multimodal_context['total_visual_assets']} visual assets") except Exception as e: print(f"❌ Error building multimodal context: {e}") @@ -180,7 +182,7 @@ Be genuine and specific in your feedback, drawing on your personal experiences a }) # Generate response using contextual conversation method - response_text = LLMService.generate_contextual_response( + response_text = await LLMService.generate_contextual_response( prompt=prompt, conversation_context=multimodal_context['conversation_context'], temperature=temperature, @@ -192,7 +194,7 @@ Be genuine and specific in your feedback, drawing on your personal experiences a print(f"💬 Using standard response generation (no visual context)") current_app.logger.info(f"Generating standard response") - response_text = generate_persona_response( + response_text = await generate_persona_response( persona=persona, current_topic=current_topic, previous_messages=recent_messages, @@ -267,7 +269,7 @@ Be genuine and specific in your feedback, drawing on your personal experiences a @focus_group_ai_bp.route('/generate-key-themes', methods=['POST']) @jwt_required(optional=True) # Make JWT optional for development -def generate_key_themes(): +async def generate_key_themes(): """ Generate key themes from a focus group discussion. @@ -281,7 +283,7 @@ def generate_key_themes(): A JSON object containing the generated key themes """ try: - data = request.get_json() or {} + data = (await request.get_json()) or {} # Validate required fields if 'focus_group_id' not in data: @@ -293,7 +295,7 @@ def generate_key_themes(): temperature = data.get('temperature', 0.7) # Validate focus group exists - focus_group = FocusGroup.find_by_id(focus_group_id) + focus_group = await FocusGroup.find_by_id(focus_group_id) if not focus_group: return jsonify({"error": "Focus group not found"}), 404 @@ -302,7 +304,7 @@ def generate_key_themes(): # Generate key themes try: - themes = KeyThemeService.generate_key_themes( + themes = await KeyThemeService.generate_key_themes( focus_group_id=focus_group_id, temperature=temperature, llm_model=llm_model @@ -312,7 +314,7 @@ def generate_key_themes(): current_app.logger.info(f"Generated {len(themes)} key themes for focus group {focus_group_id}") # Save themes to database - theme_ids = FocusGroup.add_generated_themes(focus_group_id, themes) + theme_ids = await FocusGroup.add_generated_themes(focus_group_id, themes) if not theme_ids: current_app.logger.error("Failed to save themes to database") @@ -355,7 +357,7 @@ def generate_key_themes(): @focus_group_ai_bp.route('/key-themes/', methods=['GET']) @jwt_required(optional=True) # Make JWT optional for development -def get_key_themes(focus_group_id): +async def get_key_themes(focus_group_id): """ Get all generated key themes for a focus group. @@ -364,12 +366,12 @@ def get_key_themes(focus_group_id): """ try: # Validate focus group exists - focus_group = FocusGroup.find_by_id(focus_group_id) + focus_group = await FocusGroup.find_by_id(focus_group_id) if not focus_group: return jsonify({"error": "Focus group not found"}), 404 # Get themes - themes = FocusGroup.get_generated_themes(focus_group_id) + themes = await FocusGroup.get_generated_themes(focus_group_id) # Format themes for response formatted_themes = [] @@ -397,7 +399,7 @@ def get_key_themes(focus_group_id): @focus_group_ai_bp.route('/key-themes//', methods=['DELETE']) @jwt_required(optional=True) # Make JWT optional for development -def delete_key_theme(focus_group_id, theme_id): +async def delete_key_theme(focus_group_id, theme_id): """ Delete a key theme from a focus group. @@ -406,12 +408,12 @@ def delete_key_theme(focus_group_id, theme_id): """ try: # Validate focus group exists - focus_group = FocusGroup.find_by_id(focus_group_id) + focus_group = await FocusGroup.find_by_id(focus_group_id) if not focus_group: return jsonify({"error": "Focus group not found"}), 404 # Delete theme - success = FocusGroup.delete_generated_theme(focus_group_id, theme_id) + success = await FocusGroup.delete_generated_theme(focus_group_id, theme_id) if not success: return jsonify({ @@ -434,7 +436,7 @@ def delete_key_theme(focus_group_id, theme_id): @focus_group_ai_bp.route('/moderator/status/', methods=['GET']) @jwt_required(optional=True) # Make JWT optional for development -def get_moderator_status(focus_group_id): +async def get_moderator_status(focus_group_id): """ Get the current moderator status for a focus group. @@ -442,7 +444,7 @@ def get_moderator_status(focus_group_id): A JSON object containing the current moderator status """ try: - status = AIModeratorService.get_moderator_status(focus_group_id) + status = await AIModeratorService.get_moderator_status(focus_group_id) if "error" in status: return jsonify(status), 404 if "not found" in status["error"] else 500 @@ -462,7 +464,7 @@ def get_moderator_status(focus_group_id): @focus_group_ai_bp.route('/moderator/advance/', methods=['POST']) @jwt_required(optional=True) # Make JWT optional for development -def advance_moderator_discussion(focus_group_id): +async def advance_moderator_discussion(focus_group_id): """ Advance the moderator to the next item in the discussion guide. For manual mode, also use AI to decide which participant should respond next. @@ -477,7 +479,7 @@ def advance_moderator_discussion(focus_group_id): A JSON object containing the moderator response, updated position, and optionally participant response """ try: - data = request.get_json() or {} + data = (await request.get_json()) or {} temperature = data.get('temperature', 0.7) # Check if focus group is in autonomous mode @@ -493,7 +495,7 @@ def advance_moderator_discussion(focus_group_id): # Default: generate participant response for manual mode, not for autonomous mode generate_participant_response = data.get('generate_participant_response', not is_autonomous_mode) - result = AIModeratorService.advance_discussion(focus_group_id) + result = await AIModeratorService.advance_discussion(focus_group_id) if "error" in result: return jsonify(result), 404 if "not found" in result["error"] else 500 @@ -534,7 +536,7 @@ def advance_moderator_discussion(focus_group_id): # Generate AI description and enhance the moderator response try: print(f"🎨 AI MODE: Generating description for {asset_filename}") - description = ImageDescriptionService.generate_description(focus_group_id, asset_filename) + description = await ImageDescriptionService.generate_description(focus_group_id, asset_filename) # Enhance the moderator response with the description using display reference if available if display_reference: @@ -597,21 +599,21 @@ def advance_moderator_discussion(focus_group_id): if generate_participant_response and not result.get("completed", False): try: # Use AI to decide which participant should respond next - decision = ConversationDecisionService.decide_next_action(focus_group_id, temperature, 'ai') + decision = await ConversationDecisionService.decide_next_action(focus_group_id, temperature, 'ai') if decision.get('action') == 'participant_respond': participant_id = decision['details']['participant_id'] topic_context = decision['details']['topic_context'] # Get participant data - persona = Persona.find_by_id(participant_id) + persona = await Persona.find_by_id(participant_id) if persona: # Get recent messages for context messages = FocusGroup.get_messages(focus_group_id) recent_messages = messages[-20:] if len(messages) > 20 else messages # Generate participant response - response_text = generate_persona_response( + response_text = await generate_persona_response( persona=persona, current_topic=topic_context, previous_messages=recent_messages, @@ -661,7 +663,7 @@ def advance_moderator_discussion(focus_group_id): @focus_group_ai_bp.route('/moderator/position/', methods=['PUT']) @jwt_required(optional=True) # Make JWT optional for development -def set_moderator_position(focus_group_id): +async def set_moderator_position(focus_group_id): """ Set the moderator position to a specific section and item. @@ -675,7 +677,7 @@ def set_moderator_position(focus_group_id): A JSON object confirming the position change """ try: - data = request.get_json() or {} + data = (await request.get_json()) or {} # Validate required fields if 'section_id' not in data: @@ -686,7 +688,7 @@ def set_moderator_position(focus_group_id): section_id = data['section_id'] item_id = data.get('item_id') - result = AIModeratorService.set_moderator_position(focus_group_id, section_id, item_id) + result = await AIModeratorService.set_moderator_position(focus_group_id, section_id, item_id) if "error" in result: return jsonify(result), 404 if "not found" in result["error"] else 400 @@ -705,7 +707,7 @@ def set_moderator_position(focus_group_id): @focus_group_ai_bp.route('/autonomous/start/', methods=['POST']) @jwt_required(optional=True) -def start_autonomous_conversation(focus_group_id): +async def start_autonomous_conversation(focus_group_id): """ Start autonomous conversation for a focus group. @@ -719,7 +721,7 @@ def start_autonomous_conversation(focus_group_id): """ try: current_app.logger.info(f"=== START AUTONOMOUS CONVERSATION API called for focus group {focus_group_id} ===") - data = request.get_json() or {} + data = (await request.get_json()) or {} initial_prompt = data.get('initial_prompt') current_app.logger.info(f"Request data: {data}") @@ -728,49 +730,30 @@ def start_autonomous_conversation(focus_group_id): controller = AutonomousConversationController(focus_group_id, current_app.logger) current_app.logger.info("Controller created successfully") - # Start the conversation (this will run asynchronously) - import asyncio + current_app.logger.info("Preparing to submit conversation to AI Runner...") - current_app.logger.info("Setting up asyncio loop...") - # For now, we'll run this synchronously. In production, you might want to use a task queue + # Get the AI Runner service and submit the conversation + ai_runner = get_ai_runner() + if not ai_runner.is_running: + current_app.logger.error("AI Runner service is not running") + return jsonify({"error": "AI Runner service is not available"}), 503 + + # Submit the conversation to the AI Runner (non-blocking) + current_app.logger.info("Submitting conversation to AI Runner...") try: - # Create a new event loop if one doesn't exist - loop = asyncio.get_event_loop() - current_app.logger.info("Using existing event loop") - except RuntimeError: - current_app.logger.info("Creating new event loop") - loop = asyncio.new_event_loop() - asyncio.set_event_loop(loop) - - current_app.logger.info("Calling controller.start_autonomous_conversation...") - - # GPT-5 fix: Start the conversation in Socket.IO background task instead of threading - # This ensures the AI loop runs in the correct eventlet greenlet - from app.extensions import socketio - from flask import copy_current_request_context - - @copy_current_request_context - def start_conversation_in_socketio_greenlet(): - """Run the autonomous conversation in the eventlet greenlet context.""" - try: - with current_app.app_context(): - # Run the async conversation in this greenlet using asyncio - import asyncio - result = asyncio.run(controller.start_autonomous_conversation(initial_prompt)) - current_app.logger.info(f"Background conversation result: {result}") - except Exception as e: - try: - current_app.logger.error(f"Background conversation error: {e}") - except: - print(f"Background conversation error: {e}") # Fallback if logger fails - - # Use socketio.start_background_task instead of threading - socketio.start_background_task(start_conversation_in_socketio_greenlet) + future = ai_runner.submit_conversation( + focus_group_id, + controller.start_autonomous_conversation(initial_prompt) + ) + current_app.logger.info("Conversation submitted to AI Runner successfully") + except Exception as e: + current_app.logger.error(f"Failed to submit conversation to AI Runner: {e}") + return jsonify({"error": f"Failed to start AI conversation: {str(e)}"}), 500 # Log the AI mode start event try: user_id = get_jwt_identity() # Get user ID if available - mode_event_id = FocusGroup.add_mode_event(focus_group_id, 'ai_mode_started', user_id) + mode_event_id = await FocusGroup.add_mode_event(focus_group_id, 'ai_mode_started', user_id) current_app.logger.info(f"Logged AI mode start event: {mode_event_id}") except Exception as e: current_app.logger.warning(f"Failed to log AI mode start event: {e}") @@ -780,7 +763,8 @@ def start_autonomous_conversation(focus_group_id): "message": "Autonomous conversation started", "focus_group_id": focus_group_id, "state": "starting", - "background": True + "background": True, + "ai_runner_active": True } current_app.logger.info(f"Controller returned result: {result}") @@ -803,7 +787,7 @@ def start_autonomous_conversation(focus_group_id): @focus_group_ai_bp.route('/autonomous/stop/', methods=['POST']) @jwt_required(optional=True) -def stop_autonomous_conversation(focus_group_id): +async def stop_autonomous_conversation(focus_group_id): """ Stop autonomous conversation for a focus group. @@ -816,25 +800,30 @@ def stop_autonomous_conversation(focus_group_id): A JSON object containing the stop result """ try: - data = request.get_json() or {} + data = (await request.get_json()) or {} reason = data.get('reason', 'manual_stop') current_app.logger.info(f"=== STOP AUTONOMOUS CONVERSATION API called for focus group {focus_group_id} ===") current_app.logger.info(f"Stop reason: {reason}") # Create autonomous conversation controller - controller = AutonomousConversationController(focus_group_id, current_app.logger) + # Use AI Runner to stop the conversation + current_app.logger.info("Requesting AI Runner to stop conversation...") + ai_runner = get_ai_runner() - # Signal the running conversation loop to stop gracefully - # No need for asyncio.run() or background task - just set flags - from datetime import datetime - - controller.is_running = False - controller.conversation_state = "completed" + if ai_runner.is_running: + success = ai_runner.stop_conversation(focus_group_id) + if success: + current_app.logger.info(f"Successfully requested stop for focus group {focus_group_id}") + else: + current_app.logger.warning(f"No active conversation found for focus group {focus_group_id}") + else: + current_app.logger.warning("AI Runner is not running, cannot stop conversation") # Update focus group status in database + from datetime import datetime status = 'completed' if reason in ['completed', 'discussion_guide_completed', 'natural_completion'] else 'active' - FocusGroup.update(focus_group_id, { + await FocusGroup.update(focus_group_id, { 'status': status, 'autonomous_ended_at': datetime.utcnow(), 'completion_reason': reason @@ -842,8 +831,13 @@ def stop_autonomous_conversation(focus_group_id): current_app.logger.info(f"Signaled autonomous conversation to stop for focus group {focus_group_id}: {reason}") - # Mode events are now handled by AIModeratorService.end_session_with_concluding_statement() - # to prevent duplicate mode event indicators + # Add mode event for AI session concluded (regardless of reason) + user_id = get_jwt_identity() if get_jwt_identity() else None + mode_event_id = await FocusGroup.add_mode_event(focus_group_id, 'ai_session_concluded', user_id) + if mode_event_id: + current_app.logger.info(f"🎯 Added AI session concluded mode event: {mode_event_id}") + else: + current_app.logger.warning(f"Failed to add AI session concluded mode event for focus group {focus_group_id}") # Return immediately with a success response like start_autonomous_conversation result = { @@ -865,7 +859,7 @@ def stop_autonomous_conversation(focus_group_id): @focus_group_ai_bp.route('/autonomous/status/', methods=['GET']) @jwt_required(optional=True) -def get_autonomous_conversation_status(focus_group_id): +async def get_autonomous_conversation_status(focus_group_id): """ Get the status of autonomous conversation for a focus group. @@ -877,7 +871,7 @@ def get_autonomous_conversation_status(focus_group_id): controller = AutonomousConversationController(focus_group_id, current_app.logger) # Get status - status = controller.get_conversation_status() + status = await controller.get_conversation_status() return jsonify({ "message": "Autonomous conversation status retrieved", @@ -958,7 +952,7 @@ def get_conversation_analytics(focus_group_id): @focus_group_ai_bp.route('/conversation/decision/', methods=['POST']) @jwt_required(optional=True) -def make_conversation_decision(focus_group_id): +async def make_conversation_decision(focus_group_id): """ Make a conversation decision using the LLM decision engine. @@ -972,12 +966,12 @@ def make_conversation_decision(focus_group_id): A JSON object containing the decision """ try: - data = request.get_json() or {} + data = (await request.get_json()) or {} temperature = data.get('temperature', 0.7) mode = data.get('mode', 'ai') # Default to 'ai' mode for backward compatibility # Make decision - decision = ConversationDecisionService.decide_next_action(focus_group_id, temperature, mode) + decision = await ConversationDecisionService.decide_next_action(focus_group_id, temperature, mode) response_data = { "message": "Conversation decision made", @@ -1002,7 +996,7 @@ def make_conversation_decision(focus_group_id): @focus_group_ai_bp.route('/conversation/insights/', methods=['GET']) @jwt_required(optional=True) -def get_conversation_insights(focus_group_id): +async def get_conversation_insights(focus_group_id): """ Get LLM-generated insights about the conversation. @@ -1011,7 +1005,7 @@ def get_conversation_insights(focus_group_id): """ try: # Get insights - insights = ConversationDecisionService.get_conversation_insights(focus_group_id) + insights = await ConversationDecisionService.get_conversation_insights(focus_group_id) return jsonify({ "message": "Conversation insights generated", @@ -1034,7 +1028,7 @@ def get_conversation_insights(focus_group_id): @focus_group_ai_bp.route('/conversation/intervene/', methods=['POST']) @jwt_required(optional=True) -def manual_intervention(focus_group_id): +async def manual_intervention(focus_group_id): """ Manually intervene in autonomous conversation. @@ -1049,7 +1043,7 @@ def manual_intervention(focus_group_id): A JSON object containing the intervention result """ try: - data = request.get_json() or {} + data = (await request.get_json()) or {} action = data.get('action', 'pause') message = data.get('message') participant_id = data.get('participant_id') @@ -1073,7 +1067,7 @@ def manual_intervention(focus_group_id): result = {"message": "Moderator message added"} elif action == 'call_participant' and participant_id: # Add moderator message calling on specific participant - persona = Persona.find_by_id(participant_id) + persona = await Persona.find_by_id(participant_id) if persona: call_message = f"{persona.get('name', 'Participant')}, what are your thoughts on this?" FocusGroup.add_message(focus_group_id, { @@ -1106,7 +1100,7 @@ def manual_intervention(focus_group_id): @focus_group_ai_bp.route('/conversation/reasoning-history/', methods=['GET']) @jwt_required(optional=True) -def get_reasoning_history(focus_group_id): +async def get_reasoning_history(focus_group_id): """ Get the AI reasoning history for an autonomous conversation. @@ -1116,7 +1110,7 @@ def get_reasoning_history(focus_group_id): try: # Create autonomous conversation controller to get reasoning history controller = AutonomousConversationController(focus_group_id) - status = controller.get_conversation_status() + status = await controller.get_conversation_status() reasoning_history = status.get('reasoning_history', []) @@ -1136,7 +1130,7 @@ def get_reasoning_history(focus_group_id): @focus_group_ai_bp.route('/moderator/end-session/', methods=['POST']) @jwt_required(optional=True) -def end_focus_group_session(focus_group_id): +async def end_focus_group_session(focus_group_id): """ End a focus group session with a concluding moderator statement. @@ -1151,7 +1145,7 @@ def end_focus_group_session(focus_group_id): try: current_app.logger.info(f"=== END FOCUS GROUP SESSION API called for focus group {focus_group_id} ===") - data = request.get_json() or {} + data = (await request.get_json()) or {} reason = data.get('reason', 'session_ended') current_app.logger.info(f"Session ending reason: {reason}") @@ -1165,7 +1159,7 @@ def end_focus_group_session(focus_group_id): current_app.logger.info(f"Focus group found: {focus_group.get('name', 'Unnamed')}") # End the session with concluding statement - result = AIModeratorService.end_session_with_concluding_statement(focus_group_id, reason) + result = await AIModeratorService.end_session_with_concluding_statement(focus_group_id, reason) if "error" in result: current_app.logger.error(f"Error ending session: {result['error']}") diff --git a/backend/app/routes/focus_groups.py b/backend/app/routes/focus_groups.py index 42b48560..03af4e6e 100644 --- a/backend/app/routes/focus_groups.py +++ b/backend/app/routes/focus_groups.py @@ -1,5 +1,5 @@ -from flask import Blueprint, request, jsonify, Response, send_file -from flask_jwt_extended import jwt_required, get_jwt_identity +from quart import Blueprint, request, jsonify, Response, send_file +from app.auth.quart_jwt import jwt_required, get_jwt_identity from app.models.focus_group import FocusGroup from app.models.persona import Persona from app.services.focus_group_service import FocusGroupService @@ -250,39 +250,49 @@ except Exception as e: # Request data cache for direct processing request_data_cache = {} -@focus_groups_bp.before_request +# Temporarily disable this before_request handler due to Quart ASGI context issues +# @focus_groups_bp.before_request def cache_multipart_data(): """Cache multipart request data only when temp directories are unavailable.""" - from flask import request, g - - # Only cache for asset upload endpoints when temp directory issues are expected - if (request.endpoint and 'upload_assets' in request.endpoint and - request.method == 'POST' and - request.content_type and 'multipart/form-data' in request.content_type): + try: + from quart import request, g - # Check if temp directory is available - if so, let Flask handle normally - temp_dir = setup_temp_directory() - if temp_dir: - # Temp directory is available, skip caching to allow normal Flask processing + # Safely check if we have an active request context + if not request: return + + # Safely check request properties - handle Quart/Flask differences + endpoint = getattr(request, 'endpoint', None) + method = getattr(request, 'method', None) + content_type = getattr(request, 'content_type', None) - try: - # Only cache when temp directories are unavailable - raw_data = request.stream.read() - if raw_data: - # Store in flask g object for this request - g.cached_request_data = raw_data - # Create a new stream from the cached data - from io import BytesIO - request.stream = BytesIO(raw_data) - except Exception as e: - # If caching fails, continue normally - pass + # Only cache for asset upload endpoints when temp directory issues are expected + if (endpoint and 'upload_assets' in str(endpoint) and + method == 'POST' and + content_type and 'multipart/form-data' in content_type): + + # Check if temp directory is available - if so, let Quart handle normally + temp_dir = setup_temp_directory() + if temp_dir: + # Temp directory is available, skip caching to allow normal processing + return + + # Enable the rest of the caching logic if needed + # For now, just return to prevent context errors + return + else: + # Not an upload endpoint, skip processing + return + + except (RuntimeError, AttributeError, Exception) as e: + # Handle "Working outside of request context" gracefully + # This can happen during startup or shutdown with ASGI + return @focus_groups_bp.route('', methods=['GET']) @focus_groups_bp.route('/', methods=['GET']) @jwt_required(optional=True) # Make JWT optional for development -def get_focus_groups(): +async def get_focus_groups(): import logging logger = logging.getLogger('app.focus_groups') @@ -293,7 +303,7 @@ def get_focus_groups(): # Always return all focus groups for now logger.debug("Calling FocusGroup.get_all() to show all focus groups") - focus_groups = FocusGroup.get_all() + focus_groups = await FocusGroup.get_all() logger.debug(f"Found {len(focus_groups)} total focus groups") # Make focus groups serializable @@ -310,9 +320,9 @@ def get_focus_groups(): @focus_groups_bp.route('/all', methods=['GET']) @jwt_required(optional=True) # Make JWT optional for development -def get_all_focus_groups(): +async def get_all_focus_groups(): try: - focus_groups = FocusGroup.get_all() + focus_groups = await FocusGroup.get_all() # Make focus groups serializable serializable_groups = make_serializable(focus_groups) return jsonify(serializable_groups), 200 @@ -322,9 +332,9 @@ def get_all_focus_groups(): @focus_groups_bp.route('/', methods=['GET']) @jwt_required(optional=True) # Make JWT optional for development -def get_focus_group(focus_group_id): +async def get_focus_group(focus_group_id): try: - focus_group = FocusGroup.find_by_id(focus_group_id) + focus_group = await FocusGroup.find_by_id(focus_group_id) if not focus_group: return jsonify({"message": "Focus group not found"}), 404 @@ -337,7 +347,7 @@ def get_focus_group(focus_group_id): participants_data = [] for persona_id in focus_group['participants']: try: - persona = Persona.find_by_id(persona_id) + persona = await Persona.find_by_id(persona_id) if persona: participants_data.append(persona) except Exception as e: @@ -354,14 +364,14 @@ def get_focus_group(focus_group_id): @focus_groups_bp.route('', methods=['POST']) @focus_groups_bp.route('/', methods=['POST']) @jwt_required(optional=True) # Make JWT optional for development -def create_focus_group(): +async def create_focus_group(): try: user_id = get_jwt_identity() # Use default user ID if not authenticated if not user_id: user_id = 'default_id' - data = request.get_json() + data = await request.get_json() if not data or not data.get('name'): return jsonify({"message": "Missing required fields"}), 400 @@ -378,10 +388,10 @@ def create_focus_group(): if 'participants_count' not in data: data['participants_count'] = len(data['participants']) - focus_group_id = FocusGroup.create(data, user_id) + focus_group_id = await FocusGroup.create(data, user_id) # Get the created focus group to return - focus_group = FocusGroup.find_by_id(focus_group_id) + focus_group = await FocusGroup.find_by_id(focus_group_id) return jsonify({ "message": "Focus group created successfully", @@ -402,7 +412,7 @@ def test_logging_endpoint(focus_group_id): @focus_groups_bp.route('/', methods=['PUT']) @jwt_required() -def update_focus_group(focus_group_id): +async def update_focus_group(focus_group_id): import datetime import os @@ -416,7 +426,7 @@ def update_focus_group(focus_group_id): except: pass # Don't let logging errors break the endpoint - data = request.get_json() + data = await request.get_json() try: log_msg = f"🔧 [{datetime.datetime.now()}] UPDATE DATA: {data}\n" @@ -442,11 +452,11 @@ def update_focus_group(focus_group_id): if not data: return jsonify({"message": "No data provided"}), 400 - focus_group = FocusGroup.find_by_id(focus_group_id) + focus_group = await FocusGroup.find_by_id(focus_group_id) if not focus_group: return jsonify({"message": "Focus group not found"}), 404 - success = FocusGroup.update(focus_group_id, data) + success = await FocusGroup.update(focus_group_id, data) if success: return jsonify({"message": "Focus group updated successfully"}), 200 @@ -455,12 +465,12 @@ def update_focus_group(focus_group_id): @focus_groups_bp.route('/', methods=['DELETE']) @jwt_required() -def delete_focus_group(focus_group_id): - focus_group = FocusGroup.find_by_id(focus_group_id) +async def delete_focus_group(focus_group_id): + focus_group = await FocusGroup.find_by_id(focus_group_id) if not focus_group: return jsonify({"message": "Focus group not found"}), 404 - success = FocusGroup.delete(focus_group_id) + success = await FocusGroup.delete(focus_group_id) if success: return jsonify({"message": "Focus group deleted successfully"}), 200 @@ -469,8 +479,8 @@ def delete_focus_group(focus_group_id): @focus_groups_bp.route('//participants', methods=['POST']) @jwt_required() -def add_participant(focus_group_id): - data = request.get_json() +async def add_participant(focus_group_id): + data = await request.get_json() if not data or not data.get('persona_id'): return jsonify({"message": "Missing persona_id"}), 400 @@ -478,16 +488,16 @@ def add_participant(focus_group_id): persona_id = data.get('persona_id') # Verify focus group exists - focus_group = FocusGroup.find_by_id(focus_group_id) + focus_group = await FocusGroup.find_by_id(focus_group_id) if not focus_group: return jsonify({"message": "Focus group not found"}), 404 # Verify persona exists - persona = Persona.find_by_id(persona_id) + persona = await Persona.find_by_id(persona_id) if not persona: return jsonify({"message": "Persona not found"}), 404 - success = FocusGroup.add_participant(focus_group_id, persona_id) + success = await FocusGroup.add_participant(focus_group_id, persona_id) if success: return jsonify({"message": "Participant added successfully"}), 200 @@ -496,13 +506,13 @@ def add_participant(focus_group_id): @focus_groups_bp.route('//participants/', methods=['DELETE']) @jwt_required() -def remove_participant(focus_group_id, persona_id): +async def remove_participant(focus_group_id, persona_id): # Verify focus group exists - focus_group = FocusGroup.find_by_id(focus_group_id) + focus_group = await FocusGroup.find_by_id(focus_group_id) if not focus_group: return jsonify({"message": "Focus group not found"}), 404 - success = FocusGroup.remove_participant(focus_group_id, persona_id) + success = await FocusGroup.remove_participant(focus_group_id, persona_id) if success: return jsonify({"message": "Participant removed successfully"}), 200 @@ -511,17 +521,17 @@ def remove_participant(focus_group_id, persona_id): @focus_groups_bp.route('//messages', methods=['GET']) @jwt_required(optional=True) # Make JWT optional for development -def get_focus_group_messages(focus_group_id): +async def get_focus_group_messages(focus_group_id): """Get all messages for a focus group, including mode events.""" try: # Verify focus group exists - focus_group = FocusGroup.find_by_id(focus_group_id) + focus_group = await FocusGroup.find_by_id(focus_group_id) if not focus_group: return jsonify({"message": "Focus group not found"}), 404 # Get messages and mode events - messages = FocusGroup.get_messages(focus_group_id) - mode_events = FocusGroup.get_mode_events(focus_group_id) + messages = await FocusGroup.get_messages(focus_group_id) + mode_events = await FocusGroup.get_mode_events(focus_group_id) # Make messages and events serializable and convert field names for frontend compatibility serializable_messages = make_serializable(messages) @@ -544,17 +554,17 @@ def get_focus_group_messages(focus_group_id): @focus_groups_bp.route('//messages', methods=['POST']) @jwt_required(optional=True) # Make JWT optional for development -def add_focus_group_message(focus_group_id): +async def add_focus_group_message(focus_group_id): """Add a new message to a focus group.""" try: # Get message data from request - data = request.get_json() + data = await request.get_json() if not data or not data.get('text'): return jsonify({"message": "Missing required fields"}), 400 # Verify focus group exists - focus_group = FocusGroup.find_by_id(focus_group_id) + focus_group = await FocusGroup.find_by_id(focus_group_id) if not focus_group: return jsonify({"message": "Focus group not found"}), 404 @@ -577,7 +587,7 @@ def add_focus_group_message(focus_group_id): # Activate visual assets in the focus group for LLM context try: - success = FocusGroup._activate_visual_assets(focus_group_id, [filename], None) + success = await FocusGroup._activate_visual_assets(focus_group_id, [filename], None) if success: print(f"✅ VISUAL CONTEXT ACTIVATED: {filename} ({visual_asset.get('displayReference')})") else: @@ -604,7 +614,7 @@ def add_focus_group_message(focus_group_id): # Activate visual assets in the focus group for LLM context try: - success = FocusGroup._activate_visual_assets(focus_group_id, [asset_filename], None) + success = await FocusGroup._activate_visual_assets(focus_group_id, [asset_filename], None) if success: print(f"✅ VISUAL CONTEXT ACTIVATED: {asset_filename}") else: @@ -623,7 +633,7 @@ def add_focus_group_message(focus_group_id): print(f" - Activates visual context: {data.get('activates_visual_context', False)}") # Add message - message_id = FocusGroup.add_message(focus_group_id, data) + message_id = await FocusGroup.add_message(focus_group_id, data) if not message_id: return jsonify({"message": "Failed to add message"}), 500 @@ -638,22 +648,22 @@ def add_focus_group_message(focus_group_id): @focus_groups_bp.route('//messages/', methods=['PATCH']) @jwt_required(optional=True) # Make JWT optional for development -def update_focus_group_message(focus_group_id, message_id): +async def update_focus_group_message(focus_group_id, message_id): """Update a message in a focus group, currently only for highlighted status.""" try: # Get message data from request - data = request.get_json() + data = await request.get_json() if data is None or 'highlighted' not in data: return jsonify({"message": "Missing highlighted field"}), 400 # Verify focus group exists - focus_group = FocusGroup.find_by_id(focus_group_id) + focus_group = await FocusGroup.find_by_id(focus_group_id) if not focus_group: return jsonify({"message": "Focus group not found"}), 404 # Update message highlight status - success = FocusGroup.update_message_highlight( + success = await FocusGroup.update_message_highlight( focus_group_id, message_id, data['highlighted'] @@ -671,16 +681,16 @@ def update_focus_group_message(focus_group_id, message_id): @focus_groups_bp.route('//notes', methods=['GET']) @jwt_required(optional=True) # Make JWT optional for development -def get_focus_group_notes(focus_group_id): +async def get_focus_group_notes(focus_group_id): """Get all notes for a focus group.""" try: # Verify focus group exists - focus_group = FocusGroup.find_by_id(focus_group_id) + focus_group = await FocusGroup.find_by_id(focus_group_id) if not focus_group: return jsonify({"message": "Focus group not found"}), 404 # Get notes - notes = FocusGroup.get_notes(focus_group_id) + notes = await FocusGroup.get_notes(focus_group_id) # Make notes serializable serializable_notes = make_serializable(notes) @@ -691,28 +701,28 @@ def get_focus_group_notes(focus_group_id): @focus_groups_bp.route('//notes', methods=['POST']) @jwt_required(optional=True) # Make JWT optional for development -def add_focus_group_note(focus_group_id): +async def add_focus_group_note(focus_group_id): """Add a new note to a focus group.""" try: # Get note data from request - data = request.get_json() + data = await request.get_json() if not data or not data.get('content'): return jsonify({"message": "Missing required fields"}), 400 # Verify focus group exists - focus_group = FocusGroup.find_by_id(focus_group_id) + focus_group = await FocusGroup.find_by_id(focus_group_id) if not focus_group: return jsonify({"message": "Focus group not found"}), 404 # Add note - note_id = FocusGroup.add_note(focus_group_id, data) + note_id = await FocusGroup.add_note(focus_group_id, data) if not note_id: return jsonify({"message": "Failed to add note"}), 500 # Get the created note to return - notes = FocusGroup.get_notes(focus_group_id) + notes = await FocusGroup.get_notes(focus_group_id) created_note = None for note in notes: if str(note.get('_id', '')) == str(note_id): @@ -730,16 +740,16 @@ def add_focus_group_note(focus_group_id): @focus_groups_bp.route('//notes/', methods=['DELETE']) @jwt_required(optional=True) # Make JWT optional for development -def delete_focus_group_note(focus_group_id, note_id): +async def delete_focus_group_note(focus_group_id, note_id): """Delete a note from a focus group.""" try: # Verify focus group exists - focus_group = FocusGroup.find_by_id(focus_group_id) + focus_group = await FocusGroup.find_by_id(focus_group_id) if not focus_group: return jsonify({"message": "Focus group not found"}), 404 # Delete note - success = FocusGroup.delete_note(focus_group_id, note_id) + success = await FocusGroup.delete_note(focus_group_id, note_id) if not success: return jsonify({"message": "Failed to delete note"}), 500 @@ -754,7 +764,7 @@ def delete_focus_group_note(focus_group_id, note_id): @focus_groups_bp.route('/generate-discussion-guide', methods=['POST']) @focus_groups_bp.route('//generate-discussion-guide', methods=['POST']) @jwt_required(optional=True) # Make JWT optional for development -def generate_discussion_guide(focus_group_id=None): +async def generate_discussion_guide(focus_group_id=None): """Generate a discussion guide for a focus group using the LLM service.""" import logging logger = logging.getLogger(__name__) @@ -764,7 +774,7 @@ def generate_discussion_guide(focus_group_id=None): try: # Get request data - data = request.get_json() + data = await request.get_json() if not data: logger.warning("Discussion guide generation failed: Missing request data") @@ -814,7 +824,7 @@ def generate_discussion_guide(focus_group_id=None): llm_model = None if focus_group_id: try: - focus_group = FocusGroup.find_by_id(focus_group_id) + focus_group = await FocusGroup.find_by_id(focus_group_id) if focus_group: llm_model = focus_group.get('llm_model') logger.info(f"Using LLM model for focus group {focus_group_id}: {llm_model}") @@ -826,7 +836,7 @@ def generate_discussion_guide(focus_group_id=None): llm_model = data.get('llm_model') # Generate the discussion guide - discussion_guide = FocusGroupService.generate_discussion_guide( + discussion_guide = await FocusGroupService.generate_discussion_guide( focus_group_name=focus_group_name, research_brief=research_brief, discussion_topics=formatted_topic, @@ -1038,7 +1048,7 @@ def generate_discussion_guide_filename(focus_group_name=None, guide_title=None): @focus_groups_bp.route('//discussion-guide/download', methods=['GET']) @jwt_required(optional=True) # Make JWT optional for development -def download_discussion_guide(focus_group_id): +async def download_discussion_guide(focus_group_id): """ Download the discussion guide for a focus group as a markdown file. @@ -1052,7 +1062,7 @@ def download_discussion_guide(focus_group_id): logger.debug(f"=== DOWNLOAD DISCUSSION GUIDE API called for focus group {focus_group_id} ===") # Verify focus group exists - focus_group = FocusGroup.find_by_id(focus_group_id) + focus_group = await FocusGroup.find_by_id(focus_group_id) if not focus_group: logger.warning(f"Focus group not found: {focus_group_id}") return jsonify({"error": "Focus group not found"}), 404 @@ -1104,7 +1114,8 @@ def download_discussion_guide(focus_group_id): # Additional asset upload utility functions def get_upload_folder(focus_group_id): """Get the upload folder path for a focus group.""" - base_dir = os.path.dirname(os.path.dirname(os.path.dirname(__file__))) # Go up to backend/ + # Use absolute path to avoid working directory issues + base_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..')) # Go up to backend/ upload_dir = os.path.join(base_dir, 'uploads', f'focus-group-{focus_group_id}') return upload_dir @@ -1189,7 +1200,7 @@ def save_uploaded_file_directly(file, file_path): @focus_groups_bp.route('//assets', methods=['POST']) @jwt_required(optional=True) # Make JWT optional for development -def upload_assets(focus_group_id): +async def upload_assets(focus_group_id): """Upload creative assets for a focus group.""" import logging logger = logging.getLogger('app.focus_groups') @@ -1197,8 +1208,9 @@ def upload_assets(focus_group_id): try: logger.debug(f"=== UPLOAD ASSETS API called for focus group {focus_group_id} ===") - # Check for replace flag - replace_existing = request.form.get('replace', '').lower() == 'true' + # Check for replace flag (Quart async form access) + form_data = await request.form + replace_existing = form_data.get('replace', '').lower() == 'true' logger.info(f"Replace existing assets flag: {replace_existing}") # Set up temporary directory for file processing (optional) @@ -1209,7 +1221,7 @@ def upload_assets(focus_group_id): logger.info("No temp directory available, processing files directly") # Verify focus group exists - focus_group = FocusGroup.find_by_id(focus_group_id) + focus_group = await FocusGroup.find_by_id(focus_group_id) if not focus_group: logger.warning(f"Focus group not found: {focus_group_id}") return jsonify({"error": "Focus group not found"}), 404 @@ -1256,7 +1268,7 @@ def upload_assets(focus_group_id): logger.warning(f"Could not find or delete existing asset file: {filename}") # Clear assets from database - success = FocusGroup.clear_uploaded_assets(focus_group_id) + success = await FocusGroup.clear_uploaded_assets(focus_group_id) if success: logger.info("Successfully cleared existing assets from database") else: @@ -1271,14 +1283,17 @@ def upload_assets(focus_group_id): logger.info(f"Request content type: {request.content_type}") logger.info(f"Request content length: {request.content_length}") logger.info(f"Request method: {request.method}") - logger.info(f"Request files keys: {list(request.files.keys()) if hasattr(request, 'files') else 'No files attribute'}") + + # Get files using Quart async pattern + files_data = await request.files + logger.info(f"Request files keys: {list(files_data.keys())}") try: - if 'assets' not in request.files: - logger.warning(f"No 'assets' key in request.files. Available keys: {list(request.files.keys())}") + if 'assets' not in files_data: + logger.warning(f"No 'assets' key in request.files. Available keys: {list(files_data.keys())}") return jsonify({"error": "No files provided"}), 400 - files = request.files.getlist('assets') + files = files_data.getlist('assets') if not files or all(f.filename == '' for f in files): logger.warning("No files selected") return jsonify({"error": "No files selected"}), 400 @@ -1338,9 +1353,9 @@ def upload_assets(focus_group_id): # Try direct save first if not save_uploaded_file_directly(file, file_path): - # Fallback to standard save method + # Fallback to standard save method (Quart async version) try: - file.save(file_path) + await file.save(file_path) except Exception as save_error: logger.error(f"Both direct and standard file save failed: {save_error}") errors.append(f"{file.filename}: Save failed - {str(save_error)}") @@ -1378,7 +1393,7 @@ def upload_assets(focus_group_id): logger.info(f"Updating focus group {focus_group_id} with {len(uploaded_assets)} assets") logger.info(f"Asset metadata to save: {uploaded_assets}") - success = FocusGroup.add_uploaded_assets(focus_group_id, uploaded_assets) + success = await FocusGroup.add_uploaded_assets(focus_group_id, uploaded_assets) logger.info(f"Database update success: {success}") if not success: @@ -1396,7 +1411,7 @@ def upload_assets(focus_group_id): # DEBUG: Verify the data was saved by reading it back try: - verification_assets = FocusGroup.get_uploaded_assets(focus_group_id) + verification_assets = await FocusGroup.get_uploaded_assets(focus_group_id) logger.info(f"Verification: Found {len(verification_assets)} assets after save") logger.info(f"Verification asset data: {verification_assets}") except Exception as verify_error: @@ -1433,11 +1448,11 @@ def upload_assets(focus_group_id): @focus_groups_bp.route('//assets', methods=['GET']) @jwt_required(optional=True) # Make JWT optional for development -def get_assets(focus_group_id): +async def get_assets(focus_group_id): """Get list of uploaded assets for a focus group.""" try: # Verify focus group exists - focus_group = FocusGroup.find_by_id(focus_group_id) + focus_group = await FocusGroup.find_by_id(focus_group_id) if not focus_group: return jsonify({"error": "Focus group not found"}), 404 @@ -1468,11 +1483,11 @@ def get_assets(focus_group_id): @focus_groups_bp.route('//assets/', methods=['GET']) @jwt_required(optional=True) # Make JWT optional for development -def serve_asset(focus_group_id, filename): +async def serve_asset(focus_group_id, filename): """Serve an uploaded asset file.""" try: # Verify focus group exists - focus_group = FocusGroup.find_by_id(focus_group_id) + focus_group = await FocusGroup.find_by_id(focus_group_id) if not focus_group: return jsonify({"error": "Focus group not found"}), 404 @@ -1493,7 +1508,7 @@ def serve_asset(focus_group_id, filename): file_path = subdirectory_path else: # Try flat storage location (main uploads directory) - base_dir = os.path.dirname(os.path.dirname(os.path.dirname(__file__))) + base_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..')) main_upload_dir = os.path.join(base_dir, 'uploads') flat_path = os.path.join(main_upload_dir, filename) if os.path.isfile(flat_path): @@ -1503,12 +1518,12 @@ def serve_asset(focus_group_id, filename): if not file_path or not os.path.exists(file_path): return jsonify({"error": "Asset file not found on disk"}), 404 - # Serve the file - return send_file( + # Serve the file (Quart uses attachment_filename instead of download_name) + return await send_file( file_path, mimetype=asset.get('mime_type', 'image/jpeg'), as_attachment=False, - download_name=asset.get('original_name', filename) + attachment_filename=asset.get('original_name', filename) ) except Exception as e: @@ -1517,16 +1532,16 @@ def serve_asset(focus_group_id, filename): @focus_groups_bp.route('//assets/', methods=['DELETE']) @jwt_required(optional=True) # Make JWT optional for development -def delete_asset(focus_group_id, filename): +async def delete_asset(focus_group_id, filename): """Delete an uploaded asset.""" try: # Verify focus group exists - focus_group = FocusGroup.find_by_id(focus_group_id) + focus_group = await FocusGroup.find_by_id(focus_group_id) if not focus_group: return jsonify({"error": "Focus group not found"}), 404 # Remove asset from focus group metadata - success = FocusGroup.remove_uploaded_asset(focus_group_id, filename) + success = await FocusGroup.remove_uploaded_asset(focus_group_id, filename) if not success: return jsonify({"error": "Failed to update focus group metadata"}), 500 @@ -1557,16 +1572,16 @@ def delete_asset(focus_group_id, filename): @focus_groups_bp.route('//assets/', methods=['PATCH']) @jwt_required(optional=True) # Make JWT optional for development -def update_asset_name(focus_group_id, filename): +async def update_asset_name(focus_group_id, filename): """Update the user assigned name for an uploaded asset.""" try: # Verify focus group exists - focus_group = FocusGroup.find_by_id(focus_group_id) + focus_group = await FocusGroup.find_by_id(focus_group_id) if not focus_group: return jsonify({"error": "Focus group not found"}), 404 # Get request data - data = request.get_json() + data = await request.get_json() if not data or 'user_assigned_name' not in data: return jsonify({"error": "Missing user_assigned_name field"}), 400 @@ -1579,7 +1594,7 @@ def update_asset_name(focus_group_id, filename): return jsonify({"error": "Asset not found"}), 404 # Update the asset name - success = FocusGroup.update_asset_name(focus_group_id, filename, user_assigned_name) + success = await FocusGroup.update_asset_name(focus_group_id, filename, user_assigned_name) if not success: return jsonify({"error": "Failed to update asset name"}), 500 @@ -1624,13 +1639,13 @@ def test_websocket_emission(focus_group_id): @focus_groups_bp.route('//describe-asset', methods=['POST']) @jwt_required(optional=True) # Make JWT optional for development -def describe_asset(focus_group_id): +async def describe_asset(focus_group_id): """Generate AI description of an asset for enhanced creative review questions.""" print(f"🔍 API ENDPOINT: describe-asset called for focus group {focus_group_id}") try: # Verify focus group exists print(f"🔍 API: Looking up focus group {focus_group_id}") - focus_group = FocusGroup.find_by_id(focus_group_id) + focus_group = await FocusGroup.find_by_id(focus_group_id) if not focus_group: print(f"❌ API: Focus group {focus_group_id} not found") return jsonify({"error": "Focus group not found"}), 404 @@ -1638,7 +1653,7 @@ def describe_asset(focus_group_id): print(f"✅ API: Focus group {focus_group_id} found") # Get asset filename from request - data = request.get_json() + data = await request.get_json() print(f"🔍 API: Request data: {data}") if not data or 'asset_filename' not in data: print(f"❌ API: Missing asset_filename in request") @@ -1651,7 +1666,7 @@ def describe_asset(focus_group_id): # Generate AI description try: - description = ImageDescriptionService.generate_description(focus_group_id, asset_filename) + description = await ImageDescriptionService.generate_description(focus_group_id, asset_filename) return jsonify({ "message": "Asset description generated successfully", diff --git a/backend/app/routes/folders.py b/backend/app/routes/folders.py index 0355d83f..b7f41f18 100644 --- a/backend/app/routes/folders.py +++ b/backend/app/routes/folders.py @@ -1,5 +1,5 @@ -from flask import Blueprint, request, jsonify -from flask_jwt_extended import jwt_required, get_jwt_identity +from quart import Blueprint, request, jsonify +from app.auth.quart_jwt import jwt_required, get_jwt_identity from app.models.folder import Folder from bson import ObjectId import datetime @@ -22,11 +22,11 @@ folders_bp = Blueprint('folders', __name__) @folders_bp.route('', methods=['GET']) @folders_bp.route('/', methods=['GET']) @jwt_required(optional=True) # Make JWT optional for development -def get_folders(): +async def get_folders(): """Get all folders - shared across all users.""" try: # Always return all folders - this is a shared persona system - folders = Folder.get_all() + folders = await Folder.get_all() # Make folders serializable serializable_folders = make_serializable(folders) @@ -37,10 +37,10 @@ def get_folders(): @folders_bp.route('/', methods=['GET']) @jwt_required(optional=True) # Make JWT optional for development -def get_folder(folder_id): +async def get_folder(folder_id): """Get a specific folder by ID.""" try: - folder = Folder.find_by_id(folder_id) + folder = await Folder.find_by_id(folder_id) if not folder: return jsonify({"message": "Folder not found"}), 404 @@ -54,10 +54,10 @@ def get_folder(folder_id): @folders_bp.route('', methods=['POST']) @folders_bp.route('/', methods=['POST']) @jwt_required() -def create_folder(): +async def create_folder(): """Create a new folder.""" user_id = get_jwt_identity() - data = request.get_json() + data = await request.get_json() if not data: return jsonify({"message": "No data provided"}), 400 @@ -65,7 +65,7 @@ def create_folder(): if not data.get('name'): return jsonify({"message": "Folder name is required"}), 400 - folder_id = Folder.create(data, user_id) + folder_id = await Folder.create(data, user_id) return jsonify({ "message": "Folder created successfully", @@ -74,15 +74,15 @@ def create_folder(): @folders_bp.route('/', methods=['PUT']) @jwt_required() -def update_folder(folder_id): +async def update_folder(folder_id): """Update a folder.""" try: - data = request.get_json() + data = await request.get_json() if not data: return jsonify({"message": "No data provided"}), 400 - folder = Folder.find_by_id(folder_id) + folder = await Folder.find_by_id(folder_id) if not folder: return jsonify({"message": "Folder not found"}), 404 @@ -96,11 +96,11 @@ def update_folder(folder_id): if 'id' in data: del data['id'] - success = Folder.update(folder_id, data) + success = await Folder.update(folder_id, data) if success: # Get the updated folder and return it - updated_folder = Folder.find_by_id(folder_id) + updated_folder = await Folder.find_by_id(folder_id) return jsonify({ "message": "Folder updated successfully", "folder": make_serializable(updated_folder) @@ -113,9 +113,9 @@ def update_folder(folder_id): @folders_bp.route('/', methods=['DELETE']) @jwt_required() -def delete_folder(folder_id): +async def delete_folder(folder_id): """Delete a folder.""" - folder = Folder.find_by_id(folder_id) + folder = await Folder.find_by_id(folder_id) if not folder: return jsonify({"message": "Folder not found"}), 404 @@ -124,7 +124,7 @@ def delete_folder(folder_id): if folder.get('created_by') != user_id: return jsonify({"message": "Unauthorized"}), 403 - success = Folder.delete(folder_id) + success = await Folder.delete(folder_id) if success: return jsonify({"message": "Folder deleted successfully"}), 200 @@ -133,22 +133,22 @@ def delete_folder(folder_id): @folders_bp.route('//personas', methods=['POST']) @jwt_required() -def add_persona_to_folder(folder_id): +async def add_persona_to_folder(folder_id): """Add a persona to a folder (supports multiple folders per persona).""" try: - data = request.get_json() + data = await request.get_json() if not data or not data.get('persona_id'): return jsonify({"message": "Persona ID is required"}), 400 - folder = Folder.find_by_id(folder_id) + folder = await Folder.find_by_id(folder_id) if not folder: return jsonify({"message": "Folder not found"}), 404 # Folder operations are shared across all users in this system persona_id = data['persona_id'] - success = Folder.add_persona(folder_id, persona_id) + success = await Folder.add_persona(folder_id, persona_id) if success: return jsonify({"message": "Persona added to folder successfully"}), 200 @@ -160,16 +160,16 @@ def add_persona_to_folder(folder_id): @folders_bp.route('//personas/', methods=['DELETE']) @jwt_required() -def remove_persona_from_folder(folder_id, persona_id): +async def remove_persona_from_folder(folder_id, persona_id): """Remove a persona from a folder (persona can remain in other folders).""" try: - folder = Folder.find_by_id(folder_id) + folder = await Folder.find_by_id(folder_id) if not folder: return jsonify({"message": "Folder not found"}), 404 # Folder operations are shared across all users in this system - success = Folder.remove_persona(folder_id, persona_id) + success = await Folder.remove_persona(folder_id, persona_id) if success: return jsonify({"message": "Persona removed from folder successfully"}), 200 @@ -181,15 +181,15 @@ def remove_persona_from_folder(folder_id, persona_id): @folders_bp.route('//personas/batch', methods=['POST']) @jwt_required() -def add_personas_to_folder_batch(folder_id): +async def add_personas_to_folder_batch(folder_id): """Add multiple personas to a folder (personas can be in multiple folders).""" try: - data = request.get_json() + data = await request.get_json() if not data or not data.get('persona_ids'): return jsonify({"message": "Persona IDs are required"}), 400 - folder = Folder.find_by_id(folder_id) + folder = await Folder.find_by_id(folder_id) if not folder: return jsonify({"message": "Folder not found"}), 404 @@ -199,7 +199,7 @@ def add_personas_to_folder_batch(folder_id): if not isinstance(persona_ids, list): return jsonify({"message": "persona_ids must be a list"}), 400 - success = Folder.add_personas_batch(folder_id, persona_ids) + success = await Folder.add_personas_batch(folder_id, persona_ids) if success: return jsonify({"message": f"Successfully added {len(persona_ids)} personas to folder"}), 200 @@ -211,11 +211,11 @@ def add_personas_to_folder_batch(folder_id): @folders_bp.route('//personas/remove-batch', methods=['POST']) @jwt_required() -def remove_personas_from_folder_batch(folder_id): +async def remove_personas_from_folder_batch(folder_id): """Remove multiple personas from a folder (personas remain in other folders).""" print(f"🌐 BACKEND: POST /folders/{folder_id}/personas/remove-batch endpoint hit") try: - data = request.get_json() + data = await request.get_json() print(f"🌐 BACKEND: Raw request data: {data}") print(f"🌐 BACKEND: Request content type: {request.content_type}") print(f"🌐 BACKEND: Request method: {request.method}") @@ -224,7 +224,7 @@ def remove_personas_from_folder_batch(folder_id): print(f"❌ BACKEND: Missing persona_ids in data: {data}") return jsonify({"message": "Persona IDs are required"}), 400 - folder = Folder.find_by_id(folder_id) + folder = await Folder.find_by_id(folder_id) if not folder: return jsonify({"message": "Folder not found"}), 404 @@ -234,7 +234,7 @@ def remove_personas_from_folder_batch(folder_id): if not isinstance(persona_ids, list): return jsonify({"message": "persona_ids must be a list"}), 400 - success = Folder.remove_personas_batch(folder_id, persona_ids) + success = await Folder.remove_personas_batch(folder_id, persona_ids) if success: return jsonify({"message": f"Successfully removed {len(persona_ids)} personas from folder"}), 200 diff --git a/backend/app/routes/personas.py b/backend/app/routes/personas.py index dd3a4545..4bbae4a7 100644 --- a/backend/app/routes/personas.py +++ b/backend/app/routes/personas.py @@ -1,5 +1,5 @@ -from flask import Blueprint, request, jsonify -from flask_jwt_extended import jwt_required, get_jwt_identity +from quart import Blueprint, request, jsonify +from app.auth.quart_jwt import jwt_required, get_jwt_identity from app.models.persona import Persona from app.services.persona_export_service import PersonaExportService from app.services.persona_modification_service import PersonaModificationService, PersonaModificationError @@ -24,15 +24,15 @@ personas_bp = Blueprint('personas', __name__) @personas_bp.route('', methods=['GET']) @personas_bp.route('/', methods=['GET']) @jwt_required(optional=True) # Make JWT optional for development -def get_personas(): +async def get_personas(): try: user_id = get_jwt_identity() if user_id: # If authenticated, get user's personas - personas = Persona.find_by_user(user_id) + personas = await Persona.find_by_user(user_id) else: # For development, return all personas if not authenticated - personas = Persona.get_all() + personas = await Persona.get_all() # Make personas serializable serializable_personas = make_serializable(personas) @@ -43,9 +43,9 @@ def get_personas(): @personas_bp.route('/all', methods=['GET']) @jwt_required(optional=True) # Make JWT optional for development -def get_all_personas(): +async def get_all_personas(): try: - personas = Persona.get_all() + personas = await Persona.get_all() # Make personas serializable serializable_personas = make_serializable(personas) return jsonify(serializable_personas), 200 @@ -55,9 +55,9 @@ def get_all_personas(): @personas_bp.route('/', methods=['GET']) @jwt_required(optional=True) # Make JWT optional for development -def get_persona(persona_id): +async def get_persona(persona_id): try: - persona = Persona.find_by_id(persona_id) + persona = await Persona.find_by_id(persona_id) if not persona: return jsonify({"message": "Persona not found"}), 404 @@ -71,14 +71,14 @@ def get_persona(persona_id): @personas_bp.route('', methods=['POST']) @personas_bp.route('/', methods=['POST']) @jwt_required() -def create_persona(): +async def create_persona(): user_id = get_jwt_identity() - data = request.get_json() + data = await request.get_json() if not data: return jsonify({"message": "No data provided"}), 400 - persona_id = Persona.create(data, user_id) + persona_id = await Persona.create(data, user_id) return jsonify({ "message": "Persona created successfully", @@ -87,14 +87,14 @@ def create_persona(): @personas_bp.route('/', methods=['PUT']) @jwt_required() -def update_persona(persona_id): +async def update_persona(persona_id): try: - data = request.get_json() + data = await request.get_json() if not data: return jsonify({"message": "No data provided"}), 400 - persona = Persona.find_by_id(persona_id) + persona = await Persona.find_by_id(persona_id) if not persona: return jsonify({"message": "Persona not found"}), 404 @@ -106,11 +106,11 @@ def update_persona(persona_id): if 'id' in data: del data['id'] - success = Persona.update(persona_id, data) + success = await Persona.update(persona_id, data) if success: # Get the updated persona and return it - updated_persona = Persona.find_by_id(persona_id) + updated_persona = await Persona.find_by_id(persona_id) return jsonify({ "message": "Persona updated successfully", "persona": make_serializable(updated_persona) @@ -123,12 +123,12 @@ def update_persona(persona_id): @personas_bp.route('/', methods=['DELETE']) @jwt_required() -def delete_persona(persona_id): - persona = Persona.find_by_id(persona_id) +async def delete_persona(persona_id): + persona = await Persona.find_by_id(persona_id) if not persona: return jsonify({"message": "Persona not found"}), 404 - success = Persona.delete(persona_id) + success = await Persona.delete(persona_id) if success: return jsonify({"message": "Persona deleted successfully"}), 200 @@ -137,16 +137,16 @@ def delete_persona(persona_id): @personas_bp.route('/batch', methods=['POST']) @jwt_required() -def create_multiple_personas(): +async def create_multiple_personas(): user_id = get_jwt_identity() - data = request.get_json() + data = await request.get_json() if not data or not isinstance(data, list): return jsonify({"message": "Invalid data format. Expected list of personas"}), 400 persona_ids = [] for persona_data in data: - persona_id = Persona.create(persona_data, user_id) + persona_id = await Persona.create(persona_data, user_id) persona_ids.append(persona_id) return jsonify({ @@ -156,7 +156,7 @@ def create_multiple_personas(): @personas_bp.route('//modify-with-ai', methods=['POST']) @jwt_required(optional=True) # Make JWT optional for development -def modify_persona_with_ai(persona_id): +async def modify_persona_with_ai(persona_id): """ Modify a persona using AI based on natural language instructions. @@ -168,7 +168,7 @@ def modify_persona_with_ai(persona_id): """ try: # Get request data - request_data = request.get_json() + request_data = await request.get_json() if not request_data: return jsonify({"error": "No request data provided"}), 400 @@ -184,7 +184,7 @@ def modify_persona_with_ai(persona_id): print(f"📝 Modification prompt: {modification_prompt[:100]}...") # Call the modification service - modified_persona_data = PersonaModificationService.modify_persona( + modified_persona_data = await PersonaModificationService.modify_persona( persona_id=persona_id, modification_prompt=modification_prompt, llm_model=llm_model, @@ -207,7 +207,7 @@ def modify_persona_with_ai(persona_id): @personas_bp.route('//export-profile', methods=['POST']) @jwt_required(optional=True) # Make JWT optional for development -def export_persona_profile(persona_id): +async def export_persona_profile(persona_id): """ Export a persona profile as beautifully formatted markdown. @@ -217,12 +217,12 @@ def export_persona_profile(persona_id): """ try: # Get the persona data - persona = Persona.find_by_id(persona_id) + persona = await Persona.find_by_id(persona_id) if not persona: return jsonify({"error": "Persona not found"}), 404 # Get optional parameters from request - request_data = request.get_json() or {} + request_data = await request.get_json() or {} llm_model = request_data.get('llm_model', 'gpt-4.1') temperature = request_data.get('temperature', 0.3) @@ -235,7 +235,7 @@ def export_persona_profile(persona_id): print(f"🤖 Backend: Exporting profile for persona {persona_data.get('name', persona_id)} using {llm_model}") # Generate the markdown profile - result = export_service.generate_profile_markdown( + result = await export_service.generate_profile_markdown( persona_data=persona_data, llm_model=llm_model, temperature=temperature diff --git a/backend/app/services/__pycache__/ai_moderator_service.cpython-313.pyc b/backend/app/services/__pycache__/ai_moderator_service.cpython-313.pyc index f83a2751e06b1219b9d45bed077262a63c8b6015..a5ff9436adc813d985505ad4bc7ae54a76284752 100644 GIT binary patch delta 9307 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 diff --git a/backend/app/services/__pycache__/ai_persona_service.cpython-313.pyc b/backend/app/services/__pycache__/ai_persona_service.cpython-313.pyc index c99259ee3bbd1450bd2e26bfde19755182ecb2ed..3adfedcfd8c721dc0074b8bc227430654fbd379f 100644 GIT binary patch delta 1579 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 delta 10629 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 diff --git a/backend/app/services/__pycache__/conversation_context_service.cpython-313.pyc b/backend/app/services/__pycache__/conversation_context_service.cpython-313.pyc index 108daf1d2924a5a5734856a999d1ed795ffaede5..ca1c6f49cfaf57c1e538e4f194a0b6ec1da26465 100644 GIT binary patch delta 2459 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 diff --git a/backend/app/services/__pycache__/conversation_decision_service.cpython-313.pyc b/backend/app/services/__pycache__/conversation_decision_service.cpython-313.pyc index 328a088f1aa1895595e8c7ed7deeb1147348010c..7e89945ef87dc131fe0aaa28c363310b19d9e798 100644 GIT binary patch delta 3045 zcmbW3TWlN06^3UxyCip|B_&?85=C-JiMp8V&HwpGYZSj)1cTB2{dB&F@x zjT84FiE0!H(4r3mBS~wuMU#i3NL{Cd5kDktmH})=2HK_7genl!Lf{tzMPWGxY!m_7 zGfPRPC8998z|Q21EPlV1_>JT#shi`Sa3go=?@Mb+ zyd7EZz%B1U+P3IDm@PXdJQceIlxoGh&0MOj-w%W94h;F%owZDN8M+%o==B}qZbWWa+6H>ajUFC! z8gVe6He-lP3*z1`Ow!KC9!x&R(q3q7kv+ZS^IjhG0wxf;z_|v)WT8B28>}TaYk3&l z3=_Co(&auW)_(FNK1YN>W8#iAMO5@q2X0|Xb8QO-KE)i9pXdYA4OEj;h zadkzLwF$MaUK`>C1GgCALnv0XwkGt&5n=#;hQ{lh7I>%z;fZQSm_$iFVVbP|5&*vy zO3+84tIAFI+Xmorw{WNc;2Slk)&W-mU^m@Y8ysGTEHlUogY4txJ3whptjbt(S()}4 z+J~7|MAZdgP0=vX!V=S+vT7$OI}bYLB9KLYU0as(Y~-pxh35N>!bTRxj5Wl*0l9o08 zLbMU1b&%~yf?Gn>*0-xOCYN--fNrjBKmwwm1|RdfKp3eX=xNlD@nk& z3<~MxmU^}u%K!AyFIws)b2}``j(zNdB(`p4JEQz4L_1nris4K4Q#gm=4AAM;elV*L z6`m-n*IMu3q2#5uJEr6t+k;s78Wzo!)fyp=?`+!0eB9Z%<7qZBcYJkOU*|1f=aO8r zu+a5h|pppvO*HwSy+cV?vC+2X!2VbaA zo6$U0!`v@BL1|@JZR9k0JLr@Rt4&`R&B zXK12(u4}q_a1I{|hTazeE54 delta 2338 zcma)-TTB#J7{||!93raMxu)7g#}dVQ-2GWI#nqgO_EIO9i=z;$@S1V#_9u!vH2ADw0Wsbp&tLLD$2HpuKA zCd|?;#vSys(T_PAHG1e@#@8`7bj2iN%5zSWF~X#vu!)v%#U}Hj&aocGVNNzlrXJjb z%83*8;9S&NNZ0Xps&M;ZtqlGgw6F0;p#6xVuOwi?JRgXrVb zJi%HrX_dyUQq(hH-A6rX+Xb6z(&mlXyf;!NY?ZV>&7N-0o3w||El+*pd zH1vL&4IAcW!pCfPD1*0Q*AC%kabrMQmmz*ht1U`HTmeXvV3Uo z=I}Ke$lby~O*R?HWKHyAzu&{24|{a$1V&FLPK9=c5(3=n1f?nEpgEjH+MlM5fa8M1cVv=GS(pkGen;; zXzk*NT{bc%MkYPd>@m2sNPQ*fIS=DPa0v!&YwauY)~PX-#P`5)M#@PD9@mB}?H)A%L-6wp{j+X_PU^L=n+ zG$5IzbfoZRtxe~v%)o$=oJ4U7n%*Q<5i(3z&}Ygayi0?y@GL?})1l~Q2|Q(%jZ5tD zFJ__V-D|6jUKC6q2Xp+lWs(skEJMF{*CaV5xN!}py9z6S%`1gBZFk`8np4ubwq!O9 zMaOifj1-mCF0e65sYu~WTARvO8Hr+NBnJ=@G+iiGVKR(a&?|f8iC#m+7oPHpb#onOqEbjWWDBS`od?Iy0GI{LPEto5lEv3~$x2 zfj$nnX=$)PU>APfgTqF8IJk*k4f^Ph!D_)Ma}u|hjz2U#l(l)9fsfOZvRrIa5((vT z9e8^A7i#;q;>fZu)L`8YTdLo%yuJF()z>ZevI3(SgSFFQ?xg4&7kyJ=-qgB+DZA@| z$&i&YxO;XL%341u2FJzVjiVD{<>2nAbo=0*x#`te^xJLj?D@Vd4N+A}RVo^WirGwt z?P8UW4EtEnw0rw|6*G|VtLPg;#eFvQ0doMehwyd(4AR9%k%Un9h8I+Om)d1!M;0u#Grc9q-k~)`aLerXH8oQ+?|3zF5y$8ZCD?uXky`HRC87@728x zYNSbRIjBYs#`d<+C*=Y*;RwJX-xP0v?299lw7wh3$bX#Hq|x<8^GXi@jKjJ45^wN+rN61D?J acgB0Qp-F9iL2c_$n>%6+okNc*Jos-?5h#rS diff --git a/backend/app/services/__pycache__/conversation_state_manager.cpython-313.pyc b/backend/app/services/__pycache__/conversation_state_manager.cpython-313.pyc index b238f20e190167b6afea30a31f0a27b54aed3913..d580c8e86200891534618b5416dba5ce647cda2e 100644 GIT binary patch delta 1734 zcmb7^UrbY19LLY^o_k9F0Bx!LOH0dI$DM&hPs>_mdB$KQBlnk4sAQ1fOFEZyf3yE%~gd_ye>a&r?GD#K-TM&IMv54u{Zw z2QJedR!ox64>e*9J_(tSxD==SJ@dA*ryM1I-)!e;t?iQEz;A2Uf##B~l7IDrj(?}M z^Sv+Wcum;G_v?0phL7oDlCF`1&Q~JG^&x3#|6NX=F-@JLS_ zUGVF|7GiSp6Q;oF-&031;}|3x2#9U$qwQq89(R z7L5O>1%A`)0y+Jy*$=?@Vp*qFOL`XZo#J7Ol}d39f7Rl3LXw4U*hb3=>xBjd?UDv{LW5!Zss@pI)F7fk4Un$- zkx+vO=2}-O*e-@q1HqdiBBKT!42=m86B7~$mep3HkJQl#lQB|7C%hoo$AnK66I@4; z?-ad#EHQB6Kx)vvd@-;=G36aCj>rNyEZl?Vrs05Y!gEe@@@e6{mws&LgP6Th255N9RcON4U{L za)#vBm~}hhfqQMvT|eutA4%jI!ZQuw@#d_%J7??WW3JZXf|J;HV0F7iW`A~x%=Y5) zv#vc#Oix|wTp@QuyJ3z2YA8wBnOj(Clql7rLzCy?V{&C~LzjVJWLY4m|RK~5Iehl>u&yoGHl~4`lkN0E^ zzB#M!!uESs&-v|htHHbH{p+r^UunbJ5q7>8 z%Gpj(PKAu}lC%;BNF_3R%%rMfY_?47WbN%%Nf@Yyi&kPF9 a6YRzlQ%%#o@u^Lj)yDy}3!+{8`eabEdho%j~l0NYl8gYbjo$8wIzK+I9}o z%~ntZt$UEEAcG$R9ZdJO`)_27Od^WoW_R3x7{|d<|H_Kf`_kc1DL{! z?Nw@3w(E#$gV*?W!f(}&@n_7c*hN(rJm&k4MbU7ic_kAIIvqf$#Ic)OhGW~i1ul&@ zV<+l1DR|NpMyk8SWx4p4kK$4E5(y)?B9zKDdj;dc@*~G6CRZ?m{bENZ5U&SPOM#Tm z)s@_$lB)&!F24S&C$ubw7ot52zJm*{gBxZf@2dN{mV8~>R6W(ZnCh+ha^-JC59Xe% ziZcA5P2nuZa&AP>dc3jJxuaPTgbx~#iMpIXT~g^vs0$Ut-+h)+nF}W z-!(le`EzbRCgm^OF^oC5>*<#nt@tL39a@ypq9A$0t>zNVQpTj|J4^<>WAMGV%#fUE zox|h+Lz4D&^gIiaRbTW!K;UcN>?V(4Io`9)qfcQxm{gs)D6{(tpZAjc88I)C2R6*& z2O?$qi{<@uBd4d{nS{5`OXPQiAmGo#gnu8#aNX}eVd^4mexh;;jwAL~>%ZPg*clxb zws#BUe}0C!M6yPgrteS)2EK%uz%(9&L{QuHDeEwe8r~N6Ust6_T}muT35~0F9$M@? zbnmU2l!wQ`%;qx>x5b}-I%!b9khNr;sLB&VoqYBMQkBJQl2jAeh?A6Q5yPnuyw)LB zei;=IM7vwzkG3@Xs(T2$+@66C+b5Jn^ZMUJPhCtdipgauywZ`JIeFt`t>eHNB`JO# z5tq*KSb1~T$W-@IqOI4@UOnUOa#lnU3Tw4CnI`mcYyfmpic e=Bc2?8i-kitEls_%JHi6Pa|!}=JF5I{oLQj11#16 diff --git a/backend/app/services/__pycache__/customer_data_service.cpython-313.pyc b/backend/app/services/__pycache__/customer_data_service.cpython-313.pyc index ab0db232215d914bb2206c8f73329ad3736cb354..dc812b929dca078eb4dd627fb4cb5bdc863a792d 100644 GIT binary patch delta 3140 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 delta 1380 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 HroPbM`Sh^f diff --git a/backend/app/services/__pycache__/focus_group_service.cpython-313.pyc b/backend/app/services/__pycache__/focus_group_service.cpython-313.pyc index 14fdf0f4d25fb5f9cbf5b4b464b08968ef641956..7107a00ad9eacd788c1a1f985da291d8bf5941f3 100644 GIT binary patch delta 770 zcmZ8cT}V@582-MUeTR9rb7pIE9#3s&KaT0#Y(FulE7=0Wib2UNf}jhNvoXaA>@J~@ zpoo;OMc3VUC)DXCqCfnLL=c5s-D{pTFH#|iE_zQELEp{$JkR^Q@AJK{U*J10F~7s} z9D+A>Wm*|{%HJ?rZv)$zZ$QY0WK>4gn z1tKb4Xu*Y*4&#Em9q@Z=mMbLC2QgzVM1)0u#t&@Cx8A`Gc32_VuD=`Wn(~A zU1QnSV$$%@k|j_oj;sO7XX&&D%6giCvGwj|z#D!7cq5YKplo(vh}!Jp3L!=#QX#-- zG7GvEAi5IL$Fv9w$y$OdMj7pRq}aw(+X&!lRPR<37}BdrdnqH6ujUL_l1R;A1g$0- spkI?nNhUR!(G?#zHq)$ delta 552 zcmXwz&ubGw6vyXfcEWbo**3AwcCsOxtZlH>q}pJ$*3u>-9;DG)(u)Y5!X_jk;z3V} zLh&Gl)KNkF9~7C3B8XO_;*U#CrVMz16YJ$(IBhbfhswM=)Lv_IejNAG?SQIDq2cVRl ztixY>L;MLP+@J~hyF?@WB2DhlSzzJ^9g<5z@0kGE(|Z`dOHT%Q{5or7!6^ole6D!l5P z6Ng9c;Xo+1W{s^_V|8n|9yw6A2kQ3zzg^<{=zTBVZ2S53@rtt5o>%zhc=g7=E6?QB z%Tvtt8*ly0HI16h+>r4u%-pE*9;d*Kp${ajGm%LnlKAuI304Ih!*U|t!}u(iTe2sefj1rs+- uQV8Lu-CI;iGnvwgnEakpWPS@1*=nhHn{}<^C9S?#3CJHHl0|J|Ir<-(BbDd? diff --git a/backend/app/services/__pycache__/image_description_service.cpython-313.pyc b/backend/app/services/__pycache__/image_description_service.cpython-313.pyc index 3f0a3cd70149c4dd56758bdbf2546ffbaf3f896f..a366f159201b53700b3c3d535eb955bb44f39886 100644 GIT binary patch delta 480 zcmZ1#wK|&jGcPX}0}%X8U6)a?k@qSKPcxABc?uf?gEE7{=2tAz%>14V!TcG_3=CzA zQKD{;9%t7VDLK##GRW9IqxwtnKN(p<>p~$Z360jxj9BCm(fW@ z@4AZRMHR~(dY4r^ugiH(;J#<$KEr#y|4jcC-dAKSPJ~S0d;(JBbxFnRhP2`hMePMV zHx!g_$ZLG$WK|XV&M=u-WU0`vk3tOcUW^~vSmlL&d=X+0QJ&l&qBZ%3$cOqr#g~B| zXkzr`U~F;L_F`f*4n7DjdnNRVE`!X<}QgP(2QU a5sa+n8;n0Nfanv<5c)zu@aACc>x=-b>w*UW delta 344 zcmZ1-y()_LGcPX}0}v!z%+840$a|HAgBi&CoWjPi`5lWi^JGbml+C>y9E_Xia^7Ry zT+hwL&YZ){u(?ktm(fa2-wa!$4tS<3wTi`M3kBC?G2zlvu9?QC+^_S9iGtmi1| zuE%hcMa4sk;i#q)izhR~aXl^%apvO|QXWFgCxlpm>=WY5K(!~NxI7t{PpF7{STmon zW(BfOGJwQSGIM!qGN0rX_f%m%slp0kYl6jfxP0`PPnwDQXfdDSbL90_WI8R)=%b*| zd|HvqN1gSwwz-cI>lr0hAp49uGf>qTEmok8&CkV7h^VXsy0MMnCX3GlR>2QU5sa+n VD}p~Tfan9t5c-7j<}2FQ83A3FWZD1# diff --git a/backend/app/services/__pycache__/key_theme_service.cpython-313.pyc b/backend/app/services/__pycache__/key_theme_service.cpython-313.pyc index 569129316f916f4eec5c500fad8b8ec12c1d901f..84b99cf02b7e2f9260328847efef7df543539335 100644 GIT binary patch delta 1119 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 K|6~y9V){RF=eflI diff --git a/backend/app/services/__pycache__/persona_export_service.cpython-313.pyc b/backend/app/services/__pycache__/persona_export_service.cpython-313.pyc index 69f7d0d8642f04c673517d60f1a726f92bdf2ead..b21a3f47f0344710c327fba3050eec12f7d27861 100644 GIT binary patch delta 303 zcmZp+xM0EinU|M~0SLZ`tjn0Zk++tUry0om{D6spL772e@+8jFJkdNL1t1W_H*tZ8 zOek9@Qz&yVZ!n)Fizh=ca|Sa5LmA^#W3B!ha2KbH>6c=$f)0tmjB4fs>b(ya-z^S z&X3lt3Vc7lcri|v5{|6@Q=A2~zKPLQkFkZ+%j4sd=Z$eD;#O|tJnZ&eiN??Gs6T& sJ0N+8-xf$7<~0S9N0_->RhW?1agkbvjWM@--M$$0O)Zyp#T5? diff --git a/backend/app/services/__pycache__/persona_modification_service.cpython-313.pyc b/backend/app/services/__pycache__/persona_modification_service.cpython-313.pyc index dcff060e11cbb64155cb2d0cb943c7bc7f85a9fc..03bad89375bd1aa2e19570507f94ecf6d1a06841 100644 GIT binary patch delta 715 zcmZXQOH30{6o${4JGavgw54YHl1GQO1GXW|qCmq#NCQb9gu!GSLc+>Kk(CQg+^_(Z zjc#um7d{qlSV=%O#<&rLJ}@Bxk}h2+3wH=45_I96vV*(&&j0`C-gEBKmluTBI9KnzrtRCI41Oq?SCkFiJ$+Q_Dr7D=$c114efN*F~}RPG^V zf0dZaVM_gig{G5r`Zd(pgnzyhEx~LWm7{FdefJ+?iERa@uDp0X zcvnIvWYJh=3&B*Y%aT2i5_-{|96*H`dO-lD%yxq@KR%E%@+{86O%j3Gi(Zvss2Hg` zS)AD5*()jIfUSjIx-T-qM5~=)R``kUQZh}}e}wM~Xs|-d=XI&|JyCW3L%(1Vj(T$F zvhb(YRnyXYTKc2BuZ`9sqik3G;7s<^5@UObG4{N@Lrez#09$E)<DWw&Hh0H!4Z_!JnMPi0wFx}6 zrFL`P_Fz8d*!Jo+58HYP?E6btXHsVoZCzTBbCi^PdJae#DCaUI+$cMdHEwd3=cMIQ8)~*-2BI ThX|kLp^tBEB*gW_xJdp3FUG9k delta 513 zcmZ9H&1(}u7>D1P$(UrCZ4_qn(X1}rO=C&0p(Khmv}G@*NvT^{Rzy!lj9!{cj~@KM zyWooyi{RCQg=$WUAXb}?6)!E=f509*2!&RVdU4X?#W~FUe$O*A%&gBpaj)IN+Ep;-buii4TL|QG6`ZgyE{ZMM4I$S zCYcEhf==pb=7?3v+$#X@fB!4BtQ~tg$NL_ir5_xVf48&>L$@iS@t$01xu@4yCa!*V zANI;KBG{u z#C{(P;I1{z{v71-oP9&B7x0Pw{NCY`dLle@%(?*|E$ea~HbQZgH>4A;G5X2KRtxkq zI5AZ=V^o>VK_{U~JKHbqvqy7YR7Y96T&&uxV{?@|qfFUOLDqnGrq9n*c()u int: """ @@ -143,7 +145,7 @@ class AIModeratorService: return completed_count @staticmethod - def get_moderator_status(focus_group_id: str) -> Dict[str, Any]: + async def get_moderator_status(focus_group_id: str) -> Dict[str, Any]: """ Get the current moderator status for a focus group. @@ -154,7 +156,7 @@ class AIModeratorService: Dictionary containing current moderator status """ try: - focus_group = FocusGroup.find_by_id(focus_group_id) + focus_group = await FocusGroup.find_by_id(focus_group_id) if not focus_group: return {"error": "Focus group not found"} @@ -171,7 +173,7 @@ class AIModeratorService: # Save the initial position to the database try: - FocusGroup.update(focus_group_id, { + await FocusGroup.update(focus_group_id, { 'moderator_position': moderator_position }) print(f"Initialized moderator position for focus group {focus_group_id}") @@ -245,7 +247,7 @@ class AIModeratorService: return {"error": f"Error getting moderator status: {str(e)}"} @staticmethod - def advance_discussion(focus_group_id: str) -> Dict[str, Any]: + async def advance_discussion(focus_group_id: str) -> Dict[str, Any]: """ Advance the discussion to the next item in the guide and generate appropriate moderator response. @@ -256,7 +258,7 @@ class AIModeratorService: Dictionary containing the moderator response and updated position """ try: - focus_group = FocusGroup.find_by_id(focus_group_id) + focus_group = await FocusGroup.find_by_id(focus_group_id) if not focus_group: return {"error": "Focus group not found"} @@ -265,7 +267,7 @@ class AIModeratorService: # Handle legacy markdown format if isinstance(discussion_guide, str): - return AIModeratorService._handle_legacy_advance(focus_group_id, discussion_guide) + return await AIModeratorService._handle_legacy_advance(focus_group_id, discussion_guide) # Handle structured JSON format if not discussion_guide or 'sections' not in discussion_guide: @@ -292,13 +294,13 @@ class AIModeratorService: } # Generate moderator response based on the next item - moderator_response = AIModeratorService._generate_moderator_response( + moderator_response = await AIModeratorService._generate_moderator_response( focus_group_id, next_item, section_info, new_position ) # Update focus group with new position print(f"🎯 Advancing moderator position for focus group {focus_group_id}: {new_position}") - update_success = FocusGroup.update(focus_group_id, { + update_success = await FocusGroup.update(focus_group_id, { 'moderator_position': new_position }) @@ -307,14 +309,14 @@ class AIModeratorService: # Emit WebSocket event for moderator position change (same pattern as FocusGroup.add_message) try: - moderator_status = AIModeratorService.get_moderator_status(focus_group_id) + moderator_status = await AIModeratorService.get_moderator_status(focus_group_id) if "error" not in moderator_status: - emit_websocket_event('moderator_status_update', focus_group_id, { + await emit_websocket_event('moderator_status_update', focus_group_id, { 'moderator_status': moderator_status }) - current_app.logger.debug(f"🔔 Emitted moderator_status_update websocket event for focus group {focus_group_id}") + AIModeratorService.logger.debug(f"🔔 Emitted moderator_status_update websocket event for focus group {focus_group_id}") except Exception as e: - current_app.logger.warning(f"Failed to emit moderator position websocket event: {str(e)}") + AIModeratorService.logger.warning(f"Failed to emit moderator position websocket event: {str(e)}") else: print(f"❌ Failed to update moderator position in database") @@ -331,7 +333,7 @@ class AIModeratorService: return {"error": f"Error advancing discussion: {str(e)}"} @staticmethod - def set_moderator_position(focus_group_id: str, section_id: str, item_id: Optional[str] = None) -> Dict[str, Any]: + async def set_moderator_position(focus_group_id: str, section_id: str, item_id: Optional[str] = None) -> Dict[str, Any]: """ Set the moderator position to a specific section and item. @@ -344,7 +346,7 @@ class AIModeratorService: Dictionary containing the result and new position """ try: - focus_group = FocusGroup.find_by_id(focus_group_id) + focus_group = await FocusGroup.find_by_id(focus_group_id) if not focus_group: return {"error": "Focus group not found"} @@ -408,7 +410,7 @@ class AIModeratorService: item_index = i item_type = 'activity' found = True - current_app.logger.info(f"📍 Found item '{item_id}' in subsection {subsection_idx} activity {i}, using subsection_index={subsection_index}, item_index={item_index}") + AIModeratorService.logger.info(f"📍 Found item '{item_id}' in subsection {subsection_idx} activity {i}, using subsection_index={subsection_index}, item_index={item_index}") break if found: break @@ -421,7 +423,7 @@ class AIModeratorService: item_index = i item_type = 'question' found = True - current_app.logger.info(f"📍 Found item '{item_id}' in subsection {subsection_idx} question {i}, using subsection_index={subsection_index}, item_index={item_index}") + AIModeratorService.logger.info(f"📍 Found item '{item_id}' in subsection {subsection_idx} question {i}, using subsection_index={subsection_index}, item_index={item_index}") break if found: @@ -443,25 +445,25 @@ class AIModeratorService: # Log detailed position information for debugging if subsection_index is not None: - current_app.logger.info(f"🎯 Setting moderator position: section_index={section_index}, subsection_index={subsection_index}, item_index={item_index}, item_type={item_type}") + AIModeratorService.logger.info(f"🎯 Setting moderator position: section_index={section_index}, subsection_index={subsection_index}, item_index={item_index}, item_type={item_type}") else: - current_app.logger.info(f"🎯 Setting moderator position: section_index={section_index}, item_index={item_index}, item_type={item_type}") + AIModeratorService.logger.info(f"🎯 Setting moderator position: section_index={section_index}, item_index={item_index}, item_type={item_type}") # Update focus group - FocusGroup.update(focus_group_id, { + await FocusGroup.update(focus_group_id, { 'moderator_position': new_position }) # Emit WebSocket event for moderator position change (same pattern as FocusGroup.add_message) try: - moderator_status = AIModeratorService.get_moderator_status(focus_group_id) + moderator_status = await AIModeratorService.get_moderator_status(focus_group_id) if "error" not in moderator_status: - emit_websocket_event('moderator_status_update', focus_group_id, { + await emit_websocket_event('moderator_status_update', focus_group_id, { 'moderator_status': moderator_status }) - current_app.logger.debug(f"🔔 Emitted moderator_status_update websocket event for focus group {focus_group_id}") + AIModeratorService.logger.debug(f"🔔 Emitted moderator_status_update websocket event for focus group {focus_group_id}") except Exception as e: - current_app.logger.warning(f"Failed to emit moderator position websocket event: {str(e)}") + AIModeratorService.logger.warning(f"Failed to emit moderator position websocket event: {str(e)}") return { "message": "Moderator position updated successfully", @@ -569,7 +571,7 @@ class AIModeratorService: }) @staticmethod - def _generate_moderator_response(focus_group_id: str, item: Dict[str, Any], section_info: Dict[str, Any], position: Dict[str, Any]) -> str: + async def _generate_moderator_response(focus_group_id: str, item: Dict[str, Any], section_info: Dict[str, Any], position: Dict[str, Any]) -> str: """ Generate an appropriate moderator response for the current item. @@ -584,7 +586,7 @@ class AIModeratorService: """ try: # Get previous messages for context - messages = FocusGroup.get_messages(focus_group_id) + messages = await FocusGroup.get_messages(focus_group_id) recent_messages = messages[-10:] if messages else [] # Last 10 messages # Format context @@ -602,11 +604,11 @@ class AIModeratorService: prompt = load_prompt('ai-moderator-system', context) # Get LLM model for this focus group - focus_group = FocusGroup.find_by_id(focus_group_id) + focus_group = await FocusGroup.find_by_id(focus_group_id) llm_model = focus_group.get('llm_model') if focus_group else None # Generate response - response = LLMService.generate_content( + response = await LLMService.generate_content( prompt=prompt, temperature=0.7, model_name=llm_model @@ -640,13 +642,13 @@ class AIModeratorService: return "\n".join(formatted) @staticmethod - def _handle_legacy_advance(focus_group_id: str, discussion_guide: str) -> Dict[str, Any]: + async def _handle_legacy_advance(focus_group_id: str, discussion_guide: str) -> Dict[str, Any]: """Handle advancement for legacy markdown format guides.""" # For legacy format, we'll generate a generic moderator response # This is a fallback for older discussion guides try: # Get recent messages for context - messages = FocusGroup.get_messages(focus_group_id) + messages = await FocusGroup.get_messages(focus_group_id) recent_messages = messages[-5:] if messages else [] # Create a simple context @@ -660,10 +662,10 @@ class AIModeratorService: prompt = load_prompt('ai-moderator-system', context) # Get LLM model for this focus group - focus_group = FocusGroup.find_by_id(focus_group_id) + focus_group = await FocusGroup.find_by_id(focus_group_id) llm_model = focus_group.get('llm_model') if focus_group else None - response = LLMService.generate_content( + response = await LLMService.generate_content( prompt=prompt, temperature=0.7, model_name=llm_model @@ -684,7 +686,7 @@ class AIModeratorService: } @staticmethod - def end_session_with_concluding_statement(focus_group_id: str, reason: str = 'session_ended') -> Dict[str, Any]: + async def end_session_with_concluding_statement(focus_group_id: str, reason: str = 'session_ended') -> Dict[str, Any]: """ End a focus group session with a concluding moderator statement. @@ -696,12 +698,12 @@ class AIModeratorService: Dictionary containing the concluding statement and session end confirmation """ try: - focus_group = FocusGroup.find_by_id(focus_group_id) + focus_group = await FocusGroup.find_by_id(focus_group_id) if not focus_group: return {"error": "Focus group not found"} # Generate concluding statement - concluding_message = AIModeratorService._generate_concluding_statement( + concluding_message = await AIModeratorService._generate_concluding_statement( focus_group_id, reason ) @@ -712,19 +714,19 @@ class AIModeratorService: "senderId": "moderator" } - message_id = FocusGroup.add_message(focus_group_id, message_data) + message_id = await FocusGroup.add_message(focus_group_id, message_data) if not message_id: print(f"Warning: Failed to save concluding message for focus group {focus_group_id}") # Update focus group status to completed - FocusGroup.update(focus_group_id, { + await FocusGroup.update(focus_group_id, { 'status': 'completed' }) # Add mode event for all AI session conclusions # This includes auto_complete, natural_completion, discussion_guide_completed, manual_stop, etc. - mode_event_id = FocusGroup.add_mode_event( + mode_event_id = await FocusGroup.add_mode_event( focus_group_id=focus_group_id, event_type='ai_session_concluded' ) @@ -749,7 +751,7 @@ class AIModeratorService: return {"error": f"Error ending session: {str(e)}"} @staticmethod - def _generate_concluding_statement(focus_group_id: str, reason: str) -> str: + async def _generate_concluding_statement(focus_group_id: str, reason: str) -> str: """ Generate an appropriate concluding statement for the session. @@ -762,12 +764,12 @@ class AIModeratorService: """ try: # Get focus group details for context - focus_group = FocusGroup.find_by_id(focus_group_id) + focus_group = await FocusGroup.find_by_id(focus_group_id) if not focus_group: return AIModeratorService._get_fallback_concluding_message(reason) # Get recent messages for context - messages = FocusGroup.get_messages(focus_group_id) + messages = await FocusGroup.get_messages(focus_group_id) recent_messages = messages[-5:] if messages else [] # Create context for LLM @@ -783,10 +785,10 @@ class AIModeratorService: prompt = load_prompt('ai-moderator-system', context) # Get LLM model for this focus group - focus_group = FocusGroup.find_by_id(focus_group_id) + focus_group = await FocusGroup.find_by_id(focus_group_id) llm_model = focus_group.get('llm_model') if focus_group else None - response = LLMService.generate_content( + response = await LLMService.generate_content( prompt=prompt, temperature=0.5, # Lower temperature for more consistent, professional responses model_name=llm_model diff --git a/backend/app/services/ai_persona_service.py b/backend/app/services/ai_persona_service.py index 87fa677a..7211967f 100644 --- a/backend/app/services/ai_persona_service.py +++ b/backend/app/services/ai_persona_service.py @@ -58,7 +58,7 @@ def _sanitize_persona_data_for_json(persona_data: Dict[str, Any]) -> Dict[str, A return sanitized -def generate_basic_personas( +async def generate_basic_personas( audience_brief: str, research_objective: Optional[str] = None, count: int = 5, @@ -117,7 +117,7 @@ def generate_basic_personas( # Log the LLM API call print(f"🤖 Backend: Making LLM API call to {llm_model or 'gemini-2.5-pro'} for basic persona generation") - raw_response = LLMService.generate_content( + raw_response = await LLMService.generate_content( prompt=final_prompt, temperature=temperature, system_prompt=system_prompt, @@ -187,7 +187,7 @@ def generate_basic_personas( raise PersonaGenerationError(f"Error generating basic personas: {str(e)}") -def generate_persona( +async def generate_persona( prompt_customization: Optional[str] = None, basic_persona: Optional[Dict[str, Any]] = None, temperature: float = 0.7, @@ -254,7 +254,7 @@ def generate_persona( persona_name = basic_persona.get('name', 'Unknown') if basic_persona else 'New Persona' print(f"🤖 Backend: Making LLM API call to {llm_model or 'gemini-2.5-pro'} for detailed persona generation of '{persona_name}'") - persona_data = LLMService.generate_structured_response( + persona_data = await LLMService.generate_structured_response( prompt=final_prompt, temperature=temperature, system_prompt=system_prompt, @@ -283,7 +283,7 @@ def generate_persona( raise PersonaGenerationError(f"Error generating persona: {str(e)}") -def generate_persona_summary( +async def generate_persona_summary( persona_data: Dict[str, Any], temperature: float = 0.7, llm_model: Optional[str] = None @@ -325,7 +325,7 @@ def generate_persona_summary( persona_name = persona_data.get('name', 'Unknown') print(f"🤖 Backend: Making LLM API call to {llm_model or 'gemini-2.5-pro'} for summary generation of '{persona_name}'") - raw_response = LLMService.generate_content( + raw_response = await LLMService.generate_content( prompt=final_prompt, temperature=temperature, system_prompt=system_prompt, @@ -388,7 +388,7 @@ def generate_persona_summary( raise PersonaGenerationError(f"Error generating persona summary: {str(e)}") -def generate_persona_download_summary( +async def generate_persona_download_summary( persona_data: Dict[str, Any], temperature: float = 0.7, llm_model: Optional[str] = None @@ -431,7 +431,7 @@ def generate_persona_download_summary( print(f"🤖 Backend: Making LLM API call to {llm_model or 'gemini-2.5-pro'} for download summary of '{persona_name}'") # Generate the markdown content directly - markdown_response = LLMService.generate_content( + markdown_response = await LLMService.generate_content( prompt=final_prompt, temperature=temperature, system_prompt=system_prompt, @@ -544,7 +544,7 @@ LIFE SCENARIOS REQUIREMENTS: return "Create a persona with these characteristics: " + "; ".join(customizations) -def enhance_audience_brief( +async def enhance_audience_brief( audience_brief: str, research_objective: str, temperature: float = 0.7 @@ -575,7 +575,7 @@ def enhance_audience_brief( # Generate suggestions using the LLM service try: - raw_response = LLMService.generate_content( + raw_response = await LLMService.generate_content( prompt=final_prompt, temperature=temperature ) diff --git a/backend/app/services/ai_runner_service.py b/backend/app/services/ai_runner_service.py new file mode 100644 index 00000000..1525af6e --- /dev/null +++ b/backend/app/services/ai_runner_service.py @@ -0,0 +1,376 @@ +""" +AI Runner Service + +Provides a single dedicated thread with an asyncio event loop for all AI conversations. +This fixes Motor loop affinity issues and improves scalability by avoiding one-thread-per-conversation. + +Based on GPT-5 recommendations for clean async/threading architecture. +""" + +import asyncio +import threading +import logging +from typing import Dict, Any, Optional, Callable, Awaitable +from datetime import datetime +from concurrent.futures import Future +import weakref + +from app.db import get_db +from motor.motor_asyncio import AsyncIOMotorClient + + +class AIRunnerService: + """Singleton service that runs all AI conversations in a dedicated thread with single event loop.""" + + _instance: Optional['AIRunnerService'] = None + _lock = threading.Lock() + + def __new__(cls): + if cls._instance is None: + with cls._lock: + if cls._instance is None: + cls._instance = super().__new__(cls) + return cls._instance + + def __init__(self): + if hasattr(self, '_initialized'): + return + + self.logger = logging.getLogger(__name__) + self._thread: Optional[threading.Thread] = None + self._loop: Optional[asyncio.AbstractEventLoop] = None + self._running = False + self._stopping = False + + # Task registry for tracking and cancellation + self._active_conversations: Dict[str, asyncio.Task] = {} # focus_group_id -> Task + self._task_registry_lock = asyncio.Lock() # Will be created on the AI loop + + # Database client for AI operations (will be created on AI loop) + self._db_client: Optional[AsyncIOMotorClient] = None + self._db = None + + self._initialized = True + + def start(self) -> None: + """Start the AI runner thread and event loop.""" + if self._running: + self.logger.warning("AI Runner already running") + return + + self.logger.info("Starting AI Runner service...") + self._stopping = False + self._thread = threading.Thread(target=self._run_event_loop, daemon=True) + self._thread.start() + + # Wait for loop to be ready + while self._loop is None and not self._stopping: + threading.Event().wait(0.01) + + if self._loop: + self.logger.info("AI Runner service started successfully") + else: + self.logger.error("Failed to start AI Runner service") + + def stop(self) -> None: + """Stop the AI runner service gracefully (idempotent).""" + self.logger.info("Stopping AI Runner service...") + self._stopping = True + + # Get references (they might change during shutdown) + thread = self._thread + loop = self._loop + + if loop is not None: + # Cancel all active conversations + try: + future = asyncio.run_coroutine_threadsafe(self._cancel_all_conversations(), loop) + future.result(timeout=3.0) + self.logger.info("All AI conversations cancelled") + except Exception as e: + self.logger.warning(f"Error cancelling conversations (continuing shutdown): {e}") + + # Stop the event loop + try: + loop.call_soon_threadsafe(loop.stop) + self.logger.info("AI Runner event loop stop requested") + except Exception as e: + self.logger.warning(f"Error stopping event loop (continuing): {e}") + + # Always try to join thread if it exists + if thread and thread.is_alive(): + self.logger.info("Waiting for AI Runner thread to finish...") + thread.join(timeout=5.0) + if thread.is_alive(): + self.logger.warning("AI Runner thread did not shut down gracefully") + else: + self.logger.info("AI Runner thread joined successfully") + + self._running = False + self.logger.info("AI Runner service shutdown complete") + + def _run_event_loop(self) -> None: + """Main thread function that runs the asyncio event loop.""" + try: + # Create new event loop for this thread + self._loop = asyncio.new_event_loop() + asyncio.set_event_loop(self._loop) + + # Initialize async resources on this loop + self._loop.run_until_complete(self._initialize_async_resources()) + + self._running = True + self.logger.info("AI Runner event loop started") + + # Run the event loop + self._loop.run_forever() + + # Cleanup phase after loop.stop() + self.logger.info("AI Runner event loop stopped, cleaning up...") + self._loop.run_until_complete(self._cleanup_async_resources()) + + # Cancel any remaining tasks + pending = [t for t in asyncio.all_tasks(loop=self._loop) if t is not asyncio.current_task(self._loop)] + for t in pending: + t.cancel() + if pending: + self._loop.run_until_complete(asyncio.gather(*pending, return_exceptions=True)) + self.logger.info(f"Cancelled {len(pending)} remaining tasks") + + # Shutdown async generators and default executor (Python 3.9+) + try: + self._loop.run_until_complete(self._loop.shutdown_asyncgens()) + except Exception as e: + self.logger.debug(f"Error shutting down async generators: {e}") + + try: + self._loop.run_until_complete(self._loop.shutdown_default_executor()) + except Exception as e: + self.logger.debug(f"Error shutting down default executor: {e}") + + except Exception as e: + self.logger.error(f"AI Runner event loop error: {e}") + finally: + # Close the loop + try: + if self._loop: + self._loop.close() + self.logger.info("AI Runner event loop closed") + except Exception as e: + self.logger.debug(f"Error closing loop: {e}") + + self._running = False + self.logger.info("AI Runner thread cleanup complete") + + async def _initialize_async_resources(self) -> None: + """Initialize async resources (database, HTTP clients) on the AI loop.""" + try: + # Initialize database connection + self._db = await get_db() + self.logger.info("AI Runner: Database connection initialized") + + # Create task registry lock on this loop + self._task_registry_lock = asyncio.Lock() + + self.logger.info("AI Runner: Async resources initialized") + + except Exception as e: + self.logger.error(f"Failed to initialize AI Runner async resources: {e}") + raise + + async def _cleanup_async_resources(self) -> None: + """Cleanup async resources.""" + try: + # Cancel any remaining tasks + await self._cancel_all_conversations() + + # Close database connections + if self._db_client: + self._db_client.close() + + self.logger.info("AI Runner: Async resources cleaned up") + + except Exception as e: + self.logger.error(f"Error cleaning up AI Runner resources: {e}") + + async def _cancel_all_conversations(self) -> None: + """Cancel all active conversation tasks.""" + if not self._task_registry_lock: + return + + async with self._task_registry_lock: + tasks_to_cancel = list(self._active_conversations.values()) + self._active_conversations.clear() + + if tasks_to_cancel: + self.logger.info(f"Cancelling {len(tasks_to_cancel)} active conversations") + for task in tasks_to_cancel: + if not task.done(): + task.cancel() + + # Wait for cancellation to complete + if tasks_to_cancel: + try: + await asyncio.gather(*tasks_to_cancel, return_exceptions=True) + except Exception as e: + self.logger.debug(f"Expected cancellation errors: {e}") + + def submit_conversation(self, focus_group_id: str, coro: Awaitable[Any]) -> Future: + """ + Submit a conversation coroutine to run on the AI event loop. + + Args: + focus_group_id: The focus group ID for tracking + coro: The coroutine to execute + + Returns: + Future that will contain the result + """ + if not self._running or not self._loop: + raise RuntimeError("AI Runner is not running") + + # Use run_coroutine_threadsafe to schedule on the AI loop + future = asyncio.run_coroutine_threadsafe( + self._run_conversation_with_tracking(focus_group_id, coro), + self._loop + ) + + return future + + async def _run_conversation_with_tracking(self, focus_group_id: str, coro: Awaitable[Any]) -> Any: + """Run a conversation coroutine with proper task tracking.""" + # Create task for this conversation + task = asyncio.create_task(coro) + + # Register the task + async with self._task_registry_lock: + # Cancel existing conversation for this focus group if any + existing_task = self._active_conversations.get(focus_group_id) + if existing_task and not existing_task.done(): + self.logger.info(f"Cancelling existing conversation for focus group {focus_group_id}") + existing_task.cancel() + try: + await existing_task + except asyncio.CancelledError: + pass + + self._active_conversations[focus_group_id] = task + + try: + # Run the conversation + self.logger.info(f"Starting AI conversation for focus group {focus_group_id}") + result = await task + self.logger.info(f"AI conversation completed for focus group {focus_group_id}") + return result + + except asyncio.CancelledError: + self.logger.info(f"AI conversation cancelled for focus group {focus_group_id}") + raise + except Exception as e: + self.logger.error(f"AI conversation error for focus group {focus_group_id}: {e}") + raise + finally: + # Unregister the task + async with self._task_registry_lock: + if self._active_conversations.get(focus_group_id) is task: + del self._active_conversations[focus_group_id] + + def stop_conversation(self, focus_group_id: str) -> bool: + """ + Stop a specific conversation. + + Args: + focus_group_id: The focus group ID to stop + + Returns: + True if conversation was found and cancelled, False otherwise + """ + if not self._running or not self._loop: + return False + + # Schedule cancellation on the AI loop + future = asyncio.run_coroutine_threadsafe( + self._cancel_conversation(focus_group_id), + self._loop + ) + + try: + return future.result(timeout=5.0) + except Exception as e: + self.logger.error(f"Error stopping conversation {focus_group_id}: {e}") + return False + + async def _cancel_conversation(self, focus_group_id: str) -> bool: + """Cancel a specific conversation task.""" + async with self._task_registry_lock: + task = self._active_conversations.get(focus_group_id) + if task and not task.done(): + task.cancel() + try: + await task + except asyncio.CancelledError: + pass + return True + return False + + def get_active_conversations(self) -> Dict[str, Dict[str, Any]]: + """Get information about active conversations.""" + if not self._running or not self._loop: + return {} + + future = asyncio.run_coroutine_threadsafe( + self._get_conversation_info(), + self._loop + ) + + try: + return future.result(timeout=2.0) + except Exception as e: + self.logger.error(f"Error getting conversation info: {e}") + return {} + + async def _get_conversation_info(self) -> Dict[str, Dict[str, Any]]: + """Get conversation information from the AI loop.""" + async with self._task_registry_lock: + info = {} + for focus_group_id, task in self._active_conversations.items(): + info[focus_group_id] = { + 'status': 'running' if not task.done() else 'completed', + 'cancelled': task.cancelled() if task.done() else False, + 'exception': str(task.exception()) if task.done() and task.exception() else None + } + return info + + @property + def is_running(self) -> bool: + """Check if the AI Runner is running.""" + return self._running + + @property + def active_conversation_count(self) -> int: + """Get count of active conversations.""" + return len(self._active_conversations) if self._active_conversations else 0 + + +# Global AI Runner instance +_ai_runner: Optional[AIRunnerService] = None + +def get_ai_runner() -> AIRunnerService: + """Get the global AI Runner instance.""" + global _ai_runner + if _ai_runner is None: + _ai_runner = AIRunnerService() + return _ai_runner + +def init_ai_runner() -> None: + """Initialize and start the AI Runner service.""" + ai_runner = get_ai_runner() + if not ai_runner.is_running: + ai_runner.start() + +def shutdown_ai_runner() -> None: + """Shutdown the AI Runner service.""" + global _ai_runner + if _ai_runner and _ai_runner.is_running: + _ai_runner.stop() + _ai_runner = None \ No newline at end of file diff --git a/backend/app/services/autonomous_conversation_controller.py b/backend/app/services/autonomous_conversation_controller.py index 267286b6..5dbbd967 100644 --- a/backend/app/services/autonomous_conversation_controller.py +++ b/backend/app/services/autonomous_conversation_controller.py @@ -12,7 +12,7 @@ import logging from app.services.conversation_decision_service import ConversationDecisionService, ConversationDecisionError from app.services.focus_group_response_service import generate_persona_response, FocusGroupResponseError from app.services.ai_moderator_service import AIModeratorService -from app.models.focus_group import FocusGroup +from app.models.focus_group import FocusGroup # Now fully async from app.models.persona import Persona @@ -47,27 +47,11 @@ class AutonomousConversationController: self._initialize_state_from_database() def _initialize_state_from_database(self): - """Initialize the controller's state from the database.""" - try: - focus_group = FocusGroup.find_by_id(self.focus_group_id) - if focus_group: - db_status = focus_group.get('status', 'unknown') - - # Set initial state based on database status - if db_status == 'ai_mode': - self.is_running = True - self.conversation_state = "running" - else: - self.is_running = False - self.conversation_state = "idle" - - self.logger.debug(f"Initialized controller state from DB - status: {db_status}, is_running: {self.is_running}") - else: - self.logger.warning(f"Focus group {self.focus_group_id} not found during initialization") - - except Exception as e: - self.logger.error(f"Error initializing state from database: {str(e)}") - # Keep default values if database check fails + """Initialize the controller's state with defaults (database check happens during start).""" + # Set default state - actual database check will happen when start_autonomous_conversation is called + self.is_running = False + self.conversation_state = "idle" + self.logger.debug(f"Initialized controller with default state - database state will be checked on start") async def start_autonomous_conversation(self, initial_prompt: Optional[str] = None) -> Dict[str, Any]: """ @@ -88,8 +72,8 @@ class AutonomousConversationController: self.is_running = False self.conversation_state = "stopped" - # Validate focus group exists - focus_group = FocusGroup.find_by_id(self.focus_group_id) + # Validate focus group exists (using async model) + focus_group = await FocusGroup.find_by_id(self.focus_group_id) if not focus_group: return {"error": "Focus group not found"} @@ -98,8 +82,8 @@ class AutonomousConversationController: if not participants: return {"error": "Focus group has no participants"} - # Update focus group status - FocusGroup.update(self.focus_group_id, { + # Update focus group status (using async model) + await FocusGroup.update(self.focus_group_id, { 'status': 'ai_mode', 'autonomous_started_at': datetime.utcnow() }) @@ -145,9 +129,9 @@ class AutonomousConversationController: self.is_running = False self.conversation_state = "completed" - # Update focus group status + # Update focus group status (using async model) status = 'completed' if reason in ['completed', 'discussion_guide_completed', 'natural_completion'] else 'active' - FocusGroup.update(self.focus_group_id, { + await FocusGroup.update(self.focus_group_id, { 'status': status, 'autonomous_ended_at': datetime.utcnow(), 'completion_reason': reason @@ -174,7 +158,7 @@ class AutonomousConversationController: # Use the AI moderator service to properly end the session with mode events from app.services.ai_moderator_service import AIModeratorService - ending_result = AIModeratorService.end_session_with_concluding_statement( + ending_result = await AIModeratorService.end_session_with_concluding_statement( self.focus_group_id, reason ) @@ -185,6 +169,16 @@ class AutonomousConversationController: await self._add_moderator_message(completion_message, "system") else: self.logger.info(f"Successfully ended session with concluding statement: {ending_result.get('concluding_statement', '')[:100]}...") + elif reason == "manual_stop": + # For manual stops, add a mode event to indicate AI session concluded + mode_event_id = await FocusGroup.add_mode_event( + focus_group_id=self.focus_group_id, + event_type='ai_session_concluded' + ) + if mode_event_id: + self.logger.info(f"🎯 Added AI session concluded mode event for manual stop: {mode_event_id}") + else: + self.logger.warning(f"Failed to add AI session concluded mode event for manual stop") # For discussion guide completion, ensure all items are marked as completed (100% progress) if reason in ["discussion_guide_completed", "natural_completion"]: @@ -231,7 +225,7 @@ class AutonomousConversationController: # Update reasoning history with execution result reasoning_id = decision.get('reasoning_id') - self._update_reasoning_execution(reasoning_id, result) + await self._update_reasoning_execution(reasoning_id, result) if result.get("error"): self.logger.error(f"Error executing decision: {result['error']}") @@ -272,8 +266,8 @@ class AutonomousConversationController: async def _should_continue_conversation(self) -> bool: """Check if the conversation should continue based on various conditions.""" try: - # Check focus group status - focus_group = FocusGroup.find_by_id(self.focus_group_id) + # Check focus group status (using async model) + focus_group = await FocusGroup.find_by_id(self.focus_group_id) if not focus_group: return False @@ -368,7 +362,7 @@ class AutonomousConversationController: Dictionary containing the decision (with reasoning_id added), or None if no decision could be made """ try: - decision = ConversationDecisionService.decide_next_action( + decision = await ConversationDecisionService.decide_next_action( self.focus_group_id, temperature=0.7, mode='ai' @@ -377,7 +371,7 @@ class AutonomousConversationController: self.logger.info(f"LLM Decision: {decision['action']} - {decision['reasoning']}") # Store reasoning in history for UI display and get the database ID - reasoning_id = self._store_reasoning(decision) + reasoning_id = await self._store_reasoning(decision) # Add the reasoning_id to the decision for later use decision['reasoning_id'] = reasoning_id @@ -391,7 +385,7 @@ class AutonomousConversationController: self.logger.error(f"Unexpected error in decision making: {str(e)}") return None - def _store_reasoning(self, decision: Dict[str, Any]) -> Optional[str]: + async def _store_reasoning(self, decision: Dict[str, Any]) -> Optional[str]: """ Store reasoning from AI decision for UI display. @@ -412,7 +406,7 @@ class AutonomousConversationController: } # Store to database for persistence - reasoning_id = FocusGroup.add_reasoning_entry(self.focus_group_id, reasoning_entry) + reasoning_id = await FocusGroup.add_reasoning_entry(self.focus_group_id, reasoning_entry) # Also keep in memory for quick access during active session reasoning_entry['_id'] = reasoning_id # Add the database ID @@ -428,7 +422,7 @@ class AutonomousConversationController: self.logger.error(f"Error storing reasoning: {str(e)}") return None - def _update_reasoning_execution(self, reasoning_id: Optional[str], execution_result: Dict[str, Any]) -> None: + async def _update_reasoning_execution(self, reasoning_id: Optional[str], execution_result: Dict[str, Any]) -> None: """ Update the reasoning entry with execution results. @@ -439,7 +433,7 @@ class AutonomousConversationController: try: # Update the database record if reasoning_id: - FocusGroup.update_reasoning_execution(self.focus_group_id, reasoning_id, execution_result) + await FocusGroup.update_reasoning_execution(self.focus_group_id, reasoning_id, execution_result) # Also update in memory for quick access during active session if self.reasoning_history: @@ -624,7 +618,7 @@ class AutonomousConversationController: # Advance position past current item so final question shows as completed try: - advance_result = AIModeratorService.advance_discussion(self.focus_group_id) + advance_result = await AIModeratorService.advance_discussion(self.focus_group_id) if advance_result.get('error'): self.logger.info(f"Could not advance past final item when ending session: {advance_result['error']}") else: @@ -648,7 +642,7 @@ class AutonomousConversationController: try: # Get participant data - persona = Persona.find_by_id(participant_id) + persona = await Persona.find_by_id(participant_id) if not persona: error_msg = f"Participant {participant_id} not found" self.logger.error(error_msg) @@ -656,7 +650,7 @@ class AutonomousConversationController: # Get focus group data - focus_group = FocusGroup.find_by_id(self.focus_group_id) + focus_group = await FocusGroup.find_by_id(self.focus_group_id) if not focus_group: error_msg = "Focus group not found" self.logger.error(error_msg) @@ -673,12 +667,12 @@ class AutonomousConversationController: self.logger.info(f"🤖 Autonomous conversation using model: {llm_model or 'default (gemini-2.5-pro)'} for focus group {self.focus_group_id}") # Get recent messages - messages = FocusGroup.get_messages(self.focus_group_id) + messages = await FocusGroup.get_messages(self.focus_group_id) recent_messages = messages[-20:] if len(messages) > 20 else messages # Generate response try: - response_text = generate_persona_response( + response_text = await generate_persona_response( persona=persona, current_topic=topic, previous_messages=recent_messages, @@ -701,7 +695,7 @@ class AutonomousConversationController: "senderId": participant_id } - message_id = FocusGroup.add_message(self.focus_group_id, message_data) + message_id = await FocusGroup.add_message(self.focus_group_id, message_data) # GPT-5 fix: Yield after database write to flush WebSocket events await self._yield_to_eventlet() @@ -733,7 +727,7 @@ class AutonomousConversationController: async def _get_item_by_position_id(self, position_id: str) -> Optional[Dict[str, Any]]: """Get a discussion guide item by its position ID.""" try: - focus_group = FocusGroup.find_by_id(self.focus_group_id) + focus_group = await FocusGroup.find_by_id(self.focus_group_id) if not focus_group: return None @@ -788,11 +782,11 @@ class AutonomousConversationController: print(f"🔍 Checking current discussion guide item for image attachments") - moderator_status = AIModeratorService.get_moderator_status(self.focus_group_id) + moderator_status = await AIModeratorService.get_moderator_status(self.focus_group_id) current_item = None if moderator_status and 'moderator_position' in moderator_status: - focus_group = FocusGroup.find_by_id(self.focus_group_id) + focus_group = await FocusGroup.find_by_id(self.focus_group_id) if focus_group: discussion_guide = focus_group.get('discussionGuide') if discussion_guide and isinstance(discussion_guide, dict): @@ -842,7 +836,7 @@ class AutonomousConversationController: from app.services.image_description_service import ImageDescriptionService, ImageDescriptionError print(f"🎨 AI MODE: Generating description for {asset_filename}") - description = ImageDescriptionService.generate_description(self.focus_group_id, asset_filename) + description = await ImageDescriptionService.generate_description(self.focus_group_id, asset_filename) # Enhance the content with the description using display reference if available if display_reference: @@ -902,7 +896,7 @@ class AutonomousConversationController: "visual_asset": visual_asset_metadata # Frontend needs this for image display } - message_id = FocusGroup.add_message(self.focus_group_id, message_data) + message_id = await FocusGroup.add_message(self.focus_group_id, message_data) # GPT-5 fix: Yield after database write to flush WebSocket events await self._yield_to_eventlet() @@ -932,7 +926,7 @@ class AutonomousConversationController: if section_id and item_id: # LLM specified valid position - set it precisely try: - result = AIModeratorService.set_moderator_position(self.focus_group_id, section_id, item_id) + result = await AIModeratorService.set_moderator_position(self.focus_group_id, section_id, item_id) position_desc = f"position '{position_id}'" if result.get('error'): @@ -958,7 +952,7 @@ class AutonomousConversationController: async def _validate_position_id(self, position_id: str) -> tuple[Optional[str], Optional[str]]: """Validate that the position ID exists and return the section_id and item_id it maps to.""" try: - focus_group = FocusGroup.find_by_id(self.focus_group_id) + focus_group = await FocusGroup.find_by_id(self.focus_group_id) if not focus_group: return None, None @@ -1034,7 +1028,7 @@ class AutonomousConversationController: async def _fallback_advance_position(self) -> None: """Fallback method to advance position sequentially.""" try: - advance_result = AIModeratorService.advance_discussion(self.focus_group_id) + advance_result = await AIModeratorService.advance_discussion(self.focus_group_id) if advance_result.get('error'): self.logger.warning(f"Sequential advancement failed: {advance_result['error']}") else: @@ -1043,12 +1037,12 @@ class AutonomousConversationController: self.logger.warning(f"Failed to advance moderator position sequentially: {str(e)}") async def _yield_to_eventlet(self): - """GPT-5 fix: Yield to the eventlet hub to flush WebSocket frames.""" + """Yield control to allow other tasks to run and flush WebSocket frames.""" try: - from app.extensions import socketio - socketio.sleep(0) # Cooperative yielding for eventlet + # Use asyncio sleep instead of socketio.sleep since we're in async context + await asyncio.sleep(0) # Yield to other tasks except Exception as e: - self.logger.warning(f"Could not yield to eventlet: {e}") + self.logger.warning(f"Could not yield to event loop: {e}") async def _wait_between_actions(self): """Wait an appropriate amount of time between actions.""" @@ -1058,11 +1052,11 @@ class AutonomousConversationController: delay = random.uniform(self.min_delay_between_actions, self.max_delay_between_actions) await asyncio.sleep(delay) - def get_conversation_status(self) -> Dict[str, Any]: + async def get_conversation_status(self) -> Dict[str, Any]: """Get the current status of the autonomous conversation.""" try: # Check the actual database state to determine if autonomous mode is truly running - focus_group = FocusGroup.find_by_id(self.focus_group_id) + focus_group = await FocusGroup.find_by_id(self.focus_group_id) if focus_group: db_status = focus_group.get('status', 'unknown') @@ -1086,7 +1080,7 @@ class AutonomousConversationController: # Keep existing instance state if database check fails # Load reasoning history from database to ensure it persists across controller instances - reasoning_history = FocusGroup.get_reasoning_history(self.focus_group_id, self.max_reasoning_history) + reasoning_history = await FocusGroup.get_reasoning_history(self.focus_group_id, self.max_reasoning_history) return { "focus_group_id": self.focus_group_id, @@ -1105,7 +1099,7 @@ class AutonomousConversationController: the moderator position to indicate 100% completion. """ try: - focus_group = FocusGroup.find_by_id(self.focus_group_id) + focus_group = await FocusGroup.find_by_id(self.focus_group_id) if not focus_group: self.logger.error("Focus group not found when marking all questions completed") return @@ -1161,7 +1155,7 @@ class AutonomousConversationController: } # Update the focus group with the completion position - FocusGroup.update(self.focus_group_id, { + await FocusGroup.update(self.focus_group_id, { 'moderator_position': completion_position }) diff --git a/backend/app/services/conversation_context_service.py b/backend/app/services/conversation_context_service.py index c8fe4a89..41e92785 100644 --- a/backend/app/services/conversation_context_service.py +++ b/backend/app/services/conversation_context_service.py @@ -18,7 +18,7 @@ class ConversationContextService: """Service for aggregating conversation context for LLM decision making.""" @staticmethod - def get_full_context(focus_group_id: str) -> Dict[str, Any]: + async def get_full_context(focus_group_id: str) -> Dict[str, Any]: """ Get complete conversation context for LLM decision making. @@ -30,15 +30,15 @@ class ConversationContextService: """ try: # Get focus group data - focus_group = FocusGroup.find_by_id(focus_group_id) + focus_group = await FocusGroup.find_by_id(focus_group_id) if not focus_group: raise ValueError(f"Focus group {focus_group_id} not found") # Get all participants - participants = ConversationContextService._get_participants_context(focus_group) + participants = await ConversationContextService._get_participants_context(focus_group) # Get conversation history - messages = FocusGroup.get_messages(focus_group_id) + messages = await FocusGroup.get_messages(focus_group_id) conversation_history = ConversationContextService._format_conversation_history(messages) # Get conversation analytics @@ -69,13 +69,13 @@ class ConversationContextService: raise Exception(f"Error getting conversation context: {str(e)}") @staticmethod - def _get_participants_context(focus_group: Dict[str, Any]) -> List[Dict[str, Any]]: + async def _get_participants_context(focus_group: Dict[str, Any]) -> List[Dict[str, Any]]: """Get formatted participant context with OCEAN traits and participation stats.""" participants = [] participant_ids = focus_group.get('participants', []) for participant_id in participant_ids: - persona = Persona.find_by_id(participant_id) + persona = await Persona.find_by_id(participant_id) if persona: participant_context = { 'id': participant_id, @@ -497,7 +497,7 @@ class ConversationContextService: # ================== MULTIMODAL CONVERSATION CONTEXT METHODS ================== @staticmethod - def build_multimodal_context(focus_group_id: str, messages: Optional[List[Dict[str, Any]]] = None) -> Dict[str, Any]: + async def build_multimodal_context(focus_group_id: str, messages: Optional[List[Dict[str, Any]]] = None) -> Dict[str, Any]: """ Build complete multimodal conversation context including text and images in proper sequence. @@ -513,10 +513,10 @@ class ConversationContextService: # Get messages with visual context if not provided if messages is None: - messages = FocusGroup.get_messages_with_visual_context(focus_group_id) + messages = await FocusGroup.get_messages_with_visual_context(focus_group_id) # Get active visual context - active_visual_context = FocusGroup.get_active_visual_context(focus_group_id) + active_visual_context = await FocusGroup.get_active_visual_context(focus_group_id) print(f" - Total messages: {len(messages)}") print(f" - Active visual assets: {len(active_visual_context)}") @@ -687,7 +687,7 @@ class ConversationContextService: return "\n".join(formatted) @staticmethod - def get_current_visual_assets(focus_group_id: str) -> List[str]: + async def get_current_visual_assets(focus_group_id: str) -> List[str]: """ Get list of asset paths that are currently active in conversation context. @@ -698,7 +698,7 @@ class ConversationContextService: List of full paths to currently active visual assets """ try: - active_context = FocusGroup.get_active_visual_context(focus_group_id) + active_context = await FocusGroup.get_active_visual_context(focus_group_id) asset_paths = [] for asset in active_context: @@ -715,7 +715,7 @@ class ConversationContextService: return [] @staticmethod - def has_visual_context(focus_group_id: str) -> bool: + async def has_visual_context(focus_group_id: str) -> bool: """ Check if a focus group currently has any active visual context. @@ -726,7 +726,7 @@ class ConversationContextService: True if there are active visual assets, False otherwise """ try: - active_context = FocusGroup.get_active_visual_context(focus_group_id) + active_context = await FocusGroup.get_active_visual_context(focus_group_id) return len(active_context) > 0 except Exception as e: print(f"❌ Error checking visual context: {e}") diff --git a/backend/app/services/conversation_decision_service.py b/backend/app/services/conversation_decision_service.py index a100d739..11e9983e 100644 --- a/backend/app/services/conversation_decision_service.py +++ b/backend/app/services/conversation_decision_service.py @@ -19,7 +19,7 @@ class ConversationDecisionService: """Service for making LLM-based conversation decisions.""" @staticmethod - def decide_next_action(focus_group_id: str, temperature: float = 0.7, mode: str = "ai") -> Dict[str, Any]: + async def decide_next_action(focus_group_id: str, temperature: float = 0.7, mode: str = "ai") -> Dict[str, Any]: """ Use LLM to decide the next action in the conversation. @@ -38,7 +38,7 @@ class ConversationDecisionService: try: # Get full conversation context - context = ConversationContextService.get_full_context(focus_group_id) + context = await ConversationContextService.get_full_context(focus_group_id) formatted_context = ConversationContextService.format_context_for_llm(context) # Load the appropriate prompt based on mode @@ -55,12 +55,12 @@ class ConversationDecisionService: # Get LLM model for this focus group from app.models.focus_group import FocusGroup - focus_group = FocusGroup.find_by_id(focus_group_id) + focus_group = await FocusGroup.find_by_id(focus_group_id) llm_model = focus_group.get('llm_model') if focus_group else None # Get LLM decision try: - response = LLMService.generate_content( + response = await LLMService.generate_content( prompt=prompt, temperature=temperature, model_name=llm_model @@ -160,7 +160,7 @@ class ConversationDecisionService: return True @staticmethod - def select_next_participant(focus_group_id: str, current_topic: str, temperature: float = 0.7) -> Dict[str, Any]: + async def select_next_participant(focus_group_id: str, current_topic: str, temperature: float = 0.7) -> Dict[str, Any]: """ Use LLM to select the next participant to respond. @@ -173,7 +173,7 @@ class ConversationDecisionService: Dictionary containing participant selection details """ try: - decision = ConversationDecisionService.decide_next_action(focus_group_id, temperature) + decision = await ConversationDecisionService.decide_next_action(focus_group_id, temperature) if decision['action'] == 'participant_respond': return { @@ -195,7 +195,7 @@ class ConversationDecisionService: raise ConversationDecisionError(f"Error selecting participant: {str(e)}") @staticmethod - def detect_probe_triggers(focus_group_id: str, temperature: float = 0.7) -> Dict[str, Any]: + async def detect_probe_triggers(focus_group_id: str, temperature: float = 0.7) -> Dict[str, Any]: """ Use LLM to detect if probe triggers are needed. @@ -207,7 +207,7 @@ class ConversationDecisionService: Dictionary containing probe trigger information """ try: - decision = ConversationDecisionService.decide_next_action(focus_group_id, temperature) + decision = await ConversationDecisionService.decide_next_action(focus_group_id, temperature) if decision['action'] == 'probe_trigger': return { @@ -230,7 +230,7 @@ class ConversationDecisionService: raise ConversationDecisionError(f"Error detecting probe triggers: {str(e)}") @staticmethod - def generate_moderator_response(focus_group_id: str, context: str, temperature: float = 0.7) -> Dict[str, Any]: + async def generate_moderator_response(focus_group_id: str, context: str, temperature: float = 0.7) -> Dict[str, Any]: """ Use LLM to generate appropriate moderator response. @@ -243,7 +243,7 @@ class ConversationDecisionService: Dictionary containing moderator response details """ try: - decision = ConversationDecisionService.decide_next_action(focus_group_id, temperature) + decision = await ConversationDecisionService.decide_next_action(focus_group_id, temperature) if decision['action'] == 'moderator_speak': return { @@ -264,7 +264,7 @@ class ConversationDecisionService: raise ConversationDecisionError(f"Error generating moderator response: {str(e)}") @staticmethod - def detect_persona_interactions(focus_group_id: str, temperature: float = 0.7) -> Dict[str, Any]: + async def detect_persona_interactions(focus_group_id: str, temperature: float = 0.7) -> Dict[str, Any]: """ Use LLM to detect when personas should interact directly. @@ -276,7 +276,7 @@ class ConversationDecisionService: Dictionary containing persona interaction details """ try: - decision = ConversationDecisionService.decide_next_action(focus_group_id, temperature) + decision = await ConversationDecisionService.decide_next_action(focus_group_id, temperature) if decision['action'] == 'participant_interaction': return { @@ -299,7 +299,7 @@ class ConversationDecisionService: raise ConversationDecisionError(f"Error detecting persona interactions: {str(e)}") @staticmethod - def should_end_session(focus_group_id: str, temperature: float = 0.7) -> Dict[str, Any]: + async def should_end_session(focus_group_id: str, temperature: float = 0.7) -> Dict[str, Any]: """ Use LLM to determine if the session should end. @@ -311,7 +311,7 @@ class ConversationDecisionService: Dictionary containing session ending decision """ try: - decision = ConversationDecisionService.decide_next_action(focus_group_id, temperature) + decision = await ConversationDecisionService.decide_next_action(focus_group_id, temperature) if decision['action'] == 'end_session': return { @@ -333,7 +333,7 @@ class ConversationDecisionService: raise ConversationDecisionError(f"Error determining session end: {str(e)}") @staticmethod - def get_conversation_insights(focus_group_id: str, temperature: float = 0.7) -> Dict[str, Any]: + async def get_conversation_insights(focus_group_id: str, temperature: float = 0.7) -> Dict[str, Any]: """ Use LLM to generate insights about the current conversation state. @@ -346,7 +346,7 @@ class ConversationDecisionService: """ try: # Get conversation context - context = ConversationContextService.get_full_context(focus_group_id) + context = await ConversationContextService.get_full_context(focus_group_id) # Create a specialized prompt for insights insight_prompt = f""" @@ -368,10 +368,10 @@ class ConversationDecisionService: # Get LLM model for this focus group from app.models.focus_group import FocusGroup - focus_group = FocusGroup.find_by_id(focus_group_id) + focus_group = await FocusGroup.find_by_id(focus_group_id) llm_model = focus_group.get('llm_model') if focus_group else None - response = LLMService.generate_content( + response = await LLMService.generate_content( prompt=insight_prompt, temperature=temperature, model_name=llm_model diff --git a/backend/app/services/conversation_state_manager.py b/backend/app/services/conversation_state_manager.py index 6f4a0608..af815603 100644 --- a/backend/app/services/conversation_state_manager.py +++ b/backend/app/services/conversation_state_manager.py @@ -21,7 +21,7 @@ class ConversationStateManager: self.cache_ttl = 60 # seconds self.last_cache_update = None - def get_conversation_state(self) -> Dict[str, Any]: + async def get_conversation_state(self) -> Dict[str, Any]: """ Get the current conversation state. @@ -33,12 +33,12 @@ class ConversationStateManager: if self._is_cache_valid(): return self.state_cache - focus_group = FocusGroup.find_by_id(self.focus_group_id) + focus_group = await FocusGroup.find_by_id(self.focus_group_id) if not focus_group: return {"error": "Focus group not found"} # Get messages - messages = FocusGroup.get_messages(self.focus_group_id) + messages = await FocusGroup.get_messages(self.focus_group_id) # Calculate conversation state state = { @@ -71,7 +71,7 @@ class ConversationStateManager: except Exception as e: return {"error": f"Error getting conversation state: {str(e)}"} - def get_conversation_analytics(self) -> Dict[str, Any]: + async def get_conversation_analytics(self) -> Dict[str, Any]: """ Get detailed conversation analytics. @@ -83,11 +83,11 @@ class ConversationStateManager: if self._is_analytics_cache_valid(): return self.analytics_cache - focus_group = FocusGroup.find_by_id(self.focus_group_id) + focus_group = await FocusGroup.find_by_id(self.focus_group_id) if not focus_group: return {"error": "Focus group not found"} - messages = FocusGroup.get_messages(self.focus_group_id) + messages = await FocusGroup.get_messages(self.focus_group_id) participants = focus_group.get('participants', []) analytics = { @@ -111,7 +111,7 @@ class ConversationStateManager: except Exception as e: return {"error": f"Error getting conversation analytics: {str(e)}"} - def update_conversation_state(self, updates: Dict[str, Any]) -> Dict[str, Any]: + async def update_conversation_state(self, updates: Dict[str, Any]) -> Dict[str, Any]: """ Update conversation state. @@ -123,7 +123,7 @@ class ConversationStateManager: """ try: # Update focus group - success = FocusGroup.update(self.focus_group_id, updates) + success = await FocusGroup.update(self.focus_group_id, updates) if success: # Clear cache to force refresh @@ -140,22 +140,22 @@ class ConversationStateManager: except Exception as e: return {"error": f"Error updating conversation state: {str(e)}"} - def start_autonomous_mode(self) -> Dict[str, Any]: + async def start_autonomous_mode(self) -> Dict[str, Any]: """Start autonomous conversation mode.""" - return self.update_conversation_state({ + return await self.update_conversation_state({ 'status': 'ai_mode', 'autonomous_started_at': datetime.utcnow() }) - def end_autonomous_mode(self, reason: str = "completed") -> Dict[str, Any]: + async def end_autonomous_mode(self, reason: str = "completed") -> Dict[str, Any]: """End autonomous conversation mode.""" if reason == "completed": status = 'completed' else: status = 'active' - return self.update_conversation_state({ + return await self.update_conversation_state({ 'status': status, 'autonomous_ended_at': datetime.utcnow(), 'completion_reason': reason diff --git a/backend/app/services/customer_data_service.py b/backend/app/services/customer_data_service.py index 10d049ba..198e7d7b 100644 --- a/backend/app/services/customer_data_service.py +++ b/backend/app/services/customer_data_service.py @@ -30,7 +30,8 @@ class CustomerDataService: raise CustomerDataServiceError("llama-cloud-services package not installed") self.api_key = api_key - self.base_dir = os.path.join(os.path.dirname(__file__), "..", "..", "persona_data") + # Resolve to absolute path to avoid working directory issues + self.base_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", "persona_data")) # Ensure base directory exists os.makedirs(self.base_dir, exist_ok=True) @@ -50,7 +51,7 @@ class CustomerDataService: """Generate a unique session ID for this upload session.""" return str(uuid.uuid4()) - def upload_and_parse_files(self, files: List[FileStorage]) -> str: + async def upload_and_parse_files(self, files: List[FileStorage]) -> str: """ Upload files and parse them using LlamaParse. @@ -80,14 +81,40 @@ class CustomerDataService: # Secure filename filename = f"{session_id}_{file.filename}" file_path = os.path.join(session_dir, filename) - file.save(file_path) - uploaded_files.append(file_path) + + try: + # Save file and verify it exists (Quart async version) + await file.save(file_path) + + if os.path.exists(file_path) and os.path.getsize(file_path) > 0: + uploaded_files.append(file_path) + print(f"✅ Successfully saved file: {file_path} ({os.path.getsize(file_path)} bytes)") + else: + raise CustomerDataServiceError(f"Failed to save file: {file.filename}") + except CustomerDataServiceError: + raise # Re-raise our own errors + except Exception as e: + raise CustomerDataServiceError(f"Failed to save file {file.filename}: {str(e)}") if not uploaded_files: raise CustomerDataServiceError("No valid files uploaded") # Parse files using LlamaParse - parsed_documents = self.parser.load_data(uploaded_files) + print(f"🔄 Starting LlamaParse for {len(uploaded_files)} files...") + for file_path in uploaded_files: + print(f"📄 File to parse: {file_path} (exists: {os.path.exists(file_path)})") + + try: + parsed_documents = self.parser.load_data(uploaded_files) + print(f"✅ LlamaParse completed successfully. Generated {len(parsed_documents)} documents.") + except Exception as parse_error: + print(f"❌ LlamaParse failed: {str(parse_error)}") + # Check which files still exist before the error + for file_path in uploaded_files: + exists = os.path.exists(file_path) + size = os.path.getsize(file_path) if exists else 0 + print(f"📄 File status: {file_path} - exists: {exists}, size: {size}") + raise CustomerDataServiceError(f"LlamaParse failed: {str(parse_error)}") # Save parsed markdown files for i, document in enumerate(parsed_documents): diff --git a/backend/app/services/focus_group_response_service.py b/backend/app/services/focus_group_response_service.py index 14e3b51f..858f52c7 100644 --- a/backend/app/services/focus_group_response_service.py +++ b/backend/app/services/focus_group_response_service.py @@ -15,7 +15,7 @@ class FocusGroupResponseError(Exception): pass -def generate_persona_response( +async def generate_persona_response( persona: Dict[str, Any], current_topic: str, previous_messages: List[Dict[str, Any]], @@ -64,11 +64,11 @@ def generate_persona_response( if focus_group_id: try: from app.services.conversation_context_service import ConversationContextService - has_visual_context = ConversationContextService.has_visual_context(focus_group_id) + has_visual_context = await ConversationContextService.has_visual_context(focus_group_id) if has_visual_context: print(f"🎨 Visual context detected, building multimodal context...") - multimodal_context = ConversationContextService.build_multimodal_context( + multimodal_context = await ConversationContextService.build_multimodal_context( focus_group_id, previous_messages ) print(f"🎨 Built context with {multimodal_context['total_visual_assets']} visual assets") @@ -121,7 +121,7 @@ Be genuine and specific in your feedback, drawing on your personal experiences a raise FocusGroupResponseError(f"Error loading contextual response prompt: {str(e)}") # Generate response using contextual conversation method - response = LLMService.generate_contextual_response( + response = await LLMService.generate_contextual_response( prompt=prompt, conversation_context=multimodal_context['conversation_context'], temperature=temperature, @@ -150,7 +150,7 @@ Be genuine and specific in your feedback, drawing on your personal experiences a raise FocusGroupResponseError(f"Error loading response prompt: {str(e)}") # Generate the standard response - response = LLMService.generate_content( + response = await LLMService.generate_content( prompt=prompt, temperature=temperature, model_name=llm_model, @@ -391,7 +391,7 @@ This is your chance to provide more detailed insights and personal anecdotes. """ -def generate_creative_review_response( +async def generate_creative_review_response( persona: Dict[str, Any], current_topic: str, creative_asset_path: str, @@ -493,7 +493,7 @@ Be genuine and specific in your feedback, drawing on your personal experiences a print(f" - image_paths: {[full_asset_path]}") print(f" - temperature: {temperature}") - response = LLMService.generate_multimodal_content( + response = await LLMService.generate_multimodal_content( prompt=prompt, image_paths=[full_asset_path], temperature=temperature diff --git a/backend/app/services/focus_group_service.py b/backend/app/services/focus_group_service.py index e631fd08..d3ba0f2e 100644 --- a/backend/app/services/focus_group_service.py +++ b/backend/app/services/focus_group_service.py @@ -22,7 +22,7 @@ class FocusGroupService: """Service for focus group operations.""" @staticmethod - def generate_discussion_guide( + async def generate_discussion_guide( focus_group_name: str, research_brief: str, discussion_topics: str, @@ -94,7 +94,7 @@ class FocusGroupService: uploaded_assets = [] if focus_group_id: try: - uploaded_assets = FocusGroup.get_uploaded_assets(focus_group_id) + uploaded_assets = await FocusGroup.get_uploaded_assets(focus_group_id) if uploaded_assets: logger.info(f"Retrieved {len(uploaded_assets)} assets for focus group {focus_group_id}") except Exception as e: @@ -155,7 +155,7 @@ class FocusGroupService: enhanced_prompt = asset_emphasis + prompt # Generate content using LLM - response = LLMService.generate_content( + response = await LLMService.generate_content( prompt=enhanced_prompt, temperature=temperature, max_tokens=16000, # Use a much higher token limit to avoid truncation diff --git a/backend/app/services/image_description_service.py b/backend/app/services/image_description_service.py index d5ae058a..740bb250 100644 --- a/backend/app/services/image_description_service.py +++ b/backend/app/services/image_description_service.py @@ -22,7 +22,7 @@ class ImageDescriptionService: """Service for generating AI-powered descriptions of creative assets.""" @staticmethod - def generate_description(focus_group_id: str, asset_filename: str) -> str: + async def generate_description(focus_group_id: str, asset_filename: str) -> str: """ Generate a detailed AI description of a creative asset image. @@ -76,10 +76,10 @@ class ImageDescriptionService: # Get LLM model for this focus group from app.models.focus_group import FocusGroup - focus_group = FocusGroup.find_by_id(focus_group_id) + focus_group = await FocusGroup.find_by_id(focus_group_id) llm_model = focus_group.get('llm_model') if focus_group else None - description = LLMService.generate_multimodal_content( + description = await LLMService.generate_multimodal_content( prompt=prompt, image_paths=[asset_path], temperature=0.7, diff --git a/backend/app/services/key_theme_service.py b/backend/app/services/key_theme_service.py index a54a9812..2ffc3683 100644 --- a/backend/app/services/key_theme_service.py +++ b/backend/app/services/key_theme_service.py @@ -20,7 +20,7 @@ class KeyThemeService: """Service for generating key themes from focus group discussions.""" @staticmethod - def generate_key_themes( + async def generate_key_themes( focus_group_id: str, temperature: float = 0.7, llm_model: Optional[str] = None @@ -45,12 +45,12 @@ class KeyThemeService: try: # Get the focus group - focus_group = FocusGroup.find_by_id(focus_group_id) + focus_group = await FocusGroup.find_by_id(focus_group_id) if not focus_group: raise KeyThemeServiceError(f"Focus group not found with ID: {focus_group_id}") # Get all messages from the focus group - messages = FocusGroup.get_messages(focus_group_id) + messages = await FocusGroup.get_messages(focus_group_id) if not messages: raise KeyThemeServiceError("No messages found in this focus group") @@ -61,14 +61,14 @@ class KeyThemeService: if 'participants' in focus_group and focus_group['participants']: for persona_id in focus_group['participants']: try: - persona = Persona.find_by_id(persona_id) + persona = await Persona.find_by_id(persona_id) if persona: participants_data.append(persona) except Exception as e: print(f"Error fetching participant {persona_id}: {e}") # Generate key themes using LLM - return KeyThemeService._extract_themes_from_discussion( + return await KeyThemeService._extract_themes_from_discussion( messages=messages, participants=participants_data, discussion_guide=focus_group.get('discussionGuide', ''), @@ -80,7 +80,7 @@ class KeyThemeService: raise KeyThemeServiceError(f"Error generating key themes: {str(e)}") @staticmethod - def _extract_themes_from_discussion( + async def _extract_themes_from_discussion( messages: List[Dict[str, Any]], participants: List[Dict[str, Any]], discussion_guide: str, @@ -138,7 +138,7 @@ class KeyThemeService: logger.info(f"Attempt {attempt_num}/{max_retries}: Calling LLM ({llm_model or 'gemini-2.5-pro'}) for theme generation") try: - themes = LLMService.generate_structured_array( + themes = await LLMService.generate_structured_array( prompt=prompt, temperature=temperature, system_prompt=system_prompt, diff --git a/backend/app/services/llm_service.py b/backend/app/services/llm_service.py index 9f99ce34..f25908b4 100644 --- a/backend/app/services/llm_service.py +++ b/backend/app/services/llm_service.py @@ -7,21 +7,23 @@ different application features. import os import json -import time +import asyncio import logging -import google.generativeai as genai -from openai import OpenAI +import base64 +from google import genai +from openai import AsyncOpenAI +import httpx from typing import Dict, Any, Optional, Union, List from PIL import Image import io -# Set up the Gemini API key +# Set up the Gemini API key and client GEMINI_API_KEY = os.environ.get('GEMINI_API_KEY', 'AIzaSyAc50jzC3k9K1PmKT1vGFi0sCdhhnqsvl0') -genai.configure(api_key=GEMINI_API_KEY) +gemini_client = genai.Client(api_key=GEMINI_API_KEY) # Set up OpenAI API key OPENAI_API_KEY = os.environ.get('OPENAI_API_KEY', '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') -openai_client = OpenAI(api_key=OPENAI_API_KEY) +openai_client = AsyncOpenAI(api_key=OPENAI_API_KEY) # The default model we're using DEFAULT_MODEL = "gemini-2.5-pro" @@ -85,26 +87,14 @@ class LLMService: actual_model = model_name or DEFAULT_MODEL return SUPPORTED_MODELS.get(actual_model, 'gemini') - @staticmethod - def get_model(model_name: Optional[str] = None) -> genai.GenerativeModel: - """ - Get a configured Gemini model. - - Args: - model_name: Optional model name to use. Defaults to the default model. - - Returns: - A configured Gemini generative model - """ - return genai.GenerativeModel(model_name or DEFAULT_MODEL) @staticmethod - def _extract_text_from_response(response) -> str: + def _extract_text_from_new_genai_response(response) -> str: """ - Extract text from a Gemini API response, handling both simple and multi-part responses. + Extract text from a new Google GenAI SDK response. Args: - response: The response object from the Gemini API + response: The response object from the new Google GenAI SDK Returns: The extracted text content @@ -113,56 +103,35 @@ class LLMService: LLMServiceError: If no text content can be extracted """ try: - # Try the simple text accessor first - return response.text.strip() - except Exception: - # If that fails, try to extract from parts using the recommended approach - try: - text_parts = [] - - # Check if response has direct parts attribute (as suggested in error message) - if hasattr(response, 'parts') and response.parts: - for part in response.parts: - if hasattr(part, 'text'): - text_parts.append(part.text) - - # If that didn't work, try the candidates approach - if not text_parts and hasattr(response, 'candidates') and response.candidates: - for candidate in response.candidates: - # Check if finish reason indicates blocking - if candidate.finish_reason == 3: - raise LLMServiceError("Response was blocked for safety reasons") - elif candidate.finish_reason == 4: - raise LLMServiceError("Response was blocked for recitation reasons") - elif candidate.finish_reason == 2: - raise LLMServiceError("Response was cut off due to length limit - try reducing max_tokens or removing the limit") - - if hasattr(candidate, 'content') and hasattr(candidate.content, 'parts'): + # New SDK has a simpler text attribute + if hasattr(response, 'text') and response.text: + return response.text.strip() + + # If that doesn't work, check for candidates structure + if hasattr(response, 'candidates') and response.candidates: + for candidate in response.candidates: + if hasattr(candidate, 'content') and candidate.content: + if hasattr(candidate.content, 'parts') and candidate.content.parts: + text_parts = [] for part in candidate.content.parts: - if hasattr(part, 'text'): + if hasattr(part, 'text') and part.text: text_parts.append(part.text) + if text_parts: + return ''.join(text_parts).strip() + + # If no text found, check if the response object has direct text content + if hasattr(response, 'content') and response.content: + return str(response.content).strip() + + raise LLMServiceError("Unable to extract text from new GenAI SDK response") - # Join all text parts if we found any - if text_parts: - return ''.join(text_parts).strip() - - # If we still can't extract text, it might be a safety/blocking issue - if hasattr(response, 'candidates') and response.candidates: - finish_reason = response.candidates[0].finish_reason - if finish_reason == 3: - raise LLMServiceError("Response was blocked for safety reasons") - elif finish_reason == 4: - raise LLMServiceError("Response was blocked for recitation reasons") - elif finish_reason == 2: - raise LLMServiceError("Response was cut off due to length limit - try reducing max_tokens or removing the limit") - - raise LLMServiceError("Unable to extract text from response parts") - - except Exception as e: - raise LLMServiceError(f"Error extracting text from multi-part response: {str(e)}") + except Exception as e: + if isinstance(e, LLMServiceError): + raise + raise LLMServiceError(f"Error extracting text from new GenAI SDK response: {str(e)}") @staticmethod - def generate_content( + async def generate_content( prompt: str, temperature: float = 0.7, max_tokens: Optional[int] = None, @@ -233,7 +202,7 @@ class LLMService: # Note: GPT-5 Responses API does not support max_tokens parameter - response = openai_client.responses.create(**kwargs) + response = await openai_client.responses.create(**kwargs) result = LLMService._extract_responses_api_content(response) else: @@ -252,39 +221,33 @@ class LLMService: if max_tokens: kwargs["max_tokens"] = max_tokens - response = openai_client.chat.completions.create(**kwargs) + response = await openai_client.chat.completions.create(**kwargs) result = response.choices[0].message.content.strip() else: - # Gemini API call (existing logic) - model = LLMService.get_model(model_name) - - generation_config = { - "temperature": temperature, - } + # New Google GenAI SDK - async call + config = genai.types.GenerateContentConfig( + temperature=temperature, + ) if max_tokens: - generation_config["max_output_tokens"] = max_tokens + config.max_output_tokens = max_tokens - # If system prompt is provided, use it to create a structured chat + # Prepare the prompt - combine system prompt with user prompt if needed if system_prompt: - # For Gemini models, system prompts need to be passed as part of the user prompt - # as Gemini API doesn't support 'system' role directly - response = model.generate_content( - [ - {"role": "user", "parts": [f"System: {system_prompt}\n\nUser: {prompt}"]} - ], - generation_config=genai.types.GenerationConfig(**generation_config) - ) + combined_prompt = f"System: {system_prompt}\n\nUser: {prompt}" else: - # Otherwise use the standard prompt-only approach - response = model.generate_content( - prompt, - generation_config=genai.types.GenerationConfig(**generation_config) - ) + combined_prompt = prompt - # If successful, extract and return the response - result = LLMService._extract_text_from_response(response) + # Make async call to new GenAI SDK + response = await gemini_client.aio.models.generate_content( + model=actual_model, + contents=combined_prompt, + config=config + ) + + # Extract text from new SDK response + result = LLMService._extract_text_from_new_genai_response(response) if attempt > 0: logger.info(f"LLM content generation succeeded on attempt {attempt_num}/{max_retries}") @@ -308,7 +271,7 @@ class LLMService: # Wait before retrying (exponential backoff) wait_time = 2 ** attempt # 1s, 2s, 4s logger.info(f"Retryable error detected. Waiting {wait_time} seconds before retry {attempt_num + 1}/{max_retries}") - time.sleep(wait_time) + await asyncio.sleep(wait_time) continue else: logger.error(f"Retryable error detected but max retries ({max_retries}) reached") @@ -353,7 +316,7 @@ class LLMService: raise LLMServiceError(error_msg) @staticmethod - def generate_structured_response( + async def generate_structured_response( prompt: str, temperature: float = 0.7, max_tokens: Optional[int] = None, @@ -380,7 +343,7 @@ class LLMService: Raises: LLMServiceError: If there's an issue with generation or parsing """ - response_text = LLMService.generate_content( + response_text = await LLMService.generate_content( prompt=prompt, temperature=temperature, max_tokens=max_tokens, @@ -393,7 +356,7 @@ class LLMService: return LLMService.parse_json_response(response_text) @staticmethod - def generate_structured_array( + async def generate_structured_array( prompt: str, temperature: float = 0.7, max_tokens: Optional[int] = None, @@ -420,7 +383,7 @@ class LLMService: Raises: LLMServiceError: If there's an issue with generation or parsing """ - response_text = LLMService.generate_content( + response_text = await LLMService.generate_content( prompt=prompt, temperature=temperature, max_tokens=max_tokens, @@ -439,7 +402,7 @@ class LLMService: return result @staticmethod - def generate_multimodal_content( + async def generate_multimodal_content( prompt: str, image_paths: List[str], temperature: float = 0.7, @@ -526,7 +489,7 @@ class LLMService: # Note: GPT-5 Responses API does not support max_tokens parameter - response = openai_client.responses.create(**kwargs) + response = await openai_client.responses.create(**kwargs) result = LLMService._extract_responses_api_content(response) else: @@ -543,50 +506,59 @@ class LLMService: if max_tokens: kwargs["max_tokens"] = max_tokens - response = openai_client.chat.completions.create(**kwargs) + response = await openai_client.chat.completions.create(**kwargs) result = response.choices[0].message.content.strip() else: - # Gemini multimodal API call (existing logic) - # Load and validate images - images = [] + # New Google GenAI SDK - multimodal async call + config = genai.types.GenerateContentConfig( + temperature=temperature, + ) + + if max_tokens: + config.max_output_tokens = max_tokens + + # Prepare multimodal content for new SDK + content_parts = [] + + # Add text prompt + content_parts.append(genai.types.Part.from_text(prompt)) + + # Add images for image_path in image_paths: try: if not os.path.exists(image_path): raise LLMServiceError(f"Image file not found: {image_path}") - # Load image using PIL - with Image.open(image_path) as img: - # Convert to RGB if necessary - if img.mode != 'RGB': - img = img.convert('RGB') - images.append(img.copy()) - - logger.debug(f"Successfully loaded image for Gemini: {image_path}") + # Read image data for new SDK + with open(image_path, 'rb') as img_file: + image_data = img_file.read() + + # Determine MIME type from file extension + ext = os.path.splitext(image_path)[1].lower() + mime_type = { + '.jpg': 'image/jpeg', + '.jpeg': 'image/jpeg', + '.png': 'image/png', + '.gif': 'image/gif', + '.webp': 'image/webp' + }.get(ext, 'image/jpeg') # Default to JPEG + + content_parts.append(genai.types.Part.from_bytes(image_data, mime_type=mime_type)) + logger.debug(f"Successfully loaded image for new GenAI SDK: {image_path}") except Exception as e: raise LLMServiceError(f"Failed to load image {image_path}: {str(e)}") - model = LLMService.get_model(model_name) - - generation_config = { - "temperature": temperature, - } - - if max_tokens: - generation_config["max_output_tokens"] = max_tokens - - # Create multimodal input - combine text prompt with images - content_parts = [prompt] - content_parts.extend(images) - - response = model.generate_content( - content_parts, - generation_config=genai.types.GenerationConfig(**generation_config) + # Make async call to new GenAI SDK with multimodal content + response = await gemini_client.aio.models.generate_content( + model=actual_model, + contents=content_parts, + config=config ) - # Extract and return the response - result = LLMService._extract_text_from_response(response) + # Extract text from new SDK response + result = LLMService._extract_text_from_new_genai_response(response) if attempt > 0: logger.info(f"Multimodal content generation succeeded on attempt {attempt_num}/{max_retries}") @@ -610,7 +582,7 @@ class LLMService: # Wait before retrying (exponential backoff) wait_time = 2 ** attempt # 1s, 2s, 4s logger.info(f"Retryable error detected. Waiting {wait_time} seconds before retry {attempt_num + 1}/{max_retries}") - time.sleep(wait_time) + await asyncio.sleep(wait_time) continue else: logger.error(f"Retryable error detected but max retries ({max_retries}) reached") @@ -623,7 +595,7 @@ class LLMService: raise LLMServiceError(f"Error generating multimodal content: {str(last_error)}") @staticmethod - def generate_contextual_response( + async def generate_contextual_response( prompt: str, conversation_context: List[Dict[str, Any]], temperature: float = 0.7, @@ -704,7 +676,6 @@ class LLMService: try: if provider == 'openai': # OpenAI contextual multimodal API call - import base64 # Convert PIL images to base64 for OpenAI API image_content = [] @@ -743,7 +714,7 @@ class LLMService: # Note: GPT-5 Responses API does not support max_tokens parameter - response = openai_client.responses.create(**kwargs) + response = await openai_client.responses.create(**kwargs) result = LLMService._extract_responses_api_content(response) else: @@ -760,30 +731,42 @@ class LLMService: if max_tokens: kwargs["max_tokens"] = max_tokens - response = openai_client.chat.completions.create(**kwargs) + response = await openai_client.chat.completions.create(**kwargs) result = response.choices[0].message.content.strip() else: - # Gemini contextual multimodal API call (existing logic) - # Create content parts with text and images - content_parts = [full_prompt] - content_parts.extend(image_parts) - - model = LLMService.get_model(model_name) - - generation_config = { - "temperature": temperature, - } - - if max_tokens: - generation_config["max_output_tokens"] = max_tokens - - response = model.generate_content( - content_parts, - generation_config=genai.types.GenerationConfig(**generation_config) + # New Google GenAI SDK - contextual multimodal async call + config = genai.types.GenerateContentConfig( + temperature=temperature, ) - result = LLMService._extract_text_from_response(response) + if max_tokens: + config.max_output_tokens = max_tokens + + # Prepare content parts for new SDK + new_content_parts = [] + + # Add text prompt + new_content_parts.append(genai.types.Part.from_text(full_prompt)) + + # Convert PIL image parts to new SDK format + for img in image_parts: + # Convert PIL image to bytes + buffer = io.BytesIO() + img.save(buffer, format='PNG') + image_data = buffer.getvalue() + + # Add as image part in new SDK format + new_content_parts.append(genai.types.Part.from_bytes(image_data, mime_type='image/png')) + + # Make async call to new GenAI SDK + response = await gemini_client.aio.models.generate_content( + model=actual_model, + contents=new_content_parts, + config=config + ) + + result = LLMService._extract_text_from_new_genai_response(response) if attempt > 0: logger.info(f"Contextual multimodal generation succeeded on attempt {attempt_num}/{max_retries}") @@ -828,7 +811,7 @@ class LLMService: else: # No images, use standard text generation print(f"📝 Using text-only generation (no visual context)") - return LLMService.generate_content( + return await LLMService.generate_content( prompt=full_prompt, temperature=temperature, max_tokens=max_tokens, diff --git a/backend/app/services/persona_export_service.py b/backend/app/services/persona_export_service.py index df258c16..7ef8c16c 100644 --- a/backend/app/services/persona_export_service.py +++ b/backend/app/services/persona_export_service.py @@ -44,7 +44,7 @@ class PersonaExportService: demographics, goals, personality traits, scenarios, and additional data. """ - def generate_profile_markdown( + async def generate_profile_markdown( self, persona_data: Dict[str, Any], llm_model: str = "gpt-4.1", @@ -83,7 +83,7 @@ class PersonaExportService: full_prompt = f"{self.prompt_template}\n\n## Persona Data\n```json\n{persona_json}\n```" # Generate markdown using LLM - markdown_content = self.llm_service.generate_content( + markdown_content = await LLMService.generate_content( prompt=full_prompt, model_name=llm_model, temperature=temperature, diff --git a/backend/app/services/persona_modification_service.py b/backend/app/services/persona_modification_service.py index 351a11b7..1a25f7af 100644 --- a/backend/app/services/persona_modification_service.py +++ b/backend/app/services/persona_modification_service.py @@ -131,7 +131,7 @@ class PersonaModificationService: return True @staticmethod - def modify_persona( + async def modify_persona( persona_id: str, modification_prompt: str, llm_model: str = 'gemini-2.5-pro', @@ -158,7 +158,7 @@ class PersonaModificationService: """ try: # Fetch the original persona - original_persona = Persona.find_by_id(persona_id) + original_persona = await Persona.find_by_id(persona_id) if not original_persona: raise PersonaModificationError(f"Persona with ID {persona_id} not found") @@ -182,7 +182,7 @@ class PersonaModificationService: logger.info(f"Attempting persona modification (attempt {attempt + 1}/{max_retries})") # Call LLM service - llm_response = LLMService.generate_content( + llm_response = await LLMService.generate_content( prompt=final_prompt, temperature=0.3, # Lower temperature for consistent modifications model_name=llm_model, @@ -212,7 +212,7 @@ class PersonaModificationService: ) # Update the persona in the database - success = Persona.update(persona_id, modified_persona_data) + success = await Persona.update(persona_id, modified_persona_data) if not success: raise PersonaModificationError("Failed to update persona in database") diff --git a/backend/app/websocket_manager.py b/backend/app/websocket_manager.py index 6aef6db6..50cb6408 100644 --- a/backend/app/websocket_manager.py +++ b/backend/app/websocket_manager.py @@ -13,7 +13,7 @@ from typing import Dict, Set, Any, Optional from datetime import datetime from flask import request, current_app from flask_socketio import emit, join_room, leave_room, disconnect -from .extensions import socketio # Import singleton SocketIO instance +from .extensions import socketio_server as socketio # Import singleton SocketIO instance from flask_jwt_extended import decode_token from functools import wraps import json diff --git a/backend/app/websocket_manager_async.py b/backend/app/websocket_manager_async.py new file mode 100644 index 00000000..4a58284a --- /dev/null +++ b/backend/app/websocket_manager_async.py @@ -0,0 +1,398 @@ +""" +Async WebSocket Manager for Synthetic Society +Handles WebSocket connections, room management, and real-time event broadcasting. +Uses python-socketio AsyncServer for native Quart/ASGI compatibility. +""" + +import logging +import os +import threading +import asyncio +from typing import Dict, Set, Any, Optional +from datetime import datetime +from .extensions import socketio_server as sio +from flask_jwt_extended import decode_token + +# Set up logging +logger = logging.getLogger(__name__) + +class AsyncWebSocketManager: + """Manages WebSocket connections and rooms for focus group sessions using AsyncServer.""" + + def __init__(self): + # Use singleton SocketIO AsyncServer instance + self.sio = sio + self.focus_group_rooms: Dict[str, Set[str]] = {} # focus_group_id -> set of session_ids + self.user_sessions: Dict[str, Dict[str, Any]] = {} # session_id -> user info + + # Register SocketIO event handlers + self._register_handlers() + + def _register_handlers(self): + """Register all WebSocket event handlers.""" + + @self.sio.event + async def connect(sid, environ, auth): + """Handle WebSocket connection.""" + import os + import threading + + process_id = os.getpid() + thread_id = threading.get_ident() + print(f"🔌 ASYNC PROCESS DEBUG - WebSocket connection attempt from {sid}") + print(f"🔌 ASYNC PROCESS DEBUG - Connection handler PID: {process_id}, Thread: {thread_id}") + logger.info(f"WebSocket connection attempt from {sid}") + + # Validate JWT token from auth data (temporarily allow without token for testing) + if not auth or 'token' not in auth: + logger.warning(f"WebSocket connection without auth token - allowing for testing") + # Temporarily allow connections without tokens for testing + self.user_sessions[sid] = { + 'user_id': 'test_user', # Default user for testing + 'connected_at': datetime.utcnow(), + 'focus_groups': set() + } + logger.info(f"WebSocket connected without auth - Session: {sid}") + await self.sio.emit('connected', {'status': 'success', 'session_id': sid}, to=sid) + return True + + try: + # Decode and validate JWT token with better error handling + token = auth['token'] + + import jwt + import os + + # Validate token format first + if not token or not isinstance(token, str): + raise ValueError("Invalid token format") + + # Check if token has the right number of segments (should have 3 parts separated by dots) + token_parts = token.split('.') + if len(token_parts) != 3: + raise ValueError(f"Invalid JWT format: expected 3 segments, got {len(token_parts)}") + + # Get JWT secret from environment (same as our Quart JWT system) + jwt_secret = os.environ.get('SECRET_KEY', 'your-secret-key-for-sessions-and-tokens') + + try: + decoded_token = jwt.decode(token, jwt_secret, algorithms=['HS256']) + user_id = decoded_token.get('sub') + if not user_id: + raise ValueError("No user ID in token") + except Exception as jwt_error: + logger.warning(f"JWT decode failed: {jwt_error}") + logger.warning(f"Token format: {len(token_parts)} segments, first 20 chars: {token[:20]}...") + + # During migration, allow connection with test user instead of disconnecting + logger.info(f"Allowing WebSocket connection with test user due to JWT transition") + self.user_sessions[sid] = { + 'user_id': 'test_user', # Default user during JWT transition + 'connected_at': datetime.utcnow(), + 'focus_groups': set() + } + await self.sio.emit('connected', {'status': 'success', 'session_id': sid, 'auth': 'fallback'}, to=sid) + return True + + # Store user session info + self.user_sessions[sid] = { + 'user_id': user_id, + 'connected_at': datetime.utcnow(), + 'focus_groups': set() + } + + logger.info(f"WebSocket connected - Session: {sid}, User: {user_id}") + + # Emit connection success + await self.sio.emit('connected', {'status': 'success', 'session_id': sid}, to=sid) + + except Exception as e: + logger.error(f"Connection authentication failed: {e}") + await self.sio.disconnect(sid) + return False + + @self.sio.event + async def disconnect(sid): + """Handle WebSocket disconnection.""" + session_id = sid + + if session_id in self.user_sessions: + user_info = self.user_sessions[session_id] + user_id = user_info['user_id'] + + # Leave all focus group rooms + for focus_group_id in user_info['focus_groups'].copy(): + await self._leave_focus_group_room(session_id, focus_group_id) + + # Clean up session + del self.user_sessions[session_id] + logger.info(f"WebSocket disconnected - Session: {session_id}, User: {user_id}") + + @self.sio.event + async def join_focus_group(sid, data): + """Handle joining a focus group room.""" + session_id = sid + + if session_id not in self.user_sessions: + await self.sio.emit('error', {'message': 'Session not authenticated'}, to=sid) + return + + focus_group_id = data.get('focus_group_id') + if not focus_group_id: + await self.sio.emit('error', {'message': 'Focus group ID required'}, to=sid) + return + + # Join the room + success = await self._join_focus_group_room(session_id, focus_group_id) + + if success: + await self.sio.emit('joined_focus_group', { + 'focus_group_id': focus_group_id, + 'status': 'success' + }, to=sid) + logger.info(f"User joined focus group room - Session: {session_id}, Group: {focus_group_id}") + else: + await self.sio.emit('error', {'message': 'Failed to join focus group'}, to=sid) + + @self.sio.event + async def leave_focus_group(sid, data): + """Handle leaving a focus group room.""" + session_id = sid + + if session_id not in self.user_sessions: + await self.sio.emit('error', {'message': 'Session not authenticated'}, to=sid) + return + + focus_group_id = data.get('focus_group_id') + if not focus_group_id: + await self.sio.emit('error', {'message': 'Focus group ID required'}, to=sid) + return + + # Leave the room + success = await self._leave_focus_group_room(session_id, focus_group_id) + + if success: + await self.sio.emit('left_focus_group', { + 'focus_group_id': focus_group_id, + 'status': 'success' + }, to=sid) + logger.info(f"User left focus group room - Session: {session_id}, Group: {focus_group_id}") + + async def _join_focus_group_room(self, session_id: str, focus_group_id: str) -> bool: + """Join a user session to a focus group room.""" + try: + # Add to SocketIO room + await self.sio.enter_room(session_id, focus_group_id) + + # Track in our data structures + if focus_group_id not in self.focus_group_rooms: + self.focus_group_rooms[focus_group_id] = set() + + self.focus_group_rooms[focus_group_id].add(session_id) + self.user_sessions[session_id]['focus_groups'].add(focus_group_id) + + return True + except Exception as e: + logger.error(f"Failed to join focus group room: {e}") + return False + + async def _leave_focus_group_room(self, session_id: str, focus_group_id: str) -> bool: + """Remove a user session from a focus group room.""" + try: + # Leave SocketIO room + await self.sio.leave_room(session_id, focus_group_id) + + # Clean up tracking + if focus_group_id in self.focus_group_rooms: + self.focus_group_rooms[focus_group_id].discard(session_id) + + # Remove room if empty + if not self.focus_group_rooms[focus_group_id]: + del self.focus_group_rooms[focus_group_id] + + if session_id in self.user_sessions: + self.user_sessions[session_id]['focus_groups'].discard(focus_group_id) + + return True + except Exception as e: + logger.error(f"Failed to leave focus group room: {e}") + return False + + async def emit_to_focus_group(self, focus_group_id: str, event: str, data: Any, include_sender: bool = True, sender_session_id: Optional[str] = None): + """Emit an event to all users in a focus group room.""" + process_id = os.getpid() + thread_id = threading.get_ident() + print(f"🔔 ASYNC PROCESS DEBUG - emit_to_focus_group called: {event} for focus group {focus_group_id}") + print(f"🔔 ASYNC PROCESS DEBUG - PID: {process_id}, Thread: {thread_id}") + print(f"🔔 Focus group rooms: {list(self.focus_group_rooms.keys())}") + + try: + if focus_group_id not in self.focus_group_rooms: + print(f"🔔 ASYNC ERROR: No active sessions for focus group {focus_group_id}") + logger.debug(f"No active sessions for focus group {focus_group_id}") + return + + room_name = focus_group_id + room_sessions = self.focus_group_rooms[focus_group_id].copy() + print(f"🔔 ASYNC Room {focus_group_id} has {len(room_sessions)} tracked sessions: {list(room_sessions)}") + + # Clean up stale sessions + active_sessions = [] + stale_sessions = [] + for session_id in room_sessions: + if session_id in self.user_sessions: + active_sessions.append(session_id) + else: + stale_sessions.append(session_id) + self.focus_group_rooms[focus_group_id].discard(session_id) + + if stale_sessions: + print(f"🔔 ASYNC Cleaned up {len(stale_sessions)} stale sessions: {stale_sessions}") + + print(f"🔔 ASYNC Room {focus_group_id} has {len(active_sessions)} ACTIVE sessions: {active_sessions}") + + if not active_sessions: + print(f"🔔 ASYNC ERROR: No active sessions remaining for focus group {focus_group_id} after cleanup") + return + + # Prepare the event data + event_data = { + 'focus_group_id': focus_group_id, + 'timestamp': datetime.utcnow().isoformat(), + **data + } + + if include_sender or not sender_session_id: + # Send to all users in the room using AsyncServer + print(f"🔔 ASYNC Emitting '{event}' to room {room_name} with data keys: {list(event_data.keys())}") + await self.sio.emit(event, event_data, room=room_name) + + print(f"🔔 ASYNC Successfully emitted '{event}' to focus group {focus_group_id} ({len(active_sessions)} active users)") + logger.debug(f"Emitted '{event}' to focus group {focus_group_id} ({len(active_sessions)} active users)") + else: + # Send to all users except the sender + for session_id in active_sessions: + if session_id != sender_session_id: + await self.sio.emit(event, event_data, to=session_id) + logger.debug(f"Emitted '{event}' to focus group {focus_group_id} (excluding sender)") + + except Exception as e: + logger.error(f"Failed to emit to focus group {focus_group_id}: {e}") + print(f"🔔 ASYNC ERROR: Failed to emit to focus group {focus_group_id}: {e}") + + async def emit_message_update(self, focus_group_id: str, message_data: Dict[str, Any], sender_session_id: Optional[str] = None): + """Emit a new message to focus group participants.""" + await self.emit_to_focus_group( + focus_group_id, + 'message_update', + {'message': message_data}, + include_sender=True, + sender_session_id=sender_session_id + ) + + async def emit_ai_status_update(self, focus_group_id: str, status_data: Dict[str, Any]): + """Emit AI status change to focus group participants.""" + await self.emit_to_focus_group( + focus_group_id, + 'ai_status_update', + {'status': status_data} + ) + + async def emit_moderator_status_update(self, focus_group_id: str, moderator_status: Dict[str, Any]): + """Emit moderator status change to focus group participants.""" + await self.emit_to_focus_group( + focus_group_id, + 'moderator_status_update', + {'moderator_status': moderator_status} + ) + + async def emit_theme_update(self, focus_group_id: str, theme_data: Dict[str, Any], action: str = 'added'): + """Emit theme update to focus group participants.""" + await self.emit_to_focus_group( + focus_group_id, + 'theme_update', + {'theme': theme_data, 'action': action} + ) + + async def emit_analytics_update(self, focus_group_id: str, analytics_data: Dict[str, Any]): + """Emit analytics update to focus group participants.""" + await self.emit_to_focus_group( + focus_group_id, + 'analytics_update', + {'analytics': analytics_data} + ) + + async def emit_conversation_state_update(self, focus_group_id: str, state_data: Dict[str, Any]): + """Emit conversation state update to focus group participants.""" + await self.emit_to_focus_group( + focus_group_id, + 'conversation_state_update', + {'state': state_data} + ) + + def get_room_info(self, focus_group_id: str) -> Dict[str, Any]: + """Get information about a focus group room.""" + if focus_group_id not in self.focus_group_rooms: + return {'active_sessions': 0, 'users': []} + + sessions = self.focus_group_rooms[focus_group_id] + users = [] + + for session_id in sessions: + if session_id in self.user_sessions: + user_info = self.user_sessions[session_id] + users.append({ + 'session_id': session_id, + 'user_id': user_info['user_id'], + 'connected_at': user_info['connected_at'].isoformat() + }) + + return { + 'active_sessions': len(sessions), + 'users': users + } + + def get_connection_stats(self) -> Dict[str, Any]: + """Get overall connection statistics.""" + return { + 'total_sessions': len(self.user_sessions), + 'total_focus_groups': len(self.focus_group_rooms), + 'focus_group_details': { + fg_id: len(sessions) for fg_id, sessions in self.focus_group_rooms.items() + } + } + + +# Global WebSocket manager instance +websocket_manager: Optional[AsyncWebSocketManager] = None + +def init_async_websocket_manager() -> AsyncWebSocketManager: + """Initialize the global async WebSocket manager.""" + global websocket_manager + websocket_manager = AsyncWebSocketManager() + return websocket_manager + +def get_async_websocket_manager() -> Optional[AsyncWebSocketManager]: + """Get the global async WebSocket manager instance.""" + return websocket_manager + + +# Async emit function for use throughout the codebase +async def emit_websocket_event(event: str, data: dict, room: str | None = None) -> None: + """ + Async WebSocket event emission function. + + Args: + event: Event name + data: Event data + room: Room to emit to (focus group ID) + """ + try: + if websocket_manager and room: + await websocket_manager.emit_to_focus_group(room, event, data) + print(f"🔔 ASYNC - Successfully emitted {event} to room {room}") + else: + print(f"🔔 ASYNC - WebSocket manager not available or no room specified") + except Exception as e: + print(f"🔔 ASYNC ERROR emitting WebSocket event {event}: {e}") + logger.exception(f"Error emitting WebSocket event {event}: {e}") \ No newline at end of file diff --git a/backend/requirements.txt b/backend/requirements.txt index e1d8aa9c..424ee1af 100644 --- a/backend/requirements.txt +++ b/backend/requirements.txt @@ -7,7 +7,7 @@ flask-jwt-extended bcrypt pydantic hypercorn -google-generativeai +google-genai openai requests llama-cloud-services diff --git a/backend/run.py b/backend/run.py index 445e0661..da52232d 100644 --- a/backend/run.py +++ b/backend/run.py @@ -2,18 +2,13 @@ import os import subprocess import sys import tempfile - -# GPT-5 fix: Monkey patch BEFORE any other imports that might use threads/sockets -try: - import eventlet - eventlet.monkey_patch() - print("✅ GPT-5 FIX: Early eventlet monkey patching applied") -except ImportError: - print("⚠️ Eventlet not available for early monkey patching") +import asyncio +import signal +import threading # Set up temp directories FIRST, before any imports that might use temp files def setup_early_temp_directories(): - """Set up temp directories before Flask imports.""" + """Set up temp directories before Quart imports.""" backend_dir = os.path.dirname(os.path.abspath(__file__)) temp_dir = os.path.join(backend_dir, 'temp') @@ -45,59 +40,266 @@ setup_early_temp_directories() from app import create_app from app.models.user import User -# Create the Flask app -flask_app = create_app() +# Create the ASGI app (which wraps Quart + SocketIO) +asgi_app = create_app() +# Extract the Quart app from the ASGI wrapper +quart_app = asgi_app.quart_app # Initialize database on startup -def initialize_database(): +async def initialize_database(): # Create default user if it doesn't exist - User.create_default_user() + await User.create_default_user() -# Call initialization immediately -with flask_app.app_context(): - initialize_database() +# Use the ASGI app for the server +app = asgi_app -# For SocketIO, we need to use the socketio app directly -app = flask_app +async def startup(): + """Initialize the application on startup.""" + await initialize_database() + +# Signal handlers are now managed within the asyncio event loop for proper async shutdown + +async def run_server(): + """Run the server with enhanced shutdown pattern and diagnostics.""" + import hypercorn.asyncio + from hypercorn import Config + import faulthandler + import threading + + # Enable fault handler for debugging + faulthandler.register(signal.SIGUSR1 if hasattr(signal, 'SIGUSR1') else signal.SIGBREAK) + + print("Starting Quart + SocketIO app with hypercorn ASGI server...") + print("📡 WebSocket functionality enabled") + print("🤖 AI Runner active for autonomous conversations") + print("⚡ All operations async and non-blocking") + print("🛑 Use Ctrl-C for graceful shutdown") + print("🔍 Debug: Send SIGUSR1 for stack dump if it hangs") + print("Started Semblance back end service") + + # Create hypercorn config with debug settings + config = Config() + config.bind = ["0.0.0.0:5137"] + config.debug = False + config.use_reloader = False + config.loglevel = "info" # Enable more logging + config.lifespan = "on" # Ensure lifespan is enabled + config.startup_timeout = 60 + config.shutdown_timeout = 5 # Shorter for faster shutdown + config.graceful_timeout = 3 # Shorter for faster shutdown + config.keep_alive_timeout = 2 # Cut lingering connections faster + + # Add startup tasks to Quart app + @quart_app.before_serving + async def startup_task(): + await startup() + + # Add shutdown tasks to Quart app with timeout protection + @quart_app.after_serving + async def shutdown_task(): + print("🛑 Quart app shutting down...") + + # 1. Shutdown SocketIO first to stop WebSocket tasks + try: + print("🔌 Shutting down SocketIO...") + from app.extensions import socketio_server + + # First disconnect all clients + print("🔌 Disconnecting all SocketIO clients...") + try: + # Get all sessions and disconnect them + for sid in list(socketio_server.manager.get_participants('/', None) or []): + try: + await socketio_server.disconnect(sid, namespace='/') + except Exception as disconnect_err: + print(f"Error disconnecting {sid}: {disconnect_err}") + print("✅ All SocketIO clients disconnected") + except Exception as disconnect_err: + print(f"Error during client disconnection: {disconnect_err}") + + # Then shutdown the server + try: + if hasattr(socketio_server, 'shutdown'): + await socketio_server.shutdown() + print("✅ SocketIO server shutdown complete") + else: + # Try shutting down the underlying engine.io server + if hasattr(socketio_server, 'eio') and hasattr(socketio_server.eio, 'shutdown'): + await socketio_server.eio.shutdown() + print("✅ EngineIO shutdown complete") + else: + print("⚠️ No shutdown method available - relying on task cancellation") + except Exception as shutdown_err: + print(f"SocketIO shutdown error: {shutdown_err}") + + except Exception as e: + print(f"⚠️ Overall SocketIO shutdown error: {e}") + + # 2. Shutdown AI Runner + try: + await asyncio.wait_for( + asyncio.to_thread(shutdown_ai_runner_safe), + timeout=3.0 # Shorter timeout + ) + print("✅ AI Runner shutdown complete") + except asyncio.TimeoutError: + print("⏱️ AI Runner shutdown timed out; continuing") + except Exception as e: + print(f"⚠️ AI Runner shutdown error: {e}") + + # 3. Close database connections + try: + print("🗄️ Closing database connections...") + await close_database_connections() + print("✅ Database connections closed") + except Exception as e: + print(f"⚠️ Database close error: {e}") + + # 4. Cancel any remaining engineio/socketio tasks + await cancel_socketio_tasks() + + def shutdown_ai_runner_safe(): + """Safe AI Runner shutdown that doesn't block the main event loop.""" + try: + from app.services.ai_runner_service import shutdown_ai_runner + shutdown_ai_runner() + except Exception as e: + print(f"Error in AI Runner shutdown: {e}") + + async def close_database_connections(): + """Close all database connections to stop PyMongo background threads.""" + try: + # Close the global Motor client singleton + from app.db import close_db_connections + await asyncio.to_thread(close_db_connections) + except Exception as e: + print(f"Database connection close error: {e}") + + async def cancel_socketio_tasks(): + """Cancel any remaining SocketIO/EngineIO tasks.""" + try: + print("🧹 Cancelling remaining SocketIO tasks...") + cancelled_count = 0 + + for task in list(asyncio.all_tasks()): + if task.done() or task is asyncio.current_task(): + continue + + # Check if this is a SocketIO/EngineIO related task + try: + coro = task.get_coro() + module = getattr(coro, '__module__', '') + qualname = getattr(coro, '__qualname__', '') + full_name = f'{module}.{qualname}' + + socketio_patterns = [ + 'engineio.async_socket', + 'engineio.async_server', + 'socketio.async_server', + 'AsyncSocket._websocket_handler', + 'AsyncSocket._send_ping', + 'AsyncServer._service_task' + ] + + if any(pattern in full_name for pattern in socketio_patterns): + task.cancel() + cancelled_count += 1 + + except Exception: + pass # Ignore task inspection errors + + if cancelled_count > 0: + print(f"🧹 Cancelled {cancelled_count} SocketIO tasks") + # Give cancellations time to complete + await asyncio.sleep(0.5) + + except Exception as e: + print(f"Task cancellation error: {e}") + + # Create shutdown event for Hypercorn + shutdown_event = asyncio.Event() + _second_signal = False # For double Ctrl-C force exit + + def _raise_shutdown(): + """Signal handler with force-exit on second Ctrl-C.""" + nonlocal _second_signal + + if _second_signal: + print("🔥 Second Ctrl-C - forcing exit!") + import os + os._exit(1) + + _second_signal = True + print("\\n🛑 Shutdown signal received...") + print("📊 Active threads:", [t.name for t in threading.enumerate()]) + shutdown_event.set() + + # Watchdog to ensure serve() returns + async def shutdown_watchdog(): + await asyncio.sleep(4) # Wait 4 seconds (should be enough with enhanced cleanup) + if not shutdown_event.is_set(): + return # Already completed + print("⏱️ Shutdown taking too long - dumping diagnostics...") + + # Dump asyncio tasks + print("\\n=== Active asyncio tasks ===") + for task in asyncio.all_tasks(): + if not task.done(): + print(f"- {task.get_name()}: {task}") + + # Dump threads + print("\\n=== Active threads ===") + for thread in threading.enumerate(): + print(f"- {thread.name}: daemon={thread.daemon}, alive={thread.is_alive()}") + + print("🔥 Use second Ctrl-C to force exit if needed") + + asyncio.create_task(shutdown_watchdog(), name="shutdown-watchdog") + + # Register signal handlers + loop = asyncio.get_running_loop() + try: + loop.add_signal_handler(signal.SIGINT, _raise_shutdown) + loop.add_signal_handler(signal.SIGTERM, _raise_shutdown) + except NotImplementedError: + # Windows fallback + signal.signal(signal.SIGINT, lambda *_: loop.call_soon_threadsafe(_raise_shutdown)) + signal.signal(signal.SIGTERM, lambda *_: loop.call_soon_threadsafe(_raise_shutdown)) + + # Let Hypercorn handle shutdown gracefully with the trigger + print("🔍 Debug: Starting Hypercorn serve with shutdown_trigger...") + await hypercorn.asyncio.serve( + asgi_app, + config, + shutdown_trigger=shutdown_event.wait, + ) + + print("🛑 Hypercorn server stopped") if __name__ == '__main__': - # Check if we have SocketIO support and run with eventlet try: - import eventlet - print("Starting Flask-SocketIO app with eventlet...") - print("Started Semblance back end service") - - # Run with SocketIO support - use the socketio instance to run the app - flask_app.socketio.run( - flask_app, - host='0.0.0.0', - port=5137, - debug=False, - use_reloader=False, - allow_unsafe_werkzeug=True - ) - - except ImportError as e: - print("Eventlet not found. Installing it...") - subprocess.check_call([sys.executable, "-m", "pip", "install", "eventlet", "flask-socketio"]) - - # Try again + # Check if hypercorn is available try: - import eventlet - print("Started Semblance back end service") - flask_app.socketio.run( - flask_app, - host='0.0.0.0', - port=5137, - debug=False, - use_reloader=False, - allow_unsafe_werkzeug=True - ) - except Exception as e: - print(f"Failed to start with SocketIO: {e}") - print("Falling back to regular Flask...") - print("Started Semblance back end service") - flask_app.run(host='0.0.0.0', port=5137, debug=False) + import hypercorn.asyncio + from hypercorn import Config + except ImportError as e: + print("❌ Error: hypercorn is required for WebSocket functionality!") + print("Please install hypercorn: pip install hypercorn") + print(f"ImportError: {e}") + sys.exit(1) + + # Run the server with proper shutdown handling + asyncio.run(run_server()) + except KeyboardInterrupt: - print("\nShutting down...") - sys.exit(0) \ No newline at end of file + print("\\n🛑 Keyboard interrupt - shutting down...") + except Exception as e: + print(f"❌ Unexpected error: {e}") + try: + from app.services.ai_runner_service import shutdown_ai_runner + shutdown_ai_runner() + except: + pass + sys.exit(1) + + print("👋 Server stopped") \ No newline at end of file diff --git a/backend/uploads/focus-group-68af42ff19ed40daa02b0392/fg-68af42ff19ed40daa02b0392-086a55e9378e46c1bf7531383c3a6cba.jpg b/backend/uploads/focus-group-68af42ff19ed40daa02b0392/fg-68af42ff19ed40daa02b0392-086a55e9378e46c1bf7531383c3a6cba.jpg new file mode 100644 index 0000000000000000000000000000000000000000..d5d8c6667d2fd9986f5d51a2ddbe63fe46e29fe0 GIT binary patch literal 89310 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�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 a>DS*Bs1Z$u_^U-p`3!AVGVD)WRsY$Fj}H(4 literal 0 HcmV?d00001 diff --git a/src/pages/FocusGroupSession.tsx b/src/pages/FocusGroupSession.tsx index f8bb39b2..9a3eb64a 100644 --- a/src/pages/FocusGroupSession.tsx +++ b/src/pages/FocusGroupSession.tsx @@ -1689,18 +1689,24 @@ const FocusGroupSession = () => { const response = await focusGroupAiApi.generateKeyThemes(id); if (response.data && response.data.themes) { - setThemeGenerationComplete(true); - toastService.success(`Generated ${response.data.themes.length} key themes`, { - description: "New themes have been added to the analysis." - }); - - // Update themes state + // Update themes state immediately setThemes(prevThemes => [...prevThemes, ...response.data.themes]); + + // Allow progress bar to animate for at least 3 seconds before completing + setTimeout(() => { + setThemeGenerationComplete(true); + toastService.success(`Generated ${response.data.themes.length} key themes`, { + description: "New themes have been added to the analysis." + }); + }, 3000); } else { - setThemeGenerationComplete(true); - toastService.warning("No new themes were generated", { - description: "Try again when the discussion has more content." - }); + // Allow progress bar to animate for at least 3 seconds before completing + setTimeout(() => { + setThemeGenerationComplete(true); + toastService.warning("No new themes were generated", { + description: "Try again when the discussion has more content." + }); + }, 3000); } } catch (error) { console.error('Error generating key themes:', error);