mirror of
https://github.com/arc53/DocsGPT.git
synced 2025-11-29 08:33:20 +00:00
(feat:search bar) initiating seach bar
This commit is contained in:
@@ -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",
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import React from "react"
|
||||
import {DocsGPTWidget} from "./components/DocsGPTWidget"
|
||||
const App = () => {
|
||||
import {SearchBar} from "./components/SearchBar"
|
||||
export const App = () => {
|
||||
return (
|
||||
<div>
|
||||
<SearchBar/>
|
||||
<DocsGPTWidget/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default App
|
||||
}
|
||||
177
extensions/react-widget/src/components/SearchBar.tsx
Normal file
177
extensions/react-widget/src/components/SearchBar.tsx
Normal file
@@ -0,0 +1,177 @@
|
||||
import React from 'react'
|
||||
import styled, { keyframes, createGlobalStyle } 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 Main = styled.div`
|
||||
font-family: sans-serif;
|
||||
`
|
||||
const TextField = styled.input`
|
||||
padding: 8px;
|
||||
border-radius: 8px;
|
||||
display: inline;
|
||||
color: rgb(107 114 128);
|
||||
outline: none;
|
||||
border: 2px solid transparent;
|
||||
background-color: rgba(0, 0, 0, .05);;
|
||||
&:focus {
|
||||
outline: #467f95; /* remove default outline */
|
||||
border:2px solid skyblue; /* change border color on focus */
|
||||
box-shadow: 0px 0px 2px skyblue; /* add a red box shadow on focus */
|
||||
}
|
||||
`
|
||||
|
||||
const Container = styled.div`
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
`
|
||||
const SearchResults = styled.div`
|
||||
position: absolute;
|
||||
background-color: white;
|
||||
opacity: 90%;
|
||||
border: 1px solid rgba(0, 0, 0, .1);
|
||||
border-radius: 12px;
|
||||
padding: 20px;
|
||||
width: 576px;
|
||||
z-index: 100;
|
||||
height: 25vh;
|
||||
overflow-y: auto;
|
||||
top: 45px;
|
||||
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);
|
||||
`
|
||||
const Title = styled.h3`
|
||||
font-size: 12px;
|
||||
color: rgb(107, 114, 128);
|
||||
padding-bottom: 4px;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
border-bottom: 1px solid rgba(0, 0, 0, 0.1);
|
||||
|
||||
`
|
||||
|
||||
const Content = styled.div`
|
||||
font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
|
||||
`
|
||||
const Markdown = styled.div`
|
||||
|
||||
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: rgb(31, 41, 55);
|
||||
}
|
||||
|
||||
|
||||
h3 {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 0px;
|
||||
line-height: 1.35rem;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
ul{
|
||||
padding:0px;
|
||||
list-style-position: inside;
|
||||
}
|
||||
`
|
||||
export const SearchBar = ({
|
||||
apiKey = "79bcbf0e-3dd1-4ac3-b893-e41b3d40ec8d",
|
||||
apiHost = "http://127.0.0.1:7091",
|
||||
theme = "dark"
|
||||
}: SearchBarProps) => {
|
||||
const [input, setInput] = React.useState("")
|
||||
const [isWidgetOpen, setIsWidgetOpen] = React.useState<boolean>(false);
|
||||
const inputRef = React.useRef<HTMLInputElement>(null)
|
||||
const [results, setResults] = React.useState<Result[]>([])
|
||||
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<HTMLInputElement>) => {
|
||||
if (event.key === 'Enter') {
|
||||
setIsWidgetOpen(true);
|
||||
}
|
||||
};
|
||||
const handleClose = () => {
|
||||
setIsWidgetOpen(false);
|
||||
}
|
||||
const md = new MarkdownIt();
|
||||
return (
|
||||
<Main>
|
||||
<Container>
|
||||
<TextField
|
||||
ref={inputRef}
|
||||
onKeyDown={(e) => handleKeyDown(e)}
|
||||
placeholder='Search here or Ask DocsGPT'
|
||||
value={input}
|
||||
onChange={(e) => setInput(e.target.value)}
|
||||
/>
|
||||
{
|
||||
results.length > 0 && (
|
||||
<SearchResults>
|
||||
{results.map((res) => (
|
||||
<div>
|
||||
<Title>{res.title}</Title>
|
||||
<Content>
|
||||
<Markdown
|
||||
dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(md.render(res.text)) }}
|
||||
/>
|
||||
</Content>
|
||||
</div>
|
||||
))
|
||||
}
|
||||
</SearchResults>
|
||||
|
||||
)
|
||||
}
|
||||
</Container>
|
||||
<WidgetCore
|
||||
theme={theme}
|
||||
apiHost={apiHost}
|
||||
apiKey={apiKey}
|
||||
prefilledQuery={input}
|
||||
isOpen={isWidgetOpen}
|
||||
handleClose={handleClose} size={'large'}
|
||||
/>
|
||||
|
||||
</Main>
|
||||
)
|
||||
}
|
||||
@@ -9,11 +9,11 @@
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="main.tsx"></script>
|
||||
<script type="module" src="../dist/main.js"></script>
|
||||
<script type="module">
|
||||
<!-- <script type="module">
|
||||
window.onload = function() {
|
||||
renderDocsGPTWidget('app');
|
||||
renderSearchBar('app')
|
||||
}
|
||||
</script>
|
||||
</script> -->
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
export { DocsGPTWidget } from "./components/DocsGPTWidget";
|
||||
export {SearchBar} from "./components/SearchBar"
|
||||
export { DocsGPTWidget } from "./components/DocsGPTWidget";
|
||||
|
||||
@@ -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(<DocsGPTWidget {...props} />);
|
||||
};
|
||||
const renderSearchBar = (elementId: string, props = {}) => {
|
||||
const root = createRoot(document.getElementById(elementId) as HTMLElement);
|
||||
root.render(<SearchBar {...props} />);
|
||||
};
|
||||
(window as any).renderDocsGPTWidget = renderWidget;
|
||||
|
||||
(window as any).renderSearchBar = renderSearchBar;
|
||||
}
|
||||
export { DocsGPTWidget };
|
||||
const container = document.getElementById("app") as HTMLElement;
|
||||
const root = createRoot(container)
|
||||
root.render(<App />);
|
||||
|
||||
export { DocsGPTWidget };
|
||||
export { SearchBar }
|
||||
|
||||
34
extensions/react-widget/src/requests/searchAPI.ts
Normal file
34
extensions/react-widget/src/requests/searchAPI.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import { Result } from "@/types";
|
||||
|
||||
async function getSearchResults(question: string, apiKey:string, apiHost:string): Promise<Result[]> {
|
||||
|
||||
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
|
||||
}
|
||||
@@ -32,5 +32,22 @@ export interface WidgetProps {
|
||||
buttonText?:string;
|
||||
buttonBg?:string;
|
||||
collectFeedback?:boolean;
|
||||
deafultOpen?: boolean;
|
||||
}
|
||||
defaultOpen?: boolean;
|
||||
}
|
||||
export interface WidgetCoreProps extends WidgetProps {
|
||||
widgetRef?:React.RefObject<HTMLDivElement> | null;
|
||||
handleClose?:React.MouseEventHandler | undefined;
|
||||
isOpen:boolean;
|
||||
prefilledQuery?: string;
|
||||
}
|
||||
|
||||
export interface SearchBarProps {
|
||||
apiHost?: string;
|
||||
apiKey?: string;
|
||||
theme?:THEME
|
||||
}
|
||||
|
||||
export interface Result {
|
||||
text:string;
|
||||
title:string
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user