Ai patterns
Skill smicolon/ai-kit/packs/tanstack-router/skills/ai-patterns
Convention packs for any AI coding tool - agents, skills, commands, and rules for 15 tools including Claude Code, Cursor, Windsurf, and Copilot
npx -y skills add smicolon/ai-kit --skill ai-patternsAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
2 things to look at
- no licenseNo license file was found in the repository. Code published without one is not open source by default, so using it at work is a question for whoever answers licensing questions where you are.
- 6 stars6 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its author says it does
Copied from the file, not written here
TanStack AI patterns for unified AI SDK integration. Activates when implementing AI chat, streaming responses, or AI-powered features. NOTE: Alpha library - API may change.
SKILL.md
8.0 KB, as published. Nobody here has run it
TanStack AI Patterns (Alpha)
Alpha Library: TanStack AI is in alpha. APIs may change between versions.
TanStack AI provides a unified SDK for integrating AI capabilities into React applications.
Core Concepts
- Providers: Backend AI providers (OpenAI, Anthropic, etc.)
- Streams: Real-time streaming responses
- Chat: Conversational interfaces
- Completion: Text completion
- Hooks: React hooks for AI interactions
Basic Setup
// lib/ai.ts
import { createAI } from '@tanstack/ai'
export const ai = createAI({
provider: 'openai',
apiKey: import.meta.env.VITE_OPENAI_API_KEY,
// Or use server-side proxy
baseUrl: '/api/ai',
})
Chat Interface
import { useChat } from '@tanstack/ai-react'
import { ai } from '@/lib/ai'
function ChatInterface() {
const {
messages,
input,
setInput,
sendMessage,
isLoading,
error,
} = useChat({
ai,
model: 'gpt-4',
systemPrompt: 'You are a helpful assistant.',
})
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault()
if (input.trim()) {
sendMessage(input)
setInput('')
}
}
return (
<div className="chat-container">
<div className="messages">
{messages.map((message) => (
<div
key={message.id}
className={`message ${message.role}`}
>
{message.content}
</div>
))}
{isLoading && <div className="loading">Thinking...</div>}
</div>
<form onSubmit={handleSubmit}>
<input
value={input}
onChange={(e) => setInput(e.target.value)}
placeholder="Type a message..."
disabled={isLoading}
/>
<button type="submit" disabled={isLoading || !input.trim()}>
Send
</button>
</form>
{error && <div className="error">{error.message}</div>}
</div>
)
}
Streaming Responses
import { useCompletion } from '@tanstack/ai-react'
import { ai } from '@/lib/ai'
function StreamingCompletion() {
const {
completion,
complete,
isLoading,
stop,
} = useCompletion({
ai,
model: 'gpt-4',
})
const handleGenerate = () => {
complete('Write a short story about a robot learning to paint.')
}
return (
<div>
<button onClick={handleGenerate} disabled={isLoading}>
Generate Story
</button>
{isLoading && (
<button onClick={stop}>Stop</button>
)}
<div className="completion">
{completion}
{isLoading && <span className="cursor">|</span>}
</div>
</div>
)
}
Server-Side Proxy Pattern
// For security, proxy AI calls through your server
// api/ai/chat.ts (server route)
import { OpenAI } from 'openai'
const openai = new OpenAI({
apiKey: process.env.OPENAI_API_KEY,
})
export async function POST(request: Request) {
const { messages, model } = await request.json()
const stream = await openai.chat.completions.create({
model,
messages,
stream: true,
})
return new Response(stream.toReadableStream(), {
headers: {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive',
},
})
}
// Client setup
export const ai = createAI({
provider: 'openai',
baseUrl: '/api/ai', // Use server proxy
})
Chat with Context
import { useChat } from '@tanstack/ai-react'
function ContextualChat({ documentContent }: { documentContent: string }) {
const chat = useChat({
ai,
model: 'gpt-4',
systemPrompt: `You are analyzing the following document. Answer questions about it.
Document:
${documentContent}`,
})
return <ChatUI {...chat} />
}
Multi-Turn Conversation
import { useChat } from '@tanstack/ai-react'
import { useState } from 'react'
function ConversationWithHistory() {
const [conversations, setConversations] = useState<Conversation[]>([])
const [activeConversation, setActiveConversation] = useState<string | null>(null)
const chat = useChat({
ai,
model: 'gpt-4',
initialMessages: activeConversation
? conversations.find(c => c.id === activeConversation)?.messages
: [],
onFinish: (message) => {
// Save conversation
if (activeConversation) {
setConversations(prev =>
prev.map(c =>
c.id === activeConversation
? { ...c, messages: [...c.messages, message] }
: c
)
)
}
},
})
const startNewConversation = () => {
const id = crypto.randomUUID()
setConversations(prev => [...prev, { id, messages: [] }])
setActiveConversation(id)
}
return (
<div className="flex">
<aside>
<button onClick={startNewConversation}>New Chat</button>
{conversations.map(conv => (
<button
key={conv.id}
onClick={() => setActiveConversation(conv.id)}
className={activeConversation === conv.id ? 'active' : ''}
>
Conversation {conv.id.slice(0, 8)}
</button>
))}
</aside>
<main>
<ChatUI {...chat} />
</main>
</div>
)
}
Function Calling
import { useChat } from '@tanstack/ai-react'
const functions = [
{
name: 'get_weather',
description: 'Get the current weather for a location',
parameters: {
type: 'object',
properties: {
location: { type: 'string', description: 'City name' },
},
required: ['location'],
},
},
]
function ChatWithFunctions() {
const chat = useChat({
ai,
model: 'gpt-4',
functions,
onFunctionCall: async (name, args) => {
if (name === 'get_weather') {
const weather = await fetchWeather(args.location)
return JSON.stringify(weather)
}
return null
},
})
return <ChatUI {...chat} />
}
Integration with Query
import { useQuery } from '@tanstack/react-query'
import { useChat } from '@tanstack/ai-react'
function AIAssistedSearch({ query }: { query: string }) {
// Fetch data with Query
const { data: results } = useQuery({
queryKey: ['search', query],
queryFn: () => searchApi.search(query),
})
// Use AI to summarize results
const { completion, complete } = useCompletion({
ai,
model: 'gpt-4',
})
useEffect(() => {
if (results?.length) {
complete(`Summarize these search results:\n${JSON.stringify(results)}`)
}
}, [results])
return (
<div>
<h2>AI Summary</h2>
<p>{completion}</p>
<h2>Results</h2>
<ResultsList results={results} />
</div>
)
}
Conventions
- Server proxy - Never expose API keys to client
- Streaming - Use streaming for better UX
- Error handling - Always handle AI errors gracefully
- Rate limiting - Implement client-side throttling
- Token management - Track and limit token usage
- Cancellation - Allow users to stop generation
Anti-Patterns
// ❌ WRONG: API key in client
const ai = createAI({
provider: 'openai',
apiKey: 'sk-...', // Never do this!
})
// ✅ CORRECT: Use server proxy
const ai = createAI({
provider: 'openai',
baseUrl: '/api/ai',
})
// ❌ WRONG: No loading state
function Chat() {
const { messages, sendMessage } = useChat({ ai })
return <div>{messages.map(...)}</div>
}
// ✅ CORRECT: Show loading state
function Chat() {
const { messages, sendMessage, isLoading } = useChat({ ai })
return (
<div>
{messages.map(...)}
{isLoading && <Typing />}
</div>
)
}
// ❌ WRONG: No error handling
const { completion } = useCompletion({ ai })
// ✅ CORRECT: Handle errors
const { completion, error } = useCompletion({ ai })
if (error) return <ErrorMessage error={error} />