Unverified Commit ee22e641 authored by Timothy Jaeryang Baek's avatar Timothy Jaeryang Baek Committed by GitHub
Browse files

Merge branch 'main' into litellm

parents 9b6dca3d f69c0d2e
## Pull Request Checklist
- [ ] **Description:** Briefly describe the changes in this pull request.
- [ ] **Changelog:** Ensure a changelog entry following the format of [Keep a Changelog](https://keepachangelog.com/) is added at the bottom of the PR description.
- [ ] **Documentation:** Have you updated relevant documentation?
- [ ] **Dependencies:** Are there any new dependencies? Have you updated the dependency versions in the documentation?
---
## Description
[Insert a brief description of the changes made in this pull request]
---
### Changelog Entry
### Added
- [List any new features or additions]
### Fixed
- [List any fixes or corrections]
### Changed
- [List any changes or updates]
### Removed
- [List any removed features or files]
name: Release
on:
push:
branches:
- main # or whatever branch you want to use
jobs:
release:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v2
- name: Check for changes in package.json
run: |
git diff --cached --diff-filter=d package.json || {
echo "No changes to package.json"
exit 1
}
- name: Get version number from package.json
id: get_version
run: |
VERSION=$(jq -r '.version' package.json)
echo "::set-output name=version::$VERSION"
- name: Create GitHub release
uses: actions/github-script@v5
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const release = await github.rest.repos.createRelease({
owner: context.repo.owner,
repo: context.repo.repo,
tag_name: `v${{ steps.get_version.outputs.version }}`,
name: `v${{ steps.get_version.outputs.version }}`,
body: 'Automatically created new release',
})
console.log(`Created release ${release.data.html_url}`)
- name: Upload package to GitHub release
uses: actions/upload-artifact@v3
with:
name: package
path: .
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# 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 AUTOMATIC1111/stable-diffusion-webui API. You can set this up in Settings > Images.
- **📝 Change title generation prompt**: Change the prompt 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.
## [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.
...@@ -73,6 +73,8 @@ COPY --from=build /app/onnx /root/.cache/chroma/onnx_models/all-MiniLM-L6-v2/onn ...@@ -73,6 +73,8 @@ COPY --from=build /app/onnx /root/.cache/chroma/onnx_models/all-MiniLM-L6-v2/onn
# copy built frontend files # copy built frontend files
COPY --from=build /app/build /app/build 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 files
COPY ./backend . COPY ./backend .
......
...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,7 @@
[![Discord](https://img.shields.io/badge/Discord-Open_WebUI-blue?logo=discord&logoColor=white)](https://discord.gg/5rJgQTnV4s) [![Discord](https://img.shields.io/badge/Discord-Open_WebUI-blue?logo=discord&logoColor=white)](https://discord.gg/5rJgQTnV4s)
[![](https://img.shields.io/static/v1?label=Sponsor&message=%E2%9D%A4&logo=GitHub&color=%23fe8e86)](https://github.com/sponsors/tjbck) [![](https://img.shields.io/static/v1?label=Sponsor&message=%E2%9D%A4&logo=GitHub&color=%23fe8e86)](https://github.com/sponsors/tjbck)
ChatGPT-Style Web Interface for Ollama 🦙 User-friendly WebUI for LLMs, supported LLM runners include Ollama and OpenAI-compatible APIs.
![Open WebUI Demo](./demo.gif) ![Open WebUI Demo](./demo.gif)
...@@ -286,6 +286,8 @@ cp -RPp .env.example .env ...@@ -286,6 +286,8 @@ cp -RPp .env.example .env
# Building Frontend Using Node # Building Frontend Using Node
npm i npm i
npm run build npm run build
# or for development (hot reload)
# npm run dev
# or Building Frontend Using Bun # or Building Frontend Using Bun
# bun install # bun install
...@@ -295,6 +297,9 @@ npm run build ...@@ -295,6 +297,9 @@ npm run build
cd ./backend cd ./backend
pip install -r requirements.txt -U pip install -r requirements.txt -U
sh start.sh sh start.sh
# or for development (hot reload)
# npm run build must have been run once before!
# sh dev.sh
``` ```
You should have Open WebUI up and running at http://localhost:8080/. Enjoy! 😄 You should have Open WebUI up and running at http://localhost:8080/. Enjoy! 😄
......
import os import re
import requests import requests
from fastapi import ( from fastapi import (
FastAPI, FastAPI,
...@@ -34,6 +34,7 @@ app.add_middleware( ...@@ -34,6 +34,7 @@ app.add_middleware(
app.state.AUTOMATIC1111_BASE_URL = AUTOMATIC1111_BASE_URL app.state.AUTOMATIC1111_BASE_URL = AUTOMATIC1111_BASE_URL
app.state.ENABLED = app.state.AUTOMATIC1111_BASE_URL != "" app.state.ENABLED = app.state.AUTOMATIC1111_BASE_URL != ""
app.state.IMAGE_SIZE = "512x512"
@app.get("/enabled", response_model=bool) @app.get("/enabled", response_model=bool)
...@@ -74,6 +75,33 @@ async def update_openai_url(form_data: UrlUpdateForm, user=Depends(get_admin_use ...@@ -74,6 +75,33 @@ async def update_openai_url(form_data: UrlUpdateForm, user=Depends(get_admin_use
} }
class ImageSizeUpdateForm(BaseModel):
size: str
@app.get("/size")
async def get_image_size(user=Depends(get_admin_user)):
return {"IMAGE_SIZE": app.state.IMAGE_SIZE}
@app.post("/size/update")
async def update_image_size(
form_data: ImageSizeUpdateForm, user=Depends(get_admin_user)
):
pattern = r"^\d+x\d+$" # Regular expression pattern
if re.match(pattern, form_data.size):
app.state.IMAGE_SIZE = form_data.size
return {
"IMAGE_SIZE": app.state.IMAGE_SIZE,
"status": True,
}
else:
raise HTTPException(
status_code=400,
detail=ERROR_MESSAGES.INCORRECT_FORMAT(" (e.g., 512x512)."),
)
@app.get("/models") @app.get("/models")
def get_models(user=Depends(get_current_user)): def get_models(user=Depends(get_current_user)):
try: try:
...@@ -140,7 +168,7 @@ def generate_image( ...@@ -140,7 +168,7 @@ def generate_image(
if form_data.model: if form_data.model:
set_model_handler(form_data.model) set_model_handler(form_data.model)
width, height = tuple(map(int, form_data.size.split("x"))) width, height = tuple(map(int, app.state.IMAGE_SIZE.split("x")))
data = { data = {
"prompt": form_data.prompt, "prompt": form_data.prompt,
......
...@@ -423,7 +423,7 @@ def get_loader(filename: str, file_content_type: str, file_path: str): ...@@ -423,7 +423,7 @@ def get_loader(filename: str, file_content_type: str, file_path: str):
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
] or file_ext in ["xls", "xlsx"]: ] or file_ext in ["xls", "xlsx"]:
loader = UnstructuredExcelLoader(file_path) loader = UnstructuredExcelLoader(file_path)
elif file_ext in known_source_ext or file_content_type.find("text/") >= 0: elif file_ext in known_source_ext or (file_content_type and file_content_type.find("text/") >= 0):
loader = TextLoader(file_path) loader = TextLoader(file_path)
else: else:
loader = TextLoader(file_path) loader = TextLoader(file_path)
...@@ -486,8 +486,8 @@ def store_doc( ...@@ -486,8 +486,8 @@ def store_doc(
@app.get("/scan") @app.get("/scan")
def scan_docs_dir(user=Depends(get_admin_user)): def scan_docs_dir(user=Depends(get_admin_user)):
try:
for path in Path(DOCS_DIR).rglob("./**/*"): for path in Path(DOCS_DIR).rglob("./**/*"):
try:
if path.is_file() and not path.name.startswith("."): if path.is_file() and not path.name.startswith("."):
tags = extract_folders_after_data_docs(path) tags = extract_folders_after_data_docs(path)
filename = path.name filename = path.name
......
import os import os
import chromadb import chromadb
from chromadb import Settings from chromadb import Settings
from secrets import token_bytes
from base64 import b64encode from base64 import b64encode
from constants import ERROR_MESSAGES from bs4 import BeautifulSoup
from pathlib import Path from pathlib import Path
import json import json
import markdown
import requests
import shutil
from secrets import token_bytes
from constants import ERROR_MESSAGES
try: try:
...@@ -15,6 +21,8 @@ try: ...@@ -15,6 +21,8 @@ try:
except ImportError: except ImportError:
print("dotenv not installed, skipping...") print("dotenv not installed, skipping...")
WEBUI_NAME = "Open WebUI"
shutil.copyfile("../build/favicon.png", "./static/favicon.png")
#################################### ####################################
# ENV (dev,test,prod) # ENV (dev,test,prod)
...@@ -22,6 +30,104 @@ except ImportError: ...@@ -22,6 +30,104 @@ 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"]
# 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
####################################
# CUSTOM_NAME
####################################
CUSTOM_NAME = os.environ.get("CUSTOM_NAME", "")
if CUSTOM_NAME:
try:
r = requests.get(f"https://api.openwebui.com/api/v1/custom/{CUSTOM_NAME}")
data = r.json()
if r.ok:
if "logo" in data:
url = (
f"https://api.openwebui.com{data['logo']}"
if data["logo"][0] == "/"
else data["logo"]
)
r = requests.get(url, stream=True)
if r.status_code == 200:
with open("./static/favicon.png", "wb") as f:
r.raw.decode_content = True
shutil.copyfileobj(r.raw, f)
WEBUI_NAME = data["name"]
except Exception as e:
print(e)
pass
#################################### ####################################
# DATA/FRONTEND BUILD DIR # DATA/FRONTEND BUILD DIR
...@@ -116,7 +222,7 @@ DEFAULT_PROMPT_SUGGESTIONS = ( ...@@ -116,7 +222,7 @@ DEFAULT_PROMPT_SUGGESTIONS = (
) )
DEFAULT_USER_ROLE = "pending" DEFAULT_USER_ROLE = os.getenv("DEFAULT_USER_ROLE", "pending")
USER_PERMISSIONS = {"chat": {"deletion": True}} USER_PERMISSIONS = {"chat": {"deletion": True}}
......
...@@ -44,3 +44,6 @@ class ERROR_MESSAGES(str, Enum): ...@@ -44,3 +44,6 @@ class ERROR_MESSAGES(str, Enum):
MALICIOUS = "Unusual activities detected, please try again in a few minutes." MALICIOUS = "Unusual activities detected, please try again in a few minutes."
PANDOC_NOT_INSTALLED = "Pandoc is not installed on the server. Please contact your administrator for assistance." PANDOC_NOT_INSTALLED = "Pandoc is not installed on the server. Please contact your administrator for assistance."
INCORRECT_FORMAT = (
lambda err="": f"Invalid format. Please use the correct format{err if err else ''}"
)
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
...@@ -18,7 +22,7 @@ from apps.rag.main import app as rag_app ...@@ -18,7 +22,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 WEBUI_NAME, ENV, VERSION, CHANGELOG, FRONTEND_BUILD_DIR
class SPAStaticFiles(StaticFiles): class SPAStaticFiles(StaticFiles):
...@@ -69,14 +73,25 @@ app.mount("/rag/api/v1", rag_app) ...@@ -69,14 +73,25 @@ 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,
"name": WEBUI_NAME,
"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,
} }
@app.get("/api/changelog")
async def get_app_changelog():
return CHANGELOG
app.mount("/static", StaticFiles(directory="static"), name="static")
app.mount( app.mount(
"/", "/",
SPAStaticFiles(directory=FRONTEND_BUILD_DIR, html=True), SPAStaticFiles(directory=FRONTEND_BUILD_DIR, html=True),
......
No preview for this file type
...@@ -4,7 +4,7 @@ metadata: ...@@ -4,7 +4,7 @@ metadata:
labels: labels:
app: ollama-webui app: ollama-webui
name: ollama-webui-pvc name: ollama-webui-pvc
namespace: ollama-namespace namespace: open-webui
spec: spec:
accessModes: ["ReadWriteOnce"] accessModes: ["ReadWriteOnce"]
resources: resources:
......
...@@ -5,6 +5,7 @@ resources: ...@@ -5,6 +5,7 @@ resources:
- base/webui-deployment.yaml - base/webui-deployment.yaml
- base/webui-service.yaml - base/webui-service.yaml
- base/webui-ingress.yaml - base/webui-ingress.yaml
- base/webui-pvc.yaml
apiVersion: kustomize.config.k8s.io/v1beta1 apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization kind: Kustomization
......
{ {
"name": "open-webui", "name": "open-webui",
"version": "0.0.1", "version": "v1.0.0-alpha.101",
"lockfileVersion": 2, "lockfileVersion": 2,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "open-webui", "name": "open-webui",
"version": "0.0.1", "version": "v1.0.0-alpha.101",
"dependencies": { "dependencies": {
"@sveltejs/adapter-node": "^1.3.1", "@sveltejs/adapter-node": "^1.3.1",
"async": "^3.2.5", "async": "^3.2.5",
...@@ -38,6 +38,7 @@ ...@@ -38,6 +38,7 @@
"prettier-plugin-svelte": "^2.10.1", "prettier-plugin-svelte": "^2.10.1",
"svelte": "^4.0.5", "svelte": "^4.0.5",
"svelte-check": "^3.4.3", "svelte-check": "^3.4.3",
"svelte-confetti": "^1.3.2",
"tailwindcss": "^3.3.3", "tailwindcss": "^3.3.3",
"tslib": "^2.4.1", "tslib": "^2.4.1",
"typescript": "^5.0.0", "typescript": "^5.0.0",
...@@ -3174,6 +3175,15 @@ ...@@ -3174,6 +3175,15 @@
"svelte": "^3.55.0 || ^4.0.0-next.0 || ^4.0.0" "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": { "node_modules/svelte-eslint-parser": {
"version": "0.33.1", "version": "0.33.1",
"resolved": "https://registry.npmjs.org/svelte-eslint-parser/-/svelte-eslint-parser-0.33.1.tgz", "resolved": "https://registry.npmjs.org/svelte-eslint-parser/-/svelte-eslint-parser-0.33.1.tgz",
...@@ -5852,6 +5862,13 @@ ...@@ -5852,6 +5862,13 @@
"typescript": "^5.0.3" "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": { "svelte-eslint-parser": {
"version": "0.33.1", "version": "0.33.1",
"resolved": "https://registry.npmjs.org/svelte-eslint-parser/-/svelte-eslint-parser-0.33.1.tgz", "resolved": "https://registry.npmjs.org/svelte-eslint-parser/-/svelte-eslint-parser-0.33.1.tgz",
......
{ {
"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",
...@@ -32,6 +32,7 @@ ...@@ -32,6 +32,7 @@
"prettier-plugin-svelte": "^2.10.1", "prettier-plugin-svelte": "^2.10.1",
"svelte": "^4.0.5", "svelte": "^4.0.5",
"svelte-check": "^3.4.3", "svelte-check": "^3.4.3",
"svelte-confetti": "^1.3.2",
"tailwindcss": "^3.3.3", "tailwindcss": "^3.3.3",
"tslib": "^2.4.1", "tslib": "^2.4.1",
"typescript": "^5.0.0", "typescript": "^5.0.0",
......
...@@ -131,6 +131,73 @@ export const updateAUTOMATIC1111Url = async (token: string = '', url: string) => ...@@ -131,6 +131,73 @@ export const updateAUTOMATIC1111Url = async (token: string = '', url: string) =>
return res.AUTOMATIC1111_BASE_URL; return res.AUTOMATIC1111_BASE_URL;
}; };
export const getImageSize = async (token: string = '') => {
let error = null;
const res = await fetch(`${IMAGES_API_BASE_URL}/size`, {
method: 'GET',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
...(token && { authorization: `Bearer ${token}` })
}
})
.then(async (res) => {
if (!res.ok) throw await res.json();
return res.json();
})
.catch((err) => {
console.log(err);
if ('detail' in err) {
error = err.detail;
} else {
error = 'Server connection failed';
}
return null;
});
if (error) {
throw error;
}
return res.IMAGE_SIZE;
};
export const updateImageSize = async (token: string = '', size: string) => {
let error = null;
const res = await fetch(`${IMAGES_API_BASE_URL}/size/update`, {
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
...(token && { authorization: `Bearer ${token}` })
},
body: JSON.stringify({
size: size
})
})
.then(async (res) => {
if (!res.ok) throw await res.json();
return res.json();
})
.catch((err) => {
console.log(err);
if ('detail' in err) {
error = err.detail;
} else {
error = 'Server connection failed';
}
return null;
});
if (error) {
throw error;
}
return res.IMAGE_SIZE;
};
export const getDiffusionModels = async (token: string = '') => { export const getDiffusionModels = async (token: string = '') => {
let error = null; let error = null;
......
...@@ -21,3 +21,25 @@ export const getBackendConfig = async () => { ...@@ -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;
};
<script lang="ts">
import { onMount } from 'svelte';
import { Confetti } from 'svelte-confetti';
import { WEBUI_NAME, config } from '$lib/stores';
import { WEBUI_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{WEBUI_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>
...@@ -222,6 +222,34 @@ ...@@ -222,6 +222,34 @@
scrollToBottom(); scrollToBottom();
}, 100); }, 100);
}; };
// TODO: change delete behaviour
// const deleteMessageAndDescendants = async (messageId: string) => {
// if (history.messages[messageId]) {
// history.messages[messageId].deleted = true;
// for (const childId of history.messages[messageId].childrenIds) {
// await deleteMessageAndDescendants(childId);
// }
// }
// };
// const triggerDeleteMessageRecursive = async (messageId: string) => {
// await deleteMessageAndDescendants(messageId);
// await updateChatById(localStorage.token, chatId, { history });
// await chats.set(await getChatList(localStorage.token));
// };
const messageDeleteHandler = async (messageId) => {
if (history.messages[messageId]) {
history.messages[messageId].deleted = true;
for (const childId of history.messages[messageId].childrenIds) {
history.messages[childId].deleted = true;
}
}
await updateChatById(localStorage.token, chatId, { history });
};
</script> </script>
{#if messages.length == 0} {#if messages.length == 0}
...@@ -230,6 +258,7 @@ ...@@ -230,6 +258,7 @@
<div class=" pb-10"> <div class=" pb-10">
{#key chatId} {#key chatId}
{#each messages as message, messageIdx} {#each messages as message, messageIdx}
{#if !message.deleted}
<div class=" w-full"> <div class=" w-full">
<div <div
class="flex flex-col justify-between px-5 mb-3 {$settings?.fullScreenMode ?? null class="flex flex-col justify-between px-5 mb-3 {$settings?.fullScreenMode ?? null
...@@ -238,8 +267,10 @@ ...@@ -238,8 +267,10 @@
> >
{#if message.role === 'user'} {#if message.role === 'user'}
<UserMessage <UserMessage
on:delete={() => messageDeleteHandler(message.id)}
user={$user} user={$user}
{message} {message}
isFirstMessage={messageIdx === 0}
siblings={message.parentId !== null siblings={message.parentId !== null
? history.messages[message.parentId]?.childrenIds ?? [] ? history.messages[message.parentId]?.childrenIds ?? []
: Object.values(history.messages) : Object.values(history.messages)
...@@ -323,6 +354,7 @@ ...@@ -323,6 +354,7 @@
{/if} {/if}
</div> </div>
</div> </div>
{/if}
{/each} {/each}
{#if bottomPadding} {#if bottomPadding}
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment