Agent skill
openai-chatkit-skill
Integrate OpenAI ChatKit for conversational AI interface
Install this agent skill to your Project
npx add-skill https://github.com/majiayu000/claude-skill-registry/tree/main/skills/other/other/skills-wajahatali3218664-hackathon-02-mytodo-3
SKILL.md
OpenAI ChatKit Skill
Instructions
This skill provides guidance for integrating OpenAI's ChatKit interface for conversational AI experiences.
Project Structure
src/
├── components/
│ └── chat/
│ ├── ChatWindow.tsx # Main chat container
│ ├── ChatMessage.tsx # Individual message display
│ ├── ChatInput.tsx # User input area
│ ├── ChatToolbar.tsx # Quick actions toolbar
│ └── typing-indicator.tsx
├── hooks/
│ └── useChat.ts # Chat state management
├── lib/
│ └── openai.ts # OpenAI client configuration
└── types/
└── chat.ts # Chat type definitions
Type Definitions
// src/types/chat.ts
export interface ChatMessage {
id: string
role: 'user' | 'assistant' | 'system'
content: string
timestamp: Date
tools?: ToolCall[]
}
export interface ToolCall {
id: string
name: string
arguments: Record<string, unknown>
result?: unknown
}
export interface ChatState {
messages: ChatMessage[]
isLoading: boolean
error: string | null
}
export interface UseChatReturn extends ChatState {
sendMessage: (content: string) => Promise<void>
clearMessages: () => void
retry: () => Promise<void>
}
OpenAI Client Setup
// src/lib/openai.ts
import OpenAI from 'openai'
export const openai = new OpenAI({
apiKey: process.env.NEXT_PUBLIC_OPENAI_API_KEY || process.env.OPENAI_API_KEY,
})
export interface ChatCompletionOptions {
model?: string
temperature?: number
max_tokens?: number
stream?: boolean
}
export async function createChatCompletion(
messages: ChatMessage[],
options: ChatCompletionOptions = {}
) {
const response = await openai.chat.completions.create({
model: options.model || 'gpt-4o',
messages: messages.map(m => ({
role: m.role,
content: m.content,
})),
temperature: options.temperature ?? 0.7,
max_tokens: options.max_tokens ?? 1024,
stream: options.stream ?? false,
tools: getToolDefinitions(),
})
return response
}
// Tool definitions for the AI
export function getToolDefinitions() {
return [
{
type: 'function',
function: {
name: 'get_tasks',
description: 'Get all tasks for the current user',
parameters: {
type: 'object',
properties: {},
},
},
},
{
type: 'function',
function: {
name: 'create_task',
description: 'Create a new task',
parameters: {
type: 'object',
properties: {
title: { type: 'string', description: 'Task title' },
description: { type: 'string', description: 'Optional description' },
due_date: { type: 'string', description: 'Optional due date' },
},
required: ['title'],
},
},
},
{
type: 'function',
function: {
name: 'update_task',
description: 'Update an existing task',
parameters: {
type: 'object',
properties: {
task_id: { type: 'number', description: 'Task ID' },
title: { type: 'string', description: 'New title' },
completed: { type: 'boolean', description: 'Completion status' },
},
required: ['task_id'],
},
},
},
{
type: 'function',
function: {
name: 'delete_task',
description: 'Delete a task',
parameters: {
type: 'object',
properties: {
task_id: { type: 'number', description: 'Task ID to delete' },
},
required: ['task_id'],
},
},
},
]
}
Chat Hook
// src/hooks/useChat.ts
'use client'
import { useCallback, useState } from 'react'
import { ChatMessage, UseChatReturn } from '@/types/chat'
import { createChatCompletion } from '@/lib/openai'
import { v4 as uuidv4 } from 'uuid'
const SYSTEM_PROMPT = `You are a helpful AI assistant for task management.
You can help users create, update, and manage their tasks through natural language.
Always be helpful, friendly, and concise in your responses.`
export function useChat(): UseChatReturn {
const [messages, setMessages] = useState<ChatMessage[]>([
{
id: 'system',
role: 'system',
content: SYSTEM_PROMPT,
timestamp: new Date(),
},
])
const [isLoading, setIsLoading] = useState(false)
const [error, setError] = useState<string | null>(null)
const sendMessage = useCallback(async (content: string) => {
const userMessage: ChatMessage = {
id: uuidv4(),
role: 'user',
content,
timestamp: new Date(),
}
setMessages(prev => [...prev, userMessage])
setIsLoading(true)
setError(null)
try {
const response = await createChatCompletion(
[...messages, userMessage],
{ stream: false }
)
const assistantMessage: ChatMessage = {
id: uuidv4(),
role: 'assistant',
content: response.choices[0]?.message?.content || 'No response',
timestamp: new Date(),
}
setMessages(prev => [...prev, assistantMessage])
} catch (err) {
setError(err instanceof Error ? err.message : 'Unknown error')
} finally {
setIsLoading(false)
}
}, [messages])
const clearMessages = useCallback(() => {
setMessages([
{
id: 'system',
role: 'system',
content: SYSTEM_PROMPT,
timestamp: new Date(),
},
])
}, [])
const retry = useCallback(async () => {
const lastUserMessage = [...messages].reverse().find(m => m.role === 'user')
if (lastUserMessage) {
await sendMessage(lastUserMessage.content)
}
}, [messages, sendMessage])
return {
messages,
isLoading,
error,
sendMessage,
clearMessages,
retry,
}
}
Chat Window Component
// src/components/chat/ChatWindow.tsx
'use client'
import { useRef, useEffect } from 'react'
import { useChat } from '@/hooks/useChat'
import { ChatMessage } from './ChatMessage'
import { ChatInput } from './ChatInput'
export function ChatWindow() {
const { messages, isLoading, error, sendMessage } = useChat()
const messagesEndRef = useRef<HTMLDivElement>(null)
useEffect(() => {
messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' })
}, [messages])
const handleSend = async (content: string) => {
await sendMessage(content)
}
const assistantMessages = messages.filter(m => m.role === 'assistant')
const userMessages = messages.filter(m => m.role === 'user')
return (
<div className="flex flex-col h-[600px] max-w-2xl mx-auto border rounded-xl overflow-hidden">
{/* Header */}
<div className="bg-gradient-to-r from-blue-600 to-purple-600 text-white p-4">
<h2 className="text-lg font-semibold">AI Assistant</h2>
<p className="text-sm opacity-80">Ask me to help with your tasks</p>
</div>
{/* Messages */}
<div className="flex-1 overflow-y-auto p-4 space-y-4 bg-gray-50">
{messages
.filter(m => m.role !== 'system')
.map((message, index) => (
<ChatMessage
key={message.id}
message={message}
isLatest={index === messages.length - 2}
/>
))}
{isLoading && (
<div className="flex items-center gap-2 text-gray-500">
<div className="flex gap-1">
<span className="w-2 h-2 bg-gray-400 rounded-full animate-bounce" />
<span className="w-2 h-2 bg-gray-400 rounded-full animate-bounce delay-100" />
<span className="w-2 h-2 bg-gray-400 rounded-full animate-bounce delay-200" />
</div>
<span className="text-sm">AI is thinking...</span>
</div>
)}
{error && (
<div className="p-3 bg-red-50 text-red-700 rounded-lg">
Error: {error}
</div>
)}
<div ref={messagesEndRef} />
</div>
{/* Input */}
<ChatInput onSend={handleSend} isLoading={isLoading} />
</div>
)
}
Chat Message Component
// src/components/chat/ChatMessage.tsx
import { ChatMessage as ChatMessageType } from '@/types/chat'
interface ChatMessageProps {
message: ChatMessageType
isLatest?: boolean
}
export function ChatMessage({ message, isLatest }: ChatMessageProps) {
const isUser = message.role === 'user'
return (
<div className={`flex ${isUser ? 'justify-end' : 'justify-start'}`}>
<div className={`max-w-[80%] ${isUser ? 'order-2' : 'order-1'}`}>
{/* Avatar */}
<div className={`flex items-center gap-2 mb-1 ${isUser ? 'flex-row-reverse' : ''}`}>
<div className={`
w-8 h-8 rounded-full flex items-center justify-center text-white text-sm
${isUser ? 'bg-blue-500' : 'bg-gradient-to-br from-purple-500 to-pink-500'}
`}>
{isUser ? 'U' : 'AI'}
</div>
<span className="text-xs text-gray-500">
{new Date(message.timestamp).toLocaleTimeString()}
</span>
</div>
{/* Message bubble */}
<div className={`
rounded-2xl px-4 py-3
${isUser
? 'bg-blue-500 text-white rounded-tr-sm'
: 'bg-white border shadow-sm rounded-tl-sm'
}
`}>
<p className="whitespace-pre-wrap leading-relaxed">
{message.content}
</p>
</div>
{/* Tool calls indicator */}
{message.tools && message.tools.length > 0 && (
<div className="mt-2 flex flex-wrap gap-2">
{message.tools.map(tool => (
<span
key={tool.id}
className="inline-flex items-center gap-1 px-2 py-1 bg-purple-100 text-purple-700 rounded text-xs"
>
<span className="w-2 h-2 bg-purple-500 rounded-full" />
{tool.name}
</span>
))}
</div>
)}
</div>
</div>
)
}
Chat Input Component
// src/components/chat/ChatInput.tsx
'use client'
import { useState, useRef, useEffect } from 'react'
interface ChatInputProps {
onSend: (content: string) => Promise<void>
isLoading: boolean
}
export function ChatInput({ onSend, isLoading }: ChatInputProps) {
const [input, setInput] = useState('')
const textareaRef = useRef<HTMLTextAreaElement>(null)
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault()
if (!input.trim() || isLoading) return
const content = input.trim()
setInput('')
await onSend(content)
}
const handleKeyDown = (e: React.KeyboardEvent) => {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault()
handleSubmit(e)
}
}
// Auto-resize textarea
useEffect(() => {
if (textareaRef.current) {
textareaRef.current.style.height = 'auto'
textareaRef.current.style.height = `${textareaRef.current.scrollHeight}px`
}
}, [input])
return (
<form onSubmit={handleSubmit} className="p-4 bg-white border-t">
<div className="flex items-end gap-2">
<textarea
ref={textareaRef}
value={input}
onChange={e => setInput(e.target.value)}
onKeyDown={handleKeyDown}
placeholder="Ask me to help with your tasks..."
className="flex-1 resize-none border rounded-lg px-4 py-3 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent max-h-32"
rows={1}
disabled={isLoading}
/>
<button
type="submit"
disabled={!input.trim() || isLoading}
className={`
px-4 py-2 rounded-lg font-medium transition-colors
${input.trim() && !isLoading
? 'bg-blue-600 text-white hover:bg-blue-700'
: 'bg-gray-200 text-gray-400 cursor-not-allowed'
}
`}
>
{isLoading ? 'Sending...' : 'Send'}
</button>
</div>
<p className="text-xs text-gray-500 mt-2">
Press Enter to send, Shift+Enter for new line
</p>
</form>
)
}
Streaming Response Example
// Streaming chat implementation
export async function* streamChat(messages: ChatMessage[]) {
const response = await openai.chat.completions.create({
model: 'gpt-4o',
messages: messages.map(m => ({ role: m.role, content: m.content })),
stream: true,
})
for await (const chunk of response) {
const content = chunk.choices[0]?.delta?.content
if (content) {
yield content
}
}
}
// Usage in component
async function handleStreamingSend(content: string) {
const encoder = new TextEncoder()
const stream = await streamChat([...messages, userMessage])
// Display streaming content
for await (const chunk of stream) {
setAssistantContent(prev => prev + chunk)
}
}
Recommended Agent Skills
Expand your agent's capabilities with these related and highly-rated skills.
agent-ops-spec
Manage specification documents in .agent/specs/. Use when user provides requirements, acceptance criteria, or feature descriptions that need to be tracked and validated against implementation.
agent-ops-state
Maintain .agent state files. Use at session start, after meaningful steps, and before concluding: read/update constitution/memory/focus/issues/baseline consistently.
agent-ops-spec
Manage specification documents in .agent/specs/. Use when user provides requirements, acceptance criteria, or feature descriptions that need to be tracked and validated against implementation.
agent-ops-testing
Test strategy, execution, and coverage analysis. Use when designing tests, running test suites, or analyzing test results beyond baseline checks.
agent-ops-testing
Test strategy, execution, and coverage analysis. Use when designing tests, running test suites, or analyzing test results beyond baseline checks.
agent-ops-state
Maintain .agent state files. Use at session start, after meaningful steps, and before concluding: read/update constitution/memory/focus/issues/baseline consistently.
Didn't find tool you were looking for?