Repository: gradio-app/gradio

Issue:
Allow canceling the like/dislike status in `gr.Chatbot`
- [ ] I have searched to see if a similar issue already exists.
I installed the newest version:
```
pip install https://gradio-pypi-previews.s3.amazonaws.com/936988dbbfd3cb1431ecd59fd7061e0217fe66f1/gradio-5.9.1-py3-none-any.whl
```

My Code is here：
```
import gradio as gr

disliked_messages = {}

def greet(history, user_input):
    return history + [{"role": "user", "content": user_input}, {"role": "assistant", "content": "Hello, " + user_input}], gr.update(value="")  

def vote(data: gr.LikeData, history):
    print(data.index, data.liked)
    
    if isinstance(data.index, int):
        message = history[data.index]["content"]  
    else:
        message = ""
    
    if disliked_messages.get(message, False):  # dislike -> cancel dislike
        disliked_messages[message] = False
        print("You canceled the dislike for this response: " + message)
    else:  
        disliked_messages[message] = True
        print("You disliked this response: " + message)
    
    updated_history = [
        {"role": item["role"], "content": item["content"]} if disliked_messages.get(item["content"], False) else item
        for item in history
    ]

    return gr.update(value=updated_history)

with gr.Blocks() as demo:
    chatbot = gr.Chatbot(feedback_options=["Dislike"], type="messages") 
    textbox = gr.Textbox()
    textbox.submit(greet, [chatbot, textbox], [chatbot, textbox])  
    chatbot.like(vote, [chatbot], chatbot) 

if __name__ == "__main__":
    demo.launch()
```

No matter how I modify the vote function, when I click on "thumb-down" again, "Dislike" cannot be canceled.

When I click “thumb-down” ,it turn to
![image](https://github.com/user-attachments/assets/856acfa6-8f85-416c-a8e9-1eecdf2c2b1f)

and if I click “thumb-down” again, it turn to
![image](https://github.com/user-attachments/assets/0ef42ee1-dd2f-44d3-a29c-dcda5410a9a6)

but now:
<img width="1407" alt="image" src="https://github.com/user-attachments/assets/419cb734-d9c0-4988-aa86-693dafc241b0" />

Discussion:
@HenryBao91: @abidlabs Thank you , That's really cool

Solution patch:
diff --git a/.changeset/short-rice-clean.md b/.changeset/short-rice-clean.md
new file mode 100644
index 000000000..bef1f67c2
--- /dev/null
+++ b/.changeset/short-rice-clean.md
@@ -0,0 +1,7 @@
+---
+"@gradio/atoms": minor
+"@gradio/chatbot": minor
+"gradio": minor
+---
+
+feat:Reset flagged values when switching conversations in chat history
diff --git a/demo/chatinterface_streaming_echo/run.ipynb b/demo/chatinterface_streaming_echo/run.ipynb
index 9b82faac9..ee11ccac4 100644
--- a/demo/chatinterface_streaming_echo/run.ipynb
+++ b/demo/chatinterface_streaming_echo/run.ipynb
@@ -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}
\ No newline at end of file
+{"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}
\ No newline at end of file
diff --git a/demo/chatinterface_streaming_echo/run.py b/demo/chatinterface_streaming_echo/run.py
index 089c6711d..27409b697 100644
--- a/demo/chatinterface_streaming_echo/run.py
+++ b/demo/chatinterface_streaming_echo/run.py
@@ -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,
 )
 
diff --git a/gradio/chat_interface.py b/gradio/chat_interface.py
index d8bdaa39a..81e336bc4 100644
--- a/gradio/chat_interface.py
+++ b/gradio/chat_interface.py
@@ -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":
diff --git a/gradio/components/chatbot.py b/gradio/components/chatbot.py
index aa1e0eb41..73d8810a3 100644
--- a/gradio/components/chatbot.py
+++ b/gradio/components/chatbot.py
@@ -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,
diff --git a/gradio/events.py b/gradio/events.py
index bc7597cf8..b3ee2ec73 100644
--- a/gradio/events.py
+++ b/gradio/events.py
@@ -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")])
diff --git a/js/atoms/src/IconButtonWrapper.svelte b/js/atoms/src/IconButtonWrapper.svelte
index d6d2cc9e4..a5428e354 100644
--- a/js/atoms/src/IconButtonWrapper.svelte
+++ b/js/atoms/src/IconButtonWrapper.svelte
@@ -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;
diff --git a/js/chatbot/Index.svelte b/js/chatbot/Index.svelte
index 04e46bdd7..3d113e6e2 100644
--- a/js/chatbot/Index.svelte
+++ b/js/chatbot/Index.svelte
@@ -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}
diff --git a/js/chatbot/shared/ButtonPanel.svelte b/js/chatbot/shared/ButtonPanel.svelte
index c427a3821..a025b50d0 100644
--- a/js/chatbot/shared/ButtonPanel.svelte
+++ b/js/chatbot/shared/ButtonPanel.svelte
@@ -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>
diff --git a/js/chatbot/shared/ChatBot.svelte b/js/chatbot/shared/ChatBot.svelte
index 38f407813..3ad7d18c8 100644
--- a/js/chatbot/shared/ChatBot.svelte
+++ b/js/chatbot/shared/ChatBot.svelte
@@ -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)}
diff --git a/js/chatbot/shared/LikeDislike.svelte b/js/chatbot/shared/LikeDislike.svelte
index b4ac9c70f..89d096337 100644
--- a/js/chatbot/shared/LikeDislike.svelte
+++ b/js/chatbot/shared/LikeDislike.svelte
@@ -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}
diff --git a/js/chatbot/shared/Message.svelte b/js/chatbot/shared/Message.svelte
index 88a0aecdb..d4f2a9e5c 100644
--- a/js/chatbot/shared/Message.svelte
+++ b/js/chatbot/shared/Message.svelte
@@ -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}


Test patch:
diff --git a/test/components/test_chatbot.py b/test/components/test_chatbot.py
index 1d5a2b410..03710fdb1 100644
--- a/test/components/test_chatbot.py
+++ b/test/components/test_chatbot.py
@@ -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,