alwrity chatbot assistant, content scheduler, and content repurposing
This commit is contained in:
7
lib/content_scheduler/ui/__init__.py
Normal file
7
lib/content_scheduler/ui/__init__.py
Normal file
@@ -0,0 +1,7 @@
|
||||
"""
|
||||
UI module for the Content Scheduler dashboard.
|
||||
"""
|
||||
|
||||
from .dashboard import run_dashboard
|
||||
|
||||
__all__ = ['run_dashboard']
|
||||
386
lib/content_scheduler/ui/dashboard.py
Normal file
386
lib/content_scheduler/ui/dashboard.py
Normal file
@@ -0,0 +1,386 @@
|
||||
"""
|
||||
Main dashboard implementation for the Content Scheduler.
|
||||
"""
|
||||
|
||||
import streamlit as st
|
||||
import pandas as pd
|
||||
from datetime import datetime, timedelta
|
||||
from typing import List, Dict, Any
|
||||
import plotly.express as px
|
||||
import plotly.graph_objects as go
|
||||
from lib.database.models import ContentItem, Schedule, ScheduleStatus, ContentType, Platform, get_engine, get_session, init_db
|
||||
|
||||
engine = get_engine()
|
||||
init_db(engine)
|
||||
session = get_session(engine)
|
||||
|
||||
def run_dashboard():
|
||||
"""Run the Streamlit dashboard."""
|
||||
|
||||
st.title("📅 Alwrity Content Scheduler Dashboard")
|
||||
|
||||
# Sidebar navigation
|
||||
st.sidebar.title("Navigation")
|
||||
page = st.sidebar.radio(
|
||||
"Go to",
|
||||
["Overview", "Schedule Management", "Create Schedule", "Job Monitor", "Analytics"]
|
||||
)
|
||||
|
||||
if page == "Overview":
|
||||
show_overview()
|
||||
elif page == "Schedule Management":
|
||||
show_schedule_management()
|
||||
elif page == "Create Schedule":
|
||||
show_create_schedule()
|
||||
elif page == "Job Monitor":
|
||||
show_job_monitor()
|
||||
else:
|
||||
show_analytics()
|
||||
|
||||
def show_overview():
|
||||
"""Display the overview dashboard."""
|
||||
st.header("📊 Overview")
|
||||
|
||||
# Get data from unified database
|
||||
all_content = session.query(ContentItem).all()
|
||||
all_schedules = session.query(Schedule).all()
|
||||
|
||||
# Display metrics
|
||||
col1, col2, col3, col4 = st.columns(4)
|
||||
|
||||
with col1:
|
||||
st.metric("Total Content Items", len(all_content))
|
||||
|
||||
with col2:
|
||||
scheduled_count = len([s for s in all_schedules if s.status == ScheduleStatus.SCHEDULED])
|
||||
st.metric("Scheduled Items", scheduled_count)
|
||||
|
||||
with col3:
|
||||
completed_count = len([s for s in all_schedules if s.status == ScheduleStatus.COMPLETED])
|
||||
st.metric("Completed", completed_count)
|
||||
|
||||
with col4:
|
||||
failed_count = len([s for s in all_schedules if s.status == ScheduleStatus.FAILED])
|
||||
st.metric("Failed", failed_count)
|
||||
|
||||
# Recent content
|
||||
st.subheader("📝 Recent Content Items")
|
||||
if all_content:
|
||||
recent_content = sorted(all_content, key=lambda x: x.created_at, reverse=True)[:5]
|
||||
for item in recent_content:
|
||||
with st.expander(f"{item.title} ({item.content_type.value})"):
|
||||
st.write(f"**Description:** {item.description or 'No description'}")
|
||||
st.write(f"**Platforms:** {', '.join(item.platforms) if isinstance(item.platforms, list) else item.platforms}")
|
||||
st.write(f"**Status:** {item.status}")
|
||||
st.write(f"**Created:** {item.created_at}")
|
||||
|
||||
# Show associated schedules
|
||||
item_schedules = [s for s in all_schedules if s.content_item_id == item.id]
|
||||
if item_schedules:
|
||||
st.write("**Schedules:**")
|
||||
for schedule in item_schedules:
|
||||
st.write(f" - {schedule.scheduled_time} ({schedule.status.value})")
|
||||
else:
|
||||
st.info("No content items found. Create some content in the Content Calendar first!")
|
||||
|
||||
def show_schedule_management():
|
||||
"""Display the schedule management interface."""
|
||||
st.header("📅 Schedule Management")
|
||||
|
||||
# Get all schedules
|
||||
all_schedules = session.query(Schedule).all()
|
||||
|
||||
if not all_schedules:
|
||||
st.info("No schedules found. Create schedules from the 'Create Schedule' tab.")
|
||||
return
|
||||
|
||||
# Filter options
|
||||
col1, col2 = st.columns(2)
|
||||
with col1:
|
||||
status_filter = st.selectbox(
|
||||
"Filter by Status",
|
||||
options=["All"] + [status.value for status in ScheduleStatus],
|
||||
key="schedule_status_filter"
|
||||
)
|
||||
|
||||
with col2:
|
||||
date_filter = st.date_input(
|
||||
"Filter by Date (from)",
|
||||
value=datetime.now().date() - timedelta(days=30),
|
||||
key="schedule_date_filter"
|
||||
)
|
||||
|
||||
# Apply filters
|
||||
filtered_schedules = all_schedules
|
||||
if status_filter != "All":
|
||||
filtered_schedules = [s for s in filtered_schedules if s.status.value == status_filter]
|
||||
|
||||
filtered_schedules = [s for s in filtered_schedules if s.scheduled_time.date() >= date_filter]
|
||||
|
||||
# Display schedules
|
||||
st.subheader(f"📋 Schedules ({len(filtered_schedules)} items)")
|
||||
|
||||
for schedule in sorted(filtered_schedules, key=lambda x: x.scheduled_time, reverse=True):
|
||||
content_item = session.query(ContentItem).get(schedule.content_item_id)
|
||||
|
||||
if content_item:
|
||||
with st.expander(f"{content_item.title} - {schedule.scheduled_time.strftime('%Y-%m-%d %H:%M')} ({schedule.status.value})"):
|
||||
col1, col2 = st.columns(2)
|
||||
|
||||
with col1:
|
||||
st.write(f"**Content:** {content_item.title}")
|
||||
st.write(f"**Type:** {content_item.content_type.value}")
|
||||
st.write(f"**Platforms:** {', '.join(content_item.platforms) if isinstance(content_item.platforms, list) else content_item.platforms}")
|
||||
st.write(f"**Scheduled Time:** {schedule.scheduled_time}")
|
||||
st.write(f"**Status:** {schedule.status.value}")
|
||||
|
||||
with col2:
|
||||
st.write(f"**Recurrence:** {schedule.recurrence or 'One-time'}")
|
||||
st.write(f"**Priority:** {schedule.priority}")
|
||||
st.write(f"**Created:** {schedule.created_at}")
|
||||
if schedule.result:
|
||||
st.write(f"**Result:** {schedule.result}")
|
||||
|
||||
# Action buttons
|
||||
col1, col2, col3 = st.columns(3)
|
||||
|
||||
with col1:
|
||||
if st.button(f"Edit Schedule", key=f"edit_{schedule.id}"):
|
||||
st.session_state.edit_schedule_id = schedule.id
|
||||
st.rerun()
|
||||
|
||||
with col2:
|
||||
if schedule.status == ScheduleStatus.SCHEDULED:
|
||||
if st.button(f"Cancel", key=f"cancel_{schedule.id}"):
|
||||
schedule.status = ScheduleStatus.CANCELLED
|
||||
session.commit()
|
||||
st.success("Schedule cancelled!")
|
||||
st.rerun()
|
||||
|
||||
with col3:
|
||||
if st.button(f"Delete", key=f"delete_{schedule.id}"):
|
||||
session.delete(schedule)
|
||||
session.commit()
|
||||
st.success("Schedule deleted!")
|
||||
st.rerun()
|
||||
|
||||
def show_create_schedule():
|
||||
"""Display the schedule creation interface."""
|
||||
st.header("➕ Create New Schedule")
|
||||
|
||||
# Get available content items
|
||||
content_items = session.query(ContentItem).all()
|
||||
|
||||
if not content_items:
|
||||
st.warning("No content items available. Please create content in the Content Calendar first.")
|
||||
return
|
||||
|
||||
# Create schedule form
|
||||
with st.form("create_schedule_form"):
|
||||
st.subheader("Schedule Configuration")
|
||||
|
||||
# Select content item
|
||||
content_options = {f"{item.title} ({item.content_type.value})": item.id for item in content_items}
|
||||
selected_content = st.selectbox(
|
||||
"Select Content Item",
|
||||
options=list(content_options.keys()),
|
||||
key="schedule_content_select"
|
||||
)
|
||||
|
||||
# Schedule timing
|
||||
col1, col2 = st.columns(2)
|
||||
with col1:
|
||||
schedule_date = st.date_input(
|
||||
"Schedule Date",
|
||||
value=datetime.now().date() + timedelta(days=1),
|
||||
key="schedule_date"
|
||||
)
|
||||
|
||||
with col2:
|
||||
schedule_time = st.time_input(
|
||||
"Schedule Time",
|
||||
value=datetime.now().time(),
|
||||
key="schedule_time"
|
||||
)
|
||||
|
||||
# Combine date and time
|
||||
schedule_datetime = datetime.combine(schedule_date, schedule_time)
|
||||
|
||||
# Recurrence options
|
||||
recurrence = st.selectbox(
|
||||
"Recurrence",
|
||||
options=["none", "daily", "weekly", "monthly"],
|
||||
key="schedule_recurrence"
|
||||
)
|
||||
|
||||
# Priority
|
||||
priority = st.slider(
|
||||
"Priority",
|
||||
min_value=1,
|
||||
max_value=10,
|
||||
value=5,
|
||||
key="schedule_priority"
|
||||
)
|
||||
|
||||
# Platform selection (override content item platforms if needed)
|
||||
content_item_id = content_options[selected_content]
|
||||
content_item = session.query(ContentItem).get(content_item_id)
|
||||
|
||||
if content_item:
|
||||
current_platforms = content_item.platforms if isinstance(content_item.platforms, list) else [content_item.platforms]
|
||||
st.write(f"**Current Platforms:** {', '.join(current_platforms)}")
|
||||
|
||||
override_platforms = st.checkbox("Override Platforms", key="override_platforms")
|
||||
|
||||
if override_platforms:
|
||||
available_platforms = [p.value for p in Platform]
|
||||
selected_platforms = st.multiselect(
|
||||
"Select Platforms",
|
||||
options=available_platforms,
|
||||
default=current_platforms,
|
||||
key="schedule_platforms"
|
||||
)
|
||||
else:
|
||||
selected_platforms = current_platforms
|
||||
|
||||
# Submit button
|
||||
submitted = st.form_submit_button("Create Schedule")
|
||||
|
||||
if submitted:
|
||||
try:
|
||||
# Create new schedule
|
||||
new_schedule = Schedule(
|
||||
content_item_id=content_item_id,
|
||||
scheduled_time=schedule_datetime,
|
||||
status=ScheduleStatus.SCHEDULED,
|
||||
recurrence=recurrence if recurrence != "none" else None,
|
||||
priority=priority
|
||||
)
|
||||
|
||||
session.add(new_schedule)
|
||||
session.commit()
|
||||
|
||||
st.success(f"✅ Schedule created successfully! Content will be published on {schedule_datetime}")
|
||||
|
||||
# Show schedule details
|
||||
with st.expander("Schedule Details", expanded=True):
|
||||
st.write(f"**Content:** {content_item.title}")
|
||||
st.write(f"**Scheduled Time:** {schedule_datetime}")
|
||||
st.write(f"**Platforms:** {', '.join(selected_platforms)}")
|
||||
st.write(f"**Recurrence:** {recurrence}")
|
||||
st.write(f"**Priority:** {priority}")
|
||||
|
||||
except Exception as e:
|
||||
st.error(f"❌ Error creating schedule: {str(e)}")
|
||||
|
||||
def show_job_monitor():
|
||||
"""Display the job monitoring interface."""
|
||||
st.header("🔍 Job Monitor")
|
||||
|
||||
# Get all schedules with their status
|
||||
all_schedules = session.query(Schedule).all()
|
||||
|
||||
if not all_schedules:
|
||||
st.info("No jobs to monitor.")
|
||||
return
|
||||
|
||||
# Status distribution
|
||||
status_counts = {}
|
||||
for schedule in all_schedules:
|
||||
status = schedule.status.value
|
||||
status_counts[status] = status_counts.get(status, 0) + 1
|
||||
|
||||
# Display status chart
|
||||
if status_counts:
|
||||
fig = px.pie(
|
||||
values=list(status_counts.values()),
|
||||
names=list(status_counts.keys()),
|
||||
title="Job Status Distribution"
|
||||
)
|
||||
st.plotly_chart(fig, use_container_width=True)
|
||||
|
||||
# Recent job activity
|
||||
st.subheader("📊 Recent Job Activity")
|
||||
|
||||
recent_schedules = sorted(all_schedules, key=lambda x: x.updated_at, reverse=True)[:10]
|
||||
|
||||
for schedule in recent_schedules:
|
||||
content_item = session.query(ContentItem).get(schedule.content_item_id)
|
||||
|
||||
if content_item:
|
||||
status_color = {
|
||||
ScheduleStatus.SCHEDULED: "🟡",
|
||||
ScheduleStatus.RUNNING: "🔵",
|
||||
ScheduleStatus.COMPLETED: "🟢",
|
||||
ScheduleStatus.FAILED: "🔴",
|
||||
ScheduleStatus.CANCELLED: "⚫"
|
||||
}.get(schedule.status, "⚪")
|
||||
|
||||
st.write(f"{status_color} **{content_item.title}** - {schedule.status.value} - {schedule.updated_at.strftime('%Y-%m-%d %H:%M')}")
|
||||
|
||||
if schedule.result:
|
||||
st.write(f" └─ {schedule.result}")
|
||||
|
||||
def show_analytics():
|
||||
"""Display the analytics dashboard."""
|
||||
st.header("📈 Analytics")
|
||||
|
||||
# Get data
|
||||
all_content = session.query(ContentItem).all()
|
||||
all_schedules = session.query(Schedule).all()
|
||||
|
||||
if not all_schedules:
|
||||
st.info("No data available for analytics.")
|
||||
return
|
||||
|
||||
# Time-based analytics
|
||||
st.subheader("📅 Schedule Timeline")
|
||||
|
||||
# Create timeline data
|
||||
timeline_data = []
|
||||
for schedule in all_schedules:
|
||||
content_item = session.query(ContentItem).get(schedule.content_item_id)
|
||||
if content_item:
|
||||
timeline_data.append({
|
||||
'Date': schedule.scheduled_time.date(),
|
||||
'Content': content_item.title,
|
||||
'Status': schedule.status.value,
|
||||
'Type': content_item.content_type.value
|
||||
})
|
||||
|
||||
if timeline_data:
|
||||
df = pd.DataFrame(timeline_data)
|
||||
|
||||
# Schedule frequency by date
|
||||
date_counts = df.groupby('Date').size().reset_index(name='Count')
|
||||
fig = px.line(date_counts, x='Date', y='Count', title='Scheduled Content Over Time')
|
||||
st.plotly_chart(fig, use_container_width=True)
|
||||
|
||||
# Content type distribution
|
||||
type_counts = df['Type'].value_counts()
|
||||
fig = px.bar(x=type_counts.index, y=type_counts.values, title='Content Type Distribution')
|
||||
st.plotly_chart(fig, use_container_width=True)
|
||||
|
||||
# Status breakdown
|
||||
status_counts = df['Status'].value_counts()
|
||||
fig = px.pie(values=status_counts.values, names=status_counts.index, title='Status Distribution')
|
||||
st.plotly_chart(fig, use_container_width=True)
|
||||
|
||||
# Performance metrics
|
||||
st.subheader("📊 Performance Metrics")
|
||||
|
||||
col1, col2, col3 = st.columns(3)
|
||||
|
||||
with col1:
|
||||
total_schedules = len(all_schedules)
|
||||
st.metric("Total Schedules", total_schedules)
|
||||
|
||||
with col2:
|
||||
completed_schedules = len([s for s in all_schedules if s.status == ScheduleStatus.COMPLETED])
|
||||
success_rate = (completed_schedules / total_schedules * 100) if total_schedules > 0 else 0
|
||||
st.metric("Success Rate", f"{success_rate:.1f}%")
|
||||
|
||||
with col3:
|
||||
failed_schedules = len([s for s in all_schedules if s.status == ScheduleStatus.FAILED])
|
||||
failure_rate = (failed_schedules / total_schedules * 100) if total_schedules > 0 else 0
|
||||
st.metric("Failure Rate", f"{failure_rate:.1f}%")
|
||||
392
lib/content_scheduler/ui/views/timeline_view.py
Normal file
392
lib/content_scheduler/ui/views/timeline_view.py
Normal file
@@ -0,0 +1,392 @@
|
||||
"""
|
||||
Timeline view implementation for the Content Scheduler.
|
||||
Provides interactive Gantt charts and progress tracking visualization.
|
||||
"""
|
||||
|
||||
import streamlit as st
|
||||
import plotly.figure_factory as ff
|
||||
import plotly.graph_objects as go
|
||||
from datetime import datetime, timedelta
|
||||
from typing import List, Dict, Any, Optional
|
||||
import pandas as pd
|
||||
import json
|
||||
|
||||
# Use unified database models
|
||||
from lib.database.models import ContentItem, Schedule, ScheduleStatus, get_session
|
||||
|
||||
class TimelineView:
|
||||
"""Interactive timeline view with Gantt charts and progress tracking."""
|
||||
|
||||
def __init__(self):
|
||||
"""Initialize the timeline view."""
|
||||
self.session = get_session()
|
||||
|
||||
def render(self):
|
||||
"""Render the timeline view."""
|
||||
st.header("Schedule Timeline")
|
||||
|
||||
# Timeline controls
|
||||
self._render_timeline_controls()
|
||||
|
||||
# Main timeline view
|
||||
self._render_timeline()
|
||||
|
||||
# Progress tracking
|
||||
self._render_progress_tracking()
|
||||
|
||||
def _render_timeline_controls(self):
|
||||
"""Render timeline control options."""
|
||||
col1, col2, col3 = st.columns([2, 2, 1])
|
||||
|
||||
with col1:
|
||||
view_type = st.selectbox(
|
||||
"View Type",
|
||||
["Gantt Chart", "Timeline", "List View"],
|
||||
help="Select the type of timeline visualization"
|
||||
)
|
||||
|
||||
with col2:
|
||||
date_range = st.date_input(
|
||||
"Date Range",
|
||||
value=(
|
||||
datetime.now().date(),
|
||||
datetime.now().date() + timedelta(days=7)
|
||||
),
|
||||
help="Select the date range to display"
|
||||
)
|
||||
|
||||
with col3:
|
||||
if st.button("Export", help="Export timeline data"):
|
||||
self._export_timeline_data()
|
||||
|
||||
def _render_timeline(self):
|
||||
"""Render the main timeline visualization."""
|
||||
# Get schedules for the selected date range
|
||||
schedules = self._get_schedules_for_timeline()
|
||||
|
||||
if not schedules:
|
||||
st.info("No schedules found for the selected date range.")
|
||||
return
|
||||
|
||||
# Create Gantt chart data
|
||||
gantt_data = self._create_gantt_data(schedules)
|
||||
|
||||
# Create and display Gantt chart
|
||||
fig = self._create_gantt_chart(gantt_data)
|
||||
st.plotly_chart(fig, use_container_width=True)
|
||||
|
||||
# Display schedule details
|
||||
self._render_schedule_details(schedules)
|
||||
|
||||
def _render_progress_tracking(self):
|
||||
"""Render progress tracking visualization."""
|
||||
st.subheader("Progress Tracking")
|
||||
|
||||
# Progress metrics
|
||||
col1, col2, col3 = st.columns(3)
|
||||
|
||||
with col1:
|
||||
self._render_progress_metric(
|
||||
"Completed",
|
||||
self._get_completed_count(),
|
||||
"green"
|
||||
)
|
||||
|
||||
with col2:
|
||||
self._render_progress_metric(
|
||||
"In Progress",
|
||||
self._get_in_progress_count(),
|
||||
"orange"
|
||||
)
|
||||
|
||||
with col3:
|
||||
self._render_progress_metric(
|
||||
"Pending",
|
||||
self._get_pending_count(),
|
||||
"blue"
|
||||
)
|
||||
|
||||
# Progress chart
|
||||
self._render_progress_chart()
|
||||
|
||||
def _get_schedules_for_timeline(self) -> List[Schedule]:
|
||||
"""Get schedules for the timeline view."""
|
||||
try:
|
||||
# Get date range from session state or use default
|
||||
if hasattr(st.session_state, 'date_range') and st.session_state.date_range:
|
||||
start_date, end_date = st.session_state.date_range
|
||||
else:
|
||||
start_date = datetime.now().date()
|
||||
end_date = start_date + timedelta(days=7)
|
||||
|
||||
# Convert to datetime
|
||||
start_datetime = datetime.combine(start_date, datetime.min.time())
|
||||
end_datetime = datetime.combine(end_date, datetime.max.time())
|
||||
|
||||
# Query schedules from unified database
|
||||
schedules = self.session.query(Schedule).filter(
|
||||
Schedule.scheduled_time >= start_datetime,
|
||||
Schedule.scheduled_time <= end_datetime
|
||||
).all()
|
||||
|
||||
return schedules
|
||||
|
||||
except Exception as e:
|
||||
st.error(f"Failed to get schedules: {str(e)}")
|
||||
return []
|
||||
|
||||
def _create_gantt_data(self, schedules: List[Schedule]) -> List[Dict[str, Any]]:
|
||||
"""Create data for Gantt chart."""
|
||||
gantt_data = []
|
||||
|
||||
for schedule in schedules:
|
||||
# Get content item details
|
||||
content_item = self.session.query(ContentItem).filter(
|
||||
ContentItem.id == schedule.content_item_id
|
||||
).first()
|
||||
|
||||
if content_item:
|
||||
# Calculate task duration
|
||||
duration = timedelta(hours=1) # Default duration
|
||||
|
||||
# Create task data
|
||||
task = {
|
||||
'Task': content_item.title[:50] + "..." if len(content_item.title) > 50 else content_item.title,
|
||||
'Start': schedule.scheduled_time,
|
||||
'Finish': schedule.scheduled_time + duration,
|
||||
'Resource': schedule.status.value,
|
||||
'Status': schedule.status.value,
|
||||
'Progress': self._calculate_progress(schedule)
|
||||
}
|
||||
|
||||
gantt_data.append(task)
|
||||
|
||||
return gantt_data
|
||||
|
||||
def _create_gantt_chart(self, gantt_data: List[Dict[str, Any]]) -> go.Figure:
|
||||
"""Create Gantt chart visualization."""
|
||||
if not gantt_data:
|
||||
# Return empty figure
|
||||
fig = go.Figure()
|
||||
fig.update_layout(
|
||||
title='Content Schedule Timeline',
|
||||
xaxis_title='Timeline',
|
||||
yaxis_title='Status',
|
||||
height=400
|
||||
)
|
||||
return fig
|
||||
|
||||
# Convert data to DataFrame
|
||||
df = pd.DataFrame(gantt_data)
|
||||
|
||||
# Create Gantt chart
|
||||
fig = ff.create_gantt(
|
||||
df,
|
||||
index_col='Resource',
|
||||
show_colorbar=True,
|
||||
group_tasks=True,
|
||||
showgrid_x=True,
|
||||
showgrid_y=True
|
||||
)
|
||||
|
||||
# Update layout
|
||||
fig.update_layout(
|
||||
title='Content Schedule Timeline',
|
||||
xaxis_title='Timeline',
|
||||
yaxis_title='Status',
|
||||
height=400,
|
||||
showlegend=True
|
||||
)
|
||||
|
||||
return fig
|
||||
|
||||
def _render_schedule_details(self, schedules: List[Schedule]):
|
||||
"""Render detailed schedule information."""
|
||||
st.subheader("Schedule Details")
|
||||
|
||||
for schedule in schedules:
|
||||
# Get content item details
|
||||
content_item = self.session.query(ContentItem).filter(
|
||||
ContentItem.id == schedule.content_item_id
|
||||
).first()
|
||||
|
||||
if content_item:
|
||||
with st.expander(f"{content_item.title} - {schedule.status.value}"):
|
||||
col1, col2 = st.columns(2)
|
||||
|
||||
with col1:
|
||||
st.write("**Schedule Information**")
|
||||
st.write(f"Content Type: {content_item.content_type.value if content_item.content_type else 'Unknown'}")
|
||||
st.write(f"Status: {schedule.status.value}")
|
||||
st.write(f"Scheduled Time: {schedule.scheduled_time}")
|
||||
st.write(f"Priority: {schedule.priority}")
|
||||
if schedule.recurrence:
|
||||
st.write(f"Recurrence: {schedule.recurrence}")
|
||||
|
||||
with col2:
|
||||
st.write("**Progress**")
|
||||
progress = self._calculate_progress(schedule)
|
||||
st.progress(progress / 100)
|
||||
st.write(f"Progress: {progress:.1f}%")
|
||||
|
||||
# Action buttons
|
||||
col2a, col2b = st.columns(2)
|
||||
with col2a:
|
||||
if st.button(f"Edit {schedule.id}", key=f"edit_{schedule.id}"):
|
||||
st.session_state.edit_schedule_id = schedule.id
|
||||
with col2b:
|
||||
if st.button(f"Cancel {schedule.id}", key=f"cancel_{schedule.id}"):
|
||||
self._cancel_schedule(schedule.id)
|
||||
|
||||
def _render_progress_metric(self, label: str, value: int, color: str):
|
||||
"""Render a progress metric."""
|
||||
st.metric(label, value)
|
||||
|
||||
def _render_progress_chart(self):
|
||||
"""Render progress chart visualization."""
|
||||
try:
|
||||
# Get progress data
|
||||
progress_data = self._get_progress_data()
|
||||
|
||||
if progress_data:
|
||||
# Create pie chart
|
||||
labels = list(progress_data.keys())
|
||||
values = list(progress_data.values())
|
||||
|
||||
fig = go.Figure(data=[go.Pie(labels=labels, values=values)])
|
||||
fig.update_layout(
|
||||
title="Schedule Status Distribution",
|
||||
height=300
|
||||
)
|
||||
|
||||
st.plotly_chart(fig, use_container_width=True)
|
||||
else:
|
||||
st.info("No progress data available.")
|
||||
|
||||
except Exception as e:
|
||||
st.error(f"Error rendering progress chart: {str(e)}")
|
||||
|
||||
def _calculate_progress(self, schedule: Schedule) -> float:
|
||||
"""Calculate progress percentage for a schedule."""
|
||||
try:
|
||||
if schedule.status == ScheduleStatus.COMPLETED:
|
||||
return 100.0
|
||||
elif schedule.status == ScheduleStatus.RUNNING:
|
||||
return 50.0
|
||||
elif schedule.status == ScheduleStatus.FAILED:
|
||||
return 0.0
|
||||
else: # PENDING
|
||||
return 0.0
|
||||
|
||||
except Exception as e:
|
||||
st.error(f"Error calculating progress: {str(e)}")
|
||||
return 0.0
|
||||
|
||||
def _get_completed_count(self) -> int:
|
||||
"""Get count of completed schedules."""
|
||||
try:
|
||||
return self.session.query(Schedule).filter(
|
||||
Schedule.status == ScheduleStatus.COMPLETED
|
||||
).count()
|
||||
except Exception as e:
|
||||
st.error(f"Error getting completed count: {str(e)}")
|
||||
return 0
|
||||
|
||||
def _get_in_progress_count(self) -> int:
|
||||
"""Get count of in-progress schedules."""
|
||||
try:
|
||||
return self.session.query(Schedule).filter(
|
||||
Schedule.status == ScheduleStatus.RUNNING
|
||||
).count()
|
||||
except Exception as e:
|
||||
st.error(f"Error getting in-progress count: {str(e)}")
|
||||
return 0
|
||||
|
||||
def _get_pending_count(self) -> int:
|
||||
"""Get count of pending schedules."""
|
||||
try:
|
||||
return self.session.query(Schedule).filter(
|
||||
Schedule.status == ScheduleStatus.PENDING
|
||||
).count()
|
||||
except Exception as e:
|
||||
st.error(f"Error getting pending count: {str(e)}")
|
||||
return 0
|
||||
|
||||
def _get_progress_data(self) -> Dict[str, int]:
|
||||
"""Get progress data for visualization."""
|
||||
try:
|
||||
progress_data = {}
|
||||
|
||||
# Count schedules by status
|
||||
for status in ScheduleStatus:
|
||||
count = self.session.query(Schedule).filter(
|
||||
Schedule.status == status
|
||||
).count()
|
||||
progress_data[status.value] = count
|
||||
|
||||
return progress_data
|
||||
|
||||
except Exception as e:
|
||||
st.error(f"Error getting progress data: {str(e)}")
|
||||
return {}
|
||||
|
||||
def _cancel_schedule(self, schedule_id: int):
|
||||
"""Cancel a schedule."""
|
||||
try:
|
||||
schedule = self.session.query(Schedule).filter(
|
||||
Schedule.id == schedule_id
|
||||
).first()
|
||||
|
||||
if schedule:
|
||||
schedule.status = ScheduleStatus.CANCELLED
|
||||
self.session.commit()
|
||||
st.success(f"Schedule {schedule_id} cancelled successfully!")
|
||||
st.experimental_rerun()
|
||||
else:
|
||||
st.error("Schedule not found.")
|
||||
|
||||
except Exception as e:
|
||||
st.error(f"Error cancelling schedule: {str(e)}")
|
||||
self.session.rollback()
|
||||
|
||||
def _export_timeline_data(self):
|
||||
"""Export timeline data."""
|
||||
try:
|
||||
schedules = self._get_schedules_for_timeline()
|
||||
|
||||
if schedules:
|
||||
# Prepare export data
|
||||
export_data = []
|
||||
|
||||
for schedule in schedules:
|
||||
content_item = self.session.query(ContentItem).filter(
|
||||
ContentItem.id == schedule.content_item_id
|
||||
).first()
|
||||
|
||||
if content_item:
|
||||
export_data.append({
|
||||
'Schedule ID': schedule.id,
|
||||
'Title': content_item.title,
|
||||
'Content Type': content_item.content_type.value if content_item.content_type else 'Unknown',
|
||||
'Scheduled Time': schedule.scheduled_time.isoformat(),
|
||||
'Status': schedule.status.value,
|
||||
'Priority': schedule.priority,
|
||||
'Recurrence': schedule.recurrence or 'None'
|
||||
})
|
||||
|
||||
# Convert to CSV
|
||||
df = pd.DataFrame(export_data)
|
||||
csv = df.to_csv(index=False)
|
||||
|
||||
# Provide download
|
||||
st.download_button(
|
||||
label="Download Timeline Data",
|
||||
data=csv,
|
||||
file_name=f"timeline_data_{datetime.now().strftime('%Y%m%d_%H%M%S')}.csv",
|
||||
mime="text/csv"
|
||||
)
|
||||
else:
|
||||
st.warning("No data to export.")
|
||||
|
||||
except Exception as e:
|
||||
st.error(f"Error exporting data: {str(e)}")
|
||||
Reference in New Issue
Block a user