diff --git a/.github/workflows/build-release.yml b/.github/workflows/build-release.yml new file mode 100644 index 00000000..fa3fa296 --- /dev/null +++ b/.github/workflows/build-release.yml @@ -0,0 +1,49 @@ +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 }} diff --git a/README.md b/README.md index 421a9bd2..c5cc439b 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ [![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) -ChatGPT-Style Web Interface for Ollama 🦙 +User-friendly WebUI for LLMs, Inspired by ChatGPT ![Open WebUI Demo](./demo.gif) diff --git a/backend/apps/images/main.py b/backend/apps/images/main.py index 998af3dd..39d3f96a 100644 --- a/backend/apps/images/main.py +++ b/backend/apps/images/main.py @@ -1,4 +1,4 @@ -import os +import re import requests from fastapi import ( FastAPI, @@ -34,6 +34,7 @@ app.add_middleware( app.state.AUTOMATIC1111_BASE_URL = AUTOMATIC1111_BASE_URL app.state.ENABLED = app.state.AUTOMATIC1111_BASE_URL != "" +app.state.IMAGE_SIZE = "512x512" @app.get("/enabled", response_model=bool) @@ -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") def get_models(user=Depends(get_current_user)): try: @@ -140,7 +168,7 @@ def generate_image( if 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 = { "prompt": form_data.prompt, diff --git a/backend/constants.py b/backend/constants.py index 580db9c5..cb802c7f 100644 --- a/backend/constants.py +++ b/backend/constants.py @@ -44,3 +44,6 @@ class ERROR_MESSAGES(str, Enum): 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." + INCORRECT_FORMAT = ( + lambda err="": f"Invalid format. Please use the correct format{err if err else ''}" + ) diff --git a/src/lib/apis/images/index.ts b/src/lib/apis/images/index.ts index b25499d6..205ee90a 100644 --- a/src/lib/apis/images/index.ts +++ b/src/lib/apis/images/index.ts @@ -131,6 +131,73 @@ export const updateAUTOMATIC1111Url = async (token: string = '', url: string) => 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 = '') => { let error = null; diff --git a/src/lib/components/chat/Messages.svelte b/src/lib/components/chat/Messages.svelte index 64574530..c9e516ab 100644 --- a/src/lib/components/chat/Messages.svelte +++ b/src/lib/components/chat/Messages.svelte @@ -222,6 +222,34 @@ scrollToBottom(); }, 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 }); + }; {#if messages.length == 0} @@ -230,99 +258,103 @@
{#key chatId} {#each messages as message, messageIdx} -
-
- {#if message.role === 'user'} - message.parentId === null) - .map((message) => message.id) ?? []} - {confirmEditMessage} - {showPreviousMessage} - {showNextMessage} - {copyToClipboard} - /> + {#if !message.deleted} +
+
+ {#if message.role === 'user'} + messageDeleteHandler(message.id)} + user={$user} + {message} + isFirstMessage={messageIdx === 0} + siblings={message.parentId !== null + ? history.messages[message.parentId]?.childrenIds ?? [] + : Object.values(history.messages) + .filter((message) => message.parentId === null) + .map((message) => message.id) ?? []} + {confirmEditMessage} + {showPreviousMessage} + {showNextMessage} + {copyToClipboard} + /> - {#if messages.length - 1 === messageIdx && processing !== ''} -
-
-
+
+ + @keyframes spinner_8HQG { + 0%, + 57.14% { + animation-timing-function: cubic-bezier(0.33, 0.66, 0.66, 1); + transform: translate(0); + } + 28.57% { + animation-timing-function: cubic-bezier(0.33, 0, 0.66, 0.33); + transform: translateY(-6px); + } + 100% { + transform: translate(0); + } + } + +
+
+ {processing} +
-
- {processing} -
-
+ {/if} + {:else} + { + console.log('save', e); + + const message = e.detail; + history.messages[message.id] = message; + await updateChatById(localStorage.token, chatId, { + messages: messages, + history: history + }); + }} + /> {/if} - {:else} - { - console.log('save', e); - - const message = e.detail; - history.messages[message.id] = message; - await updateChatById(localStorage.token, chatId, { - messages: messages, - history: history - }); - }} - /> - {/if} +
-
+ {/if} {/each} {#if bottomPadding} diff --git a/src/lib/components/chat/Messages/ResponseMessage.svelte b/src/lib/components/chat/Messages/ResponseMessage.svelte index e9dedc12..5af3a47e 100644 --- a/src/lib/components/chat/Messages/ResponseMessage.svelte +++ b/src/lib/components/chat/Messages/ResponseMessage.svelte @@ -20,6 +20,7 @@ import ProfileImage from './ProfileImage.svelte'; import Skeleton from './Skeleton.svelte'; import CodeBlock from './CodeBlock.svelte'; + import Image from '$lib/components/common/Image.svelte'; export let modelfiles = []; export let message; @@ -46,7 +47,6 @@ let speakingIdx = null; let loadingSpeech = false; - let generatingImage = false; $: tokens = marked.lexer(message.content); @@ -323,7 +323,7 @@ {#each message.files as file}
{#if file.type === 'image'} - input + {/if}
{/each} diff --git a/src/lib/components/chat/Messages/UserMessage.svelte b/src/lib/components/chat/Messages/UserMessage.svelte index 998fe4e4..6f41c5df 100644 --- a/src/lib/components/chat/Messages/UserMessage.svelte +++ b/src/lib/components/chat/Messages/UserMessage.svelte @@ -1,14 +1,17 @@
@@ -189,11 +196,11 @@
{message.content}
-
+
{#if siblings.length > 1}
-
+
{siblings.indexOf(message.id) + 1} / {siblings.length}
+ + {#if !isFirstMessage} + + {/if}
{/if} diff --git a/src/lib/components/chat/Settings/About.svelte b/src/lib/components/chat/Settings/About.svelte index 66514054..d31920f8 100644 --- a/src/lib/components/chat/Settings/About.svelte +++ b/src/lib/components/chat/Settings/About.svelte @@ -22,7 +22,7 @@
- {WEB_UI_VERSION} + v{WEB_UI_VERSION} diff --git a/src/lib/components/common/ImagePreview.svelte b/src/lib/components/common/ImagePreview.svelte new file mode 100644 index 00000000..cf69327f --- /dev/null +++ b/src/lib/components/common/ImagePreview.svelte @@ -0,0 +1,62 @@ + + +{#if show} + + +
+
+
+ +
+ +
+ +
+
+ +
+{/if} diff --git a/src/routes/(app)/+page.svelte b/src/routes/(app)/+page.svelte index 9783ef5d..b81fab59 100644 --- a/src/routes/(app)/+page.svelte +++ b/src/routes/(app)/+page.svelte @@ -334,7 +334,7 @@ content: $settings.system } : undefined, - ...messages + ...messages.filter(message => !message.deleted) ] .filter((message) => message) .map((message, idx, arr) => ({ @@ -540,7 +540,7 @@ content: $settings.system } : undefined, - ...messages + ...messages.filter(message => !message.deleted) ] .filter((message) => message) .map((message, idx, arr) => ({ diff --git a/src/routes/(app)/c/[id]/+page.svelte b/src/routes/(app)/c/[id]/+page.svelte index d68e8582..62fac5ef 100644 --- a/src/routes/(app)/c/[id]/+page.svelte +++ b/src/routes/(app)/c/[id]/+page.svelte @@ -348,7 +348,7 @@ content: $settings.system } : undefined, - ...messages + ...messages.filter((message) => !message.deleted) ] .filter((message) => message) .map((message, idx, arr) => ({ @@ -555,7 +555,7 @@ content: $settings.system } : undefined, - ...messages + ...messages.filter((message) => !message.deleted) ] .filter((message) => message) .map((message, idx, arr) => ({