diff --git a/src/app.d.ts b/src/app.d.ts new file mode 100644 index 0000000000000000000000000000000000000000..a4ea1d124d243ac63c82b2ab2f33774fef7db7ff --- /dev/null +++ b/src/app.d.ts @@ -0,0 +1,20 @@ +/// +/// + +import type { ObjectId } from "mongodb"; + +// See https://kit.svelte.dev/docs/types#app +// for information about these interfaces +declare global { + namespace App { + // interface Error {} + interface Locals { + sessionId: string; + userId?: ObjectId; + } + // interface PageData {} + // interface Platform {} + } +} + +export {}; diff --git a/src/app.html b/src/app.html new file mode 100644 index 0000000000000000000000000000000000000000..5972be35a6aad2a293a111f7aba5e40d8ed87ab9 --- /dev/null +++ b/src/app.html @@ -0,0 +1,73 @@ + + + + + + + Macie + + %sveltekit.head% + + +
%sveltekit.body%
+ + + + + + + + diff --git a/src/hooks.server.ts b/src/hooks.server.ts new file mode 100644 index 0000000000000000000000000000000000000000..665f69fa99528ec1b36037e28bff5bec9bbbbb5a --- /dev/null +++ b/src/hooks.server.ts @@ -0,0 +1,95 @@ +import { dev } from "$app/environment"; +import { COOKIE_NAME } from "$env/static/private"; +import type { Handle } from "@sveltejs/kit"; +import { + PUBLIC_GOOGLE_ANALYTICS_ID, + PUBLIC_DEPRECATED_GOOGLE_ANALYTICS_ID, +} from "$env/static/public"; +import { addYears } from "date-fns"; +import { collections } from "$lib/server/database"; +import { base } from "$app/paths"; +import { requiresUser } from "$lib/server/auth"; + +export const handle: Handle = async ({ event, resolve }) => { + const token = event.cookies.get(COOKIE_NAME); + + event.locals.sessionId = token || crypto.randomUUID(); + + const user = await collections.users.findOne({ sessionId: event.locals.sessionId }); + + if (user) { + event.locals.userId = user._id; + } + + if ( + !event.url.pathname.startsWith(`${base}/admin`) && + !["GET", "OPTIONS", "HEAD"].includes(event.request.method) + ) { + const sendJson = + event.request.headers.get("accept")?.includes("application/json") || + event.request.headers.get("content-type")?.includes("application/json"); + + if (!user && requiresUser) { + return new Response( + sendJson + ? JSON.stringify({ error: "You need to be logged in first" }) + : "You need to be logged in first", + { + status: 401, + headers: { + "content-type": sendJson ? "application/json" : "text/plain", + }, + } + ); + } + + if (!event.url.pathname.startsWith(`${base}/settings`)) { + const hasAcceptedEthicsModal = await collections.settings.countDocuments({ + sessionId: event.locals.sessionId, + ethicsModalAcceptedAt: { $exists: true }, + }); + + if (!hasAcceptedEthicsModal) { + return new Response( + sendJson + ? JSON.stringify({ error: "You need to accept the welcome modal first" }) + : "You need to accept the welcome modal first", + { + status: 405, + headers: { + "content-type": sendJson ? "application/json" : "text/plain", + }, + } + ); + } + } + } + + // Refresh cookie expiration date + event.cookies.set(COOKIE_NAME, event.locals.sessionId, { + path: "/", + // So that it works inside the space's iframe + sameSite: dev ? "lax" : "none", + secure: !dev, + httpOnly: true, + expires: addYears(new Date(), 1), + }); + + let replaced = false; + + const response = await resolve(event, { + transformPageChunk: (chunk) => { + // For some reason, Sveltekit doesn't let us load env variables from .env in the app.html template + if (replaced || !chunk.html.includes("%gaId%") || !chunk.html.includes("%gaIdDeprecated%")) { + return chunk.html; + } + replaced = true; + + return chunk.html + .replace("%gaId%", PUBLIC_GOOGLE_ANALYTICS_ID) + .replace("%gaIdDeprecated%", PUBLIC_DEPRECATED_GOOGLE_ANALYTICS_ID); + }, + }); + + return response; +}; diff --git a/src/lib/actions/snapScrollToBottom.ts b/src/lib/actions/snapScrollToBottom.ts new file mode 100644 index 0000000000000000000000000000000000000000..b22a0648221f6b58853a910fb6286f79574a0246 --- /dev/null +++ b/src/lib/actions/snapScrollToBottom.ts @@ -0,0 +1,54 @@ +import { navigating } from "$app/stores"; +import { tick } from "svelte"; +import { get } from "svelte/store"; + +const detachedOffset = 10; + +/** + * @param node element to snap scroll to bottom + * @param dependency pass in a dependency to update scroll on changes. + */ +export const snapScrollToBottom = (node: HTMLElement, dependency: unknown) => { + let prevScrollValue = node.scrollTop; + let isDetached = false; + + const handleScroll = () => { + // if user scrolled up, we detach + if (node.scrollTop < prevScrollValue) { + isDetached = true; + } + + // if user scrolled back to within 10px of bottom, we reattach + if (node.scrollTop - (node.scrollHeight - node.clientHeight) >= -detachedOffset) { + isDetached = false; + } + + prevScrollValue = node.scrollTop; + }; + + const updateScroll = async (_options: { force?: boolean } = {}) => { + const defaultOptions = { force: false }; + const options = { ...defaultOptions, ..._options }; + const { force } = options; + + if (!force && isDetached && !get(navigating)) return; + + // wait for next tick to ensure that the DOM is updated + await tick(); + + node.scrollTo({ top: node.scrollHeight }); + }; + + node.addEventListener("scroll", handleScroll); + + if (dependency) { + updateScroll({ force: true }); + } + + return { + update: updateScroll, + destroy: () => { + node.removeEventListener("scroll", handleScroll); + }, + }; +}; diff --git a/src/lib/buildPrompt.ts b/src/lib/buildPrompt.ts new file mode 100644 index 0000000000000000000000000000000000000000..6ddff81ab66ccfee60378bf604603fd202c9fe3a --- /dev/null +++ b/src/lib/buildPrompt.ts @@ -0,0 +1,36 @@ +import type { BackendModel } from "./server/models"; +import type { Message } from "./types/Message"; + +/** + * Convert [{user: "assistant", content: "hi"}, {user: "user", content: "hello"}] to: + * + * <|assistant|>hi<|endoftext|><|prompter|>hello<|endoftext|><|assistant|> + */ +export function buildPrompt( + messages: Pick[], + model: BackendModel +): string { + const prompt = + messages + .map( + (m) => + (m.from === "user" + ? model.userMessageToken + m.content + : model.assistantMessageToken + m.content) + + (model.messageEndToken + ? m.content.endsWith(model.messageEndToken) + ? "" + : model.messageEndToken + : "") + ) + .join("") + model.assistantMessageToken; + + // Not super precise, but it's truncated in the model's backend anyway + return ( + model.preprompt + + prompt + .split(" ") + .slice(-(model.parameters?.truncate ?? 0)) + .join(" ") + ); +} diff --git a/src/lib/components/AnnouncementBanner.svelte b/src/lib/components/AnnouncementBanner.svelte new file mode 100644 index 0000000000000000000000000000000000000000..2d702c40ff4280fe94f85735ac45b95393b4f4ed --- /dev/null +++ b/src/lib/components/AnnouncementBanner.svelte @@ -0,0 +1,15 @@ + + +
+ New! + {title} +
+ +
+
diff --git a/src/lib/components/CodeBlock.svelte b/src/lib/components/CodeBlock.svelte new file mode 100644 index 0000000000000000000000000000000000000000..dc7cbc500d9eb55e709ef2767ad25a91692fe129 --- /dev/null +++ b/src/lib/components/CodeBlock.svelte @@ -0,0 +1,28 @@ + + +
+ +
{@html highlightedCode || code.replaceAll("<", "<")}
+ +
diff --git a/src/lib/components/CopyToClipBoardBtn.svelte b/src/lib/components/CopyToClipBoardBtn.svelte new file mode 100644 index 0000000000000000000000000000000000000000..bf5a8e31aab6939dadcd43a17885762ffe768b1a --- /dev/null +++ b/src/lib/components/CopyToClipBoardBtn.svelte @@ -0,0 +1,50 @@ + + + diff --git a/src/lib/components/EthicsModal.svelte b/src/lib/components/EthicsModal.svelte new file mode 100644 index 0000000000000000000000000000000000000000..10816f5bdbb2eb79aed872bef632cb3131bca53c --- /dev/null +++ b/src/lib/components/EthicsModal.svelte @@ -0,0 +1,46 @@ + + + +
+

+ Macie +
+ Duet-1 +
+

+

+ Macie Chat is in development phase. +

+

+ Macie is in early stages of development. Don't take her advice as fact or to make big decisions. We ourselves don't know exactly what she says. +

+

+ You're conversations will be shared with Macie Developers to improve and support a better world of AI. +

+
+ + {#each Object.entries(settings) as [key, val]} + + {/each} + +
+
+
diff --git a/src/lib/components/MobileNav.svelte b/src/lib/components/MobileNav.svelte new file mode 100644 index 0000000000000000000000000000000000000000..cb9c2ec1fbd1c7bacd7dcc0b5a7ae5d08f84ccf0 --- /dev/null +++ b/src/lib/components/MobileNav.svelte @@ -0,0 +1,62 @@ + + + + diff --git a/src/lib/components/Modal.svelte b/src/lib/components/Modal.svelte new file mode 100644 index 0000000000000000000000000000000000000000..90559b5eae0fd28e2a384acde0eb954972fd1f1e --- /dev/null +++ b/src/lib/components/Modal.svelte @@ -0,0 +1,59 @@ + + + + + diff --git a/src/lib/components/ModelCardMetadata.svelte b/src/lib/components/ModelCardMetadata.svelte new file mode 100644 index 0000000000000000000000000000000000000000..9589761cd345a845aca6c1b1c83761374b3ca892 --- /dev/null +++ b/src/lib/components/ModelCardMetadata.svelte @@ -0,0 +1,53 @@ + + + diff --git a/src/lib/components/ModelsModal.svelte b/src/lib/components/ModelsModal.svelte new file mode 100644 index 0000000000000000000000000000000000000000..36882e3a92ea6fe4e4b82e31c9267f7cba57bfea --- /dev/null +++ b/src/lib/components/ModelsModal.svelte @@ -0,0 +1,80 @@ + + + +
{ + dispatch("close"); + }} + class="flex w-full flex-col gap-5 p-6" + > + {#each Object.entries(settings).filter(([k]) => k !== "activeModel") as [key, val]} + + {/each} +
+

Models

+ +
+ +
+ {#each models as model} +
+ + +
+ {/each} +
+ +
+
diff --git a/src/lib/components/NavConversationItem.svelte b/src/lib/components/NavConversationItem.svelte new file mode 100644 index 0000000000000000000000000000000000000000..380dabc34ef4d7c939c7f2c39cb7787450f4f950 --- /dev/null +++ b/src/lib/components/NavConversationItem.svelte @@ -0,0 +1,87 @@ + + + { + confirmDelete = false; + }} + href="{base}/conversation/{conv.id}" + class="group flex h-11 flex-none items-center gap-1.5 rounded-lg pl-3 pr-2 text-gray-500 hover:bg-gray-100 dark:text-gray-400 dark:hover:bg-gray-700 {conv.id === + $page.params.id + ? 'bg-gray-100 dark:bg-gray-700' + : ''}" +> +
+ {#if confirmDelete} + Delete + {/if} + {conv.title} +
+ + {#if confirmDelete} + + + {:else} + + + + {/if} +
diff --git a/src/lib/components/NavMenu.svelte b/src/lib/components/NavMenu.svelte new file mode 100644 index 0000000000000000000000000000000000000000..38ab92927cbe598ec59f8483d3b40c9690328016 --- /dev/null +++ b/src/lib/components/NavMenu.svelte @@ -0,0 +1,71 @@ + + + +
+ {#each conversations as conv (conv.id)} + + {/each} +
+
+ + + + + Privacy and Security + +
diff --git a/src/lib/components/Portal.svelte b/src/lib/components/Portal.svelte new file mode 100644 index 0000000000000000000000000000000000000000..dad285ed6bd7317f94c4e6152bf6c076ecbc52b5 --- /dev/null +++ b/src/lib/components/Portal.svelte @@ -0,0 +1,19 @@ + + + diff --git a/src/lib/components/ScrollToBottomBtn.svelte b/src/lib/components/ScrollToBottomBtn.svelte new file mode 100644 index 0000000000000000000000000000000000000000..07e1d985def7d9e1fa2e4edd93c104c47b23b178 --- /dev/null +++ b/src/lib/components/ScrollToBottomBtn.svelte @@ -0,0 +1,46 @@ + + +{#if visible} + +{/if} diff --git a/src/lib/components/SettingsModal.svelte b/src/lib/components/SettingsModal.svelte new file mode 100644 index 0000000000000000000000000000000000000000..914ee15151c1eccd0a87f2d60c8de38530054611 --- /dev/null +++ b/src/lib/components/SettingsModal.svelte @@ -0,0 +1,65 @@ + + + +
{ + dispatch("close"); + }} + method="post" + action="{base}/settings" + > +
+

Settings

+ +
+ + + +

+ Sharing your data will help improve the training data and make open models better over time. +

+

+ You can change this setting at any time, it applies to all your conversations. +

+

+ Read more about this model's authors, + Open Assistant. +

+ +
+
diff --git a/src/lib/components/StopGeneratingBtn.svelte b/src/lib/components/StopGeneratingBtn.svelte new file mode 100644 index 0000000000000000000000000000000000000000..32bd1db67422f264bb111edc1cf5c9d1653f20d4 --- /dev/null +++ b/src/lib/components/StopGeneratingBtn.svelte @@ -0,0 +1,17 @@ + + + diff --git a/src/lib/components/Switch.svelte b/src/lib/components/Switch.svelte new file mode 100644 index 0000000000000000000000000000000000000000..bdae385bbe1d596178391d25416b2f46e6475178 --- /dev/null +++ b/src/lib/components/Switch.svelte @@ -0,0 +1,11 @@ + + + +
+
+
diff --git a/src/lib/components/Toast.svelte b/src/lib/components/Toast.svelte new file mode 100644 index 0000000000000000000000000000000000000000..2470d358ae52406fbedeb43cbf8e1dacd09c5aec --- /dev/null +++ b/src/lib/components/Toast.svelte @@ -0,0 +1,19 @@ + + +
+
+ +

{message}

+
+
diff --git a/src/lib/components/Tooltip.svelte b/src/lib/components/Tooltip.svelte new file mode 100644 index 0000000000000000000000000000000000000000..0caf14d5edb956e14c686c68b588c1e984ee42a6 --- /dev/null +++ b/src/lib/components/Tooltip.svelte @@ -0,0 +1,22 @@ + + +
+ diff --git a/src/lib/components/chat/ChatInput.svelte b/src/lib/components/chat/ChatInput.svelte new file mode 100644 index 0000000000000000000000000000000000000000..a7e97b2f2e3109c946f786d2384fadb4637cb0b1 --- /dev/null +++ b/src/lib/components/chat/ChatInput.svelte @@ -0,0 +1,64 @@ + + + + +
+ + +