Creates the API layer for Open Notebook Creates a services API gateway for the Streamlit front-end Migrates the SurrealDB SDK to the official one Change all database calls to async New podcast framework supporting multiple speaker configurations Implement the surreal-commands library for async processing Improve docker image and docker-compose configurations
12 KiB
12 KiB
Podcast Page UX Redesign - Architecture Document
🏗️ High-Level System Overview
Before (Current State)
┌─────────────────────────────────────────┐
│ Podcast Page │
├─────────────────────────────────────────┤
│ ┌─────────────────┐ ┌─────────────────┐ │
│ │ Tab: Episodes │ │ Tab: Speakers │ │
│ │ • Episode List │ │ • Complex forms │ │
│ │ • Status │ │ • Session state │ │
│ │ • Audio Player │ │ • Inline edit │ │
│ └─────────────────┘ └─────────────────┘ │
│ ┌─────────────────┐ │
│ │ Tab: Ep Profiles│ │
│ │ • Dropdown deps │ │
│ │ • Complex forms │ │
│ └─────────────────┘ │
└─────────────────────────────────────────┘
After (Target State)
┌─────────────────────────────────────────┐
│ Podcast Page │
├─────────────────────────────────────────┤
│ ┌─────────────────┐ ┌─────────────────┐ │
│ │ Tab: Episodes │ │Tab: Templates │ │
│ │ • Episode List │ │ ┌─────────────┐ │ │
│ │ • Status │ │ │ Header │ │ │
│ │ • Audio Player │ │ │ Explanation │ │ │
│ │ (unchanged) │ │ └─────────────┘ │ │
│ └─────────────────┘ │ ┌───────┐┌────┐ │ │
│ │ │Episode││Spk │ │ │
│ │ │Profile││Pro │ │ │
│ │ │ Area ││Side│ │ │
│ │ │ ││bar │ │ │
│ │ └───────┘└────┘ │ │
│ └─────────────────┘ │
└─────────────────────────────────────────┘
↕ st.dialog
┌─────────────────────────────────────────┐
│ Speaker Configuration │
│ • Create/Edit Form │
│ • Dynamic speaker count │
│ • Model selection │
└─────────────────────────────────────────┘
🔧 Affected Components and Dependencies
Primary File to Modify
pages/5_🎙️_Podcasts.py- Complete restructure with new layout
External Dependencies (No Changes)
api/routers/speaker_profiles.py- Existing CRUD endpointsapi/routers/episode_profiles.py- Existing CRUD endpointsopen_notebook/domain/podcast.py- Data models and validationapi/models_service.py- Model provider/type management
Session State Dependencies
- Current session state keys that will be modified/removed
- New session state structure for dialog management
📱 New Component Structure
Main Layout Components
def render_podcast_page():
"""Main page orchestrator"""
episodes_tab, templates_tab = st.tabs(["Episodes", "Templates"])
with episodes_tab:
render_episodes_section() # Keep existing functionality
with templates_tab:
render_header_section()
col_main, col_side = st.columns([3, 1])
with col_main:
render_episode_profiles_section()
with col_side:
render_speaker_profiles_sidebar()
def render_episodes_section():
"""Episodes list - keep existing functionality unchanged"""
def render_header_section():
"""Explanatory header about relationships and workflow"""
def render_episode_profiles_section():
"""Main focus: Episode profiles CRUD with inline speaker info"""
def render_speaker_profiles_sidebar():
"""Secondary: Speaker profiles overview with usage indicators"""
Dialog Components
@st.dialog("Configure Speaker Profile", width="large")
def speaker_configuration_dialog(mode="create", profile_id=None, episode_context=None):
"""Unified dialog for speaker profile create/edit"""
# Mode: "create" | "edit" | "select_for_episode"
@st.dialog("Confirm Delete")
def confirm_delete_dialog(item_type, item_id, item_name):
"""Reusable confirmation dialog"""
Data Flow Architecture
graph TD
A[User Action] --> B{Action Type}
B -->|Episode CRUD| C[Episode API Calls]
B -->|Speaker Select| D[Open Speaker Dialog]
B -->|Speaker CRUD| E[Speaker API Calls]
D --> F{Dialog Mode}
F -->|Create New| G[Speaker Create Form]
F -->|Edit Existing| H[Speaker Edit Form]
F -->|Select Existing| I[Speaker Dropdown]
G --> J[API Create Speaker]
H --> K[API Update Speaker]
I --> L[Update Episode Reference]
C --> M[Refresh Episode Data]
E --> N[Refresh Speaker Data]
J --> N
K --> N
L --> M
M --> O[Update UI State]
N --> O
🔄 Session State Management Strategy
Current Session State (To Remove)
# Complex nested speaker editing states
st.session_state.new_speakers = [...]
st.session_state.edit_speakers_{profile_id} = [...]
st.session_state.edit_speaker_{profile_id} = True/False
st.session_state.edit_episode_{profile_id} = True/False
New Session State (Simplified)
# Dialog state management
st.session_state.dialog_mode = "create" | "edit" | "select"
st.session_state.dialog_target_id = profile_id | None
st.session_state.episode_context = episode_id | None # When selecting speaker for episode
# Temporary form data (only while dialog open)
st.session_state.dialog_speakers = [...] # Cleared on dialog close
st.session_state.dialog_form_data = {...} # Cleared on dialog close
# Data refresh triggers
st.session_state.refresh_speakers = False
st.session_state.refresh_episodes = False
Session State Lifecycle
- Dialog Open: Initialize temp form data
- Dialog Interaction: Update temp data only
- Dialog Submit: API call + clear temp data + trigger refresh
- Dialog Cancel: Clear temp data only
🎨 UI/UX Patterns
Episode Profile Display
def episode_profile_card(profile, speakers_data):
with st.container(border=True):
col_info, col_actions = st.columns([3, 1])
with col_info:
st.subheader(profile.name)
st.write(profile.description)
render_speaker_info_inline(profile.speaker_config, speakers_data)
render_ai_models_info(profile)
with col_actions:
if st.button("⚙️ Configure Speaker"):
open_speaker_dialog("select", episode_context=profile.id)
if st.button("✏️ Edit"):
open_episode_edit_form(profile.id)
if st.button("🗑️ Delete"):
confirm_delete_dialog("episode", profile.id, profile.name)
Speaker Profile Sidebar
def speaker_profiles_sidebar():
st.subheader("🎤 Speaker Profiles")
if st.button("➕ New Speaker Profile"):
speaker_configuration_dialog("create")
for profile in speaker_profiles:
usage_indicator = get_usage_indicator(profile.name)
with st.expander(f"🎤 {profile.name} {usage_indicator}"):
render_speaker_summary(profile)
col1, col2, col3 = st.columns(3)
with col1:
if st.button("✏️", key=f"edit_sp_{profile.id}"):
speaker_configuration_dialog("edit", profile.id)
with col2:
if st.button("📋", key=f"dup_sp_{profile.id}"):
duplicate_speaker_profile(profile.id)
with col3:
if st.button("🗑️", key=f"del_sp_{profile.id}"):
confirm_delete_dialog("speaker", profile.id, profile.name)
🔒 Data Validation and Constraints
Maintained Validation Rules
- Speaker profiles: 1-4 speakers, all required fields
- Episode profiles: Valid speaker_config reference, valid AI models
- Names must be unique within profile type
- All existing domain model validators preserved
New Validation Requirements
- Speaker profile usage checking before deletion
- Episode profile validation when speaker config changes
- Dialog form validation before submission
⚡ Performance Considerations
Optimizations
- Lazy Loading: Load speaker details only when needed for episode display
- Data Caching: Cache speakers data for episode profile rendering
- Minimal Re-renders: Update only affected sections, not entire page
- Dialog Isolation: Dialog state doesn't trigger main page re-renders
API Call Patterns
# Efficient data loading
async def load_page_data():
speakers, episodes = await asyncio.gather(
fetch_speaker_profiles(),
fetch_episode_profiles()
)
return speakers, episodes
# Speaker usage analysis
def analyze_speaker_usage(speakers, episodes):
usage_map = {}
for episode in episodes:
speaker_name = episode.speaker_config
usage_map[speaker_name] = usage_map.get(speaker_name, 0) + 1
return usage_map
🚀 Implementation Trade-offs
Positive Consequences
- Better UX: Single page workflow eliminates confusion
- Faster Workflow: Inline creation via dialogs
- Clearer Relationships: Visual indicators show usage
- Maintainable Code: Simplified session state management
Negative Consequences
- Code Reorganization: Large refactor of existing file
- Dialog Complexity: More complex dialog state management
- Screen Real Estate: Less space per profile in sidebar
- Migration Effort: Users need to learn new interface
Alternative Approaches Considered
- Keep tabs, improve explanations: Lower impact but doesn't solve core UX issue
- Separate pages with better navigation: Still requires multiple page loads
- Wizard-style workflow: Too rigid for power users
📋 Implementation Priority
Phase 1: Core Structure
- Create new layout with header/main/sidebar
- Move episode profiles to main area
- Move speaker profiles to sidebar (read-only)
Phase 2: Dialog Integration
- Implement speaker configuration dialog
- Add create/edit/select modes
- Integrate with episode profile workflow
Phase 3: Polish & Optimization
- Add usage indicators
- Optimize data loading
- Add better validation feedback
- Polish animations and interactions
📁 Files to Edit/Create
Primary Modification
pages/5_🎙️_Podcasts.py- Complete rewrite (~900 lines → ~600 lines)
No Changes Required
- API routers and services (well-designed, reusable)
- Domain models (validation rules preserved)
- Database schema (no data migration needed)
Validation Notes
- All existing API endpoints remain unchanged
- All existing data models and validation preserved
- Migration path: gradual rollout possible by feature flag
- Backward compatibility: API contracts unchanged
Architecture Ready for Implementation ✅
This architecture maintains all existing functionality while dramatically improving the user experience through better information architecture and progressive disclosure patterns.