Merge pull request #1089 from ManishMadan2882/main

Upgrading options in React widget
This commit is contained in:
Alex
2024-08-23 12:10:12 +01:00
committed by GitHub
5 changed files with 137 additions and 129 deletions

View File

@@ -1,12 +1,12 @@
{
"name": "docsgpt",
"version": "0.3.9",
"version": "0.4.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "docsgpt",
"version": "0.3.9",
"version": "0.4.0",
"license": "Apache-2.0",
"dependencies": {
"@babel/plugin-transform-flow-strip-types": "^7.23.3",

View File

@@ -1,7 +0,0 @@
<svg width="36" height="36" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M4.37891 9.44824H7.75821" stroke="white" stroke-width="1.68965" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M11.1377 9.44824H12.8273" stroke="white" stroke-width="1.68965" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M4.37891 6.06934H6.06856" stroke="white" stroke-width="1.68965" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M9.44824 6.06934H12.8276" stroke="white" stroke-width="1.68965" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M16.2069 11.1379C16.2069 11.5861 16.0289 12.0158 15.712 12.3327C15.3951 12.6496 14.9654 12.8276 14.5172 12.8276H4.37931L1 16.2069V2.68965C1 2.24153 1.17802 1.81176 1.49489 1.49489C1.81176 1.17802 2.24153 1 2.68965 1H14.5172C14.9654 1 15.3951 1.17802 15.712 1.49489C16.0289 1.81176 16.2069 2.24153 16.2069 2.68965V11.1379Z" stroke="white" stroke-width="1.68965" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 1009 B

View File

@@ -4,10 +4,35 @@ import DOMPurify from 'dompurify';
import snarkdown from '@bpmn-io/snarkdown';
import styled, { keyframes, createGlobalStyle } from 'styled-components';
import { PaperPlaneIcon, RocketIcon, ExclamationTriangleIcon, Cross2Icon } from '@radix-ui/react-icons';
import MessageIcon from '../assets/message.svg';
import { MESSAGE_TYPE, Query, Status, WidgetProps } from '../types/index';
import { fetchAnswerStreaming } from '../requests/streamingApi';
import { ThemeProvider } from 'styled-components';
const themes = {
dark: {
bg: '#222327',
text: '#fff',
primary: {
text: "#FAFAFA",
bg: '#222327'
},
secondary: {
text: "#A1A1AA",
bg: "#38383b"
}
},
light: {
bg: '#fff',
text: '#000',
primary: {
text: "#222327",
bg: "#fff"
},
secondary: {
text: "#A1A1AA",
bg: "#F6F6F6"
}
}
}
const GlobalStyles = createGlobalStyle`
.response pre {
padding: 8px;
@@ -53,12 +78,12 @@ const StyledContainer = styled.div`
bottom: 0;
left: 0;
border-radius: 0.75rem;
background-color: #222327;
background-color: ${props => props.theme.primary.bg};
font-family: sans-serif;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05), 0 2px 4px rgba(0, 0, 0, 0.1);
transition: visibility 0.3s, opacity 0.3s;
`;
const FloatingButton = styled.div`
const FloatingButton = styled.div<{bg:string}>`
position: fixed;
display: flex;
z-index: 500;
@@ -69,7 +94,7 @@ const FloatingButton = styled.div`
width: 5rem;
height: 5rem;
border-radius: 9999px;
background-image: linear-gradient(to bottom right, #5AF0EC, #E80D9D);
background: ${props => props.bg};
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
cursor: pointer;
&:hover {
@@ -119,14 +144,14 @@ const ContentWrapper = styled.div`
const Title = styled.h3`
font-size: 1rem;
font-weight: normal;
color: #FAFAFA;
color: ${props => props.theme.primary.text};
margin-top: 0;
margin-bottom: 0.25rem;
`;
const Description = styled.p`
font-size: 0.85rem;
color: #A1A1AA;
color: ${props => props.theme.secondary.text};
margin-top: 0;
`;
const Conversation = styled.div<{ size: string }>`
@@ -154,11 +179,11 @@ const MessageBubble = styled.div<{ type: MESSAGE_TYPE }>`
justify-content: ${props => props.type === 'QUESTION' ? 'flex-end' : 'flex-start'};
margin: 0.5rem;
`;
const Message = styled.p<{ type: MESSAGE_TYPE }>`
const Message = styled.div<{ type: MESSAGE_TYPE }>`
background: ${props => props.type === 'QUESTION' ?
'linear-gradient(to bottom right, #8860DB, #6D42C5)' :
'#38383b'};
color: #ffff;
props.theme.secondary.bg};
color: ${props => props.type === 'ANSWER' ? props.theme.primary.text : '#fff'};
border: none;
max-width: ${props => props.type === 'ANSWER' ? '100%' : '80'};
overflow: auto;
@@ -213,7 +238,7 @@ const StyledInput = styled.input`
background-color: transparent;
font-size: 16px;
border-radius: 6px;
color: #ffff;
color: ${props => props.theme.text};
outline: none;
`;
const StyledButton = styled.button`
@@ -250,7 +275,7 @@ const HeroContainer = styled.div`
padding: 2px;
`;
const HeroWrapper = styled.div`
background-color: #222327;
background-color: ${props => props.theme.primary.bg};
border-radius: 10px;
font-weight: normal;
padding: 6px;
@@ -258,23 +283,22 @@ const HeroWrapper = styled.div`
justify-content: space-between;
`
const HeroTitle = styled.h3`
color: #fff;
font-size: 17px;
color: ${props => props.theme.text};
margin-bottom: 5px;
padding: 2px;
`;
const HeroDescription = styled.p`
color: #fff;
color: ${props => props.theme.text};
font-size: 14px;
line-height: 1.5;
`;
const Hero = ({ title, description }: { title: string, description: string }) => {
const Hero = ({ title, description, theme }: { title: string, description: string, theme: string }) => {
return (
<>
<HeroContainer>
<HeroWrapper>
<IconWrapper style={{ marginTop: '8px' }}>
<RocketIcon color='white' width={20} height={20} />
<IconWrapper style={{ marginTop: '12px' }}>
<RocketIcon color={theme === 'light' ? 'black' : 'white'} width={20} height={20} />
</IconWrapper>
<div>
<HeroTitle>{title}</HeroTitle>
@@ -289,14 +313,16 @@ const Hero = ({ title, description }: { title: string, description: string }) =>
};
export const DocsGPTWidget = ({
apiHost = 'https://gptcloud.arc53.com',
selectDocs = 'default',
apiKey = '82962c9a-aa77-4152-94e5-a4f84fd44c6a',
avatar = 'https://d3dg1063dc54p9.cloudfront.net/cute-docsgpt.png',
title = 'Get AI assistance',
description = 'DocsGPT\'s AI Chatbot is here to help',
heroTitle = 'Welcome to DocsGPT !',
heroDescription = 'This chatbot is built with DocsGPT and utilises GenAI, please review important information using sources.',
size = 'small'
size = 'small',
theme = 'light',
buttonIcon = 'https://d3dg1063dc54p9.cloudfront.net/widget/message.svg',
buttonBg = 'linear-gradient(to bottom right, #5AF0EC, #E80D9D)'
}: WidgetProps) => {
const [prompt, setPrompt] = React.useState('');
const [status, setStatus] = React.useState<Status>('idle');
@@ -334,7 +360,6 @@ export const DocsGPTWidget = ({
question: question,
apiKey: apiKey,
apiHost: apiHost,
selectedDocs: selectDocs,
history: queries,
conversationId: conversationId,
onEvent: (event: MessageEvent) => {
@@ -377,16 +402,17 @@ export const DocsGPTWidget = ({
event.currentTarget.src = "https://d3dg1063dc54p9.cloudfront.net/cute-docsgpt.png";
};
return (
<>
<ThemeProvider theme={themes[theme]}>
<WidgetContainer>
<GlobalStyles />
{!open && <FloatingButton onClick={() => setOpen(true)} hidden={open}>
<MessageIcon style={{ marginTop: '8px' }} />
</FloatingButton>}
{!open &&
<FloatingButton bg={buttonBg} onClick={() => setOpen(true)} hidden={open}>
<img style={{maxHeight:'4rem',maxWidth:'4rem'}} src={buttonIcon} />
</FloatingButton>}
{open && <StyledContainer>
<div>
<CancelButton onClick={() => setOpen(false)}>
<Cross2Icon width={24} height={24} color='white' />
<Cross2Icon width={24} height={24} color={theme === 'light' ? 'black' : 'white'} />
</CancelButton>
<Header>
<IconWrapper>
@@ -444,7 +470,7 @@ export const DocsGPTWidget = ({
}
</React.Fragment>)
})
: <Hero title={heroTitle} description={heroDescription} />
: <Hero title={heroTitle} description={heroDescription} theme={theme} />
}
</Conversation>
@@ -460,6 +486,6 @@ export const DocsGPTWidget = ({
</PromptContainer>
</StyledContainer>}
</WidgetContainer>
</>
</ThemeProvider>
)
}

View File

@@ -1,92 +1,83 @@
interface HistoryItem {
prompt: string;
response?: string;
}
prompt: string;
response?: string;
}
interface FetchAnswerStreamingProps {
question?: string;
apiKey?: string;
selectedDocs?: string;
history?: HistoryItem[];
conversationId?: string | null;
apiHost?: string;
onEvent?: (event: MessageEvent) => void;
}
question?: string;
apiKey?: string;
selectedDocs?: string;
history?: HistoryItem[];
conversationId?: string | null;
apiHost?: string;
onEvent?: (event: MessageEvent) => void;
}
export function fetchAnswerStreaming({
question = '',
apiKey = '',
selectedDocs = '',
history = [],
conversationId = null,
apiHost = '',
onEvent = () => {console.log("Event triggered, but no handler provided.");}
}: FetchAnswerStreamingProps): Promise<void> {
let docPath = 'default';
if (selectedDocs) {
docPath = selectedDocs;
}
return new Promise<void>((resolve, reject) => {
const body = {
question: question,
api_key: apiKey,
embeddings_key: apiKey,
active_docs: docPath,
history: JSON.stringify(history),
conversation_id: conversationId,
model: 'default'
};
fetch(apiHost + '/stream', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(body),
})
.then((response) => {
if (!response.body) throw Error('No response body');
const reader = response.body.getReader();
const decoder = new TextDecoder('utf-8');
let counterrr = 0;
const processStream = ({
done,
value,
}: ReadableStreamReadResult<Uint8Array>) => {
if (done) {
resolve();
return;
question = '',
apiKey = '',
history = [],
conversationId = null,
apiHost = '',
onEvent = () => { console.log("Event triggered, but no handler provided."); }
}: FetchAnswerStreamingProps): Promise<void> {
return new Promise<void>((resolve, reject) => {
const body= {
question: question,
history: JSON.stringify(history),
conversation_id: conversationId,
model: 'default',
apiKey:apiKey
};
fetch(apiHost + '/stream', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(body),
})
.then((response) => {
if (!response.body) throw Error('No response body');
const reader = response.body.getReader();
const decoder = new TextDecoder('utf-8');
let counterrr = 0;
const processStream = ({
done,
value,
}: ReadableStreamReadResult<Uint8Array>) => {
if (done) {
resolve();
return;
}
counterrr += 1;
const chunk = decoder.decode(value);
const lines = chunk.split('\n');
for (let line of lines) {
if (line.trim() == '') {
continue;
}
counterrr += 1;
const chunk = decoder.decode(value);
const lines = chunk.split('\n');
for (let line of lines) {
if (line.trim() == '') {
continue;
}
if (line.startsWith('data:')) {
line = line.substring(5);
}
const messageEvent = new MessageEvent('message', {
data: line,
});
onEvent(messageEvent); // handle each message
if (line.startsWith('data:')) {
line = line.substring(5);
}
reader.read().then(processStream).catch(reject);
};
const messageEvent = new MessageEvent('message', {
data: line,
});
onEvent(messageEvent); // handle each message
}
reader.read().then(processStream).catch(reject);
})
.catch((error) => {
console.error('Connection failed:', error);
reject(error);
});
});
}
};
reader.read().then(processStream).catch(reject);
})
.catch((error) => {
console.error('Connection failed:', error);
reject(error);
});
});
}

View File

@@ -1,11 +1,7 @@
export type MESSAGE_TYPE = 'QUESTION' | 'ANSWER' | 'ERROR';
export type Status = 'idle' | 'loading' | 'failed';
export type FEEDBACK = 'LIKE' | 'DISLIKE';
export type DIMENSION = {
width: string,
height: string
}
export type THEME = 'light' | 'dark';
export interface Query {
prompt: string;
response?: string;
@@ -17,7 +13,6 @@ export interface Query {
}
export interface WidgetProps {
apiHost?: string;
selectDocs?: string;
apiKey?: string;
avatar?: string;
title?: string;
@@ -25,4 +20,7 @@ export interface WidgetProps {
heroTitle?: string;
heroDescription?: string;
size?: 'small' | 'medium';
theme?:THEME,
buttonIcon?:string;
buttonBg?:string;
}