kramp HF Staff victor HF Staff commited on
Commit
bdd27fe
·
unverified ·
1 Parent(s): 32b18be

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 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: "true"
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
- isHuggingChat && Boolean(page.params?.id) && page.route.id?.startsWith("/conversation/")
 
 
 
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 BackgroundGenerationPoller from "$lib/components/BackgroundGenerationPoller.svelte";
26
 
27
  let { data = $bindable(), children } = $props();
28
 
@@ -98,7 +99,7 @@
98
  function closeWelcomeModal() {
99
  settings.set({ welcomeModalSeen: true });
100
 
101
- if (!data.user && data.loginEnabled) {
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(!$settings.welcomeModalSeen && !(page.data.shared === true));
181
  </script>
182
 
183
  <svelte:head>
@@ -220,7 +221,7 @@
220
  {/if}
221
  </svelte:head>
222
 
223
- {#if showWelcome || (!data.user && data.loginEnabled)}
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=window.plausible||function(){(plausible.q=plausible.q||[]).push(arguments)},plausible.init=plausible.init||function(i){plausible.o=i||{}};
 
 
 
 
 
 
 
 
 
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
- import { ERROR_MESSAGES, error } from "$lib/stores/errors";
11
- import { pendingMessage } from "$lib/stores/pendingMessage";
12
- import { useSettingsStore } from "$lib/stores/settings.js";
13
- import { findCurrentModel } from "$lib/utils/models";
14
- import { sanitizeUrlParam } from "$lib/utils/urlParams";
15
- import { onMount } from "svelte";
 
 
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}