Compare commits
63 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 6c70d0f770 | |||
| 96e2ee48f7 | |||
| 63e7246c97 | |||
| 6f4a863562 | |||
| a7ae141301 | |||
| 2564b154fc | |||
| 8310392c9d | |||
| 78d9c469a2 | |||
| e5c812f1d2 | |||
| 8461b92f08 | |||
| 3396723057 | |||
| 4fe47f43be | |||
| 705f5aecd7 | |||
| 19d49df76f | |||
| 30c94ff10f | |||
| 941ab5d6cf | |||
| 5a81ca7e39 | |||
| f4122fd0ce | |||
| 5745b9c5db | |||
| 3fd55ebc32 | |||
| 776b463ff3 | |||
| 5640b38694 | |||
| a181b2b63b | |||
| 32361dc3da | |||
| 59ae5b6d34 | |||
| a36627cbce | |||
| 8a63d22000 | |||
| 781be2779b | |||
| f347d664ac | |||
| 260a8b8b83 | |||
| 1d4d1ddbea | |||
| 8f2d580991 | |||
| 6bbc3721b4 | |||
| 7fe412374c | |||
| 03210c9204 | |||
| d24b6fa9af | |||
| 7c7e16c382 | |||
| 70f807e8c9 | |||
| 210ae331cc | |||
| bbeaa92904 | |||
| a7a817d12b | |||
| 6df2505bf0 | |||
| 3a7ded3f80 | |||
| 6d8b1385bb | |||
| 92c922b061 | |||
| 63628a70a6 | |||
| 95dd618a62 | |||
| f8cf43c0f7 | |||
| 334d5246ba | |||
| 42251c232a | |||
| 6b0eae9fad | |||
| 1ff0c9a95d | |||
| 0a2ee691db | |||
| 6245146661 | |||
| 588daace91 | |||
| 8f4a12b479 | |||
| 81a50c5104 | |||
| d1656d5da8 | |||
| 0f6bdaa602 | |||
| a016171573 | |||
| 7d0d504390 | |||
| 6bc627bbfb | |||
| 5adff7767f |
@@ -52,6 +52,7 @@ jobs:
|
||||
type=ref,event=tag
|
||||
type=sha,prefix=git-
|
||||
type=semver,pattern={{version}}
|
||||
type=semver,pattern={{major}}.{{minor}}
|
||||
flavor: |
|
||||
latest=${{ github.ref == 'refs/heads/main' }}
|
||||
|
||||
|
||||
@@ -5,6 +5,46 @@ All notable changes to this project will be documented in this file.
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [0.1.107] - 2024-03-01
|
||||
|
||||
### Added
|
||||
|
||||
- **🚀 Makefile and LLM Update Script**: Included Makefile and a script for LLM updates in the repository.
|
||||
|
||||
### Fixed
|
||||
|
||||
- Corrected issue where links in the settings modal didn't appear clickable (#960).
|
||||
- Fixed problem with web UI port not taking effect due to incorrect environment variable name in run-compose.sh (#996).
|
||||
- Enhanced user experience by displaying chat in browser title and enabling automatic scrolling to the bottom (#992).
|
||||
|
||||
### Changed
|
||||
|
||||
- Upgraded toast library from `svelte-french-toast` to `svelte-sonner` for a more polished UI.
|
||||
- Enhanced accessibility with the addition of dark mode on the authentication page.
|
||||
|
||||
## [0.1.106] - 2024-02-27
|
||||
|
||||
### Added
|
||||
|
||||
- **🎯 Auto-focus Feature**: The input area now automatically focuses when initiating or opening a chat conversation.
|
||||
|
||||
### Fixed
|
||||
|
||||
- Corrected typo from "HuggingFace" to "Hugging Face" (Issue #924).
|
||||
- Resolved bug causing errors in chat completion API calls to OpenAI due to missing "num_ctx" parameter (Issue #927).
|
||||
- Fixed issues preventing text editing, selection, and cursor retention in the input field (Issue #940).
|
||||
- Fixed a bug where defining an OpenAI-compatible API server using 'OPENAI_API_BASE_URL' containing 'openai' string resulted in hiding models not containing 'gpt' string from the model menu. (Issue #930)
|
||||
|
||||
## [0.1.105] - 2024-02-25
|
||||
|
||||
### Added
|
||||
|
||||
- **📄 Document Selection**: Now you can select and delete multiple documents at once for easier management.
|
||||
|
||||
### Changed
|
||||
|
||||
- **🏷️ Document Pre-tagging**: Simply click the "+" button at the top, enter tag names in the popup window, or select from a list of existing tags. Then, upload files with the added tags for streamlined organization.
|
||||
|
||||
## [0.1.104] - 2024-02-25
|
||||
|
||||
### Added
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
install:
|
||||
@docker-compose up -d
|
||||
|
||||
remove:
|
||||
@chmod +x confirm_remove.sh
|
||||
@./confirm_remove.sh
|
||||
|
||||
|
||||
start:
|
||||
@docker-compose start
|
||||
|
||||
stop:
|
||||
@docker-compose stop
|
||||
|
||||
update:
|
||||
# Calls the LLM update script
|
||||
chmod +x update_ollama_models.sh
|
||||
@./update_ollama_models.sh
|
||||
@git pull
|
||||
@docker-compose down
|
||||
# Make sure the ollama-webui container is stopped before rebuilding
|
||||
@docker stop open-webui || true
|
||||
@docker-compose up --build -d
|
||||
@docker-compose start
|
||||
|
||||
@@ -160,6 +160,16 @@ This project is licensed under the [MIT License](LICENSE) - see the [LICENSE](LI
|
||||
If you have any questions, suggestions, or need assistance, please open an issue or join our
|
||||
[Open WebUI Discord community](https://discord.gg/5rJgQTnV4s) to connect with us! 🤝
|
||||
|
||||
## Star History
|
||||
|
||||
<a href="https://star-history.com/#open-webui/open-webui&Date">
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/svg?repos=open-webui/open-webui&type=Date&theme=dark" />
|
||||
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/svg?repos=open-webui/open-webui&type=Date" />
|
||||
<img alt="Star History Chart" src="https://api.star-history.com/svg?repos=open-webui/open-webui&type=Date" />
|
||||
</picture>
|
||||
</a>
|
||||
|
||||
---
|
||||
|
||||
Created by [Timothy J. Baek](https://github.com/tjbck) - Let's make Open Web UI even more amazing together! 💪
|
||||
|
||||
@@ -146,6 +146,13 @@ async def proxy(path: str, request: Request, user=Depends(get_verified_user)):
|
||||
body["max_tokens"] = 4000
|
||||
print("Modified body_dict:", body)
|
||||
|
||||
# Fix for ChatGPT calls failing because the num_ctx key is in body
|
||||
if "num_ctx" in body:
|
||||
# If 'num_ctx' is in the dictionary, delete it
|
||||
# Leaving it there generates an error with the
|
||||
# OpenAI API (Feb 2024)
|
||||
del body["num_ctx"]
|
||||
|
||||
# Convert the modified body back to JSON
|
||||
body = json.dumps(body)
|
||||
except json.JSONDecodeError as e:
|
||||
@@ -184,7 +191,7 @@ async def proxy(path: str, request: Request, user=Depends(get_verified_user)):
|
||||
|
||||
response_data = r.json()
|
||||
|
||||
if "openai" in app.state.OPENAI_API_BASE_URL and path == "models":
|
||||
if "api.openai.com" in app.state.OPENAI_API_BASE_URL and path == "models":
|
||||
response_data["data"] = list(
|
||||
filter(lambda model: "gpt" in model["id"], response_data["data"])
|
||||
)
|
||||
|
||||
Executable
+8
@@ -0,0 +1,8 @@
|
||||
#!/bin/bash
|
||||
echo "Warning: This will remove all containers and volumes, including persistent data. Do you want to continue? [Y/N]"
|
||||
read ans
|
||||
if [ "$ans" == "Y" ] || [ "$ans" == "y" ]; then
|
||||
docker-compose down -v
|
||||
else
|
||||
echo "Operation cancelled."
|
||||
fi
|
||||
Generated
+12
-34
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "open-webui",
|
||||
"version": "v1.0.0-alpha.101",
|
||||
"version": "0.1.106",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "open-webui",
|
||||
"version": "v1.0.0-alpha.101",
|
||||
"version": "0.1.106",
|
||||
"dependencies": {
|
||||
"@sveltejs/adapter-node": "^1.3.1",
|
||||
"async": "^3.2.5",
|
||||
@@ -17,7 +17,7 @@
|
||||
"js-sha256": "^0.10.1",
|
||||
"katex": "^0.16.9",
|
||||
"marked": "^9.1.0",
|
||||
"svelte-french-toast": "^1.2.0",
|
||||
"svelte-sonner": "^0.3.19",
|
||||
"tippy.js": "^6.3.7",
|
||||
"uuid": "^9.0.1"
|
||||
},
|
||||
@@ -3211,17 +3211,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/svelte-french-toast": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/svelte-french-toast/-/svelte-french-toast-1.2.0.tgz",
|
||||
"integrity": "sha512-5PW+6RFX3xQPbR44CngYAP1Sd9oCq9P2FOox4FZffzJuZI2mHOB7q5gJBVnOiLF5y3moVGZ7u2bYt7+yPAgcEQ==",
|
||||
"dependencies": {
|
||||
"svelte-writable-derived": "^3.1.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"svelte": "^3.57.0 || ^4.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/svelte-hmr": {
|
||||
"version": "0.15.3",
|
||||
"resolved": "https://registry.npmjs.org/svelte-hmr/-/svelte-hmr-0.15.3.tgz",
|
||||
@@ -3307,15 +3296,12 @@
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/svelte-writable-derived": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/svelte-writable-derived/-/svelte-writable-derived-3.1.0.tgz",
|
||||
"integrity": "sha512-cTvaVFNIJ036vSDIyPxJYivKC7ZLtcFOPm1Iq6qWBDo1fOHzfk6ZSbwaKrxhjgy52Rbl5IHzRcWgos6Zqn9/rg==",
|
||||
"funding": {
|
||||
"url": "https://ko-fi.com/pixievoltno1"
|
||||
},
|
||||
"node_modules/svelte-sonner": {
|
||||
"version": "0.3.19",
|
||||
"resolved": "https://registry.npmjs.org/svelte-sonner/-/svelte-sonner-0.3.19.tgz",
|
||||
"integrity": "sha512-jpPOgLtHwRaB6Vqo2dUQMv15/yUV/BQWTjKpEqQ11uqRSHKjAYUKZyGrHB2cQsGmyjR0JUzBD58btpgNqINQ/Q==",
|
||||
"peerDependencies": {
|
||||
"svelte": "^3.2.1 || ^4.0.0-next.1"
|
||||
"svelte": ">=3 <5"
|
||||
}
|
||||
},
|
||||
"node_modules/tailwindcss": {
|
||||
@@ -5882,14 +5868,6 @@
|
||||
"postcss-scss": "^4.0.8"
|
||||
}
|
||||
},
|
||||
"svelte-french-toast": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/svelte-french-toast/-/svelte-french-toast-1.2.0.tgz",
|
||||
"integrity": "sha512-5PW+6RFX3xQPbR44CngYAP1Sd9oCq9P2FOox4FZffzJuZI2mHOB7q5gJBVnOiLF5y3moVGZ7u2bYt7+yPAgcEQ==",
|
||||
"requires": {
|
||||
"svelte-writable-derived": "^3.1.0"
|
||||
}
|
||||
},
|
||||
"svelte-hmr": {
|
||||
"version": "0.15.3",
|
||||
"resolved": "https://registry.npmjs.org/svelte-hmr/-/svelte-hmr-0.15.3.tgz",
|
||||
@@ -5920,10 +5898,10 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"svelte-writable-derived": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/svelte-writable-derived/-/svelte-writable-derived-3.1.0.tgz",
|
||||
"integrity": "sha512-cTvaVFNIJ036vSDIyPxJYivKC7ZLtcFOPm1Iq6qWBDo1fOHzfk6ZSbwaKrxhjgy52Rbl5IHzRcWgos6Zqn9/rg==",
|
||||
"svelte-sonner": {
|
||||
"version": "0.3.19",
|
||||
"resolved": "https://registry.npmjs.org/svelte-sonner/-/svelte-sonner-0.3.19.tgz",
|
||||
"integrity": "sha512-jpPOgLtHwRaB6Vqo2dUQMv15/yUV/BQWTjKpEqQ11uqRSHKjAYUKZyGrHB2cQsGmyjR0JUzBD58btpgNqINQ/Q==",
|
||||
"requires": {}
|
||||
},
|
||||
"tailwindcss": {
|
||||
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "open-webui",
|
||||
"version": "0.1.104",
|
||||
"version": "0.1.107",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "vite dev --host",
|
||||
@@ -49,7 +49,7 @@
|
||||
"js-sha256": "^0.10.1",
|
||||
"katex": "^0.16.9",
|
||||
"marked": "^9.1.0",
|
||||
"svelte-french-toast": "^1.2.0",
|
||||
"svelte-sonner": "^0.3.19",
|
||||
"tippy.js": "^6.3.7",
|
||||
"uuid": "^9.0.1"
|
||||
}
|
||||
|
||||
+1
-1
@@ -182,7 +182,7 @@ else
|
||||
export OLLAMA_DATA_DIR=$data_dir # Set OLLAMA_DATA_DIR environment variable
|
||||
fi
|
||||
if [[ -n $webui_port ]]; then
|
||||
export OLLAMA_WEBUI_PORT=$webui_port # Set OLLAMA_WEBUI_PORT environment variable
|
||||
export OPEN_WEBUI_PORT=$webui_port # Set OPEN_WEBUI_PORT environment variable
|
||||
fi
|
||||
DEFAULT_COMPOSE_COMMAND+=" up -d"
|
||||
DEFAULT_COMPOSE_COMMAND+=" --remove-orphans"
|
||||
|
||||
@@ -5,7 +5,8 @@ export const createNewDoc = async (
|
||||
collection_name: string,
|
||||
filename: string,
|
||||
name: string,
|
||||
title: string
|
||||
title: string,
|
||||
content: object | null = null
|
||||
) => {
|
||||
let error = null;
|
||||
|
||||
@@ -20,7 +21,8 @@ export const createNewDoc = async (
|
||||
collection_name: collection_name,
|
||||
filename: filename,
|
||||
name: name,
|
||||
title: title
|
||||
title: title,
|
||||
...(content ? { content: JSON.stringify(content) } : {})
|
||||
})
|
||||
})
|
||||
.then(async (res) => {
|
||||
|
||||
@@ -150,7 +150,6 @@ export const getOpenAIModels = async (token: string = '') => {
|
||||
return res.json();
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log(err);
|
||||
error = `OpenAI: ${err?.error?.message ?? 'Network Problem'}`;
|
||||
return [];
|
||||
});
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import toast from 'svelte-french-toast';
|
||||
import { toast } from 'svelte-sonner';
|
||||
import dayjs from 'dayjs';
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
import { onMount } from 'svelte';
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import toast from 'svelte-french-toast';
|
||||
import { toast } from 'svelte-sonner';
|
||||
import { onMount, tick } from 'svelte';
|
||||
import { settings } from '$lib/stores';
|
||||
import { blobToFile, calculateSHA256, findWordIndices } from '$lib/utils';
|
||||
@@ -293,6 +293,9 @@
|
||||
};
|
||||
|
||||
onMount(() => {
|
||||
const chatInput = document.getElementById('chat-textarea');
|
||||
window.setTimeout(() => chatInput?.focus(), 0);
|
||||
|
||||
const dropZone = document.querySelector('body');
|
||||
|
||||
const onDragOver = (e) => {
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
import { documents } from '$lib/stores';
|
||||
import { removeFirstHashWord, isValidHttpUrl } from '$lib/utils';
|
||||
import { tick } from 'svelte';
|
||||
import toast from 'svelte-french-toast';
|
||||
import { toast } from 'svelte-sonner';
|
||||
|
||||
export let prompt = '';
|
||||
|
||||
@@ -89,16 +89,16 @@
|
||||
|
||||
{#if filteredItems.length > 0 || prompt.split(' ')?.at(0)?.substring(1).startsWith('http')}
|
||||
<div class="md:px-2 mb-3 text-left w-full absolute bottom-0 left-0 right-0">
|
||||
<div class="flex w-full rounded-lg border border-gray-100 dark:border-gray-700">
|
||||
<div class=" bg-gray-100 dark:bg-gray-700 w-10 rounded-l-lg text-center">
|
||||
<div class="flex w-full px-2">
|
||||
<div class=" bg-gray-100 dark:bg-gray-700 w-10 rounded-l-xl text-center">
|
||||
<div class=" text-lg font-semibold mt-2">#</div>
|
||||
</div>
|
||||
|
||||
<div class="max-h-60 flex flex-col w-full rounded-r-lg">
|
||||
<div class=" overflow-y-auto bg-white p-2 rounded-tr-lg space-y-0.5">
|
||||
<div class="max-h-60 flex flex-col w-full rounded-r-xl bg-white">
|
||||
<div class="m-1 overflow-y-auto p-1 rounded-r-xl space-y-0.5">
|
||||
{#each filteredItems as doc, docIdx}
|
||||
<button
|
||||
class=" px-3 py-1.5 rounded-lg w-full text-left {docIdx === selectedIdx
|
||||
class=" px-3 py-1.5 rounded-xl w-full text-left {docIdx === selectedIdx
|
||||
? ' bg-gray-100 selected-command-option-button'
|
||||
: ''}"
|
||||
type="button"
|
||||
@@ -132,7 +132,7 @@
|
||||
|
||||
{#if prompt.split(' ')?.at(0)?.substring(1).startsWith('http')}
|
||||
<button
|
||||
class="px-3 py-1.5 rounded-lg w-full text-left bg-gray-100 selected-command-option-button"
|
||||
class="px-3 py-1.5 rounded-xl w-full text-left bg-gray-100 selected-command-option-button"
|
||||
type="button"
|
||||
on:click={() => {
|
||||
const url = prompt.split(' ')?.at(0)?.substring(1);
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
import { models } from '$lib/stores';
|
||||
import { splitStream } from '$lib/utils';
|
||||
import { tick } from 'svelte';
|
||||
import toast from 'svelte-french-toast';
|
||||
import { toast } from 'svelte-sonner';
|
||||
|
||||
export let prompt = '';
|
||||
export let user = null;
|
||||
@@ -121,16 +121,16 @@
|
||||
|
||||
{#if filteredModels.length > 0}
|
||||
<div class="md:px-2 mb-3 text-left w-full absolute bottom-0 left-0 right-0">
|
||||
<div class="flex w-full rounded-lg border border-gray-100 dark:border-gray-700">
|
||||
<div class=" bg-gray-100 dark:bg-gray-700 w-10 rounded-l-lg text-center">
|
||||
<div class="flex w-full px-2">
|
||||
<div class=" bg-gray-100 dark:bg-gray-700 w-10 rounded-l-xl text-center">
|
||||
<div class=" text-lg font-semibold mt-2">@</div>
|
||||
</div>
|
||||
|
||||
<div class="max-h-60 flex flex-col w-full rounded-r-lg">
|
||||
<div class=" overflow-y-auto bg-white p-2 rounded-tr-lg space-y-0.5">
|
||||
<div class="max-h-60 flex flex-col w-full rounded-r-xl bg-white">
|
||||
<div class="m-1 overflow-y-auto p-1 rounded-r-xl space-y-0.5">
|
||||
{#each filteredModels as model, modelIdx}
|
||||
<button
|
||||
class=" px-3 py-1.5 rounded-lg w-full text-left {modelIdx === selectedIdx
|
||||
class=" px-3 py-1.5 rounded-xl w-full text-left {modelIdx === selectedIdx
|
||||
? ' bg-gray-100 selected-command-option-button'
|
||||
: ''}"
|
||||
type="button"
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import { prompts } from '$lib/stores';
|
||||
import { findWordIndices } from '$lib/utils';
|
||||
import { tick } from 'svelte';
|
||||
import toast from 'svelte-french-toast';
|
||||
import { toast } from 'svelte-sonner';
|
||||
|
||||
export let prompt = '';
|
||||
let selectedCommandIdx = 0;
|
||||
@@ -60,16 +60,16 @@
|
||||
|
||||
{#if filteredPromptCommands.length > 0}
|
||||
<div class="md:px-2 mb-3 text-left w-full absolute bottom-0 left-0 right-0">
|
||||
<div class="flex w-full rounded-lg border border-gray-100 dark:border-gray-700">
|
||||
<div class=" bg-gray-100 dark:bg-gray-700 w-10 rounded-l-lg text-center">
|
||||
<div class="flex w-full px-2">
|
||||
<div class=" bg-gray-100 dark:bg-gray-700 w-10 rounded-l-xl text-center">
|
||||
<div class=" text-lg font-semibold mt-2">/</div>
|
||||
</div>
|
||||
|
||||
<div class="max-h-60 flex flex-col w-full rounded-r-lg">
|
||||
<div class=" overflow-y-auto bg-white p-2 rounded-tr-lg space-y-0.5">
|
||||
<div class="max-h-60 flex flex-col w-full rounded-r-xl bg-white">
|
||||
<div class="m-1 overflow-y-auto p-1 rounded-r-xl space-y-0.5">
|
||||
{#each filteredPromptCommands as command, commandIdx}
|
||||
<button
|
||||
class=" px-3 py-1.5 rounded-lg w-full text-left {commandIdx === selectedCommandIdx
|
||||
class=" px-3 py-1.5 rounded-xl w-full text-left {commandIdx === selectedCommandIdx
|
||||
? ' bg-gray-100 selected-command-option-button'
|
||||
: ''}"
|
||||
type="button"
|
||||
@@ -93,7 +93,7 @@
|
||||
</div>
|
||||
|
||||
<div
|
||||
class=" px-2 pb-1 text-xs text-gray-600 bg-white rounded-br-lg flex items-center space-x-1"
|
||||
class=" px-2 pb-1 text-xs text-gray-600 bg-white rounded-br-xl flex items-center space-x-1"
|
||||
>
|
||||
<div>
|
||||
<svg
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
import { chats, config, modelfiles, settings, user } from '$lib/stores';
|
||||
import { tick } from 'svelte';
|
||||
|
||||
import toast from 'svelte-french-toast';
|
||||
import { toast } from 'svelte-sonner';
|
||||
import { getChatList, updateChatById } from '$lib/apis/chats';
|
||||
|
||||
import UserMessage from './Messages/UserMessage.svelte';
|
||||
@@ -281,52 +281,6 @@
|
||||
{showNextMessage}
|
||||
{copyToClipboard}
|
||||
/>
|
||||
|
||||
{#if messages.length - 1 === messageIdx && processing !== ''}
|
||||
<div class="flex my-2.5 ml-12 items-center w-fit space-x-2.5">
|
||||
<div class=" dark:text-blue-100">
|
||||
<svg
|
||||
class=" w-4 h-4 translate-y-[0.5px]"
|
||||
fill="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
><style>
|
||||
.spinner_qM83 {
|
||||
animation: spinner_8HQG 1.05s infinite;
|
||||
}
|
||||
.spinner_oXPr {
|
||||
animation-delay: 0.1s;
|
||||
}
|
||||
.spinner_ZTLf {
|
||||
animation-delay: 0.2s;
|
||||
}
|
||||
@keyframes spinner_8HQG {
|
||||
0%,
|
||||
57.14% {
|
||||
animation-timing-function: cubic-bezier(0.33, 0.66, 0.66, 1);
|
||||
transform: translate(0);
|
||||
}
|
||||
28.57% {
|
||||
animation-timing-function: cubic-bezier(0.33, 0, 0.66, 0.33);
|
||||
transform: translateY(-6px);
|
||||
}
|
||||
100% {
|
||||
transform: translate(0);
|
||||
}
|
||||
}
|
||||
</style><circle class="spinner_qM83" cx="4" cy="12" r="2.5" /><circle
|
||||
class="spinner_qM83 spinner_oXPr"
|
||||
cx="12"
|
||||
cy="12"
|
||||
r="2.5"
|
||||
/><circle class="spinner_qM83 spinner_ZTLf" cx="20" cy="12" r="2.5" /></svg
|
||||
>
|
||||
</div>
|
||||
<div class=" text-sm font-medium">
|
||||
{processing}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
{:else}
|
||||
<ResponseMessage
|
||||
{message}
|
||||
|
||||
@@ -48,7 +48,7 @@
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
<div class=" mt-2 text-2xl text-gray-800 dark:text-gray-100 font-semibold">
|
||||
<div class=" mt-2 mb-5 text-2xl text-gray-800 dark:text-gray-100 font-semibold">
|
||||
{#if modelfile}
|
||||
<span class=" capitalize">
|
||||
{modelfile.title}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import toast from 'svelte-french-toast';
|
||||
import { toast } from 'svelte-sonner';
|
||||
import dayjs from 'dayjs';
|
||||
import { marked } from 'marked';
|
||||
import tippy from 'tippy.js';
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import { setDefaultModels } from '$lib/apis/configs';
|
||||
import { models, showSettings, settings, user } from '$lib/stores';
|
||||
import { onMount, tick } from 'svelte';
|
||||
import toast from 'svelte-french-toast';
|
||||
import { toast } from 'svelte-sonner';
|
||||
|
||||
export let selectedModels = [''];
|
||||
export let disabled = false;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import toast from 'svelte-french-toast';
|
||||
import { toast } from 'svelte-sonner';
|
||||
import { onMount } from 'svelte';
|
||||
|
||||
import { user } from '$lib/stores';
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import toast from 'svelte-french-toast';
|
||||
import { toast } from 'svelte-sonner';
|
||||
import { updateUserPassword } from '$lib/apis/auths';
|
||||
|
||||
let show = false;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { createEventDispatcher, onMount } from 'svelte';
|
||||
import toast from 'svelte-french-toast';
|
||||
import { toast } from 'svelte-sonner';
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
export let saveSettings: Function;
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
import { getImportOrigin, convertOpenAIChats } from '$lib/utils';
|
||||
import { onMount } from 'svelte';
|
||||
import { goto } from '$app/navigation';
|
||||
import toast from 'svelte-french-toast';
|
||||
import { toast } from 'svelte-sonner';
|
||||
|
||||
export let saveSettings: Function;
|
||||
// Chats
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
import { getOllamaAPIUrl, getOllamaVersion, updateOllamaAPIUrl } from '$lib/apis/ollama';
|
||||
import { getOpenAIKey, getOpenAIUrl, updateOpenAIKey, updateOpenAIUrl } from '$lib/apis/openai';
|
||||
import toast from 'svelte-french-toast';
|
||||
import { toast } from 'svelte-sonner';
|
||||
|
||||
export let getModels: Function;
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import toast from 'svelte-french-toast';
|
||||
import { toast } from 'svelte-sonner';
|
||||
import { createEventDispatcher, onMount } from 'svelte';
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import toast from 'svelte-french-toast';
|
||||
import { toast } from 'svelte-sonner';
|
||||
|
||||
import { createEventDispatcher, onMount } from 'svelte';
|
||||
import { config, user } from '$lib/stores';
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
import { setDefaultPromptSuggestions } from '$lib/apis/configs';
|
||||
import { config, models, user } from '$lib/stores';
|
||||
import { createEventDispatcher, onMount } from 'svelte';
|
||||
import toast from 'svelte-french-toast';
|
||||
import { toast } from 'svelte-sonner';
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
export let saveSettings: Function;
|
||||
@@ -191,10 +191,12 @@
|
||||
placeholder="Select a model"
|
||||
>
|
||||
<option value="" selected>Current Model</option>
|
||||
{#each $models.filter((m) => m.size != null) as model}
|
||||
<option value={model.name} class="bg-gray-100 dark:bg-gray-700"
|
||||
>{model.name + ' (' + (model.size / 1024 ** 3).toFixed(1) + ' GB)'}</option
|
||||
>
|
||||
{#each $models as model}
|
||||
{#if model.size != null}
|
||||
<option value={model.name} class="bg-gray-100 dark:bg-gray-700">
|
||||
{model.name + ' (' + (model.size / 1024 ** 3).toFixed(1) + ' GB)'}
|
||||
</option>
|
||||
{/if}
|
||||
{/each}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import queue from 'async/queue';
|
||||
import toast from 'svelte-french-toast';
|
||||
import { toast } from 'svelte-sonner';
|
||||
|
||||
import { createModel, deleteModel, getOllamaVersion, pullModel } from '$lib/apis/ollama';
|
||||
import { WEBUI_API_BASE_URL, WEBUI_BASE_URL } from '$lib/constants';
|
||||
@@ -432,7 +432,7 @@
|
||||
|
||||
<div class="mt-2 mb-1 text-xs text-gray-400 dark:text-gray-500">
|
||||
To access the available model names for downloading, <a
|
||||
class=" text-gray-500 dark:text-gray-300 font-medium"
|
||||
class=" text-gray-500 dark:text-gray-300 font-medium underline"
|
||||
href="https://ollama.com/library"
|
||||
target="_blank">click here.</a
|
||||
>
|
||||
@@ -580,7 +580,7 @@
|
||||
type="url"
|
||||
required
|
||||
bind:value={modelFileUrl}
|
||||
placeholder="Type HuggingFace Resolve (Download) URL"
|
||||
placeholder="Type Hugging Face Resolve (Download) URL"
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
@@ -651,7 +651,7 @@
|
||||
{/if}
|
||||
<div class=" mt-1 text-xs text-gray-400 dark:text-gray-500">
|
||||
To access the GGUF models available for downloading, <a
|
||||
class=" text-gray-500 dark:text-gray-300 font-medium"
|
||||
class=" text-gray-500 dark:text-gray-300 font-medium underline"
|
||||
href="https://huggingface.co/models?search=gguf"
|
||||
target="_blank">click here.</a
|
||||
>
|
||||
@@ -790,7 +790,7 @@
|
||||
<div class="mb-2 text-xs text-gray-400 dark:text-gray-500">
|
||||
Not sure what to add?
|
||||
<a
|
||||
class=" text-gray-300 font-medium"
|
||||
class=" text-gray-300 font-medium underline"
|
||||
href="https://litellm.vercel.app/docs/proxy/configs#quick-start"
|
||||
target="_blank"
|
||||
>
|
||||
@@ -913,7 +913,7 @@
|
||||
<div class="mt-2 text-xs text-gray-400 dark:text-gray-500">
|
||||
Not sure what to add?
|
||||
<a
|
||||
class=" text-gray-300 font-medium"
|
||||
class=" text-gray-300 font-medium underline"
|
||||
href="https://litellm.vercel.app/docs/proxy/configs#quick-start"
|
||||
target="_blank"
|
||||
>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import toast from 'svelte-french-toast';
|
||||
import { toast } from 'svelte-sonner';
|
||||
import { models, settings, user } from '$lib/stores';
|
||||
|
||||
import { getOllamaModels } from '$lib/apis/ollama';
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
<script lang="ts">
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
export let state = 'unchecked';
|
||||
export let indeterminate = false;
|
||||
|
||||
let _state = 'unchecked';
|
||||
|
||||
$: _state = state;
|
||||
</script>
|
||||
|
||||
<button
|
||||
class=" outline -outline-offset-1 outline-[1.5px] outline-gray-200 dark:outline-gray-600 {state !==
|
||||
'unchecked'
|
||||
? 'bg-black outline-black '
|
||||
: 'hover:outline-gray-500 hover:bg-gray-50 dark:hover:bg-gray-800'} text-white transition-all rounded inline-block w-3.5 h-3.5 relative"
|
||||
on:click={() => {
|
||||
if (_state === 'unchecked') {
|
||||
_state = 'checked';
|
||||
dispatch('change', _state);
|
||||
} else if (_state === 'checked') {
|
||||
_state = 'unchecked';
|
||||
if (!indeterminate) {
|
||||
dispatch('change', _state);
|
||||
}
|
||||
} else if (indeterminate) {
|
||||
_state = 'checked';
|
||||
dispatch('change', _state);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div class="top-0 left-0 absolute w-full flex justify-center">
|
||||
{#if _state === 'checked'}
|
||||
<svg
|
||||
class="w-3.5 h-3.5"
|
||||
aria-hidden="true"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
stroke="currentColor"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="3"
|
||||
d="m5 12 4.7 4.5 9.3-9"
|
||||
/>
|
||||
</svg>
|
||||
{:else if indeterminate}
|
||||
<svg
|
||||
class="w-3 h-3.5 text-gray-800 dark:text-white"
|
||||
aria-hidden="true"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
stroke="currentColor"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="3"
|
||||
d="M5 12h14"
|
||||
/>
|
||||
</svg>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<!-- {checked} -->
|
||||
</button>
|
||||
@@ -41,7 +41,7 @@
|
||||
}}
|
||||
>
|
||||
<div
|
||||
class=" modal-content m-auto rounded-xl max-w-full {sizeToWidth(
|
||||
class=" modal-content m-auto rounded-2xl max-w-full {sizeToWidth(
|
||||
size
|
||||
)} mx-2 bg-gray-50 dark:bg-gray-900 shadow-3xl"
|
||||
in:fade={{ duration: 10 }}
|
||||
|
||||
@@ -0,0 +1,188 @@
|
||||
<script lang="ts">
|
||||
import { toast } from 'svelte-sonner';
|
||||
import dayjs from 'dayjs';
|
||||
import { onMount } from 'svelte';
|
||||
|
||||
import { createNewDoc, getDocs, tagDocByName, updateDocByName } from '$lib/apis/documents';
|
||||
import Modal from '../common/Modal.svelte';
|
||||
import { documents } from '$lib/stores';
|
||||
import TagInput from '../common/Tags/TagInput.svelte';
|
||||
import Tags from '../common/Tags.svelte';
|
||||
import { addTagById } from '$lib/apis/chats';
|
||||
import { uploadDocToVectorDB } from '$lib/apis/rag';
|
||||
import { transformFileName } from '$lib/utils';
|
||||
import { SUPPORTED_FILE_EXTENSIONS, SUPPORTED_FILE_TYPE } from '$lib/constants';
|
||||
|
||||
export let show = false;
|
||||
export let selectedDoc;
|
||||
|
||||
let inputFiles;
|
||||
let tags = [];
|
||||
|
||||
let doc = {
|
||||
name: '',
|
||||
title: '',
|
||||
content: null
|
||||
};
|
||||
|
||||
const uploadDoc = async (file) => {
|
||||
const res = await uploadDocToVectorDB(localStorage.token, '', file).catch((error) => {
|
||||
toast.error(error);
|
||||
return null;
|
||||
});
|
||||
|
||||
if (res) {
|
||||
await createNewDoc(
|
||||
localStorage.token,
|
||||
res.collection_name,
|
||||
res.filename,
|
||||
transformFileName(res.filename),
|
||||
res.filename,
|
||||
tags.length > 0
|
||||
? {
|
||||
tags: tags
|
||||
}
|
||||
: null
|
||||
).catch((error) => {
|
||||
toast.error(error);
|
||||
return null;
|
||||
});
|
||||
await documents.set(await getDocs(localStorage.token));
|
||||
}
|
||||
};
|
||||
|
||||
const submitHandler = async () => {
|
||||
if (inputFiles && inputFiles.length > 0) {
|
||||
for (const file of inputFiles) {
|
||||
console.log(file, file.name.split('.').at(-1));
|
||||
if (
|
||||
SUPPORTED_FILE_TYPE.includes(file['type']) ||
|
||||
SUPPORTED_FILE_EXTENSIONS.includes(file.name.split('.').at(-1))
|
||||
) {
|
||||
uploadDoc(file);
|
||||
} else {
|
||||
toast.error(
|
||||
`Unknown File Type '${file['type']}', but accepting and treating as plain text`
|
||||
);
|
||||
uploadDoc(file);
|
||||
}
|
||||
}
|
||||
|
||||
inputFiles = null;
|
||||
document.getElementById('upload-doc-input').value = '';
|
||||
} else {
|
||||
toast.error(`File not found.`);
|
||||
}
|
||||
|
||||
show = false;
|
||||
documents.set(await getDocs(localStorage.token));
|
||||
};
|
||||
|
||||
const addTagHandler = async (tagName) => {
|
||||
if (!tags.find((tag) => tag.name === tagName) && tagName !== '') {
|
||||
tags = [...tags, { name: tagName }];
|
||||
} else {
|
||||
console.log('tag already exists');
|
||||
}
|
||||
};
|
||||
|
||||
const deleteTagHandler = async (tagName) => {
|
||||
tags = tags.filter((tag) => tag.name !== tagName);
|
||||
};
|
||||
|
||||
onMount(() => {});
|
||||
</script>
|
||||
|
||||
<Modal size="sm" bind:show>
|
||||
<div>
|
||||
<div class=" flex justify-between dark:text-gray-300 px-5 py-4">
|
||||
<div class=" text-lg font-medium self-center">Add Docs</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 px-5 py-4 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">
|
||||
<form
|
||||
class="flex flex-col w-full"
|
||||
on:submit|preventDefault={() => {
|
||||
submitHandler();
|
||||
}}
|
||||
>
|
||||
<div class="mb-3 w-full">
|
||||
<input id="upload-doc-input" hidden bind:files={inputFiles} type="file" multiple />
|
||||
|
||||
<button
|
||||
class="w-full text-sm font-medium py-3 bg-gray-850 hover:bg-gray-800 text-center rounded-xl"
|
||||
type="button"
|
||||
on:click={() => {
|
||||
document.getElementById('upload-doc-input')?.click();
|
||||
}}
|
||||
>
|
||||
{#if inputFiles}
|
||||
{inputFiles.length > 0 ? `${inputFiles.length}` : ''} document(s) selected.
|
||||
{:else}
|
||||
Click here to select documents.
|
||||
{/if}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class=" flex flex-col space-y-1.5">
|
||||
<div class="flex flex-col w-full">
|
||||
<div class=" mb-1.5 text-xs text-gray-500">Tags</div>
|
||||
|
||||
<Tags {tags} addTag={addTagHandler} deleteTag={deleteTagHandler} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-end pt-5 text-sm font-medium">
|
||||
<button
|
||||
class=" px-4 py-2 bg-emerald-600 hover:bg-emerald-700 text-gray-100 transition rounded"
|
||||
type="submit"
|
||||
>
|
||||
Save
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</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>
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import toast from 'svelte-french-toast';
|
||||
import { toast } from 'svelte-sonner';
|
||||
import dayjs from 'dayjs';
|
||||
import { onMount } from 'svelte';
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
} from '$lib/apis/rag';
|
||||
import { documents } from '$lib/stores';
|
||||
import { onMount } from 'svelte';
|
||||
import toast from 'svelte-french-toast';
|
||||
import { toast } from 'svelte-sonner';
|
||||
|
||||
export let saveHandler: Function;
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import toast from 'svelte-french-toast';
|
||||
import { toast } from 'svelte-sonner';
|
||||
import fileSaver from 'file-saver';
|
||||
const { saveAs } = fileSaver;
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
updateChatById,
|
||||
getAllChatTags
|
||||
} from '$lib/apis/chats';
|
||||
import toast from 'svelte-french-toast';
|
||||
import { toast } from 'svelte-sonner';
|
||||
import { slide } from 'svelte/transition';
|
||||
import { WEBUI_BASE_URL } from '$lib/constants';
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
let chatTitle = '';
|
||||
|
||||
let showDropdown = false;
|
||||
let isEditing = false;
|
||||
|
||||
onMount(async () => {
|
||||
if (window.innerWidth > 1280) {
|
||||
@@ -100,18 +101,17 @@
|
||||
? ''
|
||||
: 'invisible'}"
|
||||
>
|
||||
<div class="px-2.5 flex justify-center space-x-2">
|
||||
<button
|
||||
<div class="px-2 flex justify-center space-x-2">
|
||||
<a
|
||||
id="sidebar-new-chat-button"
|
||||
class="flex-grow flex justify-between rounded-md px-3 py-2 hover:bg-gray-900 transition"
|
||||
class="flex-grow flex justify-between rounded-xl px-3.5 py-2 hover:bg-gray-900 transition"
|
||||
href="/"
|
||||
on:click={async () => {
|
||||
goto('/');
|
||||
|
||||
await goto('/');
|
||||
const newChatButton = document.getElementById('new-chat-button');
|
||||
|
||||
if (newChatButton) {
|
||||
newChatButton.click();
|
||||
}
|
||||
setTimeout(() => {
|
||||
newChatButton?.click();
|
||||
}, 0);
|
||||
}}
|
||||
>
|
||||
<div class="flex self-center">
|
||||
@@ -141,13 +141,13 @@
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</button>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
{#if $user?.role === 'admin'}
|
||||
<div class="px-2.5 flex justify-center mt-0.5">
|
||||
<div class="px-2 flex justify-center mt-0.5">
|
||||
<a
|
||||
class="flex-grow flex space-x-3 rounded-md px-3 py-2 hover:bg-gray-900 transition"
|
||||
class="flex-grow flex space-x-3 rounded-xl px-3.5 py-2 hover:bg-gray-900 transition"
|
||||
href="/modelfiles"
|
||||
>
|
||||
<div class="self-center">
|
||||
@@ -173,9 +173,9 @@
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="px-2.5 flex justify-center">
|
||||
<div class="px-2 flex justify-center">
|
||||
<a
|
||||
class="flex-grow flex space-x-3 rounded-md px-3 py-2 hover:bg-gray-900 transition"
|
||||
class="flex-grow flex space-x-3 rounded-xl px-3.5 py-2 hover:bg-gray-900 transition"
|
||||
href="/prompts"
|
||||
>
|
||||
<div class="self-center">
|
||||
@@ -201,9 +201,9 @@
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="px-2.5 flex justify-center mb-1">
|
||||
<div class="px-2 flex justify-center mb-1">
|
||||
<a
|
||||
class="flex-grow flex space-x-3 rounded-md px-3 py-2 hover:bg-gray-900 transition"
|
||||
class="flex-grow flex space-x-3 rounded-xl px-3.5 py-2 hover:bg-gray-900 transition"
|
||||
href="/documents"
|
||||
>
|
||||
<div class="self-center">
|
||||
@@ -272,9 +272,9 @@
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div class="px-2.5 mt-1 mb-2 flex justify-center space-x-2">
|
||||
<div class="px-2 mt-1 mb-2 flex justify-center space-x-2">
|
||||
<div class="flex w-full" id="chat-search">
|
||||
<div class="self-center pl-3 py-2 rounded-l bg-gray-950">
|
||||
<div class="self-center pl-3 py-2 rounded-l-xl bg-gray-950">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 20 20"
|
||||
@@ -290,7 +290,7 @@
|
||||
</div>
|
||||
|
||||
<input
|
||||
class="w-full rounded-r py-1.5 pl-2.5 pr-4 text-sm text-gray-300 bg-gray-950 outline-none"
|
||||
class="w-full rounded-r-xl py-1.5 pl-2.5 pr-4 text-sm text-gray-300 bg-gray-950 outline-none"
|
||||
placeholder="Search"
|
||||
bind:value={search}
|
||||
on:focus={() => {
|
||||
@@ -345,7 +345,7 @@
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div class="pl-2.5 my-2 flex-1 flex flex-col space-y-1 overflow-y-auto">
|
||||
<div class="pl-2 my-2 flex-1 flex flex-col space-y-1 overflow-y-auto">
|
||||
{#each $chats.filter((chat) => {
|
||||
if (search === '') {
|
||||
return true;
|
||||
@@ -366,27 +366,34 @@
|
||||
}
|
||||
}) as chat, i}
|
||||
<div class=" w-full pr-2 relative">
|
||||
<a
|
||||
class=" w-full flex justify-between rounded-md px-3 py-2 hover:bg-gray-900 {chat.id ===
|
||||
$chatId
|
||||
? 'bg-gray-900'
|
||||
: ''} transition whitespace-nowrap text-ellipsis"
|
||||
href="/c/{chat.id}"
|
||||
>
|
||||
<div class=" flex self-center flex-1 w-full">
|
||||
<div
|
||||
class=" text-left self-center overflow-hidden {chat.id === $chatId
|
||||
? 'w-[160px]'
|
||||
: 'w-full'} "
|
||||
>
|
||||
{#if chatTitleEditId === chat.id}
|
||||
<input bind:value={chatTitle} class=" bg-transparent w-full" />
|
||||
{:else}
|
||||
{chat.title}
|
||||
{/if}
|
||||
</div>
|
||||
{#if chatTitleEditId === chat.id}
|
||||
<div
|
||||
class=" w-full flex justify-between rounded-xl px-3 py-2 hover:bg-gray-900 {chat.id ===
|
||||
$chatId
|
||||
? 'bg-gray-900'
|
||||
: ''} transition whitespace-nowrap text-ellipsis"
|
||||
>
|
||||
<input bind:value={chatTitle} class=" bg-transparent w-full outline-none mr-10" />
|
||||
</div>
|
||||
</a>
|
||||
{:else}
|
||||
<a
|
||||
class=" w-full flex justify-between rounded-xl px-3 py-2 hover:bg-gray-900 {chat.id ===
|
||||
$chatId
|
||||
? 'bg-gray-900'
|
||||
: ''} transition whitespace-nowrap text-ellipsis"
|
||||
href="/c/{chat.id}"
|
||||
>
|
||||
<div class=" flex self-center flex-1 w-full">
|
||||
<div
|
||||
class=" text-left self-center overflow-hidden {chat.id === $chatId
|
||||
? 'w-[160px]'
|
||||
: 'w-full'} "
|
||||
>
|
||||
{chat.title}
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
{/if}
|
||||
|
||||
{#if chat.id === $chatId}
|
||||
<div class=" absolute right-[22px] top-[10px]">
|
||||
@@ -485,7 +492,6 @@
|
||||
on:click={() => {
|
||||
chatTitle = chat.title;
|
||||
chatTitleEditId = chat.id;
|
||||
// editChatTitle(chat.id, 'a');
|
||||
}}
|
||||
>
|
||||
<svg
|
||||
@@ -534,12 +540,12 @@
|
||||
</div>
|
||||
|
||||
<div class="px-2.5">
|
||||
<hr class=" border-gray-900 mb-1 w-full" />
|
||||
<!-- <hr class=" border-gray-900 mb-1 w-full" /> -->
|
||||
|
||||
<div class="flex flex-col">
|
||||
{#if $user !== undefined}
|
||||
<button
|
||||
class=" flex rounded-md py-3 px-3.5 w-full hover:bg-gray-900 transition"
|
||||
class=" flex rounded-xl py-3 px-3.5 w-full hover:bg-gray-900 transition"
|
||||
on:click={() => {
|
||||
showDropdown = !showDropdown;
|
||||
}}
|
||||
@@ -557,7 +563,7 @@
|
||||
{#if showDropdown}
|
||||
<div
|
||||
id="dropdownDots"
|
||||
class="absolute z-40 bottom-[70px] 4.5rem rounded-lg shadow w-[240px] bg-gray-900"
|
||||
class="absolute z-40 bottom-[70px] 4.5rem rounded-xl shadow w-[240px] bg-gray-900"
|
||||
in:slide={{ duration: 150 }}
|
||||
>
|
||||
<div class="py-2 w-full">
|
||||
@@ -656,36 +662,6 @@
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
{:else}
|
||||
<button
|
||||
class=" flex rounded-md py-3 px-3.5 w-full hover:bg-gray-900 transition"
|
||||
on:click={async () => {
|
||||
await showSettings.set(true);
|
||||
}}
|
||||
>
|
||||
<div class=" self-center mr-3">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
stroke="currentColor"
|
||||
class="w-5 h-5"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="M10.343 3.94c.09-.542.56-.94 1.11-.94h1.093c.55 0 1.02.398 1.11.94l.149.894c.07.424.384.764.78.93.398.164.855.142 1.205-.108l.737-.527a1.125 1.125 0 011.45.12l.773.774c.39.389.44 1.002.12 1.45l-.527.737c-.25.35-.272.806-.107 1.204.165.397.505.71.93.78l.893.15c.543.09.94.56.94 1.109v1.094c0 .55-.397 1.02-.94 1.11l-.893.149c-.425.07-.765.383-.93.78-.165.398-.143.854.107 1.204l.527.738c.32.447.269 1.06-.12 1.45l-.774.773a1.125 1.125 0 01-1.449.12l-.738-.527c-.35-.25-.806-.272-1.203-.107-.397.165-.71.505-.781.929l-.149.894c-.09.542-.56.94-1.11.94h-1.094c-.55 0-1.019-.398-1.11-.94l-.148-.894c-.071-.424-.384-.764-.781-.93-.398-.164-.854-.142-1.204.108l-.738.527c-.447.32-1.06.269-1.45-.12l-.773-.774a1.125 1.125 0 01-.12-1.45l.527-.737c.25-.35.273-.806.108-1.204-.165-.397-.505-.71-.93-.78l-.894-.15c-.542-.09-.94-.56-.94-1.109v-1.094c0-.55.398-1.02.94-1.11l.894-.149c.424-.07.765-.383.93-.78.165-.398.143-.854-.107-1.204l-.527-.738a1.125 1.125 0 01.12-1.45l.773-.773a1.125 1.125 0 011.45-.12l.737.527c.35.25.807.272 1.204.107.397-.165.71-.505.78-.929l.15-.894z"
|
||||
/>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<div class=" self-center font-medium">Settings</div>
|
||||
</button>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import toast from 'svelte-french-toast';
|
||||
import { toast } from 'svelte-sonner';
|
||||
import { openDB, deleteDB } from 'idb';
|
||||
import fileSaver from 'file-saver';
|
||||
const { saveAs } = fileSaver;
|
||||
@@ -63,9 +63,6 @@
|
||||
.filter((models) => models)
|
||||
.reduce((a, e, i, arr) => a.concat(e, ...(i < arr.length - 1 ? [{ name: 'hr' }] : [])), []);
|
||||
|
||||
// models.push(...(ollamaModels ? [{ name: 'hr' }, ...ollamaModels] : []));
|
||||
// models.push(...(openAIModels ? [{ name: 'hr' }, ...openAIModels] : []));
|
||||
// models.push(...(liteLLMModels ? [{ name: 'hr' }, ...liteLLMModels] : []));
|
||||
return models;
|
||||
};
|
||||
|
||||
@@ -110,6 +107,10 @@
|
||||
}
|
||||
|
||||
console.log();
|
||||
|
||||
await models.set(await getModels());
|
||||
await tick();
|
||||
|
||||
await settings.set(JSON.parse(localStorage.getItem('settings') ?? '{}'));
|
||||
|
||||
await modelfiles.set(await getModelfiles(localStorage.token));
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import toast from 'svelte-french-toast';
|
||||
import { toast } from 'svelte-sonner';
|
||||
|
||||
import { onMount, tick } from 'svelte';
|
||||
import { goto } from '$app/navigation';
|
||||
@@ -14,6 +14,7 @@
|
||||
chats,
|
||||
chatId,
|
||||
config,
|
||||
WEBUI_NAME,
|
||||
tags as _tags
|
||||
} from '$lib/stores';
|
||||
import { copyToClipboard, splitStream } from '$lib/utils';
|
||||
@@ -38,7 +39,6 @@
|
||||
import { RAGTemplate } from '$lib/utils/rag';
|
||||
import { LITELLM_API_BASE_URL, OPENAI_API_BASE_URL } from '$lib/constants';
|
||||
import { WEBUI_BASE_URL } from '$lib/constants';
|
||||
|
||||
let stopResponseFlag = false;
|
||||
let autoScroll = true;
|
||||
let processing = '';
|
||||
@@ -104,13 +104,8 @@
|
||||
await cancelChatCompletion(localStorage.token, currentRequestId);
|
||||
currentRequestId = null;
|
||||
}
|
||||
|
||||
window.history.replaceState(history.state, '', `/`);
|
||||
|
||||
console.log('initNewChat');
|
||||
|
||||
await chatId.set('');
|
||||
console.log($chatId);
|
||||
|
||||
autoScroll = true;
|
||||
|
||||
@@ -121,8 +116,6 @@
|
||||
currentId: null
|
||||
};
|
||||
|
||||
console.log($config);
|
||||
|
||||
if ($page.url.searchParams.get('models')) {
|
||||
selectedModels = $page.url.searchParams.get('models')?.split(',');
|
||||
} else if ($settings?.models) {
|
||||
@@ -141,6 +134,9 @@
|
||||
settings.set({
|
||||
..._settings
|
||||
});
|
||||
|
||||
const chatInput = document.getElementById('chat-textarea');
|
||||
setTimeout(() => chatInput?.focus(), 0);
|
||||
};
|
||||
|
||||
const scrollToBottom = () => {
|
||||
@@ -732,25 +728,26 @@
|
||||
responseMessage.done = false;
|
||||
await tick();
|
||||
|
||||
const modelTag = $models.filter((m) => m.name === responseMessage.model).at(0);
|
||||
const model = $models.filter((m) => m.id === responseMessage.model).at(0);
|
||||
|
||||
if (modelTag?.external) {
|
||||
await sendPromptOpenAI(
|
||||
responseMessage.model,
|
||||
history.messages[responseMessage.parentId].content,
|
||||
responseMessage.id,
|
||||
_chatId
|
||||
);
|
||||
} else if (modelTag) {
|
||||
await sendPromptOllama(
|
||||
responseMessage.model,
|
||||
history.messages[responseMessage.parentId].content,
|
||||
responseMessage.id,
|
||||
_chatId
|
||||
);
|
||||
} else {
|
||||
toast.error(`Model ${model} not found`);
|
||||
if (model) {
|
||||
if (model?.external) {
|
||||
await sendPromptOpenAI(
|
||||
model,
|
||||
history.messages[responseMessage.parentId].content,
|
||||
responseMessage.id,
|
||||
_chatId
|
||||
);
|
||||
} else
|
||||
await sendPromptOllama(
|
||||
model,
|
||||
history.messages[responseMessage.parentId].content,
|
||||
responseMessage.id,
|
||||
_chatId
|
||||
);
|
||||
}
|
||||
} else {
|
||||
toast.error(`Model ${modelId} not found`);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -812,6 +809,14 @@
|
||||
};
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>
|
||||
{title
|
||||
? `${title.length > 30 ? `${title.slice(0, 30)}...` : title} | ${$WEBUI_NAME}`
|
||||
: `${$WEBUI_NAME}`}
|
||||
</title>
|
||||
</svelte:head>
|
||||
|
||||
<div class="h-screen max-h-[100dvh] w-full flex flex-col">
|
||||
<Navbar {title} shareEnabled={messages.length > 0} {initNewChat} {tags} {addTag} {deleteTag} />
|
||||
<div class="flex flex-col flex-auto">
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
<script>
|
||||
import { WEBUI_API_BASE_URL } from '$lib/constants';
|
||||
import { config, user } from '$lib/stores';
|
||||
import { WEBUI_NAME, config, user } from '$lib/stores';
|
||||
import { goto } from '$app/navigation';
|
||||
import { onMount } from 'svelte';
|
||||
|
||||
import toast from 'svelte-french-toast';
|
||||
import { toast } from 'svelte-sonner';
|
||||
|
||||
import { updateUserRole, getUsers, deleteUserById } from '$lib/apis/users';
|
||||
import { getSignUpEnabledStatus, toggleSignUpEnabledStatus } from '$lib/apis/auths';
|
||||
@@ -61,6 +61,12 @@
|
||||
});
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>
|
||||
{`Admin Panel | ${$WEBUI_NAME}`}
|
||||
</title>
|
||||
</svelte:head>
|
||||
|
||||
{#key selectedUser}
|
||||
<EditUserModal
|
||||
bind:show={showEditUserModal}
|
||||
@@ -74,13 +80,11 @@
|
||||
|
||||
<SettingsModal bind:show={showSettingsModal} />
|
||||
|
||||
<div
|
||||
class="min-h-screen max-h-[100dvh] w-full flex justify-center dark:text-white bg-white dark:bg-gray-900 font-mona"
|
||||
>
|
||||
<div class="min-h-screen max-h-[100dvh] w-full flex justify-center dark:text-white font-mona">
|
||||
{#if loaded}
|
||||
<div class="overflow-y-auto w-full flex justify-center">
|
||||
<div class="w-full max-w-3xl px-6 md:px-16 flex flex-col">
|
||||
<div class="py-10 w-full">
|
||||
<div class=" flex flex-col justify-between w-full overflow-y-auto">
|
||||
<div class="max-w-2xl mx-auto w-full px-3 md:px-0 my-10">
|
||||
<div class="w-full">
|
||||
<div class=" flex flex-col justify-center">
|
||||
<div class=" flex justify-between items-center">
|
||||
<div class="flex items-center text-2xl font-semibold">
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import toast from 'svelte-french-toast';
|
||||
import { toast } from 'svelte-sonner';
|
||||
|
||||
import { onMount, tick } from 'svelte';
|
||||
import { goto } from '$app/navigation';
|
||||
@@ -14,6 +14,7 @@
|
||||
chats,
|
||||
chatId,
|
||||
config,
|
||||
WEBUI_NAME,
|
||||
tags as _tags
|
||||
} from '$lib/stores';
|
||||
import { copyToClipboard, splitStream, convertMessagesToHistory } from '$lib/utils';
|
||||
@@ -37,7 +38,7 @@
|
||||
import ModelSelector from '$lib/components/chat/ModelSelector.svelte';
|
||||
import Navbar from '$lib/components/layout/Navbar.svelte';
|
||||
import { RAGTemplate } from '$lib/utils/rag';
|
||||
import { WEBUI_BASE_URL } from '$lib/constants';
|
||||
import { LITELLM_API_BASE_URL, OPENAI_API_BASE_URL, WEBUI_BASE_URL } from '$lib/constants';
|
||||
|
||||
let loaded = false;
|
||||
|
||||
@@ -99,6 +100,10 @@
|
||||
if (await loadChat()) {
|
||||
await tick();
|
||||
loaded = true;
|
||||
|
||||
window.setTimeout(() => scrollToBottom(), 0);
|
||||
const chatInput = document.getElementById('chat-textarea');
|
||||
chatInput?.focus();
|
||||
} else {
|
||||
await goto('/');
|
||||
}
|
||||
@@ -238,7 +243,6 @@
|
||||
await sendPrompt(userPrompt, userMessageId);
|
||||
}
|
||||
};
|
||||
|
||||
const sendPrompt = async (prompt, parentId) => {
|
||||
const _chatId = JSON.parse(JSON.stringify($chatId));
|
||||
|
||||
@@ -292,40 +296,41 @@
|
||||
}
|
||||
|
||||
await Promise.all(
|
||||
selectedModels.map(async (model) => {
|
||||
console.log(model);
|
||||
const modelTag = $models.filter((m) => m.name === model).at(0);
|
||||
selectedModels.map(async (modelId) => {
|
||||
const model = $models.filter((m) => m.id === modelId).at(0);
|
||||
|
||||
// Create response message
|
||||
let responseMessageId = uuidv4();
|
||||
let responseMessage = {
|
||||
parentId: parentId,
|
||||
id: responseMessageId,
|
||||
childrenIds: [],
|
||||
role: 'assistant',
|
||||
content: '',
|
||||
model: model,
|
||||
timestamp: Math.floor(Date.now() / 1000) // Unix epoch
|
||||
};
|
||||
if (model) {
|
||||
// Create response message
|
||||
let responseMessageId = uuidv4();
|
||||
let responseMessage = {
|
||||
parentId: parentId,
|
||||
id: responseMessageId,
|
||||
childrenIds: [],
|
||||
role: 'assistant',
|
||||
content: '',
|
||||
model: model.id,
|
||||
timestamp: Math.floor(Date.now() / 1000) // Unix epoch
|
||||
};
|
||||
|
||||
// Add message to history and Set currentId to messageId
|
||||
history.messages[responseMessageId] = responseMessage;
|
||||
history.currentId = responseMessageId;
|
||||
// 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,
|
||||
responseMessageId
|
||||
];
|
||||
}
|
||||
// Append messageId to childrenIds of parent message
|
||||
if (parentId !== null) {
|
||||
history.messages[parentId].childrenIds = [
|
||||
...history.messages[parentId].childrenIds,
|
||||
responseMessageId
|
||||
];
|
||||
}
|
||||
|
||||
if (modelTag?.external) {
|
||||
await sendPromptOpenAI(model, prompt, responseMessageId, _chatId);
|
||||
} else if (modelTag) {
|
||||
await sendPromptOllama(model, prompt, responseMessageId, _chatId);
|
||||
if (model?.external) {
|
||||
await sendPromptOpenAI(model, prompt, responseMessageId, _chatId);
|
||||
} else if (model) {
|
||||
await sendPromptOllama(model, prompt, responseMessageId, _chatId);
|
||||
}
|
||||
} else {
|
||||
toast.error(`Model ${model} not found`);
|
||||
toast.error(`Model ${modelId} not found`);
|
||||
}
|
||||
})
|
||||
);
|
||||
@@ -334,6 +339,7 @@
|
||||
};
|
||||
|
||||
const sendPromptOllama = async (model, userPrompt, responseMessageId, _chatId) => {
|
||||
model = model.id;
|
||||
const responseMessage = history.messages[responseMessageId];
|
||||
|
||||
// Wait until history/message have been updated
|
||||
@@ -543,57 +549,60 @@
|
||||
|
||||
const sendPromptOpenAI = async (model, userPrompt, responseMessageId, _chatId) => {
|
||||
const responseMessage = history.messages[responseMessageId];
|
||||
|
||||
scrollToBottom();
|
||||
|
||||
const res = await generateOpenAIChatCompletion(localStorage.token, {
|
||||
model: model,
|
||||
stream: true,
|
||||
messages: [
|
||||
$settings.system
|
||||
? {
|
||||
role: 'system',
|
||||
content: $settings.system
|
||||
}
|
||||
: undefined,
|
||||
...messages.filter((message) => !message.deleted)
|
||||
]
|
||||
.filter((message) => message)
|
||||
.map((message, idx, arr) => ({
|
||||
role: message.role,
|
||||
...(message.files?.filter((file) => file.type === 'image').length > 0 ?? false
|
||||
const res = await generateOpenAIChatCompletion(
|
||||
localStorage.token,
|
||||
{
|
||||
model: model.id,
|
||||
stream: true,
|
||||
messages: [
|
||||
$settings.system
|
||||
? {
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text:
|
||||
arr.length - 1 !== idx
|
||||
? message.content
|
||||
: message?.raContent ?? message.content
|
||||
},
|
||||
...message.files
|
||||
.filter((file) => file.type === 'image')
|
||||
.map((file) => ({
|
||||
type: 'image_url',
|
||||
image_url: {
|
||||
url: file.url
|
||||
}
|
||||
}))
|
||||
]
|
||||
role: 'system',
|
||||
content: $settings.system
|
||||
}
|
||||
: {
|
||||
content:
|
||||
arr.length - 1 !== idx ? message.content : message?.raContent ?? message.content
|
||||
})
|
||||
})),
|
||||
seed: $settings?.options?.seed ?? undefined,
|
||||
stop: $settings?.options?.stop ?? undefined,
|
||||
temperature: $settings?.options?.temperature ?? undefined,
|
||||
top_p: $settings?.options?.top_p ?? undefined,
|
||||
num_ctx: $settings?.options?.num_ctx ?? undefined,
|
||||
frequency_penalty: $settings?.options?.repeat_penalty ?? undefined,
|
||||
max_tokens: $settings?.options?.num_predict ?? undefined
|
||||
});
|
||||
: undefined,
|
||||
...messages.filter((message) => !message.deleted)
|
||||
]
|
||||
.filter((message) => message)
|
||||
.map((message, idx, arr) => ({
|
||||
role: message.role,
|
||||
...(message.files?.filter((file) => file.type === 'image').length > 0 ?? false
|
||||
? {
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text:
|
||||
arr.length - 1 !== idx
|
||||
? message.content
|
||||
: message?.raContent ?? message.content
|
||||
},
|
||||
...message.files
|
||||
.filter((file) => file.type === 'image')
|
||||
.map((file) => ({
|
||||
type: 'image_url',
|
||||
image_url: {
|
||||
url: file.url
|
||||
}
|
||||
}))
|
||||
]
|
||||
}
|
||||
: {
|
||||
content:
|
||||
arr.length - 1 !== idx ? message.content : message?.raContent ?? message.content
|
||||
})
|
||||
})),
|
||||
seed: $settings?.options?.seed ?? undefined,
|
||||
stop: $settings?.options?.stop ?? undefined,
|
||||
temperature: $settings?.options?.temperature ?? undefined,
|
||||
top_p: $settings?.options?.top_p ?? undefined,
|
||||
num_ctx: $settings?.options?.num_ctx ?? undefined,
|
||||
frequency_penalty: $settings?.options?.repeat_penalty ?? undefined,
|
||||
max_tokens: $settings?.options?.num_predict ?? undefined
|
||||
},
|
||||
model.source === 'litellm' ? `${LITELLM_API_BASE_URL}/v1` : `${OPENAI_API_BASE_URL}`
|
||||
);
|
||||
|
||||
if (res && res.ok) {
|
||||
const reader = res.body
|
||||
@@ -704,7 +713,6 @@
|
||||
await setChatTitle(_chatId, userPrompt);
|
||||
}
|
||||
};
|
||||
|
||||
const stopResponse = () => {
|
||||
stopResponseFlag = true;
|
||||
console.log('stopResponse');
|
||||
@@ -719,25 +727,26 @@
|
||||
responseMessage.done = false;
|
||||
await tick();
|
||||
|
||||
const modelTag = $models.filter((m) => m.name === responseMessage.model).at(0);
|
||||
const model = $models.filter((m) => m.id === responseMessage.model).at(0);
|
||||
|
||||
if (modelTag?.external) {
|
||||
await sendPromptOpenAI(
|
||||
responseMessage.model,
|
||||
history.messages[responseMessage.parentId].content,
|
||||
responseMessage.id,
|
||||
_chatId
|
||||
);
|
||||
} else if (modelTag) {
|
||||
await sendPromptOllama(
|
||||
responseMessage.model,
|
||||
history.messages[responseMessage.parentId].content,
|
||||
responseMessage.id,
|
||||
_chatId
|
||||
);
|
||||
} else {
|
||||
toast.error(`Model ${model} not found`);
|
||||
if (model) {
|
||||
if (model?.external) {
|
||||
await sendPromptOpenAI(
|
||||
model,
|
||||
history.messages[responseMessage.parentId].content,
|
||||
responseMessage.id,
|
||||
_chatId
|
||||
);
|
||||
} else
|
||||
await sendPromptOllama(
|
||||
model,
|
||||
history.messages[responseMessage.parentId].content,
|
||||
responseMessage.id,
|
||||
_chatId
|
||||
);
|
||||
}
|
||||
} else {
|
||||
toast.error(`Model ${modelId} not found`);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -816,6 +825,14 @@
|
||||
});
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>
|
||||
{title
|
||||
? `${title.length > 30 ? `${title.slice(0, 30)}...` : title} | ${$WEBUI_NAME}`
|
||||
: `${$WEBUI_NAME}`}
|
||||
</title>
|
||||
</svelte:head>
|
||||
|
||||
{#if loaded}
|
||||
<div class="min-h-screen max-h-screen w-full flex flex-col">
|
||||
<Navbar
|
||||
|
||||
@@ -1,19 +1,22 @@
|
||||
<script lang="ts">
|
||||
import toast from 'svelte-french-toast';
|
||||
import { toast } from 'svelte-sonner';
|
||||
import fileSaver from 'file-saver';
|
||||
const { saveAs } = fileSaver;
|
||||
|
||||
import { onMount } from 'svelte';
|
||||
import { documents } from '$lib/stores';
|
||||
import { WEBUI_NAME, documents } from '$lib/stores';
|
||||
import { createNewDoc, deleteDocByName, getDocs } from '$lib/apis/documents';
|
||||
|
||||
import { SUPPORTED_FILE_TYPE, SUPPORTED_FILE_EXTENSIONS } from '$lib/constants';
|
||||
import { uploadDocToVectorDB } from '$lib/apis/rag';
|
||||
import { transformFileName } from '$lib/utils';
|
||||
|
||||
import Checkbox from '$lib/components/common/Checkbox.svelte';
|
||||
|
||||
import EditDocModal from '$lib/components/documents/EditDocModal.svelte';
|
||||
import AddFilesPlaceholder from '$lib/components/AddFilesPlaceholder.svelte';
|
||||
import SettingsModal from '$lib/components/documents/SettingsModal.svelte';
|
||||
import AddDocModal from '$lib/components/documents/AddDocModal.svelte';
|
||||
let importFiles = '';
|
||||
|
||||
let inputFiles = '';
|
||||
@@ -22,6 +25,7 @@
|
||||
let tags = [];
|
||||
|
||||
let showSettingsModal = false;
|
||||
let showAddDocModal = false;
|
||||
let showEditDocModal = false;
|
||||
let selectedDoc;
|
||||
let selectedTag = '';
|
||||
@@ -33,6 +37,16 @@
|
||||
await documents.set(await getDocs(localStorage.token));
|
||||
};
|
||||
|
||||
const deleteDocs = async (docs) => {
|
||||
const res = await Promise.all(
|
||||
docs.map(async (doc) => {
|
||||
return await deleteDocByName(localStorage.token, doc.name);
|
||||
})
|
||||
);
|
||||
|
||||
await documents.set(await getDocs(localStorage.token));
|
||||
};
|
||||
|
||||
const uploadDoc = async (file) => {
|
||||
const res = await uploadDocToVectorDB(localStorage.token, '', file).catch((error) => {
|
||||
toast.error(error);
|
||||
@@ -123,8 +137,23 @@
|
||||
dropZone?.removeEventListener('dragleave', onDragLeave);
|
||||
};
|
||||
});
|
||||
|
||||
let filteredDocs;
|
||||
|
||||
$: filteredDocs = $documents.filter(
|
||||
(doc) =>
|
||||
(selectedTag === '' ||
|
||||
(doc?.content?.tags ?? []).map((tag) => tag.name).includes(selectedTag)) &&
|
||||
(query === '' || doc.name.includes(query))
|
||||
);
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>
|
||||
{`Documents | ${$WEBUI_NAME}`}
|
||||
</title>
|
||||
</svelte:head>
|
||||
|
||||
{#if dragged}
|
||||
<div
|
||||
class="fixed w-full h-full flex z-50 touch-none pointer-events-none"
|
||||
@@ -150,36 +179,7 @@
|
||||
<EditDocModal bind:show={showEditDocModal} {selectedDoc} />
|
||||
{/key}
|
||||
|
||||
<input
|
||||
id="upload-doc-input"
|
||||
bind:files={inputFiles}
|
||||
type="file"
|
||||
multiple
|
||||
hidden
|
||||
on:change={async (e) => {
|
||||
if (inputFiles && inputFiles.length > 0) {
|
||||
for (const file of inputFiles) {
|
||||
console.log(file, file.name.split('.').at(-1));
|
||||
if (
|
||||
SUPPORTED_FILE_TYPE.includes(file['type']) ||
|
||||
SUPPORTED_FILE_EXTENSIONS.includes(file.name.split('.').at(-1))
|
||||
) {
|
||||
uploadDoc(file);
|
||||
} else {
|
||||
toast.error(
|
||||
`Unknown File Type '${file['type']}', but accepting and treating as plain text`
|
||||
);
|
||||
uploadDoc(file);
|
||||
}
|
||||
}
|
||||
|
||||
inputFiles = null;
|
||||
e.target.value = '';
|
||||
} else {
|
||||
toast.error(`File not found.`);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<AddDocModal bind:show={showAddDocModal} />
|
||||
|
||||
<SettingsModal bind:show={showSettingsModal} />
|
||||
|
||||
@@ -247,7 +247,7 @@
|
||||
<button
|
||||
class=" px-2 py-2 rounded-xl border border-gray-200 dark:border-gray-600 dark:border-0 hover:bg-gray-100 dark:bg-gray-800 dark:hover:bg-gray-700 transition font-medium text-sm flex items-center space-x-1"
|
||||
on:click={() => {
|
||||
document.getElementById('upload-doc-input')?.click();
|
||||
showAddDocModal = true;
|
||||
}}
|
||||
>
|
||||
<svg
|
||||
@@ -287,38 +287,96 @@
|
||||
|
||||
{#if tags.length > 0}
|
||||
<div class="px-2.5 pt-1 flex gap-1 flex-wrap">
|
||||
<button
|
||||
class="px-2 py-0.5 space-x-1 flex h-fit items-center rounded-full transition bg-gray-50 hover:bg-gray-100 dark:bg-gray-800 dark:text-white"
|
||||
on:click={async () => {
|
||||
selectedTag = '';
|
||||
// await chats.set(await getChatListByTagName(localStorage.token, tag.name));
|
||||
}}
|
||||
>
|
||||
<div class=" text-xs font-medium self-center line-clamp-1">all</div>
|
||||
</button>
|
||||
{#each tags as tag}
|
||||
<div class="ml-0.5 pr-3 my-auto flex items-center">
|
||||
<Checkbox
|
||||
state={filteredDocs.filter((doc) => doc?.selected === 'checked').length ===
|
||||
filteredDocs.length
|
||||
? 'checked'
|
||||
: 'unchecked'}
|
||||
indeterminate={filteredDocs.filter((doc) => doc?.selected === 'checked').length > 0 &&
|
||||
filteredDocs.filter((doc) => doc?.selected === 'checked').length !==
|
||||
filteredDocs.length}
|
||||
on:change={(e) => {
|
||||
if (e.detail === 'checked') {
|
||||
filteredDocs = filteredDocs.map((doc) => ({ ...doc, selected: 'checked' }));
|
||||
} else if (e.detail === 'unchecked') {
|
||||
filteredDocs = filteredDocs.map((doc) => ({ ...doc, selected: 'unchecked' }));
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{#if filteredDocs.filter((doc) => doc?.selected === 'checked').length === 0}
|
||||
<button
|
||||
class="px-2 py-0.5 space-x-1 flex h-fit items-center rounded-full transition bg-gray-50 hover:bg-gray-100 dark:bg-gray-800 dark:text-white"
|
||||
on:click={async () => {
|
||||
selectedTag = tag;
|
||||
selectedTag = '';
|
||||
// await chats.set(await getChatListByTagName(localStorage.token, tag.name));
|
||||
}}
|
||||
>
|
||||
<div class=" text-xs font-medium self-center line-clamp-1">
|
||||
#{tag}
|
||||
</div>
|
||||
<div class=" text-xs font-medium self-center line-clamp-1">all</div>
|
||||
</button>
|
||||
{/each}
|
||||
|
||||
{#each tags as tag}
|
||||
<button
|
||||
class="px-2 py-0.5 space-x-1 flex h-fit items-center rounded-full transition bg-gray-50 hover:bg-gray-100 dark:bg-gray-800 dark:text-white"
|
||||
on:click={async () => {
|
||||
selectedTag = tag;
|
||||
// await chats.set(await getChatListByTagName(localStorage.token, tag.name));
|
||||
}}
|
||||
>
|
||||
<div class=" text-xs font-medium self-center line-clamp-1">
|
||||
#{tag}
|
||||
</div>
|
||||
</button>
|
||||
{/each}
|
||||
{:else}
|
||||
<div class="flex-1 flex w-full justify-between items-center">
|
||||
<div class="text-xs font-medium py-0.5 self-center mr-1">
|
||||
{filteredDocs.filter((doc) => doc?.selected === 'checked').length} Selected
|
||||
</div>
|
||||
|
||||
<div class="flex gap-1">
|
||||
<!-- <button
|
||||
class="px-2 py-0.5 space-x-1 flex h-fit items-center rounded-full transition bg-gray-50 hover:bg-gray-100 dark:bg-gray-800 dark:text-white"
|
||||
on:click={async () => {
|
||||
selectedTag = '';
|
||||
// await chats.set(await getChatListByTagName(localStorage.token, tag.name));
|
||||
}}
|
||||
>
|
||||
<div class=" text-xs font-medium self-center line-clamp-1">add tags</div>
|
||||
</button> -->
|
||||
|
||||
<button
|
||||
class="px-2 py-0.5 space-x-1 flex h-fit items-center rounded-full transition bg-gray-50 hover:bg-gray-100 dark:bg-gray-800 dark:text-white"
|
||||
on:click={async () => {
|
||||
deleteDocs(filteredDocs.filter((doc) => doc.selected === 'checked'));
|
||||
// await chats.set(await getChatListByTagName(localStorage.token, tag.name));
|
||||
}}
|
||||
>
|
||||
<div class=" text-xs font-medium self-center line-clamp-1">delete</div>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div class="my-3 mb-5">
|
||||
{#each $documents.filter((doc) => (selectedTag === '' || (doc?.content?.tags ?? [])
|
||||
.map((tag) => tag.name)
|
||||
.includes(selectedTag)) && (query === '' || doc.name.includes(query))) as doc}
|
||||
<div
|
||||
class=" flex space-x-4 cursor-pointer w-full px-3 py-2 dark:hover:bg-white/5 hover:bg-black/5 rounded-xl"
|
||||
{#each filteredDocs as doc}
|
||||
<button
|
||||
class=" flex space-x-4 cursor-pointer text-left w-full px-3 py-2 dark:hover:bg-white/5 hover:bg-black/5 rounded-xl"
|
||||
on:click={() => {
|
||||
if (doc?.selected === 'checked') {
|
||||
doc.selected = 'unchecked';
|
||||
} else {
|
||||
doc.selected = 'checked';
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div class="my-auto flex items-center">
|
||||
<Checkbox state={doc?.selected ?? 'unchecked'} />
|
||||
</div>
|
||||
<div class=" flex flex-1 space-x-4 cursor-pointer w-full">
|
||||
<div class=" flex items-center space-x-3">
|
||||
<div class="p-2.5 bg-red-400 text-white rounded-lg">
|
||||
@@ -387,9 +445,10 @@
|
||||
</div>
|
||||
<div class="flex flex-row space-x-1 self-center">
|
||||
<button
|
||||
class="self-center w-fit text-sm px-2 py-2 dark:text-gray-300 dark:hover:text-white hover:bg-black/5 dark:hover:bg-white/5 rounded-xl"
|
||||
class="self-center w-fit text-sm z-20 px-2 py-2 dark:text-gray-300 dark:hover:text-white hover:bg-black/5 dark:hover:bg-white/5 rounded-xl"
|
||||
type="button"
|
||||
on:click={async () => {
|
||||
on:click={async (e) => {
|
||||
e.stopPropagation();
|
||||
showEditDocModal = !showEditDocModal;
|
||||
selectedDoc = doc;
|
||||
}}
|
||||
@@ -435,7 +494,9 @@
|
||||
<button
|
||||
class="self-center w-fit text-sm px-2 py-2 dark:text-gray-300 dark:hover:text-white hover:bg-black/5 dark:hover:bg-white/5 rounded-xl"
|
||||
type="button"
|
||||
on:click={() => {
|
||||
on:click={(e) => {
|
||||
e.stopPropagation();
|
||||
|
||||
deleteDoc(doc.name);
|
||||
}}
|
||||
>
|
||||
@@ -455,7 +516,7 @@
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
<script lang="ts">
|
||||
import toast from 'svelte-french-toast';
|
||||
import { toast } from 'svelte-sonner';
|
||||
import fileSaver from 'file-saver';
|
||||
const { saveAs } = fileSaver;
|
||||
|
||||
import { onMount } from 'svelte';
|
||||
|
||||
import { modelfiles, settings, user } from '$lib/stores';
|
||||
import { WEBUI_NAME, modelfiles, settings, user } from '$lib/stores';
|
||||
import { createModel, deleteModel } from '$lib/apis/ollama';
|
||||
import {
|
||||
createNewModelfile,
|
||||
@@ -69,6 +69,12 @@
|
||||
});
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>
|
||||
{`Modelfiles | ${$WEBUI_NAME}`}
|
||||
</title>
|
||||
</svelte:head>
|
||||
|
||||
<div class="min-h-screen max-h-[100dvh] w-full flex justify-center dark:text-white">
|
||||
<div class="flex flex-col justify-between w-full overflow-y-auto">
|
||||
<div class="max-w-2xl mx-auto w-full px-3 md:px-0 my-10">
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script>
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import { toast } from 'svelte-french-toast';
|
||||
import { toast } from 'svelte-sonner';
|
||||
import { goto } from '$app/navigation';
|
||||
import { settings, user, config, modelfiles, models } from '$lib/stores';
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script>
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import { toast } from 'svelte-french-toast';
|
||||
import { toast } from 'svelte-sonner';
|
||||
import { goto } from '$app/navigation';
|
||||
|
||||
import { onMount } from 'svelte';
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
<script lang="ts">
|
||||
import toast from 'svelte-french-toast';
|
||||
import { toast } from 'svelte-sonner';
|
||||
import fileSaver from 'file-saver';
|
||||
const { saveAs } = fileSaver;
|
||||
|
||||
import { onMount } from 'svelte';
|
||||
import { prompts } from '$lib/stores';
|
||||
import { WEBUI_NAME, prompts } from '$lib/stores';
|
||||
import { createNewPrompt, deletePromptByCommand, getPrompts } from '$lib/apis/prompts';
|
||||
import { error } from '@sveltejs/kit';
|
||||
import { goto } from '$app/navigation';
|
||||
@@ -36,6 +36,12 @@
|
||||
};
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>
|
||||
{`Prompts | ${$WEBUI_NAME}`}
|
||||
</title>
|
||||
</svelte:head>
|
||||
|
||||
<div class="min-h-screen max-h-[100dvh] w-full flex justify-center dark:text-white">
|
||||
<div class="flex flex-col justify-between w-full overflow-y-auto">
|
||||
<div class="max-w-2xl mx-auto w-full px-3 md:px-0 my-10">
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script>
|
||||
import toast from 'svelte-french-toast';
|
||||
import { toast } from 'svelte-sonner';
|
||||
|
||||
import { goto } from '$app/navigation';
|
||||
import { prompts } from '$lib/stores';
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script>
|
||||
import toast from 'svelte-french-toast';
|
||||
import { toast } from 'svelte-sonner';
|
||||
|
||||
import { goto } from '$app/navigation';
|
||||
import { prompts } from '$lib/stores';
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import { onMount, tick } from 'svelte';
|
||||
import { config, user, theme, WEBUI_NAME } from '$lib/stores';
|
||||
import { goto } from '$app/navigation';
|
||||
import toast, { Toaster } from 'svelte-french-toast';
|
||||
import { Toaster, toast } from 'svelte-sonner';
|
||||
|
||||
import { getBackendConfig } from '$lib/apis';
|
||||
import { getSessionUser } from '$lib/apis/auths';
|
||||
@@ -68,4 +68,4 @@
|
||||
<slot />
|
||||
{/if}
|
||||
|
||||
<Toaster />
|
||||
<Toaster richColors position="top-center" />
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
import { WEBUI_API_BASE_URL, WEBUI_BASE_URL } from '$lib/constants';
|
||||
import { WEBUI_NAME, config, user } from '$lib/stores';
|
||||
import { onMount } from 'svelte';
|
||||
import toast from 'svelte-french-toast';
|
||||
import { toast } from 'svelte-sonner';
|
||||
|
||||
let loaded = false;
|
||||
let mode = 'signin';
|
||||
@@ -57,6 +57,12 @@
|
||||
});
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>
|
||||
{`${$WEBUI_NAME}`}
|
||||
</title>
|
||||
</svelte:head>
|
||||
|
||||
{#if loaded}
|
||||
<div class="fixed m-10 z-50">
|
||||
<div class="flex space-x-2">
|
||||
@@ -66,7 +72,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class=" bg-white min-h-screen w-full flex justify-center font-mona">
|
||||
<div class=" bg-white dark:bg-gray-900 min-h-screen w-full flex justify-center font-mona">
|
||||
<!-- <div class="hidden lg:flex lg:flex-1 px-10 md:px-16 w-full bg-yellow-50 justify-center">
|
||||
<div class=" my-auto pb-16 text-left">
|
||||
<div>
|
||||
@@ -81,15 +87,15 @@
|
||||
</div>
|
||||
</div> -->
|
||||
|
||||
<div class="w-full max-w-lg px-10 md:px-16 bg-white min-h-screen flex flex-col">
|
||||
<div class="w-full sm:max-w-lg px-4 min-h-screen flex flex-col">
|
||||
<div class=" my-auto pb-10 w-full">
|
||||
<form
|
||||
class=" flex flex-col justify-center"
|
||||
class=" flex flex-col justify-center bg-white py-6 sm:py-16 px-6 sm:px-16 rounded-2xl"
|
||||
on:submit|preventDefault={() => {
|
||||
submitHandler();
|
||||
}}
|
||||
>
|
||||
<div class=" text-xl md:text-2xl font-bold">
|
||||
<div class=" text-xl sm:text-2xl font-bold">
|
||||
{mode === 'signin' ? 'Sign in' : 'Sign up'} to {$WEBUI_NAME}
|
||||
</div>
|
||||
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
#!/bin/bash
|
||||
# update_llm.sh
|
||||
|
||||
# Retrieves the list of LLMs installed in the Docker container
|
||||
llm_list=$(docker exec ollama ollama list | tail -n +2 | awk '{print $1}')
|
||||
|
||||
# Loop over each LLM to update it
|
||||
for llm in $llm_list; do
|
||||
docker exec ollama ollama pull $llm
|
||||
done
|
||||
Reference in New Issue
Block a user