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)} +
+ 0} + {chat} + {initNewChat} + /> +
+ +
+
+ + +{/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 @@