diff --git a/.config/playwright-setup.js b/.config/playwright-setup.js index 8ce06d653840..c0832966806a 100644 --- a/.config/playwright-setup.js +++ b/.config/playwright-setup.js @@ -132,8 +132,8 @@ ${demos.map((d) => `from demo.${d}.run import demo as ${d}`).join("\n")} app = FastAPI() ${demos - .map((d) => `app = gr.mount_gradio_app(app, ${d}, path="/${d}")`) - .join("\n")} + .map((d) => `app = gr.mount_gradio_app(app, ${d}, path="/${d}")`) + .join("\n")} config = uvicorn.Config(app, port=${port}, log_level="info") server = uvicorn.Server(config=config) diff --git a/demo/chatbot_multimodal/run.ipynb b/demo/chatbot_multimodal/run.ipynb index 8130dab1c330..d4ed8aa95814 100644 --- a/demo/chatbot_multimodal/run.ipynb +++ b/demo/chatbot_multimodal/run.ipynb @@ -1 +1 @@ -{"cells": [{"cell_type": "markdown", "id": 302934307671667531413257853548643485645, "metadata": {}, "source": ["# Gradio Demo: chatbot_multimodal"]}, {"cell_type": "code", "execution_count": null, "id": 272996653310673477252411125948039410165, "metadata": {}, "outputs": [], "source": ["!pip install -q gradio "]}, {"cell_type": "code", "execution_count": null, "id": 288918539441861185822528903084949547379, "metadata": {}, "outputs": [], "source": ["import gradio as gr\n", "\n", "\n", "def add_text(history, text):\n", " history = history + [(text, None)]\n", " return history, gr.update(value=\"\", interactive=False)\n", "\n", "\n", "def add_file(history, file):\n", " history = history + [((file.name,), None)]\n", " return history\n", "\n", "\n", "def bot(history):\n", " response = \"**That's cool!**\"\n", " history[-1][1] = response\n", " return history\n", "\n", "\n", "with gr.Blocks() as demo:\n", " chatbot = gr.Chatbot([], elem_id=\"chatbot\").style(height=750)\n", "\n", " with gr.Row():\n", " with gr.Column(scale=0.85):\n", " txt = gr.Textbox(\n", " show_label=False,\n", " placeholder=\"Enter text and press enter, or upload an image\",\n", " ).style(container=False)\n", " with gr.Column(scale=0.15, min_width=0):\n", " btn = gr.UploadButton(\"\ud83d\udcc1\", file_types=[\"image\", \"video\", \"audio\"])\n", "\n", " txt_msg = txt.submit(add_text, [chatbot, txt], [chatbot, txt], queue=False).then(\n", " bot, chatbot, chatbot\n", " )\n", " txt_msg.then(lambda: gr.update(interactive=True), None, [txt], queue=False)\n", " file_msg = btn.upload(add_file, [chatbot, btn], [chatbot], queue=False).then(\n", " bot, chatbot, chatbot\n", " )\n", "\n", "if __name__ == \"__main__\":\n", " demo.launch()\n"]}], "metadata": {}, "nbformat": 4, "nbformat_minor": 5} \ No newline at end of file +{"cells": [{"cell_type": "markdown", "id": 302934307671667531413257853548643485645, "metadata": {}, "source": ["# Gradio Demo: chatbot_multimodal"]}, {"cell_type": "code", "execution_count": null, "id": 272996653310673477252411125948039410165, "metadata": {}, "outputs": [], "source": ["!pip install -q gradio "]}, {"cell_type": "code", "execution_count": null, "id": 288918539441861185822528903084949547379, "metadata": {}, "outputs": [], "source": ["import gradio as gr\n", "import random\n", "import time\n", "\n", "# Chatbot demo with multimodal input (text, markdown, LaTeX, code blocks, image, audio, & video). Plus shows support for streaming text.\n", "\n", "def add_text(history, text):\n", " history = history + [(text, None)]\n", " return history, gr.update(value=\"\", interactive=False)\n", "\n", "\n", "def add_file(history, file):\n", " history = history + [((file.name,), None)]\n", " return history\n", "\n", "\n", "def bot(history):\n", " response = \"**That's cool!**\"\n", " history[-1][1] = \"\"\n", " for character in response:\n", " history[-1][1] += character\n", " time.sleep(0.05)\n", " yield history\n", "\n", "\n", "with gr.Blocks() as demo:\n", " chatbot = gr.Chatbot([], elem_id=\"chatbot\").style(height=750)\n", "\n", " with gr.Row():\n", " with gr.Column(scale=0.85):\n", " txt = gr.Textbox(\n", " show_label=False,\n", " placeholder=\"Enter text and press enter, or upload an image\",\n", " ).style(container=False)\n", " with gr.Column(scale=0.15, min_width=0):\n", " btn = gr.UploadButton(\"\ud83d\udcc1\", file_types=[\"image\", \"video\", \"audio\"])\n", "\n", " txt_msg = txt.submit(add_text, [chatbot, txt], [chatbot, txt], queue=False).then(\n", " bot, chatbot, chatbot\n", " )\n", " txt_msg.then(lambda: gr.update(interactive=True), None, [txt], queue=False)\n", " file_msg = btn.upload(add_file, [chatbot, btn], [chatbot], queue=False).then(\n", " bot, chatbot, chatbot\n", " )\n", "\n", "demo.queue()\n", "if __name__ == \"__main__\":\n", " demo.launch()\n"]}], "metadata": {}, "nbformat": 4, "nbformat_minor": 5} \ No newline at end of file diff --git a/demo/chatbot_multimodal/run.py b/demo/chatbot_multimodal/run.py index 824c7c0fc761..3c680155570f 100644 --- a/demo/chatbot_multimodal/run.py +++ b/demo/chatbot_multimodal/run.py @@ -1,5 +1,8 @@ import gradio as gr +import random +import time +# Chatbot demo with multimodal input (text, markdown, LaTeX, code blocks, image, audio, & video). Plus shows support for streaming text. def add_text(history, text): history = history + [(text, None)] @@ -13,8 +16,11 @@ def add_file(history, file): def bot(history): response = "**That's cool!**" - history[-1][1] = response - return history + history[-1][1] = "" + for character in response: + history[-1][1] += character + time.sleep(0.05) + yield history with gr.Blocks() as demo: @@ -37,5 +43,6 @@ def bot(history): bot, chatbot, chatbot ) +demo.queue() if __name__ == "__main__": demo.launch() diff --git a/demo/chatbot_streaming/run.ipynb b/demo/chatbot_streaming/run.ipynb deleted file mode 100644 index 292a8177e84d..000000000000 --- a/demo/chatbot_streaming/run.ipynb +++ /dev/null @@ -1 +0,0 @@ -{"cells": [{"cell_type": "markdown", "id": 302934307671667531413257853548643485645, "metadata": {}, "source": ["# Gradio Demo: chatbot_streaming"]}, {"cell_type": "code", "execution_count": null, "id": 272996653310673477252411125948039410165, "metadata": {}, "outputs": [], "source": ["!pip install -q gradio "]}, {"cell_type": "code", "execution_count": null, "id": 288918539441861185822528903084949547379, "metadata": {}, "outputs": [], "source": ["import gradio as gr\n", "import random\n", "import time\n", "\n", "with gr.Blocks() as demo:\n", " chatbot = gr.Chatbot()\n", " msg = gr.Textbox()\n", " clear = gr.ClearButton([msg, chatbot])\n", "\n", " def user(user_message, history):\n", " return gr.update(value=\"\", interactive=False), history + [[user_message, None]]\n", "\n", " def bot(history):\n", " bot_message = random.choice([\"How are you?\", \"I love you\", \"I'm very hungry\"])\n", " history[-1][1] = \"\"\n", " for character in bot_message:\n", " history[-1][1] += character\n", " time.sleep(0.05)\n", " yield history\n", "\n", " response = msg.submit(user, [msg, chatbot], [msg, chatbot], queue=False).then(\n", " bot, chatbot, chatbot\n", " )\n", " response.then(lambda: gr.update(interactive=True), None, [msg], queue=False)\n", "\n", "demo.queue()\n", "if __name__ == \"__main__\":\n", " demo.launch()\n"]}], "metadata": {}, "nbformat": 4, "nbformat_minor": 5} \ No newline at end of file diff --git a/demo/chatbot_streaming/run.py b/demo/chatbot_streaming/run.py deleted file mode 100644 index ab6b13502173..000000000000 --- a/demo/chatbot_streaming/run.py +++ /dev/null @@ -1,28 +0,0 @@ -import gradio as gr -import random -import time - -with gr.Blocks() as demo: - chatbot = gr.Chatbot() - msg = gr.Textbox() - clear = gr.ClearButton([msg, chatbot]) - - def user(user_message, history): - return gr.update(value="", interactive=False), history + [[user_message, None]] - - def bot(history): - bot_message = random.choice(["How are you?", "I love you", "I'm very hungry"]) - history[-1][1] = "" - for character in bot_message: - history[-1][1] += character - time.sleep(0.05) - yield history - - response = msg.submit(user, [msg, chatbot], [msg, chatbot], queue=False).then( - bot, chatbot, chatbot - ) - response.then(lambda: gr.update(interactive=True), None, [msg], queue=False) - -demo.queue() -if __name__ == "__main__": - demo.launch() diff --git a/js/app/package.json b/js/app/package.json index f5107132a85d..b83154985547 100644 --- a/js/app/package.json +++ b/js/app/package.json @@ -16,7 +16,7 @@ "preview": "vite preview", "test:snapshot": "pnpm exec playwright test snapshots/ --config=../../.config/playwright.config.js", "test:browser": "pnpm exec playwright test test/ --config=../../.config/playwright.config.js", - "test:browser:debug": "pnpm exec playwright test test/ --debug --config=../../.config/playwright.config.js", + "test:browser:dev": "pnpm exec playwright test test/ --ui --config=../../.config/playwright.config.js", "build:css": "pollen -c pollen.config.cjs -o src/pollen-dev.css" }, "dependencies": { @@ -57,4 +57,4 @@ "msw": { "workerDirectory": "public" } -} +} \ No newline at end of file diff --git a/js/app/src/components/Chatbot/Chatbot.test.ts b/js/app/src/components/Chatbot/Chatbot.test.ts index 337ae292ad77..8bb4b7c80530 100644 --- a/js/app/src/components/Chatbot/Chatbot.test.ts +++ b/js/app/src/components/Chatbot/Chatbot.test.ts @@ -1,6 +1,5 @@ import { test, describe, assert, afterEach } from "vitest"; import { cleanup, render, get_text } from "@gradio/tootils"; -import { tick } from "svelte"; import Chatbot from "./Chatbot.svelte"; import type { LoadingStatus } from "../StatusTracker/types"; diff --git a/js/app/test/chatbot_multimodal.spec.ts b/js/app/test/chatbot_multimodal.spec.ts new file mode 100644 index 000000000000..00bdae4e6495 --- /dev/null +++ b/js/app/test/chatbot_multimodal.spec.ts @@ -0,0 +1,108 @@ +import { test, expect } from "@gradio/tootils"; + +test("text input by a user should be shown in the chatbot as a paragraph", async ({ page }) => { + const textbox = await page.getByTestId('textbox'); + await textbox.fill("Lorem ipsum"); + await page.keyboard.press('Enter'); + const user_message = await page.getByTestId('user').first().getByRole('paragraph').textContent(); + const bot_message = await page.getByTestId('bot').first().getByRole('paragraph').textContent(); + await expect(user_message).toEqual("Lorem ipsum"); + await expect(bot_message).toBeTruthy(); +}); + +test("images uploaded by a user should be shown in the chat", async ({ page }) => { + const fileChooserPromise = page.waitForEvent('filechooser'); + await page.getByRole('button', { name: '📁' }).click(); + const fileChooser = await fileChooserPromise; + await fileChooser.setFiles("./test/files/cheetah1.jpg"); + await page.keyboard.press('Enter'); + + const user_message = await page.getByTestId('user').first().getByRole('img'); + const bot_message = await page.getByTestId('bot').first().getByRole('paragraph').textContent(); + const image_data = await user_message.getAttribute("src"); + await expect(image_data).toContain("cheetah1.jpg"); + await expect(bot_message).toBeTruthy(); +}); + +test("audio uploaded by a user should be shown in the chatbot", async ({ page }) => { + const fileChooserPromise = page.waitForEvent('filechooser'); + await page.getByRole('button', { name: '📁' }).click(); + const fileChooser = await fileChooserPromise; + await fileChooser.setFiles("../../test/test_files/audio_sample.wav"); + await page.keyboard.press('Enter'); + + const user_message = await page.getByTestId('user').first().locator('audio'); + const bot_message = await page.getByTestId('bot').first().getByRole('paragraph').textContent(); + const audio_data = await user_message.getAttribute("src"); + await expect(audio_data).toContain("audio_sample.wav"); + await expect(bot_message).toBeTruthy(); + +}); + +test("videos uploaded by a user should be shown in the chatbot", async ({ page }) => { + const fileChooserPromise = page.waitForEvent('filechooser'); + await page.getByRole('button', { name: '📁' }).click(); + const fileChooser = await fileChooserPromise; + await fileChooser.setFiles("../../test/test_files/video_sample.mp4"); + await page.keyboard.press('Enter'); + + const user_message = await page.getByTestId('user').first().locator('video'); + const bot_message = await page.getByTestId('bot').first().getByRole('paragraph').textContent(); + const video_data = await user_message.getAttribute("src"); + await expect(video_data).toContain("video_sample.mp4"); + await expect(bot_message).toBeTruthy(); +}); + + +test("markdown input by a user should be correctly formatted: bold, italics, links", async ({ page }) => { + const textbox = await page.getByTestId('textbox'); + await textbox.fill("This is **bold text**. This is *italic text*. This is a [link](https://gradio.app)."); + await page.keyboard.press('Enter'); + const user_message = await page.getByTestId('user').first().getByRole('paragraph').innerHTML(); + const bot_message = await page.getByTestId('bot').first().getByRole('paragraph').textContent(); + await expect(user_message).toEqual('This is bold text. This is italic text. This is a link.'); + await expect(bot_message).toBeTruthy(); +}); + +test("inline code markdown input by the user should be correctly formatted", async ({ page }) => { + const textbox = await page.getByTestId('textbox'); + await textbox.fill("This is `code`."); + await page.keyboard.press('Enter'); + const user_message = await page.getByTestId('user').first().getByRole('paragraph').innerHTML(); + const bot_message = await page.getByTestId('bot').first().getByRole('paragraph').textContent(); + await expect(user_message).toEqual("This is code."); + await expect(bot_message).toBeTruthy(); +}); + +test("markdown code blocks input by a user should be rendered correctly with the correct language tag", async ({ page }) => { + const textbox = await page.getByTestId('textbox'); + await textbox.fill("```python\nprint('Hello')\nprint('World!')\n```"); + await page.keyboard.press('Enter'); + const user_message = await page.getByTestId('user').first().locator('pre').innerHTML(); + const bot_message = await page.getByTestId('bot').first().getByRole('paragraph').textContent(); + await expect(user_message).toContain("language-python"); + await expect(bot_message).toBeTruthy(); + +}); + +test("LaTeX input by a user should be rendered correctly", async ({ page }) => { + const textbox = await page.getByTestId('textbox'); + await textbox.fill("This is LaTeX $$x^2$$"); + await page.keyboard.press('Enter'); + const user_message = await page.getByTestId('user').first().getByRole('paragraph').innerHTML(); + const bot_message = await page.getByTestId('bot').first().getByRole('paragraph').textContent(); + await expect(user_message).toContain("katex-display"); + await expect(bot_message).toBeTruthy(); +}); + + +test("when a new message is sent the chatbot should scroll to the latest message", async ({ page }) => { + const textbox = await page.getByTestId('textbox'); + const line_break = "
" + await textbox.fill(line_break.repeat(30)); + await page.keyboard.press('Enter'); + const bot_message = await page.getByTestId('bot').first().getByRole('paragraph'); + await expect(bot_message).toBeVisible(); + const bot_message_text = bot_message.textContent(); + await expect(bot_message_text).toBeTruthy(); +}); \ No newline at end of file diff --git a/package.json b/package.json index 049cba29b264..f7833e121fe0 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,7 @@ "test:browser": "pnpm --filter @gradio/app test:browser", "test:browser:full": "run-s build test:browser", "test:browser:verbose": "GRADIO_TEST_VERBOSE=true pnpm test:browser", - "test:browser:debug": "pnpm --filter @gradio/app test:browser:debug", + "test:browser:dev": "pnpm --filter @gradio/app test:browser:dev", "ci:publish": "pnpm publish --no-git-checks --access public -r", "ci:version": "changeset version && pnpm i --lockfile-only", "test:ct": "playwright test -c ./.config/playwright-ct.config.ts" @@ -92,4 +92,4 @@ ".." ] } -} +} \ No newline at end of file