Browse Source

enh: generate title

Timothy Jaeryang Baek 5 months ago
parent
commit
9e1400f055
2 changed files with 90 additions and 4 deletions
  1. 34 2
      src/lib/apis/index.ts
  2. 56 2
      src/lib/components/layout/Sidebar/ChatItem.svelte

+ 34 - 2
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 (

+ 56 - 2
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;
+	};
 </script>
 
 <ShareChatModal bind:show={showShareChatModal} chatId={id} />
@@ -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'
+				: ''}"
 		>
 			<input
 				id="chat-title-input-{id}"
 				bind:value={chatTitle}
 				class=" bg-transparent w-full outline-hidden mr-10"
+				placeholder={generating ? $i18n.t('Generating...') : ''}
 				on:keydown={chatTitleInputKeydownHandler}
 				on:blur={async (e) => {
+					// 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]"
 			>
 				<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" />
 					</button>
 				</Tooltip>