enh: generate note title

This commit is contained in:
Timothy Jaeryang Baek 2025-07-12 22:43:09 +04:00
parent bc7e5c7c04
commit 0df488e456

View file

@ -29,7 +29,7 @@
import { compressImage, copyToClipboard, splitStream } from '$lib/utils';
import { WEBUI_API_BASE_URL, WEBUI_BASE_URL } from '$lib/constants';
import { uploadFile } from '$lib/apis/files';
import { chatCompletion } from '$lib/apis/openai';
import { chatCompletion, generateOpenAIChatCompletion } from '$lib/apis/openai';
import { config, models, settings, showSidebar, socket, user } from '$lib/stores';
@ -121,6 +121,9 @@
let showDeleteConfirm = false;
let showAccessControlModal = false;
let titleInputFocused = false;
let titleGenerating = false;
let dragged = false;
let loading = false;
@ -196,6 +199,81 @@
editor.commands.setContent(note.data.content.html);
};
const generateTitleHandler = async () => {
const content = note.data.content.md;
const DEFAULT_TITLE_GENERATION_PROMPT_TEMPLATE = `### Task:
Generate a concise, 3-5 word title with an emoji summarizing the content.
### Guidelines:
- The title should clearly represent the main theme or subject of the content.
- Use emojis that enhance understanding of the topic, but avoid quotation marks or special formatting.
- Write the title in the chat's primary language; default to English if multilingual.
- Prioritize accuracy over excessive creativity; keep it clear and simple.
- Your entire response must consist solely of the JSON object, without any introductory or concluding text.
- The output must be a single, raw JSON object, without any markdown code fences or other encapsulating text.
- Ensure no conversational text, affirmations, or explanations precede or follow the raw JSON output, as this will cause direct parsing failure.
### Output:
JSON format: { "title": "your concise title here" }
### Examples:
- { "title": "📉 Stock Market Trends" },
- { "title": "🍪 Perfect Chocolate Chip Recipe" },
- { "title": "Evolution of Music Streaming" },
- { "title": "Remote Work Productivity Tips" },
- { "title": "Artificial Intelligence in Healthcare" },
- { "title": "🎮 Video Game Development Insights" }
### Content:
<content>
${content}
</content>`;
const oldTitle = JSON.parse(JSON.stringify(note.title));
note.title = '';
titleGenerating = true;
const res = await generateOpenAIChatCompletion(
localStorage.token,
{
model: selectedModelId,
stream: false,
messages: [
{
role: 'user',
content: DEFAULT_TITLE_GENERATION_PROMPT_TEMPLATE
}
]
},
`${WEBUI_BASE_URL}/api`
);
if (res) {
// Step 1: Safely extract the response string
const response = res?.choices[0]?.message?.content ?? '';
try {
const jsonStartIndex = response.indexOf('{');
const jsonEndIndex = response.lastIndexOf('}');
if (jsonStartIndex !== -1 && jsonEndIndex !== -1) {
const jsonResponse = response.substring(jsonStartIndex, jsonEndIndex + 1);
const parsed = JSON.parse(jsonResponse);
if (parsed && parsed.title) {
note.title = parsed.title.trim();
}
}
} catch (e) {
console.error('Error parsing JSON response:', e);
toast.error($i18n.t('Failed to generate title'));
}
}
if (!note.title) {
note.title = oldTitle;
}
titleGenerating = false;
await tick();
changeDebounceHandler();
};
async function enhanceNoteHandler() {
if (selectedModelId === '') {
toast.error($i18n.t('Please select a model.'));
@ -776,12 +854,47 @@ Provide the enhanced notes in markdown format. Use markdown syntax for headings,
class="w-full text-2xl font-medium bg-transparent outline-hidden"
type="text"
bind:value={note.title}
placeholder={$i18n.t('Title')}
disabled={note?.user_id !== $user?.id && $user?.role !== 'admin'}
placeholder={titleGenerating ? $i18n.t('Generating...') : $i18n.t('Title')}
disabled={(note?.user_id !== $user?.id && $user?.role !== 'admin') ||
titleGenerating}
required
on:input={changeDebounceHandler}
on:focus={() => {
titleInputFocused = true;
}}
on:blur={(e) => {
// check if target is generate button
if (e.relatedTarget?.id === 'generate-title-button') {
return;
}
titleInputFocused = false;
changeDebounceHandler();
}}
/>
{#if titleInputFocused && !titleGenerating}
<div
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"
id="generate-title-button"
on:click={(e) => {
e.preventDefault();
e.stopImmediatePropagation();
e.stopPropagation();
generateTitleHandler();
}}
>
<Sparkles strokeWidth="2" />
</button>
</Tooltip>
</div>
{/if}
<div class="flex items-center gap-0.5 translate-x-1">
{#if editor}
<div>