mirror of
https://github.com/open-webui/open-webui
synced 2025-06-26 18:26:48 +00:00
feat: chat auto tag
This commit is contained in:
@@ -245,6 +245,78 @@ export const generateTitle = async (
|
||||
return res?.choices[0]?.message?.content.replace(/["']/g, '') ?? 'New Chat';
|
||||
};
|
||||
|
||||
export const generateTags = async (
|
||||
token: string = '',
|
||||
model: string,
|
||||
messages: string,
|
||||
chat_id?: string
|
||||
) => {
|
||||
let error = null;
|
||||
|
||||
const res = await fetch(`${WEBUI_BASE_URL}/api/task/tags/completions`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${token}`
|
||||
},
|
||||
body: JSON.stringify({
|
||||
model: model,
|
||||
messages: messages,
|
||||
...(chat_id && { chat_id: chat_id })
|
||||
})
|
||||
})
|
||||
.then(async (res) => {
|
||||
if (!res.ok) throw await res.json();
|
||||
return res.json();
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log(err);
|
||||
if ('detail' in err) {
|
||||
error = err.detail;
|
||||
}
|
||||
return null;
|
||||
});
|
||||
|
||||
if (error) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
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.tags) {
|
||||
return Array.isArray(parsed.tags) ? parsed.tags : [];
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
// If no valid JSON block found, return an empty array
|
||||
return [];
|
||||
} catch (e) {
|
||||
// Catch and safely return empty array on any parsing errors
|
||||
console.error('Failed to parse response: ', e);
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
export const generateEmoji = async (
|
||||
token: string = '',
|
||||
model: string,
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
import { goto } from '$app/navigation';
|
||||
import { page } from '$app/stores';
|
||||
|
||||
import type { Unsubscriber, Writable } from 'svelte/store';
|
||||
import { get, type Unsubscriber, type Writable } from 'svelte/store';
|
||||
import type { i18n as i18nType } from 'i18next';
|
||||
import { WEBUI_BASE_URL } from '$lib/constants';
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
config,
|
||||
type Model,
|
||||
models,
|
||||
tags as allTags,
|
||||
settings,
|
||||
showSidebar,
|
||||
WEBUI_NAME,
|
||||
@@ -46,7 +47,9 @@
|
||||
|
||||
import { generateChatCompletion } from '$lib/apis/ollama';
|
||||
import {
|
||||
addTagById,
|
||||
createNewChat,
|
||||
getAllTags,
|
||||
getChatById,
|
||||
getChatList,
|
||||
getTagsById,
|
||||
@@ -62,7 +65,8 @@
|
||||
generateTitle,
|
||||
generateSearchQuery,
|
||||
chatAction,
|
||||
generateMoACompletion
|
||||
generateMoACompletion,
|
||||
generateTags
|
||||
} from '$lib/apis';
|
||||
|
||||
import Banner from '../common/Banner.svelte';
|
||||
@@ -537,7 +541,10 @@
|
||||
});
|
||||
|
||||
if (chat) {
|
||||
tags = await getTags();
|
||||
tags = await getTagsById(localStorage.token, $chatId).catch(async (error) => {
|
||||
return [];
|
||||
});
|
||||
|
||||
const chatContent = chat.chat;
|
||||
|
||||
if (chatContent) {
|
||||
@@ -1393,6 +1400,10 @@
|
||||
window.history.replaceState(history.state, '', `/c/${_chatId}`);
|
||||
const title = await generateChatTitle(userPrompt);
|
||||
await setChatTitle(_chatId, title);
|
||||
|
||||
if ($settings?.autoTags ?? true) {
|
||||
await setChatTags(messages);
|
||||
}
|
||||
}
|
||||
|
||||
return _response;
|
||||
@@ -1707,6 +1718,10 @@
|
||||
window.history.replaceState(history.state, '', `/c/${_chatId}`);
|
||||
const title = await generateChatTitle(userPrompt);
|
||||
await setChatTitle(_chatId, title);
|
||||
|
||||
if ($settings?.autoTags ?? true) {
|
||||
await setChatTags(messages);
|
||||
}
|
||||
}
|
||||
|
||||
return _response;
|
||||
@@ -1893,6 +1908,33 @@
|
||||
}
|
||||
};
|
||||
|
||||
const setChatTags = async (messages) => {
|
||||
if (!$temporaryChatEnabled) {
|
||||
let generatedTags = await generateTags(
|
||||
localStorage.token,
|
||||
selectedModels[0],
|
||||
messages,
|
||||
$chatId
|
||||
).catch((error) => {
|
||||
console.error(error);
|
||||
return [];
|
||||
});
|
||||
|
||||
const currentTags = await getTagsById(localStorage.token, $chatId);
|
||||
generatedTags = generatedTags.filter(
|
||||
(tag) => !currentTags.find((t) => t.id === tag.replaceAll(' ', '_').toLowerCase())
|
||||
);
|
||||
console.log(generatedTags);
|
||||
|
||||
for (const tag of generatedTags) {
|
||||
await addTagById(localStorage.token, $chatId, tag);
|
||||
}
|
||||
|
||||
chat = await getChatById(localStorage.token, $chatId);
|
||||
allTags.set(await getAllTags(localStorage.token));
|
||||
}
|
||||
};
|
||||
|
||||
const getWebSearchResults = async (
|
||||
model: string,
|
||||
parentId: string,
|
||||
@@ -1978,12 +2020,6 @@
|
||||
}
|
||||
};
|
||||
|
||||
const getTags = async () => {
|
||||
return await getTagsById(localStorage.token, $chatId).catch(async (error) => {
|
||||
return [];
|
||||
});
|
||||
};
|
||||
|
||||
const initChatHandler = async () => {
|
||||
if (!$temporaryChatEnabled) {
|
||||
chat = await createNewChat(localStorage.token, {
|
||||
|
||||
@@ -19,6 +19,8 @@
|
||||
|
||||
// Addons
|
||||
let titleAutoGenerate = true;
|
||||
let autoTags = true;
|
||||
|
||||
let responseAutoCopy = false;
|
||||
let widescreenMode = false;
|
||||
let splitLargeChunks = false;
|
||||
@@ -112,6 +114,11 @@
|
||||
});
|
||||
};
|
||||
|
||||
const toggleAutoTags = async () => {
|
||||
autoTags = !autoTags;
|
||||
saveSettings({ autoTags });
|
||||
};
|
||||
|
||||
const toggleResponseAutoCopy = async () => {
|
||||
const permission = await navigator.clipboard
|
||||
.readText()
|
||||
@@ -149,6 +156,7 @@
|
||||
|
||||
onMount(async () => {
|
||||
titleAutoGenerate = $settings?.title?.auto ?? true;
|
||||
autoTags = $settings.autoTags ?? true;
|
||||
|
||||
responseAutoCopy = $settings.responseAutoCopy ?? false;
|
||||
showUsername = $settings.showUsername ?? false;
|
||||
@@ -431,6 +439,26 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div class=" py-0.5 flex w-full justify-between">
|
||||
<div class=" self-center text-xs">{$i18n.t('Chat Tags Auto-Generation')}</div>
|
||||
|
||||
<button
|
||||
class="p-1 px-3 text-xs flex rounded transition"
|
||||
on:click={() => {
|
||||
toggleAutoTags();
|
||||
}}
|
||||
type="button"
|
||||
>
|
||||
{#if autoTags === true}
|
||||
<span class="ml-2 self-center">{$i18n.t('On')}</span>
|
||||
{:else}
|
||||
<span class="ml-2 self-center">{$i18n.t('Off')}</span>
|
||||
{/if}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div class=" py-0.5 flex w-full justify-between">
|
||||
<div class=" self-center text-xs">
|
||||
|
||||
@@ -144,7 +144,7 @@
|
||||
{#if filteredTags.length > 0}
|
||||
<div class="px-1 font-medium dark:text-gray-300 text-gray-700 mb-1">Tags</div>
|
||||
|
||||
<div class="">
|
||||
<div class="max-h-60 overflow-auto">
|
||||
{#each filteredTags as tag, tagIdx}
|
||||
<button
|
||||
class=" px-1.5 py-0.5 flex gap-1 hover:bg-gray-100 dark:hover:bg-gray-900 w-full rounded {selectedIdx ===
|
||||
@@ -174,7 +174,7 @@
|
||||
{:else if filteredOptions.length > 0}
|
||||
<div class="px-1 font-medium dark:text-gray-300 text-gray-700 mb-1">Search options</div>
|
||||
|
||||
<div class="">
|
||||
<div class=" max-h-60 overflow-auto">
|
||||
{#each filteredOptions as option, optionIdx}
|
||||
<button
|
||||
class=" px-1.5 py-0.5 flex gap-1 hover:bg-gray-100 dark:hover:bg-gray-900 w-full rounded {selectedIdx ===
|
||||
|
||||
Reference in New Issue
Block a user