Add predefined feedback option

This commit is contained in:
Weves 2024-05-09 18:51:51 -07:00 committed by Chris Weaver
parent 957d3625c2
commit 7f1ffa3921
13 changed files with 122 additions and 37 deletions

View File

@ -0,0 +1,25 @@
"""Add pre-defined feedback
Revision ID: f1c6478c3fd8
Revises: 643a84a42a33
Create Date: 2024-05-09 18:11:49.210667
"""
from alembic import op
import sqlalchemy as sa
revision = "f1c6478c3fd8"
down_revision = "643a84a42a33"
branch_labels = None
depends_on = None
def upgrade() -> None:
op.add_column(
"chat_feedback",
sa.Column("predefined_feedback", sa.String(), nullable=True),
)
def downgrade() -> None:
op.drop_column("chat_feedback", "predefined_feedback")

View File

@ -148,8 +148,14 @@ def create_chat_message_feedback(
db_session: Session, db_session: Session,
# Slack user requested help from human # Slack user requested help from human
required_followup: bool | None = None, required_followup: bool | None = None,
predefined_feedback: str | None = None, # Added predefined_feedback parameter
) -> None: ) -> None:
if is_positive is None and feedback_text is None and required_followup is None: if (
is_positive is None
and feedback_text is None
and required_followup is None
and predefined_feedback is None
):
raise ValueError("No feedback provided") raise ValueError("No feedback provided")
chat_message = get_chat_message( chat_message = get_chat_message(
@ -164,6 +170,7 @@ def create_chat_message_feedback(
is_positive=is_positive, is_positive=is_positive,
feedback_text=feedback_text, feedback_text=feedback_text,
required_followup=required_followup, required_followup=required_followup,
predefined_feedback=predefined_feedback,
) )
db_session.add(message_feedback) db_session.add(message_feedback)

View File

@ -763,6 +763,7 @@ class ChatMessageFeedback(Base):
is_positive: Mapped[bool | None] = mapped_column(Boolean, nullable=True) is_positive: Mapped[bool | None] = mapped_column(Boolean, nullable=True)
required_followup: Mapped[bool | None] = mapped_column(Boolean, nullable=True) required_followup: Mapped[bool | None] = mapped_column(Boolean, nullable=True)
feedback_text: Mapped[str | None] = mapped_column(Text, nullable=True) feedback_text: Mapped[str | None] = mapped_column(Text, nullable=True)
predefined_feedback: Mapped[str | None] = mapped_column(String, nullable=True)
chat_message: Mapped[ChatMessage] = relationship( chat_message: Mapped[ChatMessage] = relationship(
"ChatMessage", back_populates="chat_message_feedbacks" "ChatMessage", back_populates="chat_message_feedbacks"

View File

@ -288,6 +288,7 @@ def create_chat_feedback(
create_chat_message_feedback( create_chat_message_feedback(
is_positive=feedback.is_positive, is_positive=feedback.is_positive,
feedback_text=feedback.feedback_text, feedback_text=feedback.feedback_text,
predefined_feedback=feedback.predefined_feedback,
chat_message_id=feedback.chat_message_id, chat_message_id=feedback.chat_message_id,
user_id=user_id, user_id=user_id,
db_session=db_session, db_session=db_session,

View File

@ -51,6 +51,7 @@ class ChatFeedbackRequest(BaseModel):
chat_message_id: int chat_message_id: int
is_positive: bool | None = None is_positive: bool | None = None
feedback_text: str | None = None feedback_text: str | None = None
predefined_feedback: str | None = None
@root_validator @root_validator
def check_is_positive_or_feedback_text(cls: BaseModel, values: dict) -> dict: def check_is_positive_or_feedback_text(cls: BaseModel, values: dict) -> dict:

View File

@ -201,6 +201,8 @@ services:
args: args:
- NEXT_PUBLIC_DISABLE_STREAMING=${NEXT_PUBLIC_DISABLE_STREAMING:-false} - NEXT_PUBLIC_DISABLE_STREAMING=${NEXT_PUBLIC_DISABLE_STREAMING:-false}
- NEXT_PUBLIC_NEW_CHAT_DIRECTS_TO_SAME_PERSONA=${NEXT_PUBLIC_NEW_CHAT_DIRECTS_TO_SAME_PERSONA:-false} - NEXT_PUBLIC_NEW_CHAT_DIRECTS_TO_SAME_PERSONA=${NEXT_PUBLIC_NEW_CHAT_DIRECTS_TO_SAME_PERSONA:-false}
- NEXT_PUBLIC_POSITIVE_PREDEFINED_FEEDBACK_OPTIONS=${NEXT_PUBLIC_POSITIVE_PREDEFINED_FEEDBACK_OPTIONS:-}
- NEXT_PUBLIC_NEGATIVE_PREDEFINED_FEEDBACK_OPTIONS=${NEXT_PUBLIC_NEGATIVE_PREDEFINED_FEEDBACK_OPTIONS:-}
depends_on: depends_on:
- api_server - api_server
restart: always restart: always

View File

@ -197,6 +197,8 @@ services:
args: args:
- NEXT_PUBLIC_DISABLE_STREAMING=${NEXT_PUBLIC_DISABLE_STREAMING:-false} - NEXT_PUBLIC_DISABLE_STREAMING=${NEXT_PUBLIC_DISABLE_STREAMING:-false}
- NEXT_PUBLIC_NEW_CHAT_DIRECTS_TO_SAME_PERSONA=${NEXT_PUBLIC_NEW_CHAT_DIRECTS_TO_SAME_PERSONA:-false} - NEXT_PUBLIC_NEW_CHAT_DIRECTS_TO_SAME_PERSONA=${NEXT_PUBLIC_NEW_CHAT_DIRECTS_TO_SAME_PERSONA:-false}
- NEXT_PUBLIC_POSITIVE_PREDEFINED_FEEDBACK_OPTIONS=${NEXT_PUBLIC_POSITIVE_PREDEFINED_FEEDBACK_OPTIONS:-}
- NEXT_PUBLIC_NEGATIVE_PREDEFINED_FEEDBACK_OPTIONS=${NEXT_PUBLIC_NEGATIVE_PREDEFINED_FEEDBACK_OPTIONS:-}
depends_on: depends_on:
- api_server - api_server
restart: always restart: always

View File

@ -73,6 +73,8 @@ services:
args: args:
- NEXT_PUBLIC_DISABLE_STREAMING=${NEXT_PUBLIC_DISABLE_STREAMING:-false} - NEXT_PUBLIC_DISABLE_STREAMING=${NEXT_PUBLIC_DISABLE_STREAMING:-false}
- NEXT_PUBLIC_NEW_CHAT_DIRECTS_TO_SAME_PERSONA=${NEXT_PUBLIC_NEW_CHAT_DIRECTS_TO_SAME_PERSONA:-false} - NEXT_PUBLIC_NEW_CHAT_DIRECTS_TO_SAME_PERSONA=${NEXT_PUBLIC_NEW_CHAT_DIRECTS_TO_SAME_PERSONA:-false}
- NEXT_PUBLIC_POSITIVE_PREDEFINED_FEEDBACK_OPTIONS=${NEXT_PUBLIC_POSITIVE_PREDEFINED_FEEDBACK_OPTIONS:-}
- NEXT_PUBLIC_NEGATIVE_PREDEFINED_FEEDBACK_OPTIONS=${NEXT_PUBLIC_NEGATIVE_PREDEFINED_FEEDBACK_OPTIONS:-}
depends_on: depends_on:
- api_server - api_server
restart: always restart: always

View File

@ -73,6 +73,8 @@ services:
args: args:
- NEXT_PUBLIC_DISABLE_STREAMING=${NEXT_PUBLIC_DISABLE_STREAMING:-false} - NEXT_PUBLIC_DISABLE_STREAMING=${NEXT_PUBLIC_DISABLE_STREAMING:-false}
- NEXT_PUBLIC_NEW_CHAT_DIRECTS_TO_SAME_PERSONA=${NEXT_PUBLIC_NEW_CHAT_DIRECTS_TO_SAME_PERSONA:-false} - NEXT_PUBLIC_NEW_CHAT_DIRECTS_TO_SAME_PERSONA=${NEXT_PUBLIC_NEW_CHAT_DIRECTS_TO_SAME_PERSONA:-false}
- NEXT_PUBLIC_POSITIVE_PREDEFINED_FEEDBACK_OPTIONS=${NEXT_PUBLIC_POSITIVE_PREDEFINED_FEEDBACK_OPTIONS:-}
- NEXT_PUBLIC_NEGATIVE_PREDEFINED_FEEDBACK_OPTIONS=${NEXT_PUBLIC_NEGATIVE_PREDEFINED_FEEDBACK_OPTIONS:-}
depends_on: depends_on:
- api_server - api_server
restart: always restart: always

View File

@ -44,6 +44,13 @@ ENV NEXT_PUBLIC_DISABLE_STREAMING=${NEXT_PUBLIC_DISABLE_STREAMING}
ARG NEXT_PUBLIC_NEW_CHAT_DIRECTS_TO_SAME_PERSONA ARG NEXT_PUBLIC_NEW_CHAT_DIRECTS_TO_SAME_PERSONA
ENV NEXT_PUBLIC_NEW_CHAT_DIRECTS_TO_SAME_PERSONA=${NEXT_PUBLIC_NEW_CHAT_DIRECTS_TO_SAME_PERSONA} ENV NEXT_PUBLIC_NEW_CHAT_DIRECTS_TO_SAME_PERSONA=${NEXT_PUBLIC_NEW_CHAT_DIRECTS_TO_SAME_PERSONA}
# allow user to specify custom feedback options
ARG NEXT_PUBLIC_POSITIVE_PREDEFINED_FEEDBACK_OPTIONS
ENV NEXT_PUBLIC_POSITIVE_PREDEFINED_FEEDBACK_OPTIONS=${NEXT_PUBLIC_POSITIVE_PREDEFINED_FEEDBACK_OPTIONS}
ARG NEXT_PUBLIC_NEGATIVE_PREDEFINED_FEEDBACK_OPTIONS
ENV NEXT_PUBLIC_NEGATIVE_PREDEFINED_FEEDBACK_OPTIONS=${NEXT_PUBLIC_NEGATIVE_PREDEFINED_FEEDBACK_OPTIONS}
RUN npm run build RUN npm run build
# Step 3. Production image, copy all the files and run next # Step 3. Production image, copy all the files and run next
@ -82,6 +89,13 @@ ENV NEXT_PUBLIC_DISABLE_STREAMING=${NEXT_PUBLIC_DISABLE_STREAMING}
ARG NEXT_PUBLIC_NEW_CHAT_DIRECTS_TO_SAME_PERSONA ARG NEXT_PUBLIC_NEW_CHAT_DIRECTS_TO_SAME_PERSONA
ENV NEXT_PUBLIC_NEW_CHAT_DIRECTS_TO_SAME_PERSONA=${NEXT_PUBLIC_NEW_CHAT_DIRECTS_TO_SAME_PERSONA} ENV NEXT_PUBLIC_NEW_CHAT_DIRECTS_TO_SAME_PERSONA=${NEXT_PUBLIC_NEW_CHAT_DIRECTS_TO_SAME_PERSONA}
# allow user to specify custom feedback options
ARG NEXT_PUBLIC_POSITIVE_PREDEFINED_FEEDBACK_OPTIONS
ENV NEXT_PUBLIC_POSITIVE_PREDEFINED_FEEDBACK_OPTIONS=${NEXT_PUBLIC_POSITIVE_PREDEFINED_FEEDBACK_OPTIONS}
ARG NEXT_PUBLIC_NEGATIVE_PREDEFINED_FEEDBACK_OPTIONS
ENV NEXT_PUBLIC_NEGATIVE_PREDEFINED_FEEDBACK_OPTIONS=${NEXT_PUBLIC_NEGATIVE_PREDEFINED_FEEDBACK_OPTIONS}
# Note: Don't expose ports here, Compose will handle that for us if necessary. # Note: Don't expose ports here, Compose will handle that for us if necessary.
# If you want to run this without compose, specify the ports to # If you want to run this without compose, specify the ports to
# expose via cli # expose via cli

View File

@ -576,7 +576,8 @@ export function ChatPage({
const onFeedback = async ( const onFeedback = async (
messageId: number, messageId: number,
feedbackType: FeedbackType, feedbackType: FeedbackType,
feedbackDetails: string feedbackDetails: string,
predefinedFeedback: string | undefined
) => { ) => {
if (chatSessionId === null) { if (chatSessionId === null) {
return; return;
@ -585,7 +586,8 @@ export function ChatPage({
const response = await handleChatFeedback( const response = await handleChatFeedback(
messageId, messageId,
feedbackType, feedbackType,
feedbackDetails feedbackDetails,
predefinedFeedback
); );
if (response.ok) { if (response.ok) {
@ -648,11 +650,12 @@ export function ChatPage({
<FeedbackModal <FeedbackModal
feedbackType={currentFeedback[0]} feedbackType={currentFeedback[0]}
onClose={() => setCurrentFeedback(null)} onClose={() => setCurrentFeedback(null)}
onSubmit={(feedbackDetails) => { onSubmit={({ message, predefinedFeedback }) => {
onFeedback( onFeedback(
currentFeedback[1], currentFeedback[1],
currentFeedback[0], currentFeedback[0],
feedbackDetails message,
predefinedFeedback
); );
setCurrentFeedback(null); setCurrentFeedback(null);
}} }}

View File

@ -150,7 +150,8 @@ export async function nameChatSession(chatSessionId: number, message: string) {
export async function handleChatFeedback( export async function handleChatFeedback(
messageId: number, messageId: number,
feedback: FeedbackType, feedback: FeedbackType,
feedbackDetails: string feedbackDetails: string,
predefinedFeedback: string | undefined
) { ) {
const response = await fetch("/api/chat/create-chat-message-feedback", { const response = await fetch("/api/chat/create-chat-message-feedback", {
method: "POST", method: "POST",
@ -161,11 +162,11 @@ export async function handleChatFeedback(
chat_message_id: messageId, chat_message_id: messageId,
is_positive: feedback === "like", is_positive: feedback === "like",
feedback_text: feedbackDetails, feedback_text: feedbackDetails,
predefined_feedback: predefinedFeedback,
}), }),
}); });
return response; return response;
} }
export async function renameChatSession( export async function renameChatSession(
chatSessionId: number, chatSessionId: number,
newName: string newName: string

View File

@ -5,10 +5,23 @@ import { FeedbackType } from "../types";
import { FiThumbsDown, FiThumbsUp } from "react-icons/fi"; import { FiThumbsDown, FiThumbsUp } from "react-icons/fi";
import { ModalWrapper } from "./ModalWrapper"; import { ModalWrapper } from "./ModalWrapper";
const predefinedPositiveFeedbackOptions =
process.env.NEXT_PUBLIC_POSITIVE_PREDEFINED_FEEDBACK_OPTIONS?.split(",") ||
[];
const predefinedNegativeFeedbackOptions =
process.env.NEXT_PUBLIC_NEGATIVE_PREDEFINED_FEEDBACK_OPTIONS?.split(",") || [
"Retrieved documents were not relevant",
"AI misread the documents",
"Cited source had incorrect information",
];
interface FeedbackModalProps { interface FeedbackModalProps {
feedbackType: FeedbackType; feedbackType: FeedbackType;
onClose: () => void; onClose: () => void;
onSubmit: (feedbackDetails: string) => void; onSubmit: (feedbackDetails: {
message: string;
predefinedFeedback?: string;
}) => void;
} }
export const FeedbackModal = ({ export const FeedbackModal = ({
@ -17,6 +30,23 @@ export const FeedbackModal = ({
onSubmit, onSubmit,
}: FeedbackModalProps) => { }: FeedbackModalProps) => {
const [message, setMessage] = useState(""); const [message, setMessage] = useState("");
const [predefinedFeedback, setPredefinedFeedback] = useState<
string | undefined
>();
const handlePredefinedFeedback = (feedback: string) => {
setPredefinedFeedback(feedback);
};
const handleSubmit = () => {
onSubmit({ message, predefinedFeedback });
onClose();
};
const predefinedFeedbackOptions =
feedbackType === "like"
? predefinedPositiveFeedbackOptions
: predefinedNegativeFeedbackOptions;
return ( return (
<ModalWrapper onClose={onClose} modalClassName="max-w-5xl"> <ModalWrapper onClose={onClose} modalClassName="max-w-5xl">
@ -31,49 +61,43 @@ export const FeedbackModal = ({
</div> </div>
Provide additional feedback Provide additional feedback
</h2> </h2>
<div className="mb-4 flex flex-wrap justify-start">
{predefinedFeedbackOptions.map((feedback, index) => (
<button
key={index}
className={`bg-border hover:bg-hover text-default py-2 px-4 rounded m-1
${predefinedFeedback === feedback && "ring-2 ring-accent"}`}
onClick={() => handlePredefinedFeedback(feedback)}
>
{feedback}
</button>
))}
</div>
<textarea <textarea
autoFocus autoFocus
className={` className={`
w-full w-full flex-grow
flex-grow border border-border-strong rounded
ml-2 outline-none placeholder-subtle
border pl-4 pr-4 py-4 bg-background
border-border-strong overflow-hidden h-28
rounded whitespace-normal resize-none
outline-none break-all overscroll-contain
placeholder-subtle `}
pl-4
pr-14
py-4
bg-background
overflow-hidden
h-28
whitespace-normal
resize-none
break-all
overscroll-contain`}
role="textarea" role="textarea"
aria-multiline aria-multiline
placeholder={ placeholder={
feedbackType === "like" feedbackType === "like"
? "What did you like about this response?" ? "(Optional) What did you like about this response?"
: "What was the issue with the response? How could it be improved?" : "(Optional) What was the issue with the response? How could it be improved?"
} }
value={message} value={message}
onChange={(e) => setMessage(e.target.value)} onChange={(e) => setMessage(e.target.value)}
onKeyDown={(event) => {
if (event.key === "Enter" && !event.shiftKey) {
onSubmit(message);
event.preventDefault();
}
}}
suppressContentEditableWarning={true}
/> />
<div className="flex mt-2"> <div className="flex mt-2">
<button <button
className="bg-accent text-white py-2 px-4 rounded hover:bg-blue-600 focus:outline-none mx-auto" className="bg-accent text-white py-2 px-4 rounded hover:bg-blue-600 focus:outline-none mx-auto"
onClick={() => onSubmit(message)} onClick={handleSubmit}
> >
Submit feedback Submit feedback
</button> </button>