riazmo commited on
Commit
c98ef88
·
verified ·
1 Parent(s): 61c22de

Upload 2 files

Browse files
Files changed (2) hide show
  1. app.py +140 -0
  2. workflow.py +83 -0
app.py ADDED
@@ -0,0 +1,140 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Hugging Face Spaces Entry Point
3
+ Fixed for Gradio 4.12.0 compatibility.
4
+ """
5
+
6
+ import gradio as gr
7
+ import os
8
+ import sys
9
+ from datetime import datetime
10
+ from workflow import create_workflow, run_workflow_step_1, resume_workflow
11
+
12
+ # Add current directory to path
13
+ sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
14
+
15
+ def start_test(figma_key, figma_id, url, hf_token):
16
+ """Start the test and stop at the breakpoint."""
17
+ print(f"🚀 Starting test for URL: {url}")
18
+ execution_id = f"exec_{datetime.now().strftime('%Y%m%d_%H%M%S')}"
19
+ thread_id = f"thread_{execution_id}"
20
+
21
+ try:
22
+ # Run until breakpoint
23
+ print(f" 🧵 Thread ID: {thread_id}")
24
+ state_snapshot = run_workflow_step_1(figma_id, figma_key, url, execution_id, thread_id, hf_token)
25
+
26
+ if not state_snapshot or not state_snapshot.values:
27
+ print(" ❌ Error: Workflow returned empty state.")
28
+ return "❌ Error: Workflow failed to start or returned empty state.", None, []
29
+
30
+ status = f"✅ Step 1 Complete. Thread ID: {thread_id}\n"
31
+ status += f"Screenshots captured. Please review them below before proceeding to AI analysis."
32
+
33
+ # Extract screenshots from state
34
+ screenshots = state_snapshot.values.get("website_screenshots", {})
35
+ print(f" 📸 Captured {len(screenshots)} screenshots.")
36
+
37
+ return status, thread_id, list(screenshots.values())
38
+ except Exception as e:
39
+ import traceback
40
+ error_msg = f"❌ Error in start_test: {str(e)}"
41
+ print(error_msg)
42
+ traceback.print_exc()
43
+ return error_msg, None, []
44
+
45
+ def continue_test(thread_id):
46
+ """Resume the test after human approval."""
47
+ if not thread_id:
48
+ return "❌ No active thread found.", None
49
+
50
+ state_snapshot = resume_workflow(thread_id, user_approval=True)
51
+
52
+ if not state_snapshot:
53
+ return "❌ Workflow failed to resume.", None
54
+
55
+ score = state_snapshot.values.get("similarity_score", 0)
56
+ diffs = len(state_snapshot.values.get("visual_differences", []))
57
+
58
+ result = f"✅ Analysis Complete!\n"
59
+ result += f"Similarity Score: {score}/100\n"
60
+ result += f"Differences Found: {diffs}"
61
+
62
+ return result, None
63
+
64
+ # Gradio UI
65
+ def create_interface():
66
+ # For Gradio 4.12.0, theme must be in the Blocks constructor
67
+ with gr.Blocks(title="Advanced UI Regression Testing", theme=gr.themes.Soft()) as demo:
68
+ gr.Markdown("# 🚀 Advanced UI Regression Testing (LangGraph Powered)")
69
+ gr.Markdown("This version uses **LangGraph Persistence**, **Breakpoints**, and **Subgraphs**.")
70
+
71
+ with gr.Row():
72
+ with gr.Column():
73
+ f_key = gr.Textbox(label="Figma API Key", type="password")
74
+ f_id = gr.Textbox(label="Figma File ID")
75
+ w_url = gr.Textbox(label="Website URL")
76
+ hf_token = gr.Textbox(label="Hugging Face API Token", type="password")
77
+ start_btn = gr.Button("1. Start Capture & Setup", variant="primary")
78
+
79
+ with gr.Column():
80
+ status_out = gr.Textbox(label="Status")
81
+ thread_id_state = gr.State()
82
+ gallery = gr.Gallery(label="Captured Screenshots")
83
+ resume_btn = gr.Button("2. Approve & Run AI Analysis", variant="secondary")
84
+ final_out = gr.Textbox(label="Final Results")
85
+ log_viewer = gr.Code(label="Execution Logs", language="python", interactive=False, lines=10)
86
+ refresh_logs = gr.Timer(value=2)
87
+
88
+ def on_start_click():
89
+ return "⏳ Processing... Please check the logs below for real-time updates.", None, []
90
+
91
+ start_btn.click(
92
+ on_start_click,
93
+ outputs=[status_out, thread_id_state, gallery]
94
+ ).then(
95
+ start_test,
96
+ inputs=[f_key, f_id, w_url, hf_token],
97
+ outputs=[status_out, thread_id_state, gallery]
98
+ )
99
+
100
+ resume_btn.click(
101
+ continue_test,
102
+ inputs=[thread_id_state],
103
+ outputs=[final_out, thread_id_state]
104
+ )
105
+
106
+ def update_logs():
107
+ # This is a simplified way to show stdout in the UI
108
+ # In a real HF Space, you'd read from a log file
109
+ try:
110
+ with open("app.log", "r") as f:
111
+ return f.readlines()[-20:] # Show last 20 lines
112
+ except:
113
+ return "No logs available yet."
114
+
115
+ refresh_logs.tick(update_logs, outputs=[log_viewer])
116
+ return demo
117
+
118
+ if __name__ == "__main__":
119
+ import sys
120
+
121
+ class Logger(object):
122
+ def __init__(self):
123
+ self.terminal = sys.stdout
124
+ self.log = open("app.log", "a")
125
+
126
+ def write(self, message):
127
+ self.terminal.write(message)
128
+ self.log.write(message)
129
+ self.log.flush()
130
+
131
+ def flush(self):
132
+ self.terminal.flush()
133
+ self.log.flush()
134
+
135
+ sys.stdout = Logger()
136
+ sys.stderr = Logger()
137
+
138
+ print(f"--- Application Started at {datetime.now()} ---")
139
+ demo = create_interface()
140
+ demo.launch()
workflow.py ADDED
@@ -0,0 +1,83 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ LangGraph Workflow Orchestration
3
+ Updated for dictionary-based state and persistence.
4
+ """
5
+
6
+ from typing import Dict, Any, List
7
+ from langgraph.graph import StateGraph, END
8
+ from langgraph.checkpoint.memory import MemorySaver
9
+ from state_schema import WorkflowState, create_initial_state
10
+ from agents import (
11
+ agent_0_node,
12
+ agent_1_node,
13
+ agent_2_node,
14
+ agent_3_integrated_node
15
+ )
16
+
17
+ def create_workflow():
18
+ """Create the LangGraph workflow."""
19
+ workflow = StateGraph(WorkflowState)
20
+
21
+ # Add Nodes
22
+ workflow.add_node("setup", agent_0_node)
23
+ workflow.add_node("capture_design", agent_1_node)
24
+ workflow.add_node("capture_website", agent_2_node)
25
+ workflow.add_node("analysis", agent_3_integrated_node)
26
+
27
+ # Define Edges
28
+ workflow.set_entry_point("setup")
29
+ workflow.add_edge("setup", "capture_design")
30
+ workflow.add_edge("capture_design", "capture_website")
31
+
32
+ # Human-in-the-loop breakpoint before analysis
33
+ workflow.add_edge("capture_website", "analysis")
34
+ workflow.add_edge("analysis", END)
35
+
36
+ # Add persistence
37
+ checkpointer = MemorySaver()
38
+
39
+ return workflow.compile(
40
+ checkpointer=checkpointer,
41
+ interrupt_before=["analysis"]
42
+ )
43
+
44
+ def run_workflow_step_1(figma_id, figma_key, url, execution_id, thread_id, hf_token=""):
45
+ """Run the first part of the workflow until the breakpoint."""
46
+ print(f" ⚙️ Initializing workflow for thread: {thread_id}")
47
+ app = create_workflow()
48
+ config = {"configurable": {"thread_id": thread_id}}
49
+
50
+ initial_state = create_initial_state(
51
+ figma_file_key=figma_id,
52
+ figma_access_token=figma_key,
53
+ website_url=url,
54
+ hf_token=hf_token,
55
+ execution_id=execution_id
56
+ )
57
+
58
+ # Run until interrupt
59
+ print(" 🏃 Running workflow nodes...")
60
+ try:
61
+ for event in app.stream(initial_state, config, stream_mode="values"):
62
+ # Log the current node if possible
63
+ if event:
64
+ print(f" 📍 Current state updated")
65
+ except Exception as e:
66
+ print(f" ❌ Error during workflow execution: {str(e)}")
67
+ raise
68
+
69
+ return app.get_state(config)
70
+
71
+ def resume_workflow(thread_id, user_approval=True):
72
+ """Resume the workflow after human approval."""
73
+ app = create_workflow()
74
+ config = {"configurable": {"thread_id": thread_id}}
75
+
76
+ # Update state with approval
77
+ app.update_state(config, {"user_approval": user_approval})
78
+
79
+ # Resume execution
80
+ for event in app.stream(None, config, stream_mode="values"):
81
+ state = event
82
+
83
+ return app.get_state(config)