diff --git a/README.md b/README.md index ddb94ec9a..26e6ed75e 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,8 @@ ChatGPT-Style Web Interface for Ollama 🦙 - ⚡ **Swift Responsiveness**: Enjoy fast and responsive performance. - 🚀 **Effortless Setup**: Install seamlessly using Docker for a hassle-free experience. - 🤖 **Multiple Model Support**: Seamlessly switch between different chat models for diverse interactions. +- 📜 **Chat History**: Effortlessly access and manage your conversation history. +- 💻 **Code Syntax Highlighting**: Enjoy enhanced code readability with our syntax highlighting feature. - 🌟 **Continuous Updates**: We are committed to improving Ollama Web UI with regular updates and new features. ## How to Install 🚀 @@ -30,14 +32,14 @@ Your Ollama Web UI should now be hosted at [http://localhost:3000](http://localh Here are some exciting tasks on our to-do list: -- 📜 **Chat History**: Effortlessly access and manage your conversation history. - 📤📥 **Import/Export Chat History**: Seamlessly move your chat data in and out of the platform. +- 🌐 **Web Browser Extension**: Seamlessly integrate our services into your browsing experience with our convenient browser extension. +- 🚀 **Integration with Messaging Platforms**: Explore possibilities for integrating with popular messaging platforms like Slack and Discord. - 🎨 **Customization**: Tailor your chat environment with personalized themes and styles. - 📥🗑️ **Download/Delete Models**: Easily acquire or remove models directly from the web UI. - ⚙️ **Advanced Parameters Support**: Harness the power of advanced settings for fine-tuned control. - 📚 **Enhanced Documentation**: Elevate your setup and customization experience with improved, comprehensive documentation. - 🌟 **User Interface Enhancement**: Elevate the user interface to deliver a smoother, more enjoyable interaction. -- 🚀 **Integration with Messaging Platforms**: Explore possibilities for integrating with popular messaging platforms like Slack and Discord. - 🧐 **User Testing and Feedback Gathering**: Conduct thorough user testing to gather insights and refine our offerings based on valuable user feedback. Feel free to contribute and help us make Ollama Web UI even better! 🙌 diff --git a/package-lock.json b/package-lock.json index 5ffc0dfd3..ed7a8483f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,8 +9,11 @@ "version": "0.0.1", "dependencies": { "@sveltejs/adapter-node": "^1.3.1", + "highlight.js": "^11.9.0", + "idb": "^7.1.1", "marked": "^9.1.0", - "svelte-french-toast": "^1.2.0" + "svelte-french-toast": "^1.2.0", + "uuid": "^9.0.1" }, "devDependencies": { "@sveltejs/adapter-auto": "^2.0.0", @@ -2084,6 +2087,19 @@ "node": ">=8" } }, + "node_modules/highlight.js": { + "version": "11.9.0", + "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-11.9.0.tgz", + "integrity": "sha512-fJ7cW7fQGCYAkgv4CPfwFHrfd/cLS4Hau96JuJ+ZTOWhjnhoeN1ub1tFmALm/+lW5z4WCAuAV9bm05AP0mS6Gw==", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/idb": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/idb/-/idb-7.1.1.tgz", + "integrity": "sha512-gchesWBzyvGHRO9W8tzUWFDycow5gwjvFKfyV9FF32Y7F50yZMp7mP+T2mJIWFx49zicqyC4uefHM17o6xKIVQ==" + }, "node_modules/ignore": { "version": "5.2.4", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", @@ -3709,6 +3725,18 @@ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", "dev": true }, + "node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/vite": { "version": "4.4.11", "resolved": "https://registry.npmjs.org/vite/-/vite-4.4.11.tgz", @@ -5143,6 +5171,16 @@ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true }, + "highlight.js": { + "version": "11.9.0", + "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-11.9.0.tgz", + "integrity": "sha512-fJ7cW7fQGCYAkgv4CPfwFHrfd/cLS4Hau96JuJ+ZTOWhjnhoeN1ub1tFmALm/+lW5z4WCAuAV9bm05AP0mS6Gw==" + }, + "idb": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/idb/-/idb-7.1.1.tgz", + "integrity": "sha512-gchesWBzyvGHRO9W8tzUWFDycow5gwjvFKfyV9FF32Y7F50yZMp7mP+T2mJIWFx49zicqyC4uefHM17o6xKIVQ==" + }, "ignore": { "version": "5.2.4", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", @@ -6205,6 +6243,11 @@ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", "dev": true }, + "uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==" + }, "vite": { "version": "4.4.11", "resolved": "https://registry.npmjs.org/vite/-/vite-4.4.11.tgz", diff --git a/package.json b/package.json index 16d515b8a..c69dc354a 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,10 @@ "type": "module", "dependencies": { "@sveltejs/adapter-node": "^1.3.1", + "highlight.js": "^11.9.0", + "idb": "^7.1.1", "marked": "^9.1.0", - "svelte-french-toast": "^1.2.0" + "svelte-french-toast": "^1.2.0", + "uuid": "^9.0.1" } -} \ No newline at end of file +} diff --git a/src/app.css b/src/app.css index f706915a0..7d101cfa2 100644 --- a/src/app.css +++ b/src/app.css @@ -8,6 +8,10 @@ html { @apply bg-gray-800; } +.hljs { + @apply rounded-lg; +} + ::-webkit-scrollbar-thumb { --tw-border-opacity: 1; background-color: rgba(217, 217, 227, 0.8); diff --git a/src/lib/components/layout/Navbar.svelte b/src/lib/components/layout/Navbar.svelte index 17a86cd25..559c2a4ee 100644 --- a/src/lib/components/layout/Navbar.svelte +++ b/src/lib/components/layout/Navbar.svelte @@ -1,6 +1,13 @@
-
Ollama Web UI
+
+ {title != '' ? title.split(' ').slice(0, 7).join(' ') : 'Ollama Web UI'} +
-
- - + {/each} +
+ +
+
+ +
+ + + +
diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte index 546584f17..e7678f9f4 100644 --- a/src/routes/+layout.svelte +++ b/src/routes/+layout.svelte @@ -2,7 +2,6 @@ import { Toaster } from 'svelte-french-toast'; import '../app.css'; - import '../tailwind.css'; diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index c90742d9a..bd9bbd3a5 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -2,23 +2,33 @@ import toast from 'svelte-french-toast'; import Navbar from '$lib/components/layout/Navbar.svelte'; + import { v4 as uuidv4 } from 'uuid'; import { marked } from 'marked'; + import hljs from 'highlight.js'; + import 'highlight.js/styles/dark.min.css'; import type { PageData } from './$types'; import { ENDPOINT } from '$lib/contants'; import { onMount, tick } from 'svelte'; + import { openDB, deleteDB } from 'idb'; + export let data: PageData; $: ({ models } = data); let textareaElement; + let db; let selectedModel = ''; let systemPrompt = ''; let temperature = ''; + + let chats = []; + let chatId = uuidv4(); + let title = ``; let prompt = ''; let messages = []; - onMount(() => { + onMount(async () => { let settings = localStorage.getItem('settings'); if (settings) { settings = JSON.parse(settings); @@ -28,6 +38,20 @@ systemPrompt = settings.systemPrompt ?? ''; temperature = settings.temperature ?? ''; } + + db = await openDB('Chats', 1, { + upgrade(db) { + const store = db.createObjectStore('chats', { + keyPath: 'id', + autoIncrement: true + }); + store.createIndex('timestamp', 'timestamp'); + } + }); + + chats = await db.getAllFromIndex('chats', 'timestamp'); + console.log(chats); + console.log(chatId); }); ////////////////////////// @@ -101,11 +125,32 @@ toast.success('Default model updated'); }; + const createNewChat = () => { + if (messages.length > 0) { + messages = []; + title = ''; + chatId = uuidv4(); + } + }; + + const loadChat = async (id) => { + const chat = await db.get('chats', id); + messages = chat.messages; + title = chat.title; + chatId = chat.id; + }; + + const deleteChatHistory = async () => { + const tx = db.transaction('chats', 'readwrite'); + await Promise.all([tx.store.clear(), tx.done]); + chats = await db.getAllFromIndex('chats', 'timestamp'); + }; + ////////////////////////// // Ollama functions ////////////////////////// - const submitPrompt = async () => { + const submitPrompt = async (user_prompt) => { console.log('submitPrompt'); if (selectedModel === '') { @@ -113,9 +158,15 @@ } else if (messages.length != 0 && messages.at(-1).done != true) { console.log('wait'); } else { - console.log(prompt); - - let user_prompt = prompt; + if (messages.length == 0) { + await db.put('chats', { + id: chatId, + title: 'New Chat', + timestamp: Date.now(), + messages: messages + }); + chats = await db.getAllFromIndex('chats', 'timestamp'); + } messages = [ ...messages, { @@ -180,6 +231,7 @@ responseMessage.done = true; responseMessage.context = data.context; messages = messages; + hljs.highlightAll(); } } } @@ -190,6 +242,17 @@ } window.scrollTo({ top: document.body.scrollHeight }); + + if (messages.length == 2) { + await generateTitle(user_prompt); + } + await db.put('chats', { + id: chatId, + title: title, + timestamp: Date.now(), + messages: messages + }); + chats = await db.getAllFromIndex('chats', 'timestamp'); } }; @@ -252,6 +315,7 @@ responseMessage.done = true; responseMessage.context = data.context; messages = messages; + hljs.highlightAll(); } } } @@ -262,15 +326,51 @@ } window.scrollTo({ top: document.body.scrollHeight }); + await db.put('chats', { + id: chatId, + title: title, + timestamp: Date.now(), + messages: messages + }); + chats = await db.getAllFromIndex('chats', 'timestamp'); } console.log(messages); }; + + const generateTitle = async (user_prompt) => { + console.log('generateTitle'); + + const res = await fetch(`${ENDPOINT}/api/generate`, { + method: 'POST', + headers: { + 'Content-Type': 'text/event-stream' + }, + body: JSON.stringify({ + model: selectedModel, + prompt: `Create an extremely short title for the following question with 3-5 words without using the word 'title.': ${user_prompt}`, + stream: false + }) + }) + .then(async (res) => { + if (!res.ok) throw await res.json(); + return res.json(); + }) + .catch((error) => { + console.log(error); + return null; + }); + + if (res) { + console.log(res); + title = res.response; + } + };
- +
@@ -300,9 +400,9 @@
-
+
{#if messages.length == 0} -
+
@@ -391,6 +491,113 @@
+ {#if messages.length == 0} +
+ + + + + + + +
+ {/if} + {#if messages.length != 0 && messages.at(-1).role == 'assistant' && messages.at(-1).done == true}
{/if} -
+ { + submitPrompt(prompt); + }} + >