11from fastapi import APIRouter , HTTPException , Depends , Request , BackgroundTasks
22from sqlalchemy .ext .asyncio import AsyncSession
3- from typing import Dict , List , Any
3+ from sqlalchemy .future import select
4+ from core import database , models
5+ import auth
6+ from config .settings import settings
7+ from typing import Dict , List , Any , Optional
48from pydantic import BaseModel
59import asyncio
610import json
11+ import hashlib
712
813from services .form .parser import get_form_schema , create_template
914from core .dependencies import (
1621from services .ai .gemini import GeminiService , SmartFormFillerChain
1722from services .form .conventions import get_form_schema as get_schema
1823from services .ai .smart_autofill import get_smart_autofill
19- from core import database , models
20- import auth
21- from config .settings import settings
22- from sqlalchemy .future import select
2324from services .ai .profile .service import generate_profile_background
25+ from utils .cache import get_cached , set_cached
26+ from utils .ws import emit_field_filled
27+ from routers .websocket import manager
28+ from utils .cache import get_cached , set_cached
29+ import hashlib
2430
2531# --- Pydantic Models ---
2632class ScrapeRequest (BaseModel ):
@@ -33,16 +39,16 @@ class VoiceProcessRequest(BaseModel):
3339
3440class FormFillRequest (BaseModel ):
3541 url : str
36- form_data : Dict [str , str ]
42+ form_data : Dict [str , Any ]
3743
3844class FormSubmitRequest (BaseModel ):
3945 url : str
40- form_data : Dict [str , str ]
46+ form_data : Dict [str , Any ]
4147 form_schema : List [Dict [str , Any ]]
4248 use_cdp : bool = False # If True, connect to user's browser via Chrome DevTools Protocol
4349
4450class ConversationalFlowRequest (BaseModel ):
45- extracted_fields : Dict [str , str ]
51+ extracted_fields : Dict [str , Any ]
4652 form_schema : List [Dict [str , Any ]]
4753
4854class ComprehensiveFormRequest (BaseModel ):
@@ -52,6 +58,8 @@ class ComprehensiveFormRequest(BaseModel):
5258class MagicFillRequest (BaseModel ):
5359 form_schema : List [Dict [str , Any ]]
5460 user_profile : Dict [str , Any ] = {} # Optional extra profile data
61+ form_url : Optional [str ] = None
62+ form_url : str = "" # Required for caching
5563
5664router = APIRouter (tags = ["Forms & Automation" ])
5765
@@ -273,7 +281,7 @@ async def process_voice(
273281 """Process voice input with LLM enhancement and smart formatting."""
274282 try :
275283 # Process with LLM
276- result = voice_processor .process_voice_input (
284+ result = await voice_processor .process_voice_input (
277285 data .transcript ,
278286 data .field_info ,
279287 data .form_context
@@ -399,30 +407,32 @@ async def fill_form(data: FormFillRequest):
399407async def magic_fill (
400408 data : MagicFillRequest ,
401409 request : Request ,
410+ background_tasks : BackgroundTasks ,
402411 db : AsyncSession = Depends (database .get_db ),
403412 gemini_service : GeminiService = Depends (get_gemini_service )
404413):
405414 """
406- 🪄 Magic Fill - Intelligently pre-fill entire form from user profile.
407-
408- Uses LangChain + Gemini to map user data to form fields.
415+ Magic Fill - Intelligently pre-fill entire form from user profile.
416+ Order: instant fill -> parallel AI -> stream progress via WebSocket.
409417 """
410418 try :
411- # 1. Get user profile from database if authenticated
412- user_profile = dict (data .user_profile ) # Start with request data
413-
419+ client_id = request .headers .get ("X-Client-ID" ) or request .query_params .get ("client_id" )
420+ cache_user_id = "anonymous"
421+ user_profile = dict (data .user_profile )
422+
423+ # Merge DB profile + history when authenticated
414424 auth_header = request .headers .get ('Authorization' )
415425 if auth_header and auth_header .startswith ('Bearer ' ):
416- token = auth_header .split (' ' )[1 ]
417426 try :
427+ token = auth_header .split (' ' )[1 ]
418428 payload = auth .decode_access_token (token )
419429 if payload :
420430 email = payload .get ("sub" )
421431 if email :
422432 result = await db .execute (select (models .User ).filter (models .User .email == email ))
423433 user = result .scalars ().first ()
424434 if user :
425- # Merge DB profile with request profile (request takes precedence )
435+ cache_user_id = str ( user . id )
426436 db_profile = {
427437 "first_name" : user .first_name ,
428438 "last_name" : user .last_name ,
@@ -434,23 +444,28 @@ async def magic_fill(
434444 "fullname" : f"{ user .first_name } { user .last_name } " .strip ()
435445 }
436446 user_profile = {** db_profile , ** user_profile }
437-
438- # 🧠 Merge with learned history (fills gaps like Company, Title, specific addresses)
439447 try :
440448 history_profile = await get_smart_autofill ().get_profile_from_history (str (user .id ))
441449 if history_profile :
442- print (f"🧠 Merging { len (history_profile )} learned fields from history" )
443- # Base = History, Overlay = Current Result (DB + Request)
444- # We want History to fill gaps, so History is the base.
445- # But wait, user_profile already has DB+Request.
446- # So: final = {**history_profile, **user_profile}
447- # This ensures DB/Request values (verified/explicit) override History.
448450 user_profile = {** history_profile , ** user_profile }
449451 except Exception as e :
450- print (f"⚠️ History merge failed: { e } " )
452+ print (f"Warning: history merge failed: { e } " )
451453 except Exception as e :
452- print (f"⚠️ Auth lookup failed: { e } " )
453-
454+ print (f"Warning: auth lookup failed: { e } " )
455+
456+ # Cache check (per user + form URL, TTL 24h)
457+ cache_key = None
458+ if data .form_url :
459+ url_hash = hashlib .md5 (data .form_url .encode ()).hexdigest ()
460+ cache_key = f"magic_fill:{ cache_user_id } :{ url_hash } "
461+ cached_result = await get_cached (cache_key )
462+ if cached_result :
463+ print (f"Magic Fill cache hit for { data .form_url } " )
464+ cached_dict = cached_result if isinstance (cached_result , dict ) else json .loads (cached_result )
465+ for fname , val in cached_dict .get ("filled" , {}).items ():
466+ await emit_field_filled (fname , val , client_id )
467+ return {** cached_dict , "cached" : True }
468+
454469 if not user_profile :
455470 return {
456471 "success" : False ,
@@ -459,22 +474,27 @@ async def magic_fill(
459474 "unfilled" : [],
460475 "summary" : "Please sign in to use Magic Fill"
461476 }
462-
463- # 2. Call Smart Form Filler Chain
477+
464478 if not gemini_service :
465479 raise HTTPException (status_code = 500 , detail = "Gemini service not available" )
466-
480+
467481 filler = SmartFormFillerChain (gemini_service .llm )
482+
483+ async def progress_cb (field_id : str , value : Any ):
484+ await emit_field_filled (field_id , value , client_id )
485+
468486 result = await filler .fill (
469487 user_profile = user_profile ,
470488 form_schema = data .form_schema ,
471- min_confidence = 0.5
489+ min_confidence = 0.5 ,
490+ progress_cb = progress_cb
472491 )
473-
474- print (f"✨ Magic Fill: { len (result .get ('filled' , {}))} fields filled" )
475-
492+
493+ if cache_key and result .get ("success" ):
494+ await set_cached (cache_key , result , ttl = 86400 )
495+
476496 return result
477-
497+
478498 except Exception as e :
479499 import traceback
480500 traceback .print_exc ()
0 commit comments