浏览代码

Merge pull request #910 from open-webui/dev

0.1.104
Timothy Jaeryang Baek 1 年之前
父节点
当前提交
3c10c3b928

+ 11 - 0
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

+ 21 - 0
backend/apps/web/models/tags.py

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

+ 5 - 2
backend/apps/web/routers/chats.py

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

+ 1 - 0
backend/constants.py

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

+ 21 - 1
backend/main.py

@@ -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 - 1
package.json

@@ -1,6 +1,6 @@
 {
 	"name": "open-webui",
-	"version": "0.1.103",
+	"version": "0.1.104",
 	"private": true,
 	"scripts": {
 		"dev": "vite dev --host",

+ 34 - 0
src/lib/apis/index.ts

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

+ 12 - 14
src/lib/components/chat/MessageInput.svelte

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

+ 40 - 38
src/lib/components/chat/MessageInput/Suggestions.svelte

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

+ 4 - 1
src/lib/components/chat/Messages/Placeholder.svelte

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

+ 48 - 2
src/lib/components/chat/Settings/About.svelte

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

+ 8 - 2
src/lib/components/layout/Sidebar.svelte

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

+ 2 - 3
src/lib/utils/index.ts

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

+ 2 - 2
src/routes/(app)/+layout.svelte

@@ -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'}`);
 		}
 	};