Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Reset flagged values when switching conversations in chat history #10292

Merged
merged 23 commits into from
Jan 6, 2025
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changeset/short-rice-clean.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@gradio/chatbot": minor
"gradio": minor
---

feat:Store flagged values as part of the saved chat history
2 changes: 1 addition & 1 deletion demo/chatinterface_streaming_echo/run.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ def slow_echo(message, history):
slow_echo,
type="messages",
flagging_mode="manual",
flagging_options=["Like", "Spam", "Inappropriate", "Other"],
flagging_options=["Like", "Spam", "Inappropriate", "Other"],
save_history=True,
)

Expand Down
102 changes: 85 additions & 17 deletions gradio/chat_interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@
)
from gradio.components.multimodal_textbox import MultimodalPostprocess, MultimodalValue
from gradio.context import get_blocks_context
from gradio.events import Dependency, EditData, SelectData
from gradio.events import Dependency, EditData, LikeData, SelectData
from gradio.flagging import ChatCSVLogger
from gradio.helpers import create_examples as Examples # noqa: N812
from gradio.helpers import special_args, update
Expand Down Expand Up @@ -249,7 +249,10 @@ def __init__(

with self:
self.saved_conversations = BrowserState(
[], storage_key="_saved_conversations"
[], storage_key=f"_saved_conversations_{self._id}"
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adds a self._id in case your Gradio app has multiple gr.ChatInterface-s

Suggested change
[], storage_key=f"_saved_conversations_{self._id}"
[], storage_key=f"_saved_conversations_{self._id}"

)
self.saved_feedback_values = BrowserState(
{}, storage_key=f"_saved_feedback_values_{self._id}"
)
self.conversation_id = State(None)
self.saved_input = State() # Stores the most recent user message
Expand Down Expand Up @@ -462,10 +465,62 @@ def _delete_conversation(
self,
index: int | None,
saved_conversations: list[list[MessageDict]],
saved_feedback_values: dict[str, list[str | None]],
):
if index is not None:
saved_conversations.pop(index)
return None, saved_conversations
saved_feedback_values.pop(str(index), [])
return None, saved_conversations, saved_feedback_values

def _flag_message(
self,
conversation: list[MessageDict],
conversation_id: int,
feedback_values: dict[str, list[str | None]],
like_data: LikeData,
) -> dict[str, list[str | None]]:
assistant_indices = [
i for i, msg in enumerate(conversation) if msg["role"] == "assistant"
]
assistant_index = assistant_indices.index(like_data.index) # type: ignore
value = (
"Like"
if like_data.liked is True
else "Dislike"
if like_data.liked is False
else like_data.liked
)
feedback_value = feedback_values.get(str(conversation_id), [])
if len(feedback_value) <= assistant_index:
while len(feedback_value) <= assistant_index:
feedback_value.append(None)
feedback_value[assistant_index] = value
feedback_values[str(conversation_id)] = feedback_value
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

orjson requires dictionaries to have str keys, otherwise serialization fails and the gradio app crashes.

return feedback_values

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]],
feedback_values: dict[str, list[str | None]],
):
feedback_value = feedback_values.get(str(index), [])
return (
index,
Chatbot(
value=conversations[index], # type: ignore
feedback_value=feedback_value,
),
)

def _setup_events(self) -> None:
from gradio import on
Expand Down Expand Up @@ -614,8 +669,24 @@ def _setup_events(self) -> None:

self.chatbot.clear(**synchronize_chat_state_kwargs).then(
self._delete_conversation,
[self.conversation_id, self.saved_conversations],
[self.conversation_id, self.saved_conversations],
[
self.conversation_id,
self.saved_conversations,
self.saved_feedback_values,
],
[
self.conversation_id,
self.saved_conversations,
self.saved_feedback_values,
],
show_api=False,
queue=False,
)

self.chatbot.like(
self._flag_message,
[self.chatbot, self.conversation_id, self.saved_feedback_values],
[self.saved_feedback_values],
show_api=False,
queue=False,
)
Expand Down Expand Up @@ -645,25 +716,22 @@ def _setup_events(self) -> None:
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):
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Decided to avoid using decorator syntax for consistency with the rest of the file

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]),
[self.chat_history_dataset, self.saved_conversations],
self._load_conversation,
[
self.chat_history_dataset,
self.saved_conversations,
self.saved_feedback_values,
],
[self.conversation_id, self.chatbot],
show_api=False,
queue=False,
Expand Down
3 changes: 3 additions & 0 deletions gradio/components/chatbot.py
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,7 @@ def __init__(
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,
Expand Down Expand Up @@ -234,6 +235,7 @@ def __init__(
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".
Expand Down Expand Up @@ -290,6 +292,7 @@ def __init__(
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,
Expand Down
2 changes: 1 addition & 1 deletion gradio/events.py
Original file line number Diff line number Diff line change
Expand Up @@ -280,7 +280,7 @@ def test(value, like_data: gr.LikeData):
"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")])
Expand Down
2 changes: 1 addition & 1 deletion gradio/icons/plus.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 2 additions & 0 deletions js/chatbot/Index.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -128,6 +129,7 @@
selectable={_selectable}
{likeable}
{feedback_options}
{feedback_value}
{show_share_button}
{show_copy_all_button}
value={_value}
Expand Down
7 changes: 6 additions & 1 deletion js/chatbot/shared/ButtonPanel.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -94,7 +95,11 @@
/>
{/if}
{#if likeable}
<LikeDislike {handle_action} {feedback_options} />
<LikeDislike
{handle_action}
{feedback_options}
selected={current_feedback}
/>
{/if}
{/if}
</IconButtonWrapper>
Expand Down
15 changes: 12 additions & 3 deletions js/chatbot/shared/ChatBot.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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}
Expand All @@ -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)}
Expand Down
32 changes: 15 additions & 17 deletions js/chatbot/shared/LikeDislike.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -9,38 +9,36 @@

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}
Expand All @@ -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}
Expand Down
6 changes: 5 additions & 1 deletion js/chatbot/shared/Message.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -102,6 +103,7 @@
layout: "bubble" | "panel";
avatar: FileData | null;
dispatch: any;
current_feedback: string | null;
};

let button_panel_props: ButtonPanelProps;
Expand All @@ -119,7 +121,8 @@
position: role === "user" ? "right" : "left",
avatar: avatar_img,
layout,
dispatch
dispatch,
current_feedback
};
</script>

Expand Down Expand Up @@ -235,6 +238,7 @@
{#if layout === "panel"}
<ButtonPanel
{...button_panel_props}
{current_feedback}
on:copy={(e) => dispatch("copy", e.detail)}
/>
{/if}
Expand Down
Loading