fix: add missing app list page online users

This commit is contained in:
hjlarry 2026-04-11 10:47:04 +08:00
parent 8e38a19de7
commit 0220006c50
2 changed files with 71 additions and 3 deletions

View File

@ -5,6 +5,7 @@ import type { HtmlContentProps } from '@/app/components/base/popover'
import type { Tag } from '@/app/components/base/tag-management/constant'
import type { CreateAppModalProps } from '@/app/components/explore/create-app-modal'
import type { EnvironmentVariable } from '@/app/components/workflow/types'
import type { WorkflowOnlineUser } from '@/models/app'
import type { App } from '@/types/app'
import { RiBuildingLine, RiGlobalLine, RiLockLine, RiMoreFill, RiVerifiedBadgeLine } from '@remixicon/react'
import * as React from 'react'
@ -27,6 +28,7 @@ import {
AlertDialogTitle,
} from '@/app/components/base/ui/alert-dialog'
import { toast } from '@/app/components/base/ui/toast'
import { UserAvatarList } from '@/app/components/base/user-avatar-list'
import { NEED_REFRESH_APP_LIST_KEY } from '@/config'
import { useAppContext } from '@/context/app-context'
import { useGlobalPublicStore } from '@/context/global-public-context'
@ -65,10 +67,11 @@ const AccessControl = dynamic(() => import('@/app/components/app/app-access-cont
type AppCardProps = {
app: App
onlineUsers?: WorkflowOnlineUser[]
onRefresh?: () => void
}
const AppCard = ({ app, onRefresh }: AppCardProps) => {
const AppCard = ({ app, onlineUsers = [], onRefresh }: AppCardProps) => {
const { t } = useTranslation()
const deleteAppNameInputId = useId()
const systemFeatures = useGlobalPublicStore(s => s.systemFeatures)
@ -360,6 +363,20 @@ const AppCard = ({ app, onRefresh }: AppCardProps) => {
return `${t('segment.editedAt', { ns: 'datasetDocuments' })} ${timeText}`
}, [app.updated_at, app.created_at, t])
const onlinePresenceUsers = useMemo(() => {
return onlineUsers
.map((user, index) => {
const id = user.user_id || user.sid || `${app.id}-online-${index}`
const name = user.username || user.user_id || user.sid || `${index + 1}`
return {
id,
name,
avatar_url: user.avatar || null,
}
})
.filter(user => Boolean(user.id))
}, [app.id, onlineUsers])
return (
<>
<div
@ -389,6 +406,11 @@ const AppCard = ({ app, onRefresh }: AppCardProps) => {
<div>·</div>
<div className="truncate" title={EditTimeText}>{EditTimeText}</div>
</div>
{onlinePresenceUsers.length > 0 && (
<div className="mt-1">
<UserAvatarList users={onlinePresenceUsers} size="xxs" maxVisible={4} />
</div>
)}
</div>
<div className="flex h-5 w-5 shrink-0 items-center justify-center">
{app.access_mode === AccessMode.PUBLIC && (

View File

@ -1,9 +1,10 @@
'use client'
import type { FC } from 'react'
import type { WorkflowOnlineUser } from '@/models/app'
import { useDebounceFn } from 'ahooks'
import { parseAsStringLiteral, useQueryState } from 'nuqs'
import { useCallback, useEffect, useRef, useState } from 'react'
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next'
import Checkbox from '@/app/components/base/checkbox'
import Input from '@/app/components/base/input'
@ -15,6 +16,7 @@ import { useAppContext } from '@/context/app-context'
import { useGlobalPublicStore } from '@/context/global-public-context'
import { CheckModal } from '@/hooks/use-pay'
import dynamic from '@/next/dynamic'
import { fetchWorkflowOnlineUsers } from '@/service/apps'
import { useInfiniteAppList } from '@/service/use-apps'
import { AppModeEnum, AppModes } from '@/types/app'
import { cn } from '@/utils/classnames'
@ -68,6 +70,7 @@ const List: FC<Props> = ({
const containerRef = useRef<HTMLDivElement>(null)
const [showCreateFromDSLModal, setShowCreateFromDSLModal] = useState(false)
const [droppedDSLFile, setDroppedDSLFile] = useState<File | undefined>()
const [workflowOnlineUsersMap, setWorkflowOnlineUsersMap] = useState<Record<string, WorkflowOnlineUser[]>>({})
const setKeywords = useCallback((keywords: string) => {
setQuery(prev => ({ ...prev, keywords }))
}, [setQuery])
@ -183,6 +186,44 @@ const List: FC<Props> = ({
}, [isCreatedByMe, setQuery])
const pages = data?.pages ?? []
const workflowIds = useMemo(() => {
const ids = new Set<string>()
pages.forEach((page) => {
page.data?.forEach((app) => {
const workflowId = app.workflow?.id
if (workflowId)
ids.add(workflowId)
})
})
return Array.from(ids)
}, [pages])
const refreshWorkflowOnlineUsers = useCallback(async () => {
if (!workflowIds.length)
return
try {
const onlineUsersMap = await fetchWorkflowOnlineUsers({ workflowIds })
setWorkflowOnlineUsersMap(onlineUsersMap)
}
catch {
setWorkflowOnlineUsersMap({})
}
}, [workflowIds])
useEffect(() => {
void refreshWorkflowOnlineUsers()
}, [refreshWorkflowOnlineUsers])
useEffect(() => {
const timer = window.setInterval(() => {
void refetch()
void refreshWorkflowOnlineUsers()
}, 10000)
return () => window.clearInterval(timer)
}, [refetch, refreshWorkflowOnlineUsers])
const hasAnyApp = (pages[0]?.total ?? 0) > 0
// Show skeleton during initial load or when refetching with no previous data
const showSkeleton = isLoading || (isFetching && pages.length === 0)
@ -242,7 +283,12 @@ const List: FC<Props> = ({
if (hasAnyApp) {
return pages.flatMap(({ data: apps }) => apps).map(app => (
<AppCard key={app.id} app={app} onRefresh={refetch} />
<AppCard
key={app.id}
app={app}
onlineUsers={app.workflow?.id ? (workflowOnlineUsersMap[app.workflow.id] ?? []) : []}
onRefresh={refetch}
/>
))
}