feat: Add loading state to all threads page (#260)

* feat: Implement a loading and empty state for recent & running threads

* cr

* feat: Add loading state to all threads page
This commit is contained in:
Brace Sproul 2025-06-19 12:37:47 -07:00 • committed by GitHub
parent a93614ffa6
commit 42f67464bc
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 24 additions and 12 deletions

View file

@ -33,7 +33,7 @@ export default function ThreadPage({
process.env.NEXT_PUBLIC_MANAGER_ASSISTANT_ID,
);
// Find the thread by ID
const thread = threads?.find((t) => t.thread_id === thread_id);
const thread = threads.find((t) => t.thread_id === thread_id);
const handleBackToHome = () => {
router.push("/chat");
@ -44,8 +44,7 @@ export default function ThreadPage({
}
// Convert all threads to display format
const displayThreads: ThreadDisplayInfo[] =
threads?.map(threadToDisplayInfo) ?? [];
const displayThreads: ThreadDisplayInfo[] = threads.map(threadToDisplayInfo);
const currentDisplayThread = threadToDisplayInfo(thread);
return (

View file

@ -14,8 +14,7 @@ export default function ChatPage() {
);
// Convert Thread objects to ThreadDisplayInfo for UI
const displayThreads: ThreadDisplayInfo[] =
threads?.map(threadToDisplayInfo) ?? [];
const displayThreads: ThreadDisplayInfo[] = threads.map(threadToDisplayInfo);
return (
<div className="bg-background h-screen overflow-y-hidden">

View file

@ -10,22 +10,21 @@ import { useRouter } from "next/navigation";
import { ThreadDisplayInfo, threadToDisplayInfo } from "@/components/v2/types";
import { useThreads } from "@/hooks/useThreads";
import { GraphState } from "@open-swe/shared/open-swe/types";
import { ThreadCard } from "@/components/v2/thread-card";
import { ThreadCard, ThreadCardLoading } from "@/components/v2/thread-card";
import { ThemeToggle } from "@/components/theme-toggle";
type FilterStatus = "all" | "running" | "completed" | "failed" | "pending";
export default function AllThreadsPage() {
const router = useRouter();
const { threads } = useThreads<GraphState>(
const { threads, threadsLoading } = useThreads<GraphState>(
process.env.NEXT_PUBLIC_MANAGER_ASSISTANT_ID,
);
const [searchQuery, setSearchQuery] = useState("");
const [statusFilter, setStatusFilter] = useState<FilterStatus>("all");
// Convert Thread objects to ThreadDisplayInfo for UI
const displayThreads: ThreadDisplayInfo[] =
threads?.map(threadToDisplayInfo) ?? [];
const displayThreads: ThreadDisplayInfo[] = threads.map(threadToDisplayInfo);
// Filter and search threads
const filteredThreads = displayThreads.filter((thread) => {
@ -178,7 +177,7 @@ export default function AllThreadsPage() {
</div>
)}
{filteredThreads.length === 0 && (
{filteredThreads.length === 0 && !threadsLoading && (
<div className="py-12 text-center">
<div className="text-muted-foreground mb-2">No threads found</div>
<div className="text-muted-foreground/70 text-xs">
@ -188,6 +187,21 @@ export default function AllThreadsPage() {
</div>
</div>
)}
{threadsLoading && threads.length === 0 && (
<div>
<div className="mb-3 flex items-center gap-2">
<h2 className="text-foreground text-base font-semibold capitalize">
Loading threads...
</h2>
</div>
<div className="grid gap-3 md:grid-cols-2 lg:grid-cols-3">
{Array.from({ length: 9 }).map((_, index) => (
<ThreadCardLoading key={`all-threads-loading-${index}`} />
))}
</div>
</div>
)}
</div>
</div>
</div>

View file

@ -6,7 +6,7 @@ export function useThreads<State extends Record<string, any>>(
assistantId?: string,
) {
const apiUrl: string | undefined = process.env.NEXT_PUBLIC_API_URL ?? "";
const [threads, setThreads] = useState<Thread<State>[] | null>(null);
const [threads, setThreads] = useState<Thread<State>[]>([]);
const [threadsLoading, setThreadsLoading] = useState(false);
const getThread = useCallback(
@ -50,7 +50,7 @@ export function useThreads<State extends Record<string, any>>(
useEffect(() => {
getThreads().then((threads) => {
setThreads(threads);
setThreads(threads ?? []);
});
}, [getThreads]);