Skip to content

Commit

Permalink
fix pending chatbot message styling and ensure messages with value `N…
Browse files Browse the repository at this point in the history
…one` don't render (#5775)

* fix pending chatbot message styling

* border fixes

* add changeset

* add changeset

* ensure null messages arent shown

* add hide css again

* render loading inside message + add test

* fix test

* add changeset

* add changeset

* add changeset

---------

Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com>
  • Loading branch information
hannahblair and gradio-pr-bot committed Oct 4, 2023
1 parent 6e56a0d commit e2874bc
Show file tree
Hide file tree
Showing 4 changed files with 169 additions and 140 deletions.
6 changes: 6 additions & 0 deletions .changeset/rude-dots-grab.md
@@ -0,0 +1,6 @@
---
"@gradio/chatbot": patch
"gradio": patch
---

fix:fix pending chatbot message styling and ensure messages with value `None` don't render
35 changes: 29 additions & 6 deletions js/chatbot/Chatbot.test.ts
Expand Up @@ -36,8 +36,8 @@ describe("Chatbot", () => {
assert.exists(user);
});

test("renders none messages", async () => {
const { getAllByTestId } = await render(Chatbot, {
test("null messages are not visible", async () => {
const { getByRole, container } = await render(Chatbot, {
loading_status,
label: "chatbot",
value: [[null, null]],
Expand All @@ -47,10 +47,33 @@ describe("Chatbot", () => {
theme_mode: "dark"
});

const user = getAllByTestId("user");
const bot = getAllByTestId("bot");
assert.isFalse(user[0].innerHTML.includes("span"));
assert.isFalse(bot[0].innerHTML.includes("span"));
const chatbot = getByRole("log");

const userButton = container.querySelector(".user button");
const botButton = container.querySelector(".bot button");

assert.notExists(userButton);
assert.notExists(botButton);

assert.isFalse(chatbot.innerHTML.includes("button"));
});

test("empty string messages are visible", async () => {
const { container } = await render(Chatbot, {
loading_status,
label: "chatbot",
value: [["", ""]],
root: "",
root_url: "",
latex_delimiters: [{ left: "$$", right: "$$", display: true }],
theme_mode: "dark"
});

const userButton = container.querySelector(".user button");
const botButton = container.querySelector(".bot button");

assert.exists(userButton);
assert.exists(botButton);
});

test("renders additional message as they are passed", async () => {
Expand Down
234 changes: 117 additions & 117 deletions js/chatbot/static/ChatBot.svelte
Expand Up @@ -121,131 +121,131 @@
{#if value !== null}
{#each value as message_pair, i}
{#each message_pair as message, j}
<div
class="message-row {layout} {j == 0 ? 'user-row' : 'bot-row'}"
class:hide={message === null}
>
{#if avatar_images[j] !== null}
<div class="avatar-container">
<img
class="avatar-image"
src={get_fetchable_url_or_file(
avatar_images[j],
root,
root_url
)}
alt="{j == 0 ? 'user' : 'bot'} avatar"
/>
</div>
{/if}

<div
class="message {j == 0 ? 'user' : 'bot'}"
class:message-fit={layout === "bubble" && !bubble_full_width}
class:panel-full-width={layout === "panel"}
class:message-bubble-border={layout === "bubble"}
class:message-markdown-disabled={!render_markdown}
>
<button
data-testid={j == 0 ? "user" : "bot"}
class:latest={i === value.length - 1}
class:message-markdown-disabled={!render_markdown}
class:selectable
style:text-align="left"
on:click={() => handle_select(i, j, message)}
on:keydown={(e) => {
if (e.key === "Enter") {
handle_select(i, j, message);
}
}}
dir={rtl ? "rtl" : "ltr"}
aria-label={(j == 0 ? "user" : "bot") +
"'s message:' " +
message}
>
{#if typeof message === "string"}
<Markdown
{message}
{latex_delimiters}
{sanitize_html}
{render_markdown}
{line_breaks}
on:load={scroll}
/>
{:else if message !== null && message.mime_type?.includes("audio")}
<audio
data-testid="chatbot-audio"
controls
preload="metadata"
src={message.data}
title={message.alt_text}
on:play
on:pause
on:ended
/>
{:else if message !== null && message.mime_type?.includes("video")}
<video
data-testid="chatbot-video"
controls
src={message.data}
title={message.alt_text}
preload="auto"
on:play
on:pause
on:ended
>
<track kind="captions" />
</video>
{:else if message !== null && message.mime_type?.includes("image")}
{#if message !== null || pending_message}
<div class="message-row {layout} {j == 0 ? 'user-row' : 'bot-row'}">
{#if avatar_images[j] !== null}
<div class="avatar-container">
<img
data-testid="chatbot-image"
src={message.data}
alt={message.alt_text}
class="avatar-image"
src={get_fetchable_url_or_file(
avatar_images[j],
root,
root_url
)}
alt="{j == 0 ? 'user' : 'bot'} avatar"
/>
{:else if message !== null && message.data !== null}
<a
data-testid="chatbot-file"
href={message.data}
target="_blank"
download={window.__is_colab__
? null
: message.orig_name || message.name}
>
{message.orig_name || message.name}
</a>
{/if}
</button>
</div>
{#if (likeable && j !== 0) || (show_copy_button && message && typeof message === "string")}
</div>
{/if}

<div
class="message-buttons-{j == 0
? 'user'
: 'bot'} message-buttons-{layout} {avatar_images[j] !==
null && 'with-avatar'}"
class:message-buttons-fit={layout === "bubble" &&
!bubble_full_width}
class:bubble-buttons-user={layout === "bubble"}
class="message {j == 0 ? 'user' : 'bot'}"
class:message-fit={layout === "bubble" && !bubble_full_width}
class:panel-full-width={layout === "panel"}
class:message-bubble-border={layout === "bubble"}
class:message-markdown-disabled={!render_markdown}
>
{#if likeable && j == 1}
<LikeDislike
action="like"
handle_action={() => handle_like(i, j, message, true)}
/>
<LikeDislike
action="dislike"
handle_action={() => handle_like(i, j, message, false)}
/>
{/if}
{#if show_copy_button && message && typeof message === "string"}
<Copy value={message} />
{/if}
<button
data-testid={j == 0 ? "user" : "bot"}
class:latest={i === value.length - 1}
class:message-markdown-disabled={!render_markdown}
class:selectable
style:text-align="left"
on:click={() => handle_select(i, j, message)}
on:keydown={(e) => {
if (e.key === "Enter") {
handle_select(i, j, message);
}
}}
dir={rtl ? "rtl" : "ltr"}
aria-label={(j == 0 ? "user" : "bot") +
"'s message:' " +
message}
>
{#if typeof message === "string"}
<Markdown
{message}
{latex_delimiters}
{sanitize_html}
{render_markdown}
{line_breaks}
on:load={scroll}
/>
{:else if message !== null && message.mime_type?.includes("audio")}
<audio
data-testid="chatbot-audio"
controls
preload="metadata"
src={message.data}
title={message.alt_text}
on:play
on:pause
on:ended
/>
{:else if message !== null && message.mime_type?.includes("video")}
<video
data-testid="chatbot-video"
controls
src={message.data}
title={message.alt_text}
preload="auto"
on:play
on:pause
on:ended
>
<track kind="captions" />
</video>
{:else if message !== null && message.mime_type?.includes("image")}
<img
data-testid="chatbot-image"
src={message.data}
alt={message.alt_text}
/>
{:else if message !== null && message.data !== null}
<a
data-testid="chatbot-file"
href={message.data}
target="_blank"
download={window.__is_colab__
? null
: message.orig_name || message.name}
>
{message.orig_name || message.name}
</a>
{:else if pending_message && j === 1}
<Pending {layout} />
{/if}
</button>
</div>
{/if}
</div>
{#if (likeable && j !== 0) || (show_copy_button && message && typeof message === "string")}
<div
class="message-buttons-{j == 0
? 'user'
: 'bot'} message-buttons-{layout} {avatar_images[j] !==
null && 'with-avatar'}"
class:message-buttons-fit={layout === "bubble" &&
!bubble_full_width}
class:bubble-buttons-user={layout === "bubble"}
>
{#if likeable && j == 1}
<LikeDislike
action="like"
handle_action={() => handle_like(i, j, message, true)}
/>
<LikeDislike
action="dislike"
handle_action={() => handle_like(i, j, message, false)}
/>
{/if}
{#if show_copy_button && message && typeof message === "string"}
<Copy value={message} />
{/if}
</div>
{/if}
</div>
{/if}
{/each}
{/each}
{/if}
<Pending {pending_message} />
</div>
</div>

Expand Down
34 changes: 17 additions & 17 deletions js/chatbot/static/Pending.svelte
@@ -1,33 +1,33 @@
<script lang="ts">
export let pending_message = false;
export let layout = "bubble";
</script>

{#if pending_message}
<div
class="message pending"
role="status"
aria-label="Loading response"
aria-live="polite"
>
<span class="sr-only">Loading content</span>
<div class="dot-flashing" />
&nbsp;
<div class="dot-flashing" />
&nbsp;
<div class="dot-flashing" />
</div>
{/if}
<div
class="message pending"
role="status"
aria-label="Loading response"
aria-live="polite"
style:border-radius={layout === "bubble" ? "var(--radius-xxl)" : "none"}
>
<span class="sr-only">Loading content</span>
<div class="dot-flashing" />
&nbsp;
<div class="dot-flashing" />
&nbsp;
<div class="dot-flashing" />
</div>

<style>
.pending {
border-color: var(--border-color-primary);
background: var(--background-fill-secondary);
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
align-self: center;
gap: 2px;
width: 100%;
height: 100%;
}
.dot-flashing {
animation: dot-flashing 1s infinite linear alternate;
Expand Down

0 comments on commit e2874bc

Please sign in to comment.