forked from open-webui/open-webui
commit
07fc6e4b11
14 changed files with 460 additions and 83 deletions
|
@ -71,6 +71,11 @@ Don't forget to explore our sibling project, [OllamaHub](https://ollamahub.com/)
|
||||||
|
|
||||||
## How to Install 🚀
|
## How to Install 🚀
|
||||||
|
|
||||||
|
🌟 **Important Note on User Roles:**
|
||||||
|
|
||||||
|
- **Admin Creation:** The very first account to sign up on the Ollama Web UI will be granted **Administrator privileges**. This account will have comprehensive control over the platform, including user management and system settings.
|
||||||
|
- **User Registrations:** All subsequent users signing up will initially have their accounts set to **Pending** status by default. These accounts will require approval from the Administrator to gain access to the platform functionalities.
|
||||||
|
|
||||||
### Installing Both Ollama and Ollama Web UI Using Docker Compose
|
### Installing Both Ollama and Ollama Web UI Using Docker Compose
|
||||||
|
|
||||||
If you don't have Ollama installed yet, you can use the provided Docker Compose file for a hassle-free installation. Simply run the following command:
|
If you don't have Ollama installed yet, you can use the provided Docker Compose file for a hassle-free installation. Simply run the following command:
|
||||||
|
|
|
@ -7,7 +7,7 @@ If you're running ollama-webui and have chosen to install webui and ollama separ
|
||||||
Here's an example of the command you should run:
|
Here's an example of the command you should run:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker run -d --network=host -e OLLAMA_API_BASE_URL=http://127.0.0.1:11434/api --name ollama-webui --restart always ghcr.io/ollama-webui/ollama-webui:main
|
docker run -d --network=host -v ollama-webui:/app/backend/data -e OLLAMA_API_BASE_URL=http://127.0.0.1:11434/api --name ollama-webui --restart always ghcr.io/ollama-webui/ollama-webui:main
|
||||||
```
|
```
|
||||||
|
|
||||||
## Connection Errors
|
## Connection Errors
|
||||||
|
|
|
@ -88,7 +88,9 @@ async def signup(form_data: SignupForm):
|
||||||
try:
|
try:
|
||||||
role = "admin" if Users.get_num_users() == 0 else "pending"
|
role = "admin" if Users.get_num_users() == 0 else "pending"
|
||||||
hashed = get_password_hash(form_data.password)
|
hashed = get_password_hash(form_data.password)
|
||||||
user = Auths.insert_new_auth(form_data.email, hashed, form_data.name, role)
|
user = Auths.insert_new_auth(
|
||||||
|
form_data.email.lower(), hashed, form_data.name, role
|
||||||
|
)
|
||||||
|
|
||||||
if user:
|
if user:
|
||||||
token = create_token(data={"email": user.email})
|
token = create_token(data={"email": user.email})
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
uvicorn main:app --port 8080 --host 0.0.0.0 --reload
|
uvicorn main:app --port 8080 --host 0.0.0.0 --forwarded-allow-ips '*' --reload
|
|
@ -1 +1 @@
|
||||||
uvicorn main:app --host 0.0.0.0 --port 8080
|
uvicorn main:app --host 0.0.0.0 --port 8080 --forwarded-allow-ips '*'
|
|
@ -3,7 +3,7 @@ import { WEBUI_API_BASE_URL } from '$lib/constants';
|
||||||
export const getSessionUser = async (token: string) => {
|
export const getSessionUser = async (token: string) => {
|
||||||
let error = null;
|
let error = null;
|
||||||
|
|
||||||
const res = await fetch(`${WEBUI_API_BASE_URL}/auths`, {
|
const res = await fetch(`${WEBUI_API_BASE_URL}/auths/`, {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
|
|
|
@ -34,7 +34,7 @@ export const updateUserRole = async (token: string, id: string, role: string) =>
|
||||||
export const getUsers = async (token: string) => {
|
export const getUsers = async (token: string) => {
|
||||||
let error = null;
|
let error = null;
|
||||||
|
|
||||||
const res = await fetch(`${WEBUI_API_BASE_URL}/users`, {
|
const res = await fetch(`${WEBUI_API_BASE_URL}/users/`, {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
|
|
|
@ -119,6 +119,7 @@
|
||||||
langDiv.style.fontSize = '0.75rem';
|
langDiv.style.fontSize = '0.75rem';
|
||||||
|
|
||||||
let button = document.createElement('button');
|
let button = document.createElement('button');
|
||||||
|
button.className = 'copy-code-button';
|
||||||
button.textContent = 'Copy Code';
|
button.textContent = 'Copy Code';
|
||||||
button.style.background = 'none';
|
button.style.background = 'none';
|
||||||
button.style.fontSize = '0.75rem';
|
button.style.fontSize = '0.75rem';
|
||||||
|
@ -832,7 +833,7 @@
|
||||||
<button
|
<button
|
||||||
class="{messageIdx + 1 === messages.length
|
class="{messageIdx + 1 === messages.length
|
||||||
? 'visible'
|
? 'visible'
|
||||||
: 'invisible group-hover:visible'} p-1 rounded dark:hover:bg-gray-800 transition"
|
: 'invisible group-hover:visible'} p-1 rounded dark:hover:bg-gray-800 transition copy-response-button"
|
||||||
on:click={() => {
|
on:click={() => {
|
||||||
copyToClipboard(message.content);
|
copyToClipboard(message.content);
|
||||||
}}
|
}}
|
||||||
|
|
|
@ -6,6 +6,11 @@
|
||||||
export let disabled = false;
|
export let disabled = false;
|
||||||
|
|
||||||
const saveDefaultModel = () => {
|
const saveDefaultModel = () => {
|
||||||
|
const hasEmptyModel = selectedModels.filter(it => it === '');
|
||||||
|
if(hasEmptyModel.length){
|
||||||
|
toast.error('Choose a model before saving...');
|
||||||
|
return;
|
||||||
|
}
|
||||||
settings.set({ ...$settings, models: selectedModels });
|
settings.set({ ...$settings, models: selectedModels });
|
||||||
localStorage.setItem('settings', JSON.stringify($settings));
|
localStorage.setItem('settings', JSON.stringify($settings));
|
||||||
toast.success('Default model updated');
|
toast.success('Default model updated');
|
||||||
|
@ -21,7 +26,7 @@
|
||||||
bind:value={selectedModel}
|
bind:value={selectedModel}
|
||||||
{disabled}
|
{disabled}
|
||||||
>
|
>
|
||||||
<option class=" text-gray-700" value="" selected>Select a model</option>
|
<option class=" text-gray-700" value="" selected disabled>Select a model</option>
|
||||||
|
|
||||||
{#each $models as model}
|
{#each $models as model}
|
||||||
{#if model.name === 'hr'}
|
{#if model.name === 'hr'}
|
||||||
|
|
|
@ -614,6 +614,7 @@
|
||||||
options.top_p = settings.top_p ?? '';
|
options.top_p = settings.top_p ?? '';
|
||||||
options.num_ctx = settings.num_ctx ?? '';
|
options.num_ctx = settings.num_ctx ?? '';
|
||||||
options = { ...options, ...settings.options };
|
options = { ...options, ...settings.options };
|
||||||
|
options.stop = (settings?.options?.stop ?? []).join(',');
|
||||||
|
|
||||||
titleAutoGenerate = settings.titleAutoGenerate ?? true;
|
titleAutoGenerate = settings.titleAutoGenerate ?? true;
|
||||||
speechAutoSend = settings.speechAutoSend ?? false;
|
speechAutoSend = settings.speechAutoSend ?? false;
|
||||||
|
@ -1051,7 +1052,8 @@
|
||||||
saveSettings({
|
saveSettings({
|
||||||
options: {
|
options: {
|
||||||
seed: (options.seed !== 0 ? options.seed : undefined) ?? undefined,
|
seed: (options.seed !== 0 ? options.seed : undefined) ?? undefined,
|
||||||
stop: options.stop !== '' ? options.stop : undefined,
|
stop:
|
||||||
|
options.stop !== '' ? options.stop.split(',').filter((e) => e) : undefined,
|
||||||
temperature: options.temperature !== '' ? options.temperature : undefined,
|
temperature: options.temperature !== '' ? options.temperature : undefined,
|
||||||
repeat_penalty:
|
repeat_penalty:
|
||||||
options.repeat_penalty !== '' ? options.repeat_penalty : undefined,
|
options.repeat_penalty !== '' ? options.repeat_penalty : undefined,
|
||||||
|
@ -1840,6 +1842,22 @@
|
||||||
|
|
||||||
<hr class=" dark:border-gray-700" />
|
<hr class=" dark:border-gray-700" />
|
||||||
|
|
||||||
|
<div class="flex space-x-1">
|
||||||
|
<a href="https://discord.gg/5rJgQTnV4s" target="_blank">
|
||||||
|
<img
|
||||||
|
alt="Discord"
|
||||||
|
src="https://img.shields.io/badge/Discord-Ollama_Web_UI-blue?logo=discord&logoColor=white"
|
||||||
|
/>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<a href="https://github.com/ollama-webui/ollama-webui" target="_blank">
|
||||||
|
<img
|
||||||
|
alt="Github Repo"
|
||||||
|
src="https://img.shields.io/github/stars/ollama-webui/ollama-webui?style=social&label=Star us on Github"
|
||||||
|
/>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="mt-2 text-xs text-gray-400 dark:text-gray-500">
|
<div class="mt-2 text-xs text-gray-400 dark:text-gray-500">
|
||||||
Created by <a
|
Created by <a
|
||||||
class=" text-gray-500 dark:text-gray-300 font-medium"
|
class=" text-gray-500 dark:text-gray-300 font-medium"
|
||||||
|
@ -1847,15 +1865,6 @@
|
||||||
target="_blank">Timothy J. Baek</a
|
target="_blank">Timothy J. Baek</a
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
|
||||||
<a href="https://github.com/ollama-webui/ollama-webui">
|
|
||||||
<img
|
|
||||||
alt="Github Repo"
|
|
||||||
src="https://img.shields.io/github/stars/ollama-webui/ollama-webui?style=social&label=Star us on Github"
|
|
||||||
/>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
216
src/lib/components/chat/ShortcutsModal.svelte
Normal file
216
src/lib/components/chat/ShortcutsModal.svelte
Normal file
|
@ -0,0 +1,216 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import Modal from '../common/Modal.svelte';
|
||||||
|
|
||||||
|
export let show = false;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Modal bind:show>
|
||||||
|
<div>
|
||||||
|
<div class=" flex justify-between dark:text-gray-300 px-5 py-4">
|
||||||
|
<div class=" text-lg font-medium self-center">Keyboard shortcuts</div>
|
||||||
|
<button
|
||||||
|
class="self-center"
|
||||||
|
on:click={() => {
|
||||||
|
show = false;
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
viewBox="0 0 20 20"
|
||||||
|
fill="currentColor"
|
||||||
|
class="w-5 h-5"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M6.28 5.22a.75.75 0 00-1.06 1.06L8.94 10l-3.72 3.72a.75.75 0 101.06 1.06L10 11.06l3.72 3.72a.75.75 0 101.06-1.06L11.06 10l3.72-3.72a.75.75 0 00-1.06-1.06L10 8.94 6.28 5.22z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<hr class=" dark:border-gray-800" />
|
||||||
|
|
||||||
|
<div class="flex flex-col md:flex-row w-full p-5 md:space-x-4 dark:text-gray-200">
|
||||||
|
<div class=" flex flex-col w-full sm:flex-row sm:justify-center sm:space-x-6">
|
||||||
|
<div class="flex flex-col space-y-3 w-full self-start">
|
||||||
|
<div class="w-full flex justify-between items-center">
|
||||||
|
<div class=" text-sm">Open new chat</div>
|
||||||
|
|
||||||
|
<div class="flex space-x-1 text-xs">
|
||||||
|
<div
|
||||||
|
class=" h-fit py-1 px-2 flex items-center justify-center rounded border border-black/10 capitalize text-gray-600 dark:border-white/10 dark:text-gray-300"
|
||||||
|
>
|
||||||
|
Ctrl/⌘
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class=" h-fit py-1 px-2 flex items-center justify-center rounded border border-black/10 capitalize text-gray-600 dark:border-white/10 dark:text-gray-300"
|
||||||
|
>
|
||||||
|
Shift
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class=" h-fit py-1 px-2 flex items-center justify-center rounded border border-black/10 capitalize text-gray-600 dark:border-white/10 dark:text-gray-300"
|
||||||
|
>
|
||||||
|
O
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="w-full flex justify-between items-center">
|
||||||
|
<div class=" text-sm">Focus chat input</div>
|
||||||
|
|
||||||
|
<div class="flex space-x-1 text-xs">
|
||||||
|
<div
|
||||||
|
class=" h-fit py-1 px-2 flex items-center justify-center rounded border border-black/10 capitalize text-gray-600 dark:border-white/10 dark:text-gray-300"
|
||||||
|
>
|
||||||
|
Shift
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class=" h-fit py-1 px-2 flex items-center justify-center rounded border border-black/10 capitalize text-gray-600 dark:border-white/10 dark:text-gray-300"
|
||||||
|
>
|
||||||
|
Esc
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="w-full flex justify-between items-center">
|
||||||
|
<div class=" text-sm">Copy last code block</div>
|
||||||
|
|
||||||
|
<div class="flex space-x-1 text-xs">
|
||||||
|
<div
|
||||||
|
class=" h-fit py-1 px-2 flex items-center justify-center rounded border border-black/10 capitalize text-gray-600 dark:border-white/10 dark:text-gray-300"
|
||||||
|
>
|
||||||
|
Ctrl/⌘
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class=" h-fit py-1 px-2 flex items-center justify-center rounded border border-black/10 capitalize text-gray-600 dark:border-white/10 dark:text-gray-300"
|
||||||
|
>
|
||||||
|
Shift
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class=" h-fit py-1 px-2 flex items-center justify-center rounded border border-black/10 capitalize text-gray-600 dark:border-white/10 dark:text-gray-300"
|
||||||
|
>
|
||||||
|
;
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="w-full flex justify-between items-center">
|
||||||
|
<div class=" text-sm">Copy last response</div>
|
||||||
|
|
||||||
|
<div class="flex space-x-1 text-xs">
|
||||||
|
<div
|
||||||
|
class=" h-fit py-1 px-2 flex items-center justify-center rounded border border-black/10 capitalize text-gray-600 dark:border-white/10 dark:text-gray-300"
|
||||||
|
>
|
||||||
|
Ctrl/⌘
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class=" h-fit py-1 px-2 flex items-center justify-center rounded border border-black/10 capitalize text-gray-600 dark:border-white/10 dark:text-gray-300"
|
||||||
|
>
|
||||||
|
Shift
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class=" h-fit py-1 px-2 flex items-center justify-center rounded border border-black/10 capitalize text-gray-600 dark:border-white/10 dark:text-gray-300"
|
||||||
|
>
|
||||||
|
C
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex flex-col space-y-3 w-full self-start">
|
||||||
|
<div class="w-full flex justify-between items-center">
|
||||||
|
<div class=" text-sm">Toggle sidebar</div>
|
||||||
|
|
||||||
|
<div class="flex space-x-1 text-xs">
|
||||||
|
<div
|
||||||
|
class=" h-fit py-1 px-2 flex items-center justify-center rounded border border-black/10 capitalize text-gray-600 dark:border-white/10 dark:text-gray-300"
|
||||||
|
>
|
||||||
|
Ctrl/⌘
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class=" h-fit py-1 px-2 flex items-center justify-center rounded border border-black/10 capitalize text-gray-600 dark:border-white/10 dark:text-gray-300"
|
||||||
|
>
|
||||||
|
Shift
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class=" h-fit py-1 px-2 flex items-center justify-center rounded border border-black/10 capitalize text-gray-600 dark:border-white/10 dark:text-gray-300"
|
||||||
|
>
|
||||||
|
S
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="w-full flex justify-between items-center">
|
||||||
|
<div class=" text-sm">Delete chat</div>
|
||||||
|
|
||||||
|
<div class="flex space-x-1 text-xs">
|
||||||
|
<div
|
||||||
|
class=" h-fit py-1 px-2 flex items-center justify-center rounded border border-black/10 capitalize text-gray-600 dark:border-white/10 dark:text-gray-300"
|
||||||
|
>
|
||||||
|
Ctrl/⌘
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class=" h-fit py-1 px-2 flex items-center justify-center rounded border border-black/10 capitalize text-gray-600 dark:border-white/10 dark:text-gray-300"
|
||||||
|
>
|
||||||
|
Shift
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class=" h-fit py-1 px-2 flex items-center justify-center rounded border border-black/10 capitalize text-gray-600 dark:border-white/10 dark:text-gray-300"
|
||||||
|
>
|
||||||
|
⌫
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="w-full flex justify-between items-center">
|
||||||
|
<div class=" text-sm">Show shortcuts</div>
|
||||||
|
|
||||||
|
<div class="flex space-x-1 text-xs">
|
||||||
|
<div
|
||||||
|
class=" h-fit py-1 px-2 flex items-center justify-center rounded border border-black/10 capitalize text-gray-600 dark:border-white/10 dark:text-gray-300"
|
||||||
|
>
|
||||||
|
Ctrl/⌘
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class=" h-fit py-1 px-2 flex items-center justify-center rounded border border-black/10 capitalize text-gray-600 dark:border-white/10 dark:text-gray-300"
|
||||||
|
>
|
||||||
|
/
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Modal>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
input::-webkit-outer-spin-button,
|
||||||
|
input::-webkit-inner-spin-button {
|
||||||
|
/* display: none; <- Crashes Chrome on hover */
|
||||||
|
-webkit-appearance: none;
|
||||||
|
margin: 0; /* <-- Apparently some margin are still there even though it's hidden */
|
||||||
|
}
|
||||||
|
|
||||||
|
.tabs::-webkit-scrollbar {
|
||||||
|
display: none; /* for Chrome, Safari and Opera */
|
||||||
|
}
|
||||||
|
|
||||||
|
.tabs {
|
||||||
|
-ms-overflow-style: none; /* IE and Edge */
|
||||||
|
scrollbar-width: none; /* Firefox */
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type='number'] {
|
||||||
|
-moz-appearance: textfield; /* Firefox */
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -61,6 +61,7 @@
|
||||||
<div class="py-2.5 my-auto flex flex-col justify-between h-screen">
|
<div class="py-2.5 my-auto flex flex-col justify-between h-screen">
|
||||||
<div class="px-2.5 flex justify-center space-x-2">
|
<div class="px-2.5 flex justify-center space-x-2">
|
||||||
<button
|
<button
|
||||||
|
id="sidebar-new-chat-button"
|
||||||
class="flex-grow flex justify-between rounded-md px-3 py-1.5 mt-2 hover:bg-gray-900 transition"
|
class="flex-grow flex justify-between rounded-md px-3 py-1.5 mt-2 hover:bg-gray-900 transition"
|
||||||
on:click={async () => {
|
on:click={async () => {
|
||||||
goto('/');
|
goto('/');
|
||||||
|
@ -320,6 +321,13 @@
|
||||||
</div>
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
<div class="flex self-center space-x-1.5">
|
<div class="flex self-center space-x-1.5">
|
||||||
|
<button
|
||||||
|
id="delete-chat-button"
|
||||||
|
class=" hidden"
|
||||||
|
on:click={() => {
|
||||||
|
deleteChat(chat.id);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
<button
|
<button
|
||||||
class=" self-center hover:text-white transition"
|
class=" self-center hover:text-white transition"
|
||||||
on:click={() => {
|
on:click={() => {
|
||||||
|
@ -382,11 +390,6 @@
|
||||||
on:click={() => {
|
on:click={() => {
|
||||||
showDropdown = !showDropdown;
|
showDropdown = !showDropdown;
|
||||||
}}
|
}}
|
||||||
on:focusout={() => {
|
|
||||||
setTimeout(() => {
|
|
||||||
showDropdown = false;
|
|
||||||
}, 150);
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<div class=" self-center mr-3">
|
<div class=" self-center mr-3">
|
||||||
<img
|
<img
|
||||||
|
@ -409,6 +412,7 @@
|
||||||
class="flex py-2.5 px-3.5 w-full hover:bg-gray-800 transition"
|
class="flex py-2.5 px-3.5 w-full hover:bg-gray-800 transition"
|
||||||
on:click={() => {
|
on:click={() => {
|
||||||
goto('/admin');
|
goto('/admin');
|
||||||
|
showDropdown = false;
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div class=" self-center mr-3">
|
<div class=" self-center mr-3">
|
||||||
|
@ -435,6 +439,7 @@
|
||||||
class="flex py-2.5 px-3.5 w-full hover:bg-gray-800 transition"
|
class="flex py-2.5 px-3.5 w-full hover:bg-gray-800 transition"
|
||||||
on:click={async () => {
|
on:click={async () => {
|
||||||
await showSettings.set(true);
|
await showSettings.set(true);
|
||||||
|
showDropdown = false;
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div class=" self-center mr-3">
|
<div class=" self-center mr-3">
|
||||||
|
@ -470,6 +475,7 @@
|
||||||
on:click={() => {
|
on:click={() => {
|
||||||
localStorage.removeItem('token');
|
localStorage.removeItem('token');
|
||||||
location.href = '/auth';
|
location.href = '/auth';
|
||||||
|
showDropdown = false;
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div class=" self-center mr-3">
|
<div class=" self-center mr-3">
|
||||||
|
@ -535,6 +541,7 @@
|
||||||
class="fixed left-0 top-[50dvh] z-40 -translate-y-1/2 transition-transform translate-x-[255px] md:translate-x-[260px] rotate-0"
|
class="fixed left-0 top-[50dvh] z-40 -translate-y-1/2 transition-transform translate-x-[255px] md:translate-x-[260px] rotate-0"
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
|
id="sidebar-toggle-button"
|
||||||
class=" group"
|
class=" group"
|
||||||
on:click={() => {
|
on:click={() => {
|
||||||
show = !show;
|
show = !show;
|
||||||
|
|
|
@ -18,6 +18,7 @@
|
||||||
import SettingsModal from '$lib/components/chat/SettingsModal.svelte';
|
import SettingsModal from '$lib/components/chat/SettingsModal.svelte';
|
||||||
import Sidebar from '$lib/components/layout/Sidebar.svelte';
|
import Sidebar from '$lib/components/layout/Sidebar.svelte';
|
||||||
import { checkVersion } from '$lib/utils';
|
import { checkVersion } from '$lib/utils';
|
||||||
|
import ShortcutsModal from '$lib/components/chat/ShortcutsModal.svelte';
|
||||||
|
|
||||||
let ollamaVersion = '';
|
let ollamaVersion = '';
|
||||||
let loaded = false;
|
let loaded = false;
|
||||||
|
@ -25,6 +26,8 @@
|
||||||
let DB = null;
|
let DB = null;
|
||||||
let localDBChats = [];
|
let localDBChats = [];
|
||||||
|
|
||||||
|
let showShortcuts = false;
|
||||||
|
|
||||||
const getModels = async () => {
|
const getModels = async () => {
|
||||||
let models = [];
|
let models = [];
|
||||||
models.push(
|
models.push(
|
||||||
|
@ -106,6 +109,65 @@
|
||||||
});
|
});
|
||||||
|
|
||||||
await setOllamaVersion();
|
await setOllamaVersion();
|
||||||
|
|
||||||
|
document.addEventListener('keydown', function (event) {
|
||||||
|
const isCtrlPressed = event.ctrlKey || event.metaKey; // metaKey is for Cmd key on Mac
|
||||||
|
// Check if the Shift key is pressed
|
||||||
|
const isShiftPressed = event.shiftKey;
|
||||||
|
|
||||||
|
// Check if Ctrl + Shift + O is pressed
|
||||||
|
if (isCtrlPressed && isShiftPressed && event.key.toLowerCase() === 'o') {
|
||||||
|
event.preventDefault();
|
||||||
|
console.log('newChat');
|
||||||
|
document.getElementById('sidebar-new-chat-button')?.click();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if Shift + Esc is pressed
|
||||||
|
if (isShiftPressed && event.key === 'Escape') {
|
||||||
|
event.preventDefault();
|
||||||
|
console.log('focusInput');
|
||||||
|
document.getElementById('chat-textarea')?.focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if Ctrl + Shift + ; is pressed
|
||||||
|
if (isCtrlPressed && isShiftPressed && event.key === ';') {
|
||||||
|
event.preventDefault();
|
||||||
|
console.log('copyLastCodeBlock');
|
||||||
|
const button = [...document.getElementsByClassName('copy-code-button')]?.at(-1);
|
||||||
|
button?.click();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if Ctrl + Shift + C is pressed
|
||||||
|
if (isCtrlPressed && isShiftPressed && event.key.toLowerCase() === 'c') {
|
||||||
|
event.preventDefault();
|
||||||
|
console.log('copyLastResponse');
|
||||||
|
const button = [...document.getElementsByClassName('copy-response-button')]?.at(-1);
|
||||||
|
console.log(button);
|
||||||
|
button?.click();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if Ctrl + Shift + S is pressed
|
||||||
|
if (isCtrlPressed && isShiftPressed && event.key.toLowerCase() === 's') {
|
||||||
|
event.preventDefault();
|
||||||
|
console.log('toggleSidebar');
|
||||||
|
document.getElementById('sidebar-toggle-button')?.click();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if Ctrl + Shift + Backspace is pressed
|
||||||
|
if (isCtrlPressed && isShiftPressed && event.key === 'Backspace') {
|
||||||
|
event.preventDefault();
|
||||||
|
console.log('deleteChat');
|
||||||
|
document.getElementById('delete-chat-button')?.click();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if Ctrl + / is pressed
|
||||||
|
if (isCtrlPressed && event.key === '/') {
|
||||||
|
event.preventDefault();
|
||||||
|
console.log('showShortcuts');
|
||||||
|
document.getElementById('show-shortcuts-button')?.click();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
await tick();
|
await tick();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -114,6 +176,20 @@
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if loaded}
|
{#if loaded}
|
||||||
|
<div class=" hidden lg:flex fixed bottom-0 right-0 px-3 py-3 z-10">
|
||||||
|
<button
|
||||||
|
id="show-shortcuts-button"
|
||||||
|
class="text-gray-600 dark:text-gray-300 bg-gray-300/20 w-6 h-6 flex items-center justify-center text-xs rounded-full"
|
||||||
|
on:click={() => {
|
||||||
|
showShortcuts = !showShortcuts;
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
?
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ShortcutsModal bind:show={showShortcuts} />
|
||||||
|
|
||||||
<div class="app relative">
|
<div class="app relative">
|
||||||
{#if !['user', 'admin'].includes($user.role)}
|
{#if !['user', 'admin'].includes($user.role)}
|
||||||
<div class="fixed w-full h-full flex z-50">
|
<div class="fixed w-full h-full flex z-50">
|
||||||
|
|
|
@ -15,6 +15,7 @@
|
||||||
} from '$lib/apis/modelfiles';
|
} from '$lib/apis/modelfiles';
|
||||||
|
|
||||||
let localModelfiles = [];
|
let localModelfiles = [];
|
||||||
|
let importFiles;
|
||||||
|
|
||||||
const deleteModelHandler = async (tagName) => {
|
const deleteModelHandler = async (tagName) => {
|
||||||
let success = null;
|
let success = null;
|
||||||
|
@ -199,78 +200,133 @@
|
||||||
</div>
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
|
|
||||||
{#if localModelfiles.length > 0}
|
<hr class=" dark:border-gray-700 my-2.5" />
|
||||||
<hr class=" dark:border-gray-700 my-2.5" />
|
|
||||||
|
|
||||||
<div class=" flex justify-end space-x-4 w-full mb-3">
|
<div class=" flex justify-between w-full mb-3">
|
||||||
<div class=" self-center text-sm font-medium">
|
<div class="flex space-x-1">
|
||||||
{localModelfiles.length} Local Modelfiles Detected
|
<input
|
||||||
</div>
|
id="modelfiles-import-input"
|
||||||
|
bind:files={importFiles}
|
||||||
|
type="file"
|
||||||
|
accept=".json"
|
||||||
|
hidden
|
||||||
|
on:change={() => {
|
||||||
|
console.log(importFiles);
|
||||||
|
|
||||||
<div class="flex space-x-1">
|
let reader = new FileReader();
|
||||||
<button
|
reader.onload = async (event) => {
|
||||||
class="self-center w-fit text-sm px-3 py-1 border dark:border-gray-600 rounded-xl flex"
|
let savedModelfiles = JSON.parse(event.target.result);
|
||||||
on:click={async () => {
|
console.log(savedModelfiles);
|
||||||
for (const modelfile of localModelfiles) {
|
|
||||||
|
for (const modelfile of savedModelfiles) {
|
||||||
await createNewModelfile(localStorage.token, modelfile).catch((error) => {
|
await createNewModelfile(localStorage.token, modelfile).catch((error) => {
|
||||||
return null;
|
return null;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
saveModelfiles(localModelfiles);
|
|
||||||
localStorage.removeItem('modelfiles');
|
|
||||||
localModelfiles = JSON.parse(localStorage.getItem('modelfiles') ?? '[]');
|
|
||||||
await modelfiles.set(await getModelfiles(localStorage.token));
|
await modelfiles.set(await getModelfiles(localStorage.token));
|
||||||
}}
|
};
|
||||||
>
|
|
||||||
<div class=" self-center mr-2 font-medium">Sync All</div>
|
|
||||||
|
|
||||||
<div class=" self-center">
|
reader.readAsText(importFiles[0]);
|
||||||
<svg
|
}}
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
/>
|
||||||
viewBox="0 0 16 16"
|
|
||||||
fill="currentColor"
|
|
||||||
class="w-3.5 h-3.5"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
fill-rule="evenodd"
|
|
||||||
d="M13.836 2.477a.75.75 0 0 1 .75.75v3.182a.75.75 0 0 1-.75.75h-3.182a.75.75 0 0 1 0-1.5h1.37l-.84-.841a4.5 4.5 0 0 0-7.08.932.75.75 0 0 1-1.3-.75 6 6 0 0 1 9.44-1.242l.842.84V3.227a.75.75 0 0 1 .75-.75Zm-.911 7.5A.75.75 0 0 1 13.199 11a6 6 0 0 1-9.44 1.241l-.84-.84v1.371a.75.75 0 0 1-1.5 0V9.591a.75.75 0 0 1 .75-.75H5.35a.75.75 0 0 1 0 1.5H3.98l.841.841a4.5 4.5 0 0 0 7.08-.932.75.75 0 0 1 1.025-.273Z"
|
|
||||||
clip-rule="evenodd"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<button
|
<button
|
||||||
class="self-center w-fit text-sm p-1.5 border dark:border-gray-600 rounded-xl flex"
|
class="self-center w-fit text-sm px-3 py-1 border dark:border-gray-600 rounded-xl flex"
|
||||||
on:click={async () => {
|
on:click={async () => {
|
||||||
saveModelfiles(localModelfiles);
|
document.getElementById('modelfiles-import-input')?.click();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div class=" self-center mr-2 font-medium">Import Modelfiles</div>
|
||||||
|
|
||||||
localStorage.removeItem('modelfiles');
|
<div class=" self-center">
|
||||||
localModelfiles = JSON.parse(localStorage.getItem('modelfiles') ?? '[]');
|
<svg
|
||||||
await modelfiles.set(await getModelfiles(localStorage.token));
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
}}
|
viewBox="0 0 16 16"
|
||||||
>
|
fill="currentColor"
|
||||||
<div class=" self-center">
|
class="w-3.5 h-3.5"
|
||||||
<svg
|
>
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
<path
|
||||||
fill="none"
|
fill-rule="evenodd"
|
||||||
viewBox="0 0 24 24"
|
d="M4 2a1.5 1.5 0 0 0-1.5 1.5v9A1.5 1.5 0 0 0 4 14h8a1.5 1.5 0 0 0 1.5-1.5V6.621a1.5 1.5 0 0 0-.44-1.06L9.94 2.439A1.5 1.5 0 0 0 8.878 2H4Zm4 9.5a.75.75 0 0 1-.75-.75V8.06l-.72.72a.75.75 0 0 1-1.06-1.06l2-2a.75.75 0 0 1 1.06 0l2 2a.75.75 0 1 1-1.06 1.06l-.72-.72v2.69a.75.75 0 0 1-.75.75Z"
|
||||||
stroke-width="1.5"
|
clip-rule="evenodd"
|
||||||
stroke="currentColor"
|
/>
|
||||||
class="w-4 h-4"
|
</svg>
|
||||||
>
|
</div>
|
||||||
<path
|
</button>
|
||||||
stroke-linecap="round"
|
|
||||||
stroke-linejoin="round"
|
|
||||||
d="M14.74 9l-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 01-2.244 2.077H8.084a2.25 2.25 0 01-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 00-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 013.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 00-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 00-7.5 0"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
|
||||||
|
{#if localModelfiles.length > 0}
|
||||||
|
<div class="flex">
|
||||||
|
<div class=" self-center text-sm font-medium mr-4">
|
||||||
|
{localModelfiles.length} Local Modelfiles Detected
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex space-x-1">
|
||||||
|
<button
|
||||||
|
class="self-center w-fit text-sm px-3 py-1 border dark:border-gray-600 rounded-xl flex"
|
||||||
|
on:click={async () => {
|
||||||
|
for (const modelfile of localModelfiles) {
|
||||||
|
await createNewModelfile(localStorage.token, modelfile).catch((error) => {
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
saveModelfiles(localModelfiles);
|
||||||
|
localStorage.removeItem('modelfiles');
|
||||||
|
localModelfiles = JSON.parse(localStorage.getItem('modelfiles') ?? '[]');
|
||||||
|
await modelfiles.set(await getModelfiles(localStorage.token));
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div class=" self-center mr-2 font-medium">Sync All</div>
|
||||||
|
|
||||||
|
<div class=" self-center">
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
viewBox="0 0 16 16"
|
||||||
|
fill="currentColor"
|
||||||
|
class="w-3.5 h-3.5"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fill-rule="evenodd"
|
||||||
|
d="M13.836 2.477a.75.75 0 0 1 .75.75v3.182a.75.75 0 0 1-.75.75h-3.182a.75.75 0 0 1 0-1.5h1.37l-.84-.841a4.5 4.5 0 0 0-7.08.932.75.75 0 0 1-1.3-.75 6 6 0 0 1 9.44-1.242l.842.84V3.227a.75.75 0 0 1 .75-.75Zm-.911 7.5A.75.75 0 0 1 13.199 11a6 6 0 0 1-9.44 1.241l-.84-.84v1.371a.75.75 0 0 1-1.5 0V9.591a.75.75 0 0 1 .75-.75H5.35a.75.75 0 0 1 0 1.5H3.98l.841.841a4.5 4.5 0 0 0 7.08-.932.75.75 0 0 1 1.025-.273Z"
|
||||||
|
clip-rule="evenodd"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button
|
||||||
|
class="self-center w-fit text-sm p-1.5 border dark:border-gray-600 rounded-xl flex"
|
||||||
|
on:click={async () => {
|
||||||
|
saveModelfiles(localModelfiles);
|
||||||
|
|
||||||
|
localStorage.removeItem('modelfiles');
|
||||||
|
localModelfiles = JSON.parse(localStorage.getItem('modelfiles') ?? '[]');
|
||||||
|
await modelfiles.set(await getModelfiles(localStorage.token));
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div class=" self-center">
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
stroke-width="1.5"
|
||||||
|
stroke="currentColor"
|
||||||
|
class="w-4 h-4"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
d="M14.74 9l-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 01-2.244 2.077H8.084a2.25 2.25 0 01-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 00-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 013.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 00-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 00-7.5 0"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class=" my-16">
|
<div class=" my-16">
|
||||||
<div class=" text-2xl font-semibold mb-6">Made by OllamaHub Community</div>
|
<div class=" text-2xl font-semibold mb-6">Made by OllamaHub Community</div>
|
||||||
|
|
Loading…
Reference in a new issue