fix pending chatbot message styling and ensure messages with value None 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>
This commit is contained in:
Hannah 2023-10-04 14:50:19 +01:00 committed by GitHub
parent 6e56a0d9b0
commit e2874bc3cb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 169 additions and 140 deletions

View File

@ -0,0 +1,6 @@
---
"@gradio/chatbot": patch
"gradio": patch
---
fix:fix pending chatbot message styling and ensure messages with value `None` don't render

View File

@ -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]],
@ -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 () => {

View File

@ -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>

View File

@ -1,26 +1,24 @@
<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;
@ -28,6 +26,8 @@
align-items: center;
align-self: center;
gap: 2px;
width: 100%;
height: 100%;
}
.dot-flashing {
animation: dot-flashing 1s infinite linear alternate;