diff --git a/agent-widget/src/react/agent.scss b/agent-widget/src/react/agent.scss index 92b9a25..7400fe5 100644 --- a/agent-widget/src/react/agent.scss +++ b/agent-widget/src/react/agent.scss @@ -12,6 +12,7 @@ body { #root { height: 100%; + padding: 10px; } .agent-container { @@ -22,14 +23,13 @@ body { display: flex; flex-direction: column; justify-content: space-between; - border: 1px solid #cecece; } .widget-container { display: flex; - flex-direction: column; justify-content: center; align-items: center; + gap: 10px; } .chat-panel { @@ -191,3 +191,9 @@ ul { padding: 1rem; width: 100%; } + +.options { + display: flex; + flex-direction: column; + gap: 10px; +} diff --git a/agent-widget/src/react/agent.tsx b/agent-widget/src/react/agent.tsx index faa1b5c..c190835 100644 --- a/agent-widget/src/react/agent.tsx +++ b/agent-widget/src/react/agent.tsx @@ -1,9 +1,13 @@ import React, { useEffect, useMemo, useRef, useState } from 'react'; import './agent.scss'; import { Modal } from './modal/modal'; -import { useParams } from 'react-router-dom'; import { useSearchParams } from '../../node_modules/react-router-dom/dist/index'; +const getHtmlCode = ( + did: string +) => `
+`; + const InjectScript = React.memo(({ script }: { script: string }) => { const divRef = useRef(null); @@ -13,7 +17,6 @@ const InjectScript = React.memo(({ script }: { script: string }) => { } const doc = document.createRange().createContextualFragment(script); - divRef.current.innerHTML = ''; divRef.current.appendChild(doc); }, [script]); @@ -24,10 +27,14 @@ const InjectScript = React.memo(({ script }: { script: string }) => { export const Agent = () => { const [searchParams, setSearchParams] = useSearchParams(); + const [did, setDid] = useState(searchParams.get('did')); + const [thread, setThread] = useState([]); const [query, setQuery] = useState(''); + const [isHtmlCodeEnabled] = useState(true); + const [agentData, setAgentData] = useState< InitAgentEvent['data']['data'] | null >(null); @@ -36,7 +43,7 @@ export const Agent = () => { const [mustTopUp, setMustTopUp] = useState(false); - const [showModal, setShowModal] = useState(false); + const [showHtmlCodeModal, setShowHtmlCodeModal] = useState(false); const [textareaWidgetHtmlCode, setTextareaWidgetHtmlCode] = useState(''); @@ -51,11 +58,15 @@ export const Agent = () => { setMustTopUp(false); - setShowModal(false); + setShowHtmlCodeModal(false); setThread([]); - setSearchParams({ html: encodeURIComponent(textareaWidgetHtmlCode) }); + if (isHtmlCodeEnabled) { + setSearchParams({ html: encodeURIComponent(textareaWidgetHtmlCode) }); + } else if (did) { + setSearchParams({ did }); + } }; const resetQuery = () => { @@ -147,13 +158,23 @@ export const Agent = () => { }, [thread]); useEffect(() => { + const did = searchParams.get('did'); const encodedHtml = searchParams.get('html'); if (encodedHtml) { - const decodedHtml = decodeURIComponent(encodedHtml) + const decodedHtml = decodeURIComponent(encodedHtml); setWidgetHtmlCode(decodedHtml); setTextareaWidgetHtmlCode(decodedHtml); + return; + } + + if (did) { + const decodedDid = decodeURIComponent(did); + const htmlCode = getHtmlCode(decodedDid); + + setWidgetHtmlCode(htmlCode); + setTextareaWidgetHtmlCode(htmlCode); } }, [searchParams]); @@ -162,105 +183,128 @@ export const Agent = () => { }, []); return ( -
-
- {showModal && ( - { - setShowModal(false); - }} - > -
- - + <> +
+
+ +
+
+ {[ + 'did:nv:6004fbe1fc4508f45fae98009854199811e5c803e035c04b21d48ac8625b3035', + 'did:nv:f4f4d59075832a43d29cc5396f6dc95e575a9673425543bd18e6fd01c3fd19e0', + isHtmlCodeEnabled && 'html', + ] + .filter(Boolean) + .map((option, index) => + option === 'html' ? ( + + ) : ( + + ) + )}
- - )} - - -
-
-
-
    - {thread - .filter((item) => item.message) - .map(({ author, message, queryResponse }, index) => ( -
  • -
    -
    - {author === 'user' ? 'You' : 'Agent'} -
    -
    - {message} - {queryResponse?.creditsUsed > 0 && ( -
    - - - - - Credits used: {queryResponse.creditsUsed} - -
    - )} -
    -
    -
  • - ))} -
    -
+
-
{ - messageRef.current?.focus(); - }} - > - + +
+ + )} + ); }; diff --git a/agent-widget/src/react/agent/agent.tsx b/agent-widget/src/react/agent/agent.tsx deleted file mode 100644 index 9aa52ce..0000000 --- a/agent-widget/src/react/agent/agent.tsx +++ /dev/null @@ -1,310 +0,0 @@ -import React, { useEffect, useMemo, useRef, useState } from 'react'; -import './agent.scss'; -import { Modal } from '../modal/modal'; -import { useSearchParams } from '../../../node_modules/react-router-dom/dist/index'; - -const getHtmlCode = ( - did: string -) => `
-`; - -const InjectScript = React.memo(({ script }: { script: string }) => { - const divRef = useRef(null); - - useEffect(() => { - if (divRef.current === null) { - return; - } - - const doc = document.createRange().createContextualFragment(script); - divRef.current.innerHTML = ''; - divRef.current.appendChild(doc); - }, [script]); - - return
; -}); - -export const Agent = () => { - const [searchParams, setSearchParams] = useSearchParams(); - - const [did, setDid] = useState(searchParams.get('did')); - - const [thread, setThread] = useState([]); - - const [query, setQuery] = useState(''); - - const [isHtmlCodeEnabled] = useState(true); - - const [agentData, setAgentData] = useState< - InitAgentEvent['data']['data'] | null - >(null); - - const [isWaitingForResponse, setIsWaitingForResponse] = useState(false); - - const [mustTopUp, setMustTopUp] = useState(false); - - const [showHtmlCodeModal, setShowHtmlCodeModal] = useState(false); - - const [textareaWidgetHtmlCode, setTextareaWidgetHtmlCode] = useState(''); - - const [widgetHtmlCode, setWidgetHtmlCode] = useState(''); - - const messageRef = useRef(null); - - const threadEndRef = useRef(null); - - const loadWidget = () => { - setAgentData(null); - - setMustTopUp(false); - - setShowHtmlCodeModal(false); - - setThread([]); - - if (isHtmlCodeEnabled) { - setSearchParams({ html: encodeURIComponent(textareaWidgetHtmlCode) }); - } else if (did) { - setSearchParams({ did }); - } - }; - - const resetQuery = () => { - setQuery(''); - messageRef.current?.focus(); - }; - - const handleAgentEvents = ( - e: - | InitAgentEvent - | QueryResponseEvent - | AssistantResponseEvent - | StatusEvent - ) => { - switch (e.data.type) { - case 'nvm-agent:init-agent': { - setAgentData(e.data.data as InitAgentEvent['data']['data']); - break; - } - case 'nvm-agent:query-response': { - setThread((prev) => { - const lastMessage = prev.at(-1); - - if (lastMessage?.messageType === 'query') { - prev.pop(); - } - - return [...prev, e.data.data as AssistantThread]; - }); - break; - } - case 'nvm-agent:assistant-response': { - setThread((prev) => [...prev, e.data.data as AssistantThread]); - setIsWaitingForResponse(false); - break; - } - case 'nvm-agent:status': { - if (e.data.data === 'top-up') { - setMustTopUp(true); - } - break; - } - } - }; - - const submitQuery = () => { - setIsWaitingForResponse(true); - - window.postMessage( - { - type: 'nvm-agent:query', - data: { - query, - threadId: thread.at(-1)?.queryResponse?.threadId, - }, - }, - '*' - ); - - resetQuery(); - }; - - const isQueryingDisabled = useMemo( - () => !agentData || isWaitingForResponse || mustTopUp, - [agentData, isWaitingForResponse, mustTopUp] - ); - - const textAreaPlaceholder = useMemo(() => { - if (!agentData) { - return 'Login or purchase the assistant'; - } - - if (!mustTopUp && isWaitingForResponse) { - return 'Waiting for the response...'; - } - - if (mustTopUp) { - return 'Top up to continue'; - } - - return 'Enter your message'; - }, [agentData, isWaitingForResponse, mustTopUp]); - - useEffect(() => { - threadEndRef.current?.scrollIntoView({ - behavior: 'smooth', - block: 'nearest', - }); - }, [thread]); - - useEffect(() => { - const did = searchParams.get('did'); - const encodedHtml = searchParams.get('html'); - - if (encodedHtml) { - const decodedHtml = decodeURIComponent(encodedHtml); - - setWidgetHtmlCode(decodedHtml); - setTextareaWidgetHtmlCode(decodedHtml); - return; - } - - if (did) { - const decodedDid = decodeURIComponent(did); - const htmlCode = getHtmlCode(decodedDid); - - setWidgetHtmlCode(htmlCode); - setTextareaWidgetHtmlCode(htmlCode); - } - }, [searchParams]); - - useEffect(() => { - window.addEventListener('message', handleAgentEvents, false); - }, []); - - return ( - <> -
-
- -
-
- {[ - 'did:nv:6004fbe1fc4508f45fae98009854199811e5c803e035c04b21d48ac8625b3035', - 'did:nv:f4f4d59075832a43d29cc5396f6dc95e575a9673425543bd18e6fd01c3fd19e0', - isHtmlCodeEnabled && 'html', - ] - .filter(Boolean) - .map((option, index) => - option === 'html' ? ( - - ) : ( - - ) - )} -
-
-
-
-
-
    - {thread - .filter((item) => item.message) - .map(({ author, message, queryResponse }, index) => ( -
  • -
    -
    - {author === 'user' ? 'You' : 'Agent'} -
    -
    - {message} - {queryResponse?.creditsUsed > 0 && ( -
    - - - - - Credits used: {queryResponse.creditsUsed} - -
    - )} -
    -
    -
  • - ))} - {mustTopUp &&
    Top up to continue
    } -
    -
-
-
{ - messageRef.current?.focus(); - }} - > - - -
- - )} - - ); -};