Skip to content

Commit f3114ae

Browse files
nsarrazingary149
andauthored
[Assistant] Delete avatar button instead of reset (#725)
* Add rate-limited image generating endpoint * Add generate avatar button * add little padding for firefox focus ring * format * fix upload image bug * Fix uploads, replace reset by delete * left-align buttons * rm avatar generation feature * final changes to delete feature * sys prompt min height * padding * Add object-cover everywhere --------- Co-authored-by: Victor Mustar <victor.mustar@gmail.com>
1 parent cf00c70 commit f3114ae

File tree

8 files changed

+77
-142
lines changed

8 files changed

+77
-142
lines changed

.env

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,4 @@ EXPOSE_API=true
129129
# PUBLIC_APP_DATA_SHARING=1
130130
# PUBLIC_APP_DISCLAIMER=1
131131

132-
ENABLE_ASSISTANTS=false #set to true to enable assistants feature
133-
ASSISTANTS_GENERATE_AVATAR=true #requires an hf token, uses the model description and name to generate an avatar using a text to image model
134-
TEXT_TO_IMAGE_MODEL="runwayml/stable-diffusion-v1-5"
132+
ENABLE_ASSISTANTS=false #set to true to enable assistants feature

src/lib/components/AssistantSettings.svelte

Lines changed: 61 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@
77
import { applyAction, enhance } from "$app/forms";
88
import { base } from "$app/paths";
99
import CarbonPen from "~icons/carbon/pen";
10+
import CarbonUpload from "~icons/carbon/upload";
1011
import { useSettingsStore } from "$lib/stores/settings";
11-
import { page } from "$app/stores";
1212
import IconLoading from "./icons/IconLoading.svelte";
1313
1414
type ActionData = {
@@ -41,20 +41,29 @@
4141
let inputMessage3 = assistant?.exampleInputs[2] ?? "";
4242
let inputMessage4 = assistant?.exampleInputs[3] ?? "";
4343
44+
function resetErrors() {
45+
if (form) {
46+
form.errors = [];
47+
form.error = false;
48+
}
49+
}
50+
4451
function onFilesChange(e: Event) {
4552
const inputEl = e.target as HTMLInputElement;
4653
if (inputEl.files?.length) {
4754
files = inputEl.files;
55+
resetErrors();
56+
deleteExistingAvatar = false;
4857
}
4958
}
5059
5160
function getError(field: string, returnForm: ActionData) {
5261
return returnForm?.errors.find((error) => error.field === field)?.message ?? "";
5362
}
5463
55-
let loading = false;
64+
let deleteExistingAvatar = false;
5665
57-
let generateAvatar = false;
66+
let loading = false;
5867
</script>
5968

6069
<form
@@ -63,10 +72,8 @@
6372
enctype="multipart/form-data"
6473
use:enhance={async ({ formData }) => {
6574
loading = true;
66-
const avatar = formData.get("avatar");
67-
68-
if (avatar && typeof avatar !== "string" && avatar.size > 0 && compress) {
69-
await compress(avatar, {
75+
if (files?.[0] && files[0].size > 0 && compress) {
76+
await compress(files[0], {
7077
maxWidth: 500,
7178
maxHeight: 500,
7279
quality: 1,
@@ -75,6 +82,16 @@
7582
});
7683
}
7784

85+
if (deleteExistingAvatar === true) {
86+
if (assistant?.avatar) {
87+
// if there is an avatar we explicitly removei t
88+
formData.set("avatar", "null");
89+
} else {
90+
// else we just remove it from the input
91+
formData.delete("avatar");
92+
}
93+
}
94+
7895
return async ({ result }) => {
7996
loading = false;
8097
await applyAction(result);
@@ -93,73 +110,72 @@
93110
</p>
94111
{/if}
95112

96-
<div class="grid flex-1 grid-cols-2 gap-4 max-sm:grid-cols-1">
113+
<div class="mx-1 grid flex-1 grid-cols-2 gap-4 max-sm:grid-cols-1">
97114
<div class="flex flex-col gap-4">
98-
<label class="truncate">
99-
<span class="mb-1 block text-sm font-semibold">Avatar</span>
115+
<div>
116+
<span class="mb-1 block pb-2 text-sm font-semibold">Avatar</span>
100117
<input
101118
type="file"
102119
accept="image/*"
103120
name="avatar"
104-
class="invisible z-10 block h-0 w-0"
105-
disabled={generateAvatar}
121+
id="avatar"
122+
class="hidden"
106123
on:change={onFilesChange}
107124
/>
108-
{#if (files && files[0]) || assistant?.avatar}
109-
<div class="group relative h-12 w-12">
125+
126+
{#if (files && files[0]) || (assistant?.avatar && !deleteExistingAvatar)}
127+
<div class="group relative mx-auto h-12 w-12">
110128
{#if files && files[0]}
111129
<img
112130
src={URL.createObjectURL(files[0])}
113131
alt="avatar"
114-
class="crop mx-auto h-12 w-12 cursor-pointer rounded-full"
132+
class="crop mx-auto h-12 w-12 cursor-pointer rounded-full object-cover"
115133
/>
116134
{:else if assistant?.avatar}
117135
<img
118136
src="{base}/settings/assistants/{assistant._id}/avatar?hash={assistant.avatar}"
119137
alt="avatar"
120-
class="crop mx-auto h-12 w-12 cursor-pointer rounded-full"
138+
class="crop mx-auto h-12 w-12 cursor-pointer rounded-full object-cover"
121139
/>
122140
{/if}
123141

124-
<div
142+
<label
143+
for="avatar"
125144
class="invisible absolute bottom-0 h-12 w-12 rounded-full bg-black bg-opacity-50 p-1 group-hover:visible hover:visible"
126145
>
127146
<CarbonPen class="mx-auto my-auto h-full cursor-pointer text-center text-white" />
128-
</div>
147+
</label>
148+
</div>
149+
<div class="mx-auto w-max pt-1">
150+
<button
151+
type="button"
152+
on:click|stopPropagation|preventDefault={() => {
153+
files = null;
154+
deleteExistingAvatar = true;
155+
}}
156+
class="mx-auto w-max text-center text-xs text-gray-600 hover:underline"
157+
>
158+
Delete
159+
</button>
129160
</div>
130-
<button
131-
type="button"
132-
on:click|stopPropagation|preventDefault={() => (files = null)}
133-
class="mt-1 text-xs text-gray-600 hover:underline"
134-
>
135-
Reset
136-
</button>
137161
{:else}
138-
<span
139-
class="text-xs text-gray-500"
140-
class:hover:underline={!generateAvatar}
141-
class:cursor-pointer={!generateAvatar}>Click to upload</span
142-
>
143-
{/if}
144-
<p class="text-xs text-red-500">{getError("avatar", form)}</p>
145-
{#if !files?.[0] && $page.data.avatarGeneration && !assistant?.avatar}
146-
<label class="text-xs text-gray-500">
147-
<input
148-
type="checkbox"
149-
name="generateAvatar"
150-
class="text-xs text-gray-500"
151-
bind:checked={generateAvatar}
152-
/>
153-
Generate avatar from description
154-
</label>
162+
<div class="mb-1 flex w-max flex-row gap-4">
163+
<label
164+
for="avatar"
165+
class="btn flex h-8 rounded-lg border bg-white px-3 py-1 text-gray-500 shadow-sm transition-all hover:bg-gray-100"
166+
>
167+
<CarbonUpload class="mr-2 text-xs " /> Upload
168+
</label>
169+
</div>
170+
<p class="text-xs text-red-500">{getError("avatar", form)}</p>
155171
{/if}
156-
</label>
172+
</div>
157173

158174
<label>
159175
<span class="mb-1 text-sm font-semibold">Name</span>
160176
<input
161177
name="name"
162-
class=" w-full rounded-lg border-2 border-gray-200 bg-gray-100 p-2"
178+
class="w-full rounded-lg border-2 border-gray-200 bg-gray-100 p-2"
163179
placeholder="My awesome model"
164180
value={assistant?.name ?? ""}
165181
/>
@@ -228,14 +244,12 @@
228244

229245
<label class="flex flex-col">
230246
<span class="mb-1 text-sm font-semibold"> Instructions (system prompt) </span>
231-
232247
<textarea
233248
name="preprompt"
234-
class="flex-1 rounded-lg border-2 border-gray-200 bg-gray-100 p-2 text-sm"
249+
class="min-h-[8lh] flex-1 rounded-lg border-2 border-gray-200 bg-gray-100 p-2 text-sm"
235250
placeholder="You'll act as..."
236251
value={assistant?.preprompt ?? ""}
237252
/>
238-
239253
<p class="text-xs text-red-500">{getError("preprompt", form)}</p>
240254
</label>
241255
</div>
@@ -245,7 +259,6 @@
245259
href={assistant ? `${base}/settings/assistants/${assistant?._id}` : `${base}/settings`}
246260
class="rounded-full bg-gray-200 px-8 py-2 font-semibold text-gray-600">Cancel</a
247261
>
248-
249262
<button
250263
type="submit"
251264
disabled={loading}

src/lib/components/NavConversationItem.svelte

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@
4141
<img
4242
src="{base}/settings/assistants/{conv.assistantId}/avatar?hash={conv.avatarHash}"
4343
alt="Assistant avatar"
44-
class="mr-1.5 inline size-4 rounded-full"
44+
class="mr-1.5 inline size-4 rounded-full object-cover"
4545
/>
4646
{conv.title.replace(/\p{Emoji}/gu, "")}
4747
{:else if conv.assistantId}

src/lib/utils/generateAvatar.ts

Lines changed: 0 additions & 24 deletions
This file was deleted.

src/routes/+layout.server.ts

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,6 @@ import {
1313
YDC_API_KEY,
1414
USE_LOCAL_WEBSEARCH,
1515
ENABLE_ASSISTANTS,
16-
ASSISTANTS_GENERATE_AVATAR,
17-
TEXT_TO_IMAGE_MODEL,
1816
} from "$env/static/private";
1917
import { ObjectId } from "mongodb";
2018
import type { ConvSidebar } from "$lib/types/ConvSidebar";
@@ -163,7 +161,6 @@ export const load: LayoutServerLoad = async ({ locals, depends }) => {
163161
email: locals.user.email,
164162
},
165163
assistant,
166-
avatarGeneration: ASSISTANTS_GENERATE_AVATAR === "true" && !!TEXT_TO_IMAGE_MODEL,
167164
enableAssistants,
168165
loginRequired,
169166
loginEnabled: requiresUser,

src/routes/settings/+layout.svelte

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@
124124
Application Settings
125125
</a>
126126
</div>
127-
<div class="col-span-1 overflow-y-auto max-md:pt-6 md:col-span-2">
127+
<div class="col-span-1 overflow-y-auto px-4 max-md:-mx-4 max-md:pt-6 md:col-span-2">
128128
<slot />
129129
</div>
130130

src/routes/settings/assistants/[assistantId]/edit/+page.server.ts

Lines changed: 13 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,6 @@ import { ObjectId } from "mongodb";
77
import { z } from "zod";
88
import sizeof from "image-size";
99
import { sha256 } from "$lib/utils/sha256";
10-
import { ASSISTANTS_GENERATE_AVATAR, HF_TOKEN } from "$env/static/private";
11-
import { generateAvatar } from "$lib/utils/generateAvatar";
12-
import { timeout } from "$lib/utils/timeout";
1310

1411
const newAsssistantSchema = z.object({
1512
name: z.string().min(1),
@@ -20,11 +17,7 @@ const newAsssistantSchema = z.object({
2017
exampleInput2: z.string().optional(),
2118
exampleInput3: z.string().optional(),
2219
exampleInput4: z.string().optional(),
23-
avatar: z.instanceof(File).optional(),
24-
generateAvatar: z
25-
.literal("on")
26-
.optional()
27-
.transform((el) => !!el),
20+
avatar: z.union([z.instanceof(File), z.literal("null")]).optional(),
2821
});
2922

3023
const uploadAvatar = async (avatar: File, assistantId: ObjectId): Promise<string> => {
@@ -87,8 +80,10 @@ export const actions: Actions = {
8780
parse?.data?.exampleInput4 ?? "",
8881
].filter((input) => !!input);
8982

83+
const deleteAvatar = parse.data.avatar === "null";
84+
9085
let hash;
91-
if (parse.data.avatar && parse.data.avatar.size > 0) {
86+
if (parse.data.avatar && parse.data.avatar !== "null" && parse.data.avatar.size > 0) {
9287
const dims = sizeof(Buffer.from(await parse.data.avatar.arrayBuffer()));
9388

9489
if ((dims.height ?? 1000) > 512 || (dims.width ?? 1000) > 512) {
@@ -106,28 +101,14 @@ export const actions: Actions = {
106101
}
107102

108103
hash = await uploadAvatar(parse.data.avatar, assistant._id);
109-
} else if (
110-
ASSISTANTS_GENERATE_AVATAR === "true" &&
111-
HF_TOKEN !== "" &&
112-
parse.data.generateAvatar
113-
) {
114-
try {
115-
const avatar = await timeout(
116-
generateAvatar(parse.data.description, parse.data.name),
117-
30000
118-
);
119-
120-
hash = await uploadAvatar(avatar, assistant._id);
121-
} catch (err) {
122-
return fail(400, {
123-
error: true,
124-
errors: [
125-
{
126-
field: "avatar",
127-
message: "Avatar generation failed. Try again or disable the feature.",
128-
},
129-
],
130-
});
104+
} else if (deleteAvatar) {
105+
// delete the avatar
106+
const fileCursor = collections.bucket.find({ filename: assistant._id.toString() });
107+
108+
let fileId = await fileCursor.next();
109+
while (fileId) {
110+
await collections.bucket.delete(fileId._id);
111+
fileId = await fileCursor.next();
131112
}
132113
}
133114

@@ -140,7 +121,7 @@ export const actions: Actions = {
140121
createdByName: locals.user?.username ?? locals.user?.name,
141122
...parse.data,
142123
exampleInputs,
143-
avatar: hash ?? assistant.avatar,
124+
avatar: deleteAvatar ? undefined : hash ?? assistant.avatar,
144125
createdAt: new Date(),
145126
updatedAt: new Date(),
146127
}

0 commit comments

Comments
 (0)