Share convos (#1942)
Browse files* don't share why generating
* login on input
* cleanup
* Revert "cleanup"
This reverts commit d0c52e5d0c9d7efddf23167c407498449412530f.
* use a store instead (for mobile)
* remove tester
* cleanup require login
---------
Co-authored-by: Victor Muštar <victor.mustar@gmail.com>
- .env +1 -1
- chart/env/prod.yaml +1 -1
- src/hooks.server.ts +0 -1
- src/lib/components/MobileNav.svelte +5 -1
- src/lib/components/NavMenu.svelte +1 -1
- src/lib/components/chat/ChatInput.svelte +9 -1
- src/lib/components/chat/ChatMessage.svelte +11 -3
- src/lib/stores/loading.ts +3 -0
- src/routes/+layout.svelte +18 -6
- src/routes/+page.svelte +11 -10
- src/routes/conversation/[id]/+page.svelte +13 -13
.env
CHANGED
|
@@ -36,7 +36,7 @@ OPENID_CLIENT_ID=
|
|
| 36 |
OPENID_CLIENT_SECRET=
|
| 37 |
OPENID_SCOPES="openid profile inference-api"
|
| 38 |
USE_USER_TOKEN=
|
| 39 |
-
AUTOMATIC_LOGIN=
|
| 40 |
|
| 41 |
### Local Storage ###
|
| 42 |
MONGO_STORAGE_PATH= # where is the db folder stored
|
|
|
|
| 36 |
OPENID_CLIENT_SECRET=
|
| 37 |
OPENID_SCOPES="openid profile inference-api"
|
| 38 |
USE_USER_TOKEN=
|
| 39 |
+
AUTOMATIC_LOGIN="false"
|
| 40 |
|
| 41 |
### Local Storage ###
|
| 42 |
MONGO_STORAGE_PATH= # where is the db folder stored
|
chart/env/prod.yaml
CHANGED
|
@@ -50,7 +50,7 @@ envVars:
|
|
| 50 |
COUPLE_SESSION_WITH_COOKIE_NAME: "token"
|
| 51 |
OPENID_SCOPES: "openid profile inference-api"
|
| 52 |
USE_USER_TOKEN: "true"
|
| 53 |
-
AUTOMATIC_LOGIN: "
|
| 54 |
|
| 55 |
ADDRESS_HEADER: "X-Forwarded-For"
|
| 56 |
APP_BASE: "/chat"
|
|
|
|
| 50 |
COUPLE_SESSION_WITH_COOKIE_NAME: "token"
|
| 51 |
OPENID_SCOPES: "openid profile inference-api"
|
| 52 |
USE_USER_TOKEN: "true"
|
| 53 |
+
AUTOMATIC_LOGIN: "false"
|
| 54 |
|
| 55 |
ADDRESS_HEADER: "X-Forwarded-For"
|
| 56 |
APP_BASE: "/chat"
|
src/hooks.server.ts
CHANGED
|
@@ -136,7 +136,6 @@ export const handle: Handle = async ({ event, resolve }) => {
|
|
| 136 |
if (
|
| 137 |
!auth.user &&
|
| 138 |
config.AUTOMATIC_LOGIN === "true" &&
|
| 139 |
-
!event.url.pathname.startsWith(`${base}/`) &&
|
| 140 |
!event.url.pathname.startsWith(`${base}/login`) &&
|
| 141 |
!event.url.pathname.startsWith(`${base}/healthcheck`)
|
| 142 |
) {
|
|
|
|
| 136 |
if (
|
| 137 |
!auth.user &&
|
| 138 |
config.AUTOMATIC_LOGIN === "true" &&
|
|
|
|
| 139 |
!event.url.pathname.startsWith(`${base}/login`) &&
|
| 140 |
!event.url.pathname.startsWith(`${base}/healthcheck`)
|
| 141 |
) {
|
src/lib/components/MobileNav.svelte
CHANGED
|
@@ -16,6 +16,7 @@
|
|
| 16 |
import IconBurger from "$lib/components/icons/IconBurger.svelte";
|
| 17 |
import { Spring } from "svelte/motion";
|
| 18 |
import { shareModal } from "$lib/stores/shareModal";
|
|
|
|
| 19 |
interface Props {
|
| 20 |
title: string | undefined;
|
| 21 |
children?: import("svelte").Snippet;
|
|
@@ -28,7 +29,10 @@
|
|
| 28 |
|
| 29 |
const isHuggingChat = $derived(Boolean(page.data?.publicConfig?.isHuggingChat));
|
| 30 |
const canShare = $derived(
|
| 31 |
-
|
|
|
|
|
|
|
|
|
|
| 32 |
);
|
| 33 |
|
| 34 |
// Define the width for the drawer (less than 100% to create the gap)
|
|
|
|
| 16 |
import IconBurger from "$lib/components/icons/IconBurger.svelte";
|
| 17 |
import { Spring } from "svelte/motion";
|
| 18 |
import { shareModal } from "$lib/stores/shareModal";
|
| 19 |
+
import { loading } from "$lib/stores/loading";
|
| 20 |
interface Props {
|
| 21 |
title: string | undefined;
|
| 22 |
children?: import("svelte").Snippet;
|
|
|
|
| 29 |
|
| 30 |
const isHuggingChat = $derived(Boolean(page.data?.publicConfig?.isHuggingChat));
|
| 31 |
const canShare = $derived(
|
| 32 |
+
!$loading &&
|
| 33 |
+
isHuggingChat &&
|
| 34 |
+
Boolean(page.params?.id) &&
|
| 35 |
+
page.route.id?.startsWith("/conversation/")
|
| 36 |
);
|
| 37 |
|
| 38 |
// Define the width for the drawer (less than 100% to create the gap)
|
src/lib/components/NavMenu.svelte
CHANGED
|
@@ -125,7 +125,7 @@
|
|
| 125 |
{publicConfig.PUBLIC_APP_NAME}
|
| 126 |
</a>
|
| 127 |
<a
|
| 128 |
-
href={`${base}/`}
|
| 129 |
onclick={handleNewChatClick}
|
| 130 |
class="flex rounded-lg border bg-white px-2 py-0.5 text-center shadow-sm hover:shadow-none dark:border-gray-600 dark:bg-gray-700 sm:text-smd"
|
| 131 |
title="Ctrl/Cmd + Shift + O"
|
|
|
|
| 125 |
{publicConfig.PUBLIC_APP_NAME}
|
| 126 |
</a>
|
| 127 |
<a
|
| 128 |
+
href={page.data.loginRequired && !page.data.user ? `${base}/login` : `${base}/`}
|
| 129 |
onclick={handleNewChatClick}
|
| 130 |
class="flex rounded-lg border bg-white px-2 py-0.5 text-center shadow-sm hover:shadow-none dark:border-gray-600 dark:bg-gray-700 sm:text-smd"
|
| 131 |
title="Ctrl/Cmd + Shift + O"
|
src/lib/components/chat/ChatInput.svelte
CHANGED
|
@@ -1,7 +1,7 @@
|
|
| 1 |
<script lang="ts">
|
| 2 |
import { onMount, tick } from "svelte";
|
| 3 |
|
| 4 |
-
import { afterNavigate } from "$app/navigation";
|
| 5 |
|
| 6 |
import HoverTooltip from "$lib/components/HoverTooltip.svelte";
|
| 7 |
import IconPaperclip from "$lib/components/icons/IconPaperclip.svelte";
|
|
@@ -9,6 +9,7 @@
|
|
| 9 |
import { loginModalOpen } from "$lib/stores/loginModal";
|
| 10 |
|
| 11 |
import { isVirtualKeyboard } from "$lib/utils/isVirtualKeyboard";
|
|
|
|
| 12 |
interface Props {
|
| 13 |
files?: File[];
|
| 14 |
mimeTypes?: string[];
|
|
@@ -45,6 +46,8 @@
|
|
| 45 |
files = [...files, ...(target.files ?? [])];
|
| 46 |
};
|
| 47 |
|
|
|
|
|
|
|
| 48 |
let textareaElement: HTMLTextAreaElement | undefined = $state();
|
| 49 |
let isCompositionOn = $state(false);
|
| 50 |
let blurTimeout: ReturnType<typeof setTimeout> | null = $state(null);
|
|
@@ -57,6 +60,7 @@
|
|
| 57 |
: Promise.resolve();
|
| 58 |
|
| 59 |
async function focusTextarea() {
|
|
|
|
| 60 |
if (!textareaElement || textareaElement.disabled || isVirtualKeyboard()) return;
|
| 61 |
if (typeof document !== "undefined" && document.activeElement === textareaElement) return;
|
| 62 |
|
|
@@ -118,6 +122,10 @@
|
|
| 118 |
}
|
| 119 |
|
| 120 |
function handleFocus() {
|
|
|
|
|
|
|
|
|
|
|
|
|
| 121 |
if (blurTimeout) {
|
| 122 |
clearTimeout(blurTimeout);
|
| 123 |
blurTimeout = null;
|
|
|
|
| 1 |
<script lang="ts">
|
| 2 |
import { onMount, tick } from "svelte";
|
| 3 |
|
| 4 |
+
import { afterNavigate, goto } from "$app/navigation";
|
| 5 |
|
| 6 |
import HoverTooltip from "$lib/components/HoverTooltip.svelte";
|
| 7 |
import IconPaperclip from "$lib/components/icons/IconPaperclip.svelte";
|
|
|
|
| 9 |
import { loginModalOpen } from "$lib/stores/loginModal";
|
| 10 |
|
| 11 |
import { isVirtualKeyboard } from "$lib/utils/isVirtualKeyboard";
|
| 12 |
+
import { base } from "$app/paths";
|
| 13 |
interface Props {
|
| 14 |
files?: File[];
|
| 15 |
mimeTypes?: string[];
|
|
|
|
| 46 |
files = [...files, ...(target.files ?? [])];
|
| 47 |
};
|
| 48 |
|
| 49 |
+
const requireLogin = $derived(page.data.loginRequired && !page.data.user);
|
| 50 |
+
|
| 51 |
let textareaElement: HTMLTextAreaElement | undefined = $state();
|
| 52 |
let isCompositionOn = $state(false);
|
| 53 |
let blurTimeout: ReturnType<typeof setTimeout> | null = $state(null);
|
|
|
|
| 60 |
: Promise.resolve();
|
| 61 |
|
| 62 |
async function focusTextarea() {
|
| 63 |
+
if (requireLogin) return;
|
| 64 |
if (!textareaElement || textareaElement.disabled || isVirtualKeyboard()) return;
|
| 65 |
if (typeof document !== "undefined" && document.activeElement === textareaElement) return;
|
| 66 |
|
|
|
|
| 122 |
}
|
| 123 |
|
| 124 |
function handleFocus() {
|
| 125 |
+
if (requireLogin) {
|
| 126 |
+
goto(`${base}/login`, { invalidateAll: true });
|
| 127 |
+
return;
|
| 128 |
+
}
|
| 129 |
if (blurTimeout) {
|
| 130 |
clearTimeout(blurTimeout);
|
| 131 |
blurTimeout = null;
|
src/lib/components/chat/ChatMessage.svelte
CHANGED
|
@@ -17,6 +17,8 @@
|
|
| 17 |
import OpenReasoningResults from "./OpenReasoningResults.svelte";
|
| 18 |
import Alternatives from "./Alternatives.svelte";
|
| 19 |
import MessageAvatar from "./MessageAvatar.svelte";
|
|
|
|
|
|
|
| 20 |
|
| 21 |
interface Props {
|
| 22 |
message: Message;
|
|
@@ -64,6 +66,14 @@
|
|
| 64 |
}
|
| 65 |
}
|
| 66 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 67 |
let editContentEl: HTMLTextAreaElement | undefined = $state();
|
| 68 |
let editFormEl: HTMLFormElement | undefined = $state();
|
| 69 |
|
|
@@ -218,9 +228,7 @@
|
|
| 218 |
class="btn rounded-sm p-1 text-xs text-gray-400 hover:text-gray-500 focus:ring-0 dark:text-gray-400 dark:hover:text-gray-300"
|
| 219 |
title="Retry"
|
| 220 |
type="button"
|
| 221 |
-
onclick={
|
| 222 |
-
onretry?.({ id: message.id });
|
| 223 |
-
}}
|
| 224 |
>
|
| 225 |
<CarbonRotate360 />
|
| 226 |
</button>
|
|
|
|
| 17 |
import OpenReasoningResults from "./OpenReasoningResults.svelte";
|
| 18 |
import Alternatives from "./Alternatives.svelte";
|
| 19 |
import MessageAvatar from "./MessageAvatar.svelte";
|
| 20 |
+
import { goto } from "$app/navigation";
|
| 21 |
+
import { page } from "$app/state";
|
| 22 |
|
| 23 |
interface Props {
|
| 24 |
message: Message;
|
|
|
|
| 66 |
}
|
| 67 |
}
|
| 68 |
|
| 69 |
+
function handleRetry() {
|
| 70 |
+
if (page.data.loginRequired && !page.data.user) {
|
| 71 |
+
goto(`${base}/login`, { invalidateAll: true });
|
| 72 |
+
return;
|
| 73 |
+
}
|
| 74 |
+
onretry?.({ id: message.id });
|
| 75 |
+
}
|
| 76 |
+
|
| 77 |
let editContentEl: HTMLTextAreaElement | undefined = $state();
|
| 78 |
let editFormEl: HTMLFormElement | undefined = $state();
|
| 79 |
|
|
|
|
| 228 |
class="btn rounded-sm p-1 text-xs text-gray-400 hover:text-gray-500 focus:ring-0 dark:text-gray-400 dark:hover:text-gray-300"
|
| 229 |
title="Retry"
|
| 230 |
type="button"
|
| 231 |
+
onclick={handleRetry}
|
|
|
|
|
|
|
| 232 |
>
|
| 233 |
<CarbonRotate360 />
|
| 234 |
</button>
|
src/lib/stores/loading.ts
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { writable } from "svelte/store";
|
| 2 |
+
|
| 3 |
+
export const loading = writable(false);
|
src/routes/+layout.svelte
CHANGED
|
@@ -20,9 +20,10 @@
|
|
| 20 |
import { setContext } from "svelte";
|
| 21 |
import { handleResponse, useAPIClient } from "$lib/APIClient";
|
| 22 |
import { isAborted } from "$lib/stores/isAborted";
|
|
|
|
| 23 |
import IconShare from "$lib/components/icons/IconShare.svelte";
|
| 24 |
import { shareModal } from "$lib/stores/shareModal";
|
| 25 |
-
import
|
| 26 |
|
| 27 |
let { data = $bindable(), children } = $props();
|
| 28 |
|
|
@@ -98,7 +99,7 @@
|
|
| 98 |
function closeWelcomeModal() {
|
| 99 |
settings.set({ welcomeModalSeen: true });
|
| 100 |
|
| 101 |
-
if (
|
| 102 |
goto(`${base}/login`, { invalidateAll: true });
|
| 103 |
}
|
| 104 |
}
|
|
@@ -177,7 +178,7 @@
|
|
| 177 |
);
|
| 178 |
|
| 179 |
// Show the welcome modal once on first app load
|
| 180 |
-
let showWelcome = $derived(!
|
| 181 |
</script>
|
| 182 |
|
| 183 |
<svelte:head>
|
|
@@ -220,7 +221,7 @@
|
|
| 220 |
{/if}
|
| 221 |
</svelte:head>
|
| 222 |
|
| 223 |
-
{#if showWelcome
|
| 224 |
<WelcomeModal close={closeWelcomeModal} />
|
| 225 |
{/if}
|
| 226 |
|
|
@@ -250,9 +251,11 @@
|
|
| 250 |
{#if canShare}
|
| 251 |
<button
|
| 252 |
type="button"
|
| 253 |
-
class="hidden size-8 items-center justify-center gap-2 rounded-xl border border-gray-200 bg-white/90 text-sm font-medium text-gray-700 shadow-sm hover:bg-white/60 hover:text-gray-500 dark:border-gray-700 dark:bg-gray-800/80 dark:text-gray-200 dark:hover:bg-gray-700 md:absolute md:right-6 md:top-5 md:flex
|
|
|
|
| 254 |
onclick={() => shareModal.open()}
|
| 255 |
aria-label="Share conversation"
|
|
|
|
| 256 |
>
|
| 257 |
<IconShare />
|
| 258 |
</button>
|
|
@@ -285,7 +288,16 @@
|
|
| 285 |
|
| 286 |
{#if publicConfig.PUBLIC_PLAUSIBLE_SCRIPT_URL}
|
| 287 |
<script>
|
| 288 |
-
window.plausible=
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 289 |
plausible.init();
|
| 290 |
</script>
|
| 291 |
{/if}
|
|
|
|
| 20 |
import { setContext } from "svelte";
|
| 21 |
import { handleResponse, useAPIClient } from "$lib/APIClient";
|
| 22 |
import { isAborted } from "$lib/stores/isAborted";
|
| 23 |
+
import BackgroundGenerationPoller from "$lib/components/BackgroundGenerationPoller.svelte";
|
| 24 |
import IconShare from "$lib/components/icons/IconShare.svelte";
|
| 25 |
import { shareModal } from "$lib/stores/shareModal";
|
| 26 |
+
import { loading } from "$lib/stores/loading";
|
| 27 |
|
| 28 |
let { data = $bindable(), children } = $props();
|
| 29 |
|
|
|
|
| 99 |
function closeWelcomeModal() {
|
| 100 |
settings.set({ welcomeModalSeen: true });
|
| 101 |
|
| 102 |
+
if (page.data.loginRequired && !data.user) {
|
| 103 |
goto(`${base}/login`, { invalidateAll: true });
|
| 104 |
}
|
| 105 |
}
|
|
|
|
| 178 |
);
|
| 179 |
|
| 180 |
// Show the welcome modal once on first app load
|
| 181 |
+
let showWelcome = $derived(!page.data.shared && !$settings.welcomeModalSeen);
|
| 182 |
</script>
|
| 183 |
|
| 184 |
<svelte:head>
|
|
|
|
| 221 |
{/if}
|
| 222 |
</svelte:head>
|
| 223 |
|
| 224 |
+
{#if showWelcome}
|
| 225 |
<WelcomeModal close={closeWelcomeModal} />
|
| 226 |
{/if}
|
| 227 |
|
|
|
|
| 251 |
{#if canShare}
|
| 252 |
<button
|
| 253 |
type="button"
|
| 254 |
+
class="hidden size-8 items-center justify-center gap-2 rounded-xl border border-gray-200 bg-white/90 text-sm font-medium text-gray-700 shadow-sm hover:bg-white/60 hover:text-gray-500 dark:border-gray-700 dark:bg-gray-800/80 dark:text-gray-200 dark:hover:bg-gray-700 md:absolute md:right-6 md:top-5 md:flex
|
| 255 |
+
{$loading ? 'cursor-not-allowed opacity-50' : ''}"
|
| 256 |
onclick={() => shareModal.open()}
|
| 257 |
aria-label="Share conversation"
|
| 258 |
+
disabled={$loading}
|
| 259 |
>
|
| 260 |
<IconShare />
|
| 261 |
</button>
|
|
|
|
| 288 |
|
| 289 |
{#if publicConfig.PUBLIC_PLAUSIBLE_SCRIPT_URL}
|
| 290 |
<script>
|
| 291 |
+
(window.plausible =
|
| 292 |
+
window.plausible ||
|
| 293 |
+
function () {
|
| 294 |
+
(plausible.q = plausible.q || []).push(arguments);
|
| 295 |
+
}),
|
| 296 |
+
(plausible.init =
|
| 297 |
+
plausible.init ||
|
| 298 |
+
function (i) {
|
| 299 |
+
plausible.o = i || {};
|
| 300 |
+
});
|
| 301 |
plausible.init();
|
| 302 |
</script>
|
| 303 |
{/if}
|
src/routes/+page.svelte
CHANGED
|
@@ -7,17 +7,18 @@
|
|
| 7 |
const publicConfig = usePublicConfig();
|
| 8 |
|
| 9 |
import ChatWindow from "$lib/components/chat/ChatWindow.svelte";
|
| 10 |
-
|
| 11 |
-
import {
|
| 12 |
-
import {
|
| 13 |
-
import {
|
| 14 |
-
import {
|
| 15 |
-
import {
|
|
|
|
|
|
|
| 16 |
|
| 17 |
let { data } = $props();
|
| 18 |
|
| 19 |
let hasModels = $derived(Boolean(data.models?.length));
|
| 20 |
-
let loading = $state(false);
|
| 21 |
let files: File[] = $state([]);
|
| 22 |
let draft = $state("");
|
| 23 |
|
|
@@ -25,7 +26,7 @@ import { onMount } from "svelte";
|
|
| 25 |
|
| 26 |
async function createConversation(message: string) {
|
| 27 |
try {
|
| 28 |
-
loading = true;
|
| 29 |
|
| 30 |
// check if $settings.activeModel is a valid model
|
| 31 |
// else check if it's an assistant, and use that model
|
|
@@ -71,7 +72,7 @@ import { onMount } from "svelte";
|
|
| 71 |
error.set((err as Error).message || ERROR_MESSAGES.default);
|
| 72 |
console.error(err);
|
| 73 |
} finally {
|
| 74 |
-
loading = false;
|
| 75 |
}
|
| 76 |
}
|
| 77 |
|
|
@@ -108,7 +109,7 @@ import { onMount } from "svelte";
|
|
| 108 |
{#if hasModels}
|
| 109 |
<ChatWindow
|
| 110 |
onmessage={(message) => createConversation(message)}
|
| 111 |
-
{loading}
|
| 112 |
{currentModel}
|
| 113 |
models={data.models}
|
| 114 |
bind:files
|
|
|
|
| 7 |
const publicConfig = usePublicConfig();
|
| 8 |
|
| 9 |
import ChatWindow from "$lib/components/chat/ChatWindow.svelte";
|
| 10 |
+
|
| 11 |
+
import { ERROR_MESSAGES, error } from "$lib/stores/errors";
|
| 12 |
+
import { pendingMessage } from "$lib/stores/pendingMessage";
|
| 13 |
+
import { useSettingsStore } from "$lib/stores/settings.js";
|
| 14 |
+
import { findCurrentModel } from "$lib/utils/models";
|
| 15 |
+
import { sanitizeUrlParam } from "$lib/utils/urlParams";
|
| 16 |
+
import { onMount } from "svelte";
|
| 17 |
+
import { loading } from "$lib/stores/loading.js";
|
| 18 |
|
| 19 |
let { data } = $props();
|
| 20 |
|
| 21 |
let hasModels = $derived(Boolean(data.models?.length));
|
|
|
|
| 22 |
let files: File[] = $state([]);
|
| 23 |
let draft = $state("");
|
| 24 |
|
|
|
|
| 26 |
|
| 27 |
async function createConversation(message: string) {
|
| 28 |
try {
|
| 29 |
+
$loading = true;
|
| 30 |
|
| 31 |
// check if $settings.activeModel is a valid model
|
| 32 |
// else check if it's an assistant, and use that model
|
|
|
|
| 72 |
error.set((err as Error).message || ERROR_MESSAGES.default);
|
| 73 |
console.error(err);
|
| 74 |
} finally {
|
| 75 |
+
$loading = false;
|
| 76 |
}
|
| 77 |
}
|
| 78 |
|
|
|
|
| 109 |
{#if hasModels}
|
| 110 |
<ChatWindow
|
| 111 |
onmessage={(message) => createConversation(message)}
|
| 112 |
+
loading={$loading}
|
| 113 |
{currentModel}
|
| 114 |
models={data.models}
|
| 115 |
bind:files
|
src/routes/conversation/[id]/+page.svelte
CHANGED
|
@@ -26,10 +26,10 @@
|
|
| 26 |
import "katex/dist/katex.min.css";
|
| 27 |
import { updateDebouncer } from "$lib/utils/updates.js";
|
| 28 |
import SubscribeModal from "$lib/components/SubscribeModal.svelte";
|
|
|
|
| 29 |
|
| 30 |
let { data = $bindable() } = $props();
|
| 31 |
|
| 32 |
-
let loading = $state(false);
|
| 33 |
let pending = $state(false);
|
| 34 |
let initialRun = true;
|
| 35 |
let showSubscribeModal = $state(false);
|
|
@@ -96,7 +96,7 @@
|
|
| 96 |
|
| 97 |
async function convFromShared() {
|
| 98 |
try {
|
| 99 |
-
loading = true;
|
| 100 |
const res = await fetch(`${base}/conversation`, {
|
| 101 |
method: "POST",
|
| 102 |
headers: {
|
|
@@ -135,7 +135,7 @@
|
|
| 135 |
}): Promise<void> {
|
| 136 |
try {
|
| 137 |
$isAborted = false;
|
| 138 |
-
loading = true;
|
| 139 |
pending = true;
|
| 140 |
const base64Files = await Promise.all(
|
| 141 |
(files ?? []).map((file) =>
|
|
@@ -335,7 +335,7 @@
|
|
| 335 |
}
|
| 336 |
console.error(err);
|
| 337 |
} finally {
|
| 338 |
-
loading = false;
|
| 339 |
pending = false;
|
| 340 |
await invalidateAll();
|
| 341 |
}
|
|
@@ -348,11 +348,11 @@
|
|
| 348 |
if (r.ok) {
|
| 349 |
setTimeout(() => {
|
| 350 |
$isAborted = true;
|
| 351 |
-
loading = false;
|
| 352 |
}, 500);
|
| 353 |
} else {
|
| 354 |
$isAborted = true;
|
| 355 |
-
loading = false;
|
| 356 |
}
|
| 357 |
});
|
| 358 |
}
|
|
@@ -375,7 +375,7 @@
|
|
| 375 |
const streaming = isConversationStreaming(messages);
|
| 376 |
if (streaming) {
|
| 377 |
addBackgroundGeneration({ id: page.params.id, startedAt: Date.now() });
|
| 378 |
-
loading = true;
|
| 379 |
}
|
| 380 |
});
|
| 381 |
|
|
@@ -388,7 +388,7 @@
|
|
| 388 |
await goto(`${base}/conversation/${convId}`, { invalidateAll: true });
|
| 389 |
})
|
| 390 |
.then(async () => await writeMessage({ prompt: content }))
|
| 391 |
-
.finally(() => (loading = false));
|
| 392 |
}
|
| 393 |
}
|
| 394 |
|
|
@@ -415,7 +415,7 @@
|
|
| 415 |
isRetry: true,
|
| 416 |
})
|
| 417 |
)
|
| 418 |
-
.finally(() => (loading = false));
|
| 419 |
}
|
| 420 |
}
|
| 421 |
|
|
@@ -447,9 +447,9 @@
|
|
| 447 |
$effect(() => {
|
| 448 |
const streaming = isConversationStreaming(messages);
|
| 449 |
if (streaming) {
|
| 450 |
-
loading = true;
|
| 451 |
} else if (!pending) {
|
| 452 |
-
loading = false;
|
| 453 |
}
|
| 454 |
|
| 455 |
if (!streaming && browser) {
|
|
@@ -478,7 +478,7 @@
|
|
| 478 |
}
|
| 479 |
|
| 480 |
$isAborted = true;
|
| 481 |
-
loading = false;
|
| 482 |
});
|
| 483 |
|
| 484 |
let title = $derived.by(() => {
|
|
@@ -494,7 +494,7 @@
|
|
| 494 |
</svelte:head>
|
| 495 |
|
| 496 |
<ChatWindow
|
| 497 |
-
{loading}
|
| 498 |
{pending}
|
| 499 |
messages={messagesPath as Message[]}
|
| 500 |
{messagesAlternatives}
|
|
|
|
| 26 |
import "katex/dist/katex.min.css";
|
| 27 |
import { updateDebouncer } from "$lib/utils/updates.js";
|
| 28 |
import SubscribeModal from "$lib/components/SubscribeModal.svelte";
|
| 29 |
+
import { loading } from "$lib/stores/loading.js";
|
| 30 |
|
| 31 |
let { data = $bindable() } = $props();
|
| 32 |
|
|
|
|
| 33 |
let pending = $state(false);
|
| 34 |
let initialRun = true;
|
| 35 |
let showSubscribeModal = $state(false);
|
|
|
|
| 96 |
|
| 97 |
async function convFromShared() {
|
| 98 |
try {
|
| 99 |
+
$loading = true;
|
| 100 |
const res = await fetch(`${base}/conversation`, {
|
| 101 |
method: "POST",
|
| 102 |
headers: {
|
|
|
|
| 135 |
}): Promise<void> {
|
| 136 |
try {
|
| 137 |
$isAborted = false;
|
| 138 |
+
$loading = true;
|
| 139 |
pending = true;
|
| 140 |
const base64Files = await Promise.all(
|
| 141 |
(files ?? []).map((file) =>
|
|
|
|
| 335 |
}
|
| 336 |
console.error(err);
|
| 337 |
} finally {
|
| 338 |
+
$loading = false;
|
| 339 |
pending = false;
|
| 340 |
await invalidateAll();
|
| 341 |
}
|
|
|
|
| 348 |
if (r.ok) {
|
| 349 |
setTimeout(() => {
|
| 350 |
$isAborted = true;
|
| 351 |
+
$loading = false;
|
| 352 |
}, 500);
|
| 353 |
} else {
|
| 354 |
$isAborted = true;
|
| 355 |
+
$loading = false;
|
| 356 |
}
|
| 357 |
});
|
| 358 |
}
|
|
|
|
| 375 |
const streaming = isConversationStreaming(messages);
|
| 376 |
if (streaming) {
|
| 377 |
addBackgroundGeneration({ id: page.params.id, startedAt: Date.now() });
|
| 378 |
+
$loading = true;
|
| 379 |
}
|
| 380 |
});
|
| 381 |
|
|
|
|
| 388 |
await goto(`${base}/conversation/${convId}`, { invalidateAll: true });
|
| 389 |
})
|
| 390 |
.then(async () => await writeMessage({ prompt: content }))
|
| 391 |
+
.finally(() => ($loading = false));
|
| 392 |
}
|
| 393 |
}
|
| 394 |
|
|
|
|
| 415 |
isRetry: true,
|
| 416 |
})
|
| 417 |
)
|
| 418 |
+
.finally(() => ($loading = false));
|
| 419 |
}
|
| 420 |
}
|
| 421 |
|
|
|
|
| 447 |
$effect(() => {
|
| 448 |
const streaming = isConversationStreaming(messages);
|
| 449 |
if (streaming) {
|
| 450 |
+
$loading = true;
|
| 451 |
} else if (!pending) {
|
| 452 |
+
$loading = false;
|
| 453 |
}
|
| 454 |
|
| 455 |
if (!streaming && browser) {
|
|
|
|
| 478 |
}
|
| 479 |
|
| 480 |
$isAborted = true;
|
| 481 |
+
$loading = false;
|
| 482 |
});
|
| 483 |
|
| 484 |
let title = $derived.by(() => {
|
|
|
|
| 494 |
</svelte:head>
|
| 495 |
|
| 496 |
<ChatWindow
|
| 497 |
+
loading={$loading}
|
| 498 |
{pending}
|
| 499 |
messages={messagesPath as Message[]}
|
| 500 |
{messagesAlternatives}
|