chore: import upstream snapshot with attribution

This commit is contained in:
wehub-resource-sync
2026-07-13 12:58:18 +08:00
commit 6d5d58c1a9
18293 changed files with 3502153 additions and 0 deletions
+41
View File
@@ -0,0 +1,41 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.*
.yarn/*
!.yarn/patches
!.yarn/plugins
!.yarn/releases
!.yarn/versions
# testing
/coverage
# next.js
/.next/
/out/
# production
/build
# misc
.DS_Store
*.pem
# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.pnpm-debug.log*
# env files (can opt-in for committing if needed)
.env*
# vercel
.vercel
# typescript
*.tsbuildinfo
next-env.d.ts
+113
View File
@@ -0,0 +1,113 @@
# demo
## 0.1.4-next.0
### Patch Changes
- Updated dependencies [d268c49]
- @copilotkit/agent@1.51.3-next.0
- @copilotkit/voice@1.51.3-next.0
- @copilotkit/core@1.51.3-next.0
- @copilotkit/react@1.51.3-next.0
- @copilotkit/runtime@1.51.3-next.0
- @copilotkit/shared@1.51.3-next.0
## 0.1.3
### Patch Changes
- Updated dependencies [e59d23f]
- Updated dependencies [f36b6b1]
- @copilotkit/runtime@1.51.2
- @copilotkit/shared@1.51.2
- @copilotkit/agent@1.51.2
- @copilotkit/react@1.51.2
- @copilotkit/core@1.51.2
## 0.1.3-next.1
### Patch Changes
- Updated dependencies [e59d23f]
- @copilotkit/runtime@1.51.2-next.1
- @copilotkit/shared@1.51.2-next.1
- @copilotkit/agent@1.51.2-next.1
- @copilotkit/react@1.51.2-next.1
- @copilotkit/core@1.51.2-next.1
## 0.1.3-next.0
### Patch Changes
- Updated dependencies [f36b6b1]
- @copilotkit/agent@1.51.2-next.0
- @copilotkit/core@1.51.2-next.0
- @copilotkit/react@1.51.2-next.0
- @copilotkit/runtime@1.51.2-next.0
- @copilotkit/shared@1.51.2-next.0
## 0.1.2
### Patch Changes
- Updated dependencies [329653b]
- @copilotkit/agent@1.51.1
- @copilotkit/react@1.51.1
- @copilotkit/core@1.51.1
- @copilotkit/runtime@1.51.1
- @copilotkit/shared@1.51.1
## 0.1.1
### Patch Changes
- Updated dependencies [2793a11]
- Updated dependencies [73f196f]
- Updated dependencies [2839a15]
- @copilotkit/runtime@1.51.0
- @copilotkit/shared@1.51.0
- @copilotkit/agent@1.51.0
- @copilotkit/react@1.51.0
- @copilotkit/core@1.51.0
## 0.1.1-next.3
### Patch Changes
- Updated dependencies [2793a11]
- @copilotkit/runtime@1.51.0-next.4
- @copilotkit/agent@1.51.0-next.4
- @copilotkit/core@1.51.0-next.4
- @copilotkit/react@1.51.0-next.4
- @copilotkit/shared@1.51.0-next.4
## 0.1.1-next.2
### Patch Changes
- Updated dependencies [73f196f]
- @copilotkit/runtime@1.51.0-next.3
- @copilotkit/agent@1.51.0-next.3
- @copilotkit/core@1.51.0-next.3
- @copilotkit/react@1.51.0-next.3
- @copilotkit/shared@1.51.0-next.3
## 0.1.1-next.1
### Patch Changes
- @copilotkit/agent@1.51.0-next.2
- @copilotkit/core@1.51.0-next.2
- @copilotkit/react@1.51.0-next.2
- @copilotkit/runtime@1.51.0-next.2
- @copilotkit/shared@1.51.0-next.2
## 0.1.1-next.0
### Patch Changes
- @copilotkit/agent@1.51.0-next.1
- @copilotkit/core@1.51.0-next.1
- @copilotkit/react@1.51.0-next.1
- @copilotkit/runtime@1.51.0-next.1
- @copilotkit/shared@1.51.0-next.1
+36
View File
@@ -0,0 +1,36 @@
This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app).
## Getting Started
First, run the development server:
```bash
npm run dev
# or
yarn dev
# or
pnpm dev
# or
bun dev
```
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel.
## Learn More
To learn more about Next.js, take a look at the following resources:
- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome!
## Deploy on Vercel
The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details.
+8
View File
@@ -0,0 +1,8 @@
import type { NextConfig } from "next";
const nextConfig: NextConfig = {
/* config options here */
serverExternalPackages: ["better-sqlite3"],
};
export default nextConfig;
+52
View File
@@ -0,0 +1,52 @@
{
"name": "demo",
"version": "0.1.4-next.0",
"private": true,
"scripts": {
"dev": "next dev --turbopack",
"build": "next build",
"start": "next start",
"test": "vitest",
"test:watch": "vitest --watch",
"test:coverage": "vitest --coverage"
},
"dependencies": {
"@ag-ui/client": "0.0.51",
"@ag-ui/mcp-apps-middleware": "0.0.2",
"@ai-sdk/openai": "3.0.36",
"@copilotkit/core": "workspace:*",
"@copilotkit/react-core": "workspace:*",
"@copilotkit/runtime": "workspace:*",
"@copilotkit/shared": "workspace:*",
"@copilotkit/voice": "workspace:*",
"@modelcontextprotocol/sdk": "^1.18.2",
"cors": "^2.8.5",
"express": "^4.21.0",
"hono": "^4.11.4",
"next": "15.5.15",
"openai": "^5.9.0",
"react": "19.1.0",
"react-dom": "19.1.0",
"rxjs": "^7.8.1",
"zod": "^3.25.75"
},
"devDependencies": {
"@tailwindcss/postcss": "^4",
"@types/better-sqlite3": "^7.6.13",
"@types/cors": "^2.8.17",
"@types/express": "^4.17.21",
"@types/node": "^20",
"@types/react": "^19",
"@types/react-dom": "^19",
"tailwindcss": "^4",
"typescript": "^5",
"vitest": "^3.2.3"
},
"nx": {
"targets": {
"build": {
"cache": false
}
}
}
}
@@ -0,0 +1,5 @@
const config = {
plugins: ["@tailwindcss/postcss"],
};
export default config;
+1
View File
@@ -0,0 +1 @@
<svg fill="none" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path d="M14.5 13.5V5.41a1 1 0 0 0-.3-.7L9.8.29A1 1 0 0 0 9.08 0H1.5v13.5A2.5 2.5 0 0 0 4 16h8a2.5 2.5 0 0 0 2.5-2.5m-1.5 0v-7H8v-5H3v12a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1M9.5 5V2.12L12.38 5zM5.13 5h-.62v1.25h2.12V5zm-.62 3h7.12v1.25H4.5zm.62 3h-.62v1.25h7.12V11z" clip-rule="evenodd" fill="#666" fill-rule="evenodd"/></svg>

After

Width:  |  Height:  |  Size: 391 B

+1
View File
@@ -0,0 +1 @@
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><g clip-path="url(#a)"><path fill-rule="evenodd" clip-rule="evenodd" d="M10.27 14.1a6.5 6.5 0 0 0 3.67-3.45q-1.24.21-2.7.34-.31 1.83-.97 3.1M8 16A8 8 0 1 0 8 0a8 8 0 0 0 0 16m.48-1.52a7 7 0 0 1-.96 0H7.5a4 4 0 0 1-.84-1.32q-.38-.89-.63-2.08a40 40 0 0 0 3.92 0q-.25 1.2-.63 2.08a4 4 0 0 1-.84 1.31zm2.94-4.76q1.66-.15 2.95-.43a7 7 0 0 0 0-2.58q-1.3-.27-2.95-.43a18 18 0 0 1 0 3.44m-1.27-3.54a17 17 0 0 1 0 3.64 39 39 0 0 1-4.3 0 17 17 0 0 1 0-3.64 39 39 0 0 1 4.3 0m1.1-1.17q1.45.13 2.69.34a6.5 6.5 0 0 0-3.67-3.44q.65 1.26.98 3.1M8.48 1.5l.01.02q.41.37.84 1.31.38.89.63 2.08a40 40 0 0 0-3.92 0q.25-1.2.63-2.08a4 4 0 0 1 .85-1.32 7 7 0 0 1 .96 0m-2.75.4a6.5 6.5 0 0 0-3.67 3.44 29 29 0 0 1 2.7-.34q.31-1.83.97-3.1M4.58 6.28q-1.66.16-2.95.43a7 7 0 0 0 0 2.58q1.3.27 2.95.43a18 18 0 0 1 0-3.44m.17 4.71q-1.45-.12-2.69-.34a6.5 6.5 0 0 0 3.67 3.44q-.65-1.27-.98-3.1" fill="#666"/></g><defs><clipPath id="a"><path fill="#fff" d="M0 0h16v16H0z"/></clipPath></defs></svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 394 80"><path fill="#000" d="M262 0h68.5v12.7h-27.2v66.6h-13.6V12.7H262V0ZM149 0v12.7H94v20.4h44.3v12.6H94v21h55v12.6H80.5V0h68.7zm34.3 0h-17.8l63.8 79.4h17.9l-32-39.7 32-39.6h-17.9l-23 28.6-23-28.6zm18.3 56.7-9-11-27.1 33.7h17.8l18.3-22.7z"/><path fill="#000" d="M81 79.3 17 0H0v79.3h13.6V17l50.2 62.3H81Zm252.6-.4c-1 0-1.8-.4-2.5-1s-1.1-1.6-1.1-2.6.3-1.8 1-2.5 1.6-1 2.6-1 1.8.3 2.5 1a3.4 3.4 0 0 1 .6 4.3 3.7 3.7 0 0 1-3 1.8zm23.2-33.5h6v23.3c0 2.1-.4 4-1.3 5.5a9.1 9.1 0 0 1-3.8 3.5c-1.6.8-3.5 1.3-5.7 1.3-2 0-3.7-.4-5.3-1s-2.8-1.8-3.7-3.2c-.9-1.3-1.4-3-1.4-5h6c.1.8.3 1.6.7 2.2s1 1.2 1.6 1.5c.7.4 1.5.5 2.4.5 1 0 1.8-.2 2.4-.6a4 4 0 0 0 1.6-1.8c.3-.8.5-1.8.5-3V45.5zm30.9 9.1a4.4 4.4 0 0 0-2-3.3 7.5 7.5 0 0 0-4.3-1.1c-1.3 0-2.4.2-3.3.5-.9.4-1.6 1-2 1.6a3.5 3.5 0 0 0-.3 4c.3.5.7.9 1.3 1.2l1.8 1 2 .5 3.2.8c1.3.3 2.5.7 3.7 1.2a13 13 0 0 1 3.2 1.8 8.1 8.1 0 0 1 3 6.5c0 2-.5 3.7-1.5 5.1a10 10 0 0 1-4.4 3.5c-1.8.8-4.1 1.2-6.8 1.2-2.6 0-4.9-.4-6.8-1.2-2-.8-3.4-2-4.5-3.5a10 10 0 0 1-1.7-5.6h6a5 5 0 0 0 3.5 4.6c1 .4 2.2.6 3.4.6 1.3 0 2.5-.2 3.5-.6 1-.4 1.8-1 2.4-1.7a4 4 0 0 0 .8-2.4c0-.9-.2-1.6-.7-2.2a11 11 0 0 0-2.1-1.4l-3.2-1-3.8-1c-2.8-.7-5-1.7-6.6-3.2a7.2 7.2 0 0 1-2.4-5.7 8 8 0 0 1 1.7-5 10 10 0 0 1 4.3-3.5c2-.8 4-1.2 6.4-1.2 2.3 0 4.4.4 6.2 1.2 1.8.8 3.2 2 4.3 3.4 1 1.4 1.5 3 1.5 5h-5.8z"/></svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

+1
View File
@@ -0,0 +1 @@
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1155 1000"><path d="m577.3 0 577.4 1000H0z" fill="#fff"/></svg>

After

Width:  |  Height:  |  Size: 128 B

+1
View File
@@ -0,0 +1 @@
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path fill-rule="evenodd" clip-rule="evenodd" d="M1.5 2.5h13v10a1 1 0 0 1-1 1h-11a1 1 0 0 1-1-1zM0 1h16v11.5a2.5 2.5 0 0 1-2.5 2.5h-11A2.5 2.5 0 0 1 0 12.5zm3.75 4.5a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5M7 4.75a.75.75 0 1 1-1.5 0 .75.75 0 0 1 1.5 0m1.75.75a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5" fill="#666"/></svg>

After

Width:  |  Height:  |  Size: 385 B

@@ -0,0 +1,7 @@
import { describe, expect, it } from "vitest";
describe("demo app", () => {
it("loads test harness", () => {
expect(true).toBe(true);
});
});
@@ -0,0 +1,60 @@
import {
CopilotRuntime,
createCopilotEndpoint,
InMemoryAgentRunner,
BasicAgent,
} from "@copilotkit/runtime/v2";
import { handle } from "hono/vercel";
import { MCPAppsMiddleware } from "@ag-ui/mcp-apps-middleware";
const determineModel = () => {
if (process.env.OPENAI_API_KEY?.trim()) {
return "openai/gpt-4o";
}
if (process.env.ANTHROPIC_API_KEY?.trim()) {
return "anthropic/claude-sonnet-4.5";
}
if (process.env.GOOGLE_API_KEY?.trim()) {
return "google/gemini-2.5-pro";
}
return "openai/gpt-4o";
};
const agent = new BasicAgent({
model: determineModel(),
prompt: "You are a helpful AI assistant with access to MCP apps and tools.",
temperature: 0.7,
}).use(
new MCPAppsMiddleware({
// Port assignments - verified by checking each server's tools
mcpServers: [
{ type: "http", url: "http://localhost:3101/mcp" }, // basic-server-react (get-time)
{ type: "http", url: "http://localhost:3102/mcp" }, // basic-server-vanillajs (get-time)
{ type: "http", url: "http://localhost:3103/mcp" }, // budget-allocator-server (get-budget-data)
{ type: "http", url: "http://localhost:3104/mcp" }, // cohort-heatmap-server (get-cohort-data)
{ type: "http", url: "http://localhost:3105/mcp" }, // customer-segmentation-server (get-customer-data)
{ type: "http", url: "http://localhost:3106/mcp" }, // integration-server (get-time)
{ type: "http", url: "http://localhost:3107/mcp" }, // scenario-modeler-server (get-scenario-data)
{ type: "http", url: "http://localhost:3108/mcp" }, // sheet-music-server (play-sheet-music)
{ type: "http", url: "http://localhost:3109/mcp" }, // system-monitor-server (get-system-stats)
{ type: "http", url: "http://localhost:3110/mcp" }, // threejs-server (show_threejs_scene)
{ type: "http", url: "http://localhost:3111/mcp" }, // video-resource-server (play_video)
{ type: "http", url: "http://localhost:3112/mcp" }, // wiki-explorer-server (get-first-degree-links)
],
}),
);
const honoRuntime = new CopilotRuntime({
agents: {
default: agent,
},
runner: new InMemoryAgentRunner(),
});
const app = createCopilotEndpoint({
runtime: honoRuntime,
basePath: "/api/copilotkit-mcp",
});
export const GET = handle(app);
export const POST = handle(app);
@@ -0,0 +1,50 @@
import {
CopilotRuntime,
InMemoryAgentRunner,
createCopilotEndpointSingleRoute,
BuiltInAgent,
} from "@copilotkit/runtime/v2";
import { TranscriptionServiceOpenAI } from "@copilotkit/voice";
import { handle } from "hono/vercel";
import OpenAI from "openai";
const determineModel = () => {
if (process.env.OPENAI_API_KEY?.trim()) {
return "openai/gpt-4o";
}
if (process.env.ANTHROPIC_API_KEY?.trim()) {
return "anthropic/claude-sonnet-4.5";
}
if (process.env.GOOGLE_API_KEY?.trim()) {
return "google/gemini-2.5-pro";
}
return "openai/gpt-4o";
};
const agent = new BuiltInAgent({
model: determineModel(),
prompt: "You are a helpful AI assistant.",
temperature: 0.7,
});
// Set up transcription service if OpenAI API key is available
const transcriptionService = process.env.OPENAI_API_KEY?.trim()
? new TranscriptionServiceOpenAI({
openai: new OpenAI({ apiKey: process.env.OPENAI_API_KEY }),
})
: undefined;
const singleRuntime = new CopilotRuntime({
agents: {
default: agent,
},
runner: new InMemoryAgentRunner(),
transcriptionService,
});
const app = createCopilotEndpointSingleRoute({
runtime: singleRuntime,
basePath: "/api/copilotkit-single",
});
export const POST = handle(app);
@@ -0,0 +1,83 @@
import {
CopilotRuntime,
createCopilotEndpoint,
InMemoryAgentRunner,
BuiltInAgent,
} from "@copilotkit/runtime/v2";
import type { BuiltInAgentClassicConfig } from "@copilotkit/runtime/v2";
import { createOpenAI } from "@ai-sdk/openai";
import { TranscriptionServiceOpenAI } from "@copilotkit/voice";
import { handle } from "hono/vercel";
import OpenAI from "openai";
const openRouterApiKey = process.env.OPENROUTER_API_KEY?.trim();
const openAIApiKey = process.env.OPENAI_API_KEY?.trim();
const OPENROUTER_BASE_URL = "https://openrouter.ai/api/v1";
const DEFAULT_OPENROUTER_MODEL = "openai/gpt-4o-mini";
const determineOpenRouterModelId = () => {
return process.env.OPENROUTER_MODEL?.trim() || DEFAULT_OPENROUTER_MODEL;
};
const determineModel = (): BuiltInAgentClassicConfig["model"] => {
if (openRouterApiKey) {
const openrouter = createOpenAI({
apiKey: openRouterApiKey,
baseURL: process.env.OPENROUTER_BASE_URL?.trim() || OPENROUTER_BASE_URL,
});
return openrouter(determineOpenRouterModelId());
}
if (openAIApiKey) {
return "openai/gpt-5.2";
}
if (process.env.ANTHROPIC_API_KEY?.trim()) {
// claude-3-7-sonnet supports extended thinking
return "anthropic/claude-3-7-sonnet-20250219";
}
if (process.env.GOOGLE_API_KEY?.trim()) {
return "google/gemini-2.5-pro";
}
return "openai/gpt-5.2";
};
const builtInAgent = new BuiltInAgent({
model: determineModel(),
prompt:
"You are a helpful AI assistant. Use reasoning to answer the user's question. If you don't know the answer, say you don't know.",
providerOptions: {
...(openAIApiKey
? { openai: { reasoningEffort: "high", reasoningSummary: "detailed" } }
: {}),
...(!openAIApiKey &&
!openRouterApiKey &&
!!process.env.ANTHROPIC_API_KEY?.trim() && {
anthropic: { thinking: { type: "enabled", budgetTokens: 5000 } },
}),
},
});
// Set up transcription service if OpenAI API key is available
const transcriptionService = openAIApiKey
? new TranscriptionServiceOpenAI({
openai: new OpenAI({ apiKey: openAIApiKey }),
})
: undefined;
const honoRuntime = new CopilotRuntime({
agents: {
default: builtInAgent,
},
runner: new InMemoryAgentRunner(),
transcriptionService,
a2ui: {},
openGenerativeUI: true,
});
const app = createCopilotEndpoint({
runtime: honoRuntime,
basePath: "/api/copilotkit",
});
export const GET = handle(app);
export const POST = handle(app);
Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

@@ -0,0 +1,26 @@
@import "tailwindcss";
:root {
--background: #ffffff;
--foreground: #171717;
}
@theme inline {
--color-background: var(--background);
--color-foreground: var(--foreground);
--font-sans: var(--font-geist-sans);
--font-mono: var(--font-geist-mono);
}
@media (prefers-color-scheme: dark) {
:root {
--background: #0a0a0a;
--foreground: #ededed;
}
}
body {
background: var(--background);
color: var(--foreground);
font-family: Arial, Helvetica, sans-serif;
}
+35
View File
@@ -0,0 +1,35 @@
import type { Metadata } from "next";
import { Geist, Geist_Mono } from "next/font/google";
import "./globals.css";
import "@copilotkit/react-core/v2/styles.css";
const geistSans = Geist({
variable: "--font-geist-sans",
subsets: ["latin"],
});
const geistMono = Geist_Mono({
variable: "--font-geist-mono",
subsets: ["latin"],
});
export const metadata: Metadata = {
title: "Create Next App",
description: "Generated by create next app",
};
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<html lang="en">
<body
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
>
{children}
</body>
</html>
);
}
@@ -0,0 +1,123 @@
"use client";
import { CopilotKitProvider, CopilotSidebar } from "@copilotkit/react-core/v2";
export const dynamic = "force-dynamic";
export default function MCPAppsDemoPage() {
return (
<CopilotKitProvider runtimeUrl="/api/copilotkit-mcp" showDevConsole="auto">
<AppLayout />
</CopilotKitProvider>
);
}
function AppLayout() {
return (
<div className="relative min-h-screen bg-gradient-to-br from-slate-100 via-white to-slate-200">
<main className="mx-auto flex w-full max-w-5xl flex-col gap-8 px-6 py-12">
<section className="space-y-4">
<h1 className="text-3xl font-semibold tracking-tight text-slate-900">
MCP Apps Demo
</h1>
<p className="max-w-2xl text-slate-600">
This page demonstrates the MCP Apps Extension. The assistant has
access to MCP tools that can render interactive UI components
directly in the chat.
</p>
<div className="rounded-lg border border-slate-200 bg-white p-4 text-sm text-slate-600">
<p className="font-medium text-slate-900">
Available MCP Tools (ext-apps):
</p>
<ul className="mt-2 list-inside list-disc space-y-1">
<li>
<code className="rounded bg-slate-100 px-1">get-time</code> -
Current server time
</li>
<li>
<code className="rounded bg-slate-100 px-1">
get-budget-data
</code>{" "}
- Interactive budget allocation
</li>
<li>
<code className="rounded bg-slate-100 px-1">
get-cohort-data
</code>{" "}
- Cohort analysis heatmap
</li>
<li>
<code className="rounded bg-slate-100 px-1">
get-customer-data
</code>{" "}
- Customer segmentation analysis
</li>
<li>
<code className="rounded bg-slate-100 px-1">
get-scenario-data
</code>{" "}
- SaaS financial scenario modeling
</li>
<li>
<code className="rounded bg-slate-100 px-1">
play-sheet-music
</code>{" "}
- Music notation rendering
</li>
<li>
<code className="rounded bg-slate-100 px-1">
get-system-stats
</code>{" "}
- System monitoring dashboard
</li>
<li>
<code className="rounded bg-slate-100 px-1">
show_threejs_scene
</code>{" "}
- 3D visualization (Three.js)
</li>
<li>
<code className="rounded bg-slate-100 px-1">play_video</code> -
Video player
</li>
<li>
<code className="rounded bg-slate-100 px-1">
get-first-degree-links
</code>{" "}
- Wikipedia link explorer
</li>
</ul>
<div className="mt-3 space-y-1 text-xs text-slate-500">
<p className="font-medium">To run the MCP servers:</p>
<pre className="rounded bg-slate-100 p-2 overflow-x-auto">
{`git clone https://github.com/modelcontextprotocol/ext-apps
cd ext-apps
npm install
npm start`}
</pre>
</div>
</div>
</section>
<section className="grid gap-6 md:grid-cols-2">
{Array.from({ length: 4 }).map((_, index) => (
<article
key={index}
className="rounded-2xl border border-slate-200 bg-white p-6 shadow-sm transition hover:shadow-md"
>
<h2 className="text-lg font-medium text-slate-900">
Demo Card {index + 1}
</h2>
<p className="mt-2 text-sm text-slate-600">
Try asking the assistant to use MCP tools. The UI will render
directly in the chat sidebar.
</p>
</article>
))}
</section>
</main>
<CopilotSidebar defaultOpen={true} width="50%" threadId="mcp-apps-003" />
</div>
);
}
+322
View File
@@ -0,0 +1,322 @@
"use client";
import {
CopilotChat,
CopilotKitProvider,
defineToolCallRenderer,
useAgentContext,
useConfigureSuggestions,
useFrontendTool,
} from "@copilotkit/react-core/v2";
import type { ToolsMenuItem, SandboxFunction } from "@copilotkit/react-core/v2";
import { useCallback, useMemo, useState } from "react";
import { z } from "zod";
// Disable static optimization for this page
export const dynamic = "force-dynamic";
type Theme = "light" | "dark";
// Match CopilotKit's oklch CSS variables for consistent theming
const themeColors = {
light: {
bg: "oklch(1 0 0)",
text: "oklch(0.145 0 0)",
border: "oklch(0.922 0 0)",
muted: "oklch(0.97 0 0)",
},
dark: {
bg: "oklch(0.145 0 0)",
text: "oklch(0.985 0 0)",
border: "oklch(0.269 0 0)",
muted: "oklch(0.269 0 0)",
},
};
export default function Home() {
const [theme, setTheme] = useState<Theme>("light");
const colors = themeColors[theme];
// Define a wildcard renderer for any undefined tools
const wildcardRenderer = defineToolCallRenderer({
name: "*",
// No args needed for wildcard - defaults to z.any()
render: ({ name, args, status }) => (
<div
style={{
padding: "12px",
margin: "8px 0",
backgroundColor: colors.muted,
borderRadius: "8px",
border: `1px solid ${colors.border}`,
color: colors.text,
}}
>
<strong>Unknown Tool: {name}</strong>
<pre style={{ marginTop: "8px", fontSize: "12px" }}>
Status: {status}
{args && "\nArguments: " + JSON.stringify(args, null, 2)}
</pre>
</div>
),
});
const handleSetTheme = useCallback(
async (args: { mode: "light" | "dark" }) => {
setTheme(args.mode);
return `Theme set to ${args.mode}`;
},
[],
);
const sandboxFunctions = useMemo<SandboxFunction[]>(
() => [
{
name: "setTheme",
description:
"Switch the host application theme between light and dark mode. " +
"Call this when the user asks to change the theme or when generating UI with a theme toggle.",
parameters: z.object({
mode: z.enum(["light", "dark"]).describe("The theme mode to set"),
}),
handler: handleSetTheme,
},
],
[handleSetTheme],
);
return (
<CopilotKitProvider
runtimeUrl="/api/copilotkit"
renderToolCalls={[wildcardRenderer]}
showDevConsole="auto"
openGenerativeUI={{ sandboxFunctions }}
>
<div
style={{
height: "100vh",
margin: 0,
padding: 0,
overflow: "hidden",
backgroundColor: colors.bg,
color: colors.text,
transition: "background-color 0.3s, color 0.3s",
}}
>
<Chat
theme={theme}
onToggleTheme={() =>
setTheme((t) => (t === "light" ? "dark" : "light"))
}
/>
</div>
</CopilotKitProvider>
);
}
function Chat({
theme,
onToggleTheme,
}: {
theme: Theme;
onToggleTheme: () => void;
}) {
const colors = themeColors[theme];
const [selectedThreadId, setSelectedThreadId] = useState<
"thread---a" | "thread---b" | "thread---c" | undefined
>(undefined);
const threadOptions: Array<{ id: typeof selectedThreadId; label: string }> = [
{ id: undefined, label: "Stateless" },
{ id: "thread---a", label: "Thread A" },
{ id: "thread---b", label: "Thread B" },
{ id: "thread---c", label: "Thread C" },
];
useConfigureSuggestions({
instructions: "Suggest follow-up tasks based on the current page content",
available: "always",
});
useAgentContext({
description: "The current Thread ID is:",
value: selectedThreadId ?? "stateless",
});
//useConfigureSuggestions({
// instructions: "Suggest helpful next actions",
//});
// useConfigureSuggestions({
// suggestions: [
// {
// title: "Action 1",
// message: "Do action 1",
// },
// {
// title: "Action 2",
// message: "Do action 2",
// },
// ],
// });
useFrontendTool({
name: "sayHello",
parameters: z.object({
name: z.string(),
}),
handler: async ({ name }) => {
alert(`Hello ${name}`);
return `Hello ${name}`;
},
});
const toolsMenu = useMemo<(ToolsMenuItem | "-")[]>(
() => [
{
label: "Say hi to CopilotKit",
action: () => {
const textarea = document.querySelector<HTMLTextAreaElement>(
"textarea[placeholder='Type a message...']",
);
if (!textarea) {
return;
}
const greeting =
"Hello Copilot! 👋 Could you help me with something?";
const nativeInputValueSetter = Object.getOwnPropertyDescriptor(
window.HTMLTextAreaElement.prototype,
"value",
)?.set;
nativeInputValueSetter?.call(textarea, greeting);
textarea.dispatchEvent(new Event("input", { bubbles: true }));
textarea.focus();
},
},
"-",
{
label: "Open CopilotKit Docs",
action: () => {
window.open(
"https://docs.copilotkit.ai",
"_blank",
"noopener,noreferrer",
);
},
},
],
[],
);
return (
<div
style={{
display: "flex",
flexDirection: "column",
height: "100%",
padding: "16px",
gap: "16px",
}}
>
<div style={{ display: "flex", gap: "10px", alignItems: "center" }}>
<button
type="button"
onClick={onToggleTheme}
aria-label={`Switch to ${theme === "light" ? "dark" : "light"} mode`}
style={{
display: "flex",
alignItems: "center",
justifyContent: "center",
width: 36,
height: 36,
borderRadius: "50%",
border: `1px solid ${colors.border}`,
backgroundColor: colors.muted,
color: colors.text,
cursor: "pointer",
transition: "all 0.15s ease-in-out",
}}
>
{theme === "light" ? (
<svg
width="18"
height="18"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
>
<path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z" />
</svg>
) : (
<svg
width="18"
height="18"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
>
<circle cx="12" cy="12" r="5" />
<line x1="12" y1="1" x2="12" y2="3" />
<line x1="12" y1="21" x2="12" y2="23" />
<line x1="4.22" y1="4.22" x2="5.64" y2="5.64" />
<line x1="18.36" y1="18.36" x2="19.78" y2="19.78" />
<line x1="1" y1="12" x2="3" y2="12" />
<line x1="21" y1="12" x2="23" y2="12" />
<line x1="4.22" y1="19.78" x2="5.64" y2="18.36" />
<line x1="18.36" y1="5.64" x2="19.78" y2="4.22" />
</svg>
)}
</button>
<div
style={{
flex: 1,
display: "flex",
gap: "10px",
justifyContent: "center",
}}
>
{threadOptions.map(({ id, label }) => {
const isActive = id === selectedThreadId;
return (
<button
key={id ?? "stateless"}
type="button"
onClick={() => setSelectedThreadId(id)}
aria-pressed={isActive}
style={{
padding: "6px 14px",
borderRadius: "20px",
border: isActive
? `2px solid ${colors.text}`
: `1px solid ${colors.border}`,
backgroundColor: isActive ? colors.text : colors.bg,
color: isActive ? colors.bg : colors.text,
fontWeight: 600,
fontSize: "0.85rem",
cursor: "pointer",
transition: "all 0.15s ease-in-out",
}}
>
{label}
</button>
);
})}
</div>
</div>
<div style={{ flex: 1, minHeight: 0 }}>
<CopilotChat
className={theme === "dark" ? "dark" : undefined}
input={{ toolsMenu }}
threadId={selectedThreadId}
key={selectedThreadId ?? "stateless"}
/>
</div>
</div>
);
}
@@ -0,0 +1,122 @@
"use client";
import {
CopilotKitProvider,
CopilotPopup,
defineToolCallRenderer,
useConfigureSuggestions,
useFrontendTool,
} from "@copilotkit/react-core/v2";
import { z } from "zod";
export const dynamic = "force-dynamic";
export default function PopupDemoPage() {
const wildcardRenderer = defineToolCallRenderer({
name: "*",
render: ({ name, args, status }) => (
<div className="rounded-lg border border-slate-200 bg-slate-50 p-3 text-sm text-slate-700 shadow-sm">
<strong className="block text-slate-900">Unknown Tool: {name}</strong>
<pre className="mt-2 whitespace-pre-wrap text-xs text-slate-600">
Status: {status}
{args && "\nArguments: " + JSON.stringify(args, null, 2)}
</pre>
</div>
),
});
return (
<CopilotKitProvider
runtimeUrl="/api/copilotkit"
renderToolCalls={[wildcardRenderer]}
showDevConsole="auto"
>
<PopupLayout />
</CopilotKitProvider>
);
}
function PopupLayout() {
return (
<div className="relative min-h-screen bg-gradient-to-br from-slate-100 via-white to-slate-200">
<main className="mx-auto flex w-full max-w-5xl flex-col gap-10 px-6 py-12 pb-40">
<section className="space-y-4">
<span className="inline-flex items-center rounded-full bg-slate-900/10 px-3 py-1 text-xs font-medium text-slate-700">
Overlay Chat Demo
</span>
<h1 className="text-3xl font-semibold tracking-tight text-slate-900">
Copilot Popup Demo
</h1>
<p className="max-w-2xl text-slate-600">
This page mounts the chat as a floating popup anchored to the
bottom-right corner. The popup animates in and out, and leaves the
rest of the interface interactive. Try clicking outside the popup or
using the toggle button to open and close the assistant.
</p>
</section>
<section className="grid gap-6 md:grid-cols-2">
{Array.from({ length: 6 }).map((_, index) => (
<article
key={index}
className="rounded-2xl border border-slate-200 bg-white p-6 shadow-sm transition hover:shadow-md"
>
<h2 className="text-lg font-medium text-slate-900">
In-Flow Task {index + 1}
</h2>
<p className="mt-2 text-sm text-slate-600">
Use the popup assistant to draft updates, summarize status, or
trigger custom tools without losing context.
</p>
</article>
))}
</section>
<section className="rounded-2xl border border-dashed border-slate-300 bg-white/70 p-6 shadow-inner">
<h3 className="text-base font-semibold text-slate-900">
How the popup behaves
</h3>
<ul className="mt-3 space-y-2 text-sm text-slate-600">
<li>
Appears with a bottom-right scale and translate animation.
</li>
<li>
Leaves the page scrollable and interactiveno backdrop overlay.
</li>
<li>
Supports closing when you click outside (enabled in this demo).
</li>
<li>
Reuses all chat slots, tools, and suggestion hooks from the core
chat.
</li>
</ul>
</section>
</main>
<PopupChat />
</div>
);
}
function PopupChat() {
useConfigureSuggestions({
instructions:
"Suggest short summaries or next actions based on the dashboard content",
available: "always",
maxSuggestions: 2,
});
useFrontendTool({
name: "notify",
parameters: z.object({
message: z.string(),
}),
handler: async ({ message }) => {
alert(`Notification: ${message}`);
return `Displayed notification: ${message}`;
},
});
return <CopilotPopup defaultOpen={true} clickOutsideToClose={true} />;
}
@@ -0,0 +1,95 @@
"use client";
import {
CopilotKitProvider,
CopilotSidebar,
defineToolCallRenderer,
useConfigureSuggestions,
useFrontendTool,
} from "@copilotkit/react-core/v2";
import { z } from "zod";
export const dynamic = "force-dynamic";
export default function SidebarDemoPage() {
const wildcardRenderer = defineToolCallRenderer({
name: "*",
render: ({ name, args, status }) => (
<div className="rounded-lg border border-slate-200 bg-slate-50 p-3 text-sm text-slate-700 shadow-sm">
<strong className="block text-slate-900">Unknown Tool: {name}</strong>
<pre className="mt-2 whitespace-pre-wrap text-xs text-slate-600">
Status: {status}
{args && "\nArguments: " + JSON.stringify(args, null, 2)}
</pre>
</div>
),
});
return (
<CopilotKitProvider
runtimeUrl="/api/copilotkit"
renderToolCalls={[wildcardRenderer]}
showDevConsole="auto"
>
<AppLayout />
</CopilotKitProvider>
);
}
function AppLayout() {
return (
<div className="relative min-h-screen bg-gradient-to-br from-slate-100 via-white to-slate-200">
<main className="mx-auto flex w-full max-w-5xl flex-col gap-8 px-6 py-12">
<section className="space-y-4">
<h1 className="text-3xl font-semibold tracking-tight text-slate-900">
Copilot Sidebar Demo
</h1>
<p className="max-w-2xl text-slate-600">
This page shows the chat embedded as a right-aligned sidebar. Toggle
the chat to see the main content reflow. The assistant can suggest
actions and invoke custom tools just like the full-screen chat.
</p>
</section>
<section className="grid gap-6 md:grid-cols-2">
{Array.from({ length: 4 }).map((_, index) => (
<article
key={index}
className="rounded-2xl border border-slate-200 bg-white p-6 shadow-sm transition hover:shadow-md"
>
<h2 className="text-lg font-medium text-slate-900">
Project Card {index + 1}
</h2>
<p className="mt-2 text-sm text-slate-600">
Placeholder content to demonstrate how the sidebar pushes layout
elements without overlapping the page.
</p>
</article>
))}
</section>
</main>
<SidebarChat />
</div>
);
}
function SidebarChat() {
useConfigureSuggestions({
instructions: "Suggest follow-up tasks based on the current page content",
available: "always",
});
useFrontendTool({
name: "toastNotification",
parameters: z.object({
message: z.string(),
}),
handler: async ({ message }) => {
alert(`Notification: ${message}`);
return `Displayed toast: ${message}`;
},
});
return <CopilotSidebar defaultOpen={true} width="50%" />;
}
@@ -0,0 +1,110 @@
"use client";
import {
CopilotChat,
CopilotKitProvider,
useFrontendTool,
defineToolCallRenderer,
useConfigureSuggestions,
} from "@copilotkit/react-core/v2";
import type { ToolsMenuItem } from "@copilotkit/react-core/v2";
import { z } from "zod";
import { useMemo } from "react";
export const dynamic = "force-dynamic";
export default function SingleEndpointDemo() {
const wildcardRenderer = defineToolCallRenderer({
name: "*",
render: ({ name, args, status }) => (
<div
style={{
padding: "12px",
margin: "8px 0",
backgroundColor: "#f0f0f0",
borderRadius: "8px",
border: "1px solid #ccc",
}}
>
<strong>Unknown Tool: {name}</strong>
<pre style={{ marginTop: "8px", fontSize: "12px" }}>
Status: {status}
{args && "\nArguments: " + JSON.stringify(args, null, 2)}
</pre>
</div>
),
});
return (
<CopilotKitProvider
runtimeUrl="/api/copilotkit-single"
useSingleEndpoint
renderToolCalls={[wildcardRenderer]}
showDevConsole="auto"
>
<div
style={{ height: "100vh", margin: 0, padding: 0, overflow: "hidden" }}
>
<Chat />
</div>
</CopilotKitProvider>
);
}
function Chat() {
useFrontendTool({
name: "sayHello",
parameters: z.object({
name: z.string(),
}),
handler: async ({ name }) => {
alert(`Hello ${name}`);
return `Hello ${name}`;
},
});
useConfigureSuggestions({
instructions: "Suggest follow-up tasks based on the current page content",
});
const toolsMenu = useMemo<(ToolsMenuItem | "-")[]>(
() => [
{
label: "Say hi to CopilotKit",
action: () => {
const textarea = document.querySelector<HTMLTextAreaElement>(
"textarea[placeholder='Type a message...']",
);
if (!textarea) {
return;
}
const greeting =
"Hello Copilot! 👋 Could you help me with something?";
const nativeInputValueSetter = Object.getOwnPropertyDescriptor(
window.HTMLTextAreaElement.prototype,
"value",
)?.set;
nativeInputValueSetter?.call(textarea, greeting);
textarea.dispatchEvent(new Event("input", { bubbles: true }));
textarea.focus();
},
},
"-",
{
label: "Open CopilotKit Docs",
action: () => {
window.open(
"https://docs.copilotkit.ai",
"_blank",
"noopener,noreferrer",
);
},
},
],
[],
);
return <CopilotChat input={{ toolsMenu }} threadId="xyz" />;
}
+33
View File
@@ -0,0 +1,33 @@
{
"compilerOptions": {
"target": "ES2017",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "bundler",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "react-jsx",
"incremental": true,
"plugins": [
{
"name": "next"
}
],
"paths": {
"@/*": ["./src/*"]
}
},
"include": [
"next-env.d.ts",
"**/*.ts",
"**/*.tsx",
".next/types/**/*.ts",
".next/dev/types/**/*.ts"
],
"exclude": ["node_modules", "mcp-apps"]
}
+14
View File
@@ -0,0 +1,14 @@
import { defineConfig } from "vitest/config";
export default defineConfig({
test: {
environment: "node",
globals: true,
include: ["src/**/__tests__/**/*.{test,spec}.ts"],
coverage: {
reporter: ["text", "lcov", "html"],
include: ["src/lib/**/*.ts"],
exclude: ["src/**/*.d.ts", "src/**/index.ts", "src/**/__tests__/**"],
},
},
});
@@ -0,0 +1,21 @@
import type { StorybookConfig } from "@storybook/nextjs";
const config: StorybookConfig = {
framework: {
name: "@storybook/nextjs",
options: {},
},
stories: ["../stories/**/*.stories.@(tsx|mdx)"],
addons: ["@storybook/addon-docs", "@storybook/addon-themes"],
webpackFinal: async (cfg) => {
// Suppress size warnings for development
cfg.performance = {
...cfg.performance,
maxAssetSize: 5000000, // 5MB
maxEntrypointSize: 5000000, // 5MB
};
return cfg;
},
};
export default config;
@@ -0,0 +1,15 @@
/* Storybook dark mode custom background */
html.dark {
background-color: #212121;
}
html.dark body {
background-color: #212121;
}
/* Ensure the story canvas also uses the dark background */
html.dark .docs-story,
html.dark .sb-show-main,
html.dark .sb-main-padded {
background-color: #212121;
}
@@ -0,0 +1,38 @@
import type { Preview } from "@storybook/nextjs";
import { withThemeByClassName } from "@storybook/addon-themes";
import "@copilotkit/react-core/v2/styles.css";
import "./preview.css";
const preview: Preview = {
parameters: {
// Disable the backgrounds addon to avoid conflicts with dark mode
backgrounds: { disable: true },
controls: {
matchers: {
color: /(background|color)$/i,
date: /Date$/i,
},
},
docs: {
// Canvas (bottom) code panel behavior
canvas: { sourceState: "shown" }, // Show source code by default
// Enable the separate Code panel in Docs tab
codePanel: true,
// Configure source display
source: {
type: "dynamic",
},
},
},
decorators: [
withThemeByClassName({
themes: {
light: "", // default = no extra class
dark: "dark", // adds class="dark" to <html> in the preview iframe
},
defaultTheme: "light",
}),
],
};
export default preview;
+33
View File
@@ -0,0 +1,33 @@
{
"name": "@copilotkit-storybook/react",
"version": "0.0.0",
"private": true,
"scripts": {
"dev": "pnpm --filter @copilotkit/react-core run build:css && sleep 1 && concurrently \"pnpm --filter @copilotkit/react-core run dev\" \"pnpm --filter @copilotkit/web-inspector run dev\" \"storybook dev -p 6006 --no-open\"",
"build": "storybook build",
"storybook:dev": "pnpm --filter @copilotkit/react-core run build:css && sleep 1 && concurrently \"pnpm --filter @copilotkit/react-core run dev\" \"pnpm --filter @copilotkit/web-inspector run dev\" \"storybook dev -p 6006 --no-open\"",
"storybook:build": "storybook build"
},
"dependencies": {
"@storybook/nextjs": "^10.2.10",
"@storybook/react": "^10.2.10",
"next": "^15",
"react": "^19",
"react-dom": "^19",
"storybook": "^10.2.10"
},
"devDependencies": {
"@copilotkit/core": "workspace:*",
"@copilotkit/react-core": "workspace:*",
"@copilotkit/typescript-config": "workspace:*",
"@storybook/addon-docs": "^10",
"@storybook/addon-themes": "^10.2.10",
"@types/node": "^22",
"@types/react": "^19",
"@types/react-dom": "^19",
"concurrently": "^9.1.0",
"lucide-react": "^0.525.0",
"typescript": "5.8.2",
"zod": "^3.25.75"
}
}
@@ -0,0 +1,54 @@
import type { Meta, StoryObj } from "@storybook/react-webpack5";
import { fn } from "storybook/test";
import { Button } from "./Button";
// More on how to set up stories at: https://storybook.js.org/docs/writing-stories#default-export
const meta = {
title: "Example/Button",
component: Button,
parameters: {
// Optional parameter to center the component in the Canvas. More info: https://storybook.js.org/docs/configure/story-layout
layout: "centered",
},
// This component will have an automatically generated Autodocs entry: https://storybook.js.org/docs/writing-docs/autodocs
tags: ["autodocs"],
// More on argTypes: https://storybook.js.org/docs/api/argtypes
argTypes: {
backgroundColor: { control: "color" },
},
// Use `fn` to spy on the onClick arg, which will appear in the actions panel once invoked: https://storybook.js.org/docs/essentials/actions#action-args
args: { onClick: fn() },
} satisfies Meta<typeof Button>;
export default meta;
type Story = StoryObj<typeof meta>;
// More on writing stories with args: https://storybook.js.org/docs/writing-stories/args
export const Primary: Story = {
args: {
primary: true,
label: "Button",
},
};
export const Secondary: Story = {
args: {
label: "Button",
},
};
export const Large: Story = {
args: {
size: "large",
label: "Button",
},
};
export const Small: Story = {
args: {
size: "small",
label: "Button",
},
};
@@ -0,0 +1,39 @@
import "./button.css";
export interface ButtonProps {
/** Is this the principal call to action on the page? */
primary?: boolean;
/** What background color to use */
backgroundColor?: string;
/** How large should the button be? */
size?: "small" | "medium" | "large";
/** Button contents */
label: string;
/** Optional click handler */
onClick?: () => void;
}
/** Primary UI component for user interaction */
export const Button = ({
primary = false,
size = "medium",
backgroundColor,
label,
...props
}: ButtonProps) => {
const mode = primary
? "storybook-button--primary"
: "storybook-button--secondary";
return (
<button
type="button"
className={["storybook-button", `storybook-button--${size}`, mode].join(
" ",
)}
style={{ backgroundColor }}
{...props}
>
{label}
</button>
);
};
@@ -0,0 +1,369 @@
import { Meta } from "@storybook/addon-docs/blocks";
import Github from "./assets/github.svg";
import Discord from "./assets/discord.svg";
import Youtube from "./assets/youtube.svg";
import Tutorials from "./assets/tutorials.svg";
import Styling from "./assets/styling.png";
import Context from "./assets/context.png";
import Assets from "./assets/assets.png";
import Docs from "./assets/docs.png";
import Share from "./assets/share.png";
import FigmaPlugin from "./assets/figma-plugin.png";
import Testing from "./assets/testing.png";
import Accessibility from "./assets/accessibility.png";
import Theming from "./assets/theming.png";
import AddonLibrary from "./assets/addon-library.png";
export const RightArrow = () => (
<svg
viewBox="0 0 14 14"
width="8px"
height="14px"
style={{
marginLeft: "4px",
display: "inline-block",
shapeRendering: "inherit",
verticalAlign: "middle",
fill: "currentColor",
"path fill": "currentColor",
}}
>
<path d="m11.1 7.35-5.5 5.5a.5.5 0 0 1-.7-.7L10.04 7 4.9 1.85a.5.5 0 1 1 .7-.7l5.5 5.5c.2.2.2.5 0 .7Z" />
</svg>
);
<Meta title="Configure your project" />
<div className="sb-container">
<div className='sb-section-title'>
# Configure your project
Because Storybook works separately from your app, you'll need to configure it for your specific stack and setup. Below, explore guides for configuring Storybook with popular frameworks and tools. If you get stuck, learn how you can ask for help from our community.
</div>
<div className="sb-section">
<div className="sb-section-item">
<img
src={Styling}
alt="A wall of logos representing different styling technologies"
/>
<h4 className="sb-section-item-heading">Add styling and CSS</h4>
<p className="sb-section-item-paragraph">Like with web applications, there are many ways to include CSS within Storybook. Learn more about setting up styling within Storybook.</p>
<a
href="https://storybook.js.org/docs/configure/styling-and-css/?renderer=react"
target="_blank"
>Learn more<RightArrow /></a>
</div>
<div className="sb-section-item">
<img
src={Context}
alt="An abstraction representing the composition of data for a component"
/>
<h4 className="sb-section-item-heading">Provide context and mocking</h4>
<p className="sb-section-item-paragraph">Often when a story doesn't render, it's because your component is expecting a specific environment or context (like a theme provider) to be available.</p>
<a
href="https://storybook.js.org/docs/writing-stories/decorators/?renderer=react#context-for-mocking"
target="_blank"
>Learn more<RightArrow /></a>
</div>
<div className="sb-section-item">
<img src={Assets} alt="A representation of typography and image assets" />
<div>
<h4 className="sb-section-item-heading">Load assets and resources</h4>
<p className="sb-section-item-paragraph">To link static files (like fonts) to your projects and stories, use the
`staticDirs` configuration option to specify folders to load when
starting Storybook.</p>
<a
href="https://storybook.js.org/docs/configure/images-and-assets/?renderer=react"
target="_blank"
>Learn more<RightArrow /></a>
</div>
</div>
</div>
</div>
<div className="sb-container">
<div className='sb-section-title'>
# Do more with Storybook
Now that you know the basics, let's explore other parts of Storybook that will improve your experience. This list is just to get you started. You can customise Storybook in many ways to fit your needs.
</div>
<div className="sb-section">
<div className="sb-features-grid">
<div className="sb-grid-item">
<img src={Docs} alt="A screenshot showing the autodocs tag being set, pointing a docs page being generated" />
<h4 className="sb-section-item-heading">Autodocs</h4>
<p className="sb-section-item-paragraph">Auto-generate living,
interactive reference documentation from your components and stories.</p>
<a
href="https://storybook.js.org/docs/writing-docs/autodocs/?renderer=react"
target="_blank"
>Learn more<RightArrow /></a>
</div>
<div className="sb-grid-item">
<img src={Share} alt="A browser window showing a Storybook being published to a chromatic.com URL" />
<h4 className="sb-section-item-heading">Publish to Chromatic</h4>
<p className="sb-section-item-paragraph">Publish your Storybook to review and collaborate with your entire team.</p>
<a
href="https://storybook.js.org/docs/sharing/publish-storybook/?renderer=react#publish-storybook-with-chromatic"
target="_blank"
>Learn more<RightArrow /></a>
</div>
<div className="sb-grid-item">
<img src={FigmaPlugin} alt="Windows showing the Storybook plugin in Figma" />
<h4 className="sb-section-item-heading">Figma Plugin</h4>
<p className="sb-section-item-paragraph">Embed your stories into Figma to cross-reference the design and live
implementation in one place.</p>
<a
href="https://storybook.js.org/docs/sharing/design-integrations/?renderer=react#embed-storybook-in-figma-with-the-plugin"
target="_blank"
>Learn more<RightArrow /></a>
</div>
<div className="sb-grid-item">
<img src={Testing} alt="Screenshot of tests passing and failing" />
<h4 className="sb-section-item-heading">Testing</h4>
<p className="sb-section-item-paragraph">Use stories to test a component in all its variations, no matter how
complex.</p>
<a
href="https://storybook.js.org/docs/writing-tests/?renderer=react"
target="_blank"
>Learn more<RightArrow /></a>
</div>
<div className="sb-grid-item">
<img src={Accessibility} alt="Screenshot of accessibility tests passing and failing" />
<h4 className="sb-section-item-heading">Accessibility</h4>
<p className="sb-section-item-paragraph">Automatically test your components for a11y issues as you develop.</p>
<a
href="https://storybook.js.org/docs/writing-tests/accessibility-testing/?renderer=react"
target="_blank"
>Learn more<RightArrow /></a>
</div>
<div className="sb-grid-item">
<img src={Theming} alt="Screenshot of Storybook in light and dark mode" />
<h4 className="sb-section-item-heading">Theming</h4>
<p className="sb-section-item-paragraph">Theme Storybook's UI to personalize it to your project.</p>
<a
href="https://storybook.js.org/docs/configure/theming/?renderer=react"
target="_blank"
>Learn more<RightArrow /></a>
</div>
</div>
</div>
</div>
<div className='sb-addon'>
<div className='sb-addon-text'>
<h4>Addons</h4>
<p className="sb-section-item-paragraph">Integrate your tools with Storybook to connect workflows.</p>
<a
href="https://storybook.js.org/addons/"
target="_blank"
>Discover all addons<RightArrow /></a>
</div>
<div className='sb-addon-img'>
<img src={AddonLibrary} alt="Integrate your tools with Storybook to connect workflows." />
</div>
</div>
<div className="sb-section sb-socials">
<div className="sb-section-item">
<img src={Github} alt="Github logo" className="sb-explore-image"/>
Join our contributors building the future of UI development.
<a
href="https://github.com/storybookjs/storybook"
target="_blank"
>Star on GitHub<RightArrow /></a>
</div>
<div className="sb-section-item">
<img src={Discord} alt="Discord logo" className="sb-explore-image"/>
<div>
Get support and chat with frontend developers.
<a
href="https://discord.gg/storybook"
target="_blank"
>Join Discord server<RightArrow /></a>
</div>
</div>
<div className="sb-section-item">
<img src={Youtube} alt="Youtube logo" className="sb-explore-image"/>
<div>
Watch tutorials, feature previews and interviews.
<a
href="https://www.youtube.com/@chromaticui"
target="_blank"
>Watch on YouTube<RightArrow /></a>
</div>
</div>
<div className="sb-section-item">
<img src={Tutorials} alt="A book" className="sb-explore-image"/>
<p>Follow guided walkthroughs on for key workflows.</p>
<a
href="https://storybook.js.org/tutorials/"
target="_blank"
>Discover tutorials<RightArrow /></a>
</div>
</div>
<style>
{`
.sb-container {
margin-bottom: 48px;
}
.sb-section {
width: 100%;
display: flex;
flex-direction: row;
gap: 20px;
}
img {
object-fit: cover;
}
.sb-section-title {
margin-bottom: 32px;
}
.sb-section a:not(h1 a, h2 a, h3 a) {
font-size: 14px;
}
.sb-section-item, .sb-grid-item {
flex: 1;
display: flex;
flex-direction: column;
}
.sb-section-item-heading {
padding-top: 20px !important;
padding-bottom: 5px !important;
margin: 0 !important;
}
.sb-section-item-paragraph {
margin: 0;
padding-bottom: 10px;
}
.sb-chevron {
margin-left: 5px;
}
.sb-features-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
grid-gap: 32px 20px;
}
.sb-socials {
display: grid;
grid-template-columns: repeat(4, 1fr);
}
.sb-socials p {
margin-bottom: 10px;
}
.sb-explore-image {
max-height: 32px;
align-self: flex-start;
}
.sb-addon {
width: 100%;
display: flex;
align-items: center;
position: relative;
background-color: #EEF3F8;
border-radius: 5px;
border: 1px solid rgba(0, 0, 0, 0.05);
background: #EEF3F8;
height: 180px;
margin-bottom: 48px;
overflow: hidden;
}
.sb-addon-text {
padding-left: 48px;
max-width: 240px;
}
.sb-addon-text h4 {
padding-top: 0px;
}
.sb-addon-img {
position: absolute;
left: 345px;
top: 0;
height: 100%;
width: 200%;
overflow: hidden;
}
.sb-addon-img img {
width: 650px;
transform: rotate(-15deg);
margin-left: 40px;
margin-top: -72px;
box-shadow: 0 0 1px rgba(255, 255, 255, 0);
backface-visibility: hidden;
}
@media screen and (max-width: 800px) {
.sb-addon-img {
left: 300px;
}
}
@media screen and (max-width: 600px) {
.sb-section {
flex-direction: column;
}
.sb-features-grid {
grid-template-columns: repeat(1, 1fr);
}
.sb-socials {
grid-template-columns: repeat(2, 1fr);
}
.sb-addon {
height: 280px;
align-items: flex-start;
padding-top: 32px;
overflow: hidden;
}
.sb-addon-text {
padding-left: 24px;
}
.sb-addon-img {
right: 0;
left: 0;
top: 130px;
bottom: 0;
overflow: hidden;
height: auto;
width: 124%;
}
.sb-addon-img img {
width: 1200px;
transform: rotate(-12deg);
margin-left: 0;
margin-top: 48px;
margin-bottom: -40px;
margin-left: -24px;
}
}
`}
</style>
@@ -0,0 +1,503 @@
import type { Meta, StoryObj } from "@storybook/react";
import {
CopilotChatAssistantMessage,
CopilotChatConfigurationProvider,
CopilotKitProvider,
type CopilotChatAssistantMessageProps,
} from "@copilotkit/react-core/v2";
// Simple default message
const simpleMessage = {
id: "simple-message",
content: "Hello! How can I help you today?",
timestamp: new Date(),
role: "assistant" as const,
};
// Comprehensive markdown test content
const markdownTestMessage = {
id: "test-message",
content: `# Markdown Test Message
This message tests various markdown features including **bold**, *italic*, and \`inline code\`.
## Code Blocks with Copy Buttons
Here are some code examples to test the copy functionality:
### JavaScript/TypeScript
\`\`\`javascript
function greet(name) {
console.log(\`Hello, \${name}!\`);
return \`Welcome, \${name}\`;
}
// Usage
greet("World");
\`\`\`
### Python
\`\`\`python
def fibonacci(n):
if n <= 1:
return n
return fibonacci(n-1) + fibonacci(n-2)
# Generate sequence
for i in range(10):
print(fibonacci(i))
\`\`\`
### SQL
\`\`\`sql
SELECT u.name, COUNT(o.id) as order_count
FROM users u
LEFT JOIN orders o ON u.id = o.user_id
WHERE u.created_at > '2023-01-01'
GROUP BY u.id, u.name
ORDER BY order_count DESC;
\`\`\`
### JSON
\`\`\`json
{
"name": "CopilotKit",
"version": "2.0.0",
"dependencies": {
"react": "^18.0.0",
"react-markdown": "^10.1.0"
}
}
\`\`\`
### Shell/Bash
\`\`\`bash
#!/bin/bash
echo "Building project..."
npm install
npm run build
echo "Build complete!"
\`\`\`
## Inline Code Testing
Here's some \`inline code\` that should not have a copy button. You can also have \`npm install\` or \`const variable = "value"\` inline.
## Links and Images
- [External link](https://example.com)
- [Internal link](#section)
- ![Alt text](https://picsum.photos/150/100)
## Blockquotes
> This is a blockquote
>
> It can span multiple lines
>
> > And can be nested
## Tables
| Feature | Supported | Notes |
|---------|-----------|-------|
| Headers | ✅ | All levels |
| Lists | ✅ | Nested support |
| Code | ✅ | Syntax highlighting |
| Links | ✅ | External & internal |
| Copy Button | ✅ | On code blocks only |
## Horizontal Rule
---
## Miscellaneous
- [ ] Unchecked task
- [x] Checked task
- Emoji support: 🚀 ✨ 💡 🎉
### Math (if supported)
Inline math: $E = mc^2$
Block math:
$$
\\sum_{i=1}^{n} i = \\frac{n(n+1)}{2}
$$
---
*End of markdown test content*`,
timestamp: new Date(),
role: "assistant" as const,
};
const meta = {
title: "UI/CopilotChatAssistantMessage",
component: CopilotChatAssistantMessage,
decorators: [
(Story) => (
<div
style={{
display: "flex",
justifyContent: "center",
alignItems: "flex-start",
minHeight: "100vh",
padding: "16px",
}}
>
<div style={{ width: "100%", maxWidth: "640px" }}>
<CopilotKitProvider runtimeUrl="https://copilotkit.ai">
<CopilotChatConfigurationProvider threadId="storybook-thread">
<Story />
</CopilotChatConfigurationProvider>
</CopilotKitProvider>
</div>
</div>
),
],
args: {
message: simpleMessage,
onThumbsUp: () => console.log("Thumbs up clicked!"),
onThumbsDown: () => console.log("Thumbs down clicked!"),
onReadAloud: () => console.log("Read aloud clicked!"),
onRegenerate: () => console.log("Regenerate clicked!"),
},
} satisfies Meta<typeof CopilotChatAssistantMessage>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Default: Story = {
parameters: {
docs: {
source: {
code: `<CopilotChatAssistantMessage
message={{
id: "simple-message",
content: "Hello! How can I help you today?",
timestamp: new Date(),
role: "assistant"
}}
/>`,
},
},
},
};
export const TestAllMarkdownFeatures: Story = {
args: {
message: markdownTestMessage,
},
parameters: {
docs: {
source: {
code: `<CopilotChatAssistantMessage
message={{
id: "test-message",
content: \`# Markdown Test Message
This message tests various markdown features including **bold**, *italic*, and \\\`inline code\\\`.
## Lists
- Unordered item 1
- Unordered item 2
## Code Example
\\\`\\\`\\\`javascript
function greet(name) {
console.log(\\\`Hello, \\\${name}!\\\`);
}
\\\`\\\`\\\`
\`,
timestamp: new Date(),
role: "assistant"
}}
/>`,
},
},
},
};
export const WithToolbarButtons: Story = {
args: {
message: simpleMessage,
onThumbsUp: () => alert("Thumbs up clicked!"),
onThumbsDown: () => alert("Thumbs down clicked!"),
onReadAloud: () => alert("Read aloud clicked!"),
onRegenerate: () => alert("Regenerate clicked!"),
},
parameters: {
docs: {
source: {
code: `<CopilotChatAssistantMessage
message={{
id: "simple-message",
content: "Hello! How can I help you today?",
timestamp: new Date(),
role: "assistant"
}}
onThumbsUp={() => alert("Thumbs up clicked!")}
onThumbsDown={() => alert("Thumbs down clicked!")}
onReadAloud={() => alert("Read aloud clicked!")}
onRegenerate={() => alert("Regenerate clicked!")}
/>`,
},
},
},
};
export const WithAdditionalToolbarItems: Story = {
args: {
message: simpleMessage,
onThumbsUp: () => console.log("Thumbs up clicked!"),
onThumbsDown: () => console.log("Thumbs down clicked!"),
onReadAloud: () => console.log("Read aloud clicked!"),
onRegenerate: () => console.log("Regenerate clicked!"),
additionalToolbarItems: (
<>
<button
className="h-8 w-8 p-0 rounded-md bg-gray-100 hover:bg-gray-200 flex items-center justify-center"
onClick={() => alert("Custom button 1 clicked!")}
title="Custom Action 1"
>
📌
</button>
<button
className="h-8 w-8 p-0 rounded-md bg-gray-100 hover:bg-gray-200 flex items-center justify-center"
onClick={() => alert("Custom button 2 clicked!")}
title="Custom Action 2"
>
</button>
</>
),
},
parameters: {
docs: {
source: {
code: `<CopilotChatAssistantMessage
message={{
id: "simple-message",
content: "Hello! How can I help you today?",
timestamp: new Date(),
role: "assistant"
}}
onThumbsUp={() => console.log("Thumbs up clicked!")}
onThumbsDown={() => console.log("Thumbs down clicked!")}
onReadAloud={() => console.log("Read aloud clicked!")}
onRegenerate={() => console.log("Regenerate clicked!")}
additionalToolbarItems={
<>
<button
className="h-8 w-8 p-0 rounded-md bg-gray-100 hover:bg-gray-200 flex items-center justify-center"
onClick={() => alert("Custom button 1 clicked!")}
title="Custom Action 1"
>
📌
</button>
<button
className="h-8 w-8 p-0 rounded-md bg-gray-100 hover:bg-gray-200 flex items-center justify-center"
onClick={() => alert("Custom button 2 clicked!")}
title="Custom Action 2"
>
❤️
</button>
</>
}
/>`,
},
},
},
};
// Message with code blocks and inline literals
const codeBlocksTestMessage = {
id: "msg-code-blocks-test",
content:
"# Code Blocks and Inline Literals Test\n\n" +
"This message demonstrates code syntax highlighting with various languages and inline code usage. " +
"When you want to reference a variable like `userName` or a function like `getData()`, you can use inline code blocks.\n\n" +
"## JavaScript Example\n" +
"Here's how you might handle user authentication in JavaScript, so that you can verify credentials:\n\n" +
"```javascript\n" +
"const authenticateUser = async (email, password) => {\n" +
" try {\n" +
" const response = await fetch('/api/auth', {\n" +
" method: 'POST',\n" +
" headers: { 'Content-Type': 'application/json' },\n" +
" body: JSON.stringify({ email, password })\n" +
" });\n" +
" \n" +
" if (!response.ok) {\n" +
" throw new Error('Authentication failed');\n" +
" }\n" +
" \n" +
" return await response.json();\n" +
" } catch (error) {\n" +
" console.error('Error:', error);\n" +
" return null;\n" +
" }\n" +
"};\n" +
"```\n\n" +
"## Python Data Processing\n" +
"Python is great for data manipulation, so here's an example with pandas:\n\n" +
"```python\n" +
"import pandas as pd\n" +
"import numpy as np\n\n" +
"def process_user_data(csv_file):\n" +
" # Read the data\n" +
" df = pd.read_csv(csv_file)\n" +
" \n" +
" # Clean the data\n" +
" df['age'] = pd.to_numeric(df['age'], errors='coerce')\n" +
" df = df.dropna(subset=['age'])\n" +
" \n" +
" # Calculate statistics\n" +
" stats = {\n" +
" 'mean_age': df['age'].mean(),\n" +
" 'median_age': df['age'].median(),\n" +
" 'total_users': len(df)\n" +
" }\n" +
" \n" +
" return stats\n\n" +
"# Usage\n" +
"result = process_user_data('users.csv')\n" +
"print(f\"Average age: {result['mean_age']:.1f}\")\n" +
"```\n\n" +
"## SQL Database Query\n" +
"When working with databases, you might use SQL queries like `SELECT * FROM users` or more complex ones:\n\n" +
"```sql\n" +
"SELECT \n" +
" u.id,\n" +
" u.username,\n" +
" u.email,\n" +
" COUNT(p.id) as post_count,\n" +
" MAX(p.created_at) as last_post_date\n" +
"FROM users u\n" +
"LEFT JOIN posts p ON u.id = p.user_id\n" +
"WHERE u.active = true\n" +
" AND u.created_at >= '2023-01-01'\n" +
"GROUP BY u.id, u.username, u.email\n" +
"HAVING COUNT(p.id) > 0\n" +
"ORDER BY post_count DESC, last_post_date DESC\n" +
"LIMIT 50;\n" +
"```\n\n" +
"## Shell/Bash Commands\n" +
"For deployment scripts, you might use bash commands. The `chmod` command changes permissions, so you can make files executable:\n\n" +
"```bash\n" +
"#!/bin/bash\n\n" +
"# Deploy script\n" +
'APP_NAME="my-app"\n' +
"VERSION=$(git describe --tags --abbrev=0)\n\n" +
'echo "Deploying $APP_NAME version $VERSION"\n\n' +
"# Build the application\n" +
"npm install\n" +
"npm run build\n\n" +
"# Create deployment package\n" +
'tar -czf "${APP_NAME}-${VERSION}.tar.gz" dist/\n\n' +
"# Upload to server\n" +
'scp "${APP_NAME}-${VERSION}.tar.gz" user@server:/opt/deployments/\n\n' +
"# Extract and restart\n" +
"ssh user@server << EOF\n" +
" cd /opt/deployments\n" +
" tar -xzf ${APP_NAME}-${VERSION}.tar.gz\n" +
" sudo systemctl restart ${APP_NAME}\n" +
' echo "Deployment complete"\n' +
"EOF\n" +
"```\n\n" +
"## TypeScript Interface\n" +
"TypeScript helps with type safety, so you can define interfaces like:\n\n" +
"```typescript\n" +
"interface UserProfile {\n" +
" id: string;\n" +
" username: string;\n" +
" email: string;\n" +
" preferences: {\n" +
" theme: 'light' | 'dark';\n" +
" language: string;\n" +
" notifications: boolean;\n" +
" };\n" +
" lastLoginAt: Date | null;\n" +
"}\n\n" +
"class UserService {\n" +
" private users: Map<string, UserProfile> = new Map();\n\n" +
" async createUser(data: Omit<UserProfile, 'id' | 'lastLoginAt'>): Promise<UserProfile> {\n" +
" const user: UserProfile = {\n" +
" ...data,\n" +
" id: crypto.randomUUID(),\n" +
" lastLoginAt: null,\n" +
" };\n" +
" \n" +
" this.users.set(user.id, user);\n" +
" return user;\n" +
" }\n" +
" \n" +
" updateLastLogin(userId: string): void {\n" +
" const user = this.users.get(userId);\n" +
" if (user) {\n" +
" user.lastLoginAt = new Date();\n" +
" }\n" +
" }\n" +
"}\n" +
"```\n\n" +
"## CSS Styling\n" +
"For styling components, you can use CSS classes like `.container` or `#header`:\n\n" +
"```css\n" +
".user-profile-card {\n" +
" background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);\n" +
" border-radius: 12px;\n" +
" padding: 24px;\n" +
" box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);\n" +
" transition: transform 0.3s ease;\n" +
"}\n\n" +
".user-profile-card:hover {\n" +
" transform: translateY(-4px);\n" +
"}\n\n" +
".user-avatar {\n" +
" width: 80px;\n" +
" height: 80px;\n" +
" border-radius: 50%;\n" +
" border: 3px solid rgba(255, 255, 255, 0.2);\n" +
" object-fit: cover;\n" +
"}\n\n" +
"@media (max-width: 768px) {\n" +
" .user-profile-card {\n" +
" padding: 16px;\n" +
" margin: 8px;\n" +
" }\n" +
"}\n" +
"```\n\n" +
"All these examples show how inline code like `const`, `function`, and `class` can be mixed with code blocks to create comprehensive documentation.",
timestamp: new Date(),
role: "assistant" as const,
};
export const CodeBlocksWithLanguages: Story = {
args: {
message: codeBlocksTestMessage,
},
parameters: {
docs: {
source: {
code: `<CopilotChatAssistantMessage
message={{
id: "msg-code-blocks-test",
content: \`# Code Blocks Test
Here's a JavaScript example:
\\\`\\\`\\\`javascript
const greet = (name) => {
console.log(\\\`Hello, \\\${name}!\\\`);
};
\\\`\\\`\\\`
And a Python example:
\\\`\\\`\\\`python
def greet(name):
print(f"Hello, {name}!")
\\\`\\\`\\\`
\`,
timestamp: new Date(),
role: "assistant"
}}
/>`,
},
},
},
};
@@ -0,0 +1,484 @@
import React, { useState } from "react";
import type { Meta, StoryObj } from "@storybook/react";
import {
CopilotChatInput,
CopilotChatConfigurationProvider,
type ToolsMenuItem,
} from "@copilotkit/react-core/v2";
const meta = {
title: "UI/CopilotChatInput",
component: CopilotChatInput,
tags: ["autodocs"],
decorators: [
(Story) => (
<div
style={{
position: "fixed",
bottom: 0,
left: 0,
right: 0,
display: "flex",
justifyContent: "center",
padding: "16px",
}}
>
<div style={{ width: "100%", maxWidth: "640px" }}>
<CopilotChatConfigurationProvider threadId="storybook-thread">
<Story />
</CopilotChatConfigurationProvider>
</div>
</div>
),
],
parameters: {
layout: "fullscreen",
docs: {
description: {
component: `
The CopilotChatInput component provides a streamlined chat entry experience with a persistent add menu trigger that sits beside the text area. When the message grows beyond a single row, the textarea automatically moves above the controls to preserve layout.
## Key Features
- 📝 Auto-resizing textarea with configurable \`maxRows\`
- Dedicated add menu button that opens attachments or custom actions
- 🎤 Voice transcription mode with audio recorder
- 🎨 Fully themable through class overrides or slot replacements
- ♿ Keyboard accessible and screen-reader friendly
## Basic Usage
\`\`\`tsx
import { CopilotChatInput, CopilotChatConfigurationProvider } from '@copilotkit/react-core/v2';
function ChatComponent() {
return (
<CopilotChatConfigurationProvider threadId="demo-thread">
<CopilotChatInput
onSubmitMessage={(value) => console.log('Message:', value)}
onAddFile={() => console.log('Add file')}
/>
</CopilotChatConfigurationProvider>
);
}
\`\`\`
## Customization
The component supports deep customization via:
- **Slots** for the textarea, send button, add menu button, and audio recorder
- **Render props** to compose your own layout while reusing internal primitives
- **Props** such as \`toolsMenu\` for declarative menu configuration
- **Styling overrides** through Tailwind-compatible class names
`,
},
},
},
argTypes: {
mode: {
control: { type: "radio" },
options: ["input", "transcribe"],
description: "Select between text entry and transcription modes",
table: {
type: { summary: "'input' | 'transcribe'" },
defaultValue: { summary: "input" },
category: "Behavior",
},
},
toolsMenu: {
description: "Menu configuration rendered inside the add button dropdown",
table: {
type: { summary: "(ToolsMenuItem | '-')[]" },
category: "Features",
},
},
addMenuButton: {
description: "Slot override or class override for the add menu trigger",
table: {
type: { summary: "SlotValue<typeof CopilotChatInput.AddMenuButton>" },
category: "Customization",
},
},
sendButton: {
description: "Slot override for the send button",
table: {
type: { summary: "SlotValue<typeof CopilotChatInput.SendButton>" },
category: "Customization",
},
},
textArea: {
description: "Props or overrides for the textarea slot",
table: {
type: { summary: "SlotValue<typeof CopilotChatInput.TextArea>" },
category: "Configuration",
},
},
value: {
control: { type: "text" },
description: "Controlled input value",
table: {
type: { summary: "string" },
category: "Data",
},
},
onStartTranscribe: {
action: "startTranscribe",
description: "Invoked when transcription mode starts",
table: {
type: { summary: "() => void" },
category: "Events",
},
},
onCancelTranscribe: {
action: "cancelTranscribe",
description: "Invoked when transcription mode is cancelled",
table: {
type: { summary: "() => void" },
category: "Events",
},
},
onFinishTranscribe: {
action: "finishTranscribe",
description: "Invoked when transcription mode completes",
table: {
type: { summary: "() => void" },
category: "Events",
},
},
onAddFile: {
action: "addFile",
description: "Called when the default add menu item is selected",
table: {
type: { summary: "() => void" },
category: "Events",
},
},
onSubmitMessage: {
action: "submit",
description: "Called when the send button or Enter submits a message",
table: {
type: { summary: "(value: string) => void" },
category: "Events",
},
},
},
args: {
onStartTranscribe: () => console.log("Transcribe started"),
onCancelTranscribe: () => console.log("Transcribe cancelled"),
onFinishTranscribe: () => console.log("Transcribe completed"),
onAddFile: () => console.log("Add file clicked"),
onSubmitMessage: (value: string) => console.log(`Message sent: ${value}`),
},
} satisfies Meta<typeof CopilotChatInput>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Default: Story = {
parameters: {
docs: {
description: {
story:
"Default configuration with the add menu enabled and empty input.",
},
source: {
code: `<CopilotChatInput />`,
},
},
},
};
export const WithMenuItems: Story = {
args: {
toolsMenu: [
{
label: "Insert template",
action: () => alert("Template inserted"),
},
"-",
{
label: "Advanced",
items: [
{
label: "Summarize selection",
action: () => alert("Summarize action"),
},
{
label: "Tag teammate",
action: () => alert("Tagging teammate"),
},
],
},
] as (ToolsMenuItem | "-")[],
},
parameters: {
docs: {
description: {
story:
"Demonstrates configuring nested items inside the add menu dropdown.",
},
source: {
code: `<CopilotChatInput
toolsMenu={[
{
label: "Insert template",
action: () => alert("Template inserted")
},
"-",
{
label: "Advanced",
items: [
{
label: "Summarize selection",
action: () => alert("Summarize action")
},
{
label: "Tag teammate",
action: () => alert("Tagging teammate")
}
]
}
]}
/>`,
},
},
},
};
export const TranscribeMode: Story = {
args: {
mode: "transcribe",
},
parameters: {
docs: {
description: {
story:
"Shows the audio recorder interface with cancel/finish controls in transcription mode.",
},
source: {
code: `<CopilotChatInput mode="transcribe" />`,
},
},
},
};
export const CustomButtons: Story = {
args: {
sendButton: (props: React.ButtonHTMLAttributes<HTMLButtonElement>) => (
<button
{...props}
className="mr-2 flex h-10 w-10 items-center justify-center rounded-full bg-indigo-500 text-white transition hover:bg-indigo-600 disabled:opacity-40"
aria-label="Send message"
>
</button>
),
addMenuButton: {
className:
"border border-indigo-200 bg-white text-indigo-500 hover:bg-indigo-50",
},
},
parameters: {
docs: {
description: {
story:
"Overrides the send button with a custom component and tweaks the add menu button styling via slot props.",
},
source: {
code: `<CopilotChatInput
sendButton={(props) => (
<button
{...props}
className="mr-2 flex h-10 w-10 items-center justify-center rounded-full bg-indigo-500 text-white transition hover:bg-indigo-600 disabled:opacity-40"
aria-label="Send message"
>
✈️
</button>
)}
addMenuButton={{
className: "border border-indigo-200 bg-white text-indigo-500 hover:bg-indigo-50"
}}
/>`,
},
},
},
};
export const PrefilledText: Story = {
args: {
value: "Hello, this is a prefilled message!",
},
parameters: {
docs: {
description: {
story:
"Illustrates controlled usage by supplying a preset value to the textarea.",
},
source: {
code: `<CopilotChatInput value="Hello, this is a prefilled message!" />`,
},
},
},
};
export const ExpandedTextarea: Story = {
args: {
value:
"This is a longer message that will cause the textarea to expand to multiple rows.\n\nThe textarea remains beside the add button until a wrap occurs, then moves above the controls.",
textArea: {
maxRows: 10,
},
},
parameters: {
docs: {
description: {
story:
"Demonstrates automatic multiline layout when the message spans multiple rows.",
},
source: {
code: `<CopilotChatInput
value="This is a longer message that will cause the textarea to expand to multiple rows.
The textarea remains beside the add button until a wrap occurs, then moves above the controls."
textArea={{
maxRows: 10
}}
/>`,
},
},
},
};
export const CustomStyling: Story = {
decorators: [
(Story) => (
<>
<style>{`
.custom-chat-input {
border: 2px solid #4f46e5 !important;
border-radius: 14px !important;
background: linear-gradient(to right, #eef2ff, #ffffff) !important;
box-shadow: 0 4px 10px rgb(79 70 229 / 0.15) !important;
}
.custom-chat-input textarea {
font-family: 'JetBrains Mono', monospace !important;
font-size: 14px !important;
}
`}</style>
<Story />
</>
),
],
args: {
className: "custom-chat-input",
addMenuButton: {
className: "border border-indigo-300 bg-white text-indigo-600",
},
sendButton: {
className: "bg-indigo-500 text-white hover:bg-indigo-600",
},
},
parameters: {
docs: {
description: {
story:
"Applies custom classes to the container and key slots to achieve a distinct visual style.",
},
source: {
code: `<CopilotChatInput
className="custom-chat-input"
addMenuButton={{
className: "border border-indigo-300 bg-white text-indigo-600"
}}
sendButton={{
className: "bg-indigo-500 text-white hover:bg-indigo-600"
}}
/>`,
},
},
},
};
export const CustomLayout: Story = {
render: (args) => (
<CopilotChatInput {...args}>
{({ textArea, sendButton, addMenuButton, isMultiline }) => (
<div className="flex flex-col gap-3 rounded-2xl border border-slate-200 bg-white p-4 shadow-sm">
<div className="flex items-center justify-between">
<span className="text-sm font-medium text-slate-600">
{isMultiline ? "Multiline message" : "Single line message"}
</span>
{addMenuButton}
</div>
<div className="flex items-end gap-2">
<div className="flex-1">{textArea}</div>
{sendButton}
</div>
</div>
)}
</CopilotChatInput>
),
parameters: {
docs: {
description: {
story:
"Uses the render prop API to compose a custom layout while still leveraging the provided slots.",
},
source: {
code: `<CopilotChatInput>
{({ textArea, sendButton, addMenuButton, isMultiline }) => (
<div className="flex flex-col gap-3 rounded-2xl border border-slate-200 bg-white p-4 shadow-sm">
<div className="flex items-center justify-between">
<span className="text-sm font-medium text-slate-600">
{isMultiline ? "Multiline message" : "Single line message"}
</span>
{addMenuButton}
</div>
<div className="flex items-end gap-2">
<div className="flex-1">{textArea}</div>
{sendButton}
</div>
</div>
)}
</CopilotChatInput>`,
},
},
},
};
export const ControlledInputExample: Story = {
render: (args) => {
const [value, setValue] = useState("Draft message ready to send.");
return (
<CopilotChatInput
{...args}
value={value}
onChange={setValue}
onSubmitMessage={(submitted) => {
alert(`Submitted: ${submitted}`);
setValue("");
}}
/>
);
},
parameters: {
docs: {
description: {
story:
"Showcases a controlled input pattern with external state management.",
},
source: {
code: `const [value, setValue] = useState("Draft message ready to send.");
<CopilotChatInput
value={value}
onChange={setValue}
onSubmitMessage={(submitted) => {
alert(\`Submitted: \${submitted}\`);
setValue("");
}}
/>`,
},
},
},
};
@@ -0,0 +1,731 @@
import React from "react";
import type { Meta, StoryObj } from "@storybook/react";
import {
CopilotChatConfigurationProvider,
CopilotChatMessageView,
CopilotKitProvider,
} from "@copilotkit/react-core/v2";
import { z } from "zod";
import { ToolCallStatus } from "@copilotkit/core";
const STORYBOOK_THREAD_ID = "storybook-thread";
const meta = {
title: "UI/CopilotChatMessageView",
parameters: {
docs: {
description: {
component:
"A simple conversation between user and AI using CopilotChatMessageView component.",
},
},
},
} satisfies Meta<{}>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Default: Story = {
parameters: {
layout: "fullscreen",
},
decorators: [
(Story) => (
<div style={{ height: "100vh", margin: 0, padding: 0, overflow: "auto" }}>
<Story />
</div>
),
],
render: () => {
const messages = [
{
id: "user-1",
content: "Hello! Can you help me understand how React hooks work?",
timestamp: new Date(),
role: "user" as const,
},
{
id: "assistant-1",
content: `React hooks are functions that let you use state and other React features in functional components. Here are the most common ones:
- **useState** - Manages local state
- **useEffect** - Handles side effects
- **useContext** - Accesses context values
- **useCallback** - Memoizes functions
- **useMemo** - Memoizes values
Would you like me to explain any of these in detail?`,
timestamp: new Date(),
role: "assistant" as const,
},
{
id: "user-2",
content: "Yes, could you explain useState with a simple example?",
timestamp: new Date(),
role: "user" as const,
},
{
id: "assistant-2",
content: `Absolutely! Here's a simple useState example:
\`\`\`jsx
import React, { useState } from 'react';
function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
\`\`\`
In this example:
- \`useState(0)\` initializes the state with value 0
- It returns an array: \`[currentValue, setterFunction]\`
- \`count\` is the current state value
- \`setCount\` is the function to update the state`,
timestamp: new Date(),
role: "assistant" as const,
},
];
return (
<CopilotKitProvider runtimeUrl="https://copilotkit.ai">
<CopilotChatConfigurationProvider threadId={"123"}>
<div style={{ height: "100%" }}>
<CopilotChatMessageView
messages={messages}
assistantMessage={{
onThumbsUp: () => {
alert("thumbsUp");
},
onThumbsDown: () => {
alert("thumbsDown");
},
}}
/>
</div>
</CopilotChatConfigurationProvider>
</CopilotKitProvider>
);
},
};
export const ShowCursor: Story = {
parameters: {
layout: "fullscreen",
},
decorators: [
(Story) => (
<div style={{ height: "100vh", margin: 0, padding: 0, overflow: "auto" }}>
<Story />
</div>
),
],
render: () => {
const messages = [
{
id: "user-1",
content: "Can you explain how AI models work?",
timestamp: new Date(),
role: "user" as const,
},
];
return (
<CopilotKitProvider runtimeUrl="https://copilotkit.ai">
<CopilotChatConfigurationProvider threadId={"123"}>
<div style={{ height: "100%" }}>
<CopilotChatMessageView
messages={messages}
isRunning={true}
assistantMessage={{
onThumbsUp: () => {
alert("thumbsUp");
},
onThumbsDown: () => {
alert("thumbsDown");
},
}}
/>
</div>
</CopilotChatConfigurationProvider>
</CopilotKitProvider>
);
},
};
export const WithToolCalls: Story = {
parameters: {
layout: "fullscreen",
docs: {
description: {
story:
"Demonstrates tool call rendering with CopilotKitProvider's renderToolCalls prop",
},
source: {
type: "code",
code: `import {
CopilotChatConfigurationProvider,
CopilotChatMessageView,
CopilotKitProvider,
} from "@copilotkit/react-core/v2";
import { z } from "zod";
import { ToolCallStatus } from "@copilotkit/core";
// Define schemas for different tool arguments
const searchSchema = z.object({
query: z.string(),
filters: z.array(z.string()).optional(),
});
const calculatorSchema = z.object({
expression: z.string(),
variables: z.record(z.number()).optional(),
});
// Create render components for different tools
const SearchToolRender = ({ args, status, result }) => (
<div style={{
padding: "12px",
margin: "8px 0",
backgroundColor: status === ToolCallStatus.InProgress ? "#f0f4f8" : "#e6f3ff",
borderRadius: "8px",
border: "1px solid #cce0ff",
}}>
<div style={{ fontWeight: "bold", marginBottom: "4px" }}>🔍 Search Tool</div>
<div style={{ fontSize: "14px", color: "#666" }}>
Query: {args.query}
{args.filters && args.filters.length > 0 && (
<div>Filters: {args.filters.join(", ")}</div>
)}
</div>
{status === ToolCallStatus.InProgress && (
<div style={{ marginTop: "8px", color: "#0066cc" }}>Searching...</div>
)}
{status === ToolCallStatus.Complete && result && (
<div style={{ marginTop: "8px", color: "#006600" }}>
Results: {result}
</div>
)}
</div>
);
// Wildcard render for unmatched tools
const WildcardToolRender = ({ args, status, result }) => (
<div style={{
padding: "12px",
margin: "8px 0",
backgroundColor: "#f5f5f5",
borderRadius: "8px",
border: "1px solid #ddd",
}}>
<div style={{ fontWeight: "bold", marginBottom: "4px" }}>🔧 Tool Execution</div>
<div style={{ fontSize: "14px", color: "#666" }}>
<pre>{JSON.stringify(args, null, 2)}</pre>
</div>
{status === ToolCallStatus.InProgress && (
<div style={{ marginTop: "8px", color: "#666" }}>Processing...</div>
)}
{status === ToolCallStatus.Complete && result && (
<div style={{ marginTop: "8px", color: "#333" }}>
Output: {result}
</div>
)}
</div>
);
export function WithToolCallsExample() {
const messages = [
{
id: "user-1",
content: "Search for React hooks documentation and calculate 42 * 17",
timestamp: new Date(),
role: "user",
},
{
id: "assistant-1",
content: "I'll help you search for React hooks documentation and calculate that expression.",
timestamp: new Date(),
role: "assistant",
toolCalls: [
{
id: "search-1",
function: {
name: "search",
arguments: JSON.stringify({
query: "React hooks documentation",
filters: ["official", "latest"],
}),
},
},
{
id: "calc-1",
function: {
name: "calculator",
arguments: JSON.stringify({
expression: "42 * 17",
}),
},
},
],
},
{
id: "tool-search-1",
role: "tool",
toolCallId: "search-1",
content: "Found 5 relevant documentation pages about React hooks including useState, useEffect, and custom hooks.",
},
{
id: "tool-calc-1",
role: "tool",
toolCallId: "calc-1",
content: "714",
},
];
const renderToolCalls = [
{
name: "search",
args: searchSchema,
render: SearchToolRender,
},
{
name: "calculator",
args: calculatorSchema,
render: CalculatorToolRender,
},
{
name: "*",
args: z.any(),
render: WildcardToolRender,
},
];
return (
<CopilotKitProvider renderToolCalls={renderToolCalls}>
<CopilotChatConfigurationProvider threadId={"123}>
<div style={{ height: "100%" }}>
<CopilotChatMessageView messages={messages} />
</div>
</CopilotChatConfigurationProvider>
</CopilotKitProvider>
);
}`,
language: "tsx",
},
},
},
decorators: [
(Story) => (
<div style={{ height: "100vh", margin: 0, padding: 0, overflow: "auto" }}>
<Story />
</div>
),
],
render: () => {
// Global counter shared between calculator instances
const [globalCounter, setGlobalCounter] = React.useState(0);
// Define schemas for different tool arguments
const searchSchema = z.object({
query: z.string(),
filters: z.array(z.string()).optional(),
});
const calculatorSchema = z.object({
expression: z.string(),
variables: z.record(z.number()).optional(),
});
// Create render components for different tools
const SearchToolRender: React.FC<any> = ({ args, status, result }) => (
<div
style={{
padding: "12px",
margin: "8px 0",
backgroundColor:
status === ToolCallStatus.InProgress ? "#f0f4f8" : "#e6f3ff",
borderRadius: "8px",
border: "1px solid #cce0ff",
}}
>
<div style={{ fontWeight: "bold", marginBottom: "4px" }}>
🔍 Search Tool
</div>
<div style={{ fontSize: "14px", color: "#666" }}>
Query: {args?.query}
{args?.filters && args.filters.length > 0 && (
<div>Filters: {args.filters.join(", ")}</div>
)}
</div>
{status === ToolCallStatus.InProgress && (
<div style={{ marginTop: "8px", color: "#0066cc" }}>Searching...</div>
)}
{status === ToolCallStatus.Complete && result && (
<div style={{ marginTop: "8px", color: "#006600" }}>
Results: {result}
</div>
)}
</div>
);
const CalculatorToolRender: React.FC<any> = ({ args, status, result }) => {
const [counter, setCounter] = React.useState(0);
return (
<div
style={{
padding: "12px",
margin: "8px 0",
backgroundColor:
status === ToolCallStatus.InProgress ? "#fff9e6" : "#fff4cc",
borderRadius: "8px",
border: "1px solid #ffcc66",
}}
>
<div style={{ fontWeight: "bold", marginBottom: "4px" }}>
🧮 Calculator
</div>
<div style={{ fontSize: "14px", color: "#666" }}>
Expression: {args?.expression}
</div>
{status === ToolCallStatus.InProgress && (
<div style={{ marginTop: "8px", color: "#cc6600" }}>
Calculating...
</div>
)}
{status === ToolCallStatus.Complete && result && (
<div style={{ marginTop: "8px", color: "#006600" }}>
Result: {result}
</div>
)}
<div
style={{
marginTop: "12px",
padding: "8px",
backgroundColor: "#fff8e6",
borderRadius: "4px",
}}
>
<div
style={{ fontSize: "13px", color: "#666", marginBottom: "4px" }}
>
Local counter: {counter}
</div>
<div style={{ display: "flex", gap: "8px", marginBottom: "8px" }}>
<button
onClick={() => setCounter(counter - 1)}
style={{
padding: "4px 12px",
backgroundColor: "#ff9933",
color: "white",
border: "none",
borderRadius: "4px",
cursor: "pointer",
}}
>
-
</button>
<button
onClick={() => setCounter(counter + 1)}
style={{
padding: "4px 12px",
backgroundColor: "#ff9933",
color: "white",
border: "none",
borderRadius: "4px",
cursor: "pointer",
}}
>
+
</button>
</div>
<div style={{ borderTop: "1px solid #ffcc66", paddingTop: "8px" }}>
<div
style={{
fontSize: "13px",
color: "#666",
marginBottom: "4px",
fontWeight: "bold",
}}
>
Global counter: {globalCounter}
</div>
<div style={{ display: "flex", gap: "8px" }}>
<button
onClick={() => setGlobalCounter(globalCounter - 1)}
style={{
padding: "4px 12px",
backgroundColor: "#cc6600",
color: "white",
border: "none",
borderRadius: "4px",
cursor: "pointer",
}}
>
Global -
</button>
<button
onClick={() => setGlobalCounter(globalCounter + 1)}
style={{
padding: "4px 12px",
backgroundColor: "#cc6600",
color: "white",
border: "none",
borderRadius: "4px",
cursor: "pointer",
}}
>
Global +
</button>
</div>
</div>
</div>
</div>
);
};
// Wildcard render for unmatched tools
const WildcardToolRender: React.FC<any> = ({ args, status, result }) => (
<div
style={{
padding: "12px",
margin: "8px 0",
backgroundColor: "#f5f5f5",
borderRadius: "8px",
border: "1px solid #ddd",
}}
>
<div style={{ fontWeight: "bold", marginBottom: "4px" }}>
🔧 Tool Execution
</div>
<div style={{ fontSize: "14px", color: "#666" }}>
<pre>{JSON.stringify(args, null, 2)}</pre>
</div>
{status === ToolCallStatus.InProgress && (
<div style={{ marginTop: "8px", color: "#666" }}>Processing...</div>
)}
{status === ToolCallStatus.Complete && result && (
<div style={{ marginTop: "8px", color: "#333" }}>
Output: {result}
</div>
)}
</div>
);
const messages = [
{
id: "user-1",
content:
"Search for React hooks documentation, calculate 42 * 17 and 100 / 4 + 75, and check the weather in San Francisco",
timestamp: new Date(),
role: "user" as const,
},
{
id: "assistant-1",
content:
"I'll help you search for React hooks documentation, calculate both expressions, and check the weather.",
timestamp: new Date(),
role: "assistant" as const,
toolCalls: [
{
id: "search-1",
type: "function" as const,
function: {
name: "search",
arguments: JSON.stringify({
query: "React hooks documentation",
filters: ["official", "latest"],
}),
},
},
{
id: "calc-1",
type: "function" as const,
function: {
name: "calculator",
arguments: JSON.stringify({
expression: "42 * 17",
}),
},
},
{
id: "calc-2",
type: "function" as const,
function: {
name: "calculator",
arguments: JSON.stringify({
expression: "100 / 4 + 75",
}),
},
},
{
id: "weather-1",
type: "function" as const,
function: {
name: "getWeather",
arguments: '{"location": "San Francisco", "units": "fahren', // Intentionally cut off mid-word
},
},
],
},
{
id: "tool-search-1",
role: "tool" as const,
toolCallId: "search-1",
content:
"Found 5 relevant documentation pages about React hooks including useState, useEffect, and custom hooks.",
},
{
id: "tool-calc-1",
role: "tool" as const,
toolCallId: "calc-1",
content: "714",
},
{
id: "tool-calc-2",
role: "tool" as const,
toolCallId: "calc-2",
content: "100",
},
{
id: "tool-weather-1",
role: "tool" as const,
toolCallId: "weather-1",
content:
"Current weather in San Francisco: 68°F, partly cloudy with a gentle breeze.",
},
];
const renderToolCalls = React.useMemo(
() => [
{
name: "search",
args: searchSchema,
render: SearchToolRender,
},
{
name: "calculator",
args: calculatorSchema,
render: CalculatorToolRender,
},
{
name: "*",
args: z.any(),
render: WildcardToolRender,
},
],
[globalCounter],
);
return (
<CopilotKitProvider
renderToolCalls={renderToolCalls}
runtimeUrl="https://copilotkit.ai"
>
<CopilotChatConfigurationProvider threadId={"123"}>
<div style={{ height: "100%" }}>
<CopilotChatMessageView messages={messages} />
</div>
</CopilotChatConfigurationProvider>
</CopilotKitProvider>
);
},
};
export const DefaultToolRendererDarkTheme: Story = {
parameters: {
layout: "fullscreen",
docs: {
description: {
story:
"Shows the built-in zero-config tool-call fallback in a dark CopilotKit surface.",
},
},
},
decorators: [
(Story, context) => {
const isDarkTheme = context.globals.theme === "dark";
return (
<div
style={{
minHeight: "100vh",
margin: 0,
padding: "32px",
overflow: "auto",
background: isDarkTheme ? "#212121" : "#ffffff",
}}
>
<Story />
</div>
);
},
],
render: () => {
const messages = [
{
id: "user-default-tools",
content:
"Find the latest CopilotKit release notes and check npm status.",
timestamp: new Date(),
role: "user" as const,
},
{
id: "assistant-default-tools",
content: "I'll check both sources and summarize what I find.",
timestamp: new Date(),
role: "assistant" as const,
toolCalls: [
{
id: "release-notes-tool",
type: "function" as const,
function: {
name: "searchReleaseNotes",
arguments: JSON.stringify({
query: "CopilotKit release notes",
includePrereleases: false,
}),
},
},
{
id: "npm-status-tool",
type: "function" as const,
function: {
name: "checkPackageStatus",
arguments: JSON.stringify({
packageName: "@copilotkit/react-core",
}),
},
},
],
},
{
id: "tool-release-notes",
role: "tool" as const,
toolCallId: "release-notes-tool",
content:
"Found release notes for the current React core package and related runtime packages.",
},
];
return (
<CopilotKitProvider runtimeUrl="https://copilotkit.ai">
<CopilotChatConfigurationProvider threadId={STORYBOOK_THREAD_ID}>
<div style={{ width: "min(760px, 100%)", margin: "0 auto" }}>
<CopilotChatMessageView messages={messages} />
</div>
</CopilotChatConfigurationProvider>
</CopilotKitProvider>
);
},
};
@@ -0,0 +1,39 @@
import type { Meta, StoryObj } from "@storybook/react";
import { Sparkles, ArrowRight } from "lucide-react";
import { CopilotChatSuggestionPill } from "@copilotkit/react-core/v2";
const meta = {
title: "UI/CopilotChatSuggestionPill",
component: CopilotChatSuggestionPill,
args: {
children: "Draft a project brief",
},
parameters: {
layout: "centered",
},
} satisfies Meta<typeof CopilotChatSuggestionPill>;
export default meta;
type Story = StoryObj<typeof CopilotChatSuggestionPill>;
export const Default: Story = {};
export const WithIcon: Story = {
args: {
icon: <Sparkles className="h-4 w-4" aria-hidden="true" />,
},
};
export const Loading: Story = {
args: {
isLoading: true,
},
};
export const WithArrow: Story = {
args: {
icon: <ArrowRight className="h-4 w-4" aria-hidden="true" />,
children: "Summarize notes into next steps",
},
};
@@ -0,0 +1,58 @@
import type { Meta, StoryObj } from "@storybook/react";
import {
CopilotChatSuggestionView,
CopilotChatSuggestionPill,
} from "@copilotkit/react-core/v2";
import { Suggestion } from "@copilotkit/core";
import { Sparkles } from "lucide-react";
const suggestions: Suggestion[] = [
{
title: "Summarize this thread",
message: "Summarize the latest chat",
isLoading: false,
},
{
title: "Draft a reply",
message: "Draft a polite follow-up",
isLoading: false,
},
{
title: "Create action items",
message: "List next steps",
isLoading: false,
},
];
const meta = {
title: "UI/CopilotChatSuggestionView",
component: CopilotChatSuggestionView,
args: {
suggestions,
},
parameters: {
layout: "centered",
},
} satisfies Meta<typeof CopilotChatSuggestionView>;
export default meta;
type Story = StoryObj<typeof CopilotChatSuggestionView>;
export const Default: Story = {};
export const LoadingSecond: Story = {
args: {
suggestions: suggestions.map((suggestion, index) =>
index === 1 ? { ...suggestion, isLoading: true } : suggestion,
),
},
};
export const CustomSuggestionSlot: Story = {
args: {
suggestion: {
icon: <Sparkles className="h-4 w-4" aria-hidden="true" />,
},
},
};
@@ -0,0 +1,69 @@
import type { Meta, StoryObj } from "@storybook/react";
import { MessageCirclePlus, Minus } from "lucide-react";
import React from "react";
import {
CopilotChatConfigurationProvider,
CopilotChatToggleButton,
type CopilotChatToggleButtonProps,
useCopilotChatConfiguration,
} from "@copilotkit/react-core/v2";
const StatePreview: React.FC<CopilotChatToggleButtonProps> = (args) => {
const configuration = useCopilotChatConfiguration();
return (
<div className="flex flex-col items-center gap-3">
<CopilotChatToggleButton {...args} />
<span className="text-sm text-muted-foreground">
{configuration?.isModalOpen ? "Chat is open" : "Chat is closed"}
</span>
</div>
);
};
const meta = {
title: "UI/CopilotChatToggleButton",
component: CopilotChatToggleButton,
parameters: {
layout: "centered",
},
render: (args) => (
<CopilotChatConfigurationProvider threadId="storybook-toggle-button">
<StatePreview {...args} />
</CopilotChatConfigurationProvider>
),
} satisfies Meta<typeof CopilotChatToggleButton>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Default: Story = {};
export const WithCustomIcons: Story = {
args: {
openIcon: (props) => (
<MessageCirclePlus
{...props}
className={[props.className, "text-emerald-400"]
.filter(Boolean)
.join(" ")}
strokeWidth={1.5}
/>
),
closeIcon: (props) => (
<Minus
{...props}
className={[props.className, "text-rose-400"].filter(Boolean).join(" ")}
strokeWidth={2}
/>
),
},
};
export const Disabled: Story = {
args: {
disabled: true,
},
};
@@ -0,0 +1,349 @@
import type { Meta, StoryObj } from "@storybook/react";
import {
CopilotChatUserMessage,
CopilotChatConfigurationProvider,
type CopilotChatUserMessageProps,
} from "@copilotkit/react-core/v2";
// Simple default message
const simpleMessage = {
id: "simple-user-message",
content: "Hello! Can you help me build a React component?",
timestamp: new Date(),
role: "user" as const,
};
// Longer user message
const longMessage = {
id: "long-user-message",
content: `I need help with creating a complex React component that handles user authentication. Here are my requirements:
1. The component should have login and signup forms
2. It needs to integrate with Firebase Auth
3. Should handle form validation
4. Must be responsive and work on mobile
5. Include forgot password functionality
6. Support social login (Google, GitHub)
Can you help me implement this step by step? I'm particularly struggling with the form validation and state management parts.`,
timestamp: new Date(),
role: "user" as const,
};
// Code-related user message
const codeMessage = {
id: "code-user-message",
content: `I'm getting this error in my React app:
TypeError: Cannot read property 'map' of undefined
The error happens in this component:
function UserList({ users }) {
return (
<div>
{users.map(user => (
<div key={user.id}>{user.name}</div>
))}
</div>
);
}
How can I fix this?`,
timestamp: new Date(),
role: "user" as const,
};
// Short question
const shortMessage = {
id: "short-user-message",
content: "What's the difference between useState and useReducer?",
timestamp: new Date(),
role: "user" as const,
};
const meta = {
title: "UI/CopilotChatUserMessage",
component: CopilotChatUserMessage,
decorators: [
(Story) => (
<div
style={{
display: "flex",
justifyContent: "center",
alignItems: "flex-start",
minHeight: "100vh",
padding: "16px",
}}
>
<div style={{ width: "100%", maxWidth: "640px" }}>
<CopilotChatConfigurationProvider threadId="storybook-thread">
<Story />
</CopilotChatConfigurationProvider>
</div>
</div>
),
],
args: {
message: simpleMessage,
onEditMessage: () => console.log("Edit clicked!"),
},
} satisfies Meta<typeof CopilotChatUserMessage>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Default: Story = {
parameters: {
docs: {
source: {
code: `<CopilotChatUserMessage
message={{
id: "simple-user-message",
content: "Hello! Can you help me build a React component?",
timestamp: new Date(),
role: "user"
}}
/>`,
},
},
},
};
export const LongMessage: Story = {
args: {
message: longMessage,
},
};
export const WithEditButton: Story = {
args: {
message: simpleMessage,
onEditMessage: () => alert("Edit message clicked!"),
},
parameters: {
docs: {
source: {
code: `<CopilotChatUserMessage
message={{
id: "simple-user-message",
content: "Hello! Can you help me build a React component?",
timestamp: new Date(),
role: "user"
}}
onEditMessage={() => alert("Edit message clicked!")}
/>`,
},
},
},
};
export const WithoutEditButton: Story = {
args: {
message: simpleMessage,
onEditMessage: undefined, // No edit callback means no edit button
},
};
export const CodeRelatedMessage: Story = {
args: {
message: codeMessage,
onEditMessage: () => alert("Edit code message clicked!"),
},
};
export const ShortQuestion: Story = {
args: {
message: shortMessage,
onEditMessage: () => console.log("Edit short message clicked!"),
},
};
export const WithAdditionalToolbarItems: Story = {
args: {
message: simpleMessage,
onEditMessage: () => console.log("Edit clicked!"),
additionalToolbarItems: (
<>
<button
className="h-8 w-8 p-0 rounded-md bg-gray-100 hover:bg-gray-200 flex items-center justify-center"
onClick={() => alert("Custom button 1 clicked!")}
title="Custom Action 1"
>
📎
</button>
<button
className="h-8 w-8 p-0 rounded-md bg-gray-100 hover:bg-gray-200 flex items-center justify-center"
onClick={() => alert("Custom button 2 clicked!")}
title="Custom Action 2"
>
🔄
</button>
</>
),
},
parameters: {
docs: {
source: {
code: `<CopilotChatUserMessage
message={{
id: "simple-user-message",
content: "Hello! Can you help me build a React component?",
timestamp: new Date(),
role: "user"
}}
onEditMessage={() => console.log("Edit clicked!")}
additionalToolbarItems={
<>
<button
className="h-8 w-8 p-0 rounded-md bg-gray-100 hover:bg-gray-200 flex items-center justify-center"
onClick={() => alert("Custom button 1 clicked!")}
title="Custom Action 1"
>
📎
</button>
<button
className="h-8 w-8 p-0 rounded-md bg-gray-100 hover:bg-gray-200 flex items-center justify-center"
onClick={() => alert("Custom button 2 clicked!")}
title="Custom Action 2"
>
🔄
</button>
</>
}
/>`,
},
},
},
};
export const CustomAppearance: Story = {
args: {
message: simpleMessage,
onEditMessage: () => console.log("Edit clicked!"),
className: "bg-blue-50 border border-blue-200 rounded-lg p-4",
messageRenderer: ({ content }) => (
<div className="prose dark:prose-invert bg-muted relative max-w-[80%] rounded-[18px] px-4 py-1.5 data-[multiline]:py-3 inline-block whitespace-pre-wrap text-blue-900 font-medium">
{content}
</div>
),
toolbar: ({
children,
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) => (
<div
className="w-full bg-transparent flex items-center justify-end -mr-[5px] mt-[8px] invisible group-hover:visible"
{...props}
>
{children}
</div>
),
copyButton: ({
children,
className,
...props
}: React.ButtonHTMLAttributes<HTMLButtonElement>) => (
<button
className="h-8 w-8 p-0 rounded-md text-blue-600 hover:bg-blue-100 flex items-center justify-center"
{...props}
>
{children}
</button>
),
editButton: ({
children,
className,
...props
}: React.ButtonHTMLAttributes<HTMLButtonElement>) => (
<button
className="h-8 w-8 p-0 rounded-md text-blue-600 hover:bg-blue-100 flex items-center justify-center"
{...props}
>
{children}
</button>
),
},
};
export const CustomComponents: Story = {
args: {
message: simpleMessage,
onEditMessage: () => console.log("Edit clicked!"),
className:
"bg-gradient-to-r from-purple-100 to-pink-100 rounded-xl p-4 shadow-sm",
messageRenderer: ({ content }: { content: string; className?: string }) => (
<div className="font-mono text-purple-800 bg-white/50 rounded-lg px-3 py-2 inline-block">
💬 {content}
</div>
),
},
};
export const UsingChildrenRenderProp: Story = {
args: {
message: longMessage,
onEditMessage: () => console.log("Edit clicked!"),
children: ({
messageRenderer,
toolbar,
copyButton,
editButton,
}: {
messageRenderer: React.ReactElement;
toolbar: React.ReactElement;
copyButton: React.ReactElement;
editButton: React.ReactElement;
branchNavigation: React.ReactElement;
message: any;
branchIndex?: number;
numberOfBranches?: number;
additionalToolbarItems?: React.ReactNode;
}) => (
<div className="bg-yellow-50 border-l-4 border-yellow-400 p-4">
<div className="flex items-start justify-between">
<div className="flex-1 mr-4">{messageRenderer}</div>
<div className="flex items-center gap-1">
{copyButton}
{editButton}
</div>
</div>
<div className="mt-2 text-xs text-yellow-700">
Custom layout using children render prop
</div>
</div>
),
},
};
export const WithBranchNavigation: Story = {
args: {
message: {
id: "branch-message",
content:
"This message has multiple branches. You can navigate between them using the branch controls.",
role: "user" as const,
},
onEditMessage: () => console.log("Edit clicked!"),
branchIndex: 2,
numberOfBranches: 3,
onSwitchToBranch: ({ branchIndex }) =>
console.log(`Switching to branch ${branchIndex + 1}`),
},
};
export const WithManyBranches: Story = {
args: {
message: {
id: "many-branches-message",
content:
"This is branch 5 of 10. Use the navigation arrows to explore different variations of this message.",
role: "user" as const,
},
onEditMessage: () => console.log("Edit clicked!"),
branchIndex: 4,
numberOfBranches: 10,
onSwitchToBranch: ({ branchIndex }) =>
alert(`Would switch to branch ${branchIndex + 1} of 10`),
},
};
@@ -0,0 +1,298 @@
import type { Meta, StoryObj } from "@storybook/react";
import {
CopilotChatConfigurationProvider,
CopilotChatView,
CopilotKitProvider,
} from "@copilotkit/react-core/v2";
import { Suggestion } from "@copilotkit/core";
const meta = {
title: "UI/CopilotChatView",
parameters: {
docs: {
description: {
component:
"A complete chat interface with message feed and input components.",
},
},
},
} satisfies Meta<{}>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Default: Story = {
parameters: {
layout: "fullscreen",
},
decorators: [
(Story) => (
<div
style={{ height: "100vh", margin: 0, padding: 0, overflow: "hidden" }}
>
<Story />
</div>
),
],
render: () => {
return (
<CopilotKitProvider runtimeUrl="https://copilotkit.ai">
<CopilotChatConfigurationProvider threadId="storybook-thread">
<div style={{ height: "100%" }}>
<CopilotChatView
messages={storyMessages}
onSubmitMessage={(value) => {
alert(`Message submitted: ${value}`);
}}
messageView={{
assistantMessage: {
onThumbsUp: () => {
alert("thumbsUp");
},
onThumbsDown: () => {
alert("thumbsDown");
},
},
}}
/>
</div>
</CopilotChatConfigurationProvider>
</CopilotKitProvider>
);
},
};
export const PinToSend: Story = {
parameters: {
layout: "fullscreen",
docs: {
description: {
story:
"Pin-to-send mode anchors the user's last message at the top of the viewport when they submit. Useful for inspecting how content fades (or doesn't) above the input.",
},
},
},
decorators: Default.decorators,
render: () => {
return (
<CopilotKitProvider runtimeUrl="https://copilotkit.ai">
<CopilotChatConfigurationProvider threadId="storybook-pin-to-send">
<div style={{ height: "100%" }}>
<CopilotChatView
autoScroll="pin-to-send"
messages={pinToSendMessages}
onSubmitMessage={(value) => {
alert(`Message submitted: ${value}`);
}}
/>
</div>
</CopilotChatConfigurationProvider>
</CopilotKitProvider>
);
},
};
export const WithSuggestions: Story = {
parameters: {
layout: "fullscreen",
},
decorators: Default.decorators,
render: () => (
<CopilotKitProvider runtimeUrl="https://copilotkit.ai">
<CopilotChatConfigurationProvider threadId="storybook-thread">
<div style={{ height: "100%" }}>
<CopilotChatView
messages={storyMessages}
suggestions={suggestionSamples}
onSelectSuggestion={(suggestion) =>
alert(`Selected suggestion: ${suggestion.title}`)
}
onSubmitMessage={(value) => {
alert(`Message submitted: ${value}`);
}}
messageView={{
assistantMessage: {
onThumbsUp: () => alert("thumbsUp"),
onThumbsDown: () => alert("thumbsDown"),
},
}}
/>
</div>
</CopilotChatConfigurationProvider>
</CopilotKitProvider>
),
};
const suggestionSamples: Suggestion[] = [
{
title: "Summarize conversation",
message: "Summarize our latest messages",
isLoading: false,
},
{
title: "Draft reply",
message: "Draft a detailed response",
isLoading: false,
},
{
title: "List next steps",
message: "List action items from this chat",
isLoading: true,
},
];
const storyMessages = [
{
id: "user-1",
content: "Hello! Can you help me understand how React hooks work?",
timestamp: new Date(),
role: "user" as const,
},
{
id: "assistant-1",
content: `React hooks are functions that let you use state and other React features in functional components. Here are the most common ones:
- **useState** - Manages local state
- **useEffect** - Handles side effects
- **useContext** - Accesses context values
- **useCallback** - Memoizes functions
- **useMemo** - Memoizes values
Would you like me to explain any of these in detail?`,
timestamp: new Date(),
role: "assistant" as const,
},
{
id: "user-2",
content: "Yes, could you explain useState with a simple example?",
timestamp: new Date(),
role: "user" as const,
},
{
id: "assistant-2",
content: `Absolutely! Here's a simple useState example:
\`\`\`jsx
import React, { useState } from 'react';
function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
\`\`\`
In this example:
- \`useState(0)\` initializes the state with value 0
- It returns an array: \`[currentValue, setterFunction]\`
- \`count\` is the current state value
- \`setCount\` is the function to update the state`,
timestamp: new Date(),
role: "assistant" as const,
},
];
// Enough back-and-forth to force scrolling so the feather region above the
// input is clearly visible in pin-to-send mode.
const pinToSendMessages = [
{
id: "u1",
role: "user" as const,
content: "Give me a quick intro to useEffect.",
timestamp: new Date(),
},
{
id: "a1",
role: "assistant" as const,
content: `\`useEffect\` runs side effects after render. Common uses:
- Data fetching
- Subscriptions
- Manual DOM work
- Timers
It takes a callback and an optional dependency array. If the deps change between renders, the callback re-runs. Return a cleanup function to tear down subscriptions or timers.`,
timestamp: new Date(),
},
{
id: "u2",
role: "user" as const,
content: "Show me a subscription example.",
timestamp: new Date(),
},
{
id: "a2",
role: "assistant" as const,
content: `\`\`\`jsx
useEffect(() => {
const socket = new WebSocket(url);
socket.addEventListener("message", onMessage);
return () => socket.close();
}, [url]);
\`\`\`
The cleanup closes the socket if \`url\` changes or the component unmounts. Without it you'd leak connections on every dependency change.`,
timestamp: new Date(),
},
{
id: "u3",
role: "user" as const,
content: "What about running something only once on mount?",
timestamp: new Date(),
},
{
id: "a3",
role: "assistant" as const,
content: `Pass an empty dependency array:
\`\`\`jsx
useEffect(() => {
analytics.track("page_viewed");
}, []);
\`\`\`
With \`[]\`, React runs the effect once after the first render and never again (in production — Strict Mode runs it twice in dev to help surface cleanup bugs).`,
timestamp: new Date(),
},
{
id: "u4",
role: "user" as const,
content:
"How do I avoid the stale-closure trap when reading state inside an effect?",
timestamp: new Date(),
},
{
id: "a4",
role: "assistant" as const,
content: `A few options:
1. **Add the value to deps** so the effect re-subscribes with the fresh closure.
2. **Use a ref** (\`useRef\`) and read \`ref.current\` inside the callback — the ref always sees the latest value.
3. **Use functional setState** when updating: \`setCount(c => c + 1)\` avoids reading the stale \`count\`.
Dependency arrays are the honest answer — refs are an escape hatch when the value changes too often to re-subscribe on.`,
timestamp: new Date(),
},
{
id: "u5",
role: "user" as const,
content: "Anything to watch out for with async work inside useEffect?",
timestamp: new Date(),
},
{
id: "a5",
role: "assistant" as const,
content: `Two big ones:
1. **The effect itself can't be \`async\`.** Define an inner async function and call it: \`useEffect(() => { (async () => { ... })(); }, [])\`.
2. **Guard against unmount / stale responses.** If a fetch resolves after the component unmounts (or after a new request starts), you'll either set state on an unmounted component or overwrite newer data with older. An \`ignore\` flag in cleanup, or an \`AbortController\`, handles both.`,
timestamp: new Date(),
},
];
@@ -0,0 +1,15 @@
import type { Meta, StoryObj } from "@storybook/react";
import { CopilotKitInspector } from "@copilotkit/react-core/v2";
const meta: Meta<typeof CopilotKitInspector> = {
title: "Components/CopilotKit Inspector",
component: CopilotKitInspector,
};
export default meta;
type Story = StoryObj<typeof CopilotKitInspector>;
export const Default: Story = {
args: {},
};
@@ -0,0 +1,73 @@
import type { Meta, StoryObj } from "@storybook/react";
import React from "react";
import {
CopilotModalHeader,
CopilotSidebarView,
type CopilotSidebarViewProps,
} from "@copilotkit/react-core/v2";
import { CopilotStoryLayout } from "./CopilotStoryLayout";
const meta = {
title: "UI/CopilotSidebarView",
component: CopilotSidebarView,
parameters: {
layout: "fullscreen",
},
render: (args) => (
<CopilotStoryLayout>
<CopilotSidebarView {...(args as CopilotSidebarViewProps)} />
</CopilotStoryLayout>
),
} satisfies Meta<typeof CopilotSidebarView>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Default: Story = {
args: {
autoScroll: true,
},
};
export const RightPosition: Story = {
args: {
autoScroll: true,
position: "right",
width: 480,
},
};
export const LeftPosition: Story = {
args: {
autoScroll: true,
position: "left",
width: 480,
},
};
export const CustomHeader: Story = {
args: {
header: {
title: "Workspace Copilot",
titleContent: (props) => (
<CopilotModalHeader.Title
{...props}
className="text-lg font-semibold tracking-tight text-foreground"
>
<span>{props.children}</span>
<span className="mt-1 block text-xs font-normal text-muted-foreground">
Always-on teammate
</span>
</CopilotModalHeader.Title>
),
closeButton: (props) => (
<CopilotModalHeader.CloseButton
{...props}
className="text-destructive hover:bg-destructive/10 hover:text-destructive"
/>
),
},
},
};
@@ -0,0 +1,67 @@
import React from "react";
import {
CopilotChatConfigurationProvider,
CopilotKitProvider,
} from "@copilotkit/react-core/v2";
interface CopilotStoryLayoutProps {
children: React.ReactNode;
threadId?: string;
isModalDefaultOpen?: boolean;
content?: React.ReactNode;
}
const defaultContent = (
<>
<section className="space-y-4">
<h1 className="text-3xl font-semibold tracking-tight">
Project Dashboard
</h1>
<p className="text-muted-foreground">
Toggle the assistant to draft updates, summarize discussions, and keep
track of action items while you stay in context.
</p>
</section>
<div className="grid gap-4 sm:grid-cols-2">
{Array.from({ length: 4 }).map((_, index) => (
<article
key={index}
className="rounded-xl border border-border bg-card p-4 shadow-sm transition hover:shadow-md"
>
<h2 className="text-lg font-medium">Task {index + 1}</h2>
<p className="mt-2 text-sm text-muted-foreground">
Placeholder content to illustrate how the assistant integrates
alongside your existing workflow.
</p>
</article>
))}
</div>
</>
);
class MockAgent {}
export const CopilotStoryLayout: React.FC<CopilotStoryLayoutProps> = ({
children,
threadId = "story-copilot-layout",
isModalDefaultOpen = true,
content,
}) => (
<CopilotKitProvider runtimeUrl="https://copilotkit.ai">
<CopilotChatConfigurationProvider
threadId={"123"}
isModalDefaultOpen={isModalDefaultOpen}
>
<div className="min-h-screen bg-background text-foreground">
<div className="mx-auto grid max-w-5xl grid-cols-1 gap-6 px-6 py-10">
{content ?? defaultContent}
</div>
{children}
</div>
</CopilotChatConfigurationProvider>
</CopilotKitProvider>
);
export default CopilotStoryLayout;
@@ -0,0 +1,34 @@
import type { Meta, StoryObj } from "@storybook/react-webpack5";
import { fn } from "storybook/test";
import { Header } from "./Header";
const meta = {
title: "Example/Header",
component: Header,
// This component will have an automatically generated Autodocs entry: https://storybook.js.org/docs/writing-docs/autodocs
tags: ["autodocs"],
parameters: {
// More on how to position stories at: https://storybook.js.org/docs/configure/story-layout
layout: "fullscreen",
},
args: {
onLogin: fn(),
onLogout: fn(),
onCreateAccount: fn(),
},
} satisfies Meta<typeof Header>;
export default meta;
type Story = StoryObj<typeof meta>;
export const LoggedIn: Story = {
args: {
user: {
name: "Jane Doe",
},
},
};
export const LoggedOut: Story = {};
@@ -0,0 +1,69 @@
import { Button } from "./Button";
import "./header.css";
type User = {
name: string;
};
export interface HeaderProps {
user?: User;
onLogin?: () => void;
onLogout?: () => void;
onCreateAccount?: () => void;
}
export const Header = ({
user,
onLogin,
onLogout,
onCreateAccount,
}: HeaderProps) => (
<header>
<div className="storybook-header">
<div>
<svg
width="32"
height="32"
viewBox="0 0 32 32"
xmlns="http://www.w3.org/2000/svg"
>
<g fill="none" fillRule="evenodd">
<path
d="M10 0h12a10 10 0 0110 10v12a10 10 0 01-10 10H10A10 10 0 010 22V10A10 10 0 0110 0z"
fill="#FFF"
/>
<path
d="M5.3 10.6l10.4 6v11.1l-10.4-6v-11zm11.4-6.2l9.7 5.5-9.7 5.6V4.4z"
fill="#555AB9"
/>
<path
d="M27.2 10.6v11.2l-10.5 6V16.5l10.5-6zM15.7 4.4v11L6 10l9.7-5.5z"
fill="#91BAF8"
/>
</g>
</svg>
<h1>Acme</h1>
</div>
<div>
{user ? (
<>
<span className="welcome">
Welcome, <b>{user.name}</b>!
</span>
<Button size="small" onClick={onLogout} label="Log out" />
</>
) : (
<>
<Button size="small" onClick={onLogin} label="Log in" />
<Button
primary
size="small"
onClick={onCreateAccount}
label="Sign up"
/>
</>
)}
</div>
</div>
</header>
);
@@ -0,0 +1,183 @@
import React, { useEffect, useState } from "react";
import type { Meta, StoryObj } from "@storybook/react";
import { IntelligenceIndicatorView } from "@copilotkit/react-core/v2";
import type { IntelligenceIndicatorViewProps } from "@copilotkit/react-core/v2";
/**
* `IntelligenceIndicatorView` is the presentational face of the
* "CopilotKit Intelligence" indicator — the default rendered by the
* `intelligenceIndicator` slot on `CopilotChat`. Single-element
* three-stage design: glassmorphism chrome around a 270° arc that
* morphs to a checkmark on `status="finished"`, after which the
* chrome fades and the label settles to a neutral gray with a slight
* italic-like slant.
*
* The orchestration brain (`IntelligenceIndicator`) is provider- and
* agent-driven, so it's covered by unit/e2e tests rather than stories.
*/
const mockMessage = {
id: "story-message",
role: "assistant" as const,
content: "",
};
const meta = {
title: "UI/IntelligenceIndicator",
component: IntelligenceIndicatorView,
decorators: [
(Story) => (
<div
style={{
display: "flex",
justifyContent: "center",
alignItems: "center",
minHeight: "240px",
padding: "32px",
}}
>
<Story />
</div>
),
],
args: {
message: mockMessage,
label: "CopilotKit Intelligence",
status: "in-progress",
},
} satisfies Meta<typeof IntelligenceIndicatorView>;
export default meta;
type Story = StoryObj<typeof meta>;
/** Active phase: glassmorphism pill with a spinning ring. */
export const InProgress: Story = {
args: { status: "in-progress" },
};
/**
* Persistent finished phase: compact icon + text tag. This is what
* sits quietly in chat history per turn — one tag per agent turn that
* used Intelligence, kept forever in scroll-back.
*/
export const Finished: Story = {
args: { status: "finished" },
};
/** Props-tier slot override demo: a custom label passed through the slot. */
export const WithCustomLabel: Story = {
args: { status: "finished", label: "Recalling memory" },
};
/**
* Animated timeline: enter as `in-progress`, then flip to `finished`
* after ~1.5s. The face's built-in opacity cross-fade swaps the
* spinner pill out for the persistent tag. Click "Restart" to replay.
*/
export const Playground: Story = {
render: (args) => {
const [tick, setTick] = useState(0);
const [status, setStatus] = useState<"in-progress" | "finished">(
"in-progress",
);
useEffect(() => {
setStatus("in-progress");
const t = setTimeout(() => setStatus("finished"), 1500);
return () => clearTimeout(t);
}, [tick]);
return (
<div
style={{
display: "flex",
flexDirection: "column",
gap: "16px",
alignItems: "center",
}}
>
<IntelligenceIndicatorView {...args} status={status} />
<button
onClick={() => setTick((n) => n + 1)}
style={{
padding: "6px 14px",
borderRadius: 6,
border: "1px solid #d4d4d8",
background: "#fafafa",
cursor: "pointer",
}}
>
Restart
</button>
</div>
);
},
};
/**
* A fully custom face — what a slot consumer would render when they
* pass `<CopilotChat intelligenceIndicator={MyCustomIndicator} />`. It
* receives the same `{ status, label, message }` props the brain hands
* the default face, and decides its own look from `status`.
*/
const CustomFace: React.FC<IntelligenceIndicatorViewProps> = ({
status,
label,
}) => {
const isFinished = status === "finished";
return (
<span
style={{
display: "inline-flex",
alignItems: "center",
gap: 8,
padding: "6px 14px",
borderRadius: 999,
background: isFinished ? "#dcfce7" : "#fef3c7",
color: isFinished ? "#166534" : "#92400e",
fontSize: 13,
fontWeight: 600,
transition: "background 360ms ease-out, color 360ms ease-out",
}}
>
<span>{isFinished ? "✅" : "⏳"}</span>
<span>{isFinished ? `${label} — done` : `${label}`}</span>
</span>
);
};
export const FullyCustomFace: Story = {
render: (args) => {
const [tick, setTick] = useState(0);
const [status, setStatus] = useState<"in-progress" | "finished">(
"in-progress",
);
useEffect(() => {
setStatus("in-progress");
const t = setTimeout(() => setStatus("finished"), 1500);
return () => clearTimeout(t);
}, [tick]);
return (
<div
style={{
display: "flex",
flexDirection: "column",
gap: "16px",
alignItems: "center",
}}
>
<CustomFace {...args} status={status} />
<button
onClick={() => setTick((n) => n + 1)}
style={{
padding: "6px 14px",
borderRadius: 6,
border: "1px solid #d4d4d8",
background: "#fafafa",
cursor: "pointer",
}}
>
Restart
</button>
</div>
);
},
};
@@ -0,0 +1,33 @@
import type { Meta, StoryObj } from "@storybook/react-webpack5";
import { expect, userEvent, within } from "storybook/test";
import { Page } from "./Page";
const meta = {
title: "Example/Page",
component: Page,
parameters: {
// More on how to position stories at: https://storybook.js.org/docs/configure/story-layout
layout: "fullscreen",
},
} satisfies Meta<typeof Page>;
export default meta;
type Story = StoryObj<typeof meta>;
export const LoggedOut: Story = {};
// More on component testing: https://storybook.js.org/docs/writing-tests/interaction-testing
export const LoggedIn: Story = {
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
const loginButton = canvas.getByRole("button", { name: /Log in/i });
await expect(loginButton).toBeInTheDocument();
await userEvent.click(loginButton);
await expect(loginButton).not.toBeInTheDocument();
const logoutButton = canvas.getByRole("button", { name: /Log out/i });
await expect(logoutButton).toBeInTheDocument();
},
};
@@ -0,0 +1,91 @@
import React from "react";
import { Header } from "./Header";
import "./page.css";
type User = {
name: string;
};
export const Page: React.FC = () => {
const [user, setUser] = React.useState<User>();
return (
<article>
<Header
user={user}
onLogin={() => setUser({ name: "Jane Doe" })}
onLogout={() => setUser(undefined)}
onCreateAccount={() => setUser({ name: "Jane Doe" })}
/>
<section className="storybook-page">
<h2>Pages in Storybook</h2>
<p>
We recommend building UIs with a{" "}
<a
href="https://componentdriven.org"
target="_blank"
rel="noopener noreferrer"
>
<strong>component-driven</strong>
</a>{" "}
process starting with atomic components and ending with pages.
</p>
<p>
Render pages with mock data. This makes it easy to build and review
page states without needing to navigate to them in your app. Here are
some handy patterns for managing page data in Storybook:
</p>
<ul>
<li>
Use a higher-level connected component. Storybook helps you compose
such data from the "args" of child component stories
</li>
<li>
Assemble data in the page component from your services. You can mock
these services out using Storybook.
</li>
</ul>
<p>
Get a guided tutorial on component-driven development at{" "}
<a
href="https://storybook.js.org/tutorials/"
target="_blank"
rel="noopener noreferrer"
>
Storybook tutorials
</a>
. Read more in the{" "}
<a
href="https://storybook.js.org/docs"
target="_blank"
rel="noopener noreferrer"
>
docs
</a>
.
</p>
<div className="tip-wrapper">
<span className="tip">Tip</span> Adjust the width of the canvas with
the{" "}
<svg
width="10"
height="10"
viewBox="0 0 12 12"
xmlns="http://www.w3.org/2000/svg"
>
<g fill="none" fillRule="evenodd">
<path
d="M1.5 5.2h4.8c.3 0 .5.2.5.4v5.1c-.1.2-.3.3-.4.3H1.4a.5.5 0 01-.5-.4V5.7c0-.3.2-.5.5-.5zm0-2.1h6.9c.3 0 .5.2.5.4v7a.5.5 0 01-1 0V4H1.5a.5.5 0 010-1zm0-2.1h9c.3 0 .5.2.5.4v9.1a.5.5 0 01-1 0V2H1.5a.5.5 0 010-1zm4.3 5.2H2V10h3.8V6.2z"
id="a"
fill="#999"
/>
</g>
</svg>
Viewports addon in the toolbar
</div>
</section>
</article>
);
};
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:cd6d60f7f8fee968e7687b6d886f766990424923f2a4c4b7d03b7ba54ccee191
size 42336
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" fill="none" viewBox="0 0 48 48"><title>Accessibility</title><circle cx="24.334" cy="24" r="24" fill="#A849FF" fill-opacity=".3"/><path fill="#A470D5" fill-rule="evenodd" d="M27.8609 11.585C27.8609 9.59506 26.2497 7.99023 24.2519 7.99023C22.254 7.99023 20.6429 9.65925 20.6429 11.585C20.6429 13.575 22.254 15.1799 24.2519 15.1799C26.2497 15.1799 27.8609 13.575 27.8609 11.585ZM21.8922 22.6473C21.8467 23.9096 21.7901 25.4788 21.5897 26.2771C20.9853 29.0462 17.7348 36.3314 17.3325 37.2275C17.1891 37.4923 17.1077 37.7955 17.1077 38.1178C17.1077 39.1519 17.946 39.9902 18.9802 39.9902C19.6587 39.9902 20.253 39.6293 20.5814 39.0889L20.6429 38.9874L24.2841 31.22C24.2841 31.22 27.5529 37.9214 27.9238 38.6591C28.2948 39.3967 28.8709 39.9902 29.7168 39.9902C30.751 39.9902 31.5893 39.1519 31.5893 38.1178C31.5893 37.7951 31.3639 37.2265 31.3639 37.2265C30.9581 36.3258 27.698 29.0452 27.0938 26.2771C26.8975 25.4948 26.847 23.9722 26.8056 22.7236C26.7927 22.333 26.7806 21.9693 26.7653 21.6634C26.7008 21.214 27.0231 20.8289 27.4097 20.7005L35.3366 18.3253C36.3033 18.0685 36.8834 16.9773 36.6256 16.0144C36.3678 15.0515 35.2722 14.4737 34.3055 14.7305C34.3055 14.7305 26.8619 17.1057 24.2841 17.1057C21.7062 17.1057 14.456 14.7947 14.456 14.7947C13.4893 14.5379 12.3937 14.9873 12.0715 15.9502C11.7493 16.9131 12.3293 18.0044 13.3604 18.3253L21.2873 20.7005C21.674 20.8289 21.9318 21.214 21.9318 21.6634C21.9174 21.9493 21.9053 22.2857 21.8922 22.6473Z" clip-rule="evenodd"/></svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:bc7ba70560462f6ddc4d22a6fcaaf40e577b2f37f3c3df88fcaa5b08a708a5e8
size 467366
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:2c90cb173ddcf13fec797baf538fbb211f4675eb6cd438b09ec64a84d7a2ffde
size 3899
Binary file not shown.

After

Width:  |  Height:  |  Size: 829 B

@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:c612d889d515d6e680ecf1131c4f116532eb19ae17654c6df5bc8ff0864f96fa
size 6119
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="33" height="32" fill="none" viewBox="0 0 33 32"><g clip-path="url(#clip0_10031_177575)"><mask id="mask0_10031_177575" style="mask-type:luminance" width="33" height="25" x="0" y="4" maskUnits="userSpaceOnUse"><path fill="#fff" d="M32.5034 4.00195H0.503906V28.7758H32.5034V4.00195Z"/></mask><g mask="url(#mask0_10031_177575)"><path fill="#5865F2" d="M27.5928 6.20817C25.5533 5.27289 23.3662 4.58382 21.0794 4.18916C21.0378 4.18154 20.9962 4.20057 20.9747 4.23864C20.6935 4.73863 20.3819 5.3909 20.1637 5.90358C17.7042 5.53558 15.2573 5.53558 12.8481 5.90358C12.6299 5.37951 12.307 4.73863 12.0245 4.23864C12.003 4.20184 11.9614 4.18281 11.9198 4.18916C9.63431 4.58255 7.44721 5.27163 5.40641 6.20817C5.38874 6.21578 5.3736 6.22848 5.36355 6.24497C1.21508 12.439 0.078646 18.4809 0.636144 24.4478C0.638667 24.477 0.655064 24.5049 0.677768 24.5227C3.41481 26.5315 6.06609 27.7511 8.66815 28.5594C8.70979 28.5721 8.75392 28.5569 8.78042 28.5226C9.39594 27.6826 9.94461 26.7968 10.4151 25.8653C10.4428 25.8107 10.4163 25.746 10.3596 25.7244C9.48927 25.3945 8.66058 24.9922 7.86343 24.5354C7.80038 24.4986 7.79533 24.4084 7.85333 24.3653C8.02108 24.2397 8.18888 24.109 8.34906 23.977C8.37804 23.9529 8.41842 23.9478 8.45249 23.963C13.6894 26.3526 19.359 26.3526 24.5341 23.963C24.5682 23.9465 24.6086 23.9516 24.6388 23.9757C24.799 24.1077 24.9668 24.2397 25.1358 24.3653C25.1938 24.4084 25.19 24.4986 25.127 24.5354C24.3298 25.0011 23.5011 25.3945 22.6296 25.7232C22.5728 25.7447 22.5476 25.8107 22.5754 25.8653C23.0559 26.7955 23.6046 27.6812 24.2087 28.5213C24.234 28.5569 24.2794 28.5721 24.321 28.5594C26.9357 27.7511 29.5869 26.5315 32.324 24.5227C32.348 24.5049 32.3631 24.4783 32.3656 24.4491C33.0328 17.5506 31.2481 11.5584 27.6344 6.24623C27.6256 6.22848 27.6105 6.21578 27.5928 6.20817ZM11.1971 20.8146C9.62043 20.8146 8.32129 19.3679 8.32129 17.5913C8.32129 15.8146 9.59523 14.368 11.1971 14.368C12.8115 14.368 14.0981 15.8273 14.0729 17.5913C14.0729 19.3679 12.7989 20.8146 11.1971 20.8146ZM21.8299 20.8146C20.2533 20.8146 18.9541 19.3679 18.9541 17.5913C18.9541 15.8146 20.228 14.368 21.8299 14.368C23.4444 14.368 24.7309 15.8273 24.7057 17.5913C24.7057 19.3679 23.4444 20.8146 21.8299 20.8146Z"/></g></g><defs><clipPath id="clip0_10031_177575"><rect width="32" height="32" fill="#fff" transform="translate(0.5)"/></clipPath></defs></svg>

After

Width:  |  Height:  |  Size: 2.3 KiB

@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:5b0c7100c80607174ad6e930376103fae0d729328e9de85ee292b36b6b102214
size 27875
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:b0a5ad2d021efea93a13ae3829211714ab162a49b0b9e215559dab01c2328149
size 44246
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" fill="none" viewBox="0 0 32 32"><path fill="#161614" d="M16.0001 0C7.16466 0 0 7.17472 0 16.0256C0 23.1061 4.58452 29.1131 10.9419 31.2322C11.7415 31.3805 12.0351 30.8845 12.0351 30.4613C12.0351 30.0791 12.0202 28.8167 12.0133 27.4776C7.56209 28.447 6.62283 25.5868 6.62283 25.5868C5.89499 23.7345 4.8463 23.2419 4.8463 23.2419C3.39461 22.2473 4.95573 22.2678 4.95573 22.2678C6.56242 22.3808 7.40842 23.9192 7.40842 23.9192C8.83547 26.3691 11.1514 25.6609 12.0645 25.2514C12.2081 24.2156 12.6227 23.5087 13.0803 23.1085C9.52648 22.7032 5.7906 21.3291 5.7906 15.1886C5.7906 13.4389 6.41563 12.0094 7.43916 10.8871C7.27303 10.4834 6.72537 8.85349 7.59415 6.64609C7.59415 6.64609 8.93774 6.21539 11.9953 8.28877C13.2716 7.9337 14.6404 7.75563 16.0001 7.74953C17.3599 7.75563 18.7297 7.9337 20.0084 8.28877C23.0623 6.21539 24.404 6.64609 24.404 6.64609C25.2749 8.85349 24.727 10.4834 24.5608 10.8871C25.5868 12.0094 26.2075 13.4389 26.2075 15.1886C26.2075 21.3437 22.4645 22.699 18.9017 23.0957C19.4756 23.593 19.9869 24.5683 19.9869 26.0634C19.9869 28.2077 19.9684 29.9334 19.9684 30.4613C19.9684 30.8877 20.2564 31.3874 21.0674 31.2301C27.4213 29.1086 32 23.1037 32 16.0256C32 7.17472 24.8364 0 16.0001 0ZM5.99257 22.8288C5.95733 22.9084 5.83227 22.9322 5.71834 22.8776C5.60229 22.8253 5.53711 22.7168 5.57474 22.6369C5.60918 22.5549 5.7345 22.5321 5.85029 22.587C5.9666 22.6393 6.03284 22.7489 5.99257 22.8288ZM6.7796 23.5321C6.70329 23.603 6.55412 23.5701 6.45291 23.4581C6.34825 23.3464 6.32864 23.197 6.40601 23.125C6.4847 23.0542 6.62937 23.0874 6.73429 23.1991C6.83895 23.3121 6.85935 23.4605 6.7796 23.5321ZM7.31953 24.4321C7.2215 24.5003 7.0612 24.4363 6.96211 24.2938C6.86407 24.1513 6.86407 23.9804 6.96422 23.9119C7.06358 23.8435 7.2215 23.905 7.32191 24.0465C7.41968 24.1914 7.41968 24.3623 7.31953 24.4321ZM8.23267 25.4743C8.14497 25.5712 7.95818 25.5452 7.82146 25.413C7.68156 25.2838 7.64261 25.1004 7.73058 25.0035C7.81934 24.9064 8.00719 24.9337 8.14497 25.0648C8.28381 25.1938 8.3262 25.3785 8.23267 25.4743ZM9.41281 25.8262C9.37413 25.9517 9.19423 26.0088 9.013 25.9554C8.83203 25.9005 8.7136 25.7535 8.75016 25.6266C8.78778 25.5003 8.96848 25.4408 9.15104 25.4979C9.33174 25.5526 9.45044 25.6985 9.41281 25.8262ZM10.7559 25.9754C10.7604 26.1076 10.6067 26.2172 10.4165 26.2196C10.2252 26.2238 10.0704 26.1169 10.0683 25.9868C10.0683 25.8534 10.2185 25.7448 10.4098 25.7416C10.6001 25.7379 10.7559 25.8441 10.7559 25.9754ZM12.0753 25.9248C12.0981 26.0537 11.9658 26.1862 11.7769 26.2215C11.5912 26.2554 11.4192 26.1758 11.3957 26.0479C11.3726 25.9157 11.5072 25.7833 11.6927 25.7491C11.8819 25.7162 12.0512 25.7937 12.0753 25.9248Z"/></svg>

After

Width:  |  Height:  |  Size: 2.7 KiB

@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:b59d6c77b3ac1fc89d6d026e9e70ba5204801d6864022e801322ce8d579bcbd9
size 40767
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:c83082e0e8386ae5d56e13dc4cd85b06a53162a4b6575a0fbb2e45c95ec7f82e
size 7237
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:6a59f68143c561f5feb549cf417fa084d0c761b856b0d9903f4fed44d850da16
size 49313
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:b6e819c3dd77c466a6509b199a33dd2bbc9471b42891c3fcb469d9bf849bc976
size 44374
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="33" height="32" fill="none" viewBox="0 0 33 32"><g clip-path="url(#clip0_10031_177597)"><path fill="#B7F0EF" fill-rule="evenodd" d="M17 7.87059C17 6.48214 17.9812 5.28722 19.3431 5.01709L29.5249 2.99755C31.3238 2.64076 33 4.01717 33 5.85105V22.1344C33 23.5229 32.0188 24.7178 30.6569 24.9879L20.4751 27.0074C18.6762 27.3642 17 25.9878 17 24.1539L17 7.87059Z" clip-rule="evenodd" opacity=".7"/><path fill="#87E6E5" fill-rule="evenodd" d="M1 5.85245C1 4.01857 2.67623 2.64215 4.47507 2.99895L14.6569 5.01848C16.0188 5.28861 17 6.48354 17 7.87198V24.1553C17 25.9892 15.3238 27.3656 13.5249 27.0088L3.34311 24.9893C1.98119 24.7192 1 23.5242 1 22.1358V5.85245Z" clip-rule="evenodd"/><path fill="#61C1FD" fill-rule="evenodd" d="M15.543 5.71289C15.543 5.71289 16.8157 5.96289 17.4002 6.57653C17.9847 7.19016 18.4521 9.03107 18.4521 9.03107C18.4521 9.03107 18.4521 25.1106 18.4521 26.9629C18.4521 28.8152 19.3775 31.4174 19.3775 31.4174L17.4002 28.8947L16.2575 31.4174C16.2575 31.4174 15.543 29.0765 15.543 27.122C15.543 25.1674 15.543 5.71289 15.543 5.71289Z" clip-rule="evenodd"/></g><defs><clipPath id="clip0_10031_177597"><rect width="32" height="32" fill="#fff" transform="translate(0.5)"/></clipPath></defs></svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" fill="none" viewBox="0 0 32 32"><path fill="#ED1D24" d="M31.3313 8.44657C30.9633 7.08998 29.8791 6.02172 28.5022 5.65916C26.0067 5.00026 16 5.00026 16 5.00026C16 5.00026 5.99333 5.00026 3.4978 5.65916C2.12102 6.02172 1.03665 7.08998 0.668678 8.44657C0 10.9053 0 16.0353 0 16.0353C0 16.0353 0 21.1652 0.668678 23.6242C1.03665 24.9806 2.12102 26.0489 3.4978 26.4116C5.99333 27.0703 16 27.0703 16 27.0703C16 27.0703 26.0067 27.0703 28.5022 26.4116C29.8791 26.0489 30.9633 24.9806 31.3313 23.6242C32 21.1652 32 16.0353 32 16.0353C32 16.0353 32 10.9053 31.3313 8.44657Z"/><path fill="#fff" d="M12.7266 20.6934L21.0902 16.036L12.7266 11.3781V20.6934Z"/></svg>

After

Width:  |  Height:  |  Size: 716 B

@@ -0,0 +1,30 @@
.storybook-button {
display: inline-block;
cursor: pointer;
border: 0;
border-radius: 3em;
font-weight: 700;
line-height: 1;
font-family: "Nunito Sans", "Helvetica Neue", Helvetica, Arial, sans-serif;
}
.storybook-button--primary {
background-color: #555ab9;
color: white;
}
.storybook-button--secondary {
box-shadow: rgba(0, 0, 0, 0.15) 0px 0px 0px 1px inset;
background-color: transparent;
color: #333;
}
.storybook-button--small {
padding: 10px 16px;
font-size: 12px;
}
.storybook-button--medium {
padding: 11px 20px;
font-size: 14px;
}
.storybook-button--large {
padding: 12px 24px;
font-size: 16px;
}
@@ -0,0 +1,32 @@
.storybook-header {
display: flex;
justify-content: space-between;
align-items: center;
border-bottom: 1px solid rgba(0, 0, 0, 0.1);
padding: 15px 20px;
font-family: "Nunito Sans", "Helvetica Neue", Helvetica, Arial, sans-serif;
}
.storybook-header svg {
display: inline-block;
vertical-align: top;
}
.storybook-header h1 {
display: inline-block;
vertical-align: top;
margin: 6px 0 6px 10px;
font-weight: 700;
font-size: 20px;
line-height: 1;
}
.storybook-header button + button {
margin-left: 10px;
}
.storybook-header .welcome {
margin-right: 10px;
color: #333;
font-size: 14px;
}
@@ -0,0 +1,68 @@
.storybook-page {
margin: 0 auto;
padding: 48px 20px;
max-width: 600px;
color: #333;
font-size: 14px;
line-height: 24px;
font-family: "Nunito Sans", "Helvetica Neue", Helvetica, Arial, sans-serif;
}
.storybook-page h2 {
display: inline-block;
vertical-align: top;
margin: 0 0 4px;
font-weight: 700;
font-size: 32px;
line-height: 1;
}
.storybook-page p {
margin: 1em 0;
}
.storybook-page a {
color: inherit;
}
.storybook-page ul {
margin: 1em 0;
padding-left: 30px;
}
.storybook-page li {
margin-bottom: 8px;
}
.storybook-page .tip {
display: inline-block;
vertical-align: top;
margin-right: 10px;
border-radius: 1em;
background: #e7fdd8;
padding: 4px 12px;
color: #357a14;
font-weight: 700;
font-size: 11px;
line-height: 12px;
}
.storybook-page .tip-wrapper {
margin-top: 40px;
margin-bottom: 40px;
font-size: 13px;
line-height: 20px;
}
.storybook-page .tip-wrapper svg {
display: inline-block;
vertical-align: top;
margin-top: 3px;
margin-right: 4px;
width: 12px;
height: 12px;
}
.storybook-page .tip-wrapper svg path {
fill: #1ea7fd;
}
@@ -0,0 +1,11 @@
/** @type {import('tailwindcss').Config} */
module.exports = {
content: [
"./stories/**/*.{ts,tsx,js,jsx}",
"../../../packages/react/src/**/*.{ts,tsx,js,jsx}",
],
theme: {
extend: {},
},
plugins: [],
};
+13
View File
@@ -0,0 +1,13 @@
{
"extends": "@copilotkit/typescript-config/base.json",
"compilerOptions": {
"jsx": "react-jsx",
"baseUrl": ".",
"paths": {
"@copilotkit/react-core/v2": ["../../packages/react-core/src/v2"],
"@/*": ["../../packages/react-core/src/v2/*"]
},
"types": ["node"]
},
"include": [".storybook/**/*", "stories/**/*"]
}
+5
View File
@@ -0,0 +1,5 @@
{
"buildCommand": "cd ../../../../ && npx nx run @copilotkit-storybook/react:storybook:build",
"installCommand": "cd ../../../../ && npm i -g pnpm@10.13.1 && pnpm install --no-frozen-lockfile",
"outputDirectory": "storybook-static"
}