Claude-skill-registry configure-chatkit
This skill should be used when setting up OpenAI ChatKit in Next.js, adding domain allowlist to environment, building chat UI component, integrating with /api/chat endpoint, displaying history and tool calls visually, and integrating Better Auth session.
install
source · Clone the upstream repo
git clone https://github.com/majiayu000/claude-skill-registry
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/configure-chatkit" ~/.claude/skills/majiayu000-claude-skill-registry-configure-chatkit && rm -rf "$T"
manifest:
skills/data/configure-chatkit/SKILL.mdsource content
Configure ChatKit Skill
This skill provides guidance for integrating OpenAI ChatKit in a Next.js frontend.
Purpose
Setup OpenAI ChatKit in Next.js:
- Add domain allowlist key to environment
- Build chat UI component
- POST to /api/chat endpoint with conversation_id
- Display conversation history and tool calls visually
- Integrate Better Auth session
When to Use
Use this skill when:
- Building the chatbot frontend UI
- Integrating ChatKit components
- Setting up chat API communication
- Displaying tool execution results
- Connecting frontend with authenticated backend
Capabilities
- ChatKit Integration: Use OpenAI ChatKit for chat UI
- Environment Configuration: Set up domain allowlist
- API Communication: POST to chat endpoint with conversation context
- Visual Feedback: Display history, tool calls, and results
- Authentication: Integrate Better Auth session
Environment Setup
# .env.local NEXT_PUBLIC_CHAT_KIT_PUBLIC_KEY=your_chatkit_public_key CHAT_KIT_SECRET=your_chatkit_secret_key CHAT_KIT_INSTANCE_ID=your_instance_id NEXT_PUBLIC_ALLOWED_DOMAINS=localhost:3000,yourdomain.com
Implementation Pattern
Chat Component
"use client"; import { useState, useEffect, useRef } from "react"; import { useChat } from "@openai/chatkit"; import { useSession } from "better-auth/react"; interface ChatWidgetProps { conversationId?: string; } export function ChatWidget({ conversationId }: ChatWidgetProps) { const { data: session } = useSession(); const [input, setInput] = useState(""); const [isLoading, setIsLoading] = useState(false); const messagesEndRef = useRef<HTMLDivElement>(null); const { messages, sendMessage, isLoading: chatLoading, error } = useChat({ apiUrl: "/api/chat", conversationId, auth: { getToken: () => session?.accessToken } }); const handleSend = async () => { if (!input.trim() || isLoading) return; setIsLoading(true); try { await sendMessage(input); setInput(""); } catch (err) { console.error("Failed to send message:", err); } finally { setIsLoading(false); } }; return ( <div className="chat-container"> <div className="chat-messages"> {messages.map((msg) => ( <MessageBubble key={msg.id} role={msg.role} content={msg.content} toolCalls={msg.tool_calls} /> ))} <div ref={messagesEndRef} /> </div> <div className="chat-input"> <input value={input} onChange={(e) => setInput(e.target.value)} onKeyDown={(e) => e.key === "Enter" && handleSend()} placeholder="Ask me to manage your tasks..." disabled={isLoading} /> <button onClick={handleSend} disabled={isLoading}> {isLoading ? "Sending..." : "Send"} </button> </div> {error && <ErrorToast message={error.message} />} </div> ); }
Message Bubble with Tool Calls
interface MessageBubbleProps { role: "user" | "assistant" | "tool"; content: string; toolCalls?: ToolCall[]; } function MessageBubble({ role, content, toolCalls }: MessageBubbleProps) { const isUser = role === "user"; return ( <div className={`message ${isUser ? "user" : "assistant"}`}> <div className="message-content">{content}</div> {toolCalls && toolCalls.length > 0 && ( <div className="tool-calls"> {toolCalls.map((call) => ( <ToolCallBadge key={call.id} tool={call} /> ))} </div> )} </div> ); } function ToolCallBadge({ tool }: { tool: ToolCall }) { return ( <div className="tool-badge"> <span className="tool-icon">🔧</span> <span className="tool-name">{tool.name}</span> {tool.status === "completed" && <span className="check">✓</span>} {tool.status === "failed" && <span className="x">✗</span>} </div> ); }
API Communication
async function sendMessage(message: string): Promise<void> { const response = await fetch("/api/chat", { method: "POST", headers: { "Content-Type": "application/json", "Authorization": `Bearer ${session?.accessToken}` }, body: JSON.stringify({ message, conversation_id: conversationId }) }); if (!response.ok) { throw new Error("Failed to send message"); } const data = await response.json(); // Update conversation_id if new if (!conversationId && data.conversation_id) { setConversationId(data.conversation_id); } }
Better Auth Integration
"use client"; import { SessionProvider } from "better-auth/react"; export function AuthProvider({ children }: { children: React.ReactNode }) { return ( <SessionProvider> {children} </SessionProvider> ); } // In your chat component const { data: session } = useSession(); useEffect(() => { if (session?.accessToken) { // Configure chat client with auth token chatClient.setAuthToken(session.accessToken); } }, [session]);
Styling (Tailwind CSS)
.chat-container { @apply flex flex-col h-full max-w-2xl mx-auto border rounded-lg shadow-lg; } .chat-messages { @apply flex-1 overflow-y-auto p-4 space-y-4; } .message { @apply p-3 rounded-lg max-w-[80%]; } .message.user { @apply bg-blue-500 text-white ml-auto; } .message.assistant { @apply bg-gray-100 text-gray-900; } .chat-input { @apply border-t p-4 flex gap-2; } .chat-input input { @apply flex-1 px-4 py-2 border rounded-lg focus:outline-none focus:ring-2; } .chat-input button { @apply px-4 py-2 bg-blue-500 text-white rounded-lg hover:bg-blue-600; } .tool-calls { @apply mt-2 flex flex-wrap gap-2; } .tool-badge { @apply inline-flex items-center gap-1 px-2 py-1 bg-yellow-100 text-yellow-800 text-xs rounded; } .error-toast { @apply absolute bottom-20 right-4 bg-red-500 text-white px-4 py-2 rounded-lg shadow; }
Error Handling
function ErrorToast({ message }: { message: string }) { return ( <div className="error-toast"> <span>Error: {message}</span> <button onClick={() => clearError()}>Dismiss</button> </div> ); } function LoadingIndicator() { return ( <div className="typing-indicator"> <span></span> <span></span> <span></span> </div> ); }
Verification Checklist
- ChatKit configured with allowed domains
- Chat widget displays messages correctly
- Messages sent to /api/chat endpoint
- Conversation ID maintained across messages
- Tool calls displayed visually
- Better Auth session integrated
- Error toasts shown on failures
- Loading indicators during API calls