refac: user status

This commit is contained in:
Timothy Jaeryang Baek 2025-09-16 22:17:35 -05:00
parent b27243df81
commit aef1e06f0b
5 changed files with 131 additions and 130 deletions

View file

@ -413,7 +413,7 @@ input[type='number'] {
border-radius: 0.4rem; border-radius: 0.4rem;
box-decoration-break: clone; box-decoration-break: clone;
padding: 0.1rem 0.3rem; padding: 0.1rem 0.3rem;
@apply text-sky-700 dark:text-blue-300 bg-sky-300/15 dark:bg-sky-500/15; @apply text-sky-800 dark:text-sky-200 bg-sky-300/15 dark:bg-sky-500/15;
} }
.mention::after { .mention::after {
@ -424,7 +424,7 @@ input[type='number'] {
border-radius: 0.4rem; border-radius: 0.4rem;
box-decoration-break: clone; box-decoration-break: clone;
padding: 0.1rem 0.3rem; padding: 0.1rem 0.3rem;
@apply text-sky-700 dark:text-blue-300 bg-sky-300/15 dark:bg-sky-500/15; @apply text-sky-800 dark:text-sky-200 bg-sky-300/15 dark:bg-sky-500/15;
} }
.tiptap .suggestion::after { .tiptap .suggestion::after {

View file

@ -140,10 +140,7 @@
{#if showUserProfile} {#if showUserProfile}
<ProfilePreview user={message.user}> <ProfilePreview user={message.user}>
<ProfileImage <ProfileImage
src={message.user?.profile_image_url ?? src={message.user?.profile_image_url ?? `${WEBUI_BASE_URL}/static/favicon.png`}
($i18n.language === 'dg-DG'
? `${WEBUI_BASE_URL}/doge.png`
: `${WEBUI_BASE_URL}/static/favicon.png`)}
className={'size-8 translate-y-1 ml-0.5'} className={'size-8 translate-y-1 ml-0.5'}
/> />
</ProfilePreview> </ProfilePreview>

View file

@ -1,101 +1,24 @@
<script lang="ts"> <script lang="ts">
import { DropdownMenu } from 'bits-ui'; import { LinkPreview } from 'bits-ui';
import { getContext } from 'svelte'; import { getContext } from 'svelte';
const i18n = getContext('i18n'); const i18n = getContext('i18n');
import UserStatus from './UserStatus.svelte';
import { flyAndScale } from '$lib/utils/transitions';
import { WEBUI_BASE_URL } from '$lib/constants';
import { getUserActiveStatusById } from '$lib/apis/users';
export let side = 'right';
export let align = 'top';
export let user = null; export let user = null;
let show = false;
let active = false;
const getActiveStatus = async () => {
const res = await getUserActiveStatusById(localStorage.token, user.id).catch((error) => {
console.error('Error fetching user active status:', error);
});
if (res) {
active = res.active;
} else {
active = false;
}
};
$: if (show) {
getActiveStatus();
}
</script> </script>
<DropdownMenu.Root <LinkPreview.Root openDelay={0} closeDelay={0}>
bind:open={show} <LinkPreview.Trigger class=" cursor-pointer no-underline! font-normal! ">
closeFocus={false}
onOpenChange={(state) => {}}
typeahead={false}
>
<DropdownMenu.Trigger>
<slot /> <slot />
</DropdownMenu.Trigger> </LinkPreview.Trigger>
<slot name="content"> <LinkPreview.Content
<DropdownMenu.Content class="max-w-full w-[240px] rounded-2xl z-9999 bg-white dark:bg-black dark:text-white shadow-lg"
class="max-w-full w-[240px] rounded-lg z-9999 bg-white dark:bg-black dark:text-white shadow-lg" side="left"
sideOffset={8} align="center"
{side} sideOffset={8}
{align} >
transition={flyAndScale} <UserStatus id={user.id} />
> </LinkPreview.Content>
{#if user} </LinkPreview.Root>
<div class=" flex flex-col gap-2 w-full rounded-lg">
<div class="py-8 relative bg-gray-900 rounded-t-lg">
<img
crossorigin="anonymous"
src={user?.profile_image_url ?? `${WEBUI_BASE_URL}/static/favicon.png`}
class=" absolute -bottom-5 left-3 size-12 ml-0.5 object-cover rounded-full -translate-y-[1px]"
alt="profile"
/>
</div>
<div class=" flex flex-col pt-4 pb-2.5 px-4">
<div class=" -mb-1">
<span class="font-medium text-sm line-clamp-1"> {user.name} </span>
</div>
<div class=" flex items-center gap-2">
{#if active}
<div>
<span class="relative flex size-2">
<span
class="animate-ping absolute inline-flex h-full w-full rounded-full bg-green-400 opacity-75"
/>
<span class="relative inline-flex rounded-full size-2 bg-green-500" />
</span>
</div>
<div class=" -translate-y-[1px]">
<span class="text-xs"> {$i18n.t('Active')} </span>
</div>
{:else}
<div>
<span class="relative flex size-2">
<span class="relative inline-flex rounded-full size-2 bg-gray-500" />
</span>
</div>
<div class=" -translate-y-[1px]">
<span class="text-xs"> {$i18n.t('Away')} </span>
</div>
{/if}
</div>
</div>
</div>
{/if}
</DropdownMenu.Content>
</slot>
</DropdownMenu.Root>

View file

@ -0,0 +1,61 @@
<script lang="ts">
import { getContext, onMount } from 'svelte';
const i18n = getContext('i18n');
import { WEBUI_BASE_URL } from '$lib/constants';
import { getUserActiveStatusById, getUserById } from '$lib/apis/users';
export let id = null;
let user = null;
onMount(async () => {
if (id) {
user = await getUserById(localStorage.token, id).catch((error) => {
console.error('Error fetching user by ID:', error);
return null;
});
}
});
</script>
{#if user}
<div class=" flex gap-3.5 w-full py-3 px-3 items-center">
<div class=" items-center flex shrink-0">
<img
crossorigin="anonymous"
src={user?.profile_image_url ?? `${WEBUI_BASE_URL}/static/favicon.png`}
class=" size-12 object-cover rounded-xl"
alt="profile"
/>
</div>
<div class=" flex flex-col w-full flex-1">
<div class="mb-0.5">
<span class="font-medium line-clamp-1"> {user.name} </span>
</div>
<div class=" flex items-center gap-2">
{#if user?.active}
<div>
<span class="relative flex size-2">
<span
class="animate-ping absolute inline-flex h-full w-full rounded-full bg-green-400 opacity-75"
/>
<span class="relative inline-flex rounded-full size-2 bg-green-500" />
</span>
</div>
<span class="text-xs"> {$i18n.t('Active')} </span>
{:else}
<div>
<span class="relative flex size-2">
<span class="relative inline-flex rounded-full size-2 bg-gray-500" />
</span>
</div>
<span class="text-xs"> {$i18n.t('Away')} </span>
{/if}
</div>
</div>
</div>
{/if}

View file

@ -1,9 +1,14 @@
<script lang="ts"> <script lang="ts">
import type { Token } from 'marked'; import type { Token } from 'marked';
import Tooltip from '$lib/components/common/Tooltip.svelte'; import { LinkPreview } from 'bits-ui';
import { getContext } from 'svelte';
import { goto } from '$app/navigation'; import { goto } from '$app/navigation';
import { channels, models } from '$lib/stores'; import { channels, models } from '$lib/stores';
import i18n from '$lib/i18n'; import UserStatus from '$lib/components/channel/Messages/Message/UserStatus.svelte';
const i18n = getContext('i18n');
export let token: Token; export let token: Token;
@ -61,35 +66,50 @@
}; };
</script> </script>
<Tooltip <LinkPreview.Root openDelay={0} closeDelay={0}>
as="span" <LinkPreview.Trigger class="mention cursor-pointer no-underline! font-normal! ">
className="mention cursor-pointer" <!-- svelte-ignore a11y-click-events-have-key-events -->
onClick={async () => { <!-- svelte-ignore a11y-no-static-element-interactions -->
if (triggerChar === '@') {
if (idType === 'U') { <span
// Open user profile on:click={async () => {
console.log('Clicked user mention', id); if (triggerChar === '@') {
} else if (idType === 'A') { if (idType === 'U') {
// Open agent/assistant/ai model profile // Open user profile
console.log('Clicked agent mention', id); console.log('Clicked user mention', id);
await goto(`/?model=${id}`); } else if (idType === 'A') {
} // Open agent/assistant/ai model profile
} else if (triggerChar === '#') { console.log('Clicked agent mention', id);
if (idType === 'C') { await goto(`/?model=${id}`);
// Open channel }
if ($channels.find((c) => c.id === id)) { } else if (triggerChar === '#') {
await goto(`/channels/${id}`); if (idType === 'C') {
// Open channel
if ($channels.find((c) => c.id === id)) {
await goto(`/channels/${id}`);
}
} else if (idType === 'T') {
// Open thread
}
} else {
// Unknown trigger char, just log
console.log('Clicked mention', id);
} }
} else if (idType === 'T') { }}
// Open thread >
} {triggerChar}{label}
} else { </span>
// Unknown trigger char, just log </LinkPreview.Trigger>
console.log('Clicked mention', id);
} <LinkPreview.Content
}} class="max-w-full w-[240px] rounded-2xl z-9999 bg-white dark:bg-black dark:text-white shadow-lg"
content={id} side="top"
placement="top" align="start"
> sideOffset={6}
{triggerChar}{label} >
</Tooltip> {#if triggerChar === '@' && idType === 'U'}
<UserStatus {id} />
{/if}
<!-- <div class="flex space-x-4">HI</div> -->
</LinkPreview.Content>
</LinkPreview.Root>