1717 get_embedder ,
1818 get_qdrant ,
1919 sanitize_error_message ,
20+ get_persisted_workspace_files ,
2021 get_persisted_document ,
2122 save_persisted_document ,
2223)
@@ -42,24 +43,26 @@ class ExtractRequest(BaseModel):
4243@router .post ("/api/extract-jsonld-stream" )
4344async def extract_jsonld_stream (req : ExtractRequest ):
4445 file_name = req .file_name
45- if file_name not in WORKSPACE_FILES :
46+ workspace_files = get_persisted_workspace_files ()
47+ if file_name not in workspace_files :
4648 raise HTTPException (status_code = 404 , detail = "File belum diunggah ke workspace." )
4749 if req .base_url and not is_safe_custom_endpoint (req .base_url ):
4850 raise HTTPException (status_code = 400 , detail = "Disallowed or unsafe custom base_url parameter." )
4951
52+ loop = asyncio .get_running_loop ()
53+ log_queue = asyncio .Queue ()
54+
5055 # SSE Generator for real-time extraction logs
5156 async def event_generator ():
52- log_queue = asyncio .Queue ()
53-
54- def sync_logger (msg : str ):
57+ def thread_safe_logger (msg : str ):
5558 clean_msg = sanitize_error_message (msg )
56- log_queue .put_nowait ( {"type" : "log" , "message" : clean_msg })
59+ loop . call_soon_threadsafe ( log_queue .put_nowait , {"type" : "log" , "message" : clean_msg })
5760
5861 async def run_extraction ():
5962 try :
6063 # Ensure chunks exist safely with workspace lock
6164 async with _WORKSPACE_LOCK :
62- fpath = WORKSPACE_FILES [file_name ]
65+ fpath = workspace_files [file_name ]
6366 file_chunks = [c for c in EXTRACTED_CHUNKS if c .get ("metadata" , {}).get ("source" ) == file_name ]
6467 if not file_chunks :
6568 file_chunks = parse_document (fpath , file_name )
@@ -77,7 +80,7 @@ async def run_extraction():
7780 chunks = file_chunks ,
7881 qdrant_client = qdrant if IS_INDEXED else None ,
7982 embedder = embedder ,
80- progress_callback = sync_logger ,
83+ progress_callback = thread_safe_logger ,
8184 llm_provider = req .llm_provider ,
8285 llm_model = req .llm_model ,
8386 api_key = req .api_key ,
@@ -87,16 +90,16 @@ async def run_extraction():
8790 existing_record = get_persisted_document (file_name )
8891 if existing_record :
8992 final_res = merge_and_enrich_json_ld (existing_record , res )
90- sync_logger ("🔄 [Database Optimization] Menggabungkan field & struktur baru dengan data terverifikasi sebelumnya secara non-destruktif." )
93+ thread_safe_logger ("🔄 [Database Optimization] Menggabungkan field & struktur baru dengan data terverifikasi sebelumnya secara non-destruktif." )
9194 else :
9295 final_res = res
9396
9497 async with _WORKSPACE_LOCK :
9598 save_persisted_document (file_name , final_res )
96- await log_queue . put ( {"type" : "complete" , "result" : final_res })
99+ loop . call_soon_threadsafe ( log_queue . put_nowait , {"type" : "complete" , "result" : final_res })
97100 except Exception as e :
98101 clean_err = sanitize_error_message (str (e ))
99- await log_queue . put ( {"type" : "error" , "error" : clean_err })
102+ loop . call_soon_threadsafe ( log_queue . put_nowait , {"type" : "error" , "error" : clean_err })
100103
101104 # Launch extraction task
102105 task = asyncio .create_task (run_extraction ())
@@ -117,7 +120,13 @@ async def run_extraction():
117120 pass
118121 raise
119122
120- return StreamingResponse (event_generator (), media_type = "text/event-stream" )
123+ headers = {
124+ "Cache-Control" : "no-cache" ,
125+ "Connection" : "keep-alive" ,
126+ "X-Accel-Buffering" : "no" ,
127+ "Content-Type" : "text/event-stream; charset=utf-8" ,
128+ }
129+ return StreamingResponse (event_generator (), media_type = "text/event-stream" , headers = headers )
121130
122131
123132@router .get ("/api/jsonld/{file_name}" )
0 commit comments