diff --git a/apps/web/src/app/(v2)/chat/[thread_id]/page.tsx b/apps/web/src/app/(v2)/chat/[thread_id]/page.tsx index bd3ca9c8..0d34bdbc 100644 --- a/apps/web/src/app/(v2)/chat/[thread_id]/page.tsx +++ b/apps/web/src/app/(v2)/chat/[thread_id]/page.tsx @@ -2,10 +2,10 @@ import { ThreadView } from "@/components/v2/thread-view"; import { ThreadViewLoading } from "@/components/v2/thread-view-loading"; -import { ThreadMetadata } from "@/components/v2/types"; import { useThreadMetadata } from "@/hooks/useThreadMetadata"; import { useThreadsSWR } from "@/hooks/useThreadsSWR"; import { useStream } from "@langchain/langgraph-sdk/react"; +import { useGitHubAppProvider } from "@/providers/GitHubApp"; import { MANAGER_GRAPH_ID } from "@open-swe/shared/constants"; import { ManagerGraphState } from "@open-swe/shared/open-swe/manager/types"; import { useRouter } from "next/navigation"; @@ -24,6 +24,7 @@ export default function ThreadPage({ }) { const router = useRouter(); const { thread_id } = use(params); + const { currentInstallation } = useGitHubAppProvider(); const stream = useStream({ apiUrl: process.env.NEXT_PUBLIC_API_URL ?? "", assistantId: MANAGER_GRAPH_ID, @@ -34,6 +35,8 @@ export default function ThreadPage({ const { threads, isLoading: threadsLoading } = useThreadsSWR({ assistantId: MANAGER_GRAPH_ID, + currentInstallation, + disableOrgFiltering: true, }); const threadsMetadata = useMemo(() => threadsToMetadata(threads), [threads]); diff --git a/apps/web/src/app/(v2)/chat/layout.tsx b/apps/web/src/app/(v2)/chat/layout.tsx index 9538dcf4..0f7e3c35 100644 --- a/apps/web/src/app/(v2)/chat/layout.tsx +++ b/apps/web/src/app/(v2)/chat/layout.tsx @@ -1,6 +1,7 @@ import type { Metadata } from "next"; import "../../globals.css"; -import React from "react"; +import React, { Suspense } from "react"; +import { GitHubAppProvider } from "@/providers/GitHubApp"; export const metadata: Metadata = { title: "Open SWE - Chat", @@ -17,5 +18,9 @@ export default function RootLayout({ }: Readonly<{ children: React.ReactNode; }>) { - return children; + return ( + Loading...}> + {children} + + ); } diff --git a/apps/web/src/app/(v2)/chat/page.tsx b/apps/web/src/app/(v2)/chat/page.tsx index 75ed461a..cebded62 100644 --- a/apps/web/src/app/(v2)/chat/page.tsx +++ b/apps/web/src/app/(v2)/chat/page.tsx @@ -2,14 +2,16 @@ import { DefaultView } from "@/components/v2/default-view"; import { useThreadsSWR } from "@/hooks/useThreadsSWR"; -import { GitHubAppProvider } from "@/providers/GitHubApp"; +import { useGitHubAppProvider } from "@/providers/GitHubApp"; import { Toaster } from "@/components/ui/sonner"; import { Suspense } from "react"; import { MANAGER_GRAPH_ID } from "@open-swe/shared/constants"; export default function ChatPage() { + const { currentInstallation } = useGitHubAppProvider(); const { threads, isLoading: threadsLoading } = useThreadsSWR({ assistantId: MANAGER_GRAPH_ID, + currentInstallation, }); if (!threads) { @@ -20,12 +22,10 @@ export default function ChatPage() {
- - - +
); diff --git a/apps/web/src/app/(v2)/chat/threads/page.tsx b/apps/web/src/app/(v2)/chat/threads/page.tsx index a078c175..f8f1bb4a 100644 --- a/apps/web/src/app/(v2)/chat/threads/page.tsx +++ b/apps/web/src/app/(v2)/chat/threads/page.tsx @@ -11,7 +11,7 @@ import { useThreadsSWR } from "@/hooks/useThreadsSWR"; import { ThreadCard, ThreadCardLoading } from "@/components/v2/thread-card"; import { ThemeToggle } from "@/components/theme-toggle"; import { InstallationSelector } from "@/components/github/installation-selector"; -import { GitHubAppProvider } from "@/providers/GitHubApp"; +import { useGitHubAppProvider } from "@/providers/GitHubApp"; import { MANAGER_GRAPH_ID } from "@open-swe/shared/constants"; import { useThreadsStatus } from "@/hooks/useThreadsStatus"; import { cn } from "@/lib/utils"; @@ -29,8 +29,10 @@ type FilterStatus = function AllThreadsPageContent() { const router = useRouter(); + const { currentInstallation } = useGitHubAppProvider(); const { threads, isLoading: threadsLoading } = useThreadsSWR({ assistantId: MANAGER_GRAPH_ID, + currentInstallation, }); const [searchQuery, setSearchQuery] = useState(""); const [statusFilter, setStatusFilter] = useState("all"); @@ -82,180 +84,176 @@ function AllThreadsPageContent() { }; return ( - -
- {/* Header */} -
-
- +
+ {/* Header */} +
+
+ +
+
+ + All Threads + +
+
-
- - All Threads + + {filteredThreads.length} threads
-
-
- - {filteredThreads.length} threads - -
-
- - -
+
+ +
- - {/* Search and Filters */} -
-
-
- - setSearchQuery(e.target.value)} - className="border-border bg-background text-foreground placeholder:text-muted-foreground pl-10 dark:bg-gray-900" - /> -
-
- - - Filter: - - {( - [ - "all", - "running", - "completed", - "failed", - "pending", - "idle", - "paused", - "error", - ] as FilterStatus[] - ).map((status) => ( - - ))} -
-
-
- - {/* Content */} -
-
- {statusFilter === "all" ? ( -
- {Object.entries(groupedThreads).map(([status, threads]) => { - if (threads.length === 0) return null; - return ( -
-
-

- {status} Threads -

- - {threads.length} - -
-
- {threads.map((thread) => ( - - ))} -
-
- ); - })} -
- ) : ( -
- {filteredThreads.map((thread) => ( - - ))} -
- )} - - {filteredThreads.length === 0 && - !threadsLoading && - !statusLoading && ( -
-
- No threads found -
-
- {!threads || threads.length === 0 - ? "No threads have been created yet" - : searchQuery - ? "Try adjusting your search query" - : "No threads match the selected filter"} -
-
- )} - - {(threadsLoading || statusLoading) && - (!threads || threads.length === 0) && ( -
-
-

- Loading threads... -

-
-
- {Array.from({ length: 9 }).map((_, index) => ( - - ))} -
-
- )} -
-
- + + {/* Search and Filters */} +
+
+
+ + setSearchQuery(e.target.value)} + className="border-border bg-background text-foreground placeholder:text-muted-foreground pl-10 dark:bg-gray-900" + /> +
+
+ + Filter: + {( + [ + "all", + "running", + "completed", + "failed", + "pending", + "idle", + "paused", + "error", + ] as FilterStatus[] + ).map((status) => ( + + ))} +
+
+
+ + {/* Content */} +
+
+ {statusFilter === "all" ? ( +
+ {Object.entries(groupedThreads).map(([status, threads]) => { + if (threads.length === 0) return null; + return ( +
+
+

+ {status} Threads +

+ + {threads.length} + +
+
+ {threads.map((thread) => ( + + ))} +
+
+ ); + })} +
+ ) : ( +
+ {filteredThreads.map((thread) => ( + + ))} +
+ )} + + {filteredThreads.length === 0 && + !threadsLoading && + !statusLoading && ( +
+
+ No threads found +
+
+ {!threads || threads.length === 0 + ? "No threads have been created yet" + : searchQuery + ? "Try adjusting your search query" + : "No threads match the selected filter"} +
+
+ )} + + {(threadsLoading || statusLoading) && + (!threads || threads.length === 0) && ( +
+
+

+ Loading threads... +

+
+
+ {Array.from({ length: 9 }).map((_, index) => ( + + ))} +
+
+ )} +
+
+
); } diff --git a/apps/web/src/hooks/useThreadsSWR.ts b/apps/web/src/hooks/useThreadsSWR.ts index d78eba8b..3c723ee7 100644 --- a/apps/web/src/hooks/useThreadsSWR.ts +++ b/apps/web/src/hooks/useThreadsSWR.ts @@ -6,6 +6,8 @@ import { ManagerGraphState } from "@open-swe/shared/open-swe/manager/types"; import { PlannerGraphState } from "@open-swe/shared/open-swe/planner/types"; import { ReviewerGraphState } from "@open-swe/shared/open-swe/reviewer/types"; import { GraphState } from "@open-swe/shared/open-swe/types"; +import { useMemo } from "react"; +import type { Installation } from "./useGitHubInstallations"; /** * Union type representing all possible graph states in the Open SWE system @@ -21,6 +23,8 @@ interface UseThreadsSWROptions { refreshInterval?: number; revalidateOnFocus?: boolean; revalidateOnReconnect?: boolean; + currentInstallation?: Installation | null; + disableOrgFiltering?: boolean; } /** @@ -39,6 +43,8 @@ export function useThreadsSWR< refreshInterval = THREAD_SWR_CONFIG.refreshInterval, revalidateOnFocus = THREAD_SWR_CONFIG.revalidateOnFocus, revalidateOnReconnect = THREAD_SWR_CONFIG.revalidateOnReconnect, + currentInstallation, + disableOrgFiltering, } = options; const apiUrl: string | undefined = process.env.NEXT_PUBLIC_API_URL ?? ""; @@ -76,11 +82,31 @@ export function useThreadsSWR< }, ); + const threads = useMemo(() => { + const allThreads = data ?? []; + + if (disableOrgFiltering) { + return allThreads; + } + + if (!currentInstallation) { + return []; + } + + return allThreads.filter((thread) => { + const threadInstallationName = thread.metadata?.installation_name; + return ( + typeof threadInstallationName === "string" && + threadInstallationName === currentInstallation.accountName + ); + }); + }, [data, currentInstallation, disableOrgFiltering]); + return { - threads: data ?? [], + threads, error, isLoading, isValidating, - mutate, // For manual revalidation + mutate, }; } diff --git a/apps/web/src/providers/GitHubApp.tsx b/apps/web/src/providers/GitHubApp.tsx index eb71cc00..1935cb61 100644 --- a/apps/web/src/providers/GitHubApp.tsx +++ b/apps/web/src/providers/GitHubApp.tsx @@ -1,3 +1,5 @@ +"use client"; + import { useGitHubApp } from "@/hooks/useGitHubApp"; import { createContext, useContext, ReactNode } from "react";