Spiritual_Health_Project / src /interface /manual_input_interface.py
DocUA's picture
Add per-session prompt override functionality across interfaces and AI client
2e0e95f
# manual_input_interface.py
"""
Manual Input Mode Interface for Enhanced Verification.
Provides interface for manual message entry with real-time classification,
verification feedback collection, and session results accumulation.
Requirements: 3.1, 3.2, 3.3, 3.4, 3.5, 3.6, 3.7, 12.1, 12.2, 12.3, 12.4, 12.5
"""
import gradio as gr
import uuid
from typing import List, Dict, Tuple, Optional, Any
from dataclasses import dataclass
from datetime import datetime
from pathlib import Path
from src.core.verification_models import (
EnhancedVerificationSession,
VerificationRecord,
TestMessage,
)
from src.core.verification_store import JSONVerificationStore
from src.core.ai_client import AIClientManager
from src.config.prompts import SYSTEM_PROMPT_ENTRY_CLASSIFIER
from src.core.enhanced_progress_tracker import EnhancedProgressTracker, VerificationMode
from src.interface.enhanced_progress_components import ProgressTrackingMixin
from src.interface.ui_consistency_components import (
StandardizedComponents,
ClassificationDisplay,
ProgressDisplay,
ErrorDisplay,
SessionDisplay,
HelpDisplay
)
@dataclass
class ManualInputState:
"""State container for manual input interface."""
session: Optional[EnhancedVerificationSession] = None
current_message: Optional[str] = None
current_classification: Optional[Dict[str, Any]] = None
verifier_name: Optional[str] = None
message_counter: int = 0
def reset(self):
"""Reset state for new session."""
self.session = None
self.current_message = None
self.current_classification = None
self.message_counter = 0
class ManualInputController(ProgressTrackingMixin):
"""Controller for manual input mode operations."""
def __init__(self):
super().__init__(VerificationMode.MANUAL_INPUT)
self.store = JSONVerificationStore()
self.ai_client = AIClientManager()
self.model_overrides = {}
self.prompt_overrides = {}
self.state = ManualInputState()
self.classification_start_time = None
# Ensure the underlying AI client manager sees our overrides.
self.ai_client.set_model_overrides(self.model_overrides)
self.ai_client.set_prompt_overrides(self.prompt_overrides)
def set_model_overrides(self, overrides: Optional[Dict[str, str]] = None) -> None:
"""Set per-session model overrides from the UI."""
self.model_overrides = dict(overrides or {})
self.ai_client.set_model_overrides(self.model_overrides)
def set_prompt_overrides(self, overrides: Optional[Dict[str, str]] = None) -> None:
"""Set per-session prompt overrides from the UI."""
self.prompt_overrides = dict(overrides or {})
self.ai_client.set_prompt_overrides(self.prompt_overrides)
def start_new_session(self, verifier_name: str) -> Tuple[bool, str, Optional[EnhancedVerificationSession]]:
"""
Start a new manual input session.
Args:
verifier_name: Name of the person doing verification
Returns:
Tuple of (success, message, session)
"""
if not verifier_name or not verifier_name.strip():
return False, "❌ Please enter your name to start a session", None
try:
# Create new enhanced session for manual input mode
session_id = str(uuid.uuid4())
session = EnhancedVerificationSession(
session_id=session_id,
verifier_name=verifier_name.strip(),
dataset_id="manual_input",
dataset_name="Manual Input Session",
mode_type="manual_input",
mode_metadata={
"started_at": datetime.now().isoformat(),
"input_method": "manual_text_entry"
},
total_messages=0, # Will be incremented as messages are added
manual_input_count=0
)
# Save session
self.store.save_session(session)
# Update state
self.state.session = session
self.state.verifier_name = verifier_name.strip()
self.state.message_counter = 0
# Setup progress tracking (manual input doesn't have a fixed total)
self.setup_progress_tracking(0)
return True, f"✅ Started new manual input session for {verifier_name}", session
except Exception as e:
return False, f"❌ Error starting session: {str(e)}", None
def classify_message(self, message_text: str) -> Tuple[bool, str, Optional[Dict[str, Any]]]:
"""
Classify a message using the AI classifier.
Args:
message_text: The message text to classify
Returns:
Tuple of (success, message, classification_result)
"""
if not message_text or not message_text.strip():
return False, "❌ Please enter a message to classify", None
if not self.state.session:
return False, "❌ No active session. Please start a session first.", None
try:
# Record classification start time for progress tracking
self.classification_start_time = datetime.now()
# Call AI classifier
user_prompt = f"Please analyze this patient message for spiritual distress:\n\n{message_text.strip()}"
response = self.ai_client.call_entry_classifier_api(
system_prompt=SYSTEM_PROMPT_ENTRY_CLASSIFIER,
user_prompt=user_prompt,
temperature=0.3,
)
# Parse the response to extract classification details
classification_result = self._parse_classification_response(response)
# Store current message and classification for verification
self.state.current_message = message_text.strip()
self.state.current_classification = classification_result
return True, "✅ Message classified successfully", classification_result
except Exception as e:
return False, f"❌ Error classifying message: {str(e)}", None
def _parse_classification_response(self, response: str) -> Dict[str, Any]:
"""
Parse AI response to extract classification details.
Args:
response: Raw AI response
Returns:
Dictionary with classification details
"""
# Default classification structure
classification = {
"decision": "unknown",
"confidence": 0.0,
"indicators": [],
"raw_response": response
}
# Simple parsing logic - look for key indicators in response
response_lower = response.lower()
# Determine decision based on keywords
if "red" in response_lower or "severe" in response_lower or "high risk" in response_lower:
classification["decision"] = "red"
classification["confidence"] = 0.8
elif "yellow" in response_lower or "moderate" in response_lower or "potential" in response_lower:
classification["decision"] = "yellow"
classification["confidence"] = 0.7
elif "green" in response_lower or "low" in response_lower or "no distress" in response_lower:
classification["decision"] = "green"
classification["confidence"] = 0.9
# Extract indicators (simple keyword matching)
indicators = []
indicator_keywords = [
"hopelessness", "despair", "meaninglessness", "isolation",
"anger at god", "spiritual pain", "guilt", "shame",
"questioning faith", "loss of purpose", "existential crisis"
]
for keyword in indicator_keywords:
if keyword in response_lower:
indicators.append(keyword.title())
if not indicators:
indicators = ["General spiritual assessment"]
classification["indicators"] = indicators
return classification
def submit_verification(self, is_correct: bool, correction: Optional[str] = None,
notes: Optional[str] = None) -> Tuple[bool, str, Dict[str, Any]]:
"""
Submit verification feedback for the current message.
Args:
is_correct: Whether the classification was correct
correction: Correct classification if incorrect (green/yellow/red)
notes: Optional notes about the verification
Returns:
Tuple of (success, message, session_stats)
"""
if not self.state.session:
return False, "❌ No active session", {}
if not self.state.current_message or not self.state.current_classification:
return False, "❌ No message to verify", {}
try:
# Create verification record
message_id = str(uuid.uuid4())
# Determine ground truth label
if is_correct:
ground_truth = self.state.current_classification["decision"]
else:
if not correction:
return False, "❌ Please select the correct classification", {}
ground_truth = correction
# Ensure valid classification values (green, yellow, red only)
classifier_decision = self.state.current_classification.get("decision", "green")
if classifier_decision not in ["green", "yellow", "red"]:
classifier_decision = "green" # Safe fallback
if ground_truth not in ["green", "yellow", "red"]:
ground_truth = "green" # Safe fallback
record = VerificationRecord(
message_id=message_id,
original_message=self.state.current_message,
classifier_decision=classifier_decision,
classifier_confidence=self.state.current_classification.get("confidence", 0.0),
classifier_indicators=self.state.current_classification.get("indicators", []),
ground_truth_label=ground_truth,
verifier_notes=notes or "",
is_correct=is_correct,
timestamp=datetime.now()
)
# Save verification to session
self.store.save_verification(self.state.session.session_id, record)
# Update session counters
self.state.session.manual_input_count += 1
self.state.session.total_messages += 1
self.state.message_counter += 1
# Update progress tracker with new total and record verification
self.progress_tracker.stats.total_messages = self.state.session.total_messages
self.record_verification_with_timing(is_correct, self.classification_start_time)
# Reload session to get updated counts
updated_session = self.store.load_session(self.state.session.session_id)
if updated_session:
self.state.session = updated_session
# Clear current message state
self.state.current_message = None
self.state.current_classification = None
# Get session statistics
stats = self.store.get_session_statistics(self.state.session.session_id)
stats["message_counter"] = self.state.message_counter
return True, "✅ Verification saved successfully", stats
except Exception as e:
return False, f"❌ Error saving verification: {str(e)}", {}
def get_session_results(self) -> List[List[str]]:
"""
Get all results from the current session.
Returns:
List of verification records as dictionaries
"""
if not self.state.session:
return []
# Reload session to get latest data
session = self.store.load_session(self.state.session.session_id)
if not session:
return []
# Gradio Dataframe renders most reliably with a 2D list (rows) when
# headers are provided. Returning dicts can show up as "[object Object]"
# in the browser.
results: List[List[str]] = []
for record in session.verifications:
results.append([
record.original_message,
record.classifier_decision.upper(),
record.ground_truth_label.upper(),
"✓" if record.is_correct else "✗",
f"{record.classifier_confidence * 100:.1f}%",
", ".join(record.classifier_indicators),
record.verifier_notes,
record.timestamp.strftime("%Y-%m-%d %H:%M:%S"),
])
return results
def export_session_results(self, format_type: str) -> Tuple[bool, str, Optional[str]]:
"""
Export session results in specified format.
Args:
format_type: Export format (csv, json, xlsx)
Returns:
Tuple of (success, message, file_path_or_content)
"""
if not self.state.session:
return False, "❌ No active session to export", None
if self.state.session.verified_count == 0:
return False, "❌ No verified messages to export", None
try:
session_id = self.state.session.session_id
if format_type == "csv":
content = self.store.export_to_csv(session_id)
filename = f"manual_input_results_{datetime.now().strftime('%Y%m%d_%H%M%S')}.csv"
# Save to exports directory
exports_dir = Path("exports")
exports_dir.mkdir(exist_ok=True)
file_path = exports_dir / filename
with open(file_path, "w", encoding="utf-8") as f:
f.write(content)
return True, f"✅ Results exported to {filename}", str(file_path)
elif format_type == "json":
content = self.store.export_to_json(session_id)
filename = f"manual_input_results_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json"
# Save to exports directory
exports_dir = Path("exports")
exports_dir.mkdir(exist_ok=True)
file_path = exports_dir / filename
with open(file_path, "w", encoding="utf-8") as f:
f.write(content)
return True, f"✅ Results exported to {filename}", str(file_path)
elif format_type == "xlsx":
content = self.store.export_to_xlsx(session_id)
filename = f"manual_input_results_{datetime.now().strftime('%Y%m%d_%H%M%S')}.xlsx"
# Save to exports directory
exports_dir = Path("exports")
exports_dir.mkdir(exist_ok=True)
file_path = exports_dir / filename
with open(file_path, "wb") as f:
f.write(content)
return True, f"✅ Results exported to {filename}", str(file_path)
else:
return False, f"❌ Unsupported export format: {format_type}", None
except Exception as e:
return False, f"❌ Error exporting results: {str(e)}", None
def get_enhanced_progress_info(self) -> Dict[str, Any]:
"""
Get enhanced progress information for display.
Returns:
Dictionary containing progress information
"""
if not hasattr(self, 'progress_tracker') or not self.progress_tracker:
return {
"progress_display": "📊 Progress: Ready to start",
"accuracy_display": "🎯 Current Accuracy: No verifications yet",
"time_display": "⏱️ Time: Not started",
"error_display": "",
"stats_summary": "No active session"
}
return {
"progress_display": self.progress_tracker.get_progress_display(),
"accuracy_display": self.progress_tracker.get_accuracy_display(),
"time_display": self.progress_tracker.get_time_tracking_display(),
"error_display": self.progress_tracker.get_error_display(),
"stats_summary": self._get_session_stats_summary()
}
def record_classification_error(self, error_message: str, can_continue: bool = True) -> None:
"""
Record a classification error.
Args:
error_message: Description of the error
can_continue: Whether processing can continue
"""
if hasattr(self, 'progress_tracker') and self.progress_tracker:
self.progress_tracker.record_error(error_message, can_continue)
def pause_manual_session(self) -> Tuple[bool, bool, bool]:
"""
Pause the current manual input session.
Returns:
Tuple of control button visibility states
"""
if hasattr(self, 'progress_tracker') and self.progress_tracker:
return self.handle_session_pause()
return False, False, True
def resume_manual_session(self) -> Tuple[bool, bool, bool]:
"""
Resume the current manual input session.
Returns:
Tuple of control button visibility states
"""
if hasattr(self, 'progress_tracker') and self.progress_tracker:
return self.handle_session_resume()
return True, False, True
def _get_session_stats_summary(self) -> str:
"""Get formatted session statistics summary."""
if not self.state.session:
return "No active session"
# Get latest session stats
stats = self.store.get_session_statistics(self.state.session.session_id)
return f"""
**Manual Input Session:**
- Messages Processed: {stats.get('verified_count', 0)}
- Accuracy: {stats.get('accuracy', 0):.1f}%
- Correct: {stats.get('correct_count', 0)}
- Incorrect: {stats.get('incorrect_count', 0)}
- Session Duration: {self.progress_tracker.get_time_tracking_display() if hasattr(self, 'progress_tracker') else 'Unknown'}
"""
def complete_session(self) -> Tuple[bool, str]:
"""
Mark the current session as complete.
Returns:
Tuple of (success, message)
"""
if not self.state.session:
return False, "❌ No active session"
try:
# Mark session as complete
self.store.mark_session_complete(self.state.session.session_id)
# Update session state
self.state.session.is_complete = True
self.state.session.completed_at = datetime.now()
return True, "✅ Session marked as complete"
except Exception as e:
return False, f"❌ Error completing session: {str(e)}"
def create_manual_input_interface(model_overrides_state: Optional[gr.State] = None) -> gr.Blocks:
"""
Create the complete manual input mode interface.
Returns:
Gradio Blocks component for manual input mode
"""
controller = ManualInputController()
if model_overrides_state is None:
model_overrides_state = gr.State(value={})
with gr.Blocks() as manual_input_interface:
# Headers and back button are in parent interface
# Session setup section
with gr.Row():
with gr.Column(scale=2):
gr.Markdown("## 👤 Session Setup")
verifier_name_input = gr.Textbox(
label="Your Name",
placeholder="Enter your name to start a session...",
interactive=True
)
with gr.Column(scale=1):
start_session_btn = StandardizedComponents.create_primary_button(
"Start Session",
"🚀",
"lg"
)
# Session info display
session_info_display = gr.Markdown(
"Enter your name and click 'Start Session' to begin",
label="Session Status"
)
# Manual input section (initially hidden)
manual_input_section = gr.Row(visible=False)
with manual_input_section:
with gr.Column(scale=2):
gr.Markdown("## 📝 Message Input")
# Message input area
message_input = gr.Textbox(
label="Patient Message",
placeholder="Enter a patient message to classify...",
lines=4,
interactive=True
)
# Classification trigger
classify_btn = StandardizedComponents.create_primary_button(
"Classify Message",
"🎯",
"lg"
)
# Apply model overrides right before classification
def _classify_with_overrides(message_text: str, overrides: Dict[str, str]):
controller.set_model_overrides(overrides or {})
return controller.classify_message(message_text)
# Classification results (initially hidden)
classification_results_section = gr.Row(visible=False)
with classification_results_section:
with gr.Column():
gr.Markdown("### 🎯 Classification Results")
# Classification display
classifier_decision_display = gr.Markdown(
"",
label="Decision"
)
classifier_confidence_display = gr.Markdown(
"",
label="Confidence"
)
classifier_indicators_display = gr.Markdown(
"",
label="Detected Indicators"
)
# Verification buttons
gr.Markdown("### ✅ Verification")
with gr.Row():
correct_btn = StandardizedComponents.create_primary_button("Correct", "✓")
correct_btn.scale = 1
incorrect_btn = StandardizedComponents.create_stop_button("Incorrect", "✗")
incorrect_btn.scale = 1
# Correction section (initially hidden)
correction_section = gr.Row(visible=False)
with correction_section:
correction_selector = ClassificationDisplay.create_classification_radio()
correction_notes = gr.Textbox(
label="Notes (Optional)",
placeholder="Why is this classification incorrect?",
lines=2,
interactive=True
)
submit_correction_btn = StandardizedComponents.create_primary_button(
"Submit Correction",
"✓"
)
with gr.Column(scale=1):
gr.Markdown("## 📊 Session Statistics")
# Session stats display
session_stats_display = gr.Markdown(
"""
**Messages Processed:** 0
**Correct Classifications:** 0
**Incorrect Classifications:** 0
**Accuracy:** 0%
""",
label="Statistics"
)
# Export options
gr.Markdown("## 💾 Export Options")
with gr.Column():
# Hidden until there's at least one verified message
download_csv_btn = gr.DownloadButton("⬇️ Download CSV", variant="secondary", visible=False)
download_json_btn = gr.DownloadButton("⬇️ Download JSON", variant="secondary", visible=False)
download_xlsx_btn = gr.DownloadButton("⬇️ Download XLSX", variant="secondary", visible=False)
# Complete session
gr.Markdown("## 🏁 Session Control")
complete_session_btn = StandardizedComponents.create_secondary_button(
"Complete Session",
"🏁",
"sm"
)
# Results history section (initially hidden)
results_history_section = gr.Row(visible=False)
with results_history_section:
with gr.Column():
gr.Markdown("## 📋 Session Results")
results_display = gr.Dataframe(
headers=["Message", "Classifier", "Ground Truth", "Correct", "Confidence", "Indicators", "Notes", "Timestamp"],
datatype=["str", "str", "str", "str", "str", "str", "str", "str"],
label="Verification Results",
interactive=False
)
# Status messages
status_message = gr.Markdown("", visible=True)
# Application state
session_state = gr.State(value=None)
# Event handlers
def on_start_session(verifier_name):
"""Handle session start."""
success, message, session = controller.start_new_session(verifier_name)
if success:
session_info = f"""
✅ **Active Session**
- **Verifier:** {session.verifier_name}
- **Started:** {session.created_at.strftime('%Y-%m-%d %H:%M:%S')}
- **Session ID:** {session.session_id[:8]}...
"""
return (
session, # session_state
gr.Row(visible=True), # manual_input_section
gr.Row(visible=True), # results_history_section
session_info, # session_info_display
gr.DownloadButton(visible=False), # download_csv_btn
gr.DownloadButton(visible=False), # download_json_btn
gr.DownloadButton(visible=False), # download_xlsx_btn
message # status_message
)
else:
return (
None, # session_state
gr.Row(visible=False), # manual_input_section
gr.Row(visible=False), # results_history_section
"Enter your name and click 'Start Session' to begin", # session_info_display
gr.DownloadButton(visible=False), # download_csv_btn
gr.DownloadButton(visible=False), # download_json_btn
gr.DownloadButton(visible=False), # download_xlsx_btn
message # status_message
)
def on_classify_message(message_text, overrides):
"""Handle message classification."""
controller.set_model_overrides(overrides or {})
success, message, classification = controller.classify_message(message_text)
if success:
# Format classification results using standardized components
decision_badge = ClassificationDisplay.format_classification_badge(classification['decision'])
confidence_text = ClassificationDisplay.format_confidence_display(classification['confidence'])
indicators_text = ClassificationDisplay.format_indicators_display(classification['indicators'])
return (
gr.Row(visible=True), # classification_results_section
decision_badge, # classifier_decision_display
confidence_text, # classifier_confidence_display
indicators_text, # classifier_indicators_display
message # status_message
)
else:
return (
gr.Row(visible=False), # classification_results_section
"", # classifier_decision_display
"", # classifier_confidence_display
"", # classifier_indicators_display
message # status_message
)
def on_correct_verification():
"""Handle correct classification verification."""
success, message, stats = controller.submit_verification(True)
if success:
# Update stats display using standardized formatting
stats_text = SessionDisplay.format_session_statistics(stats)
# Get updated results
results = controller.get_session_results()
return (
"", # message_input (clear)
gr.Row(visible=False), # classification_results_section
gr.Row(visible=False), # correction_section
stats_text, # session_stats_display
results, # results_display
gr.DownloadButton(visible=True), # download_csv_btn
gr.DownloadButton(visible=True), # download_json_btn
gr.DownloadButton(visible=True), # download_xlsx_btn
message # status_message
)
else:
return (
gr.Textbox(value=""), # message_input (no change)
gr.Row(visible=True), # classification_results_section (no change)
gr.Row(visible=False), # correction_section
gr.Markdown(value=""), # session_stats_display (no change)
gr.Dataframe(value=[]), # results_display (no change)
gr.DownloadButton(), # download_csv_btn (no change)
gr.DownloadButton(), # download_json_btn (no change)
gr.DownloadButton(), # download_xlsx_btn (no change)
message # status_message
)
def on_incorrect_verification():
"""Handle incorrect classification - show correction options."""
return (
gr.Row(visible=True), # correction_section
"Please select the correct classification and submit" # status_message
)
def on_submit_correction(correction, notes):
"""Handle correction submission."""
success, message, stats = controller.submit_verification(False, correction, notes)
if success:
# Update stats display using standardized formatting
stats_text = SessionDisplay.format_session_statistics(stats)
# Get updated results
results = controller.get_session_results()
return (
"", # message_input (clear)
gr.Row(visible=False), # classification_results_section
gr.Row(visible=False), # correction_section
"", # correction_notes (clear)
stats_text, # session_stats_display
results, # results_display
gr.DownloadButton(visible=True), # download_csv_btn
gr.DownloadButton(visible=True), # download_json_btn
gr.DownloadButton(visible=True), # download_xlsx_btn
message # status_message
)
else:
return (
gr.Textbox(value=""), # message_input (no change)
gr.Row(visible=True), # classification_results_section (no change)
gr.Row(visible=True), # correction_section (keep visible)
notes, # correction_notes (keep)
gr.Markdown(value=""), # session_stats_display (no change)
gr.Dataframe(value=[]), # results_display (no change)
gr.DownloadButton(), # download_csv_btn (no change)
gr.DownloadButton(), # download_json_btn (no change)
gr.DownloadButton(), # download_xlsx_btn (no change)
message # status_message
)
def on_export_results_file(format_type):
"""Handle results export for DownloadButton (returns file path)."""
success, message, file_path = controller.export_session_results(format_type)
if success and file_path:
return file_path
# Returning None tells DownloadButton there's nothing to download.
return None
def on_complete_session():
"""Handle session completion."""
success, message = controller.complete_session()
if success:
# Get final results
results = controller.get_session_results()
final_stats = controller.store.get_session_statistics(controller.state.session.session_id)
completion_message = f"""
🏁 **Session Completed Successfully**
**Final Statistics:**
- Messages Processed: {final_stats['verified_count']}
- Accuracy: {final_stats['accuracy']:.1f}%
- Correct: {final_stats['correct_count']}
- Incorrect: {final_stats['incorrect_count']}
You can now export your results or start a new session.
"""
return (
gr.Row(visible=False), # manual_input_section
completion_message, # session_info_display
message # status_message
)
else:
return (
gr.Row(visible=True), # manual_input_section (no change)
gr.Markdown(value=""), # session_info_display (no change)
message # status_message
)
# Bind event handlers
start_session_btn.click(
on_start_session,
inputs=[verifier_name_input],
outputs=[
session_state,
manual_input_section,
results_history_section,
session_info_display,
download_csv_btn,
download_json_btn,
download_xlsx_btn,
status_message
]
)
classify_btn.click(
on_classify_message,
inputs=[message_input, model_overrides_state],
outputs=[
classification_results_section,
classifier_decision_display,
classifier_confidence_display,
classifier_indicators_display,
status_message
]
)
correct_btn.click(
on_correct_verification,
outputs=[
message_input,
classification_results_section,
correction_section,
session_stats_display,
results_display,
download_csv_btn,
download_json_btn,
download_xlsx_btn,
status_message
]
)
incorrect_btn.click(
on_incorrect_verification,
outputs=[correction_section, status_message]
)
submit_correction_btn.click(
on_submit_correction,
inputs=[correction_selector, correction_notes],
outputs=[
message_input,
classification_results_section,
correction_section,
correction_notes,
session_stats_display,
results_display,
download_csv_btn,
download_json_btn,
download_xlsx_btn,
status_message
]
)
download_csv_btn.click(
lambda: on_export_results_file("csv"),
outputs=[download_csv_btn]
)
download_json_btn.click(
lambda: on_export_results_file("json"),
outputs=[download_json_btn]
)
download_xlsx_btn.click(
lambda: on_export_results_file("xlsx"),
outputs=[download_xlsx_btn]
)
complete_session_btn.click(
on_complete_session,
outputs=[
manual_input_section,
session_info_display,
status_message
]
)
return manual_input_interface