mirror of
https://github.com/hanzoai/chat.git
synced 2026-09-30 20:51:17 +00:00
Titling is a second model call, and it fails: it times out at 45s, it is refused on a free route for want of balance, the gateway answers it with a 200 envelope carrying no choices. The agents path treated every one of those as "no name" and returned without saving, so the conversation reached the sidebar with no title field at all — which the client renders as the word Untitled. Measured on the live store: 353 of 485 conversations have no title, across every model and both guest and account users. The assistants path already knew the answer and fell back to the user's own first line; the agents path, which is the one that actually runs, never got it. So `opening` is that fallback as one function, and both paths call it. It needs no call and cannot time out, because the line is already in hand — the only empty case is a conversation opened with a file and no words, which has nothing to read. Naming this deterministically is also what makes the model call optional rather than load-bearing: a chat now has a name from the moment it has a message, and a successful title improves it rather than supplying it.
90 lines
2.6 KiB
JavaScript
90 lines
2.6 KiB
JavaScript
const { isEnabled, opening, sanitizeTitle } = require('@hanzochat/api');
|
|
const { logger } = require('@hanzochat/data-schemas');
|
|
const { CacheKeys } = require('@hanzochat/data-provider');
|
|
const { saveConvo } = require('~/models/Conversation');
|
|
const getLogStores = require('~/cache/getLogStores');
|
|
const initializeClient = require('./initalize');
|
|
|
|
/**
|
|
* Generates a conversation title using OpenAI SDK
|
|
* @param {Object} params
|
|
* @param {OpenAI} params.openai - The OpenAI SDK client instance
|
|
* @param {string} params.text - User's message text
|
|
* @param {string} params.responseText - Assistant's response text
|
|
* @returns {Promise<string>}
|
|
*/
|
|
const generateTitle = async ({ openai, text, responseText }) => {
|
|
const titlePrompt = `Please generate a concise title (max 40 characters) for a conversation that starts with:
|
|
User: ${text}
|
|
Assistant: ${responseText}
|
|
|
|
Title:`;
|
|
|
|
const completion = await openai.chat.completions.create({
|
|
model: 'gpt-3.5-turbo',
|
|
messages: [
|
|
{
|
|
role: 'user',
|
|
content: titlePrompt,
|
|
},
|
|
],
|
|
temperature: 0.7,
|
|
max_tokens: 20,
|
|
});
|
|
|
|
const title = completion.choices[0]?.message?.content?.trim() || 'New conversation';
|
|
return sanitizeTitle(title);
|
|
};
|
|
|
|
/**
|
|
* Adds a title to a conversation asynchronously
|
|
* @param {ServerRequest} req
|
|
* @param {Object} params
|
|
* @param {string} params.text - User's message text
|
|
* @param {string} params.responseText - Assistant's response text
|
|
* @param {string} params.conversationId - Conversation ID
|
|
*/
|
|
const addTitle = async (req, { text, responseText, conversationId }) => {
|
|
const { TITLE_CONVO = 'true' } = process.env ?? {};
|
|
if (!isEnabled(TITLE_CONVO)) {
|
|
return;
|
|
}
|
|
|
|
// Skip title generation for temporary conversations
|
|
if (req?.body?.isTemporary) {
|
|
return;
|
|
}
|
|
|
|
const titleCache = getLogStores(CacheKeys.GEN_TITLE);
|
|
const key = `${req.user.id}-${conversationId}`;
|
|
|
|
try {
|
|
const { openai } = await initializeClient({ req });
|
|
const title = await generateTitle({ openai, text, responseText });
|
|
await titleCache.set(key, title, 120000);
|
|
|
|
await saveConvo(
|
|
req,
|
|
{
|
|
conversationId,
|
|
title,
|
|
},
|
|
{ context: 'api/server/services/Endpoints/assistants/addTitle.js', noUpsert: true },
|
|
);
|
|
} catch (error) {
|
|
logger.error('[addTitle] Error generating title:', error);
|
|
const fallbackTitle = opening(text);
|
|
await titleCache.set(key, fallbackTitle, 120000);
|
|
await saveConvo(
|
|
req,
|
|
{
|
|
conversationId,
|
|
title: fallbackTitle,
|
|
},
|
|
{ context: 'api/server/services/Endpoints/assistants/addTitle.js', noUpsert: true },
|
|
);
|
|
}
|
|
};
|
|
|
|
module.exports = addTitle;
|