Skip to content
86 changes: 86 additions & 0 deletions frontend/src/components/Modals/ChatFeedback/index.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import React, { useState } from "react";
import ModalWrapper from "@/components/ModalWrapper";
import { X } from "@phosphor-icons/react";
import Workspace from "@/models/workspace";
import { useTranslation } from "react-i18next";

export default function ChatFeedbackModal({
isOpen,
hideModal,
chatId,
slug,
onSubmitted,
}) {
const { t } = useTranslation();
const [comment, setComment] = useState("");
const [submitting, setSubmitting] = useState(false);

const handleSubmit = async (e) => {
e.preventDefault();
if (!chatId || !slug) {
hideModal();
return;
}
setSubmitting(true);
try {
// Only forward the comment to parent if there's content; feedback is optional
if (comment && comment.trim() !== "") {
onSubmitted && onSubmitted(comment.trim());
}
} catch (err) {
// ignore errors for now
}
setSubmitting(false);
hideModal();
};

return (
<ModalWrapper isOpen={isOpen}>
<div className="w-full max-w-lg bg-theme-bg-secondary rounded-lg shadow border-2 border-theme-modal-border overflow-hidden">
<div className="relative p-6 border-b rounded-t border-theme-modal-border">
<div className="w-full flex gap-x-2 items-center">
<h3 className="text-xl font-semibold text-white overflow-hidden overflow-ellipsis whitespace-nowrap">
{t("chat_window.provide_feedback")}
</h3>
</div>
<button
onClick={hideModal}
type="button"
className="absolute top-4 right-4 transition-all duration-300 bg-transparent rounded-lg text-sm p-1 inline-flex items-center hover:bg-theme-modal-border hover:border-theme-modal-border hover:border-opacity-50 border-transparent border"
>
<X size={24} weight="bold" className="text-white" />
</button>
</div>
<form onSubmit={handleSubmit}>
<div className="py-7 px-9 space-y-2">
<p className="text-sm text-theme-settings-input-placeholder">
{t("chat_window.bad_response_optional_feedback")}
</p>
<textarea
value={comment}
onChange={(e) => setComment(e.target.value)}
className="w-full min-h-[120px] p-3 rounded bg-theme-settings-input-bg text-white placeholder:text-theme-settings-input-placeholder outline-none"
placeholder={t("chat_window.bad_response_placeholder")}
/>
</div>
<div className="flex w-full justify-end items-center p-6 space-x-2 border-t border-theme-modal-border rounded-b">
<button
type="button"
onClick={hideModal}
className="transition-all duration-300 bg-transparent border border-theme-modal-border text-white hover:opacity-60 px-4 py-2 rounded-lg text-sm"
>
{t("chat_window.cancel")}
</button>
<button
type="submit"
disabled={submitting}
className="transition-all duration-300 bg-white text-black hover:opacity-60 px-4 py-2 rounded-lg text-sm"
>
{submitting ? t("common.saving") : t("common.save")}
</button>
</div>
</form>
</div>
</ModalWrapper>
);
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,14 @@
import React, { memo, useState } from "react";
import useCopyText from "@/hooks/useCopyText";
import { Check, ThumbsUp, ArrowsClockwise, Copy } from "@phosphor-icons/react";
import {
Check,
ThumbsUp,
ThumbsDown,
ArrowsClockwise,
Copy,
} from "@phosphor-icons/react";
import Workspace from "@/models/workspace";
import ChatFeedbackModal from "@/components/Modals/ChatFeedback";
import { EditMessageAction } from "./EditMessage";
import RenderMetrics from "./RenderMetrics";
import ActionMenu from "./ActionMenu";
Expand All @@ -22,11 +29,19 @@ const Actions = ({
}) => {
const { t } = useTranslation();
const [selectedFeedback, setSelectedFeedback] = useState(feedbackScore);
const [showFeedbackModal, setShowFeedbackModal] = useState(false);
const [submittingComment, setSubmittingComment] = useState(false);
const handleFeedback = async (newFeedback) => {
const updatedFeedback =
selectedFeedback === newFeedback ? null : newFeedback;
// persist feedback score first
await Workspace.updateChatFeedback(chatId, slug, updatedFeedback);
setSelectedFeedback(updatedFeedback);

// If user just set negative feedback (not unsetting), show optional feedback modal
if (updatedFeedback === false) {
setShowFeedbackModal(true);
}
};

return (
Expand Down Expand Up @@ -55,6 +70,15 @@ const Actions = ({
IconComponent={ThumbsUp}
/>
)}
{chatId && role !== "user" && !isEditing && (
<FeedbackButton
isSelected={selectedFeedback === false}
handleFeedback={() => handleFeedback(false)}
tooltipId="feedback-button"
tooltipContent={t("chat_window.bad_response")}
IconComponent={ThumbsDown}
/>
)}
<ActionMenu
chatId={chatId}
forkThread={forkThread}
Expand All @@ -64,6 +88,20 @@ const Actions = ({
</div>
</div>
<RenderMetrics metrics={metrics} />
<ChatFeedbackModal
isOpen={showFeedbackModal}
hideModal={() => setShowFeedbackModal(false)}
chatId={chatId}
slug={slug}
onSubmitted={async (comment) => {
if (submittingComment) return;
setSubmittingComment(true);
try {
await Workspace.submitChatFeedbackComment(chatId, slug, comment);
} catch (e) {}
setSubmittingComment(false);
}}
/>
</div>
);
};
Expand Down
4 changes: 4 additions & 0 deletions frontend/src/locales/ar/common.js
Original file line number Diff line number Diff line change
Expand Up @@ -660,6 +660,10 @@ const TRANSLATIONS = {
regenerate: null,
regenerate_response: null,
good_response: null,
bad_response: null,
provide_feedback: null,
bad_response_optional_feedback: null,
bad_response_placeholder: null,
more_actions: null,
hide_citations: null,
show_citations: null,
Expand Down
4 changes: 4 additions & 0 deletions frontend/src/locales/da/common.js
Original file line number Diff line number Diff line change
Expand Up @@ -698,6 +698,10 @@ const TRANSLATIONS = {
regenerate: null,
regenerate_response: null,
good_response: null,
bad_response: null,
provide_feedback: null,
bad_response_optional_feedback: null,
bad_response_placeholder: null,
more_actions: null,
hide_citations: null,
show_citations: null,
Expand Down
4 changes: 4 additions & 0 deletions frontend/src/locales/de/common.js
Original file line number Diff line number Diff line change
Expand Up @@ -897,6 +897,10 @@ const TRANSLATIONS = {
regenerate: "Neu generieren",
regenerate_response: "Antwort neu generieren",
good_response: "Gute Antwort",
bad_response: "Schlechte Antwort",
provide_feedback: null,
bad_response_optional_feedback: null,
bad_response_placeholder: null,
more_actions: "Weitere Aktionen",
hide_citations: "Quellenangaben ausblenden",
show_citations: "Quellenangaben anzeigen",
Expand Down
4 changes: 4 additions & 0 deletions frontend/src/locales/en/common.js
Original file line number Diff line number Diff line change
Expand Up @@ -955,6 +955,10 @@ const TRANSLATIONS = {
regenerate: "Regenerate",
regenerate_response: "Regenerate response",
good_response: "Good response",
bad_response: "Bad response",
provide_feedback: "Provide Feedback",
bad_response_optional_feedback: "Optional feedback (help us improve)",
bad_response_placeholder: "Tell us what was wrong (optional)",
more_actions: "More actions",
hide_citations: "Hide citations",
show_citations: "Show citations",
Expand Down
4 changes: 4 additions & 0 deletions frontend/src/locales/es/common.js
Original file line number Diff line number Diff line change
Expand Up @@ -910,6 +910,10 @@ const TRANSLATIONS = {
regenerate: "Regenerar",
regenerate_response: "Regenerar respuesta",
good_response: "Buena respuesta",
bad_response: "Mala respuesta",
provide_feedback: null,
bad_response_optional_feedback: null,
bad_response_placeholder: null,
more_actions: "MΓ‘s acciones",
hide_citations: "Ocultar citas",
show_citations: "Mostrar citas",
Expand Down
1 change: 1 addition & 0 deletions frontend/src/locales/et/common.js
Original file line number Diff line number Diff line change
Expand Up @@ -857,6 +857,7 @@ const TRANSLATIONS = {
regenerate: "Loo uuesti",
regenerate_response: "Loo vastus uuesti",
good_response: "Hea vastus",
bad_response: "Halb vastus",
more_actions: "Rohkem toiminguid",
hide_citations: "Peida viited",
show_citations: "NΓ€ita viiteid",
Expand Down
4 changes: 4 additions & 0 deletions frontend/src/locales/fa/common.js
Original file line number Diff line number Diff line change
Expand Up @@ -652,6 +652,10 @@ const TRANSLATIONS = {
regenerate: null,
regenerate_response: null,
good_response: null,
bad_response: null,
provide_feedback: null,
bad_response_optional_feedback: null,
bad_response_placeholder: null,
more_actions: null,
hide_citations: null,
show_citations: null,
Expand Down
4 changes: 4 additions & 0 deletions frontend/src/locales/fr/common.js
Original file line number Diff line number Diff line change
Expand Up @@ -660,6 +660,10 @@ const TRANSLATIONS = {
regenerate: null,
regenerate_response: null,
good_response: null,
bad_response: null,
provide_feedback: null,
bad_response_optional_feedback: null,
bad_response_placeholder: null,
more_actions: null,
hide_citations: null,
show_citations: null,
Expand Down
4 changes: 4 additions & 0 deletions frontend/src/locales/he/common.js
Original file line number Diff line number Diff line change
Expand Up @@ -864,6 +864,10 @@ const TRANSLATIONS = {
regenerate: "Χ¦Χ•Χ¨ ΧžΧ—Χ“Χ©",
regenerate_response: "Χ¦Χ•Χ¨ ΧͺΧ’Χ•Χ‘Χ” ΧžΧ—Χ“Χ©",
good_response: "ΧͺΧ’Χ•Χ‘Χ” Χ˜Χ•Χ‘Χ”",
bad_response: "ΧͺΧ’Χ•Χ‘Χ” Χ¨Χ’Χ”",
provide_feedback: null,
bad_response_optional_feedback: null,
bad_response_placeholder: null,
more_actions: "Χ€Χ’Χ•ΧœΧ•Χͺ Χ Χ•Χ‘Χ€Χ•Χͺ",
hide_citations: "Χ”Χ‘ΧͺΧ¨ Χ¦Χ™Χ˜Χ•Χ˜Χ™Χ",
show_citations: "Χ”Χ¦Χ’ Χ¦Χ™Χ˜Χ•Χ˜Χ™Χ",
Expand Down
4 changes: 4 additions & 0 deletions frontend/src/locales/it/common.js
Original file line number Diff line number Diff line change
Expand Up @@ -658,6 +658,10 @@ const TRANSLATIONS = {
regenerate: null,
regenerate_response: null,
good_response: null,
bad_response: null,
provide_feedback: null,
bad_response_optional_feedback: null,
bad_response_placeholder: null,
more_actions: null,
hide_citations: null,
show_citations: null,
Expand Down
4 changes: 4 additions & 0 deletions frontend/src/locales/ja/common.js
Original file line number Diff line number Diff line change
Expand Up @@ -690,6 +690,10 @@ const TRANSLATIONS = {
regenerate: null,
regenerate_response: null,
good_response: null,
bad_response: null,
provide_feedback: null,
bad_response_optional_feedback: null,
bad_response_placeholder: null,
more_actions: null,
hide_citations: null,
show_citations: null,
Expand Down
4 changes: 4 additions & 0 deletions frontend/src/locales/ko/common.js
Original file line number Diff line number Diff line change
Expand Up @@ -873,6 +873,10 @@ const TRANSLATIONS = {
regenerate: "λ‹€μ‹œ 생성",
regenerate_response: "응닡 λ‹€μ‹œ 생성",
good_response: "쒋은 λ‹΅λ³€",
bad_response: "λ‚˜μœ λ‹΅λ³€",
provide_feedback: null,
bad_response_optional_feedback: null,
bad_response_placeholder: null,
more_actions: "더 λ§Žμ€ μž‘μ—…",
hide_citations: "인용 숨기기",
show_citations: "인용 보기",
Expand Down
4 changes: 4 additions & 0 deletions frontend/src/locales/lv/common.js
Original file line number Diff line number Diff line change
Expand Up @@ -889,6 +889,10 @@ const TRANSLATIONS = {
regenerate: null,
regenerate_response: null,
good_response: null,
bad_response: null,
provide_feedback: null,
bad_response_optional_feedback: null,
bad_response_placeholder: null,
more_actions: null,
hide_citations: null,
show_citations: null,
Expand Down
4 changes: 4 additions & 0 deletions frontend/src/locales/nl/common.js
Original file line number Diff line number Diff line change
Expand Up @@ -655,6 +655,10 @@ const TRANSLATIONS = {
regenerate: null,
regenerate_response: null,
good_response: null,
bad_response: null,
provide_feedback: null,
bad_response_optional_feedback: null,
bad_response_placeholder: null,
more_actions: null,
hide_citations: null,
show_citations: null,
Expand Down
4 changes: 4 additions & 0 deletions frontend/src/locales/pl/common.js
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,10 @@ const TRANSLATIONS = {
selection: "WybΓ³r modelu",
saving: "Zapisywanie...",
save: "Zapisz zmiany",
bad_response: "ZΕ‚a odpowiedΕΊ",
provide_feedback: null,
bad_response_optional_feedback: null,
bad_response_placeholder: null,
previous: "Poprzednia strona",
next: "NastΔ™pna strona",
optional: "Opcjonalnie",
Expand Down
4 changes: 4 additions & 0 deletions frontend/src/locales/pt_BR/common.js
Original file line number Diff line number Diff line change
Expand Up @@ -870,6 +870,10 @@ const TRANSLATIONS = {
regenerate: "Regerar",
regenerate_response: "Regerar resposta",
good_response: "Resposta satisfatΓ³ria",
bad_response: "Resposta ruim",
provide_feedback: null,
bad_response_optional_feedback: null,
bad_response_placeholder: null,
more_actions: "Mais aΓ§Γ΅es",
hide_citations: "Esconder citaΓ§Γ΅es",
show_citations: "Exibir citaΓ§Γ΅es",
Expand Down
4 changes: 4 additions & 0 deletions frontend/src/locales/ru/common.js
Original file line number Diff line number Diff line change
Expand Up @@ -699,6 +699,10 @@ const TRANSLATIONS = {
regenerate: null,
regenerate_response: null,
good_response: null,
bad_response: null,
provide_feedback: null,
bad_response_optional_feedback: null,
bad_response_placeholder: null,
more_actions: null,
hide_citations: null,
show_citations: null,
Expand Down
4 changes: 4 additions & 0 deletions frontend/src/locales/vn/common.js
Original file line number Diff line number Diff line change
Expand Up @@ -654,6 +654,10 @@ const TRANSLATIONS = {
regenerate: null,
regenerate_response: null,
good_response: null,
bad_response: null,
provide_feedback: null,
bad_response_optional_feedback: null,
bad_response_placeholder: null,
more_actions: null,
hide_citations: null,
show_citations: null,
Expand Down
4 changes: 4 additions & 0 deletions frontend/src/locales/zh/common.js
Original file line number Diff line number Diff line change
Expand Up @@ -832,6 +832,10 @@ const TRANSLATIONS = {
regenerate: "重新",
regenerate_response: "ι‡ζ–°ε›žεΊ”",
good_response: "反应良ε₯½",
bad_response: "δΈθ‰―ε›žε€",
provide_feedback: null,
bad_response_optional_feedback: null,
bad_response_placeholder: null,
more_actions: "ζ›΄ε€šζ“δ½œ",
hide_citations: "ιšθ—εΌ•ζ–‡",
show_citations: "ζ˜Ύη€ΊεΌ•ζ–‡",
Expand Down
4 changes: 4 additions & 0 deletions frontend/src/locales/zh_TW/common.js
Original file line number Diff line number Diff line change
Expand Up @@ -661,6 +661,10 @@ const TRANSLATIONS = {
regenerate: "重新",
regenerate_response: "ι‡ζ–°ε›žζ‡‰",
good_response: "反應良ε₯½",
bad_response: "δΈθ‰―ε›žζ‡‰",
provide_feedback: null,
bad_response_optional_feedback: null,
bad_response_placeholder: null,
more_actions: "ζ›΄ε€šζ“δ½œ",
hide_citations: "ιš±θ—εΌ•ζ–‡",
show_citations: "ι‘―η€ΊεΌ•ζ–‡",
Expand Down
14 changes: 14 additions & 0 deletions frontend/src/models/workspace.js
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,20 @@ const Workspace = {
.catch(() => false);
return result;
},
submitChatFeedbackComment: async function (chatId, slug, comment) {
if (!chatId || !slug || !comment) return false;
const result = await fetch(
`${API_BASE}/workspace/${slug}/chat-feedback/${chatId}/comment`,
{
method: "POST",
headers: baseHeaders(),
body: JSON.stringify({ comment }),
}
)
.then((res) => res.ok)
.catch(() => false);
return result;
},

deleteChats: async function (slug = "", chatIds = []) {
return await fetch(`${API_BASE}/workspace/${slug}/delete-chats`, {
Expand Down
Loading