feat: changelog.md

This commit is contained in:
Timothy J. Baek 2024-02-23 00:30:26 -08:00
parent 9b27d952f8
commit 9f950aea9c
13 changed files with 237 additions and 154 deletions

33
CHANGELOG.md Normal file
View file

@ -0,0 +1,33 @@
# 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.
### Fixed
- X, Y, and Z
### Changed
- X, Y, and Z
### Removed
- X, Y, and Z
## [0.1.101] - 2024-02-21
### Added
- X, Y, and Z

View file

@ -23,6 +23,14 @@ except ImportError:
ENV = os.environ.get("ENV", "dev") 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"]
#################################### ####################################
# DATA/FRONTEND BUILD DIR # DATA/FRONTEND BUILD DIR
#################################### ####################################

View file

@ -1,5 +1,9 @@
from bs4 import BeautifulSoup
import json
import markdown
import time import time
from fastapi import FastAPI, Request from fastapi import FastAPI, Request
from fastapi.staticfiles import StaticFiles from fastapi.staticfiles import StaticFiles
from fastapi import HTTPException 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 apps.web.main import app as webui_app
from config import ENV, FRONTEND_BUILD_DIR from config import ENV, VERSION, FRONTEND_BUILD_DIR
class SPAStaticFiles(StaticFiles): class SPAStaticFiles(StaticFiles):
@ -65,14 +69,85 @@ app.mount("/rag/api/v1", rag_app)
@app.get("/api/config") @app.get("/api/config")
async def get_app_config(): async def get_app_config():
return { return {
"status": True, "status": True,
"version": VERSION,
"images": images_app.state.ENABLED, "images": images_app.state.ENABLED,
"default_models": webui_app.state.DEFAULT_MODELS, "default_models": webui_app.state.DEFAULT_MODELS,
"default_prompt_suggestions": webui_app.state.DEFAULT_PROMPT_SUGGESTIONS, "default_prompt_suggestions": webui_app.state.DEFAULT_PROMPT_SUGGESTIONS,
} }
# 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
@app.get("/api/changelog")
async def get_app_changelog():
try:
with open("../CHANGELOG.md", "r") as file:
changelog_content = file.read()
# Convert markdown content to HTML
html_content = markdown.markdown(changelog_content)
# Parse the HTML content
soup = BeautifulSoup(html_content, "html.parser")
print(soup)
# 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
# print(changelog_json)
# Return content as JSON string
return changelog_json
except FileNotFoundError:
return {"error": "readme.md not found"}
except Exception as e:
return {"error": f"An error occurred: {e}"}
app.mount( app.mount(
"/", "/",
SPAStaticFiles(directory=FRONTEND_BUILD_DIR, html=True), SPAStaticFiles(directory=FRONTEND_BUILD_DIR, html=True),

View file

@ -1,6 +1,6 @@
{ {
"name": "open-webui", "name": "open-webui",
"version": "v1.0.0-alpha.101", "version": "0.1.102",
"private": true, "private": true,
"scripts": { "scripts": {
"dev": "vite dev --host", "dev": "vite dev --host",

View file

@ -21,3 +21,25 @@ export const getBackendConfig = async () => {
return res; 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;
};

View file

@ -0,0 +1,90 @@
<script lang="ts">
import Modal from './common/Modal.svelte';
import { Confetti } from 'svelte-confetti';
import { WEBUI_NAME, WEB_UI_VERSION } from '$lib/constants';
import { onMount } from 'svelte';
import { getChangelog } from '$lib/apis';
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">
{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=" pb-3 flex items-center mt-2">
<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>
<hr class=" dark:border-gray-800" />
<div class=" overflow-y-scroll max-h-80">
<div class="my-3">
{#if changelog}
{#each Object.keys(changelog) as version}
<div class="font-bold text-xl mb-1">
v{version} - {changelog[version].date}
</div>
{#each Object.keys(changelog[version]).filter((section) => section !== 'date') as section}
<div class="text-lg">
<div class="font-bold capitalize">{section}</div>
<div class="my-2">
{#each Object.keys(changelog[version][section]) as item}
<div class="text-sm mb-2">
<div class="font-semibold">
{changelog[version][section][item].title}
</div>
<div class="my-1.5">{changelog[version][section][item].content}</div>
</div>
{/each}
</div>
</div>
{/each}
{/each}
{/if}
</div>
</div>
<div class="flex justify-end pt-3 text-sm font-medium">
<button
on:click={() => {
show = false;
}}
class=" px-4 py-2 bg-emerald-600 hover:bg-emerald-700 text-gray-100 transition rounded"
>
<span class="relative">Ok, let's go!</span>
</button>
</div>
</div>
</Modal>

View file

@ -1,7 +1,7 @@
<script lang="ts"> <script lang="ts">
import { getBackendConfig } from '$lib/apis'; import { getBackendConfig } from '$lib/apis';
import { setDefaultPromptSuggestions } from '$lib/apis/configs'; import { setDefaultPromptSuggestions } from '$lib/apis/configs';
import { config, models, user, showWhatsChanged } from '$lib/stores'; import { config, models, user } from '$lib/stores';
import { createEventDispatcher, onMount } from 'svelte'; import { createEventDispatcher, onMount } from 'svelte';
import toast from 'svelte-french-toast'; import toast from 'svelte-french-toast';
const dispatch = createEventDispatcher(); const dispatch = createEventDispatcher();
@ -18,7 +18,6 @@
// Interface // Interface
let promptSuggestions = []; let promptSuggestions = [];
let showUsername = false; let showUsername = false;
let enableWhatsChanged = true;
const toggleFullScreenMode = async () => { const toggleFullScreenMode = async () => {
fullScreenMode = !fullScreenMode; fullScreenMode = !fullScreenMode;
@ -30,14 +29,6 @@
saveSettings({ showUsername: showUsername }); saveSettings({ showUsername: showUsername });
}; };
const toggleenableWhatsChanged = async () => {
enableWhatsChanged = !enableWhatsChanged;
if (enableWhatsChanged) {
showWhatsChanged.update((value) => true);
}
saveSettings({ enableWhatsChanged: enableWhatsChanged });
};
const toggleTitleAutoGenerate = async () => { const toggleTitleAutoGenerate = async () => {
titleAutoGenerate = !titleAutoGenerate; titleAutoGenerate = !titleAutoGenerate;
saveSettings({ titleAutoGenerate: titleAutoGenerate }); saveSettings({ titleAutoGenerate: titleAutoGenerate });
@ -86,7 +77,6 @@
titleAutoGenerate = settings.titleAutoGenerate ?? true; titleAutoGenerate = settings.titleAutoGenerate ?? true;
responseAutoCopy = settings.responseAutoCopy ?? false; responseAutoCopy = settings.responseAutoCopy ?? false;
showUsername = settings.showUsername ?? false; showUsername = settings.showUsername ?? false;
enableWhatsChanged = settings.enableWhatsChanged ?? true;
fullScreenMode = settings.fullScreenMode ?? false; fullScreenMode = settings.fullScreenMode ?? false;
titleAutoGenerateModel = settings.titleAutoGenerateModel ?? ''; titleAutoGenerateModel = settings.titleAutoGenerateModel ?? '';
titleGenerationPrompt = titleGenerationPrompt =
@ -187,25 +177,6 @@
</button> </button>
</div> </div>
</div> </div>
<div>
<div class=" py-0.5 flex w-full justify-between">
<div class=" self-center text-xs font-medium">Show "WhatsChanged" Modal on Startup</div>
<button
class="p-1 px-3 text-xs flex rounded transition"
on:click={() => {
toggleenableWhatsChanged();
}}
type="button"
>
{#if enableWhatsChanged === true}
<span class="ml-2 self-center">On</span>
{:else}
<span class="ml-2 self-center">Off</span>
{/if}
</button>
</div>
</div>
</div> </div>
<hr class=" dark:border-gray-700" /> <hr class=" dark:border-gray-700" />

View file

@ -1,79 +0,0 @@
<script lang="ts">
import Modal from '../common/Modal.svelte';
import { Confetti } from 'svelte-confetti';
import { WEBUI_NAME, WEB_UI_VERSION, RELEASE_NOTES } from '$lib/constants';
import { config, showWhatsChanged } from '$lib/stores';
export let show = false;
function toggleVisibility() {
showWhatsChanged.update((value) => !value);
}
function handleClick() {
toggleVisibility();
}
let hasValidNotes = Array.isArray(RELEASE_NOTES) && RELEASE_NOTES.length > 0;
</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">
{WEBUI_NAME}
<Confetti x={[-1, -0.25]} y={[0, 0.5]} />
</div>
<button class="self-center" on:click={handleClick}>
<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=" pb-3 flex items-center mt-2">
<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">
{$config && $config.version ? $config.version : WEB_UI_VERSION}
</div>
</div>
<hr class=" dark:border-gray-800" />
<div class="p-4 overflow-y-scroll max-h-80">
{#if !hasValidNotes}
<div class="pt-10 text-center font-bold">There are no notes given.</div>
<div class="pb-10 text-center">
Check
<a class="text-blue-500" href="https://github.com/open-webui/open-webui" target="_blank">
Open WebUI on GitHub</a
> for more information.
</div>
{:else}
{#each RELEASE_NOTES as { title, description }}
<div class="mt-4">
<div class="font-bold">{title}</div>
<div>{description}</div>
</div>
{/each}
{/if}
</div>
<div class="m-4 flex justify-end pt-3 text-sm font-medium">
<button
on:click={handleClick}
class=" rounded px-4 py-2 overflow-hidden group bg-green-600 relative hover:bg-gradient-to-r hover:from-green-600 hover:to-green-500 text-white hover:ring-2 hover:ring-offset-2 hover:ring-green-500 transition-all ease-out duration-300"
>
<span
class="absolute right-0 w-8 h-32 -mt-12 transition-all duration-1000 transform translate-x-12 bg-white opacity-10 rotate-12 group-hover:-translate-x-40 ease"
/>
<span class="relative">Ok, let's go!</span>
</button>
</div>
</div>
</Modal>

View file

@ -12,29 +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 RAG_API_BASE_URL = `${WEBUI_BASE_URL}/rag/api/v1`;
export const WEB_UI_VERSION = APP_VERSION; export const WEB_UI_VERSION = APP_VERSION;
export const RELEASE_NOTES = [
{
title: ' 🖼️ Image Generation',
description:
'Generate Images using the stable-difusion-webui API. You can set this up in settings -> images.'
},
{
title: ' 📝 Change title generation prompt',
description:
'Change the promt used to generate titles for your chats. You can set this up in the settings -> interface.'
},
{
title: ' 🤖 Change embedding model',
description:
'Change the embedding model used to generate embeddings for your chats in the Dockerfile. Use any sentence transformer model from huggingface.co.'
},
{
title: ' 📢 This Whats Changed Popup',
description:
'This popup will show you the latest changes. You can edit it in the constants.ts file.'
}
//...
];
export const REQUIRED_OLLAMA_VERSION = '0.1.16'; export const REQUIRED_OLLAMA_VERSION = '0.1.16';
export const SUPPORTED_FILE_TYPE = [ export const SUPPORTED_FILE_TYPE = [

View file

@ -32,16 +32,3 @@ export const documents = writable([
export const settings = writable({}); export const settings = writable({});
export const showSettings = writable(false); export const showSettings = writable(false);
function createLocalStorageStore(key, startValue) {
const storedValue = localStorage.getItem(key);
const initialValue = storedValue ? JSON.parse(storedValue) : startValue;
const store = writable(initialValue);
store.subscribe((value) => {
localStorage.setItem(key, JSON.stringify(value));
});
return store;
}
export const showWhatsChanged = createLocalStorageStore('showWhatsChanged', true);

View file

@ -31,6 +31,7 @@
import ShortcutsModal from '$lib/components/chat/ShortcutsModal.svelte'; import ShortcutsModal from '$lib/components/chat/ShortcutsModal.svelte';
import { getDocs } from '$lib/apis/documents'; import { getDocs } from '$lib/apis/documents';
import { getAllChatTags } from '$lib/apis/chats'; import { getAllChatTags } from '$lib/apis/chats';
import ChangelogModal from '$lib/components/ChangelogModal.svelte';
let ollamaVersion = ''; let ollamaVersion = '';
let loaded = false; let loaded = false;
@ -348,6 +349,8 @@
</div> </div>
</div> </div>
</div> </div>
{:else}
<ChangelogModal show={true} />
{/if} {/if}
<div <div

View file

@ -14,7 +14,6 @@
chats, chats,
chatId, chatId,
config, config,
showWhatsChanged,
tags as _tags tags as _tags
} from '$lib/stores'; } from '$lib/stores';
import { copyToClipboard, splitStream } from '$lib/utils'; import { copyToClipboard, splitStream } from '$lib/utils';
@ -36,7 +35,6 @@
import Messages from '$lib/components/chat/Messages.svelte'; import Messages from '$lib/components/chat/Messages.svelte';
import ModelSelector from '$lib/components/chat/ModelSelector.svelte'; import ModelSelector from '$lib/components/chat/ModelSelector.svelte';
import Navbar from '$lib/components/layout/Navbar.svelte'; import Navbar from '$lib/components/layout/Navbar.svelte';
import WhatsChangedModal from '$lib/components/chat//WhatsChangedModal.svelte';
import { RAGTemplate } from '$lib/utils/rag'; import { RAGTemplate } from '$lib/utils/rag';
let stopResponseFlag = false; let stopResponseFlag = false;
@ -799,9 +797,6 @@
</script> </script>
<div class="h-screen max-h-[100dvh] w-full flex flex-col"> <div class="h-screen max-h-[100dvh] w-full flex flex-col">
{#if $showWhatsChanged && !['pending'].includes($user.role) && $settings.enableWhatsChanged}
<WhatsChangedModal show={true} />
{/if}
<Navbar {title} shareEnabled={messages.length > 0} {initNewChat} {tags} {addTag} {deleteTag} /> <Navbar {title} shareEnabled={messages.length > 0} {initNewChat} {tags} {addTag} {deleteTag} />
<div class="flex flex-col flex-auto"> <div class="flex flex-col flex-auto">
<div <div

View file

@ -5,5 +5,6 @@ export default defineConfig({
plugins: [sveltekit()], plugins: [sveltekit()],
define: { define: {
APP_VERSION: JSON.stringify(process.env.npm_package_version) APP_VERSION: JSON.stringify(process.env.npm_package_version)
} },
assetsInclude: ['**/*.md']
}); });