Skip to content

Commit 47ab4c7

Browse files
committed
refac
1 parent 39100ec commit 47ab4c7

1 file changed

Lines changed: 218 additions & 130 deletions

File tree

src/lib/components/chat/Settings/Personalization/ManageModal.svelte

Lines changed: 218 additions & 130 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,18 @@
99
import AddMemoryModal from './AddMemoryModal.svelte';
1010
import { deleteMemoriesByUserId, deleteMemoryById, getMemories } from '$lib/apis/memories';
1111
import Tooltip from '$lib/components/common/Tooltip.svelte';
12-
import { error } from '@sveltejs/kit';
1312
import EditMemoryModal from './EditMemoryModal.svelte';
1413
import localizedFormat from 'dayjs/plugin/localizedFormat';
1514
import ConfirmDialog from '$lib/components/common/ConfirmDialog.svelte';
15+
import Spinner from '$lib/components/common/Spinner.svelte';
16+
17+
import XMark from '$lib/components/icons/XMark.svelte';
18+
import Pencil from '$lib/components/icons/Pencil.svelte';
19+
import GarbageBin from '$lib/components/icons/GarbageBin.svelte';
20+
import Plus from '$lib/components/icons/Plus.svelte';
21+
import Search from '$lib/components/icons/Search.svelte';
22+
import ChevronUp from '$lib/components/icons/ChevronUp.svelte';
23+
import ChevronDown from '$lib/components/icons/ChevronDown.svelte';
1624
1725
const i18n = getContext('i18n');
1826
dayjs.extend(localizedFormat);
@@ -22,13 +30,46 @@
2230
let memories = [];
2331
let loading = true;
2432
33+
let query = '';
34+
let orderBy = 'updated_at';
35+
let direction = 'desc';
36+
37+
const setSortKey = (key: string) => {
38+
if (orderBy === key) {
39+
direction = direction === 'asc' ? 'desc' : 'asc';
40+
} else {
41+
orderBy = key;
42+
direction = 'asc';
43+
}
44+
};
45+
2546
let showAddMemoryModal = false;
2647
let showEditMemoryModal = false;
2748
2849
let selectedMemory = null;
2950
3051
let showClearConfirmDialog = false;
3152
53+
$: filteredMemories = query
54+
? memories.filter((m) => m.content?.toLowerCase().includes(query.toLowerCase()))
55+
: memories;
56+
57+
$: sortedMemories = [...filteredMemories].sort((a, b) => {
58+
let aVal, bVal;
59+
if (orderBy === 'content') {
60+
aVal = (a.content ?? '').toLowerCase();
61+
bVal = (b.content ?? '').toLowerCase();
62+
} else {
63+
aVal = a.updated_at ?? 0;
64+
bVal = b.updated_at ?? 0;
65+
}
66+
if (direction === 'asc') {
67+
return aVal > bVal ? 1 : -1;
68+
} else {
69+
return aVal < bVal ? 1 : -1;
70+
}
71+
});
72+
3273
let onClearConfirmed = async () => {
3374
const res = await deleteMemoriesByUserId(localStorage.token).catch((error) => {
3475
toast.error(`${error}`);
@@ -52,145 +93,185 @@
5293

5394
<Modal size="lg" bind:show>
5495
<div>
55-
<div class=" flex justify-between dark:text-gray-300 px-5 pt-4 pb-1">
56-
<div class=" text-lg font-medium self-center">{$i18n.t('Memory')}</div>
57-
<button
58-
class="self-center"
59-
on:click={() => {
60-
show = false;
61-
}}
62-
>
63-
<svg
64-
xmlns="http://www.w3.org/2000/svg"
65-
viewBox="0 0 20 20"
66-
fill="currentColor"
67-
class="w-5 h-5"
68-
>
69-
<path
70-
d="M6.28 5.22a.75.75 0 00-1.06 1.06L8.94 10l-3.72 3.72a.75.75 0 101.06 1.06L10 11.06l3.72 3.72a.75.75 0 101.06-1.06L11.06 10l3.72-3.72a.75.75 0 00-1.06-1.06L10 8.94 6.28 5.22z"
71-
/>
72-
</svg>
96+
<!-- Header -->
97+
<div class="flex justify-between dark:text-gray-300 px-5 pt-4 pb-1">
98+
<div class="flex items-center gap-2">
99+
<div class="text-lg font-medium">{$i18n.t('Memory')}</div>
100+
101+
{#if !loading}
102+
<div class="text-lg font-medium text-gray-500 dark:text-gray-500">
103+
{memories.length}
104+
</div>
105+
{/if}
106+
</div>
107+
108+
<button class="self-center" on:click={() => (show = false)}>
109+
<XMark className="size-5" />
73110
</button>
74111
</div>
75112

76-
<div class="flex flex-col w-full px-5 pb-5 dark:text-gray-200">
77-
<div
78-
class=" flex flex-col w-full sm:flex-row sm:justify-center sm:space-x-6 h-[28rem] max-h-screen rounded-xl mb-4 mt-1"
79-
>
80-
{#if memories.length > 0}
81-
<div class="text-left text-sm w-full mb-4 overflow-y-scroll">
82-
<div class="relative overflow-x-auto">
83-
<table class="w-full text-sm text-left text-gray-600 dark:text-gray-400 table-auto">
84-
<thead
85-
class="text-xs text-gray-700 uppercase bg-transparent dark:text-gray-200 border-b-2 border-gray-50 dark:border-gray-850/30"
113+
<div class="flex flex-col w-full px-5 pb-4 dark:text-gray-200">
114+
<!-- Search -->
115+
<div class="flex flex-1 items-center w-full mb-1">
116+
<div class="self-center ml-1 mr-3">
117+
<Search className="size-3.5" />
118+
</div>
119+
<input
120+
class="w-full text-sm py-1 rounded-r-xl outline-hidden bg-transparent"
121+
bind:value={query}
122+
placeholder={$i18n.t('Search Memories')}
123+
maxlength="500"
124+
/>
125+
126+
{#if query}
127+
<div class="self-center pl-1.5 translate-y-[0.5px] bg-transparent">
128+
<button
129+
class="p-0.5 rounded-full hover:bg-gray-100 dark:hover:bg-gray-900 transition"
130+
on:click={() => {
131+
query = '';
132+
}}
133+
>
134+
<XMark className="size-3" strokeWidth="2" />
135+
</button>
136+
</div>
137+
{/if}
138+
</div>
139+
140+
<!-- Memories List -->
141+
<div class="flex flex-col w-full">
142+
{#if !loading}
143+
{#if sortedMemories.length === 0}
144+
<div
145+
class="text-xs text-gray-500 dark:text-gray-400 text-center px-5 min-h-20 w-full flex justify-center items-center"
146+
>
147+
{#if memories.length === 0}
148+
{$i18n.t('Memories accessible by LLMs will be shown here.')}
149+
{:else}
150+
{$i18n.t('No results found')}
151+
{/if}
152+
</div>
153+
{:else}
154+
{#if sortedMemories.length > 0}
155+
<div class="flex text-xs font-medium mb-1">
156+
<button
157+
class="px-1.5 py-1 cursor-pointer select-none basis-3/5"
158+
on:click={() => setSortKey('content')}
159+
>
160+
<div class="flex gap-1.5 items-center">
161+
{$i18n.t('Content')}
162+
{#if orderBy === 'content'}
163+
<span class="font-normal">
164+
{#if direction === 'asc'}
165+
<ChevronUp className="size-2" />
166+
{:else}
167+
<ChevronDown className="size-2" />
168+
{/if}
169+
</span>
170+
{:else}
171+
<span class="invisible">
172+
<ChevronUp className="size-2" />
173+
</span>
174+
{/if}
175+
</div>
176+
</button>
177+
<button
178+
class="px-1.5 py-1 cursor-pointer select-none hidden sm:flex sm:basis-2/5 justify-end"
179+
on:click={() => setSortKey('updated_at')}
180+
>
181+
<div class="flex gap-1.5 items-center">
182+
{$i18n.t('Updated at')}
183+
{#if orderBy === 'updated_at'}
184+
<span class="font-normal">
185+
{#if direction === 'asc'}
186+
<ChevronUp className="size-2" />
187+
{:else}
188+
<ChevronDown className="size-2" />
189+
{/if}
190+
</span>
191+
{:else}
192+
<span class="invisible">
193+
<ChevronUp className="size-2" />
194+
</span>
195+
{/if}
196+
</div>
197+
</button>
198+
</div>
199+
{/if}
200+
201+
<div class="text-left text-sm w-full max-h-[28rem] overflow-y-auto">
202+
{#each sortedMemories as memory (memory.id)}
203+
<div
204+
class="w-full flex justify-between items-center rounded-xl text-sm py-2 px-3 hover:bg-gray-50 dark:hover:bg-gray-850 transition cursor-pointer"
205+
on:click={() => {
206+
selectedMemory = memory;
207+
showEditMemoryModal = true;
208+
}}
86209
>
87-
<tr>
88-
<th scope="col" class="px-3 py-2"> {$i18n.t('Name')} </th>
89-
<th scope="col" class="px-3 py-2 hidden md:flex">
90-
{$i18n.t('Last Modified')}
91-
</th>
92-
<th scope="col" class="px-3 py-2 text-right" />
93-
</tr>
94-
</thead>
95-
<tbody>
96-
{#each memories as memory}
97-
<tr class="border-b border-gray-50 dark:border-gray-850/30 items-center">
98-
<td class="px-3 py-1">
99-
<div class="line-clamp-1">
100-
{memory.content}
101-
</div>
102-
</td>
103-
<td class=" px-3 py-1 hidden md:flex h-[2.5rem]">
104-
<div class="my-auto whitespace-nowrap">
105-
{dayjs(memory.updated_at * 1000).format('LLL')}
106-
</div>
107-
</td>
108-
<td class="px-3 py-1">
109-
<div class="flex justify-end w-full">
110-
<Tooltip content="Edit">
111-
<button
112-
class="self-center w-fit text-sm px-2 py-2 hover:bg-black/5 dark:hover:bg-white/5 rounded-xl"
113-
on:click={() => {
114-
selectedMemory = memory;
115-
showEditMemoryModal = true;
116-
}}
117-
>
118-
<svg
119-
xmlns="http://www.w3.org/2000/svg"
120-
fill="none"
121-
viewBox="0 0 24 24"
122-
stroke-width="1.5"
123-
stroke="currentColor"
124-
class="w-4 h-4 s-FoVA_WMOgxUD"
125-
><path
126-
stroke-linecap="round"
127-
stroke-linejoin="round"
128-
d="m16.862 4.487 1.687-1.688a1.875 1.875 0 1 1 2.652 2.652L6.832 19.82a4.5 4.5 0 0 1-1.897 1.13l-2.685.8.8-2.685a4.5 4.5 0 0 1 1.13-1.897L16.863 4.487Zm0 0L19.5 7.125"
129-
class="s-FoVA_WMOgxUD"
130-
/></svg
131-
>
132-
</button>
133-
</Tooltip>
134-
135-
<Tooltip content="Delete">
136-
<button
137-
class="self-center w-fit text-sm px-2 py-2 hover:bg-black/5 dark:hover:bg-white/5 rounded-xl"
138-
on:click={async () => {
139-
const res = await deleteMemoryById(
140-
localStorage.token,
141-
memory.id
142-
).catch((error) => {
143-
toast.error(`${error}`);
144-
return null;
145-
});
146-
147-
if (res) {
148-
toast.success($i18n.t('Memory deleted successfully'));
149-
memories = await getMemories(localStorage.token);
150-
}
151-
}}
152-
>
153-
<svg
154-
xmlns="http://www.w3.org/2000/svg"
155-
fill="none"
156-
viewBox="0 0 24 24"
157-
stroke-width="1.5"
158-
stroke="currentColor"
159-
class="w-4 h-4"
160-
>
161-
<path
162-
stroke-linecap="round"
163-
stroke-linejoin="round"
164-
d="m14.74 9-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 0 1-2.244 2.077H8.084a2.25 2.25 0 0 1-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 0 0-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 0 1 3.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 0 0-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 0 0-7.5 0"
165-
/>
166-
</svg>
167-
</button>
168-
</Tooltip>
169-
</div>
170-
</td>
171-
</tr>
172-
{/each}
173-
</tbody>
174-
</table>
210+
<div class="flex-1 min-w-0 pr-2">
211+
<div class="text-ellipsis line-clamp-1">{memory.content}</div>
212+
<div class="text-xs text-gray-500 dark:text-gray-400">
213+
{dayjs(memory.updated_at * 1000).format('MMM D, YYYY')}
214+
</div>
215+
</div>
216+
217+
<div class="flex items-center shrink-0">
218+
<div
219+
class="hidden sm:flex text-gray-500 dark:text-gray-400 text-xs whitespace-nowrap mr-2"
220+
>
221+
{dayjs(memory.updated_at * 1000).format('h:mm A')}
222+
</div>
223+
224+
<div class="flex text-gray-600 dark:text-gray-300">
225+
<Tooltip content={$i18n.t('Edit')}>
226+
<button
227+
class="self-center w-fit text-sm p-1.5 hover:bg-black/5 dark:hover:bg-white/5 rounded-xl"
228+
on:click={() => {
229+
selectedMemory = memory;
230+
showEditMemoryModal = true;
231+
}}
232+
>
233+
<Pencil className="size-4" />
234+
</button>
235+
</Tooltip>
236+
237+
<Tooltip content={$i18n.t('Delete')}>
238+
<button
239+
class="self-center w-fit text-sm p-1.5 hover:bg-black/5 dark:hover:bg-white/5 rounded-xl"
240+
on:click={async () => {
241+
const res = await deleteMemoryById(
242+
localStorage.token,
243+
memory.id
244+
).catch((error) => {
245+
toast.error(`${error}`);
246+
return null;
247+
});
248+
249+
if (res) {
250+
toast.success($i18n.t('Memory deleted successfully'));
251+
memories = await getMemories(localStorage.token);
252+
}
253+
}}
254+
>
255+
<GarbageBin className="size-4" strokeWidth="1.5" />
256+
</button>
257+
</Tooltip>
258+
</div>
259+
</div>
260+
</div>
261+
{/each}
175262
</div>
176-
</div>
263+
{/if}
177264
{:else}
178-
<div class="text-center flex h-full text-sm w-full">
179-
<div class=" my-auto pb-10 px-4 w-full text-gray-500">
180-
{$i18n.t('Memories accessible by LLMs will be shown here.')}
181-
</div>
265+
<div class="w-full flex justify-center items-center min-h-20">
266+
<Spinner className="size-4" />
182267
</div>
183268
{/if}
184269
</div>
185-
<div class="flex text-sm font-medium gap-1.5">
186-
<button
187-
class=" px-3.5 py-1.5 font-medium hover:bg-black/5 dark:hover:bg-white/5 outline outline-1 outline-gray-100 dark:outline-gray-800 rounded-3xl"
188-
on:click={() => {
189-
showAddMemoryModal = true;
190-
}}>{$i18n.t('Add Memory')}</button
191-
>
270+
271+
<!-- Footer -->
272+
<div class="flex justify-between items-center text-sm mt-2">
192273
<button
193-
class=" px-3.5 py-1.5 font-medium text-red-500 hover:bg-black/5 dark:hover:bg-white/5 outline outline-1 outline-red-100 dark:outline-red-800 rounded-3xl"
274+
class="px-2 py-1 text-xs text-gray-500 hover:text-gray-700 dark:hover:text-gray-300 hover:underline transition"
194275
on:click={() => {
195276
if (memories.length > 0) {
196277
showClearConfirmDialog = true;
@@ -199,6 +280,13 @@
199280
}
200281
}}>{$i18n.t('Clear memory')}</button
201282
>
283+
284+
<button
285+
class="px-3.5 py-1.5 font-medium hover:bg-black/5 dark:hover:bg-white/5 outline outline-1 outline-gray-100 dark:outline-gray-800 rounded-3xl"
286+
on:click={() => {
287+
showAddMemoryModal = true;
288+
}}>{$i18n.t('Add Memory')}</button
289+
>
202290
</div>
203291
</div>
204292
</div>

0 commit comments

Comments
 (0)