Skip to content
Merged
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
4 changes: 3 additions & 1 deletion apps/docs/content/docs/meta.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
"multimodal",
"---AI---",
"llm-sdk",
"providers"
"providers",
"---API Reference---",
"api-reference"
]
}
92 changes: 92 additions & 0 deletions apps/docs/content/docs/ui.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,98 @@ Themes automatically support dark mode when the `dark` class is on an ancestor e

---

## Loader Variants

Customize the loading indicator shown when AI is generating a response:

```tsx
<CopilotChat loaderVariant="typing" />
```

### Available Variants

| Variant | Description |
|---------|-------------|
| `typing` | Typing indicator dots (default) |
| `dots` | Bouncing dots |
| `wave` | Wave animation |
| `terminal` | Terminal cursor blink |
| `text-blink` | Blinking "Thinking" text |
| `text-shimmer` | Shimmer effect "Thinking" text |
| `loading-dots` | "Thinking..." with animated dots |

### Text-based Loaders

The text-based variants (`text-blink`, `text-shimmer`, `loading-dots`) display "Thinking" text with animations:

```tsx
// Blinking text
<CopilotChat loaderVariant="text-blink" />

// Shimmer effect
<CopilotChat loaderVariant="text-shimmer" />

// "Thinking..." with animated dots
<CopilotChat loaderVariant="loading-dots" />
```

---

## Avatars

Customize the avatars shown next to messages:

### Basic Usage

```tsx
<CopilotChat
assistantAvatar={{
src: "/bot-avatar.png",
fallback: "AI"
}}
userAvatar={{
src: "/user-avatar.png",
fallback: "U"
}}
showUserAvatar // Enable user avatar display
/>
```

### Custom Avatar Component

Pass a custom React component for full control:

```tsx
<CopilotChat
assistantAvatar={{
component: (
<img
src="/custom-bot.svg"
className="size-full rounded-full"
/>
)
}}
userAvatar={{
component: <MyUserAvatar />
}}
showUserAvatar
/>
```

### Avatar Props

| Prop | Type | Description |
|------|------|-------------|
| `src` | `string` | Image URL |
| `fallback` | `string` | Text shown while image loads or if it fails |
| `component` | `ReactNode` | Custom component (overrides src/fallback) |

<Callout type="info">
User avatars are hidden by default. Set `showUserAvatar` to display them.
</Callout>

---

## Pre-built Components

The SDK exports these ready-to-use components:
Expand Down
2 changes: 1 addition & 1 deletion apps/docs/content/docs/why-copilot-sdk.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ Building AI assistants shouldn't require a team of ML engineers and months of de
| **100% Self-Hosted** | <Yes /> | <Yes /> | <Partial /> | <Yes /> |
| **Smart Context** | <Yes /> | <No /> | <No /> | <No /> |
| **Your Knowledgebase** | <Yes /> | <No /> | <Partial /> | <No /> |
| **Tool UI Rendering** | <Yes /> | <No /> | <Partial /> | <No /> |
| **Generative UI** | <Yes /> | <No /> | <Partial /> | <No /> |
| **MCP Support** | Soon | <No /> | <No /> | <No /> |

<Callout type="info">
Expand Down
6 changes: 6 additions & 0 deletions examples/playground/app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { ApiKeyModal } from "@/components/modals/ApiKeyModal";
import { WelcomeModal } from "@/components/modals/WelcomeModal";

// Theme CSS imports
import "@yourgpt/copilot-sdk/ui/styles.css";
import "@yourgpt/copilot-sdk/ui/themes/claude.css";
import "@yourgpt/copilot-sdk/ui/themes/linear.css";
import "@yourgpt/copilot-sdk/ui/themes/vercel.css";
Expand All @@ -36,13 +37,15 @@ export default function PlaygroundPage() {
systemPrompt,
generativeUI,
toolsEnabled,
sdkConfig,
selectedProvider,
selectedOpenRouterModel,
updateTheme,
updateLayoutTemplate,
updateSystemPrompt,
toggleGenerativeUI,
toggleTool,
updateSDKConfig,
updateProvider,
updateOpenRouterModel,
} = usePlaygroundConfig();
Expand Down Expand Up @@ -154,6 +157,8 @@ export default function PlaygroundPage() {
onReset={actions.reset}
selectedPerson={selectedPerson}
onSelectPerson={handleSelectPerson}
sdkConfig={sdkConfig}
onUpdateSDKConfig={updateSDKConfig}
/>
</div>
</div>
Expand All @@ -171,6 +176,7 @@ export default function PlaygroundPage() {
selectedProvider={selectedProvider}
selectedOpenRouterModel={selectedOpenRouterModel}
apiKeys={apiKeys}
loaderVariant={sdkConfig.loaderVariant}
/>
</div>

Expand Down
64 changes: 64 additions & 0 deletions examples/playground/components/playground/ControlPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -62,13 +62,16 @@ import type {
PersonData,
LayoutTemplate,
ProviderId,
SDKConfig,
LoaderVariant,
} from "@/lib/types";
import {
themes,
samplePersons,
layoutTemplates,
providers,
OPENROUTER_MODELS,
LOADER_VARIANTS,
} from "@/lib/constants";
import { WeatherModule } from "./modules/WeatherModule";
import { StockModule } from "./modules/StockModule";
Expand Down Expand Up @@ -273,6 +276,11 @@ interface ControlPanelProps {
onReset: () => void;
selectedPerson: PersonData;
onSelectPerson: (person: PersonData) => void;
sdkConfig: SDKConfig;
onUpdateSDKConfig: <K extends keyof SDKConfig>(
key: K,
value: SDKConfig[K],
) => void;
}

function ControlPanelComponent({
Expand All @@ -298,6 +306,8 @@ function ControlPanelComponent({
onReset,
selectedPerson,
onSelectPerson,
sdkConfig,
onUpdateSDKConfig,
}: ControlPanelProps) {
const activeToolCount = Object.values(toolsEnabled).filter(Boolean).length;
const selectedTheme = themes.find((t) => t.id === copilotTheme);
Expand Down Expand Up @@ -522,6 +532,60 @@ function ControlPanelComponent({
placeholder="// Define AI behavior..."
/>
</div>

{/* Row 3: More Settings - Bare collapsible */}
<Accordion type="single" collapsible className="w-full">
<AccordionItem value="more" className="border-none">
<AccordionTrigger className="py-0 hover:no-underline">
<span className="text-[10px] font-medium uppercase tracking-[0.12em] text-zinc-400 hover:text-zinc-600 dark:hover:text-zinc-300">
More Settings
</span>
</AccordionTrigger>
<AccordionContent className="pt-4 pb-0">
<div className="flex flex-wrap items-end gap-4">
{/* Loader Variant */}
<div className="w-40">
<div className="flex items-center gap-1 mb-3">
<CircleDot className="h-3.5 w-3.5 text-zinc-400" />
<span className="text-[10px] font-medium uppercase tracking-[0.12em] text-zinc-500">
Loader
</span>
</div>
<Select
value={sdkConfig.loaderVariant}
onValueChange={(v) =>
onUpdateSDKConfig("loaderVariant", v as LoaderVariant)
}
>
<SelectTrigger className="h-9 bg-zinc-50 dark:bg-zinc-800/50 border-zinc-200 dark:border-zinc-700">
<SelectValue>
<span className="text-xs">
{
LOADER_VARIANTS.find(
(l) => l.id === sdkConfig.loaderVariant,
)?.label
}
</span>
</SelectValue>
</SelectTrigger>
<SelectContent>
{LOADER_VARIANTS.map((variant) => (
<SelectItem key={variant.id} value={variant.id}>
<div className="flex flex-col">
<span>{variant.label}</span>
<span className="text-[10px] text-zinc-400">
{variant.description}
</span>
</div>
</SelectItem>
))}
</SelectContent>
</Select>
</div>
</div>
</AccordionContent>
</AccordionItem>
</Accordion>
</div>
</section>

Expand Down
9 changes: 6 additions & 3 deletions examples/playground/components/playground/CopilotPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import type {
LayoutTemplate,
ToolsEnabledConfig,
GenerativeUIConfig,
LoaderVariant,
} from "@/lib/types";
import type { DashboardActions } from "@/hooks/useDashboardState";
import { useDashboardContext } from "@/hooks/useDashboardContext";
Expand All @@ -23,6 +24,7 @@ interface CopilotPanelProps {
currentPerson: PersonData;
toolsEnabled: ToolsEnabledConfig;
generativeUI: GenerativeUIConfig;
loaderVariant: LoaderVariant;
}

export function CopilotPanel({
Expand All @@ -33,6 +35,7 @@ export function CopilotPanel({
currentPerson,
toolsEnabled,
generativeUI,
loaderVariant,
}: CopilotPanelProps) {
// Provide dashboard and user context to the AI
useDashboardContext({ dashboardState, currentPerson });
Expand All @@ -41,12 +44,12 @@ export function CopilotPanel({
const renderLayout = () => {
switch (layoutTemplate) {
case "saas":
return <SaasLayout theme={theme} />;
return <SaasLayout theme={theme} loaderVariant={loaderVariant} />;
case "support":
return <SupportLayout theme={theme} />;
return <SupportLayout theme={theme} loaderVariant={loaderVariant} />;
case "default":
default:
return <DefaultLayout theme={theme} />;
return <DefaultLayout theme={theme} loaderVariant={loaderVariant} />;
}
};

Expand Down
4 changes: 4 additions & 0 deletions examples/playground/components/playground/CopilotSidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import type {
GenerativeUIConfig,
ProviderId,
ApiKeys,
LoaderVariant,
} from "@/lib/types";
import type { DashboardActions } from "@/hooks/useDashboardState";

Expand All @@ -28,6 +29,7 @@ interface CopilotSidebarProps {
selectedProvider: ProviderId;
selectedOpenRouterModel: string;
apiKeys: ApiKeys;
loaderVariant: LoaderVariant;
}

export function CopilotSidebar({
Expand All @@ -42,6 +44,7 @@ export function CopilotSidebar({
selectedProvider,
selectedOpenRouterModel,
apiKeys,
loaderVariant,
}: CopilotSidebarProps) {
// Build runtime URL with provider and optional API key
const runtimeUrl = useMemo(() => {
Expand Down Expand Up @@ -99,6 +102,7 @@ export function CopilotSidebar({
currentPerson={selectedPerson}
toolsEnabled={toolsEnabled}
generativeUI={generativeUI}
loaderVariant={loaderVariant}
/>
</CopilotProvider>
</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
"use client";

import { CopilotChat } from "@yourgpt/copilot-sdk/ui";
import type { CopilotTheme } from "@/lib/types";
import type { CopilotTheme, LoaderVariant } from "@/lib/types";

export interface LayoutProps {
theme: CopilotTheme;
loaderVariant: LoaderVariant;
}

export function DefaultLayout({ theme }: LayoutProps) {
export function DefaultLayout({ theme, loaderVariant }: LayoutProps) {
return (
<div
className="h-full"
Expand All @@ -20,6 +21,14 @@ export function DefaultLayout({ theme }: LayoutProps) {
header={{ name: "AI Copilot" }}
showThreadPicker
persistence
loaderVariant={loaderVariant}
assistantAvatar={{
src: "https://api.dicebear.com/7.x/bottts/svg?seed=assistant",
}}
showUserAvatar
userAvatar={{
src: "https://api.dicebear.com/7.x/avataaars/svg?seed=user",
}}
/>
</div>
);
Expand Down
16 changes: 14 additions & 2 deletions examples/playground/components/playground/layouts/SaasLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -88,13 +88,25 @@ function CustomSuggestions() {
);
}

export function SaasLayout({ theme }: LayoutProps) {
export function SaasLayout({ theme, loaderVariant }: LayoutProps) {
// Use supabase theme by default for this layout, unless a different theme is selected
const effectiveTheme = theme === "default" ? "supabase" : theme;

return (
<div className="h-full" data-csdk-theme={effectiveTheme}>
<CopilotChat.Root persistence className="h-full" showPoweredBy={false}>
<CopilotChat.Root
persistence
className="h-full"
showPoweredBy={false}
loaderVariant={loaderVariant}
assistantAvatar={{
src: "https://api.dicebear.com/7.x/bottts/svg?seed=assistant",
}}
showUserAvatar
userAvatar={{
src: "https://api.dicebear.com/7.x/avataaars/svg?seed=user",
}}
>
{/* Home View - Custom welcome screen */}
<CopilotChat.HomeView className="gap-4 p-6 bg-gradient-to-b from-primary/30 via-background to-background items-stretch w-full">
{/* Logo */}
Expand Down
Loading
Loading