From fb5267405fc74858b1fa025ee76df78343807ab0 Mon Sep 17 00:00:00 2001 From: Mishig Davaadorj Date: Fri, 27 Sep 2024 15:17:22 +0200 Subject: [PATCH 1/6] wip --- .../chat/AssistantIntroduction.svelte | 6 +- src/lib/components/chat/ChatWindow.svelte | 8 +- src/routes/+layout.server.ts | 17 +- src/routes/+layout.svelte | 98 ++++++----- src/routes/+page.svelte | 13 +- .../assistant/[id]/embed-snippet/+server.ts | 164 ++++++++++++++++++ .../assistants/[assistantId]/+page.svelte | 25 +++ 7 files changed, 285 insertions(+), 46 deletions(-) create mode 100644 src/routes/api/assistant/[id]/embed-snippet/+server.ts diff --git a/src/lib/components/chat/AssistantIntroduction.svelte b/src/lib/components/chat/AssistantIntroduction.svelte index 24f57ef62e6..2f079f30ca0 100644 --- a/src/lib/components/chat/AssistantIntroduction.svelte +++ b/src/lib/components/chat/AssistantIntroduction.svelte @@ -124,7 +124,10 @@ -
+
diff --git a/src/lib/components/chat/ChatWindow.svelte b/src/lib/components/chat/ChatWindow.svelte index 227d1adb1cb..284c50476a2 100644 --- a/src/lib/components/chat/ChatWindow.svelte +++ b/src/lib/components/chat/ChatWindow.svelte @@ -226,7 +226,7 @@ on:drop|preventDefault={() => (onDrag = false)} /> -
+
{#if loginModalOpen} { @@ -238,11 +238,12 @@ class="scrollbar-custom mr-1 h-full overflow-y-auto" use:snapScrollToBottom={messages.length ? [...messages] : false} bind:this={chatContainer} + id="chat-container" >
- {#if $page.data?.assistant && !!messages.length} + {#if $page.data?.assistant && !!messages.length && !$page.data.embeddedAssistantId} - {:else if preprompt && preprompt != currentModel.preprompt} + {:else if preprompt && preprompt != currentModel.preprompt && !$page.data.embeddedAssistantId} {/if} @@ -455,6 +456,7 @@

Model: diff --git a/src/routes/+layout.server.ts b/src/routes/+layout.server.ts index 16405644cfa..c8b573e200d 100644 --- a/src/routes/+layout.server.ts +++ b/src/routes/+layout.server.ts @@ -11,8 +11,9 @@ import type { ConvSidebar } from "$lib/types/ConvSidebar"; import { toolFromConfigs } from "$lib/server/tools"; import { MetricsServer } from "$lib/server/metrics"; import type { ToolFront, ToolInputFile } from "$lib/types/Tool"; +import { error } from "@sveltejs/kit"; -export const load: LayoutServerLoad = async ({ locals, depends, request }) => { +export const load: LayoutServerLoad = async ({ locals, depends, request, url }) => { depends(UrlDependency.ConversationList); const settings = await collections.settings.findOne(authCondition(locals)); @@ -44,7 +45,7 @@ export const load: LayoutServerLoad = async ({ locals, depends, request }) => { const assistantActive = !models.map(({ id }) => id).includes(settings?.activeModel ?? ""); - const assistant = assistantActive + let assistant = assistantActive ? JSON.parse( JSON.stringify( await collections.assistants.findOne({ @@ -54,6 +55,17 @@ export const load: LayoutServerLoad = async ({ locals, depends, request }) => { ) : null; + const embeddedAssistantId = url.searchParams.get("embeddedAssistantId"); + if (embeddedAssistantId) { + const embeddedAssistant = await collections.assistants.findOne({ + _id: new ObjectId(embeddedAssistantId), + }); + if (!embeddedAssistant) { + error(404, "Embedded Assistant not found."); + } + assistant = JSON.parse(JSON.stringify(embeddedAssistant)); + } + const conversations = await collections.conversations .find(authCondition(locals)) .sort({ updatedAt: -1 }) @@ -245,5 +257,6 @@ export const load: LayoutServerLoad = async ({ locals, depends, request }) => { loginRequired, loginEnabled: requiresUser, guestMode: requiresUser && messagesBeforeLogin > 0, + embeddedAssistantId: url.searchParams.get("embeddedAssistantId"), }; }; diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte index 17259c17e5f..d331f3ee5ad 100644 --- a/src/routes/+layout.svelte +++ b/src/routes/+layout.svelte @@ -203,49 +203,69 @@ {#if envPublic.PUBLIC_APPLE_APP_ID} {/if} + + {#if !$page.data.embeddedAssistantId} + + {/if} {#if !$settings.ethicsModalAccepted && $page.url.pathname !== `${base}/privacy` && PUBLIC_APP_DISCLAIMER === "1"} {/if} - (isNavCollapsed = !isNavCollapsed)} - classNames="absolute inset-y-0 z-10 my-auto {!isNavCollapsed - ? 'left-[280px]' - : 'left-0'} *:transition-transform" -/> - -

- (isNavOpen = ev.detail)} title={mobileNavTitle}> - shareConversation(ev.detail.id, ev.detail.title)} - on:deleteConversation={(ev) => deleteConversation(ev.detail)} - on:editConversationTitle={(ev) => editConversationTitle(ev.detail.id, ev.detail.title)} - /> - - - {#if currentError} - - {/if} - -
+ (isNavOpen = ev.detail)} + title={mobileNavTitle} + > + shareConversation(ev.detail.id, ev.detail.title)} + on:deleteConversation={(ev) => deleteConversation(ev.detail)} + on:editConversationTitle={(ev) => editConversationTitle(ev.detail.id, ev.detail.title)} + /> + + + {#if currentError} + + {/if} + +
+{:else} +
+ +
+{/if} diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index 0f6c00212c9..0ff7aa9e842 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -44,7 +44,8 @@ body: JSON.stringify({ model, preprompt: $settings.customPrompts[$settings.activeModel], - assistantId: data.assistant?._id, + assistantId: data.embeddedAssistantId ?? data.assistant?._id, + // todo: embeddedAssistantId should be an actual field so that it can check }), }); @@ -63,6 +64,16 @@ files, }); + // embedded assistant + if (data.embeddedAssistantId) { + await goto( + `${base}/conversation/${conversationId}/?embeddedAssistantId=${encodeURIComponent( + data.embeddedAssistantId + )}` + ); + return; + } + // invalidateAll to update list of conversations await goto(`${base}/conversation/${conversationId}`, { invalidateAll: true }); } catch (err) { diff --git a/src/routes/api/assistant/[id]/embed-snippet/+server.ts b/src/routes/api/assistant/[id]/embed-snippet/+server.ts new file mode 100644 index 00000000000..ed61182b5e0 --- /dev/null +++ b/src/routes/api/assistant/[id]/embed-snippet/+server.ts @@ -0,0 +1,164 @@ +export async function GET({ params }) { + const { id } = params; + + const script = `(function() { + function resizeIframeToContentSize(iframe) { + if (iframe.contentWindow) { + const maxHeight = window.innerHeight * 0.8; // 80% of window height + const chatContainerEl = iframe.contentWindow.document.getElementById('chat-container'); + if(chatContainerEl){ + const contentHeight = chatContainerEl.scrollHeight; + console.log('Resizing iframe. Content height:', contentHeight); + iframe.style.height = Math.max(400, Math.min(contentHeight, maxHeight)) + "px"; + } + } + } + + document.addEventListener('DOMContentLoaded', function() { + const button = document.createElement('button'); + button.className = 'fixed bottom-5 right-5 z-50 px-1.5 py-1 bg-blue-500 text-white rounded cursor-pointer hover:bg-blue-600 transition-colors flex items-center focus:outline-none'; + + const img = document.createElement('img'); + img.src = 'https://huggingface.co/chat/huggingchat/logo.svg'; + img.alt = 'HuggingChat Logo'; + img.className = 'size-4 mr-0.5'; + + const text = document.createTextNode('Chat'); + + button.appendChild(img); + button.appendChild(text); + + const modal = document.createElement('div'); + modal.className = 'hidden fixed inset-0 z-[1001] overflow-auto bg-black bg-opacity-50'; + + const modalContent = document.createElement('div'); + modalContent.className = 'bg-transparent mx-auto my-[5%] max-w-2xl rounded'; + + const closeButton = document.createElement('span'); + closeButton.innerHTML = '×'; + closeButton.className = 'text-gray-500 float-right text-2xl font-bold cursor-pointer hover:text-gray-700'; + + const iframe = document.createElement('iframe'); + iframe.className = 'w-full rounded-xl'; + iframe.style.height = '400px'; // Set an initial height + iframe.src = \`http://localhost:5173/chat/?embeddedAssistantId=${id}\`; + + iframe.onload = function() { + console.log('Iframe loaded'); + const iframeWindow = this.contentWindow; + const iframeDocument = iframeWindow.document; + + let lastHeight = 0; + + function checkSize() { + const chatContainer = iframeDocument.getElementById('chat-container'); + if (chatContainer) { + const newHeight = chatContainer.scrollHeight; + if (newHeight !== lastHeight) { + console.log('Height changed from', lastHeight, 'to', newHeight); + resizeIframeToContentSize(iframe); + lastHeight = newHeight; + } + } + requestAnimationFrame(checkSize); + } + + // Start continuous size checking + checkSize(); + + // Set up MutationObserver as a backup + const observer = new MutationObserver(() => { + console.log('Mutation detected'); + resizeIframeToContentSize(iframe); + }); + + function initMutationObserver() { + const chatContainer = iframeDocument.getElementById('chat-container'); + if (chatContainer) { + console.log('Chat container found, setting up MutationObserver'); + observer.observe(chatContainer, { childList: true, subtree: true, attributes: true, characterData: true }); + } else { + console.log('Chat container not found, retrying...'); + setTimeout(initMutationObserver, 500); // Retry after 500ms + } + } + + // Start trying to initialize the MutationObserver + initMutationObserver(); + + // Resize on load + resizeIframeToContentSize(iframe); + + // Add event listener for Escape key in iframe + iframeDocument.addEventListener('keydown', function(event) { + if (event.key === 'Escape') { + closeModal(); + } + }); + }; + + modalContent.appendChild(closeButton); + modalContent.appendChild(iframe); + modal.appendChild(modalContent); + + // Store the original overflow style + let originalOverflow; + + function openModal() { + modal.classList.remove('hidden'); + resizeIframeToContentSize(iframe); + // Store the original overflow and prevent scrolling + originalOverflow = document.body.style.overflow; + document.body.style.overflow = 'hidden'; + } + + function closeModal() { + modal.classList.add('hidden'); + // Restore the original overflow + document.body.style.overflow = originalOverflow; + } + + button.onclick = openModal; + + closeButton.onclick = closeModal; + + window.onclick = function(event) { + if (event.target == modal) { + closeModal(); + } + }; + + document.addEventListener('keydown', function(event) { + if (event.key === 'Escape') { + closeModal(); + } + }); + + // Prevent default scrolling when modal is open + document.addEventListener('scroll', function(event) { + if (!modal.classList.contains('hidden')) { + event.preventDefault(); + return false; + } + }, { passive: false }); + + // Add resize event listener to adjust iframe height when window is resized + window.addEventListener('resize', function() { + if (!modal.classList.contains('hidden')) { + resizeIframeToContentSize(iframe); + } + }); + + document.body.appendChild(button); + document.body.appendChild(modal); + }); +})(); +`; + + return new Response(script, { + headers: { + "Content-Type": "application/javascript", + "Access-Control-Allow-Origin": "*", + }, + }); +} diff --git a/src/routes/settings/(nav)/assistants/[assistantId]/+page.svelte b/src/routes/settings/(nav)/assistants/[assistantId]/+page.svelte index 6e6e656451c..3a705cfcd41 100644 --- a/src/routes/settings/(nav)/assistants/[assistantId]/+page.svelte +++ b/src/routes/settings/(nav)/assistants/[assistantId]/+page.svelte @@ -31,6 +31,11 @@ envPublic.PUBLIC_SHARE_PREFIX || `${envPublic.PUBLIC_ORIGIN || $page.url.origin}${base}`; $: shareUrl = `${prefix}/assistant/${assistant?._id}`; + $: embedHtml = + ``.replaceAll( + "HF_SCRIPT", + "script" + ); let displayReportModal = false; @@ -194,6 +199,26 @@
+
+

Embed this assistant

+ +

Put the code below in your html head section.

+ +
+ + +
+ Copy +
+
+
+
+

System Instructions

From 1e8f976861f9c1e65521e32a2f52fe1d02ec9593 Mon Sep 17 00:00:00 2001 From: Mishig Davaadorj Date: Sat, 28 Sep 2024 11:00:08 +0200 Subject: [PATCH 2/6] rm dev consoles --- src/routes/api/assistant/[id]/embed-snippet/+server.ts | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/routes/api/assistant/[id]/embed-snippet/+server.ts b/src/routes/api/assistant/[id]/embed-snippet/+server.ts index ed61182b5e0..570a1ff39f2 100644 --- a/src/routes/api/assistant/[id]/embed-snippet/+server.ts +++ b/src/routes/api/assistant/[id]/embed-snippet/+server.ts @@ -8,7 +8,6 @@ export async function GET({ params }) { const chatContainerEl = iframe.contentWindow.document.getElementById('chat-container'); if(chatContainerEl){ const contentHeight = chatContainerEl.scrollHeight; - console.log('Resizing iframe. Content height:', contentHeight); iframe.style.height = Math.max(400, Math.min(contentHeight, maxHeight)) + "px"; } } @@ -44,7 +43,6 @@ export async function GET({ params }) { iframe.src = \`http://localhost:5173/chat/?embeddedAssistantId=${id}\`; iframe.onload = function() { - console.log('Iframe loaded'); const iframeWindow = this.contentWindow; const iframeDocument = iframeWindow.document; @@ -55,7 +53,6 @@ export async function GET({ params }) { if (chatContainer) { const newHeight = chatContainer.scrollHeight; if (newHeight !== lastHeight) { - console.log('Height changed from', lastHeight, 'to', newHeight); resizeIframeToContentSize(iframe); lastHeight = newHeight; } @@ -68,17 +65,16 @@ export async function GET({ params }) { // Set up MutationObserver as a backup const observer = new MutationObserver(() => { - console.log('Mutation detected'); resizeIframeToContentSize(iframe); }); function initMutationObserver() { const chatContainer = iframeDocument.getElementById('chat-container'); if (chatContainer) { - console.log('Chat container found, setting up MutationObserver'); + console.error('Chat container found, setting up MutationObserver'); observer.observe(chatContainer, { childList: true, subtree: true, attributes: true, characterData: true }); } else { - console.log('Chat container not found, retrying...'); + console.error('Chat container not found, retrying...'); setTimeout(initMutationObserver, 500); // Retry after 500ms } } From c544c28d603fb40f74dec5b5271fde97f73b3fab Mon Sep 17 00:00:00 2001 From: Mishig Davaadorj Date: Sat, 28 Sep 2024 11:07:54 +0200 Subject: [PATCH 3/6] comment --- src/routes/settings/(nav)/assistants/[assistantId]/+page.svelte | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/routes/settings/(nav)/assistants/[assistantId]/+page.svelte b/src/routes/settings/(nav)/assistants/[assistantId]/+page.svelte index 3a705cfcd41..144087f5225 100644 --- a/src/routes/settings/(nav)/assistants/[assistantId]/+page.svelte +++ b/src/routes/settings/(nav)/assistants/[assistantId]/+page.svelte @@ -35,7 +35,7 @@ ``.replaceAll( "HF_SCRIPT", "script" - ); + ); // replaceAll("HF_SCRIPT", "script") is needed to escape, otherwise svelte compiler breaks let displayReportModal = false; From fa2f44dbbec8e3b8b8c4286f9511bac8a8d30fef Mon Sep 17 00:00:00 2001 From: Victor Mustar Date: Mon, 30 Sep 2024 12:37:10 +0200 Subject: [PATCH 4/6] modal design --- src/routes/api/assistant/[id]/embed-snippet/+server.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/routes/api/assistant/[id]/embed-snippet/+server.ts b/src/routes/api/assistant/[id]/embed-snippet/+server.ts index 570a1ff39f2..c8d36e1b065 100644 --- a/src/routes/api/assistant/[id]/embed-snippet/+server.ts +++ b/src/routes/api/assistant/[id]/embed-snippet/+server.ts @@ -15,12 +15,12 @@ export async function GET({ params }) { document.addEventListener('DOMContentLoaded', function() { const button = document.createElement('button'); - button.className = 'fixed bottom-5 right-5 z-50 px-1.5 py-1 bg-blue-500 text-white rounded cursor-pointer hover:bg-blue-600 transition-colors flex items-center focus:outline-none'; + button.className = 'fixed z-[1002] bottom-5 right-5 z-50 px-4 gap-1 py-1 bg-black rounded-full text-white rounded cursor-pointer hover:bg-gray-800 border border-gray-200/30 transition-colors flex items-center focus:outline-none'; const img = document.createElement('img'); img.src = 'https://huggingface.co/chat/huggingchat/logo.svg'; img.alt = 'HuggingChat Logo'; - img.className = 'size-4 mr-0.5'; + img.className = 'size-5 mr-0.5 flex-none'; const text = document.createTextNode('Chat'); @@ -31,14 +31,14 @@ export async function GET({ params }) { modal.className = 'hidden fixed inset-0 z-[1001] overflow-auto bg-black bg-opacity-50'; const modalContent = document.createElement('div'); - modalContent.className = 'bg-transparent mx-auto my-[5%] max-w-2xl rounded'; + modalContent.className = 'bg-white max-w-2xl rounded-xl overflow-hidden bottom-16 right-5 absolute max-sm:left-5 sm:w-[460px] shadow-2xl'; const closeButton = document.createElement('span'); closeButton.innerHTML = '×'; closeButton.className = 'text-gray-500 float-right text-2xl font-bold cursor-pointer hover:text-gray-700'; const iframe = document.createElement('iframe'); - iframe.className = 'w-full rounded-xl'; + iframe.className = 'w-full'; iframe.style.height = '400px'; // Set an initial height iframe.src = \`http://localhost:5173/chat/?embeddedAssistantId=${id}\`; From 68e9950350d96b5456587692ce45be7e4e5187dc Mon Sep 17 00:00:00 2001 From: Victor Mustar Date: Mon, 30 Sep 2024 12:51:34 +0200 Subject: [PATCH 5/6] remove close button --- .../assistant/[id]/embed-snippet/+server.ts | 35 ++++++++----------- 1 file changed, 14 insertions(+), 21 deletions(-) diff --git a/src/routes/api/assistant/[id]/embed-snippet/+server.ts b/src/routes/api/assistant/[id]/embed-snippet/+server.ts index c8d36e1b065..449d70fb652 100644 --- a/src/routes/api/assistant/[id]/embed-snippet/+server.ts +++ b/src/routes/api/assistant/[id]/embed-snippet/+server.ts @@ -33,10 +33,6 @@ export async function GET({ params }) { const modalContent = document.createElement('div'); modalContent.className = 'bg-white max-w-2xl rounded-xl overflow-hidden bottom-16 right-5 absolute max-sm:left-5 sm:w-[460px] shadow-2xl'; - const closeButton = document.createElement('span'); - closeButton.innerHTML = '×'; - closeButton.className = 'text-gray-500 float-right text-2xl font-bold cursor-pointer hover:text-gray-700'; - const iframe = document.createElement('iframe'); iframe.className = 'w-full'; iframe.style.height = '400px'; // Set an initial height @@ -93,34 +89,31 @@ export async function GET({ params }) { }); }; - modalContent.appendChild(closeButton); modalContent.appendChild(iframe); modal.appendChild(modalContent); // Store the original overflow style let originalOverflow; - function openModal() { - modal.classList.remove('hidden'); - resizeIframeToContentSize(iframe); - // Store the original overflow and prevent scrolling - originalOverflow = document.body.style.overflow; - document.body.style.overflow = 'hidden'; - } - - function closeModal() { - modal.classList.add('hidden'); - // Restore the original overflow - document.body.style.overflow = originalOverflow; + function toggleModal() { + if (modal.classList.contains('hidden')) { + modal.classList.remove('hidden'); + resizeIframeToContentSize(iframe); + // Store the original overflow and prevent scrolling + originalOverflow = document.body.style.overflow; + document.body.style.overflow = 'hidden'; + } else { + modal.classList.add('hidden'); + // Restore the original overflow + document.body.style.overflow = originalOverflow; + } } - button.onclick = openModal; - - closeButton.onclick = closeModal; + button.onclick = toggleModal; window.onclick = function(event) { if (event.target == modal) { - closeModal(); + toggleModal(); } }; From fa6edd62b4319cb2bd6b3309c0fb970bcba06e8a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Victor=20Mu=C5=A1tar?= Date: Mon, 30 Sep 2024 12:53:59 +0200 Subject: [PATCH 6/6] Update src/routes/settings/(nav)/assistants/[assistantId]/+page.svelte Co-authored-by: Nathan Sarrazin --- src/routes/settings/(nav)/assistants/[assistantId]/+page.svelte | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/routes/settings/(nav)/assistants/[assistantId]/+page.svelte b/src/routes/settings/(nav)/assistants/[assistantId]/+page.svelte index 144087f5225..b3d6e81e0fa 100644 --- a/src/routes/settings/(nav)/assistants/[assistantId]/+page.svelte +++ b/src/routes/settings/(nav)/assistants/[assistantId]/+page.svelte @@ -32,7 +32,7 @@ $: shareUrl = `${prefix}/assistant/${assistant?._id}`; $: embedHtml = - ``.replaceAll( + ``.replaceAll( "HF_SCRIPT", "script" ); // replaceAll("HF_SCRIPT", "script") is needed to escape, otherwise svelte compiler breaks