For the complete documentation index, see llms.txt. Markdown variants are available by appending .md to documentation URLs. Homepage markdown is available at /llms.md/content.md. An agent skill is available at /.well-known/agent-skills/site-skill.md.

Multi Agent Chat 01

A three-column workspace for chatting with a team of agents.

Installation

Set up the @cobalt registry as described in Installation, then add the block:

$ pnpm dlx shadcn@latest add @cobalt/multi-agent-chat-01

The block is made of the existing agent-creator-01, settings-modal-01 and composer pieces. It uses local sample agents and scripted replies. Agents, conversations and edits are saved in localStorage.

Source

"use client";

import {
  RiAddLine,
  RiArrowDownSLine,
  RiCustomerServiceLine,
  RiCheckLine,
  RiDeleteBinLine,
  RiEraserLine,
  RiLayoutRightLine,
  RiChat3Line,
  RiMoonLine,
  RiMoreLine,
  RiSunLine,
  RiSearchLine,
  RiSettings3Line,
  RiUploadLine,
  RiShieldCheckLine,
  RiStoreLine,
  RiGroupLine,
  RiRobot2Line,
} from "@remixicon/react";
import { cn } from "cn";
import Link from "next/link";
import * as React from "react";

import { AgentCreator } from "@/components/blocks/agent-creator-01/components/agent-creator";
import { ComposerModelPicker } from "@/components/blocks/composer-01/components/composer-model-picker";
import { VoiceButton } from "@/components/blocks/composer-01/components/voice-button";
import {
  AgentAvatar,
  GroupAvatar,
} from "@/components/blocks/multi-agent-chat-01/components/multi-agent-chat-avatar";
import type {
  ChatAgent,
  ChatConversation,
} from "@/components/blocks/multi-agent-chat-01/components/multi-agent-chat-data";
import { MultiAgentChatGroupDialog } from "@/components/blocks/multi-agent-chat-01/components/multi-agent-chat-group-dialog";
import { MultiAgentChatMarketplace } from "@/components/blocks/multi-agent-chat-01/components/multi-agent-chat-marketplace";
import { useMultiAgentChat } from "@/components/blocks/multi-agent-chat-01/components/multi-agent-chat-store";
import { SettingsModal } from "@/components/blocks/settings-modal-01/components/settings-modal";
import type { Page as SettingsPage } from "@/components/blocks/settings-modal-01/components/settings-modal";
import type { DashboardNavItem } from "@/components/blocks/sidebar-01/components/dashboard-sidebar";
import { Avatar, AvatarFallback } from "@/components/ui/avatar";
import { Bubble, BubbleContent } from "@/components/ui/bubble";
import { Button } from "@/components/ui/button";
import { Card } from "@/components/ui/card";
import {
  Composer,
  ComposerInput,
  ComposerSend,
  ComposerShell,
} from "@/components/ui/composer";
import {
  DropdownMenu,
  DropdownMenuContent,
  DropdownMenuGroup,
  DropdownMenuItem,
  DropdownMenuLabel,
  DropdownMenuSeparator,
  DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import {
  Empty,
  EmptyDescription,
  EmptyHeader,
  EmptyMedia,
  EmptyTitle,
} from "@/components/ui/empty";
import {
  InputGroupAddon,
  InputGroupButton,
} from "@/components/ui/input-group";
import { Message, MessageContent } from "@/components/ui/message";
import {
  MessageScroller,
  MessageScrollerButton,
  MessageScrollerContent,
  MessageScrollerItem,
  MessageScrollerProvider,
  MessageScrollerViewport,
} from "@/components/ui/message-scroller";
import {
  Popover,
  PopoverContent,
  PopoverTrigger,
} from "@/components/ui/popover";
import { SearchField } from "@/components/ui/search-field";
import { Sidebar, SidebarProvider } from "@/components/ui/sidebar";

const enter = "multi-agent-chat-01-enter";
const models = ["Fable 5", "Fable 4", "Swift"];
const starters = [
  "Summarize what we decided",
  "Suggest the next three steps",
  "Review this with fresh eyes",
];

function titleOf(conversation: ChatConversation, agents: ChatAgent[]) {
  if (conversation.title) {
    return conversation.title;
  }
  const members = conversation.agentIds.flatMap((id) => {
    const agent = agents.find((item) => item.id === id);
    return agent ? [agent] : [];
  });
  return conversation.group
    ? members.map((agent) => agent.label || agent.name).join(" and ")
    : (members[0]?.name ?? "Conversation");
}

/**
 * A three-column agent workspace: conversations, a chat where several agents
 * can answer in turn, and a live avatar editor. Everything is sample data and
 * scripted replies; state is kept in localStorage.
 */
export function MultiAgentChat({
  className,
  popupClassName,
  pages,
  theme,
  onThemeChange,
}: {
  className?: string;
  popupClassName?: string;
  /** Other pages to list in the account menu, as a way back out of the app. */
  pages?: DashboardNavItem[];
  /** With `onThemeChange`, adds an appearance switch to the account menu and the settings. */
  theme?: "light" | "dark";
  onThemeChange?: (theme: "light" | "dark" | "system") => void;
}) {
  const chat = useMultiAgentChat();
  const rootRef = React.useRef<HTMLDivElement>(null);
  const [wide, setWide] = React.useState(false);
  const [inspector, setInspector] = React.useState<"auto" | "open" | "closed">(
    "auto"
  );
  const [inspectedId, setInspectedId] = React.useState<string | null>(null);
  const [drawer, setDrawer] = React.useState(false);
  const [searching, setSearching] = React.useState(false);
  const [query, setQuery] = React.useState("");
  const [draft, setDraft] = React.useState("");
  const [model, setModel] = React.useState(models[0]);
  const [bypass, setBypass] = React.useState(false);
  const [listening, setListening] = React.useState(false);
  const [plusOpen, setPlusOpen] = React.useState(false);
  const [groupOpen, setGroupOpen] = React.useState(false);
  const [settings, setSettings] = React.useState<{
    open: boolean;
    page: SettingsPage;
  }>({ open: false, page: "general" });
  const [copied, setCopied] = React.useState(false);

  React.useEffect(() => {
    const element = rootRef.current;
    if (!element) {
      return;
    }
    const observer = new ResizeObserver(([entry]) =>
      setWide(entry.contentRect.width >= 1024)
    );
    observer.observe(element);
    return () => observer.disconnect();
  }, []);

  React.useEffect(() => {
    if (!copied) {
      return;
    }
    const timer = setTimeout(() => setCopied(false), 1500);
    return () => clearTimeout(timer);
  }, [copied]);

  const { agents, conversations } = chat;
  const active =
    conversations.find((item) => item.id === chat.activeId) ?? conversations[0];
  const members = active.agentIds.flatMap((id) => {
    const agent = agents.find((item) => item.id === id);
    return agent ? [agent] : [];
  });
  const inspected =
    members.find((agent) => agent.id === inspectedId) ?? members[0];
  const inspectorVisible =
    inspector === "open" || (inspector === "auto" && wide);
  const thinkingAgent =
    chat.thinking?.conversationId === active.id
      ? agents.find((agent) => agent.id === chat.thinking?.agentId)
      : undefined;
  const needle = query.trim().toLowerCase();

  const preview = (conversation: ChatConversation) => {
    if (chat.thinking?.conversationId === conversation.id) {
      return "Thinking...";
    }
    const last = conversation.messages.at(-1);
    if (last) {
      return last.text;
    }
    const first = agents.find((agent) => agent.id === conversation.agentIds[0]);
    return conversation.group
      ? "A new group chat"
      : (first?.description ?? "Start a conversation");
  };
  const visible = conversations.filter(
    (conversation) =>
      !needle ||
      `${titleOf(conversation, agents)} ${preview(conversation)}`
        .toLowerCase()
        .includes(needle)
  );

  const openSettings = (page: "general" | "marketplace") =>
    setSettings({ open: true, page });

  const select = (id: string) => {
    chat.setActive(id);
    setInspectedId(null);
    setDrawer(false);
  };

  const share = () => {
    const text = active.messages
      .map((message) => {
        const who =
          message.role === "user"
            ? "You"
            : (agents.find((agent) => agent.id === message.agentId)?.name ??
              "Agent");
        return `${who}: ${message.text}`;
      })
      .join("\n");
    void navigator.clipboard?.writeText(text).catch(() => setCopied(false));
    setCopied(true);
  };

  const toggleInspector = () =>
    setInspector(inspectorVisible ? "closed" : "open");

  return (
    <div
      ref={rootRef}
      className={cn(
        "@container relative flex h-[620px] min-h-0 w-full gap-4 overflow-clip text-foreground",
        className
      )}
    >
      {drawer && (
        <div className="absolute inset-0 z-10 rounded-[16px] bg-foreground/20 @3xl:hidden">
          <Button
            variant="unstyled"
            radius={16}
            aria-label="Close conversations"
            className="size-full"
            onClick={() => setDrawer(false)}
          />
        </div>
      )}
      <div
        className={cn(
          "absolute inset-y-0 left-0 z-20 shrink-0 transition-transform duration-200 ease-out motion-reduce:transition-none @3xl:static @3xl:translate-x-0",
          drawer ? "translate-x-0" : "-translate-x-[110%]"
        )}
      >
        <SidebarProvider
          persistState={false}
          keyboardShortcut={false}
          style={{ "--sidebar-width": "260px" } as React.CSSProperties}
          className="h-full min-h-0 w-auto"
        >
          <Sidebar collapsible="none" variant="floating" className="shrink-0">
            <div className="flex min-h-0 flex-1 flex-col p-2">
              <div className="flex h-9 items-center gap-2">
                {searching ? (
                  <SearchField
                    aria-label="Search conversations"
                    placeholder="Search conversations"
                    size="sm"
                    autoFocus
                    fieldClassName="w-full"
                    className="w-full"
                    value={query}
                    onChange={setQuery}
                    onClear={() => {
                      setQuery("");
                      setSearching(false);
                    }}
                    onKeyDown={(event) => {
                      if (event.key === "Escape") {
                        setQuery("");
                        setSearching(false);
                      }
                    }}
                  />
                ) : (
                  <>
                    <Button
                      variant="muted"
                      shape="pill"
                      className="flex-1"
                      tooltip="Search conversations"
                      aria-label="Search conversations"
                      onClick={() => setSearching(true)}
                    >
                      <RiSearchLine />
                    </Button>
                    <Button
                      variant="muted"
                      shape="pill"
                      className="flex-1"
                      tooltip="Marketplace"
                      aria-label="Marketplace"
                      onClick={() => openSettings("marketplace")}
                    >
                      <RiStoreLine />
                    </Button>
                    <Popover open={plusOpen} onOpenChange={setPlusOpen}>
                      <PopoverTrigger
                        render={
                          <Button
                            shape="pill"
                            className="flex-1"
                            tooltip="New chat"
                            aria-label="New chat"
                          />
                        }
                      >
                        <RiAddLine />
                      </PopoverTrigger>
                      <PopoverContent
                        align="start"
                        size="xs"
                        className={cn("w-72", popupClassName)}
                      >
                        <Button
                          variant="ghost"
                          className="h-auto justify-start text-left whitespace-normal"
                          onClick={() => {
                            setPlusOpen(false);
                            chat.createBot();
                            setInspector("open");
                            setDrawer(false);
                          }}
                        >
                          <span className="flex items-center gap-3 py-1.5">
                            {agents[5] ? (
                              <AgentAvatar
                                agent={agents[5]}
                                className="size-9"
                              />
                            ) : (
                              <RiRobot2Line className="size-5" aria-hidden />
                            )}
                            <span className="min-w-0">
                              <span className="block text-sm font-medium">
                                Create a new bot
                              </span>
                              <span className="block text-xs font-normal text-muted-foreground">
                                Design its look and voice
                              </span>
                            </span>
                          </span>
                        </Button>
                        <Button
                          variant="ghost"
                          className="h-auto justify-start text-left whitespace-normal"
                          onClick={() => {
                            setPlusOpen(false);
                            setGroupOpen(true);
                          }}
                        >
                          <span className="flex items-center gap-3 py-1.5">
                            {agents.length > 1 ? (
                              <GroupAvatar
                                agents={[
                                  agents[3] ?? agents[0],
                                  agents[4] ?? agents[1],
                                ]}
                                className="size-9"
                              />
                            ) : (
                              <RiGroupLine className="size-5" aria-hidden />
                            )}
                            <span className="min-w-0">
                              <span className="block text-sm font-medium">
                                Chat with your agents
                              </span>
                              <span className="block text-xs font-normal text-muted-foreground">
                                Start a group conversation
                              </span>
                            </span>
                          </span>
                        </Button>
                      </PopoverContent>
                    </Popover>
                  </>
                )}
              </div>
              <nav
                aria-label="Conversations"
                className="mt-2 flex min-h-0 flex-1 flex-col gap-0.5 overflow-y-auto"
              >
                {visible.map((conversation, index) => {
                  const people = conversation.agentIds.flatMap((id) => {
                    const agent = agents.find((item) => item.id === id);
                    return agent ? [agent] : [];
                  });
                  const current = conversation.id === active.id;
                  return (
                    <div
                      key={conversation.id}
                      style={
                        {
                          "--delay": `${Math.min(index, 8) * 40}ms`,
                        } as React.CSSProperties
                      }
                      className={cn("shrink-0", enter)}
                    >
                      <Button
                        variant={current ? "secondary" : "ghost"}
                        radius={12}
                        aria-current={current ? "true" : undefined}
                        onClick={() => select(conversation.id)}
                        className="h-auto w-full justify-start text-left"
                      >
                        <span className="flex min-w-0 flex-1 items-center gap-3 py-2">
                          {conversation.group ? (
                            <GroupAvatar agents={people} className="size-10" />
                          ) : (
                            <AgentAvatar
                              agent={people[0]}
                              className="size-10"
                            />
                          )}
                          <span className="min-w-0 flex-1">
                            <span className="block truncate text-sm font-medium">
                              {titleOf(conversation, agents)}
                            </span>
                            <span
                              className={cn(
                                "block truncate text-sm font-normal text-muted-foreground/70",
                                chat.thinking?.conversationId ===
                                  conversation.id &&
                                  "shimmer motion-reduce:shimmer-none"
                              )}
                            >
                              {preview(conversation)}
                            </span>
                          </span>
                        </span>
                      </Button>
                    </div>
                  );
                })}
                {visible.length === 0 && (
                  <Empty variant="ghost">
                    <EmptyHeader>
                      <EmptyDescription>
                        No conversations found.
                      </EmptyDescription>
                    </EmptyHeader>
                  </Empty>
                )}
              </nav>
              <div className="mt-2 flex shrink-0 flex-col gap-1">
                <Button
                  variant="text"
                  className="justify-start gap-2"
                  nativeButton={false}
                  render={
                    // Base UI renders the Button children into this anchor.
                    // oxlint-disable-next-line jsx-a11y/anchor-has-content
                    <a href="mailto:support@example.com" />
                  }
                >
                  <RiCustomerServiceLine /> Support
                </Button>
                <Button
                  variant="text"
                  className="justify-start gap-2"
                  onClick={() => openSettings("general")}
                >
                  <RiSettings3Line /> Settings
                </Button>
                <DropdownMenu>
                  <DropdownMenuTrigger
                    aria-label="Workspace menu"
                    render={
                      <Button
                        variant="muted"
                        radius={12}
                        className="group mt-1 h-auto w-full justify-start text-left"
                      />
                    }
                  >
                    <span className="flex min-w-0 flex-1 items-center gap-2 py-1.5">
                      <Avatar color="primary">
                        <AvatarFallback>N</AvatarFallback>
                      </Avatar>
                      <span className="min-w-0 flex-1">
                        <span className="block truncate text-sm font-medium text-foreground">
                          Northstar team
                        </span>
                        <span className="block truncate text-sm font-normal text-muted-foreground">
                          hello@example.com
                        </span>
                      </span>
                      <span className="flex size-6 shrink-0 items-center justify-center rounded-[6px] bg-surface-strong text-muted-foreground">
                        <RiArrowDownSLine className="size-4 transition-transform duration-150 ease-out group-aria-expanded:rotate-180 motion-reduce:transition-none" />
                      </span>
                    </span>
                  </DropdownMenuTrigger>
                  <DropdownMenuContent
                    side="top"
                    align="start"
                    className={cn("w-56", popupClassName)}
                  >
                    {pages && pages.length > 0 && (
                      <>
                        <DropdownMenuGroup>
                          <DropdownMenuLabel>Pages</DropdownMenuLabel>
                          {pages.map(({ key, label, href, icon: Icon }) => (
                            <DropdownMenuItem
                              key={key}
                              render={<Link href={href} />}
                            >
                              <Icon /> {label}
                            </DropdownMenuItem>
                          ))}
                        </DropdownMenuGroup>
                        <DropdownMenuSeparator />
                      </>
                    )}
                    <DropdownMenuGroup>
                      <DropdownMenuItem onClick={() => openSettings("general")}>
                        <RiSettings3Line /> Account settings
                      </DropdownMenuItem>
                      {onThemeChange && (
                        <DropdownMenuItem
                          onClick={() =>
                            onThemeChange(theme === "dark" ? "light" : "dark")
                          }
                        >
                          {theme === "dark" ? <RiSunLine /> : <RiMoonLine />}
                          {theme === "dark" ? "Light theme" : "Dark theme"}
                        </DropdownMenuItem>
                      )}
                    </DropdownMenuGroup>
                  </DropdownMenuContent>
                </DropdownMenu>
              </div>
            </div>
          </Sidebar>
        </SidebarProvider>
      </div>

      <Card
        radius={24}
        size="flush"
        variant="raised"
        className="min-w-0 flex-1"
      >
        <header className="flex h-14 shrink-0 items-center gap-2 px-3">
          <Button
            variant="ghost"
            size="icon-sm"
            className="@3xl:hidden"
            tooltip="Conversations"
            onClick={() => setDrawer(true)}
          >
            <RiChat3Line />
          </Button>
          <div className="flex h-8 min-w-0">
            <Button
              variant="muted"
              size="sm"
              shape="pill"
              aria-label={`Edit ${inspected?.name ?? "agent"}`}
              onClick={() => setInspector("open")}
              className="min-w-0 gap-1.5"
            >
              {active.group ? (
                <GroupAvatar agents={members} className="size-6" />
              ) : (
                <AgentAvatar agent={members[0]} className="size-6" />
              )}
              <span className="truncate">{titleOf(active, agents)}</span>
            </Button>
          </div>
          <div className="flex-1" />
          <Button
            variant="ghost-muted"
            size="icon-sm"
            tooltip={copied ? "Copied" : "Copy conversation"}
            onClick={share}
          >
            {copied ? <RiCheckLine /> : <RiUploadLine />}
          </Button>
          <Button
            variant="ghost"
            size="icon-sm"
            className="@5xl:hidden"
            tooltip={inspectorVisible ? "Hide editor" : "Edit agent"}
            aria-pressed={inspectorVisible}
            onClick={toggleInspector}
          >
            <RiLayoutRightLine />
          </Button>
          <DropdownMenu>
            <DropdownMenuTrigger
              render={
                <Button
                  variant="ghost-muted"
                  size="icon-sm"
                  tooltip="More"
                  aria-label="More"
                />
              }
            >
              <RiMoreLine />
            </DropdownMenuTrigger>
            <DropdownMenuContent
              align="end"
              className={cn("w-52", popupClassName)}
            >
              <DropdownMenuGroup>
                <DropdownMenuItem onClick={() => openSettings("general")}>
                  <RiSettings3Line />
                  Settings
                </DropdownMenuItem>
                <DropdownMenuItem
                  disabled={active.messages.length === 0}
                  onClick={() => chat.clearConversation(active.id)}
                >
                  <RiEraserLine />
                  Clear messages
                </DropdownMenuItem>
                <DropdownMenuItem
                  disabled={conversations.length < 2}
                  onClick={() => chat.deleteConversation(active.id)}
                >
                  <RiDeleteBinLine />
                  Delete conversation
                </DropdownMenuItem>
              </DropdownMenuGroup>
            </DropdownMenuContent>
          </DropdownMenu>
        </header>

        <div className="min-h-0 flex-1">
          {active.messages.length === 0 && !thinkingAgent ? (
            <Empty variant="ghost" className="h-full">
              <EmptyHeader>
                <EmptyMedia className="mb-2 h-20" aria-hidden>
                  {members.length > 1 ? (
                    <span className="flex items-center -space-x-4">
                      {members.slice(0, 3).map((agent) => (
                        <AgentAvatar
                          key={agent.id}
                          agent={agent}
                          className="size-16"
                        />
                      ))}
                    </span>
                  ) : (
                    <AgentAvatar agent={members[0]} className="size-20" />
                  )}
                </EmptyMedia>
                <EmptyTitle>
                  {members.length > 1
                    ? "Your agents are ready"
                    : `Say hi to ${members[0]?.name}`}
                </EmptyTitle>
                <EmptyDescription>
                  {members.length > 1
                    ? "Send a message and each agent answers in turn."
                    : (members[0]?.description ?? "Ask anything to begin.")}
                </EmptyDescription>
              </EmptyHeader>
            </Empty>
          ) : (
            <MessageScrollerProvider
              key={active.id}
              autoScroll
              defaultScrollPosition="end"
            >
              <MessageScroller>
                <MessageScrollerViewport>
                  <MessageScrollerContent className="gap-4 px-4 pt-4 pb-2">
                    <div className="flex justify-center">
                      <span className="rounded-full bg-muted px-3 py-1 text-xs text-muted-foreground">
                        Today
                      </span>
                    </div>
                    {active.messages.map((message, index) => {
                      const author =
                        message.role === "agent"
                          ? agents.find((agent) => agent.id === message.agentId)
                          : undefined;
                      return (
                        <MessageScrollerItem
                          key={message.id}
                          messageId={message.id}
                          style={
                            {
                              "--delay": `${Math.min(index, 8) * 40}ms`,
                            } as React.CSSProperties
                          }
                        >
                          <div className={enter}>
                            <Message
                              align={message.role === "user" ? "end" : "start"}
                            >
                              <MessageContent className="gap-1.5">
                                <Bubble
                                  align={
                                    message.role === "user" ? "end" : "start"
                                  }
                                  variant={
                                    message.role === "user"
                                      ? "default"
                                      : "secondary"
                                  }
                                  className="max-w-[min(520px,85%)]"
                                >
                                  <BubbleContent className="flex flex-col gap-2 whitespace-normal">
                                    {message.text.split("\n\n").map((part) => (
                                      <p key={part}>{part}</p>
                                    ))}
                                  </BubbleContent>
                                </Bubble>
                                {author && (
                                  <Button
                                    variant="text"
                                    size="xs"
                                    aria-label={`Edit ${author.name}`}
                                    onClick={() => {
                                      setInspectedId(author.id);
                                      setInspector("open");
                                    }}
                                    className="w-fit gap-1.5"
                                  >
                                    <AgentAvatar
                                      agent={author}
                                      className="size-5"
                                    />
                                    {author.name}
                                  </Button>
                                )}
                              </MessageContent>
                            </Message>
                          </div>
                        </MessageScrollerItem>
                      );
                    })}
                    {thinkingAgent && (
                      <MessageScrollerItem key="thinking" messageId="thinking">
                        <span
                          role="status"
                          className={cn(
                            "flex items-center gap-1.5 px-1 text-xs text-muted-foreground",
                            enter
                          )}
                        >
                          <AgentAvatar
                            agent={thinkingAgent}
                            className="size-4"
                          />
                          <span className="shimmer motion-reduce:shimmer-none">
                            {thinkingAgent.name} is thinking...
                          </span>
                        </span>
                      </MessageScrollerItem>
                    )}
                  </MessageScrollerContent>
                </MessageScrollerViewport>
                <MessageScrollerButton />
              </MessageScroller>
            </MessageScrollerProvider>
          )}
        </div>

        <div className="p-1">
          <Composer
            value={draft}
            onValueChange={setDraft}
            working={chat.replying}
            onSend={(text) => chat.send(active.id, text)}
          >
            <ComposerShell shape="default">
              <ComposerInput
                aria-label="Message"
                placeholder="Hi, what do you need today?"
                className="min-h-13"
              />
              <InputGroupAddon align="block-end" className="relative z-1 px-2">
                <DropdownMenu>
                  <DropdownMenuTrigger
                    render={
                      <InputGroupButton
                        shape="pill"
                        size="icon"
                        aria-label="Add"
                        tooltip="Add"
                        variant="tonal"
                      />
                    }
                  >
                    <RiAddLine className="size-5" />
                  </DropdownMenuTrigger>
                  <DropdownMenuContent
                    align="start"
                    side="top"
                    className={cn("w-60", popupClassName)}
                  >
                    <DropdownMenuGroup>
                      <DropdownMenuLabel>Start with</DropdownMenuLabel>
                      {starters.map((starter) => (
                        <DropdownMenuItem
                          key={starter}
                          onClick={() => setDraft(starter)}
                        >
                          {starter}
                        </DropdownMenuItem>
                      ))}
                    </DropdownMenuGroup>
                  </DropdownMenuContent>
                </DropdownMenu>
                <Button
                  variant="ghost-muted"
                  size="sm"
                  aria-pressed={bypass}
                  onClick={() => setBypass(!bypass)}
                  className="aria-pressed:text-primary"
                >
                  <RiShieldCheckLine data-icon="inline-start" />
                  {bypass ? "Bypass on" : "Bypass all"}
                </Button>
                <div className="flex-1" />
                <ComposerModelPicker
                  model={model}
                  models={models}
                  onModelChange={setModel}
                  popupClassName={popupClassName}
                />
                <VoiceButton
                  listening={listening}
                  onClick={() => setListening(!listening)}
                />
                <ComposerSend />
              </InputGroupAddon>
            </ComposerShell>
          </Composer>
        </div>
      </Card>

      {inspected && (
        <div
          className={cn(
            "absolute inset-y-0 right-0 z-20 w-[336px] max-w-full shrink-0 rounded-[24px] bg-background shadow-md @5xl:static @5xl:z-auto @5xl:bg-transparent @5xl:shadow-none",
            inspector === "closed"
              ? "hidden"
              : inspector === "open"
                ? "block"
                : "hidden @5xl:block"
          )}
        >
          <AgentCreator
            agent={inspected}
            onChange={(next) => chat.updateAgent({ ...inspected, ...next })}
            popupClassName={popupClassName}
            onClose={() => setInspector("closed")}
            bare
            className="h-full w-full"
          />
        </div>
      )}

      <MultiAgentChatGroupDialog
        open={groupOpen}
        onOpenChange={setGroupOpen}
        agents={agents}
        onStart={(ids) => {
          chat.createGroup(ids);
          setDrawer(false);
        }}
        popupClassName={popupClassName}
      />
      <SettingsModal
        open={settings.open}
        onOpenChange={(open) =>
          setSettings((current) => ({ ...current, open }))
        }
        defaultPage={settings.page}
        onThemeChange={onThemeChange}
        className={popupClassName}
        marketplace={
          <MultiAgentChatMarketplace
            items={chat.marketItems}
            installedIds={chat.installed}
            onInstall={(item) => {
              chat.install(item);
              if (item.kind === "bot") {
                setInspectedId(null);
              }
            }}
            onRemove={chat.uninstallPlugin}
          />
        }
      />
    </div>
  );
}