feat: dark mode & general web app improvements (#615)

* feat: viewability improvements

* chore: code formatting

* fix: code fixes

* fix: merge changes lost from main

* fix: attached doc styling

* chore: attachments logo

* fix: remove attachments button

* fix: text visibility & tooltip mardown

* fix: auth state change

* feat conflict with main resolved

* fix: feat conflicts with main

* fix: conversation summary consistency with theme

* fix: conversation summary

* chore: removed text

* cr

---------

Co-authored-by: bracesproul <braceasproul@gmail.com>
This commit is contained in:
Aliyan Ishfaq 2025-07-31 11:55:39 -07:00 • committed by GitHub
parent 7a23f5fdcf
commit 9906dd4e2a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
30 changed files with 528 additions and 374 deletions

View file

@ -16,6 +16,7 @@ import { MANAGER_GRAPH_ID } from "@open-swe/shared/constants";
import { useThreadsStatus } from "@/hooks/useThreadsStatus"; import { useThreadsStatus } from "@/hooks/useThreadsStatus";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
import { threadsToMetadata } from "@/lib/thread-utils"; import { threadsToMetadata } from "@/lib/thread-utils";
import { OpenSWELogoSVG } from "@/components/icons/openswe";
type FilterStatus = type FilterStatus =
| "all" | "all"
@ -109,10 +110,10 @@ function AllThreadsPageContent() {
<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> <OpenSWELogoSVG
<span className="text-muted-foreground font-mono text-sm"> width={120}
All Threads height={18}
</span> />
</div> </div>
<div className="ml-auto flex items-center gap-4"> <div className="ml-auto flex items-center gap-4">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
@ -129,7 +130,7 @@ function AllThreadsPageContent() {
</div> </div>
{/* Search and Filters */} {/* Search and Filters */}
<div className="border-border bg-muted/50 border-b px-4 py-3 dark:bg-gray-950"> <div className="border-border bg-muted/50 border-b px-4 py-3">
<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="text-muted-foreground absolute top-1/2 left-3 h-4 w-4 -translate-y-1/2 transform" /> <Search className="text-muted-foreground absolute top-1/2 left-3 h-4 w-4 -translate-y-1/2 transform" />
@ -137,7 +138,7 @@ function AllThreadsPageContent() {
placeholder="Search threads..." placeholder="Search threads..."
value={searchQuery} value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)} onChange={(e) => setSearchQuery(e.target.value)}
className="border-border bg-background text-foreground placeholder:text-muted-foreground pl-10 dark:bg-gray-900" className="border-border bg-background text-foreground placeholder:text-muted-foreground pl-10"
/> />
</div> </div>
<div className="flex items-center gap-1"> <div className="flex items-center gap-1">
@ -162,7 +163,7 @@ function AllThreadsPageContent() {
className={cn( className={cn(
"h-7 text-xs", "h-7 text-xs",
statusFilter === status statusFilter === status
? "bg-muted text-foreground dark:bg-gray-700" ? "bg-muted text-foreground"
: "text-muted-foreground hover:bg-muted hover:text-foreground", : "text-muted-foreground hover:bg-muted hover:text-foreground",
)} )}
onClick={() => setStatusFilter(status)} onClick={() => setStatusFilter(status)}
@ -172,7 +173,7 @@ function AllThreadsPageContent() {
: status.charAt(0).toUpperCase() + status.slice(1)} : status.charAt(0).toUpperCase() + status.slice(1)}
<Badge <Badge
variant="secondary" variant="secondary"
className="bg-muted/70 text-muted-foreground ml-1 text-xs dark:bg-gray-800" className="bg-muted/70 text-muted-foreground ml-1 text-xs"
> >
{statusCounts[status]} {statusCounts[status]}
</Badge> </Badge>
@ -197,7 +198,7 @@ function AllThreadsPageContent() {
</h2> </h2>
<Badge <Badge
variant="secondary" variant="secondary"
className="bg-muted/70 text-muted-foreground text-xs dark:bg-gray-800" className="bg-muted/70 text-muted-foreground text-xs"
> >
{threads.length} {threads.length}
</Badge> </Badge>

View file

@ -41,38 +41,38 @@
} }
.dark { .dark {
--background: oklch(0.145 0 0); --background: oklch(0.29 0.005 280);
--foreground: oklch(0.985 0 0); --foreground: oklch(0.92 0.01 280);
--card: oklch(0.145 0 0); --card: oklch(0.32 0.005 280);
--card-foreground: oklch(0.985 0 0); --card-foreground: oklch(0.92 0.01 280);
--popover: oklch(0.145 0 0); --popover: oklch(0.32 0.005 280);
--popover-foreground: oklch(0.985 0 0); --popover-foreground: oklch(0.92 0.01 280);
--primary: oklch(0.985 0 0); --primary: oklch(0.92 0.01 280);
--primary-foreground: oklch(0.205 0 0); --primary-foreground: oklch(0.24 0.005 280);
--secondary: oklch(0.269 0 0); --secondary: oklch(0.38 0.005 280);
--secondary-foreground: oklch(0.985 0 0); --secondary-foreground: oklch(0.92 0.01 280);
--muted: oklch(0.269 0 0); --muted: oklch(0.35 0.005 280);
--muted-foreground: oklch(0.708 0 0); --muted-foreground: oklch(0.65 0.01 280);
--accent: oklch(0.269 0 0); --accent: oklch(0.38 0.005 280);
--accent-foreground: oklch(0.985 0 0); --accent-foreground: oklch(0.92 0.01 280);
--destructive: oklch(0.396 0.141 25.723); --destructive: oklch(0.55 0.15 20);
--destructive-foreground: oklch(0.637 0.237 25.331); --destructive-foreground: oklch(0.95 0.01 280);
--border: oklch(0.269 0 0); --border: oklch(0.42 0.005 280);
--input: oklch(0.269 0 0); --input: oklch(0.42 0.005 280);
--ring: oklch(0.439 0 0); --ring: oklch(0.5 0.01 280);
--chart-1: oklch(0.488 0.243 264.376); --chart-1: oklch(0.488 0.243 264.376);
--chart-2: oklch(0.696 0.17 162.48); --chart-2: oklch(0.696 0.17 162.48);
--chart-3: oklch(0.769 0.188 70.08); --chart-3: oklch(0.769 0.188 70.08);
--chart-4: oklch(0.627 0.265 303.9); --chart-4: oklch(0.627 0.265 303.9);
--chart-5: oklch(0.645 0.246 16.439); --chart-5: oklch(0.645 0.246 16.439);
--sidebar: oklch(0.205 0 0); --sidebar: oklch(0.3 0.005 280);
--sidebar-foreground: oklch(0.985 0 0); --sidebar-foreground: oklch(0.92 0.01 280);
--sidebar-primary: oklch(0.488 0.243 264.376); --sidebar-primary: oklch(0.488 0.243 264.376);
--sidebar-primary-foreground: oklch(0.985 0 0); --sidebar-primary-foreground: oklch(0.92 0.01 280);
--sidebar-accent: oklch(0.269 0 0); --sidebar-accent: oklch(0.38 0.005 280);
--sidebar-accent-foreground: oklch(0.985 0 0); --sidebar-accent-foreground: oklch(0.92 0.01 280);
--sidebar-border: oklch(0.269 0 0); --sidebar-border: oklch(0.42 0.005 280);
--sidebar-ring: oklch(0.439 0 0); --sidebar-ring: oklch(0.5 0.01 280);
} }
@theme inline { @theme inline {
@ -167,6 +167,6 @@
} }
.dark .scrollbar-pretty-auto::-webkit-scrollbar-thumb { .dark .scrollbar-pretty-auto::-webkit-scrollbar-thumb {
background-color: rgb(75 85 99); background-color: rgb(95 105 119);
} }
} }

View file

@ -1,6 +1,7 @@
"use client"; "use client";
import { JSX, useState } from "react"; import { JSX, useState } from "react";
import { AnimatePresence, motion } from "framer-motion";
import { import {
Terminal, Terminal,
FileText, FileText,
@ -165,7 +166,7 @@ function MatchCaseIcon({ matchCase }: { matchCase: boolean }) {
"rounded-sm border border-gray-300 px-1 py-[2px] text-xs dark:border-gray-600", "rounded-sm border border-gray-300 px-1 py-[2px] text-xs dark:border-gray-600",
matchCase matchCase
? "border-blue-500 bg-blue-500/80 text-white" ? "border-blue-500 bg-blue-500/80 text-white"
: "bg-gray-100 text-gray-800 dark:bg-gray-800 dark:text-gray-200", : "dark:bg-muted dark:text-muted-foreground bg-muted text-gray-800",
)} )}
> >
<p className="font-mono">Aa</p> <p className="font-mono">Aa</p>
@ -554,7 +555,7 @@ function ActionItem(props: ActionItemProps) {
props.output props.output
) { ) {
return ( return (
<div className="bg-muted text-foreground/90 overflow-x-auto p-2 dark:bg-gray-900"> <div className="bg-muted text-foreground/90 overflow-x-auto p-2">
<pre className="text-xs font-normal whitespace-pre-wrap"> <pre className="text-xs font-normal whitespace-pre-wrap">
{props.output} {props.output}
</pre> </pre>
@ -577,7 +578,7 @@ function ActionItem(props: ActionItemProps) {
} }
return ( return (
<div className="bg-muted overflow-x-auto p-2 dark:bg-gray-900"> <div className="bg-muted overflow-x-auto p-2">
{hasArgs && ( {hasArgs && (
<div className="mb-3"> <div className="mb-3">
<div className="text-muted-foreground mb-2 text-xs font-medium tracking-wide uppercase"> <div className="text-muted-foreground mb-2 text-xs font-medium tracking-wide uppercase">
@ -623,7 +624,7 @@ function ActionItem(props: ActionItemProps) {
); );
} else if (props.actionType === "get_url_content" && props.output) { } else if (props.actionType === "get_url_content" && props.output) {
return ( return (
<div className="bg-muted text-foreground/90 overflow-x-auto p-2 dark:bg-gray-900"> <div className="bg-muted text-foreground/90 overflow-x-auto p-2">
<pre className="text-xs font-normal whitespace-pre-wrap"> <pre className="text-xs font-normal whitespace-pre-wrap">
{props.output} {props.output}
</pre> </pre>
@ -631,7 +632,7 @@ function ActionItem(props: ActionItemProps) {
); );
} else if (props.actionType === "apply-patch" && props.diff) { } else if (props.actionType === "apply-patch" && props.diff) {
return ( return (
<div className="bg-muted overflow-x-auto p-2 dark:bg-gray-900"> <div className="bg-muted overflow-x-auto p-2">
<pre <pre
className="text-foreground/90 text-xs font-normal whitespace-pre-wrap" className="text-foreground/90 text-xs font-normal whitespace-pre-wrap"
dangerouslySetInnerHTML={{ __html: formatDiff(props.diff) }} dangerouslySetInnerHTML={{ __html: formatDiff(props.diff) }}
@ -664,7 +665,7 @@ function ActionItem(props: ActionItemProps) {
props.scratchpad.length > 0 props.scratchpad.length > 0
) { ) {
return ( return (
<div className="bg-muted overflow-x-auto p-2 dark:bg-gray-900"> <div className="bg-muted overflow-x-auto p-2">
<ul className="list-disc pl-5 text-xs font-normal"> <ul className="list-disc pl-5 text-xs font-normal">
{props.scratchpad.map((note, i) => ( {props.scratchpad.map((note, i) => (
<li <li
@ -684,7 +685,7 @@ function ActionItem(props: ActionItemProps) {
return ( return (
<div className="border-border mb-2 overflow-hidden rounded-md border last:mb-0"> <div className="border-border mb-2 overflow-hidden rounded-md border last:mb-0">
<div className="border-border flex w-full items-center border-b bg-gray-50 p-2 dark:bg-gray-800"> <div className="border-border bg-muted/50 flex w-full items-center border-b p-2">
{renderHeaderIcon()} {renderHeaderIcon()}
{renderHeaderContent()} {renderHeaderContent()}
<div className="ml-auto flex items-center gap-2"> <div className="ml-auto flex items-center gap-2">
@ -707,15 +708,24 @@ function ActionItem(props: ActionItemProps) {
</div> </div>
</div> </div>
{renderContent()} <AnimatePresence>
{expanded && (
<motion.div
initial={{ height: 0, opacity: 0 }}
animate={{ height: "auto", opacity: 1 }}
exit={{ height: 0, opacity: 0 }}
transition={{ duration: 0.2, ease: "easeInOut" }}
className="overflow-hidden"
>
{renderContent()}
</motion.div>
)}
</AnimatePresence>
</div> </div>
); );
} }
export function ActionStep(props: ActionStepProps) { export function ActionStep(props: ActionStepProps) {
const [showReasoning, setShowReasoning] = useState(true);
const [showSummary, setShowSummary] = useState(true);
const reasoningText = const reasoningText =
"reasoningText" in props ? props.reasoningText : undefined; "reasoningText" in props ? props.reasoningText : undefined;
const summaryText = "summaryText" in props ? props.summaryText : undefined; const summaryText = "summaryText" in props ? props.summaryText : undefined;
@ -724,21 +734,41 @@ export function ActionStep(props: ActionStepProps) {
(action: ActionItemProps) => action.status === "done", (action: ActionItemProps) => action.status === "done",
); );
const [showReasoning, setShowReasoning] = useState(!!reasoningText);
const [showSummary, setShowSummary] = useState(!!summaryText);
return ( return (
<div className="border-border overflow-hidden rounded-md border"> <div className="border-border bg-muted overflow-hidden rounded-md border">
<div className="border-b border-blue-300 bg-blue-100/50 p-2 dark:border-blue-800 dark:bg-blue-900/50"> <div className="bg-muted dark:bg-muted border-b border-l-4 border-gray-200 border-l-gray-500 p-2 dark:border-gray-600 dark:border-l-gray-400">
<button <button
onClick={() => setShowReasoning(!showReasoning)} onClick={() => setShowReasoning(!showReasoning)}
className="flex cursor-pointer items-center gap-1 text-xs font-normal text-blue-600 hover:text-blue-700 dark:text-blue-400 dark:hover:text-blue-300" className="flex cursor-pointer items-center gap-1.5 text-xs font-medium text-gray-700 transition-colors duration-200 hover:text-gray-800 dark:text-gray-300 dark:hover:text-gray-200"
> >
<MessageSquare className="h-3 w-3" /> <ChevronDown
{showReasoning ? "Hide reasoning" : "Show reasoning"} className={cn(
"h-3 w-3 transition-transform duration-200",
showReasoning && "rotate-180",
)}
/>
<span>{showReasoning ? "Hide reasoning" : "Show reasoning"}</span>
</button> </button>
{showReasoning && ( <AnimatePresence>
<BasicMarkdownText className="mt-1 text-xs font-normal text-blue-700 dark:text-blue-300"> {showReasoning && (
{reasoningText || "No reasoning provided."} <motion.div
</BasicMarkdownText> initial={{ height: 0, opacity: 0 }}
)} animate={{ height: "auto", opacity: 1 }}
exit={{ height: 0, opacity: 0 }}
transition={{ duration: 0.2, ease: "easeInOut" }}
className="overflow-hidden"
>
<div className="mt-3 rounded-md bg-gray-50/50 px-3 py-2 dark:bg-gray-800/30">
<BasicMarkdownText className="text-xs leading-relaxed text-gray-700 dark:text-gray-300">
{reasoningText || "No reasoning provided."}
</BasicMarkdownText>
</div>
</motion.div>
)}
</AnimatePresence>
</div> </div>
<div className="p-2"> <div className="p-2">
@ -751,19 +781,36 @@ export function ActionStep(props: ActionStepProps) {
</div> </div>
{summaryText && anyActionDone && ( {summaryText && anyActionDone && (
<div className="border-t border-green-300 bg-green-100/50 p-2 dark:border-green-800 dark:bg-green-900/50"> <div className="bg-muted dark:bg-muted border-t border-gray-300 p-2 dark:border-gray-600">
<button <button
onClick={() => setShowSummary(!showSummary)} onClick={() => setShowSummary(!showSummary)}
className="flex cursor-pointer items-center gap-1 text-xs font-normal text-green-600 hover:text-green-700 dark:text-green-400 dark:hover:text-green-300" className="flex cursor-pointer items-center gap-1.5 text-xs font-medium text-gray-600 transition-colors duration-200 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-300"
> >
<FileText className="h-3 w-3" /> <ChevronDown
{showSummary ? "Hide summary" : "Show summary"} className={cn(
"h-3 w-3 transition-transform duration-200",
showSummary && "rotate-180",
)}
/>
<span>{showSummary ? "Hide summary" : "Show summary"}</span>
</button> </button>
{showSummary && ( <AnimatePresence>
<span className="text-green-700 dark:text-green-300"> {showSummary && (
<BasicMarkdownText>{summaryText}</BasicMarkdownText> <motion.div
</span> initial={{ height: 0, opacity: 0 }}
)} animate={{ height: "auto", opacity: 1 }}
exit={{ height: 0, opacity: 0 }}
transition={{ duration: 0.2, ease: "easeInOut" }}
className="overflow-hidden"
>
<div className="mt-3 rounded-md bg-gray-50/50 px-3 py-2 dark:bg-gray-800/30">
<BasicMarkdownText className="text-xs leading-relaxed text-gray-700 dark:text-gray-300">
{summaryText}
</BasicMarkdownText>
</div>
</motion.div>
)}
</AnimatePresence>
</div> </div>
)} )}
</div> </div>

View file

@ -1,8 +1,8 @@
"use client"; "use client";
import { Sparkles } from "lucide-react"; import { FileSearch } from "lucide-react";
import { Badge } from "../ui/badge"; import { Badge } from "../ui/badge";
import { Check } from "lucide-react"; import { Clock } from "lucide-react";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
type CodeReviewStartedProps = { type CodeReviewStartedProps = {
@ -13,36 +13,34 @@ export function CodeReviewStarted({ status = "done" }: CodeReviewStartedProps) {
return ( return (
<div <div
className={cn( className={cn(
"group via-background to-background dark:via-background dark:to-background rounded-xl border bg-gradient-to-br from-blue-50/50 transition-shadow dark:from-blue-950/20", "dark:border-muted-foreground/20 dark:bg-muted/30 rounded-lg border border-blue-200/60 bg-blue-50/30 shadow-sm transition-shadow",
"shadow-sm hover:shadow-md", "shadow-sm hover:shadow-md",
)} )}
> >
{/* Header */} {/* Header */}
<div <div
className={cn( className={cn(
"relative flex items-center bg-gradient-to-r from-blue-50 to-blue-50/50 p-4 backdrop-blur-sm dark:from-blue-950/30 dark:to-blue-950/10", "dark:bg-muted/40 relative flex items-center bg-blue-50/50 p-3",
"rounded-xl", "rounded-lg",
)} )}
> >
<div className="flex h-8 w-8 items-center justify-center rounded-full bg-blue-500 shadow-md dark:bg-blue-600"> <div className="flex h-7 w-7 items-center justify-center rounded-full bg-blue-500/90 dark:bg-blue-600">
<Sparkles className="h-4 w-4 text-white" /> <FileSearch className="h-3.5 w-3.5 text-white" />
</div> </div>
<div className="ml-3 flex-1"> <div className="ml-3 flex-1">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<h3 className="text-foreground text-sm font-semibold"> <h3 className="text-foreground text-sm font-medium">Code review</h3>
Code review started
</h3>
<Badge <Badge
variant="secondary" variant="secondary"
className="border-blue-200 bg-blue-100 text-blue-700 dark:border-blue-800 dark:bg-blue-950 dark:text-blue-300" className="border-blue-200/60 bg-blue-100/80 text-blue-700 dark:border-blue-700/40 dark:bg-blue-900/50 dark:text-blue-300"
> >
<Check className="h-3 w-3" /> <Clock className="h-3 w-3" />
In Progress In progress
</Badge> </Badge>
</div> </div>
<p className="text-muted-foreground mt-1 text-xs"> <p className="text-muted-foreground/80 mt-1 text-xs">
Analyzing code for best practices and potential improvements Analyzing code quality
</p> </p>
</div> </div>
</div> </div>

View file

@ -1,5 +1,6 @@
import { FileText, ChevronDown, Check } from "lucide-react"; import { FileText, ChevronDown, Check } from "lucide-react";
import { useState } from "react"; import { useState } from "react";
import { AnimatePresence, motion } from "framer-motion";
import { MarkdownText } from "../thread/markdown-text"; import { MarkdownText } from "../thread/markdown-text";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
import { Button } from "../ui/button"; import { Button } from "../ui/button";
@ -15,18 +16,18 @@ export function ConversationHistorySummary({ summary }: { summary: string }) {
return ( return (
<div <div
className={cn( className={cn(
"group via-background to-background dark:via-background dark:to-background rounded-xl border bg-gradient-to-br from-blue-50/50 transition-shadow dark:from-blue-950/20", "group via-background to-background dark:via-background dark:to-background rounded-xl border bg-gradient-to-br from-gray-50/50 transition-shadow dark:from-gray-900/20",
"shadow-sm hover:shadow-md", "shadow-sm hover:shadow-md",
)} )}
> >
{/* Header */} {/* Header */}
<div <div
className={cn( className={cn(
"relative flex items-center bg-gradient-to-r from-blue-50 to-blue-50/50 p-4 backdrop-blur-sm dark:from-blue-950/30 dark:to-blue-950/10", "relative flex items-center bg-gradient-to-r from-gray-50 to-gray-50/50 p-4 backdrop-blur-sm dark:from-gray-900/20 dark:to-gray-900/10",
"rounded-xl", "rounded-xl",
)} )}
> >
<div className="flex h-8 w-8 items-center justify-center rounded-full bg-blue-500 shadow-md dark:bg-blue-600"> <div className="flex h-8 w-8 items-center justify-center rounded-full bg-gray-500 shadow-md dark:bg-gray-600">
<FileText className="h-4 w-4 text-white" /> <FileText className="h-4 w-4 text-white" />
</div> </div>
@ -37,7 +38,7 @@ export function ConversationHistorySummary({ summary }: { summary: string }) {
</h3> </h3>
<Badge <Badge
variant="secondary" variant="secondary"
className="border-blue-200 bg-blue-100 text-blue-700 dark:border-blue-800 dark:bg-blue-950 dark:text-blue-300" className="border-gray-200 bg-gray-100 text-gray-700 dark:border-gray-700 dark:bg-gray-800/50 dark:text-gray-300"
> >
<Check className="h-3 w-3" /> <Check className="h-3 w-3" />
Complete Complete
@ -64,13 +65,23 @@ export function ConversationHistorySummary({ summary }: { summary: string }) {
</div> </div>
{/* Content */} {/* Content */}
{expanded && ( <AnimatePresence>
<div className="p-4"> {expanded && (
<div className="text-sm"> <motion.div
<MarkdownText>{summary}</MarkdownText> initial={{ height: 0, opacity: 0 }}
</div> animate={{ height: "auto", opacity: 1 }}
</div> exit={{ height: 0, opacity: 0 }}
)} transition={{ duration: 0.2, ease: "easeInOut" }}
className="overflow-hidden"
>
<div className="p-4">
<div className="text-sm">
<MarkdownText>{summary}</MarkdownText>
</div>
</div>
</motion.div>
)}
</AnimatePresence>
</div> </div>
); );
} }

View file

@ -1,6 +1,7 @@
"use client"; "use client";
import { useState } from "react"; import { useState } from "react";
import { AnimatePresence, motion } from "framer-motion";
import { import {
GitPullRequest, GitPullRequest,
Loader2, Loader2,
@ -43,7 +44,7 @@ export function PullRequestOpened({
return ( return (
<Badge <Badge
variant="secondary" variant="secondary"
className="border-purple-200 bg-purple-100 text-purple-700 dark:border-purple-800 dark:bg-purple-950 dark:text-purple-300" className="border-gray-200 bg-gray-100 text-gray-700 dark:border-gray-700 dark:bg-gray-800/50 dark:text-gray-300"
> >
<Clock className="h-3 w-3" /> <Clock className="h-3 w-3" />
Preparing Preparing
@ -53,7 +54,7 @@ export function PullRequestOpened({
return ( return (
<Badge <Badge
variant="secondary" variant="secondary"
className="border-purple-200 bg-purple-100 text-purple-700 dark:border-purple-800 dark:bg-purple-950 dark:text-purple-300" className="border-gray-200 bg-gray-100 text-gray-700 dark:border-gray-700 dark:bg-gray-800/50 dark:text-gray-300"
> >
<Loader2 className="h-3 w-3 animate-spin" /> <Loader2 className="h-3 w-3 animate-spin" />
Opening Opening
@ -64,7 +65,7 @@ export function PullRequestOpened({
return ( return (
<Badge <Badge
variant="secondary" variant="secondary"
className="border-slate-200 bg-slate-100 text-slate-700 dark:border-slate-700 dark:bg-slate-800 dark:text-slate-300" className="border-slate-200 bg-slate-100 text-slate-700 dark:border-slate-700 dark:bg-slate-800/50 dark:text-slate-300"
> >
<GitPullRequestDraft className="h-3 w-3" /> <GitPullRequestDraft className="h-3 w-3" />
Draft Draft
@ -74,7 +75,7 @@ export function PullRequestOpened({
return ( return (
<Badge <Badge
variant="secondary" variant="secondary"
className="border-emerald-200 bg-emerald-100 text-emerald-700 dark:border-emerald-800 dark:bg-emerald-950 dark:text-emerald-300" className="border-emerald-200 bg-emerald-100 text-emerald-700 dark:border-emerald-700 dark:bg-emerald-800/50 dark:text-emerald-300"
> >
<Check className="h-3 w-3" /> <Check className="h-3 w-3" />
Opened Opened
@ -110,18 +111,18 @@ export function PullRequestOpened({
return ( return (
<div <div
className={cn( className={cn(
"group via-background to-background dark:via-background dark:to-background rounded-xl border bg-gradient-to-br from-purple-50/50 transition-shadow dark:from-purple-950/20", "group via-background to-background dark:via-background dark:to-background rounded-xl border bg-gradient-to-br from-gray-50/50 transition-shadow dark:from-gray-900/20",
"shadow-sm hover:shadow-md", "shadow-sm hover:shadow-md",
)} )}
> >
{/* Header */} {/* Header */}
<div <div
className={cn( className={cn(
"relative flex items-center bg-gradient-to-r from-purple-50 to-purple-50/50 p-4 backdrop-blur-sm dark:from-purple-950/30 dark:to-purple-950/10", "relative flex items-center bg-gradient-to-r from-gray-50 to-gray-50/50 p-4 backdrop-blur-sm dark:from-gray-900/20 dark:to-gray-900/10",
"rounded-xl", "rounded-xl",
)} )}
> >
<div className="flex h-8 w-8 items-center justify-center rounded-full bg-purple-500 shadow-md dark:bg-purple-600"> <div className="flex h-8 w-8 items-center justify-center rounded-full bg-gray-500 shadow-md dark:bg-gray-600">
{isDraft ? ( {isDraft ? (
<GitPullRequestDraft className="h-4 w-4 text-white" /> <GitPullRequestDraft className="h-4 w-4 text-white" />
) : ( ) : (
@ -178,18 +179,28 @@ export function PullRequestOpened({
</div> </div>
{/* Content */} {/* Content */}
{expanded && description && status === "done" && ( <AnimatePresence>
<div className="border-t p-4"> {expanded && description && status === "done" && (
<div className="bg-muted/30 rounded-lg p-3"> <motion.div
<h4 className="text-muted-foreground mb-2 text-xs font-medium"> initial={{ height: 0, opacity: 0 }}
Description animate={{ height: "auto", opacity: 1 }}
</h4> exit={{ height: 0, opacity: 0 }}
<pre className="text-foreground text-sm whitespace-pre-wrap"> transition={{ duration: 0.2, ease: "easeInOut" }}
{description} className="overflow-hidden"
</pre> >
</div> <div className="border-t p-4">
</div> <div className="rounded-lg bg-gray-50/50 p-3 dark:bg-gray-800/30">
)} <h4 className="text-muted-foreground mb-2 text-xs font-medium">
Description
</h4>
<pre className="text-foreground text-sm whitespace-pre-wrap">
{description}
</pre>
</div>
</div>
</motion.div>
)}
</AnimatePresence>
</div> </div>
); );
} }

View file

@ -72,16 +72,16 @@ export function PushChanges({
return ( return (
<div className="overflow-hidden rounded-md border border-gray-200"> <div className="overflow-hidden rounded-md border border-gray-200">
{reasoningText && ( {reasoningText && (
<div className="border-b border-blue-100 bg-blue-50 p-2"> <div className="border-b border-blue-200 bg-blue-50 p-2 dark:border-blue-800/50 dark:bg-blue-950/20">
<button <button
onClick={() => setShowReasoning(!showReasoning)} onClick={() => setShowReasoning(!showReasoning)}
className="flex items-center gap-1 text-xs font-normal text-blue-700 hover:text-blue-800" className="flex items-center gap-1 text-xs font-normal text-blue-700 hover:text-blue-800 dark:text-blue-300 dark:hover:text-blue-200"
> >
<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 && (
<BasicMarkdownText className="mt-1 text-xs font-normal text-blue-800"> <BasicMarkdownText className="mt-1 text-xs font-normal text-blue-800 dark:text-blue-200">
{reasoningText} {reasoningText}
</BasicMarkdownText> </BasicMarkdownText>
)} )}

View file

@ -34,7 +34,9 @@ export function ReplanningStep({
case "generating": case "generating":
return <Loader2 className="h-3.5 w-3.5 animate-spin text-gray-500" />; return <Loader2 className="h-3.5 w-3.5 animate-spin text-gray-500" />;
case "done": case "done":
return <CheckCircle className="h-3.5 w-3.5 text-green-500" />; return (
<CheckCircle className="h-3.5 w-3.5 text-green-600 dark:text-green-400" />
);
} }
}; };
@ -52,16 +54,16 @@ export function ReplanningStep({
return ( return (
<div className="overflow-hidden rounded-md border border-gray-200"> <div className="overflow-hidden rounded-md border border-gray-200">
{reasoningText && ( {reasoningText && (
<div className="border-b border-blue-100 bg-blue-50 p-2"> <div className="border-b border-blue-200 bg-blue-50 p-2 dark:border-blue-800/50 dark:bg-blue-950/20">
<button <button
onClick={() => setShowReasoning(!showReasoning)} onClick={() => setShowReasoning(!showReasoning)}
className="flex items-center gap-1 text-xs font-normal text-blue-700 hover:text-blue-800" className="flex items-center gap-1 text-xs font-normal text-blue-700 hover:text-blue-800 dark:text-blue-300 dark:hover:text-blue-200"
> >
<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 && (
<BasicMarkdownText className="mt-1 text-xs font-normal text-blue-800"> <BasicMarkdownText className="mt-1 text-xs font-normal text-blue-800 dark:text-blue-200">
{reasoningText} {reasoningText}
</BasicMarkdownText> </BasicMarkdownText>
)} )}

View file

@ -42,7 +42,7 @@ export function MarkTaskCompleted({
); );
case "done": case "done":
return ( return (
<CheckCircle className="h-3.5 w-3.5 text-green-500 dark:text-green-400" /> <CheckCircle className="h-3.5 w-3.5 text-green-600 dark:text-green-400" />
); );
} }
}; };
@ -61,16 +61,16 @@ export function MarkTaskCompleted({
return ( return (
<div className="overflow-hidden rounded-md border border-gray-200 dark:border-gray-800"> <div className="overflow-hidden rounded-md border border-gray-200 dark:border-gray-800">
{reasoningText && ( {reasoningText && (
<div className="border-b border-blue-100 bg-blue-50 p-2 dark:border-blue-800 dark:bg-blue-900/50"> <div className="border-b border-blue-200 bg-blue-50 p-2 dark:border-blue-800/50 dark:bg-blue-950/20">
<button <button
onClick={() => setShowReasoning(!showReasoning)} onClick={() => setShowReasoning(!showReasoning)}
className="flex items-center gap-1 text-xs font-normal text-blue-700 hover:text-blue-800 dark:text-blue-400 dark:hover:text-blue-300" className="flex items-center gap-1 text-xs font-normal text-blue-700 hover:text-blue-800 dark:text-blue-300 dark:hover:text-blue-200"
> >
<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 && (
<BasicMarkdownText className="mt-1 text-xs font-normal text-blue-800 dark:text-blue-400"> <BasicMarkdownText className="mt-1 text-xs font-normal text-blue-800 dark:text-blue-200">
{reasoningText} {reasoningText}
</BasicMarkdownText> </BasicMarkdownText>
)} )}
@ -185,10 +185,10 @@ export function MarkTaskIncomplete({
return ( return (
<div className="overflow-hidden rounded-md border border-gray-200 dark:border-gray-800"> <div className="overflow-hidden rounded-md border border-gray-200 dark:border-gray-800">
{reasoningText && ( {reasoningText && (
<div className="border-b border-blue-100 bg-blue-50 p-2 dark:border-blue-800 dark:bg-blue-900/50"> <div className="border-b border-blue-200 bg-blue-50 p-2 dark:border-blue-800/50 dark:bg-blue-950/20">
<button <button
onClick={() => setShowReasoning(!showReasoning)} onClick={() => setShowReasoning(!showReasoning)}
className="flex items-center gap-1 text-xs font-normal text-blue-700 hover:text-blue-800 dark:text-blue-400 dark:hover:text-blue-300" className="flex items-center gap-1 text-xs font-normal text-blue-700 hover:text-blue-800 dark:text-blue-300 dark:hover:text-blue-200"
> >
<MessageSquare className="h-3 w-3" /> <MessageSquare className="h-3 w-3" />
{showReasoning ? "Hide reasoning" : "Show reasoning"} {showReasoning ? "Hide reasoning" : "Show reasoning"}

View file

@ -1,7 +1,8 @@
"use client"; "use client";
import { useState } from "react"; import { useState } from "react";
import { CheckCircle, Loader2, FileText, MinusCircle } from "lucide-react"; import { AnimatePresence, motion } from "framer-motion";
import { CheckCircle, Loader2, ChevronDown, MinusCircle } from "lucide-react";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
import { BasicMarkdownText } from "../thread/markdown-text"; import { BasicMarkdownText } from "../thread/markdown-text";
@ -29,9 +30,13 @@ export function TaskSummary({
); );
case "done": case "done":
if (completed === false) { if (completed === false) {
return <MinusCircle className="size-3.5 text-amber-500" />; return (
<MinusCircle className="size-3.5 text-amber-500 dark:text-amber-400" />
);
} }
return <CheckCircle className="size-3.5 text-green-500" />; return (
<CheckCircle className="size-3.5 text-green-600 dark:text-green-400" />
);
} }
}; };
@ -52,7 +57,7 @@ export function TaskSummary({
return ( return (
<div className="border-border overflow-hidden rounded-md border"> <div className="border-border overflow-hidden rounded-md border">
<div <div
className={"flex items-center border-b bg-gray-50 p-2 dark:bg-gray-800"} className={"bg-muted/50 flex items-center border-b p-2"}
onClick={ onClick={
status === "done" && summaryText status === "done" && summaryText
? () => setExpanded(!expanded) ? () => setExpanded(!expanded)
@ -68,36 +73,60 @@ export function TaskSummary({
{summaryText && ( {summaryText && (
<div <div
className={cn( className={cn(
"border-t p-2", "border-t border-l-4 p-2",
completed === false completed === false
? "border-amber-300 bg-amber-100/50 dark:border-amber-800 dark:bg-amber-900/50" ? "border-amber-300 border-l-amber-500 bg-amber-100/50 dark:border-amber-700/50 dark:border-l-amber-400/70 dark:bg-amber-900/30"
: "border-green-300 bg-green-100/50 dark:border-green-800 dark:bg-green-900/50", : "border-green-200/80 border-l-green-500/80 bg-green-50/60 dark:border-green-800/30 dark:border-l-green-400/70 dark:bg-green-950/10",
)} )}
> >
<button <button
onClick={() => setShowSummary(!showSummary)} onClick={() => setShowSummary(!showSummary)}
className={cn( className={cn(
"flex cursor-pointer items-center gap-1 text-xs font-normal", "flex cursor-pointer items-center gap-1.5 text-xs font-medium transition-colors duration-200",
completed === false completed === false
? "text-amber-600 hover:text-amber-700 dark:text-amber-400 dark:hover:text-amber-300" ? "text-amber-600 hover:text-amber-700 dark:text-amber-400/90 dark:hover:text-amber-300"
: "text-green-600 hover:text-green-700 dark:text-green-400 dark:hover:text-green-300", : "text-green-600 hover:text-green-700 dark:text-green-400 dark:hover:text-green-300",
)} )}
> >
<FileText className="h-3 w-3" /> <ChevronDown
{showSummary ? "Hide reflection" : "Show reflection"}
</button>
{showSummary && (
<BasicMarkdownText
className={cn( className={cn(
"mt-1 text-xs", "h-3 w-3 transition-transform duration-200",
completed === false showSummary && "rotate-180",
? "text-amber-700 dark:text-amber-300"
: "text-green-700 dark:text-green-300",
)} )}
> />
{summaryText} <span>{showSummary ? "Hide reflection" : "Show reflection"}</span>
</BasicMarkdownText> </button>
)} <AnimatePresence>
{showSummary && (
<motion.div
initial={{ height: 0, opacity: 0 }}
animate={{ height: "auto", opacity: 1 }}
exit={{ height: 0, opacity: 0 }}
transition={{ duration: 0.2, ease: "easeInOut" }}
className="overflow-hidden"
>
<div
className={cn(
"mt-3 rounded-md px-3 py-2",
completed === false
? "bg-amber-50/50 dark:bg-amber-900/20"
: "bg-green-50/50 dark:bg-green-900/20",
)}
>
<BasicMarkdownText
className={cn(
"text-xs leading-relaxed",
completed === false
? "text-amber-700 dark:text-amber-300/90"
: "text-green-700 dark:text-green-300",
)}
>
{summaryText}
</BasicMarkdownText>
</div>
</motion.div>
)}
</AnimatePresence>
</div> </div>
)} )}
</div> </div>

View file

@ -72,7 +72,7 @@ function AuthStatusContent() {
if (!showGetStarted && !showInstallApp && !showLoading) { if (!showGetStarted && !showInstallApp && !showLoading) {
router.push("/chat"); router.push("/chat");
} }
}, [showGetStarted, showInstallApp, showLoading]); }, [showGetStarted, showInstallApp, showLoading, router]);
if (showGetStarted) { if (showGetStarted) {
return ( return (
@ -172,6 +172,7 @@ function AuthStatusContent() {
</div> </div>
); );
} }
return null;
} }
export default function AuthStatus() { export default function AuthStatus() {

View file

@ -6,9 +6,9 @@ export function RepositoryBranchSelectors() {
const [threadId] = useQueryState("threadId"); const [threadId] = useQueryState("threadId");
const chatStarted = !!threadId; const chatStarted = !!threadId;
const defaultButtonStyles = const defaultButtonStyles =
"bg-inherit border-none text-muted-foreground hover:text-muted-foreground/70 text-xs p-0 px-0 py-0 !p-0 !px-0 !py-0 h-fit hover:bg-inherit shadow-none"; "bg-inherit border-none text-foreground hover:text-foreground/80 text-xs p-0 px-0 py-0 !p-0 !px-0 !py-0 h-fit hover:bg-inherit shadow-none";
const defaultStylesChatStarted = const defaultStylesChatStarted =
"hover:bg-inherit cursor-default hover:cursor-default text-muted-foreground hover:border-gray-300 hover:ring-inherit shadow-none p-0 px-0 py-0 !p-0 !px-0 !py-0"; "hover:bg-inherit cursor-default hover:cursor-default text-foreground hover:border-gray-300 hover:ring-inherit shadow-none p-0 px-0 py-0 !p-0 !px-0 !py-0";
return ( return (
<div className="flex items-center gap-1 rounded-md border border-gray-200 p-1 dark:border-gray-700"> <div className="flex items-center gap-1 rounded-md border border-gray-200 p-1 dark:border-gray-700">

File diff suppressed because one or more lines are too long

View file

@ -5,6 +5,7 @@ import { TooltipIconButton } from "../ui/tooltip-icon-button";
import { Dispatch, SetStateAction, useState } from "react"; import { Dispatch, SetStateAction, useState } from "react";
import { Textarea } from "../ui/textarea"; import { Textarea } from "../ui/textarea";
import { Button } from "../ui/button"; import { Button } from "../ui/button";
import { MarkdownText } from "../thread/markdown-text";
interface PlanViewerProps { interface PlanViewerProps {
planItems: PlanItem[]; planItems: PlanItem[];
@ -166,7 +167,7 @@ export function PlanViewer({
className="text-foreground" className="text-foreground"
/> />
) : ( ) : (
<p <MarkdownText
className={cn("text-sm leading-relaxed", { className={cn("text-sm leading-relaxed", {
"text-foreground": status === "current", "text-foreground": status === "current",
"text-foreground/80": "text-foreground/80":
@ -175,7 +176,7 @@ export function PlanViewer({
})} })}
> >
{planItems[item.index].plan} {planItems[item.index].plan}
</p> </MarkdownText>
)} )}
{/* Summary for completed tasks */} {/* Summary for completed tasks */}
@ -184,9 +185,9 @@ export function PlanViewer({
<p className="mb-1 text-xs font-medium text-green-700 dark: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-600 dark:text-green-400"> <MarkdownText className="text-xs text-green-600 dark:text-green-400">
{item.summary} {item.summary}
</p> </MarkdownText>
</div> </div>
)} )}
</div> </div>

View file

@ -1,8 +1,8 @@
import { PlanViewer } from "./plan-viewer"; import { PlanViewer } from "./plan-viewer";
import { Button } from "../ui/button";
import { useProposedPlan } from "../thread/agent-inbox/hooks/useProposedPlan"; import { useProposedPlan } from "../thread/agent-inbox/hooks/useProposedPlan";
import { PlanItem } from "@open-swe/shared/open-swe/types"; import { PlanItem } from "@open-swe/shared/open-swe/types";
import { useStream } from "@langchain/langgraph-sdk/react"; import { useStream } from "@langchain/langgraph-sdk/react";
import { X, ArrowDown } from "lucide-react";
export function ProposedPlan({ export function ProposedPlan({
originalPlanItems, originalPlanItems,
@ -28,21 +28,32 @@ export function ProposedPlan({
setPlanItems={setPlanItems} setPlanItems={setPlanItems}
isProposedPlan={true} isProposedPlan={true}
/> />
<div className="flex w-full flex-row items-center justify-center gap-2"> <div className="py-8">
<Button <div className="flex items-center justify-center gap-8">
onClick={handleResumePlan} <button
className="w-full" onClick={handleRejectPlan}
variant="brand" className="group flex cursor-pointer flex-col items-center gap-2 transition-all"
> >
{changesMade ? "Submit" : "Approve"} <div className="rounded-full border-2 border-dashed border-red-500 p-3 transition-all group-hover:border-solid group-hover:bg-red-50 dark:group-hover:bg-red-950/50">
</Button> <X className="h-5 w-5 text-red-500" />
<Button </div>
onClick={handleRejectPlan} <span className="text-muted-foreground text-xs group-hover:text-red-600 dark:group-hover:text-red-400">
className="w-full" Reject
variant="destructive" </span>
> </button>
Reject
</Button> <button
onClick={handleResumePlan}
className="group flex cursor-pointer flex-col items-center gap-2 transition-all"
>
<div className="rounded-full border-2 border-dashed border-green-500 p-3 transition-all group-hover:border-solid group-hover:bg-green-50 dark:group-hover:bg-green-950/50">
<ArrowDown className="h-5 w-5 text-green-500" />
</div>
<span className="text-muted-foreground text-xs group-hover:text-green-600 dark:group-hover:text-green-400">
{changesMade ? "Submit changes" : "Continue"}
</span>
</button>
</div>
</div> </div>
</div> </div>
); );

View file

@ -102,10 +102,12 @@ export function TasksSidebar({
switch (state) { switch (state) {
case "completed": case "completed":
return ( return (
<CheckCircle2 className="h-4 w-4 text-green-600 dark:text-green-400" /> <CheckCircle2 className="h-4 w-4 text-green-500 dark:text-green-400/80" />
); );
case "current": case "current":
return <Play className="h-4 w-4 text-blue-500 dark:text-blue-400" />; return (
<Play className="h-4 w-4 text-orange-600 dark:text-orange-400" />
);
default: default:
return <Circle className="h-4 w-4 text-gray-400 dark:text-gray-500" />; return <Circle className="h-4 w-4 text-gray-400 dark:text-gray-500" />;
} }
@ -140,7 +142,7 @@ export function TasksSidebar({
return ( return (
<div <div
className={cn( className={cn(
"fixed top-0 right-0 z-10 h-screen border-l border-gray-200 bg-white shadow-lg transition-all duration-300 dark:border-gray-700 dark:bg-gray-900", "dark:bg-background fixed top-0 right-0 z-10 h-screen bg-white shadow-lg transition-all duration-300",
isOpen ? "w-80 md:w-xl" : "w-0 overflow-hidden border-l-0", isOpen ? "w-80 md:w-xl" : "w-0 overflow-hidden border-l-0",
)} )}
> >
@ -161,7 +163,7 @@ export function TasksSidebar({
variant="ghost" variant="ghost"
size="sm" size="sm"
onClick={onClose} onClick={onClose}
className="h-8 w-8 p-0 hover:bg-gray-100 dark:hover:bg-gray-800" className="h-8 w-8 p-0 hover:bg-gray-100 dark:hover:bg-gray-700/50"
> >
<PanelLeftClose className="h-4 w-4" /> <PanelLeftClose className="h-4 w-4" />
</Button> </Button>
@ -174,7 +176,7 @@ export function TasksSidebar({
Task Task
</label> </label>
<select <select
className="w-full rounded border border-gray-200 px-2 py-1 text-sm dark:border-gray-600 dark:bg-gray-800 dark:text-gray-200" className="bg-muted dark:bg-muted w-full rounded border border-gray-200 px-2 py-1 text-sm dark:border-gray-600 dark:text-gray-200"
value={currentTask.id} value={currentTask.id}
onChange={(e) => { onChange={(e) => {
const newTaskIndex = taskPlan.tasks.findIndex( const newTaskIndex = taskPlan.tasks.findIndex(
@ -205,12 +207,12 @@ export function TasksSidebar({
Plan Revision Plan Revision
</label> </label>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<div className="flex flex-1 items-center gap-1 rounded border border-gray-200 bg-gray-50 px-2 py-1 dark:border-gray-600 dark:bg-gray-800"> <div className="bg-muted dark:bg-muted flex flex-1 items-center gap-1 rounded border border-gray-200 px-2 py-1 dark:border-gray-600">
<div className="flex items-center gap-1"> <div className="flex items-center gap-1">
{currentRevision?.createdBy === "agent" ? ( {currentRevision?.createdBy === "agent" ? (
<Bot className="h-3 w-3 text-blue-500" /> <Bot className="h-3 w-3 text-gray-600 dark:text-gray-400" />
) : ( ) : (
<User className="h-3 w-3 text-green-500" /> <User className="h-3 w-3 text-green-500 dark:text-green-400/80" />
)} )}
<Clock className="h-3 w-3 text-gray-500 dark:text-gray-400" /> <Clock className="h-3 w-3 text-gray-500 dark:text-gray-400" />
</div> </div>
@ -278,7 +280,7 @@ export function TasksSidebar({
{/* Filter controls */} {/* Filter controls */}
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<div className="flex items-center gap-1 rounded border border-gray-200 bg-gray-50 px-2 py-1 dark:border-gray-600 dark:bg-gray-800"> <div className="bg-muted dark:bg-muted flex items-center gap-1 rounded border border-gray-200 px-2 py-1 dark:border-gray-600">
<Filter className="h-3 w-3 text-gray-500 dark:text-gray-400" /> <Filter className="h-3 w-3 text-gray-500 dark:text-gray-400" />
<select <select
className="border-none bg-transparent text-xs outline-none dark:text-gray-300" className="border-none bg-transparent text-xs outline-none dark:text-gray-300"
@ -312,11 +314,11 @@ export function TasksSidebar({
className={cn( className={cn(
"rounded-lg border p-3", "rounded-lg border p-3",
state === "current" && state === "current" &&
"border-blue-200 bg-blue-50 dark:border-blue-700 dark:bg-blue-900/20", "border-orange-200 bg-orange-50 dark:border-orange-800/50 dark:bg-orange-950/20",
state === "completed" && state === "completed" &&
"border-green-200 bg-green-50 dark:border-green-700 dark:bg-green-900/20", "border-green-200/80 bg-green-50/60 dark:border-green-800/30 dark:bg-green-950/15",
state === "remaining" && state === "remaining" &&
"border-gray-200 bg-white dark:border-gray-700 dark:bg-gray-800", "bg-muted dark:bg-muted border-gray-200 dark:border-gray-700",
)} )}
> >
<div className="flex items-start gap-3"> <div className="flex items-start gap-3">
@ -340,11 +342,11 @@ export function TasksSidebar({
className={cn( className={cn(
"rounded-full px-2 py-1 text-xs", "rounded-full px-2 py-1 text-xs",
state === "completed" && state === "completed" &&
"bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-400", "bg-green-100/70 text-green-600 dark:bg-green-900/20 dark:text-green-300/90",
state === "current" && state === "current" &&
"bg-blue-100 text-blue-700 dark:bg-blue-900/30 dark:text-blue-400", "bg-orange-100 text-orange-700 dark:bg-orange-900/30 dark:text-orange-300",
state === "remaining" && state === "remaining" &&
"bg-gray-100 text-gray-700 dark:bg-gray-700 dark:text-gray-300", "bg-muted dark:bg-muted text-gray-700 dark:text-gray-300",
)} )}
> >
{state === "completed" {state === "completed"
@ -373,7 +375,7 @@ export function TasksSidebar({
<Button <Button
variant="ghost" variant="ghost"
size="sm" size="sm"
className="h-6 p-0 text-xs text-green-700 dark:text-green-400" className="h-6 p-0 text-xs text-green-600 dark:text-green-300/90"
> >
{isExpanded ? ( {isExpanded ? (
<ChevronDown className="mr-1 h-3 w-3" /> <ChevronDown className="mr-1 h-3 w-3" />
@ -384,7 +386,7 @@ export function TasksSidebar({
</Button> </Button>
</CollapsibleTrigger> </CollapsibleTrigger>
<CollapsibleContent className="mt-2"> <CollapsibleContent className="mt-2">
<div className="rounded border border-green-200 bg-green-50 p-2 text-xs text-green-800 dark:border-green-700 dark:bg-green-900/30 dark:text-green-400"> <div className="rounded border border-green-200/80 bg-green-50/60 p-2 text-xs text-green-700 dark:border-green-800/30 dark:bg-green-950/15 dark:text-green-300/90">
<BasicMarkdownText> <BasicMarkdownText>
{item.summary} {item.summary}
</BasicMarkdownText> </BasicMarkdownText>
@ -412,7 +414,7 @@ export function TaskPlanView({ taskPlan, onTaskChange }: TaskPlanViewProps) {
if (taskPlan.tasks.length === 0) { if (taskPlan.tasks.length === 0) {
return ( return (
<div className="w-full rounded border border-gray-200 bg-gray-50 p-2 dark:border-gray-700 dark:bg-gray-800"> <div className="bg-muted dark:bg-muted w-full rounded border border-gray-200 p-2 dark:border-gray-700">
<div className="text-center text-xs text-gray-500 dark:text-gray-400"> <div className="text-center text-xs text-gray-500 dark:text-gray-400">
No tasks available No tasks available
</div> </div>

View file

@ -20,7 +20,7 @@ import {
HoverCardContent, HoverCardContent,
HoverCardTrigger, HoverCardTrigger,
} from "@/components/ui/hover-card"; } from "@/components/ui/hover-card";
import { InlineMarkdownText } from "../thread/markdown-text"; import { InlineMarkdownText } from "@/components/thread/markdown-text";
interface ProgressBarProps { interface ProgressBarProps {
taskPlan?: TaskPlan; taskPlan?: TaskPlan;
@ -39,7 +39,7 @@ export function ProgressBar({
return ( return (
<div <div
className={cn( className={cn(
"w-full rounded-lg border border-gray-200 bg-gray-50 px-3 py-2 sm:mt-4 dark:border-gray-700 dark:bg-gray-800", "w-full rounded-lg border border-gray-200 bg-gray-50 px-3 py-2 sm:mt-4 dark:border-gray-600/50 dark:bg-gray-700/30",
className, className,
)} )}
> >
@ -73,7 +73,7 @@ export function ProgressBar({
return ( return (
<div <div
className={cn( className={cn(
"w-full rounded-lg border border-gray-200 bg-gray-50 px-3 py-2 sm:mt-4 dark:border-gray-700 dark:bg-gray-800", "w-full rounded-lg border border-gray-200 bg-gray-50 px-3 py-2 sm:mt-4 dark:border-gray-600/50 dark:bg-gray-700/30",
className, className,
)} )}
> >
@ -105,7 +105,7 @@ export function ProgressBar({
const getSegmentColor = (state: string) => { const getSegmentColor = (state: string) => {
switch (state) { switch (state) {
case "completed": case "completed":
return "bg-green-400 dark:bg-green-500"; return "bg-green-500/90 dark:bg-green-400/80";
case "current": case "current":
return "bg-blue-400 dark:bg-blue-500"; return "bg-blue-400 dark:bg-blue-500";
default: default:
@ -116,7 +116,7 @@ export function ProgressBar({
return ( return (
<div <div
className={cn( className={cn(
"bg-muted/70 w-full overflow-hidden rounded-lg px-1 py-1.5 sm:px-3 dark:bg-gray-800", "dark:bg-muted w-full overflow-hidden rounded-lg bg-gray-50 px-1 py-1.5 sm:px-3",
className, className,
)} )}
> >
@ -140,11 +140,12 @@ export function ProgressBar({
</TooltipTrigger> </TooltipTrigger>
<TooltipContent <TooltipContent
side="top" side="top"
className="max-w-xs p-2 text-xs" className="bg-popover text-popover-foreground max-w-xs border p-2 text-xs before:hidden after:hidden"
sideOffset={5}
> >
<div className="space-y-1"> <div className="space-y-1">
<p className="font-medium">Plan Progress</p> <p className="text-foreground font-medium">Plan Progress</p>
<p> <p className="text-muted-foreground">
Shows the current progress of the AI agent's plan execution. Shows the current progress of the AI agent's plan execution.
</p> </p>
</div> </div>
@ -165,7 +166,7 @@ export function ProgressBar({
variant="outline" variant="outline"
size="sm" size="sm"
onClick={onOpenSidebar} onClick={onOpenSidebar}
className="h-6 border-blue-200 text-xs hover:bg-blue-50 dark:border-blue-700 dark:hover:bg-blue-900/20" className="hover:bg-muted/80 hover:border-muted-foreground/50 h-6 text-xs"
> >
<List className="size-3" /> <List className="size-3" />
<span className="hidden sm:inline">Tasks</span> <span className="hidden sm:inline">Tasks</span>
@ -176,9 +177,9 @@ export function ProgressBar({
</div> </div>
{showLegend && ( {showLegend && (
<div className="mb-2 flex flex-wrap items-center gap-2 rounded border border-gray-200 bg-gray-50 px-2 py-1 text-xs sm:gap-3 dark:border-gray-700 dark:bg-gray-800 dark:text-gray-200"> <div className="mb-2 flex flex-wrap items-center gap-2 rounded border border-gray-200 bg-gray-50 px-2 py-1 text-xs sm:gap-3 dark:border-gray-600/50 dark:bg-gray-700/30 dark:text-gray-200">
<div className="flex items-center gap-1"> <div className="flex items-center gap-1">
<div className="h-2 w-2 rounded-full bg-green-400 dark:bg-green-500"></div> <div className="h-2 w-2 rounded-full bg-green-500/90 dark:bg-green-400/80"></div>
<span>Completed</span> <span>Completed</span>
</div> </div>
<div className="flex items-center gap-1"> <div className="flex items-center gap-1">

View file

@ -22,7 +22,7 @@ export const ContentBlocksPreview: React.FC<ContentBlocksPreviewProps> = ({
}) => { }) => {
if (!blocks.length) return null; if (!blocks.length) return null;
return ( return (
<div className={cn("flex flex-wrap gap-2 pb-1", className)}> <div className={cn("flex flex-wrap gap-2 pb-3", className)}>
{blocks.map((block, idx) => ( {blocks.map((block, idx) => (
<MultimodalPreview <MultimodalPreview
key={idx} key={idx}

View file

@ -26,9 +26,14 @@ export const MultimodalPreview: React.FC<MultimodalPreviewProps> = ({
block.mime_type.startsWith("image/") block.mime_type.startsWith("image/")
) { ) {
const url = `data:${block.mime_type};base64,${block.data}`; const url = `data:${block.mime_type};base64,${block.data}`;
let imgClass: string = "rounded-md object-cover h-16 w-16 text-lg"; let imgClass: string =
if (size === "sm") imgClass = "rounded-md object-cover h-10 w-10 text-base"; "rounded-md object-cover h-16 w-16 text-lg shadow-sm ring-1 ring-border";
if (size === "lg") imgClass = "rounded-md object-cover h-24 w-24 text-xl"; if (size === "sm")
imgClass =
"rounded-md object-cover h-10 w-10 text-base shadow-sm ring-1 ring-border";
if (size === "lg")
imgClass =
"rounded-md object-cover h-24 w-24 text-xl shadow-sm ring-1 ring-border";
return ( return (
<div className={cn("relative inline-block", className)}> <div className={cn("relative inline-block", className)}>
<Image <Image
@ -41,11 +46,11 @@ export const MultimodalPreview: React.FC<MultimodalPreviewProps> = ({
{removable && ( {removable && (
<button <button
type="button" type="button"
className="absolute top-1 right-1 z-10 cursor-pointer rounded-full p-1 text-white hover:bg-gray-800" className="bg-destructive/90 text-destructive-foreground hover:bg-destructive absolute -top-2 -right-2 z-10 cursor-pointer rounded-full p-1 shadow-sm"
onClick={onRemove} onClick={onRemove}
aria-label="Remove image" aria-label="Remove image"
> >
<XIcon className="h-4 w-4" /> <XIcon className="h-3 w-3" />
</button> </button>
)} )}
</div> </div>
@ -63,20 +68,20 @@ export const MultimodalPreview: React.FC<MultimodalPreviewProps> = ({
return ( return (
<div <div
className={cn( className={cn(
"relative flex items-center justify-start gap-2 rounded-md border-[1px] border-gray-800 bg-inherit px-3 py-2", "border-border bg-card/50 relative flex items-center justify-start gap-2 rounded-md border px-3 py-2",
className, className,
)} )}
> >
<div className="flex flex-shrink-0 flex-col items-start justify-start"> <div className="flex flex-shrink-0 flex-col items-start justify-start">
<File <File
className={cn( className={cn(
"text-teal-700", "text-primary",
size === "sm" ? "h-5 w-5" : "h-7 w-7", size === "sm" ? "h-5 w-5" : "h-7 w-7",
)} )}
/> />
</div> </div>
<span <span
className={cn("min-w-0 flex-1 text-sm break-all text-gray-300")} className={cn("text-foreground min-w-0 flex-1 text-sm break-all")}
style={{ wordBreak: "break-all", whiteSpace: "pre-wrap" }} style={{ wordBreak: "break-all", whiteSpace: "pre-wrap" }}
> >
{String(filename)} {String(filename)}
@ -84,7 +89,7 @@ export const MultimodalPreview: React.FC<MultimodalPreviewProps> = ({
{removable && ( {removable && (
<button <button
type="button" type="button"
className="cursor-pointer self-start rounded-full p-1 text-teal-700 hover:bg-gray-800" className="text-muted-foreground hover:bg-accent hover:text-accent-foreground cursor-pointer self-start rounded-full p-1"
onClick={onRemove} onClick={onRemove}
aria-label="Remove PDF" aria-label="Remove PDF"
> >
@ -99,7 +104,7 @@ export const MultimodalPreview: React.FC<MultimodalPreviewProps> = ({
return ( return (
<div <div
className={cn( className={cn(
"flex items-center gap-2 rounded-md border-[1px] border-gray-800 bg-inherit px-3 py-2 text-gray-500", "border-border bg-card/50 text-muted-foreground flex items-center gap-2 rounded-md border px-3 py-2",
className, className,
)} )}
> >
@ -108,7 +113,7 @@ export const MultimodalPreview: React.FC<MultimodalPreviewProps> = ({
{removable && ( {removable && (
<button <button
type="button" type="button"
className="cursor-pointer self-start rounded-full p-1 text-gray-500 hover:bg-gray-800" className="text-muted-foreground hover:bg-accent hover:text-accent-foreground cursor-pointer self-start rounded-full p-1"
onClick={onRemove} onClick={onRemove}
aria-label="Remove file" aria-label="Remove file"
> >

View file

@ -47,7 +47,7 @@ const CodeHeader: FC<CodeHeaderProps> = ({ language, code }) => {
}; };
return ( return (
<div className="flex items-center justify-between gap-4 rounded-t-lg bg-zinc-900 px-4 py-2 text-sm font-semibold text-white"> <div className="bg-muted text-foreground flex items-center justify-between gap-4 rounded-t-lg border-b px-4 py-2 text-sm font-semibold">
<span className="lowercase [&>span]:text-xs">{language}</span> <span className="lowercase [&>span]:text-xs">{language}</span>
<TooltipIconButton <TooltipIconButton
tooltip="Copy" tooltip="Copy"
@ -196,7 +196,7 @@ const defaultComponents: any = {
pre: ({ className, ...props }: { className?: string }) => ( pre: ({ className, ...props }: { className?: string }) => (
<pre <pre
className={cn( className={cn(
"w-full overflow-x-auto rounded-lg bg-black text-white", "bg-muted text-foreground w-full overflow-x-auto rounded-lg",
className, className,
)} )}
{...props} {...props}
@ -321,12 +321,17 @@ const InlineMarkdownTextImpl: FC<{ children: string; className?: string }> = ({
if (match) { if (match) {
// If it's a code block, render as plain text to keep it inline // If it's a code block, render as plain text to keep it inline
return ( return (
<span className={cn("font-mono", className)}>{String(children)}</span> <span className={cn("font-mono text-sm", className)}>
{String(children)}
</span>
); );
} }
return ( return (
<code <code
className={cn("bg-muted rounded px-1 py-0.5 font-mono", className)} className={cn(
"bg-muted rounded px-1 py-0.5 font-mono text-sm",
className,
)}
{...props} {...props}
> >
{children} {children}

View file

@ -24,11 +24,11 @@ export function ToolCalls({
key={idx} key={idx}
className="border-border overflow-hidden rounded-lg border" className="border-border overflow-hidden rounded-lg border"
> >
<div className="border-border bg-card border-b px-4 py-2 dark:bg-gray-800"> <div className="border-border bg-card border-b px-4 py-2">
<h3 className="text-foreground font-medium"> <h3 className="text-foreground font-medium">
{tc.name} {tc.name}
{tc.id && ( {tc.id && (
<code className="bg-muted ml-2 rounded px-2 py-1 text-sm dark:bg-gray-700"> <code className="bg-muted ml-2 rounded px-2 py-1 text-sm">
{tc.id} {tc.id}
</code> </code>
)} )}
@ -44,7 +44,7 @@ export function ToolCalls({
</td> </td>
<td className="text-foreground/80 px-4 py-2 text-sm"> <td className="text-foreground/80 px-4 py-2 text-sm">
{isComplexValue(value) ? ( {isComplexValue(value) ? (
<code className="bg-muted/70 rounded px-2 py-1 font-mono text-sm break-all dark:bg-gray-800"> <code className="bg-muted/70 rounded px-2 py-1 font-mono text-sm break-all">
{JSON.stringify(value, null, 2)} {JSON.stringify(value, null, 2)}
</code> </code>
) : ( ) : (
@ -98,12 +98,12 @@ 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="border-border overflow-hidden rounded-lg border"> <div className="border-border overflow-hidden rounded-lg border">
<div className="border-border bg-card border-b px-4 py-2 dark:bg-gray-800"> <div className="border-border bg-card border-b px-4 py-2">
<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="text-foreground font-medium"> <h3 className="text-foreground font-medium">
Tool Result:{" "} Tool Result:{" "}
<code className="bg-muted rounded px-2 py-1 dark:bg-gray-700"> <code className="bg-muted rounded px-2 py-1">
{message.name} {message.name}
</code> </code>
</h3> </h3>
@ -111,14 +111,14 @@ export function ToolResult({ message }: { message: ToolMessage }) {
<h3 className="text-foreground font-medium">Tool Result</h3> <h3 className="text-foreground font-medium">Tool Result</h3>
)} )}
{message.tool_call_id && ( {message.tool_call_id && (
<code className="bg-muted text-foreground ml-2 rounded px-2 py-1 text-sm dark:bg-gray-700"> <code className="bg-muted text-foreground ml-2 rounded px-2 py-1 text-sm">
{message.tool_call_id} {message.tool_call_id}
</code> </code>
)} )}
</div> </div>
</div> </div>
<motion.div <motion.div
className="bg-muted min-w-full dark:bg-gray-700" className="bg-muted min-w-full"
initial={false} initial={false}
animate={{ height: "auto" }} animate={{ height: "auto" }}
transition={{ duration: 0.3 }} transition={{ duration: 0.3 }}
@ -152,9 +152,9 @@ export function ToolResult({ message }: { message: ToolMessage }) {
<td className="text-foreground px-4 py-2 text-sm font-medium whitespace-nowrap"> <td className="text-foreground px-4 py-2 text-sm font-medium whitespace-nowrap">
{key} {key}
</td> </td>
<td className="text-foreground/80 px-4 py-2 text-sm"> <td className="text-foreground px-4 py-2 text-sm">
{isComplexValue(value) ? ( {isComplexValue(value) ? (
<code className="bg-muted/70 rounded px-2 py-1 font-mono text-sm break-all dark:bg-gray-800"> <code className="bg-muted/70 rounded px-2 py-1 font-mono text-sm break-all">
{JSON.stringify(value, null, 2)} {JSON.stringify(value, null, 2)}
</code> </code>
) : ( ) : (
@ -167,7 +167,7 @@ export function ToolResult({ message }: { message: ToolMessage }) {
</tbody> </tbody>
</table> </table>
) : ( ) : (
<code className="text-foreground/80 block text-sm"> <code className="text-foreground block text-sm">
{displayedContent} {displayedContent}
</code> </code>
)} )}
@ -180,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="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" 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"
initial={{ scale: 1 }} initial={{ scale: 1 }}
whileHover={{ scale: 1.02 }} whileHover={{ scale: 1.02 }}
whileTap={{ scale: 0.98 }} whileTap={{ scale: 0.98 }}

View file

@ -1,7 +1,7 @@
"use client"; "use client";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Card, CardContent } from "@/components/ui/card"; import { Card, CardContent } from "@/components/ui/card";
import { FilePlus2, Archive, ListChecks } from "lucide-react"; import { Archive, ListChecks } from "lucide-react";
import { useRouter } from "next/navigation"; import { useRouter } from "next/navigation";
import { TerminalInput } from "./terminal-input"; import { TerminalInput } from "./terminal-input";
import { useFileUpload } from "@/hooks/useFileUpload"; import { useFileUpload } from "@/hooks/useFileUpload";
@ -12,7 +12,6 @@ import {
TooltipProvider, TooltipProvider,
TooltipTrigger, TooltipTrigger,
} from "../ui/tooltip"; } from "../ui/tooltip";
import { Label } from "../ui/label";
import { ContentBlocksPreview } from "../thread/ContentBlocksPreview"; import { ContentBlocksPreview } from "../thread/ContentBlocksPreview";
import { ThemeToggle } from "../theme-toggle"; import { ThemeToggle } from "../theme-toggle";
import { ThreadCard, ThreadCardLoading } from "./thread-card"; import { ThreadCard, ThreadCardLoading } from "./thread-card";
@ -32,6 +31,7 @@ import { useState, useMemo } from "react";
import { threadsToMetadata } from "@/lib/thread-utils"; import { threadsToMetadata } from "@/lib/thread-utils";
import { Settings, BookOpen } from "lucide-react"; import { Settings, BookOpen } from "lucide-react";
import NextLink from "next/link"; import NextLink from "next/link";
import { OpenSWELogoSVG } from "../icons/openswe";
function OpenSettingsButton() { function OpenSettingsButton() {
return ( return (
@ -89,7 +89,6 @@ export function DefaultView({ threads, threadsLoading }: DefaultViewProps) {
const { const {
contentBlocks, contentBlocks,
setContentBlocks, setContentBlocks,
handleFileUpload,
dropRef, dropRef,
removeBlock, removeBlock,
dragOver, dragOver,
@ -121,10 +120,10 @@ export function DefaultView({ threads, threadsLoading }: DefaultViewProps) {
<div className="border-border bg-card border-b 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> <OpenSWELogoSVG
<span className="text-muted-foreground font-mono text-sm"> width={120}
Open SWE height={18}
</span> />
</div> </div>
<div className="flex items-center gap-4"> <div className="flex items-center gap-4">
<InstallationSelector /> <InstallationSelector />
@ -148,7 +147,7 @@ export function DefaultView({ threads, threadsLoading }: DefaultViewProps) {
{/* Terminal Chat Input */} {/* Terminal Chat Input */}
<Card <Card
className={cn( className={cn(
"border-border bg-card py-0 dark:bg-gray-950", "border-border bg-card py-0",
dragOver dragOver
? "border-primary border-2 border-dotted" ? "border-primary border-2 border-dotted"
: "border border-solid", : "border border-solid",
@ -160,14 +159,6 @@ export function DefaultView({ threads, threadsLoading }: DefaultViewProps) {
blocks={contentBlocks} blocks={contentBlocks}
onRemove={removeBlock} onRemove={removeBlock}
/> />
<input
id="file-input"
type="file"
onChange={handleFileUpload}
multiple
accept="image/jpeg,image/png,image/gif,image/webp,application/pdf"
className="hidden"
/>
<div className="space-y-3"> <div className="space-y-3">
<TerminalInput <TerminalInput
placeholder="Describe your coding task or ask a question..." placeholder="Describe your coding task or ask a question..."
@ -183,27 +174,13 @@ export function DefaultView({ threads, threadsLoading }: DefaultViewProps) {
setAutoAcceptPlan={setAutoAccept} setAutoAcceptPlan={setAutoAccept}
/> />
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<TooltipProvider>
<Tooltip>
<TooltipTrigger>
<Label
htmlFor="file-input"
className="text-muted-foreground hover:text-foreground flex cursor-pointer items-center justify-center rounded-full bg-inherit"
>
<FilePlus2 className="size-4" />
</Label>
</TooltipTrigger>
<TooltipContent side="bottom">
Attach files
</TooltipContent>
</Tooltip>
</TooltipProvider>
<TooltipIconButton <TooltipIconButton
variant={autoAccept ? "brand" : "ghost"} variant={autoAccept ? "default" : "ghost"}
tooltip="Automatically accept the plan" tooltip="Automatically accept the plan"
className={cn( className={cn(
"transition-colors duration-200",
autoAccept autoAccept
? "text-secondary" ? "bg-primary hover:bg-primary/90"
: "text-muted-foreground hover:text-foreground", : "text-muted-foreground hover:text-foreground",
)} )}
onClick={() => setAutoAccept((prev) => !prev)} onClick={() => setAutoAccept((prev) => !prev)}

View file

@ -1,13 +1,16 @@
"use client"; "use client";
import { useState } from "react"; import { useState } from "react";
import { AnimatePresence, motion } from "framer-motion";
import { import {
AlertTriangle, AlertTriangle,
Loader2, Loader2,
CheckCircle, CheckCircle,
MessageSquare, MessageSquare,
ChevronDown,
} from "lucide-react"; } from "lucide-react";
import { BasicMarkdownText } from "../thread/markdown-text"; import { BasicMarkdownText } from "../thread/markdown-text";
import { cn } from "@/lib/utils";
type DiagnoseErrorActionProps = { type DiagnoseErrorActionProps = {
status: "loading" | "generating" | "done"; status: "loading" | "generating" | "done";
@ -31,7 +34,9 @@ export function DiagnoseErrorAction({
<Loader2 className="text-muted-foreground size-3.5 animate-spin" /> <Loader2 className="text-muted-foreground size-3.5 animate-spin" />
); );
case "done": case "done":
return <CheckCircle className="size-3.5 text-green-500" />; return (
<CheckCircle className="size-3.5 text-green-600 dark:text-green-400" />
);
} }
}; };
@ -49,19 +54,36 @@ export function DiagnoseErrorAction({
return ( return (
<div className="border-border overflow-hidden rounded-md border"> <div className="border-border overflow-hidden rounded-md border">
{reasoningText && ( {reasoningText && (
<div className="border-b border-blue-100 bg-blue-50 p-2 dark:border-blue-800 dark:bg-blue-950"> <div className="border-b border-l-4 border-blue-200 border-l-blue-500 bg-blue-50 p-2 dark:border-blue-800/50 dark:border-l-blue-400 dark:bg-blue-950/20">
<button <button
onClick={() => setShowReasoning(!showReasoning)} onClick={() => setShowReasoning(!showReasoning)}
className="flex items-center gap-1 text-xs font-normal text-blue-700 hover:text-blue-800 dark:text-blue-400 dark:hover:text-blue-300" className="flex cursor-pointer items-center gap-1.5 text-xs font-medium text-blue-700 transition-colors duration-200 hover:text-blue-800 dark:text-blue-300 dark:hover:text-blue-200"
> >
<MessageSquare className="h-3 w-3" /> <ChevronDown
{showReasoning ? "Hide reasoning" : "Show reasoning"} className={cn(
"h-3 w-3 transition-transform duration-200",
showReasoning && "rotate-180",
)}
/>
<span>{showReasoning ? "Hide reasoning" : "Show reasoning"}</span>
</button> </button>
{showReasoning && ( <AnimatePresence>
<BasicMarkdownText className="mt-1 text-xs font-normal text-blue-800 dark:text-blue-300"> {showReasoning && (
{reasoningText} <motion.div
</BasicMarkdownText> initial={{ height: 0, opacity: 0 }}
)} animate={{ height: "auto", opacity: 1 }}
exit={{ height: 0, opacity: 0 }}
transition={{ duration: 0.2, ease: "easeInOut" }}
className="overflow-hidden"
>
<div className="mt-3 rounded-md bg-blue-50/50 px-3 py-2 dark:bg-blue-900/20">
<BasicMarkdownText className="text-xs leading-relaxed text-blue-800 dark:text-blue-200">
{reasoningText}
</BasicMarkdownText>
</div>
</motion.div>
)}
</AnimatePresence>
</div> </div>
)} )}

View file

@ -5,15 +5,7 @@ import { useState } from "react";
import { StickToBottom } from "use-stick-to-bottom"; import { StickToBottom } from "use-stick-to-bottom";
import { TooltipIconButton } from "../ui/tooltip-icon-button"; import { TooltipIconButton } from "../ui/tooltip-icon-button";
import { AnimatePresence, motion } from "framer-motion"; import { AnimatePresence, motion } from "framer-motion";
import { import { Bot, Copy, CopyCheck, ArrowUp, User, AlertCircle } from "lucide-react";
Bot,
Copy,
CopyCheck,
Send,
User,
Loader2,
AlertCircle,
} from "lucide-react";
import { Textarea } from "../ui/textarea"; import { Textarea } from "../ui/textarea";
import { Button } from "../ui/button"; import { Button } from "../ui/button";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
@ -21,6 +13,7 @@ import { isAIMessageSDK } from "@/lib/langchain-messages";
import { BasicMarkdownText } from "../thread/markdown-text"; import { BasicMarkdownText } from "../thread/markdown-text";
import { ErrorState } from "./types"; import { ErrorState } from "./types";
import { CollapsibleAlert } from "./collapsible-alert"; import { CollapsibleAlert } from "./collapsible-alert";
import { Loader2 } from "lucide-react";
function MessageCopyButton({ content }: { content: string }) { function MessageCopyButton({ content }: { content: string }) {
const [copied, setCopied] = useState(false); const [copied, setCopied] = useState(false);
@ -102,7 +95,7 @@ export function ManagerChat({
errorState, errorState,
}: ManagerChatProps) { }: ManagerChatProps) {
return ( return (
<div className="border-border bg-muted/30 flex h-full w-1/3 flex-col border-r dark:bg-gray-950"> <div className="border-border bg-muted/30 flex h-full w-1/3 flex-col border-r">
<div className="relative flex-1"> <div className="relative flex-1">
<StickToBottom <StickToBottom
className="absolute inset-0" className="absolute inset-0"
@ -119,16 +112,16 @@ export function ManagerChat({
return ( return (
<div <div
key={message.id} key={message.id}
className="group flex gap-3" className="group bg-muted flex gap-3 rounded-lg p-3"
> >
<div className="flex-shrink-0"> <div className="flex-shrink-0">
{message.type === "human" ? ( {message.type === "human" ? (
<div className="bg-muted flex h-6 w-6 items-center justify-center rounded-full dark:bg-gray-700"> <div className="bg-muted flex h-6 w-6 items-center justify-center rounded-full">
<User className="text-muted-foreground h-3 w-3" /> <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-100 dark:bg-blue-900"> <div className="flex h-6 w-6 items-center justify-center rounded-full bg-blue-100 dark:bg-blue-950/50">
<Bot className="h-3 w-3 text-blue-700 dark:text-blue-400" /> <Bot className="h-3 w-3 text-blue-700 dark:text-blue-300" />
</div> </div>
)} )}
</div> </div>
@ -166,13 +159,13 @@ export function ManagerChat({
</StickToBottom> </StickToBottom>
</div> </div>
<div className="border-border bg-muted/30 border-t p-4 dark:bg-gray-950"> <div className="border-border bg-muted/30 border-t p-4">
<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="border-border bg-background text-foreground placeholder:text-muted-foreground min-h-[60px] flex-1 resize-none text-sm dark:bg-gray-900" className="border-border bg-background text-foreground placeholder:text-muted-foreground min-h-[60px] flex-1 resize-none text-sm"
onKeyDown={(e) => { onKeyDown={(e) => {
if (e.key === "Enter" && (e.metaKey || e.ctrlKey) && !isLoading) { if (e.key === "Enter" && (e.metaKey || e.ctrlKey) && !isLoading) {
e.preventDefault(); e.preventDefault();
@ -180,22 +173,26 @@ export function ManagerChat({
} }
}} }}
/> />
<Button {isLoading ? (
onClick={isLoading ? cancelRun : handleSendMessage} <TooltipIconButton
disabled={isLoading ? false : !chatInput.trim()} className="size-8 rounded-full border border-white/20 transition-all duration-200 hover:border-white/30 disabled:border-transparent"
size={isLoading ? "sm" : "icon"} variant="destructive"
variant={isLoading ? "destructive" : "brand"} onClick={cancelRun}
className={cn(isLoading ? "h-12 px-4 py-2" : "")} tooltip="Cancel run"
> >
{isLoading ? ( <Loader2 className="size-4 animate-spin" />
<> </TooltipIconButton>
<Loader2 className="mr-2 h-4 w-4 animate-spin" /> ) : (
Cancel <Button
</> onClick={handleSendMessage}
) : ( disabled={!chatInput.trim()}
<Send className="size-4" /> size="icon"
)} variant="brand"
</Button> className="size-8 rounded-full border border-white/20 transition-all duration-200 hover:border-white/30 disabled:border-transparent"
>
<ArrowUp className="size-4" />
</Button>
)}
</div> </div>
<div className="text-muted-foreground mt-2 text-xs"> <div className="text-muted-foreground mt-2 text-xs">
Press Cmd+Enter to send Press Cmd+Enter to send

View file

@ -41,7 +41,7 @@ function DevReadmePromptQuickAction({
return ( return (
<Card <Card
onClick={() => setQuickActionPrompt(DEV_README_PROMPT)} onClick={() => setQuickActionPrompt(DEV_README_PROMPT)}
className="border-border bg-card hover:bg-muted cursor-pointer py-3 transition-shadow hover:shadow-lg dark:bg-gray-950" className="border-border bg-card hover:bg-muted/30 dark:hover:bg-muted/20 hover:shadow-primary/2 cursor-pointer py-3 transition-all duration-200 hover:shadow-sm"
> >
<CardHeader className="px-3"> <CardHeader className="px-3">
<CardTitle className="text-foreground text-sm"> <CardTitle className="text-foreground text-sm">
@ -71,7 +71,7 @@ export function QuickActions({ setQuickActionPrompt }: QuickActionsProps) {
/> />
<Card <Card
onClick={() => setQuickActionPrompt(GENERATE_RULES_PROMPT)} onClick={() => setQuickActionPrompt(GENERATE_RULES_PROMPT)}
className="border-border bg-card hover:bg-muted cursor-pointer py-3 transition-shadow hover:shadow-lg dark:bg-gray-950" className="border-border bg-card hover:bg-muted/30 dark:hover:bg-muted/20 hover:shadow-primary/2 cursor-pointer py-3 transition-all duration-200 hover:shadow-sm"
> >
<CardHeader className="px-3"> <CardHeader className="px-3">
<CardTitle className="text-foreground text-sm"> <CardTitle className="text-foreground text-sm">

View file

@ -4,11 +4,10 @@ import type React from "react";
import { v4 as uuidv4 } from "uuid"; import { v4 as uuidv4 } from "uuid";
import { Dispatch, SetStateAction, useEffect, useState } from "react"; import { Dispatch, SetStateAction, useEffect, useState } from "react";
import { Textarea } from "@/components/ui/textarea"; import { Textarea } from "@/components/ui/textarea";
import { Loader2, Send } from "lucide-react"; import { ArrowUp, Loader2 } from "lucide-react";
import { RepositoryBranchSelectors } from "../github/repo-branch-selectors"; import { RepositoryBranchSelectors } from "../github/repo-branch-selectors";
import { Button } from "../ui/button"; import { Button } from "../ui/button";
import { useStream } from "@langchain/langgraph-sdk/react"; import { useStream } from "@langchain/langgraph-sdk/react";
import { StreamMode } from "@langchain/langgraph-sdk";
import { useRouter } from "next/navigation"; import { useRouter } from "next/navigation";
import { useGitHubAppProvider } from "@/providers/GitHubApp"; import { useGitHubAppProvider } from "@/providers/GitHubApp";
import { GraphState } from "@open-swe/shared/open-swe/types"; import { GraphState } from "@open-swe/shared/open-swe/types";
@ -18,7 +17,6 @@ import { DEFAULT_CONFIG_KEY, useConfigStore } from "@/hooks/useConfigStore";
import { import {
API_KEY_REQUIRED_MESSAGE, API_KEY_REQUIRED_MESSAGE,
MANAGER_GRAPH_ID, MANAGER_GRAPH_ID,
OPEN_SWE_STREAM_MODE,
} from "@open-swe/shared/constants"; } from "@open-swe/shared/constants";
import { ManagerGraphUpdate } from "@open-swe/shared/open-swe/manager/types"; import { ManagerGraphUpdate } from "@open-swe/shared/open-swe/manager/types";
import { useDraftStorage } from "@/hooks/useDraftStorage"; import { useDraftStorage } from "@/hooks/useDraftStorage";
@ -106,7 +104,7 @@ export function TerminalInput({
}, },
ifNotExists: "create", ifNotExists: "create",
streamResumable: true, streamResumable: true,
streamMode: OPEN_SWE_STREAM_MODE as StreamMode[], streamMode: ["values", "messages-tuple", "custom"],
}, },
); );
@ -172,9 +170,9 @@ export function TerminalInput({
}, [draftToLoad, setMessage]); }, [draftToLoad, setMessage]);
return ( return (
<div className="border-border bg-muted rounded-md border p-2 font-mono text-xs dark:bg-black"> <div className="border-border bg-muted hover:border-muted-foreground/50 hover:bg-muted/80 focus-within:border-muted-foreground/70 focus-within:bg-muted/80 focus-within:shadow-muted-foreground/20 rounded-md border p-2 font-mono text-xs transition-all duration-200 focus-within:shadow-md">
<div className="text-foreground flex items-center gap-1"> <div className="text-foreground flex items-center gap-1">
<div className="flex items-center gap-1 rounded-md border border-gray-200 p-1 dark:border-gray-700"> <div className="border-border bg-background/50 flex items-center gap-1 rounded-md border p-1 transition-colors duration-200">
<span className="text-muted-foreground">open-swe</span> <span className="text-muted-foreground">open-swe</span>
<span className="text-muted-foreground/70">@</span> <span className="text-muted-foreground/70">@</span>
<span className="text-muted-foreground">github</span> <span className="text-muted-foreground">github</span>
@ -191,12 +189,12 @@ export function TerminalInput({
disabled={disabled || !message.trim() || !selectedRepository} disabled={disabled || !message.trim() || !selectedRepository}
size="icon" size="icon"
variant="brand" variant="brand"
className="ml-auto size-8" className="ml-auto size-8 rounded-full border border-white/20 transition-all duration-200 hover:border-white/30 disabled:border-transparent"
> >
{loading ? ( {loading ? (
<Loader2 className="size-4 animate-spin" /> <Loader2 className="h-4 w-4 animate-spin" />
) : ( ) : (
<Send className="size-4" /> <ArrowUp className="size-4" />
)} )}
</Button> </Button>
</div> </div>
@ -209,7 +207,7 @@ export function TerminalInput({
onKeyDown={handleKeyPress} onKeyDown={handleKeyPress}
placeholder={placeholder} placeholder={placeholder}
disabled={disabled} disabled={disabled}
className="text-foreground placeholder:text-muted-foreground max-h-[50vh] min-h-[80px] flex-1 resize-none border-none bg-transparent p-0 font-mono text-xs shadow-none focus-visible:ring-0 focus-visible:ring-offset-0" className="text-foreground placeholder:text-muted-foreground focus:placeholder:text-muted-foreground/60 max-h-[50vh] min-h-[80px] flex-1 resize-none border-none bg-transparent p-0 font-mono text-xs shadow-none transition-all duration-200 focus-visible:ring-0 focus-visible:ring-offset-0"
rows={6} rows={6}
onPaste={onPaste} onPaste={onPaste}
/> />

View file

@ -3,11 +3,11 @@ import {
CheckCircle, CheckCircle,
GitBranch, GitBranch,
GitPullRequest, GitPullRequest,
Loader2,
AlertCircle, AlertCircle,
Pause, Pause,
XCircle, XCircle,
Clock, Clock,
Loader2,
} from "lucide-react"; } from "lucide-react";
import { Card, CardContent, CardHeader, CardTitle } from "../ui/card"; import { Card, CardContent, CardHeader, CardTitle } from "../ui/card";
import { useRouter } from "next/navigation"; import { useRouter } from "next/navigation";
@ -76,19 +76,19 @@ export function ThreadCard({
const getStatusColor = (status: ThreadUIStatus) => { const getStatusColor = (status: ThreadUIStatus) => {
switch (status) { switch (status) {
case "running": case "running":
return "dark:bg-blue-950 bg-blue-100 dark:text-blue-400 text-blue-700"; return "bg-blue-100 dark:bg-blue-950/50 text-blue-700 dark:text-blue-300";
case "completed": case "completed":
return "dark:bg-green-950 bg-green-100 dark:text-green-400 text-green-700"; return "bg-green-100 dark:bg-green-950/50 text-green-700 dark:text-green-300";
case "error": case "error":
return "dark:bg-red-950 bg-red-100 dark:text-red-400 text-red-700"; return "bg-red-100 dark:bg-red-950/50 text-red-700 dark:text-red-300";
case "paused": case "paused":
return "dark:bg-yellow-950 bg-yellow-100 dark:text-yellow-400 text-yellow-700"; return "bg-yellow-100 dark:bg-yellow-950/50 text-yellow-700 dark:text-yellow-300";
case "failed": case "failed":
return "dark:bg-red-950 bg-red-100 dark:text-red-400 text-red-700"; return "bg-red-100 dark:bg-red-950/50 text-red-700 dark:text-red-300";
case "pending": case "pending":
return "dark:bg-yellow-950 bg-yellow-100 dark:text-yellow-400 text-yellow-700"; return "bg-yellow-100 dark:bg-yellow-950/50 text-yellow-700 dark:text-yellow-300";
default: default:
return "dark:bg-gray-800 bg-gray-200 dark:text-gray-400 text-gray-700"; return "bg-gray-200 dark:bg-muted text-gray-700 dark:text-muted-foreground";
} }
}; };
@ -129,7 +129,7 @@ export function ThreadCard({
return ( return (
<Card <Card
key={thread.id} 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" className="border-border bg-card hover:bg-muted/50 hover:shadow-primary/3 hover:border-primary/10 group cursor-pointer px-0 py-3 transition-all duration-200 hover:shadow-md"
onClick={() => { onClick={() => {
router.push(`/chat/${thread.id}`); router.push(`/chat/${thread.id}`);
}} }}
@ -137,7 +137,7 @@ export function ThreadCard({
<CardHeader> <CardHeader>
<div className="flex items-start justify-between gap-2"> <div className="flex items-start justify-between gap-2">
<div className="min-w-0 flex-1"> <div className="min-w-0 flex-1">
<CardTitle className="text-foreground truncate text-sm"> <CardTitle className="text-foreground line-clamp-2 text-sm leading-tight">
<InlineMarkdownText>{threadTitle}</InlineMarkdownText> <InlineMarkdownText>{threadTitle}</InlineMarkdownText>
</CardTitle> </CardTitle>
<div className="mt-1 flex items-center gap-1"> <div className="mt-1 flex items-center gap-1">
@ -150,9 +150,9 @@ export function ThreadCard({
<Badge <Badge
variant="secondary" variant="secondary"
className={cn( className={cn(
"text-xs", "text-xs transition-all duration-300 group-hover:scale-105",
isStatusLoading isStatusLoading
? "bg-gray-200 text-gray-600 dark:bg-gray-800 dark:text-gray-400" ? "dark:bg-muted dark:text-muted-foreground bg-gray-200 text-gray-600"
: getStatusColor(displayStatus), : getStatusColor(displayStatus),
)} )}
> >
@ -160,7 +160,9 @@ export function ThreadCard({
{isStatusLoading ? ( {isStatusLoading ? (
<Loader2 className="h-4 w-4 animate-spin" /> <Loader2 className="h-4 w-4 animate-spin" />
) : ( ) : (
getStatusIcon(displayStatus) <div className="transition-transform duration-300 group-hover:rotate-12">
{getStatusIcon(displayStatus)}
</div>
)} )}
<span className="capitalize"> <span className="capitalize">
{isStatusLoading ? "Loading..." : displayStatus} {isStatusLoading ? "Loading..." : displayStatus}
@ -187,13 +189,13 @@ export function ThreadCard({
<Button <Button
variant="ghost" variant="ghost"
size="sm" size="sm"
className="text-muted-foreground hover:text-foreground h-5 w-5 p-0" className="text-muted-foreground hover:text-foreground h-5 w-5 p-0 transition-all duration-200 hover:scale-110 hover:bg-orange-100 dark:hover:bg-orange-950"
onClick={(e) => { onClick={(e) => {
e.stopPropagation(); e.stopPropagation();
window.open(thread.githubIssue!.url, "_blank"); window.open(thread.githubIssue!.url, "_blank");
}} }}
> >
<Bug className="h-3 w-3" /> <Bug className="h-3 w-3 transition-colors duration-200" />
</Button> </Button>
)} )}
{thread.pullRequest && ( {thread.pullRequest && (
@ -201,15 +203,21 @@ export function ThreadCard({
variant="ghost" variant="ghost"
size="sm" size="sm"
className={cn( className={cn(
"h-5 w-5 p-0 hover:text-gray-300", "h-5 w-5 p-0 transition-all duration-200 hover:scale-110 hover:text-gray-300",
getPRStatusColor(thread.pullRequest.status), getPRStatusColor(thread.pullRequest.status),
thread.pullRequest.status === "merged" &&
"hover:bg-purple-100 dark:hover:bg-purple-950",
thread.pullRequest.status === "open" &&
"hover:bg-green-100 dark:hover:bg-green-950",
thread.pullRequest.status === "closed" &&
"hover:bg-red-100 dark:hover:bg-red-950",
)} )}
onClick={(e) => { onClick={(e) => {
e.stopPropagation(); e.stopPropagation();
window.open(thread.pullRequest!.url, "_blank"); window.open(thread.pullRequest!.url, "_blank");
}} }}
> >
<GitPullRequest className="h-3 w-3" /> <GitPullRequest className="h-3 w-3 transition-colors duration-200" />
</Button> </Button>
)} )}
</div> </div>
@ -221,7 +229,7 @@ export function ThreadCard({
export function ThreadCardLoading() { export function ThreadCardLoading() {
return ( return (
<Card className="border-border bg-card px-0 py-3 dark:bg-gray-950"> <Card className="border-border bg-card px-0 py-3">
<CardHeader> <CardHeader>
<div className="flex items-start justify-between"> <div className="flex items-start justify-between">
<div className="min-w-0 flex-1"> <div className="min-w-0 flex-1">

View file

@ -47,7 +47,7 @@ export function ThreadSwitcher({
<Button <Button
variant="outline" variant="outline"
size="sm" size="sm"
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" className="border-border bg-card text-muted-foreground hover:bg-muted hover:text-foreground h-7 gap-1 text-xs"
> >
<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>
@ -55,7 +55,7 @@ export function ThreadSwitcher({
</SheetTrigger> </SheetTrigger>
<SheetContent <SheetContent
side="right" side="right"
className="border-border bg-background w-80 sm:w-96 dark:border-gray-800 dark:bg-gray-950" className="border-border bg-background w-80 sm:w-96"
> >
<SheetHeader className="pb-4"> <SheetHeader className="pb-4">
<SheetTitle className="text-foreground text-base"> <SheetTitle className="text-foreground text-base">
@ -70,7 +70,7 @@ export function ThreadSwitcher({
router.push("/chat"); router.push("/chat");
setOpen(false); setOpen(false);
}} }}
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" className="border-border bg-card text-foreground hover:bg-muted h-8 w-full justify-start gap-2 text-xs"
variant="outline" variant="outline"
> >
<Plus className="h-3 w-3" /> <Plus className="h-3 w-3" />
@ -79,7 +79,7 @@ export function ThreadSwitcher({
{/* Current Thread */} {/* Current Thread */}
<div className="space-y-2"> <div className="space-y-2">
<h3 className="text-muted-foreground text-xs font-medium tracking-wide uppercase dark:text-gray-500"> <h3 className="text-muted-foreground text-xs font-medium tracking-wide uppercase">
Current Thread Current Thread
</h3> </h3>
<ThreadCard thread={currentThread} /> <ThreadCard thread={currentThread} />
@ -88,7 +88,7 @@ export function ThreadSwitcher({
{/* Other Threads */} {/* Other Threads */}
{otherThreads.length > 0 && ( {otherThreads.length > 0 && (
<div className="h-full space-y-2"> <div className="h-full space-y-2">
<h3 className="text-muted-foreground text-xs font-medium tracking-wide uppercase dark:text-gray-500"> <h3 className="text-muted-foreground text-xs font-medium tracking-wide uppercase">
Other Threads Other Threads
</h3> </h3>
<ScrollArea className="h-full"> <ScrollArea className="h-full">

View file

@ -1,7 +1,7 @@
"use client"; "use client";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Card, CardContent, CardHeader } from "@/components/ui/card"; import { Card, CardHeader } from "@/components/ui/card";
import { ArrowLeft, Loader2 } from "lucide-react"; import { ArrowLeft, Loader2 } from "lucide-react";
interface ThreadViewLoadingProps { interface ThreadViewLoadingProps {
@ -18,35 +18,35 @@ export function LoadingActionsCardContent() {
{Array.from({ length: 8 }, (_, i) => ( {Array.from({ length: 8 }, (_, i) => (
<div <div
key={i} key={i}
className="border-border bg-muted/30 space-y-2 rounded-lg border p-3 dark:bg-gray-900" className="border-border bg-muted/30 space-y-2 rounded-lg border p-3"
> >
{isEven(i) ? ( {isEven(i) ? (
<div className="bg-background space-y-1 rounded p-2 dark:bg-black"> <div className="bg-background space-y-1 rounded p-2">
<div className="bg-muted h-2 w-32 animate-pulse rounded dark:bg-gray-700"></div> <div className="bg-muted h-2 w-32 animate-pulse rounded"></div>
<div className="bg-muted h-2 w-48 animate-pulse rounded dark:bg-gray-700"></div> <div className="bg-muted h-2 w-48 animate-pulse rounded"></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="bg-muted h-4 w-4 animate-pulse rounded dark:bg-gray-700"></div> <div className="bg-muted h-4 w-4 animate-pulse rounded"></div>
<div className="bg-muted h-3 w-40 animate-pulse rounded dark:bg-gray-700"></div> <div className="bg-muted h-3 w-40 animate-pulse rounded"></div>
</div> </div>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<div className="bg-muted h-3 w-16 animate-pulse rounded dark:bg-gray-700"></div> <div className="bg-muted h-3 w-16 animate-pulse rounded"></div>
<div className="bg-muted h-4 w-4 animate-pulse rounded dark:bg-gray-700"></div> <div className="bg-muted h-4 w-4 animate-pulse rounded"></div>
</div> </div>
</div> </div>
)} )}
</div> </div>
))} ))}
<div className="border-border space-y-2 rounded-lg border-2 border-dashed p-3"> <div className="border-border space-y-2 rounded-lg border-2 border-dashed p-3">
<div className="bg-muted h-12 animate-pulse rounded dark:bg-gray-800"></div> <div className="bg-muted h-12 animate-pulse rounded"></div>
<div className="bg-muted h-7 w-16 animate-pulse rounded dark:bg-gray-800"></div> <div className="bg-muted h-7 w-16 animate-pulse rounded"></div>
</div> </div>
<div className="flex gap-2 pt-3"> <div className="flex gap-2 pt-3">
<div className="bg-muted h-8 flex-1 animate-pulse rounded dark:bg-gray-700"></div> <div className="bg-muted h-8 flex-1 animate-pulse rounded"></div>
<div className="bg-muted h-8 flex-1 animate-pulse rounded dark:bg-gray-700"></div> <div className="bg-muted h-8 flex-1 animate-pulse rounded"></div>
</div> </div>
</div> </div>
); );
@ -54,12 +54,12 @@ export function LoadingActionsCardContent() {
export function LoadingActionsCard() { export function LoadingActionsCard() {
return ( return (
<Card className="border-border bg-card dark:bg-gray-950"> <Card className="border-border bg-card">
<CardHeader className="p-3"> <CardHeader className="p-3">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<div className="bg-muted h-4 w-32 animate-pulse rounded dark:bg-gray-700"></div> <div className="bg-muted h-4 w-32 animate-pulse rounded"></div>
<div className="ml-auto flex items-center gap-1"> <div className="ml-auto flex items-center gap-2">
<Loader2 className="text-muted-foreground h-3 w-3 animate-spin" /> <Loader2 className="h-3 w-3 animate-spin" />
<span className="text-muted-foreground text-xs"> <span className="text-muted-foreground text-xs">
Loading actions... Loading actions...
</span> </span>
@ -77,7 +77,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="text-muted-foreground flex items-center gap-2"> <div className="text-muted-foreground flex items-center gap-3">
<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>
@ -89,18 +89,18 @@ export function LoadingChatSection() {
className="flex gap-3" className="flex gap-3"
> >
<div className="flex-shrink-0"> <div className="flex-shrink-0">
<div className="bg-muted h-6 w-6 animate-pulse rounded-full dark:bg-gray-700"></div> <div className="bg-muted h-6 w-6 animate-pulse rounded-full"></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="bg-muted h-3 w-16 animate-pulse rounded dark:bg-gray-700"></div> <div className="bg-muted h-3 w-16 animate-pulse rounded"></div>
<div className="bg-muted h-3 w-12 animate-pulse rounded dark:bg-gray-700"></div> <div className="bg-muted h-3 w-12 animate-pulse rounded"></div>
</div> </div>
<div className="space-y-1"> <div className="space-y-1">
<div className="bg-muted h-3 w-full animate-pulse rounded dark:bg-gray-700"></div> <div className="bg-muted h-3 w-full animate-pulse rounded"></div>
<div className="bg-muted h-3 w-3/4 animate-pulse rounded dark:bg-gray-700"></div> <div className="bg-muted h-3 w-3/4 animate-pulse rounded"></div>
{i === 1 && ( {i === 1 && (
<div className="bg-muted h-3 w-1/2 animate-pulse rounded dark:bg-gray-700"></div> <div className="bg-muted h-3 w-1/2 animate-pulse rounded"></div>
)} )}
</div> </div>
</div> </div>
@ -124,30 +124,30 @@ export function ThreadViewLoading({ onBackToHome }: ThreadViewLoadingProps) {
<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="bg-muted h-2 w-2 animate-pulse rounded-full dark:bg-gray-700"></div> <div className="bg-muted h-2 w-2 animate-pulse rounded-full"></div>
<div className="bg-muted h-3 w-48 animate-pulse rounded dark:bg-gray-700"></div> <div className="bg-muted h-3 w-48 animate-pulse rounded"></div>
<span className="text-muted-foreground text-xs">•</span> <span className="text-muted-foreground text-xs">•</span>
<div className="bg-muted h-3 w-24 animate-pulse rounded dark:bg-gray-700"></div> <div className="bg-muted h-3 w-24 animate-pulse rounded"></div>
</div> </div>
<div className="bg-muted/70 h-7 w-28 animate-pulse rounded dark:bg-gray-800"></div> <div className="bg-muted/70 h-7 w-28 animate-pulse rounded"></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="border-border bg-muted/30 flex h-full w-1/3 flex-col border-r dark:bg-gray-950"> <div className="border-border bg-muted/30 flex h-full w-1/3 flex-col border-r">
<LoadingChatSection /> <LoadingChatSection />
<div className="border-border bg-muted/30 border-t p-4 dark:bg-gray-950"> <div className="border-border bg-muted/30 border-t p-4">
<div className="flex gap-2"> <div className="flex gap-2">
<div className="border-border bg-background flex-1 rounded-md border p-3 dark:bg-gray-900"> <div className="border-border bg-background flex-1 rounded-md border p-3">
<div className="space-y-2"> <div className="space-y-2">
<div className="bg-muted h-3 w-1/3 animate-pulse rounded dark:bg-gray-700"></div> <div className="bg-muted h-3 w-1/3 animate-pulse rounded"></div>
<div className="bg-muted h-3 w-1/2 animate-pulse rounded dark:bg-gray-700"></div> <div className="bg-muted h-3 w-1/2 animate-pulse rounded"></div>
</div> </div>
</div> </div>
<div className="bg-muted h-10 w-10 animate-pulse self-end rounded dark:bg-gray-700"></div> <div className="bg-muted h-10 w-10 animate-pulse self-end rounded"></div>
</div> </div>
<div className="bg-muted mt-2 h-3 w-32 animate-pulse rounded dark:bg-gray-700"></div> <div className="bg-muted mt-2 h-3 w-32 animate-pulse rounded"></div>
</div> </div>
</div> </div>

View file

@ -346,7 +346,7 @@ export function ThreadView({
} }
> >
<div className="flex flex-shrink-0 items-center gap-3"> <div className="flex flex-shrink-0 items-center gap-3">
<TabsList className="bg-muted/70 dark:bg-gray-800"> <TabsList className="bg-muted/70">
<TabsTrigger value="planner">Planner</TabsTrigger> <TabsTrigger value="planner">Planner</TabsTrigger>
<TabsTrigger value="programmer">Programmer</TabsTrigger> <TabsTrigger value="programmer">Programmer</TabsTrigger>
</TabsList> </TabsList>
@ -390,7 +390,7 @@ export function ThreadView({
value="planner" value="planner"
className="mb-2" className="mb-2"
> >
<Card className="border-border bg-card relative h-full p-0 dark:bg-gray-950"> <Card className="border-border bg-card relative h-full p-0">
<CardContent className="h-full p-0"> <CardContent className="h-full p-0">
<StickToBottom <StickToBottom
className="absolute inset-0 h-full" className="absolute inset-0 h-full"
@ -403,7 +403,6 @@ export function ThreadView({
{plannerSession ? ( {plannerSession ? (
<div className="scrollbar-pretty-auto overflow-y-auto px-2"> <div className="scrollbar-pretty-auto overflow-y-auto px-2">
<ActionsRenderer<PlannerGraphState> <ActionsRenderer<PlannerGraphState>
taskPlan={programmerTaskPlan}
runId={plannerSession.runId} runId={plannerSession.runId}
customNodeEvents={customPlannerNodeEvents} customNodeEvents={customPlannerNodeEvents}
setCustomNodeEvents={ setCustomNodeEvents={
@ -437,7 +436,7 @@ export function ThreadView({
value="programmer" value="programmer"
className="mb-2" className="mb-2"
> >
<Card className="border-border bg-card relative h-full p-0 dark:bg-gray-950"> <Card className="border-border bg-card relative h-full p-0">
<CardContent className="h-full p-0"> <CardContent className="h-full p-0">
<StickToBottom <StickToBottom
className="absolute inset-0 h-full" className="absolute inset-0 h-full"