Spaces:
Running
Running
File size: 7,926 Bytes
f3adf00 e88d77a cc54380 1dae9c1 98051f8 e88d77a a8a9533 e88d77a ffb0dba af319d7 2ec957c ffb0dba e88d77a c6896b8 df3243b e88d77a 7086abd e68d3eb 8aab1a5 7086abd e88d77a 59de250 ffb0dba e3c83d9 308b54f bd8f105 98051f8 308b54f 98051f8 308b54f ffb0dba 308b54f 1dae9c1 d72b096 308b54f ffb0dba 308b54f ffb0dba 78cdca3 1dae9c1 78cdca3 ffb0dba 3c110ed c6896b8 9d36148 c6896b8 9d36148 c6896b8 9d36148 c6896b8 e99e7c2 48288dc cc54380 48288dc cc54380 7af80c2 9d36148 f3adf00 a89f288 a8a9533 a89f288 6a95eb2 df3243b 6e16d44 a8a9533 df3243b a8a9533 df3243b a8a9533 df3243b a8a9533 df3243b 67577d7 a8a9533 922a0f7 67577d7 a8a9533 922a0f7 67577d7 a8a9533 67577d7 2acfeb9 a8a9533 2acfeb9 7affb80 a8a9533 7affb80 a8a9533 7affb80 845c6da a8a9533 845c6da a89f288 11a1c67 7bcf4d9 df3243b e88d77a 8aab1a5 e88d77a 06feee8 8aab1a5 7af80c2 7086abd 566c2fc cc38b64 7086abd 78cdca3 7086abd e88d77a 7086abd 566c2fc cc38b64 7086abd 78cdca3 7086abd 8aab1a5 ffb0dba 8aab1a5 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 |
<script lang="ts">
import "../styles/main.css";
import { onDestroy, onMount } from "svelte";
import { goto } from "$app/navigation";
import { base } from "$app/paths";
import { page } from "$app/stores";
import { env as envPublic } from "$env/dynamic/public";
import { error } from "$lib/stores/errors";
import { createSettingsStore } from "$lib/stores/settings";
import { shareConversation } from "$lib/shareConversation";
import Toast from "$lib/components/Toast.svelte";
import NavMenu from "$lib/components/NavMenu.svelte";
import MobileNav from "$lib/components/MobileNav.svelte";
import titleUpdate from "$lib/stores/titleUpdate";
import DisclaimerModal from "$lib/components/DisclaimerModal.svelte";
import ExpandNavigation from "$lib/components/ExpandNavigation.svelte";
export let data;
let isNavOpen = false;
let isNavCollapsed = false;
let errorToastTimeout: ReturnType<typeof setTimeout>;
let currentError: string | null;
async function onError() {
// If a new different error comes, wait for the current error to hide first
if ($error && currentError && $error !== currentError) {
clearTimeout(errorToastTimeout);
currentError = null;
await new Promise((resolve) => setTimeout(resolve, 300));
}
currentError = $error;
errorToastTimeout = setTimeout(() => {
$error = null;
currentError = null;
}, 3000);
}
async function deleteConversation(id: string) {
try {
const res = await fetch(`${base}/conversation/${id}`, {
method: "DELETE",
headers: {
"Content-Type": "application/json",
},
});
if (!res.ok) {
$error = "Error while deleting conversation, try again.";
return;
}
data.conversations.then((convs) => {
const newConvs = convs.filter((conv) => conv.id !== id);
data.conversations = Promise.resolve(newConvs);
});
if ($page.params.id === id) {
await goto(`${base}/`, { invalidateAll: true });
}
} catch (err) {
console.error(err);
$error = String(err);
}
}
async function editConversationTitle(id: string, title: string) {
try {
const res = await fetch(`${base}/conversation/${id}`, {
method: "PATCH",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ title }),
});
if (!res.ok) {
$error = "Error while editing title, try again.";
return;
}
data.conversations.then((convs) => {
const newConvs = convs.map((conv) => (conv.id === id ? { ...conv, title } : conv));
data.conversations = Promise.resolve(newConvs);
});
} catch (err) {
console.error(err);
$error = String(err);
}
}
onDestroy(() => {
clearTimeout(errorToastTimeout);
});
$: if ($error) onError();
$: if ($titleUpdate) {
data.conversations.then((convs) => {
const convIdx = convs.findIndex(({ id }) => id === $titleUpdate?.convId);
if (convIdx != -1) {
convs[convIdx].title = $titleUpdate?.title ?? convs[convIdx].title;
}
// update data.conversations
data.conversations = Promise.resolve([...convs]);
$titleUpdate = null;
});
}
const settings = createSettingsStore(data.settings);
onMount(async () => {
if ($page.url.searchParams.has("model")) {
await settings
.instantSet({
activeModel: $page.url.searchParams.get("model") ?? $settings.activeModel,
})
.then(async () => {
const query = new URLSearchParams($page.url.searchParams.toString());
query.delete("model");
await goto(`${base}/?${query.toString()}`, {
invalidateAll: true,
});
});
}
if ($page.url.searchParams.has("tools")) {
const tools = $page.url.searchParams.get("tools")?.split(",");
await settings
.instantSet({
tools: [...($settings.tools ?? []), ...(tools ?? [])],
})
.then(async () => {
const query = new URLSearchParams($page.url.searchParams.toString());
query.delete("tools");
await goto(`${base}/?${query.toString()}`, {
invalidateAll: true,
});
});
}
});
$: mobileNavTitle = ["/models", "/assistants", "/privacy"].includes($page.route.id ?? "")
? ""
: data.conversations.then((convs) => convs.find((conv) => conv.id === $page.params.id)?.title);
</script>
<svelte:head>
<title>{envPublic.PUBLIC_APP_NAME}</title>
<meta name="description" content="The first open source alternative to ChatGPT. 💪" />
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:site" content="@huggingface" />
<!-- use those meta tags everywhere except on the share assistant page -->
<!-- feel free to refacto if there's a better way -->
{#if !$page.url.pathname.includes("/assistant/") && $page.route.id !== "/assistants" && !$page.url.pathname.includes("/models/") && !$page.url.pathname.includes("/tools")}
<meta property="og:title" content={envPublic.PUBLIC_APP_NAME} />
<meta property="og:type" content="website" />
<meta property="og:url" content="{envPublic.PUBLIC_ORIGIN || $page.url.origin}{base}" />
<meta
property="og:image"
content="{envPublic.PUBLIC_ORIGIN ||
$page.url.origin}{base}/{envPublic.PUBLIC_APP_ASSETS}/thumbnail.png"
/>
<meta property="og:description" content={envPublic.PUBLIC_APP_DESCRIPTION} />
{/if}
<link
rel="icon"
href="{envPublic.PUBLIC_ORIGIN ||
$page.url.origin}{base}/{envPublic.PUBLIC_APP_ASSETS}/favicon.ico"
sizes="32x32"
/>
<link
rel="icon"
href="{envPublic.PUBLIC_ORIGIN ||
$page.url.origin}{base}/{envPublic.PUBLIC_APP_ASSETS}/icon.svg"
type="image/svg+xml"
/>
<link
rel="apple-touch-icon"
href="{envPublic.PUBLIC_ORIGIN ||
$page.url.origin}{base}/{envPublic.PUBLIC_APP_ASSETS}/apple-touch-icon.png"
/>
<link
rel="manifest"
href="{envPublic.PUBLIC_ORIGIN ||
$page.url.origin}{base}/{envPublic.PUBLIC_APP_ASSETS}/manifest.json"
/>
{#if envPublic.PUBLIC_PLAUSIBLE_SCRIPT_URL && envPublic.PUBLIC_ORIGIN}
<script
defer
data-domain={new URL(envPublic.PUBLIC_ORIGIN).hostname}
src={envPublic.PUBLIC_PLAUSIBLE_SCRIPT_URL}
></script>
{/if}
{#if envPublic.PUBLIC_APPLE_APP_ID}
<meta name="apple-itunes-app" content={`app-id=${envPublic.PUBLIC_APPLE_APP_ID}`} />
{/if}
</svelte:head>
{#if !$settings.ethicsModalAccepted && $page.url.pathname !== `${base}/privacy` && envPublic.PUBLIC_APP_DISCLAIMER === "1"}
<DisclaimerModal on:close={() => ($settings.ethicsModalAccepted = true)} />
{/if}
<ExpandNavigation
isCollapsed={isNavCollapsed}
on:click={() => (isNavCollapsed = !isNavCollapsed)}
classNames="absolute inset-y-0 z-10 my-auto {!isNavCollapsed
? 'left-[280px]'
: 'left-0'} *:transition-transform"
/>
<div
class="grid h-full w-screen grid-cols-1 grid-rows-[auto,1fr] overflow-hidden text-smd {!isNavCollapsed
? 'md:grid-cols-[280px,1fr]'
: 'md:grid-cols-[0px,1fr]'} transition-[300ms] [transition-property:grid-template-columns] dark:text-gray-300 md:grid-rows-[1fr]"
>
<MobileNav isOpen={isNavOpen} on:toggle={(ev) => (isNavOpen = ev.detail)} title={mobileNavTitle}>
<NavMenu
conversations={data.conversations}
user={data.user}
canLogin={data.user === undefined && data.loginEnabled}
on:shareConversation={(ev) => shareConversation(ev.detail.id, ev.detail.title)}
on:deleteConversation={(ev) => deleteConversation(ev.detail)}
on:editConversationTitle={(ev) => editConversationTitle(ev.detail.id, ev.detail.title)}
/>
</MobileNav>
<nav
class=" grid max-h-screen grid-cols-1 grid-rows-[auto,1fr,auto] overflow-hidden *:w-[280px] max-md:hidden"
>
<NavMenu
conversations={data.conversations}
user={data.user}
canLogin={data.user === undefined && data.loginEnabled}
on:shareConversation={(ev) => shareConversation(ev.detail.id, ev.detail.title)}
on:deleteConversation={(ev) => deleteConversation(ev.detail)}
on:editConversationTitle={(ev) => editConversationTitle(ev.detail.id, ev.detail.title)}
/>
</nav>
{#if currentError}
<Toast message={currentError} />
{/if}
<slot />
</div>
|