Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions ui/jest.setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,22 @@ global.ResizeObserver = class ResizeObserver {
// jsdom: cmdk scrolls selected items into view
Element.prototype.scrollIntoView = function scrollIntoView() {};

// jsdom: SidebarProvider / useIsMobile
Object.defineProperty(window, "matchMedia", {
writable: true,
configurable: true,
value: jest.fn().mockImplementation((query: string) => ({
matches: false,
media: query,
onchange: null,
addListener: jest.fn(),
removeListener: jest.fn(),
addEventListener: jest.fn(),
removeEventListener: jest.fn(),
dispatchEvent: jest.fn(),
})),
});
Comment on lines +35 to +48

// Mock next/router
jest.mock('next/router', () => ({
useRouter() {
Expand Down
2 changes: 1 addition & 1 deletion ui/src/components/chat/ChatInterface.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -911,7 +911,7 @@ export default function ChatInterface({ selectedAgentName, selectedNamespace, se
);
}
return (
<div className="w-full h-screen flex flex-col justify-center min-w-full items-center transition-all duration-300 ease-in-out">
<div className="flex h-screen w-full min-w-0 flex-col items-center justify-center transition-all duration-300 ease-in-out">
<div className="flex-1 w-full overflow-hidden relative">
<ScrollArea ref={containerRef} className="w-full h-full py-12">
<div className="flex flex-col space-y-5 px-4">
Expand Down
19 changes: 10 additions & 9 deletions ui/src/components/chat/ChatLayoutUI.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -108,17 +108,18 @@ export default function ChatLayoutUI({
agentSessions={sessions}
isLoadingSessions={isLoadingSessions}
/>
<main className="w-full max-w-6xl mx-auto px-4">
<ChatAgentProvider
agentType={currentAgent.agent.spec.type}
runInSandbox={currentAgent.workloadMode === "sandbox"}
substrateSandbox={isSubstrateSandboxAgent(currentAgent)}
>
{children}
</ChatAgentProvider>
<main className="flex min-h-svh min-w-0 flex-1 flex-col overflow-x-hidden px-4">
<div className="mx-auto flex w-full min-w-0 max-w-6xl flex-1 flex-col">
<ChatAgentProvider
agentType={currentAgent.agent.spec.type}
runInSandbox={currentAgent.workloadMode === "sandbox"}
substrateSandbox={isSubstrateSandboxAgent(currentAgent)}
>
{children}
</ChatAgentProvider>
</div>
</main>
<AgentDetailsSidebar
selectedAgentName={agentName}
currentAgent={currentAgent}
allTools={convertedTools}
/>
Expand Down
3 changes: 0 additions & 3 deletions ui/src/components/sidebars/AgentDetailsSidebar.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -154,23 +154,20 @@ const mockTools: ToolsResponse[] = [

export const AgentWithTools: Story = {
args: {
selectedAgentName: "kagent/momus-gpt",
currentAgent: mockAgent,
allTools: mockTools,
},
};

export const AgentWithNoTools: Story = {
args: {
selectedAgentName: "kagent/simple-agent",
currentAgent: mockAgentNoTools,
allTools: [],
},
};

export const BYOAgent: Story = {
args: {
selectedAgentName: "kagent/custom-agent",
currentAgent: mockBYOAgent,
allTools: [],
},
Expand Down
52 changes: 35 additions & 17 deletions ui/src/components/sidebars/AgentDetailsSidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,11 @@ import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/comp
import { Badge } from "@/components/ui/badge";

interface AgentDetailsSidebarProps {
selectedAgentName: string;
currentAgent: AgentResponse;
allTools: ToolsResponse[];
}

export function AgentDetailsSidebar({ selectedAgentName, currentAgent, allTools }: AgentDetailsSidebarProps) {
export function AgentDetailsSidebar({ currentAgent, allTools }: AgentDetailsSidebarProps) {
const [toolDescriptions, setToolDescriptions] = useState<Record<string, string>>({});
const [expandedTools, setExpandedTools] = useState<Record<string, boolean>>({});
const [availableAgents, setAvailableAgents] = useState<AgentResponse[]>([]);
Expand Down Expand Up @@ -233,28 +232,47 @@ export function AgentDetailsSidebar({ selectedAgentName, currentAgent, allTools
// Declarative agents (including SandboxAgent with declarative spec) share model-backed config.
const isDeclarativeLikeAgent = selectedTeam?.agent.spec.type === "Declarative";

const agentNamespace = selectedTeam.agent.metadata.namespace ?? "";
const agentName = selectedTeam.agent.metadata.name ?? "";
const agentRef = `${agentNamespace}/${agentName}`;
const editHref = `/agents/new?${new URLSearchParams({
edit: "true",
name: agentName,
namespace: agentNamespace,
}).toString()}`;

return (
<>
<Sidebar side={"right"} collapsible="offcanvas">
<SidebarHeader>Agent Details</SidebarHeader>
<SidebarHeader className="flex flex-row items-center justify-between gap-2">
<span className="text-sm font-semibold leading-none">Agent Details</span>
<Button
variant="ghost"
size="icon"
className="h-7 w-7 shrink-0"
asChild
aria-label={`Edit agent ${agentRef}`}
>
<Link href={editHref}>
<Edit className="h-3.5 w-3.5" />
</Link>
</Button>
</SidebarHeader>
<SidebarContent>
<ScrollArea>
<SidebarGroup>
<div className="flex items-center justify-between px-2 mb-1">
<SidebarGroupLabel className="font-bold mb-0 p-0">
{selectedTeam?.agent.metadata.namespace}/{selectedTeam?.agent.metadata.name} {selectedTeam?.model && `(${selectedTeam?.model})`}
</SidebarGroupLabel>
<Button
variant="ghost"
size="icon"
className="h-7 w-7"
asChild
aria-label={`Edit agent ${selectedTeam?.agent.metadata.namespace}/${selectedTeam?.agent.metadata.name}`}
<div className="min-w-0 px-2 mb-1">
<SidebarGroupLabel
className="font-bold mb-0 block w-full truncate p-0"
title={selectedTeam?.model ? `${agentRef} (${selectedTeam.model})` : agentRef}
>
<Link href={`/agents/new?edit=true&name=${selectedAgentName}&namespace=${currentAgent.agent.metadata.namespace}`}>
<Edit className="h-3.5 w-3.5" />
</Link>
</Button>
{agentRef}
</SidebarGroupLabel>
{selectedTeam?.model && (
<p className="mt-0.5 truncate text-xs text-muted-foreground" title={selectedTeam.model}>
{selectedTeam.model}
</p>
)}
</div>
<p className="text-sm flex px-2 text-muted-foreground">{selectedTeam?.agent.spec.description}</p>
</SidebarGroup>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
/**
* @jest-environment jsdom
*/
import { render, screen } from "@testing-library/react";
import { AgentDetailsSidebar } from "@/components/sidebars/AgentDetailsSidebar";
import { SidebarProvider } from "@/components/ui/sidebar";
import type { AgentResponse } from "@/types";

jest.mock("@/app/actions/agents", () => ({
getAgents: jest.fn().mockResolvedValue({ data: [] }),
}));

function renderSidebar(currentAgent: AgentResponse) {
return render(
<SidebarProvider defaultOpen>
<AgentDetailsSidebar
currentAgent={currentAgent}
allTools={[]}
/>
</SidebarProvider>,
);
}

const longNameAgent: AgentResponse = {
id: 1,
agent: {
metadata: {
name: "test-my-agent-qwen7b",
namespace: "ak-poc-testing",
},
spec: {
description: "testing me agent bro",
type: "Declarative",
},
},
model: "vllm/Qwen/Qwen2.5-7B-Instruct",
modelProvider: "openai",
modelConfigRef: "ak-poc-testing/qwen7b",
deploymentReady: true,
accepted: true,
tools: [
{
type: "Agent",
agent: {
name: "k8s-agent",
namespace: "ak-poc-testing",
kind: "Agent",
apiGroup: "kagent.dev",
},
},
],
};

describe("AgentDetailsSidebar", () => {
it("shows the edit control in the header for agents with long names and model strings", () => {
renderSidebar(longNameAgent);

const editLink = screen.getByRole("link", {
name: "Edit agent ak-poc-testing/test-my-agent-qwen7b",
});
expect(editLink).toBeInTheDocument();
expect(editLink).toHaveAttribute(
"href",
"/agents/new?edit=true&name=test-my-agent-qwen7b&namespace=ak-poc-testing",
);
});

it("renders agent ref and model on separate truncated lines", () => {
renderSidebar(longNameAgent);

expect(screen.getByText("ak-poc-testing/test-my-agent-qwen7b")).toBeInTheDocument();
expect(screen.getByText("vllm/Qwen/Qwen2.5-7B-Instruct")).toBeInTheDocument();
});
});