refactor: migrate log data fetching to react query

This commit is contained in:
yyh 2025-12-09 16:13:52 +08:00
parent 27e5abc39b
commit d77837e800
No known key found for this signature in database
4 changed files with 199 additions and 96 deletions

View File

@ -2,7 +2,6 @@
import type { FC } from 'react'
import React from 'react'
import { useTranslation } from 'react-i18next'
import useSWR from 'swr'
import dayjs from 'dayjs'
import { RiCalendarLine } from '@remixicon/react'
import quarterOfYear from 'dayjs/plugin/quarterOfYear'
@ -10,7 +9,7 @@ import type { QueryParam } from './index'
import Chip from '@/app/components/base/chip'
import Input from '@/app/components/base/input'
import Sort from '@/app/components/base/sort'
import { fetchAnnotationsCount } from '@/service/log'
import { useAnnotationsCount } from '@/service/use-log'
dayjs.extend(quarterOfYear)
const today = dayjs()
@ -35,7 +34,7 @@ type IFilterProps = {
}
const Filter: FC<IFilterProps> = ({ isChatMode, appId, queryParams, setQueryParams }: IFilterProps) => {
const { data } = useSWR({ url: `/apps/${appId}/annotations/count` }, fetchAnnotationsCount)
const { data } = useAnnotationsCount(appId)
const { t } = useTranslation()
if (!data)
return null

View File

@ -1,7 +1,6 @@
'use client'
import type { FC } from 'react'
import React, { useCallback, useEffect, useState } from 'react'
import useSWR from 'swr'
import React, { useCallback, useEffect, useMemo, useState } from 'react'
import { useDebounce } from 'ahooks'
import { omit } from 'lodash-es'
import dayjs from 'dayjs'
@ -12,7 +11,7 @@ import Filter, { TIME_PERIOD_MAPPING } from './filter'
import EmptyElement from './empty-element'
import Pagination from '@/app/components/base/pagination'
import Loading from '@/app/components/base/loading'
import { fetchChatConversations, fetchCompletionConversations } from '@/service/log'
import { useChatConversations, useCompletionConversations } from '@/service/use-log'
import { APP_PAGE_LIMIT } from '@/config'
import type { App } from '@/types/app'
import { AppModeEnum } from '@/types/app'
@ -72,7 +71,7 @@ const Logs: FC<ILogsProps> = ({ appDetail }) => {
// Get the app type first
const isChatMode = appDetail.mode !== AppModeEnum.COMPLETION
const query = {
const query = useMemo(() => ({
page: currPage + 1,
limit,
...((debouncedQueryParams.period !== '9')
@ -83,25 +82,23 @@ const Logs: FC<ILogsProps> = ({ appDetail }) => {
: {}),
...(isChatMode ? { sort_by: debouncedQueryParams.sort_by } : {}),
...omit(debouncedQueryParams, ['period']),
}
}), [currPage, debouncedQueryParams, isChatMode, limit])
// When the details are obtained, proceed to the next request
const { data: chatConversations, mutate: mutateChatList } = useSWR(() => isChatMode
? {
url: `/apps/${appDetail.id}/chat-conversations`,
params: query,
}
: null, fetchChatConversations)
const { data: chatConversations, refetch: refetchChatList } = useChatConversations(appDetail.id, query as any, isChatMode)
const { data: completionConversations, mutate: mutateCompletionList } = useSWR(() => !isChatMode
? {
url: `/apps/${appDetail.id}/completion-conversations`,
params: query,
}
: null, fetchCompletionConversations)
const { data: completionConversations, refetch: refetchCompletionList } = useCompletionConversations(appDetail.id, query as any, !isChatMode)
const total = isChatMode ? chatConversations?.total : completionConversations?.total
const handleRefreshList = useCallback(() => {
if (isChatMode) {
void refetchChatList()
return
}
void refetchCompletionList()
}, [isChatMode, refetchChatList, refetchCompletionList])
const handleQueryParamsChange = useCallback((next: QueryParam) => {
setCurrPage(0)
setQueryParams(next)
@ -124,12 +121,13 @@ const Logs: FC<ILogsProps> = ({ appDetail }) => {
<p className='system-sm-regular shrink-0 text-text-tertiary'>{t('appLog.description')}</p>
<div className='flex max-h-[calc(100%-16px)] flex-1 grow flex-col py-4'>
<Filter isChatMode={isChatMode} appId={appDetail.id} queryParams={queryParams} setQueryParams={handleQueryParamsChange} />
{total === undefined
? <Loading type='app' />
: total > 0
? <List logs={isChatMode ? chatConversations : completionConversations} appDetail={appDetail} onRefresh={isChatMode ? mutateChatList : mutateCompletionList} />
: <EmptyElement appDetail={appDetail} />
}
{(() => {
if (total === undefined)
return <Loading type='app' />
if (total > 0)
return <List logs={isChatMode ? chatConversations : completionConversations} appDetail={appDetail} onRefresh={handleRefreshList} />
return <EmptyElement appDetail={appDetail} />
})()}
{/* Show Pagination only if the total is more than the limit */}
{(total && total > APP_PAGE_LIMIT)
? <Pagination

View File

@ -1,7 +1,6 @@
'use client'
import type { FC } from 'react'
import React, { useCallback, useEffect, useRef, useState } from 'react'
import useSWR from 'swr'
import {
HandThumbDownIcon,
HandThumbUpIcon,
@ -18,7 +17,7 @@ import { usePathname, useRouter, useSearchParams } from 'next/navigation'
import type { ChatItemInTree } from '../../base/chat/types'
import Indicator from '../../header/indicator'
import VarPanel from './var-panel'
import type { FeedbackFunc, FeedbackType, IChatItem, SubmitAnnotationFunc } from '@/app/components/base/chat/chat/type'
import type { FeedbackFunc, IChatItem, SubmitAnnotationFunc } from '@/app/components/base/chat/chat/type'
import type { Annotation, ChatConversationGeneralDetail, ChatConversationsResponse, ChatMessage, ChatMessagesRequest, CompletionConversationGeneralDetail, CompletionConversationsResponse, LogAnnotation } from '@/models/log'
import { type App, AppModeEnum } from '@/types/app'
import ActionButton from '@/app/components/base/action-button'
@ -26,7 +25,8 @@ import Loading from '@/app/components/base/loading'
import Drawer from '@/app/components/base/drawer'
import Chat from '@/app/components/base/chat/chat'
import { ToastContext } from '@/app/components/base/toast'
import { fetchChatConversationDetail, fetchChatMessages, fetchCompletionConversationDetail, updateLogMessageAnnotations, updateLogMessageFeedbacks } from '@/service/log'
import { fetchChatMessages } from '@/service/log'
import { useChatConversationDetail, useCompletionConversationDetail, useUpdateLogMessageAnnotation, useUpdateLogMessageFeedback } from '@/service/use-log'
import ModelInfo from '@/app/components/app/log/model-info'
import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints'
import TextGeneration from '@/app/components/app/text-generate/item'
@ -199,6 +199,39 @@ type IDetailPanel = {
onSubmitAnnotation: SubmitAnnotationFunc
}
const useConversationDetailActions = (appId: string | undefined, detailQueryKey: any) => {
const { notify } = useContext(ToastContext)
const { t } = useTranslation()
const { mutateAsync: submitFeedback } = useUpdateLogMessageFeedback(appId, detailQueryKey)
const { mutateAsync: submitAnnotation } = useUpdateLogMessageAnnotation(appId, detailQueryKey)
const handleFeedback = useCallback<FeedbackFunc>(async (mid, { rating, content }) => {
try {
await submitFeedback({ mid, rating, content })
notify({ type: 'success', message: t('common.actionMsg.modifiedSuccessfully') })
return true
}
catch {
notify({ type: 'error', message: t('common.actionMsg.modifiedUnsuccessfully') })
return false
}
}, [notify, submitFeedback, t])
const handleAnnotation = useCallback<SubmitAnnotationFunc>(async (mid, value) => {
try {
await submitAnnotation({ mid, value })
notify({ type: 'success', message: t('common.actionMsg.modifiedSuccessfully') })
return true
}
catch {
notify({ type: 'error', message: t('common.actionMsg.modifiedUnsuccessfully') })
return false
}
}, [notify, submitAnnotation, t])
return { handleFeedback, handleAnnotation }
}
function DetailPanel({ detail, onFeedback }: IDetailPanel) {
const MIN_ITEMS_FOR_SCROLL_LOADING = 8
const SCROLL_THRESHOLD_PX = 50
@ -369,7 +402,7 @@ function DetailPanel({ detail, onFeedback }: IDetailPanel) {
notify({ type: 'error', message: t('common.actionMsg.modifiedUnsuccessfully') })
return false
}
}, [allChatItems, appDetail?.id, t])
}, [allChatItems, appDetail?.id, notify, t])
const fetchInitiated = useRef(false)
@ -516,7 +549,7 @@ function DetailPanel({ detail, onFeedback }: IDetailPanel) {
finally {
setIsLoading(false)
}
}, [allChatItems, detail.id, hasMore, isLoading, timezone, t, appDetail])
}, [allChatItems, detail?.model_config?.configs?.introduction, detail.id, hasMore, isLoading, timezone, t, appDetail])
useEffect(() => {
const scrollableDiv = document.getElementById('scrollableDiv')
@ -811,39 +844,8 @@ function DetailPanel({ detail, onFeedback }: IDetailPanel) {
*/
const CompletionConversationDetailComp: FC<{ appId?: string; conversationId?: string }> = ({ appId, conversationId }) => {
// Text Generator App Session Details Including Message List
const detailParams = ({ url: `/apps/${appId}/completion-conversations/${conversationId}` })
const { data: conversationDetail, mutate: conversationDetailMutate } = useSWR(() => (appId && conversationId) ? detailParams : null, fetchCompletionConversationDetail)
const { notify } = useContext(ToastContext)
const { t } = useTranslation()
const handleFeedback = async (mid: string, { rating, content }: FeedbackType): Promise<boolean> => {
try {
await updateLogMessageFeedbacks({
url: `/apps/${appId}/feedbacks`,
body: { message_id: mid, rating, content: content ?? undefined },
})
conversationDetailMutate()
notify({ type: 'success', message: t('common.actionMsg.modifiedSuccessfully') })
return true
}
catch {
notify({ type: 'error', message: t('common.actionMsg.modifiedUnsuccessfully') })
return false
}
}
const handleAnnotation = async (mid: string, value: string): Promise<boolean> => {
try {
await updateLogMessageAnnotations({ url: `/apps/${appId}/annotations`, body: { message_id: mid, content: value } })
conversationDetailMutate()
notify({ type: 'success', message: t('common.actionMsg.modifiedSuccessfully') })
return true
}
catch {
notify({ type: 'error', message: t('common.actionMsg.modifiedUnsuccessfully') })
return false
}
}
const { data: conversationDetail, queryKey: detailQueryKey } = useCompletionConversationDetail(appId, conversationId)
const { handleFeedback, handleAnnotation } = useConversationDetailActions(appId, detailQueryKey)
if (!conversationDetail)
return null
@ -859,37 +861,8 @@ const CompletionConversationDetailComp: FC<{ appId?: string; conversationId?: st
* Chat App Conversation Detail Component
*/
const ChatConversationDetailComp: FC<{ appId?: string; conversationId?: string }> = ({ appId, conversationId }) => {
const detailParams = { url: `/apps/${appId}/chat-conversations/${conversationId}` }
const { data: conversationDetail } = useSWR(() => (appId && conversationId) ? detailParams : null, fetchChatConversationDetail)
const { notify } = useContext(ToastContext)
const { t } = useTranslation()
const handleFeedback = async (mid: string, { rating, content }: FeedbackType): Promise<boolean> => {
try {
await updateLogMessageFeedbacks({
url: `/apps/${appId}/feedbacks`,
body: { message_id: mid, rating, content: content ?? undefined },
})
notify({ type: 'success', message: t('common.actionMsg.modifiedSuccessfully') })
return true
}
catch {
notify({ type: 'error', message: t('common.actionMsg.modifiedUnsuccessfully') })
return false
}
}
const handleAnnotation = async (mid: string, value: string): Promise<boolean> => {
try {
await updateLogMessageAnnotations({ url: `/apps/${appId}/annotations`, body: { message_id: mid, content: value } })
notify({ type: 'success', message: t('common.actionMsg.modifiedSuccessfully') })
return true
}
catch {
notify({ type: 'error', message: t('common.actionMsg.modifiedUnsuccessfully') })
return false
}
}
const { data: conversationDetail, queryKey: detailQueryKey } = useChatConversationDetail(appId, conversationId)
const { handleFeedback, handleAnnotation } = useConversationDetailActions(appId, detailQueryKey)
if (!conversationDetail)
return null
@ -997,7 +970,7 @@ const ConversationList: FC<IConversationList> = ({ logs, appDetail, onRefresh })
if (pendingConversationCacheRef.current?.id === conversationIdInUrl || matchedConversation)
pendingConversationCacheRef.current = undefined
}, [conversationIdInUrl, currentConversation, isChatMode, logs?.data, showDrawer])
}, [conversationIdInUrl, currentConversation, currentConversationId, isChatMode, logs?.data, showDrawer])
const onCloseDrawer = useCallback(() => {
onRefresh()

133
web/service/use-log.ts Normal file
View File

@ -0,0 +1,133 @@
import type { QueryKey } from '@tanstack/react-query'
import {
useMutation,
useQuery,
useQueryClient,
} from '@tanstack/react-query'
import {
fetchAnnotationsCount,
fetchChatConversationDetail,
fetchChatConversations,
fetchCompletionConversationDetail,
fetchCompletionConversations,
updateLogMessageAnnotations,
updateLogMessageFeedbacks,
} from '@/service/log'
import type {
AnnotationsCountResponse,
ChatConversationFullDetailResponse,
ChatConversationsRequest,
ChatConversationsResponse,
CompletionConversationFullDetailResponse,
CompletionConversationsRequest,
CompletionConversationsResponse,
MessageRating,
} from '@/models/log'
type FeedbackPayload = {
mid: string
rating: MessageRating
content?: string | null
}
type AnnotationPayload = {
mid: string
value: string
}
export const chatConversationsKey = (appId?: string, params?: ChatConversationsRequest): QueryKey => ['chat-conversations', appId, params]
export const completionConversationsKey = (appId?: string, params?: CompletionConversationsRequest): QueryKey => ['completion-conversations', appId, params]
export const completionConversationDetailKey = (appId?: string, conversationId?: string): QueryKey => ['completion-conversation-detail', appId, conversationId]
export const chatConversationDetailKey = (appId?: string, conversationId?: string): QueryKey => ['chat-conversation-detail', appId, conversationId]
export const annotationsCountKey = (appId?: string): QueryKey => ['annotations-count', appId]
export const useChatConversations = (appId?: string, params?: ChatConversationsRequest, enabled = true) => {
const queryKey = chatConversationsKey(appId, params)
const queryResult = useQuery<ChatConversationsResponse>({
queryKey,
queryFn: () => fetchChatConversations({ url: `/apps/${appId}/chat-conversations`, params }),
enabled: Boolean(appId && enabled),
})
return { ...queryResult, queryKey }
}
export const useCompletionConversations = (appId?: string, params?: CompletionConversationsRequest, enabled = true) => {
const queryKey = completionConversationsKey(appId, params)
const queryResult = useQuery<CompletionConversationsResponse>({
queryKey,
queryFn: () => fetchCompletionConversations({ url: `/apps/${appId}/completion-conversations`, params }),
enabled: Boolean(appId && enabled),
})
return { ...queryResult, queryKey }
}
export const useAnnotationsCount = (appId?: string) => {
const queryKey = annotationsCountKey(appId)
const queryResult = useQuery<AnnotationsCountResponse>({
queryKey,
queryFn: () => fetchAnnotationsCount({ url: `/apps/${appId}/annotations/count` }),
enabled: Boolean(appId),
})
return { ...queryResult, queryKey }
}
export const useCompletionConversationDetail = (appId?: string, conversationId?: string) => {
const queryKey = completionConversationDetailKey(appId, conversationId)
const queryResult = useQuery<CompletionConversationFullDetailResponse>({
queryKey,
queryFn: () => fetchCompletionConversationDetail({ url: `/apps/${appId}/completion-conversations/${conversationId}` }),
enabled: Boolean(appId && conversationId),
})
return { ...queryResult, queryKey }
}
export const useChatConversationDetail = (appId?: string, conversationId?: string) => {
const queryKey = chatConversationDetailKey(appId, conversationId)
const queryResult = useQuery<ChatConversationFullDetailResponse>({
queryKey,
queryFn: () => fetchChatConversationDetail({ url: `/apps/${appId}/chat-conversations/${conversationId}` }),
enabled: Boolean(appId && conversationId),
})
return { ...queryResult, queryKey }
}
export const useUpdateLogMessageFeedback = (appId?: string, invalidateKey?: QueryKey) => {
const queryClient = useQueryClient()
return useMutation({
mutationFn: async ({ mid, rating, content }: FeedbackPayload) => {
return await updateLogMessageFeedbacks({
url: `/apps/${appId}/feedbacks`,
body: { message_id: mid, rating, content: content ?? undefined },
})
},
onSuccess: () => {
if (invalidateKey)
queryClient.invalidateQueries({ queryKey: invalidateKey })
},
})
}
export const useUpdateLogMessageAnnotation = (appId?: string, invalidateKey?: QueryKey) => {
const queryClient = useQueryClient()
return useMutation({
mutationFn: async ({ mid, value }: AnnotationPayload) => {
return await updateLogMessageAnnotations({
url: `/apps/${appId}/annotations`,
body: { message_id: mid, content: value },
})
},
onSuccess: () => {
if (invalidateKey)
queryClient.invalidateQueries({ queryKey: invalidateKey })
},
})
}