Spaces:
Running
on
CPU Upgrade
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]>
@@ -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 |
-
|
304 |
-
|
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={() =>
|
|
|
|
|
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 |
-
|
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 |
-
|
421 |
-
|
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 |
: ''}"
|
@@ -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 |
-
|
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} />
|
@@ -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 = {
|
@@ -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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
}
|
@@ -1,5 +1,7 @@
|
|
1 |
-
import { redirect } from "@sveltejs/kit";
|
2 |
|
3 |
-
export const load = async ({ params }) => {
|
4 |
-
|
|
|
|
|
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 |
};
|