enh: generate title

This commit is contained in:
Timothy Jaeryang Baek 2025-05-05 10:36:01 +04:00
parent aa374823ce
commit 9e1400f055
2 changed files with 90 additions and 4 deletions

View File

@ -539,7 +539,7 @@ export const updateTaskConfig = async (token: string, config: object) => {
export const generateTitle = async ( export const generateTitle = async (
token: string = '', token: string = '',
model: string, model: string,
messages: string[], messages: object[],
chat_id?: string chat_id?: string
) => { ) => {
let error = null; let error = null;
@ -573,7 +573,39 @@ export const generateTitle = async (
throw error; throw error;
} }
return res?.choices[0]?.message?.content.replace(/["']/g, '') ?? 'New Chat'; try {
// Step 1: Safely extract the response string
const response = res?.choices[0]?.message?.content ?? '';
// Step 2: Attempt to fix common JSON format issues like single quotes
const sanitizedResponse = response.replace(/['`]/g, '"'); // Convert single quotes to double quotes for valid JSON
// Step 3: Find the relevant JSON block within the response
const jsonStartIndex = sanitizedResponse.indexOf('{');
const jsonEndIndex = sanitizedResponse.lastIndexOf('}');
// Step 4: Check if we found a valid JSON block (with both `{` and `}`)
if (jsonStartIndex !== -1 && jsonEndIndex !== -1) {
const jsonResponse = sanitizedResponse.substring(jsonStartIndex, jsonEndIndex + 1);
// Step 5: Parse the JSON block
const parsed = JSON.parse(jsonResponse);
// Step 6: If there's a "tags" key, return the tags array; otherwise, return an empty array
if (parsed && parsed.title) {
return parsed.title;
} else {
return null;
}
}
// If no valid JSON block found, return an empty array
return null;
} catch (e) {
// Catch and safely return empty array on any parsing errors
console.error('Failed to parse response: ', e);
return null;
}
}; };
export const generateTags = async ( export const generateTags = async (

View File

@ -39,6 +39,7 @@
import XMark from '$lib/components/icons/XMark.svelte'; import XMark from '$lib/components/icons/XMark.svelte';
import Document from '$lib/components/icons/Document.svelte'; import Document from '$lib/components/icons/Document.svelte';
import Sparkles from '$lib/components/icons/Sparkles.svelte'; import Sparkles from '$lib/components/icons/Sparkles.svelte';
import { generateTitle } from '$lib/apis';
export let className = ''; export let className = '';
@ -136,6 +137,7 @@
let itemElement; let itemElement;
let generating = false;
let doubleClicked = false; let doubleClicked = false;
let dragged = false; let dragged = false;
let x = 0; let x = 0;
@ -223,6 +225,40 @@
if (input) input.focus(); if (input) input.focus();
}, 0); }, 0);
}; };
const generateTitleHandler = async () => {
generating = true;
if (!chat) {
chat = await getChatById(localStorage.token, id);
}
const messages = (chat.chat?.messages ?? []).map((message) => {
return {
role: message.role,
content: message.content
};
});
const model = chat.chat.models.at(0) ?? chat.models.at(0) ?? '';
chatTitle = '';
const generatedTitle = await generateTitle(localStorage.token, model, messages).catch(
(error) => {
toast.error(`${error}`);
return null;
}
);
if (generatedTitle) {
if (generatedTitle !== title) {
editChatTitle(id, generatedTitle);
}
confirmEdit = false;
}
generating = false;
};
</script> </script>
<ShareChatModal bind:show={showShareChatModal} chatId={id} /> <ShareChatModal bind:show={showShareChatModal} chatId={id} />
@ -264,14 +300,22 @@
? 'bg-gray-200 dark:bg-gray-900' ? 'bg-gray-200 dark:bg-gray-900'
: selected : selected
? 'bg-gray-100 dark:bg-gray-950' ? 'bg-gray-100 dark:bg-gray-950'
: 'group-hover:bg-gray-100 dark:group-hover:bg-gray-950'} whitespace-nowrap text-ellipsis" : 'group-hover:bg-gray-100 dark:group-hover:bg-gray-950'} whitespace-nowrap text-ellipsis relative {generating
? 'cursor-not-allowed'
: ''}"
> >
<input <input
id="chat-title-input-{id}" id="chat-title-input-{id}"
bind:value={chatTitle} bind:value={chatTitle}
class=" bg-transparent w-full outline-hidden mr-10" class=" bg-transparent w-full outline-hidden mr-10"
placeholder={generating ? $i18n.t('Generating...') : ''}
on:keydown={chatTitleInputKeydownHandler} on:keydown={chatTitleInputKeydownHandler}
on:blur={async (e) => { on:blur={async (e) => {
// check if target is generate button
if (e.relatedTarget?.id === 'generate-title-button') {
return;
}
if (doubleClicked) { if (doubleClicked) {
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
@ -360,7 +404,17 @@
class="flex self-center items-center space-x-1.5 z-10 translate-y-[0.5px] -translate-x-[0.5px]" class="flex self-center items-center space-x-1.5 z-10 translate-y-[0.5px] -translate-x-[0.5px]"
> >
<Tooltip content={$i18n.t('Generate')}> <Tooltip content={$i18n.t('Generate')}>
<button class=" self-center dark:hover:text-white transition" on:click={() => {}}> <button
class=" self-center dark:hover:text-white transition"
id="generate-title-button"
on:click={(e) => {
e.preventDefault();
e.stopImmediatePropagation();
e.stopPropagation();
generateTitleHandler();
}}
>
<Sparkles strokeWidth="2" /> <Sparkles strokeWidth="2" />
</button> </button>
</Tooltip> </Tooltip>