From aa57984bde7b4b6117c3d88bf421447560bdc50d Mon Sep 17 00:00:00 2001 From: utin-francis-peter Date: Fri, 11 Oct 2024 03:55:35 +0100 Subject: [PATCH 01/13] build: added missing dependency --- extensions/react-widget/package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/extensions/react-widget/package.json b/extensions/react-widget/package.json index d449d0a3..677a6564 100644 --- a/extensions/react-widget/package.json +++ b/extensions/react-widget/package.json @@ -40,6 +40,7 @@ }, "dependencies": { "@babel/plugin-transform-flow-strip-types": "^7.23.3", + "@bpmn-io/snarkdown": "^2.2.0", "@parcel/resolver-glob": "^2.12.0", "@parcel/transformer-svg-react": "^2.12.0", "@parcel/transformer-typescript-tsc": "^2.12.0", From 0481e766ae420bbab3ed86b106448d3338952277 Mon Sep 17 00:00:00 2001 From: utin-francis-peter Date: Mon, 14 Oct 2024 12:30:57 +0100 Subject: [PATCH 02/13] chore: updated Query and WidgetProps interface with source property --- extensions/react-widget/src/types/index.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/extensions/react-widget/src/types/index.ts b/extensions/react-widget/src/types/index.ts index a55b6342..bd27910b 100644 --- a/extensions/react-widget/src/types/index.ts +++ b/extensions/react-widget/src/types/index.ts @@ -1,16 +1,21 @@ export type MESSAGE_TYPE = 'QUESTION' | 'ANSWER' | 'ERROR'; + export type Status = 'idle' | 'loading' | 'failed'; + export type FEEDBACK = 'LIKE' | 'DISLIKE'; + export type THEME = 'light' | 'dark'; + export interface Query { prompt: string; response?: string; feedback?: FEEDBACK; error?: string; - sources?: { title: string; text: string }[]; + sources?: { title: string; text: string, source:string }[]; conversationId?: string | null; title?: string | null; } + export interface WidgetProps { apiHost?: string; apiKey?: string; @@ -24,4 +29,5 @@ export interface WidgetProps { buttonIcon?:string; buttonBg?:string; collectFeedback?:boolean + showSources?: boolean } \ No newline at end of file From 848beb11dfe0fb50b056b0fbb9fce2545080c846 Mon Sep 17 00:00:00 2001 From: utin-francis-peter Date: Mon, 14 Oct 2024 12:33:56 +0100 Subject: [PATCH 03/13] chore: corrected typo in var declaration --- extensions/react-widget/src/requests/streamingApi.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/extensions/react-widget/src/requests/streamingApi.ts b/extensions/react-widget/src/requests/streamingApi.ts index 9cb9fddc..d5f79fe1 100644 --- a/extensions/react-widget/src/requests/streamingApi.ts +++ b/extensions/react-widget/src/requests/streamingApi.ts @@ -1,8 +1,10 @@ import { FEEDBACK } from "@/types"; + interface HistoryItem { prompt: string; response?: string; } + interface FetchAnswerStreamingProps { question?: string; apiKey?: string; @@ -12,12 +14,14 @@ interface FetchAnswerStreamingProps { apiHost?: string; onEvent?: (event: MessageEvent) => void; } + interface FeedbackPayload { question: string; answer: string; apikey: string; feedback: FEEDBACK; } + export function fetchAnswerStreaming({ question = '', apiKey = '', @@ -46,7 +50,7 @@ export function fetchAnswerStreaming({ const reader = response.body.getReader(); const decoder = new TextDecoder('utf-8'); - let counterrr = 0; + let counter = 0; const processStream = ({ done, value, @@ -56,7 +60,7 @@ export function fetchAnswerStreaming({ return; } - counterrr += 1; + counter += 1; const chunk = decoder.decode(value); From 62802eb13882e6ba8581e62610670e8639bf830f Mon Sep 17 00:00:00 2001 From: utin-francis-peter Date: Mon, 14 Oct 2024 12:37:02 +0100 Subject: [PATCH 04/13] chore: styled component styles for sources, added showSources prop to widget, handled sources data.type, and rendering sources when available --- .../src/components/DocsGPTWidget.tsx | 166 +++++++++++++----- 1 file changed, 124 insertions(+), 42 deletions(-) diff --git a/extensions/react-widget/src/components/DocsGPTWidget.tsx b/extensions/react-widget/src/components/DocsGPTWidget.tsx index 83defbcf..4e13a931 100644 --- a/extensions/react-widget/src/components/DocsGPTWidget.tsx +++ b/extensions/react-widget/src/components/DocsGPTWidget.tsx @@ -1,14 +1,15 @@ "use client"; -import React, { useRef } from 'react' +import React, { useRef, useState } from 'react' import DOMPurify from 'dompurify'; -import styled, { keyframes, createGlobalStyle } from 'styled-components'; -import { PaperPlaneIcon, RocketIcon, ExclamationTriangleIcon, Cross2Icon } from '@radix-ui/react-icons'; +import styled, { keyframes, createGlobalStyle, ThemeProvider } from 'styled-components'; +import { PaperPlaneIcon, RocketIcon, ExclamationTriangleIcon, Cross2Icon, ExternalLinkIcon } from '@radix-ui/react-icons'; 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: { bg: '#222327', @@ -35,6 +36,7 @@ const themes = { } } } + const GlobalStyles = createGlobalStyle` .response pre { padding: 8px; @@ -70,6 +72,7 @@ const GlobalStyles = createGlobalStyle` line-break: loose !important; } `; + const Overlay = styled.div` position: fixed; top: 0; @@ -80,6 +83,7 @@ const Overlay = styled.div` z-index: 999; transition: opacity 0.5s; ` + const WidgetContainer = styled.div<{ modal: boolean }>` display: block; position: fixed; @@ -98,6 +102,7 @@ const WidgetContainer = styled.div<{ modal: boolean }>` overflow: auto; } `; + const StyledContainer = styled.div` display: flex; position: relative; @@ -111,6 +116,7 @@ const StyledContainer = styled.div` 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<{ bgcolor: string }>` position: fixed; display: flex; @@ -130,6 +136,7 @@ const FloatingButton = styled.div<{ bgcolor: string }>` transition: transform 0.2s ease-in-out; } `; + const CancelButton = styled.button` cursor: pointer; position: absolute; @@ -201,6 +208,7 @@ 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; @@ -209,6 +217,7 @@ const Feedback = styled.div` padding: 6px; clear: both; `; + const MessageBubble = styled.div<{ type: MESSAGE_TYPE }>` display: block; font-size: 16px; @@ -220,6 +229,7 @@ const MessageBubble = styled.div<{ type: MESSAGE_TYPE }>` visibility: visible !important; } `; + const Message = styled.div<{ type: MESSAGE_TYPE }>` background: ${props => props.type === 'QUESTION' ? 'linear-gradient(to bottom right, #8860DB, #6D42C5)' : @@ -235,6 +245,7 @@ const Message = styled.div<{ type: MESSAGE_TYPE }>` padding: 0.75rem; border-radius: 0.375rem; `; + const ErrorAlert = styled.div` color: #b91c1c; border:0.1px solid #b91c1c; @@ -247,6 +258,7 @@ const ErrorAlert = styled.div` border-radius: 0.375rem; justify-content: space-evenly; ` + //dot loading animation const dotBounce = keyframes` 0%, 80%, 100% { @@ -261,10 +273,12 @@ const DotAnimation = styled.div` display: inline-block; animation: ${dotBounce} 1s infinite ease-in-out; `; + // delay classes as styled components const Delay = styled(DotAnimation) <{ delay: number }>` animation-delay: ${props => props.delay + 'ms'}; `; + const PromptContainer = styled.form<{ size: string }>` background-color: transparent; height: ${props => props.size == 'large' ? '60px' : '40px'}; @@ -272,6 +286,7 @@ const PromptContainer = styled.form<{ size: string }>` display: flex; justify-content: space-evenly; `; + const StyledInput = styled.input` width: 100%; border: 1px solid #686877; @@ -282,6 +297,7 @@ const StyledInput = styled.input` color: ${props => props.theme.text}; outline: none; `; + const StyledButton = styled.button<{ size: string }>` display: flex; justify-content: center; @@ -301,6 +317,7 @@ const StyledButton = styled.button<{ size: string }>` &:disabled { opacity: 60%; }`; + const HeroContainer = styled.div` position: absolute; top: 50%; @@ -316,6 +333,7 @@ const HeroContainer = styled.div` margin: 0 auto; padding: 2px; `; + const HeroWrapper = styled.div` background-color: ${props => props.theme.primary.bg}; border-radius: 10px; @@ -329,12 +347,51 @@ const HeroTitle = styled.h3` margin-bottom: 5px; padding: 2px; `; + const HeroDescription = styled.p` color: ${props => props.theme.text}; font-size: 14px; line-height: 1.5; `; +const SourcesContainer = styled.div` + display: flex; + flex-wrap: wrap; + gap: 8px; + margin-bottom: 8px; +`; + +const SourceBox = styled.div` + background-color: ${props => props.theme.secondary.bg}; + border-radius: 6px; + padding: 8px; + font-size: 12px; + cursor: pointer; + display: flex; + align-items: center; + gap: 4px; + max-width: 200px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +`; + +const LoadingIndicator = styled.div` + display: inline-block; + width: 20px; + height: 20px; + border: 2px solid ${props => props.theme.secondary.text}; + border-radius: 50%; + border-top-color: transparent; + animation: spin 1s linear infinite; + + @keyframes spin { + to { + transform: rotate(360deg); + } + } +`; + const Hero = ({ title, description, theme }: { title: string, description: string, theme: string }) => { return ( <> @@ -354,6 +411,7 @@ const Hero = ({ title, description, theme }: { title: string, description: strin ); }; + export const DocsGPTWidget = ({ apiHost = 'https://gptcloud.arc53.com', apiKey = '82962c9a-aa77-4152-94e5-a4f84fd44c6a', @@ -366,14 +424,15 @@ export const DocsGPTWidget = ({ theme = 'dark', buttonIcon = 'https://d3dg1063dc54p9.cloudfront.net/widget/message.svg', buttonBg = 'linear-gradient(to bottom right, #5AF0EC, #E80D9D)', - collectFeedback = true + collectFeedback = true, + showSources = true }: WidgetProps) => { const [prompt, setPrompt] = React.useState(''); const [status, setStatus] = React.useState('idle'); const [queries, setQueries] = React.useState([]) const [conversationId, setConversationId] = React.useState(null) const [open, setOpen] = React.useState(false) - const [eventInterrupt, setEventInterrupt] = React.useState(false); //click or scroll by user while autoScrolling + const [eventInterrupt, setEventInterrupt] = React.useState(false); const isBubbleHovered = useRef(false) const endMessageRef = React.useRef(null); const md = new MarkdownIt(); @@ -381,6 +440,7 @@ export const DocsGPTWidget = ({ const handleUserInterrupt = () => { (status === 'loading') && setEventInterrupt(true); } + const scrollToBottom = (element: Element | null) => { //recursive function to scroll to the last child of the last child ... // to get to the bottom most element @@ -394,6 +454,7 @@ export const DocsGPTWidget = ({ const lastChild = element?.children?.[element.children.length - 1] lastChild && scrollToBottom(lastChild) }; + React.useEffect(() => { !eventInterrupt && scrollToBottom(endMessageRef.current); }, [queries.length, queries[queries.length - 1]?.response]); @@ -440,7 +501,6 @@ export const DocsGPTWidget = ({ conversationId: conversationId, onEvent: (event: MessageEvent) => { const data = JSON.parse(event.data); - // check if the 'end' event has been received if (data.type === 'end') { setStatus('idle'); } @@ -453,6 +513,11 @@ export const DocsGPTWidget = ({ setQueries(updatedQueries); setStatus('idle') } + else if (data.type === 'sources') { + const updatedQueries = [...queries]; + updatedQueries[updatedQueries.length - 1].sources = data.sources; + setQueries(updatedQueries); + } else { const result = data.answer; const streamingResponse = queries[queries.length - 1].response ? queries[queries.length - 1].response : ''; @@ -468,11 +533,9 @@ export const DocsGPTWidget = ({ updatedQueries[updatedQueries.length - 1].error = 'Something went wrong !' setQueries(updatedQueries); setStatus('idle') - //setEventInterrupt(false) } - } - // submit handler + const handleSubmit = async (e: React.FormEvent) => { e.preventDefault() setEventInterrupt(false); @@ -480,9 +543,11 @@ export const DocsGPTWidget = ({ setPrompt('') await stream(prompt) } + const handleImageError = (event: React.SyntheticEvent) => { event.currentTarget.src = "https://d3dg1063dc54p9.cloudfront.net/cute-docsgpt.png"; }; + return ( {open && size === 'large' && @@ -525,36 +590,51 @@ export const DocsGPTWidget = ({ } { - query.response ? { isBubbleHovered.current = true }} type='ANSWER'> - -
- - - {collectFeedback && - - handleFeedback("LIKE", index)} /> - handleFeedback("DISLIKE", index)} /> - } - - :
+ query.response ? ( + { isBubbleHovered.current = true }} type='ANSWER'> + {showSources && query.sources && ( + + {query.sources.map((source, sourceIndex) => ( + window.open(source.source, '_blank', 'noopener,noreferrer')} + title={source.title} + > + {source.title} + + + ))} + + )} + +
+ + {collectFeedback && + + handleFeedback("LIKE", index)} /> + handleFeedback("DISLIKE", index)} /> + } + + ) : ( +
{ query.error ? @@ -574,10 +654,12 @@ export const DocsGPTWidget = ({ }
+ ) } - ) + + ); }) - : + : } Date: Mon, 28 Oct 2024 17:33:40 +0100 Subject: [PATCH 05/13] chore: wrapped the base component with ThemeProvider at the root level to make theme props available globally --- .../src/components/DocsGPTWidget.tsx | 111 ++++-------------- extensions/react-widget/src/main.tsx | 40 ++++++- 2 files changed, 59 insertions(+), 92 deletions(-) diff --git a/extensions/react-widget/src/components/DocsGPTWidget.tsx b/extensions/react-widget/src/components/DocsGPTWidget.tsx index 4e13a931..704954d9 100644 --- a/extensions/react-widget/src/components/DocsGPTWidget.tsx +++ b/extensions/react-widget/src/components/DocsGPTWidget.tsx @@ -1,42 +1,15 @@ "use client"; -import React, { useRef, useState } from 'react' +import React, { useRef } from 'react' import DOMPurify from 'dompurify'; -import styled, { keyframes, createGlobalStyle, ThemeProvider } from 'styled-components'; -import { PaperPlaneIcon, RocketIcon, ExclamationTriangleIcon, Cross2Icon, ExternalLinkIcon } from '@radix-ui/react-icons'; +import styled, { keyframes, createGlobalStyle, } from 'styled-components'; +import { PaperPlaneIcon, RocketIcon, ExclamationTriangleIcon, Cross2Icon, } from '@radix-ui/react-icons'; import { FEEDBACK, MESSAGE_TYPE, Query, Status, WidgetProps } from '../types/index'; import { fetchAnswerStreaming, sendFeedback } from '../requests/streamingApi'; +import QuerySources from "./QuerySources"; import Like from "../assets/like.svg" import Dislike from "../assets/dislike.svg" import MarkdownIt from 'markdown-it'; - -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; @@ -354,44 +327,6 @@ const HeroDescription = styled.p` line-height: 1.5; `; -const SourcesContainer = styled.div` - display: flex; - flex-wrap: wrap; - gap: 8px; - margin-bottom: 8px; -`; - -const SourceBox = styled.div` - background-color: ${props => props.theme.secondary.bg}; - border-radius: 6px; - padding: 8px; - font-size: 12px; - cursor: pointer; - display: flex; - align-items: center; - gap: 4px; - max-width: 200px; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -`; - -const LoadingIndicator = styled.div` - display: inline-block; - width: 20px; - height: 20px; - border: 2px solid ${props => props.theme.secondary.text}; - border-radius: 50%; - border-top-color: transparent; - animation: spin 1s linear infinite; - - @keyframes spin { - to { - transform: rotate(360deg); - } - } -`; - const Hero = ({ title, description, theme }: { title: string, description: string, theme: string }) => { return ( <> @@ -412,6 +347,8 @@ const Hero = ({ title, description, theme }: { title: string, description: strin ); }; + + export const DocsGPTWidget = ({ apiHost = 'https://gptcloud.arc53.com', apiKey = '82962c9a-aa77-4152-94e5-a4f84fd44c6a', @@ -494,13 +431,14 @@ export const DocsGPTWidget = ({ try { await fetchAnswerStreaming( { - question: question, - apiKey: apiKey, - apiHost: apiHost, + question, + apiKey, + apiHost, history: queries, - conversationId: conversationId, + conversationId, onEvent: (event: MessageEvent) => { const data = JSON.parse(event.data); + if (data.type === 'end') { setStatus('idle'); } @@ -513,10 +451,11 @@ export const DocsGPTWidget = ({ setQueries(updatedQueries); setStatus('idle') } - else if (data.type === 'sources') { + else if (data.type === 'source') { const updatedQueries = [...queries]; - updatedQueries[updatedQueries.length - 1].sources = data.sources; - setQueries(updatedQueries); + updatedQueries[updatedQueries.length - 1].sources = data.source; + setQueries(updatedQueries); + } else { const result = data.answer; @@ -549,15 +488,17 @@ export const DocsGPTWidget = ({ }; return ( - +<> {open && size === 'large' && { setOpen(false) }} /> } + setOpen(!open)} hidden={open}> + {open && @@ -593,18 +534,7 @@ export const DocsGPTWidget = ({ query.response ? ( { isBubbleHovered.current = true }} type='ANSWER'> {showSources && query.sources && ( - - {query.sources.map((source, sourceIndex) => ( - window.open(source.source, '_blank', 'noopener,noreferrer')} - title={source.title} - > - {source.title} - - - ))} - + )} } + @@ -676,6 +607,6 @@ export const DocsGPTWidget = ({ } - + ) } \ No newline at end of file diff --git a/extensions/react-widget/src/main.tsx b/extensions/react-widget/src/main.tsx index 4fb3bbb4..6ada95dc 100644 --- a/extensions/react-widget/src/main.tsx +++ b/extensions/react-widget/src/main.tsx @@ -1,11 +1,47 @@ import React from 'react'; import { createRoot } from 'react-dom/client'; import { DocsGPTWidget } from './components/DocsGPTWidget'; +import { ThemeProvider } from 'styled-components'; +import { THEME } from './types'; + +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" + } + } +} if (typeof window !== 'undefined') { - const renderWidget = (elementId: string, props = {}) => { + const renderWidget = (elementId: string, props={ + theme: "dark" as THEME + }) => { const root = createRoot(document.getElementById(elementId) as HTMLElement); - root.render(); + root.render( + + + + ); }; (window as any).renderDocsGPTWidget = renderWidget; } From 656f4da8f9f3bd0d554d086ef5d6dadbc4da0518 Mon Sep 17 00:00:00 2001 From: utin-francis-peter Date: Mon, 28 Oct 2024 17:34:35 +0100 Subject: [PATCH 06/13] feat: rendering of response source --- .../src/components/QuerySources.tsx | 166 ++++++++++++++++++ 1 file changed, 166 insertions(+) create mode 100644 extensions/react-widget/src/components/QuerySources.tsx diff --git a/extensions/react-widget/src/components/QuerySources.tsx b/extensions/react-widget/src/components/QuerySources.tsx new file mode 100644 index 00000000..d322c48d --- /dev/null +++ b/extensions/react-widget/src/components/QuerySources.tsx @@ -0,0 +1,166 @@ +import React, { useState } from 'react'; +import {Query, THEME} from "../types/index" +import styled from 'styled-components'; +import { ExternalLinkIcon, FileTextIcon } from '@radix-ui/react-icons' + + +const SourcesWrapper = styled.div` +margin-bottom: 1rem; +display: flex; +flex-direction: column; +overflow: hidden; +` + +const SourcesHeader = styled.div` + margin: 0.5rem 0; + display: flex; + align-items: center; + gap: 0.75rem; +` + +const SourcesTitle = styled.p` + font-size: 1rem; + font-weight: 600; + color: ${props => props.theme.text}; +` + +const SourcesGrid = styled.div` + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 0.5rem; + margin-left: 0.75rem; + margin-right: 1.25rem; + max-width: 90vw; + overflow-x: scroll; + + @media(min-width: 768px){ + max-width: 70vw; + } +` + +const SourceItem = styled.div` +height: 7rem; +cursor: pointer; +display: flex; +flex-direction: column; +justify-content: space-between; +border-radius: 1.25rem; +background-color: ${props =>props.theme.secondary.bg}; +padding: 1rem; +color:${props => props.theme.text}; +transform: background-color .2s, color .2s; + +&:hover{ + background-color: ${props => props.theme.primary.bg}; + color: ${props => props.theme.primary.text}; +} +` + +const SourceText = styled.p` + height: 3rem; + overflow: hidden; + text-overflow: ellipsis; +font-size: 0.75rem; +line-height: 1rem; +color: ${props => props.theme.text}; +` + +const SourceLink = styled.div` + margin-top: 0.875rem; + display: flex; + flex-direction: row; + align-items: center; + gap: 0.375rem; + text-decoration: underline; + text-underline-offset: 2px; +` + +const SourceLinkText = styled.p` + margin-top: 0.125rem; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + font-size: 0.75rem; + line-height: 1rem; +` + +const Tooltip = styled.div` + position: absolute; + left: 50%; + z-index: 30; + max-height: 12rem; + width: 10rem; + transform: translateX(-50%) translateY(3px); + border-radius: 0.75rem; + background-color: ${props => props.theme.bg}; + padding: 1rem; + color: ${props => props.theme.text}; + box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05); + + @media (min-width: 640px) { + width: 14rem; + } +` + +const TooltipText = styled.p` + max-height: 10.25rem; + overflow-y: auto; + word-break: break-word; + border-radius: 0.375rem; + font-size: 0.875rem; + line-height: 1.25rem; +` + +type TQuerySources = { + sources: Pick["sources"], + theme?: THEME +} + +const QuerySources = ({sources, theme}:TQuerySources) => { + const [activeTooltip, setActiveTooltip] = useState(null) + + return ( + + + + Sources + + + + {sources?.slice(0, 3)?.map((source, index) => ( + setActiveTooltip(index)} + onMouseLeave={() => setActiveTooltip(null)} + > + {source.text} + + source.source && source.source !== 'local' + ? window.open(source.source, '_blank', 'noopener,noreferrer') + : null + } + > + + + {source.source && source.source !== 'local' ? source.source : source.title} + + + {activeTooltip === index && ( + setActiveTooltip(index)} + onMouseLeave={() => setActiveTooltip(null)} + > + + {source.text} + + + )} + + ))} + + + ) +} + +export default QuerySources \ No newline at end of file From 1a9f47b1bcb6668e3e90e1d72cf289afc7547ea4 Mon Sep 17 00:00:00 2001 From: utin-francis-peter Date: Mon, 4 Nov 2024 16:33:00 +0100 Subject: [PATCH 07/13] chore: modified query sources and removed tooltip --- .../src/components/DocsGPTWidget.tsx | 4 +- .../src/components/QuerySources.tsx | 65 ++----------------- 2 files changed, 9 insertions(+), 60 deletions(-) diff --git a/extensions/react-widget/src/components/DocsGPTWidget.tsx b/extensions/react-widget/src/components/DocsGPTWidget.tsx index c59377e7..1354e4e8 100644 --- a/extensions/react-widget/src/components/DocsGPTWidget.tsx +++ b/extensions/react-widget/src/components/DocsGPTWidget.tsx @@ -454,7 +454,9 @@ export const DocsGPTWidget = ({ else if (data.type === 'source') { const updatedQueries = [...queries]; updatedQueries[updatedQueries.length - 1].sources = data.source; - setQueries(updatedQueries); + setQueries(updatedQueries); + console.log("SOURCE:", data); + } else { diff --git a/extensions/react-widget/src/components/QuerySources.tsx b/extensions/react-widget/src/components/QuerySources.tsx index d322c48d..504bb917 100644 --- a/extensions/react-widget/src/components/QuerySources.tsx +++ b/extensions/react-widget/src/components/QuerySources.tsx @@ -1,25 +1,26 @@ -import React, { useState } from 'react'; import {Query, THEME} from "../types/index" import styled from 'styled-components'; import { ExternalLinkIcon, FileTextIcon } from '@radix-ui/react-icons' const SourcesWrapper = styled.div` -margin-bottom: 1rem; +padding: 12px; +margin-left: 4px; +margin-bottom: 0.75rem; display: flex; flex-direction: column; overflow: hidden; ` const SourcesHeader = styled.div` - margin: 0.5rem 0; + margin: 0.3rem 0; display: flex; align-items: center; gap: 0.75rem; ` const SourcesTitle = styled.p` - font-size: 1rem; + font-size: 0.75rem; font-weight: 600; color: ${props => props.theme.text}; ` @@ -39,7 +40,6 @@ const SourcesGrid = styled.div` ` const SourceItem = styled.div` -height: 7rem; cursor: pointer; display: flex; flex-direction: column; @@ -56,17 +56,7 @@ transform: background-color .2s, color .2s; } ` -const SourceText = styled.p` - height: 3rem; - overflow: hidden; - text-overflow: ellipsis; -font-size: 0.75rem; -line-height: 1rem; -color: ${props => props.theme.text}; -` - const SourceLink = styled.div` - margin-top: 0.875rem; display: flex; flex-direction: row; align-items: center; @@ -76,7 +66,6 @@ const SourceLink = styled.div` ` const SourceLinkText = styled.p` - margin-top: 0.125rem; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; @@ -84,45 +73,16 @@ const SourceLinkText = styled.p` line-height: 1rem; ` -const Tooltip = styled.div` - position: absolute; - left: 50%; - z-index: 30; - max-height: 12rem; - width: 10rem; - transform: translateX(-50%) translateY(3px); - border-radius: 0.75rem; - background-color: ${props => props.theme.bg}; - padding: 1rem; - color: ${props => props.theme.text}; - box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05); - - @media (min-width: 640px) { - width: 14rem; - } -` - -const TooltipText = styled.p` - max-height: 10.25rem; - overflow-y: auto; - word-break: break-word; - border-radius: 0.375rem; - font-size: 0.875rem; - line-height: 1.25rem; -` - type TQuerySources = { sources: Pick["sources"], theme?: THEME } const QuerySources = ({sources, theme}:TQuerySources) => { - const [activeTooltip, setActiveTooltip] = useState(null) - return ( - + Sources @@ -130,10 +90,7 @@ const QuerySources = ({sources, theme}:TQuerySources) => { {sources?.slice(0, 3)?.map((source, index) => ( setActiveTooltip(index)} - onMouseLeave={() => setActiveTooltip(null)} > - {source.text} source.source && source.source !== 'local' @@ -146,16 +103,6 @@ const QuerySources = ({sources, theme}:TQuerySources) => { {source.source && source.source !== 'local' ? source.source : source.title} - {activeTooltip === index && ( - setActiveTooltip(index)} - onMouseLeave={() => setActiveTooltip(null)} - > - - {source.text} - - - )} ))} From 3e87d83ae8e624981b67ea5fa4913be351582ac6 Mon Sep 17 00:00:00 2001 From: utin-francis-peter Date: Tue, 5 Nov 2024 21:50:42 +0100 Subject: [PATCH 08/13] chore: adjusted spacing in source bubble --- extensions/react-widget/src/components/QuerySources.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/extensions/react-widget/src/components/QuerySources.tsx b/extensions/react-widget/src/components/QuerySources.tsx index 504bb917..edb7fc0c 100644 --- a/extensions/react-widget/src/components/QuerySources.tsx +++ b/extensions/react-widget/src/components/QuerySources.tsx @@ -4,7 +4,7 @@ import { ExternalLinkIcon, FileTextIcon } from '@radix-ui/react-icons' const SourcesWrapper = styled.div` -padding: 12px; +padding: 8px; margin-left: 4px; margin-bottom: 0.75rem; display: flex; @@ -46,7 +46,8 @@ flex-direction: column; justify-content: space-between; border-radius: 1.25rem; background-color: ${props =>props.theme.secondary.bg}; -padding: 1rem; +padding-left: 0.75rem; +padding-right: 0.75rem; color:${props => props.theme.text}; transform: background-color .2s, color .2s; From 1a8f89573d717e986935605c9fad327f66e6273e Mon Sep 17 00:00:00 2001 From: utin-francis-peter Date: Sat, 9 Nov 2024 01:09:22 +0100 Subject: [PATCH 09/13] feat: query sources in widget --- .../src/components/DocsGPTWidget.tsx | 4 +-- .../src/components/QuerySources.tsx | 29 ++++--------------- 2 files changed, 8 insertions(+), 25 deletions(-) diff --git a/extensions/react-widget/src/components/DocsGPTWidget.tsx b/extensions/react-widget/src/components/DocsGPTWidget.tsx index 1354e4e8..bea3de3a 100644 --- a/extensions/react-widget/src/components/DocsGPTWidget.tsx +++ b/extensions/react-widget/src/components/DocsGPTWidget.tsx @@ -351,7 +351,7 @@ const Hero = ({ title, description, theme }: { title: string, description: strin export const DocsGPTWidget = ({ apiHost = 'https://gptcloud.arc53.com', - apiKey = '82962c9a-aa77-4152-94e5-a4f84fd44c6a', + apiKey = '0d7407f7-a843-42fb-ad83-dd4a213a935d', avatar = 'https://d3dg1063dc54p9.cloudfront.net/cute-docsgpt.png', title = 'Get AI assistance', description = 'DocsGPT\'s AI Chatbot is here to help', @@ -536,7 +536,7 @@ export const DocsGPTWidget = ({ query.response ? ( { isBubbleHovered.current = true }} type='ANSWER'> {showSources && query.sources && ( - + )} props.theme.text}; -` - const SourcesGrid = styled.div` display: grid; grid-template-columns: repeat(3, 1fr); @@ -57,12 +44,12 @@ transform: background-color .2s, color .2s; } ` -const SourceLink = styled.div` +const SourceLink = styled.div<{$hasExternalSource: boolean}>` display: flex; flex-direction: row; align-items: center; gap: 0.375rem; - text-decoration: underline; + text-decoration: ${({$hasExternalSource}) => ($hasExternalSource? "underline": "none")}; text-underline-offset: 2px; ` @@ -76,16 +63,11 @@ const SourceLinkText = styled.p` type TQuerySources = { sources: Pick["sources"], - theme?: THEME } -const QuerySources = ({sources, theme}:TQuerySources) => { +const QuerySources = ({sources}:TQuerySources) => { return ( - - - Sources - {sources?.slice(0, 3)?.map((source, index) => ( @@ -93,6 +75,7 @@ const QuerySources = ({sources, theme}:TQuerySources) => { key={index} > source.source && source.source !== 'local' ? window.open(source.source, '_blank', 'noopener,noreferrer') From 97916bf925638b85f08d053e1fa0006d3e21977f Mon Sep 17 00:00:00 2001 From: utin-francis-peter Date: Sun, 10 Nov 2024 03:08:35 +0100 Subject: [PATCH 10/13] chore: returned themes cofig into DocsGPTWidget component --- .../src/components/DocsGPTWidget.tsx | 28 ++++++++++++++- extensions/react-widget/src/main.tsx | 36 +------------------ 2 files changed, 28 insertions(+), 36 deletions(-) diff --git a/extensions/react-widget/src/components/DocsGPTWidget.tsx b/extensions/react-widget/src/components/DocsGPTWidget.tsx index 341c9d14..809ececc 100644 --- a/extensions/react-widget/src/components/DocsGPTWidget.tsx +++ b/extensions/react-widget/src/components/DocsGPTWidget.tsx @@ -10,7 +10,33 @@ import Like from "../assets/like.svg" import Dislike from "../assets/dislike.svg" import MarkdownIt from 'markdown-it'; +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 sizesConfig = { small: { size: 'small', width: '320px', height: '400px' }, @@ -631,7 +657,7 @@ export const DocsGPTWidget = ({ onClick={() => handleFeedback("DISLIKE", index)} /> } - :
+ : (
{ query.error ? diff --git a/extensions/react-widget/src/main.tsx b/extensions/react-widget/src/main.tsx index 6ada95dc..368dc394 100644 --- a/extensions/react-widget/src/main.tsx +++ b/extensions/react-widget/src/main.tsx @@ -1,46 +1,12 @@ import React from 'react'; import { createRoot } from 'react-dom/client'; import { DocsGPTWidget } from './components/DocsGPTWidget'; -import { ThemeProvider } from 'styled-components'; -import { THEME } from './types'; - -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" - } - } -} if (typeof window !== 'undefined') { - const renderWidget = (elementId: string, props={ - theme: "dark" as THEME - }) => { + const renderWidget = (elementId: string, props={}) => { const root = createRoot(document.getElementById(elementId) as HTMLElement); root.render( - - ); }; (window as any).renderDocsGPTWidget = renderWidget; From 25feab9a2918b7d044aecc0e38c201f6c9180a36 Mon Sep 17 00:00:00 2001 From: utin-francis-peter Date: Sun, 10 Nov 2024 03:11:41 +0100 Subject: [PATCH 11/13] chore: removed unused import --- extensions/react-widget/src/components/DocsGPTWidget.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/react-widget/src/components/DocsGPTWidget.tsx b/extensions/react-widget/src/components/DocsGPTWidget.tsx index 809ececc..16b2be1c 100644 --- a/extensions/react-widget/src/components/DocsGPTWidget.tsx +++ b/extensions/react-widget/src/components/DocsGPTWidget.tsx @@ -1,7 +1,7 @@ "use client"; import React, { useRef } from 'react' import DOMPurify from 'dompurify'; -import styled, { keyframes, createGlobalStyle, ThemeProvider, } from 'styled-components'; +import styled, { keyframes, ThemeProvider, } from 'styled-components'; import { PaperPlaneIcon, RocketIcon, ExclamationTriangleIcon, Cross2Icon, } from '@radix-ui/react-icons'; import { FEEDBACK, MESSAGE_TYPE, Query, Status, WidgetProps } from '../types/index'; import { fetchAnswerStreaming, sendFeedback } from '../requests/streamingApi'; From a7aae3ff7e54b615d7803741867c50cbc53b72bf Mon Sep 17 00:00:00 2001 From: utin-francis-peter Date: Sun, 10 Nov 2024 03:29:56 +0100 Subject: [PATCH 12/13] style: minor adjustments in border-radius and spacings --- .../react-widget/src/components/QuerySources.tsx | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/extensions/react-widget/src/components/QuerySources.tsx b/extensions/react-widget/src/components/QuerySources.tsx index 7090a96a..325930df 100644 --- a/extensions/react-widget/src/components/QuerySources.tsx +++ b/extensions/react-widget/src/components/QuerySources.tsx @@ -4,9 +4,7 @@ import { ExternalLinkIcon } from '@radix-ui/react-icons' const SourcesWrapper = styled.div` -padding: 8px; -margin-left: 4px; -margin-bottom: 0.75rem; +margin: 4px; display: flex; flex-direction: column; overflow: hidden; @@ -16,8 +14,6 @@ const SourcesGrid = styled.div` display: grid; grid-template-columns: repeat(3, 1fr); gap: 0.5rem; - margin-left: 0.75rem; - margin-right: 1.25rem; max-width: 90vw; overflow-x: scroll; @@ -31,10 +27,10 @@ cursor: pointer; display: flex; flex-direction: column; justify-content: space-between; -border-radius: 1.25rem; +border-radius: 6px; background-color: ${props =>props.theme.secondary.bg}; -padding-left: 0.75rem; -padding-right: 0.75rem; + padding-left: 12px; +padding-right: 12px; color:${props => props.theme.text}; transform: background-color .2s, color .2s; From 2c8a2945f02c43c3953a8db614f0f8923710b6af Mon Sep 17 00:00:00 2001 From: utin-francis-peter Date: Sun, 17 Nov 2024 10:34:22 +0100 Subject: [PATCH 13/13] feat: better sources scroll management --- .../src/components/QuerySources.tsx | 41 +++++++++++++++++-- 1 file changed, 38 insertions(+), 3 deletions(-) diff --git a/extensions/react-widget/src/components/QuerySources.tsx b/extensions/react-widget/src/components/QuerySources.tsx index 325930df..a619a822 100644 --- a/extensions/react-widget/src/components/QuerySources.tsx +++ b/extensions/react-widget/src/components/QuerySources.tsx @@ -1,6 +1,7 @@ import {Query} from "../types/index" import styled from 'styled-components'; import { ExternalLinkIcon } from '@radix-ui/react-icons' +import { useEffect, useMemo, useState } from "react"; const SourcesWrapper = styled.div` @@ -57,16 +58,41 @@ const SourceLinkText = styled.p` line-height: 1rem; ` +const OtherSources = styled.button` +cursor: pointer; +background: transparent; +color: #8860DB; +border: none; +outline: none; +margin-top: 0.5rem; +align-self: flex-start; +` + type TQuerySources = { sources: Pick["sources"], } const QuerySources = ({sources}:TQuerySources) => { - return ( - +const [showAllSources, setShowAllSources] = useState(false) +const visibleSources = useMemo(() => { + if(!sources) return []; + + return showAllSources? sources : sources.slice(0, 3) +}, [sources, showAllSources]) + +const handleToggleShowAll = () => { + setShowAllSources(prev => !prev) +} + +if(!sources || sources.length === 0){ + return null; +} + +return ( + - {sources?.slice(0, 3)?.map((source, index) => ( + {visibleSources?.map((source, index) => ( @@ -86,6 +112,15 @@ const QuerySources = ({sources}:TQuerySources) => { ))} + + { + sources.length > 3 && ( + { + showAllSources ? `Show less` : `+ ${sources.length - 3} more` + } + + ) +} ) }