From 524f2f04b897baf16540fdda75b70e877e650cc1 Mon Sep 17 00:00:00 2001 From: Santthosh Selvadurai Date: Tue, 21 May 2024 21:33:42 -0700 Subject: [PATCH 1/3] 43 Preparing to add a fullscreen chatbot --- README.md | 37 ++-- src/app/assistants/[id]/chat/ChatPopup.tsx | 193 ++---------------- .../assistants/[id]/chat/useChatContext.ts | 185 +++++++++++++++++ src/app/link/[id]/layout.tsx | 11 + src/app/link/[id]/page.tsx | 19 ++ 5 files changed, 248 insertions(+), 197 deletions(-) create mode 100644 src/app/assistants/[id]/chat/useChatContext.ts create mode 100644 src/app/link/[id]/layout.tsx create mode 100644 src/app/link/[id]/page.tsx diff --git a/README.md b/README.md index 8bc0ef2..ff78084 100644 --- a/README.md +++ b/README.md @@ -64,35 +64,34 @@ Below is an AI assistant demo generated with Assistants Hub available at [Math W All models that support [OpenAI's Assistants API](https://platform.openai.com/docs/models/overview) are supported by [Assistants Hub](https://assistantshub.ai). -| Model Name | Provider | Streaming
Responses | Documents | Functions | -|--------------------|----------|---------------------|-----------|-----| -| GPT-4o | OpenAI | :white_check_mark: | :white_check_mark: | :construction: | -| GPT-4-Turbo | OpenAI | :white_check_mark: | :white_check_mark: | :construction: | -| GPT-4 | OpenAI | :white_check_mark: | :white_check_mark: | :construction: | -| GPT-3.5-Turbo | OpenAI | :white_check_mark: | :heavy_multiplication_x: | :heavy_multiplication_x: | -| GPT-3.5-Turbo-16k | OpenAI | :white_check_mark: | :heavy_multiplication_x: | :heavy_multiplication_x: | -| GPT-3.5-Turbo-0125 | OpenAI | :white_check_mark: | :heavy_multiplication_x: | :heavy_multiplication_x: | +| Model Name | Provider | Streaming
Responses | Documents | Functions | +| ------------------ | -------- | ------------------------ | ------------------------ | ------------------------ | +| GPT-4o | OpenAI | :white_check_mark: | :white_check_mark: | :construction: | +| GPT-4-Turbo | OpenAI | :white_check_mark: | :white_check_mark: | :construction: | +| GPT-4 | OpenAI | :white_check_mark: | :white_check_mark: | :construction: | +| GPT-3.5-Turbo | OpenAI | :white_check_mark: | :heavy_multiplication_x: | :heavy_multiplication_x: | +| GPT-3.5-Turbo-16k | OpenAI | :white_check_mark: | :heavy_multiplication_x: | :heavy_multiplication_x: | +| GPT-3.5-Turbo-0125 | OpenAI | :white_check_mark: | :heavy_multiplication_x: | :heavy_multiplication_x: | ### Google Gemini Models The gemini-1.5-pro-latest model is a large-scale language model developed by Google. It is designed to generate human-like text based on the input provided to it. The model is trained on a diverse range of text data to ensure that it can handle a wide variety of tasks and topics. [Read More](https://blog.google/technology/ai/google-gemini-next-generation-model-february-2024/#sundar-note) -| Model Name | Provider | Streaming
Responses | Documents | Functions | -|-------------------------|----------|---------------------|-----------|-----| -| Gemini-1.5-Pro-latest | Google | :white_check_mark: | :heavy_multiplication_x: | :heavy_multiplication_x: | -| Gemini-1.5-Flash-latest | Google | :white_check_mark: | :heavy_multiplication_x: | :heavy_multiplication_x: | - +| Model Name | Provider | Streaming
Responses | Documents | Functions | +| ----------------------- | -------- | ------------------------ | ------------------------ | ------------------------ | +| Gemini-1.5-Pro-latest | Google | :white_check_mark: | :heavy_multiplication_x: | :heavy_multiplication_x: | +| Gemini-1.5-Flash-latest | Google | :white_check_mark: | :heavy_multiplication_x: | :heavy_multiplication_x: | ### Gorq Cloud All models that support [Gorq Cloud API](https://console.groq.com/docs/models) are supported by [Assistants Hub](https://assistantshub.ai). -| Model Name | Provider | Streaming
Responses | Documents | Functions | -|--------------------|----------|---------------------|-----------|-----| -| Llama3-8b-8192 | Groq | :white_check_mark: | :heavy_multiplication_x: | :heavy_multiplication_x: | -| Llama3-70b-8192 | Groq | :white_check_mark: | :heavy_multiplication_x: | :heavy_multiplication_x: | -| Mixtral-8x7b-32768 | Groq | :white_check_mark: | :heavy_multiplication_x: | :heavy_multiplication_x: | -| Gemma-7b-it-8192 | Groq | :white_check_mark: | :heavy_multiplication_x: | :heavy_multiplication_x: | +| Model Name | Provider | Streaming
Responses | Documents | Functions | +| ------------------ | -------- | ------------------------ | ------------------------ | ------------------------ | +| Llama3-8b-8192 | Groq | :white_check_mark: | :heavy_multiplication_x: | :heavy_multiplication_x: | +| Llama3-70b-8192 | Groq | :white_check_mark: | :heavy_multiplication_x: | :heavy_multiplication_x: | +| Mixtral-8x7b-32768 | Groq | :white_check_mark: | :heavy_multiplication_x: | :heavy_multiplication_x: | +| Gemma-7b-it-8192 | Groq | :white_check_mark: | :heavy_multiplication_x: | :heavy_multiplication_x: | ## Getting Started diff --git a/src/app/assistants/[id]/chat/ChatPopup.tsx b/src/app/assistants/[id]/chat/ChatPopup.tsx index 9f413f7..374b8db 100644 --- a/src/app/assistants/[id]/chat/ChatPopup.tsx +++ b/src/app/assistants/[id]/chat/ChatPopup.tsx @@ -5,29 +5,15 @@ import ChatMessage from '@/app/assistants/[id]/chat/ChatMessage'; import { Button, TextInput } from 'flowbite-react'; import React, { useState, useRef, useEffect, useContext } from 'react'; import { Message } from '@/app/types/message'; -import { - createMessage, - createRun, - createThread, - getMessages, -} from '@/app/assistants/[id]/client'; import ChatTyping from '@/app/assistants/[id]/chat/ChatTyping'; -import { getFingerprint } from '@thumbmarkjs/thumbmarkjs'; -import { streamAsyncIterator } from '@/app/utils/streamAsyncIterator'; import ChatMessageStreaming from '@/app/assistants/[id]/chat/ChatMessageStreaming'; -import { - getItemWithExpiry, - removeItem, - setItemWithExpiry, -} from '@/app/utils/store'; -import ChatDisMissalAlert from '@/app/assistants/[id]/chat/ChatDismissalAlert'; import AssistantContext from '@/app/assistants/[id]/AssistantContext'; import { - getInitialPrompt, getInputMessageLabel, getPrimaryColor, getSecondaryColor, } from '@/app/utils/assistant'; +import { useChatContext } from '@/app/assistants/[id]/chat/useChatContext'; export interface ChatPopupProps extends ChatProps { hide: boolean; @@ -36,61 +22,21 @@ export interface ChatPopupProps extends ChatProps { export default function ChatPopup(props: ChatPopupProps) { const { assistant } = useContext(AssistantContext); + const bottomRef = useRef(null); - const [typedMessage, setTypedMessage] = useState(''); - const [messageStatus, setMessageStatus] = useState('' as string); - const [openConfirmationModal, setOpenConfirmationModal] = useState(false); - const [streamText, setStreamText] = useState(''); - const [currentThread, setCurrentThread] = useState(null); - const [currentMessage, setCurrentMessage] = useState(null); - const [messages, setMessages] = useState([]); const messagesRef = useRef(null); - const [fingerprint, setFingerprint] = useState(''); - const getModelProviderId = () => { - return assistant.modelProviderId ? assistant.modelProviderId : 'openai'; - }; - - useEffect(() => { - setMessages([ - { - created_at: Date.now() / 1000, - role: 'assistant', - content: [ - { - type: 'text', - text: { - value: getInitialPrompt(assistant), - annotations: [], - }, - }, - ], - }, - ]); - }, [assistant]); - - useEffect(() => { - getFingerprint() - .then((fingerprint) => { - setFingerprint(fingerprint); - }) - .catch((error) => { - console.error(error); - }); - - // Check to see if there is a thread in the session storage - let threadId = getItemWithExpiry(getAssistantThreadStorageKey()); - if (threadId) { - setCurrentThread(threadId); - getMessages(assistant.id, getModelProviderId(), threadId || '', '').then( - ([threadedMessageStatus, threadMessages]) => { - const newMessages: Message[] = threadMessages.data; - setStreamText(''); - setMessages([...messages, ...newMessages]); - } - ); // eslint-disable-line - } - }, []); + const { + typedMessage, + setTypedMessage, + messageStatus, + setMessageStatus, + streamText, + setStreamText, + messages, + setMessages, + sendMessage, + } = useChatContext(); useEffect(() => { if (messagesRef?.current && 'scrollIntoView' in messagesRef.current) { @@ -98,115 +44,11 @@ export default function ChatPopup(props: ChatPopupProps) { } }, [messages]); - useEffect(() => { - if (messageStatus === 'in_progress') { - sendMessageAndPoll().then((r) => {}); - } - }, [currentMessage]); - useEffect(() => { // @ts-ignore bottomRef.current?.scrollIntoView({ behavior: 'smooth', block: 'end' }); }, [streamText]); - const sendMessageAndPoll = async () => { - if (!currentMessage) { - return; - } - // If thread doesn't exist create thread - let thread = currentThread; - if (!thread) { - let [status, threadResponse] = await createThread( - assistant.id, - getModelProviderId(), - fingerprint - ); - thread = threadResponse.id; - setCurrentThread(threadResponse.id); - setItemWithExpiry( - getAssistantThreadStorageKey(), - threadResponse.id, - 24 * 60 * 60 * 1000 - ); - } - - // Send message to thread - let [messageStatus, messageResponse] = await createMessage( - assistant.id, - getModelProviderId(), - thread, - currentMessage - ); - let currentMessageId = messageResponse.id; - - // Run the thread - let runResponse = await createRun( - assistant.id, - getModelProviderId(), - thread - ); - - let textDecoder = new TextDecoder(); - let messageBuffer = ''; - for await (const chunk of streamAsyncIterator(runResponse)) { - const result = textDecoder.decode(chunk); - messageBuffer = messageBuffer + result; - setStreamText(messageBuffer); - } - const [threadedMessageStatus, threadMessages] = await getMessages( - assistant.id, - getModelProviderId(), - thread || '', - currentMessageId - ); - setMessageStatus('completed'); - const newMessages: Message[] = threadMessages.data; - if (newMessages.length > 0) { - setStreamText(''); - setMessages([...messages, ...newMessages]); - } else { - // Something wrong happened here, no new messages, but we just streamed text - console.log( - 'TODO: No new messages, but we just streamed text, check this' - ); - //TODO: There should be a way to handle this error - } - }; - - const handleSendMessage = async () => { - if (!typedMessage || !typedMessage.trim() || typedMessage.length <= 0) { - return; - } - let message: Message = { - created_at: Date.now() / 1000, - role: 'user', - content: [ - { - type: 'text', - text: { - value: typedMessage, - annotations: [], - }, - }, - ], - }; - setCurrentMessage(message); - setMessages([...messages, message]); - setTypedMessage(''); - setMessageStatus('in_progress' as string); - }; - - const getAssistantThreadStorageKey = () => { - return `ai.assistantshub.assistant.${assistant.id}.thread`; - }; - - const closeChatPopup = (confirmation: boolean) => { - if (confirmation) { - removeItem(getAssistantThreadStorageKey()); - props.setHide ? props.setHide(true) : null; - } - }; - return (
@@ -266,11 +108,6 @@ export default function ChatPopup(props: ChatPopupProps) { ) : ( <> )} -
{ if (e.key === 'Enter' && !e.shiftKey) { - handleSendMessage(); + sendMessage(); } e.stopPropagation(); }} @@ -295,7 +132,7 @@ export default function ChatPopup(props: ChatPopupProps) { style={{ color: getPrimaryColor(assistant), }} - onClick={handleSendMessage} + onClick={sendMessage} > { + const { assistant } = useContext(AssistantContext); + + const [typedMessage, setTypedMessage] = useState(''); + const [messageStatus, setMessageStatus] = useState('' as string); + const [streamText, setStreamText] = useState(''); + const [currentThread, setCurrentThread] = useState(null); + const [currentMessage, setCurrentMessage] = useState(null); + const [messages, setMessages] = useState([]); + const [fingerprint, setFingerprint] = useState(''); + + useEffect(() => { + setMessages([ + { + created_at: Date.now() / 1000, + role: 'assistant', + content: [ + { + type: 'text', + text: { + value: getInitialPrompt(assistant), + annotations: [], + }, + }, + ], + }, + ]); + }, [assistant]); + + useEffect(() => { + getFingerprint() + .then((fingerprint) => { + setFingerprint(fingerprint); + }) + .catch((error) => { + console.error(error); + }); + + // Check to see if there is a thread in the session storage + let threadId = getItemWithExpiry(getAssistantThreadStorageKey()); + if (threadId) { + setCurrentThread(threadId); + getMessages(assistant.id, getModelProviderId(), threadId || '', '').then( + ([threadedMessageStatus, threadMessages]) => { + const newMessages: Message[] = threadMessages.data; + setStreamText(''); + setMessages([...messages, ...newMessages]); + } + ); // eslint-disable-line + } + }, []); + + useEffect(() => { + if (messageStatus === 'in_progress') { + sendMessageAndPoll().then((r) => {}); + } + }, [currentMessage]); + + const getModelProviderId = () => { + return assistant.modelProviderId ? assistant.modelProviderId : 'openai'; + }; + + const sendMessageAndPoll = async () => { + if (!currentMessage) { + return; + } + // If thread doesn't exist create thread + let thread = currentThread; + if (!thread) { + let [status, threadResponse] = await createThread( + assistant.id, + getModelProviderId(), + fingerprint + ); + thread = threadResponse.id; + setCurrentThread(threadResponse.id); + setItemWithExpiry( + getAssistantThreadStorageKey(), + threadResponse.id, + 24 * 60 * 60 * 1000 + ); + } + + // Send message to thread + let [messageStatus, messageResponse] = await createMessage( + assistant.id, + getModelProviderId(), + thread, + currentMessage + ); + let currentMessageId = messageResponse.id; + + // Run the thread + let runResponse = await createRun( + assistant.id, + getModelProviderId(), + thread + ); + + let textDecoder = new TextDecoder(); + let messageBuffer = ''; + for await (const chunk of streamAsyncIterator(runResponse)) { + const result = textDecoder.decode(chunk); + messageBuffer = messageBuffer + result; + setStreamText(messageBuffer); + } + const [threadedMessageStatus, threadMessages] = await getMessages( + assistant.id, + getModelProviderId(), + thread || '', + currentMessageId + ); + setMessageStatus('completed'); + const newMessages: Message[] = threadMessages.data; + if (newMessages.length > 0) { + setStreamText(''); + setMessages([...messages, ...newMessages]); + } else { + // Something wrong happened here, no new messages, but we just streamed text + console.log( + 'TODO: No new messages, but we just streamed text, check this' + ); + //TODO: There should be a way to handle this error + } + }; + + const sendMessage = async () => { + if (!typedMessage || !typedMessage.trim() || typedMessage.length <= 0) { + return; + } + let message: Message = { + created_at: Date.now() / 1000, + role: 'user', + content: [ + { + type: 'text', + text: { + value: typedMessage, + annotations: [], + }, + }, + ], + }; + setCurrentMessage(message); + setMessages([...messages, message]); + setTypedMessage(''); + setMessageStatus('in_progress' as string); + }; + + const getAssistantThreadStorageKey = () => { + return `ai.assistantshub.assistant.${assistant.id}.thread`; + }; + + return { + typedMessage, + setTypedMessage, + messageStatus, + setMessageStatus, + streamText, + setStreamText, + currentThread, + setCurrentThread, + currentMessage, + setCurrentMessage, + messages, + setMessages, + fingerprint, + setFingerprint, + sendMessage, + }; +}; diff --git a/src/app/link/[id]/layout.tsx b/src/app/link/[id]/layout.tsx new file mode 100644 index 0000000..e40a20f --- /dev/null +++ b/src/app/link/[id]/layout.tsx @@ -0,0 +1,11 @@ +'use client'; + +import React from 'react'; + +export default function LinkLayout({ + children, +}: { + children: React.ReactNode; +}) { + return
{children}
; +} diff --git a/src/app/link/[id]/page.tsx b/src/app/link/[id]/page.tsx new file mode 100644 index 0000000..c60a123 --- /dev/null +++ b/src/app/link/[id]/page.tsx @@ -0,0 +1,19 @@ +'use client'; + +import { useParams } from 'next/navigation'; +import ChatWindow from '@/app/assistants/[id]/chat/ChatWindow'; + +export interface ChatComponentProps { + params: { id: string }; +} + +const LinkComponent: React.FC = ({ params }) => { + return ; +}; + +export default function Link() { + const params = useParams<{ id: string }>(); + + /* http://localhost:3001/link/asst_GDAqYSylPoffSzu9eZygXU7l */ + return ; +} From 0a02ed7d1e2e569d42799c37851b02ca9d794576 Mon Sep 17 00:00:00 2001 From: Santthosh Selvadurai Date: Tue, 21 May 2024 22:44:56 -0700 Subject: [PATCH 2/3] 43_permalink_chatbot is now ready --- src/app/assistants/[id]/chat/ChatPage.tsx | 122 ++++++++++++++++++ .../[id]/chat/ChatPageContextWrapper.tsx | 38 ++++++ .../assistants/[id]/chat/ChatPageHeader.tsx | 39 ++++++ src/app/link/[id]/page.tsx | 4 +- 4 files changed, 202 insertions(+), 1 deletion(-) create mode 100644 src/app/assistants/[id]/chat/ChatPage.tsx create mode 100644 src/app/assistants/[id]/chat/ChatPageContextWrapper.tsx create mode 100644 src/app/assistants/[id]/chat/ChatPageHeader.tsx diff --git a/src/app/assistants/[id]/chat/ChatPage.tsx b/src/app/assistants/[id]/chat/ChatPage.tsx new file mode 100644 index 0000000..b28f641 --- /dev/null +++ b/src/app/assistants/[id]/chat/ChatPage.tsx @@ -0,0 +1,122 @@ +import { Button, TextInput } from 'flowbite-react'; +import React, { useContext, useEffect, useRef } from 'react'; +import AssistantContext from '@/app/assistants/[id]/AssistantContext'; +import { useChatContext } from '@/app/assistants/[id]/chat/useChatContext'; +import { getInputMessageLabel, getPrimaryColor } from '@/app/utils/assistant'; +import { ChatPageHeader } from '@/app/assistants/[id]/chat/ChatPageHeader'; +import { Message } from '@/app/types/message'; +import ChatMessage from '@/app/assistants/[id]/chat/ChatMessage'; +import ChatMessageStreaming from '@/app/assistants/[id]/chat/ChatMessageStreaming'; +import ChatTyping from '@/app/assistants/[id]/chat/ChatTyping'; + +export default function ChatPage() { + const { assistant } = useContext(AssistantContext); + + const bottomRef = useRef(null); + const messagesRef = useRef(null); + + const { + typedMessage, + setTypedMessage, + messageStatus, + setMessageStatus, + streamText, + setStreamText, + messages, + setMessages, + sendMessage, + } = useChatContext(); + + useEffect(() => { + if (messagesRef?.current && 'scrollIntoView' in messagesRef.current) { + messagesRef.current.scrollIntoView({ block: 'end', behavior: 'smooth' }); + } + }, [messages]); + + useEffect(() => { + // @ts-ignore + bottomRef.current?.scrollIntoView({ behavior: 'smooth', block: 'end' }); + }, [streamText]); + + return ( +
+
+ +
+
+
+
+
+ {messages.map((message: Message, index) => { + return ; + })} + {streamText ? ( + <> + +
+ + ) : ( + <> + )} + {messageStatus === 'in_progress' && !streamText ? ( + + ) : ( + <> + )} +
+
+
+
+
+ {messageStatus === 'in_progress' ? ( + + {assistant.name} is typing... + + ) : ( + <> + )} +
+ { + if (e.key === 'Enter' && !e.shiftKey) { + sendMessage(); + } + e.stopPropagation(); + }} + onChange={(event) => { + setTypedMessage(event.target.value); + }} + > + +
+
+
+ ); +} diff --git a/src/app/assistants/[id]/chat/ChatPageContextWrapper.tsx b/src/app/assistants/[id]/chat/ChatPageContextWrapper.tsx new file mode 100644 index 0000000..f91bb4e --- /dev/null +++ b/src/app/assistants/[id]/chat/ChatPageContextWrapper.tsx @@ -0,0 +1,38 @@ +import { useGetAssistant } from '@/app/assistants/[id]/client'; +import React, { useEffect, useState } from 'react'; +import { Assistant } from '@/app/types/assistant'; +import ChatPage from '@/app/assistants/[id]/chat/ChatPage'; +import { Spinner } from 'flowbite-react'; +import AssistantContext from '@/app/assistants/[id]/AssistantContext'; + +export default function ChatPageContextWrapper(props: { assistantId: string }) { + let { assistantLoading, assistantResponse, assistantEmpty, reload } = + useGetAssistant(props.assistantId); + + const [loading, setLoading] = useState(true); + const [assistant, setAssistant] = useState(assistantResponse); + + useEffect(() => { + if (assistantResponse) { + setAssistant(assistantResponse); + setLoading(false); + } + }, [assistantLoading]); + + const changeAssistant = async (assistant: Assistant) => { + setAssistant(assistant); + // We don't allow changes from the end-user so this is a no-op + }; + + return loading ? ( +
+ +
+ ) : ( + + + + ); +} diff --git a/src/app/assistants/[id]/chat/ChatPageHeader.tsx b/src/app/assistants/[id]/chat/ChatPageHeader.tsx new file mode 100644 index 0000000..9aad344 --- /dev/null +++ b/src/app/assistants/[id]/chat/ChatPageHeader.tsx @@ -0,0 +1,39 @@ +'use client'; + +import { Card, Dropdown } from 'flowbite-react'; +import Image from 'next/image'; +import { useContext } from 'react'; +import AssistantContext from '@/app/assistants/[id]/AssistantContext'; +import { getImageHash } from '@/app/utils/hash'; + +export function ChatPageHeader() { + const { assistant } = useContext(AssistantContext); + + return ( + +
+
+ Assistant +
+
+

+ {assistant.name} +

+

+ {assistant.description} +

+
+
+
+ ); +} diff --git a/src/app/link/[id]/page.tsx b/src/app/link/[id]/page.tsx index c60a123..cb4b204 100644 --- a/src/app/link/[id]/page.tsx +++ b/src/app/link/[id]/page.tsx @@ -2,13 +2,15 @@ import { useParams } from 'next/navigation'; import ChatWindow from '@/app/assistants/[id]/chat/ChatWindow'; +import ChatPage from '@/app/assistants/[id]/chat/ChatPage'; +import ChatPageContextWrapper from '@/app/assistants/[id]/chat/ChatPageContextWrapper'; export interface ChatComponentProps { params: { id: string }; } const LinkComponent: React.FC = ({ params }) => { - return ; + return ; }; export default function Link() { From d254f9705f94304cef5b012a57ffd8925708479a Mon Sep 17 00:00:00 2001 From: Santthosh Selvadurai Date: Tue, 21 May 2024 23:28:50 -0700 Subject: [PATCH 3/3] 43 Permalink is now ready --- src/app/assistants/[id]/chat/ChatPage.tsx | 15 ++++---- .../assistants/[id]/chat/ChatPageHeader.tsx | 2 +- src/app/assistants/[id]/integrate/page.tsx | 36 ++++++++++++++----- 3 files changed, 35 insertions(+), 18 deletions(-) diff --git a/src/app/assistants/[id]/chat/ChatPage.tsx b/src/app/assistants/[id]/chat/ChatPage.tsx index b28f641..05c1ef5 100644 --- a/src/app/assistants/[id]/chat/ChatPage.tsx +++ b/src/app/assistants/[id]/chat/ChatPage.tsx @@ -19,11 +19,8 @@ export default function ChatPage() { typedMessage, setTypedMessage, messageStatus, - setMessageStatus, streamText, - setStreamText, messages, - setMessages, sendMessage, } = useChatContext(); @@ -40,14 +37,14 @@ export default function ChatPage() { return (
-
+
-
+
-
+
{messages.map((message: Message, index) => { @@ -72,7 +69,7 @@ export default function ChatPage() {
-
+
{messageStatus === 'in_progress' ? ( {assistant.name} is typing... @@ -80,7 +77,7 @@ export default function ChatPage() { ) : ( <> )} -
+
+

Integrate

- Use the below embed code to integrate your assistant to any web page + Use the below link direct users to your assistant or embed codes to + integrate to your web pages

-
- - +
+
+ + + +
Fully hosted assistant with a permanent URL
+
+ +
+ + + +
+
+
Floating assistant icon on the bottom right corner of the page
-
+
{`\`\`\`xml