Skip to content

Commit ed116a5

Browse files
committed
wip
1 parent c42d5f7 commit ed116a5

File tree

7 files changed

+261
-46
lines changed

7 files changed

+261
-46
lines changed

src/lib/components/chat/AssistantIntroduction.svelte

+5-1
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,10 @@
124124
</div>
125125
</div>
126126

127-
<div class="absolute right-3 top-3 md:right-4 md:top-4">
127+
<div
128+
class="absolute right-3 top-3 md:right-4 md:top-4"
129+
class:hidden={$page.data.embeddedAssistantId}
130+
>
128131
<div class="flex flex-row items-center gap-1">
129132
<button
130133
class="flex h-7 items-center gap-1.5 rounded-full border bg-white px-2.5 py-1 text-gray-800 shadow-sm hover:shadow-inner dark:border-gray-700 dark:bg-gray-700 dark:text-gray-300/90 dark:hover:bg-gray-800 max-sm:px-1.5 md:text-sm"
@@ -161,6 +164,7 @@
161164
goto(`${base}/`);
162165
}}
163166
class="absolute -bottom-6 right-2 inline-flex items-center justify-center text-xs text-gray-600 underline hover:brightness-50 dark:text-gray-400 dark:hover:brightness-110"
167+
class:hidden={$page.data.embeddedAssistantId}
164168
>
165169
<CarbonRenew class="mr-1.5 text-xxs" /> Reset to default model
166170
</button>

src/lib/components/chat/ChatWindow.svelte

+5-3
Original file line numberDiff line numberDiff line change
@@ -225,7 +225,7 @@
225225
on:drop|preventDefault={() => (onDrag = false)}
226226
/>
227227

228-
<div class="relative min-h-0 min-w-0">
228+
<div class="relative min-w-0" class:min-h-[400px]={$page.data.embeddedAssistantId}>
229229
{#if loginModalOpen}
230230
<LoginModal
231231
on:close={() => {
@@ -237,11 +237,12 @@
237237
class="scrollbar-custom mr-1 h-full overflow-y-auto"
238238
use:snapScrollToBottom={messages.length ? [...messages] : false}
239239
bind:this={chatContainer}
240+
id="chat-container"
240241
>
241242
<div
242243
class="mx-auto flex h-full max-w-3xl flex-col gap-6 px-5 pt-6 sm:gap-8 xl:max-w-4xl xl:pt-10"
243244
>
244-
{#if $page.data?.assistant && !!messages.length}
245+
{#if $page.data?.assistant && !!messages.length && !$page.data.embeddedAssistantId}
245246
<a
246247
class="mx-auto flex items-center gap-1.5 rounded-full border border-gray-100 bg-gray-50 py-1 pl-1 pr-3 text-sm text-gray-800 hover:bg-gray-100 dark:border-gray-800 dark:bg-gray-800 dark:text-gray-200 dark:hover:bg-gray-700"
247248
href="{base}/settings/assistants/{$page.data.assistant._id}"
@@ -263,7 +264,7 @@
263264

264265
{$page.data.assistant.name}
265266
</a>
266-
{:else if preprompt && preprompt != currentModel.preprompt}
267+
{:else if preprompt && preprompt != currentModel.preprompt && !$page.data.embeddedAssistantId}
267268
<SystemPromptModal preprompt={preprompt ?? ""} />
268269
{/if}
269270

@@ -448,6 +449,7 @@
448449
</form>
449450
<div
450451
class="mt-2 flex justify-between self-stretch px-1 text-xs text-gray-400/90 max-md:mb-2 max-sm:gap-2"
452+
class:hidden={$page.data.embeddedAssistantId}
451453
>
452454
<p>
453455
Model:

src/routes/+layout.server.ts

+15-2
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,9 @@ import type { ConvSidebar } from "$lib/types/ConvSidebar";
1111
import { toolFromConfigs } from "$lib/server/tools";
1212
import { MetricsServer } from "$lib/server/metrics";
1313
import type { ToolFront, ToolInputFile } from "$lib/types/Tool";
14+
import { error } from "@sveltejs/kit";
1415

15-
export const load: LayoutServerLoad = async ({ locals, depends, request }) => {
16+
export const load: LayoutServerLoad = async ({ locals, depends, request, url }) => {
1617
depends(UrlDependency.ConversationList);
1718

1819
const settings = await collections.settings.findOne(authCondition(locals));
@@ -44,7 +45,7 @@ export const load: LayoutServerLoad = async ({ locals, depends, request }) => {
4445

4546
const assistantActive = !models.map(({ id }) => id).includes(settings?.activeModel ?? "");
4647

47-
const assistant = assistantActive
48+
let assistant = assistantActive
4849
? JSON.parse(
4950
JSON.stringify(
5051
await collections.assistants.findOne({
@@ -54,6 +55,17 @@ export const load: LayoutServerLoad = async ({ locals, depends, request }) => {
5455
)
5556
: null;
5657

58+
const embeddedAssistantId = url.searchParams.get("embeddedAssistantId");
59+
if (embeddedAssistantId) {
60+
const embeddedAssistant = await collections.assistants.findOne({
61+
_id: new ObjectId(embeddedAssistantId),
62+
});
63+
if (!embeddedAssistant) {
64+
error(404, "Embedded Assistant not found.");
65+
}
66+
assistant = JSON.parse(JSON.stringify(embeddedAssistant));
67+
}
68+
5769
const conversations = await collections.conversations
5870
.find(authCondition(locals))
5971
.sort({ updatedAt: -1 })
@@ -244,5 +256,6 @@ export const load: LayoutServerLoad = async ({ locals, depends, request }) => {
244256
loginRequired,
245257
loginEnabled: requiresUser,
246258
guestMode: requiresUser && messagesBeforeLogin > 0,
259+
embeddedAssistantId: url.searchParams.get("embeddedAssistantId"),
247260
};
248261
};

src/routes/+layout.svelte

+60-39
Original file line numberDiff line numberDiff line change
@@ -203,49 +203,70 @@
203203
{#if envPublic.PUBLIC_APPLE_APP_ID}
204204
<meta name="apple-itunes-app" content={`app-id=${envPublic.PUBLIC_APPLE_APP_ID}`} />
205205
{/if}
206+
<!-- TODO: remove -->
207+
{#if !$page.data.embeddedAssistantId}
208+
<!-- pick the last assistant for test purposes -->
209+
<script
210+
src="http://localhost:5173/chat/api/assistant/{$page.data.assistants.at(-1)
211+
._id}/embed-snippet"
212+
defer
213+
></script>
214+
{/if}
206215
</svelte:head>
207216

208217
{#if !$settings.ethicsModalAccepted && $page.url.pathname !== `${base}/privacy` && PUBLIC_APP_DISCLAIMER === "1"}
209218
<DisclaimerModal />
210219
{/if}
211220

212-
<ExpandNavigation
213-
isCollapsed={isNavCollapsed}
214-
on:click={() => (isNavCollapsed = !isNavCollapsed)}
215-
classNames="absolute inset-y-0 z-10 my-auto {!isNavCollapsed
216-
? 'left-[280px]'
217-
: 'left-0'} *:transition-transform"
218-
/>
219-
220-
<div
221-
class="grid h-full w-screen grid-cols-1 grid-rows-[auto,1fr] overflow-hidden text-smd {!isNavCollapsed
222-
? 'md:grid-cols-[280px,1fr]'
223-
: 'md:grid-cols-[0px,1fr]'} transition-[300ms] [transition-property:grid-template-columns] dark:text-gray-300 md:grid-rows-[1fr]"
224-
>
225-
<MobileNav isOpen={isNavOpen} on:toggle={(ev) => (isNavOpen = ev.detail)} title={mobileNavTitle}>
226-
<NavMenu
227-
conversations={data.conversations}
228-
user={data.user}
229-
canLogin={data.user === undefined && data.loginEnabled}
230-
on:shareConversation={(ev) => shareConversation(ev.detail.id, ev.detail.title)}
231-
on:deleteConversation={(ev) => deleteConversation(ev.detail)}
232-
on:editConversationTitle={(ev) => editConversationTitle(ev.detail.id, ev.detail.title)}
233-
/>
234-
</MobileNav>
235-
<nav
236-
class=" grid max-h-screen grid-cols-1 grid-rows-[auto,1fr,auto] overflow-hidden *:w-[280px] max-md:hidden"
221+
{#if !$page.data.embeddedAssistantId}
222+
<ExpandNavigation
223+
isCollapsed={isNavCollapsed}
224+
on:click={() => (isNavCollapsed = !isNavCollapsed)}
225+
classNames="absolute inset-y-0 z-10 my-auto {!isNavCollapsed
226+
? 'left-[280px]'
227+
: 'left-0'} *:transition-transform"
228+
/>
229+
230+
<div
231+
class="grid h-full w-screen grid-cols-1 grid-rows-[auto,1fr] overflow-hidden text-smd {!isNavCollapsed
232+
? 'md:grid-cols-[280px,1fr]'
233+
: 'md:grid-cols-[0px,1fr]'} transition-[300ms] [transition-property:grid-template-columns] dark:text-gray-300 md:grid-rows-[1fr]"
237234
>
238-
<NavMenu
239-
conversations={data.conversations}
240-
user={data.user}
241-
canLogin={data.user === undefined && data.loginEnabled}
242-
on:shareConversation={(ev) => shareConversation(ev.detail.id, ev.detail.title)}
243-
on:deleteConversation={(ev) => deleteConversation(ev.detail)}
244-
on:editConversationTitle={(ev) => editConversationTitle(ev.detail.id, ev.detail.title)}
245-
/>
246-
</nav>
247-
{#if currentError}
248-
<Toast message={currentError} />
249-
{/if}
250-
<slot />
251-
</div>
235+
<MobileNav
236+
isOpen={isNavOpen}
237+
on:toggle={(ev) => (isNavOpen = ev.detail)}
238+
title={mobileNavTitle}
239+
>
240+
<NavMenu
241+
conversations={data.conversations}
242+
user={data.user}
243+
canLogin={data.user === undefined && data.loginEnabled}
244+
on:shareConversation={(ev) => shareConversation(ev.detail.id, ev.detail.title)}
245+
on:deleteConversation={(ev) => deleteConversation(ev.detail)}
246+
on:editConversationTitle={(ev) => editConversationTitle(ev.detail.id, ev.detail.title)}
247+
/>
248+
</MobileNav>
249+
<nav
250+
class=" grid max-h-screen grid-cols-1 grid-rows-[auto,1fr,auto] overflow-hidden *:w-[280px] max-md:hidden"
251+
>
252+
<NavMenu
253+
conversations={data.conversations}
254+
user={data.user}
255+
canLogin={data.user === undefined && data.loginEnabled}
256+
on:shareConversation={(ev) => shareConversation(ev.detail.id, ev.detail.title)}
257+
on:deleteConversation={(ev) => deleteConversation(ev.detail)}
258+
on:editConversationTitle={(ev) => editConversationTitle(ev.detail.id, ev.detail.title)}
259+
/>
260+
</nav>
261+
{#if currentError}
262+
<Toast message={currentError} />
263+
{/if}
264+
<slot />
265+
</div>
266+
{:else}
267+
<div
268+
class="grid h-full w-screen grid-cols-1 grid-rows-[auto,1fr] overflow-hidden text-smd dark:text-gray-300"
269+
>
270+
<slot />
271+
</div>
272+
{/if}

src/routes/+page.svelte

+12-1
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,8 @@
4444
body: JSON.stringify({
4545
model,
4646
preprompt: $settings.customPrompts[$settings.activeModel],
47-
assistantId: data.assistant?._id,
47+
assistantId: data.embeddedAssistantId ?? data.assistant?._id,
48+
// todo: embeddedAssistantId should be an actual field so that it can check
4849
}),
4950
});
5051
@@ -63,6 +64,16 @@
6364
files,
6465
});
6566
67+
// embedded assistant
68+
if (data.embeddedAssistantId) {
69+
await goto(
70+
`${base}/conversation/${conversationId}/?embeddedAssistantId=${encodeURIComponent(
71+
data.embeddedAssistantId
72+
)}`
73+
);
74+
return;
75+
}
76+
6677
// invalidateAll to update list of conversations
6778
await goto(`${base}/conversation/${conversationId}`, { invalidateAll: true });
6879
} catch (err) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
export async function GET({ params }) {
2+
const { id } = params;
3+
4+
const script = `(function() {
5+
function resizeIframeToContentSize(iframe) {
6+
if (iframe.contentWindow) {
7+
const maxHeight = window.innerHeight * 0.8; // 80% of window height
8+
const chatContainerEl = iframe.contentWindow.document.getElementById('chat-container');
9+
if(chatContainerEl){
10+
const contentHeight = chatContainerEl.scrollHeight;
11+
console.log('Resizing iframe. Content height:', contentHeight);
12+
iframe.style.height = Math.max(400, Math.min(contentHeight, maxHeight)) + "px";
13+
}
14+
}
15+
}
16+
17+
document.addEventListener('DOMContentLoaded', function() {
18+
const button = document.createElement('button');
19+
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';
20+
21+
const img = document.createElement('img');
22+
img.src = 'https://huggingface.co/chat/huggingchat/logo.svg';
23+
img.alt = 'HuggingChat Logo';
24+
img.className = 'size-4 mr-0.5';
25+
26+
const text = document.createTextNode('Chat');
27+
28+
button.appendChild(img);
29+
button.appendChild(text);
30+
31+
const modal = document.createElement('div');
32+
modal.className = 'hidden fixed inset-0 z-[1001] overflow-auto bg-black bg-opacity-50';
33+
34+
const modalContent = document.createElement('div');
35+
modalContent.className = 'bg-transparent mx-auto my-[5%] max-w-2xl rounded';
36+
37+
const closeButton = document.createElement('span');
38+
closeButton.innerHTML = '&times;';
39+
closeButton.className = 'text-gray-500 float-right text-2xl font-bold cursor-pointer hover:text-gray-700';
40+
41+
const iframe = document.createElement('iframe');
42+
iframe.className = 'w-full rounded-xl';
43+
iframe.style.height = '400px'; // Set an initial height
44+
iframe.src = \`http://localhost:5173/chat/?embeddedAssistantId=${id}\`;
45+
46+
iframe.onload = function() {
47+
console.log('Iframe loaded');
48+
const iframeWindow = this.contentWindow;
49+
const iframeDocument = iframeWindow.document;
50+
51+
let lastHeight = 0;
52+
53+
function checkSize() {
54+
const chatContainer = iframeDocument.getElementById('chat-container');
55+
if (chatContainer) {
56+
const newHeight = chatContainer.scrollHeight;
57+
if (newHeight !== lastHeight) {
58+
console.log('Height changed from', lastHeight, 'to', newHeight);
59+
resizeIframeToContentSize(iframe);
60+
lastHeight = newHeight;
61+
}
62+
}
63+
requestAnimationFrame(checkSize);
64+
}
65+
66+
// Start continuous size checking
67+
checkSize();
68+
69+
// Set up MutationObserver as a backup
70+
const observer = new MutationObserver(() => {
71+
console.log('Mutation detected');
72+
resizeIframeToContentSize(iframe);
73+
});
74+
75+
function initMutationObserver() {
76+
const chatContainer = iframeDocument.getElementById('chat-container');
77+
if (chatContainer) {
78+
console.log('Chat container found, setting up MutationObserver');
79+
observer.observe(chatContainer, { childList: true, subtree: true, attributes: true, characterData: true });
80+
} else {
81+
console.log('Chat container not found, retrying...');
82+
setTimeout(initMutationObserver, 500); // Retry after 500ms
83+
}
84+
}
85+
86+
// Start trying to initialize the MutationObserver
87+
initMutationObserver();
88+
89+
// Resize on load
90+
resizeIframeToContentSize(iframe);
91+
};
92+
93+
modalContent.appendChild(closeButton);
94+
modalContent.appendChild(iframe);
95+
modal.appendChild(modalContent);
96+
97+
function closeModal() {
98+
modal.classList.add('hidden');
99+
}
100+
101+
button.onclick = function() {
102+
modal.classList.remove('hidden');
103+
resizeIframeToContentSize(iframe); // Resize on opening to ensure correct initial size
104+
};
105+
106+
closeButton.onclick = closeModal;
107+
108+
window.onclick = function(event) {
109+
if (event.target == modal) {
110+
closeModal();
111+
}
112+
};
113+
114+
document.addEventListener('keydown', function(event) {
115+
if (event.key === 'Escape') {
116+
closeModal();
117+
}
118+
});
119+
120+
// Add resize event listener to adjust iframe height when window is resized
121+
window.addEventListener('resize', function() {
122+
if (!modal.classList.contains('hidden')) {
123+
resizeIframeToContentSize(iframe);
124+
}
125+
});
126+
127+
document.body.appendChild(button);
128+
document.body.appendChild(modal);
129+
});
130+
})();
131+
`;
132+
133+
return new Response(script, {
134+
headers: {
135+
"Content-Type": "application/javascript",
136+
"Access-Control-Allow-Origin": "*",
137+
},
138+
});
139+
}

0 commit comments

Comments
 (0)