krramos gloodan17 Eduardo Rocha Eduardo Rocha nsarrazin HF Staff commited on
Commit
e58f3c4
·
unverified ·
1 Parent(s): cc0d6c5

Maintain Conversation Tree State on Refresh or Share (#1397)

Browse files

* Saving and reloading the current tree on page refresh

* Saving and reloading the current tree on page refresh / Moving to the last branch when editing a user message or retrying an assistant message.

* Update Reload the current tree state on page refresh

* updated updateCurrentIndex function

* Update updateCurrentIndex Function

* Persist current tree in navigation/shared conversations v1

Co-authored-by: Kenneth Ramos <[email protected]>
Co-authored-by: Eduardo Rocha <[email protected]>

* Removed uncessary comment.

Co-authored-by: Long Dao <[email protected]>
Co-authored-by: Eduardo Rocha <[email protected]>

* Added enhance import and document focus fix.

Co-authored-by: Long Dao <[email protected]>
Co-authored-by: Eduardo Rocha <[email protected]>

* Fixed code to have updated formatting and LoadEvent type in page.ts

Co-authored-by: Long Dao <[email protected]>
Co-authored-by: Eduardo Rocha <[email protected]>

* run formatting

* simplify pr

---------

Co-authored-by: Long Dao <[email protected]>
Co-authored-by: Eduardo Rocha <[email protected]>
Co-authored-by: Eduardo Rocha <[email protected]>
Co-authored-by: Nathan Sarrazin <[email protected]>

src/lib/components/chat/ChatMessage.svelte CHANGED
@@ -34,6 +34,7 @@
34
  import { useSettingsStore } from "$lib/stores/settings";
35
  import DOMPurify from "isomorphic-dompurify";
36
  import { enhance } from "$app/forms";
 
37
 
38
  function sanitizeMd(md: string) {
39
  let ret = md
@@ -207,6 +208,22 @@
207
  }
208
  const convTreeStore = useConvTreeStore();
209
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
210
  $: if (message.children?.length === 0) $convTreeStore.leaf = message.id;
211
  </script>
212
 
@@ -300,9 +317,9 @@
300
  {#if !loading && (message.content || toolUpdates)}
301
  <div
302
  class="absolute -bottom-4 right-0 flex max-md:transition-all md:group-hover:visible md:group-hover:opacity-100
303
- {message.score ? 'visible opacity-100' : 'invisible max-md:-translate-y-4 max-md:opacity-0'}
304
- {isTapped || isCopied ? 'max-md:visible max-md:translate-y-0 max-md:opacity-100' : ''}
305
- "
306
  >
307
  {#if isAuthor}
308
  <button
@@ -334,7 +351,9 @@
334
  class="btn rounded-sm p-1 text-sm text-gray-400 hover:text-gray-500 focus:ring-0 dark:text-gray-400 dark:hover:text-gray-300"
335
  title="Retry"
336
  type="button"
337
- on:click={() => dispatch("retry", { id: message.id })}
 
 
338
  >
339
  <CarbonRotate360 />
340
  </button>
@@ -394,7 +413,7 @@
394
  <button
395
  type="submit"
396
  class="btn rounded-lg px-3 py-1.5 text-sm
397
- {loading
398
  ? 'bg-gray-300 text-gray-400 dark:bg-gray-700 dark:text-gray-600'
399
  : 'bg-gray-200 text-gray-600 hover:text-gray-800 focus:ring-0 dark:bg-gray-800 dark:text-gray-300 dark:hover:text-gray-200'}
400
  "
@@ -417,8 +436,8 @@
417
  {#if !loading && !editMode}
418
  <div
419
  class="
420
- max-md:opacity-0' invisible absolute
421
- right-0 top-3.5 z-10 h-max max-md:-translate-y-4 max-md:transition-all md:bottom-0 md:group-hover:visible md:group-hover:opacity-100 {isTapped ||
422
  isCopied
423
  ? 'max-md:visible max-md:translate-y-0 max-md:opacity-100'
424
  : ''}"
 
34
  import { useSettingsStore } from "$lib/stores/settings";
35
  import DOMPurify from "isomorphic-dompurify";
36
  import { enhance } from "$app/forms";
37
+ import { browser } from "$app/environment";
38
 
39
  function sanitizeMd(md: string) {
40
  let ret = md
 
208
  }
209
  const convTreeStore = useConvTreeStore();
210
 
211
+ $: if (message.children?.length === 0) {
212
+ $convTreeStore.leaf = message.id;
213
+ // Check if the code is running in a browser
214
+ if (browser) {
215
+ // Remember the last message viewed or interacted by the user
216
+ localStorage.setItem("leafId", message.id);
217
+ }
218
+ }
219
+
220
+ let isRun = false;
221
+ $: {
222
+ if (message.id && !isRun) {
223
+ if (message.currentChildIndex) childrenToRender = message.currentChildIndex;
224
+ isRun = true;
225
+ }
226
+ }
227
  $: if (message.children?.length === 0) $convTreeStore.leaf = message.id;
228
  </script>
229
 
 
317
  {#if !loading && (message.content || toolUpdates)}
318
  <div
319
  class="absolute -bottom-4 right-0 flex max-md:transition-all md:group-hover:visible md:group-hover:opacity-100
320
+ {message.score ? 'visible opacity-100' : 'invisible max-md:-translate-y-4 max-md:opacity-0'}
321
+ {isTapped || isCopied ? 'max-md:visible max-md:translate-y-0 max-md:opacity-100' : ''}
322
+ "
323
  >
324
  {#if isAuthor}
325
  <button
 
351
  class="btn rounded-sm p-1 text-sm text-gray-400 hover:text-gray-500 focus:ring-0 dark:text-gray-400 dark:hover:text-gray-300"
352
  title="Retry"
353
  type="button"
354
+ on:click={() => {
355
+ dispatch("retry", { id: message.id });
356
+ }}
357
  >
358
  <CarbonRotate360 />
359
  </button>
 
413
  <button
414
  type="submit"
415
  class="btn rounded-lg px-3 py-1.5 text-sm
416
+ {loading
417
  ? 'bg-gray-300 text-gray-400 dark:bg-gray-700 dark:text-gray-600'
418
  : 'bg-gray-200 text-gray-600 hover:text-gray-800 focus:ring-0 dark:bg-gray-800 dark:text-gray-300 dark:hover:text-gray-200'}
419
  "
 
436
  {#if !loading && !editMode}
437
  <div
438
  class="
439
+ max-md:opacity-0' invisible absolute
440
+ right-0 top-3.5 z-10 h-max max-md:-translate-y-4 max-md:transition-all md:bottom-0 md:group-hover:visible md:group-hover:opacity-100 {isTapped ||
441
  isCopied
442
  ? 'max-md:visible max-md:translate-y-0 max-md:opacity-100'
443
  : ''}"
src/lib/components/chat/ChatWindow.svelte CHANGED
@@ -108,6 +108,55 @@
108
 
109
  const convTreeStore = useConvTreeStore();
110
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
111
  $: lastMessage = browser && (messages.find((m) => m.id == $convTreeStore.leaf) as Message);
112
  $: lastIsError =
113
  lastMessage &&
@@ -344,7 +393,7 @@
344
  aria-label={isFileUploadEnabled ? "file dropzone" : undefined}
345
  on:submit|preventDefault={handleSubmit}
346
  class="relative flex w-full max-w-4xl flex-1 items-center rounded-xl border bg-gray-100 focus-within:border-gray-300 dark:border-gray-600 dark:bg-gray-700 dark:focus-within:border-gray-500
347
- {isReadOnly ? 'opacity-30' : ''}"
348
  >
349
  {#if onDrag && isFileUploadEnabled}
350
  <FileDropzone bind:files bind:onDrag mimeTypes={activeMimeTypes} />
 
108
 
109
  const convTreeStore = useConvTreeStore();
110
 
111
+ const updateCurrentIndex = () => {
112
+ const url = new URL($page.url);
113
+ let leafId = url.searchParams.get("leafId");
114
+
115
+ // Ensure the function is only run in the browser.
116
+ if (!browser) return;
117
+
118
+ if (leafId) {
119
+ // Remove the 'leafId' from the URL to clean up after retrieving it.
120
+ url.searchParams.delete("leafId");
121
+ history.replaceState(null, "", url.toString());
122
+ } else {
123
+ // Retrieve the 'leafId' from localStorage if it's not in the URL.
124
+ leafId = localStorage.getItem("leafId");
125
+ }
126
+
127
+ // If a 'leafId' exists, find the corresponding message and update indices.
128
+ if (leafId) {
129
+ let leafMessage = messages.find((m) => m.id == leafId);
130
+ if (!leafMessage?.ancestors) return; // Exit if the message has no ancestors.
131
+
132
+ let ancestors = leafMessage.ancestors;
133
+
134
+ // Loop through all ancestors to update the current child index.
135
+ for (let i = 0; i < ancestors.length; i++) {
136
+ let curMessage = messages.find((m) => m.id == ancestors[i]);
137
+ if (curMessage?.children) {
138
+ for (let j = 0; j < curMessage.children.length; j++) {
139
+ // Check if the current message's child matches the next ancestor
140
+ // or the leaf itself, and update the currentChildIndex accordingly.
141
+ if (i + 1 < ancestors.length) {
142
+ if (curMessage.children[j] == ancestors[i + 1]) {
143
+ curMessage.currentChildIndex = j;
144
+ break;
145
+ }
146
+ } else {
147
+ if (curMessage.children[j] == leafId) {
148
+ curMessage.currentChildIndex = j;
149
+ break;
150
+ }
151
+ }
152
+ }
153
+ }
154
+ }
155
+ }
156
+ };
157
+
158
+ updateCurrentIndex();
159
+
160
  $: lastMessage = browser && (messages.find((m) => m.id == $convTreeStore.leaf) as Message);
161
  $: lastIsError =
162
  lastMessage &&
 
393
  aria-label={isFileUploadEnabled ? "file dropzone" : undefined}
394
  on:submit|preventDefault={handleSubmit}
395
  class="relative flex w-full max-w-4xl flex-1 items-center rounded-xl border bg-gray-100 focus-within:border-gray-300 dark:border-gray-600 dark:bg-gray-700 dark:focus-within:border-gray-500
396
+ {isReadOnly ? 'opacity-30' : ''}"
397
  >
398
  {#if onDrag && isFileUploadEnabled}
399
  <FileDropzone bind:files bind:onDrag mimeTypes={activeMimeTypes} />
src/lib/types/Message.ts CHANGED
@@ -23,6 +23,9 @@ export type Message = Partial<Timestamps> & {
23
 
24
  // goes one level deep
25
  children?: Message["id"][];
 
 
 
26
  };
27
 
28
  export type MessageFile = {
 
23
 
24
  // goes one level deep
25
  children?: Message["id"][];
26
+
27
+ // the index of the current child in the children array of the message if the message has more than one child
28
+ currentChildIndex?: number;
29
  };
30
 
31
  export type MessageFile = {
src/lib/utils/share.ts CHANGED
@@ -1,7 +1,28 @@
 
 
1
  export async function share(url: string, title: string) {
 
 
 
 
 
 
 
 
 
 
2
  if (navigator.share) {
3
  navigator.share({ url, title });
4
  } else {
5
- await navigator.clipboard.writeText(url);
 
 
 
 
 
 
 
 
 
6
  }
7
  }
 
1
+ import { browser } from "$app/environment";
2
+
3
  export async function share(url: string, title: string) {
4
+ if (!browser) return;
5
+ // Retrieve the leafId from localStorage
6
+ const leafId = localStorage.getItem("leafId");
7
+ if (leafId) {
8
+ // Use URL and URLSearchParams to add the leafId parameter
9
+ const shareUrl = new URL(url);
10
+ shareUrl.searchParams.append("leafId", leafId);
11
+ url = shareUrl.toString();
12
+ }
13
+
14
  if (navigator.share) {
15
  navigator.share({ url, title });
16
  } else {
17
+ alert("Please focus the document within 3 seconds by clicking somewhere or pressing Tab.");
18
+ // Document Focus Error Handling
19
+ setTimeout(async () => {
20
+ if (document.hasFocus()) {
21
+ await navigator.clipboard.writeText(url);
22
+ } else {
23
+ console.error("Document is not focused. Unable to write to clipboard.");
24
+ alert("Document is not focused. Please try again.");
25
+ }
26
+ }, 3000); // 3-second delay to allow focusing
27
  }
28
  }
src/routes/r/[id]/+page.ts CHANGED
@@ -1,5 +1,7 @@
1
- import { redirect } from "@sveltejs/kit";
2
 
3
- export const load = async ({ params }) => {
4
- redirect(302, "../conversation/" + params.id);
 
 
5
  };
 
1
+ import { redirect, type LoadEvent } from "@sveltejs/kit";
2
 
3
+ export const load = async ({ params, url }: LoadEvent) => {
4
+ const leafId = url.searchParams.get("leafId");
5
+
6
+ throw redirect(302, "../conversation/" + params.id + `?leafId=${leafId}`);
7
  };