diff --git a/README.md b/README.md
index caaf5be..c924faf 100644
--- a/README.md
+++ b/README.md
@@ -87,7 +87,8 @@ From there, it's easy to implement popular design patterns like ([Multi-](https:
| [Text2SQL](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-text2sql) | ★☆☆
*Beginner* | Convert natural language to SQL queries with an auto-debug loop |
| [MCP](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-mcp) | ★☆☆
*Beginner* | Agent using Model Context Protocol for numerical operations |
| [A2A](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-a2a) | ★☆☆
*Beginner* | Agent wrapped with Agent-to-Agent protocol for inter-agent communication |
-| [Web HITL](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-web-hitl) | ★☆☆
*Beginner* | A minimal web service for a human review loop with SSE updates |
+| [Streamlit HITL](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-streamlit-hitl) | ★☆☆
*Beginner* | Streamlit app for human-in-the-loop review |
+| [FastAPI HITL](https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-fastapi-hitl) | ★☆☆
*Beginner* | FastAPI app for async human review loop with SSE |
diff --git a/cookbook/pocketflow-web-hitl/README.md b/cookbook/pocketflow-fastapi-hitl/README.md
similarity index 100%
rename from cookbook/pocketflow-web-hitl/README.md
rename to cookbook/pocketflow-fastapi-hitl/README.md
diff --git a/cookbook/pocketflow-web-hitl/assets/banner.png b/cookbook/pocketflow-fastapi-hitl/assets/banner.png
similarity index 100%
rename from cookbook/pocketflow-web-hitl/assets/banner.png
rename to cookbook/pocketflow-fastapi-hitl/assets/banner.png
diff --git a/cookbook/pocketflow-web-hitl/docs/design.md b/cookbook/pocketflow-fastapi-hitl/docs/design.md
similarity index 100%
rename from cookbook/pocketflow-web-hitl/docs/design.md
rename to cookbook/pocketflow-fastapi-hitl/docs/design.md
diff --git a/cookbook/pocketflow-web-hitl/flow.py b/cookbook/pocketflow-fastapi-hitl/flow.py
similarity index 100%
rename from cookbook/pocketflow-web-hitl/flow.py
rename to cookbook/pocketflow-fastapi-hitl/flow.py
diff --git a/cookbook/pocketflow-web-hitl/main.py b/cookbook/pocketflow-fastapi-hitl/main.py
similarity index 100%
rename from cookbook/pocketflow-web-hitl/main.py
rename to cookbook/pocketflow-fastapi-hitl/main.py
diff --git a/cookbook/pocketflow-web-hitl/nodes.py b/cookbook/pocketflow-fastapi-hitl/nodes.py
similarity index 100%
rename from cookbook/pocketflow-web-hitl/nodes.py
rename to cookbook/pocketflow-fastapi-hitl/nodes.py
diff --git a/cookbook/pocketflow-web-hitl/requirements.txt b/cookbook/pocketflow-fastapi-hitl/requirements.txt
similarity index 100%
rename from cookbook/pocketflow-web-hitl/requirements.txt
rename to cookbook/pocketflow-fastapi-hitl/requirements.txt
diff --git a/cookbook/pocketflow-web-hitl/server.py b/cookbook/pocketflow-fastapi-hitl/server.py
similarity index 100%
rename from cookbook/pocketflow-web-hitl/server.py
rename to cookbook/pocketflow-fastapi-hitl/server.py
diff --git a/cookbook/pocketflow-web-hitl/static/style.css b/cookbook/pocketflow-fastapi-hitl/static/style.css
similarity index 100%
rename from cookbook/pocketflow-web-hitl/static/style.css
rename to cookbook/pocketflow-fastapi-hitl/static/style.css
diff --git a/cookbook/pocketflow-web-hitl/templates/index.html b/cookbook/pocketflow-fastapi-hitl/templates/index.html
similarity index 100%
rename from cookbook/pocketflow-web-hitl/templates/index.html
rename to cookbook/pocketflow-fastapi-hitl/templates/index.html
diff --git a/cookbook/pocketflow-web-hitl/utils/__init__.py b/cookbook/pocketflow-fastapi-hitl/utils/__init__.py
similarity index 100%
rename from cookbook/pocketflow-web-hitl/utils/__init__.py
rename to cookbook/pocketflow-fastapi-hitl/utils/__init__.py
diff --git a/cookbook/pocketflow-web-hitl/utils/process_task.py b/cookbook/pocketflow-fastapi-hitl/utils/process_task.py
similarity index 100%
rename from cookbook/pocketflow-web-hitl/utils/process_task.py
rename to cookbook/pocketflow-fastapi-hitl/utils/process_task.py
diff --git a/cookbook/pocketflow-streamlit-hitl/app.py b/cookbook/pocketflow-streamlit-hitl/app.py
index 0e82097..d349e40 100644
--- a/cookbook/pocketflow-streamlit-hitl/app.py
+++ b/cookbook/pocketflow-streamlit-hitl/app.py
@@ -30,12 +30,18 @@ with st.expander("Show Session State (Shared Data)"):
st.json(display_state)
# --- Stage: Initial Input ---
-if st.session_state.stage == "initial":
- st.header("1. Submit Data for Processing")
- # Use st.session_state.task_input directly for the text area's value
- task_input_value = st.text_area("Enter data to process:", value=st.session_state.task_input, height=150)
-
- if st.button("Submit"):
+st.header("1. Submit Data for Processing")
+task_input_value = st.text_area(
+ "Enter data to process:",
+ value=st.session_state.task_input,
+ height=150,
+ disabled=(st.session_state.stage != "initial")
+)
+
+# Disable button if not in 'initial' stage
+submit_button_disabled = (st.session_state.stage != "initial")
+if st.button("Submit", disabled=submit_button_disabled):
+ if not submit_button_disabled: # Process click only if button was not meant to be disabled
if not task_input_value.strip():
st.error("Please enter some data to process.")
else:
@@ -70,17 +76,21 @@ if st.session_state.stage == "initial":
# Keep stage as initial
# --- Stage: Awaiting Review ---
-elif st.session_state.stage == "awaiting_review":
- st.header("2. Review Processed Output")
- # Get processed output directly from session state
- processed_output = st.session_state.get("processed_output", "Error: Processed output not found!")
-
- st.subheader("Output to Review:")
- st.markdown(f"```\n{str(processed_output)}\n```") # Display as markdown code block
-
- col1, col2, _ = st.columns([1, 1, 5]) # Layout buttons
- with col1:
- if st.button("Approve"):
+st.header("2. Review Processed Output")
+# Get processed output directly from session state
+processed_output = st.session_state.get("processed_output", "No output to review yet.")
+# Display placeholder if no output yet or not in review stage
+output_to_display = processed_output if st.session_state.stage == "awaiting_review" else "Output will appear here after submission."
+
+st.subheader("Output to Review:")
+st.markdown(f"```\\n{str(output_to_display)}\\n```") # Display as markdown code block
+
+col1, col2, _ = st.columns([1, 1, 5]) # Layout buttons
+with col1:
+ # Disable button if not in 'awaiting_review' stage
+ approve_button_disabled = (st.session_state.stage != "awaiting_review")
+ if st.button("Approve", disabled=approve_button_disabled):
+ if not approve_button_disabled: # Process click only if button was not meant to be disabled
print("Approve button clicked.")
st.session_state.error_message = None
try:
@@ -106,51 +116,61 @@ elif st.session_state.stage == "awaiting_review":
st.rerun() # Rerun to show error message
with col2:
- if st.button("Reject"):
- print("Reject button clicked.")
- st.session_state.error_message = None # Clear previous errors
- # Go back to initial stage to allow modification/resubmission
- st.session_state.stage = "initial"
- # Keep the rejected output visible in the input field for modification
- st.session_state.task_input = st.session_state.get("processed_output", st.session_state.task_input)
- # Clear the processed output so it doesn't linger
- if "processed_output" in st.session_state: del st.session_state.processed_output
- if "final_result" in st.session_state: del st.session_state.final_result
- st.info("Task rejected. Modify the input below and resubmit.")
- print("Task rejected. Moving back to 'initial' stage.")
- st.rerun()
+ # Disable button if not in 'awaiting_review' stage
+ reject_button_disabled = (st.session_state.stage != "awaiting_review")
+ if st.button("Reject", disabled=reject_button_disabled):
+ if not reject_button_disabled: # Process click only if button was not meant to be disabled
+ print("Reject button clicked.")
+ st.session_state.error_message = None # Clear previous errors
+ # Go back to initial stage to allow modification/resubmission
+ st.session_state.stage = "initial"
+ # Keep the rejected output visible in the input field for modification
+ st.session_state.task_input = st.session_state.get("processed_output", st.session_state.task_input)
+ # Clear the processed output so it doesn't linger
+ if "processed_output" in st.session_state: del st.session_state.processed_output
+ if "final_result" in st.session_state: del st.session_state.final_result
+ st.info("Task rejected. Modify the input below and resubmit.")
+ print("Task rejected. Moving back to 'initial' stage.")
+ st.rerun()
# --- Stage: Completed ---
-elif st.session_state.stage == "completed":
- st.header("3. Task Completed")
- # Get final result directly from session state
- final_result = st.session_state.get("final_result", "Error: Final result not found!")
- st.subheader("Final Result:")
+st.header("3. Task Completed")
+# Get final result directly from session state
+final_result = st.session_state.get("final_result", "Task not completed yet.")
+# Display placeholder if not completed
+result_to_display = final_result if st.session_state.stage == "completed" else "Final result will appear here upon completion."
+
+st.subheader("Final Result:")
+if st.session_state.stage == "completed":
st.success("Task approved and completed successfully!")
- st.text_area("", value=str(final_result), height=200, disabled=True)
-
- if st.button("Start Over"):
+st.text_area("", value=str(result_to_display), height=200, disabled=True) # Always disabled for display
+
+# Disable button if not in 'completed' or 'rejected_final' stage
+start_over_button_disabled = not (st.session_state.stage == "completed" or st.session_state.stage == "rejected_final")
+if st.button("Start Over", disabled=start_over_button_disabled):
+ if not start_over_button_disabled: # Process click only if button was not meant to be disabled
print("Start Over button clicked.")
reset_state()
st.rerun()
-# --- Stage: Rejected ---
-elif st.session_state.stage == "rejected_final":
- st.header("3. Task Rejected")
+# --- Stage: Rejected -- (This section appears to be for a final rejected state, let's adjust for visibility)
+# elif st.session_state.stage == "rejected_final": # Removed conditional rendering
+# We'll integrate the display of rejection into the "Task Completed" area or manage it distinctly
+# For now, this specific "rejected_final" header might be redundant if we always show "3. Task Completed" area
+# And handle the message within it.
+
+if st.session_state.stage == "rejected_final":
+ st.header("3. Task Rejected") # This can be shown when in this specific state.
st.error("The processed output was rejected.")
# Get rejected output directly from session state
rejected_output = st.session_state.get("processed_output", "")
if rejected_output:
st.text_area("Rejected Output:", value=str(rejected_output), height=150, disabled=True)
-
- if st.button("Start Over"):
- print("Start Over button clicked.")
- reset_state()
- st.rerun()
+ # The "Start Over" button for this state is handled by the one in "Task Completed" section due to shared disabling logic.
# --- Display Error Messages ---
if st.session_state.error_message:
st.error(st.session_state.error_message)
-
# --- Add a button to reset state anytime (for debugging) ---
# st.sidebar.button("Reset State", on_click=reset_state) # Removed sidebar
+
diff --git a/cookbook/pocketflow-streamlit-hitl/utils/process_task.py b/cookbook/pocketflow-streamlit-hitl/utils/process_task.py
index 5bb7a13..1cc13b0 100644
--- a/cookbook/pocketflow-streamlit-hitl/utils/process_task.py
+++ b/cookbook/pocketflow-streamlit-hitl/utils/process_task.py
@@ -7,7 +7,7 @@ def process_task(task_input: str) -> str:
"""
print(f"Processing task: {task_input[:50]}...")
- result = f"Rephrased text for the following input: {task_input}"
+ result = f"Dummy rephrased text for the following input: {task_input}"
# Simulate some work
time.sleep(2)