import React, { useState, useEffect, useCallback } from 'react'; import { Button } from '../ui/button'; import { ScrollArea } from '../ui/scroll-area'; import BackButton from '../ui/BackButton'; import { Card } from '../ui/card'; import MoreMenuLayout from '../more_menu/MoreMenuLayout'; import { fetchSessionDetails, SessionDetails } from '../../sessions'; import { getScheduleSessions, runScheduleNow, listSchedules, ScheduledJob } from '../../schedule'; import SessionHistoryView from '../sessions/SessionHistoryView'; import { toastError, toastSuccess } from '../../toasts'; import { Loader2 } from 'lucide-react'; import cronstrue from 'cronstrue'; interface ScheduleSessionMeta { id: string; name: string; createdAt: string; workingDir?: string; scheduleId?: string | null; messageCount?: number; totalTokens?: number | null; inputTokens?: number | null; outputTokens?: number | null; accumulatedTotalTokens?: number | null; accumulatedInputTokens?: number | null; accumulatedOutputTokens?: number | null; } interface ScheduleDetailViewProps { scheduleId: string | null; onNavigateBack: () => void; } const ScheduleDetailView: React.FC = ({ scheduleId, onNavigateBack }) => { const [sessions, setSessions] = useState([]); const [isLoadingSessions, setIsLoadingSessions] = useState(false); const [sessionsError, setSessionsError] = useState(null); const [runNowLoading, setRunNowLoading] = useState(false); const [scheduleDetails, setScheduleDetails] = useState(null); const [isLoadingSchedule, setIsLoadingSchedule] = useState(false); const [scheduleError, setScheduleError] = useState(null); const [selectedSessionDetails, setSelectedSessionDetails] = useState(null); const [isLoadingSessionDetails, setIsLoadingSessionDetails] = useState(false); const [sessionDetailsError, setSessionDetailsError] = useState(null); const fetchScheduleSessions = useCallback(async (sId: string) => { if (!sId) return; setIsLoadingSessions(true); setSessionsError(null); try { const fetchedSessions = await getScheduleSessions(sId, 20); // MODIFIED // Assuming ScheduleSession from ../../schedule can be cast or mapped to ScheduleSessionMeta // You may need to transform/map fields if they differ significantly setSessions(fetchedSessions as ScheduleSessionMeta[]); } catch (err) { console.error('Failed to fetch schedule sessions:', err); setSessionsError(err instanceof Error ? err.message : 'Failed to fetch schedule sessions'); } finally { setIsLoadingSessions(false); } }, []); const fetchScheduleDetails = useCallback(async (sId: string) => { if (!sId) return; setIsLoadingSchedule(true); setScheduleError(null); try { const allSchedules = await listSchedules(); const schedule = allSchedules.find((s) => s.id === sId); if (schedule) { setScheduleDetails(schedule); } else { setScheduleError('Schedule not found'); } } catch (err) { console.error('Failed to fetch schedule details:', err); setScheduleError(err instanceof Error ? err.message : 'Failed to fetch schedule details'); } finally { setIsLoadingSchedule(false); } }, []); const getReadableCron = (cronString: string) => { try { return cronstrue.toString(cronString); } catch (e) { console.warn(`Could not parse cron string "${cronString}":`, e); return cronString; } }; useEffect(() => { if (scheduleId && !selectedSessionDetails) { fetchScheduleSessions(scheduleId); fetchScheduleDetails(scheduleId); } else if (!scheduleId) { setSessions([]); setSessionsError(null); setRunNowLoading(false); setSelectedSessionDetails(null); setScheduleDetails(null); setScheduleError(null); } }, [scheduleId, fetchScheduleSessions, fetchScheduleDetails, selectedSessionDetails]); const handleRunNow = async () => { if (!scheduleId) return; setRunNowLoading(true); try { const newSessionId = await runScheduleNow(scheduleId); // MODIFIED toastSuccess({ title: 'Schedule Triggered', msg: `Successfully triggered schedule. New session ID: ${newSessionId}`, }); setTimeout(() => { if (scheduleId) { fetchScheduleSessions(scheduleId); fetchScheduleDetails(scheduleId); } }, 1000); } catch (err) { console.error('Failed to run schedule now:', err); const errorMsg = err instanceof Error ? err.message : 'Failed to trigger schedule'; toastError({ title: 'Run Schedule Error', msg: errorMsg }); } finally { setRunNowLoading(false); } }; // Add a periodic refresh for schedule details to keep the running status up to date useEffect(() => { if (!scheduleId) return; // Initial fetch fetchScheduleDetails(scheduleId); // Set up periodic refresh every 5 seconds const intervalId = setInterval(() => { if (scheduleId) { fetchScheduleDetails(scheduleId); } }, 5000); // Clean up on unmount or when scheduleId changes return () => { clearInterval(intervalId); }; }, [scheduleId, fetchScheduleDetails]); const loadAndShowSessionDetails = async (sessionId: string) => { setIsLoadingSessionDetails(true); setSessionDetailsError(null); setSelectedSessionDetails(null); try { const details = await fetchSessionDetails(sessionId); setSelectedSessionDetails(details); } catch (err) { console.error(`Failed to load session details for ${sessionId}:`, err); const errorMsg = err instanceof Error ? err.message : 'Failed to load session details.'; setSessionDetailsError(errorMsg); toastError({ title: 'Failed to load session details', msg: errorMsg, }); } finally { setIsLoadingSessionDetails(false); } }; const handleSessionCardClick = (sessionIdFromCard: string) => { loadAndShowSessionDetails(sessionIdFromCard); }; const handleResumeViewedSession = () => { if (selectedSessionDetails) { const { session_id, metadata } = selectedSessionDetails; if (metadata.working_dir) { console.log( `Resuming session ID ${session_id} in new chat window. Dir: ${metadata.working_dir}` ); window.electron.createChatWindow(undefined, metadata.working_dir, undefined, session_id); } else { console.error('Cannot resume session: working directory is missing.'); toastError({ title: 'Cannot Resume Session', msg: 'Working directory is missing.' }); } } }; if (selectedSessionDetails) { return ( { setSelectedSessionDetails(null); setSessionDetailsError(null); }} onResume={handleResumeViewedSession} onRetry={() => loadAndShowSessionDetails(selectedSessionDetails.session_id)} showActionButtons={true} /> ); } if (!scheduleId) { return (

Schedule Not Found

No schedule ID was provided. Please return to the schedules list and select a schedule.

); } return (

Schedule Details

Viewing Schedule ID: {scheduleId}

Schedule Information

{isLoadingSchedule && (
Loading schedule details...
)} {scheduleError && (

Error: {scheduleError}

)} {!isLoadingSchedule && !scheduleError && scheduleDetails && (

{scheduleDetails.id}

{scheduleDetails.currently_running && (
Currently Running
)}

Schedule:{' '} {getReadableCron(scheduleDetails.cron)}

Cron Expression: {scheduleDetails.cron}

Recipe Source: {scheduleDetails.source}

Last Run:{' '} {scheduleDetails.last_run ? new Date(scheduleDetails.last_run).toLocaleString() : 'Never'}

)}

Actions

{scheduleDetails?.currently_running && (

Cannot trigger a schedule while it's already running.

)}

Recent Sessions for this Schedule

{isLoadingSessions && (

Loading sessions...

)} {sessionsError && (

Error: {sessionsError}

)} {!isLoadingSessions && !sessionsError && sessions.length === 0 && (

No sessions found for this schedule.

)} {!isLoadingSessions && sessions.length > 0 && (
{sessions.map((session) => ( handleSessionCardClick(session.id)} role="button" tabIndex={0} onKeyPress={(e) => { if (e.key === 'Enter' || e.key === ' ') { handleSessionCardClick(session.id); } }} >

{session.name || `Session ID: ${session.id}`}{' '}

Created:{' '} {session.createdAt ? new Date(session.createdAt).toLocaleString() : 'N/A'}

{session.messageCount !== undefined && (

Messages: {session.messageCount}

)} {session.workingDir && (

Dir: {session.workingDir}

)} {session.accumulatedTotalTokens !== undefined && session.accumulatedTotalTokens !== null && (

Tokens: {session.accumulatedTotalTokens}

)}

ID: {session.id}

))}
)}
); }; export default ScheduleDetailView;