Last updated

/iblai-vibe-agent-chat-sidebar

Mirrored from iblai/vibe Β· skills/agents/iblai-vibe-agent-chat-sidebar/SKILL.md. This page is generated β€” edit it in the repository, not here.

Add the SDK's AppSidebar next to the Chat component to get the full ibl.ai shell: a Projects dropdown (list, create, rename, delete, navigate), Pinned Messages, Recent Messages, host-defined content items (top of sidebar) and footer items (bottom). The sidebar is fully host-themable through shadcn's SidebarProvider and works on top of the /iblai-vibe-agent-chat route wiring.

Sidebar overview
Sidebar overview
Project active
Project active

Builds on top of /iblai-vibe-agent-chat. This skill assumes the host already has <Chat> rendering correctly β€” same providers, same store slices, same peer deps. If you haven't done that yet, run /iblai-vibe-agent-chat first.

Do NOT add custom styles, colors, or CSS overrides to the SDK AppSidebar. It ships with its own styling. Keep the component as-is. Do NOT implement dark mode unless the user explicitly asks for it.

Common setup (brand, conventions, env files, verification): see docs/skill-setup.md.

Prerequisites

  • /iblai-vibe-agent-chat must be working β€” the Chat component renders at /agents/<mentorId>/chat-new (or wherever you placed it), backed by the providers tree and reducers it requires.
  • @iblai/iblai-js must export SidebarProvider and AppSidebar from @iblai/iblai-js/web-containers/next. If your published version doesn't include SidebarProvider, you'll get useSidebar must be used within a SidebarProvider β€” the SDK source needs to re-export SidebarProvider from components/ui/sidebar. Use a version with that export, or pin to a worktree that has it.

What Gets Wired

FileChange
app/agents/[mentorId]/chat-projects/page.tsxNew route rendering <SidebarProvider> + <AppSidebar> + <Chat> side-by-side
public/icons/new-project.svgCopied from the SDK monorepo's apps/mentor/public/icons/
public/icons/projects.svgSame β€” referenced by the SDK at absolute path /icons/projects.svg
public/placeholder.svgSame β€” referenced by SDK avatar/image fallbacks at /placeholder.svg

Step 1: Copy the SDK Icons into public/

The SDK's ProjectsSidebarDropdown and several avatar fallbacks load icons from absolute paths (/icons/new-project.svg, /icons/projects.svg, /placeholder.svg) β€” i.e. they expect the host app to serve these from its public/ folder. The package does not ship these files. Add them once:

mkdir -p public/icons
# pull from the SDK monorepo if you have it cloned locally
cp <ibl-web-frontend-checkout>/apps/mentor/public/icons/new-project.svg public/icons/new-project.svg
cp <ibl-web-frontend-checkout>/apps/mentor/public/icons/projects.svg   public/icons/projects.svg
cp <ibl-web-frontend-checkout>/apps/skills/public/placeholder.svg      public/placeholder.svg

If you don't have the monorepo checked out, copy any 24Γ—24 SVG that matches your brand and place them at those exact paths.

Step 2: Create the Route

Create app/agents/[mentorId]/chat-projects/page.tsx. The structure mirrors /iblai-vibe-agent-chat's route but wraps everything in <SidebarProvider> and mounts <AppSidebar> to the left of <Chat>:

"use client";

export const dynamic = "force-dynamic";

import { Suspense, useEffect, useState } from "react";
import { useParams, useRouter } from "next/navigation";
import {
  AppSidebar,
  Chat,
  SidebarProvider,
  type ChatConfig,
} from "@iblai/iblai-js/web-containers/next";
import {
  useUsername,
  useAxdToken,
  useUserTenants,
  useVisitingTenant,
  useIsAdmin,
  isLoggedIn,
} from "@iblai/iblai-js/web-utils";
import { Home, Settings, LogOut } from "lucide-react";
import { redirectToAuthSpa } from "@/lib/iblai/auth-utils";
import config from "@/lib/iblai/config";

export default function AgentChatProjectsPageWrapper() {
  return (
    <Suspense fallback={null}>
      <AgentChatProjectsPage />
    </Suspense>
  );
}

function AgentChatProjectsPage() {
  const { mentorId } = useParams<{ mentorId: string }>();
  const router = useRouter();
  const [tenantKey, setTenantKey] = useState("");

  useEffect(() => {
    const appTenant = localStorage.getItem("app_tenant");
    const tenant = localStorage.getItem("tenant");
    let currentTenant = "";
    try {
      currentTenant =
        JSON.parse(localStorage.getItem("current_tenant") ?? "{}")?.key ?? "";
    } catch {}
    setTenantKey(
      appTenant || currentTenant || tenant || config.mainTenantKey(),
    );
  }, []);

  const username = useUsername();
  const axdToken = useAxdToken();
  const { userTenants } = useUserTenants();
  const { visitingTenant } = useVisitingTenant();
  const isAdmin = useIsAdmin();

  const chatConfig: ChatConfig = {
    baseWsUrl: () => config.wsUrl(),
    supportEmail: () => `support@${config.platformBaseDomain()}`,
    authUrl: () => config.authUrl(),
    mainTenantKey: config.mainTenantKey(),
    navigateToAdminBilling: () =>
      router.push(`/agents/${mentorId}/settings?tab=billing`),
    navigateToExplore: () => router.push("/agents"),
    navigateToMentor: (id) => router.push(`/agents/${id}`),
  };

  if (!tenantKey) return null;

  return (
    <SidebarProvider>
      <div className="flex h-screen w-full">
        <AppSidebar
          contentItems={[
            { icon: Home,     label: "Home",     onClick: () => router.push("/") },
            { icon: Settings, label: "Settings", onClick: () => router.push(`/agents/${mentorId}/settings`) },
          ]}
          footerItems={[
            {
              icon: LogOut,
              label: "Log out",
              onClick: () =>
                redirectToAuthSpa(window.location.href, tenantKey, true),
            },
          ]}
          showProjects
          showPinnedMessages
          showRecentMessages
          projectsConfig={{
            tenantKey,
            username: username ?? "",
            isLoggedIn: isLoggedIn(),
            activeMentorId: mentorId,
            navigateToProject: (projectId, mentorUniqueId) =>
              router.push(
                `/agents/${mentorUniqueId}/chat-projects?project=${projectId}`,
              ),
            navigateToMentorInProject: (mentorUniqueId, projectId) =>
              router.push(
                `/agents/${mentorUniqueId}/chat-projects?project=${projectId}`,
              ),
          }}
        />

        <div className="flex flex-1 flex-col">
          <Chat
            isPreviewMode={false}
            mentorId={mentorId}
            tenantKey={tenantKey}
            config={chatConfig}
            redirectToAuthSpa={redirectToAuthSpa}
            username={username ?? null}
            userTenants={userTenants ?? []}
            visitingTenant={visitingTenant}
            axdToken={axdToken ?? ""}
            userIsStudent={!isAdmin}
          />
        </div>
      </div>
    </SidebarProvider>
  );
}

Why each new piece (vs /iblai-vibe-agent-chat):

ItemWhy
<SidebarProvider> (root wrapper)The SDK's AppSidebar calls useSidebar() internally β€” requires a SidebarProvider ancestor.
<AppSidebar contentItems={...} footerItems={...} />Required props β€” the host owns the top and bottom menu sections. Pass an empty array if you don't want any.
showProjects / showPinnedMessages / showRecentMessagesToggle each panel. showProjects is what enables the projects dropdown.
projectsConfig.{navigateToProject, navigateToMentorInProject}Host-owned navigation. When the user clicks a project or a chat inside it, the SDK calls these so the host can route. We encode ?project=<id> so the active project can be picked up after navigation.
projectsConfig.isLoggedIn: isLoggedIn()Switches the dropdown between authenticated and anonymous triggers.

Props

AppSidebar

PropTypeRequiredDescription
contentItemsMenuItem[]yesTop-of-sidebar host menu items (Home, Settings, etc.)
footerItemsMenuItem[]yesBottom-of-sidebar host menu items (Log out, Account, etc.)
logoReactNodenoOptional logo rendered in the sidebar header
showProjectsbooleannoRender the Projects dropdown
showPinnedMessagesbooleannoRender the Pinned Messages section
showRecentMessagesbooleannoRender the Recent Messages section
projectsConfigProjectsConfigonly if showProjectsSee below
FieldTypeDescription
iconReact.ComponentType<{className?: string}>Lucide icon or any component that accepts className
labelstringVisible label
onClick() => voidHost-owned click handler
hasBorder?booleanOptional separator border

ProjectsConfig

(Equivalent to ProjectsSidebarDropdownProps)

FieldTypeRequiredDescription
tenantKeystringyesActive organization β€” used for project list query + mutations
usernamestringyesAuthenticated username
isLoggedInbooleanyesToggles authenticated vs anonymous trigger
activeProjectIdstringnoCurrently active project β€” highlight + expand
activeMentorIdstringnoHighlight a specific chat inside the active project
navigateToProject(projectId, mentorUniqueId) => voidyesCalled when the user clicks a project or one of its chat items
navigateToMentorInProject(mentorUniqueId, projectId) => voidyesCalled right after a brand-new project is created β€” navigate to the project's first agent
executeWithTrialCheck(action: () => void) => voidnoWraps user-initiated actions (open create / rename / delete modal, navigate) behind a free-trial check. Omit to run actions directly.
renderUnauthenticatedTrigger(trigger: ReactNode) => ReactNodenoRender-prop for the "New Project" trigger when the user is signed out β€” typical use: wrap in an AuthPopover that redirects to /login.

Step 3: Verify

  1. pnpm build β€” must pass with zero errors.
  2. pnpm dev β€” open http://localhost:3000/agents/<NEXT_PUBLIC_DEFAULT_AGENT_ID>/chat-projects.
  3. After SSO login, you should see:
    • the sidebar on the left with Home / Settings at the top (your contentItems),
    • a New Project entry with the folder-plus icon,
    • any existing projects below it with the folder icon,
    • Pinned Messages and Recent Messages headers further down,
    • Log out at the bottom (your footerItems),
    • and the existing <Chat> surface on the right.
  4. Click an existing project. The URL should update to /agents/<projectMentorId>/chat-projects?project=<projectId>, the sidebar item highlights, and the chat re-renders with the project's default agent.
  5. Click New Project. The create-project modal opens; submit to create one, and the SDK calls navigateToMentorInProject so your route changes to the newly-created project.

Known Gaps in the SDK

When opening a PR to land these patches upstream, note:

GapWhereSuggested fix
SidebarProvider (and friends) not in the public barrelpackages/web-containers/src/next/index.tsexport { SidebarProvider, SidebarTrigger, SidebarInset, useSidebar } from '../components/ui/sidebar'; β€” without this, AppSidebar can't be mounted by any host.
Icons referenced by absolute path but not shippedprojects-sidebar-dropdown.tsx, project-item.tsx, and several <img src="/placeholder.svg" /> fallbacksEither bundle the SVGs into packages/web-containers/public/ with a rollup copy plugin emitting them into dist/, or import them as ES modules / inline data URIs so they ride along with the JS.

Brand guidelines: BRAND.md

Copyright Β© ibl.ai | support@iblai.zendesk.com