|
@@ -6,13 +6,14 @@
|
|
|
import fileSaver from 'file-saver';
|
|
|
const { saveAs } = fileSaver;
|
|
|
import hljs from 'highlight.js';
|
|
|
- import 'highlight.js/styles/dark.min.css';
|
|
|
- import { API_BASE_URL } from '$lib/constants';
|
|
|
+ import 'highlight.js/styles/github-dark.min.css';
|
|
|
+ import { API_BASE_URL as BUILD_TIME_API_BASE_URL } from '$lib/constants';
|
|
|
import { onMount, tick } from 'svelte';
|
|
|
|
|
|
import Navbar from '$lib/components/layout/Navbar.svelte';
|
|
|
import SettingsModal from '$lib/components/chat/SettingsModal.svelte';
|
|
|
|
|
|
+ let API_BASE_URL = BUILD_TIME_API_BASE_URL;
|
|
|
let suggestions = ''; // $page.url.searchParams.get('suggestions');
|
|
|
|
|
|
let models = [];
|
|
@@ -26,26 +27,24 @@
|
|
|
|
|
|
let chats = [];
|
|
|
let chatId = uuidv4();
|
|
|
- let title = ``;
|
|
|
+ let title = '';
|
|
|
let prompt = '';
|
|
|
let messages = [];
|
|
|
|
|
|
onMount(async () => {
|
|
|
+ let settings = JSON.parse(localStorage.getItem('settings') ?? '{}');
|
|
|
+
|
|
|
+ API_BASE_URL = settings.API_BASE_URL ?? BUILD_TIME_API_BASE_URL;
|
|
|
console.log(API_BASE_URL);
|
|
|
+ system = settings.system ?? null;
|
|
|
+ temperature = settings.temperature ?? null;
|
|
|
+
|
|
|
await getModelTags();
|
|
|
|
|
|
- let settings = localStorage.getItem('settings');
|
|
|
- if (settings) {
|
|
|
- settings = JSON.parse(settings);
|
|
|
- console.log(settings);
|
|
|
-
|
|
|
- selectedModel =
|
|
|
- settings.model && models.map((model) => model.name).includes(settings.model)
|
|
|
- ? settings.model
|
|
|
- : '';
|
|
|
- system = settings.system ?? null;
|
|
|
- temperature = settings.temperature ?? null;
|
|
|
- }
|
|
|
+ selectedModel =
|
|
|
+ settings.model && models.map((model) => model.name).includes(settings.model)
|
|
|
+ ? settings.model
|
|
|
+ : '';
|
|
|
|
|
|
db = await openDB('Chats', 1, {
|
|
|
upgrade(db) {
|
|
@@ -117,6 +116,41 @@
|
|
|
);
|
|
|
};
|
|
|
|
|
|
+ const createCopyCodeBlockButton = () => {
|
|
|
+ // use a class selector if available
|
|
|
+ let blocks = document.querySelectorAll('pre');
|
|
|
+ console.log(blocks);
|
|
|
+
|
|
|
+ blocks.forEach((block) => {
|
|
|
+ // only add button if browser supports Clipboard API
|
|
|
+
|
|
|
+ if (navigator.clipboard && block.childNodes.length < 2) {
|
|
|
+ let button = document.createElement('button');
|
|
|
+
|
|
|
+ button.innerText = 'Copy Code';
|
|
|
+ block.appendChild(button);
|
|
|
+
|
|
|
+ button.addEventListener('click', async () => {
|
|
|
+ await copyCode(block, button);
|
|
|
+ });
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ async function copyCode(block, button) {
|
|
|
+ let code = block.querySelector('code');
|
|
|
+ let text = code.innerText;
|
|
|
+
|
|
|
+ await navigator.clipboard.writeText(text);
|
|
|
+
|
|
|
+ // visual feedback that task is completed
|
|
|
+ button.innerText = 'Code Copied';
|
|
|
+
|
|
|
+ setTimeout(() => {
|
|
|
+ button.innerText = 'Copy Code';
|
|
|
+ }, 700);
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
//////////////////////////
|
|
|
// Web functions
|
|
|
//////////////////////////
|
|
@@ -133,21 +167,23 @@
|
|
|
toast.success('Default model updated');
|
|
|
};
|
|
|
|
|
|
- const saveSettings = (_system, _temperature) => {
|
|
|
+ const saveSettings = async (_api_base_url, _system, _temperature) => {
|
|
|
+ API_BASE_URL = _api_base_url;
|
|
|
system = _system;
|
|
|
temperature = _temperature;
|
|
|
|
|
|
let settings = localStorage.getItem('settings') ?? '{}';
|
|
|
if (settings) {
|
|
|
settings = JSON.parse(settings);
|
|
|
+
|
|
|
+ settings.API_BASE_URL = API_BASE_URL;
|
|
|
settings.system = system;
|
|
|
settings.temperature = temperature;
|
|
|
localStorage.setItem('settings', JSON.stringify(settings));
|
|
|
}
|
|
|
|
|
|
console.log(settings);
|
|
|
-
|
|
|
- console.log('saved');
|
|
|
+ await getModelTags();
|
|
|
};
|
|
|
|
|
|
const createNewChat = () => {
|
|
@@ -163,7 +199,10 @@
|
|
|
settings = JSON.parse(settings);
|
|
|
console.log(settings);
|
|
|
|
|
|
- selectedModel = settings.model ?? selectedModel;
|
|
|
+ selectedModel =
|
|
|
+ settings.model && models.map((model) => model.name).includes(settings.model)
|
|
|
+ ? settings.model
|
|
|
+ : '';
|
|
|
system = settings.system ?? system;
|
|
|
temperature = settings.temperature ?? temperature;
|
|
|
}
|
|
@@ -172,12 +211,18 @@
|
|
|
|
|
|
const loadChat = async (id) => {
|
|
|
const chat = await db.get('chats', id);
|
|
|
- messages = chat.messages;
|
|
|
- title = chat.title;
|
|
|
- chatId = chat.id;
|
|
|
- selectedModel = chat.model ?? selectedModel;
|
|
|
- system = chat.system ?? system;
|
|
|
- temperature = chat.temperature ?? temperature;
|
|
|
+ if (chatId !== chat.id) {
|
|
|
+ messages = chat.messages;
|
|
|
+ title = chat.title;
|
|
|
+ chatId = chat.id;
|
|
|
+ selectedModel = chat.model ?? selectedModel;
|
|
|
+ system = chat.system ?? system;
|
|
|
+ temperature = chat.temperature ?? temperature;
|
|
|
+
|
|
|
+ await tick();
|
|
|
+ hljs.highlightAll();
|
|
|
+ createCopyCodeBlockButton();
|
|
|
+ }
|
|
|
};
|
|
|
|
|
|
const deleteChatHistory = async () => {
|
|
@@ -219,8 +264,8 @@
|
|
|
// Ollama functions
|
|
|
//////////////////////////
|
|
|
|
|
|
- const getModelTags = async () => {
|
|
|
- const res = await fetch(`${API_BASE_URL}/tags`, {
|
|
|
+ const getModelTags = async (url = null) => {
|
|
|
+ const res = await fetch(`${url === null ? API_BASE_URL : url}/tags`, {
|
|
|
method: 'GET',
|
|
|
headers: {
|
|
|
Accept: 'application/json',
|
|
@@ -233,11 +278,13 @@
|
|
|
})
|
|
|
.catch((error) => {
|
|
|
console.log(error);
|
|
|
- return { models: [] };
|
|
|
+ toast.error('Server connection failed');
|
|
|
+ return null;
|
|
|
});
|
|
|
|
|
|
console.log(res);
|
|
|
- models = res.models ?? [];
|
|
|
+ models = res?.models ?? [];
|
|
|
+ return res;
|
|
|
};
|
|
|
|
|
|
const submitPrompt = async (user_prompt) => {
|
|
@@ -334,6 +381,7 @@
|
|
|
responseMessage.context = data.context;
|
|
|
messages = messages;
|
|
|
hljs.highlightAll();
|
|
|
+ createCopyCodeBlockButton();
|
|
|
}
|
|
|
}
|
|
|
}
|
|
@@ -341,24 +389,25 @@
|
|
|
console.log(error);
|
|
|
}
|
|
|
window.scrollTo({ top: document.body.scrollHeight });
|
|
|
+
|
|
|
+ await db.put('chats', {
|
|
|
+ id: chatId,
|
|
|
+ title: title === '' ? 'New Chat' : title,
|
|
|
+ model: selectedModel,
|
|
|
+ system: system,
|
|
|
+ options: {
|
|
|
+ temperature: temperature
|
|
|
+ },
|
|
|
+ timestamp: Date.now(),
|
|
|
+ messages: messages
|
|
|
+ });
|
|
|
}
|
|
|
|
|
|
window.scrollTo({ top: document.body.scrollHeight });
|
|
|
|
|
|
if (messages.length == 2) {
|
|
|
- await generateTitle(user_prompt);
|
|
|
+ await generateTitle(chatId, user_prompt);
|
|
|
}
|
|
|
- await db.put('chats', {
|
|
|
- id: chatId,
|
|
|
- title: title,
|
|
|
- model: selectedModel,
|
|
|
- system: system,
|
|
|
- options: {
|
|
|
- temperature: temperature
|
|
|
- },
|
|
|
- timestamp: Date.now(),
|
|
|
- messages: messages
|
|
|
- });
|
|
|
chats = await db.getAllFromIndex('chats', 'timestamp');
|
|
|
}
|
|
|
};
|
|
@@ -430,6 +479,7 @@
|
|
|
responseMessage.context = data.context;
|
|
|
messages = messages;
|
|
|
hljs.highlightAll();
|
|
|
+ createCopyCodeBlockButton();
|
|
|
}
|
|
|
}
|
|
|
}
|
|
@@ -442,7 +492,7 @@
|
|
|
window.scrollTo({ top: document.body.scrollHeight });
|
|
|
await db.put('chats', {
|
|
|
id: chatId,
|
|
|
- title: title,
|
|
|
+ title: title === '' ? 'New Chat' : title,
|
|
|
model: selectedModel,
|
|
|
system: system,
|
|
|
options: {
|
|
@@ -457,7 +507,7 @@
|
|
|
console.log(messages);
|
|
|
};
|
|
|
|
|
|
- const generateTitle = async (user_prompt) => {
|
|
|
+ const generateTitle = async (_chatId, user_prompt) => {
|
|
|
console.log('generateTitle');
|
|
|
|
|
|
const res = await fetch(`${API_BASE_URL}/generate`, {
|
|
@@ -482,7 +532,11 @@
|
|
|
|
|
|
if (res) {
|
|
|
console.log(res);
|
|
|
- title = res.response;
|
|
|
+ const chat = await db.get('chats', _chatId);
|
|
|
+ await db.put('chats', { ...chat, title: res.response === '' ? 'New Chat' : res.response });
|
|
|
+ if (chat.id === chatId) {
|
|
|
+ title = res.response;
|
|
|
+ }
|
|
|
}
|
|
|
};
|
|
|
</script>
|
|
@@ -609,7 +663,9 @@
|
|
|
</div>
|
|
|
</div>
|
|
|
{:else}
|
|
|
- <div class="markdown-body whitespace-pre-line">
|
|
|
+ <div
|
|
|
+ class="prose max-w-full prose-invert prose-headings:my-0 prose-p:my-0 prose-pre:my-0 prose-table:my-0 prose-blockquote:my-0 prose-img:my-0 prose-ul:-my-2 prose-ol:-my-2 prose-li:-my-2 whitespace-pre-line"
|
|
|
+ >
|
|
|
{@html marked.parse(message.content)}
|
|
|
</div>
|
|
|
{/if}
|
|
@@ -863,4 +919,33 @@
|
|
|
clip-path: inset(0 -1ch 0 0);
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
+ pre[class*='language-'] {
|
|
|
+ position: relative;
|
|
|
+ overflow: auto;
|
|
|
+
|
|
|
+ /* make space */
|
|
|
+ margin: 5px 0;
|
|
|
+ padding: 1.75rem 0 1.75rem 1rem;
|
|
|
+ border-radius: 10px;
|
|
|
+ }
|
|
|
+
|
|
|
+ pre[class*='language-'] button {
|
|
|
+ position: absolute;
|
|
|
+ top: 5px;
|
|
|
+ right: 5px;
|
|
|
+
|
|
|
+ font-size: 0.9rem;
|
|
|
+ padding: 0.15rem;
|
|
|
+ background-color: #828282;
|
|
|
+
|
|
|
+ border: ridge 1px #7b7b7c;
|
|
|
+ border-radius: 5px;
|
|
|
+ text-shadow: #c4c4c4 0 0 2px;
|
|
|
+ }
|
|
|
+
|
|
|
+ pre[class*='language-'] button:hover {
|
|
|
+ cursor: pointer;
|
|
|
+ background-color: #bcbabb;
|
|
|
+ }
|
|
|
</style>
|