refactor(frontend): remove defaultExpanded prop from ToolAccordion components (#12054)

### Changes

- Removed `defaultExpanded` prop from `ToolAccordion` in CreateAgent,
EditAgent, RunAgent, and RunBlock components to streamline the code and
improve readability.

### Impact

- This refactor enhances maintainability by reducing complexity in the
component structure while preserving existing functionality.

### Changes 🏗️

- Removed conditional expansion logic from all tool components
- Simplified ToolAccordion implementation across all affected components

### Checklist 📋

#### For code changes:

- [x] I have clearly listed my changes in the PR description
- [x] I have made a test plan
- [x] I have tested my changes according to the test plan:
- [x] Create and run agents with various tools to verify accordion
behavior works correctly
    - [x] Verify that UI components expand and collapse as expected
    - [x] Test with different output types to ensure proper rendering

---------

Co-authored-by: Ubbe <hi@ubbe.dev>
Co-authored-by: Lluis Agusti <hi@llu.lu>
This commit is contained in:
Abhimanyu Yadav
2026-02-10 21:52:01 +05:30
committed by GitHub
parent 017a00af46
commit 4df5b7bde7
11 changed files with 79 additions and 42 deletions

View File

@@ -1,11 +1,11 @@
"use client";
import { LoadingSpinner } from "@/components/atoms/LoadingSpinner/LoadingSpinner";
import { SidebarProvider } from "@/components/ui/sidebar";
import { ChatContainer } from "./components/ChatContainer/ChatContainer";
import { ChatSidebar } from "./components/ChatSidebar/ChatSidebar";
import { MobileDrawer } from "./components/MobileDrawer/MobileDrawer";
import { MobileHeader } from "./components/MobileHeader/MobileHeader";
import { ScaleLoader } from "./components/ScaleLoader/ScaleLoader";
import { useCopilotPage } from "./useCopilotPage";
export function CopilotPage() {
@@ -34,7 +34,11 @@ export function CopilotPage() {
} = useCopilotPage();
if (isUserLoading || !isLoggedIn) {
return <LoadingSpinner size="large" cover />;
return (
<div className="fixed inset-0 z-50 flex items-center justify-center bg-[#f8f8f9]">
<ScaleLoader className="text-neutral-400" />
</div>
);
}
return (

View File

@@ -143,10 +143,10 @@ export const ChatMessagesContainer = ({
return (
<Conversation className="min-h-0 flex-1">
<ConversationContent className="gap-6 px-3 py-6">
<ConversationContent className="flex min-h-screen flex-1 flex-col gap-6 px-3 py-6">
{isLoading && messages.length === 0 && (
<div className="flex flex-1 items-center justify-center">
<LoadingSpinner size="large" className="text-neutral-400" />
<div className="flex min-h-full flex-1 items-center justify-center">
<LoadingSpinner className="text-neutral-600" />
</div>
)}
{messages.map((message, messageIndex) => {

View File

@@ -121,8 +121,8 @@ export function ChatSidebar() {
className="mt-4 flex flex-col gap-1"
>
{isLoadingSessions ? (
<div className="flex items-center justify-center py-4">
<LoadingSpinner size="small" className="text-neutral-400" />
<div className="flex min-h-[30rem] items-center justify-center py-4">
<LoadingSpinner size="small" className="text-neutral-600" />
</div>
) : sessions.length === 0 ? (
<p className="py-4 text-center text-sm text-neutral-500">

View File

@@ -0,0 +1,35 @@
.loader {
width: 48px;
height: 48px;
display: inline-block;
position: relative;
}
.loader::after,
.loader::before {
content: "";
box-sizing: border-box;
width: 100%;
height: 100%;
border-radius: 50%;
background: currentColor;
position: absolute;
left: 0;
top: 0;
animation: animloader 2s linear infinite;
}
.loader::after {
animation-delay: 1s;
}
@keyframes animloader {
0% {
transform: scale(0);
opacity: 1;
}
100% {
transform: scale(1);
opacity: 0;
}
}

View File

@@ -0,0 +1,16 @@
import { cn } from "@/lib/utils";
import styles from "./ScaleLoader.module.css";
interface Props {
size?: number;
className?: string;
}
export function ScaleLoader({ size = 48, className }: Props) {
return (
<div
className={cn(styles.loader, className)}
style={{ width: size, height: size }}
/>
);
}

View File

@@ -49,12 +49,7 @@ interface Props {
part: CreateAgentToolPart;
}
function getAccordionMeta(output: CreateAgentToolOutput): {
icon: React.ReactNode;
title: React.ReactNode;
titleClassName?: string;
description?: string;
} {
function getAccordionMeta(output: CreateAgentToolOutput) {
const icon = <AccordionIcon />;
if (isAgentSavedOutput(output)) {
@@ -73,6 +68,7 @@ function getAccordionMeta(output: CreateAgentToolOutput): {
icon,
title: "Needs clarification",
description: `${questions.length} question${questions.length === 1 ? "" : "s"}`,
expanded: true,
};
}
if (
@@ -97,18 +93,23 @@ function getAccordionMeta(output: CreateAgentToolOutput): {
export function CreateAgentTool({ part }: Props) {
const text = getAnimationText(part);
const { onSend } = useCopilotChatActions();
const isStreaming =
part.state === "input-streaming" || part.state === "input-available";
const output = getCreateAgentToolOutput(part);
const isError =
part.state === "output-error" || (!!output && isErrorOutput(output));
const isOperating =
!!output &&
(isOperationStartedOutput(output) ||
isOperationPendingOutput(output) ||
isOperationInProgressOutput(output));
const progress = useAsymptoticProgress(isOperating);
const hasExpandableContent =
part.state === "output-available" &&
!!output &&
@@ -149,10 +150,7 @@ export function CreateAgentTool({ part }: Props) {
</div>
{hasExpandableContent && output && (
<ToolAccordion
{...getAccordionMeta(output)}
defaultExpanded={isOperating || isClarificationNeededOutput(output)}
>
<ToolAccordion {...getAccordionMeta(output)}>
{isOperating && (
<ContentGrid>
<ProgressBar value={progress} className="max-w-[280px]" />

View File

@@ -146,10 +146,7 @@ export function EditAgentTool({ part }: Props) {
</div>
{hasExpandableContent && output && (
<ToolAccordion
{...getAccordionMeta(output)}
defaultExpanded={isOperating || isClarificationNeededOutput(output)}
>
<ToolAccordion {...getAccordionMeta(output)}>
{isOperating && (
<ContentGrid>
<ProgressBar value={progress} className="max-w-[280px]" />

View File

@@ -61,14 +61,7 @@ export function RunAgentTool({ part }: Props) {
</div>
{hasExpandableContent && output && (
<ToolAccordion
{...getAccordionMeta(output)}
defaultExpanded={
isRunAgentExecutionStartedOutput(output) ||
isRunAgentSetupRequirementsOutput(output) ||
isRunAgentAgentDetailsOutput(output)
}
>
<ToolAccordion {...getAccordionMeta(output)}>
{isRunAgentExecutionStartedOutput(output) && (
<ExecutionStartedCard output={output} />
)}

View File

@@ -10,7 +10,7 @@ import {
WarningDiamondIcon,
} from "@phosphor-icons/react";
import type { ToolUIPart } from "ai";
import { SpinnerLoader } from "../../components/SpinnerLoader/SpinnerLoader";
import { OrbitLoader } from "../../components/OrbitLoader/OrbitLoader";
export interface RunAgentInput {
username_agent_slug?: string;
@@ -171,7 +171,7 @@ export function ToolIcon({
);
}
if (isStreaming) {
return <SpinnerLoader size={40} className="text-neutral-700" />;
return <OrbitLoader size={24} />;
}
return <PlayIcon size={14} weight="regular" className="text-neutral-400" />;
}
@@ -203,7 +203,7 @@ export function getAccordionMeta(output: RunAgentToolOutput): {
? output.status.trim()
: "started";
return {
icon: <SpinnerLoader size={28} className="text-neutral-700" />,
icon: <OrbitLoader size={28} className="text-neutral-700" />,
title: output.graph_name,
description: `Status: ${statusText}`,
};

View File

@@ -55,13 +55,7 @@ export function RunBlockTool({ part }: Props) {
</div>
{hasExpandableContent && output && (
<ToolAccordion
{...getAccordionMeta(output)}
defaultExpanded={
isRunBlockBlockOutput(output) ||
isRunBlockSetupRequirementsOutput(output)
}
>
<ToolAccordion {...getAccordionMeta(output)}>
{isRunBlockBlockOutput(output) && <BlockOutputCard output={output} />}
{isRunBlockSetupRequirementsOutput(output) && (

View File

@@ -8,7 +8,7 @@ import {
WarningDiamondIcon,
} from "@phosphor-icons/react";
import type { ToolUIPart } from "ai";
import { SpinnerLoader } from "../../components/SpinnerLoader/SpinnerLoader";
import { OrbitLoader } from "../../components/OrbitLoader/OrbitLoader";
export interface RunBlockInput {
block_id?: string;
@@ -120,7 +120,7 @@ export function ToolIcon({
);
}
if (isStreaming) {
return <SpinnerLoader size={40} className="text-neutral-700" />;
return <OrbitLoader size={24} />;
}
return <PlayIcon size={14} weight="regular" className="text-neutral-400" />;
}
@@ -149,7 +149,7 @@ export function getAccordionMeta(output: RunBlockToolOutput): {
if (isRunBlockBlockOutput(output)) {
const keys = Object.keys(output.outputs ?? {});
return {
icon: <SpinnerLoader size={32} className="text-neutral-700" />,
icon: <OrbitLoader size={24} className="text-neutral-700" />,
title: output.block_name,
description:
keys.length > 0