File size: 2,786 Bytes
48059af
43606a3
486ffa7
564e576
 
bf75aa7
4123958
7aa951e
564e576
27b2e17
 
564e576
 
 
 
 
 
 
7bf1507
27b2e17
 
564e576
 
 
4123958
564e576
 
ee5c213
564e576
 
 
27b2e17
48059af
7bf1507
4123958
 
 
2bae046
ee1ec85
7bf1507
6655689
bf75aa7
 
a1d1276
7bf1507
a1d1276
 
 
 
 
 
3b53c7a
 
 
7bf1507
27b2e17
bf75aa7
 
43606a3
7bf1507
4123958
7bf1507
4123958
43606a3
 
486ffa7
7bf1507
4123958
43606a3
7aa951e
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
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<MessageUpdate, undefined, undefined> {
	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;
		});
}