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
33 changes: 28 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,26 +9,50 @@ If the term "Deep Agents" is new to you, check out these videos!

And check out this [video](https://youtu.be/0CE_BhdnZZI) for a walkthrough of this UI.

## Features

- **Multi-Agent Support**: Switch between different AI agents with completely isolated contexts
- **Agent-Scoped State**: Each agent maintains its own threads, todos, and files
- **Real-time Streaming**: Live updates from agent execution
- **Task Management**: Visual todo lists and file tracking per agent
- **Sub-Agent Visualization**: See spawned sub-agents and their status

## Configuration

### Connecting to a Local LangGraph Server

Create a `.env.local` file and set two variables
Create a `.env.local` file and set the server URL:

```env
NEXT_PUBLIC_DEPLOYMENT_URL="http://127.0.0.1:2024" # Or your server URL
NEXT_PUBLIC_AGENT_ID=<your agent ID from langgraph.json>
```

### Connecting to a Production LangGraph Deployment on LGP

Create a `.env.local` file and set three variables
Create a `.env.local` file and set the deployment URL and API key:

```env
NEXT_PUBLIC_DEPLOYMENT_URL="your agent server URL"
NEXT_PUBLIC_AGENT_ID=<your agent ID from langgraph.json>
NEXT_PUBLIC_LANGSMITH_API_KEY=<langsmith-api-key>
```

### Managing Available Agents

Agents are configured in `src/lib/agents/config.ts`. You can modify the `AVAILABLE_AGENTS` array to add, remove, or update agent configurations:

```typescript
export const AVAILABLE_AGENTS: Agent[] = [
{
id: "deepagent",
name: "Deep Agent",
description: "General purpose AI agent for complex tasks",
color: "#3B82F6",
},
// Add more agents here...
];
```

Each agent must have a corresponding agent ID configured in your LangGraph server.

Once you have your environment variables set, install all dependencies and run your app.

Expand All @@ -37,5 +61,4 @@ npm install
npm run dev
```


Open [http://localhost:3000](http://localhost:3000) to test out your deep agent!
169 changes: 169 additions & 0 deletions src/app/components/AgentSelector/AgentSelector.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
.container {
position: relative;
z-index: 1000;
background-color: hsl(var(--background));
display: inline-block;
}

.trigger {
display: flex;
align-items: center;
justify-content: space-between;
min-width: 200px;
padding: 8px 12px;
border: 1px solid hsl(var(--border));
border-radius: 6px;
background-color: hsl(var(--background));
cursor: pointer;
transition: all 0.2s ease;
position: relative;

&:hover {
background-color: hsl(var(--accent));
}

&:focus-visible {
outline: 2px solid hsl(var(--ring));
outline-offset: 2px;
}

&:disabled {
opacity: 0.5;
cursor: not-allowed;
}
}

.currentAgent {
display: flex;
align-items: center;
gap: 8px;
}

.agentColor {
width: 8px;
height: 8px;
border-radius: 50%;
flex-shrink: 0;
}

.icon {
width: 16px;
height: 16px;
flex-shrink: 0;
}

.agentName {
font-weight: 500;
font-size: 14px;
}

.chevron {
width: 16px;
height: 16px;
transition: transform 0.2s ease;

&.open {
transform: rotate(180deg);
}
}

.overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 9998;
background: transparent;
}

.dropdown {
position: absolute;
top: calc(100% + 4px);
right: 0;
left: auto;
z-index: 9999;
border: 1px solid hsl(var(--border));
border-radius: 6px;
background-color: #303030;
box-shadow:
0 10px 25px -5px rgb(0 0 0 / 0.1),
0 10px 10px -5px rgb(0 0 0 / 0.04);
max-height: 300px;
overflow-y: auto;
animation: dropdown-in 0.15s ease-out;
transform-origin: top right;
min-width: 260px;
width: max-content;
max-width: 90vw;
}

@keyframes dropdown-in {
from {
opacity: 0;
transform: scaleY(0.95) translateY(-4px);
}
to {
opacity: 1;
transform: scaleY(1) translateY(0);
}
}

.dropdownContent {
padding: 4px;
}

.agentOption {
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
padding: 8px 12px;
border: none;
border-radius: 4px;
background: none;
cursor: pointer;
transition: all 0.15s ease;
text-align: left;

&:hover {
background-color: #bdbdbd;
color: #303030;
}

&:focus-visible {
outline: 2px solid hsl(var(--ring));
outline-offset: -2px;
}

&.selected {
background-color: hsl(var(--accent));
color: hsl(var(--accent-foreground));
}
}

.agentInfo {
display: flex;
align-items: center;
gap: 8px;
flex: 1;
}

.agentDetails {
display: flex;
flex-direction: column;
gap: 2px;
}

.agentDescription {
font-size: 12px;
color: hsl(var(--muted-foreground));
line-height: 1.3;
}

.checkIcon {
width: 16px;
height: 16px;
color: hsl(var(--primary));
flex-shrink: 0;
}
93 changes: 93 additions & 0 deletions src/app/components/AgentSelector/AgentSelector.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
"use client";

import React, { useState } from "react";
import { ChevronDown, Bot, Check } from "lucide-react";

import { Button } from "@/components/ui/button";

import styles from "./AgentSelector.module.scss";
import type { Agent } from "../../types/types";

interface AgentSelectorProps {
availableAgents: Agent[];
currentAgent: Agent;
onAgentChange: (agent: Agent) => void;
disabled?: boolean;
}

export const AgentSelector: React.FC<AgentSelectorProps> = ({
availableAgents,
currentAgent,
onAgentChange,
disabled = false,
}) => {
const [isOpen, setIsOpen] = useState(false);

const handleAgentSelect = (agent: Agent) => {
if (agent.id !== currentAgent.id) {
onAgentChange(agent);
}
setIsOpen(false);
};

return (
<div className={styles.container}>
<Button
variant="outline"
onClick={() => setIsOpen(!isOpen)}
disabled={disabled}
className={styles.trigger}
>
<div className={styles.currentAgent}>
<div
className={styles.agentColor}
style={{ backgroundColor: currentAgent.color }}
/>
<Bot className={styles.icon} />
<span className={styles.agentName}>{currentAgent.name}</span>
</div>
<ChevronDown
className={`${styles.chevron} ${isOpen ? styles.open : ""}`}
/>
</Button>

{isOpen && (
<div className={styles.dropdown}>
<div className={styles.dropdownContent}>
{availableAgents.map((agent) => (
<button
key={agent.id}
className={`${styles.agentOption} ${
agent.id === currentAgent.id ? styles.selected : ""
}`}
onClick={() => handleAgentSelect(agent)}
>
<div className={styles.agentInfo}>
<div
className={styles.agentColor}
style={{ backgroundColor: agent.color }}
/>
<div className={styles.agentDetails}>
<span className={styles.agentName}>{agent.name}</span>
{agent.description && (
<span className={styles.agentDescription}>
{agent.description}
</span>
)}
</div>
</div>
{agent.id === currentAgent.id && (
<Check className={styles.checkIcon} />
)}
</button>
))}
</div>
</div>
)}

{isOpen && (
<div className={styles.overlay} onClick={() => setIsOpen(false)} />
)}
</div>
);
};
Loading