33import logging
44import docker
55from fastapi import FastAPI , HTTPException
6- from fastapi .responses import HTMLResponse
6+ from fastapi .responses import HTMLResponse , StreamingResponse
77from fastapi .staticfiles import StaticFiles
88from pydantic import BaseModel
99from dotenv import load_dotenv
10+ from fastapi .middleware .cors import CORSMiddleware
11+ import json
1012
1113# Import the new agentic workflow orchestrator
1214from backend .robot_generator import run_agentic_workflow
@@ -26,114 +28,112 @@ class Query(BaseModel):
2628# --- FastAPI App ---
2729app = FastAPI ()
2830
29- # --- Static Files and Root Endpoint ---
30- FRONTEND_DIR = os .path .join (os .path .dirname (os .path .abspath (__file__ )), ".." , "frontend" )
31- app .mount ("/static" , StaticFiles (directory = FRONTEND_DIR ), name = "static" )
32-
33- @app .get ("/" , response_class = HTMLResponse )
34- async def read_root ():
35- """Serves the main index.html file."""
36- with open (os .path .join (FRONTEND_DIR , "index.html" )) as f :
37- return HTMLResponse (content = f .read (), status_code = 200 )
31+ app .add_middleware (
32+ CORSMiddleware ,
33+ allow_origins = ["*" ],
34+ allow_credentials = True ,
35+ allow_methods = ["*" ],
36+ allow_headers = ["*" ],
37+ )
3838
3939# --- Main Endpoint ---
40- @app .post ('/generate-and-run' )
41- async def generate_and_run (query : Query ):
42- user_query = query .query
43- if not user_query :
44- raise HTTPException (status_code = 400 , detail = "Query not provided" )
45-
46- # Determine the model provider and model name
47- model_provider = os .getenv ("MODEL_PROVIDER" , "online" ).lower ()
48- if model_provider == "local" :
49- model_name = os .getenv ("LOCAL_MODEL" , "llama3" )
50- logging .info (f"Using local model provider: { model_name } " )
51- else :
52- # Fallback to online model, using the request body if env var is not set
53- model_name = os .getenv ("ONLINE_MODEL" , query .model )
54- logging .info (f"Using online model provider: { model_name } " )
55-
56-
57- logging .info (f"Received query: '{ user_query } ' for model '{ model_name } '. Starting agentic workflow..." )
40+ async def stream_generate_and_run (user_query : str , model_name : str ):
41+ """Generator function to stream logs and results."""
5842
59- # Call our new agentic workflow to generate the code
60- robot_code = run_agentic_workflow (
61- natural_language_query = user_query ,
62- model_provider = model_provider ,
63- model_name = model_name
64- )
43+ # Stage 1: Generating Code
44+ yield f"data: { json .dumps ({'stage' : 'generation' , 'status' : 'running' , 'message' : 'AI agents are analyzing your query...' })} \n \n "
6545
66- if not robot_code :
67- logging .error ("Agentic workflow failed to generate Robot Framework code." )
68- raise HTTPException (status_code = 500 , detail = "Failed to generate valid Robot Framework code from the query." )
46+ try :
47+ model_provider = os .getenv ("MODEL_PROVIDER" , "online" ).lower ()
48+ robot_code = run_agentic_workflow (
49+ natural_language_query = user_query ,
50+ model_provider = model_provider ,
51+ model_name = model_name
52+ )
53+ if not robot_code :
54+ raise Exception ("Agentic workflow failed to generate Robot Framework code." )
6955
70- logging .info (f"Generated Robot Code:\n { robot_code } " )
71- logging .info ("Attempting to run test in Docker..." )
56+ yield f"data: { json .dumps ({'stage' : 'generation' , 'status' : 'complete' , 'message' : 'Code generation complete.' , 'robot_code' : robot_code })} \n \n "
57+ except Exception as e :
58+ logging .error (f"Error during code generation: { e } " )
59+ yield f"data: { json .dumps ({'stage' : 'generation' , 'status' : 'error' , 'message' : str (e )})} \n \n "
60+ return
7261
73- # --- Docker Execution ---
74- robot_tests_dir = os .path .join (os .path .dirname (os .path .abspath (__file__ )), '..' , 'robot_tests' )
62+ # Stage 2: Docker Execution
63+ run_id = str (uuid .uuid4 ())
64+ robot_tests_dir = os .path .join (os .path .dirname (os .path .abspath (__file__ )), '..' , 'robot_tests' , run_id )
7565 os .makedirs (robot_tests_dir , exist_ok = True )
76-
77- # Use a unique filename for this test run
78- test_filename = f"test_{ uuid .uuid4 ()} .robot"
66+ test_filename = "test.robot"
7967 test_filepath = os .path .join (robot_tests_dir , test_filename )
80-
8168 with open (test_filepath , 'w' ) as f :
8269 f .write (robot_code )
8370
8471 try :
8572 client = docker .from_env ()
8673 image_tag = "robot-test-runner:latest"
74+ dockerfile_path = os .path .join (os .path .dirname (os .path .abspath (__file__ )), '..' , 'robot_tests' )
8775
88- logging . info ( f"Building Docker image ' { image_tag } ' (if not already cached)..." )
89- client . images . build ( path = robot_tests_dir , tag = image_tag , rm = True )
90- logging . info ( "Docker image build process completed." )
76+ # Stage 2a: Building Docker Image
77+ yield f"data: { json . dumps ({ 'stage' : 'execution' , 'status' : 'running' , 'message' : 'Building container image for test execution...' }) } \n \n "
78+ client . images . build ( path = dockerfile_path , tag = image_tag , rm = True )
9179
92- logging .info (f"Running Docker container with test: { test_filename } " )
93-
94- # Command to execute inside the container.
95- # We specify an output directory so that logs are written to the mounted volume.
96- robot_command = [
97- "robot" ,
98- "--outputdir" , "/app/robot_tests" ,
99- f"robot_tests/{ test_filename } "
100- ]
101-
102- # Run the container with the volume mounted as read-write.
80+ # Stage 2b: Running Docker Container
81+ yield f"data: { json .dumps ({'stage' : 'execution' , 'status' : 'running' , 'message' : 'Executing test inside the container...' })} \n \n "
82+ robot_command = ["robot" , "--outputdir" , f"/app/robot_tests/{ run_id } " , f"robot_tests/{ run_id } /{ test_filename } " ]
10383 container_logs = client .containers .run (
10484 image = image_tag ,
10585 command = robot_command ,
106- volumes = {os .path .abspath (robot_tests_dir ): {'bind' : '/app/robot_tests' , 'mode' : 'rw' }},
86+ volumes = {os .path .abspath (os . path . join ( robot_tests_dir , '..' ) ): {'bind' : '/app/robot_tests' , 'mode' : 'rw' }},
10787 working_dir = "/app" ,
10888 stderr = True ,
10989 stdout = True ,
11090 detach = False ,
11191 auto_remove = True
11292 )
11393 logs = container_logs .decode ('utf-8' )
114- logging .info ("Docker container finished execution successfully." )
11594
116- # On success, also include a hint about where to find the detailed logs.
117- logs += " \n \n --- Robot Framework HTML logs (log.html, report.html) are available in the 'robot_tests' directory. --- "
95+ log_html_path = f"/reports/ { run_id } /log.html"
96+ report_html_path = f"/reports/ { run_id } / report.html"
11897
119- return {'model_used' : model_name , 'robot_code' : robot_code , 'logs' : logs }
98+ final_result = {
99+ 'logs' : logs ,
100+ 'log_html' : log_html_path ,
101+ 'report_html' : report_html_path
102+ }
103+ yield f"data: { json .dumps ({'stage' : 'execution' , 'status' : 'complete' , 'message' : 'Test execution finished.' , 'result' : final_result })} \n \n "
120104
121105 except docker .errors .BuildError as e :
122106 logging .error (f"Docker build failed: { e } " )
123- return { 'model_used ' : model_name , 'robot_code ' : robot_code , 'logs ' : f" Docker build failed: { e } " }
107+ yield f"data: { json . dumps ({ 'stage ' : 'execution' , 'status ' : 'error' , 'message ' : f' Docker build failed: { e } ' }) } \n \n "
124108 except docker .errors .ContainerError as e :
125109 logging .error (f"Docker container failed: { e } " )
126110 error_logs = f"Docker container exited with error code { e .exit_status } .\n "
127-
128- # The output from the container should be in the 'logs' attribute of the exception
129111 if hasattr (e , 'logs' ) and e .logs :
130112 error_logs += f"Container Logs:\n { e .logs .decode ('utf-8' , errors = 'ignore' )} "
131- else :
132- error_logs += "No logs were captured from the container."
113+ yield f"data: { json .dumps ({'stage' : 'execution' , 'status' : 'error' , 'message' : error_logs })} \n \n "
114+ except Exception as e :
115+ logging .error (f"An unexpected error occurred during execution: { e } " )
116+ yield f"data: { json .dumps ({'stage' : 'execution' , 'status' : 'error' , 'message' : str (e )})} \n \n "
133117
134- error_logs += "\n \n --- Robot Framework HTML logs (log.html, report.html) may be available in the 'robot_tests' directory for inspection. ---"
135118
136- return {'model_used' : model_name , 'robot_code' : robot_code , 'logs' : error_logs }
137- except Exception as e :
138- logging .error (f"An unexpected error occurred: { e } " )
139- raise HTTPException (status_code = 500 , detail = str (e ))
119+ @app .post ('/generate-and-run' )
120+ async def generate_and_run_streaming (query : Query ):
121+ user_query = query .query
122+ if not user_query :
123+ raise HTTPException (status_code = 400 , detail = "Query not provided" )
124+
125+ model_provider = os .getenv ("MODEL_PROVIDER" , "online" ).lower ()
126+ if model_provider == "local" :
127+ model_name = os .getenv ("LOCAL_MODEL" , "llama3" )
128+ logging .info (f"Using local model provider: { model_name } " )
129+ else :
130+ model_name = os .getenv ("ONLINE_MODEL" , query .model )
131+ logging .info (f"Using online model provider: { model_name } " )
132+
133+ return StreamingResponse (stream_generate_and_run (user_query , model_name ), media_type = "text/event-stream" )
134+
135+ # --- Static Files and Root Endpoint ---
136+ FRONTEND_DIR = os .path .join (os .path .dirname (os .path .abspath (__file__ )), ".." , "frontend" )
137+ ROBOT_TESTS_DIR = os .path .join (os .path .dirname (os .path .abspath (__file__ )), ".." , "robot_tests" )
138+ app .mount ("/reports" , StaticFiles (directory = ROBOT_TESTS_DIR ), name = "reports" )
139+ app .mount ("/" , StaticFiles (directory = FRONTEND_DIR , html = True ), name = "static" )
0 commit comments