Skip to content

Commit

Permalink
Merge pull request #1117 from ManishMadan2882/main
Browse files Browse the repository at this point in the history
Adding option to collect feedback in React widget
  • Loading branch information
dartpain authored Sep 10, 2024
2 parents 72842ec + e318228 commit 4aeaec9
Show file tree
Hide file tree
Showing 7 changed files with 132 additions and 22 deletions.
12 changes: 4 additions & 8 deletions application/api/user/routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,14 +84,10 @@ def api_feedback():
question = data["question"]
answer = data["answer"]
feedback = data["feedback"]

feedback_collection.insert_one(
{
"question": question,
"answer": answer,
"feedback": feedback,
}
)
new_doc = {"question": question, "answer": answer, "feedback": feedback}
if "api_key" in data:
new_doc["api_key"] = data["api_key"]
feedback_collection.insert_one(new_doc)
return {"status": "ok"}


Expand Down
14 changes: 13 additions & 1 deletion extensions/react-widget/package.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"name": "docsgpt-react",
"name": "docsgpt",
"version": "0.4.2",
"private": false,
"description": "DocsGPT 🦖 is an innovative open-source tool designed to simplify the retrieval of information from project documentation using advanced GPT models 🤖.",
Expand All @@ -11,6 +11,18 @@
"dist",
"package.json"
],
"targets": {
"modern": {
"engines": {
"browsers": "Chrome 80"
}
},
"legacy": {
"engines": {
"browsers": "> 0.5%, last 2 versions, not dead"
}
}
},
"@parcel/resolver-default": {
"packageExports": true
},
Expand Down
4 changes: 4 additions & 0 deletions extensions/react-widget/src/assets/dislike.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions extensions/react-widget/src/assets/like.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
90 changes: 80 additions & 10 deletions extensions/react-widget/src/components/DocsGPTWidget.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
"use client";
import React from 'react'
import React, { useRef } from 'react'
import DOMPurify from 'dompurify';
import styled, { keyframes, createGlobalStyle } from 'styled-components';
import { PaperPlaneIcon, RocketIcon, ExclamationTriangleIcon, Cross2Icon } from '@radix-ui/react-icons';
import { MESSAGE_TYPE, Query, Status, WidgetProps } from '../types/index';
import { fetchAnswerStreaming } from '../requests/streamingApi';
import { FEEDBACK, MESSAGE_TYPE, Query, Status, WidgetProps } from '../types/index';
import { fetchAnswerStreaming, sendFeedback } from '../requests/streamingApi';
import { ThemeProvider } from 'styled-components';
import Like from "../assets/like.svg"
import Dislike from "../assets/dislike.svg"
import MarkdownIt from 'markdown-it';
const themes = {
dark: {
Expand Down Expand Up @@ -63,6 +65,10 @@ const GlobalStyles = createGlobalStyle`
background-color: #646464;
color: #fff !important;
}
.response code {
white-space: pre-wrap !important;
line-break: loose !important;
}
`;
const Overlay = styled.div`
position: fixed;
Expand Down Expand Up @@ -195,19 +201,32 @@ const Conversation = styled.div<{ size: string }>`
width:${props => props.size === 'large' ? '90vw' : props.size === 'medium' ? '60vw' : '400px'} !important;
}
`;

const Feedback = styled.div`
background-color: transparent;
font-weight: normal;
gap: 12px;
display: flex;
padding: 6px;
clear: both;
`;
const MessageBubble = styled.div<{ type: MESSAGE_TYPE }>`
display: flex;
display: block;
font-size: 16px;
justify-content: ${props => props.type === 'QUESTION' ? 'flex-end' : 'flex-start'};
margin: 0.5rem;
position: relative;
width: 100%;;
float: right;
margin: 0rem;
&:hover ${Feedback} * {
visibility: visible !important;
}
`;
const Message = styled.div<{ type: MESSAGE_TYPE }>`
background: ${props => props.type === 'QUESTION' ?
'linear-gradient(to bottom right, #8860DB, #6D42C5)' :
props.theme.secondary.bg};
color: ${props => props.type === 'ANSWER' ? props.theme.primary.text : '#fff'};
border: none;
float: ${props => props.type === 'QUESTION' ? 'right' : 'left'};
max-width: ${props => props.type === 'ANSWER' ? '100%' : '80'};
overflow: auto;
margin: 4px;
Expand Down Expand Up @@ -315,6 +334,7 @@ const HeroDescription = styled.p`
font-size: 14px;
line-height: 1.5;
`;

const Hero = ({ title, description, theme }: { title: string, description: string, theme: string }) => {
return (
<>
Expand Down Expand Up @@ -345,14 +365,16 @@ export const DocsGPTWidget = ({
size = 'small',
theme = 'dark',
buttonIcon = 'https://d3dg1063dc54p9.cloudfront.net/widget/message.svg',
buttonBg = 'linear-gradient(to bottom right, #5AF0EC, #E80D9D)'
buttonBg = 'linear-gradient(to bottom right, #5AF0EC, #E80D9D)',
collectFeedback = true
}: WidgetProps) => {
const [prompt, setPrompt] = React.useState('');
const [status, setStatus] = React.useState<Status>('idle');
const [queries, setQueries] = React.useState<Query[]>([])
const [conversationId, setConversationId] = React.useState<string | null>(null)
const [open, setOpen] = React.useState<boolean>(false)
const [eventInterrupt, setEventInterrupt] = React.useState<boolean>(false); //click or scroll by user while autoScrolling
const isBubbleHovered = useRef<boolean>(false)
const endMessageRef = React.useRef<HTMLDivElement | null>(null);
const md = new MarkdownIt();

Expand All @@ -376,6 +398,36 @@ export const DocsGPTWidget = ({
!eventInterrupt && scrollToBottom(endMessageRef.current);
}, [queries.length, queries[queries.length - 1]?.response]);

async function handleFeedback(feedback: FEEDBACK, index: number) {
let query = queries[index]
if (!query.response)
return;
if (query.feedback != feedback) {
sendFeedback({
question: query.prompt,
answer: query.response,
feedback: feedback,
apikey: apiKey
}, apiHost)
.then(res => {
if (res.status == 200) {
query.feedback = feedback;
setQueries((prev: Query[]) => {
return prev.map((q, i) => (i === index ? query : q));
});
}
})
.catch(err => console.log("Connection failed",err))
}
else {
delete query.feedback;
setQueries((prev: Query[]) => {
return prev.map((q, i) => (i === index ? query : q));
});

}
}

async function stream(question: string) {
setStatus('loading')
try {
Expand Down Expand Up @@ -473,7 +525,7 @@ export const DocsGPTWidget = ({
</MessageBubble>
}
{
query.response ? <MessageBubble type='ANSWER'>
query.response ? <MessageBubble onMouseOver={() => { isBubbleHovered.current = true }} type='ANSWER'>
<Message
type='ANSWER'
ref={(index === queries.length - 1) ? endMessageRef : null}
Expand All @@ -483,6 +535,24 @@ export const DocsGPTWidget = ({
dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(md.render(query.response)) }}
/>
</Message>

{collectFeedback &&
<Feedback>
<Like
style={{
stroke: query.feedback == 'LIKE' ? '#8860DB' : '#c0c0c0',
visibility: query.feedback == 'LIKE' ? 'visible' : 'hidden'
}}
fill='none'
onClick={() => handleFeedback("LIKE", index)} />
<Dislike
style={{
stroke: query.feedback == 'DISLIKE' ? '#ed8085' : '#c0c0c0',
visibility: query.feedback == 'DISLIKE' ? 'visible' : 'hidden'
}}
fill='none'
onClick={() => handleFeedback("DISLIKE", index)} />
</Feedback>}
</MessageBubble>
: <div>
{
Expand Down Expand Up @@ -518,7 +588,7 @@ export const DocsGPTWidget = ({
type='text' placeholder="What do you want to do?" />
<StyledButton
size={size}
disabled={prompt.length == 0 || status !== 'idle'}>
disabled={prompt.trim().length == 0 || status !== 'idle'}>
<PaperPlaneIcon width={15} height={15} color='white' />
</StyledButton>
</PromptContainer>
Expand Down
29 changes: 26 additions & 3 deletions extensions/react-widget/src/requests/streamingApi.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { FEEDBACK } from "@/types";
interface HistoryItem {
prompt: string;
response?: string;
Expand All @@ -11,6 +12,12 @@ interface FetchAnswerStreamingProps {
apiHost?: string;
onEvent?: (event: MessageEvent) => void;
}
interface FeedbackPayload {
question: string;
answer: string;
apikey: string;
feedback: FEEDBACK;
}
export function fetchAnswerStreaming({
question = '',
apiKey = '',
Expand All @@ -20,12 +27,12 @@ export function fetchAnswerStreaming({
onEvent = () => { console.log("Event triggered, but no handler provided."); }
}: FetchAnswerStreamingProps): Promise<void> {
return new Promise<void>((resolve, reject) => {
const body= {
const body = {
question: question,
history: JSON.stringify(history),
conversation_id: conversationId,
model: 'default',
api_key:apiKey
api_key: apiKey
};
fetch(apiHost + '/stream', {
method: 'POST',
Expand Down Expand Up @@ -80,4 +87,20 @@ export function fetchAnswerStreaming({
reject(error);
});
});
}
}


export const sendFeedback = (payload: FeedbackPayload,apiHost:string): Promise<Response> => {
return fetch(`${apiHost}/api/feedback`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
question: payload.question,
answer: payload.answer,
feedback: payload.feedback,
api_key:payload.apikey
}),
});
};
1 change: 1 addition & 0 deletions extensions/react-widget/src/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,5 @@ export interface WidgetProps {
theme?:THEME,
buttonIcon?:string;
buttonBg?:string;
collectFeedback?:boolean
}

0 comments on commit 4aeaec9

Please sign in to comment.