refac
This commit is contained in:
@@ -124,119 +124,108 @@
|
||||
});
|
||||
</script>
|
||||
|
||||
<div id="tab-account" class="flex flex-col h-full justify-between text-sm">
|
||||
<div class="flex-1 min-h-0 overflow-y-auto scrollbar-hover pr-1.5 -mr-1.5">
|
||||
<div class="space-y-1">
|
||||
<div>
|
||||
<div class="text-xs text-gray-400 dark:text-gray-600 mb-2">
|
||||
{$i18n.t('Your Account')}
|
||||
</div>
|
||||
|
||||
<div class="text-xs text-gray-500 mt-0.5">
|
||||
{$i18n.t('Manage your account information.')}
|
||||
</div>
|
||||
</div>
|
||||
<div id="tab-account" class="flex h-full flex-col text-sm">
|
||||
<div class="flex-1 min-h-0 w-full overflow-y-auto scrollbar-hover pr-1.5 -mr-1.5 py-1">
|
||||
<h2 class="mb-4 text-sm font-medium text-gray-900 dark:text-white">{$i18n.t('Account')}</h2>
|
||||
|
||||
<div class="space-y-3">
|
||||
<!-- <div class=" text-sm font-normal">{$i18n.t('Account')}</div> -->
|
||||
|
||||
<div class="flex space-x-5 my-4">
|
||||
<UserProfileImage bind:profileImageUrl user={$user} />
|
||||
<UserProfileImage
|
||||
bind:profileImageUrl
|
||||
user={$user}
|
||||
variant="account"
|
||||
displayName={$user?.name}
|
||||
/>
|
||||
|
||||
<div class="flex flex-1 flex-col">
|
||||
<div class=" flex-1">
|
||||
<div class="flex flex-col w-full">
|
||||
<div class=" mb-1 text-xs font-normal">{$i18n.t('Name')}</div>
|
||||
<div class="flex flex-col w-full">
|
||||
<div class="mb-1 text-xs text-gray-400 dark:text-gray-600">{$i18n.t('Name')}</div>
|
||||
|
||||
<div class="flex-1">
|
||||
<input
|
||||
class="w-full text-sm dark:text-gray-300 bg-transparent outline-hidden"
|
||||
type="text"
|
||||
bind:value={name}
|
||||
aria-label={$i18n.t('Name')}
|
||||
required
|
||||
placeholder={$i18n.t('Enter your name')}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<input
|
||||
class="w-full bg-transparent py-1 text-[0.8125rem] text-gray-700 outline-hidden placeholder:text-gray-300 dark:text-gray-300 dark:placeholder:text-gray-700"
|
||||
type="text"
|
||||
bind:value={name}
|
||||
aria-label={$i18n.t('Name')}
|
||||
required
|
||||
placeholder={$i18n.t('Enter your name')}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col w-full mt-2">
|
||||
<div class=" mb-1 text-xs font-normal">{$i18n.t('Bio')}</div>
|
||||
<div class="mt-2 flex flex-col w-full">
|
||||
<div class="mb-1 text-xs text-gray-400 dark:text-gray-600">{$i18n.t('Bio')}</div>
|
||||
|
||||
<div class="flex-1">
|
||||
<Textarea
|
||||
className="w-full text-sm dark:text-gray-300 bg-transparent outline-hidden"
|
||||
minSize={60}
|
||||
bind:value={bio}
|
||||
ariaLabel={$i18n.t('Bio')}
|
||||
placeholder={$i18n.t('Share your background and interests')}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<Textarea
|
||||
className="w-full bg-transparent py-1 text-[0.8125rem] text-gray-700 outline-hidden placeholder:text-gray-300 dark:text-gray-300 dark:placeholder:text-gray-700"
|
||||
minSize={60}
|
||||
bind:value={bio}
|
||||
ariaLabel={$i18n.t('Bio')}
|
||||
placeholder={$i18n.t('Share your background and interests')}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col w-full mt-2">
|
||||
<div class=" mb-1 text-xs font-normal">{$i18n.t('Gender')}</div>
|
||||
|
||||
<div class="flex-1">
|
||||
<select
|
||||
class="w-full text-sm dark:text-gray-300 bg-transparent outline-hidden"
|
||||
bind:value={_gender}
|
||||
aria-label={$i18n.t('Gender')}
|
||||
on:change={(e) => {
|
||||
console.log(_gender);
|
||||
|
||||
if (_gender === 'custom') {
|
||||
// Handle custom gender input
|
||||
gender = '';
|
||||
} else {
|
||||
gender = _gender;
|
||||
}
|
||||
}}
|
||||
>
|
||||
<option value="" selected>{$i18n.t('Prefer not to say')}</option>
|
||||
<option value="male">{$i18n.t('Male')}</option>
|
||||
<option value="female">{$i18n.t('Female')}</option>
|
||||
<option value="custom">{$i18n.t('Custom')}</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
{#if _gender === 'custom'}
|
||||
<input
|
||||
class="w-full text-sm dark:text-gray-300 bg-transparent outline-hidden mt-1"
|
||||
type="text"
|
||||
required
|
||||
aria-label={$i18n.t('Custom Gender')}
|
||||
placeholder={$i18n.t('Enter your gender')}
|
||||
bind:value={gender}
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col w-full mt-2">
|
||||
<div class=" mb-1 text-xs font-normal">{$i18n.t('Birth Date')}</div>
|
||||
|
||||
<div class="flex-1">
|
||||
<input
|
||||
class="w-full text-sm dark:text-gray-300 dark:placeholder:text-gray-300 bg-transparent outline-hidden"
|
||||
type="date"
|
||||
aria-label={$i18n.t('Birth Date')}
|
||||
bind:value={dateOfBirth}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-2 flex flex-col w-full">
|
||||
<div class="mb-1 text-xs text-gray-400 dark:text-gray-600">
|
||||
{$i18n.t('Gender')}
|
||||
</div>
|
||||
|
||||
<select
|
||||
class="w-full bg-transparent py-1 text-[0.8125rem] text-gray-700 outline-hidden dark:text-gray-300"
|
||||
bind:value={_gender}
|
||||
aria-label={$i18n.t('Gender')}
|
||||
on:change={(e) => {
|
||||
console.log(_gender);
|
||||
|
||||
if (_gender === 'custom') {
|
||||
// Handle custom gender input
|
||||
gender = '';
|
||||
} else {
|
||||
gender = _gender;
|
||||
}
|
||||
}}
|
||||
>
|
||||
<option value="" selected>{$i18n.t('Prefer not to say')}</option>
|
||||
<option value="male">{$i18n.t('Male')}</option>
|
||||
<option value="female">{$i18n.t('Female')}</option>
|
||||
<option value="custom">{$i18n.t('Custom')}</option>
|
||||
</select>
|
||||
|
||||
{#if _gender === 'custom'}
|
||||
<input
|
||||
class="mt-1 w-full bg-transparent py-1 text-[0.8125rem] text-gray-700 outline-hidden placeholder:text-gray-300 dark:text-gray-300 dark:placeholder:text-gray-700"
|
||||
type="text"
|
||||
required
|
||||
aria-label={$i18n.t('Custom Gender')}
|
||||
placeholder={$i18n.t('Enter your gender')}
|
||||
bind:value={gender}
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div class="mt-2 flex flex-col w-full">
|
||||
<div class="mb-1 text-xs text-gray-400 dark:text-gray-600">
|
||||
{$i18n.t('Birth Date')}
|
||||
</div>
|
||||
|
||||
<input
|
||||
class="w-full bg-transparent py-1 text-[0.8125rem] text-gray-700 outline-hidden dark:text-gray-300 dark:placeholder:text-gray-700"
|
||||
type="date"
|
||||
aria-label={$i18n.t('Birth Date')}
|
||||
bind:value={dateOfBirth}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{#if $config?.features?.enable_user_webhooks && ($user?.role === 'admin' || ($user?.permissions?.features?.webhooks ?? false))}
|
||||
<div class="mt-2">
|
||||
<div class="mt-3">
|
||||
<div class="flex flex-col w-full">
|
||||
<div class=" mb-1 text-xs font-normal">{$i18n.t('Notification Webhook')}</div>
|
||||
<div class="mb-1 text-xs text-gray-400 dark:text-gray-600">
|
||||
{$i18n.t('Notification Webhook')}
|
||||
</div>
|
||||
|
||||
<div class="flex-1">
|
||||
<input
|
||||
class="w-full text-sm outline-hidden"
|
||||
class="w-full bg-transparent py-1 text-[0.8125rem] text-gray-700 outline-hidden placeholder:text-gray-300 dark:text-gray-300 dark:placeholder:text-gray-700"
|
||||
type="url"
|
||||
placeholder={$i18n.t('Enter your webhook URL')}
|
||||
aria-label={$i18n.t('Notification Webhook')}
|
||||
@@ -248,7 +237,7 @@
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<hr class="border-gray-50 dark:border-gray-850/30 my-4" />
|
||||
<hr class="my-4 border-gray-100/40 dark:border-white/[0.03]" />
|
||||
|
||||
{#if $config?.features.enable_login_form && $config?.features.enable_password_change_form}
|
||||
<div class="mt-2">
|
||||
@@ -257,10 +246,10 @@
|
||||
{/if}
|
||||
|
||||
{#if ($config?.features?.enable_api_keys ?? true) && ($user?.role === 'admin' || ($user?.permissions?.features?.api_keys ?? false))}
|
||||
<div class="flex justify-between items-center text-sm mt-2">
|
||||
<div class=" font-normal">{$i18n.t('API keys')}</div>
|
||||
<div class="mt-3 flex items-center justify-between text-sm">
|
||||
<div class="text-xs text-gray-400 dark:text-gray-600">{$i18n.t('API keys')}</div>
|
||||
<button
|
||||
class=" text-xs font-normal text-gray-500"
|
||||
class="text-xs font-normal text-gray-500 transition-colors hover:text-gray-700 dark:hover:text-gray-300"
|
||||
type="button"
|
||||
on:click={() => {
|
||||
showAPIKeys = !showAPIKeys;
|
||||
@@ -273,14 +262,16 @@
|
||||
{#if $user?.role === 'admin'}
|
||||
<div class="justify-between w-full mt-2">
|
||||
<div class="flex justify-between w-full">
|
||||
<div class="self-center text-xs font-normal mb-1">{$i18n.t('JWT Token')}</div>
|
||||
<div class="mb-1 self-center text-xs text-gray-400 dark:text-gray-600">
|
||||
{$i18n.t('JWT Token')}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex">
|
||||
<SensitiveInput value={localStorage.token} readOnly={true} />
|
||||
|
||||
<button
|
||||
class="ml-1.5 px-1.5 py-1 dark:hover:bg-gray-850 transition rounded-lg"
|
||||
class="ml-1.5 rounded-sm px-1.5 py-1 text-gray-500 transition hover:bg-gray-100 hover:text-gray-700 dark:hover:bg-gray-850 dark:hover:text-gray-300"
|
||||
aria-label={$i18n.t('Copy Token')}
|
||||
on:click={() => {
|
||||
copyToClipboard(localStorage.token);
|
||||
@@ -331,7 +322,9 @@
|
||||
<div class="justify-between w-full mt-2">
|
||||
{#if $user?.role === 'admin'}
|
||||
<div class="flex justify-between w-full">
|
||||
<div class="self-center text-xs font-normal mb-1">{$i18n.t('API Key')}</div>
|
||||
<div class="mb-1 self-center text-xs text-gray-400 dark:text-gray-600">
|
||||
{$i18n.t('API Key')}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
<div class="flex">
|
||||
@@ -339,7 +332,7 @@
|
||||
<SensitiveInput value={APIKey} readOnly={true} />
|
||||
|
||||
<button
|
||||
class="ml-1.5 px-1.5 py-1 dark:hover:bg-gray-850 transition rounded-lg"
|
||||
class="ml-1.5 rounded-sm px-1.5 py-1 text-gray-500 transition hover:bg-gray-100 hover:text-gray-700 dark:hover:bg-gray-850 dark:hover:text-gray-300"
|
||||
aria-label={$i18n.t('Copy API Key')}
|
||||
on:click={() => {
|
||||
copyToClipboard(APIKey);
|
||||
@@ -385,7 +378,7 @@
|
||||
|
||||
<Tooltip content={$i18n.t('Create new key')}>
|
||||
<button
|
||||
class=" px-1.5 py-1 dark:hover:bg-gray-850transition rounded-lg"
|
||||
class="rounded-sm px-1.5 py-1 text-gray-500 transition hover:bg-gray-100 hover:text-gray-700 dark:hover:bg-gray-850 dark:hover:text-gray-300"
|
||||
aria-label={$i18n.t('Create new key')}
|
||||
on:click={() => {
|
||||
createAPIKeyHandler();
|
||||
@@ -409,7 +402,7 @@
|
||||
</Tooltip>
|
||||
{:else}
|
||||
<button
|
||||
class="flex gap-1.5 items-center font-normal px-3.5 py-1.5 rounded-lg bg-gray-100/70 hover:bg-gray-100 dark:bg-gray-850 dark:hover:bg-gray-850 transition"
|
||||
class="flex items-center gap-1.5 text-[0.8125rem] font-normal text-gray-600 transition-colors hover:text-gray-900 dark:text-gray-400 dark:hover:text-white"
|
||||
on:click={() => {
|
||||
createAPIKeyHandler();
|
||||
}}
|
||||
@@ -427,7 +420,7 @@
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div class="shrink-0 flex justify-end pt-3 text-sm font-normal">
|
||||
<div class="shrink-0 flex w-full justify-end pt-3 text-sm font-normal">
|
||||
<button
|
||||
class="px-3.5 py-1.5 text-sm font-normal bg-black hover:bg-gray-900 text-white dark:bg-white dark:text-black dark:hover:bg-gray-100 transition rounded-full"
|
||||
on:click={async () => {
|
||||
|
||||
@@ -44,9 +44,9 @@
|
||||
}}
|
||||
>
|
||||
<div class="flex justify-between items-center text-sm">
|
||||
<div class=" font-normal">{$i18n.t('Change Password')}</div>
|
||||
<div class="text-xs text-gray-400 dark:text-gray-600">{$i18n.t('Change Password')}</div>
|
||||
<button
|
||||
class=" text-xs font-normal text-gray-500"
|
||||
class="text-xs font-normal text-gray-500 transition-colors hover:text-gray-700 dark:hover:text-gray-300"
|
||||
type="button"
|
||||
on:click={() => {
|
||||
show = !show;
|
||||
|
||||
@@ -13,6 +13,8 @@
|
||||
export let user = null;
|
||||
|
||||
export let imageClassName = 'size-14 md:size-18';
|
||||
export let variant = 'default';
|
||||
export let displayName = '';
|
||||
|
||||
let profileImageInputElement;
|
||||
</script>
|
||||
@@ -79,10 +81,11 @@
|
||||
}}
|
||||
/>
|
||||
|
||||
<div class="flex flex-col self-start group">
|
||||
<div class="self-center flex">
|
||||
{#if variant === 'account'}
|
||||
<div class="mb-6 flex items-center gap-4">
|
||||
<button
|
||||
class="relative rounded-full dark:bg-gray-700"
|
||||
class="group relative flex h-10 w-10 shrink-0 items-center justify-center overflow-hidden rounded-full bg-gray-100
|
||||
ring-1 ring-gray-200 transition-all duration-200 hover:ring-2 hover:ring-gray-300 dark:bg-white/8 dark:ring-white/10 dark:hover:ring-white/20"
|
||||
type="button"
|
||||
on:click={() => {
|
||||
profileImageInputElement.click();
|
||||
@@ -91,61 +94,154 @@
|
||||
<img
|
||||
src={profileImageUrl !== '' ? profileImageUrl : generateInitialsImage(user?.name)}
|
||||
alt="profile"
|
||||
class=" rounded-full {imageClassName} object-cover"
|
||||
class="h-full w-full object-cover"
|
||||
/>
|
||||
|
||||
<div class="absolute bottom-0 right-0 opacity-0 group-hover:opacity-100 transition">
|
||||
<div class="p-1 rounded-full bg-white text-black border-gray-100 shadow">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 20 20"
|
||||
fill="currentColor"
|
||||
class="size-3"
|
||||
>
|
||||
<path
|
||||
d="m2.695 14.762-1.262 3.155a.5.5 0 0 0 .65.65l3.155-1.262a4 4 0 0 0 1.343-.886L17.5 5.501a2.121 2.121 0 0 0-3-3L3.58 13.419a4 4 0 0 0-.885 1.343Z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<div
|
||||
class="absolute inset-0 flex items-center justify-center bg-black/0 transition-colors duration-200 group-hover:bg-black/40"
|
||||
>
|
||||
<svg
|
||||
class="h-4 w-4 text-white opacity-0 drop-shadow-sm transition-opacity duration-200 group-hover:opacity-100"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
stroke-width="1.5"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="M6.827 6.175A2.31 2.31 0 0 1 5.186 7.23c-.38.054-.757.112-1.134.175C2.999 7.58 2.25 8.507 2.25 9.574V18a2.25 2.25 0 0 0 2.25 2.25h15A2.25 2.25 0 0 0 21.75 18V9.574c0-1.067-.75-1.994-1.802-2.169a47.865 47.865 0 0 0-1.134-.175 2.31 2.31 0 0 1-1.64-1.055l-.822-1.316a2.192 2.192 0 0 0-1.736-1.039 48.774 48.774 0 0 0-5.232 0 2.192 2.192 0 0 0-1.736 1.039l-.821 1.316Z"
|
||||
/>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="M16.5 12.75a4.5 4.5 0 1 1-9 0 4.5 4.5 0 0 1 9 0Z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
<div class="flex flex-col w-full justify-center mt-2">
|
||||
<button
|
||||
class=" text-xs text-center text-gray-500 rounded-lg py-0.5 opacity-0 group-hover:opacity-100 transition-all"
|
||||
type="button"
|
||||
on:click={async () => {
|
||||
profileImageUrl = `${WEBUI_BASE_URL}/user.png`;
|
||||
}}>{$i18n.t('Remove')}</button
|
||||
>
|
||||
|
||||
<button
|
||||
class=" text-xs text-center text-gray-800 dark:text-gray-400 rounded-lg py-0.5 opacity-0 group-hover:opacity-100 transition-all"
|
||||
type="button"
|
||||
on:click={async () => {
|
||||
if (canvasPixelTest()) {
|
||||
profileImageUrl = generateInitialsImage(user?.name);
|
||||
} else {
|
||||
toast.info(
|
||||
$i18n.t(
|
||||
'Fingerprint spoofing detected: Unable to use initials as avatar. Defaulting to default profile image.'
|
||||
),
|
||||
{
|
||||
duration: 1000 * 10
|
||||
<div class="flex flex-col gap-1">
|
||||
<span class="text-sm font-medium text-gray-900 dark:text-white"
|
||||
>{displayName || user?.name}</span
|
||||
>
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<button
|
||||
class="text-[0.6875rem] text-gray-400 transition-colors duration-100 hover:text-gray-600 dark:hover:text-gray-300"
|
||||
type="button"
|
||||
on:click={() => {
|
||||
profileImageInputElement.click();
|
||||
}}>{$i18n.t('Upload Photo')}</button
|
||||
>
|
||||
<span class="text-[0.6875rem] text-gray-300 dark:text-gray-700">·</span>
|
||||
<button
|
||||
class="text-[0.6875rem] text-gray-400 transition-colors duration-100 hover:text-gray-600 dark:hover:text-gray-300"
|
||||
type="button"
|
||||
on:click={async () => {
|
||||
profileImageUrl = `${WEBUI_BASE_URL}/user.png`;
|
||||
}}>{$i18n.t('Remove')}</button
|
||||
>
|
||||
<span class="text-[0.6875rem] text-gray-300 dark:text-gray-700">·</span>
|
||||
<button
|
||||
class="text-[0.6875rem] text-gray-400 transition-colors duration-100 hover:text-gray-600 dark:hover:text-gray-300"
|
||||
type="button"
|
||||
on:click={async () => {
|
||||
if (canvasPixelTest()) {
|
||||
profileImageUrl = generateInitialsImage(user?.name);
|
||||
} else {
|
||||
toast.info(
|
||||
$i18n.t(
|
||||
'Fingerprint spoofing detected: Unable to use initials as avatar. Defaulting to default profile image.'
|
||||
),
|
||||
{
|
||||
duration: 1000 * 10
|
||||
}
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
}}>{$i18n.t('Initials')}</button
|
||||
>
|
||||
}}>{$i18n.t('Initials')}</button
|
||||
>
|
||||
<span class="text-[0.6875rem] text-gray-300 dark:text-gray-700">·</span>
|
||||
<button
|
||||
class="text-[0.6875rem] text-gray-400 transition-colors duration-100 hover:text-gray-600 dark:hover:text-gray-300"
|
||||
type="button"
|
||||
on:click={async () => {
|
||||
const url = await getGravatarUrl(localStorage.token, user?.email);
|
||||
|
||||
<button
|
||||
class=" text-xs text-center text-gray-800 dark:text-gray-400 rounded-lg py-0.5 opacity-0 group-hover:opacity-100 transition-all"
|
||||
type="button"
|
||||
on:click={async () => {
|
||||
const url = await getGravatarUrl(localStorage.token, user?.email);
|
||||
|
||||
profileImageUrl = url;
|
||||
}}>{$i18n.t('Gravatar')}</button
|
||||
>
|
||||
profileImageUrl = url;
|
||||
}}>{$i18n.t('Gravatar')}</button
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="flex flex-col self-start group">
|
||||
<div class="self-center flex">
|
||||
<button
|
||||
class="relative rounded-full dark:bg-gray-700"
|
||||
type="button"
|
||||
on:click={() => {
|
||||
profileImageInputElement.click();
|
||||
}}
|
||||
>
|
||||
<img
|
||||
src={profileImageUrl !== '' ? profileImageUrl : generateInitialsImage(user?.name)}
|
||||
alt="profile"
|
||||
class=" rounded-full {imageClassName} object-cover"
|
||||
/>
|
||||
|
||||
<div class="absolute bottom-0 right-0 opacity-0 group-hover:opacity-100 transition">
|
||||
<div class="p-1 rounded-full bg-white text-black border-gray-100 shadow">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 20 20"
|
||||
fill="currentColor"
|
||||
class="size-3"
|
||||
>
|
||||
<path
|
||||
d="m2.695 14.762-1.262 3.155a.5.5 0 0 0 .65.65l3.155-1.262a4 4 0 0 0 1.343-.886L17.5 5.501a2.121 2.121 0 0 0-3-3L3.58 13.419a4 4 0 0 0-.885 1.343Z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
<div class="flex flex-col w-full justify-center mt-2">
|
||||
<button
|
||||
class=" text-xs text-center text-gray-500 rounded-lg py-0.5 opacity-0 group-hover:opacity-100 transition-all"
|
||||
type="button"
|
||||
on:click={async () => {
|
||||
profileImageUrl = `${WEBUI_BASE_URL}/user.png`;
|
||||
}}>{$i18n.t('Remove')}</button
|
||||
>
|
||||
|
||||
<button
|
||||
class=" text-xs text-center text-gray-800 dark:text-gray-400 rounded-lg py-0.5 opacity-0 group-hover:opacity-100 transition-all"
|
||||
type="button"
|
||||
on:click={async () => {
|
||||
if (canvasPixelTest()) {
|
||||
profileImageUrl = generateInitialsImage(user?.name);
|
||||
} else {
|
||||
toast.info(
|
||||
$i18n.t(
|
||||
'Fingerprint spoofing detected: Unable to use initials as avatar. Defaulting to default profile image.'
|
||||
),
|
||||
{
|
||||
duration: 1000 * 10
|
||||
}
|
||||
);
|
||||
}
|
||||
}}>{$i18n.t('Initials')}</button
|
||||
>
|
||||
|
||||
<button
|
||||
class=" text-xs text-center text-gray-800 dark:text-gray-400 rounded-lg py-0.5 opacity-0 group-hover:opacity-100 transition-all"
|
||||
type="button"
|
||||
on:click={async () => {
|
||||
const url = await getGravatarUrl(localStorage.token, user?.email);
|
||||
|
||||
profileImageUrl = url;
|
||||
}}>{$i18n.t('Gravatar')}</button
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
Reference in New Issue
Block a user