createThreadTitleHandler
Creates an API route handler that automatically generates descriptive titles for chat conversations based on the message content. This helps users organize and find their conversations later.
Import
import { createThreadTitleHandler } from "ai-chat-bootstrap/server";Syntax
function createThreadTitleHandler(options: ThreadTitleHandlerOptions): RequestHandlerParameters
interface ThreadTitleHandlerOptions {
// Required
model: LanguageModel;
// Optional configuration
maxTitleLength?: number;
titleStyle?: "descriptive" | "concise" | "creative" | "technical";
onTitleGenerated?: (title: string, context: RequestContext) => string | Promise<string>;
onError?: (error: Error, context: RequestContext) => Response | Promise<Response>;
validateRequest?: (request: Request) => boolean | Promise<boolean>;
}Basic Usage
Simple Setup
// app/api/thread-title/route.ts
import { createThreadTitleHandler } from "ai-chat-bootstrap/server";
import { openai } from "@ai-sdk/openai";
const handler = createThreadTitleHandler({
model: openai("gpt-4o-mini"), // Fast model for title generation
maxTitleLength: 60,
});
export { handler as POST };With Custom Configuration
// app/api/thread-title/route.ts
import { createThreadTitleHandler } from "ai-chat-bootstrap/server";
import { openai } from "@ai-sdk/openai";
const handler = createThreadTitleHandler({
model: openai("gpt-4o-mini"),
maxTitleLength: 80,
titleStyle: "descriptive",
onTitleGenerated: async (title, context) => {
// Clean up the title
const cleanTitle = title
.replace(/^["']|["']$/g, '') // Remove quotes
.replace(/\.$/, '') // Remove trailing period
.trim();
// Ensure it's not too generic
const genericTitles = ["Chat", "Conversation", "Discussion"];
if (genericTitles.includes(cleanTitle)) {
return "New Conversation";
}
return cleanTitle;
}
});
export { handler as POST };Frontend Integration
Enable automatic title generation:
// Frontend component
import { ChatContainer } from "ai-chat-bootstrap";
function ChatWithAutoTitles() {
return (
<ChatContainer
transport={{ api: "/api/chat" }}
messages={{ systemPrompt: "You are a helpful assistant." }}
threads={{
enabled: true,
title: {
enabled: true,
api: "/api/thread-title",
sampleCount: 6,
},
warnOnMissing: true,
}}
/>
);
}Request Processing
The handler processes requests containing:
interface ThreadTitleRequest {
messages: UIMessage[];
existingTitle?: string;
context?: ContextItem[];
maxLength?: number;
style?: "descriptive" | "concise" | "creative" | "technical";
}Response Format
The handler returns a structured response:
interface ThreadTitleResponse {
title: string;
confidence: number;
generated_at: string;
word_count: number;
character_count: number;
}Example response:
{
"title": "React Component State Management Discussion",
"confidence": 0.85,
"generated_at": "2024-01-15T10:30:00Z",
"word_count": 5,
"character_count": 41
}Title Styles
Descriptive (Default)
Clear, informative titles that describe the main topic:
"Setting up Next.js with TypeScript and Tailwind CSS"
"Debugging API Authentication Issues"
"User Profile Component Implementation"Concise
Short, punchy titles that capture the essence:
"Next.js Setup"
"Auth Debug"
"Profile Component"Creative
Engaging titles with personality:
"Building the Perfect Next.js Stack"
"Solving the Authentication Mystery"
"Crafting User Profiles Like a Pro"Technical
Precise, technical language for developer audiences:
"Next.js 14 App Router Configuration with TypeScript"
"OAuth2 Bearer Token Validation Troubleshooting"
"React Functional Component State Architecture"Advanced Configuration
Custom Title Processing
const handler = createThreadTitleHandler({
model: openai("gpt-4o-mini"),
maxTitleLength: 60,
onTitleGenerated: async (title, context) => {
// Add emoji based on conversation type
const lowerTitle = title.toLowerCase();
if (lowerTitle.includes("bug") || lowerTitle.includes("error")) {
return `🐛 ${title}`;
}
if (lowerTitle.includes("feature") || lowerTitle.includes("new")) {
return `✨ ${title}`;
}
if (lowerTitle.includes("question") || lowerTitle.includes("help")) {
return `❓ ${title}`;
}
if (lowerTitle.includes("tutorial") || lowerTitle.includes("guide")) {
return `📚 ${title}`;
}
return title;
}
});Domain-Specific Titles
const handler = createThreadTitleHandler({
model: openai("gpt-4o-mini"),
titleStyle: "technical",
onTitleGenerated: async (title, context) => {
// Add project context if available
const projectName = context.projectName;
if (projectName && !title.includes(projectName)) {
return `${projectName}: ${title}`;
}
// Standardize technical terms
const standardizations = {
"js": "JavaScript",
"ts": "TypeScript",
"api": "API",
"db": "Database",
"ui": "UI",
};
let standardizedTitle = title;
Object.entries(standardizations).forEach(([short, full]) => {
const regex = new RegExp(`\\b${short}\\b`, 'gi');
standardizedTitle = standardizedTitle.replace(regex, full);
});
return standardizedTitle;
}
});Multi-language Support
const handler = createThreadTitleHandler({
model: openai("gpt-4o-mini"),
onTitleGenerated: async (title, context) => {
const userLanguage = context.userLanguage || "en";
// Translate title if needed
if (userLanguage !== "en") {
const translatedTitle = await translateText(title, "en", userLanguage);
return translatedTitle;
}
return title;
}
});Performance Optimization
Use Fast Models
Title generation should be quick:
const handler = createThreadTitleHandler({
model: openai("gpt-4o-mini"), // Faster and cheaper
maxTitleLength: 50, // Shorter = faster generation
});Caching
Cache titles for similar conversations:
const handler = createThreadTitleHandler({
model: openai("gpt-4o-mini"),
onTitleGenerated: async (title, context) => {
const conversationHash = hashConversation(context.messages);
await cacheService.set(`title:${conversationHash}`, title, 3600); // 1 hour cache
return title;
}
});Automatic Cooldown
useAIChat already applies a cooldown between auto-title requests. Configure the title endpoint once when creating the hook:
const chat = useAIChat({
transport: { api: "/api/chat" },
threads: {
enabled: true,
title: {
enabled: true,
api: "/api/thread-title",
sampleCount: 6,
},
},
});Production Examples
SaaS Application
// app/api/thread-title/route.ts
import { createThreadTitleHandler } from "ai-chat-bootstrap/server";
import { openai } from "@ai-sdk/openai";
const handler = createThreadTitleHandler({
model: openai("gpt-4o-mini"),
maxTitleLength: 60,
titleStyle: "descriptive",
validateRequest: async (request) => {
const userId = request.headers.get("x-user-id");
const subscription = await getUserSubscription(userId);
// Only allow title generation for paid users
return subscription?.plan !== "free";
},
onTitleGenerated: async (title, context) => {
// Log title generation for analytics
await logEvent("title_generated", {
userId: context.userId,
titleLength: title.length,
messageCount: context.messages?.length || 0,
timestamp: new Date().toISOString(),
});
// Clean up common issues
let cleanTitle = title
.replace(/^["']|["']$/g, '') // Remove quotes
.replace(/\.$/, '') // Remove trailing period
.replace(/^\w/, c => c.toUpperCase()); // Capitalize first letter
// Ensure minimum quality
if (cleanTitle.length < 10) {
cleanTitle = "New Conversation";
}
return cleanTitle;
}
});
export { handler as POST };Documentation Assistant
// app/api/thread-title/route.ts
import { createThreadTitleHandler } from "ai-chat-bootstrap/server";
import { openai } from "@ai-sdk/openai";
const handler = createThreadTitleHandler({
model: openai("gpt-4o-mini"),
titleStyle: "technical",
maxTitleLength: 80,
onTitleGenerated: async (title, context) => {
// Categorize based on content
const messages = context.messages || [];
const conversationText = messages
.map(m => m.parts.filter(p => p.type === 'text').map(p => p.text).join(' '))
.join(' ')
.toLowerCase();
// Add category prefixes
if (conversationText.includes('install') || conversationText.includes('setup')) {
return `Setup: ${title}`;
}
if (conversationText.includes('tutorial') || conversationText.includes('guide')) {
return `Tutorial: ${title}`;
}
if (conversationText.includes('troubleshoot') || conversationText.includes('debug')) {
return `Troubleshooting: ${title}`;
}
if (conversationText.includes('api') || conversationText.includes('endpoint')) {
return `API: ${title}`;
}
return title;
}
});
export { handler as POST };Customer Support
// app/api/thread-title/route.ts
import { createThreadTitleHandler } from "ai-chat-bootstrap/server";
import { openai } from "@ai-sdk/openai";
const handler = createThreadTitleHandler({
model: openai("gpt-4o-mini"),
titleStyle: "descriptive",
onTitleGenerated: async (title, context) => {
const messages = context.messages || [];
const userMessages = messages.filter(m => m.role === 'user');
// Analyze sentiment for priority
const urgentKeywords = ['urgent', 'critical', 'emergency', 'broken', 'down'];
const isUrgent = userMessages.some(msg =>
msg.parts.some(part =>
part.type === 'text' &&
urgentKeywords.some(keyword => part.text.toLowerCase().includes(keyword))
)
);
if (isUrgent) {
return `🚨 ${title}`;
}
// Categorize support type
const bugKeywords = ['bug', 'error', 'issue', 'problem', 'not working'];
const isBug = userMessages.some(msg =>
msg.parts.some(part =>
part.type === 'text' &&
bugKeywords.some(keyword => part.text.toLowerCase().includes(keyword))
)
);
if (isBug) {
return `🐛 ${title}`;
}
return `💬 ${title}`;
}
});
export { handler as POST };See Also
Last updated on