nsarrazin HF staff victor HF staff commited on
Commit
315e56b
β€’
1 Parent(s): 8e839b8

Make assistant settings form fill the modal (#900)

Browse files

* Make assistant settings form fill the modal

* tweaks

* Use layout groups instead of parsing the url

* fix console error & overflows

* scroll on dialog instead

---------

Co-authored-by: Victor Mustar <victor.mustar@gmail.com>

src/lib/components/AssistantSettings.svelte CHANGED
@@ -8,8 +8,8 @@
8
  import { base } from "$app/paths";
9
  import CarbonPen from "~icons/carbon/pen";
10
  import CarbonUpload from "~icons/carbon/upload";
 
11
  import { useSettingsStore } from "$lib/stores/settings";
12
- import IconLoading from "./icons/IconLoading.svelte";
13
 
14
  type ActionData = {
15
  error: boolean;
@@ -75,7 +75,7 @@
75
 
76
  <form
77
  method="POST"
78
- class="flex h-full flex-col"
79
  enctype="multipart/form-data"
80
  use:enhance={async ({ formData }) => {
81
  loading = true;
@@ -110,7 +110,9 @@
110
  }}
111
  >
112
  {#if assistant}
113
- <h2 class="text-xl font-semibold">Edit assistant ({assistant?.name ?? ""})</h2>
 
 
114
  <p class="mb-6 text-sm text-gray-500">
115
  Modifying an existing assistant will propagate those changes to all users.
116
  </p>
@@ -123,10 +125,10 @@
123
  </p>
124
  {/if}
125
 
126
- <div class="mx-1 grid flex-1 grid-cols-2 gap-4 max-sm:grid-cols-1">
127
  <div class="flex flex-col gap-4">
128
  <div>
129
- <span class="mb-1 block pb-2 text-sm font-semibold">Avatar</span>
130
  <input
131
  type="file"
132
  accept="image/*"
@@ -185,7 +187,7 @@
185
  </div>
186
 
187
  <label>
188
- <span class="mb-1 text-sm font-semibold">Name</span>
189
  <input
190
  name="name"
191
  class="w-full rounded-lg border-2 border-gray-200 bg-gray-100 p-2"
@@ -196,10 +198,10 @@
196
  </label>
197
 
198
  <label>
199
- <span class="mb-1 text-sm font-semibold">Description</span>
200
  <textarea
201
  name="description"
202
- class="w-full rounded-lg border-2 border-gray-200 bg-gray-100 p-2"
203
  placeholder="He knows everything about python"
204
  value={assistant?.description ?? ""}
205
  />
@@ -207,7 +209,7 @@
207
  </label>
208
 
209
  <label>
210
- <span class="mb-1 text-sm font-semibold">Model</span>
211
  <select name="modelId" class="w-full rounded-lg border-2 border-gray-200 bg-gray-100 p-2">
212
  {#each models.filter((model) => !model.unlisted) as model}
213
  <option
@@ -222,8 +224,8 @@
222
  </label>
223
 
224
  <label>
225
- <span class="mb-1 text-sm font-semibold">User start messages</span>
226
- <div class="flex flex-col gap-2 md:max-h-32">
227
  <input
228
  name="exampleInput1"
229
  bind:value={inputMessage1}
@@ -256,7 +258,7 @@
256
  </div>
257
 
258
  <label class="flex flex-col">
259
- <span class="mb-1 text-sm font-semibold"> Instructions (system prompt) </span>
260
  <textarea
261
  name="preprompt"
262
  class="min-h-[8lh] flex-1 rounded-lg border-2 border-gray-200 bg-gray-100 p-2 text-sm"
@@ -267,24 +269,23 @@
267
  </label>
268
  </div>
269
 
270
- <div class="mt-5 flex justify-end gap-2">
271
  <a
272
  href={assistant ? `${base}/settings/assistants/${assistant?._id}` : `${base}/settings`}
273
- class="rounded-full bg-gray-200 px-8 py-2 font-semibold text-gray-600">Cancel</a
274
  >
 
 
275
  <button
276
  type="submit"
277
  disabled={loading}
278
  aria-disabled={loading}
279
- class="rounded-full bg-black px-8 py-2 font-semibold md:px-20"
280
  class:bg-gray-200={loading}
281
  class:text-gray-600={loading}
282
  class:text-white={!loading}
283
  >
284
  {assistant ? "Save" : "Create"}
285
- {#if loading}
286
- <IconLoading classNames="ml-2 h-min" />
287
- {/if}
288
  </button>
289
  </div>
290
  </form>
 
8
  import { base } from "$app/paths";
9
  import CarbonPen from "~icons/carbon/pen";
10
  import CarbonUpload from "~icons/carbon/upload";
11
+
12
  import { useSettingsStore } from "$lib/stores/settings";
 
13
 
14
  type ActionData = {
15
  error: boolean;
 
75
 
76
  <form
77
  method="POST"
78
+ class="flex h-full flex-col overflow-y-auto p-4 md:p-8"
79
  enctype="multipart/form-data"
80
  use:enhance={async ({ formData }) => {
81
  loading = true;
 
110
  }}
111
  >
112
  {#if assistant}
113
+ <h2 class="text-xl font-semibold">
114
+ Edit {assistant?.name ?? "assistant"}
115
+ </h2>
116
  <p class="mb-6 text-sm text-gray-500">
117
  Modifying an existing assistant will propagate those changes to all users.
118
  </p>
 
125
  </p>
126
  {/if}
127
 
128
+ <div class="grid h-full w-full flex-1 grid-cols-2 gap-6 text-sm max-sm:grid-cols-1">
129
  <div class="flex flex-col gap-4">
130
  <div>
131
+ <div class="mb-1 block pb-2 text-sm font-semibold">Avatar</div>
132
  <input
133
  type="file"
134
  accept="image/*"
 
187
  </div>
188
 
189
  <label>
190
+ <div class="mb-1 font-semibold">Name</div>
191
  <input
192
  name="name"
193
  class="w-full rounded-lg border-2 border-gray-200 bg-gray-100 p-2"
 
198
  </label>
199
 
200
  <label>
201
+ <div class="mb-1 font-semibold">Description</div>
202
  <textarea
203
  name="description"
204
+ class="h-15 w-full rounded-lg border-2 border-gray-200 bg-gray-100 p-2"
205
  placeholder="He knows everything about python"
206
  value={assistant?.description ?? ""}
207
  />
 
209
  </label>
210
 
211
  <label>
212
+ <div class="mb-1 font-semibold">Model</div>
213
  <select name="modelId" class="w-full rounded-lg border-2 border-gray-200 bg-gray-100 p-2">
214
  {#each models.filter((model) => !model.unlisted) as model}
215
  <option
 
224
  </label>
225
 
226
  <label>
227
+ <div class="mb-1 font-semibold">User start messages</div>
228
+ <div class="flex flex-col gap-2">
229
  <input
230
  name="exampleInput1"
231
  bind:value={inputMessage1}
 
258
  </div>
259
 
260
  <label class="flex flex-col">
261
+ <div class="mb-1 text-sm font-semibold">Instructions (system prompt)</div>
262
  <textarea
263
  name="preprompt"
264
  class="min-h-[8lh] flex-1 rounded-lg border-2 border-gray-200 bg-gray-100 p-2 text-sm"
 
269
  </label>
270
  </div>
271
 
272
+ <div class="mt-6 flex justify-end gap-2">
273
  <a
274
  href={assistant ? `${base}/settings/assistants/${assistant?._id}` : `${base}/settings`}
275
+ class="flex items-center justify-center rounded-full bg-gray-200 px-5 py-2 font-semibold text-gray-600"
276
  >
277
+ Cancel
278
+ </a>
279
  <button
280
  type="submit"
281
  disabled={loading}
282
  aria-disabled={loading}
283
+ class="flex items-center justify-center rounded-full bg-black px-8 py-2 font-semibold"
284
  class:bg-gray-200={loading}
285
  class:text-gray-600={loading}
286
  class:text-white={!loading}
287
  >
288
  {assistant ? "Save" : "Create"}
 
 
 
289
  </button>
290
  </div>
291
  </form>
src/routes/settings/(nav)/+layout.svelte ADDED
@@ -0,0 +1,135 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <script lang="ts">
2
+ import { onMount } from "svelte";
3
+ import { base } from "$app/paths";
4
+ import { afterNavigate, goto } from "$app/navigation";
5
+ import { page } from "$app/stores";
6
+ import { useSettingsStore } from "$lib/stores/settings";
7
+ import CarbonClose from "~icons/carbon/close";
8
+ import CarbonArrowUpRight from "~icons/carbon/ArrowUpRight";
9
+ import CarbonAdd from "~icons/carbon/add";
10
+
11
+ import UserIcon from "~icons/carbon/user";
12
+ import type { LayoutData } from "../$types";
13
+
14
+ export let data: LayoutData;
15
+
16
+ let previousPage: string = base;
17
+ let assistantsSection: HTMLHeadingElement;
18
+
19
+ onMount(() => {
20
+ if ($page.params?.assistantId) {
21
+ assistantsSection.scrollIntoView();
22
+ }
23
+ });
24
+
25
+ afterNavigate(({ from }) => {
26
+ if (!from?.url.pathname.includes("settings")) {
27
+ previousPage = from?.url.toString() || previousPage;
28
+ }
29
+ });
30
+
31
+ const settings = useSettingsStore();
32
+ </script>
33
+
34
+ <div
35
+ class="grid h-full w-full grid-cols-1 grid-rows-[auto,1fr] content-start gap-x-8 overflow-hidden p-4 md:grid-cols-3 md:grid-rows-[auto,1fr] md:p-8"
36
+ >
37
+ <div class="col-span-1 mb-4 flex items-center justify-between md:col-span-3">
38
+ <h2 class="text-xl font-bold">Settings</h2>
39
+ <button
40
+ class="btn rounded-lg"
41
+ on:click={() => {
42
+ goto(previousPage);
43
+ }}
44
+ >
45
+ <CarbonClose class="text-xl text-gray-900 hover:text-black" />
46
+ </button>
47
+ </div>
48
+ <div
49
+ class="col-span-1 flex flex-col overflow-y-auto whitespace-nowrap max-md:-mx-4 max-md:h-[245px] max-md:border max-md:border-b-2 md:pr-6"
50
+ >
51
+ <h3 class="pb-3 pl-3 pt-2 text-[.8rem] text-gray-800 sm:pl-1">Models</h3>
52
+
53
+ {#each data.models.filter((el) => !el.unlisted) as model}
54
+ <a
55
+ href="{base}/settings/{model.id}"
56
+ class="group flex h-10 flex-none items-center gap-2 pl-3 pr-2 text-sm text-gray-500 hover:bg-gray-100 md:rounded-xl
57
+ {model.id === $page.params.model ? '!bg-gray-100 !text-gray-800' : ''}"
58
+ >
59
+ <div class="truncate">{model.displayName}</div>
60
+ {#if model.id === $settings.activeModel}
61
+ <div
62
+ class="ml-auto rounded-lg bg-black px-2 py-1.5 text-xs font-semibold leading-none text-white"
63
+ >
64
+ Active
65
+ </div>
66
+ {/if}
67
+ </a>
68
+ {/each}
69
+ <!-- if its huggingchat, the number of assistants owned by the user must be non-zero to show the UI -->
70
+ {#if data.enableAssistants}
71
+ <h3 bind:this={assistantsSection} class="pb-3 pl-3 pt-5 text-[.8rem] text-gray-800 sm:pl-1">
72
+ Assistants
73
+ </h3>
74
+ {#if !data.loginEnabled || (data.loginEnabled && !!data.user)}
75
+ <a
76
+ href="{base}/settings/assistants/new"
77
+ class="group flex h-10 flex-none items-center gap-2 pl-3 pr-2 text-sm text-gray-500 hover:bg-gray-100 md:rounded-xl
78
+ {$page.url.pathname === `${base}/settings/assistants/new` ? '!bg-gray-100 !text-gray-800' : ''}"
79
+ >
80
+ <CarbonAdd />
81
+ <div class="truncate">Create new assistant</div>
82
+ </a>
83
+ {/if}
84
+ {#each data.assistants as assistant}
85
+ <a
86
+ href="{base}/settings/assistants/{assistant._id.toString()}"
87
+ class="group flex h-10 flex-none items-center gap-2 pl-2 pr-2 text-sm text-gray-500 hover:bg-gray-100 md:rounded-xl
88
+ {assistant._id.toString() === $page.params.assistantId ? '!bg-gray-100 !text-gray-800' : ''}"
89
+ >
90
+ {#if assistant.avatar}
91
+ <img
92
+ src="{base}/settings/assistants/{assistant._id.toString()}/avatar.jpg?hash={assistant.avatar}"
93
+ alt="Avatar"
94
+ class="h-6 w-6 rounded-full object-cover"
95
+ />
96
+ {:else}
97
+ <div
98
+ class="flex size-6 items-center justify-center rounded-full bg-gray-300 font-bold uppercase text-gray-500"
99
+ >
100
+ {assistant.name[0]}
101
+ </div>
102
+ {/if}
103
+ <div class="truncate">{assistant.name}</div>
104
+ {#if assistant._id.toString() === $settings.activeModel}
105
+ <div
106
+ class="ml-auto rounded-lg bg-black px-2 py-1.5 text-xs font-semibold leading-none text-white"
107
+ >
108
+ Active
109
+ </div>
110
+ {/if}
111
+ </a>
112
+ {/each}
113
+ <a
114
+ href="{base}/assistants"
115
+ class="group flex h-10 flex-none items-center gap-2 pl-3 pr-2 text-sm text-gray-500 hover:bg-gray-100 md:rounded-xl"
116
+ ><CarbonArrowUpRight class="mr-1.5 shrink-0 text-xs " />
117
+ <div class="truncate">Browse Assistants</div>
118
+ </a>
119
+ {/if}
120
+
121
+ <a
122
+ href="{base}/settings"
123
+ class="group mt-auto flex h-10 flex-none items-center gap-2 pl-3 pr-2 text-sm text-gray-500 hover:bg-gray-100 max-md:order-first md:rounded-xl
124
+ {$page.url.pathname === `${base}/settings` ? '!bg-gray-100 !text-gray-800' : ''}"
125
+ >
126
+ <UserIcon class="text-sm" />
127
+ Application Settings
128
+ </a>
129
+ </div>
130
+ <div
131
+ class="col-span-1 w-full overflow-y-auto overflow-x-clip px-1 max-md:pt-4 md:col-span-2 md:row-span-2"
132
+ >
133
+ <slot />
134
+ </div>
135
+ </div>
src/routes/settings/{+page.svelte β†’ (nav)/+page.svelte} RENAMED
File without changes
src/routes/settings/{+server.ts β†’ (nav)/+server.ts} RENAMED
File without changes
src/routes/settings/{[...model] β†’ (nav)/[...model]}/+page.svelte RENAMED
File without changes
src/routes/settings/{[...model] β†’ (nav)/[...model]}/+page.ts RENAMED
@@ -4,7 +4,7 @@ import { redirect } from "@sveltejs/kit";
4
  export async function load({ parent, params }) {
5
  const data = await parent();
6
 
7
- const model = data.models.find(({ id }) => id === params.model);
8
 
9
  if (!model || model.unlisted) {
10
  throw redirect(302, `${base}/settings`);
 
4
  export async function load({ parent, params }) {
5
  const data = await parent();
6
 
7
+ const model = data.models.find((m: { id: string }) => m.id === params.model);
8
 
9
  if (!model || model.unlisted) {
10
  throw redirect(302, `${base}/settings`);
src/routes/settings/{assistants β†’ (nav)/assistants}/[assistantId]/+page.server.ts RENAMED
File without changes
src/routes/settings/{assistants β†’ (nav)/assistants}/[assistantId]/+page.svelte RENAMED
File without changes
src/routes/settings/{assistants β†’ (nav)/assistants}/[assistantId]/+page.ts RENAMED
File without changes
src/routes/settings/{assistants β†’ (nav)/assistants}/[assistantId]/ReportModal.svelte RENAMED
File without changes
src/routes/settings/{assistants β†’ (nav)/assistants}/[assistantId]/avatar.jpg/+server.ts RENAMED
File without changes
src/routes/settings/{assistants β†’ (nav)/assistants}/[assistantId]/edit/+page.server.ts RENAMED
File without changes
src/routes/settings/{assistants/[assistantId]/edit/+page.svelte β†’ (nav)/assistants/[assistantId]/edit/+page@settings.svelte} RENAMED
File without changes
src/routes/settings/{assistants β†’ (nav)/assistants}/new/+page.server.ts RENAMED
File without changes
src/routes/settings/{assistants/new/+page.svelte β†’ (nav)/assistants/new/+page@settings.svelte} RENAMED
File without changes
src/routes/settings/+layout.svelte CHANGED
@@ -1,27 +1,13 @@
1
  <script lang="ts">
2
- import { onMount } from "svelte";
3
  import { base } from "$app/paths";
4
  import { clickOutside } from "$lib/actions/clickOutside";
5
  import { afterNavigate, goto } from "$app/navigation";
6
- import { page } from "$app/stores";
7
  import { useSettingsStore } from "$lib/stores/settings";
8
- import CarbonClose from "~icons/carbon/close";
9
- import CarbonArrowUpRight from "~icons/carbon/ArrowUpRight";
10
  import CarbonCheckmark from "~icons/carbon/checkmark";
11
- import CarbonAdd from "~icons/carbon/add";
12
 
13
- import UserIcon from "~icons/carbon/user";
14
  import { fade, fly } from "svelte/transition";
15
- export let data;
16
 
17
  let previousPage: string = base;
18
- let assistantsSection: HTMLHeadingElement;
19
-
20
- onMount(() => {
21
- if ($page.params?.assistantId) {
22
- assistantsSection.scrollIntoView();
23
- }
24
- });
25
 
26
  afterNavigate(({ from }) => {
27
  if (!from?.url.pathname.includes("settings")) {
@@ -42,105 +28,9 @@
42
  use:clickOutside={() => {
43
  goto(previousPage);
44
  }}
45
- class="grid h-[95dvh] w-[90dvw] grid-cols-1 content-start gap-x-8 overflow-hidden rounded-2xl bg-white p-4 shadow-2xl outline-none sm:h-[80dvh] md:grid-cols-3 md:grid-rows-[auto,1fr] md:p-8 xl:w-[1200px] 2xl:h-[70dvh]"
46
  >
47
- <div class="col-span-1 mb-4 flex items-center justify-between md:col-span-3">
48
- <h2 class="text-xl font-bold">Settings</h2>
49
- <button
50
- class="btn rounded-lg"
51
- on:click={() => {
52
- goto(previousPage);
53
- }}
54
- >
55
- <CarbonClose class="text-xl text-gray-900 hover:text-black" />
56
- </button>
57
- </div>
58
- <div
59
- class="col-span-1 flex flex-col overflow-y-auto whitespace-nowrap max-md:-mx-4 max-md:h-[245px] max-md:border max-md:border-b-2 md:pr-6"
60
- >
61
- <h3 class="pb-3 pl-3 pt-2 text-[.8rem] text-gray-800 sm:pl-1">Models</h3>
62
-
63
- {#each data.models.filter((el) => !el.unlisted) as model}
64
- <a
65
- href="{base}/settings/{model.id}"
66
- class="group flex h-10 flex-none items-center gap-2 pl-3 pr-2 text-sm text-gray-500 hover:bg-gray-100 md:rounded-xl
67
- {model.id === $page.params.model ? '!bg-gray-100 !text-gray-800' : ''}"
68
- >
69
- <div class="truncate">{model.displayName}</div>
70
- {#if model.id === $settings.activeModel}
71
- <div
72
- class="ml-auto rounded-lg bg-black px-2 py-1.5 text-xs font-semibold leading-none text-white"
73
- >
74
- Active
75
- </div>
76
- {/if}
77
- </a>
78
- {/each}
79
- <!-- if its huggingchat, the number of assistants owned by the user must be non-zero to show the UI -->
80
- {#if data.enableAssistants}
81
- <h3 bind:this={assistantsSection} class="pb-3 pl-3 pt-5 text-[.8rem] text-gray-800 sm:pl-1">
82
- Assistants
83
- </h3>
84
- {#if !data.loginEnabled || (data.loginEnabled && !!data.user)}
85
- <a
86
- href="{base}/settings/assistants/new"
87
- class="group flex h-10 flex-none items-center gap-2 pl-3 pr-2 text-sm text-gray-500 hover:bg-gray-100 md:rounded-xl
88
- {$page.url.pathname === `${base}/settings/assistants/new` ? '!bg-gray-100 !text-gray-800' : ''}"
89
- >
90
- <CarbonAdd />
91
- <div class="truncate">Create new assistant</div>
92
- </a>
93
- {/if}
94
- {#each data.assistants as assistant}
95
- <a
96
- href="{base}/settings/assistants/{assistant._id.toString()}"
97
- class="group flex h-10 flex-none items-center gap-2 pl-2 pr-2 text-sm text-gray-500 hover:bg-gray-100 md:rounded-xl
98
- {assistant._id.toString() === $page.params.assistantId ? '!bg-gray-100 !text-gray-800' : ''}"
99
- >
100
- {#if assistant.avatar}
101
- <img
102
- src="{base}/settings/assistants/{assistant._id.toString()}/avatar.jpg?hash={assistant.avatar}"
103
- alt="Avatar"
104
- class="h-6 w-6 rounded-full object-cover"
105
- />
106
- {:else}
107
- <div
108
- class="flex size-6 items-center justify-center rounded-full bg-gray-300 font-bold uppercase text-gray-500"
109
- >
110
- {assistant.name[0]}
111
- </div>
112
- {/if}
113
- <div class="truncate">{assistant.name}</div>
114
- {#if assistant._id.toString() === $settings.activeModel}
115
- <div
116
- class="ml-auto rounded-lg bg-black px-2 py-1.5 text-xs font-semibold leading-none text-white"
117
- >
118
- Active
119
- </div>
120
- {/if}
121
- </a>
122
- {/each}
123
- <a
124
- href="{base}/assistants"
125
- class="group flex h-10 flex-none items-center gap-2 pl-3 pr-2 text-sm text-gray-500 hover:bg-gray-100 md:rounded-xl"
126
- ><CarbonArrowUpRight class="mr-1.5 shrink-0 text-xs " />
127
- <div class="truncate">Browse Assistants</div>
128
- </a>
129
- {/if}
130
-
131
- <a
132
- href="{base}/settings"
133
- class="group mt-auto flex h-10 flex-none items-center gap-2 pl-3 pr-2 text-sm text-gray-500 hover:bg-gray-100 max-md:order-first md:rounded-xl
134
- {$page.url.pathname === `${base}/settings` ? '!bg-gray-100 !text-gray-800' : ''}"
135
- >
136
- <UserIcon class="text-sm" />
137
- Application Settings
138
- </a>
139
- </div>
140
- <div class="col-span-1 overflow-y-auto px-4 max-md:-mx-4 max-md:pt-6 md:col-span-2">
141
- <slot />
142
- </div>
143
-
144
  {#if $settings.recentlySaved}
145
  <div
146
  class="absolute bottom-4 right-4 m-2 flex items-center gap-1.5 rounded-full border border-gray-300 bg-gray-200 px-3 py-1 text-black"
 
1
  <script lang="ts">
 
2
  import { base } from "$app/paths";
3
  import { clickOutside } from "$lib/actions/clickOutside";
4
  import { afterNavigate, goto } from "$app/navigation";
 
5
  import { useSettingsStore } from "$lib/stores/settings";
 
 
6
  import CarbonCheckmark from "~icons/carbon/checkmark";
 
7
 
 
8
  import { fade, fly } from "svelte/transition";
 
9
 
10
  let previousPage: string = base;
 
 
 
 
 
 
 
11
 
12
  afterNavigate(({ from }) => {
13
  if (!from?.url.pathname.includes("settings")) {
 
28
  use:clickOutside={() => {
29
  goto(previousPage);
30
  }}
31
+ class="h-[95dvh] w-[90dvw] overflow-hidden rounded-2xl bg-white shadow-2xl outline-none sm:h-[80dvh] xl:w-[1200px] 2xl:h-[70dvh]"
32
  >
33
+ <slot />
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
34
  {#if $settings.recentlySaved}
35
  <div
36
  class="absolute bottom-4 right-4 m-2 flex items-center gap-1.5 rounded-full border border-gray-300 bg-gray-200 px-3 py-1 text-black"