From 8637397c86726261ca4320d8a77bc30250962400 Mon Sep 17 00:00:00 2001 From: ManishMadan2882 Date: Fri, 23 Aug 2024 02:55:00 +0530 Subject: [PATCH] options: theme, button icon and bg --- extensions/react-widget/package-lock.json | 4 +- .../react-widget/src/assets/message.svg | 7 - .../src/components/DocsGPTWidget.tsx | 82 ++++++--- .../react-widget/src/requests/streamingApi.ts | 163 +++++++++--------- extensions/react-widget/src/types/index.ts | 10 +- 5 files changed, 137 insertions(+), 129 deletions(-) delete mode 100644 extensions/react-widget/src/assets/message.svg diff --git a/extensions/react-widget/package-lock.json b/extensions/react-widget/package-lock.json index 59280d2db..3ae1c1011 100644 --- a/extensions/react-widget/package-lock.json +++ b/extensions/react-widget/package-lock.json @@ -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", diff --git a/extensions/react-widget/src/assets/message.svg b/extensions/react-widget/src/assets/message.svg deleted file mode 100644 index 2a70dcee6..000000000 --- a/extensions/react-widget/src/assets/message.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/extensions/react-widget/src/components/DocsGPTWidget.tsx b/extensions/react-widget/src/components/DocsGPTWidget.tsx index 320b83121..30a292eff 100644 --- a/extensions/react-widget/src/components/DocsGPTWidget.tsx +++ b/extensions/react-widget/src/components/DocsGPTWidget.tsx @@ -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 ( <> - - + +
{title} @@ -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('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 ( - <> + - {!open && setOpen(true)} hidden={open}> - - } + {!open && + setOpen(true)} hidden={open}> + + } {open &&
setOpen(false)}> - +
@@ -444,7 +470,7 @@ export const DocsGPTWidget = ({ } ) }) - : + : } @@ -460,6 +486,6 @@ export const DocsGPTWidget = ({ } - + ) } \ No newline at end of file diff --git a/extensions/react-widget/src/requests/streamingApi.ts b/extensions/react-widget/src/requests/streamingApi.ts index a0a671de5..cea339b99 100644 --- a/extensions/react-widget/src/requests/streamingApi.ts +++ b/extensions/react-widget/src/requests/streamingApi.ts @@ -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 { - let docPath = 'default'; - if (selectedDocs) { - docPath = selectedDocs; - } - - return new Promise((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) => { - if (done) { - resolve(); - return; + question = '', + apiKey = '', + history = [], + conversationId = null, + apiHost = '', + onEvent = () => { console.log("Event triggered, but no handler provided."); } +}: FetchAnswerStreamingProps): Promise { + return new Promise((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) => { + 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); - }); - }); - } \ No newline at end of file + }; + + reader.read().then(processStream).catch(reject); + }) + .catch((error) => { + console.error('Connection failed:', error); + reject(error); + }); + }); +} \ No newline at end of file diff --git a/extensions/react-widget/src/types/index.ts b/extensions/react-widget/src/types/index.ts index 6810dd904..9c49063f1 100644 --- a/extensions/react-widget/src/types/index.ts +++ b/extensions/react-widget/src/types/index.ts @@ -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; } \ No newline at end of file