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)}
- />
-
-
+{: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..449d70fb652
--- /dev/null
+++ b/src/routes/api/assistant/[id]/embed-snippet/+server.ts
@@ -0,0 +1,153 @@
+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;
+ iframe.style.height = Math.max(400, Math.min(contentHeight, maxHeight)) + "px";
+ }
+ }
+ }
+
+ document.addEventListener('DOMContentLoaded', function() {
+ const button = document.createElement('button');
+ 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-5 mr-0.5 flex-none';
+
+ 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-white max-w-2xl rounded-xl overflow-hidden bottom-16 right-5 absolute max-sm:left-5 sm:w-[460px] shadow-2xl';
+
+ const iframe = document.createElement('iframe');
+ iframe.className = 'w-full';
+ iframe.style.height = '400px'; // Set an initial height
+ iframe.src = \`http://localhost:5173/chat/?embeddedAssistantId=${id}\`;
+
+ iframe.onload = function() {
+ 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) {
+ resizeIframeToContentSize(iframe);
+ lastHeight = newHeight;
+ }
+ }
+ requestAnimationFrame(checkSize);
+ }
+
+ // Start continuous size checking
+ checkSize();
+
+ // Set up MutationObserver as a backup
+ const observer = new MutationObserver(() => {
+ resizeIframeToContentSize(iframe);
+ });
+
+ function initMutationObserver() {
+ const chatContainer = iframeDocument.getElementById('chat-container');
+ if (chatContainer) {
+ console.error('Chat container found, setting up MutationObserver');
+ observer.observe(chatContainer, { childList: true, subtree: true, attributes: true, characterData: true });
+ } else {
+ console.error('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(iframe);
+ modal.appendChild(modalContent);
+
+ // Store the original overflow style
+ let 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 = toggleModal;
+
+ window.onclick = function(event) {
+ if (event.target == modal) {
+ toggleModal();
+ }
+ };
+
+ 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..b3d6e81e0fa 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"
+ ); // replaceAll("HF_SCRIPT", "script") is needed to escape, otherwise svelte compiler breaks
let displayReportModal = false;
@@ -194,6 +199,26 @@