forked from open-webui/open-webui
commit
3c10c3b928
14 changed files with 210 additions and 66 deletions
11
CHANGELOG.md
11
CHANGELOG.md
|
@ -5,6 +5,17 @@ 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.104] - 2024-02-25
|
||||
|
||||
### Added
|
||||
|
||||
- **🔄 Check for Updates**: Keep your system current by checking for updates conveniently located in Settings > About.
|
||||
- **🗑️ Automatic Tag Deletion**: Unused tags on the sidebar will now be deleted automatically with just a click.
|
||||
|
||||
### Changed
|
||||
|
||||
- **🎨 Modernized Styling**: Enjoy a refreshed look with updated styling for a more contemporary experience.
|
||||
|
||||
## [0.1.103] - 2024-02-25
|
||||
|
||||
### Added
|
||||
|
|
|
@ -167,6 +167,27 @@ class TagTable:
|
|||
.count()
|
||||
)
|
||||
|
||||
def delete_tag_by_tag_name_and_user_id(self, tag_name: str, user_id: str) -> bool:
|
||||
try:
|
||||
query = ChatIdTag.delete().where(
|
||||
(ChatIdTag.tag_name == tag_name) & (ChatIdTag.user_id == user_id)
|
||||
)
|
||||
res = query.execute() # Remove the rows, return number of rows removed.
|
||||
print(res)
|
||||
|
||||
tag_count = self.count_chat_ids_by_tag_name_and_user_id(tag_name, user_id)
|
||||
if tag_count == 0:
|
||||
# Remove tag item from Tag col as well
|
||||
query = Tag.delete().where(
|
||||
(Tag.name == tag_name) & (Tag.user_id == user_id)
|
||||
)
|
||||
query.execute() # Remove the rows, return number of rows removed.
|
||||
|
||||
return True
|
||||
except Exception as e:
|
||||
print("delete_tag", e)
|
||||
return False
|
||||
|
||||
def delete_tag_by_tag_name_and_chat_id_and_user_id(
|
||||
self, tag_name: str, chat_id: str, user_id: str
|
||||
) -> bool:
|
||||
|
|
|
@ -115,9 +115,12 @@ async def get_user_chats_by_tag_name(
|
|||
for chat_id_tag in Tags.get_chat_ids_by_tag_name_and_user_id(tag_name, user.id)
|
||||
]
|
||||
|
||||
print(chat_ids)
|
||||
chats = Chats.get_chat_lists_by_chat_ids(chat_ids, skip, limit)
|
||||
|
||||
return Chats.get_chat_lists_by_chat_ids(chat_ids, skip, limit)
|
||||
if len(chats) == 0:
|
||||
Tags.delete_tag_by_tag_name_and_user_id(tag_name, user.id)
|
||||
|
||||
return chats
|
||||
|
||||
|
||||
############################
|
||||
|
|
|
@ -47,3 +47,4 @@ class ERROR_MESSAGES(str, Enum):
|
|||
INCORRECT_FORMAT = (
|
||||
lambda err="": f"Invalid format. Please use the correct format{err if err else ''}"
|
||||
)
|
||||
RATE_LIMIT_EXCEEDED = "API rate limit exceeded"
|
||||
|
|
|
@ -4,8 +4,9 @@ import markdown
|
|||
import time
|
||||
import os
|
||||
import sys
|
||||
import requests
|
||||
|
||||
from fastapi import FastAPI, Request, Depends
|
||||
from fastapi import FastAPI, Request, Depends, status
|
||||
from fastapi.staticfiles import StaticFiles
|
||||
from fastapi import HTTPException
|
||||
from fastapi.responses import JSONResponse
|
||||
|
@ -26,6 +27,8 @@ from apps.web.main import app as webui_app
|
|||
|
||||
|
||||
from config import WEBUI_NAME, ENV, VERSION, CHANGELOG, FRONTEND_BUILD_DIR
|
||||
from constants import ERROR_MESSAGES
|
||||
|
||||
from utils.utils import get_http_authorization_cred, get_current_user
|
||||
|
||||
|
||||
|
@ -127,6 +130,23 @@ async def get_app_changelog():
|
|||
return CHANGELOG
|
||||
|
||||
|
||||
@app.get("/api/version/updates")
|
||||
async def get_app_latest_release_version():
|
||||
try:
|
||||
response = requests.get(
|
||||
f"https://api.github.com/repos/open-webui/open-webui/releases/latest"
|
||||
)
|
||||
response.raise_for_status()
|
||||
latest_version = response.json()["tag_name"]
|
||||
|
||||
return {"current": VERSION, "latest": latest_version[1:]}
|
||||
except Exception as e:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
|
||||
detail=ERROR_MESSAGES.RATE_LIMIT_EXCEEDED,
|
||||
)
|
||||
|
||||
|
||||
app.mount("/static", StaticFiles(directory="static"), name="static")
|
||||
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "open-webui",
|
||||
"version": "0.1.103",
|
||||
"version": "0.1.104",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "vite dev --host",
|
||||
|
|
|
@ -19,6 +19,10 @@ export const getBackendConfig = async () => {
|
|||
return null;
|
||||
});
|
||||
|
||||
if (error) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
return res;
|
||||
};
|
||||
|
||||
|
@ -41,5 +45,35 @@ export const getChangelog = async () => {
|
|||
return null;
|
||||
});
|
||||
|
||||
if (error) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
return res;
|
||||
};
|
||||
|
||||
export const getVersionUpdates = async () => {
|
||||
let error = null;
|
||||
|
||||
const res = await fetch(`${WEBUI_BASE_URL}/api/version/updates`, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
})
|
||||
.then(async (res) => {
|
||||
if (!res.ok) throw await res.json();
|
||||
return res.json();
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log(err);
|
||||
error = err;
|
||||
return null;
|
||||
});
|
||||
|
||||
if (error) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
return res;
|
||||
};
|
||||
|
|
|
@ -490,7 +490,7 @@
|
|||
}}
|
||||
/>
|
||||
<form
|
||||
class=" flex flex-col relative w-full rounded-xl border dark:border-gray-700 bg-white dark:bg-gray-900 dark:text-gray-100"
|
||||
class=" flex flex-col relative w-full rounded-3xl px-1.5 border border-gray-100 dark:border-gray-850 bg-white dark:bg-gray-900 dark:text-gray-100"
|
||||
on:submit|preventDefault={() => {
|
||||
submitPrompt(prompt, user);
|
||||
}}
|
||||
|
@ -633,9 +633,9 @@
|
|||
|
||||
<div class=" flex">
|
||||
{#if fileUploadEnabled}
|
||||
<div class=" self-end mb-2 ml-1.5">
|
||||
<div class=" self-end mb-2 ml-1">
|
||||
<button
|
||||
class=" text-gray-600 dark:text-gray-200 transition rounded-lg p-1 ml-1"
|
||||
class="bg-gray-50 hover:bg-gray-100 text-gray-800 dark:bg-gray-850 dark:text-white dark:hover:bg-gray-800 transition rounded-full p-1.5"
|
||||
type="button"
|
||||
on:click={() => {
|
||||
filesInputElement.click();
|
||||
|
@ -643,14 +643,12 @@
|
|||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 20 20"
|
||||
viewBox="0 0 16 16"
|
||||
fill="currentColor"
|
||||
class="w-5 h-5"
|
||||
class="w-[1.2rem] h-[1.2rem]"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
d="M15.621 4.379a3 3 0 00-4.242 0l-7 7a3 3 0 004.241 4.243h.001l.497-.5a.75.75 0 011.064 1.057l-.498.501-.002.002a4.5 4.5 0 01-6.364-6.364l7-7a4.5 4.5 0 016.368 6.36l-3.455 3.553A2.625 2.625 0 119.52 9.52l3.45-3.451a.75.75 0 111.061 1.06l-3.45 3.451a1.125 1.125 0 001.587 1.595l3.454-3.553a3 3 0 000-4.242z"
|
||||
clip-rule="evenodd"
|
||||
d="M8.75 3.75a.75.75 0 0 0-1.5 0v3.5h-3.5a.75.75 0 0 0 0 1.5h3.5v3.5a.75.75 0 0 0 1.5 0v-3.5h3.5a.75.75 0 0 0 0-1.5h-3.5v-3.5Z"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
|
@ -659,7 +657,7 @@
|
|||
|
||||
<textarea
|
||||
id="chat-textarea"
|
||||
class=" dark:bg-gray-900 dark:text-gray-100 outline-none w-full py-3 px-2 {fileUploadEnabled
|
||||
class=" dark:bg-gray-900 dark:text-gray-100 outline-none w-full py-3 px-3 {fileUploadEnabled
|
||||
? ''
|
||||
: ' pl-4'} rounded-xl resize-none h-[48px]"
|
||||
placeholder={chatInputPlaceholder !== ''
|
||||
|
@ -803,12 +801,12 @@
|
|||
}}
|
||||
/>
|
||||
|
||||
<div class="self-end mb-2 flex space-x-0.5 mr-2">
|
||||
<div class="self-end mb-2 flex space-x-1 mr-1">
|
||||
{#if messages.length == 0 || messages.at(-1).done == true}
|
||||
{#if speechRecognitionEnabled}
|
||||
<button
|
||||
id="voice-input-button"
|
||||
class=" text-gray-600 dark:text-gray-300 transition rounded-lg p-1.5 mr-0.5 self-center"
|
||||
class=" text-gray-600 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-850 transition rounded-full p-1.5 mr-0.5 self-center"
|
||||
type="button"
|
||||
on:click={() => {
|
||||
speechRecognitionHandler();
|
||||
|
@ -869,7 +867,7 @@
|
|||
<button
|
||||
class="{prompt !== ''
|
||||
? 'bg-black text-white hover:bg-gray-900 dark:bg-white dark:text-black dark:hover:bg-gray-100 '
|
||||
: 'text-white bg-gray-100 dark:text-gray-900 dark:bg-gray-800 disabled'} transition rounded-lg p-1 mr-0.5 w-7 h-7 self-center"
|
||||
: 'text-white bg-gray-100 dark:text-gray-900 dark:bg-gray-800 disabled'} transition rounded-full p-1.5 self-center"
|
||||
type="submit"
|
||||
disabled={prompt === ''}
|
||||
>
|
||||
|
@ -877,7 +875,7 @@
|
|||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 16 16"
|
||||
fill="currentColor"
|
||||
class="w-4.5 h-4.5 mx-auto"
|
||||
class="w-5 h-5"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
|
@ -888,7 +886,7 @@
|
|||
</button>
|
||||
{:else}
|
||||
<button
|
||||
class="bg-white hover:bg-gray-100 text-gray-800 dark:bg-gray-700 dark:text-white dark:hover:bg-gray-800 transition rounded-lg p-1.5"
|
||||
class="bg-white hover:bg-gray-100 text-gray-800 dark:bg-gray-700 dark:text-white dark:hover:bg-gray-800 transition rounded-full p-1.5"
|
||||
on:click={stopResponse}
|
||||
>
|
||||
<svg
|
||||
|
|
|
@ -10,45 +10,47 @@
|
|||
: suggestionPrompts.sort(() => Math.random() - 0.5).slice(0, 4);
|
||||
</script>
|
||||
|
||||
<div class=" flex flex-wrap-reverse mb-3 md:p-1 text-left w-full">
|
||||
{#each prompts as prompt, promptIdx}
|
||||
<div
|
||||
class="{promptIdx > 1 ? 'hidden sm:inline-flex' : ''} basis-full sm:basis-1/2 p-[5px] px-2"
|
||||
>
|
||||
<button
|
||||
class=" flex-1 flex justify-between w-full h-full px-4 py-2.5 bg-white hover:bg-gray-50 dark:bg-gray-900 dark:hover:bg-gray-700 outline outline-1 outline-gray-200 dark:outline-gray-800 rounded-lg transition group"
|
||||
on:click={() => {
|
||||
submitPrompt(prompt.content);
|
||||
}}
|
||||
<div class=" mb-3 md:p-1 text-left w-full">
|
||||
<div class=" flex flex-wrap-reverse px-2 text-left">
|
||||
{#each prompts as prompt, promptIdx}
|
||||
<div
|
||||
class="{promptIdx > 1 ? 'hidden sm:inline-flex' : ''} basis-full sm:basis-1/2 p-[5px] px-1"
|
||||
>
|
||||
<div class="flex flex-col text-left self-center">
|
||||
{#if prompt.title && prompt.title[0] !== ''}
|
||||
<div class="text-sm font-medium dark:text-gray-300">{prompt.title[0]}</div>
|
||||
<div class="text-sm text-gray-500 line-clamp-1">{prompt.title[1]}</div>
|
||||
{:else}
|
||||
<div class=" self-center text-sm font-medium dark:text-gray-300 line-clamp-2">
|
||||
{prompt.content}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="self-center p-1 rounded-lg text-white group-hover:bg-gray-100 group-hover:text-gray-800 dark:group-hover:bg-gray-800 dark:group-hover:text-gray-100 dark:text-gray-900 transition"
|
||||
<button
|
||||
class=" flex-1 flex justify-between w-full h-full px-4 py-2.5 bg-gray-50 hover:bg-gray-100 dark:bg-gray-850 dark:hover:bg-gray-800 rounded-2xl transition group"
|
||||
on:click={() => {
|
||||
submitPrompt(prompt.content);
|
||||
}}
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 16 16"
|
||||
fill="currentColor"
|
||||
class="w-4 h-4"
|
||||
<div class="flex flex-col text-left self-center">
|
||||
{#if prompt.title && prompt.title[0] !== ''}
|
||||
<div class="text-sm font-medium dark:text-gray-300">{prompt.title[0]}</div>
|
||||
<div class="text-sm text-gray-500 line-clamp-1">{prompt.title[1]}</div>
|
||||
{:else}
|
||||
<div class=" self-center text-sm font-medium dark:text-gray-300 line-clamp-2">
|
||||
{prompt.content}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="self-center p-1 rounded-lg text-gray-50 group-hover:text-gray-800 dark:text-gray-850 dark:group-hover:text-gray-100 transition"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
d="M8 14a.75.75 0 0 1-.75-.75V4.56L4.03 7.78a.75.75 0 0 1-1.06-1.06l4.5-4.5a.75.75 0 0 1 1.06 0l4.5 4.5a.75.75 0 0 1-1.06 1.06L8.75 4.56v8.69A.75.75 0 0 1 8 14Z"
|
||||
clip-rule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
{/each}
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 16 16"
|
||||
fill="currentColor"
|
||||
class="w-4 h-4"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
d="M8 14a.75.75 0 0 1-.75-.75V4.56L4.03 7.78a.75.75 0 0 1-1.06-1.06l4.5-4.5a.75.75 0 0 1 1.06 0l4.5 4.5a.75.75 0 0 1-1.06 1.06L8.75 4.56v8.69A.75.75 0 0 1 8 14Z"
|
||||
clip-rule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
<script lang="ts">
|
||||
import { WEBUI_BASE_URL } from '$lib/constants';
|
||||
import { user } from '$lib/stores';
|
||||
import { onMount } from 'svelte';
|
||||
|
||||
export let models = [];
|
||||
|
@ -63,7 +64,9 @@
|
|||
</div>
|
||||
{/if}
|
||||
{:else}
|
||||
How can I help you today?
|
||||
<div class=" line-clamp-1">Hello, {$user.name}</div>
|
||||
|
||||
<div>How can I help you today?</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,14 +1,40 @@
|
|||
<script lang="ts">
|
||||
import { getVersionUpdates } from '$lib/apis';
|
||||
import { getOllamaVersion } from '$lib/apis/ollama';
|
||||
import { WEBUI_VERSION } from '$lib/constants';
|
||||
import { WEBUI_NAME, config, showChangelog } from '$lib/stores';
|
||||
import { compareVersion } from '$lib/utils';
|
||||
import { onMount } from 'svelte';
|
||||
|
||||
let ollamaVersion = '';
|
||||
|
||||
let updateAvailable = null;
|
||||
let version = {
|
||||
current: '',
|
||||
latest: ''
|
||||
};
|
||||
|
||||
const checkForVersionUpdates = async () => {
|
||||
updateAvailable = null;
|
||||
version = await getVersionUpdates(localStorage.token).catch((error) => {
|
||||
return {
|
||||
current: WEBUI_VERSION,
|
||||
latest: WEBUI_VERSION
|
||||
};
|
||||
});
|
||||
|
||||
console.log(version);
|
||||
|
||||
updateAvailable = compareVersion(version.latest, version.current);
|
||||
console.log(updateAvailable);
|
||||
};
|
||||
|
||||
onMount(async () => {
|
||||
ollamaVersion = await getOllamaVersion(localStorage.token).catch((error) => {
|
||||
return '';
|
||||
});
|
||||
|
||||
checkForVersionUpdates();
|
||||
});
|
||||
</script>
|
||||
|
||||
|
@ -20,10 +46,21 @@
|
|||
{$WEBUI_NAME} Version
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex w-full">
|
||||
<div class="flex-1 text-xs text-gray-700 dark:text-gray-200 flex space-x-1.5 items-center">
|
||||
<div class="flex w-full justify-between items-center">
|
||||
<div class="flex flex-col text-xs text-gray-700 dark:text-gray-200">
|
||||
<div>
|
||||
v{WEBUI_VERSION}
|
||||
|
||||
<a
|
||||
href="https://github.com/open-webui/open-webui/releases/tag/v{version.latest}"
|
||||
target="_blank"
|
||||
>
|
||||
{updateAvailable === null
|
||||
? 'Checking for updates...'
|
||||
: updateAvailable
|
||||
? `(v${version.latest} available!)`
|
||||
: '(latest)'}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<button
|
||||
|
@ -35,6 +72,15 @@
|
|||
<div>See what's new</div>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<button
|
||||
class=" text-xs px-3 py-1.5 bg-gray-100 hover:bg-gray-200 dark:bg-gray-850 dark:hover:bg-gray-800 transition rounded-lg font-medium"
|
||||
on:click={() => {
|
||||
checkForVersionUpdates();
|
||||
}}
|
||||
>
|
||||
Check for updates
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -13,7 +13,8 @@
|
|||
getChatList,
|
||||
getChatById,
|
||||
getChatListByTagName,
|
||||
updateChatById
|
||||
updateChatById,
|
||||
getAllChatTags
|
||||
} from '$lib/apis/chats';
|
||||
import toast from 'svelte-french-toast';
|
||||
import { slide } from 'svelte/transition';
|
||||
|
@ -330,7 +331,12 @@
|
|||
<button
|
||||
class="px-2.5 text-xs font-medium bg-gray-900 hover:bg-gray-800 transition rounded-full"
|
||||
on:click={async () => {
|
||||
await chats.set(await getChatListByTagName(localStorage.token, tag.name));
|
||||
let chatIds = await getChatListByTagName(localStorage.token, tag.name);
|
||||
if (chatIds.length === 0) {
|
||||
await tags.set(await getAllChatTags(localStorage.token));
|
||||
chatIds = await getChatList(localStorage.token);
|
||||
}
|
||||
await chats.set(chatIds);
|
||||
}}
|
||||
>
|
||||
{tag.name}
|
||||
|
|
|
@ -101,11 +101,10 @@ export const copyToClipboard = (text) => {
|
|||
);
|
||||
};
|
||||
|
||||
export const checkVersion = (required, current) => {
|
||||
// Returns true when current version is below required
|
||||
export const compareVersion = (latest, current) => {
|
||||
return current === '0.0.0'
|
||||
? false
|
||||
: current.localeCompare(required, undefined, {
|
||||
: current.localeCompare(latest, undefined, {
|
||||
numeric: true,
|
||||
sensitivity: 'case',
|
||||
caseFirst: 'upper'
|
||||
|
|
|
@ -28,7 +28,7 @@
|
|||
config
|
||||
} from '$lib/stores';
|
||||
import { REQUIRED_OLLAMA_VERSION, WEBUI_API_BASE_URL } from '$lib/constants';
|
||||
import { checkVersion } from '$lib/utils';
|
||||
import { compareVersion } from '$lib/utils';
|
||||
|
||||
import SettingsModal from '$lib/components/chat/SettingsModal.svelte';
|
||||
import Sidebar from '$lib/components/layout/Sidebar.svelte';
|
||||
|
@ -79,7 +79,7 @@
|
|||
ollamaVersion = version;
|
||||
|
||||
console.log(ollamaVersion);
|
||||
if (checkVersion(REQUIRED_OLLAMA_VERSION, ollamaVersion)) {
|
||||
if (compareVersion(REQUIRED_OLLAMA_VERSION, ollamaVersion)) {
|
||||
toast.error(`Ollama Version: ${ollamaVersion !== '' ? ollamaVersion : 'Not Detected'}`);
|
||||
}
|
||||
};
|
||||
|
|
Loading…
Reference in a new issue