mirror of
https://github.com/Sea-Haven-Industries/open-swe.git
synced 2026-10-03 02:13:28 +00:00
feat: Dark mode (#241)
* feat: Dark mode * more work * more dark/light mode, refactor thread card * fix rest of dark mode * cr * cr * cr
This commit is contained in:
parent
b752b2326e
commit
de8d465695
17 changed files with 504 additions and 675 deletions
|
|
@ -29,7 +29,9 @@ export default function ThreadPage({
|
||||||
reconnectOnMount: true,
|
reconnectOnMount: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
const { threads, threadsLoading } = useThreads<GraphState>();
|
const { threads, threadsLoading } = useThreads<GraphState>(
|
||||||
|
process.env.NEXT_PUBLIC_MANAGER_ASSISTANT_ID,
|
||||||
|
);
|
||||||
// Find the thread by 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);
|
||||||
|
|
||||||
|
|
@ -37,7 +39,7 @@ export default function ThreadPage({
|
||||||
router.push("/chat");
|
router.push("/chat");
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!thread || threadsLoading || !threadsLoading) {
|
if (!thread || threadsLoading) {
|
||||||
return <ThreadViewLoading onBackToHome={handleBackToHome} />;
|
return <ThreadViewLoading onBackToHome={handleBackToHome} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -46,17 +48,12 @@ export default function ThreadPage({
|
||||||
threads?.map(threadToDisplayInfo) ?? [];
|
threads?.map(threadToDisplayInfo) ?? [];
|
||||||
const currentDisplayThread = threadToDisplayInfo(thread);
|
const currentDisplayThread = threadToDisplayInfo(thread);
|
||||||
|
|
||||||
const handleThreadSelect = (selectedThread: ThreadDisplayInfo) => {
|
|
||||||
router.push(`/chat/${selectedThread.id}`);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="h-screen bg-black">
|
<div className="bg-background fixed inset-0">
|
||||||
<ThreadView
|
<ThreadView
|
||||||
stream={stream}
|
stream={stream}
|
||||||
displayThread={currentDisplayThread}
|
displayThread={currentDisplayThread}
|
||||||
allDisplayThreads={displayThreads}
|
allDisplayThreads={displayThreads}
|
||||||
onThreadSelect={handleThreadSelect}
|
|
||||||
onBackToHome={handleBackToHome}
|
onBackToHome={handleBackToHome}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ export default function ChatPage() {
|
||||||
threads?.map(threadToDisplayInfo) ?? [];
|
threads?.map(threadToDisplayInfo) ?? [];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="h-screen overflow-y-hidden bg-black">
|
<div className="bg-background h-screen overflow-y-hidden">
|
||||||
<Suspense>
|
<Suspense>
|
||||||
<Toaster />
|
<Toaster />
|
||||||
<GitHubAppProvider>
|
<GitHubAppProvider>
|
||||||
|
|
|
||||||
|
|
@ -3,32 +3,23 @@
|
||||||
import type React from "react";
|
import type React from "react";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
|
||||||
import { Badge } from "@/components/ui/badge";
|
import { Badge } from "@/components/ui/badge";
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
import {
|
import { ArrowLeft, Search, Filter } from "lucide-react";
|
||||||
ArrowLeft,
|
|
||||||
Search,
|
|
||||||
Filter,
|
|
||||||
CheckCircle,
|
|
||||||
XCircle,
|
|
||||||
Loader2,
|
|
||||||
GitBranch,
|
|
||||||
GitPullRequest,
|
|
||||||
Bug,
|
|
||||||
Calendar,
|
|
||||||
Clock,
|
|
||||||
} from "lucide-react";
|
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
import { ThreadDisplayInfo, threadToDisplayInfo } from "@/components/v2/types";
|
import { ThreadDisplayInfo, threadToDisplayInfo } from "@/components/v2/types";
|
||||||
import { useThreads } from "@/hooks/useThreads";
|
import { useThreads } from "@/hooks/useThreads";
|
||||||
import { GraphState } from "@open-swe/shared/open-swe/types";
|
import { GraphState } from "@open-swe/shared/open-swe/types";
|
||||||
|
import { ThreadCard } from "@/components/v2/thread-card";
|
||||||
|
import { ThemeToggle } from "@/components/theme-toggle";
|
||||||
|
|
||||||
type FilterStatus = "all" | "running" | "completed" | "failed" | "pending";
|
type FilterStatus = "all" | "running" | "completed" | "failed" | "pending";
|
||||||
|
|
||||||
export default function AllThreadsPage() {
|
export default function AllThreadsPage() {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { threads } = useThreads<GraphState>();
|
const { threads } = useThreads<GraphState>(
|
||||||
|
process.env.NEXT_PUBLIC_MANAGER_ASSISTANT_ID,
|
||||||
|
);
|
||||||
const [searchQuery, setSearchQuery] = useState("");
|
const [searchQuery, setSearchQuery] = useState("");
|
||||||
const [statusFilter, setStatusFilter] = useState<FilterStatus>("all");
|
const [statusFilter, setStatusFilter] = useState<FilterStatus>("all");
|
||||||
|
|
||||||
|
|
@ -36,51 +27,6 @@ export default function AllThreadsPage() {
|
||||||
const displayThreads: ThreadDisplayInfo[] =
|
const displayThreads: ThreadDisplayInfo[] =
|
||||||
threads?.map(threadToDisplayInfo) ?? [];
|
threads?.map(threadToDisplayInfo) ?? [];
|
||||||
|
|
||||||
const getStatusColor = (status: ThreadDisplayInfo["status"]) => {
|
|
||||||
switch (status) {
|
|
||||||
case "running":
|
|
||||||
return "bg-blue-950 text-blue-400";
|
|
||||||
case "completed":
|
|
||||||
return "bg-green-950 text-green-400";
|
|
||||||
case "failed":
|
|
||||||
return "bg-red-950 text-red-400";
|
|
||||||
case "pending":
|
|
||||||
return "bg-yellow-950 text-yellow-400";
|
|
||||||
default:
|
|
||||||
return "bg-gray-800 text-gray-400";
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const getStatusIcon = (status: ThreadDisplayInfo["status"]) => {
|
|
||||||
switch (status) {
|
|
||||||
case "running":
|
|
||||||
return <Loader2 className="h-4 w-4 animate-spin" />;
|
|
||||||
case "completed":
|
|
||||||
return <CheckCircle className="h-4 w-4" />;
|
|
||||||
case "failed":
|
|
||||||
return <XCircle className="h-4 w-4" />;
|
|
||||||
case "pending":
|
|
||||||
return <Clock className="h-4 w-4" />;
|
|
||||||
default:
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const getPRStatusColor = (status: string) => {
|
|
||||||
switch (status) {
|
|
||||||
case "merged":
|
|
||||||
return "text-purple-400";
|
|
||||||
case "open":
|
|
||||||
return "text-green-400";
|
|
||||||
case "draft":
|
|
||||||
return "text-gray-400";
|
|
||||||
case "closed":
|
|
||||||
return "text-red-400";
|
|
||||||
default:
|
|
||||||
return "text-gray-400";
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Filter and search threads
|
// Filter and search threads
|
||||||
const filteredThreads = displayThreads.filter((thread) => {
|
const filteredThreads = displayThreads.filter((thread) => {
|
||||||
const matchesSearch =
|
const matchesSearch =
|
||||||
|
|
@ -107,50 +53,51 @@ export default function AllThreadsPage() {
|
||||||
pending: displayThreads.filter((t) => t.status === "pending").length,
|
pending: displayThreads.filter((t) => t.status === "pending").length,
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleThreadClick = (thread: ThreadDisplayInfo) => {
|
|
||||||
router.push(`/chat/${thread.id}`);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex h-screen flex-col bg-black">
|
<div className="bg-background flex h-screen flex-col">
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div className="border-b border-gray-900 bg-black px-4 py-3">
|
<div className="border-border bg-card border-b px-4 py-3">
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="sm"
|
size="sm"
|
||||||
className="h-6 w-6 p-0 text-gray-600 hover:bg-gray-900 hover:text-gray-400"
|
className="text-muted-foreground hover:bg-muted hover:text-foreground h-6 w-6 p-0"
|
||||||
onClick={() => router.push("/chat")}
|
onClick={() => router.push("/chat")}
|
||||||
>
|
>
|
||||||
<ArrowLeft className="h-3 w-3" />
|
<ArrowLeft className="h-3 w-3" />
|
||||||
</Button>
|
</Button>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<div className="h-2 w-2 rounded-full bg-green-500"></div>
|
<div className="h-2 w-2 rounded-full bg-green-500"></div>
|
||||||
<span className="font-mono text-sm text-gray-400">All Threads</span>
|
<span className="text-muted-foreground font-mono text-sm">
|
||||||
</div>
|
All Threads
|
||||||
<div className="ml-auto flex items-center gap-2">
|
|
||||||
<span className="text-xs text-gray-600">
|
|
||||||
{filteredThreads.length} threads
|
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="ml-auto flex items-center gap-4">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<span className="text-muted-foreground text-xs">
|
||||||
|
{filteredThreads.length} threads
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<ThemeToggle />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Search and Filters */}
|
{/* Search and Filters */}
|
||||||
<div className="border-b border-gray-900 bg-gray-950 px-4 py-3">
|
<div className="border-border bg-muted/50 border-b px-4 py-3 dark:bg-gray-950">
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<div className="relative max-w-md flex-1">
|
<div className="relative max-w-md flex-1">
|
||||||
<Search className="absolute top-1/2 left-3 h-4 w-4 -translate-y-1/2 transform text-gray-500" />
|
<Search className="text-muted-foreground absolute top-1/2 left-3 h-4 w-4 -translate-y-1/2 transform" />
|
||||||
<Input
|
<Input
|
||||||
placeholder="Search threads..."
|
placeholder="Search threads..."
|
||||||
value={searchQuery}
|
value={searchQuery}
|
||||||
onChange={(e) => setSearchQuery(e.target.value)}
|
onChange={(e) => setSearchQuery(e.target.value)}
|
||||||
className="border-gray-700 bg-gray-900 pl-10 text-gray-300 placeholder:text-gray-600"
|
className="border-border bg-background text-foreground placeholder:text-muted-foreground pl-10 dark:bg-gray-900"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-1">
|
<div className="flex items-center gap-1">
|
||||||
<Filter className="h-4 w-4 text-gray-500" />
|
<Filter className="text-muted-foreground h-4 w-4" />
|
||||||
<span className="mr-2 text-xs text-gray-500">Filter:</span>
|
<span className="text-muted-foreground mr-2 text-xs">Filter:</span>
|
||||||
{(
|
{(
|
||||||
[
|
[
|
||||||
"all",
|
"all",
|
||||||
|
|
@ -166,8 +113,8 @@ export default function AllThreadsPage() {
|
||||||
size="sm"
|
size="sm"
|
||||||
className={`h-7 text-xs ${
|
className={`h-7 text-xs ${
|
||||||
statusFilter === status
|
statusFilter === status
|
||||||
? "bg-gray-700 text-gray-200"
|
? "bg-muted text-foreground dark:bg-gray-700"
|
||||||
: "text-gray-500 hover:bg-gray-800 hover:text-gray-300"
|
: "text-muted-foreground hover:bg-muted hover:text-foreground"
|
||||||
}`}
|
}`}
|
||||||
onClick={() => setStatusFilter(status)}
|
onClick={() => setStatusFilter(status)}
|
||||||
>
|
>
|
||||||
|
|
@ -176,7 +123,7 @@ export default function AllThreadsPage() {
|
||||||
: status.charAt(0).toUpperCase() + status.slice(1)}
|
: status.charAt(0).toUpperCase() + status.slice(1)}
|
||||||
<Badge
|
<Badge
|
||||||
variant="secondary"
|
variant="secondary"
|
||||||
className="ml-1 bg-gray-800 text-xs text-gray-400"
|
className="bg-muted/70 text-muted-foreground ml-1 text-xs dark:bg-gray-800"
|
||||||
>
|
>
|
||||||
{statusCounts[status]}
|
{statusCounts[status]}
|
||||||
</Badge>
|
</Badge>
|
||||||
|
|
@ -197,12 +144,12 @@ export default function AllThreadsPage() {
|
||||||
return (
|
return (
|
||||||
<div key={status}>
|
<div key={status}>
|
||||||
<div className="mb-3 flex items-center gap-2">
|
<div className="mb-3 flex items-center gap-2">
|
||||||
<h2 className="text-base font-semibold text-gray-300 capitalize">
|
<h2 className="text-foreground text-base font-semibold capitalize">
|
||||||
{status} Threads
|
{status} Threads
|
||||||
</h2>
|
</h2>
|
||||||
<Badge
|
<Badge
|
||||||
variant="secondary"
|
variant="secondary"
|
||||||
className="bg-gray-800 text-xs text-gray-400"
|
className="bg-muted/70 text-muted-foreground text-xs dark:bg-gray-800"
|
||||||
>
|
>
|
||||||
{threads.length}
|
{threads.length}
|
||||||
</Badge>
|
</Badge>
|
||||||
|
|
@ -212,10 +159,6 @@ export default function AllThreadsPage() {
|
||||||
<ThreadCard
|
<ThreadCard
|
||||||
key={thread.id}
|
key={thread.id}
|
||||||
thread={thread}
|
thread={thread}
|
||||||
onClick={() => handleThreadClick(thread)}
|
|
||||||
getStatusColor={getStatusColor}
|
|
||||||
getStatusIcon={getStatusIcon}
|
|
||||||
getPRStatusColor={getPRStatusColor}
|
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -230,10 +173,6 @@ export default function AllThreadsPage() {
|
||||||
<ThreadCard
|
<ThreadCard
|
||||||
key={thread.id}
|
key={thread.id}
|
||||||
thread={thread}
|
thread={thread}
|
||||||
onClick={() => handleThreadClick(thread)}
|
|
||||||
getStatusColor={getStatusColor}
|
|
||||||
getStatusIcon={getStatusIcon}
|
|
||||||
getPRStatusColor={getPRStatusColor}
|
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -241,8 +180,8 @@ export default function AllThreadsPage() {
|
||||||
|
|
||||||
{filteredThreads.length === 0 && (
|
{filteredThreads.length === 0 && (
|
||||||
<div className="py-12 text-center">
|
<div className="py-12 text-center">
|
||||||
<div className="mb-2 text-gray-500">No threads found</div>
|
<div className="text-muted-foreground mb-2">No threads found</div>
|
||||||
<div className="text-xs text-gray-600">
|
<div className="text-muted-foreground/70 text-xs">
|
||||||
{searchQuery
|
{searchQuery
|
||||||
? "Try adjusting your search query"
|
? "Try adjusting your search query"
|
||||||
: "No threads match the selected filter"}
|
: "No threads match the selected filter"}
|
||||||
|
|
@ -254,95 +193,3 @@ export default function AllThreadsPage() {
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ThreadCardProps {
|
|
||||||
thread: ThreadDisplayInfo;
|
|
||||||
onClick: () => void;
|
|
||||||
getStatusColor: (status: ThreadDisplayInfo["status"]) => string;
|
|
||||||
getStatusIcon: (status: ThreadDisplayInfo["status"]) => React.ReactNode;
|
|
||||||
getPRStatusColor: (status: string) => string;
|
|
||||||
}
|
|
||||||
|
|
||||||
function ThreadCard({
|
|
||||||
thread,
|
|
||||||
onClick,
|
|
||||||
getStatusColor,
|
|
||||||
getStatusIcon,
|
|
||||||
getPRStatusColor,
|
|
||||||
}: ThreadCardProps) {
|
|
||||||
return (
|
|
||||||
<Card
|
|
||||||
className="cursor-pointer border-gray-800 bg-gray-950 transition-shadow hover:bg-gray-900 hover:shadow-lg"
|
|
||||||
onClick={onClick}
|
|
||||||
>
|
|
||||||
<CardHeader className="p-3 pb-2">
|
|
||||||
<div className="flex items-start justify-between">
|
|
||||||
<div className="min-w-0 flex-1">
|
|
||||||
<CardTitle className="truncate text-sm font-medium text-gray-300">
|
|
||||||
{thread.title}
|
|
||||||
</CardTitle>
|
|
||||||
<div className="mt-1 flex items-center gap-1">
|
|
||||||
<GitBranch className="h-2 w-2 text-gray-600" />
|
|
||||||
<span className="truncate text-xs text-gray-500">
|
|
||||||
{thread.repository}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<Badge
|
|
||||||
variant="secondary"
|
|
||||||
className={`${getStatusColor(thread.status)} text-xs`}
|
|
||||||
>
|
|
||||||
<div className="flex items-center gap-1">
|
|
||||||
{getStatusIcon(thread.status)}
|
|
||||||
<span className="capitalize">{thread.status}</span>
|
|
||||||
</div>
|
|
||||||
</Badge>
|
|
||||||
</div>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent className="p-3 pt-0">
|
|
||||||
<div className="flex items-center justify-between">
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<span className="text-xs text-gray-600">
|
|
||||||
{thread.taskCount} tasks
|
|
||||||
</span>
|
|
||||||
<span className="text-xs text-gray-600">•</span>
|
|
||||||
<div className="flex items-center gap-1">
|
|
||||||
<Calendar className="h-2 w-2 text-gray-600" />
|
|
||||||
<span className="text-xs text-gray-600">
|
|
||||||
{thread.lastActivity}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center gap-1">
|
|
||||||
{thread.githubIssue && (
|
|
||||||
<Button
|
|
||||||
variant="ghost"
|
|
||||||
size="sm"
|
|
||||||
className="h-5 w-5 p-0 text-gray-500 hover:text-gray-300"
|
|
||||||
onClick={(e) => {
|
|
||||||
e.stopPropagation();
|
|
||||||
window.open(thread.githubIssue!.url, "_blank");
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Bug className="h-3 w-3" />
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
{thread.pullRequest && (
|
|
||||||
<Button
|
|
||||||
variant="ghost"
|
|
||||||
size="sm"
|
|
||||||
className={`h-5 w-5 p-0 hover:text-gray-300 ${getPRStatusColor(thread.pullRequest.status)}`}
|
|
||||||
onClick={(e) => {
|
|
||||||
e.stopPropagation();
|
|
||||||
window.open(thread.pullRequest!.url, "_blank");
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<GitPullRequest className="h-3 w-3" />
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ import "./globals.css";
|
||||||
import { Inter } from "next/font/google";
|
import { Inter } from "next/font/google";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { NuqsAdapter } from "nuqs/adapters/next/app";
|
import { NuqsAdapter } from "nuqs/adapters/next/app";
|
||||||
|
import { ThemeProvider } from "@/components/theme-provider";
|
||||||
|
|
||||||
const inter = Inter({
|
const inter = Inter({
|
||||||
subsets: ["latin"],
|
subsets: ["latin"],
|
||||||
|
|
@ -26,9 +27,17 @@ export default function RootLayout({
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
}>) {
|
}>) {
|
||||||
return (
|
return (
|
||||||
<html lang="en">
|
<html
|
||||||
|
lang="en"
|
||||||
|
suppressHydrationWarning
|
||||||
|
>
|
||||||
<body className={inter.className}>
|
<body className={inter.className}>
|
||||||
<NuqsAdapter>{children}</NuqsAdapter>
|
<ThemeProvider
|
||||||
|
defaultTheme="system"
|
||||||
|
storageKey="theme"
|
||||||
|
>
|
||||||
|
<NuqsAdapter>{children}</NuqsAdapter>
|
||||||
|
</ThemeProvider>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -54,10 +54,12 @@ export function ActionStep(props: ActionStepProps) {
|
||||||
switch (props.status) {
|
switch (props.status) {
|
||||||
case "loading":
|
case "loading":
|
||||||
return (
|
return (
|
||||||
<div className="h-3.5 w-3.5 rounded-full border border-gray-600" />
|
<div className="border-border h-3.5 w-3.5 rounded-full border" />
|
||||||
);
|
);
|
||||||
case "generating":
|
case "generating":
|
||||||
return <Loader2 className="h-3.5 w-3.5 animate-spin text-gray-400" />;
|
return (
|
||||||
|
<Loader2 className="text-muted-foreground h-3.5 w-3.5 animate-spin" />
|
||||||
|
);
|
||||||
case "done":
|
case "done":
|
||||||
return props.success ? (
|
return props.success ? (
|
||||||
<CheckCircle className="h-3.5 w-3.5 text-green-500" />
|
<CheckCircle className="h-3.5 w-3.5 text-green-500" />
|
||||||
|
|
@ -106,13 +108,13 @@ export function ActionStep(props: ActionStepProps) {
|
||||||
const renderHeaderIcon = () => {
|
const renderHeaderIcon = () => {
|
||||||
if (props.status === "loading" || !("actionType" in props)) {
|
if (props.status === "loading" || !("actionType" in props)) {
|
||||||
// In loading state, we don't know the type yet, use a generic icon
|
// In loading state, we don't know the type yet, use a generic icon
|
||||||
return <Loader2 className="mr-2 h-3.5 w-3.5 text-gray-400" />;
|
return <Loader2 className="text-muted-foreground mr-2 h-3.5 w-3.5" />;
|
||||||
}
|
}
|
||||||
|
|
||||||
return props.actionType === "shell" ? (
|
return props.actionType === "shell" ? (
|
||||||
<Terminal className="mr-2 h-3.5 w-3.5 text-gray-400" />
|
<Terminal className="text-muted-foreground mr-2 h-3.5 w-3.5" />
|
||||||
) : (
|
) : (
|
||||||
<FileCode className="mr-2 h-3.5 w-3.5 text-gray-400" />
|
<FileCode className="text-muted-foreground mr-2 h-3.5 w-3.5" />
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -120,7 +122,7 @@ export function ActionStep(props: ActionStepProps) {
|
||||||
const renderHeaderContent = () => {
|
const renderHeaderContent = () => {
|
||||||
if (props.status === "loading" || !("actionType" in props)) {
|
if (props.status === "loading" || !("actionType" in props)) {
|
||||||
return (
|
return (
|
||||||
<span className="text-xs font-normal text-gray-300">
|
<span className="text-foreground/80 text-xs font-normal">
|
||||||
Preparing action...
|
Preparing action...
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
|
|
@ -130,18 +132,18 @@ export function ActionStep(props: ActionStepProps) {
|
||||||
return (
|
return (
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
{props.workdir && (
|
{props.workdir && (
|
||||||
<div className="mb-0.5 text-xs font-normal text-gray-400">
|
<div className="text-muted-foreground mb-0.5 text-xs font-normal">
|
||||||
{props.workdir}
|
{props.workdir}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<code className="text-xs font-normal text-gray-300">
|
<code className="text-foreground/80 text-xs font-normal">
|
||||||
{props.command.join(" ")}
|
{props.command.join(" ")}
|
||||||
</code>
|
</code>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
return (
|
return (
|
||||||
<code className="flex-1 text-xs font-normal text-gray-300">
|
<code className="text-foreground/80 flex-1 text-xs font-normal">
|
||||||
{props.file}
|
{props.file}
|
||||||
</code>
|
</code>
|
||||||
);
|
);
|
||||||
|
|
@ -156,12 +158,12 @@ export function ActionStep(props: ActionStepProps) {
|
||||||
|
|
||||||
if (props.actionType === "shell" && props.output) {
|
if (props.actionType === "shell" && props.output) {
|
||||||
return (
|
return (
|
||||||
<div className="overflow-x-auto bg-gray-900 p-2 text-gray-200">
|
<div className="bg-muted text-foreground/90 overflow-x-auto p-2 dark:bg-gray-900">
|
||||||
<pre className="text-xs font-normal whitespace-pre-wrap">
|
<pre className="text-xs font-normal whitespace-pre-wrap">
|
||||||
{props.output}
|
{props.output}
|
||||||
</pre>
|
</pre>
|
||||||
{props.errorCode !== undefined && !props.success && (
|
{props.errorCode !== undefined && !props.success && (
|
||||||
<div className="mt-1 text-xs text-red-400">
|
<div className="mt-1 text-xs text-red-500 dark:text-red-400">
|
||||||
Exit code: {props.errorCode}
|
Exit code: {props.errorCode}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
@ -169,23 +171,25 @@ export function ActionStep(props: ActionStepProps) {
|
||||||
);
|
);
|
||||||
} else if (props.actionType === "apply-patch" && props.diff) {
|
} else if (props.actionType === "apply-patch" && props.diff) {
|
||||||
return (
|
return (
|
||||||
<div className="overflow-x-auto bg-gray-900 p-2">
|
<div className="bg-muted overflow-x-auto p-2 dark:bg-gray-900">
|
||||||
<pre
|
<pre
|
||||||
className="text-xs font-normal whitespace-pre-wrap text-gray-200"
|
className="text-foreground/90 text-xs font-normal whitespace-pre-wrap"
|
||||||
dangerouslySetInnerHTML={{ __html: formatDiff(props.diff) }}
|
dangerouslySetInnerHTML={{ __html: formatDiff(props.diff) }}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{!props.success && props.errorMessage && (
|
{!props.success && props.errorMessage && (
|
||||||
<div className="mt-2 rounded border border-red-700/30 bg-red-900/30 p-2 text-xs text-red-400">
|
<div className="mt-2 rounded border border-red-700/30 bg-red-100/30 p-2 text-xs text-red-600 dark:bg-red-900/30 dark:text-red-400">
|
||||||
{props.errorMessage}
|
{props.errorMessage}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{!props.success && props.fixedDiff && (
|
{!props.success && props.fixedDiff && (
|
||||||
<div className="mt-2">
|
<div className="mt-2">
|
||||||
<div className="mb-1 text-xs text-gray-400">Fixed diff:</div>
|
<div className="text-muted-foreground mb-1 text-xs">
|
||||||
|
Fixed diff:
|
||||||
|
</div>
|
||||||
<pre
|
<pre
|
||||||
className="text-xs font-normal whitespace-pre-wrap text-gray-200"
|
className="text-foreground/90 text-xs font-normal whitespace-pre-wrap"
|
||||||
dangerouslySetInnerHTML={{
|
dangerouslySetInnerHTML={{
|
||||||
__html: formatDiff(props.fixedDiff),
|
__html: formatDiff(props.fixedDiff),
|
||||||
}}
|
}}
|
||||||
|
|
@ -200,36 +204,36 @@ export function ActionStep(props: ActionStepProps) {
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="overflow-hidden rounded-md border border-gray-700">
|
<div className="border-border overflow-hidden rounded-md border">
|
||||||
{props.reasoningText && (
|
{props.reasoningText && (
|
||||||
<div className="border-b border-blue-800 bg-blue-900/50 p-2">
|
<div className="border-b border-blue-300 bg-blue-100/50 p-2 dark:border-blue-800 dark:bg-blue-900/50">
|
||||||
<button
|
<button
|
||||||
onClick={() => setShowReasoning(!showReasoning)}
|
onClick={() => setShowReasoning(!showReasoning)}
|
||||||
className="flex items-center gap-1 text-xs font-normal text-blue-400 hover:text-blue-300"
|
className="flex items-center gap-1 text-xs font-normal text-blue-600 hover:text-blue-700 dark:text-blue-400 dark:hover:text-blue-300"
|
||||||
>
|
>
|
||||||
<MessageSquare className="h-3 w-3" />
|
<MessageSquare className="h-3 w-3" />
|
||||||
{showReasoning ? "Hide reasoning" : "Show reasoning"}
|
{showReasoning ? "Hide reasoning" : "Show reasoning"}
|
||||||
</button>
|
</button>
|
||||||
{showReasoning && (
|
{showReasoning && (
|
||||||
<p className="mt-1 text-xs font-normal text-blue-300">
|
<p className="mt-1 text-xs font-normal text-blue-700 dark:text-blue-300">
|
||||||
{props.reasoningText}
|
{props.reasoningText}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="flex items-center border-b border-gray-700 bg-gray-800 p-2">
|
<div className="border-border bg-card flex items-center border-b p-2 dark:bg-gray-800">
|
||||||
{renderHeaderIcon()}
|
{renderHeaderIcon()}
|
||||||
{renderHeaderContent()}
|
{renderHeaderContent()}
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<span className="text-xs font-normal text-gray-400">
|
<span className="text-muted-foreground text-xs font-normal">
|
||||||
{getStatusText()}
|
{getStatusText()}
|
||||||
</span>
|
</span>
|
||||||
{getStatusIcon()}
|
{getStatusIcon()}
|
||||||
{shouldShowToggle() && (
|
{shouldShowToggle() && (
|
||||||
<button
|
<button
|
||||||
onClick={() => setExpanded(!expanded)}
|
onClick={() => setExpanded(!expanded)}
|
||||||
className="text-gray-400 hover:text-gray-300"
|
className="text-muted-foreground hover:text-foreground"
|
||||||
>
|
>
|
||||||
{expanded ? (
|
{expanded ? (
|
||||||
<ChevronUp className="h-3.5 w-3.5" />
|
<ChevronUp className="h-3.5 w-3.5" />
|
||||||
|
|
@ -244,16 +248,16 @@ export function ActionStep(props: ActionStepProps) {
|
||||||
{renderContent()}
|
{renderContent()}
|
||||||
|
|
||||||
{props.summaryText && props.status === "done" && (
|
{props.summaryText && props.status === "done" && (
|
||||||
<div className="border-t border-green-800 bg-green-900/50 p-2">
|
<div className="border-t border-green-300 bg-green-100/50 p-2 dark:border-green-800 dark:bg-green-900/50">
|
||||||
<button
|
<button
|
||||||
onClick={() => setShowSummary(!showSummary)}
|
onClick={() => setShowSummary(!showSummary)}
|
||||||
className="flex items-center gap-1 text-xs font-normal text-green-400 hover:text-green-300"
|
className="flex items-center gap-1 text-xs font-normal text-green-600 hover:text-green-700 dark:text-green-400 dark:hover:text-green-300"
|
||||||
>
|
>
|
||||||
<FileText className="h-3 w-3" />
|
<FileText className="h-3 w-3" />
|
||||||
{showSummary ? "Hide summary" : "Show summary"}
|
{showSummary ? "Hide summary" : "Show summary"}
|
||||||
</button>
|
</button>
|
||||||
{showSummary && (
|
{showSummary && (
|
||||||
<p className="mt-1 text-xs font-normal text-green-300">
|
<p className="mt-1 text-xs font-normal text-green-700 dark:text-green-300">
|
||||||
{props.summaryText}
|
{props.summaryText}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
|
|
@ -268,11 +272,11 @@ function formatDiff(diff: string) {
|
||||||
.split("\n")
|
.split("\n")
|
||||||
.map((line) => {
|
.map((line) => {
|
||||||
if (line.startsWith("+")) {
|
if (line.startsWith("+")) {
|
||||||
return `<span class="text-green-400">${line}</span>`;
|
return `<span class="text-green-600 dark:text-green-400">${line}</span>`;
|
||||||
} else if (line.startsWith("-")) {
|
} else if (line.startsWith("-")) {
|
||||||
return `<span class="text-red-400">${line}</span>`;
|
return `<span class="text-red-600 dark:text-red-400">${line}</span>`;
|
||||||
} else if (line.startsWith("@")) {
|
} else if (line.startsWith("@")) {
|
||||||
return `<span class="text-blue-400">${line}</span>`;
|
return `<span class="text-blue-600 dark:text-blue-400">${line}</span>`;
|
||||||
}
|
}
|
||||||
return line;
|
return line;
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -38,11 +38,11 @@ export function PlanViewer({
|
||||||
return (
|
return (
|
||||||
<div className={cn("w-full space-y-3", className)}>
|
<div className={cn("w-full space-y-3", className)}>
|
||||||
<div className="mb-4 flex items-center gap-2">
|
<div className="mb-4 flex items-center gap-2">
|
||||||
<h3 className="text-sm font-medium text-white">
|
<h3 className="text-foreground text-sm font-medium">
|
||||||
{isProposedPlan ? "Proposed" : "Execution"} Plan
|
{isProposedPlan ? "Proposed" : "Execution"} Plan
|
||||||
</h3>
|
</h3>
|
||||||
{!isProposedPlan && (
|
{!isProposedPlan && (
|
||||||
<span className="text-xs text-gray-400">
|
<span className="text-muted-foreground text-xs">
|
||||||
{planItems.filter((item) => item.completed).length} of{" "}
|
{planItems.filter((item) => item.completed).length} of{" "}
|
||||||
{planItems.length} completed
|
{planItems.length} completed
|
||||||
</span>
|
</span>
|
||||||
|
|
@ -61,47 +61,47 @@ export function PlanViewer({
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex items-start gap-3 rounded-lg border p-3 transition-colors",
|
"flex items-start gap-3 rounded-lg border p-3 transition-colors",
|
||||||
{
|
{
|
||||||
"border-green-800 bg-green-900/30": status === "completed",
|
"border-green-300 bg-green-100/30 dark:border-green-800 dark:bg-green-900/30":
|
||||||
"border-blue-800 bg-blue-900/30": status === "current",
|
status === "completed",
|
||||||
"border-gray-800 bg-gray-900/30": [
|
"border-blue-300 bg-blue-100/30 dark:border-blue-800 dark:bg-blue-900/30":
|
||||||
"remaining",
|
status === "current",
|
||||||
"proposed",
|
"border-border bg-muted/30 dark:border-gray-800 dark:bg-gray-900/30":
|
||||||
].includes(status),
|
["remaining", "proposed"].includes(status),
|
||||||
},
|
},
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{/* Status Icon */}
|
{/* Status Icon */}
|
||||||
<div className="mt-0.5 flex-shrink-0">
|
<div className="mt-0.5 flex-shrink-0">
|
||||||
{status === "completed" && (
|
{status === "completed" && (
|
||||||
<Check className="h-4 w-4 text-green-400" />
|
<Check className="h-4 w-4 text-green-600 dark:text-green-400" />
|
||||||
)}
|
)}
|
||||||
{status === "current" && (
|
{status === "current" && (
|
||||||
<Play className="h-4 w-4 text-blue-400" />
|
<Play className="h-4 w-4 text-blue-600 dark:text-blue-400" />
|
||||||
)}
|
)}
|
||||||
{["remaining", "proposed"].includes(status) && (
|
{["remaining", "proposed"].includes(status) && (
|
||||||
<Clock className="h-4 w-4 text-gray-500" />
|
<Clock className="text-muted-foreground h-4 w-4" />
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Task Content */}
|
{/* Task Content */}
|
||||||
<div className="min-w-0 flex-1">
|
<div className="min-w-0 flex-1">
|
||||||
<div className="mb-1 flex items-center justify-between text-gray-400">
|
<div className="text-muted-foreground mb-1 flex items-center justify-between">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<span className="text-xs font-medium">
|
<span className="text-xs font-medium">
|
||||||
Step {item.index + 1}
|
Step {item.index + 1}
|
||||||
</span>
|
</span>
|
||||||
{status === "current" && (
|
{status === "current" && (
|
||||||
<span className="rounded-full bg-blue-900/50 px-2 py-0.5 text-xs text-blue-300">
|
<span className="rounded-full bg-blue-100/50 px-2 py-0.5 text-xs text-blue-700 dark:bg-blue-900/50 dark:text-blue-300">
|
||||||
In Progress
|
In Progress
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
{status === "completed" && (
|
{status === "completed" && (
|
||||||
<span className="rounded-full bg-green-900/50 px-2 py-0.5 text-xs text-green-300">
|
<span className="rounded-full bg-green-100/50 px-2 py-0.5 text-xs text-green-700 dark:bg-green-900/50 dark:text-green-300">
|
||||||
Done
|
Done
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
{status === "proposed" && (
|
{status === "proposed" && (
|
||||||
<span className="rounded-full bg-gray-800 px-2 py-0.5 text-xs text-gray-300">
|
<span className="bg-muted text-foreground/80 rounded-full px-2 py-0.5 text-xs dark:bg-gray-800 dark:text-gray-300">
|
||||||
Proposed
|
Proposed
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
|
|
@ -162,13 +162,13 @@ export function PlanViewer({
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
className="text-gray-200"
|
className="text-foreground/90 dark:text-gray-200"
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<p
|
<p
|
||||||
className={cn("text-sm leading-relaxed", {
|
className={cn("text-sm leading-relaxed", {
|
||||||
"text-white": status === "current",
|
"text-foreground": status === "current",
|
||||||
"text-gray-300":
|
"text-foreground/80":
|
||||||
status === "completed" ||
|
status === "completed" ||
|
||||||
["remaining", "proposed"].includes(status),
|
["remaining", "proposed"].includes(status),
|
||||||
})}
|
})}
|
||||||
|
|
@ -179,11 +179,13 @@ export function PlanViewer({
|
||||||
|
|
||||||
{/* Summary for completed tasks */}
|
{/* Summary for completed tasks */}
|
||||||
{item.summary && status === "completed" && (
|
{item.summary && status === "completed" && (
|
||||||
<div className="mt-2 rounded border border-green-800 bg-green-900/30 p-2">
|
<div className="mt-2 rounded border border-green-300 bg-green-100/30 p-2 dark:border-green-800 dark:bg-green-900/30">
|
||||||
<p className="mb-1 text-xs font-medium text-green-300">
|
<p className="mb-1 text-xs font-medium text-green-700 dark:text-green-300">
|
||||||
Summary:
|
Summary:
|
||||||
</p>
|
</p>
|
||||||
<p className="text-xs text-green-400">{item.summary}</p>
|
<p className="text-xs text-green-600 dark:text-green-400">
|
||||||
|
{item.summary}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -193,7 +195,7 @@ export function PlanViewer({
|
||||||
{isProposedPlan && (
|
{isProposedPlan && (
|
||||||
<div
|
<div
|
||||||
key="add-new-plan-item"
|
key="add-new-plan-item"
|
||||||
className="flex items-start gap-3 rounded-lg border border-gray-800 bg-gray-900/30 p-3 transition-colors"
|
className="border-border bg-muted/30 flex items-start gap-3 rounded-lg border p-3 transition-colors dark:border-gray-800 dark:bg-gray-900/30"
|
||||||
>
|
>
|
||||||
<Textarea
|
<Textarea
|
||||||
placeholder="Add new plan item"
|
placeholder="Add new plan item"
|
||||||
|
|
@ -214,7 +216,7 @@ export function PlanViewer({
|
||||||
]);
|
]);
|
||||||
setNewPlanItem("");
|
setNewPlanItem("");
|
||||||
}}
|
}}
|
||||||
className="mt-auto bg-inherit text-gray-200 transition-colors hover:bg-gray-800 hover:text-gray-100"
|
className="text-foreground/90 hover:bg-muted hover:text-foreground mt-auto bg-inherit transition-colors dark:text-gray-200 dark:hover:bg-gray-800 dark:hover:text-gray-100"
|
||||||
size="sm"
|
size="sm"
|
||||||
variant="outline"
|
variant="outline"
|
||||||
>
|
>
|
||||||
|
|
|
||||||
82
apps/web/src/components/theme-provider.tsx
Normal file
82
apps/web/src/components/theme-provider.tsx
Normal file
|
|
@ -0,0 +1,82 @@
|
||||||
|
"use client";
|
||||||
|
|
||||||
|
import { createContext, useContext, useEffect, useState } from "react";
|
||||||
|
|
||||||
|
type Theme = "dark" | "light" | "system";
|
||||||
|
|
||||||
|
type ThemeProviderProps = {
|
||||||
|
children: React.ReactNode;
|
||||||
|
defaultTheme?: Theme;
|
||||||
|
storageKey?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type ThemeProviderState = {
|
||||||
|
theme: Theme;
|
||||||
|
setTheme: (theme: Theme) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
const initialState: ThemeProviderState = {
|
||||||
|
theme: "system",
|
||||||
|
setTheme: () => null,
|
||||||
|
};
|
||||||
|
|
||||||
|
const ThemeProviderContext = createContext<ThemeProviderState>(initialState);
|
||||||
|
|
||||||
|
export function ThemeProvider({
|
||||||
|
children,
|
||||||
|
defaultTheme = "system",
|
||||||
|
storageKey = "theme",
|
||||||
|
...props
|
||||||
|
}: ThemeProviderProps) {
|
||||||
|
const [theme, setTheme] = useState<Theme>(defaultTheme);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const savedTheme = localStorage.getItem(storageKey) as Theme | null;
|
||||||
|
if (savedTheme) {
|
||||||
|
setTheme(savedTheme);
|
||||||
|
}
|
||||||
|
}, [storageKey]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const root = window.document.documentElement;
|
||||||
|
|
||||||
|
root.classList.remove("light", "dark");
|
||||||
|
|
||||||
|
if (theme === "system") {
|
||||||
|
const systemTheme = window.matchMedia("(prefers-color-scheme: dark)")
|
||||||
|
.matches
|
||||||
|
? "dark"
|
||||||
|
: "light";
|
||||||
|
root.classList.add(systemTheme);
|
||||||
|
} else {
|
||||||
|
root.classList.add(theme);
|
||||||
|
}
|
||||||
|
}, [theme]);
|
||||||
|
|
||||||
|
const value = {
|
||||||
|
theme,
|
||||||
|
setTheme: (theme: Theme) => {
|
||||||
|
localStorage.setItem(storageKey, theme);
|
||||||
|
setTheme(theme);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ThemeProviderContext.Provider
|
||||||
|
{...props}
|
||||||
|
value={value}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</ThemeProviderContext.Provider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useTheme = () => {
|
||||||
|
const context = useContext(ThemeProviderContext);
|
||||||
|
|
||||||
|
if (context === undefined) {
|
||||||
|
throw new Error("useTheme must be used within a ThemeProvider");
|
||||||
|
}
|
||||||
|
|
||||||
|
return context;
|
||||||
|
};
|
||||||
24
apps/web/src/components/theme-toggle.tsx
Normal file
24
apps/web/src/components/theme-toggle.tsx
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
"use client";
|
||||||
|
|
||||||
|
import { useTheme } from "@/components/theme-provider";
|
||||||
|
import { Moon, Sun } from "lucide-react";
|
||||||
|
import { TooltipIconButton } from "./ui/tooltip-icon-button";
|
||||||
|
|
||||||
|
export function ThemeToggle() {
|
||||||
|
const { theme, setTheme } = useTheme();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TooltipIconButton
|
||||||
|
tooltip="Toggle theme"
|
||||||
|
variant="ghost"
|
||||||
|
onClick={() => setTheme(theme === "light" ? "dark" : "light")}
|
||||||
|
aria-label="Toggle theme"
|
||||||
|
>
|
||||||
|
{theme === "light" ? (
|
||||||
|
<Moon className="size-4" />
|
||||||
|
) : (
|
||||||
|
<Sun className="size-4" />
|
||||||
|
)}
|
||||||
|
</TooltipIconButton>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -24,6 +24,7 @@ import {
|
||||||
Settings,
|
Settings,
|
||||||
FilePlus2,
|
FilePlus2,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
|
import { ThemeToggle } from "../theme-toggle";
|
||||||
import { useQueryState, parseAsBoolean, parseAsString } from "nuqs";
|
import { useQueryState, parseAsBoolean, parseAsString } from "nuqs";
|
||||||
import { StickToBottom, useStickToBottomContext } from "use-stick-to-bottom";
|
import { StickToBottom, useStickToBottomContext } from "use-stick-to-bottom";
|
||||||
import TaskListSidebar from "../task-list-sidebar";
|
import TaskListSidebar from "../task-list-sidebar";
|
||||||
|
|
@ -395,6 +396,7 @@ export function Thread() {
|
||||||
>
|
>
|
||||||
<Settings className="size-4" />
|
<Settings className="size-4" />
|
||||||
</TooltipIconButton>
|
</TooltipIconButton>
|
||||||
|
<ThemeToggle />
|
||||||
<GitHubOAuthButton />
|
<GitHubOAuthButton />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -22,29 +22,29 @@ export function ToolCalls({
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
key={idx}
|
key={idx}
|
||||||
className="overflow-hidden rounded-lg border border-gray-700"
|
className="border-border overflow-hidden rounded-lg border"
|
||||||
>
|
>
|
||||||
<div className="border-b border-gray-700 bg-gray-800 px-4 py-2">
|
<div className="border-border bg-card border-b px-4 py-2 dark:bg-gray-800">
|
||||||
<h3 className="font-medium text-gray-100">
|
<h3 className="text-foreground font-medium">
|
||||||
{tc.name}
|
{tc.name}
|
||||||
{tc.id && (
|
{tc.id && (
|
||||||
<code className="ml-2 rounded bg-gray-700 px-2 py-1 text-sm">
|
<code className="bg-muted ml-2 rounded px-2 py-1 text-sm dark:bg-gray-700">
|
||||||
{tc.id}
|
{tc.id}
|
||||||
</code>
|
</code>
|
||||||
)}
|
)}
|
||||||
</h3>
|
</h3>
|
||||||
</div>
|
</div>
|
||||||
{hasArgs ? (
|
{hasArgs ? (
|
||||||
<table className="min-w-full divide-y divide-gray-700">
|
<table className="divide-border min-w-full divide-y">
|
||||||
<tbody className="divide-y divide-gray-700">
|
<tbody className="divide-border divide-y">
|
||||||
{Object.entries(args).map(([key, value], argIdx) => (
|
{Object.entries(args).map(([key, value], argIdx) => (
|
||||||
<tr key={argIdx}>
|
<tr key={argIdx}>
|
||||||
<td className="px-4 py-2 text-sm font-medium whitespace-nowrap text-gray-100">
|
<td className="text-foreground px-4 py-2 text-sm font-medium whitespace-nowrap">
|
||||||
{key}
|
{key}
|
||||||
</td>
|
</td>
|
||||||
<td className="px-4 py-2 text-sm text-gray-300">
|
<td className="text-foreground/80 px-4 py-2 text-sm">
|
||||||
{isComplexValue(value) ? (
|
{isComplexValue(value) ? (
|
||||||
<code className="rounded bg-gray-800 px-2 py-1 font-mono text-sm break-all">
|
<code className="bg-muted/70 rounded px-2 py-1 font-mono text-sm break-all dark:bg-gray-800">
|
||||||
{JSON.stringify(value, null, 2)}
|
{JSON.stringify(value, null, 2)}
|
||||||
</code>
|
</code>
|
||||||
) : (
|
) : (
|
||||||
|
|
@ -56,7 +56,9 @@ export function ToolCalls({
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
) : (
|
) : (
|
||||||
<code className="block p-3 text-sm">{"{}"}</code>
|
<code className="text-foreground/80 block p-3 text-sm">
|
||||||
|
{"{}"}
|
||||||
|
</code>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
@ -95,28 +97,28 @@ export function ToolResult({ message }: { message: ToolMessage }) {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mx-auto grid grid-rows-[1fr_auto] gap-2">
|
<div className="mx-auto grid grid-rows-[1fr_auto] gap-2">
|
||||||
<div className="overflow-hidden rounded-lg border border-gray-700">
|
<div className="border-border overflow-hidden rounded-lg border">
|
||||||
<div className="border-b border-gray-700 bg-gray-800 px-4 py-2">
|
<div className="border-border bg-card border-b px-4 py-2 dark:bg-gray-800">
|
||||||
<div className="flex flex-wrap items-center justify-between gap-2">
|
<div className="flex flex-wrap items-center justify-between gap-2">
|
||||||
{message.name ? (
|
{message.name ? (
|
||||||
<h3 className="font-medium text-gray-100">
|
<h3 className="text-foreground font-medium">
|
||||||
Tool Result:{" "}
|
Tool Result:{" "}
|
||||||
<code className="rounded bg-gray-700 px-2 py-1">
|
<code className="bg-muted rounded px-2 py-1 dark:bg-gray-700">
|
||||||
{message.name}
|
{message.name}
|
||||||
</code>
|
</code>
|
||||||
</h3>
|
</h3>
|
||||||
) : (
|
) : (
|
||||||
<h3 className="font-medium text-gray-100">Tool Result</h3>
|
<h3 className="text-foreground font-medium">Tool Result</h3>
|
||||||
)}
|
)}
|
||||||
{message.tool_call_id && (
|
{message.tool_call_id && (
|
||||||
<code className="ml-2 rounded bg-gray-700 px-2 py-1 text-sm text-gray-100">
|
<code className="bg-muted text-foreground ml-2 rounded px-2 py-1 text-sm dark:bg-gray-700">
|
||||||
{message.tool_call_id}
|
{message.tool_call_id}
|
||||||
</code>
|
</code>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<motion.div
|
<motion.div
|
||||||
className="min-w-full bg-gray-700"
|
className="bg-muted min-w-full dark:bg-gray-700"
|
||||||
initial={false}
|
initial={false}
|
||||||
animate={{ height: "auto" }}
|
animate={{ height: "auto" }}
|
||||||
transition={{ duration: 0.3 }}
|
transition={{ duration: 0.3 }}
|
||||||
|
|
@ -134,8 +136,8 @@ export function ToolResult({ message }: { message: ToolMessage }) {
|
||||||
transition={{ duration: 0.2 }}
|
transition={{ duration: 0.2 }}
|
||||||
>
|
>
|
||||||
{isJsonContent ? (
|
{isJsonContent ? (
|
||||||
<table className="min-w-full divide-y divide-gray-700">
|
<table className="divide-border min-w-full divide-y">
|
||||||
<tbody className="divide-y divide-gray-700">
|
<tbody className="divide-border divide-y">
|
||||||
{(Array.isArray(parsedContent)
|
{(Array.isArray(parsedContent)
|
||||||
? isExpanded
|
? isExpanded
|
||||||
? parsedContent
|
? parsedContent
|
||||||
|
|
@ -147,12 +149,12 @@ export function ToolResult({ message }: { message: ToolMessage }) {
|
||||||
: [item[0], item[1]];
|
: [item[0], item[1]];
|
||||||
return (
|
return (
|
||||||
<tr key={argIdx}>
|
<tr key={argIdx}>
|
||||||
<td className="px-4 py-2 text-sm font-medium whitespace-nowrap text-gray-100">
|
<td className="text-foreground px-4 py-2 text-sm font-medium whitespace-nowrap">
|
||||||
{key}
|
{key}
|
||||||
</td>
|
</td>
|
||||||
<td className="px-4 py-2 text-sm text-gray-300">
|
<td className="text-foreground/80 px-4 py-2 text-sm">
|
||||||
{isComplexValue(value) ? (
|
{isComplexValue(value) ? (
|
||||||
<code className="rounded bg-gray-800 px-2 py-1 font-mono text-sm break-all">
|
<code className="bg-muted/70 rounded px-2 py-1 font-mono text-sm break-all dark:bg-gray-800">
|
||||||
{JSON.stringify(value, null, 2)}
|
{JSON.stringify(value, null, 2)}
|
||||||
</code>
|
</code>
|
||||||
) : (
|
) : (
|
||||||
|
|
@ -165,7 +167,7 @@ export function ToolResult({ message }: { message: ToolMessage }) {
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
) : (
|
) : (
|
||||||
<code className="block text-sm text-gray-300">
|
<code className="text-foreground/80 block text-sm">
|
||||||
{displayedContent}
|
{displayedContent}
|
||||||
</code>
|
</code>
|
||||||
)}
|
)}
|
||||||
|
|
@ -178,7 +180,7 @@ export function ToolResult({ message }: { message: ToolMessage }) {
|
||||||
parsedContent.length > 5)) && (
|
parsedContent.length > 5)) && (
|
||||||
<motion.button
|
<motion.button
|
||||||
onClick={() => setIsExpanded(!isExpanded)}
|
onClick={() => setIsExpanded(!isExpanded)}
|
||||||
className="flex w-full cursor-pointer items-center justify-center border-t-[1px] border-gray-700 py-2 text-gray-300 transition-all duration-200 ease-in-out hover:bg-gray-800 hover:text-gray-200"
|
className="border-border text-muted-foreground hover:bg-muted hover:text-foreground flex w-full cursor-pointer items-center justify-center border-t-[1px] py-2 transition-all duration-200 ease-in-out dark:hover:bg-gray-800 dark:hover:text-gray-200"
|
||||||
initial={{ scale: 1 }}
|
initial={{ scale: 1 }}
|
||||||
whileHover={{ scale: 1.02 }}
|
whileHover={{ scale: 1.02 }}
|
||||||
whileTap={{ scale: 0.98 }}
|
whileTap={{ scale: 0.98 }}
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,9 @@ import {
|
||||||
} from "../ui/tooltip";
|
} from "../ui/tooltip";
|
||||||
import { Label } from "../ui/label";
|
import { Label } from "../ui/label";
|
||||||
import { ContentBlocksPreview } from "../thread/ContentBlocksPreview";
|
import { ContentBlocksPreview } from "../thread/ContentBlocksPreview";
|
||||||
|
import { TooltipIconButton } from "../ui/tooltip-icon-button";
|
||||||
|
import { ThemeToggle } from "../theme-toggle";
|
||||||
|
import { ThreadCard } from "./thread-card";
|
||||||
|
|
||||||
interface DefaultViewProps {
|
interface DefaultViewProps {
|
||||||
threads: ThreadDisplayInfo[];
|
threads: ThreadDisplayInfo[];
|
||||||
|
|
@ -51,49 +54,6 @@ export function DefaultView({ threads }: DefaultViewProps) {
|
||||||
handlePaste,
|
handlePaste,
|
||||||
} = useFileUpload();
|
} = useFileUpload();
|
||||||
|
|
||||||
const getStatusColor = (status: ThreadDisplayInfo["status"]) => {
|
|
||||||
switch (status) {
|
|
||||||
case "running":
|
|
||||||
return "bg-blue-950 text-blue-400";
|
|
||||||
case "completed":
|
|
||||||
return "bg-green-950 text-green-400";
|
|
||||||
case "failed":
|
|
||||||
return "bg-red-950 text-red-400";
|
|
||||||
case "pending":
|
|
||||||
return "bg-yellow-950 text-yellow-400";
|
|
||||||
default:
|
|
||||||
return "bg-gray-800 text-gray-400";
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const getStatusIcon = (status: ThreadDisplayInfo["status"]) => {
|
|
||||||
switch (status) {
|
|
||||||
case "running":
|
|
||||||
return <Loader2 className="h-4 w-4 animate-spin" />;
|
|
||||||
case "completed":
|
|
||||||
return <CheckCircle className="h-4 w-4" />;
|
|
||||||
case "failed":
|
|
||||||
return <XCircle className="h-4 w-4" />;
|
|
||||||
default:
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const getPRStatusColor = (status: string) => {
|
|
||||||
switch (status) {
|
|
||||||
case "merged":
|
|
||||||
return "text-purple-400";
|
|
||||||
case "open":
|
|
||||||
return "text-green-400";
|
|
||||||
case "draft":
|
|
||||||
return "text-gray-400";
|
|
||||||
case "closed":
|
|
||||||
return "text-red-400";
|
|
||||||
default:
|
|
||||||
return "text-gray-400";
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if (!apiUrl || !assistantId) {
|
if (!apiUrl || !assistantId) {
|
||||||
return <div>Missing API URL or Assistant ID</div>;
|
return <div>Missing API URL or Assistant ID</div>;
|
||||||
}
|
}
|
||||||
|
|
@ -101,15 +61,20 @@ export function DefaultView({ threads }: DefaultViewProps) {
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-1 flex-col">
|
<div className="flex flex-1 flex-col">
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div className="border-b border-gray-900 bg-black px-4 py-2">
|
<div className="border-border bg-card border-b px-4 py-2">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<div className="h-2 w-2 rounded-full bg-green-500"></div>
|
<div className="h-2 w-2 rounded-full bg-green-500"></div>
|
||||||
<span className="font-mono text-sm text-gray-400">Open SWE</span>
|
<span className="text-muted-foreground font-mono text-sm">
|
||||||
|
Open SWE
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-4">
|
||||||
<span className="text-xs text-gray-600">ready</span>
|
<div className="flex items-center gap-2">
|
||||||
<div className="h-1 w-1 rounded-full bg-gray-600"></div>
|
<span className="text-muted-foreground text-xs">ready</span>
|
||||||
|
<div className="bg-muted h-1 w-1 rounded-full"></div>
|
||||||
|
</div>
|
||||||
|
<ThemeToggle />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -120,7 +85,7 @@ export function DefaultView({ threads }: DefaultViewProps) {
|
||||||
{/* Terminal Chat Input */}
|
{/* Terminal Chat Input */}
|
||||||
<Card
|
<Card
|
||||||
className={cn(
|
className={cn(
|
||||||
"border-gray-800 bg-gray-950 py-0",
|
"border-border bg-card py-0 dark:bg-gray-950",
|
||||||
dragOver
|
dragOver
|
||||||
? "border-primary border-2 border-dotted"
|
? "border-primary border-2 border-dotted"
|
||||||
: "border border-solid",
|
: "border border-solid",
|
||||||
|
|
@ -155,7 +120,7 @@ export function DefaultView({ threads }: DefaultViewProps) {
|
||||||
<TooltipTrigger>
|
<TooltipTrigger>
|
||||||
<Label
|
<Label
|
||||||
htmlFor="file-input"
|
htmlFor="file-input"
|
||||||
className="flex cursor-pointer items-center justify-center rounded-full bg-inherit text-gray-500 hover:text-gray-300"
|
className="text-muted-foreground hover:text-foreground flex cursor-pointer items-center justify-center rounded-full bg-inherit"
|
||||||
>
|
>
|
||||||
<FilePlus2 className="size-4" />
|
<FilePlus2 className="size-4" />
|
||||||
</Label>
|
</Label>
|
||||||
|
|
@ -171,13 +136,13 @@ export function DefaultView({ threads }: DefaultViewProps) {
|
||||||
{/* Recent & Running Threads */}
|
{/* Recent & Running Threads */}
|
||||||
<div>
|
<div>
|
||||||
<div className="mb-3 flex items-center justify-between">
|
<div className="mb-3 flex items-center justify-between">
|
||||||
<h2 className="text-base font-semibold text-gray-300">
|
<h2 className="text-foreground text-base font-semibold">
|
||||||
Recent & Running Threads
|
Recent & Running Threads
|
||||||
</h2>
|
</h2>
|
||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
size="sm"
|
size="sm"
|
||||||
className="h-7 border-gray-700 bg-gray-900 text-xs text-gray-400 hover:bg-gray-800 hover:text-gray-300"
|
className="border-border text-muted-foreground hover:text-foreground h-7 text-xs"
|
||||||
onClick={() => router.push("/chat/threads")}
|
onClick={() => router.push("/chat/threads")}
|
||||||
>
|
>
|
||||||
View All
|
View All
|
||||||
|
|
@ -186,117 +151,46 @@ export function DefaultView({ threads }: DefaultViewProps) {
|
||||||
|
|
||||||
<div className="grid gap-3 md:grid-cols-2">
|
<div className="grid gap-3 md:grid-cols-2">
|
||||||
{threads.slice(0, 4).map((thread) => (
|
{threads.slice(0, 4).map((thread) => (
|
||||||
<Card
|
<ThreadCard
|
||||||
key={thread.id}
|
key={thread.id}
|
||||||
className="cursor-pointer border-gray-800 bg-gray-950 px-0 py-3 transition-shadow hover:bg-gray-900 hover:shadow-lg"
|
thread={thread}
|
||||||
onClick={() => {
|
/>
|
||||||
router.push(`/chat/${thread.id}`);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<CardHeader>
|
|
||||||
<div className="flex items-start justify-between">
|
|
||||||
<div className="min-w-0 flex-1">
|
|
||||||
<CardTitle className="truncate text-sm font-medium text-gray-300">
|
|
||||||
{thread.title}
|
|
||||||
</CardTitle>
|
|
||||||
<div className="mt-1 flex items-center gap-1">
|
|
||||||
<GitBranch className="h-2 w-2 text-gray-600" />
|
|
||||||
<span className="truncate text-xs text-gray-500">
|
|
||||||
{thread.repository}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<Badge
|
|
||||||
variant="secondary"
|
|
||||||
className={`${getStatusColor(thread.status)} text-xs`}
|
|
||||||
>
|
|
||||||
<div className="flex items-center gap-1">
|
|
||||||
{getStatusIcon(thread.status)}
|
|
||||||
<span className="capitalize">{thread.status}</span>
|
|
||||||
</div>
|
|
||||||
</Badge>
|
|
||||||
</div>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent>
|
|
||||||
<div className="flex items-center justify-between">
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<span className="text-xs text-gray-600">
|
|
||||||
{thread.taskCount === 0
|
|
||||||
? "No tasks"
|
|
||||||
: `${thread.taskCount} tasks`}
|
|
||||||
</span>
|
|
||||||
<span className="text-xs text-gray-600">•</span>
|
|
||||||
<span className="text-xs text-gray-600">
|
|
||||||
{thread.lastActivity}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center gap-1">
|
|
||||||
{thread.githubIssue && (
|
|
||||||
<Button
|
|
||||||
variant="ghost"
|
|
||||||
size="sm"
|
|
||||||
className="h-5 w-5 p-0 text-gray-500 hover:text-gray-300"
|
|
||||||
onClick={(e) => {
|
|
||||||
e.stopPropagation();
|
|
||||||
window.open(thread.githubIssue!.url, "_blank");
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Bug className="h-3 w-3" />
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
{thread.pullRequest && (
|
|
||||||
<Button
|
|
||||||
variant="ghost"
|
|
||||||
size="sm"
|
|
||||||
className={`h-5 w-5 p-0 hover:text-gray-300 ${getPRStatusColor(thread.pullRequest.status)}`}
|
|
||||||
onClick={(e) => {
|
|
||||||
e.stopPropagation();
|
|
||||||
window.open(thread.pullRequest!.url, "_blank");
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<GitPullRequest className="h-3 w-3" />
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Quick Actions */}
|
{/* Quick Actions */}
|
||||||
<div>
|
<div>
|
||||||
<h2 className="mb-3 text-base font-semibold text-gray-300">
|
<h2 className="text-foreground mb-3 text-base font-semibold">
|
||||||
Quick Actions
|
Quick Actions
|
||||||
</h2>
|
</h2>
|
||||||
<div className="grid gap-3 md:grid-cols-3">
|
<div className="grid gap-3 md:grid-cols-3">
|
||||||
<Card className="cursor-pointer border-gray-800 bg-gray-950 py-3 transition-shadow hover:bg-gray-900 hover:shadow-lg">
|
<Card className="border-border bg-card hover:bg-muted cursor-pointer py-3 transition-shadow hover:shadow-lg dark:bg-gray-950">
|
||||||
<CardHeader className="px-3">
|
<CardHeader className="px-3">
|
||||||
<CardTitle className="text-sm text-gray-300">
|
<CardTitle className="text-foreground text-sm">
|
||||||
Debug Code
|
Debug Code
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
<CardDescription className="text-xs text-gray-500">
|
<CardDescription className="text-muted-foreground text-xs">
|
||||||
Find and fix issues in your codebase
|
Find and fix issues in your codebase
|
||||||
</CardDescription>
|
</CardDescription>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
</Card>
|
</Card>
|
||||||
<Card className="cursor-pointer border-gray-800 bg-gray-950 py-3 transition-shadow hover:bg-gray-900 hover:shadow-lg">
|
<Card className="border-border bg-card hover:bg-muted cursor-pointer py-3 transition-shadow hover:shadow-lg dark:bg-gray-950">
|
||||||
<CardHeader className="px-3">
|
<CardHeader className="px-3">
|
||||||
<CardTitle className="text-sm text-gray-300">
|
<CardTitle className="text-foreground text-sm">
|
||||||
Add Feature
|
Add Feature
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
<CardDescription className="text-xs text-gray-500">
|
<CardDescription className="text-muted-foreground text-xs">
|
||||||
Implement new functionality
|
Implement new functionality
|
||||||
</CardDescription>
|
</CardDescription>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
</Card>
|
</Card>
|
||||||
<Card className="cursor-pointer border-gray-800 bg-gray-950 py-3 transition-shadow hover:bg-gray-900 hover:shadow-lg">
|
<Card className="border-border bg-card hover:bg-muted cursor-pointer py-3 transition-shadow hover:shadow-lg dark:bg-gray-950">
|
||||||
<CardHeader className="px-3">
|
<CardHeader className="px-3">
|
||||||
<CardTitle className="text-sm text-gray-300">
|
<CardTitle className="text-foreground text-sm">
|
||||||
Refactor Code
|
Refactor Code
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
<CardDescription className="text-xs text-gray-500">
|
<CardDescription className="text-muted-foreground text-xs">
|
||||||
Improve code structure and performance
|
Improve code structure and performance
|
||||||
</CardDescription>
|
</CardDescription>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
|
|
|
||||||
|
|
@ -106,19 +106,19 @@ export function TerminalInput({
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="rounded-md border border-gray-600 bg-black p-2 font-mono text-xs">
|
<div className="border-border bg-muted rounded-md border p-2 font-mono text-xs dark:bg-black">
|
||||||
<div className="flex items-start gap-1 text-gray-300">
|
<div className="text-foreground flex items-start gap-1">
|
||||||
{/* User@Host */}
|
{/* User@Host */}
|
||||||
<span className="text-gray-400">open-swe</span>
|
<span className="text-muted-foreground">open-swe</span>
|
||||||
<span className="text-gray-500">@</span>
|
<span className="text-muted-foreground/70">@</span>
|
||||||
<span className="text-gray-400">github</span>
|
<span className="text-muted-foreground">github</span>
|
||||||
<span className="text-gray-500">:</span>
|
<span className="text-muted-foreground/70">:</span>
|
||||||
|
|
||||||
{/* Repository & Branch Selectors */}
|
{/* Repository & Branch Selectors */}
|
||||||
<RepositoryBranchSelectors />
|
<RepositoryBranchSelectors />
|
||||||
|
|
||||||
{/* Prompt */}
|
{/* Prompt */}
|
||||||
<span className="text-gray-400">$</span>
|
<span className="text-muted-foreground">$</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Multiline Input */}
|
{/* Multiline Input */}
|
||||||
|
|
@ -129,7 +129,7 @@ export function TerminalInput({
|
||||||
onKeyDown={handleKeyPress}
|
onKeyDown={handleKeyPress}
|
||||||
placeholder={placeholder}
|
placeholder={placeholder}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
className="min-h-[40px] flex-1 resize-none border-none bg-transparent p-0 font-mono text-xs text-white placeholder:text-gray-600 focus-visible:ring-0 focus-visible:ring-offset-0"
|
className="text-foreground placeholder:text-muted-foreground min-h-[40px] flex-1 resize-none border-none bg-transparent p-0 font-mono text-xs focus-visible:ring-0 focus-visible:ring-offset-0"
|
||||||
rows={3}
|
rows={3}
|
||||||
onPaste={onPaste}
|
onPaste={onPaste}
|
||||||
/>
|
/>
|
||||||
|
|
@ -137,7 +137,7 @@ export function TerminalInput({
|
||||||
onClick={handleSend}
|
onClick={handleSend}
|
||||||
disabled={disabled || !message.trim()}
|
disabled={disabled || !message.trim()}
|
||||||
size="sm"
|
size="sm"
|
||||||
className="h-7 w-7 self-end bg-gray-700 p-0 hover:bg-gray-600"
|
className="bg-muted-foreground/20 hover:bg-muted-foreground/30 h-7 w-7 self-end p-0 dark:bg-gray-700 hover:dark:bg-gray-600"
|
||||||
>
|
>
|
||||||
{loading ? (
|
{loading ? (
|
||||||
<Loader2 className="size-3 animate-spin" />
|
<Loader2 className="size-3 animate-spin" />
|
||||||
|
|
@ -148,7 +148,9 @@ export function TerminalInput({
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Help text */}
|
{/* Help text */}
|
||||||
<div className="mt-1 text-xs text-gray-600">Press Cmd+Enter to send</div>
|
<div className="text-muted-foreground mt-1 text-xs">
|
||||||
|
Press Cmd+Enter to send
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
138
apps/web/src/components/v2/thread-card.tsx
Normal file
138
apps/web/src/components/v2/thread-card.tsx
Normal file
|
|
@ -0,0 +1,138 @@
|
||||||
|
import {
|
||||||
|
Bug,
|
||||||
|
CheckCircle,
|
||||||
|
GitBranch,
|
||||||
|
GitPullRequest,
|
||||||
|
Loader2,
|
||||||
|
XCircle,
|
||||||
|
} from "lucide-react";
|
||||||
|
import { Card, CardContent, CardHeader, CardTitle } from "../ui/card";
|
||||||
|
import { ThreadDisplayInfo } from "./types";
|
||||||
|
import { useRouter } from "next/navigation";
|
||||||
|
import { Badge } from "../ui/badge";
|
||||||
|
import { Button } from "../ui/button";
|
||||||
|
|
||||||
|
export function ThreadCard({ thread }: { thread: ThreadDisplayInfo }) {
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
const getStatusColor = (status: ThreadDisplayInfo["status"]) => {
|
||||||
|
switch (status) {
|
||||||
|
case "running":
|
||||||
|
return "dark:bg-blue-950 bg-blue-100 dark:text-blue-400 text-blue-700";
|
||||||
|
case "completed":
|
||||||
|
return "dark:bg-green-950 bg-green-100 dark:text-green-400 text-green-700";
|
||||||
|
case "failed":
|
||||||
|
return "dark:bg-red-950 bg-red-100 dark:text-red-400 text-red-700";
|
||||||
|
case "pending":
|
||||||
|
return "dark:bg-yellow-950 bg-yellow-100 dark:text-yellow-400 text-yellow-700";
|
||||||
|
default:
|
||||||
|
return "dark:bg-gray-800 bg-gray-200 dark:text-gray-400 text-gray-700";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getStatusIcon = (status: ThreadDisplayInfo["status"]) => {
|
||||||
|
switch (status) {
|
||||||
|
case "running":
|
||||||
|
return <Loader2 className="h-4 w-4 animate-spin" />;
|
||||||
|
case "completed":
|
||||||
|
return <CheckCircle className="h-4 w-4" />;
|
||||||
|
case "failed":
|
||||||
|
return <XCircle className="h-4 w-4" />;
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getPRStatusColor = (status: string) => {
|
||||||
|
switch (status) {
|
||||||
|
case "merged":
|
||||||
|
return "dark:text-purple-400 text-purple-600";
|
||||||
|
case "open":
|
||||||
|
return "dark:text-green-400 text-green-600";
|
||||||
|
case "draft":
|
||||||
|
return "dark:text-gray-400 text-gray-600";
|
||||||
|
case "closed":
|
||||||
|
return "dark:text-red-400 text-red-600";
|
||||||
|
default:
|
||||||
|
return "dark:text-gray-400 text-gray-600";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card
|
||||||
|
key={thread.id}
|
||||||
|
className="border-border bg-card hover:bg-muted cursor-pointer px-0 py-3 transition-shadow hover:shadow-lg dark:bg-gray-950"
|
||||||
|
onClick={() => {
|
||||||
|
router.push(`/chat/${thread.id}`);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<CardHeader>
|
||||||
|
<div className="flex items-start justify-between">
|
||||||
|
<div className="min-w-0 flex-1">
|
||||||
|
<CardTitle className="text-foreground truncate text-sm font-medium">
|
||||||
|
{thread.title}
|
||||||
|
</CardTitle>
|
||||||
|
<div className="mt-1 flex items-center gap-1">
|
||||||
|
<GitBranch className="text-muted-foreground h-2 w-2" />
|
||||||
|
<span className="text-muted-foreground truncate text-xs">
|
||||||
|
{thread.repository}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Badge
|
||||||
|
variant="secondary"
|
||||||
|
className={`${getStatusColor(thread.status)} text-xs`}
|
||||||
|
>
|
||||||
|
<div className="flex items-center gap-1">
|
||||||
|
{getStatusIcon(thread.status)}
|
||||||
|
<span className="capitalize">{thread.status}</span>
|
||||||
|
</div>
|
||||||
|
</Badge>
|
||||||
|
</div>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<span className="text-muted-foreground text-xs">
|
||||||
|
{thread.taskCount === 0
|
||||||
|
? "No tasks"
|
||||||
|
: `${thread.taskCount} tasks`}
|
||||||
|
</span>
|
||||||
|
<span className="text-muted-foreground text-xs">•</span>
|
||||||
|
<span className="text-muted-foreground text-xs">
|
||||||
|
{thread.lastActivity}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-1">
|
||||||
|
{thread.githubIssue && (
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="sm"
|
||||||
|
className="text-muted-foreground hover:text-foreground h-5 w-5 p-0"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
window.open(thread.githubIssue!.url, "_blank");
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Bug className="h-3 w-3" />
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
{thread.pullRequest && (
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="sm"
|
||||||
|
className={`h-5 w-5 p-0 hover:text-gray-300 ${getPRStatusColor(thread.pullRequest.status)}`}
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
window.open(thread.pullRequest!.url, "_blank");
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<GitPullRequest className="h-3 w-3" />
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -21,67 +21,21 @@ import {
|
||||||
Bug,
|
Bug,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
import { getThreadTitle } from "@/lib/thread";
|
|
||||||
import { getActivePlanItems } from "@open-swe/shared/open-swe/tasks";
|
|
||||||
import { ThreadDisplayInfo } from "./types";
|
import { ThreadDisplayInfo } from "./types";
|
||||||
|
import { ThreadCard } from "./thread-card";
|
||||||
|
|
||||||
interface ThreadSwitcherProps {
|
interface ThreadSwitcherProps {
|
||||||
currentThread: ThreadDisplayInfo;
|
currentThread: ThreadDisplayInfo;
|
||||||
allThreads: ThreadDisplayInfo[];
|
allThreads: ThreadDisplayInfo[];
|
||||||
onThreadSelect: (thread: ThreadDisplayInfo) => void;
|
|
||||||
onNewChat: () => void;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ThreadSwitcher({
|
export function ThreadSwitcher({
|
||||||
currentThread,
|
currentThread,
|
||||||
allThreads,
|
allThreads,
|
||||||
onThreadSelect,
|
|
||||||
onNewChat,
|
|
||||||
}: ThreadSwitcherProps) {
|
}: ThreadSwitcherProps) {
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
const getStatusIcon = (status: ThreadDisplayInfo["status"]) => {
|
|
||||||
switch (status) {
|
|
||||||
case "running":
|
|
||||||
return <Loader2 className="h-3 w-3 animate-spin text-blue-400" />;
|
|
||||||
case "completed":
|
|
||||||
return <CheckCircle className="h-3 w-3 text-green-400" />;
|
|
||||||
case "failed":
|
|
||||||
return <XCircle className="h-3 w-3 text-red-400" />;
|
|
||||||
default:
|
|
||||||
return <div className="h-3 w-3 rounded-full bg-gray-700" />;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const getStatusColor = (status: ThreadDisplayInfo["status"]) => {
|
|
||||||
switch (status) {
|
|
||||||
case "running":
|
|
||||||
return "bg-blue-950 text-blue-400";
|
|
||||||
case "completed":
|
|
||||||
return "bg-green-950 text-green-400";
|
|
||||||
case "failed":
|
|
||||||
return "bg-red-950 text-red-400";
|
|
||||||
default:
|
|
||||||
return "bg-gray-800 text-gray-400";
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const getPRStatusColor = (status: string) => {
|
|
||||||
switch (status) {
|
|
||||||
case "merged":
|
|
||||||
return "text-purple-400";
|
|
||||||
case "open":
|
|
||||||
return "text-green-400";
|
|
||||||
case "draft":
|
|
||||||
return "text-gray-400";
|
|
||||||
case "closed":
|
|
||||||
return "text-red-400";
|
|
||||||
default:
|
|
||||||
return "text-gray-400";
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const otherThreads = allThreads.filter((t) => t.id !== currentThread.id);
|
const otherThreads = allThreads.filter((t) => t.id !== currentThread.id);
|
||||||
const runningCount = otherThreads.filter(
|
const runningCount = otherThreads.filter(
|
||||||
(t) => t.status === "running",
|
(t) => t.status === "running",
|
||||||
|
|
@ -96,14 +50,14 @@ export function ThreadSwitcher({
|
||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
size="sm"
|
size="sm"
|
||||||
className="h-7 gap-1 border-gray-700 bg-gray-900 text-xs text-gray-400 hover:bg-gray-800 hover:text-gray-300"
|
className="border-border bg-card text-muted-foreground hover:bg-muted hover:text-foreground h-7 gap-1 text-xs dark:border-gray-700 dark:bg-gray-900 dark:hover:bg-gray-800 dark:hover:text-gray-300"
|
||||||
>
|
>
|
||||||
<Layers3 className="h-3 w-3" />
|
<Layers3 className="h-3 w-3" />
|
||||||
<span className="hidden sm:inline">Switch Thread</span>
|
<span className="hidden sm:inline">Switch Thread</span>
|
||||||
{runningCount > 0 && (
|
{runningCount > 0 && (
|
||||||
<Badge
|
<Badge
|
||||||
variant="secondary"
|
variant="secondary"
|
||||||
className="h-4 bg-blue-950 px-1 text-xs text-blue-400"
|
className="h-4 bg-blue-100 px-1 text-xs text-blue-700 dark:bg-blue-950 dark:text-blue-400"
|
||||||
>
|
>
|
||||||
{runningCount}
|
{runningCount}
|
||||||
</Badge>
|
</Badge>
|
||||||
|
|
@ -112,22 +66,22 @@ export function ThreadSwitcher({
|
||||||
</SheetTrigger>
|
</SheetTrigger>
|
||||||
<SheetContent
|
<SheetContent
|
||||||
side="right"
|
side="right"
|
||||||
className="w-80 border-gray-800 bg-gray-950 sm:w-96"
|
className="border-border bg-background w-80 sm:w-96 dark:border-gray-800 dark:bg-gray-950"
|
||||||
>
|
>
|
||||||
<SheetHeader className="pb-4">
|
<SheetHeader className="pb-4">
|
||||||
<SheetTitle className="text-base text-gray-300">
|
<SheetTitle className="text-foreground text-base">
|
||||||
All Threads
|
All Threads
|
||||||
</SheetTitle>
|
</SheetTitle>
|
||||||
</SheetHeader>
|
</SheetHeader>
|
||||||
|
|
||||||
<div className="space-y-3">
|
<div className="mx-2 h-full space-y-3">
|
||||||
{/* New Chat Button */}
|
{/* New Chat Button */}
|
||||||
<Button
|
<Button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
router.push("/chat");
|
router.push("/chat");
|
||||||
setOpen(false);
|
setOpen(false);
|
||||||
}}
|
}}
|
||||||
className="h-8 w-full justify-start gap-2 border-gray-700 bg-gray-900 text-xs text-gray-300 hover:bg-gray-800"
|
className="border-border bg-card text-foreground hover:bg-muted h-8 w-full justify-start gap-2 text-xs dark:border-gray-700 dark:bg-gray-900 dark:text-gray-300 dark:hover:bg-gray-800"
|
||||||
variant="outline"
|
variant="outline"
|
||||||
>
|
>
|
||||||
<Plus className="h-3 w-3" />
|
<Plus className="h-3 w-3" />
|
||||||
|
|
@ -136,146 +90,25 @@ export function ThreadSwitcher({
|
||||||
|
|
||||||
{/* Current Thread */}
|
{/* Current Thread */}
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<h3 className="text-xs font-medium tracking-wide text-gray-500 uppercase">
|
<h3 className="text-muted-foreground text-xs font-medium tracking-wide uppercase dark:text-gray-500">
|
||||||
Current Thread
|
Current Thread
|
||||||
</h3>
|
</h3>
|
||||||
<div className="rounded-lg border-2 border-blue-800 bg-blue-950 p-3">
|
<ThreadCard thread={currentThread} />
|
||||||
<div className="flex items-start gap-2">
|
|
||||||
{getStatusIcon(currentThread.status)}
|
|
||||||
<div className="min-w-0 flex-1">
|
|
||||||
<div className="truncate text-xs font-medium text-gray-300">
|
|
||||||
{currentThread.title}
|
|
||||||
</div>
|
|
||||||
<div className="mt-1 flex items-center gap-1">
|
|
||||||
<GitBranch className="h-2 w-2 text-gray-600" />
|
|
||||||
<span className="truncate text-xs text-gray-500">
|
|
||||||
{currentThread.repository}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div className="mt-2 flex items-center justify-between">
|
|
||||||
<Badge
|
|
||||||
variant="secondary"
|
|
||||||
className={`${getStatusColor(currentThread.status)} text-xs`}
|
|
||||||
>
|
|
||||||
{currentThread.status}
|
|
||||||
</Badge>
|
|
||||||
<div className="flex items-center gap-1">
|
|
||||||
{currentThread.githubIssue && (
|
|
||||||
<Button
|
|
||||||
variant="ghost"
|
|
||||||
size="sm"
|
|
||||||
className="h-5 w-5 p-0 text-gray-500 hover:text-gray-300"
|
|
||||||
onClick={(e) => {
|
|
||||||
e.stopPropagation();
|
|
||||||
// TODO: Open issue in GitHub
|
|
||||||
alert("Open issue in github not implemented.");
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Bug className="h-3 w-3" />
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
{/* TODO: Add PR info to state, then hook this up. */}
|
|
||||||
{/* {currentThread.pullRequest && (
|
|
||||||
<Button
|
|
||||||
variant="ghost"
|
|
||||||
size="sm"
|
|
||||||
className={`h-5 w-5 p-0 hover:text-gray-300 ${getPRStatusColor(currentThread.pullRequest.status)}`}
|
|
||||||
onClick={(e) => {
|
|
||||||
e.stopPropagation()
|
|
||||||
window.open(currentThread.pullRequest!.url, "_blank")
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<GitPullRequest className="h-3 w-3" />
|
|
||||||
</Button>
|
|
||||||
)} */}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Other Threads */}
|
{/* Other Threads */}
|
||||||
{otherThreads.length > 0 && (
|
{otherThreads.length > 0 && (
|
||||||
<div className="space-y-2">
|
<div className="h-full space-y-2">
|
||||||
<h3 className="text-xs font-medium tracking-wide text-gray-500 uppercase">
|
<h3 className="text-muted-foreground text-xs font-medium tracking-wide uppercase dark:text-gray-500">
|
||||||
Other Threads
|
Other Threads
|
||||||
</h3>
|
</h3>
|
||||||
<ScrollArea className="h-96">
|
<ScrollArea className="h-full">
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
{otherThreads.map((thread) => (
|
{otherThreads.map((thread) => (
|
||||||
<Button
|
<ThreadCard
|
||||||
|
thread={thread}
|
||||||
key={thread.id}
|
key={thread.id}
|
||||||
variant="ghost"
|
/>
|
||||||
className="h-auto w-full justify-start p-3 text-left text-gray-400 hover:bg-gray-800"
|
|
||||||
onClick={() => {
|
|
||||||
router.push(`/chat/${thread.id}`);
|
|
||||||
setOpen(false);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div className="flex w-full items-start gap-2">
|
|
||||||
{getStatusIcon(thread.status)}
|
|
||||||
<div className="min-w-0 flex-1">
|
|
||||||
<div className="truncate text-xs font-medium text-gray-300">
|
|
||||||
{thread.title}
|
|
||||||
</div>
|
|
||||||
<div className="mt-1 flex items-center gap-1">
|
|
||||||
<GitBranch className="h-2 w-2 text-gray-600" />
|
|
||||||
<span className="truncate text-xs text-gray-500">
|
|
||||||
{thread.repository}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div className="mt-1 flex items-center justify-between">
|
|
||||||
{thread.taskCount && (
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<span className="text-xs text-gray-600">
|
|
||||||
{thread.taskCount} tasks
|
|
||||||
</span>
|
|
||||||
<Badge
|
|
||||||
variant="secondary"
|
|
||||||
className={`${getStatusColor(thread.status)} text-xs`}
|
|
||||||
>
|
|
||||||
{thread.status}
|
|
||||||
</Badge>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<div className="flex items-center gap-1">
|
|
||||||
{thread.githubIssue && (
|
|
||||||
<Button
|
|
||||||
variant="ghost"
|
|
||||||
size="sm"
|
|
||||||
className="h-4 w-4 p-0 text-gray-600 hover:text-gray-400"
|
|
||||||
onClick={(e) => {
|
|
||||||
e.stopPropagation();
|
|
||||||
// TODO: Open issue in GitHub
|
|
||||||
alert(
|
|
||||||
"Open issue in github not implemented.",
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Bug className="h-2 w-2" />
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
{/* TODO: Add PR info to state, then hook this up. */}
|
|
||||||
{/* {thread.pullRequest && (
|
|
||||||
<Button
|
|
||||||
variant="ghost"
|
|
||||||
size="sm"
|
|
||||||
className={`h-4 w-4 p-0 hover:text-gray-400 ${getPRStatusColor(thread.pullRequest.status)}`}
|
|
||||||
onClick={(e) => {
|
|
||||||
e.stopPropagation()
|
|
||||||
window.open(thread.pullRequest!.url, "_blank")
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<GitPullRequest className="h-2 w-2" />
|
|
||||||
</Button>
|
|
||||||
)} */}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Button>
|
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</ScrollArea>
|
</ScrollArea>
|
||||||
|
|
|
||||||
|
|
@ -12,13 +12,15 @@ const isEven = (num: number) => num % 2 === 0;
|
||||||
|
|
||||||
export function LoadingActionsSection() {
|
export function LoadingActionsSection() {
|
||||||
return (
|
return (
|
||||||
<Card className="border-gray-800 bg-gray-950">
|
<Card className="border-border bg-card dark:bg-gray-950">
|
||||||
<CardHeader className="p-3">
|
<CardHeader className="p-3">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<div className="h-4 w-32 animate-pulse rounded bg-gray-700"></div>
|
<div className="bg-muted h-4 w-32 animate-pulse rounded dark:bg-gray-700"></div>
|
||||||
<div className="ml-auto flex items-center gap-1">
|
<div className="ml-auto flex items-center gap-1">
|
||||||
<Loader2 className="h-3 w-3 animate-spin text-gray-500" />
|
<Loader2 className="text-muted-foreground h-3 w-3 animate-spin" />
|
||||||
<span className="text-xs text-gray-500">Loading actions...</span>
|
<span className="text-muted-foreground text-xs">
|
||||||
|
Loading actions...
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
|
|
@ -26,35 +28,35 @@ export function LoadingActionsSection() {
|
||||||
{Array.from({ length: 8 }, (_, i) => (
|
{Array.from({ length: 8 }, (_, i) => (
|
||||||
<div
|
<div
|
||||||
key={i}
|
key={i}
|
||||||
className="space-y-2 rounded-lg border border-gray-800 bg-gray-900 p-3"
|
className="border-border bg-muted/30 space-y-2 rounded-lg border p-3 dark:bg-gray-900"
|
||||||
>
|
>
|
||||||
{isEven(i) ? (
|
{isEven(i) ? (
|
||||||
<div className="space-y-1 rounded bg-black p-2">
|
<div className="bg-background space-y-1 rounded p-2 dark:bg-black">
|
||||||
<div className="h-2 w-32 animate-pulse rounded bg-gray-700"></div>
|
<div className="bg-muted h-2 w-32 animate-pulse rounded dark:bg-gray-700"></div>
|
||||||
<div className="h-2 w-48 animate-pulse rounded bg-gray-700"></div>
|
<div className="bg-muted h-2 w-48 animate-pulse rounded dark:bg-gray-700"></div>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<div className="h-4 w-4 animate-pulse rounded bg-gray-700"></div>
|
<div className="bg-muted h-4 w-4 animate-pulse rounded dark:bg-gray-700"></div>
|
||||||
<div className="h-3 w-40 animate-pulse rounded bg-gray-700"></div>
|
<div className="bg-muted h-3 w-40 animate-pulse rounded dark:bg-gray-700"></div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<div className="h-3 w-16 animate-pulse rounded bg-gray-700"></div>
|
<div className="bg-muted h-3 w-16 animate-pulse rounded dark:bg-gray-700"></div>
|
||||||
<div className="h-4 w-4 animate-pulse rounded bg-gray-700"></div>
|
<div className="bg-muted h-4 w-4 animate-pulse rounded dark:bg-gray-700"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
<div className="space-y-2 rounded-lg border-2 border-dashed border-gray-700 p-3">
|
<div className="border-border space-y-2 rounded-lg border-2 border-dashed p-3">
|
||||||
<div className="h-12 animate-pulse rounded bg-gray-800"></div>
|
<div className="bg-muted h-12 animate-pulse rounded dark:bg-gray-800"></div>
|
||||||
<div className="h-7 w-16 animate-pulse rounded bg-gray-800"></div>
|
<div className="bg-muted h-7 w-16 animate-pulse rounded dark:bg-gray-800"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex gap-2 pt-3">
|
<div className="flex gap-2 pt-3">
|
||||||
<div className="h-8 flex-1 animate-pulse rounded bg-gray-700"></div>
|
<div className="bg-muted h-8 flex-1 animate-pulse rounded dark:bg-gray-700"></div>
|
||||||
<div className="h-8 flex-1 animate-pulse rounded bg-gray-700"></div>
|
<div className="bg-muted h-8 flex-1 animate-pulse rounded dark:bg-gray-700"></div>
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
@ -65,7 +67,7 @@ export function LoadingChatSection() {
|
||||||
return (
|
return (
|
||||||
<div className="flex-1 space-y-4 overflow-y-auto p-4">
|
<div className="flex-1 space-y-4 overflow-y-auto p-4">
|
||||||
<div className="flex items-center justify-center py-8">
|
<div className="flex items-center justify-center py-8">
|
||||||
<div className="flex items-center gap-2 text-gray-500">
|
<div className="text-muted-foreground flex items-center gap-2">
|
||||||
<Loader2 className="h-4 w-4 animate-spin" />
|
<Loader2 className="h-4 w-4 animate-spin" />
|
||||||
<span className="text-sm">Loading conversation...</span>
|
<span className="text-sm">Loading conversation...</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -77,18 +79,18 @@ export function LoadingChatSection() {
|
||||||
className="flex gap-3"
|
className="flex gap-3"
|
||||||
>
|
>
|
||||||
<div className="flex-shrink-0">
|
<div className="flex-shrink-0">
|
||||||
<div className="h-6 w-6 animate-pulse rounded-full bg-gray-700"></div>
|
<div className="bg-muted h-6 w-6 animate-pulse rounded-full dark:bg-gray-700"></div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-1 space-y-2">
|
<div className="flex-1 space-y-2">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<div className="h-3 w-16 animate-pulse rounded bg-gray-700"></div>
|
<div className="bg-muted h-3 w-16 animate-pulse rounded dark:bg-gray-700"></div>
|
||||||
<div className="h-3 w-12 animate-pulse rounded bg-gray-700"></div>
|
<div className="bg-muted h-3 w-12 animate-pulse rounded dark:bg-gray-700"></div>
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
<div className="h-3 w-full animate-pulse rounded bg-gray-700"></div>
|
<div className="bg-muted h-3 w-full animate-pulse rounded dark:bg-gray-700"></div>
|
||||||
<div className="h-3 w-3/4 animate-pulse rounded bg-gray-700"></div>
|
<div className="bg-muted h-3 w-3/4 animate-pulse rounded dark:bg-gray-700"></div>
|
||||||
{i === 1 && (
|
{i === 1 && (
|
||||||
<div className="h-3 w-1/2 animate-pulse rounded bg-gray-700"></div>
|
<div className="bg-muted h-3 w-1/2 animate-pulse rounded dark:bg-gray-700"></div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -100,42 +102,42 @@ export function LoadingChatSection() {
|
||||||
|
|
||||||
export function ThreadViewLoading({ onBackToHome }: ThreadViewLoadingProps) {
|
export function ThreadViewLoading({ onBackToHome }: ThreadViewLoadingProps) {
|
||||||
return (
|
return (
|
||||||
<div className="flex h-screen flex-1 flex-col bg-black">
|
<div className="bg-background flex h-screen flex-1 flex-col">
|
||||||
<div className="absolute top-0 right-0 left-0 z-10 border-b border-gray-900 bg-black px-4 py-2">
|
<div className="border-border bg-card absolute top-0 right-0 left-0 z-10 border-b px-4 py-2">
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="sm"
|
size="sm"
|
||||||
className="h-6 w-6 p-0 text-gray-600 hover:bg-gray-900 hover:text-gray-400"
|
className="text-muted-foreground hover:bg-muted hover:text-foreground h-6 w-6 p-0"
|
||||||
onClick={onBackToHome}
|
onClick={onBackToHome}
|
||||||
>
|
>
|
||||||
<ArrowLeft className="h-3 w-3" />
|
<ArrowLeft className="h-3 w-3" />
|
||||||
</Button>
|
</Button>
|
||||||
<div className="flex min-w-0 flex-1 items-center gap-2">
|
<div className="flex min-w-0 flex-1 items-center gap-2">
|
||||||
<div className="h-2 w-2 animate-pulse rounded-full bg-gray-700"></div>
|
<div className="bg-muted h-2 w-2 animate-pulse rounded-full dark:bg-gray-700"></div>
|
||||||
<div className="h-3 w-48 animate-pulse rounded bg-gray-700"></div>
|
<div className="bg-muted h-3 w-48 animate-pulse rounded dark:bg-gray-700"></div>
|
||||||
<span className="text-xs text-gray-600">•</span>
|
<span className="text-muted-foreground text-xs">•</span>
|
||||||
<div className="h-3 w-24 animate-pulse rounded bg-gray-700"></div>
|
<div className="bg-muted h-3 w-24 animate-pulse rounded dark:bg-gray-700"></div>
|
||||||
</div>
|
</div>
|
||||||
<div className="h-7 w-28 animate-pulse rounded bg-gray-800"></div>
|
<div className="bg-muted/70 h-7 w-28 animate-pulse rounded dark:bg-gray-800"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex h-full w-full pt-12">
|
<div className="flex h-full w-full pt-12">
|
||||||
<div className="flex h-full w-1/3 flex-col border-r border-gray-900 bg-gray-950">
|
<div className="border-border bg-muted/30 flex h-full w-1/3 flex-col border-r dark:bg-gray-950">
|
||||||
<LoadingChatSection />
|
<LoadingChatSection />
|
||||||
|
|
||||||
<div className="border-t border-gray-800 bg-gray-950 p-4">
|
<div className="border-border bg-muted/30 border-t p-4 dark:bg-gray-950">
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
<div className="flex-1 rounded-md border border-gray-700 bg-gray-900 p-3">
|
<div className="border-border bg-background flex-1 rounded-md border p-3 dark:bg-gray-900">
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<div className="h-3 w-1/3 animate-pulse rounded bg-gray-700"></div>
|
<div className="bg-muted h-3 w-1/3 animate-pulse rounded dark:bg-gray-700"></div>
|
||||||
<div className="h-3 w-1/2 animate-pulse rounded bg-gray-700"></div>
|
<div className="bg-muted h-3 w-1/2 animate-pulse rounded dark:bg-gray-700"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="h-10 w-10 animate-pulse self-end rounded bg-gray-700"></div>
|
<div className="bg-muted h-10 w-10 animate-pulse self-end rounded dark:bg-gray-700"></div>
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-2 h-3 w-32 animate-pulse rounded bg-gray-700"></div>
|
<div className="bg-muted mt-2 h-3 w-32 animate-pulse rounded dark:bg-gray-700"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ import { useStream } from "@langchain/langgraph-sdk/react";
|
||||||
import { ManagerGraphState } from "@open-swe/shared/open-swe/manager/types";
|
import { ManagerGraphState } from "@open-swe/shared/open-swe/manager/types";
|
||||||
import { PlannerGraphState } from "@open-swe/shared/open-swe/planner/types";
|
import { PlannerGraphState } from "@open-swe/shared/open-swe/planner/types";
|
||||||
import { ActionsRenderer } from "./actions-renderer";
|
import { ActionsRenderer } from "./actions-renderer";
|
||||||
|
import { ThemeToggle } from "../theme-toggle";
|
||||||
|
|
||||||
const PROGRAMMER_ASSISTANT_ID = process.env.NEXT_PUBLIC_PROGRAMMER_ASSISTANT_ID;
|
const PROGRAMMER_ASSISTANT_ID = process.env.NEXT_PUBLIC_PROGRAMMER_ASSISTANT_ID;
|
||||||
const PLANNER_ASSISTANT_ID = process.env.NEXT_PUBLIC_PLANNER_ASSISTANT_ID;
|
const PLANNER_ASSISTANT_ID = process.env.NEXT_PUBLIC_PLANNER_ASSISTANT_ID;
|
||||||
|
|
@ -21,7 +22,6 @@ interface ThreadViewProps {
|
||||||
stream: ReturnType<typeof useStream<ManagerGraphState>>;
|
stream: ReturnType<typeof useStream<ManagerGraphState>>;
|
||||||
displayThread: ThreadDisplayInfo;
|
displayThread: ThreadDisplayInfo;
|
||||||
allDisplayThreads: ThreadDisplayInfo[];
|
allDisplayThreads: ThreadDisplayInfo[];
|
||||||
onThreadSelect: (thread: ThreadDisplayInfo) => void;
|
|
||||||
onBackToHome: () => void;
|
onBackToHome: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -29,7 +29,6 @@ export function ThreadView({
|
||||||
stream,
|
stream,
|
||||||
displayThread,
|
displayThread,
|
||||||
allDisplayThreads,
|
allDisplayThreads,
|
||||||
onThreadSelect,
|
|
||||||
onBackToHome,
|
onBackToHome,
|
||||||
}: ThreadViewProps) {
|
}: ThreadViewProps) {
|
||||||
const [chatInput, setChatInput] = useState("");
|
const [chatInput, setChatInput] = useState("");
|
||||||
|
|
@ -50,14 +49,14 @@ export function ThreadView({
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex h-screen flex-1 flex-col bg-black">
|
<div className="bg-background flex h-screen flex-1 flex-col">
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div className="absolute top-0 right-0 left-0 z-10 border-b border-gray-900 bg-black px-4 py-2">
|
<div className="border-border bg-card absolute top-0 right-0 left-0 z-10 border-b px-4 py-2">
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="sm"
|
size="sm"
|
||||||
className="h-6 w-6 p-0 text-gray-600 hover:bg-gray-900 hover:text-gray-400"
|
className="text-muted-foreground hover:bg-muted hover:text-foreground h-6 w-6 p-0"
|
||||||
onClick={onBackToHome}
|
onClick={onBackToHome}
|
||||||
>
|
>
|
||||||
<ArrowLeft className="h-3 w-3" />
|
<ArrowLeft className="h-3 w-3" />
|
||||||
|
|
@ -72,28 +71,27 @@ export function ThreadView({
|
||||||
: "bg-red-500"
|
: "bg-red-500"
|
||||||
}`}
|
}`}
|
||||||
></div>
|
></div>
|
||||||
<span className="truncate font-mono text-sm text-gray-400">
|
<span className="text-muted-foreground truncate font-mono text-sm">
|
||||||
{displayThread.title}
|
{displayThread.title}
|
||||||
</span>
|
</span>
|
||||||
<span className="text-xs text-gray-600">•</span>
|
<span className="text-muted-foreground text-xs">•</span>
|
||||||
<GitBranch className="h-3 w-3 text-gray-600" />
|
<GitBranch className="text-muted-foreground h-3 w-3" />
|
||||||
<span className="truncate text-xs text-gray-600">
|
<span className="text-muted-foreground truncate text-xs">
|
||||||
{displayThread.repository}
|
{displayThread.repository}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<ThreadSwitcher
|
<ThreadSwitcher
|
||||||
currentThread={displayThread}
|
currentThread={displayThread}
|
||||||
allThreads={allDisplayThreads}
|
allThreads={allDisplayThreads}
|
||||||
onThreadSelect={onThreadSelect}
|
|
||||||
onNewChat={onBackToHome}
|
|
||||||
/>
|
/>
|
||||||
|
<ThemeToggle />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Main Content - Split Layout */}
|
{/* Main Content - Split Layout */}
|
||||||
<div className="flex h-full w-full pt-12">
|
<div className="flex h-full w-full pt-12">
|
||||||
{/* Left Side - Chat Interface */}
|
{/* Left Side - Chat Interface */}
|
||||||
<div className="flex h-full w-1/3 flex-col border-r border-gray-900 bg-gray-950">
|
<div className="border-border bg-muted/30 flex h-full w-1/3 flex-col border-r dark:bg-gray-950">
|
||||||
{/* Chat Messages */}
|
{/* Chat Messages */}
|
||||||
<div className="flex-1 space-y-4 overflow-y-auto p-4">
|
<div className="flex-1 space-y-4 overflow-y-auto p-4">
|
||||||
{stream.messages.map((message) => (
|
{stream.messages.map((message) => (
|
||||||
|
|
@ -103,22 +101,22 @@ export function ThreadView({
|
||||||
>
|
>
|
||||||
<div className="flex-shrink-0">
|
<div className="flex-shrink-0">
|
||||||
{message.type === "human" ? (
|
{message.type === "human" ? (
|
||||||
<div className="flex h-6 w-6 items-center justify-center rounded-full bg-gray-700">
|
<div className="bg-muted flex h-6 w-6 items-center justify-center rounded-full dark:bg-gray-700">
|
||||||
<User className="h-3 w-3 text-gray-400" />
|
<User className="text-muted-foreground h-3 w-3" />
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="flex h-6 w-6 items-center justify-center rounded-full bg-blue-900">
|
<div className="flex h-6 w-6 items-center justify-center rounded-full bg-blue-100 dark:bg-blue-900">
|
||||||
<Bot className="h-3 w-3 text-blue-400" />
|
<Bot className="h-3 w-3 text-blue-700 dark:text-blue-400" />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-1 space-y-1">
|
<div className="flex-1 space-y-1">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<span className="text-xs font-medium text-gray-400">
|
<span className="text-muted-foreground text-xs font-medium">
|
||||||
{message.type === "human" ? "You" : "AI Agent"}
|
{message.type === "human" ? "You" : "Agent"}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="text-sm leading-relaxed text-gray-300">
|
<div className="text-foreground text-sm leading-relaxed">
|
||||||
{getMessageContentString(message.content)}
|
{getMessageContentString(message.content)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -127,13 +125,13 @@ export function ThreadView({
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Chat Input - Fixed at bottom */}
|
{/* Chat Input - Fixed at bottom */}
|
||||||
<div className="border-t border-gray-800 bg-gray-950 p-4">
|
<div className="border-border bg-muted/30 border-t p-4 dark:bg-gray-950">
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
<Textarea
|
<Textarea
|
||||||
value={chatInput}
|
value={chatInput}
|
||||||
onChange={(e) => setChatInput(e.target.value)}
|
onChange={(e) => setChatInput(e.target.value)}
|
||||||
placeholder="Type your message..."
|
placeholder="Type your message..."
|
||||||
className="min-h-[60px] flex-1 resize-none border-gray-700 bg-gray-900 text-sm text-gray-300 placeholder:text-gray-600"
|
className="border-border bg-background text-foreground placeholder:text-muted-foreground min-h-[60px] flex-1 resize-none text-sm dark:bg-gray-900"
|
||||||
onKeyDown={(e) => {
|
onKeyDown={(e) => {
|
||||||
if (e.key === "Enter" && (e.metaKey || e.ctrlKey)) {
|
if (e.key === "Enter" && (e.metaKey || e.ctrlKey)) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
@ -145,12 +143,12 @@ export function ThreadView({
|
||||||
onClick={handleSendMessage}
|
onClick={handleSendMessage}
|
||||||
disabled={!chatInput.trim()}
|
disabled={!chatInput.trim()}
|
||||||
size="sm"
|
size="sm"
|
||||||
className="h-10 w-10 self-end bg-gray-700 p-0 hover:bg-gray-600"
|
className="bg-muted hover:bg-muted/80 h-10 w-10 self-end p-0 dark:bg-gray-700 hover:dark:bg-gray-600"
|
||||||
>
|
>
|
||||||
<Send className="h-4 w-4" />
|
<Send className="h-4 w-4" />
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-2 text-xs text-gray-600">
|
<div className="text-muted-foreground mt-2 text-xs">
|
||||||
Press Cmd+Enter to send
|
Press Cmd+Enter to send
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -163,17 +161,12 @@ export function ThreadView({
|
||||||
defaultValue="planner"
|
defaultValue="planner"
|
||||||
className="w-full"
|
className="w-full"
|
||||||
>
|
>
|
||||||
<TabsList>
|
<TabsList className="bg-muted/70 dark:bg-gray-800">
|
||||||
<TabsTrigger value="planner">Planner</TabsTrigger>
|
<TabsTrigger value="planner">Planner</TabsTrigger>
|
||||||
<TabsTrigger value="programmer">Programmer</TabsTrigger>
|
<TabsTrigger value="programmer">Programmer</TabsTrigger>
|
||||||
</TabsList>
|
</TabsList>
|
||||||
<TabsContent value="planner">
|
<TabsContent value="planner">
|
||||||
<Card className="border-gray-800 bg-gray-950 px-0 py-4">
|
<Card className="border-border bg-card px-0 py-4 dark:bg-gray-950">
|
||||||
<CardHeader>
|
|
||||||
<CardTitle className="text-base text-gray-300">
|
|
||||||
Planning Actions
|
|
||||||
</CardTitle>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent className="space-y-2 p-3 pt-0">
|
<CardContent className="space-y-2 p-3 pt-0">
|
||||||
{plannerThreadId &&
|
{plannerThreadId &&
|
||||||
plannerRunId &&
|
plannerRunId &&
|
||||||
|
|
@ -190,12 +183,7 @@ export function ThreadView({
|
||||||
</Card>
|
</Card>
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
<TabsContent value="programmer">
|
<TabsContent value="programmer">
|
||||||
<Card className="border-gray-800 bg-gray-950 px-0 py-4">
|
<Card className="border-border bg-card px-0 py-4 dark:bg-gray-950">
|
||||||
<CardHeader>
|
|
||||||
<CardTitle className="text-base text-gray-300">
|
|
||||||
Code Actions
|
|
||||||
</CardTitle>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent className="space-y-2 p-3 pt-0">
|
<CardContent className="space-y-2 p-3 pt-0">
|
||||||
{programmerSession && PROGRAMMER_ASSISTANT_ID && (
|
{programmerSession && PROGRAMMER_ASSISTANT_ID && (
|
||||||
<ActionsRenderer<PlannerGraphState>
|
<ActionsRenderer<PlannerGraphState>
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,9 @@
|
||||||
/** @type {import('tailwindcss').Config} */
|
/** @type {import('tailwindcss').Config} */
|
||||||
module.exports = {
|
module.exports = {
|
||||||
darkMode: ["class"],
|
darkMode: "class",
|
||||||
|
future: {
|
||||||
|
hoverOnlyWhenSupported: true,
|
||||||
|
},
|
||||||
content: [
|
content: [
|
||||||
"./index.html",
|
"./index.html",
|
||||||
"./src/**/*.{ts,tsx,js,jsx}",
|
"./src/**/*.{ts,tsx,js,jsx}",
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue