|
@@ -2,17 +2,21 @@
|
|
|
import { v4 as uuidv4 } from 'uuid';
|
|
|
import toast from 'svelte-french-toast';
|
|
|
|
|
|
- import { OLLAMA_API_BASE_URL } from '$lib/constants';
|
|
|
import { onMount, tick } from 'svelte';
|
|
|
- import { convertMessagesToHistory, splitStream } from '$lib/utils';
|
|
|
import { goto } from '$app/navigation';
|
|
|
- import { config, models, modelfiles, user, settings, db, chats, chatId } from '$lib/stores';
|
|
|
+ import { page } from '$app/stores';
|
|
|
+
|
|
|
+ import { models, modelfiles, user, settings, db, chats, chatId } from '$lib/stores';
|
|
|
+ import { OLLAMA_API_BASE_URL } from '$lib/constants';
|
|
|
+
|
|
|
+ import { generateChatCompletion, generateTitle } from '$lib/apis/ollama';
|
|
|
+ import { copyToClipboard, splitStream } from '$lib/utils';
|
|
|
|
|
|
import MessageInput from '$lib/components/chat/MessageInput.svelte';
|
|
|
import Messages from '$lib/components/chat/Messages.svelte';
|
|
|
import ModelSelector from '$lib/components/chat/ModelSelector.svelte';
|
|
|
import Navbar from '$lib/components/layout/Navbar.svelte';
|
|
|
- import { page } from '$app/stores';
|
|
|
+ import { createNewChat, getChatById, getChatList, updateChatById } from '$lib/apis/chats';
|
|
|
|
|
|
let loaded = false;
|
|
|
let stopResponseFlag = false;
|
|
@@ -27,6 +31,8 @@
|
|
|
? $modelfiles.filter((modelfile) => modelfile.tagName === selectedModels[0])[0]
|
|
|
: null;
|
|
|
|
|
|
+ let chat = null;
|
|
|
+
|
|
|
let title = '';
|
|
|
let prompt = '';
|
|
|
let files = [];
|
|
@@ -53,10 +59,8 @@
|
|
|
|
|
|
$: if ($page.params.id) {
|
|
|
(async () => {
|
|
|
- let chat = await loadChat();
|
|
|
-
|
|
|
- await tick();
|
|
|
- if (chat) {
|
|
|
+ if (await loadChat()) {
|
|
|
+ await tick();
|
|
|
loaded = true;
|
|
|
} else {
|
|
|
await goto('/');
|
|
@@ -70,94 +74,70 @@
|
|
|
|
|
|
const loadChat = async () => {
|
|
|
await chatId.set($page.params.id);
|
|
|
- const chat = await $db.getChatById($chatId);
|
|
|
+ chat = await getChatById(localStorage.token, $chatId).catch(async (error) => {
|
|
|
+ await goto('/');
|
|
|
+ return null;
|
|
|
+ });
|
|
|
|
|
|
if (chat) {
|
|
|
- console.log(chat);
|
|
|
-
|
|
|
- selectedModels = (chat?.models ?? undefined) !== undefined ? chat.models : [chat.model ?? ''];
|
|
|
- history =
|
|
|
- (chat?.history ?? undefined) !== undefined
|
|
|
- ? chat.history
|
|
|
- : convertMessagesToHistory(chat.messages);
|
|
|
- title = chat.title;
|
|
|
-
|
|
|
- let _settings = JSON.parse(localStorage.getItem('settings') ?? '{}');
|
|
|
- await settings.set({
|
|
|
- ..._settings,
|
|
|
- system: chat.system ?? _settings.system,
|
|
|
- options: chat.options ?? _settings.options
|
|
|
- });
|
|
|
- autoScroll = true;
|
|
|
-
|
|
|
- await tick();
|
|
|
- if (messages.length > 0) {
|
|
|
- history.messages[messages.at(-1).id].done = true;
|
|
|
- }
|
|
|
- await tick();
|
|
|
+ const chatContent = chat.chat;
|
|
|
+
|
|
|
+ if (chatContent) {
|
|
|
+ console.log(chatContent);
|
|
|
+
|
|
|
+ selectedModels =
|
|
|
+ (chatContent?.models ?? undefined) !== undefined
|
|
|
+ ? chatContent.models
|
|
|
+ : [chatContent.model ?? ''];
|
|
|
+ history =
|
|
|
+ (chatContent?.history ?? undefined) !== undefined
|
|
|
+ ? chatContent.history
|
|
|
+ : convertMessagesToHistory(chatContent.messages);
|
|
|
+ title = chatContent.title;
|
|
|
+
|
|
|
+ let _settings = JSON.parse(localStorage.getItem('settings') ?? '{}');
|
|
|
+ await settings.set({
|
|
|
+ ..._settings,
|
|
|
+ system: chatContent.system ?? _settings.system,
|
|
|
+ options: chatContent.options ?? _settings.options
|
|
|
+ });
|
|
|
+ autoScroll = true;
|
|
|
+ await tick();
|
|
|
|
|
|
- return chat;
|
|
|
- } else {
|
|
|
- return null;
|
|
|
- }
|
|
|
- };
|
|
|
+ if (messages.length > 0) {
|
|
|
+ history.messages[messages.at(-1).id].done = true;
|
|
|
+ }
|
|
|
+ await tick();
|
|
|
|
|
|
- const copyToClipboard = (text) => {
|
|
|
- if (!navigator.clipboard) {
|
|
|
- var textArea = document.createElement('textarea');
|
|
|
- textArea.value = text;
|
|
|
-
|
|
|
- // Avoid scrolling to bottom
|
|
|
- textArea.style.top = '0';
|
|
|
- textArea.style.left = '0';
|
|
|
- textArea.style.position = 'fixed';
|
|
|
-
|
|
|
- document.body.appendChild(textArea);
|
|
|
- textArea.focus();
|
|
|
- textArea.select();
|
|
|
-
|
|
|
- try {
|
|
|
- var successful = document.execCommand('copy');
|
|
|
- var msg = successful ? 'successful' : 'unsuccessful';
|
|
|
- console.log('Fallback: Copying text command was ' + msg);
|
|
|
- } catch (err) {
|
|
|
- console.error('Fallback: Oops, unable to copy', err);
|
|
|
+ return true;
|
|
|
+ } else {
|
|
|
+ return null;
|
|
|
}
|
|
|
-
|
|
|
- document.body.removeChild(textArea);
|
|
|
- return;
|
|
|
}
|
|
|
- navigator.clipboard.writeText(text).then(
|
|
|
- function () {
|
|
|
- console.log('Async: Copying to clipboard was successful!');
|
|
|
- },
|
|
|
- function (err) {
|
|
|
- console.error('Async: Could not copy text: ', err);
|
|
|
- }
|
|
|
- );
|
|
|
};
|
|
|
|
|
|
//////////////////////////
|
|
|
// Ollama functions
|
|
|
//////////////////////////
|
|
|
|
|
|
- const sendPrompt = async (userPrompt, parentId, _chatId) => {
|
|
|
+ const sendPrompt = async (prompt, parentId) => {
|
|
|
+ const _chatId = JSON.parse(JSON.stringify($chatId));
|
|
|
await Promise.all(
|
|
|
selectedModels.map(async (model) => {
|
|
|
console.log(model);
|
|
|
if ($models.filter((m) => m.name === model)[0].external) {
|
|
|
- await sendPromptOpenAI(model, userPrompt, parentId, _chatId);
|
|
|
+ await sendPromptOpenAI(model, prompt, parentId, _chatId);
|
|
|
} else {
|
|
|
- await sendPromptOllama(model, userPrompt, parentId, _chatId);
|
|
|
+ await sendPromptOllama(model, prompt, parentId, _chatId);
|
|
|
}
|
|
|
})
|
|
|
);
|
|
|
|
|
|
- await chats.set(await $db.getChats());
|
|
|
+ await chats.set(await getChatList(localStorage.token));
|
|
|
};
|
|
|
|
|
|
const sendPromptOllama = async (model, userPrompt, parentId, _chatId) => {
|
|
|
- console.log('sendPromptOllama');
|
|
|
+ // Create response message
|
|
|
let responseMessageId = uuidv4();
|
|
|
let responseMessage = {
|
|
|
parentId: parentId,
|
|
@@ -168,8 +148,11 @@
|
|
|
model: model
|
|
|
};
|
|
|
|
|
|
+ // Add message to history and Set currentId to messageId
|
|
|
history.messages[responseMessageId] = responseMessage;
|
|
|
history.currentId = responseMessageId;
|
|
|
+
|
|
|
+ // Append messageId to childrenIds of parent message
|
|
|
if (parentId !== null) {
|
|
|
history.messages[parentId].childrenIds = [
|
|
|
...history.messages[parentId].childrenIds,
|
|
@@ -177,17 +160,16 @@
|
|
|
];
|
|
|
}
|
|
|
|
|
|
+ // Wait until history/message have been updated
|
|
|
await tick();
|
|
|
+
|
|
|
+ // Scroll down
|
|
|
window.scrollTo({ top: document.body.scrollHeight });
|
|
|
|
|
|
- const res = await fetch(`${$settings?.API_BASE_URL ?? OLLAMA_API_BASE_URL}/chat`, {
|
|
|
- method: 'POST',
|
|
|
- headers: {
|
|
|
- 'Content-Type': 'text/event-stream',
|
|
|
- ...($settings.authHeader && { Authorization: $settings.authHeader }),
|
|
|
- ...($user && { Authorization: `Bearer ${localStorage.token}` })
|
|
|
- },
|
|
|
- body: JSON.stringify({
|
|
|
+ const res = await generateChatCompletion(
|
|
|
+ $settings?.API_BASE_URL ?? OLLAMA_API_BASE_URL,
|
|
|
+ localStorage.token,
|
|
|
+ {
|
|
|
model: model,
|
|
|
messages: [
|
|
|
$settings.system
|
|
@@ -209,20 +191,11 @@
|
|
|
})
|
|
|
})),
|
|
|
options: {
|
|
|
- seed: $settings.seed ?? undefined,
|
|
|
- temperature: $settings.temperature ?? undefined,
|
|
|
- repeat_penalty: $settings.repeat_penalty ?? undefined,
|
|
|
- top_k: $settings.top_k ?? undefined,
|
|
|
- top_p: $settings.top_p ?? undefined,
|
|
|
- num_ctx: $settings.num_ctx ?? undefined,
|
|
|
...($settings.options ?? {})
|
|
|
},
|
|
|
format: $settings.requestFormat ?? undefined
|
|
|
- })
|
|
|
- }).catch((err) => {
|
|
|
- console.log(err);
|
|
|
- return null;
|
|
|
- });
|
|
|
+ }
|
|
|
+ );
|
|
|
|
|
|
if (res && res.ok) {
|
|
|
const reader = res.body
|
|
@@ -311,23 +284,14 @@
|
|
|
if (autoScroll) {
|
|
|
window.scrollTo({ top: document.body.scrollHeight });
|
|
|
}
|
|
|
+ }
|
|
|
|
|
|
- await $db.updateChatById(_chatId, {
|
|
|
- title: title === '' ? 'New Chat' : title,
|
|
|
- models: selectedModels,
|
|
|
- system: $settings.system ?? undefined,
|
|
|
- options: {
|
|
|
- seed: $settings.seed ?? undefined,
|
|
|
- temperature: $settings.temperature ?? undefined,
|
|
|
- repeat_penalty: $settings.repeat_penalty ?? undefined,
|
|
|
- top_k: $settings.top_k ?? undefined,
|
|
|
- top_p: $settings.top_p ?? undefined,
|
|
|
- num_ctx: $settings.num_ctx ?? undefined,
|
|
|
- ...($settings.options ?? {})
|
|
|
- },
|
|
|
+ if ($chatId == _chatId) {
|
|
|
+ chat = await updateChatById(localStorage.token, _chatId, {
|
|
|
messages: messages,
|
|
|
history: history
|
|
|
});
|
|
|
+ await chats.set(await getChatList(localStorage.token));
|
|
|
}
|
|
|
} else {
|
|
|
if (res !== null) {
|
|
@@ -353,6 +317,7 @@
|
|
|
|
|
|
stopResponseFlag = false;
|
|
|
await tick();
|
|
|
+
|
|
|
if (autoScroll) {
|
|
|
window.scrollTo({ top: document.body.scrollHeight });
|
|
|
}
|
|
@@ -495,23 +460,14 @@
|
|
|
if (autoScroll) {
|
|
|
window.scrollTo({ top: document.body.scrollHeight });
|
|
|
}
|
|
|
+ }
|
|
|
|
|
|
- await $db.updateChatById(_chatId, {
|
|
|
- title: title === '' ? 'New Chat' : title,
|
|
|
- models: selectedModels,
|
|
|
- system: $settings.system ?? undefined,
|
|
|
- options: {
|
|
|
- seed: $settings.seed ?? undefined,
|
|
|
- temperature: $settings.temperature ?? undefined,
|
|
|
- repeat_penalty: $settings.repeat_penalty ?? undefined,
|
|
|
- top_k: $settings.top_k ?? undefined,
|
|
|
- top_p: $settings.top_p ?? undefined,
|
|
|
- num_ctx: $settings.num_ctx ?? undefined,
|
|
|
- ...($settings.options ?? {})
|
|
|
- },
|
|
|
+ if ($chatId == _chatId) {
|
|
|
+ chat = await updateChatById(localStorage.token, _chatId, {
|
|
|
messages: messages,
|
|
|
history: history
|
|
|
});
|
|
|
+ await chats.set(await getChatList(localStorage.token));
|
|
|
}
|
|
|
} else {
|
|
|
if (res !== null) {
|
|
@@ -556,16 +512,18 @@
|
|
|
};
|
|
|
|
|
|
const submitPrompt = async (userPrompt) => {
|
|
|
- const _chatId = JSON.parse(JSON.stringify($chatId));
|
|
|
- console.log('submitPrompt', _chatId);
|
|
|
+ console.log('submitPrompt', $chatId);
|
|
|
|
|
|
if (selectedModels.includes('')) {
|
|
|
toast.error('Model not selected');
|
|
|
} else if (messages.length != 0 && messages.at(-1).done != true) {
|
|
|
+ // Response not done
|
|
|
console.log('wait');
|
|
|
} else {
|
|
|
+ // Reset chat message textarea height
|
|
|
document.getElementById('chat-textarea').style.height = '';
|
|
|
|
|
|
+ // Create user message
|
|
|
let userMessageId = uuidv4();
|
|
|
let userMessage = {
|
|
|
id: userMessageId,
|
|
@@ -576,42 +534,43 @@
|
|
|
files: files.length > 0 ? files : undefined
|
|
|
};
|
|
|
|
|
|
+ // Add message to history and Set currentId to messageId
|
|
|
+ history.messages[userMessageId] = userMessage;
|
|
|
+ history.currentId = userMessageId;
|
|
|
+
|
|
|
+ // Append messageId to childrenIds of parent message
|
|
|
if (messages.length !== 0) {
|
|
|
history.messages[messages.at(-1).id].childrenIds.push(userMessageId);
|
|
|
}
|
|
|
|
|
|
- history.messages[userMessageId] = userMessage;
|
|
|
- history.currentId = userMessageId;
|
|
|
-
|
|
|
+ // Wait until history/message have been updated
|
|
|
await tick();
|
|
|
+
|
|
|
+ // Create new chat if only one message in messages
|
|
|
if (messages.length == 1) {
|
|
|
- await $db.createNewChat({
|
|
|
- id: _chatId,
|
|
|
+ chat = await createNewChat(localStorage.token, {
|
|
|
+ id: $chatId,
|
|
|
title: 'New Chat',
|
|
|
models: selectedModels,
|
|
|
system: $settings.system ?? undefined,
|
|
|
options: {
|
|
|
- seed: $settings.seed ?? undefined,
|
|
|
- temperature: $settings.temperature ?? undefined,
|
|
|
- repeat_penalty: $settings.repeat_penalty ?? undefined,
|
|
|
- top_k: $settings.top_k ?? undefined,
|
|
|
- top_p: $settings.top_p ?? undefined,
|
|
|
- num_ctx: $settings.num_ctx ?? undefined,
|
|
|
...($settings.options ?? {})
|
|
|
},
|
|
|
messages: messages,
|
|
|
- history: history
|
|
|
+ history: history,
|
|
|
+ timestamp: Date.now()
|
|
|
});
|
|
|
+ await chats.set(await getChatList(localStorage.token));
|
|
|
+ await chatId.set(chat.id);
|
|
|
+ await tick();
|
|
|
}
|
|
|
|
|
|
+ // Reset chat input textarea
|
|
|
prompt = '';
|
|
|
files = [];
|
|
|
|
|
|
- setTimeout(() => {
|
|
|
- window.scrollTo({ top: document.body.scrollHeight, behavior: 'smooth' });
|
|
|
- }, 50);
|
|
|
-
|
|
|
- await sendPrompt(userPrompt, userMessageId, _chatId);
|
|
|
+ // Send prompt
|
|
|
+ await sendPrompt(userPrompt, userMessageId);
|
|
|
}
|
|
|
};
|
|
|
|
|
@@ -621,9 +580,7 @@
|
|
|
};
|
|
|
|
|
|
const regenerateResponse = async () => {
|
|
|
- const _chatId = JSON.parse(JSON.stringify($chatId));
|
|
|
- console.log('regenerateResponse', _chatId);
|
|
|
-
|
|
|
+ console.log('regenerateResponse');
|
|
|
if (messages.length != 0 && messages.at(-1).done == true) {
|
|
|
messages.splice(messages.length - 1, 1);
|
|
|
messages = messages;
|
|
@@ -631,41 +588,21 @@
|
|
|
let userMessage = messages.at(-1);
|
|
|
let userPrompt = userMessage.content;
|
|
|
|
|
|
- await sendPrompt(userPrompt, userMessage.id, _chatId);
|
|
|
+ await sendPrompt(userPrompt, userMessage.id);
|
|
|
}
|
|
|
};
|
|
|
|
|
|
const generateChatTitle = async (_chatId, userPrompt) => {
|
|
|
if ($settings.titleAutoGenerate ?? true) {
|
|
|
- console.log('generateChatTitle');
|
|
|
-
|
|
|
- const res = await fetch(`${$settings?.API_BASE_URL ?? OLLAMA_API_BASE_URL}/generate`, {
|
|
|
- method: 'POST',
|
|
|
- headers: {
|
|
|
- 'Content-Type': 'text/event-stream',
|
|
|
- ...($settings.authHeader && { Authorization: $settings.authHeader }),
|
|
|
- ...($user && { Authorization: `Bearer ${localStorage.token}` })
|
|
|
- },
|
|
|
- body: JSON.stringify({
|
|
|
- model: selectedModels[0],
|
|
|
- prompt: `Generate a brief 3-5 word title for this question, excluding the term 'title.' Then, please reply with only the title: ${userPrompt}`,
|
|
|
- stream: false
|
|
|
- })
|
|
|
- })
|
|
|
- .then(async (res) => {
|
|
|
- if (!res.ok) throw await res.json();
|
|
|
- return res.json();
|
|
|
- })
|
|
|
- .catch((error) => {
|
|
|
- if ('detail' in error) {
|
|
|
- toast.error(error.detail);
|
|
|
- }
|
|
|
- console.log(error);
|
|
|
- return null;
|
|
|
- });
|
|
|
-
|
|
|
- if (res) {
|
|
|
- await setChatTitle(_chatId, res.response === '' ? 'New Chat' : res.response);
|
|
|
+ const title = await generateTitle(
|
|
|
+ $settings?.API_BASE_URL ?? OLLAMA_API_BASE_URL,
|
|
|
+ localStorage.token,
|
|
|
+ selectedModels[0],
|
|
|
+ userPrompt
|
|
|
+ );
|
|
|
+
|
|
|
+ if (title) {
|
|
|
+ await setChatTitle(_chatId, title);
|
|
|
}
|
|
|
} else {
|
|
|
await setChatTitle(_chatId, `${userPrompt}`);
|
|
@@ -673,10 +610,12 @@
|
|
|
};
|
|
|
|
|
|
const setChatTitle = async (_chatId, _title) => {
|
|
|
- await $db.updateChatById(_chatId, { title: _title });
|
|
|
if (_chatId === $chatId) {
|
|
|
title = _title;
|
|
|
}
|
|
|
+
|
|
|
+ chat = await updateChatById(localStorage.token, _chatId, { title: _title });
|
|
|
+ await chats.set(await getChatList(localStorage.token));
|
|
|
};
|
|
|
</script>
|
|
|
|
|
@@ -687,7 +626,13 @@
|
|
|
/>
|
|
|
|
|
|
{#if loaded}
|
|
|
- <Navbar {title} shareEnabled={messages.length > 0} />
|
|
|
+ <Navbar
|
|
|
+ {title}
|
|
|
+ shareEnabled={messages.length > 0}
|
|
|
+ initNewChat={() => {
|
|
|
+ goto('/');
|
|
|
+ }}
|
|
|
+ />
|
|
|
<div class="min-h-screen w-full flex justify-center">
|
|
|
<div class=" py-2.5 flex flex-col justify-between w-full">
|
|
|
<div class="max-w-2xl mx-auto w-full px-3 md:px-0 mt-10">
|
|
@@ -696,6 +641,7 @@
|
|
|
|
|
|
<div class=" h-full mt-10 mb-32 w-full flex flex-col">
|
|
|
<Messages
|
|
|
+ chatId={$chatId}
|
|
|
{selectedModels}
|
|
|
{selectedModelfile}
|
|
|
bind:history
|