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)