| 1 |
import { Fragment, useEffect, useRef, useState } from '@wordpress/element' |
| 2 |
import { __ } from '@wordpress/i18n' |
| 3 |
import { Icon } from '@wordpress/icons' |
| 4 |
import { Error } from '@chat/components/dialog/Error' |
| 5 |
import { useAnswer } from '@chat/hooks/useAnswer' |
| 6 |
import { robot, send } from '@chat/svg' |
| 7 |
import classnames from 'classnames' |
| 8 |
|
| 9 |
export const Answer = ({ |
| 10 |
question, |
| 11 |
reset, |
| 12 |
answerId, |
| 13 |
questionLoading, |
| 14 |
questionError, |
| 15 |
}) => { |
| 16 |
const [status, setStatus] = useState(null) |
| 17 |
const [displayedAnswer, setDisplayedAnswer] = useState('') |
| 18 |
const { data, error } = useAnswer(answerId ?? null, status) |
| 19 |
const { answer, loading, status: answerStatus } = data ?? {} |
| 20 |
const scrollRef = useRef(null) |
| 21 |
|
| 22 |
useEffect(() => setStatus(answerStatus), [answerStatus]) |
| 23 |
|
| 24 |
useEffect(() => { |
| 25 |
if (!answer) return |
| 26 |
const words = answer.slice(displayedAnswer.length).trim().split(' ') |
| 27 |
const nextWord = words[0] |
| 28 |
if (!nextWord) return |
| 29 |
const retryTime = [75, 75, 75, 150, 300].at( |
| 30 |
Math.floor(Math.random() * 5), |
| 31 |
) |
| 32 |
const timer = setTimeout(() => { |
| 33 |
setDisplayedAnswer((prev) => prev + (prev ? ' ' : '') + nextWord) |
| 34 |
if (scrollRef.current) { |
| 35 |
scrollRef.current.scrollTo({ |
| 36 |
top: scrollRef.current.scrollHeight, |
| 37 |
behavior: 'smooth', |
| 38 |
}) |
| 39 |
} |
| 40 |
}, retryTime) |
| 41 |
return () => clearTimeout(timer) |
| 42 |
}, [answer, status, displayedAnswer]) |
| 43 |
|
| 44 |
return ( |
| 45 |
<> |
| 46 |
<div className="p-6 pb-10 h-80 overflow-scroll" ref={scrollRef}> |
| 47 |
<div className="flex justify-end mb-8 ml-4 relative"> |
| 48 |
{questionError ? ( |
| 49 |
<Error |
| 50 |
text={__( |
| 51 |
'Oops! We were unable to send your question.', |
| 52 |
'extendify', |
| 53 |
)} |
| 54 |
reset={reset} |
| 55 |
/> |
| 56 |
) : ( |
| 57 |
<p className="m-0 p-5 rounded-lg bg-gray-800 text-design-text text-sm"> |
| 58 |
<span |
| 59 |
className={classnames({ |
| 60 |
'animate-pulse': questionLoading, |
| 61 |
})}> |
| 62 |
{question} |
| 63 |
</span> |
| 64 |
</p> |
| 65 |
)} |
| 66 |
</div> |
| 67 |
{(status || loading || error) && ( |
| 68 |
<div className="flex justify-start mr-4 relative"> |
| 69 |
<div className="absolute -mt-4 -ml-2 rounded-full bg-design-main p-2 flex items-center"> |
| 70 |
<Icon |
| 71 |
icon={robot} |
| 72 |
className="text-design-text fill-current w-4 h-4" |
| 73 |
/> |
| 74 |
</div> |
| 75 |
{error ? ( |
| 76 |
<Error |
| 77 |
text={__( |
| 78 |
'Oops! We encountered an error while processing your question.', |
| 79 |
'extendify', |
| 80 |
)} |
| 81 |
reset={reset} |
| 82 |
/> |
| 83 |
) : ( |
| 84 |
<p className="m-0 p-5 rounded-lg bg-gray-100 text-gray-800 text-sm"> |
| 85 |
{displayedAnswer ? ( |
| 86 |
displayedAnswer |
| 87 |
.split('\n') |
| 88 |
.map((line, index) => ( |
| 89 |
<Fragment key={index}> |
| 90 |
{line} |
| 91 |
<br /> |
| 92 |
</Fragment> |
| 93 |
)) |
| 94 |
) : ( |
| 95 |
<span className="text-gray-600 font-bold animate-pulse"> |
| 96 |
... |
| 97 |
</span> |
| 98 |
)} |
| 99 |
</p> |
| 100 |
)} |
| 101 |
</div> |
| 102 |
)} |
| 103 |
</div> |
| 104 |
<div className="ask-another-question p-6 relative flex justify-center"> |
| 105 |
<button |
| 106 |
type="button" |
| 107 |
onClick={reset} |
| 108 |
className="bg-design-main text-design-text border-none py-2 px-4 rounded-sm cursor-pointer text-sm flex items-center gap-2"> |
| 109 |
{__('Ask Another Question', 'extendify')} |
| 110 |
<Icon |
| 111 |
icon={send} |
| 112 |
className="text-design-text fill-current h-6" |
| 113 |
/> |
| 114 |
</button> |
| 115 |
</div> |
| 116 |
</> |
| 117 |
) |
| 118 |
} |
| 119 |
|