mirror of
https://github.com/langgenius/dify.git
synced 2025-12-19 17:27:16 -05:00
Refactor apps service toward TanStack Query (#29004)
This commit is contained in:
@@ -1,6 +1,5 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
import useSWR from 'swr'
|
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import {
|
import {
|
||||||
RiGraduationCapFill,
|
RiGraduationCapFill,
|
||||||
@@ -23,8 +22,9 @@ import PremiumBadge from '@/app/components/base/premium-badge'
|
|||||||
import { useGlobalPublicStore } from '@/context/global-public-context'
|
import { useGlobalPublicStore } from '@/context/global-public-context'
|
||||||
import EmailChangeModal from './email-change-modal'
|
import EmailChangeModal from './email-change-modal'
|
||||||
import { validPassword } from '@/config'
|
import { validPassword } from '@/config'
|
||||||
import { fetchAppList } from '@/service/apps'
|
|
||||||
import type { App } from '@/types/app'
|
import type { App } from '@/types/app'
|
||||||
|
import { useAppList } from '@/service/use-apps'
|
||||||
|
|
||||||
const titleClassName = `
|
const titleClassName = `
|
||||||
system-sm-semibold text-text-secondary
|
system-sm-semibold text-text-secondary
|
||||||
@@ -36,7 +36,7 @@ const descriptionClassName = `
|
|||||||
export default function AccountPage() {
|
export default function AccountPage() {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const { systemFeatures } = useGlobalPublicStore()
|
const { systemFeatures } = useGlobalPublicStore()
|
||||||
const { data: appList } = useSWR({ url: '/apps', params: { page: 1, limit: 100, name: '' } }, fetchAppList)
|
const { data: appList } = useAppList({ page: 1, limit: 100, name: '' })
|
||||||
const apps = appList?.data || []
|
const apps = appList?.data || []
|
||||||
const { mutateUserProfile, userProfile } = useAppContext()
|
const { mutateUserProfile, userProfile } = useAppContext()
|
||||||
const { isEducationAccount } = useProviderContext()
|
const { isEducationAccount } = useProviderContext()
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import type { FC } from 'react'
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import ReactECharts from 'echarts-for-react'
|
import ReactECharts from 'echarts-for-react'
|
||||||
import type { EChartsOption } from 'echarts'
|
import type { EChartsOption } from 'echarts'
|
||||||
import useSWR from 'swr'
|
|
||||||
import type { Dayjs } from 'dayjs'
|
import type { Dayjs } from 'dayjs'
|
||||||
import dayjs from 'dayjs'
|
import dayjs from 'dayjs'
|
||||||
import { get } from 'lodash-es'
|
import { get } from 'lodash-es'
|
||||||
@@ -13,7 +12,20 @@ import { formatNumber } from '@/utils/format'
|
|||||||
import Basic from '@/app/components/app-sidebar/basic'
|
import Basic from '@/app/components/app-sidebar/basic'
|
||||||
import Loading from '@/app/components/base/loading'
|
import Loading from '@/app/components/base/loading'
|
||||||
import type { AppDailyConversationsResponse, AppDailyEndUsersResponse, AppDailyMessagesResponse, AppTokenCostsResponse } from '@/models/app'
|
import type { AppDailyConversationsResponse, AppDailyEndUsersResponse, AppDailyMessagesResponse, AppTokenCostsResponse } from '@/models/app'
|
||||||
import { getAppDailyConversations, getAppDailyEndUsers, getAppDailyMessages, getAppStatistics, getAppTokenCosts, getWorkflowDailyConversations } from '@/service/apps'
|
import {
|
||||||
|
useAppAverageResponseTime,
|
||||||
|
useAppAverageSessionInteractions,
|
||||||
|
useAppDailyConversations,
|
||||||
|
useAppDailyEndUsers,
|
||||||
|
useAppDailyMessages,
|
||||||
|
useAppSatisfactionRate,
|
||||||
|
useAppTokenCosts,
|
||||||
|
useAppTokensPerSecond,
|
||||||
|
useWorkflowAverageInteractions,
|
||||||
|
useWorkflowDailyConversations,
|
||||||
|
useWorkflowDailyTerminals,
|
||||||
|
useWorkflowTokenCosts,
|
||||||
|
} from '@/service/use-apps'
|
||||||
const valueFormatter = (v: string | number) => v
|
const valueFormatter = (v: string | number) => v
|
||||||
|
|
||||||
const COLOR_TYPE_MAP = {
|
const COLOR_TYPE_MAP = {
|
||||||
@@ -272,8 +284,8 @@ const getDefaultChartData = ({ start, end, key = 'count' }: { start: string; end
|
|||||||
|
|
||||||
export const MessagesChart: FC<IBizChartProps> = ({ id, period }) => {
|
export const MessagesChart: FC<IBizChartProps> = ({ id, period }) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const { data: response } = useSWR({ url: `/apps/${id}/statistics/daily-messages`, params: period.query }, getAppDailyMessages)
|
const { data: response, isLoading } = useAppDailyMessages(id, period.query)
|
||||||
if (!response)
|
if (isLoading || !response)
|
||||||
return <Loading />
|
return <Loading />
|
||||||
const noDataFlag = !response.data || response.data.length === 0
|
const noDataFlag = !response.data || response.data.length === 0
|
||||||
return <Chart
|
return <Chart
|
||||||
@@ -286,8 +298,8 @@ export const MessagesChart: FC<IBizChartProps> = ({ id, period }) => {
|
|||||||
|
|
||||||
export const ConversationsChart: FC<IBizChartProps> = ({ id, period }) => {
|
export const ConversationsChart: FC<IBizChartProps> = ({ id, period }) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const { data: response } = useSWR({ url: `/apps/${id}/statistics/daily-conversations`, params: period.query }, getAppDailyConversations)
|
const { data: response, isLoading } = useAppDailyConversations(id, period.query)
|
||||||
if (!response)
|
if (isLoading || !response)
|
||||||
return <Loading />
|
return <Loading />
|
||||||
const noDataFlag = !response.data || response.data.length === 0
|
const noDataFlag = !response.data || response.data.length === 0
|
||||||
return <Chart
|
return <Chart
|
||||||
@@ -301,8 +313,8 @@ export const ConversationsChart: FC<IBizChartProps> = ({ id, period }) => {
|
|||||||
export const EndUsersChart: FC<IBizChartProps> = ({ id, period }) => {
|
export const EndUsersChart: FC<IBizChartProps> = ({ id, period }) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
|
||||||
const { data: response } = useSWR({ url: `/apps/${id}/statistics/daily-end-users`, id, params: period.query }, getAppDailyEndUsers)
|
const { data: response, isLoading } = useAppDailyEndUsers(id, period.query)
|
||||||
if (!response)
|
if (isLoading || !response)
|
||||||
return <Loading />
|
return <Loading />
|
||||||
const noDataFlag = !response.data || response.data.length === 0
|
const noDataFlag = !response.data || response.data.length === 0
|
||||||
return <Chart
|
return <Chart
|
||||||
@@ -315,8 +327,8 @@ export const EndUsersChart: FC<IBizChartProps> = ({ id, period }) => {
|
|||||||
|
|
||||||
export const AvgSessionInteractions: FC<IBizChartProps> = ({ id, period }) => {
|
export const AvgSessionInteractions: FC<IBizChartProps> = ({ id, period }) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const { data: response } = useSWR({ url: `/apps/${id}/statistics/average-session-interactions`, params: period.query }, getAppStatistics)
|
const { data: response, isLoading } = useAppAverageSessionInteractions(id, period.query)
|
||||||
if (!response)
|
if (isLoading || !response)
|
||||||
return <Loading />
|
return <Loading />
|
||||||
const noDataFlag = !response.data || response.data.length === 0
|
const noDataFlag = !response.data || response.data.length === 0
|
||||||
return <Chart
|
return <Chart
|
||||||
@@ -331,8 +343,8 @@ export const AvgSessionInteractions: FC<IBizChartProps> = ({ id, period }) => {
|
|||||||
|
|
||||||
export const AvgResponseTime: FC<IBizChartProps> = ({ id, period }) => {
|
export const AvgResponseTime: FC<IBizChartProps> = ({ id, period }) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const { data: response } = useSWR({ url: `/apps/${id}/statistics/average-response-time`, params: period.query }, getAppStatistics)
|
const { data: response, isLoading } = useAppAverageResponseTime(id, period.query)
|
||||||
if (!response)
|
if (isLoading || !response)
|
||||||
return <Loading />
|
return <Loading />
|
||||||
const noDataFlag = !response.data || response.data.length === 0
|
const noDataFlag = !response.data || response.data.length === 0
|
||||||
return <Chart
|
return <Chart
|
||||||
@@ -348,8 +360,8 @@ export const AvgResponseTime: FC<IBizChartProps> = ({ id, period }) => {
|
|||||||
|
|
||||||
export const TokenPerSecond: FC<IBizChartProps> = ({ id, period }) => {
|
export const TokenPerSecond: FC<IBizChartProps> = ({ id, period }) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const { data: response } = useSWR({ url: `/apps/${id}/statistics/tokens-per-second`, params: period.query }, getAppStatistics)
|
const { data: response, isLoading } = useAppTokensPerSecond(id, period.query)
|
||||||
if (!response)
|
if (isLoading || !response)
|
||||||
return <Loading />
|
return <Loading />
|
||||||
const noDataFlag = !response.data || response.data.length === 0
|
const noDataFlag = !response.data || response.data.length === 0
|
||||||
return <Chart
|
return <Chart
|
||||||
@@ -366,8 +378,8 @@ export const TokenPerSecond: FC<IBizChartProps> = ({ id, period }) => {
|
|||||||
|
|
||||||
export const UserSatisfactionRate: FC<IBizChartProps> = ({ id, period }) => {
|
export const UserSatisfactionRate: FC<IBizChartProps> = ({ id, period }) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const { data: response } = useSWR({ url: `/apps/${id}/statistics/user-satisfaction-rate`, params: period.query }, getAppStatistics)
|
const { data: response, isLoading } = useAppSatisfactionRate(id, period.query)
|
||||||
if (!response)
|
if (isLoading || !response)
|
||||||
return <Loading />
|
return <Loading />
|
||||||
const noDataFlag = !response.data || response.data.length === 0
|
const noDataFlag = !response.data || response.data.length === 0
|
||||||
return <Chart
|
return <Chart
|
||||||
@@ -384,8 +396,8 @@ export const UserSatisfactionRate: FC<IBizChartProps> = ({ id, period }) => {
|
|||||||
export const CostChart: FC<IBizChartProps> = ({ id, period }) => {
|
export const CostChart: FC<IBizChartProps> = ({ id, period }) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
|
||||||
const { data: response } = useSWR({ url: `/apps/${id}/statistics/token-costs`, params: period.query }, getAppTokenCosts)
|
const { data: response, isLoading } = useAppTokenCosts(id, period.query)
|
||||||
if (!response)
|
if (isLoading || !response)
|
||||||
return <Loading />
|
return <Loading />
|
||||||
const noDataFlag = !response.data || response.data.length === 0
|
const noDataFlag = !response.data || response.data.length === 0
|
||||||
return <Chart
|
return <Chart
|
||||||
@@ -398,8 +410,8 @@ export const CostChart: FC<IBizChartProps> = ({ id, period }) => {
|
|||||||
|
|
||||||
export const WorkflowMessagesChart: FC<IBizChartProps> = ({ id, period }) => {
|
export const WorkflowMessagesChart: FC<IBizChartProps> = ({ id, period }) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const { data: response } = useSWR({ url: `/apps/${id}/workflow/statistics/daily-conversations`, params: period.query }, getWorkflowDailyConversations)
|
const { data: response, isLoading } = useWorkflowDailyConversations(id, period.query)
|
||||||
if (!response)
|
if (isLoading || !response)
|
||||||
return <Loading />
|
return <Loading />
|
||||||
const noDataFlag = !response.data || response.data.length === 0
|
const noDataFlag = !response.data || response.data.length === 0
|
||||||
return <Chart
|
return <Chart
|
||||||
@@ -414,8 +426,8 @@ export const WorkflowMessagesChart: FC<IBizChartProps> = ({ id, period }) => {
|
|||||||
export const WorkflowDailyTerminalsChart: FC<IBizChartProps> = ({ id, period }) => {
|
export const WorkflowDailyTerminalsChart: FC<IBizChartProps> = ({ id, period }) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
|
||||||
const { data: response } = useSWR({ url: `/apps/${id}/workflow/statistics/daily-terminals`, id, params: period.query }, getAppDailyEndUsers)
|
const { data: response, isLoading } = useWorkflowDailyTerminals(id, period.query)
|
||||||
if (!response)
|
if (isLoading || !response)
|
||||||
return <Loading />
|
return <Loading />
|
||||||
const noDataFlag = !response.data || response.data.length === 0
|
const noDataFlag = !response.data || response.data.length === 0
|
||||||
return <Chart
|
return <Chart
|
||||||
@@ -429,8 +441,8 @@ export const WorkflowDailyTerminalsChart: FC<IBizChartProps> = ({ id, period })
|
|||||||
export const WorkflowCostChart: FC<IBizChartProps> = ({ id, period }) => {
|
export const WorkflowCostChart: FC<IBizChartProps> = ({ id, period }) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
|
||||||
const { data: response } = useSWR({ url: `/apps/${id}/workflow/statistics/token-costs`, params: period.query }, getAppTokenCosts)
|
const { data: response, isLoading } = useWorkflowTokenCosts(id, period.query)
|
||||||
if (!response)
|
if (isLoading || !response)
|
||||||
return <Loading />
|
return <Loading />
|
||||||
const noDataFlag = !response.data || response.data.length === 0
|
const noDataFlag = !response.data || response.data.length === 0
|
||||||
return <Chart
|
return <Chart
|
||||||
@@ -443,8 +455,8 @@ export const WorkflowCostChart: FC<IBizChartProps> = ({ id, period }) => {
|
|||||||
|
|
||||||
export const AvgUserInteractions: FC<IBizChartProps> = ({ id, period }) => {
|
export const AvgUserInteractions: FC<IBizChartProps> = ({ id, period }) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const { data: response } = useSWR({ url: `/apps/${id}/workflow/statistics/average-app-interactions`, params: period.query }, getAppStatistics)
|
const { data: response, isLoading } = useWorkflowAverageInteractions(id, period.query)
|
||||||
if (!response)
|
if (isLoading || !response)
|
||||||
return <Loading />
|
return <Loading />
|
||||||
const noDataFlag = !response.data || response.data.length === 0
|
const noDataFlag = !response.data || response.data.length === 0
|
||||||
return <Chart
|
return <Chart
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ const Empty = () => {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<DefaultCards />
|
<DefaultCards />
|
||||||
<div className='absolute inset-0 z-20 flex items-center justify-center bg-gradient-to-t from-background-body to-transparent pointer-events-none'>
|
<div className='pointer-events-none absolute inset-0 z-20 flex items-center justify-center bg-gradient-to-t from-background-body to-transparent'>
|
||||||
<span className='system-md-medium text-text-tertiary'>
|
<span className='system-md-medium text-text-tertiary'>
|
||||||
{t('app.newApp.noAppsFound')}
|
{t('app.newApp.noAppsFound')}
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import { useCallback, useEffect, useRef, useState } from 'react'
|
|||||||
import {
|
import {
|
||||||
useRouter,
|
useRouter,
|
||||||
} from 'next/navigation'
|
} from 'next/navigation'
|
||||||
import useSWRInfinite from 'swr/infinite'
|
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { useDebounceFn } from 'ahooks'
|
import { useDebounceFn } from 'ahooks'
|
||||||
import {
|
import {
|
||||||
@@ -19,8 +18,6 @@ import AppCard from './app-card'
|
|||||||
import NewAppCard from './new-app-card'
|
import NewAppCard from './new-app-card'
|
||||||
import useAppsQueryState from './hooks/use-apps-query-state'
|
import useAppsQueryState from './hooks/use-apps-query-state'
|
||||||
import { useDSLDragDrop } from './hooks/use-dsl-drag-drop'
|
import { useDSLDragDrop } from './hooks/use-dsl-drag-drop'
|
||||||
import type { AppListResponse } from '@/models/app'
|
|
||||||
import { fetchAppList } from '@/service/apps'
|
|
||||||
import { useAppContext } from '@/context/app-context'
|
import { useAppContext } from '@/context/app-context'
|
||||||
import { NEED_REFRESH_APP_LIST_KEY } from '@/config'
|
import { NEED_REFRESH_APP_LIST_KEY } from '@/config'
|
||||||
import { CheckModal } from '@/hooks/use-pay'
|
import { CheckModal } from '@/hooks/use-pay'
|
||||||
@@ -35,6 +32,7 @@ import Empty from './empty'
|
|||||||
import Footer from './footer'
|
import Footer from './footer'
|
||||||
import { useGlobalPublicStore } from '@/context/global-public-context'
|
import { useGlobalPublicStore } from '@/context/global-public-context'
|
||||||
import { AppModeEnum } from '@/types/app'
|
import { AppModeEnum } from '@/types/app'
|
||||||
|
import { useInfiniteAppList } from '@/service/use-apps'
|
||||||
|
|
||||||
const TagManagementModal = dynamic(() => import('@/app/components/base/tag-management'), {
|
const TagManagementModal = dynamic(() => import('@/app/components/base/tag-management'), {
|
||||||
ssr: false,
|
ssr: false,
|
||||||
@@ -43,30 +41,6 @@ const CreateFromDSLModal = dynamic(() => import('@/app/components/app/create-fro
|
|||||||
ssr: false,
|
ssr: false,
|
||||||
})
|
})
|
||||||
|
|
||||||
const getKey = (
|
|
||||||
pageIndex: number,
|
|
||||||
previousPageData: AppListResponse,
|
|
||||||
activeTab: string,
|
|
||||||
isCreatedByMe: boolean,
|
|
||||||
tags: string[],
|
|
||||||
keywords: string,
|
|
||||||
) => {
|
|
||||||
if (!pageIndex || previousPageData.has_more) {
|
|
||||||
const params: any = { url: 'apps', params: { page: pageIndex + 1, limit: 30, name: keywords, is_created_by_me: isCreatedByMe } }
|
|
||||||
|
|
||||||
if (activeTab !== 'all')
|
|
||||||
params.params.mode = activeTab
|
|
||||||
else
|
|
||||||
delete params.params.mode
|
|
||||||
|
|
||||||
if (tags.length)
|
|
||||||
params.params.tag_ids = tags
|
|
||||||
|
|
||||||
return params
|
|
||||||
}
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
const List = () => {
|
const List = () => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const { systemFeatures } = useGlobalPublicStore()
|
const { systemFeatures } = useGlobalPublicStore()
|
||||||
@@ -102,16 +76,24 @@ const List = () => {
|
|||||||
enabled: isCurrentWorkspaceEditor,
|
enabled: isCurrentWorkspaceEditor,
|
||||||
})
|
})
|
||||||
|
|
||||||
const { data, isLoading, error, setSize, mutate } = useSWRInfinite(
|
const appListQueryParams = {
|
||||||
(pageIndex: number, previousPageData: AppListResponse) => getKey(pageIndex, previousPageData, activeTab, isCreatedByMe, tagIDs, searchKeywords),
|
page: 1,
|
||||||
fetchAppList,
|
limit: 30,
|
||||||
{
|
name: searchKeywords,
|
||||||
revalidateFirstPage: true,
|
tag_ids: tagIDs,
|
||||||
shouldRetryOnError: false,
|
is_created_by_me: isCreatedByMe,
|
||||||
dedupingInterval: 500,
|
...(activeTab !== 'all' ? { mode: activeTab as AppModeEnum } : {}),
|
||||||
errorRetryCount: 3,
|
}
|
||||||
},
|
|
||||||
)
|
const {
|
||||||
|
data,
|
||||||
|
isLoading,
|
||||||
|
isFetchingNextPage,
|
||||||
|
fetchNextPage,
|
||||||
|
hasNextPage,
|
||||||
|
error,
|
||||||
|
refetch,
|
||||||
|
} = useInfiniteAppList(appListQueryParams, { enabled: !isCurrentWorkspaceDatasetOperator })
|
||||||
|
|
||||||
const anchorRef = useRef<HTMLDivElement>(null)
|
const anchorRef = useRef<HTMLDivElement>(null)
|
||||||
const options = [
|
const options = [
|
||||||
@@ -126,9 +108,9 @@ const List = () => {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (localStorage.getItem(NEED_REFRESH_APP_LIST_KEY) === '1') {
|
if (localStorage.getItem(NEED_REFRESH_APP_LIST_KEY) === '1') {
|
||||||
localStorage.removeItem(NEED_REFRESH_APP_LIST_KEY)
|
localStorage.removeItem(NEED_REFRESH_APP_LIST_KEY)
|
||||||
mutate()
|
refetch()
|
||||||
}
|
}
|
||||||
}, [mutate, t])
|
}, [refetch])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isCurrentWorkspaceDatasetOperator)
|
if (isCurrentWorkspaceDatasetOperator)
|
||||||
@@ -136,7 +118,9 @@ const List = () => {
|
|||||||
}, [router, isCurrentWorkspaceDatasetOperator])
|
}, [router, isCurrentWorkspaceDatasetOperator])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const hasMore = data?.at(-1)?.has_more ?? true
|
if (isCurrentWorkspaceDatasetOperator)
|
||||||
|
return
|
||||||
|
const hasMore = hasNextPage ?? true
|
||||||
let observer: IntersectionObserver | undefined
|
let observer: IntersectionObserver | undefined
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
@@ -151,8 +135,8 @@ const List = () => {
|
|||||||
const dynamicMargin = Math.max(100, Math.min(containerHeight * 0.2, 200)) // Clamps to 100-200px range, using 20% of container height as the base value
|
const dynamicMargin = Math.max(100, Math.min(containerHeight * 0.2, 200)) // Clamps to 100-200px range, using 20% of container height as the base value
|
||||||
|
|
||||||
observer = new IntersectionObserver((entries) => {
|
observer = new IntersectionObserver((entries) => {
|
||||||
if (entries[0].isIntersecting && !isLoading && !error && hasMore)
|
if (entries[0].isIntersecting && !isLoading && !isFetchingNextPage && !error && hasMore)
|
||||||
setSize((size: number) => size + 1)
|
fetchNextPage()
|
||||||
}, {
|
}, {
|
||||||
root: containerRef.current,
|
root: containerRef.current,
|
||||||
rootMargin: `${dynamicMargin}px`,
|
rootMargin: `${dynamicMargin}px`,
|
||||||
@@ -161,7 +145,7 @@ const List = () => {
|
|||||||
observer.observe(anchorRef.current)
|
observer.observe(anchorRef.current)
|
||||||
}
|
}
|
||||||
return () => observer?.disconnect()
|
return () => observer?.disconnect()
|
||||||
}, [isLoading, setSize, data, error])
|
}, [isLoading, isFetchingNextPage, fetchNextPage, error, hasNextPage, isCurrentWorkspaceDatasetOperator])
|
||||||
|
|
||||||
const { run: handleSearch } = useDebounceFn(() => {
|
const { run: handleSearch } = useDebounceFn(() => {
|
||||||
setSearchKeywords(keywords)
|
setSearchKeywords(keywords)
|
||||||
@@ -185,6 +169,9 @@ const List = () => {
|
|||||||
setQuery(prev => ({ ...prev, isCreatedByMe: newValue }))
|
setQuery(prev => ({ ...prev, isCreatedByMe: newValue }))
|
||||||
}, [isCreatedByMe, setQuery])
|
}, [isCreatedByMe, setQuery])
|
||||||
|
|
||||||
|
const pages = data?.pages ?? []
|
||||||
|
const hasAnyApp = (pages[0]?.total ?? 0) > 0
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div ref={containerRef} className='relative flex h-0 shrink-0 grow flex-col overflow-y-auto bg-background-body'>
|
<div ref={containerRef} className='relative flex h-0 shrink-0 grow flex-col overflow-y-auto bg-background-body'>
|
||||||
@@ -217,17 +204,17 @@ const List = () => {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{(data && data[0].total > 0)
|
{hasAnyApp
|
||||||
? <div className='relative grid grow grid-cols-1 content-start gap-4 px-12 pt-2 sm:grid-cols-1 md:grid-cols-2 xl:grid-cols-4 2xl:grid-cols-5 2k:grid-cols-6'>
|
? <div className='relative grid grow grid-cols-1 content-start gap-4 px-12 pt-2 sm:grid-cols-1 md:grid-cols-2 xl:grid-cols-4 2xl:grid-cols-5 2k:grid-cols-6'>
|
||||||
{isCurrentWorkspaceEditor
|
{isCurrentWorkspaceEditor
|
||||||
&& <NewAppCard ref={newAppCardRef} onSuccess={mutate} selectedAppType={activeTab} />}
|
&& <NewAppCard ref={newAppCardRef} onSuccess={refetch} selectedAppType={activeTab} />}
|
||||||
{data.map(({ data: apps }) => apps.map(app => (
|
{pages.map(({ data: apps }) => apps.map(app => (
|
||||||
<AppCard key={app.id} app={app} onRefresh={mutate} />
|
<AppCard key={app.id} app={app} onRefresh={refetch} />
|
||||||
)))}
|
)))}
|
||||||
</div>
|
</div>
|
||||||
: <div className='relative grid grow grid-cols-1 content-start gap-4 overflow-hidden px-12 pt-2 sm:grid-cols-1 md:grid-cols-2 xl:grid-cols-4 2xl:grid-cols-5 2k:grid-cols-6'>
|
: <div className='relative grid grow grid-cols-1 content-start gap-4 overflow-hidden px-12 pt-2 sm:grid-cols-1 md:grid-cols-2 xl:grid-cols-4 2xl:grid-cols-5 2k:grid-cols-6'>
|
||||||
{isCurrentWorkspaceEditor
|
{isCurrentWorkspaceEditor
|
||||||
&& <NewAppCard ref={newAppCardRef} className='z-10' onSuccess={mutate} selectedAppType={activeTab} />}
|
&& <NewAppCard ref={newAppCardRef} className='z-10' onSuccess={refetch} selectedAppType={activeTab} />}
|
||||||
<Empty />
|
<Empty />
|
||||||
</div>}
|
</div>}
|
||||||
|
|
||||||
@@ -261,7 +248,7 @@ const List = () => {
|
|||||||
onSuccess={() => {
|
onSuccess={() => {
|
||||||
setShowCreateFromDSLModal(false)
|
setShowCreateFromDSLModal(false)
|
||||||
setDroppedDSLFile(undefined)
|
setDroppedDSLFile(undefined)
|
||||||
mutate()
|
refetch()
|
||||||
}}
|
}}
|
||||||
droppedFile={droppedDSLFile}
|
droppedFile={droppedDSLFile}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import useSWR from 'swr'
|
|
||||||
import { produce } from 'immer'
|
import { produce } from 'immer'
|
||||||
import React, { Fragment } from 'react'
|
import React, { Fragment } from 'react'
|
||||||
import { usePathname } from 'next/navigation'
|
import { usePathname } from 'next/navigation'
|
||||||
@@ -9,7 +8,6 @@ import { Listbox, ListboxButton, ListboxOption, ListboxOptions, Transition } fro
|
|||||||
import { CheckIcon, ChevronDownIcon } from '@heroicons/react/20/solid'
|
import { CheckIcon, ChevronDownIcon } from '@heroicons/react/20/solid'
|
||||||
import { useFeatures, useFeaturesStore } from '@/app/components/base/features/hooks'
|
import { useFeatures, useFeaturesStore } from '@/app/components/base/features/hooks'
|
||||||
import type { Item } from '@/app/components/base/select'
|
import type { Item } from '@/app/components/base/select'
|
||||||
import { fetchAppVoices } from '@/service/apps'
|
|
||||||
import Tooltip from '@/app/components/base/tooltip'
|
import Tooltip from '@/app/components/base/tooltip'
|
||||||
import Switch from '@/app/components/base/switch'
|
import Switch from '@/app/components/base/switch'
|
||||||
import AudioBtn from '@/app/components/base/audio-btn'
|
import AudioBtn from '@/app/components/base/audio-btn'
|
||||||
@@ -17,6 +15,7 @@ import { languages } from '@/i18n-config/language'
|
|||||||
import { TtsAutoPlay } from '@/types/app'
|
import { TtsAutoPlay } from '@/types/app'
|
||||||
import type { OnFeaturesChange } from '@/app/components/base/features/types'
|
import type { OnFeaturesChange } from '@/app/components/base/features/types'
|
||||||
import classNames from '@/utils/classnames'
|
import classNames from '@/utils/classnames'
|
||||||
|
import { useAppVoices } from '@/service/use-apps'
|
||||||
|
|
||||||
type VoiceParamConfigProps = {
|
type VoiceParamConfigProps = {
|
||||||
onClose: () => void
|
onClose: () => void
|
||||||
@@ -39,7 +38,7 @@ const VoiceParamConfig = ({
|
|||||||
const localLanguagePlaceholder = languageItem?.name || t('common.placeholder.select')
|
const localLanguagePlaceholder = languageItem?.name || t('common.placeholder.select')
|
||||||
|
|
||||||
const language = languageItem?.value
|
const language = languageItem?.value
|
||||||
const voiceItems = useSWR({ appId, language }, fetchAppVoices).data
|
const { data: voiceItems } = useAppVoices(appId, language)
|
||||||
let voiceItem = voiceItems?.find(item => item.value === text2speech?.voice)
|
let voiceItem = voiceItems?.find(item => item.value === text2speech?.voice)
|
||||||
if (voiceItems && !voiceItem)
|
if (voiceItems && !voiceItem)
|
||||||
voiceItem = voiceItems[0]
|
voiceItem = voiceItems[0]
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import {
|
|||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { RiDeleteBinLine } from '@remixicon/react'
|
import { RiDeleteBinLine } from '@remixicon/react'
|
||||||
import { PlusIcon, XMarkIcon } from '@heroicons/react/20/solid'
|
import { PlusIcon, XMarkIcon } from '@heroicons/react/20/solid'
|
||||||
import useSWR, { useSWRConfig } from 'swr'
|
import useSWR from 'swr'
|
||||||
import SecretKeyGenerateModal from './secret-key-generate'
|
import SecretKeyGenerateModal from './secret-key-generate'
|
||||||
import s from './style.module.css'
|
import s from './style.module.css'
|
||||||
import ActionButton from '@/app/components/base/action-button'
|
import ActionButton from '@/app/components/base/action-button'
|
||||||
@@ -15,7 +15,6 @@ import CopyFeedback from '@/app/components/base/copy-feedback'
|
|||||||
import {
|
import {
|
||||||
createApikey as createAppApikey,
|
createApikey as createAppApikey,
|
||||||
delApikey as delAppApikey,
|
delApikey as delAppApikey,
|
||||||
fetchApiKeysList as fetchAppApiKeysList,
|
|
||||||
} from '@/service/apps'
|
} from '@/service/apps'
|
||||||
import {
|
import {
|
||||||
createApikey as createDatasetApikey,
|
createApikey as createDatasetApikey,
|
||||||
@@ -27,6 +26,7 @@ import Loading from '@/app/components/base/loading'
|
|||||||
import Confirm from '@/app/components/base/confirm'
|
import Confirm from '@/app/components/base/confirm'
|
||||||
import useTimestamp from '@/hooks/use-timestamp'
|
import useTimestamp from '@/hooks/use-timestamp'
|
||||||
import { useAppContext } from '@/context/app-context'
|
import { useAppContext } from '@/context/app-context'
|
||||||
|
import { useAppApiKeys, useInvalidateAppApiKeys } from '@/service/use-apps'
|
||||||
|
|
||||||
type ISecretKeyModalProps = {
|
type ISecretKeyModalProps = {
|
||||||
isShow: boolean
|
isShow: boolean
|
||||||
@@ -45,12 +45,14 @@ const SecretKeyModal = ({
|
|||||||
const [showConfirmDelete, setShowConfirmDelete] = useState(false)
|
const [showConfirmDelete, setShowConfirmDelete] = useState(false)
|
||||||
const [isVisible, setVisible] = useState(false)
|
const [isVisible, setVisible] = useState(false)
|
||||||
const [newKey, setNewKey] = useState<CreateApiKeyResponse | undefined>(undefined)
|
const [newKey, setNewKey] = useState<CreateApiKeyResponse | undefined>(undefined)
|
||||||
const { mutate } = useSWRConfig()
|
const invalidateAppApiKeys = useInvalidateAppApiKeys()
|
||||||
const commonParams = appId
|
const { data: appApiKeys, isLoading: isAppApiKeysLoading } = useAppApiKeys(appId, { enabled: !!appId && isShow })
|
||||||
? { url: `/apps/${appId}/api-keys`, params: {} }
|
const { data: datasetApiKeys, isLoading: isDatasetApiKeysLoading, mutate: mutateDatasetApiKeys } = useSWR(
|
||||||
: { url: '/datasets/api-keys', params: {} }
|
!appId && isShow ? { url: '/datasets/api-keys', params: {} } : null,
|
||||||
const fetchApiKeysList = appId ? fetchAppApiKeysList : fetchDatasetApiKeysList
|
fetchDatasetApiKeysList,
|
||||||
const { data: apiKeysList } = useSWR(commonParams, fetchApiKeysList)
|
)
|
||||||
|
const apiKeysList = appId ? appApiKeys : datasetApiKeys
|
||||||
|
const isApiKeysLoading = appId ? isAppApiKeysLoading : isDatasetApiKeysLoading
|
||||||
|
|
||||||
const [delKeyID, setDelKeyId] = useState('')
|
const [delKeyID, setDelKeyId] = useState('')
|
||||||
|
|
||||||
@@ -64,7 +66,10 @@ const SecretKeyModal = ({
|
|||||||
? { url: `/apps/${appId}/api-keys/${delKeyID}`, params: {} }
|
? { url: `/apps/${appId}/api-keys/${delKeyID}`, params: {} }
|
||||||
: { url: `/datasets/api-keys/${delKeyID}`, params: {} }
|
: { url: `/datasets/api-keys/${delKeyID}`, params: {} }
|
||||||
await delApikey(params)
|
await delApikey(params)
|
||||||
mutate(commonParams)
|
if (appId)
|
||||||
|
invalidateAppApiKeys(appId)
|
||||||
|
else
|
||||||
|
mutateDatasetApiKeys()
|
||||||
}
|
}
|
||||||
|
|
||||||
const onCreate = async () => {
|
const onCreate = async () => {
|
||||||
@@ -75,7 +80,10 @@ const SecretKeyModal = ({
|
|||||||
const res = await createApikey(params)
|
const res = await createApikey(params)
|
||||||
setVisible(true)
|
setVisible(true)
|
||||||
setNewKey(res)
|
setNewKey(res)
|
||||||
mutate(commonParams)
|
if (appId)
|
||||||
|
invalidateAppApiKeys(appId)
|
||||||
|
else
|
||||||
|
mutateDatasetApiKeys()
|
||||||
}
|
}
|
||||||
|
|
||||||
const generateToken = (token: string) => {
|
const generateToken = (token: string) => {
|
||||||
@@ -88,7 +96,7 @@ const SecretKeyModal = ({
|
|||||||
<XMarkIcon className="h-6 w-6 cursor-pointer text-text-tertiary" onClick={onClose} />
|
<XMarkIcon className="h-6 w-6 cursor-pointer text-text-tertiary" onClick={onClose} />
|
||||||
</div>
|
</div>
|
||||||
<p className='mt-1 shrink-0 text-[13px] font-normal leading-5 text-text-tertiary'>{t('appApi.apiKeyModal.apiSecretKeyTips')}</p>
|
<p className='mt-1 shrink-0 text-[13px] font-normal leading-5 text-text-tertiary'>{t('appApi.apiKeyModal.apiSecretKeyTips')}</p>
|
||||||
{!apiKeysList && <div className='mt-4'><Loading /></div>}
|
{isApiKeysLoading && <div className='mt-4'><Loading /></div>}
|
||||||
{
|
{
|
||||||
!!apiKeysList?.data?.length && (
|
!!apiKeysList?.data?.length && (
|
||||||
<div className='mt-4 flex grow flex-col overflow-hidden'>
|
<div className='mt-4 flex grow flex-col overflow-hidden'>
|
||||||
|
|||||||
@@ -3,7 +3,6 @@
|
|||||||
import { useCallback, useEffect, useState } from 'react'
|
import { useCallback, useEffect, useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { useParams } from 'next/navigation'
|
import { useParams } from 'next/navigation'
|
||||||
import useSWRInfinite from 'swr/infinite'
|
|
||||||
import { flatten } from 'lodash-es'
|
import { flatten } from 'lodash-es'
|
||||||
import { produce } from 'immer'
|
import { produce } from 'immer'
|
||||||
import {
|
import {
|
||||||
@@ -12,33 +11,13 @@ import {
|
|||||||
} from '@remixicon/react'
|
} from '@remixicon/react'
|
||||||
import Nav from '../nav'
|
import Nav from '../nav'
|
||||||
import type { NavItem } from '../nav/nav-selector'
|
import type { NavItem } from '../nav/nav-selector'
|
||||||
import { fetchAppList } from '@/service/apps'
|
|
||||||
import CreateAppTemplateDialog from '@/app/components/app/create-app-dialog'
|
import CreateAppTemplateDialog from '@/app/components/app/create-app-dialog'
|
||||||
import CreateAppModal from '@/app/components/app/create-app-modal'
|
import CreateAppModal from '@/app/components/app/create-app-modal'
|
||||||
import CreateFromDSLModal from '@/app/components/app/create-from-dsl-modal'
|
import CreateFromDSLModal from '@/app/components/app/create-from-dsl-modal'
|
||||||
import type { AppListResponse } from '@/models/app'
|
|
||||||
import { useAppContext } from '@/context/app-context'
|
import { useAppContext } from '@/context/app-context'
|
||||||
import { useStore as useAppStore } from '@/app/components/app/store'
|
import { useStore as useAppStore } from '@/app/components/app/store'
|
||||||
import { AppModeEnum } from '@/types/app'
|
import { AppModeEnum } from '@/types/app'
|
||||||
|
import { useInfiniteAppList } from '@/service/use-apps'
|
||||||
const getKey = (
|
|
||||||
pageIndex: number,
|
|
||||||
previousPageData: AppListResponse,
|
|
||||||
activeTab: string,
|
|
||||||
keywords: string,
|
|
||||||
) => {
|
|
||||||
if (!pageIndex || previousPageData.has_more) {
|
|
||||||
const params: any = { url: 'apps', params: { page: pageIndex + 1, limit: 30, name: keywords } }
|
|
||||||
|
|
||||||
if (activeTab !== 'all')
|
|
||||||
params.params.mode = activeTab
|
|
||||||
else
|
|
||||||
delete params.params.mode
|
|
||||||
|
|
||||||
return params
|
|
||||||
}
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
const AppNav = () => {
|
const AppNav = () => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
@@ -50,17 +29,21 @@ const AppNav = () => {
|
|||||||
const [showCreateFromDSLModal, setShowCreateFromDSLModal] = useState(false)
|
const [showCreateFromDSLModal, setShowCreateFromDSLModal] = useState(false)
|
||||||
const [navItems, setNavItems] = useState<NavItem[]>([])
|
const [navItems, setNavItems] = useState<NavItem[]>([])
|
||||||
|
|
||||||
const { data: appsData, setSize, mutate } = useSWRInfinite(
|
const {
|
||||||
appId
|
data: appsData,
|
||||||
? (pageIndex: number, previousPageData: AppListResponse) => getKey(pageIndex, previousPageData, 'all', '')
|
fetchNextPage,
|
||||||
: () => null,
|
hasNextPage,
|
||||||
fetchAppList,
|
refetch,
|
||||||
{ revalidateFirstPage: false },
|
} = useInfiniteAppList({
|
||||||
)
|
page: 1,
|
||||||
|
limit: 30,
|
||||||
|
name: '',
|
||||||
|
}, { enabled: !!appId })
|
||||||
|
|
||||||
const handleLoadMore = useCallback(() => {
|
const handleLoadMore = useCallback(() => {
|
||||||
setSize(size => size + 1)
|
if (hasNextPage)
|
||||||
}, [setSize])
|
fetchNextPage()
|
||||||
|
}, [fetchNextPage, hasNextPage])
|
||||||
|
|
||||||
const openModal = (state: string) => {
|
const openModal = (state: string) => {
|
||||||
if (state === 'blank')
|
if (state === 'blank')
|
||||||
@@ -73,7 +56,7 @@ const AppNav = () => {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (appsData) {
|
if (appsData) {
|
||||||
const appItems = flatten(appsData?.map(appData => appData.data))
|
const appItems = flatten((appsData.pages ?? []).map(appData => appData.data))
|
||||||
const navItems = appItems.map((app) => {
|
const navItems = appItems.map((app) => {
|
||||||
const link = ((isCurrentWorkspaceEditor, app) => {
|
const link = ((isCurrentWorkspaceEditor, app) => {
|
||||||
if (!isCurrentWorkspaceEditor) {
|
if (!isCurrentWorkspaceEditor) {
|
||||||
@@ -132,17 +115,17 @@ const AppNav = () => {
|
|||||||
<CreateAppModal
|
<CreateAppModal
|
||||||
show={showNewAppDialog}
|
show={showNewAppDialog}
|
||||||
onClose={() => setShowNewAppDialog(false)}
|
onClose={() => setShowNewAppDialog(false)}
|
||||||
onSuccess={() => mutate()}
|
onSuccess={() => refetch()}
|
||||||
/>
|
/>
|
||||||
<CreateAppTemplateDialog
|
<CreateAppTemplateDialog
|
||||||
show={showNewAppTemplateDialog}
|
show={showNewAppTemplateDialog}
|
||||||
onClose={() => setShowNewAppTemplateDialog(false)}
|
onClose={() => setShowNewAppTemplateDialog(false)}
|
||||||
onSuccess={() => mutate()}
|
onSuccess={() => refetch()}
|
||||||
/>
|
/>
|
||||||
<CreateFromDSLModal
|
<CreateFromDSLModal
|
||||||
show={showCreateFromDSLModal}
|
show={showCreateFromDSLModal}
|
||||||
onClose={() => setShowCreateFromDSLModal(false)}
|
onClose={() => setShowCreateFromDSLModal(false)}
|
||||||
onSuccess={() => mutate()}
|
onSuccess={() => refetch()}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -15,32 +15,10 @@ import type {
|
|||||||
OffsetOptions,
|
OffsetOptions,
|
||||||
Placement,
|
Placement,
|
||||||
} from '@floating-ui/react'
|
} from '@floating-ui/react'
|
||||||
import useSWRInfinite from 'swr/infinite'
|
import { useInfiniteAppList } from '@/service/use-apps'
|
||||||
import { fetchAppList } from '@/service/apps'
|
|
||||||
import type { AppListResponse } from '@/models/app'
|
|
||||||
|
|
||||||
const PAGE_SIZE = 20
|
const PAGE_SIZE = 20
|
||||||
|
|
||||||
const getKey = (
|
|
||||||
pageIndex: number,
|
|
||||||
previousPageData: AppListResponse,
|
|
||||||
searchText: string,
|
|
||||||
) => {
|
|
||||||
if (pageIndex === 0 || (previousPageData && previousPageData.has_more)) {
|
|
||||||
const params: any = {
|
|
||||||
url: 'apps',
|
|
||||||
params: {
|
|
||||||
page: pageIndex + 1,
|
|
||||||
limit: PAGE_SIZE,
|
|
||||||
name: searchText,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
return params
|
|
||||||
}
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
value?: {
|
value?: {
|
||||||
app_id: string
|
app_id: string
|
||||||
@@ -72,30 +50,32 @@ const AppSelector: FC<Props> = ({
|
|||||||
const [searchText, setSearchText] = useState('')
|
const [searchText, setSearchText] = useState('')
|
||||||
const [isLoadingMore, setIsLoadingMore] = useState(false)
|
const [isLoadingMore, setIsLoadingMore] = useState(false)
|
||||||
|
|
||||||
const { data, isLoading, setSize } = useSWRInfinite(
|
const {
|
||||||
(pageIndex: number, previousPageData: AppListResponse) => getKey(pageIndex, previousPageData, searchText),
|
data,
|
||||||
fetchAppList,
|
isLoading,
|
||||||
{
|
isFetchingNextPage,
|
||||||
revalidateFirstPage: true,
|
fetchNextPage,
|
||||||
shouldRetryOnError: false,
|
hasNextPage,
|
||||||
dedupingInterval: 500,
|
} = useInfiniteAppList({
|
||||||
errorRetryCount: 3,
|
page: 1,
|
||||||
},
|
limit: PAGE_SIZE,
|
||||||
)
|
name: searchText,
|
||||||
|
})
|
||||||
|
|
||||||
|
const pages = data?.pages ?? []
|
||||||
const displayedApps = useMemo(() => {
|
const displayedApps = useMemo(() => {
|
||||||
if (!data) return []
|
if (!pages.length) return []
|
||||||
return data.flatMap(({ data: apps }) => apps)
|
return pages.flatMap(({ data: apps }) => apps)
|
||||||
}, [data])
|
}, [pages])
|
||||||
|
|
||||||
const hasMore = data?.at(-1)?.has_more ?? true
|
const hasMore = hasNextPage ?? true
|
||||||
|
|
||||||
const handleLoadMore = useCallback(async () => {
|
const handleLoadMore = useCallback(async () => {
|
||||||
if (isLoadingMore || !hasMore) return
|
if (isLoadingMore || isFetchingNextPage || !hasMore) return
|
||||||
|
|
||||||
setIsLoadingMore(true)
|
setIsLoadingMore(true)
|
||||||
try {
|
try {
|
||||||
await setSize((size: number) => size + 1)
|
await fetchNextPage()
|
||||||
}
|
}
|
||||||
finally {
|
finally {
|
||||||
// Add a small delay to ensure state updates are complete
|
// Add a small delay to ensure state updates are complete
|
||||||
@@ -103,7 +83,7 @@ const AppSelector: FC<Props> = ({
|
|||||||
setIsLoadingMore(false)
|
setIsLoadingMore(false)
|
||||||
}, 300)
|
}, 300)
|
||||||
}
|
}
|
||||||
}, [isLoadingMore, hasMore, setSize])
|
}, [isLoadingMore, isFetchingNextPage, hasMore, fetchNextPage])
|
||||||
|
|
||||||
const handleTriggerClick = () => {
|
const handleTriggerClick = () => {
|
||||||
if (disabled) return
|
if (disabled) return
|
||||||
@@ -185,7 +165,7 @@ const AppSelector: FC<Props> = ({
|
|||||||
onSelect={handleSelectApp}
|
onSelect={handleSelectApp}
|
||||||
scope={scope || 'all'}
|
scope={scope || 'all'}
|
||||||
apps={displayedApps}
|
apps={displayedApps}
|
||||||
isLoading={isLoading || isLoadingMore}
|
isLoading={isLoading || isLoadingMore || isFetchingNextPage}
|
||||||
hasMore={hasMore}
|
hasMore={hasMore}
|
||||||
onLoadMore={handleLoadMore}
|
onLoadMore={handleLoadMore}
|
||||||
searchText={searchText}
|
searchText={searchText}
|
||||||
|
|||||||
@@ -1,15 +1,14 @@
|
|||||||
import type { Fetcher } from 'swr'
|
|
||||||
import { del, get, patch, post, put } from './base'
|
import { del, get, patch, post, put } from './base'
|
||||||
import type { ApiKeysListResponse, AppDailyConversationsResponse, AppDailyEndUsersResponse, AppDailyMessagesResponse, AppDetailResponse, AppListResponse, AppStatisticsResponse, AppTemplatesResponse, AppTokenCostsResponse, AppVoicesListResponse, CreateApiKeyResponse, DSLImportMode, DSLImportResponse, GenerationIntroductionResponse, TracingConfig, TracingStatus, UpdateAppModelConfigResponse, UpdateAppSiteCodeResponse, UpdateOpenAIKeyResponse, ValidateOpenAIKeyResponse, WebhookTriggerResponse, WorkflowDailyConversationsResponse } from '@/models/app'
|
import type { ApiKeysListResponse, AppDailyConversationsResponse, AppDailyEndUsersResponse, AppDailyMessagesResponse, AppDetailResponse, AppListResponse, AppStatisticsResponse, AppTemplatesResponse, AppTokenCostsResponse, AppVoicesListResponse, CreateApiKeyResponse, DSLImportMode, DSLImportResponse, GenerationIntroductionResponse, TracingConfig, TracingStatus, UpdateAppModelConfigResponse, UpdateAppSiteCodeResponse, UpdateOpenAIKeyResponse, ValidateOpenAIKeyResponse, WebhookTriggerResponse, WorkflowDailyConversationsResponse } from '@/models/app'
|
||||||
import type { CommonResponse } from '@/models/common'
|
import type { CommonResponse } from '@/models/common'
|
||||||
import type { AppIconType, AppModeEnum, ModelConfig } from '@/types/app'
|
import type { AppIconType, AppModeEnum, ModelConfig } from '@/types/app'
|
||||||
import type { TracingProvider } from '@/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/type'
|
import type { TracingProvider } from '@/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/type'
|
||||||
|
|
||||||
export const fetchAppList: Fetcher<AppListResponse, { url: string; params?: Record<string, any> }> = ({ url, params }) => {
|
export const fetchAppList = ({ url, params }: { url: string; params?: Record<string, any> }): Promise<AppListResponse> => {
|
||||||
return get<AppListResponse>(url, { params })
|
return get<AppListResponse>(url, { params })
|
||||||
}
|
}
|
||||||
|
|
||||||
export const fetchAppDetail: Fetcher<AppDetailResponse, { url: string; id: string }> = ({ url, id }) => {
|
export const fetchAppDetail = ({ url, id }: { url: string; id: string }): Promise<AppDetailResponse> => {
|
||||||
return get<AppDetailResponse>(`${url}/${id}`)
|
return get<AppDetailResponse>(`${url}/${id}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -18,24 +17,74 @@ export const fetchAppDetailDirect = async ({ url, id }: { url: string; id: strin
|
|||||||
return get<AppDetailResponse>(`${url}/${id}`)
|
return get<AppDetailResponse>(`${url}/${id}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const fetchAppTemplates: Fetcher<AppTemplatesResponse, { url: string }> = ({ url }) => {
|
export const fetchAppTemplates = ({ url }: { url: string }): Promise<AppTemplatesResponse> => {
|
||||||
return get<AppTemplatesResponse>(url)
|
return get<AppTemplatesResponse>(url)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const createApp: Fetcher<AppDetailResponse, { name: string; icon_type?: AppIconType; icon?: string; icon_background?: string; mode: AppModeEnum; description?: string; config?: ModelConfig }> = ({ name, icon_type, icon, icon_background, mode, description, config }) => {
|
export const createApp = ({
|
||||||
|
name,
|
||||||
|
icon_type,
|
||||||
|
icon,
|
||||||
|
icon_background,
|
||||||
|
mode,
|
||||||
|
description,
|
||||||
|
config,
|
||||||
|
}: {
|
||||||
|
name: string
|
||||||
|
icon_type?: AppIconType
|
||||||
|
icon?: string
|
||||||
|
icon_background?: string
|
||||||
|
mode: AppModeEnum
|
||||||
|
description?: string
|
||||||
|
config?: ModelConfig
|
||||||
|
}): Promise<AppDetailResponse> => {
|
||||||
return post<AppDetailResponse>('apps', { body: { name, icon_type, icon, icon_background, mode, description, model_config: config } })
|
return post<AppDetailResponse>('apps', { body: { name, icon_type, icon, icon_background, mode, description, model_config: config } })
|
||||||
}
|
}
|
||||||
|
|
||||||
export const updateAppInfo: Fetcher<AppDetailResponse, { appID: string; name: string; icon_type: AppIconType; icon: string; icon_background?: string; description: string; use_icon_as_answer_icon?: boolean; max_active_requests?: number | null }> = ({ appID, name, icon_type, icon, icon_background, description, use_icon_as_answer_icon, max_active_requests }) => {
|
export const updateAppInfo = ({
|
||||||
|
appID,
|
||||||
|
name,
|
||||||
|
icon_type,
|
||||||
|
icon,
|
||||||
|
icon_background,
|
||||||
|
description,
|
||||||
|
use_icon_as_answer_icon,
|
||||||
|
max_active_requests,
|
||||||
|
}: {
|
||||||
|
appID: string
|
||||||
|
name: string
|
||||||
|
icon_type: AppIconType
|
||||||
|
icon: string
|
||||||
|
icon_background?: string
|
||||||
|
description: string
|
||||||
|
use_icon_as_answer_icon?: boolean
|
||||||
|
max_active_requests?: number | null
|
||||||
|
}): Promise<AppDetailResponse> => {
|
||||||
const body = { name, icon_type, icon, icon_background, description, use_icon_as_answer_icon, max_active_requests }
|
const body = { name, icon_type, icon, icon_background, description, use_icon_as_answer_icon, max_active_requests }
|
||||||
return put<AppDetailResponse>(`apps/${appID}`, { body })
|
return put<AppDetailResponse>(`apps/${appID}`, { body })
|
||||||
}
|
}
|
||||||
|
|
||||||
export const copyApp: Fetcher<AppDetailResponse, { appID: string; name: string; icon_type: AppIconType; icon: string; icon_background?: string | null; mode: AppModeEnum; description?: string }> = ({ appID, name, icon_type, icon, icon_background, mode, description }) => {
|
export const copyApp = ({
|
||||||
|
appID,
|
||||||
|
name,
|
||||||
|
icon_type,
|
||||||
|
icon,
|
||||||
|
icon_background,
|
||||||
|
mode,
|
||||||
|
description,
|
||||||
|
}: {
|
||||||
|
appID: string
|
||||||
|
name: string
|
||||||
|
icon_type: AppIconType
|
||||||
|
icon: string
|
||||||
|
icon_background?: string | null
|
||||||
|
mode: AppModeEnum
|
||||||
|
description?: string
|
||||||
|
}): Promise<AppDetailResponse> => {
|
||||||
return post<AppDetailResponse>(`apps/${appID}/copy`, { body: { name, icon_type, icon, icon_background, mode, description } })
|
return post<AppDetailResponse>(`apps/${appID}/copy`, { body: { name, icon_type, icon, icon_background, mode, description } })
|
||||||
}
|
}
|
||||||
|
|
||||||
export const exportAppConfig: Fetcher<{ data: string }, { appID: string; include?: boolean; workflowID?: string }> = ({ appID, include = false, workflowID }) => {
|
export const exportAppConfig = ({ appID, include = false, workflowID }: { appID: string; include?: boolean; workflowID?: string }): Promise<{ data: string }> => {
|
||||||
const params = new URLSearchParams({
|
const params = new URLSearchParams({
|
||||||
include_secret: include.toString(),
|
include_secret: include.toString(),
|
||||||
})
|
})
|
||||||
@@ -44,126 +93,116 @@ export const exportAppConfig: Fetcher<{ data: string }, { appID: string; include
|
|||||||
return get<{ data: string }>(`apps/${appID}/export?${params.toString()}`)
|
return get<{ data: string }>(`apps/${appID}/export?${params.toString()}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: delete
|
export const importDSL = ({ mode, yaml_content, yaml_url, app_id, name, description, icon_type, icon, icon_background }: { mode: DSLImportMode; yaml_content?: string; yaml_url?: string; app_id?: string; name?: string; description?: string; icon_type?: AppIconType; icon?: string; icon_background?: string }): Promise<DSLImportResponse> => {
|
||||||
export const importApp: Fetcher<AppDetailResponse, { data: string; name?: string; description?: string; icon_type?: AppIconType; icon?: string; icon_background?: string }> = ({ data, name, description, icon_type, icon, icon_background }) => {
|
|
||||||
return post<AppDetailResponse>('apps/import', { body: { data, name, description, icon_type, icon, icon_background } })
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: delete
|
|
||||||
export const importAppFromUrl: Fetcher<AppDetailResponse, { url: string; name?: string; description?: string; icon?: string; icon_background?: string }> = ({ url, name, description, icon, icon_background }) => {
|
|
||||||
return post<AppDetailResponse>('apps/import/url', { body: { url, name, description, icon, icon_background } })
|
|
||||||
}
|
|
||||||
|
|
||||||
export const importDSL: Fetcher<DSLImportResponse, { mode: DSLImportMode; yaml_content?: string; yaml_url?: string; app_id?: string; name?: string; description?: string; icon_type?: AppIconType; icon?: string; icon_background?: string }> = ({ mode, yaml_content, yaml_url, app_id, name, description, icon_type, icon, icon_background }) => {
|
|
||||||
return post<DSLImportResponse>('apps/imports', { body: { mode, yaml_content, yaml_url, app_id, name, description, icon, icon_type, icon_background } })
|
return post<DSLImportResponse>('apps/imports', { body: { mode, yaml_content, yaml_url, app_id, name, description, icon, icon_type, icon_background } })
|
||||||
}
|
}
|
||||||
|
|
||||||
export const importDSLConfirm: Fetcher<DSLImportResponse, { import_id: string }> = ({ import_id }) => {
|
export const importDSLConfirm = ({ import_id }: { import_id: string }): Promise<DSLImportResponse> => {
|
||||||
return post<DSLImportResponse>(`apps/imports/${import_id}/confirm`, { body: {} })
|
return post<DSLImportResponse>(`apps/imports/${import_id}/confirm`, { body: {} })
|
||||||
}
|
}
|
||||||
|
|
||||||
export const switchApp: Fetcher<{ new_app_id: string }, { appID: string; name: string; icon_type: AppIconType; icon: string; icon_background?: string | null }> = ({ appID, name, icon_type, icon, icon_background }) => {
|
export const switchApp = ({ appID, name, icon_type, icon, icon_background }: { appID: string; name: string; icon_type: AppIconType; icon: string; icon_background?: string | null }): Promise<{ new_app_id: string }> => {
|
||||||
return post<{ new_app_id: string }>(`apps/${appID}/convert-to-workflow`, { body: { name, icon_type, icon, icon_background } })
|
return post<{ new_app_id: string }>(`apps/${appID}/convert-to-workflow`, { body: { name, icon_type, icon, icon_background } })
|
||||||
}
|
}
|
||||||
|
|
||||||
export const deleteApp: Fetcher<CommonResponse, string> = (appID) => {
|
export const deleteApp = (appID: string): Promise<CommonResponse> => {
|
||||||
return del<CommonResponse>(`apps/${appID}`)
|
return del<CommonResponse>(`apps/${appID}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const updateAppSiteStatus: Fetcher<AppDetailResponse, { url: string; body: Record<string, any> }> = ({ url, body }) => {
|
export const updateAppSiteStatus = ({ url, body }: { url: string; body: Record<string, any> }): Promise<AppDetailResponse> => {
|
||||||
return post<AppDetailResponse>(url, { body })
|
return post<AppDetailResponse>(url, { body })
|
||||||
}
|
}
|
||||||
|
|
||||||
export const updateAppApiStatus: Fetcher<AppDetailResponse, { url: string; body: Record<string, any> }> = ({ url, body }) => {
|
export const updateAppApiStatus = ({ url, body }: { url: string; body: Record<string, any> }): Promise<AppDetailResponse> => {
|
||||||
return post<AppDetailResponse>(url, { body })
|
return post<AppDetailResponse>(url, { body })
|
||||||
}
|
}
|
||||||
|
|
||||||
// path: /apps/{appId}/rate-limit
|
// path: /apps/{appId}/rate-limit
|
||||||
export const updateAppRateLimit: Fetcher<AppDetailResponse, { url: string; body: Record<string, any> }> = ({ url, body }) => {
|
export const updateAppRateLimit = ({ url, body }: { url: string; body: Record<string, any> }): Promise<AppDetailResponse> => {
|
||||||
return post<AppDetailResponse>(url, { body })
|
return post<AppDetailResponse>(url, { body })
|
||||||
}
|
}
|
||||||
|
|
||||||
export const updateAppSiteAccessToken: Fetcher<UpdateAppSiteCodeResponse, { url: string }> = ({ url }) => {
|
export const updateAppSiteAccessToken = ({ url }: { url: string }): Promise<UpdateAppSiteCodeResponse> => {
|
||||||
return post<UpdateAppSiteCodeResponse>(url)
|
return post<UpdateAppSiteCodeResponse>(url)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const updateAppSiteConfig = ({ url, body }: { url: string; body: Record<string, any> }) => {
|
export const updateAppSiteConfig = ({ url, body }: { url: string; body: Record<string, any> }): Promise<AppDetailResponse> => {
|
||||||
return post<AppDetailResponse>(url, { body })
|
return post<AppDetailResponse>(url, { body })
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getAppDailyMessages: Fetcher<AppDailyMessagesResponse, { url: string; params: Record<string, any> }> = ({ url, params }) => {
|
export const getAppDailyMessages = ({ url, params }: { url: string; params: Record<string, any> }): Promise<AppDailyMessagesResponse> => {
|
||||||
return get<AppDailyMessagesResponse>(url, { params })
|
return get<AppDailyMessagesResponse>(url, { params })
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getAppDailyConversations: Fetcher<AppDailyConversationsResponse, { url: string; params: Record<string, any> }> = ({ url, params }) => {
|
export const getAppDailyConversations = ({ url, params }: { url: string; params: Record<string, any> }): Promise<AppDailyConversationsResponse> => {
|
||||||
return get<AppDailyConversationsResponse>(url, { params })
|
return get<AppDailyConversationsResponse>(url, { params })
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getWorkflowDailyConversations: Fetcher<WorkflowDailyConversationsResponse, { url: string; params: Record<string, any> }> = ({ url, params }) => {
|
export const getWorkflowDailyConversations = ({ url, params }: { url: string; params: Record<string, any> }): Promise<WorkflowDailyConversationsResponse> => {
|
||||||
return get<WorkflowDailyConversationsResponse>(url, { params })
|
return get<WorkflowDailyConversationsResponse>(url, { params })
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getAppStatistics: Fetcher<AppStatisticsResponse, { url: string; params: Record<string, any> }> = ({ url, params }) => {
|
export const getAppStatistics = ({ url, params }: { url: string; params: Record<string, any> }): Promise<AppStatisticsResponse> => {
|
||||||
return get<AppStatisticsResponse>(url, { params })
|
return get<AppStatisticsResponse>(url, { params })
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getAppDailyEndUsers: Fetcher<AppDailyEndUsersResponse, { url: string; params: Record<string, any> }> = ({ url, params }) => {
|
export const getAppDailyEndUsers = ({ url, params }: { url: string; params: Record<string, any> }): Promise<AppDailyEndUsersResponse> => {
|
||||||
return get<AppDailyEndUsersResponse>(url, { params })
|
return get<AppDailyEndUsersResponse>(url, { params })
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getAppTokenCosts: Fetcher<AppTokenCostsResponse, { url: string; params: Record<string, any> }> = ({ url, params }) => {
|
export const getAppTokenCosts = ({ url, params }: { url: string; params: Record<string, any> }): Promise<AppTokenCostsResponse> => {
|
||||||
return get<AppTokenCostsResponse>(url, { params })
|
return get<AppTokenCostsResponse>(url, { params })
|
||||||
}
|
}
|
||||||
|
|
||||||
export const updateAppModelConfig: Fetcher<UpdateAppModelConfigResponse, { url: string; body: Record<string, any> }> = ({ url, body }) => {
|
export const updateAppModelConfig = ({ url, body }: { url: string; body: Record<string, any> }): Promise<UpdateAppModelConfigResponse> => {
|
||||||
return post<UpdateAppModelConfigResponse>(url, { body })
|
return post<UpdateAppModelConfigResponse>(url, { body })
|
||||||
}
|
}
|
||||||
|
|
||||||
// For temp testing
|
// For temp testing
|
||||||
export const fetchAppListNoMock: Fetcher<AppListResponse, { url: string; params: Record<string, any> }> = ({ url, params }) => {
|
export const fetchAppListNoMock = ({ url, params }: { url: string; params: Record<string, any> }): Promise<AppListResponse> => {
|
||||||
return get<AppListResponse>(url, params)
|
return get<AppListResponse>(url, params)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const fetchApiKeysList: Fetcher<ApiKeysListResponse, { url: string; params: Record<string, any> }> = ({ url, params }) => {
|
export const fetchApiKeysList = ({ url, params }: { url: string; params: Record<string, any> }): Promise<ApiKeysListResponse> => {
|
||||||
return get<ApiKeysListResponse>(url, params)
|
return get<ApiKeysListResponse>(url, params)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const delApikey: Fetcher<CommonResponse, { url: string; params: Record<string, any> }> = ({ url, params }) => {
|
export const delApikey = ({ url, params }: { url: string; params: Record<string, any> }): Promise<CommonResponse> => {
|
||||||
return del<CommonResponse>(url, params)
|
return del<CommonResponse>(url, params)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const createApikey: Fetcher<CreateApiKeyResponse, { url: string; body: Record<string, any> }> = ({ url, body }) => {
|
export const createApikey = ({ url, body }: { url: string; body: Record<string, any> }): Promise<CreateApiKeyResponse> => {
|
||||||
return post<CreateApiKeyResponse>(url, body)
|
return post<CreateApiKeyResponse>(url, body)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const validateOpenAIKey: Fetcher<ValidateOpenAIKeyResponse, { url: string; body: { token: string } }> = ({ url, body }) => {
|
export const validateOpenAIKey = ({ url, body }: { url: string; body: { token: string } }): Promise<ValidateOpenAIKeyResponse> => {
|
||||||
return post<ValidateOpenAIKeyResponse>(url, { body })
|
return post<ValidateOpenAIKeyResponse>(url, { body })
|
||||||
}
|
}
|
||||||
|
|
||||||
export const updateOpenAIKey: Fetcher<UpdateOpenAIKeyResponse, { url: string; body: { token: string } }> = ({ url, body }) => {
|
export const updateOpenAIKey = ({ url, body }: { url: string; body: { token: string } }): Promise<UpdateOpenAIKeyResponse> => {
|
||||||
return post<UpdateOpenAIKeyResponse>(url, { body })
|
return post<UpdateOpenAIKeyResponse>(url, { body })
|
||||||
}
|
}
|
||||||
|
|
||||||
export const generationIntroduction: Fetcher<GenerationIntroductionResponse, { url: string; body: { prompt_template: string } }> = ({ url, body }) => {
|
export const generationIntroduction = ({ url, body }: { url: string; body: { prompt_template: string } }): Promise<GenerationIntroductionResponse> => {
|
||||||
return post<GenerationIntroductionResponse>(url, { body })
|
return post<GenerationIntroductionResponse>(url, { body })
|
||||||
}
|
}
|
||||||
|
|
||||||
export const fetchAppVoices: Fetcher<AppVoicesListResponse, { appId: string; language?: string }> = ({ appId, language }) => {
|
export const fetchAppVoices = ({ appId, language }: { appId: string; language?: string }): Promise<AppVoicesListResponse> => {
|
||||||
language = language || 'en-US'
|
language = language || 'en-US'
|
||||||
return get<AppVoicesListResponse>(`apps/${appId}/text-to-audio/voices?language=${language}`)
|
return get<AppVoicesListResponse>(`apps/${appId}/text-to-audio/voices?language=${language}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tracing
|
// Tracing
|
||||||
export const fetchTracingStatus: Fetcher<TracingStatus, { appId: string }> = ({ appId }) => {
|
export const fetchTracingStatus = ({ appId }: { appId: string }): Promise<TracingStatus> => {
|
||||||
return get(`/apps/${appId}/trace`)
|
return get<TracingStatus>(`/apps/${appId}/trace`)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const updateTracingStatus: Fetcher<CommonResponse, { appId: string; body: Record<string, any> }> = ({ appId, body }) => {
|
export const updateTracingStatus = ({ appId, body }: { appId: string; body: Record<string, any> }): Promise<CommonResponse> => {
|
||||||
return post(`/apps/${appId}/trace`, { body })
|
return post<CommonResponse>(`/apps/${appId}/trace`, { body })
|
||||||
}
|
}
|
||||||
|
|
||||||
// Webhook Trigger
|
// Webhook Trigger
|
||||||
export const fetchWebhookUrl: Fetcher<WebhookTriggerResponse, { appId: string; nodeId: string }> = ({ appId, nodeId }) => {
|
export const fetchWebhookUrl = ({ appId, nodeId }: { appId: string; nodeId: string }): Promise<WebhookTriggerResponse> => {
|
||||||
return get<WebhookTriggerResponse>(
|
return get<WebhookTriggerResponse>(
|
||||||
`apps/${appId}/workflows/triggers/webhook`,
|
`apps/${appId}/workflows/triggers/webhook`,
|
||||||
{ params: { node_id: nodeId } },
|
{ params: { node_id: nodeId } },
|
||||||
@@ -171,22 +210,22 @@ export const fetchWebhookUrl: Fetcher<WebhookTriggerResponse, { appId: string; n
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const fetchTracingConfig: Fetcher<TracingConfig & { has_not_configured: true }, { appId: string; provider: TracingProvider }> = ({ appId, provider }) => {
|
export const fetchTracingConfig = ({ appId, provider }: { appId: string; provider: TracingProvider }): Promise<TracingConfig & { has_not_configured: true }> => {
|
||||||
return get(`/apps/${appId}/trace-config`, {
|
return get<TracingConfig & { has_not_configured: true }>(`/apps/${appId}/trace-config`, {
|
||||||
params: {
|
params: {
|
||||||
tracing_provider: provider,
|
tracing_provider: provider,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export const addTracingConfig: Fetcher<CommonResponse, { appId: string; body: TracingConfig }> = ({ appId, body }) => {
|
export const addTracingConfig = ({ appId, body }: { appId: string; body: TracingConfig }): Promise<CommonResponse> => {
|
||||||
return post(`/apps/${appId}/trace-config`, { body })
|
return post<CommonResponse>(`/apps/${appId}/trace-config`, { body })
|
||||||
}
|
}
|
||||||
|
|
||||||
export const updateTracingConfig: Fetcher<CommonResponse, { appId: string; body: TracingConfig }> = ({ appId, body }) => {
|
export const updateTracingConfig = ({ appId, body }: { appId: string; body: TracingConfig }): Promise<CommonResponse> => {
|
||||||
return patch(`/apps/${appId}/trace-config`, { body })
|
return patch<CommonResponse>(`/apps/${appId}/trace-config`, { body })
|
||||||
}
|
}
|
||||||
|
|
||||||
export const removeTracingConfig: Fetcher<CommonResponse, { appId: string; provider: TracingProvider }> = ({ appId, provider }) => {
|
export const removeTracingConfig = ({ appId, provider }: { appId: string; provider: TracingProvider }): Promise<CommonResponse> => {
|
||||||
return del(`/apps/${appId}/trace-config?tracing_provider=${provider}`)
|
return del<CommonResponse>(`/apps/${appId}/trace-config?tracing_provider=${provider}`)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,38 +1,85 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import type { FC } from 'react'
|
import type { FC } from 'react'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import useSWR, { useSWRConfig } from 'swr'
|
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
|
||||||
import { createApp, fetchAppDetail, fetchAppList, getAppDailyConversations, getAppDailyEndUsers, updateAppApiStatus, updateAppModelConfig, updateAppRateLimit, updateAppSiteAccessToken, updateAppSiteConfig, updateAppSiteStatus } from '../apps'
|
import { createApp, updateAppApiStatus, updateAppModelConfig, updateAppRateLimit, updateAppSiteAccessToken, updateAppSiteConfig, updateAppSiteStatus } from '../apps'
|
||||||
import Loading from '@/app/components/base/loading'
|
import Loading from '@/app/components/base/loading'
|
||||||
import { AppModeEnum } from '@/types/app'
|
import { AppModeEnum } from '@/types/app'
|
||||||
|
import {
|
||||||
|
useAppDailyConversations,
|
||||||
|
useAppDailyEndUsers,
|
||||||
|
useAppDetail,
|
||||||
|
useAppList,
|
||||||
|
} from '../use-apps'
|
||||||
|
|
||||||
const Service: FC = () => {
|
const Service: FC = () => {
|
||||||
const { data: appList, error: appListError } = useSWR({ url: '/apps', params: { page: 1 } }, fetchAppList)
|
const appId = '1'
|
||||||
const { data: firstApp, error: appDetailError } = useSWR({ url: '/apps', id: '1' }, fetchAppDetail)
|
const queryClient = useQueryClient()
|
||||||
const { data: updateAppSiteStatusRes, error: err1 } = useSWR({ url: '/apps', id: '1', body: { enable_site: false } }, updateAppSiteStatus)
|
|
||||||
const { data: updateAppApiStatusRes, error: err2 } = useSWR({ url: '/apps', id: '1', body: { enable_api: true } }, updateAppApiStatus)
|
|
||||||
const { data: updateAppRateLimitRes, error: err3 } = useSWR({ url: '/apps', id: '1', body: { api_rpm: 10, api_rph: 20 } }, updateAppRateLimit)
|
|
||||||
const { data: updateAppSiteCodeRes, error: err4 } = useSWR({ url: '/apps', id: '1', body: {} }, updateAppSiteAccessToken)
|
|
||||||
const { data: updateAppSiteConfigRes, error: err5 } = useSWR({ url: '/apps', id: '1', body: { title: 'title test', author: 'author test' } }, updateAppSiteConfig)
|
|
||||||
const { data: getAppDailyConversationsRes, error: err6 } = useSWR({ url: '/apps', id: '1', body: { start: '1', end: '2' } }, getAppDailyConversations)
|
|
||||||
const { data: getAppDailyEndUsersRes, error: err7 } = useSWR({ url: '/apps', id: '1', body: { start: '1', end: '2' } }, getAppDailyEndUsers)
|
|
||||||
const { data: updateAppModelConfigRes, error: err8 } = useSWR({ url: '/apps', id: '1', body: { model_id: 'gpt-100' } }, updateAppModelConfig)
|
|
||||||
|
|
||||||
const { mutate } = useSWRConfig()
|
const { data: appList, error: appListError, isLoading: isAppListLoading } = useAppList({ page: 1, limit: 30, name: '' })
|
||||||
|
const { data: firstApp, error: appDetailError, isLoading: isAppDetailLoading } = useAppDetail(appId)
|
||||||
|
|
||||||
const handleCreateApp = async () => {
|
const { data: updateAppSiteStatusRes, error: err1, isLoading: isUpdatingSiteStatus } = useQuery({
|
||||||
await createApp({
|
queryKey: ['demo', 'updateAppSiteStatus', appId],
|
||||||
|
queryFn: () => updateAppSiteStatus({ url: '/apps', body: { enable_site: false } }),
|
||||||
|
})
|
||||||
|
const { data: updateAppApiStatusRes, error: err2, isLoading: isUpdatingApiStatus } = useQuery({
|
||||||
|
queryKey: ['demo', 'updateAppApiStatus', appId],
|
||||||
|
queryFn: () => updateAppApiStatus({ url: '/apps', body: { enable_api: true } }),
|
||||||
|
})
|
||||||
|
const { data: updateAppRateLimitRes, error: err3, isLoading: isUpdatingRateLimit } = useQuery({
|
||||||
|
queryKey: ['demo', 'updateAppRateLimit', appId],
|
||||||
|
queryFn: () => updateAppRateLimit({ url: '/apps', body: { api_rpm: 10, api_rph: 20 } }),
|
||||||
|
})
|
||||||
|
const { data: updateAppSiteCodeRes, error: err4, isLoading: isUpdatingSiteCode } = useQuery({
|
||||||
|
queryKey: ['demo', 'updateAppSiteAccessToken', appId],
|
||||||
|
queryFn: () => updateAppSiteAccessToken({ url: '/apps' }),
|
||||||
|
})
|
||||||
|
const { data: updateAppSiteConfigRes, error: err5, isLoading: isUpdatingSiteConfig } = useQuery({
|
||||||
|
queryKey: ['demo', 'updateAppSiteConfig', appId],
|
||||||
|
queryFn: () => updateAppSiteConfig({ url: '/apps', body: { title: 'title test', author: 'author test' } }),
|
||||||
|
})
|
||||||
|
|
||||||
|
const { data: getAppDailyConversationsRes, error: err6, isLoading: isConversationsLoading } = useAppDailyConversations(appId, { start: '1', end: '2' })
|
||||||
|
const { data: getAppDailyEndUsersRes, error: err7, isLoading: isEndUsersLoading } = useAppDailyEndUsers(appId, { start: '1', end: '2' })
|
||||||
|
|
||||||
|
const { data: updateAppModelConfigRes, error: err8, isLoading: isUpdatingModelConfig } = useQuery({
|
||||||
|
queryKey: ['demo', 'updateAppModelConfig', appId],
|
||||||
|
queryFn: () => updateAppModelConfig({ url: '/apps', body: { model_id: 'gpt-100' } }),
|
||||||
|
})
|
||||||
|
|
||||||
|
const { mutateAsync: mutateCreateApp } = useMutation({
|
||||||
|
mutationKey: ['demo', 'createApp'],
|
||||||
|
mutationFn: () => createApp({
|
||||||
name: `new app${Math.round(Math.random() * 100)}`,
|
name: `new app${Math.round(Math.random() * 100)}`,
|
||||||
mode: AppModeEnum.CHAT,
|
mode: AppModeEnum.CHAT,
|
||||||
|
}),
|
||||||
|
onSuccess: () => {
|
||||||
|
queryClient.invalidateQueries({
|
||||||
|
queryKey: ['apps', 'list'],
|
||||||
})
|
})
|
||||||
// reload app list
|
},
|
||||||
mutate({ url: '/apps', params: { page: 1 } })
|
})
|
||||||
|
|
||||||
|
const handleCreateApp = async () => {
|
||||||
|
await mutateCreateApp()
|
||||||
}
|
}
|
||||||
|
|
||||||
if (appListError || appDetailError || err1 || err2 || err3 || err4 || err5 || err6 || err7 || err8)
|
if (appListError || appDetailError || err1 || err2 || err3 || err4 || err5 || err6 || err7 || err8)
|
||||||
return <div>{JSON.stringify(appListError)}</div>
|
return <div>{JSON.stringify(appListError ?? appDetailError ?? err1 ?? err2 ?? err3 ?? err4 ?? err5 ?? err6 ?? err7 ?? err8)}</div>
|
||||||
|
|
||||||
if (!appList || !firstApp || !updateAppSiteStatusRes || !updateAppApiStatusRes || !updateAppRateLimitRes || !updateAppSiteCodeRes || !updateAppSiteConfigRes || !getAppDailyConversationsRes || !getAppDailyEndUsersRes || !updateAppModelConfigRes)
|
const isLoading = isAppListLoading
|
||||||
|
|| isAppDetailLoading
|
||||||
|
|| isUpdatingSiteStatus
|
||||||
|
|| isUpdatingApiStatus
|
||||||
|
|| isUpdatingRateLimit
|
||||||
|
|| isUpdatingSiteCode
|
||||||
|
|| isUpdatingSiteConfig
|
||||||
|
|| isConversationsLoading
|
||||||
|
|| isEndUsersLoading
|
||||||
|
|| isUpdatingModelConfig
|
||||||
|
|
||||||
|
if (isLoading || !appList || !firstApp || !updateAppSiteStatusRes || !updateAppApiStatusRes || !updateAppRateLimitRes || !updateAppSiteCodeRes || !updateAppSiteConfigRes || !getAppDailyConversationsRes || !getAppDailyEndUsersRes || !updateAppModelConfigRes)
|
||||||
return <Loading />
|
return <Loading />
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -1,31 +1,63 @@
|
|||||||
import { get, post } from './base'
|
import { get, post } from './base'
|
||||||
import type { App } from '@/types/app'
|
import type {
|
||||||
import type { AppListResponse } from '@/models/app'
|
ApiKeysListResponse,
|
||||||
|
AppDailyConversationsResponse,
|
||||||
|
AppDailyEndUsersResponse,
|
||||||
|
AppDailyMessagesResponse,
|
||||||
|
AppListResponse,
|
||||||
|
AppStatisticsResponse,
|
||||||
|
AppTokenCostsResponse,
|
||||||
|
AppVoicesListResponse,
|
||||||
|
WorkflowDailyConversationsResponse,
|
||||||
|
} from '@/models/app'
|
||||||
|
import type { App, AppModeEnum } from '@/types/app'
|
||||||
import { useInvalid } from './use-base'
|
import { useInvalid } from './use-base'
|
||||||
import { useQuery } from '@tanstack/react-query'
|
import {
|
||||||
|
useInfiniteQuery,
|
||||||
|
useQuery,
|
||||||
|
useQueryClient,
|
||||||
|
} from '@tanstack/react-query'
|
||||||
import type { GeneratorType } from '@/app/components/app/configuration/config/automatic/types'
|
import type { GeneratorType } from '@/app/components/app/configuration/config/automatic/types'
|
||||||
|
|
||||||
const NAME_SPACE = 'apps'
|
const NAME_SPACE = 'apps'
|
||||||
|
|
||||||
// TODO paging for list
|
type AppListParams = {
|
||||||
|
page?: number
|
||||||
|
limit?: number
|
||||||
|
name?: string
|
||||||
|
mode?: AppModeEnum | 'all'
|
||||||
|
tag_ids?: string[]
|
||||||
|
is_created_by_me?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
type DateRangeParams = {
|
||||||
|
start?: string
|
||||||
|
end?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const normalizeAppListParams = (params: AppListParams) => {
|
||||||
|
const {
|
||||||
|
page = 1,
|
||||||
|
limit = 30,
|
||||||
|
name = '',
|
||||||
|
mode,
|
||||||
|
tag_ids,
|
||||||
|
is_created_by_me,
|
||||||
|
} = params
|
||||||
|
|
||||||
|
return {
|
||||||
|
page,
|
||||||
|
limit,
|
||||||
|
name,
|
||||||
|
...(mode && mode !== 'all' ? { mode } : {}),
|
||||||
|
...(tag_ids?.length ? { tag_ids } : {}),
|
||||||
|
...(is_created_by_me ? { is_created_by_me } : {}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const appListKey = (params: AppListParams) => [NAME_SPACE, 'list', params]
|
||||||
|
|
||||||
const useAppFullListKey = [NAME_SPACE, 'full-list']
|
const useAppFullListKey = [NAME_SPACE, 'full-list']
|
||||||
export const useAppFullList = () => {
|
|
||||||
return useQuery<AppListResponse>({
|
|
||||||
queryKey: useAppFullListKey,
|
|
||||||
queryFn: () => get<AppListResponse>('/apps', { params: { page: 1, limit: 100 } }),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
export const useInvalidateAppFullList = () => {
|
|
||||||
return useInvalid(useAppFullListKey)
|
|
||||||
}
|
|
||||||
|
|
||||||
export const useAppDetail = (appID: string) => {
|
|
||||||
return useQuery<App>({
|
|
||||||
queryKey: [NAME_SPACE, 'detail', appID],
|
|
||||||
queryFn: () => get<App>(`/apps/${appID}`),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
export const useGenerateRuleTemplate = (type: GeneratorType, disabled?: boolean) => {
|
export const useGenerateRuleTemplate = (type: GeneratorType, disabled?: boolean) => {
|
||||||
return useQuery({
|
return useQuery({
|
||||||
@@ -39,3 +71,142 @@ export const useGenerateRuleTemplate = (type: GeneratorType, disabled?: boolean)
|
|||||||
retry: 0,
|
retry: 0,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const useAppDetail = (appID: string) => {
|
||||||
|
return useQuery<App>({
|
||||||
|
queryKey: [NAME_SPACE, 'detail', appID],
|
||||||
|
queryFn: () => get<App>(`/apps/${appID}`),
|
||||||
|
enabled: !!appID,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useAppList = (params: AppListParams, options?: { enabled?: boolean }) => {
|
||||||
|
const normalizedParams = normalizeAppListParams(params)
|
||||||
|
return useQuery<AppListResponse>({
|
||||||
|
queryKey: appListKey(normalizedParams),
|
||||||
|
queryFn: () => get<AppListResponse>('/apps', { params: normalizedParams }),
|
||||||
|
...options,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useAppFullList = () => {
|
||||||
|
return useQuery<AppListResponse>({
|
||||||
|
queryKey: useAppFullListKey,
|
||||||
|
queryFn: () => get<AppListResponse>('/apps', { params: { page: 1, limit: 100, name: '' } }),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useInvalidateAppFullList = () => {
|
||||||
|
return useInvalid(useAppFullListKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useInfiniteAppList = (params: AppListParams, options?: { enabled?: boolean }) => {
|
||||||
|
const normalizedParams = normalizeAppListParams(params)
|
||||||
|
return useInfiniteQuery<AppListResponse>({
|
||||||
|
queryKey: appListKey(normalizedParams),
|
||||||
|
queryFn: ({ pageParam = normalizedParams.page }) => get<AppListResponse>('/apps', { params: { ...normalizedParams, page: pageParam } }),
|
||||||
|
getNextPageParam: lastPage => lastPage.has_more ? lastPage.page + 1 : undefined,
|
||||||
|
initialPageParam: normalizedParams.page,
|
||||||
|
...options,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useInvalidateAppList = () => {
|
||||||
|
const queryClient = useQueryClient()
|
||||||
|
return () => {
|
||||||
|
queryClient.invalidateQueries({
|
||||||
|
queryKey: [NAME_SPACE, 'list'],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const useAppStatisticsQuery = <T>(metric: string, appId: string, params?: DateRangeParams) => {
|
||||||
|
return useQuery<T>({
|
||||||
|
queryKey: [NAME_SPACE, 'statistics', metric, appId, params],
|
||||||
|
queryFn: () => get<T>(`/apps/${appId}/statistics/${metric}`, { params }),
|
||||||
|
enabled: !!appId,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const useWorkflowStatisticsQuery = <T>(metric: string, appId: string, params?: DateRangeParams) => {
|
||||||
|
return useQuery<T>({
|
||||||
|
queryKey: [NAME_SPACE, 'workflow-statistics', metric, appId, params],
|
||||||
|
queryFn: () => get<T>(`/apps/${appId}/workflow/statistics/${metric}`, { params }),
|
||||||
|
enabled: !!appId,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useAppDailyMessages = (appId: string, params?: DateRangeParams) => {
|
||||||
|
return useAppStatisticsQuery<AppDailyMessagesResponse>('daily-messages', appId, params)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useAppDailyConversations = (appId: string, params?: DateRangeParams) => {
|
||||||
|
return useAppStatisticsQuery<AppDailyConversationsResponse>('daily-conversations', appId, params)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useAppDailyEndUsers = (appId: string, params?: DateRangeParams) => {
|
||||||
|
return useAppStatisticsQuery<AppDailyEndUsersResponse>('daily-end-users', appId, params)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useAppAverageSessionInteractions = (appId: string, params?: DateRangeParams) => {
|
||||||
|
return useAppStatisticsQuery<AppStatisticsResponse>('average-session-interactions', appId, params)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useAppAverageResponseTime = (appId: string, params?: DateRangeParams) => {
|
||||||
|
return useAppStatisticsQuery<AppStatisticsResponse>('average-response-time', appId, params)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useAppTokensPerSecond = (appId: string, params?: DateRangeParams) => {
|
||||||
|
return useAppStatisticsQuery<AppStatisticsResponse>('tokens-per-second', appId, params)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useAppSatisfactionRate = (appId: string, params?: DateRangeParams) => {
|
||||||
|
return useAppStatisticsQuery<AppStatisticsResponse>('user-satisfaction-rate', appId, params)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useAppTokenCosts = (appId: string, params?: DateRangeParams) => {
|
||||||
|
return useAppStatisticsQuery<AppTokenCostsResponse>('token-costs', appId, params)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useWorkflowDailyConversations = (appId: string, params?: DateRangeParams) => {
|
||||||
|
return useWorkflowStatisticsQuery<WorkflowDailyConversationsResponse>('daily-conversations', appId, params)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useWorkflowDailyTerminals = (appId: string, params?: DateRangeParams) => {
|
||||||
|
return useWorkflowStatisticsQuery<AppDailyEndUsersResponse>('daily-terminals', appId, params)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useWorkflowTokenCosts = (appId: string, params?: DateRangeParams) => {
|
||||||
|
return useWorkflowStatisticsQuery<AppTokenCostsResponse>('token-costs', appId, params)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useWorkflowAverageInteractions = (appId: string, params?: DateRangeParams) => {
|
||||||
|
return useWorkflowStatisticsQuery<AppStatisticsResponse>('average-app-interactions', appId, params)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useAppVoices = (appId?: string, language?: string) => {
|
||||||
|
return useQuery<AppVoicesListResponse>({
|
||||||
|
queryKey: [NAME_SPACE, 'voices', appId, language || 'en-US'],
|
||||||
|
queryFn: () => get<AppVoicesListResponse>(`/apps/${appId}/text-to-audio/voices`, { params: { language: language || 'en-US' } }),
|
||||||
|
enabled: !!appId,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useAppApiKeys = (appId?: string, options?: { enabled?: boolean }) => {
|
||||||
|
return useQuery<ApiKeysListResponse>({
|
||||||
|
queryKey: [NAME_SPACE, 'api-keys', appId],
|
||||||
|
queryFn: () => get<ApiKeysListResponse>(`/apps/${appId}/api-keys`),
|
||||||
|
enabled: !!appId && (options?.enabled ?? true),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useInvalidateAppApiKeys = () => {
|
||||||
|
const queryClient = useQueryClient()
|
||||||
|
return (appId?: string) => {
|
||||||
|
if (!appId)
|
||||||
|
return
|
||||||
|
queryClient.invalidateQueries({
|
||||||
|
queryKey: [NAME_SPACE, 'api-keys', appId],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user