Skip to Content
APIServer TemplatescreateThreadTitleHandler

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): RequestHandler

Parameters

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