|
@@ -41,11 +41,13 @@
|
|
|
convertMessagesToHistory,
|
|
|
copyToClipboard,
|
|
|
getMessageContentParts,
|
|
|
+ createMessagesList,
|
|
|
extractSentencesForAudio,
|
|
|
promptTemplate,
|
|
|
splitStream,
|
|
|
sleep,
|
|
|
- removeDetailsWithReasoning
|
|
|
+ removeDetails,
|
|
|
+ getPromptVariables
|
|
|
} from '$lib/utils';
|
|
|
|
|
|
import { generateChatCompletion } from '$lib/apis/ollama';
|
|
@@ -82,10 +84,12 @@
|
|
|
import EventConfirmDialog from '../common/ConfirmDialog.svelte';
|
|
|
import Placeholder from './Placeholder.svelte';
|
|
|
import NotificationToast from '../NotificationToast.svelte';
|
|
|
+ import Spinner from '../common/Spinner.svelte';
|
|
|
|
|
|
export let chatIdProp = '';
|
|
|
|
|
|
- let loaded = false;
|
|
|
+ let loading = false;
|
|
|
+
|
|
|
const eventTarget = new EventTarget();
|
|
|
let controlPane;
|
|
|
let controlPaneComponent;
|
|
@@ -114,7 +118,7 @@
|
|
|
let selectedToolIds = [];
|
|
|
let imageGenerationEnabled = false;
|
|
|
let webSearchEnabled = false;
|
|
|
-
|
|
|
+ let codeInterpreterEnabled = false;
|
|
|
let chat = null;
|
|
|
let tags = [];
|
|
|
|
|
@@ -133,6 +137,7 @@
|
|
|
|
|
|
$: if (chatIdProp) {
|
|
|
(async () => {
|
|
|
+ loading = true;
|
|
|
console.log(chatIdProp);
|
|
|
|
|
|
prompt = '';
|
|
@@ -141,11 +146,9 @@
|
|
|
webSearchEnabled = false;
|
|
|
imageGenerationEnabled = false;
|
|
|
|
|
|
- loaded = false;
|
|
|
-
|
|
|
if (chatIdProp && (await loadChat())) {
|
|
|
await tick();
|
|
|
- loaded = true;
|
|
|
+ loading = false;
|
|
|
|
|
|
if (localStorage.getItem(`chat-input-${chatIdProp}`)) {
|
|
|
try {
|
|
@@ -223,7 +226,7 @@
|
|
|
}
|
|
|
|
|
|
await tick();
|
|
|
- saveChatHandler(_chatId);
|
|
|
+ saveChatHandler(_chatId, history);
|
|
|
};
|
|
|
|
|
|
const chatEventHandler = async (event, cb) => {
|
|
@@ -627,7 +630,7 @@
|
|
|
} catch (e) {
|
|
|
// Remove the failed doc from the files array
|
|
|
files = files.filter((f) => f.name !== url);
|
|
|
- toast.error(e);
|
|
|
+ toast.error(`${e}`);
|
|
|
}
|
|
|
};
|
|
|
|
|
@@ -715,6 +718,7 @@
|
|
|
if ($page.url.searchParams.get('web-search') === 'true') {
|
|
|
webSearchEnabled = true;
|
|
|
}
|
|
|
+
|
|
|
if ($page.url.searchParams.get('image-generation') === 'true') {
|
|
|
imageGenerationEnabled = true;
|
|
|
}
|
|
@@ -823,20 +827,6 @@
|
|
|
messagesContainerElement.scrollTop = messagesContainerElement.scrollHeight;
|
|
|
}
|
|
|
};
|
|
|
-
|
|
|
- const createMessagesList = (responseMessageId) => {
|
|
|
- if (responseMessageId === null) {
|
|
|
- return [];
|
|
|
- }
|
|
|
-
|
|
|
- const message = history.messages[responseMessageId];
|
|
|
- if (message?.parentId) {
|
|
|
- return [...createMessagesList(message.parentId), message];
|
|
|
- } else {
|
|
|
- return [message];
|
|
|
- }
|
|
|
- };
|
|
|
-
|
|
|
const chatCompletedHandler = async (chatId, modelId, responseMessageId, messages) => {
|
|
|
const res = await chatCompleted(localStorage.token, {
|
|
|
model: modelId,
|
|
@@ -893,7 +883,7 @@
|
|
|
};
|
|
|
|
|
|
const chatActionHandler = async (chatId, actionId, modelId, responseMessageId, event = null) => {
|
|
|
- const messages = createMessagesList(responseMessageId);
|
|
|
+ const messages = createMessagesList(history, responseMessageId);
|
|
|
|
|
|
const res = await chatAction(localStorage.token, actionId, {
|
|
|
model: modelId,
|
|
@@ -962,7 +952,7 @@
|
|
|
const modelId = selectedModels[0];
|
|
|
const model = $models.filter((m) => m.id === modelId).at(0);
|
|
|
|
|
|
- const messages = createMessagesList(history.currentId);
|
|
|
+ const messages = createMessagesList(history, history.currentId);
|
|
|
const parentMessage = messages.length !== 0 ? messages.at(-1) : null;
|
|
|
|
|
|
const userMessageId = uuidv4();
|
|
@@ -1007,9 +997,9 @@
|
|
|
}
|
|
|
|
|
|
if (messages.length === 0) {
|
|
|
- await initChatHandler();
|
|
|
+ await initChatHandler(history);
|
|
|
} else {
|
|
|
- await saveChatHandler($chatId);
|
|
|
+ await saveChatHandler($chatId, history);
|
|
|
}
|
|
|
}
|
|
|
};
|
|
@@ -1071,9 +1061,9 @@
|
|
|
}
|
|
|
|
|
|
if (messages.length === 0) {
|
|
|
- await initChatHandler();
|
|
|
+ await initChatHandler(history);
|
|
|
} else {
|
|
|
- await saveChatHandler($chatId);
|
|
|
+ await saveChatHandler($chatId, history);
|
|
|
}
|
|
|
};
|
|
|
|
|
@@ -1207,7 +1197,12 @@
|
|
|
);
|
|
|
|
|
|
history.messages[message.id] = message;
|
|
|
- await chatCompletedHandler(chatId, message.model, message.id, createMessagesList(message.id));
|
|
|
+ await chatCompletedHandler(
|
|
|
+ chatId,
|
|
|
+ message.model,
|
|
|
+ message.id,
|
|
|
+ createMessagesList(history, message.id)
|
|
|
+ );
|
|
|
}
|
|
|
|
|
|
console.log(data);
|
|
@@ -1223,7 +1218,7 @@
|
|
|
const submitPrompt = async (userPrompt, { _raw = false } = {}) => {
|
|
|
console.log('submitPrompt', userPrompt, $chatId);
|
|
|
|
|
|
- const messages = createMessagesList(history.currentId);
|
|
|
+ const messages = createMessagesList(history, history.currentId);
|
|
|
const _selectedModels = selectedModels.map((modelId) =>
|
|
|
$models.map((m) => m.id).includes(modelId) ? modelId : ''
|
|
|
);
|
|
@@ -1271,7 +1266,6 @@
|
|
|
}
|
|
|
|
|
|
prompt = '';
|
|
|
- await tick();
|
|
|
|
|
|
// Reset chat input textarea
|
|
|
const chatInputElement = document.getElementById('chat-input');
|
|
@@ -1313,34 +1307,25 @@
|
|
|
history.messages[messages.at(-1).id].childrenIds.push(userMessageId);
|
|
|
}
|
|
|
|
|
|
- // Wait until history/message have been updated
|
|
|
- await tick();
|
|
|
-
|
|
|
// focus on chat input
|
|
|
const chatInput = document.getElementById('chat-input');
|
|
|
chatInput?.focus();
|
|
|
|
|
|
saveSessionSelectedModels();
|
|
|
|
|
|
- await sendPrompt(userPrompt, userMessageId, { newChat: true });
|
|
|
+ await sendPrompt(history, userPrompt, userMessageId, { newChat: true });
|
|
|
};
|
|
|
|
|
|
const sendPrompt = async (
|
|
|
+ _history,
|
|
|
prompt: string,
|
|
|
parentId: string,
|
|
|
{ modelId = null, modelIdx = null, newChat = false } = {}
|
|
|
) => {
|
|
|
- // Create new chat if newChat is true and first user message
|
|
|
- if (
|
|
|
- newChat &&
|
|
|
- history.messages[history.currentId].parentId === null &&
|
|
|
- history.messages[history.currentId].role === 'user'
|
|
|
- ) {
|
|
|
- await initChatHandler();
|
|
|
- } else {
|
|
|
- await saveChatHandler($chatId);
|
|
|
- }
|
|
|
+ let _chatId = JSON.parse(JSON.stringify($chatId));
|
|
|
+ _history = JSON.parse(JSON.stringify(_history));
|
|
|
|
|
|
+ const responseMessageIds: Record<PropertyKey, string> = {};
|
|
|
// If modelId is provided, use it, else use selected model
|
|
|
let selectedModelIds = modelId
|
|
|
? [modelId]
|
|
@@ -1349,7 +1334,6 @@
|
|
|
: selectedModels;
|
|
|
|
|
|
// Create response messages for each selected model
|
|
|
- const responseMessageIds: Record<PropertyKey, string> = {};
|
|
|
for (const [_modelIdx, modelId] of selectedModelIds.entries()) {
|
|
|
const model = $models.filter((m) => m.id === modelId).at(0);
|
|
|
|
|
@@ -1384,19 +1368,26 @@
|
|
|
responseMessageIds[`${modelId}-${modelIdx ? modelIdx : _modelIdx}`] = responseMessageId;
|
|
|
}
|
|
|
}
|
|
|
+ history = history;
|
|
|
+
|
|
|
+ // Create new chat if newChat is true and first user message
|
|
|
+ if (newChat && _history.messages[_history.currentId].parentId === null) {
|
|
|
+ _chatId = await initChatHandler(_history);
|
|
|
+ }
|
|
|
+
|
|
|
await tick();
|
|
|
|
|
|
+ _history = JSON.parse(JSON.stringify(history));
|
|
|
// Save chat after all messages have been created
|
|
|
- await saveChatHandler($chatId);
|
|
|
+ await saveChatHandler(_chatId, _history);
|
|
|
|
|
|
- const _chatId = JSON.parse(JSON.stringify($chatId));
|
|
|
await Promise.all(
|
|
|
selectedModelIds.map(async (modelId, _modelIdx) => {
|
|
|
console.log('modelId', modelId);
|
|
|
const model = $models.filter((m) => m.id === modelId).at(0);
|
|
|
|
|
|
if (model) {
|
|
|
- const messages = createMessagesList(parentId);
|
|
|
+ const messages = createMessagesList(_history, parentId);
|
|
|
// If there are image files, check if model is vision capable
|
|
|
const hasImages = messages.some((message) =>
|
|
|
message.files?.some((file) => file.type === 'image')
|
|
@@ -1412,7 +1403,7 @@
|
|
|
|
|
|
let responseMessageId =
|
|
|
responseMessageIds[`${modelId}-${modelIdx ? modelIdx : _modelIdx}`];
|
|
|
- let responseMessage = history.messages[responseMessageId];
|
|
|
+ let responseMessage = _history.messages[responseMessageId];
|
|
|
|
|
|
let userContext = null;
|
|
|
if ($settings?.memory ?? false) {
|
|
@@ -1441,7 +1432,7 @@
|
|
|
const chatEventEmitter = await getChatEventEmitter(model.id, _chatId);
|
|
|
|
|
|
scrollToBottom();
|
|
|
- await sendPromptSocket(model, responseMessageId, _chatId);
|
|
|
+ await sendPromptSocket(_history, model, responseMessageId, _chatId);
|
|
|
|
|
|
if (chatEventEmitter) clearInterval(chatEventEmitter);
|
|
|
} else {
|
|
@@ -1454,9 +1445,9 @@
|
|
|
chats.set(await getChatList(localStorage.token, $currentChatPage));
|
|
|
};
|
|
|
|
|
|
- const sendPromptSocket = async (model, responseMessageId, _chatId) => {
|
|
|
- const responseMessage = history.messages[responseMessageId];
|
|
|
- const userMessage = history.messages[responseMessage.parentId];
|
|
|
+ const sendPromptSocket = async (_history, model, responseMessageId, _chatId) => {
|
|
|
+ const responseMessage = _history.messages[responseMessageId];
|
|
|
+ const userMessage = _history.messages[responseMessage.parentId];
|
|
|
|
|
|
let files = JSON.parse(JSON.stringify(chatFiles));
|
|
|
files.push(
|
|
@@ -1504,9 +1495,9 @@
|
|
|
}`
|
|
|
}
|
|
|
: undefined,
|
|
|
- ...createMessagesList(responseMessageId).map((message) => ({
|
|
|
+ ...createMessagesList(_history, responseMessageId).map((message) => ({
|
|
|
...message,
|
|
|
- content: removeDetailsWithReasoning(message.content)
|
|
|
+ content: removeDetails(message.content, ['reasoning', 'code_interpreter'])
|
|
|
}))
|
|
|
]
|
|
|
.filter((message) => message?.content?.trim())
|
|
@@ -1557,9 +1548,28 @@
|
|
|
|
|
|
files: (files?.length ?? 0) > 0 ? files : undefined,
|
|
|
tool_ids: selectedToolIds.length > 0 ? selectedToolIds : undefined,
|
|
|
+
|
|
|
features: {
|
|
|
- image_generation: imageGenerationEnabled,
|
|
|
- web_search: webSearchEnabled
|
|
|
+ image_generation:
|
|
|
+ $config?.features?.enable_image_generation &&
|
|
|
+ ($user.role === 'admin' || $user?.permissions?.features?.image_generation)
|
|
|
+ ? imageGenerationEnabled
|
|
|
+ : false,
|
|
|
+ code_interpreter:
|
|
|
+ $user.role === 'admin' || $user?.permissions?.features?.code_interpreter
|
|
|
+ ? codeInterpreterEnabled
|
|
|
+ : false,
|
|
|
+ web_search:
|
|
|
+ $config?.features?.enable_web_search &&
|
|
|
+ ($user.role === 'admin' || $user?.permissions?.features?.web_search)
|
|
|
+ ? webSearchEnabled || ($settings?.webSearch ?? false) === 'always'
|
|
|
+ : false
|
|
|
+ },
|
|
|
+ variables: {
|
|
|
+ ...getPromptVariables(
|
|
|
+ $user.name,
|
|
|
+ $settings?.userLocation ? await getAndUpdateUserLocation(localStorage.token) : undefined
|
|
|
+ )
|
|
|
},
|
|
|
|
|
|
session_id: $socket?.id,
|
|
@@ -1590,12 +1600,15 @@
|
|
|
},
|
|
|
`${WEBUI_BASE_URL}/api`
|
|
|
).catch((error) => {
|
|
|
- console.log(error);
|
|
|
+ toast.error(`${error}`);
|
|
|
+
|
|
|
responseMessage.error = {
|
|
|
content: error
|
|
|
};
|
|
|
responseMessage.done = true;
|
|
|
+
|
|
|
history.messages[responseMessageId] = responseMessage;
|
|
|
+ history.currentId = responseMessageId;
|
|
|
return null;
|
|
|
});
|
|
|
|
|
@@ -1693,7 +1706,7 @@
|
|
|
history.currentId = userMessageId;
|
|
|
|
|
|
await tick();
|
|
|
- await sendPrompt(userPrompt, userMessageId);
|
|
|
+ await sendPrompt(history, userPrompt, userMessageId);
|
|
|
};
|
|
|
|
|
|
const regenerateResponse = async (message) => {
|
|
@@ -1705,11 +1718,11 @@
|
|
|
|
|
|
if ((userMessage?.models ?? [...selectedModels]).length == 1) {
|
|
|
// If user message has only one model selected, sendPrompt automatically selects it for regeneration
|
|
|
- await sendPrompt(userPrompt, userMessage.id);
|
|
|
+ await sendPrompt(history, userPrompt, userMessage.id);
|
|
|
} else {
|
|
|
// If there are multiple models selected, use the model of the response message for regeneration
|
|
|
// e.g. many model chat
|
|
|
- await sendPrompt(userPrompt, userMessage.id, {
|
|
|
+ await sendPrompt(history, userPrompt, userMessage.id, {
|
|
|
modelId: message.model,
|
|
|
modelIdx: message.modelIdx
|
|
|
});
|
|
@@ -1731,7 +1744,7 @@
|
|
|
.at(0);
|
|
|
|
|
|
if (model) {
|
|
|
- await sendPromptSocket(model, responseMessage.id, _chatId);
|
|
|
+ await sendPromptSocket(history, model, responseMessage.id, _chatId);
|
|
|
}
|
|
|
}
|
|
|
};
|
|
@@ -1774,7 +1787,7 @@
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- await saveChatHandler(_chatId);
|
|
|
+ await saveChatHandler(_chatId, history);
|
|
|
} else {
|
|
|
console.error(res);
|
|
|
}
|
|
@@ -1783,42 +1796,48 @@
|
|
|
}
|
|
|
};
|
|
|
|
|
|
- const initChatHandler = async () => {
|
|
|
+ const initChatHandler = async (history) => {
|
|
|
+ let _chatId = $chatId;
|
|
|
+
|
|
|
if (!$temporaryChatEnabled) {
|
|
|
chat = await createNewChat(localStorage.token, {
|
|
|
- id: $chatId,
|
|
|
+ id: _chatId,
|
|
|
title: $i18n.t('New Chat'),
|
|
|
models: selectedModels,
|
|
|
system: $settings.system ?? undefined,
|
|
|
params: params,
|
|
|
history: history,
|
|
|
- messages: createMessagesList(history.currentId),
|
|
|
+ messages: createMessagesList(history, history.currentId),
|
|
|
tags: [],
|
|
|
timestamp: Date.now()
|
|
|
});
|
|
|
|
|
|
- currentChatPage.set(1);
|
|
|
+ _chatId = chat.id;
|
|
|
+ await chatId.set(_chatId);
|
|
|
+
|
|
|
await chats.set(await getChatList(localStorage.token, $currentChatPage));
|
|
|
- await chatId.set(chat.id);
|
|
|
+ currentChatPage.set(1);
|
|
|
|
|
|
- window.history.replaceState(history.state, '', `/c/${chat.id}`);
|
|
|
+ window.history.replaceState(history.state, '', `/c/${_chatId}`);
|
|
|
} else {
|
|
|
+ _chatId = 'local';
|
|
|
await chatId.set('local');
|
|
|
}
|
|
|
await tick();
|
|
|
+
|
|
|
+ return _chatId;
|
|
|
};
|
|
|
|
|
|
- const saveChatHandler = async (_chatId) => {
|
|
|
+ const saveChatHandler = async (_chatId, history) => {
|
|
|
if ($chatId == _chatId) {
|
|
|
if (!$temporaryChatEnabled) {
|
|
|
chat = await updateChatById(localStorage.token, _chatId, {
|
|
|
models: selectedModels,
|
|
|
history: history,
|
|
|
- messages: createMessagesList(history.currentId),
|
|
|
+ messages: createMessagesList(history, history.currentId),
|
|
|
params: params,
|
|
|
files: chatFiles
|
|
|
});
|
|
|
-
|
|
|
currentChatPage.set(1);
|
|
|
await chats.set(await getChatList(localStorage.token, $currentChatPage));
|
|
|
}
|
|
@@ -1861,7 +1880,7 @@
|
|
|
: ' '} w-full max-w-full flex flex-col"
|
|
|
id="chat-container"
|
|
|
>
|
|
|
- {#if !chatIdProp || (loaded && chatIdProp)}
|
|
|
+ {#if chatIdProp === '' || (!loading && chatIdProp)}
|
|
|
{#if $settings?.backgroundImageUrl ?? null}
|
|
|
<div
|
|
|
class="absolute {$showSidebar
|
|
@@ -1921,8 +1940,8 @@
|
|
|
</div>
|
|
|
{/if}
|
|
|
|
|
|
- <div class="flex flex-col flex-auto z-10 w-full">
|
|
|
- {#if $settings?.landingPageMode === 'chat' || createMessagesList(history.currentId).length > 0}
|
|
|
+ <div class="flex flex-col flex-auto z-10 w-full @container">
|
|
|
+ {#if $settings?.landingPageMode === 'chat' || createMessagesList(history, history.currentId).length > 0}
|
|
|
<div
|
|
|
class=" pb-2.5 flex flex-col justify-between w-full flex-auto overflow-auto h-0 max-w-full z-10 scrollbar-hidden"
|
|
|
id="messages-container"
|
|
@@ -1962,6 +1981,7 @@
|
|
|
bind:autoScroll
|
|
|
bind:selectedToolIds
|
|
|
bind:imageGenerationEnabled
|
|
|
+ bind:codeInterpreterEnabled
|
|
|
bind:webSearchEnabled
|
|
|
bind:atSelectedModel
|
|
|
transparentBackground={$settings?.backgroundImageUrl ?? false}
|
|
@@ -2013,6 +2033,7 @@
|
|
|
bind:autoScroll
|
|
|
bind:selectedToolIds
|
|
|
bind:imageGenerationEnabled
|
|
|
+ bind:codeInterpreterEnabled
|
|
|
bind:webSearchEnabled
|
|
|
bind:atSelectedModel
|
|
|
transparentBackground={$settings?.backgroundImageUrl ?? false}
|
|
@@ -2065,5 +2086,11 @@
|
|
|
{eventTarget}
|
|
|
/>
|
|
|
</PaneGroup>
|
|
|
+ {:else if loading}
|
|
|
+ <div class=" flex items-center justify-center h-full w-full">
|
|
|
+ <div class="m-auto">
|
|
|
+ <Spinner />
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
{/if}
|
|
|
</div>
|