Reset flagged values when switching conversations in chat history (#10292)

* chat history

* add changeset

* changes

* add changeset

* changes

* changes

* more changes

* changes

* changes

* format

* notebook

* fix test

* changes

* add changeset

* changes

* changes

* changes

* add changeset

---------

Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com>
Co-authored-by: Ali Abid <aliabid94@gmail.com>
This commit is contained in:
Abubakar Abid 2025-01-06 13:59:31 -08:00 committed by GitHub
parent 890eaa3a9e
commit f2bd72f9ef
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 95 additions and 45 deletions

View File

@ -0,0 +1,7 @@
---
"@gradio/atoms": minor
"@gradio/chatbot": minor
"gradio": minor
---
feat:Reset flagged values when switching conversations in chat history

View File

@ -1 +1 @@
{"cells": [{"cell_type": "markdown", "id": "302934307671667531413257853548643485645", "metadata": {}, "source": ["# Gradio Demo: chatinterface_streaming_echo"]}, {"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 time\n", "import gradio as gr\n", "\n", "def slow_echo(message, history):\n", " for i in range(len(message)):\n", " time.sleep(0.05)\n", " yield \"You typed: \" + message[: i + 1]\n", "\n", "demo = gr.ChatInterface(\n", " slow_echo,\n", " type=\"messages\",\n", " flagging_mode=\"manual\",\n", " flagging_options=[\"Like\", \"Spam\", \"Inappropriate\", \"Other\"], \n", " save_history=True,\n", ")\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: chatinterface_streaming_echo"]}, {"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 time\n", "import gradio as gr\n", "\n", "def slow_echo(message, history):\n", " for i in range(len(message)):\n", " time.sleep(0.05)\n", " yield \"You typed: \" + message[: i + 1]\n", "\n", "demo = gr.ChatInterface(\n", " slow_echo,\n", " type=\"messages\",\n", " flagging_mode=\"manual\",\n", " flagging_options=[\"Like\", \"Spam\", \"Inappropriate\", \"Other\"],\n", " save_history=True,\n", ")\n", "\n", "if __name__ == \"__main__\":\n", " demo.launch()\n"]}], "metadata": {}, "nbformat": 4, "nbformat_minor": 5}

View File

@ -10,7 +10,7 @@ demo = gr.ChatInterface(
slow_echo,
type="messages",
flagging_mode="manual",
flagging_options=["Like", "Spam", "Inappropriate", "Other"],
flagging_options=["Like", "Spam", "Inappropriate", "Other"],
save_history=True,
)

View File

@ -249,7 +249,7 @@ class ChatInterface(Blocks):
with self:
self.saved_conversations = BrowserState(
[], storage_key="_saved_conversations"
[], storage_key=f"_saved_conversations_{self._id}"
)
self.conversation_id = State(None)
self.saved_input = State() # Stores the most recent user message
@ -279,7 +279,7 @@ class ChatInterface(Blocks):
with Column(scale=1, min_width=100):
self.new_chat_button = Button(
"New chat",
variant="secondary",
variant="primary",
size="md",
icon=utils.get_icon_path("plus.svg"),
)
@ -467,6 +467,28 @@ class ChatInterface(Blocks):
saved_conversations.pop(index)
return None, saved_conversations
def _load_chat_history(self, conversations):
return Dataset(
samples=[
[self._generate_chat_title(conv)]
for conv in conversations or []
if conv
]
)
def _load_conversation(
self,
index: int,
conversations: list[list[MessageDict]],
):
return (
index,
Chatbot(
value=conversations[index], # type: ignore
feedback_value=[],
),
)
def _setup_events(self) -> None:
from gradio import on
@ -645,28 +667,29 @@ class ChatInterface(Blocks):
queue=False,
)
@on(
[self.load, self.saved_conversations.change],
on(
triggers=[self.load, self.saved_conversations.change],
fn=self._load_chat_history,
inputs=[self.saved_conversations],
outputs=[self.chat_history_dataset],
show_api=False,
queue=False,
)
def load_chat_history(conversations):
return Dataset(
samples=[
[self._generate_chat_title(conv)]
for conv in conversations or []
if conv
]
)
self.chat_history_dataset.click(
lambda index, conversations: (index, conversations[index]),
lambda: [],
None,
[self.chatbot],
show_api=False,
queue=False,
show_progress="hidden",
).then(
self._load_conversation,
[self.chat_history_dataset, self.saved_conversations],
[self.conversation_id, self.chatbot],
show_api=False,
queue=False,
show_progress="hidden",
).then(**synchronize_chat_state_kwargs)
if self.flagging_mode != "never":

View File

@ -195,6 +195,7 @@ class Chatbot(Component):
sanitize_html: bool = True,
render_markdown: bool = True,
feedback_options: list[str] | tuple[str, ...] | None = ("Like", "Dislike"),
feedback_value: Sequence[str | None] | None = None,
bubble_full_width=None,
line_breaks: bool = True,
layout: Literal["panel", "bubble"] | None = None,
@ -234,6 +235,7 @@ class Chatbot(Component):
sanitize_html: If False, will disable HTML sanitization for chatbot messages. This is not recommended, as it can lead to security vulnerabilities.
render_markdown: If False, will disable Markdown rendering for chatbot messages.
feedback_options: A list of strings representing the feedback options that will be displayed to the user. The exact case-sensitive strings "Like" and "Dislike" will render as thumb icons, but any other choices will appear under a separate flag icon.
feedback_value: A list of strings representing the feedback state for entire chat. Only works when type="messages". Each entry in the list corresponds to that assistant message, in order, and the value is the feedback given (e.g. "Like", "Dislike", or any custom feedback option) or None if no feedback was given for that message.
bubble_full_width: Deprecated.
line_breaks: If True (default), will enable Github-flavored Markdown line breaks in chatbot messages. If False, single new lines will be ignored. Only applies if `render_markdown` is True.
layout: If "panel", will display the chatbot in a llm style layout. If "bubble", will display the chatbot with message bubbles, with the user and bot messages on alterating sides. Will default to "bubble".
@ -290,6 +292,7 @@ class Chatbot(Component):
self.show_copy_all_button = show_copy_all_button
self.allow_file_downloads = allow_file_downloads
self.feedback_options = feedback_options
self.feedback_value = feedback_value
super().__init__(
label=label,
every=every,

View File

@ -280,7 +280,7 @@ class LikeData(EventData):
"chatbot_value": value,
"liked_message": like_data.value,
"liked_index": like_data.index,
"liked_or_disliked_as_bool": like_data.liked
"liked_or_disliked": like_data.liked
}
with gr.Blocks() as demo:
c = gr.Chatbot([("abc", "def")])

View File

@ -1,8 +1,8 @@
<svg
fill="#FF7020"
fill="white"
width="100%"
height="100%"
viewBox="0 0 24 24"
viewBox="-4.8 0 28.8 24"
xmlns="http://www.w3.org/2000/svg"
>
<path

Before

Width:  |  Height:  |  Size: 477 B

After

Width:  |  Height:  |  Size: 480 B

View File

@ -59,11 +59,9 @@
}
.icon-button-wrapper
:global(
a.download-link:not(:last-child):not(.extra-feedback-option)::after
),
:global(a.download-link:not(:last-child):not(.no-border *)::after),
.icon-button-wrapper
:global(button:not(:last-child):not(.extra-feedback-option)::after) {
:global(button:not(:last-child):not(.no-border *)::after) {
content: "";
position: absolute;
right: -4.5px;

View File

@ -33,6 +33,7 @@
export let _selectable = false;
export let likeable = false;
export let feedback_options: string[] = ["Like", "Dislike"];
export let feedback_value: (string | null)[] | null = null;
export let show_share_button = false;
export let rtl = false;
export let show_copy_button = true;
@ -128,6 +129,7 @@
selectable={_selectable}
{likeable}
{feedback_options}
{feedback_value}
{show_share_button}
{show_copy_all_button}
value={_value}

View File

@ -16,6 +16,7 @@
export let position: "right" | "left";
export let avatar: FileData | null;
export let generating: boolean;
export let current_feedback: string | null;
export let handle_action: (selected: string | null) => void;
export let layout: "bubble" | "panel";
@ -94,7 +95,11 @@
/>
{/if}
{#if likeable}
<LikeDislike {handle_action} {feedback_options} />
<LikeDislike
{handle_action}
{feedback_options}
selected={current_feedback}
/>
{/if}
{/if}
</IconButtonWrapper>

View File

@ -68,6 +68,7 @@
export let selectable = false;
export let likeable = false;
export let feedback_options: string[];
export let feedback_value: (string | null)[] | null = null;
export let editable: "user" | "all" | null = null;
export let show_share_button = false;
export let show_copy_all_button = false;
@ -204,11 +205,11 @@
});
} else {
let feedback =
selected === "like"
selected === "Like"
? true
: selected === "dislike"
: selected === "Dislike"
? false
: selected?.substring(9); // remove "feedback:" prefix
: selected || "";
if (msg_format === "tuples") {
dispatch("like", {
index: message.index,
@ -283,6 +284,13 @@
{@const role = messages[0].role === "user" ? "user" : "bot"}
{@const avatar_img = avatar_images[role === "user" ? 0 : 1]}
{@const opposite_avatar_img = avatar_images[role === "user" ? 0 : 1]}
{@const feedback_index = groupedMessages
.slice(0, i)
.filter((m) => m[0].role === "assistant").length}
{@const current_feedback =
role === "bot" && feedback_value && feedback_value[feedback_index]
? feedback_value[feedback_index]
: null}
<Message
{messages}
{display_consecutive_in_same_bubble}
@ -309,6 +317,7 @@
{generating}
{msg_format}
{feedback_options}
{current_feedback}
show_like={role === "user" ? likeable && like_user_message : likeable}
show_retry={_retryable && is_last_bot_message(messages, value)}
show_undo={_undoable && is_last_bot_message(messages, value)}

View File

@ -9,44 +9,42 @@
export let handle_action: (selected: string | null) => void;
export let feedback_options: string[];
export let selected: string | null = null;
$: extra_feedback = feedback_options.filter(
(option) => option !== "Like" && option !== "Dislike"
);
let selected: string | null = null;
function toggleSelection(newSelection: string): void {
selected = selected === newSelection ? null : newSelection;
handle_action(selected);
}
</script>
{#if feedback_options.includes("Like") || feedback_options.includes("Dislike")}
{#if feedback_options.includes("Dislike")}
<IconButton
Icon={selected === "dislike" ? ThumbDownActive : ThumbDownDefault}
label={selected === "dislike" ? "clicked dislike" : "dislike"}
color={selected === "dislike"
Icon={selected === "Dislike" ? ThumbDownActive : ThumbDownDefault}
label={selected === "Dislike" ? "clicked dislike" : "dislike"}
color={selected === "Dislike"
? "var(--color-accent)"
: "var(--block-label-text-color)"}
on:click={() => {
selected = "dislike";
handle_action(selected);
}}
on:click={() => toggleSelection("Dislike")}
/>
{/if}
{#if feedback_options.includes("Like")}
<IconButton
Icon={selected === "like" ? ThumbUpActive : ThumbUpDefault}
label={selected === "like" ? "clicked like" : "like"}
color={selected === "like"
Icon={selected === "Like" ? ThumbUpActive : ThumbUpDefault}
label={selected === "Like" ? "clicked like" : "like"}
color={selected === "Like"
? "var(--color-accent)"
: "var(--block-label-text-color)"}
on:click={() => {
selected = "like";
handle_action(selected);
}}
on:click={() => toggleSelection("Like")}
/>
{/if}
{/if}
{#if extra_feedback.length > 0}
<div class="extra-feedback">
<div class="extra-feedback no-border">
<IconButton
Icon={selected && extra_feedback.includes(selected) ? FlagActive : Flag}
label="Feedback"
@ -60,8 +58,8 @@
class="extra-feedback-option"
style:font-weight={selected === option ? "bold" : "normal"}
on:click={() => {
selected = option;
handle_action("feedback:" + selected);
toggleSelection(option);
handle_action(selected ? selected : null);
}}>{option}</button
>
{/each}

View File

@ -50,6 +50,7 @@
export let in_edit_mode: boolean;
export let edit_message: string;
export let display_consecutive_in_same_bubble: boolean;
export let current_feedback: string | null = null;
let messageElements: HTMLDivElement[] = [];
let previous_edit_mode = false;
let last_message_width = 0;
@ -102,6 +103,7 @@
layout: "bubble" | "panel";
avatar: FileData | null;
dispatch: any;
current_feedback: string | null;
};
let button_panel_props: ButtonPanelProps;
@ -119,7 +121,8 @@
position: role === "user" ? "right" : "left",
avatar: avatar_img,
layout,
dispatch
dispatch,
current_feedback
};
</script>
@ -235,6 +238,7 @@
{#if layout === "panel"}
<ButtonPanel
{...button_panel_props}
{current_feedback}
on:copy={(e) => dispatch("copy", e.detail)}
/>
{/if}

View File

@ -47,6 +47,7 @@ class TestChatbot:
"placeholder": None,
"height": 400,
"feedback_options": ("Like", "Dislike"),
"feedback_value": None,
"resizeable": False,
"max_height": None,
"min_height": None,