Parcourir la source

Merge pull request #842 from jannikstdl/release-notes-modal

Release Notes Modal
Timothy Jaeryang Baek il y a 1 an
Parent
commit
7bbb6bdabe

+ 25 - 0
CHANGELOG.md

@@ -0,0 +1,25 @@
+# Changelog
+
+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.102] - 2024-02-22
+
+### Added
+
+- **🖼️ Image Generation**: Generate Images using the stable-difusion-webui API. You can set this up in settings -> images.
+- **📝 Change title generation prompt**: Change the promt used to generate titles for your chats. You can set this up in the settings -> interface.
+- **🤖 Change embedding model**: Change the embedding model used to generate embeddings for your chats in the Dockerfile. Use any sentence transformer model from huggingface.co.
+- **📢 CHANGELOG.md/Popup**: This popup will show you the latest changes. You can edit it in the constants.ts file.
+
+## [0.1.101] - 2024-02-22
+
+### Fixed
+
+- LaTex output formatting issue (#828)
+
+### Changed
+
+- Instead of having the previous 1.0.0-alpha.101, we switched to semantic versioning as a way to respect global conventions.

+ 2 - 0
Dockerfile

@@ -73,6 +73,8 @@ COPY --from=build /app/onnx /root/.cache/chroma/onnx_models/all-MiniLM-L6-v2/onn
 
 # copy built frontend files
 COPY --from=build /app/build /app/build
+COPY --from=build /app/CHANGELOG.md /app/CHANGELOG.md
+COPY --from=build /app/package.json /app/package.json
 
 # copy backend files
 COPY ./backend .

+ 71 - 0
backend/config.py

@@ -6,6 +6,8 @@ from base64 import b64encode
 from constants import ERROR_MESSAGES
 from pathlib import Path
 import json
+import markdown
+from bs4 import BeautifulSoup
 
 
 try:
@@ -23,6 +25,75 @@ except ImportError:
 ENV = os.environ.get("ENV", "dev")
 
 
+try:
+    with open(f"../package.json", "r") as f:
+        PACKAGE_DATA = json.load(f)
+except:
+    PACKAGE_DATA = {"version": "0.0.0"}
+
+VERSION = PACKAGE_DATA["version"]
+
+
+# Function to parse each section
+def parse_section(section):
+    items = []
+    for li in section.find_all("li"):
+        # Extract raw HTML string
+        raw_html = str(li)
+
+        # Extract text without HTML tags
+        text = li.get_text(separator=" ", strip=True)
+
+        # Split into title and content
+        parts = text.split(": ", 1)
+        title = parts[0].strip() if len(parts) > 1 else ""
+        content = parts[1].strip() if len(parts) > 1 else text
+
+        items.append({"title": title, "content": content, "raw": raw_html})
+    return items
+
+
+try:
+    with open("../CHANGELOG.md", "r") as file:
+        changelog_content = file.read()
+except:
+    changelog_content = ""
+
+# Convert markdown content to HTML
+html_content = markdown.markdown(changelog_content)
+
+# Parse the HTML content
+soup = BeautifulSoup(html_content, "html.parser")
+
+# Initialize JSON structure
+changelog_json = {}
+
+# Iterate over each version
+for version in soup.find_all("h2"):
+    version_number = version.get_text().strip().split(" - ")[0][1:-1]  # Remove brackets
+    date = version.get_text().strip().split(" - ")[1]
+
+    version_data = {"date": date}
+
+    # Find the next sibling that is a h3 tag (section title)
+    current = version.find_next_sibling()
+
+    print(current)
+
+    while current and current.name != "h2":
+        if current.name == "h3":
+            section_title = current.get_text().lower()  # e.g., "added", "fixed"
+            section_items = parse_section(current.find_next_sibling("ul"))
+            version_data[section_title] = section_items
+
+        # Move to the next element
+        current = current.find_next_sibling()
+
+    changelog_json[version_number] = version_data
+
+
+CHANGELOG = changelog_json
+
 ####################################
 # DATA/FRONTEND BUILD DIR
 ####################################

+ 12 - 1
backend/main.py

@@ -1,5 +1,9 @@
+from bs4 import BeautifulSoup
+import json
+import markdown
 import time
 
+
 from fastapi import FastAPI, Request
 from fastapi.staticfiles import StaticFiles
 from fastapi import HTTPException
@@ -16,7 +20,7 @@ from apps.rag.main import app as rag_app
 
 from apps.web.main import app as webui_app
 
-from config import ENV, FRONTEND_BUILD_DIR
+from config import ENV, VERSION, CHANGELOG, FRONTEND_BUILD_DIR
 
 
 class SPAStaticFiles(StaticFiles):
@@ -65,14 +69,21 @@ app.mount("/rag/api/v1", rag_app)
 
 @app.get("/api/config")
 async def get_app_config():
+
     return {
         "status": True,
+        "version": VERSION,
         "images": images_app.state.ENABLED,
         "default_models": webui_app.state.DEFAULT_MODELS,
         "default_prompt_suggestions": webui_app.state.DEFAULT_PROMPT_SUGGESTIONS,
     }
 
 
+@app.get("/api/changelog")
+async def get_app_changelog():
+    return CHANGELOG
+
+
 app.mount(
     "/",
     SPAStaticFiles(directory=FRONTEND_BUILD_DIR, html=True),

BIN
bun.lockb


+ 19 - 2
package-lock.json

@@ -1,12 +1,12 @@
 {
 	"name": "open-webui",
-	"version": "0.0.1",
+	"version": "v1.0.0-alpha.101",
 	"lockfileVersion": 2,
 	"requires": true,
 	"packages": {
 		"": {
 			"name": "open-webui",
-			"version": "0.0.1",
+			"version": "v1.0.0-alpha.101",
 			"dependencies": {
 				"@sveltejs/adapter-node": "^1.3.1",
 				"async": "^3.2.5",
@@ -38,6 +38,7 @@
 				"prettier-plugin-svelte": "^2.10.1",
 				"svelte": "^4.0.5",
 				"svelte-check": "^3.4.3",
+				"svelte-confetti": "^1.3.2",
 				"tailwindcss": "^3.3.3",
 				"tslib": "^2.4.1",
 				"typescript": "^5.0.0",
@@ -3174,6 +3175,15 @@
 				"svelte": "^3.55.0 || ^4.0.0-next.0 || ^4.0.0"
 			}
 		},
+		"node_modules/svelte-confetti": {
+			"version": "1.3.2",
+			"resolved": "https://registry.npmjs.org/svelte-confetti/-/svelte-confetti-1.3.2.tgz",
+			"integrity": "sha512-R+JwFTC7hIgWVA/OuXrkj384B7CMoceb0t9VacyW6dORTQg0pWojVBB8Bo3tM30cLEQE48Fekzqgx+XSzHESMA==",
+			"dev": true,
+			"peerDependencies": {
+				"svelte": "^4.0.0"
+			}
+		},
 		"node_modules/svelte-eslint-parser": {
 			"version": "0.33.1",
 			"resolved": "https://registry.npmjs.org/svelte-eslint-parser/-/svelte-eslint-parser-0.33.1.tgz",
@@ -5852,6 +5862,13 @@
 				"typescript": "^5.0.3"
 			}
 		},
+		"svelte-confetti": {
+			"version": "1.3.2",
+			"resolved": "https://registry.npmjs.org/svelte-confetti/-/svelte-confetti-1.3.2.tgz",
+			"integrity": "sha512-R+JwFTC7hIgWVA/OuXrkj384B7CMoceb0t9VacyW6dORTQg0pWojVBB8Bo3tM30cLEQE48Fekzqgx+XSzHESMA==",
+			"dev": true,
+			"requires": {}
+		},
 		"svelte-eslint-parser": {
 			"version": "0.33.1",
 			"resolved": "https://registry.npmjs.org/svelte-eslint-parser/-/svelte-eslint-parser-0.33.1.tgz",

+ 1 - 0
package.json

@@ -32,6 +32,7 @@
 		"prettier-plugin-svelte": "^2.10.1",
 		"svelte": "^4.0.5",
 		"svelte-check": "^3.4.3",
+		"svelte-confetti": "^1.3.2",
 		"tailwindcss": "^3.3.3",
 		"tslib": "^2.4.1",
 		"typescript": "^5.0.0",

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

@@ -21,3 +21,25 @@ export const getBackendConfig = async () => {
 
 	return res;
 };
+
+export const getChangelog = async () => {
+	let error = null;
+
+	const res = await fetch(`${WEBUI_BASE_URL}/api/changelog`, {
+		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;
+		});
+
+	return res;
+};

+ 115 - 0
src/lib/components/ChangelogModal.svelte

@@ -0,0 +1,115 @@
+<script lang="ts">
+	import { onMount } from 'svelte';
+	import { Confetti } from 'svelte-confetti';
+
+	import { config } from '$lib/stores';
+
+	import { WEBUI_NAME, WEB_UI_VERSION } from '$lib/constants';
+	import { getChangelog } from '$lib/apis';
+
+	import Modal from './common/Modal.svelte';
+
+	export let show = false;
+
+	let changelog = null;
+
+	onMount(async () => {
+		const res = await getChangelog();
+		changelog = res;
+	});
+</script>
+
+<Modal bind:show>
+	<div class="px-5 py-4 dark:text-gray-300">
+		<div class="flex justify-between items-start">
+			<div class="text-xl font-bold">
+				What’s New in {WEBUI_NAME}
+				<Confetti x={[-1, -0.25]} y={[0, 0.5]} />
+			</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>
+		<div class="flex items-center mt-1">
+			<div class="text-sm dark:text-gray-200">Release Notes</div>
+			<div class="flex self-center w-[1px] h-6 mx-2.5 bg-gray-200 dark:bg-gray-700" />
+			<div class="text-sm dark:text-gray-200">
+				v{WEB_UI_VERSION}
+			</div>
+		</div>
+	</div>
+
+	<hr class=" dark:border-gray-800" />
+
+	<div class=" w-full p-4 px-5">
+		<div class=" overflow-y-scroll max-h-80">
+			<div class="mb-3">
+				{#if changelog}
+					{#each Object.keys(changelog) as version}
+						<div class=" mb-3 pr-2">
+							<div class="font-bold text-xl mb-1 dark:text-white">
+								v{version} - {changelog[version].date}
+							</div>
+
+							<hr class=" dark:border-gray-800 my-2" />
+
+							{#each Object.keys(changelog[version]).filter((section) => section !== 'date') as section}
+								<div class="">
+									<div
+										class="font-bold uppercase text-xs {section === 'added'
+											? 'text-white bg-blue-600'
+											: section === 'fixed'
+											? 'text-white bg-green-600'
+											: section === 'changed'
+											? 'text-white bg-yellow-600'
+											: section === 'removed'
+											? 'text-white bg-red-600'
+											: ''}  w-fit px-3 rounded-full my-2.5"
+									>
+										{section}
+									</div>
+
+									<div class="my-2.5 px-1.5">
+										{#each Object.keys(changelog[version][section]) as item}
+											<div class="text-sm mb-2">
+												<div class="font-semibold uppercase">
+													{changelog[version][section][item].title}
+												</div>
+												<div class="mb-2 mt-1">{changelog[version][section][item].content}</div>
+											</div>
+										{/each}
+									</div>
+								</div>
+							{/each}
+						</div>
+					{/each}
+				{/if}
+			</div>
+		</div>
+		<div class="flex justify-end pt-3 text-sm font-medium">
+			<button
+				on:click={() => {
+					localStorage.version = $config.version;
+					show = false;
+				}}
+				class=" px-4 py-2 bg-emerald-600 hover:bg-emerald-700 text-gray-100 transition rounded"
+			>
+				<span class="relative">Okay, Let's Go!</span>
+			</button>
+		</div>
+	</div>
+</Modal>

+ 19 - 4
src/lib/components/chat/Settings/About.svelte

@@ -1,7 +1,7 @@
 <script lang="ts">
 	import { getOllamaVersion } from '$lib/apis/ollama';
 	import { WEBUI_NAME, WEB_UI_VERSION } from '$lib/constants';
-	import { config } from '$lib/stores';
+	import { config, showChangelog } from '$lib/stores';
 	import { onMount } from 'svelte';
 
 	let ollamaVersion = '';
@@ -15,10 +15,25 @@
 <div class="flex flex-col h-full justify-between space-y-3 text-sm mb-6">
 	<div class=" space-y-3">
 		<div>
-			<div class=" mb-2.5 text-sm font-medium">{WEBUI_NAME} Version</div>
+			<div class=" mb-2.5 text-sm font-medium flex space-x-2 items-center">
+				<div>
+					{WEBUI_NAME} Version
+				</div>
+			</div>
 			<div class="flex w-full">
-				<div class="flex-1 text-xs text-gray-700 dark:text-gray-200">
-					v{WEB_UI_VERSION}
+				<div class="flex-1 text-xs text-gray-700 dark:text-gray-200 flex space-x-1.5 items-center">
+					<div>
+						v{WEB_UI_VERSION}
+					</div>
+
+					<button
+						class=" underline flex items-center space-x-1 text-xs text-gray-500 dark:text-gray-500"
+						on:click={() => {
+							showChangelog.set(true);
+						}}
+					>
+						<div>See what's new</div>
+					</button>
 				</div>
 			</div>
 		</div>

+ 22 - 4
src/lib/components/common/Modal.svelte

@@ -1,6 +1,6 @@
 <script lang="ts">
 	import { onMount } from 'svelte';
-	import { fade, blur } from 'svelte/transition';
+	import { fade } from 'svelte/transition';
 
 	export let show = true;
 	export let size = 'md';
@@ -34,16 +34,17 @@
 	<!-- svelte-ignore a11y-click-events-have-key-events -->
 	<!-- svelte-ignore a11y-no-static-element-interactions -->
 	<div
-		class="fixed top-0 right-0 left-0 bottom-0 bg-black/60 w-full min-h-screen h-screen flex justify-center z-50 overflow-hidden overscroll-contain"
+		class=" fixed top-0 right-0 left-0 bottom-0 bg-black/60 w-full min-h-screen h-screen flex justify-center z-50 overflow-hidden overscroll-contain"
+		in:fade={{ duration: 10 }}
 		on:click={() => {
 			show = false;
 		}}
 	>
 		<div
-			class="m-auto rounded-xl max-w-full {sizeToWidth(
+			class=" modal-content m-auto rounded-xl max-w-full {sizeToWidth(
 				size
 			)} mx-2 bg-gray-50 dark:bg-gray-900 shadow-3xl"
-			transition:fade={{ delay: 100, duration: 200 }}
+			in:fade={{ duration: 10 }}
 			on:click={(e) => {
 				e.stopPropagation();
 			}}
@@ -52,3 +53,20 @@
 		</div>
 	</div>
 {/if}
+
+<style>
+	.modal-content {
+		animation: scaleUp 0.1s ease-out forwards;
+	}
+
+	@keyframes scaleUp {
+		from {
+			transform: scale(0.985);
+			opacity: 0;
+		}
+		to {
+			transform: scale(1);
+			opacity: 1;
+		}
+	}
+</style>

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

@@ -16,6 +16,7 @@
 		updateChatById
 	} from '$lib/apis/chats';
 	import toast from 'svelte-french-toast';
+	import { slide } from 'svelte/transition';
 
 	let show = false;
 	let navElement;
@@ -562,6 +563,7 @@
 						<div
 							id="dropdownDots"
 							class="absolute z-40 bottom-[70px] 4.5rem rounded-lg shadow w-[240px] bg-gray-900"
+							in:slide={{ duration: 150 }}
 						>
 							<div class="py-2 w-full">
 								{#if $user.role === 'admin'}

+ 0 - 1
src/lib/constants.ts

@@ -12,7 +12,6 @@ export const IMAGES_API_BASE_URL = `${WEBUI_BASE_URL}/images/api/v1`;
 export const RAG_API_BASE_URL = `${WEBUI_BASE_URL}/rag/api/v1`;
 
 export const WEB_UI_VERSION = APP_VERSION;
-
 export const REQUIRED_OLLAMA_VERSION = '0.1.16';
 
 export const SUPPORTED_FILE_TYPE = [

+ 1 - 0
src/lib/stores/index.ts

@@ -32,3 +32,4 @@ export const documents = writable([
 
 export const settings = writable({});
 export const showSettings = writable(false);
+export const showChangelog = writable(false);

+ 15 - 8
src/routes/(app)/+layout.svelte

@@ -1,17 +1,18 @@
 <script lang="ts">
 	import toast from 'svelte-french-toast';
 	import { openDB, deleteDB } from 'idb';
-	import { onMount, tick } from 'svelte';
-	import { goto } from '$app/navigation';
-
 	import fileSaver from 'file-saver';
 	const { saveAs } = fileSaver;
 
+	import { onMount, tick } from 'svelte';
+	import { goto } from '$app/navigation';
+
 	import { getOllamaModels, getOllamaVersion } from '$lib/apis/ollama';
 	import { getModelfiles } from '$lib/apis/modelfiles';
 	import { getPrompts } from '$lib/apis/prompts';
-
 	import { getOpenAIModels } from '$lib/apis/openai';
+	import { getDocs } from '$lib/apis/documents';
+	import { getAllChatTags } from '$lib/apis/chats';
 
 	import {
 		user,
@@ -21,16 +22,17 @@
 		modelfiles,
 		prompts,
 		documents,
-		tags
+		tags,
+		showChangelog,
+		config
 	} from '$lib/stores';
 	import { REQUIRED_OLLAMA_VERSION, WEBUI_API_BASE_URL } from '$lib/constants';
+	import { checkVersion } from '$lib/utils';
 
 	import SettingsModal from '$lib/components/chat/SettingsModal.svelte';
 	import Sidebar from '$lib/components/layout/Sidebar.svelte';
-	import { checkVersion } from '$lib/utils';
 	import ShortcutsModal from '$lib/components/chat/ShortcutsModal.svelte';
-	import { getDocs } from '$lib/apis/documents';
-	import { getAllChatTags } from '$lib/apis/chats';
+	import ChangelogModal from '$lib/components/ChangelogModal.svelte';
 
 	let ollamaVersion = '';
 	let loaded = false;
@@ -182,6 +184,10 @@
 				}
 			});
 
+			if ($user.role === 'admin') {
+				showChangelog.set(localStorage.version !== $config.version);
+			}
+
 			await tick();
 		}
 
@@ -355,6 +361,7 @@
 		>
 			<Sidebar />
 			<SettingsModal bind:show={$showSettings} />
+			<ChangelogModal bind:show={$showChangelog} />
 			<slot />
 		</div>
 	</div>