diff --git a/frontend/src/components/tasks/TaskDetailsPanel.tsx b/frontend/src/components/tasks/TaskDetailsPanel.tsx index edd21f55..3f62cfda 100644 --- a/frontend/src/components/tasks/TaskDetailsPanel.tsx +++ b/frontend/src/components/tasks/TaskDetailsPanel.tsx @@ -1,4 +1,4 @@ -import { useState, useEffect, useMemo } from "react"; +import { useState, useEffect, useMemo, useRef, useCallback } from "react"; import { Link } from "react-router-dom"; import { X, @@ -152,6 +152,10 @@ export function TaskDetailsPanel({ const [followUpMessage, setFollowUpMessage] = useState(""); const [isSendingFollowUp, setIsSendingFollowUp] = useState(false); const [followUpError, setFollowUpError] = useState(null); + + // Auto-scroll state + const [shouldAutoScroll, setShouldAutoScroll] = useState(true); + const scrollContainerRef = useRef(null); const { config } = useConfig(); // Available executors @@ -231,6 +235,27 @@ export function TaskDetailsPanel({ } }, [task, isOpen]); + // Auto-scroll to bottom when activities change + useEffect(() => { + if (shouldAutoScroll && scrollContainerRef.current) { + scrollContainerRef.current.scrollTop = scrollContainerRef.current.scrollHeight; + } + }, [attemptActivities, shouldAutoScroll]); + + // Handle scroll events to detect manual scrolling + const handleScroll = useCallback(() => { + if (scrollContainerRef.current) { + const { scrollTop, scrollHeight, clientHeight } = scrollContainerRef.current; + const isAtBottom = scrollTop + clientHeight >= scrollHeight - 5; // 5px tolerance + + if (isAtBottom && !shouldAutoScroll) { + setShouldAutoScroll(true); + } else if (!isAtBottom && shouldAutoScroll) { + setShouldAutoScroll(false); + } + } + }, [shouldAutoScroll]); + const fetchTaskAttempts = async () => { if (!task) return; @@ -702,7 +727,11 @@ export function TaskDetailsPanel({ {/* Content */} -
+
{loading ? (