import { useState, useRef, useEffect } from 'react' import { markdownToHtml } from '../../utils/md2html' import './styles/vars.css' import './styles/markdown-body.css' import './styles/index.scoped.scss' import './styles/highlight-js.scss' import { Combobox, Dialog } from '@headlessui/react' interface DialogProps { open: boolean endpoint: string placeholder?: string predefinedQuestions?: string[] onClose: () => void } interface OpenAIResponseBody { // eslint-disable-next-line no-unused-vars pipeThrough: (arg0: TextDecoderStream) => { getReader: () => ReadableStreamDefaultReader } } interface Question { role: string content: string id: number } const questionList: Question[] = [] let assistantId = 0 export const DocumateDialog = ({ endpoint = '', open = false, placeholder = 'Ask a question...', predefinedQuestions = [], onClose = () => { console.warn('emit close') }, ...props }: DialogProps) => { const [isOpen, setIsOpen] = useState(open) // eslint-disable-next-line prefer-const const [query, setQuery] = useState('') const [loading, setLoading] = useState(false) const [questions, setQuestions] = useState([]) const chatContainer = useRef(null) const dialogRef = useRef(null) const scrollToBottom = () => { if (chatContainer.current) { chatContainer.current.scrollTop = chatContainer.current.scrollHeight } } // fetch ChatGPT async function startChat(question: string) { if (!question) { return } if (!endpoint) { console.error('Props endpoint is not provide') return } setLoading(true) const ask = { role: 'user', content: question, id: ++assistantId } questionList.push(ask) setQuestions(() => { return [...questionList] }) try { const response = await fetch(endpoint, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ question, }), }) setLoading(false) // eslint-disable-next-line no-inner-declarations async function streamToString( body: OpenAIResponseBody | null, assistantId: number ) { const reader = body?.pipeThrough(new TextDecoderStream()).getReader() while (reader) { const stream = await reader.read() if (stream.done) break const chunks = stream.value if (chunks) { for (const chunk of chunks) { const assistantIndex = questionList.findIndex( (q) => q.role === 'assistant' && q.id === assistantId ) const content = chunk if (!content) continue scrollToBottom() if (assistantIndex === -1) { questionList.push({ role: 'assistant', content, id: assistantId, }) } else { questionList[assistantIndex].content += content } setQuestions(() => [...questionList]) } } } } streamToString(response.body, assistantId) } catch (err) { console.error(err) } } const handleKeydown = (e: KeyboardEvent) => { if (e.key === 'Escape') { setIsOpen(false) } else if (e.metaKey && e.key === '/') { setIsOpen(true) } } useEffect(() => { window.addEventListener('keydown', handleKeydown) }, []) useEffect(() => { setIsOpen(open) }, [open]) const queryEnter = (e: React.KeyboardEvent) => { if (e.key !== 'Enter') return e.preventDefault() startChat(query) scrollToBottom() setQuery('') } return (
setQuery(e.target.value)} onKeyUp={(e) => queryEnter(e)} value={query} autoComplete="off" >
{predefinedQuestions.length > 0 && questions.length <= 0 && (
    {predefinedQuestions.map((item, index) => ( startChat(item)} > {item} ))}
)} {predefinedQuestions.length <= 0 && questions.length <= 0 && (

You can ask me any questions about this document site?

)} {questions.length > 0 && (
    {questions.map((item, index) => (
  • {item.role === 'user' && (
    {item.content}
    )} {item.role === 'assistant' && !item.content && (

    We couldn’t find anything with that term. Please try again.

    )} {item.role === 'assistant' && item.content && (
    )}
  • ))}
)}
Type + / to open esc to close
) }