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; export const Default: Story = { parameters: { layout: "fullscreen", }, decorators: [ (Story) => (
), ], 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 (

You clicked {count} times

); } \`\`\` 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 (
{ alert("thumbsUp"); }, onThumbsDown: () => { alert("thumbsDown"); }, }} />
); }, }; export const ShowCursor: Story = { parameters: { layout: "fullscreen", }, decorators: [ (Story) => (
), ], render: () => { const messages = [ { id: "user-1", content: "Can you explain how AI models work?", timestamp: new Date(), role: "user" as const, }, ]; return (
{ alert("thumbsUp"); }, onThumbsDown: () => { alert("thumbsDown"); }, }} />
); }, }; 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 }) => (
🔍 Search Tool
Query: {args.query} {args.filters && args.filters.length > 0 && (
Filters: {args.filters.join(", ")}
)}
{status === ToolCallStatus.InProgress && (
Searching...
)} {status === ToolCallStatus.Complete && result && (
Results: {result}
)}
); // Wildcard render for unmatched tools const WildcardToolRender = ({ args, status, result }) => (
🔧 Tool Execution
{JSON.stringify(args, null, 2)}
{status === ToolCallStatus.InProgress && (
Processing...
)} {status === ToolCallStatus.Complete && result && (
Output: {result}
)}
); 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 (
); }`, language: "tsx", }, }, }, decorators: [ (Story) => (
), ], 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 = ({ args, status, result }) => (
🔍 Search Tool
Query: {args?.query} {args?.filters && args.filters.length > 0 && (
Filters: {args.filters.join(", ")}
)}
{status === ToolCallStatus.InProgress && (
Searching...
)} {status === ToolCallStatus.Complete && result && (
Results: {result}
)}
); const CalculatorToolRender: React.FC = ({ args, status, result }) => { const [counter, setCounter] = React.useState(0); return (
🧮 Calculator
Expression: {args?.expression}
{status === ToolCallStatus.InProgress && (
Calculating...
)} {status === ToolCallStatus.Complete && result && (
Result: {result}
)}
Local counter: {counter}
Global counter: {globalCounter}
); }; // Wildcard render for unmatched tools const WildcardToolRender: React.FC = ({ args, status, result }) => (
🔧 Tool Execution
{JSON.stringify(args, null, 2)}
{status === ToolCallStatus.InProgress && (
Processing...
)} {status === ToolCallStatus.Complete && result && (
Output: {result}
)}
); 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 (
); }, }; 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 (
); }, ], 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 (
); }, };