Merge pull request #30 from ollama-webui/dev

feat: server url edit & markdown styling added
This commit is contained in:
Timothy Jaeryang Baek 2023-10-24 20:15:13 -05:00 committed by GitHub
commit 886c19246f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 316 additions and 51 deletions

View file

@ -117,6 +117,34 @@ docker run -d -p 3000:8080 --name ollama-webui --restart always ollama-webui
caddy run --envfile .env --config ./Caddyfile.localhost
```
## Troubleshooting
### Connection Errors
If you encounter difficulties connecting to the Ollama server, please follow these steps to diagnose and resolve the issue:
**1. Verify Ollama Server Configuration**
Ensure that the Ollama server is properly configured to accept incoming connections from all origins. To do this, make sure the server is launched with the `OLLAMA_ORIGINS=*` environment variable, as shown in the following command:
```bash
OLLAMA_HOST=0.0.0.0 OLLAMA_ORIGINS=* ollama serve
```
This configuration allows Ollama to accept connections from any source.
**2. Check Ollama URL Format**
Ensure that the Ollama URL is correctly formatted in the application settings. Follow these steps:
- Go to "Settings" within the Ollama WebUI.
- Navigate to the "General" section.
- Verify that the Ollama URL is in the following format: `http://localhost:11434/api`.
It is crucial to include the `/api` at the end of the URL to ensure that the Ollama Web UI can communicate with the server.
By following these troubleshooting steps, you should be able to identify and resolve connection issues with your Ollama server configuration. If you require further assistance or have additional questions, please don't hesitate to reach out or refer to our documentation for comprehensive guidance.
## What's Next? 🚀
### To-Do List 📝

77
package-lock.json generated
View file

@ -21,6 +21,7 @@
"@sveltejs/adapter-auto": "^2.0.0",
"@sveltejs/adapter-static": "^2.0.3",
"@sveltejs/kit": "^1.20.4",
"@tailwindcss/typography": "^0.5.10",
"@typescript-eslint/eslint-plugin": "^6.0.0",
"@typescript-eslint/parser": "^6.0.0",
"autoprefixer": "^10.4.16",
@ -838,6 +839,34 @@
"vite": "^4.0.0"
}
},
"node_modules/@tailwindcss/typography": {
"version": "0.5.10",
"resolved": "https://registry.npmjs.org/@tailwindcss/typography/-/typography-0.5.10.tgz",
"integrity": "sha512-Pe8BuPJQJd3FfRnm6H0ulKIGoMEQS+Vq01R6M5aCrFB/ccR/shT+0kXLjouGC1gFLm9hopTFN+DMP0pfwRWzPw==",
"dev": true,
"dependencies": {
"lodash.castarray": "^4.4.0",
"lodash.isplainobject": "^4.0.6",
"lodash.merge": "^4.6.2",
"postcss-selector-parser": "6.0.10"
},
"peerDependencies": {
"tailwindcss": ">=3.0.0 || insiders"
}
},
"node_modules/@tailwindcss/typography/node_modules/postcss-selector-parser": {
"version": "6.0.10",
"resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz",
"integrity": "sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==",
"dev": true,
"dependencies": {
"cssesc": "^3.0.0",
"util-deprecate": "^1.0.2"
},
"engines": {
"node": ">=4"
}
},
"node_modules/@types/cookie": {
"version": "0.5.2",
"resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.5.2.tgz",
@ -2603,6 +2632,18 @@
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
},
"node_modules/lodash.castarray": {
"version": "4.4.0",
"resolved": "https://registry.npmjs.org/lodash.castarray/-/lodash.castarray-4.4.0.tgz",
"integrity": "sha512-aVx8ztPv7/2ULbArGJ2Y42bG1mEQ5mGjpdvrbJcJFU3TbYybe+QlLS4pst9zV52ymy2in1KpFPiZnAOATxD4+Q==",
"dev": true
},
"node_modules/lodash.isplainobject": {
"version": "4.0.6",
"resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
"integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==",
"dev": true
},
"node_modules/lodash.merge": {
"version": "4.6.2",
"resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
@ -4670,6 +4711,30 @@
"debug": "^4.3.4"
}
},
"@tailwindcss/typography": {
"version": "0.5.10",
"resolved": "https://registry.npmjs.org/@tailwindcss/typography/-/typography-0.5.10.tgz",
"integrity": "sha512-Pe8BuPJQJd3FfRnm6H0ulKIGoMEQS+Vq01R6M5aCrFB/ccR/shT+0kXLjouGC1gFLm9hopTFN+DMP0pfwRWzPw==",
"dev": true,
"requires": {
"lodash.castarray": "^4.4.0",
"lodash.isplainobject": "^4.0.6",
"lodash.merge": "^4.6.2",
"postcss-selector-parser": "6.0.10"
},
"dependencies": {
"postcss-selector-parser": {
"version": "6.0.10",
"resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz",
"integrity": "sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==",
"dev": true,
"requires": {
"cssesc": "^3.0.0",
"util-deprecate": "^1.0.2"
}
}
}
},
"@types/cookie": {
"version": "0.5.2",
"resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.5.2.tgz",
@ -5909,6 +5974,18 @@
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
},
"lodash.castarray": {
"version": "4.4.0",
"resolved": "https://registry.npmjs.org/lodash.castarray/-/lodash.castarray-4.4.0.tgz",
"integrity": "sha512-aVx8ztPv7/2ULbArGJ2Y42bG1mEQ5mGjpdvrbJcJFU3TbYybe+QlLS4pst9zV52ymy2in1KpFPiZnAOATxD4+Q==",
"dev": true
},
"lodash.isplainobject": {
"version": "4.0.6",
"resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
"integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==",
"dev": true
},
"lodash.merge": {
"version": "4.6.2",
"resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",

View file

@ -20,6 +20,7 @@
"@sveltejs/adapter-auto": "^2.0.0",
"@sveltejs/adapter-static": "^2.0.3",
"@sveltejs/kit": "^1.20.4",
"@tailwindcss/typography": "^0.5.10",
"@typescript-eslint/eslint-plugin": "^6.0.0",
"@typescript-eslint/parser": "^6.0.0",
"autoprefixer": "^10.4.16",

View file

@ -3,7 +3,7 @@
<head>
<meta charset="utf-8" />
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
<meta name="viewport" content="width=device-width" />
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1" />
%sveltekit.head%
</head>
<body data-sveltekit-preload-data="hover">

View file

@ -1,13 +1,14 @@
<script lang="ts">
import Modal from '../common/Modal.svelte';
import { API_BASE_URL } from '$lib/constants';
import { API_BASE_URL as BUILD_TIME_API_BASE_URL } from '$lib/constants';
import toast from 'svelte-french-toast';
export let show = false;
export let saveSettings: Function;
export let getModelTags: Function;
let API_BASE_URL = BUILD_TIME_API_BASE_URL;
let system = '';
let temperature = 0.8;
@ -33,6 +34,22 @@
});
};
const checkOllamaConnection = async () => {
if (API_BASE_URL === '') {
API_BASE_URL = BUILD_TIME_API_BASE_URL;
}
const res = await getModelTags(API_BASE_URL);
if (res) {
toast.success('Server connection verified');
saveSettings(
API_BASE_URL,
system != '' ? system : null,
temperature != 0.8 ? temperature : null
);
}
};
const pullModelHandler = async () => {
const res = await fetch(`${API_BASE_URL}/pull`, {
method: 'POST',
@ -139,6 +156,7 @@
$: if (show) {
let settings = JSON.parse(localStorage.getItem('settings') ?? '{}');
API_BASE_URL = settings.API_BASE_URL ?? BUILD_TIME_API_BASE_URL;
system = settings.system ?? '';
temperature = settings.temperature ?? 0.8;
}
@ -227,6 +245,50 @@
<div class="flex-1 md:min-h-[300px]">
{#if selectedMenu === 'general'}
<div class="flex flex-col space-y-3">
<div>
<div class=" mb-2.5 text-sm font-medium">Ollama Server URL</div>
<div class="flex w-full">
<div class="flex-1 mr-2">
<input
class="w-full rounded py-2 px-4 text-sm text-gray-300 bg-gray-800 outline-none"
placeholder="Enter URL (e.g. http://localhost:11434/api)"
bind:value={API_BASE_URL}
/>
</div>
<button
class="px-3 bg-gray-600 hover:bg-gray-700 rounded transition"
on:click={() => {
checkOllamaConnection();
}}
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor"
class="w-4 h-4"
>
<path
fill-rule="evenodd"
d="M15.312 11.424a5.5 5.5 0 01-9.201 2.466l-.312-.311h2.433a.75.75 0 000-1.5H3.989a.75.75 0 00-.75.75v4.242a.75.75 0 001.5 0v-2.43l.31.31a7 7 0 0011.712-3.138.75.75 0 00-1.449-.39zm1.23-3.723a.75.75 0 00.219-.53V2.929a.75.75 0 00-1.5 0V5.36l-.31-.31A7 7 0 003.239 8.188a.75.75 0 101.448.389A5.5 5.5 0 0113.89 6.11l.311.31h-2.432a.75.75 0 000 1.5h4.243a.75.75 0 00.53-.219z"
clip-rule="evenodd"
/>
</svg>
</button>
</div>
<div class="mt-2 text-xs text-gray-500">
Trouble accessing Ollama? <a
class=" text-gray-300 font-medium"
href="https://github.com/ollama-webui/ollama-webui#troubleshooting"
target="_blank"
>
Click here for help.
</a>
</div>
</div>
<hr class=" border-gray-700" />
<div>
<div class=" mb-2.5 text-sm font-medium">System Prompt</div>
<textarea
@ -260,6 +322,7 @@
class=" px-4 py-2 bg-emerald-600 hover:bg-emerald-700 transition rounded"
on:click={() => {
saveSettings(
API_BASE_URL === '' ? BUILD_TIME_API_BASE_URL : API_BASE_URL,
system != '' ? system : null,
temperature != 0.8 ? temperature : null
);
@ -305,11 +368,11 @@
</div>
<div class="mt-2 text-xs text-gray-500">
To access the available model names for downloading, click <a
To access the available model names for downloading, <a
class=" text-gray-300 font-medium"
href="https://ollama.ai/library"
target="_blank">here</a
>.
target="_blank">click here.</a
>
</div>
{#if pullProgress !== ''}

View file

@ -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>

View file

@ -17,8 +17,19 @@ export default {
900: '#202123',
950: '#050509'
}
},
typography: {
DEFAULT: {
css: {
pre: false,
code: false,
'pre code': false,
'code::before': false,
'code::after': false
}
}
}
}
},
plugins: []
plugins: [require('@tailwindcss/typography')]
};