mirror of
https://github.com/Sea-Haven-Industries/open-swe.git
synced 2026-10-04 12:32:12 +00:00
fix: Better component for rendering thread errors (#528)
* fix: Better component for rendering thread errors * cr
This commit is contained in:
parent
5c7a7dcb49
commit
3d944cc81e
6 changed files with 159 additions and 3 deletions
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
import { ThreadView } from "@/components/v2/thread-view";
|
import { ThreadView } from "@/components/v2/thread-view";
|
||||||
import { ThreadViewLoading } from "@/components/v2/thread-view-loading";
|
import { ThreadViewLoading } from "@/components/v2/thread-view-loading";
|
||||||
|
import { ThreadErrorCard } from "@/components/v2/thread-error-card";
|
||||||
import { useThreadMetadata } from "@/hooks/useThreadMetadata";
|
import { useThreadMetadata } from "@/hooks/useThreadMetadata";
|
||||||
import { useThreadsSWR } from "@/hooks/useThreadsSWR";
|
import { useThreadsSWR } from "@/hooks/useThreadsSWR";
|
||||||
import { useStream } from "@langchain/langgraph-sdk/react";
|
import { useStream } from "@langchain/langgraph-sdk/react";
|
||||||
|
|
@ -53,7 +54,7 @@ export default function ThreadPage({
|
||||||
created_at: new Date().toISOString(),
|
created_at: new Date().toISOString(),
|
||||||
};
|
};
|
||||||
|
|
||||||
const { metadata: currentDisplayThread } = useThreadMetadata(
|
const { metadata: currentDisplayThread, statusError } = useThreadMetadata(
|
||||||
dummyThread as any,
|
dummyThread as any,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -61,6 +62,15 @@ export default function ThreadPage({
|
||||||
router.push("/chat");
|
router.push("/chat");
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (statusError && "message" in statusError && "type" in statusError) {
|
||||||
|
return (
|
||||||
|
<ThreadErrorCard
|
||||||
|
error={statusError}
|
||||||
|
onGoBack={handleBackToHome}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (!thread || threadsLoading) {
|
if (!thread || threadsLoading) {
|
||||||
return <ThreadViewLoading onBackToHome={handleBackToHome} />;
|
return <ThreadViewLoading onBackToHome={handleBackToHome} />;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
101
apps/web/src/components/v2/thread-error-card.tsx
Normal file
101
apps/web/src/components/v2/thread-error-card.tsx
Normal file
|
|
@ -0,0 +1,101 @@
|
||||||
|
import { Badge } from "@/components/ui/badge";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { Separator } from "@/components/ui/separator";
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
|
import { AlertCircle, ArrowLeft, Lock, Search } from "lucide-react";
|
||||||
|
|
||||||
|
export interface ThreadStatusError {
|
||||||
|
message: string;
|
||||||
|
type: "not_found" | "unauthorized";
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ThreadErrorCardProps {
|
||||||
|
error: ThreadStatusError;
|
||||||
|
onGoBack: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ThreadErrorCard({ error, onGoBack }: ThreadErrorCardProps) {
|
||||||
|
const getErrorIcon = () => {
|
||||||
|
switch (error.type) {
|
||||||
|
case "not_found":
|
||||||
|
return (
|
||||||
|
<Search className="h-4 w-4 text-amber-500 dark:text-amber-400" />
|
||||||
|
);
|
||||||
|
case "unauthorized":
|
||||||
|
return <Lock className="h-4 w-4 text-red-500 dark:text-red-400" />;
|
||||||
|
default:
|
||||||
|
return (
|
||||||
|
<AlertCircle className="h-4 w-4 text-red-500 dark:text-red-400" />
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getErrorTypeLabel = () => {
|
||||||
|
switch (error.type) {
|
||||||
|
case "not_found":
|
||||||
|
return "Not Found";
|
||||||
|
case "unauthorized":
|
||||||
|
return "Unauthorized";
|
||||||
|
default:
|
||||||
|
return "Error";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getErrorTypeColor = () => {
|
||||||
|
switch (error.type) {
|
||||||
|
case "not_found":
|
||||||
|
return "border-amber-200 text-amber-600 dark:border-amber-800 dark:text-amber-400";
|
||||||
|
case "unauthorized":
|
||||||
|
return "border-red-200 text-red-600 dark:border-red-800 dark:text-red-400";
|
||||||
|
default:
|
||||||
|
return "border-red-200 text-red-600 dark:border-red-800 dark:text-red-400";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="bg-background fixed inset-0 flex items-center justify-center p-4">
|
||||||
|
<div className="bg-card w-full max-w-md rounded-lg border p-6 shadow-lg">
|
||||||
|
<div className="space-y-4">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
{getErrorIcon()}
|
||||||
|
<h4 className="text-sm font-semibold">Thread Error</h4>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-3">
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<span className="text-muted-foreground text-xs font-medium">
|
||||||
|
Type
|
||||||
|
</span>
|
||||||
|
<Badge
|
||||||
|
variant="outline"
|
||||||
|
className={cn(getErrorTypeColor())}
|
||||||
|
>
|
||||||
|
{getErrorTypeLabel()}
|
||||||
|
</Badge>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Separator />
|
||||||
|
|
||||||
|
<div className="space-y-2">
|
||||||
|
<span className="text-muted-foreground text-xs font-medium">
|
||||||
|
Message
|
||||||
|
</span>
|
||||||
|
<p className="text-sm font-medium">{error.message}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="pt-2">
|
||||||
|
<Button
|
||||||
|
onClick={onGoBack}
|
||||||
|
variant="outline"
|
||||||
|
className="flex w-full items-center gap-2"
|
||||||
|
>
|
||||||
|
<ArrowLeft className="h-4 w-4" />
|
||||||
|
Go Back
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -5,6 +5,7 @@ import { useThreadStatus } from "./useThreadStatus";
|
||||||
import { useMemo } from "react";
|
import { useMemo } from "react";
|
||||||
import { getThreadTitle, computeThreadTitle } from "@/lib/thread";
|
import { getThreadTitle, computeThreadTitle } from "@/lib/thread";
|
||||||
import { calculateLastActivity } from "@/lib/thread-utils";
|
import { calculateLastActivity } from "@/lib/thread-utils";
|
||||||
|
import { ThreadStatusError } from "@/lib/schemas/thread-status";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Hook that combines thread metadata with real-time status
|
* Hook that combines thread metadata with real-time status
|
||||||
|
|
@ -12,7 +13,7 @@ import { calculateLastActivity } from "@/lib/thread-utils";
|
||||||
export function useThreadMetadata(thread: Thread<ManagerGraphState>): {
|
export function useThreadMetadata(thread: Thread<ManagerGraphState>): {
|
||||||
metadata: ThreadMetadata;
|
metadata: ThreadMetadata;
|
||||||
isStatusLoading: boolean;
|
isStatusLoading: boolean;
|
||||||
statusError: Error | null;
|
statusError: Error | ThreadStatusError | null;
|
||||||
} {
|
} {
|
||||||
const {
|
const {
|
||||||
status,
|
status,
|
||||||
|
|
|
||||||
|
|
@ -56,7 +56,7 @@ export function useThreadStatus(
|
||||||
status: data?.status || "idle",
|
status: data?.status || "idle",
|
||||||
taskPlan: data?.taskPlan,
|
taskPlan: data?.taskPlan,
|
||||||
isLoading,
|
isLoading,
|
||||||
error,
|
error: data?.error ?? error,
|
||||||
mutate,
|
mutate,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,11 @@ export function mapLangGraphToUIStatus(status: ThreadStatus): ThreadUIStatus {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ThreadStatusError {
|
||||||
|
message: string;
|
||||||
|
type: "not_found" | "unauthorized";
|
||||||
|
}
|
||||||
|
|
||||||
export interface ThreadStatusData {
|
export interface ThreadStatusData {
|
||||||
graph:
|
graph:
|
||||||
| typeof MANAGER_GRAPH_ID
|
| typeof MANAGER_GRAPH_ID
|
||||||
|
|
@ -42,4 +47,5 @@ export interface ThreadStatusData {
|
||||||
threadId: string;
|
threadId: string;
|
||||||
status: ThreadUIStatus;
|
status: ThreadUIStatus;
|
||||||
taskPlan?: TaskPlan; // Task plan data when available from programmer sessions
|
taskPlan?: TaskPlan; // Task plan data when available from programmer sessions
|
||||||
|
error?: ThreadStatusError;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,42 @@ import { PlannerGraphState } from "@open-swe/shared/open-swe/planner/types";
|
||||||
import { getActivePlanItems } from "@open-swe/shared/open-swe/tasks";
|
import { getActivePlanItems } from "@open-swe/shared/open-swe/tasks";
|
||||||
import { SessionCache, SessionCacheData } from "@/hooks/useThreadsStatus";
|
import { SessionCache, SessionCacheData } from "@/hooks/useThreadsStatus";
|
||||||
|
|
||||||
|
function getErrorFields(error: unknown): {
|
||||||
|
message: string;
|
||||||
|
type: "not_found" | "unauthorized";
|
||||||
|
} {
|
||||||
|
if (
|
||||||
|
!error ||
|
||||||
|
typeof error !== "object" ||
|
||||||
|
!("message" in error) ||
|
||||||
|
!("status" in error)
|
||||||
|
) {
|
||||||
|
return {
|
||||||
|
message: "Unknown error",
|
||||||
|
type: "unauthorized",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (error.status === 404) {
|
||||||
|
return {
|
||||||
|
message: "Thread not found",
|
||||||
|
type: "not_found",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (error.status === 401) {
|
||||||
|
return {
|
||||||
|
message: "Unauthorized",
|
||||||
|
type: "unauthorized",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
message: "Unknown error",
|
||||||
|
type: "unauthorized",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
interface StatusResult {
|
interface StatusResult {
|
||||||
graph: "manager" | "planner" | "programmer";
|
graph: "manager" | "planner" | "programmer";
|
||||||
runId: string;
|
runId: string;
|
||||||
|
|
@ -134,6 +170,7 @@ export async function fetchThreadStatus(
|
||||||
sessionCache,
|
sessionCache,
|
||||||
);
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
const errorFields = getErrorFields(error);
|
||||||
console.error(`Error fetching thread status for ${threadId}:`, error);
|
console.error(`Error fetching thread status for ${threadId}:`, error);
|
||||||
|
|
||||||
const graph = lastPollingState?.graph || "manager";
|
const graph = lastPollingState?.graph || "manager";
|
||||||
|
|
@ -145,6 +182,7 @@ export async function fetchThreadStatus(
|
||||||
runId,
|
runId,
|
||||||
threadId: errorThreadId,
|
threadId: errorThreadId,
|
||||||
status: "error",
|
status: "error",
|
||||||
|
error: errorFields,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue