mirror of
https://github.com/Sea-Haven-Industries/open-swe.git
synced 2026-10-03 20:13:21 +00:00
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:
parent
7a23f5fdcf
commit
9906dd4e2a
30 changed files with 528 additions and 374 deletions
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
|
|
@ -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"}
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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() {
|
||||||
|
|
|
||||||
|
|
@ -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">
|
||||||
|
|
|
||||||
28
apps/web/src/components/icons/openswe.tsx
Normal file
28
apps/web/src/components/icons/openswe.tsx
Normal file
File diff suppressed because one or more lines are too long
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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">
|
||||||
|
|
|
||||||
|
|
@ -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}
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
>
|
>
|
||||||
|
|
|
||||||
|
|
@ -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}
|
||||||
|
|
|
||||||
|
|
@ -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 }}
|
||||||
|
|
|
||||||
|
|
@ -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)}
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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">
|
||||||
|
|
|
||||||
|
|
@ -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}
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
|
|
@ -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">
|
||||||
|
|
|
||||||
|
|
@ -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">
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue