Revert chatbot styling (#8758)

* fix

* fix

* add changeset

* add changeset

* tests

* Fix button panel (#8762)

---------

Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com>
Co-authored-by: Freddy Boulton <alfonsoboulton@gmail.com>
This commit is contained in:
pngwn 2024-07-12 18:35:08 +01:00 committed by GitHub
parent 078663a3a4
commit 26cdd0ffe0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 206 additions and 119 deletions

View File

@ -0,0 +1,6 @@
---
"@gradio/chatbot": patch
"gradio": patch
---
fix:Revert chatbot styling

View File

@ -1 +1 @@
{"cells": [{"cell_type": "markdown", "id": "302934307671667531413257853548643485645", "metadata": {}, "source": ["# Gradio Demo: chatbot_with_tools"]}, {"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", "from gradio import ChatMessage\n", "import time\n", "\n", "def generate_response(history):\n", " history.append(ChatMessage(role=\"user\", content=\"What is the weather in San Francisco right now?\"))\n", " yield history\n", " time.sleep(0.25)\n", " history.append(ChatMessage(role=\"assistant\",\n", " content=\"In order to find the current weather in San Francisco, I will need to use my weather tool.\")\n", " )\n", " yield history\n", " time.sleep(0.25)\n", "\n", " history.append(ChatMessage(role=\"assistant\",\n", " content=\"API Error when connecting to weather service.\",\n", " metadata={\"title\": \"\ud83d\udca5 Error using tool 'Weather'\"})\n", " )\n", " yield history\n", " time.sleep(0.25)\n", "\n", " history.append(ChatMessage(role=\"assistant\",\n", " content=\"I will try again\",\n", " ))\n", " yield history\n", " time.sleep(0.25)\n", "\n", " history.append(ChatMessage(role=\"assistant\",\n", " content=\"Weather 72 degrees Fahrenheit with 20% chance of rain.\",\n", " metadata={\"title\": \"\ud83d\udee0\ufe0f Used tool 'Weather'\"}\n", " ))\n", " yield history\n", " time.sleep(0.25)\n", "\n", " history.append(ChatMessage(role=\"assistant\",\n", " content=\"Now that the API succeeded I can complete my task.\",\n", " ))\n", " yield history\n", " time.sleep(0.25)\n", "\n", " history.append(ChatMessage(role=\"assistant\",\n", " content=\"It's a sunny day in San Francisco with a current temperature of 72 degrees Fahrenheit and a 20% chance of rain. Enjoy the weather!\",\n", " ))\n", " yield history\n", "\n", "\n", "with gr.Blocks() as demo:\n", " chatbot = gr.Chatbot(type=\"messages\")\n", " button = gr.Button(\"Get San Francisco Weather\")\n", " button.click(generate_response, chatbot, chatbot)\n", "\n", "if __name__ == \"__main__\":\n", " demo.launch()\n"]}], "metadata": {}, "nbformat": 4, "nbformat_minor": 5}
{"cells": [{"cell_type": "markdown", "id": "302934307671667531413257853548643485645", "metadata": {}, "source": ["# Gradio Demo: chatbot_with_tools"]}, {"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", "from gradio import ChatMessage\n", "import time\n", "\n", "\n", "def generate_response(history):\n", " history.append(\n", " ChatMessage(\n", " role=\"user\", content=\"What is the weather in San Francisco right now?\"\n", " )\n", " )\n", " yield history\n", " time.sleep(0.25)\n", " history.append(\n", " ChatMessage(\n", " role=\"assistant\",\n", " content=\"In order to find the current weather in San Francisco, I will need to use my weather tool.\",\n", " )\n", " )\n", " yield history\n", " time.sleep(0.25)\n", "\n", " history.append(\n", " ChatMessage(\n", " role=\"assistant\",\n", " content=\"API Error when connecting to weather service.\",\n", " metadata={\"title\": \"\ud83d\udca5 Error using tool 'Weather'\"},\n", " )\n", " )\n", " yield history\n", " time.sleep(0.25)\n", "\n", " history.append(\n", " ChatMessage(\n", " role=\"assistant\",\n", " content=\"I will try again\",\n", " )\n", " )\n", " yield history\n", " time.sleep(0.25)\n", "\n", " history.append(\n", " ChatMessage(\n", " role=\"assistant\",\n", " content=\"Weather 72 degrees Fahrenheit with 20% chance of rain.\",\n", " metadata={\"title\": \"\ud83d\udee0\ufe0f Used tool 'Weather'\"},\n", " )\n", " )\n", " yield history\n", " time.sleep(0.25)\n", "\n", " history.append(\n", " ChatMessage(\n", " role=\"assistant\",\n", " content=\"Now that the API succeeded I can complete my task.\",\n", " )\n", " )\n", " yield history\n", " time.sleep(0.25)\n", "\n", " history.append(\n", " ChatMessage(\n", " role=\"assistant\",\n", " content=\"It's a sunny day in San Francisco with a current temperature of 72 degrees Fahrenheit and a 20% chance of rain. Enjoy the weather!\",\n", " )\n", " )\n", " yield history\n", "\n", "\n", "def like(evt: gr.LikeData):\n", " print(\"User liked the response\")\n", " print(evt.index, evt.liked, evt.value)\n", "\n", "\n", "with gr.Blocks() as demo:\n", " chatbot = gr.Chatbot(type=\"messages\", height=500, show_copy_button=True)\n", " button = gr.Button(\"Get San Francisco Weather\")\n", " button.click(generate_response, chatbot, chatbot)\n", " chatbot.like(like)\n", "\n", "if __name__ == \"__main__\":\n", " demo.launch()\n"]}], "metadata": {}, "nbformat": 4, "nbformat_minor": 5}

View File

@ -2,52 +2,81 @@ import gradio as gr
from gradio import ChatMessage
import time
def generate_response(history):
history.append(ChatMessage(role="user", content="What is the weather in San Francisco right now?"))
history.append(
ChatMessage(
role="user", content="What is the weather in San Francisco right now?"
)
)
yield history
time.sleep(0.25)
history.append(ChatMessage(role="assistant",
content="In order to find the current weather in San Francisco, I will need to use my weather tool.")
history.append(
ChatMessage(
role="assistant",
content="In order to find the current weather in San Francisco, I will need to use my weather tool.",
)
)
yield history
time.sleep(0.25)
history.append(ChatMessage(role="assistant",
history.append(
ChatMessage(
role="assistant",
content="API Error when connecting to weather service.",
metadata={"title": "💥 Error using tool 'Weather'"})
metadata={"title": "💥 Error using tool 'Weather'"},
)
)
yield history
time.sleep(0.25)
history.append(ChatMessage(role="assistant",
history.append(
ChatMessage(
role="assistant",
content="I will try again",
))
)
)
yield history
time.sleep(0.25)
history.append(ChatMessage(role="assistant",
history.append(
ChatMessage(
role="assistant",
content="Weather 72 degrees Fahrenheit with 20% chance of rain.",
metadata={"title": "🛠️ Used tool 'Weather'"}
))
metadata={"title": "🛠️ Used tool 'Weather'"},
)
)
yield history
time.sleep(0.25)
history.append(ChatMessage(role="assistant",
history.append(
ChatMessage(
role="assistant",
content="Now that the API succeeded I can complete my task.",
))
)
)
yield history
time.sleep(0.25)
history.append(ChatMessage(role="assistant",
history.append(
ChatMessage(
role="assistant",
content="It's a sunny day in San Francisco with a current temperature of 72 degrees Fahrenheit and a 20% chance of rain. Enjoy the weather!",
))
)
)
yield history
def like(evt: gr.LikeData):
print("User liked the response")
print(evt.index, evt.liked, evt.value)
with gr.Blocks() as demo:
chatbot = gr.Chatbot(type="messages")
chatbot = gr.Chatbot(type="messages", height=500, show_copy_button=True)
button = gr.Button("Get San Francisco Weather")
button.click(generate_response, chatbot, chatbot)
chatbot.like(like)
if __name__ == "__main__":
demo.launch()

View File

@ -58,9 +58,11 @@ def set_cancel_events(
root_block.set_event_trigger(
triggers,
fn=lambda: [Timer(active=False) for _ in timers_to_cancel]
fn=lambda: (
[Timer(active=False) for _ in timers_to_cancel]
if len(timers_to_cancel) > 1
else Timer(active=False),
else Timer(active=False)
),
inputs=None,
outputs=timers_to_cancel,
show_api=False,

View File

@ -239,7 +239,7 @@ for (const msg_format of ["tuples", "messages"]) {
await page.getByTestId("textbox").fill("hello");
await page.keyboard.press("Enter");
await page.getByLabel("like", { exact: true }).click();
await page.getByLabel("dislike").click();
await page.getByLabel("dislike", { exact: true }).click();
expect(await page.getByLabel("clicked dislike").count()).toEqual(1);
expect(await page.getByLabel("clicked like").count()).toEqual(0);

View File

@ -124,6 +124,7 @@
upload={gradio.client.upload}
_fetch={gradio.client.fetch}
load_component={gradio.load_component}
msg_format={type}
/>
</div>
</Block>

View File

@ -4,19 +4,43 @@
import type { FileData } from "@gradio/client";
import DownloadIcon from "./Download.svelte";
import { DownloadLink } from "@gradio/wasm/svelte";
import type { NormalisedMessage, TextMessage } from "../types";
import { is_component_message } from "./utils";
export let likeable: boolean;
export let show_copy_button: boolean;
export let show: boolean;
export let message: Record<string, any>;
export let message: NormalisedMessage | NormalisedMessage[];
export let position: "right" | "left";
export let avatar: FileData | null;
export let handle_action: (selected: string | null) => void;
export let layout: "bubble" | "panel";
$: show_copy = show_copy_button && message && message?.type === "text";
function is_all_text(
message: NormalisedMessage[] | NormalisedMessage
): message is TextMessage[] | TextMessage {
return (
(Array.isArray(message) &&
message.every((m) => typeof m.content === "string")) ||
(!Array.isArray(message) && typeof message.content === "string")
);
}
function all_text(message: TextMessage[] | TextMessage): string {
if (Array.isArray(message)) {
return message.map((m) => m.content).join("\n");
}
return message.content;
}
$: message_text = is_all_text(message) ? all_text(message) : "";
$: show_copy = show_copy_button && message && is_all_text(message);
$: show_download =
show_copy_button && (message?.value?.video?.url || message?.value?.url);
!Array.isArray(message) &&
is_component_message(message) &&
message.content.value.url;
</script>
{#if show}
@ -25,21 +49,19 @@
null && 'with-avatar'}"
>
{#if show_copy}
<Copy value={message.value} />
<Copy value={message_text} />
{/if}
{#if show_download}
{#if show_download && !Array.isArray(message) && is_component_message(message)}
<DownloadLink
href={message?.value?.video?.url || message?.value?.url}
download={message?.value?.video?.orig_name ||
message.value.orig_name ||
"image"}
href={message?.content?.value.url}
download={message.content.value.orig_name || "image"}
>
<span class="icon-wrap">
<DownloadIcon />
</span>
</DownloadLink>
{/if}
{#if likeable && position === "left"}
{#if likeable}
<LikeDislike {handle_action} padded={show_copy || show_download} />
{/if}
</div>
@ -54,36 +76,34 @@
.icon-wrap:hover {
color: var(--body-text-color);
}
.message-buttons-right,
.message-buttons-left {
.message-buttons {
border-radius: var(--radius-md);
display: flex;
align-items: center;
bottom: 0;
height: var(--size-7);
align-self: self-end;
/* position: absolute; */
bottom: -15px;
margin: 2px;
margin: 0px calc(var(--spacing-xl) * 3);
padding-left: 5px;
z-index: 1;
padding-bottom: var(--spacing-xl);
padding: var(--spacing-md) 2px;
padding: var(--spacing-md) var(--spacing-md);
border: 1px solid var(--border-color-primary);
background: var(--border-color-secondary);
gap: var(--spacing-md);
}
.message-buttons-left {
align-self: start;
left: 0px;
}
.message-buttons-right {
right: 5px;
}
.panel.message-buttons-left,
.panel.message-buttons-right {
margin: 10px 0 2px 0;
}
.message-buttons {
/* .message-buttons {
left: 0px;
right: 0px;
top: unset;
@ -92,9 +112,13 @@
justify-content: flex-start;
align-items: center;
gap: 0px;
}
} */
.message-buttons :global(> *) {
margin-right: 7px;
margin-right: 0px;
}
.with-avatar {
margin-left: calc(var(--spacing-xl) * 4 + 31px);
}
</style>

View File

@ -1,5 +1,5 @@
<script lang="ts">
import { format_chat_for_sharing } from "./utils";
import { format_chat_for_sharing, is_component_message } from "./utils";
import type { NormalisedMessage } from "../types";
import { Gradio, copy } from "@gradio/utils";
@ -92,6 +92,8 @@
export let layout: "bubble" | "panel" = "bubble";
export let placeholder: string | null = null;
export let upload: Client["upload"];
export let msg_format: "tuples" | "messages" = "tuples";
let target = document.querySelector("div.gradio-container");
let div: HTMLDivElement;
@ -172,6 +174,8 @@
}
}
$: groupedMessages = value && group_messages(value);
function handle_select(i: number, message: NormalisedMessage): void {
dispatch("select", {
index: message.index,
@ -184,11 +188,27 @@
message: NormalisedMessage,
selected: string | null
): void {
if (msg_format === "tuples") {
dispatch("like", {
index: message.index,
value: message.content,
liked: selected === "like"
});
} else {
if (!groupedMessages) return;
const message_group = groupedMessages[i];
const [first, last] = [
message_group[0],
message_group[message_group.length - 1]
];
dispatch("like", {
index: [first.index, last.index] as [number, number],
value: message_group.map((m) => m.content),
liked: selected === "like"
});
}
}
function get_message_label_data(message: NormalisedMessage): string {
@ -209,12 +229,6 @@
return `a component of type ${message.content.component ?? "unknown"}`;
}
function is_component_message(
message: NormalisedMessage
): message is ComponentMessage {
return message.type === "component";
}
function group_messages(
messages: NormalisedMessage[]
): NormalisedMessage[][] {
@ -223,6 +237,10 @@
let currentRole: MessageRole | null = null;
for (const message of messages) {
if (msg_format === "tuples") {
currentRole = null;
}
if (!(message.role === "assistant" || message.role === "user")) {
continue;
}
@ -266,8 +284,7 @@
aria-live="polite"
>
<div class="message-wrap" use:copy>
{#if value !== null && value.length > 0}
{@const groupedMessages = group_messages(value)}
{#if value !== null && value.length > 0 && groupedMessages !== null}
{#each groupedMessages as messages, i}
{@const role = messages[0].role === "user" ? "user" : "bot"}
{@const avatar_img = avatar_images[role === "user" ? 0 : 1]}
@ -297,7 +314,10 @@
/>
</div>
{/if}
<div class="flex-wrap">
<div
class="flex-wrap {role} "
class:component-wrap={messages[0].type === "component"}
>
{#each messages as message, thought_index}
{@const msg_type = messages[0].type}
<div
@ -385,20 +405,20 @@
{/if}
</button>
</div>
{/each}
</div>
</div>
<LikeButtons
show={role === "bot" && (likeable || show_copy_button)}
handle_action={(selected) => handle_like(i, message, selected)}
show={likeable || show_copy_button}
handle_action={(selected) => handle_like(i, messages[0], selected)}
{likeable}
{show_copy_button}
{message}
message={msg_format === "tuples" ? messages[0] : messages}
position={role === "user" ? "right" : "left"}
avatar={avatar_img}
{layout}
/>
{/each}
</div>
</div>
{/each}
{#if pending_message}
<Pending {layout} />
{/if}
@ -430,7 +450,6 @@
.bubble-wrap {
width: 100%;
overflow-y: auto;
height: 100%;
padding-top: var(--spacing-xxl);
}
@ -475,16 +494,7 @@
.message-bubble-border {
border-width: 1px;
border-radius: var(--radius-lg);
}
.bubble .user {
border-width: 1px;
border-radius: var(--radius-xl);
align-self: flex-start;
border-bottom-right-radius: 0;
box-shadow: var(--shadow-drop-lg);
border-radius: var(--radius-md);
}
.user {
@ -502,11 +512,30 @@
white-space: pre-line;
}
.bubble .user {
.flex-wrap.user {
border-width: 1px;
border-radius: var(--radius-md);
align-self: flex-start;
border-bottom-right-radius: 0;
box-shadow: var(--shadow-drop);
align-self: flex-start;
text-align: right;
padding: var(--spacing-sm) var(--spacing-xl);
border-color: var(--border-color-accent-subdued);
background-color: var(--color-accent-soft);
}
:not(.component-wrap).flex-wrap.bot {
border-width: 1px;
border-radius: var(--radius-lg);
align-self: flex-start;
border-bottom-left-radius: 0;
box-shadow: var(--shadow-drop);
align-self: flex-start;
text-align: right;
padding: var(--spacing-sm) var(--spacing-xl);
border-color: var(--border-color-primary);
background-color: var(--background-fill-secondary);
}
.panel .user :global(*) {
@ -518,11 +547,6 @@
border-color: var(--border-color-primary);
}
.bubble .user {
border-color: var(--border-color-accent-subdued);
background-color: var(--color-accent-soft);
}
.message-row {
display: flex;
/* flex-direction: column; */
@ -534,6 +558,7 @@
}
.message-row.bubble {
margin: calc(var(--spacing-xl) * 3);
margin-bottom: var(--spacing-xl);
}
.with_avatar.message-row.panel {

View File

@ -6,6 +6,7 @@
export let handle_action: (selected: string | null) => void;
export let padded = false;
export let position: "right" | "left" = "left";
let selected: "like" | "dislike" | null = null;
</script>
@ -18,15 +19,13 @@
aria-label={selected === "dislike" ? "clicked dislike" : "dislike"}
class:padded
class:selected={selected === "dislike"}
class="dislike-button"
class="dislike-button {position}"
>
<span>
{#if selected === "dislike"}
<ThumbDownActive />
{:else}
<ThumbDownDefault />
{/if}
</span>
</button>
<button
@ -39,13 +38,11 @@
aria-label={selected === "like" ? "clicked like" : "like"}
class:selected={selected === "like"}
>
<span>
{#if selected === "like"}
<ThumbUpActive />
{:else}
<ThumbUpDefault />
{/if}
</span>
</button>
<style>
@ -55,11 +52,6 @@
display: flex;
align-items: center;
justify-content: center;
margin-right: 7px !important;
}
span {
display: inline-block;
}
button:hover,
@ -72,8 +64,4 @@
.selected:hover {
color: var(--color-accent);
}
.like-button {
transform: translateY(0px);
}
</style>

View File

@ -30,18 +30,24 @@
cursor: pointer;
max-width: max-content;
background: var(--color-accent-soft);
border: 1px solid var(--border-color-accent-subdued);
font-size: 0.8em;
}
.title {
display: flex;
align-items: center;
padding: 8px;
padding: 3px 6px;
color: var(--body-text-color);
opacity: 0.8;
}
.content {
padding: 8px;
padding: 4px 8px;
}
.content :global(*) {
font-size: 0.8em;
}
.title-text {

View File

@ -156,3 +156,9 @@ export function normalise_tuples(
});
return msg.filter((message) => message != null) as NormalisedMessage[];
}
export function is_component_message(
message: NormalisedMessage
): message is ComponentMessage {
return message.type === "component";
}

View File

@ -18,7 +18,7 @@ export interface Message {
role: MessageRole;
metadata: Metadata;
content: string | FileData | ComponentData;
index: [number, number] | number;
index: number | [number, number];
}
export interface TextMessage extends Message {