Skip to content

Commit

Permalink
Merge pull request #16 from kpqdap/features/20231213_UserFeedback
Browse files Browse the repository at this point in the history
Features/20231213 user feedback
  • Loading branch information
kpqdap committed Jan 2, 2024
2 parents f6f8ea7 + f886567 commit 38dfce0
Show file tree
Hide file tree
Showing 11 changed files with 330 additions and 15 deletions.
2 changes: 1 addition & 1 deletion src/app/chat/[id]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export default async function Home({ params }: { params: { id: string } }) {

return (
<ChatProvider id={params.id} chats={items} chatThread={thread[0]}>
<ChatUI />
<ChatUI chatId={params.id}/>
</ChatProvider>
);
}
31 changes: 31 additions & 0 deletions src/app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,37 @@
--input: 240 3.7% 15.9%;
--ring: 214 89% 31%;
}

.modal {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: #00000099;
}

.modal-main {
position: fixed;
background: white;
width: 35rem;
height: auto;
top: 40%;
left: 50%;
transform: translate(-50%, -50%);
display: flex;
flex-direction: column;
align-items: center;
border-radius: 20px;
}

.modal.display-block {
display: flex;
}

.modal.display-none {
display: none;
}
}

@layer base {
Expand Down
108 changes: 98 additions & 10 deletions src/components/chat/chat-row.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,32 +2,94 @@
import { ChatRole } from "@/features/chat/chat-services/models";
import { isNotNullOrEmpty } from "@/features/chat/chat-services/utils";
import { cn } from "@/lib/utils";
import { CheckIcon, ClipboardIcon, UserCircle } from "lucide-react";
import { FC, useState } from "react";
import { CheckIcon, ClipboardIcon, UserCircle, ThumbsUp, ThumbsDown } from "lucide-react";
import { FC, useState} from "react";
import { Markdown } from "../markdown/markdown";
import Typography from "../typography";
import { Avatar, AvatarImage } from "../ui/avatar";
import { Button } from "../ui/button";
import Modal from "../ui/modal";
import {CreateUserFeedbackChatId} from "@/features/chat/chat-services/chat-service";



interface ChatRowProps {
chatMessageId: string;
name: string;
profilePicture: string;
message: string;
type: ChatRole;
}

const ChatRow: FC<ChatRowProps> = (props) => {
export const ChatRow: FC<ChatRowProps> = (props) => {
const [isIconChecked, setIsIconChecked] = useState(false);
const toggleIcon = () => {
setIsIconChecked((prevState) => !prevState);
};
const [thumbsUpClicked, setThumbsUpClicked] = useState(false);
const [thumbsDownClicked, setThumbsDownClicked] = useState(false);
const [isModalOpen, setIsModalOpen] = useState(false);
const [feedback, setFeedback] = useState('');
const [reason, setReason] = useState('');


const toggleButton = (buttonId: string) => {
switch (buttonId) {
case 'thumbsUp':
setThumbsUpClicked(prevState => !prevState);
setThumbsDownClicked(false);
setIsIconChecked(false);
break;
case 'thumbsDown':
setThumbsDownClicked(prevState => !prevState);
setThumbsUpClicked(false);
setIsIconChecked(false);
break;
case 'CopyButton':
setIsIconChecked(prevState => !prevState);
setThumbsUpClicked(false);
setThumbsDownClicked(false);
break;
default:
break;
}
};

const handleButtonClick = () => {
toggleIcon();
const handleCopyButton = () => {
toggleButton('CopyButton');
const messageWithAttribution = props.message + "\n\nText generated by QChat";
navigator.clipboard.writeText(messageWithAttribution);
};

async function handleModalSubmit(feedback: string, reason: string): Promise<void> {
setFeedback(feedback);
setReason(reason);
setIsModalOpen(false);
CreateUserFeedbackChatId(props.chatMessageId, feedback, reason);

};


const openModal = () => {
toggleButton('thumbsDown');
setIsModalOpen(true);
};

const closeModal = () => {
setIsModalOpen(false);
};

const buttonStyleThumbsUp = {
backgroundColor: thumbsUpClicked ? 'lightblue' : 'transparent',
} as React.CSSProperties;

const buttonStyleThumbsDown = {
backgroundColor: thumbsDownClicked ? 'lightblue' : 'transparent',
} as React.CSSProperties;


const handleThumbsUpClick = () => {
toggleButton('thumbsUp');

};

return (
<div
className={cn(
Expand Down Expand Up @@ -60,12 +122,39 @@ const ChatRow: FC<ChatRowProps> = (props) => {
{props.name}
</Typography>
</div>
<Button
variant={"ghost"}
size={"sm"}
title="Thumbs up"
className="justify-right flex"
onClick={handleThumbsUpClick}
style={buttonStyleThumbsUp}
>
<ThumbsUp size={16} />
</Button>

<Button
variant={"ghost"}
size={"sm"}
title="Thumbs down"
className="justify-right flex"
onClick={openModal}
>
<ThumbsDown size={16} />
</Button>
<Modal chatThreadId={props.chatMessageId}
open={isModalOpen}
onClose={closeModal}
onSubmit={(chatMessageId,feedback, reason) => {
handleModalSubmit(feedback, reason);
}}
/>
<Button
variant={"ghost"}
size={"sm"}
title="Copy text"
className="justify-right flex"
onClick={handleButtonClick}
onClick={handleCopyButton}
>
{isIconChecked ? (
<CheckIcon size={16} />
Expand All @@ -74,7 +163,6 @@ const ChatRow: FC<ChatRowProps> = (props) => {
)}
</Button>
</div>

<div
className={cn(
"-m-4 p-4 prose prose-slate dark:prose-invert break-words prose-p:leading-relaxed prose-pre:p-0 max-w-non",
Expand Down
109 changes: 109 additions & 0 deletions src/components/ui/modal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import React, { FC, useState, useRef, useEffect } from "react";
import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { XCircle, Ban, FileQuestion } from "lucide-react";
import Typography from "@/components/typography";


interface ModalProps {
chatThreadId: string;
open: boolean;
onClose: () => void;
onSubmit: (chatMessageId: string, feedback: string, reason: string) => void;
}

export default function Modal(props: ModalProps): ReturnType<FC> {
const [feedback, setFeedback] = useState<string>('');
const [reason, setReason] = useState("");
const textAreaRef = useRef<HTMLTextAreaElement>(null); // Reference to the textarea element
const [areTabsEnabled, setTabsEnabled] = useState<boolean>(false); // State to manage TabsTrigger disabled/enabled

// useEffect(() => {
// if (props.open) {
// setFeedback('');
// }
// }, [props.open]);


async function handleFeedbackChange(): Promise<void> {
const textareaValue = textAreaRef.current?.value || "";

if (!areTabsEnabled) {
setTabsEnabled(true);
}

setFeedback(textareaValue);
};


const handleReasonChange = (reason: string) => {
setReason(reason);
};

async function handleSubmit(): Promise<void> {
props.onSubmit(props.chatThreadId,feedback, reason);
setFeedback('');
props.onClose();
};

return (
<div className={`${"modal"} ${props.open ? "display-block" : "display-none"}`}>
<div className="modal-main">
<div className="modal-head">
<Typography variant="h4" className="text-primary">
Submit your feedback
</Typography>
</div>

<div className="col-span-2 gap-5 flex flex-col flex-1">
<textarea
placeholder="Enter your feedback here"
ref={textAreaRef}
rows={6}
cols={50}
className="textarea-with-spacing"
onChange={(event) => handleFeedbackChange()}
// defaultValue={"Enter your feedback here..."}
/>
</div>

<div className="reason-buttons">

<Tabs
defaultValue={""}
onValueChange={(value) => handleReasonChange(value)}
>
<TabsList className="flex w-full justify-center gap-5" >
<TabsTrigger
value="Unsafe"
className="flex items-center justify-center flex-2"
disabled={!areTabsEnabled}
>
<Ban size={20} /> Unsafe
</TabsTrigger>
<TabsTrigger
value="Inaccurate"
className="flex items-center justify-center flex-2"
disabled={!areTabsEnabled}
>
<XCircle size={20} /> Inaccurate
</TabsTrigger>
<TabsTrigger
value="Unhelpful"
className="lex items-center justify-center flex-2"
disabled={!areTabsEnabled}
>
<FileQuestion size={20} /> Unhelpful
</TabsTrigger>
</TabsList>
</Tabs>

</div>

<div className="btn-container">
<button type="button" className="btn" onClick={handleSubmit}>Submit</button>
<button type="button" className="btn" onClick={props.onClose}>Close</button>
</div>
</div>
</div>
);
}
63 changes: 63 additions & 0 deletions src/features/chat/chat-services/chat-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { uniqueId } from "@/features/common/util";
import { SqlQuerySpec } from "@azure/cosmos";
import { CosmosDBContainer } from "../../common/cosmos";
import { ChatMessageModel, MESSAGE_ATTRIBUTE } from "./models";
import { userHashedId } from "@/features/auth/helpers";

export const FindAllChats = async (chatThreadID: string) => {
const container = await CosmosDBContainer.getInstance().getContainer();
Expand Down Expand Up @@ -35,6 +36,66 @@ export const FindAllChats = async (chatThreadID: string) => {
return resources;
};

export const FindChatMessageByID = async (id: string) => {
const container = await CosmosDBContainer.getInstance().getContainer();

const querySpec: SqlQuerySpec = {
query:
"SELECT * FROM root r WHERE r.type=@type AND r.id=@id AND r.isDeleted=@isDeleted",
parameters: [
{
name: "@type",
value: MESSAGE_ATTRIBUTE,
},
{
name: "@id",
value: id,
},
{
name: "@isDeleted",
value: false,
},
],
};


const { resources } = await container.items
.query<ChatMessageModel>(querySpec)
.fetchAll();

return resources;
};


export const CreateUserFeedbackChatId = async (
chatMessageId: string,
feedback: string,
reason: string,
) => {

try {
const container = await CosmosDBContainer.getInstance().getContainer();
const chatMessageModel = await FindChatMessageByID(chatMessageId);

if (chatMessageModel.length !== 0) {
const message = chatMessageModel[0];
message.feedback = feedback;
message.reason = reason;

const itemToUpdate = {
...message,

};

await container.items.upsert(itemToUpdate);
return itemToUpdate;
}
} catch (e) {
console.log("There was an error in saving user feedback", e);
}
};


export const UpsertChat = async (chatModel: ChatMessageModel) => {
const modelToSave: ChatMessageModel = {
...chatModel,
Expand Down Expand Up @@ -78,5 +139,7 @@ export const newChatModel = (): ChatMessageModel => {
type: MESSAGE_ATTRIBUTE,
isDeleted: false,
context: "",
feedback: "",
reason:"",
};
};
Loading

0 comments on commit 38dfce0

Please sign in to comment.