Skip to content

Commit 5689e47

Browse files
Update main.py and index.html with new streaming implementation
1 parent 8031941 commit 5689e47

File tree

2 files changed

+818
-89
lines changed

2 files changed

+818
-89
lines changed

backend/main.py

Lines changed: 74 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,12 @@
33
import logging
44
import docker
55
from fastapi import FastAPI, HTTPException
6-
from fastapi.responses import HTMLResponse
6+
from fastapi.responses import HTMLResponse, StreamingResponse
77
from fastapi.staticfiles import StaticFiles
88
from pydantic import BaseModel
99
from dotenv import load_dotenv
10+
from fastapi.middleware.cors import CORSMiddleware
11+
import json
1012

1113
# Import the new agentic workflow orchestrator
1214
from backend.robot_generator import run_agentic_workflow
@@ -26,114 +28,112 @@ class Query(BaseModel):
2628
# --- FastAPI App ---
2729
app = 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

Comments
 (0)