diff --git a/.github/workflows/docker-build.yaml b/.github/workflows/docker-build.yaml
index e14a5d8e6..b5dd72192 100644
--- a/.github/workflows/docker-build.yaml
+++ b/.github/workflows/docker-build.yaml
@@ -63,6 +63,16 @@ jobs:
flavor: |
latest=${{ github.ref == 'refs/heads/main' }}
+ - name: Extract metadata for Docker cache
+ id: cache-meta
+ uses: docker/metadata-action@v5
+ with:
+ images: ${{ env.FULL_IMAGE_NAME }}
+ tags: |
+ type=ref,event=branch
+ flavor: |
+ prefix=cache-${{ matrix.platform }}-
+
- name: Build Docker image (latest)
uses: docker/build-push-action@v5
id: build
@@ -72,8 +82,8 @@ jobs:
platforms: ${{ matrix.platform }}
labels: ${{ steps.meta.outputs.labels }}
outputs: type=image,name=${{ env.FULL_IMAGE_NAME }},push-by-digest=true,name-canonical=true,push=true
- cache-from: type=gha
- cache-to: type=gha,mode=max
+ cache-from: type=registry,ref=${{ steps.cache-meta.outputs.tags }}
+ cache-to: type=registry,ref=${{ steps.cache-meta.outputs.tags }},mode=max
- name: Export digest
run: |
@@ -123,7 +133,7 @@ jobs:
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- - name: Extract metadata for Docker images (default latest tag)
+ - name: Extract metadata for Docker images (cuda tag)
id: meta
uses: docker/metadata-action@v5
with:
@@ -139,6 +149,16 @@ jobs:
latest=${{ github.ref == 'refs/heads/main' }}
suffix=-cuda,onlatest=true
+ - name: Extract metadata for Docker cache
+ id: cache-meta
+ uses: docker/metadata-action@v5
+ with:
+ images: ${{ env.FULL_IMAGE_NAME }}
+ tags: |
+ type=ref,event=branch
+ flavor: |
+ prefix=cache-cuda-${{ matrix.platform }}-
+
- name: Build Docker image (cuda)
uses: docker/build-push-action@v5
id: build
@@ -148,8 +168,8 @@ jobs:
platforms: ${{ matrix.platform }}
labels: ${{ steps.meta.outputs.labels }}
outputs: type=image,name=${{ env.FULL_IMAGE_NAME }},push-by-digest=true,name-canonical=true,push=true
- cache-from: type=gha
- cache-to: type=gha,mode=max
+ cache-from: type=registry,ref=${{ steps.cache-meta.outputs.tags }}
+ cache-to: type=registry,ref=${{ steps.cache-meta.outputs.tags }},mode=max
build-args: USE_CUDA=true
- name: Export digest
@@ -216,6 +236,16 @@ jobs:
latest=${{ github.ref == 'refs/heads/main' }}
suffix=-ollama,onlatest=true
+ - name: Extract metadata for Docker cache
+ id: cache-meta
+ uses: docker/metadata-action@v5
+ with:
+ images: ${{ env.FULL_IMAGE_NAME }}
+ tags: |
+ type=ref,event=branch
+ flavor: |
+ prefix=cache-ollama-${{ matrix.platform }}-
+
- name: Build Docker image (ollama)
uses: docker/build-push-action@v5
id: build
@@ -225,8 +255,8 @@ jobs:
platforms: ${{ matrix.platform }}
labels: ${{ steps.meta.outputs.labels }}
outputs: type=image,name=${{ env.FULL_IMAGE_NAME }},push-by-digest=true,name-canonical=true,push=true
- cache-from: type=gha
- cache-to: type=gha,mode=max
+ cache-from: type=registry,ref=${{ steps.cache-meta.outputs.tags }}
+ cache-to: type=registry,ref=${{ steps.cache-meta.outputs.tags }},mode=max
build-args: USE_OLLAMA=true
- name: Export digest
diff --git a/.github/workflows/integration-test.yml b/.github/workflows/integration-test.yml
index 32346d3b9..96ba50289 100644
--- a/.github/workflows/integration-test.yml
+++ b/.github/workflows/integration-test.yml
@@ -20,7 +20,11 @@ jobs:
- name: Build and run Compose Stack
run: |
- docker compose --file docker-compose.yaml --file docker-compose.api.yaml up --detach --build
+ docker compose \
+ --file docker-compose.yaml \
+ --file docker-compose.api.yaml \
+ --file docker-compose.a1111-test.yaml \
+ up --detach --build
- name: Wait for Ollama to be up
timeout-minutes: 5
diff --git a/backend/apps/rag/main.py b/backend/apps/rag/main.py
index 9a1a0c13e..f08d81a3b 100644
--- a/backend/apps/rag/main.py
+++ b/backend/apps/rag/main.py
@@ -28,6 +28,7 @@ from langchain_community.document_loaders import (
UnstructuredXMLLoader,
UnstructuredRSTLoader,
UnstructuredExcelLoader,
+ UnstructuredPowerPointLoader,
YoutubeLoader,
)
from langchain.text_splitter import RecursiveCharacterTextSplitter
@@ -768,6 +769,11 @@ def get_loader(filename: str, file_content_type: str, file_path: str):
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
] or file_ext in ["xls", "xlsx"]:
loader = UnstructuredExcelLoader(file_path)
+ elif file_content_type in [
+ "application/vnd.ms-powerpoint",
+ "application/vnd.openxmlformats-officedocument.presentationml.presentation",
+ ] or file_ext in ["ppt", "pptx"]:
+ loader = UnstructuredPowerPointLoader(file_path)
elif file_ext in known_source_ext or (
file_content_type and file_content_type.find("text/") >= 0
):
diff --git a/backend/requirements.txt b/backend/requirements.txt
index c8b699447..a82da1966 100644
--- a/backend/requirements.txt
+++ b/backend/requirements.txt
@@ -35,6 +35,7 @@ chromadb==0.4.24
sentence-transformers==2.7.0
pypdf==4.2.0
docx2txt==0.8
+python-pptx==0.6.23
unstructured==0.11.8
Markdown==3.6
pypandoc==1.13
diff --git a/cypress/e2e/chat.cy.ts b/cypress/e2e/chat.cy.ts
index ced998104..ddb33d6c0 100644
--- a/cypress/e2e/chat.cy.ts
+++ b/cypress/e2e/chat.cy.ts
@@ -74,5 +74,28 @@ describe('Settings', () => {
expect(spy).to.be.callCount(2);
});
});
+
+ it('user can generate image', () => {
+ // Click on the model selector
+ cy.get('button[aria-label="Select a model"]').click();
+ // Select the first model
+ cy.get('button[aria-label="model-item"]').first().click();
+ // Type a message
+ cy.get('#chat-textarea').type('Hi, what can you do? A single sentence only please.', {
+ force: true
+ });
+ // Send the message
+ cy.get('button[type="submit"]').click();
+ // User's message should be visible
+ cy.get('.chat-user').should('exist');
+ // Wait for the response
+ cy.get('.chat-assistant', { timeout: 120_000 }) // .chat-assistant is created after the first token is received
+ .find('div[aria-label="Generation Info"]', { timeout: 120_000 }) // Generation Info is created after the stop token is received
+ .should('exist');
+ // Click on the generate image button
+ cy.get('[aria-label="Generate Image"]').click();
+ // Wait for image to be visible
+ cy.get('img[data-cy="image"]', { timeout: 60_000 }).should('be.visible');
+ });
});
});
diff --git a/docker-compose.a1111-test.yaml b/docker-compose.a1111-test.yaml
new file mode 100644
index 000000000..e6ab12c07
--- /dev/null
+++ b/docker-compose.a1111-test.yaml
@@ -0,0 +1,31 @@
+# This is an overlay that spins up stable-diffusion-webui for integration testing
+# This is not designed to be used in production
+services:
+ stable-diffusion-webui:
+ # Not built for ARM64
+ platform: linux/amd64
+ image: ghcr.io/neggles/sd-webui-docker:latest
+ restart: unless-stopped
+ environment:
+ CLI_ARGS: "--api --use-cpu all --precision full --no-half --skip-torch-cuda-test --ckpt /empty.pt --do-not-download-clip --disable-nan-check --disable-opt-split-attention"
+ PYTHONUNBUFFERED: "1"
+ TERM: "vt100"
+ SD_WEBUI_VARIANT: "default"
+ # Hack to get container working on Apple Silicon
+ # Rosetta creates a conflict ${HOME}/.cache folder
+ entrypoint: /bin/bash
+ command:
+ - -c
+ - |
+ export HOME=/root-home
+ rm -rf $${HOME}/.cache
+ /docker/entrypoint.sh python -u webui.py --listen --port $${WEBUI_PORT} --skip-version-check $${CLI_ARGS}
+ volumes:
+ - ./test/test_files/image_gen/sd-empty.pt:/empty.pt
+
+ open-webui:
+ environment:
+ ENABLE_IMAGE_GENERATION: "true"
+ AUTOMATIC1111_BASE_URL: http://stable-diffusion-webui:7860
+ IMAGE_SIZE: "64x64"
+ IMAGE_STEPS: "3"
diff --git a/src/lib/components/chat/Chat.svelte b/src/lib/components/chat/Chat.svelte
new file mode 100644
index 000000000..2465b53cd
--- /dev/null
+++ b/src/lib/components/chat/Chat.svelte
@@ -0,0 +1,1075 @@
+
+
+
+
+ {title
+ ? `${title.length > 30 ? `${title.slice(0, 30)}...` : title} | ${$WEBUI_NAME}`
+ : `${$WEBUI_NAME}`}
+
+
+
+{#if !chatIdProp || (loaded && chatIdProp)}
+
+
+
+{/if}
diff --git a/src/lib/components/chat/Messages/CodeBlock.svelte b/src/lib/components/chat/Messages/CodeBlock.svelte
index 4131fbd85..5881d109e 100644
--- a/src/lib/components/chat/Messages/CodeBlock.svelte
+++ b/src/lib/components/chat/Messages/CodeBlock.svelte
@@ -101,7 +101,7 @@
try {
const micropip = pyodide.pyimport('micropip');
- await micropip.set_index_urls('https://pypi.org/pypi/{package_name}/json');
+ // await micropip.set_index_urls('https://pypi.org/pypi/{package_name}/json');
let packages = [
code.includes('requests') ? 'requests' : null,
@@ -213,7 +213,7 @@ __builtins__.input = input`);
{@html lang}
- {#if ['', 'python'].includes(lang) && (lang === 'python' || checkPythonCode(code))}
+ {#if lang === 'python' || (lang === '' && checkPythonCode(code))}
{#if executing}
Running
{:else}
diff --git a/src/lib/components/chat/Messages/CompareMessages.svelte b/src/lib/components/chat/Messages/CompareMessages.svelte
index 87c3c88d9..60efdb2ab 100644
--- a/src/lib/components/chat/Messages/CompareMessages.svelte
+++ b/src/lib/components/chat/Messages/CompareMessages.svelte
@@ -41,6 +41,44 @@
};
}, {});
+ const showPreviousMessage = (model) => {
+ groupedMessagesIdx[model] = Math.max(0, groupedMessagesIdx[model] - 1);
+ let messageId = groupedMessages[model].messages[groupedMessagesIdx[model]].id;
+
+ console.log(messageId);
+ let messageChildrenIds = history.messages[messageId].childrenIds;
+
+ while (messageChildrenIds.length !== 0) {
+ messageId = messageChildrenIds.at(-1);
+ messageChildrenIds = history.messages[messageId].childrenIds;
+ }
+
+ history.currentId = messageId;
+
+ dispatch('change');
+ };
+
+ const showNextMessage = (model) => {
+ groupedMessagesIdx[model] = Math.min(
+ groupedMessages[model].messages.length - 1,
+ groupedMessagesIdx[model] + 1
+ );
+
+ let messageId = groupedMessages[model].messages[groupedMessagesIdx[model]].id;
+ console.log(messageId);
+
+ let messageChildrenIds = history.messages[messageId].childrenIds;
+
+ while (messageChildrenIds.length !== 0) {
+ messageId = messageChildrenIds.at(-1);
+ messageChildrenIds = history.messages[messageId].childrenIds;
+ }
+
+ history.currentId = messageId;
+
+ dispatch('change');
+ };
+
onMount(async () => {
await tick();
currentMessageId = messages[messageIdx].id;
@@ -97,42 +135,8 @@
isLastMessage={true}
{updateChatMessages}
{confirmEditResponseMessage}
- showPreviousMessage={() => {
- groupedMessagesIdx[model] = Math.max(0, groupedMessagesIdx[model] - 1);
- let messageId = groupedMessages[model].messages[groupedMessagesIdx[model]].id;
-
- console.log(messageId);
- let messageChildrenIds = history.messages[messageId].childrenIds;
-
- while (messageChildrenIds.length !== 0) {
- messageId = messageChildrenIds.at(-1);
- messageChildrenIds = history.messages[messageId].childrenIds;
- }
-
- history.currentId = messageId;
-
- dispatch('change');
- }}
- showNextMessage={() => {
- groupedMessagesIdx[model] = Math.min(
- groupedMessages[model].messages.length - 1,
- groupedMessagesIdx[model] + 1
- );
-
- let messageId = groupedMessages[model].messages[groupedMessagesIdx[model]].id;
- console.log(messageId);
-
- let messageChildrenIds = history.messages[messageId].childrenIds;
-
- while (messageChildrenIds.length !== 0) {
- messageId = messageChildrenIds.at(-1);
- messageChildrenIds = history.messages[messageId].childrenIds;
- }
-
- history.currentId = messageId;
-
- dispatch('change');
- }}
+ showPreviousMessage={() => showPreviousMessage(model)}
+ showNextMessage={() => showNextMessage(model)}
{rateMessage}
{copyToClipboard}
{continueGeneration}
diff --git a/src/lib/components/chat/Messages/ProfileImage.svelte b/src/lib/components/chat/Messages/ProfileImage.svelte
index 80bdea2c7..44f3b5fce 100644
--- a/src/lib/components/chat/Messages/ProfileImage.svelte
+++ b/src/lib/components/chat/Messages/ProfileImage.svelte
@@ -10,7 +10,8 @@
crossorigin="anonymous"
src={src.startsWith(WEBUI_BASE_URL) ||
src.startsWith('https://www.gravatar.com/avatar/') ||
- src.startsWith('data:')
+ src.startsWith('data:') ||
+ src.startsWith('/')
? src
: `/user.png`}
class=" w-8 object-cover rounded-full"
diff --git a/src/lib/components/chat/Messages/ResponseMessage.svelte b/src/lib/components/chat/Messages/ResponseMessage.svelte
index 581441977..e1dabc2b6 100644
--- a/src/lib/components/chat/Messages/ResponseMessage.svelte
+++ b/src/lib/components/chat/Messages/ResponseMessage.svelte
@@ -391,7 +391,7 @@