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 { cn } from "@/lib/utils";
import { threadsToMetadata } from "@/lib/thread-utils";
import { OpenSWELogoSVG } from "@/components/icons/openswe";
type FilterStatus =
| "all"
@ -109,10 +110,10 @@ function AllThreadsPageContent() {
<ArrowLeft className="h-3 w-3" />
</Button>
<div className="flex items-center gap-2">
<div className="h-2 w-2 rounded-full bg-green-500"></div>
<span className="text-muted-foreground font-mono text-sm">
All Threads
</span>
<OpenSWELogoSVG
width={120}
height={18}
/>
</div>
<div className="ml-auto flex items-center gap-4">
<div className="flex items-center gap-2">
@ -129,7 +130,7 @@ function AllThreadsPageContent() {
</div>
{/* 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="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" />
@ -137,7 +138,7 @@ function AllThreadsPageContent() {
placeholder="Search threads..."
value={searchQuery}
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 className="flex items-center gap-1">
@ -162,7 +163,7 @@ function AllThreadsPageContent() {
className={cn(
"h-7 text-xs",
statusFilter === status
? "bg-muted text-foreground dark:bg-gray-700"
? "bg-muted text-foreground"
: "text-muted-foreground hover:bg-muted hover:text-foreground",
)}
onClick={() => setStatusFilter(status)}
@ -172,7 +173,7 @@ function AllThreadsPageContent() {
: status.charAt(0).toUpperCase() + status.slice(1)}
<Badge
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]}
</Badge>
@ -197,7 +198,7 @@ function AllThreadsPageContent() {
</h2>
<Badge
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}
</Badge>

View file

@ -41,38 +41,38 @@
}
.dark {
--background: oklch(0.145 0 0);
--foreground: oklch(0.985 0 0);
--card: oklch(0.145 0 0);
--card-foreground: oklch(0.985 0 0);
--popover: oklch(0.145 0 0);
--popover-foreground: oklch(0.985 0 0);
--primary: oklch(0.985 0 0);
--primary-foreground: oklch(0.205 0 0);
--secondary: oklch(0.269 0 0);
--secondary-foreground: oklch(0.985 0 0);
--muted: oklch(0.269 0 0);
--muted-foreground: oklch(0.708 0 0);
--accent: oklch(0.269 0 0);
--accent-foreground: oklch(0.985 0 0);
--destructive: oklch(0.396 0.141 25.723);
--destructive-foreground: oklch(0.637 0.237 25.331);
--border: oklch(0.269 0 0);
--input: oklch(0.269 0 0);
--ring: oklch(0.439 0 0);
--background: oklch(0.29 0.005 280);
--foreground: oklch(0.92 0.01 280);
--card: oklch(0.32 0.005 280);
--card-foreground: oklch(0.92 0.01 280);
--popover: oklch(0.32 0.005 280);
--popover-foreground: oklch(0.92 0.01 280);
--primary: oklch(0.92 0.01 280);
--primary-foreground: oklch(0.24 0.005 280);
--secondary: oklch(0.38 0.005 280);
--secondary-foreground: oklch(0.92 0.01 280);
--muted: oklch(0.35 0.005 280);
--muted-foreground: oklch(0.65 0.01 280);
--accent: oklch(0.38 0.005 280);
--accent-foreground: oklch(0.92 0.01 280);
--destructive: oklch(0.55 0.15 20);
--destructive-foreground: oklch(0.95 0.01 280);
--border: oklch(0.42 0.005 280);
--input: oklch(0.42 0.005 280);
--ring: oklch(0.5 0.01 280);
--chart-1: oklch(0.488 0.243 264.376);
--chart-2: oklch(0.696 0.17 162.48);
--chart-3: oklch(0.769 0.188 70.08);
--chart-4: oklch(0.627 0.265 303.9);
--chart-5: oklch(0.645 0.246 16.439);
--sidebar: oklch(0.205 0 0);
--sidebar-foreground: oklch(0.985 0 0);
--sidebar: oklch(0.3 0.005 280);
--sidebar-foreground: oklch(0.92 0.01 280);
--sidebar-primary: oklch(0.488 0.243 264.376);
--sidebar-primary-foreground: oklch(0.985 0 0);
--sidebar-accent: oklch(0.269 0 0);
--sidebar-accent-foreground: oklch(0.985 0 0);
--sidebar-border: oklch(0.269 0 0);
--sidebar-ring: oklch(0.439 0 0);
--sidebar-primary-foreground: oklch(0.92 0.01 280);
--sidebar-accent: oklch(0.38 0.005 280);
--sidebar-accent-foreground: oklch(0.92 0.01 280);
--sidebar-border: oklch(0.42 0.005 280);
--sidebar-ring: oklch(0.5 0.01 280);
}
@theme inline {
@ -167,6 +167,6 @@
}
.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";
import { JSX, useState } from "react";
import { AnimatePresence, motion } from "framer-motion";
import {
Terminal,
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",
matchCase
? "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>
@ -554,7 +555,7 @@ function ActionItem(props: ActionItemProps) {
props.output
) {
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">
{props.output}
</pre>
@ -577,7 +578,7 @@ function ActionItem(props: ActionItemProps) {
}
return (
<div className="bg-muted overflow-x-auto p-2 dark:bg-gray-900">
<div className="bg-muted overflow-x-auto p-2">
{hasArgs && (
<div className="mb-3">
<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) {
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">
{props.output}
</pre>
@ -631,7 +632,7 @@ function ActionItem(props: ActionItemProps) {
);
} else if (props.actionType === "apply-patch" && props.diff) {
return (
<div className="bg-muted overflow-x-auto p-2 dark:bg-gray-900">
<div className="bg-muted overflow-x-auto p-2">
<pre
className="text-foreground/90 text-xs font-normal whitespace-pre-wrap"
dangerouslySetInnerHTML={{ __html: formatDiff(props.diff) }}
@ -664,7 +665,7 @@ function ActionItem(props: ActionItemProps) {
props.scratchpad.length > 0
) {
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">
{props.scratchpad.map((note, i) => (
<li
@ -684,7 +685,7 @@ function ActionItem(props: ActionItemProps) {
return (
<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()}
{renderHeaderContent()}
<div className="ml-auto flex items-center gap-2">
@ -707,15 +708,24 @@ function ActionItem(props: ActionItemProps) {
</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>
);
}
export function ActionStep(props: ActionStepProps) {
const [showReasoning, setShowReasoning] = useState(true);
const [showSummary, setShowSummary] = useState(true);
const reasoningText =
"reasoningText" in props ? props.reasoningText : undefined;
const summaryText = "summaryText" in props ? props.summaryText : undefined;
@ -724,21 +734,41 @@ export function ActionStep(props: ActionStepProps) {
(action: ActionItemProps) => action.status === "done",
);
const [showReasoning, setShowReasoning] = useState(!!reasoningText);
const [showSummary, setShowSummary] = useState(!!summaryText);
return (
<div className="border-border 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="border-border bg-muted overflow-hidden rounded-md border">
<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
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" />
{showReasoning ? "Hide reasoning" : "Show reasoning"}
<ChevronDown
className={cn(
"h-3 w-3 transition-transform duration-200",
showReasoning && "rotate-180",
)}
/>
<span>{showReasoning ? "Hide reasoning" : "Show reasoning"}</span>
</button>
{showReasoning && (
<BasicMarkdownText className="mt-1 text-xs font-normal text-blue-700 dark:text-blue-300">
{reasoningText || "No reasoning provided."}
</BasicMarkdownText>
)}
<AnimatePresence>
{showReasoning && (
<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="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 className="p-2">
@ -751,19 +781,36 @@ export function ActionStep(props: ActionStepProps) {
</div>
{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
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" />
{showSummary ? "Hide summary" : "Show summary"}
<ChevronDown
className={cn(
"h-3 w-3 transition-transform duration-200",
showSummary && "rotate-180",
)}
/>
<span>{showSummary ? "Hide summary" : "Show summary"}</span>
</button>
{showSummary && (
<span className="text-green-700 dark:text-green-300">
<BasicMarkdownText>{summaryText}</BasicMarkdownText>
</span>
)}
<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="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>

View file

@ -1,8 +1,8 @@
"use client";
import { Sparkles } from "lucide-react";
import { FileSearch } from "lucide-react";
import { Badge } from "../ui/badge";
import { Check } from "lucide-react";
import { Clock } from "lucide-react";
import { cn } from "@/lib/utils";
type CodeReviewStartedProps = {
@ -13,36 +13,34 @@ export function CodeReviewStarted({ status = "done" }: CodeReviewStartedProps) {
return (
<div
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",
)}
>
{/* Header */}
<div
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",
"rounded-xl",
"dark:bg-muted/40 relative flex items-center bg-blue-50/50 p-3",
"rounded-lg",
)}
>
<div className="flex h-8 w-8 items-center justify-center rounded-full bg-blue-500 shadow-md dark:bg-blue-600">
<Sparkles className="h-4 w-4 text-white" />
<div className="flex h-7 w-7 items-center justify-center rounded-full bg-blue-500/90 dark:bg-blue-600">
<FileSearch className="h-3.5 w-3.5 text-white" />
</div>
<div className="ml-3 flex-1">
<div className="flex items-center gap-2">
<h3 className="text-foreground text-sm font-semibold">
Code review started
</h3>
<h3 className="text-foreground text-sm font-medium">Code review</h3>
<Badge
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" />
In Progress
<Clock className="h-3 w-3" />
In progress
</Badge>
</div>
<p className="text-muted-foreground mt-1 text-xs">
Analyzing code for best practices and potential improvements
<p className="text-muted-foreground/80 mt-1 text-xs">
Analyzing code quality
</p>
</div>
</div>

View file

@ -1,5 +1,6 @@
import { FileText, ChevronDown, Check } from "lucide-react";
import { useState } from "react";
import { AnimatePresence, motion } from "framer-motion";
import { MarkdownText } from "../thread/markdown-text";
import { cn } from "@/lib/utils";
import { Button } from "../ui/button";
@ -15,18 +16,18 @@ export function ConversationHistorySummary({ summary }: { summary: string }) {
return (
<div
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",
)}
>
{/* Header */}
<div
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",
)}
>
<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" />
</div>
@ -37,7 +38,7 @@ export function ConversationHistorySummary({ summary }: { summary: string }) {
</h3>
<Badge
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" />
Complete
@ -64,13 +65,23 @@ export function ConversationHistorySummary({ summary }: { summary: string }) {
</div>
{/* Content */}
{expanded && (
<div className="p-4">
<div className="text-sm">
<MarkdownText>{summary}</MarkdownText>
</div>
</div>
)}
<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"
>
<div className="p-4">
<div className="text-sm">
<MarkdownText>{summary}</MarkdownText>
</div>
</div>
</motion.div>
)}
</AnimatePresence>
</div>
);
}

View file

@ -1,6 +1,7 @@
"use client";
import { useState } from "react";
import { AnimatePresence, motion } from "framer-motion";
import {
GitPullRequest,
Loader2,
@ -43,7 +44,7 @@ export function PullRequestOpened({
return (
<Badge
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" />
Preparing
@ -53,7 +54,7 @@ export function PullRequestOpened({
return (
<Badge
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" />
Opening
@ -64,7 +65,7 @@ export function PullRequestOpened({
return (
<Badge
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" />
Draft
@ -74,7 +75,7 @@ export function PullRequestOpened({
return (
<Badge
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" />
Opened
@ -110,18 +111,18 @@ export function PullRequestOpened({
return (
<div
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",
)}
>
{/* Header */}
<div
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",
)}
>
<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 ? (
<GitPullRequestDraft className="h-4 w-4 text-white" />
) : (
@ -178,18 +179,28 @@ export function PullRequestOpened({
</div>
{/* Content */}
{expanded && description && status === "done" && (
<div className="border-t p-4">
<div className="bg-muted/30 rounded-lg p-3">
<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>
)}
<AnimatePresence>
{expanded && description && status === "done" && (
<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="border-t p-4">
<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>
);
}

View file

@ -72,16 +72,16 @@ export function PushChanges({
return (
<div className="overflow-hidden rounded-md border border-gray-200">
{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
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" />
{showReasoning ? "Hide reasoning" : "Show reasoning"}
</button>
{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}
</BasicMarkdownText>
)}

View file

@ -34,7 +34,9 @@ export function ReplanningStep({
case "generating":
return <Loader2 className="h-3.5 w-3.5 animate-spin text-gray-500" />;
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 (
<div className="overflow-hidden rounded-md border border-gray-200">
{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
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" />
{showReasoning ? "Hide reasoning" : "Show reasoning"}
</button>
{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}
</BasicMarkdownText>
)}

View file

@ -42,7 +42,7 @@ export function MarkTaskCompleted({
);
case "done":
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 (
<div className="overflow-hidden rounded-md border border-gray-200 dark:border-gray-800">
{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
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" />
{showReasoning ? "Hide reasoning" : "Show reasoning"}
</button>
{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}
</BasicMarkdownText>
)}
@ -185,10 +185,10 @@ export function MarkTaskIncomplete({
return (
<div className="overflow-hidden rounded-md border border-gray-200 dark:border-gray-800">
{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
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" />
{showReasoning ? "Hide reasoning" : "Show reasoning"}

View file

@ -1,7 +1,8 @@
"use client";
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 { BasicMarkdownText } from "../thread/markdown-text";
@ -29,9 +30,13 @@ export function TaskSummary({
);
case "done":
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 (
<div className="border-border overflow-hidden rounded-md border">
<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={
status === "done" && summaryText
? () => setExpanded(!expanded)
@ -68,36 +73,60 @@ export function TaskSummary({
{summaryText && (
<div
className={cn(
"border-t p-2",
"border-t border-l-4 p-2",
completed === false
? "border-amber-300 bg-amber-100/50 dark:border-amber-800 dark:bg-amber-900/50"
: "border-green-300 bg-green-100/50 dark:border-green-800 dark:bg-green-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-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
onClick={() => setShowSummary(!showSummary)}
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
? "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",
)}
>
<FileText className="h-3 w-3" />
{showSummary ? "Hide reflection" : "Show reflection"}
</button>
{showSummary && (
<BasicMarkdownText
<ChevronDown
className={cn(
"mt-1 text-xs",
completed === false
? "text-amber-700 dark:text-amber-300"
: "text-green-700 dark:text-green-300",
"h-3 w-3 transition-transform duration-200",
showSummary && "rotate-180",
)}
>
{summaryText}
</BasicMarkdownText>
)}
/>
<span>{showSummary ? "Hide reflection" : "Show reflection"}</span>
</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>

View file

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

View file

@ -6,9 +6,9 @@ export function RepositoryBranchSelectors() {
const [threadId] = useQueryState("threadId");
const chatStarted = !!threadId;
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 =
"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 (
<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 { Textarea } from "../ui/textarea";
import { Button } from "../ui/button";
import { MarkdownText } from "../thread/markdown-text";
interface PlanViewerProps {
planItems: PlanItem[];
@ -166,7 +167,7 @@ export function PlanViewer({
className="text-foreground"
/>
) : (
<p
<MarkdownText
className={cn("text-sm leading-relaxed", {
"text-foreground": status === "current",
"text-foreground/80":
@ -175,7 +176,7 @@ export function PlanViewer({
})}
>
{planItems[item.index].plan}
</p>
</MarkdownText>
)}
{/* 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">
Summary:
</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}
</p>
</MarkdownText>
</div>
)}
</div>

View file

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

View file

@ -102,10 +102,12 @@ export function TasksSidebar({
switch (state) {
case "completed":
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":
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:
return <Circle className="h-4 w-4 text-gray-400 dark:text-gray-500" />;
}
@ -140,7 +142,7 @@ export function TasksSidebar({
return (
<div
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",
)}
>
@ -161,7 +163,7 @@ export function TasksSidebar({
variant="ghost"
size="sm"
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" />
</Button>
@ -174,7 +176,7 @@ export function TasksSidebar({
Task
</label>
<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}
onChange={(e) => {
const newTaskIndex = taskPlan.tasks.findIndex(
@ -205,12 +207,12 @@ export function TasksSidebar({
Plan Revision
</label>
<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">
{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" />
</div>
@ -278,7 +280,7 @@ export function TasksSidebar({
{/* Filter controls */}
<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" />
<select
className="border-none bg-transparent text-xs outline-none dark:text-gray-300"
@ -312,11 +314,11 @@ export function TasksSidebar({
className={cn(
"rounded-lg border p-3",
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" &&
"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" &&
"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">
@ -340,11 +342,11 @@ export function TasksSidebar({
className={cn(
"rounded-full px-2 py-1 text-xs",
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" &&
"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" &&
"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"
@ -373,7 +375,7 @@ export function TasksSidebar({
<Button
variant="ghost"
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 ? (
<ChevronDown className="mr-1 h-3 w-3" />
@ -384,7 +386,7 @@ export function TasksSidebar({
</Button>
</CollapsibleTrigger>
<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>
{item.summary}
</BasicMarkdownText>
@ -412,7 +414,7 @@ export function TaskPlanView({ taskPlan, onTaskChange }: TaskPlanViewProps) {
if (taskPlan.tasks.length === 0) {
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">
No tasks available
</div>

View file

@ -20,7 +20,7 @@ import {
HoverCardContent,
HoverCardTrigger,
} from "@/components/ui/hover-card";
import { InlineMarkdownText } from "../thread/markdown-text";
import { InlineMarkdownText } from "@/components/thread/markdown-text";
interface ProgressBarProps {
taskPlan?: TaskPlan;
@ -39,7 +39,7 @@ export function ProgressBar({
return (
<div
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,
)}
>
@ -73,7 +73,7 @@ export function ProgressBar({
return (
<div
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,
)}
>
@ -105,7 +105,7 @@ export function ProgressBar({
const getSegmentColor = (state: string) => {
switch (state) {
case "completed":
return "bg-green-400 dark:bg-green-500";
return "bg-green-500/90 dark:bg-green-400/80";
case "current":
return "bg-blue-400 dark:bg-blue-500";
default:
@ -116,7 +116,7 @@ export function ProgressBar({
return (
<div
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,
)}
>
@ -140,11 +140,12 @@ export function ProgressBar({
</TooltipTrigger>
<TooltipContent
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">
<p className="font-medium">Plan Progress</p>
<p>
<p className="text-foreground font-medium">Plan Progress</p>
<p className="text-muted-foreground">
Shows the current progress of the AI agent's plan execution.
</p>
</div>
@ -165,7 +166,7 @@ export function ProgressBar({
variant="outline"
size="sm"
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" />
<span className="hidden sm:inline">Tasks</span>
@ -176,9 +177,9 @@ export function ProgressBar({
</div>
{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="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>
</div>
<div className="flex items-center gap-1">

View file

@ -22,7 +22,7 @@ export const ContentBlocksPreview: React.FC<ContentBlocksPreviewProps> = ({
}) => {
if (!blocks.length) return null;
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) => (
<MultimodalPreview
key={idx}

View file

@ -26,9 +26,14 @@ export const MultimodalPreview: React.FC<MultimodalPreviewProps> = ({
block.mime_type.startsWith("image/")
) {
const url = `data:${block.mime_type};base64,${block.data}`;
let imgClass: string = "rounded-md object-cover h-16 w-16 text-lg";
if (size === "sm") imgClass = "rounded-md object-cover h-10 w-10 text-base";
if (size === "lg") imgClass = "rounded-md object-cover h-24 w-24 text-xl";
let imgClass: string =
"rounded-md object-cover h-16 w-16 text-lg shadow-sm ring-1 ring-border";
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 (
<div className={cn("relative inline-block", className)}>
<Image
@ -41,11 +46,11 @@ export const MultimodalPreview: React.FC<MultimodalPreviewProps> = ({
{removable && (
<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}
aria-label="Remove image"
>
<XIcon className="h-4 w-4" />
<XIcon className="h-3 w-3" />
</button>
)}
</div>
@ -63,20 +68,20 @@ export const MultimodalPreview: React.FC<MultimodalPreviewProps> = ({
return (
<div
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,
)}
>
<div className="flex flex-shrink-0 flex-col items-start justify-start">
<File
className={cn(
"text-teal-700",
"text-primary",
size === "sm" ? "h-5 w-5" : "h-7 w-7",
)}
/>
</div>
<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" }}
>
{String(filename)}
@ -84,7 +89,7 @@ export const MultimodalPreview: React.FC<MultimodalPreviewProps> = ({
{removable && (
<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}
aria-label="Remove PDF"
>
@ -99,7 +104,7 @@ export const MultimodalPreview: React.FC<MultimodalPreviewProps> = ({
return (
<div
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,
)}
>
@ -108,7 +113,7 @@ export const MultimodalPreview: React.FC<MultimodalPreviewProps> = ({
{removable && (
<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}
aria-label="Remove file"
>

View file

@ -47,7 +47,7 @@ const CodeHeader: FC<CodeHeaderProps> = ({ language, code }) => {
};
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>
<TooltipIconButton
tooltip="Copy"
@ -196,7 +196,7 @@ const defaultComponents: any = {
pre: ({ className, ...props }: { className?: string }) => (
<pre
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,
)}
{...props}
@ -321,12 +321,17 @@ const InlineMarkdownTextImpl: FC<{ children: string; className?: string }> = ({
if (match) {
// If it's a code block, render as plain text to keep it inline
return (
<span className={cn("font-mono", className)}>{String(children)}</span>
<span className={cn("font-mono text-sm", className)}>
{String(children)}
</span>
);
}
return (
<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}
>
{children}

View file

@ -24,11 +24,11 @@ export function ToolCalls({
key={idx}
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">
{tc.name}
{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}
</code>
)}
@ -44,7 +44,7 @@ export function ToolCalls({
</td>
<td className="text-foreground/80 px-4 py-2 text-sm">
{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)}
</code>
) : (
@ -98,12 +98,12 @@ export function ToolResult({ message }: { message: ToolMessage }) {
return (
<div className="mx-auto grid grid-rows-[1fr_auto] gap-2">
<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">
{message.name ? (
<h3 className="text-foreground font-medium">
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}
</code>
</h3>
@ -111,14 +111,14 @@ export function ToolResult({ message }: { message: ToolMessage }) {
<h3 className="text-foreground font-medium">Tool Result</h3>
)}
{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}
</code>
)}
</div>
</div>
<motion.div
className="bg-muted min-w-full dark:bg-gray-700"
className="bg-muted min-w-full"
initial={false}
animate={{ height: "auto" }}
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">
{key}
</td>
<td className="text-foreground/80 px-4 py-2 text-sm">
<td className="text-foreground px-4 py-2 text-sm">
{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)}
</code>
) : (
@ -167,7 +167,7 @@ export function ToolResult({ message }: { message: ToolMessage }) {
</tbody>
</table>
) : (
<code className="text-foreground/80 block text-sm">
<code className="text-foreground block text-sm">
{displayedContent}
</code>
)}
@ -180,7 +180,7 @@ export function ToolResult({ message }: { message: ToolMessage }) {
parsedContent.length > 5)) && (
<motion.button
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 }}
whileHover={{ scale: 1.02 }}
whileTap={{ scale: 0.98 }}

View file

@ -1,7 +1,7 @@
"use client";
import { Button } from "@/components/ui/button";
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 { TerminalInput } from "./terminal-input";
import { useFileUpload } from "@/hooks/useFileUpload";
@ -12,7 +12,6 @@ import {
TooltipProvider,
TooltipTrigger,
} from "../ui/tooltip";
import { Label } from "../ui/label";
import { ContentBlocksPreview } from "../thread/ContentBlocksPreview";
import { ThemeToggle } from "../theme-toggle";
import { ThreadCard, ThreadCardLoading } from "./thread-card";
@ -32,6 +31,7 @@ import { useState, useMemo } from "react";
import { threadsToMetadata } from "@/lib/thread-utils";
import { Settings, BookOpen } from "lucide-react";
import NextLink from "next/link";
import { OpenSWELogoSVG } from "../icons/openswe";
function OpenSettingsButton() {
return (
@ -89,7 +89,6 @@ export function DefaultView({ threads, threadsLoading }: DefaultViewProps) {
const {
contentBlocks,
setContentBlocks,
handleFileUpload,
dropRef,
removeBlock,
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="flex items-center justify-between">
<div className="flex items-center gap-3">
<div className="h-2 w-2 rounded-full bg-green-500"></div>
<span className="text-muted-foreground font-mono text-sm">
Open SWE
</span>
<OpenSWELogoSVG
width={120}
height={18}
/>
</div>
<div className="flex items-center gap-4">
<InstallationSelector />
@ -148,7 +147,7 @@ export function DefaultView({ threads, threadsLoading }: DefaultViewProps) {
{/* Terminal Chat Input */}
<Card
className={cn(
"border-border bg-card py-0 dark:bg-gray-950",
"border-border bg-card py-0",
dragOver
? "border-primary border-2 border-dotted"
: "border border-solid",
@ -160,14 +159,6 @@ export function DefaultView({ threads, threadsLoading }: DefaultViewProps) {
blocks={contentBlocks}
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">
<TerminalInput
placeholder="Describe your coding task or ask a question..."
@ -183,27 +174,13 @@ export function DefaultView({ threads, threadsLoading }: DefaultViewProps) {
setAutoAcceptPlan={setAutoAccept}
/>
<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
variant={autoAccept ? "brand" : "ghost"}
variant={autoAccept ? "default" : "ghost"}
tooltip="Automatically accept the plan"
className={cn(
"transition-colors duration-200",
autoAccept
? "text-secondary"
? "bg-primary hover:bg-primary/90"
: "text-muted-foreground hover:text-foreground",
)}
onClick={() => setAutoAccept((prev) => !prev)}

View file

@ -1,13 +1,16 @@
"use client";
import { useState } from "react";
import { AnimatePresence, motion } from "framer-motion";
import {
AlertTriangle,
Loader2,
CheckCircle,
MessageSquare,
ChevronDown,
} from "lucide-react";
import { BasicMarkdownText } from "../thread/markdown-text";
import { cn } from "@/lib/utils";
type DiagnoseErrorActionProps = {
status: "loading" | "generating" | "done";
@ -31,7 +34,9 @@ export function DiagnoseErrorAction({
<Loader2 className="text-muted-foreground size-3.5 animate-spin" />
);
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 (
<div className="border-border overflow-hidden rounded-md border">
{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
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" />
{showReasoning ? "Hide reasoning" : "Show reasoning"}
<ChevronDown
className={cn(
"h-3 w-3 transition-transform duration-200",
showReasoning && "rotate-180",
)}
/>
<span>{showReasoning ? "Hide reasoning" : "Show reasoning"}</span>
</button>
{showReasoning && (
<BasicMarkdownText className="mt-1 text-xs font-normal text-blue-800 dark:text-blue-300">
{reasoningText}
</BasicMarkdownText>
)}
<AnimatePresence>
{showReasoning && (
<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="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>
)}

View file

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

View file

@ -41,7 +41,7 @@ function DevReadmePromptQuickAction({
return (
<Card
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">
<CardTitle className="text-foreground text-sm">
@ -71,7 +71,7 @@ export function QuickActions({ setQuickActionPrompt }: QuickActionsProps) {
/>
<Card
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">
<CardTitle className="text-foreground text-sm">

View file

@ -4,11 +4,10 @@ import type React from "react";
import { v4 as uuidv4 } from "uuid";
import { Dispatch, SetStateAction, useEffect, useState } from "react";
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 { Button } from "../ui/button";
import { useStream } from "@langchain/langgraph-sdk/react";
import { StreamMode } from "@langchain/langgraph-sdk";
import { useRouter } from "next/navigation";
import { useGitHubAppProvider } from "@/providers/GitHubApp";
import { GraphState } from "@open-swe/shared/open-swe/types";
@ -18,7 +17,6 @@ import { DEFAULT_CONFIG_KEY, useConfigStore } from "@/hooks/useConfigStore";
import {
API_KEY_REQUIRED_MESSAGE,
MANAGER_GRAPH_ID,
OPEN_SWE_STREAM_MODE,
} from "@open-swe/shared/constants";
import { ManagerGraphUpdate } from "@open-swe/shared/open-swe/manager/types";
import { useDraftStorage } from "@/hooks/useDraftStorage";
@ -106,7 +104,7 @@ export function TerminalInput({
},
ifNotExists: "create",
streamResumable: true,
streamMode: OPEN_SWE_STREAM_MODE as StreamMode[],
streamMode: ["values", "messages-tuple", "custom"],
},
);
@ -172,9 +170,9 @@ export function TerminalInput({
}, [draftToLoad, setMessage]);
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="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/70">@</span>
<span className="text-muted-foreground">github</span>
@ -191,12 +189,12 @@ export function TerminalInput({
disabled={disabled || !message.trim() || !selectedRepository}
size="icon"
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 ? (
<Loader2 className="size-4 animate-spin" />
<Loader2 className="h-4 w-4 animate-spin" />
) : (
<Send className="size-4" />
<ArrowUp className="size-4" />
)}
</Button>
</div>
@ -209,7 +207,7 @@ export function TerminalInput({
onKeyDown={handleKeyPress}
placeholder={placeholder}
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}
onPaste={onPaste}
/>

View file

@ -3,11 +3,11 @@ import {
CheckCircle,
GitBranch,
GitPullRequest,
Loader2,
AlertCircle,
Pause,
XCircle,
Clock,
Loader2,
} from "lucide-react";
import { Card, CardContent, CardHeader, CardTitle } from "../ui/card";
import { useRouter } from "next/navigation";
@ -76,19 +76,19 @@ export function ThreadCard({
const getStatusColor = (status: ThreadUIStatus) => {
switch (status) {
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":
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":
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":
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":
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":
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:
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 (
<Card
key={thread.id}
className="border-border bg-card hover:bg-muted cursor-pointer px-0 py-3 transition-shadow hover:shadow-lg dark:bg-gray-950"
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={() => {
router.push(`/chat/${thread.id}`);
}}
@ -137,7 +137,7 @@ export function ThreadCard({
<CardHeader>
<div className="flex items-start justify-between gap-2">
<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>
</CardTitle>
<div className="mt-1 flex items-center gap-1">
@ -150,9 +150,9 @@ export function ThreadCard({
<Badge
variant="secondary"
className={cn(
"text-xs",
"text-xs transition-all duration-300 group-hover:scale-105",
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),
)}
>
@ -160,7 +160,9 @@ export function ThreadCard({
{isStatusLoading ? (
<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">
{isStatusLoading ? "Loading..." : displayStatus}
@ -187,13 +189,13 @@ export function ThreadCard({
<Button
variant="ghost"
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) => {
e.stopPropagation();
window.open(thread.githubIssue!.url, "_blank");
}}
>
<Bug className="h-3 w-3" />
<Bug className="h-3 w-3 transition-colors duration-200" />
</Button>
)}
{thread.pullRequest && (
@ -201,15 +203,21 @@ export function ThreadCard({
variant="ghost"
size="sm"
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),
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) => {
e.stopPropagation();
window.open(thread.pullRequest!.url, "_blank");
}}
>
<GitPullRequest className="h-3 w-3" />
<GitPullRequest className="h-3 w-3 transition-colors duration-200" />
</Button>
)}
</div>
@ -221,7 +229,7 @@ export function ThreadCard({
export function ThreadCardLoading() {
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>
<div className="flex items-start justify-between">
<div className="min-w-0 flex-1">

View file

@ -47,7 +47,7 @@ export function ThreadSwitcher({
<Button
variant="outline"
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" />
<span className="hidden sm:inline">Switch Thread</span>
@ -55,7 +55,7 @@ export function ThreadSwitcher({
</SheetTrigger>
<SheetContent
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">
<SheetTitle className="text-foreground text-base">
@ -70,7 +70,7 @@ export function ThreadSwitcher({
router.push("/chat");
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"
>
<Plus className="h-3 w-3" />
@ -79,7 +79,7 @@ export function ThreadSwitcher({
{/* Current Thread */}
<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
</h3>
<ThreadCard thread={currentThread} />
@ -88,7 +88,7 @@ export function ThreadSwitcher({
{/* Other Threads */}
{otherThreads.length > 0 && (
<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
</h3>
<ScrollArea className="h-full">

View file

@ -1,7 +1,7 @@
"use client";
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";
interface ThreadViewLoadingProps {
@ -18,35 +18,35 @@ export function LoadingActionsCardContent() {
{Array.from({ length: 8 }, (_, i) => (
<div
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) ? (
<div className="bg-background space-y-1 rounded p-2 dark:bg-black">
<div className="bg-muted h-2 w-32 animate-pulse rounded dark:bg-gray-700"></div>
<div className="bg-muted h-2 w-48 animate-pulse rounded dark:bg-gray-700"></div>
<div className="bg-background space-y-1 rounded p-2">
<div className="bg-muted h-2 w-32 animate-pulse rounded"></div>
<div className="bg-muted h-2 w-48 animate-pulse rounded"></div>
</div>
) : (
<div className="flex items-center justify-between">
<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-3 w-40 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"></div>
</div>
<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-4 w-4 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"></div>
</div>
</div>
)}
</div>
))}
<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-7 w-16 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"></div>
</div>
<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 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"></div>
</div>
</div>
);
@ -54,12 +54,12 @@ export function LoadingActionsCardContent() {
export function LoadingActionsCard() {
return (
<Card className="border-border bg-card dark:bg-gray-950">
<Card className="border-border bg-card">
<CardHeader className="p-3">
<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="ml-auto flex items-center gap-1">
<Loader2 className="text-muted-foreground h-3 w-3 animate-spin" />
<div className="bg-muted h-4 w-32 animate-pulse rounded"></div>
<div className="ml-auto flex items-center gap-2">
<Loader2 className="h-3 w-3 animate-spin" />
<span className="text-muted-foreground text-xs">
Loading actions...
</span>
@ -77,7 +77,7 @@ export function LoadingChatSection() {
return (
<div className="flex-1 space-y-4 overflow-y-auto p-4">
<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" />
<span className="text-sm">Loading conversation...</span>
</div>
@ -89,18 +89,18 @@ export function LoadingChatSection() {
className="flex gap-3"
>
<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 className="flex-1 space-y-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-12 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"></div>
</div>
<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-3/4 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"></div>
{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>
@ -124,30 +124,30 @@ export function ThreadViewLoading({ onBackToHome }: ThreadViewLoadingProps) {
<ArrowLeft className="h-3 w-3" />
</Button>
<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-3 w-48 animate-pulse rounded 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"></div>
<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 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 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 />
<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="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="bg-muted h-3 w-1/3 animate-pulse rounded dark:bg-gray-700"></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/3 animate-pulse rounded"></div>
<div className="bg-muted h-3 w-1/2 animate-pulse rounded"></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 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>

View file

@ -346,7 +346,7 @@ export function ThreadView({
}
>
<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="programmer">Programmer</TabsTrigger>
</TabsList>
@ -390,7 +390,7 @@ export function ThreadView({
value="planner"
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">
<StickToBottom
className="absolute inset-0 h-full"
@ -403,7 +403,6 @@ export function ThreadView({
{plannerSession ? (
<div className="scrollbar-pretty-auto overflow-y-auto px-2">
<ActionsRenderer<PlannerGraphState>
taskPlan={programmerTaskPlan}
runId={plannerSession.runId}
customNodeEvents={customPlannerNodeEvents}
setCustomNodeEvents={
@ -437,7 +436,7 @@ export function ThreadView({
value="programmer"
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">
<StickToBottom
className="absolute inset-0 h-full"