diff --git a/web/app/components/app/log/filter.tsx b/web/app/components/app/log/filter.tsx index 6e259a2f19..3da585376f 100644 --- a/web/app/components/app/log/filter.tsx +++ b/web/app/components/app/log/filter.tsx @@ -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 = ({ 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 diff --git a/web/app/components/app/log/index.tsx b/web/app/components/app/log/index.tsx index 4fda71bece..98a794a6bd 100644 --- a/web/app/components/app/log/index.tsx +++ b/web/app/components/app/log/index.tsx @@ -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 = ({ 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 = ({ 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 = ({ appDetail }) => {

{t('appLog.description')}

- {total === undefined - ? - : total > 0 - ? - : - } + {(() => { + if (total === undefined) + return + if (total > 0) + return + return + })()} {/* Show Pagination only if the total is more than the limit */} {(total && total > APP_PAGE_LIMIT) ? { + const { notify } = useContext(ToastContext) + const { t } = useTranslation() + const { mutateAsync: submitFeedback } = useUpdateLogMessageFeedback(appId, detailQueryKey) + const { mutateAsync: submitAnnotation } = useUpdateLogMessageAnnotation(appId, detailQueryKey) + + const handleFeedback = useCallback(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(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 => { - 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 => { - 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 => { - 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 => { - 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 = ({ 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() diff --git a/web/service/use-log.ts b/web/service/use-log.ts new file mode 100644 index 0000000000..92366ea1e5 --- /dev/null +++ b/web/service/use-log.ts @@ -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({ + 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({ + 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({ + 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({ + 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({ + 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 }) + }, + }) +}