diff --git a/extensions/react-widget/package-lock.json b/extensions/react-widget/package-lock.json index de4c228d6..6d736c6f0 100644 --- a/extensions/react-widget/package-lock.json +++ b/extensions/react-widget/package-lock.json @@ -4860,9 +4860,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001625", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001625.tgz", - "integrity": "sha512-4KE9N2gcRH+HQhpeiRZXd+1niLB/XNLAhSy4z7fI8EzcbcPoAqjNInxVHTiTwWfTIV4w096XG8OtCOCQQKPv3w==", + "version": "1.0.30001680", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001680.tgz", + "integrity": "sha512-rPQy70G6AGUMnbwS1z6Xg+RkHYPAi18ihs47GH0jcxIG7wArmPgY3XbS2sRdBbxJljp3thdT8BIqv9ccCypiPA==", "funding": [ { "type": "opencollective", @@ -4876,7 +4876,8 @@ "type": "github", "url": "https://github.com/sponsors/ai" } - ] + ], + "license": "CC-BY-4.0" }, "node_modules/chalk": { "version": "2.4.2", diff --git a/extensions/react-widget/package.json b/extensions/react-widget/package.json index a1097403a..554b2438d 100644 --- a/extensions/react-widget/package.json +++ b/extensions/react-widget/package.json @@ -32,7 +32,7 @@ "scripts": { "build": "parcel build src/main.tsx --public-url ./", "build:react": "parcel build src/index.ts", - "dev": "parcel src/index.html -p 3000", + "dev": "parcel -p 3000", "test": "jest", "lint": "eslint", "check": "tsc --noEmit", diff --git a/extensions/react-widget/src/App.tsx b/extensions/react-widget/src/App.tsx index ec9de47be..4bb24bae1 100644 --- a/extensions/react-widget/src/App.tsx +++ b/extensions/react-widget/src/App.tsx @@ -1,11 +1,11 @@ import React from "react" import {DocsGPTWidget} from "./components/DocsGPTWidget" -const App = () => { +import {SearchBar} from "./components/SearchBar" +export const App = () => { return (
+
) -} - -export default App \ No newline at end of file +} \ 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 1baa4c626..b9ee8acc1 100644 --- a/extensions/react-widget/src/components/DocsGPTWidget.tsx +++ b/extensions/react-widget/src/components/DocsGPTWidget.tsx @@ -1,9 +1,9 @@ "use client"; import React, { useRef } from 'react' import DOMPurify from 'dompurify'; -import styled, { keyframes, createGlobalStyle } from 'styled-components'; +import styled, { keyframes,css } from 'styled-components'; import { PaperPlaneIcon, RocketIcon, ExclamationTriangleIcon, Cross2Icon } from '@radix-ui/react-icons'; -import { FEEDBACK, MESSAGE_TYPE, Query, Status, WidgetProps } from '../types/index'; +import { FEEDBACK, MESSAGE_TYPE, Query, Status, WidgetCoreProps, WidgetProps } from '../types/index'; import { fetchAnswerStreaming, sendFeedback } from '../requests/streamingApi'; import { ThemeProvider } from 'styled-components'; import Like from "../assets/like.svg" @@ -49,38 +49,8 @@ const sizesConfig = { maxHeight: custom.maxHeight || '70vh', }), }; - -const Overlay = styled.div` - position: fixed; - top: 0; - left: 0; - width: 100%; - height: 100%; - background-color: rgba(0, 0, 0, 0.5); - z-index: 999; - transition: opacity 0.5s; -` -const WidgetContainer = styled.div<{ modal?: boolean, isOpen?: boolean }>` - all: initial; - position: fixed; - right: ${props => props.modal ? '50%' : '10px'}; - bottom: ${props => props.modal ? '50%' : '10px'}; - z-index: 1000; - display: none; - transform-origin:100% 100%; - &.open { - animation: createBox 250ms cubic-bezier(0.25, 0.1, 0.25, 1) forwards; - } - &.close { - animation: closeBox 250ms cubic-bezier(0.25, 0.1, 0.25, 1) forwards; - } - ${props => props.modal && - "transform : translate(50%,50%);" - } - align-items: center; - text-align: left; - @keyframes createBox { - 0% { +const createBox = keyframes` + 0% { transform: scale(0.6); } 90% { @@ -89,10 +59,9 @@ const WidgetContainer = styled.div<{ modal?: boolean, isOpen?: boolean }>` 100% { transform: scale(1); } - } - - @keyframes closeBox { - 0% { +` +const closeBox = keyframes` + 0% { transform: scale(1); } 10% { @@ -101,32 +70,9 @@ const WidgetContainer = styled.div<{ modal?: boolean, isOpen?: boolean }>` 100% { transform: scale(0.6); } - } -`; -const StyledContainer = styled.div<{ isOpen: boolean }>` - all: initial; - max-height: ${(props) => props.theme.dimensions.maxHeight}; - max-width: ${(props) => props.theme.dimensions.maxWidth}; - position: relative; - flex-direction: column; - justify-content: space-between; - bottom: 0; - left: 0; - background-color: ${(props) => props.theme.primary.bg}; - font-family: sans-serif; - display: flex; - border-radius: 12px; - box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05), 0 2px 4px rgba(0, 0, 0, 0.1); - padding: 26px 26px 0px 26px; - animation: ${({ isOpen, theme }) => - theme.dimensions.size === 'large' - ? isOpen - ? 'fadeIn 150ms ease-in forwards' - : 'fadeOut 150ms ease-in forwards' - : isOpen - ? 'openContainer 150ms ease-in forwards' - : 'closeContainer 250ms ease-in forwards'}; - @keyframes openContainer { +` + +const openContainer = keyframes` 0% { width: 200px; height: 100px; @@ -135,10 +81,9 @@ const StyledContainer = styled.div<{ isOpen: boolean }>` width: ${(props) => props.theme.dimensions.width}; height: ${(props) => props.theme.dimensions.height}; border-radius: 12px; - } - } - @keyframes closeContainer { - 0% { + }` +const closeContainer = keyframes` + 0% { width: ${(props) => props.theme.dimensions.width}; height: ${(props) => props.theme.dimensions.height}; border-radius: 12px; @@ -147,9 +92,9 @@ const StyledContainer = styled.div<{ isOpen: boolean }>` width: 200px; height: 100px; } - } - @keyframes fadeIn { - from { +` +const fadeIn = keyframes` + from { opacity: 0; width: ${(props) => props.theme.dimensions.width}; height: ${(props) => props.theme.dimensions.height}; @@ -161,9 +106,10 @@ const StyledContainer = styled.div<{ isOpen: boolean }>` width: ${(props) => props.theme.dimensions.width}; height: ${(props) => props.theme.dimensions.height}; } - } - @keyframes fadeOut { - from { +` + +const fadeOut = keyframes` + from { opacity: 1; width: ${(props) => props.theme.dimensions.width}; height: ${(props) => props.theme.dimensions.height}; @@ -174,13 +120,80 @@ const StyledContainer = styled.div<{ isOpen: boolean }>` width: ${(props) => props.theme.dimensions.width}; height: ${(props) => props.theme.dimensions.height}; } +` +const scaleAnimation = keyframes` + from { + transform: scale(1.2); + } + to { + transform: scale(1); + } +` +const Overlay = styled.div` + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-color: rgba(0, 0, 0, 0.5); + z-index: 999; + transition: opacity 0.5s; +` + + +const WidgetContainer = styled.div<{ modal?: boolean, isOpen?: boolean }>` + all: initial; + position: fixed; + right: ${props => props.modal ? '50%' : '10px'}; + bottom: ${props => props.modal ? '50%' : '10px'}; + z-index: 1001; + transform-origin:100% 100%; + display: block; + &.modal{ + transform : translate(50%,50%); + } + &.open { + animation: css ${createBox} 250ms cubic-bezier(0.25, 0.1, 0.25, 1) forwards; + } + &.close { + animation: css ${closeBox} 250ms cubic-bezier(0.25, 0.1, 0.25, 1) forwards; } + align-items: center; + text-align: left; +`; + +const StyledContainer = styled.div<{ isOpen: boolean }>` + all: initial; + max-height: ${(props) => props.theme.dimensions.maxHeight}; + max-width: ${(props) => props.theme.dimensions.maxWidth}; + width: ${(props) => props.theme.dimensions.width}; + height: ${(props) => props.theme.dimensions.height} ; + position: relative; + flex-direction: column; + justify-content: space-between; + bottom: 0; + left: 0; + background-color: ${(props) => props.theme.primary.bg}; + font-family: sans-serif; + display: flex; + border-radius: 12px; + box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05), 0 2px 4px rgba(0, 0, 0, 0.1); + padding: 26px 26px 0px 26px; + animation: ${({ isOpen, theme }) => + theme.dimensions.size === 'large' + ? isOpen + ? css `${fadeIn} 150ms ease-in forwards` + : css ` ${fadeOut} 150ms ease-in forwards` + : isOpen + ? css `${openContainer} 150ms ease-in forwards` + : css `${closeContainer} 250ms ease-in forwards`}; @media only screen and (max-width: 768px) { max-height: 100vh; max-width: 80vw; overflow: auto; } `; + const FloatingButton = styled.div<{ bgcolor: string, hidden: boolean, isAnimatingButton: boolean }>` position: fixed; display: ${props => props.hidden ? "none" : "flex"}; @@ -198,7 +211,7 @@ const FloatingButton = styled.div<{ bgcolor: string, hidden: boolean, isAnimatin background: ${props => props.bgcolor}; box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); cursor: pointer; - animation: ${props => props.isAnimatingButton ? 'scaleAnimation 200ms forwards' : 'none'}; + animation: ${props => props.isAnimatingButton ? css `${scaleAnimation} 200ms forwards` : 'none'}; &:hover { transform: scale(1.1); transition: transform 0.2s ease-in-out; @@ -206,15 +219,6 @@ const FloatingButton = styled.div<{ bgcolor: string, hidden: boolean, isAnimatin &:not(:hover) { transition: transform 0.2s ease-in-out; } - - @keyframes scaleAnimation { - from { - transform: scale(1.2); - } - to { - transform: scale(1); - } - } `; const CancelButton = styled.button` cursor: pointer; @@ -478,7 +482,47 @@ const Hero = ({ title, description, theme }: { title: string, description: strin ); }; -export const DocsGPTWidget = ({ +export const DocsGPTWidget = (props: WidgetProps) => { + + const { + buttonIcon = 'https://d3dg1063dc54p9.cloudfront.net/widget/chat.svg', + buttonText = 'Ask a question', + buttonBg = 'linear-gradient(to bottom right, #5AF0EC, #E80D9D)', + defaultOpen = false, + ...coreProps + } = props + + const [open, setOpen] = React.useState(defaultOpen); + const [isAnimatingButton, setIsAnimatingButton] = React.useState(false); + const [isFloatingButtonVisible, setIsFloatingButtonVisible] = React.useState(true); + + React.useEffect(() => { + if (isFloatingButtonVisible) + setTimeout(() => setIsAnimatingButton(true), 250); + return () => { + setIsAnimatingButton(false) + } + }, [isFloatingButtonVisible]) + + const handleClose = () => { + setIsFloatingButtonVisible(true); + setOpen(false); + }; + const handleOpen = () => { + setOpen(true); + setIsFloatingButtonVisible(false); + } + return ( + <> + + + + ) +} +export const WidgetCore = ({ apiHost = 'https://gptcloud.arc53.com', apiKey = '82962c9a-aa77-4152-94e5-a4f84fd44c6a', avatar = 'https://d3dg1063dc54p9.cloudfront.net/cute-docsgpt.png', @@ -488,25 +532,37 @@ export const DocsGPTWidget = ({ heroDescription = 'This chatbot is built with DocsGPT and utilises GenAI, please review important information using sources.', size = 'small', theme = 'dark', - buttonIcon = 'https://d3dg1063dc54p9.cloudfront.net/widget/chat.svg', - buttonText = 'Ask a question', - buttonBg = 'linear-gradient(to bottom right, #5AF0EC, #E80D9D)', collectFeedback = true, - deafultOpen = false -}: WidgetProps) => { - const [prompt, setPrompt] = React.useState(''); + isOpen = false, + prefilledQuery = "", + handleClose +}: WidgetCoreProps) => { + const [prompt, setPrompt] = React.useState(""); + const [mounted, setMounted] = React.useState(false); const [status, setStatus] = React.useState('idle'); - const [queries, setQueries] = React.useState([]) - const [conversationId, setConversationId] = React.useState(null) - const [open, setOpen] = React.useState(deafultOpen) + const [queries, setQueries] = React.useState([]); + const [conversationId, setConversationId] = React.useState(null); const [eventInterrupt, setEventInterrupt] = React.useState(false); //click or scroll by user while autoScrolling - const [isAnimatingButton, setIsAnimatingButton] = React.useState(false); - const [isFloatingButtonVisible, setIsFloatingButtonVisible] = React.useState(true); - const isBubbleHovered = useRef(false) - const widgetRef = useRef(null) + + const isBubbleHovered = useRef(false); const endMessageRef = React.useRef(null); const md = new MarkdownIt(); + React.useEffect(() => { + if (isOpen) { + setMounted(true); // Mount the component + setPrompt(prefilledQuery) + } else { + // Wait for animations before unmounting + const timeout = setTimeout(() => { + setMounted(false) + }, 250); + return () => clearTimeout(timeout); + } + }, [isOpen]); + + + const handleUserInterrupt = () => { (status === 'loading') && setEventInterrupt(true); } @@ -615,135 +671,120 @@ export const DocsGPTWidget = ({ const handleImageError = (event: React.SyntheticEvent) => { event.currentTarget.src = "https://d3dg1063dc54p9.cloudfront.net/cute-docsgpt.png"; }; - const handleClose = () => { - setOpen(false); - setTimeout(() => { - if (widgetRef.current) widgetRef.current.style.display = "none"; - setIsFloatingButtonVisible(true); - setIsAnimatingButton(true); - setTimeout(() => setIsAnimatingButton(false), 200); - }, 250) - }; - const handleOpen = () => { - setOpen(true); - setIsFloatingButtonVisible(false); - if (widgetRef.current) - widgetRef.current.style.display = 'block' - } + const dimensions = typeof size === 'object' && 'custom' in size ? sizesConfig.getCustom(size.custom) : sizesConfig[size]; - + if (!mounted) return null; return ( - {open && size === 'large' && + {isOpen && size === 'large' && } - - - { -
- - - -
- docs-gpt - - {title} - {description} - -
-
- - { - queries.length > 0 ? queries?.map((query, index) => { - return ( - - { - query.prompt && - - {query.prompt} - - - } - { - query.response ? { isBubbleHovered.current = true }} type='ANSWER'> - - - - - {collectFeedback && - - handleFeedback("LIKE", index)} /> - handleFeedback("DISLIKE", index)} /> - } - - :
- { - query.error ? - - -
-
Network Error
- {query.error} -
-
- : - - . - . - . - - - } -
- } -
) - }) - : - } -
-
- - setPrompt(event.target.value)} - type='text' placeholder="Ask your question" /> - - - - - - Powered by  - DocsGPT - -
-
} -
+ {( + + +
+ + + +
+ docs-gpt + + {title} + {description} + +
+
+ + { + queries.length > 0 ? queries?.map((query, index) => { + return ( + + { + query.prompt && + + {query.prompt} + + + } + { + query.response ? { isBubbleHovered.current = true }} type='ANSWER'> + + + + + {collectFeedback && + + handleFeedback("LIKE", index)} /> + handleFeedback("DISLIKE", index)} /> + } + + :
+ { + query.error ? + + +
+
Network Error
+ {query.error} +
+
+ : + + . + . + . + + + } +
+ } +
) + }) + : + } +
+
+ + setPrompt(event.target.value)} + type='text' placeholder="Ask your question" /> + + + + + + Powered by  + DocsGPT + +
+
+
+ ) + }
) } \ No newline at end of file diff --git a/extensions/react-widget/src/components/SearchBar.tsx b/extensions/react-widget/src/components/SearchBar.tsx new file mode 100644 index 000000000..d1d3aa1aa --- /dev/null +++ b/extensions/react-widget/src/components/SearchBar.tsx @@ -0,0 +1,270 @@ +import React from 'react' +import styled, { keyframes, createGlobalStyle, ThemeProvider } from 'styled-components'; +import { WidgetCore } from './DocsGPTWidget'; +import { SearchBarProps } from '@/types'; +import { getSearchResults } from '../requests/searchAPI' +import { Result } from '@/types'; +import MarkdownIt from 'markdown-it'; +import DOMPurify from 'dompurify'; + +const themes = { + dark: { + bg: '#222327', + text: '#fff', + primary: { + text: "#FAFAFA", + bg: '#111111' + }, + secondary: { + text: "#A1A1AA", + bg: "#38383b" + } + }, + light: { + bg: '#fff', + text: '#000', + primary: { + text: "#222327", + bg: "#fff" + }, + secondary: { + text: "#A1A1AA", + bg: "#F6F6F6" + } + } +} + +const Main = styled.div` + all:initial; + + font-family: sans-serif; +` +const TextField = styled.input<{ inputWidth: string }>` + padding: 6px 6px; + width: ${({ inputWidth }) => inputWidth}; + border-radius: 8px; + display: inline; + color: ${props => props.theme.primary.text}; + outline: none; + border: none; + background-color: ${props => props.theme.secondary.bg}; + + &:focus { + outline: none; + box-shadow: 0px 0px 0px 2px rgba(0, 109, 199); + background-color: ${props => props.theme.primary.bg}; + } +` + +const Container = styled.div` + position: relative; + display: inline-block; +` +const SearchResults = styled.div` + position: absolute; + display: block; + background-color: ${props => props.theme.primary.bg}; + opacity: 90%; + border: 1px solid rgba(0, 0, 0, .1); + border-radius: 12px; + padding: 8px; + width: 576px; + z-index: 100; + height: 25vh; + overflow-y: auto; + top: 45px; + color: ${props => props.theme.primary.text}; + scrollbar-color: lab(48.438 0 0 / 0.4) rgba(0, 0, 0, 0); + scrollbar-gutter: stable; + scrollbar-width: thin; + box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05), 0 2px 4px rgba(0, 0, 0, 0.1); + backdrop-filter: blur(16px); + @media only screen and (max-width: 768px) { + max-height: 100vh; + max-width: 80vw; + overflow: auto; + } +` +const Title = styled.h3` + font-size: 14px; + color: ${props => props.theme.primary.text}; + opacity: 0.8; + padding-bottom: 6px; + font-weight: 600; + text-transform: uppercase; + border-bottom: 1px solid ${(props) => props.theme.secondary.text}; +` +const Content = styled.div` + font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; +` +const ResultWrapper = styled.div` + padding: 4px 8px 4px 8px; + border-radius: 8px; + cursor: pointer; + &.contains-source:hover{ + background-color: rgba(0, 92, 197, 0.15); + ${Title} { + color: rgb(0, 126, 230); + } + } +` +const Markdown = styled.div` +line-height:20px; +font-size: 12px; + pre { + padding: 8px; + width: 90%; + font-size: 12px; + border-radius: 6px; + overflow-x: auto; + background-color: #1B1C1F; + color: #fff ; + } + + h1,h2 { + font-size: 16px; + font-weight: 600; + color: ${(props) => props.theme.text}; + opacity: 0.8; + } + + + h3 { + font-size: 14px; + } + + p { + margin: 0px; + line-height: 1.35rem; + font-size: 12px; + } + + code:not(pre code) { + border-radius: 6px; + padding: 4px 4px; + font-size: 12px; + display: inline-block; + background-color: #646464; + color: #fff ; + } + + code { + white-space: pre-wrap ; + overflow-wrap: break-word; + word-break: break-all; + } + a{ + color: #007ee6; + } +` +const Toolkit = styled.kbd` + position: absolute; + right: 12px; + top: 4px; + background-color: ${(props) => props.theme.primary.bg}; + color: ${(props) => props.theme.secondary.text}; + font-weight: 600; + font-size: 10px; + padding: 3px; + border: 1px solid ${(props) => props.theme.secondary.text}; + border-radius: 4px; +` +export const SearchBar = ({ + apiKey = "74039c6d-bff7-44ce-ae55-2973cbf13837", + apiHost = "https://gptcloud.arc53.com", + theme = "dark", + placeholder = "Search or Ask AI...", + width = "240px" +}: SearchBarProps) => { + const [input, setInput] = React.useState(""); + const [isWidgetOpen, setIsWidgetOpen] = React.useState(false); + const inputRef = React.useRef(null); + const resultsRef = React.useRef(null); + const [isResultVisible, setIsResultVisible] = React.useState(true); + const [results, setResults] = React.useState([]); + React.useEffect(() => { + const handleClickOutside = (event: MouseEvent) => { + if ( + resultsRef.current && + !resultsRef.current.contains(event.target as Node) + ) { + setIsResultVisible(false); + } + }; + document.addEventListener('mousedown', handleClickOutside); + return () => { + resultsRef.current && (resultsRef.current.style.display = 'block') + document.removeEventListener('mousedown', handleClickOutside); + }; + }, []) + React.useEffect(() => { + input.length > 0 ? + getSearchResults(input, apiKey, apiHost) + .then((data) => setResults(data)) + .catch((err) => console.log(err)) + : + setResults([]) + }, [input]) + + const handleKeyDown = (event: React.KeyboardEvent) => { + if (event.ctrlKey && event.key === 'k') { + event.preventDefault(); + setIsWidgetOpen(true); + } + }; + const handleClose = () => { + setIsWidgetOpen(false); + } + const md = new MarkdownIt(); + return ( + +
+ + setIsResultVisible(true)} + ref={inputRef} + onKeyDown={(e) => handleKeyDown(e)} + placeholder={placeholder} + value={input} + onChange={(e) => setInput(e.target.value)} + /> + { + input.length > 0 && results.length > 0 && isResultVisible && ( + + {results.map((res) => { + const containsSource = res.source !== 'local' + return ( + { + if (!containsSource) return; + window.open(res.source, '_blank', 'noopener, noreferrer') + }} + className={containsSource ? "contains-source" : ""}> + {res.title} + + + + + ) + }) + } + + ) + } + Ctrl K + + +
+
+ ) +} \ No newline at end of file diff --git a/extensions/react-widget/src/index.html b/extensions/react-widget/src/index.html index 0f0710d5d..40eaad152 100644 --- a/extensions/react-widget/src/index.html +++ b/extensions/react-widget/src/index.html @@ -9,11 +9,11 @@
- - --> diff --git a/extensions/react-widget/src/index.ts b/extensions/react-widget/src/index.ts index 1efa89a63..e29b85a52 100644 --- a/extensions/react-widget/src/index.ts +++ b/extensions/react-widget/src/index.ts @@ -1 +1,2 @@ -export { DocsGPTWidget } from "./components/DocsGPTWidget"; \ No newline at end of file +export {SearchBar} from "./components/SearchBar" +export { DocsGPTWidget } from "./components/DocsGPTWidget"; diff --git a/extensions/react-widget/src/main.tsx b/extensions/react-widget/src/main.tsx index 4fb3bbb4c..a8542e263 100644 --- a/extensions/react-widget/src/main.tsx +++ b/extensions/react-widget/src/main.tsx @@ -1,12 +1,25 @@ -import React from 'react'; -import { createRoot } from 'react-dom/client'; -import { DocsGPTWidget } from './components/DocsGPTWidget'; +import { createRoot } from "react-dom/client"; +import { App } from "./App"; +import { DocsGPTWidget } from './components/DocsGPTWidget'; +import { SearchBar } from './components/SearchBar'; +import React from "react"; if (typeof window !== 'undefined') { const renderWidget = (elementId: string, props = {}) => { const root = createRoot(document.getElementById(elementId) as HTMLElement); root.render(); }; + const renderSearchBar = (elementId: string, props = {}) => { + const root = createRoot(document.getElementById(elementId) as HTMLElement); + root.render(); + }; (window as any).renderDocsGPTWidget = renderWidget; + + (window as any).renderSearchBar = renderSearchBar; } -export { DocsGPTWidget }; \ No newline at end of file +const container = document.getElementById("app") as HTMLElement; +const root = createRoot(container) +root.render(); + +export { DocsGPTWidget }; +export { SearchBar } diff --git a/extensions/react-widget/src/requests/searchAPI.ts b/extensions/react-widget/src/requests/searchAPI.ts new file mode 100644 index 000000000..18df36915 --- /dev/null +++ b/extensions/react-widget/src/requests/searchAPI.ts @@ -0,0 +1,34 @@ +import { Result } from "@/types"; + + async function getSearchResults(question: string, apiKey:string, apiHost:string): Promise { + + const payload = { + question, + api_key:apiKey + }; + + try { + const response = await fetch(`${apiHost}/api/search`, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(payload), + }); + + if (!response.ok) { + throw new Error(`Error: ${response.status}`); + } + + const data: Result[] = await response.json(); + return data; + + } catch (error) { + console.error("Failed to fetch documents:", error); + throw error; + } + } + + export { + getSearchResults + } \ 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 717efd924..cea9e43a3 100644 --- a/extensions/react-widget/src/types/index.ts +++ b/extensions/react-widget/src/types/index.ts @@ -32,5 +32,25 @@ export interface WidgetProps { buttonText?:string; buttonBg?:string; collectFeedback?:boolean; - deafultOpen?: boolean; -} \ No newline at end of file + defaultOpen?: boolean; +} +export interface WidgetCoreProps extends WidgetProps { + widgetRef?:React.RefObject | null; + handleClose?:React.MouseEventHandler | undefined; + isOpen:boolean; + prefilledQuery?: string; +} + +export interface SearchBarProps { + apiHost?: string; + apiKey?: string; + theme?:THEME; + placeholder?:string; + width?:string; +} + +export interface Result { + text:string; + title:string; + source:string; +}