diff --git a/src/lib/apis/index.ts b/src/lib/apis/index.ts index 0929e0a69..3892afeb8 100644 --- a/src/lib/apis/index.ts +++ b/src/lib/apis/index.ts @@ -539,7 +539,7 @@ export const updateTaskConfig = async (token: string, config: object) => { export const generateTitle = async ( token: string = '', model: string, - messages: string[], + messages: object[], chat_id?: string ) => { let error = null; @@ -573,7 +573,39 @@ export const generateTitle = async ( 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 ( diff --git a/src/lib/components/layout/Sidebar/ChatItem.svelte b/src/lib/components/layout/Sidebar/ChatItem.svelte index 19c19cd3d..9e1476310 100644 --- a/src/lib/components/layout/Sidebar/ChatItem.svelte +++ b/src/lib/components/layout/Sidebar/ChatItem.svelte @@ -39,6 +39,7 @@ import XMark from '$lib/components/icons/XMark.svelte'; import Document from '$lib/components/icons/Document.svelte'; import Sparkles from '$lib/components/icons/Sparkles.svelte'; + import { generateTitle } from '$lib/apis'; export let className = ''; @@ -136,6 +137,7 @@ let itemElement; + let generating = false; let doubleClicked = false; let dragged = false; let x = 0; @@ -223,6 +225,40 @@ if (input) input.focus(); }, 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; + }; @@ -264,14 +300,22 @@ ? 'bg-gray-200 dark:bg-gray-900' : selected ? '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' + : ''}" > { + // check if target is generate button + if (e.relatedTarget?.id === 'generate-title-button') { + return; + } + if (doubleClicked) { e.preventDefault(); 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]" > -