import { config } from "$lib/server/config"; import { generateFromDefaultEndpoint } from "$lib/server/generateFromDefaultEndpoint"; import { logger } from "$lib/server/logger"; import { MessageUpdateType, type MessageUpdate } from "$lib/types/MessageUpdate"; import type { Conversation } from "$lib/types/Conversation"; import { getReturnFromGenerator } from "$lib/utils/getReturnFromGenerator"; import { stripThinkBlocks } from "$lib/utils/stripThinkBlocks"; export async function* generateTitleForConversation( conv: Conversation, opts?: { apiKey?: string } ): AsyncGenerator { try { const userMessage = conv.messages.find((m) => m.from === "user"); // HACK: detect if the conversation is new if (conv.title !== "New Chat" || !userMessage) return; const prompt = userMessage.content; const modelForTitle = config.TASK_MODEL?.trim() ? config.TASK_MODEL : conv.model; const title = (await generateTitle(prompt, modelForTitle, { apiKey: opts?.apiKey })) ?? "New Chat"; yield { type: MessageUpdateType.Title, title: stripThinkBlocks(title).trim() || "New Chat", }; } catch (cause) { logger.error(Error("Failed whilte generating title for conversation", { cause })); } } export async function generateTitle(prompt: string, modelId?: string, opts?: { apiKey?: string }) { if (config.LLM_SUMMARIZATION !== "true") { // When summarization is disabled, use the first five words without adding emojis const firstFive = prompt.split(/\s+/g).slice(0, 5).join(" "); const stripped = stripThinkBlocks(firstFive).trim(); return stripped || firstFive; } // Tools removed: no tool-based title path return await getReturnFromGenerator( generateFromDefaultEndpoint({ messages: [{ from: "user", content: `Prompt to craft the title from: "${prompt}"` }], preprompt: `You are a titling assistant. Create a natural, concise title (max 4 words) that reflects the user's request. Use the same language as the user. Never answer the request. NEVER respond with the words “summary,” “summarize,” “prompt,” or any synonyms in the title. No quotes, emojis, hashtags, or trailing punctuation. Return only the title text.`, generateSettings: { max_tokens: 30, }, modelId, apiKey: opts?.apiKey, }) ) .then((summary) => { const firstFive = prompt.split(/\s+/g).slice(0, 5).join(" "); const trimmed = stripThinkBlocks(String(summary ?? "")).trim(); // Fallback: if empty, return first five words only (no emoji) return trimmed || stripThinkBlocks(firstFive).trim() || firstFive; }) .catch((e) => { logger.error(e); const firstFive = prompt.split(/\s+/g).slice(0, 5).join(" "); return stripThinkBlocks(firstFive).trim() || firstFive; }); }