mirror of
https://gitee.com/dify_ai/dify.git
synced 2025-12-06 11:29:30 +08:00
Refactor apps service toward TanStack Query (#29004)
This commit is contained in:
@@ -1,6 +1,5 @@
|
||||
'use client'
|
||||
import { useState } from 'react'
|
||||
import useSWR from 'swr'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import {
|
||||
RiGraduationCapFill,
|
||||
@@ -23,8 +22,9 @@ import PremiumBadge from '@/app/components/base/premium-badge'
|
||||
import { useGlobalPublicStore } from '@/context/global-public-context'
|
||||
import EmailChangeModal from './email-change-modal'
|
||||
import { validPassword } from '@/config'
|
||||
import { fetchAppList } from '@/service/apps'
|
||||
|
||||
import type { App } from '@/types/app'
|
||||
import { useAppList } from '@/service/use-apps'
|
||||
|
||||
const titleClassName = `
|
||||
system-sm-semibold text-text-secondary
|
||||
@@ -36,7 +36,7 @@ const descriptionClassName = `
|
||||
export default function AccountPage() {
|
||||
const { t } = useTranslation()
|
||||
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 { mutateUserProfile, userProfile } = useAppContext()
|
||||
const { isEducationAccount } = useProviderContext()
|
||||
|
||||
@@ -3,7 +3,6 @@ import type { FC } from 'react'
|
||||
import React from 'react'
|
||||
import ReactECharts from 'echarts-for-react'
|
||||
import type { EChartsOption } from 'echarts'
|
||||
import useSWR from 'swr'
|
||||
import type { Dayjs } from 'dayjs'
|
||||
import dayjs from 'dayjs'
|
||||
import { get } from 'lodash-es'
|
||||
@@ -13,7 +12,20 @@ import { formatNumber } from '@/utils/format'
|
||||
import Basic from '@/app/components/app-sidebar/basic'
|
||||
import Loading from '@/app/components/base/loading'
|
||||
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 COLOR_TYPE_MAP = {
|
||||
@@ -272,8 +284,8 @@ const getDefaultChartData = ({ start, end, key = 'count' }: { start: string; end
|
||||
|
||||
export const MessagesChart: FC<IBizChartProps> = ({ id, period }) => {
|
||||
const { t } = useTranslation()
|
||||
const { data: response } = useSWR({ url: `/apps/${id}/statistics/daily-messages`, params: period.query }, getAppDailyMessages)
|
||||
if (!response)
|
||||
const { data: response, isLoading } = useAppDailyMessages(id, period.query)
|
||||
if (isLoading || !response)
|
||||
return <Loading />
|
||||
const noDataFlag = !response.data || response.data.length === 0
|
||||
return <Chart
|
||||
@@ -286,8 +298,8 @@ export const MessagesChart: FC<IBizChartProps> = ({ id, period }) => {
|
||||
|
||||
export const ConversationsChart: FC<IBizChartProps> = ({ id, period }) => {
|
||||
const { t } = useTranslation()
|
||||
const { data: response } = useSWR({ url: `/apps/${id}/statistics/daily-conversations`, params: period.query }, getAppDailyConversations)
|
||||
if (!response)
|
||||
const { data: response, isLoading } = useAppDailyConversations(id, period.query)
|
||||
if (isLoading || !response)
|
||||
return <Loading />
|
||||
const noDataFlag = !response.data || response.data.length === 0
|
||||
return <Chart
|
||||
@@ -301,8 +313,8 @@ export const ConversationsChart: FC<IBizChartProps> = ({ id, period }) => {
|
||||
export const EndUsersChart: FC<IBizChartProps> = ({ id, period }) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
const { data: response } = useSWR({ url: `/apps/${id}/statistics/daily-end-users`, id, params: period.query }, getAppDailyEndUsers)
|
||||
if (!response)
|
||||
const { data: response, isLoading } = useAppDailyEndUsers(id, period.query)
|
||||
if (isLoading || !response)
|
||||
return <Loading />
|
||||
const noDataFlag = !response.data || response.data.length === 0
|
||||
return <Chart
|
||||
@@ -315,8 +327,8 @@ export const EndUsersChart: FC<IBizChartProps> = ({ id, period }) => {
|
||||
|
||||
export const AvgSessionInteractions: FC<IBizChartProps> = ({ id, period }) => {
|
||||
const { t } = useTranslation()
|
||||
const { data: response } = useSWR({ url: `/apps/${id}/statistics/average-session-interactions`, params: period.query }, getAppStatistics)
|
||||
if (!response)
|
||||
const { data: response, isLoading } = useAppAverageSessionInteractions(id, period.query)
|
||||
if (isLoading || !response)
|
||||
return <Loading />
|
||||
const noDataFlag = !response.data || response.data.length === 0
|
||||
return <Chart
|
||||
@@ -331,8 +343,8 @@ export const AvgSessionInteractions: FC<IBizChartProps> = ({ id, period }) => {
|
||||
|
||||
export const AvgResponseTime: FC<IBizChartProps> = ({ id, period }) => {
|
||||
const { t } = useTranslation()
|
||||
const { data: response } = useSWR({ url: `/apps/${id}/statistics/average-response-time`, params: period.query }, getAppStatistics)
|
||||
if (!response)
|
||||
const { data: response, isLoading } = useAppAverageResponseTime(id, period.query)
|
||||
if (isLoading || !response)
|
||||
return <Loading />
|
||||
const noDataFlag = !response.data || response.data.length === 0
|
||||
return <Chart
|
||||
@@ -348,8 +360,8 @@ export const AvgResponseTime: FC<IBizChartProps> = ({ id, period }) => {
|
||||
|
||||
export const TokenPerSecond: FC<IBizChartProps> = ({ id, period }) => {
|
||||
const { t } = useTranslation()
|
||||
const { data: response } = useSWR({ url: `/apps/${id}/statistics/tokens-per-second`, params: period.query }, getAppStatistics)
|
||||
if (!response)
|
||||
const { data: response, isLoading } = useAppTokensPerSecond(id, period.query)
|
||||
if (isLoading || !response)
|
||||
return <Loading />
|
||||
const noDataFlag = !response.data || response.data.length === 0
|
||||
return <Chart
|
||||
@@ -366,8 +378,8 @@ export const TokenPerSecond: FC<IBizChartProps> = ({ id, period }) => {
|
||||
|
||||
export const UserSatisfactionRate: FC<IBizChartProps> = ({ id, period }) => {
|
||||
const { t } = useTranslation()
|
||||
const { data: response } = useSWR({ url: `/apps/${id}/statistics/user-satisfaction-rate`, params: period.query }, getAppStatistics)
|
||||
if (!response)
|
||||
const { data: response, isLoading } = useAppSatisfactionRate(id, period.query)
|
||||
if (isLoading || !response)
|
||||
return <Loading />
|
||||
const noDataFlag = !response.data || response.data.length === 0
|
||||
return <Chart
|
||||
@@ -384,8 +396,8 @@ export const UserSatisfactionRate: FC<IBizChartProps> = ({ id, period }) => {
|
||||
export const CostChart: FC<IBizChartProps> = ({ id, period }) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
const { data: response } = useSWR({ url: `/apps/${id}/statistics/token-costs`, params: period.query }, getAppTokenCosts)
|
||||
if (!response)
|
||||
const { data: response, isLoading } = useAppTokenCosts(id, period.query)
|
||||
if (isLoading || !response)
|
||||
return <Loading />
|
||||
const noDataFlag = !response.data || response.data.length === 0
|
||||
return <Chart
|
||||
@@ -398,8 +410,8 @@ export const CostChart: FC<IBizChartProps> = ({ id, period }) => {
|
||||
|
||||
export const WorkflowMessagesChart: FC<IBizChartProps> = ({ id, period }) => {
|
||||
const { t } = useTranslation()
|
||||
const { data: response } = useSWR({ url: `/apps/${id}/workflow/statistics/daily-conversations`, params: period.query }, getWorkflowDailyConversations)
|
||||
if (!response)
|
||||
const { data: response, isLoading } = useWorkflowDailyConversations(id, period.query)
|
||||
if (isLoading || !response)
|
||||
return <Loading />
|
||||
const noDataFlag = !response.data || response.data.length === 0
|
||||
return <Chart
|
||||
@@ -414,8 +426,8 @@ export const WorkflowMessagesChart: FC<IBizChartProps> = ({ id, period }) => {
|
||||
export const WorkflowDailyTerminalsChart: FC<IBizChartProps> = ({ id, period }) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
const { data: response } = useSWR({ url: `/apps/${id}/workflow/statistics/daily-terminals`, id, params: period.query }, getAppDailyEndUsers)
|
||||
if (!response)
|
||||
const { data: response, isLoading } = useWorkflowDailyTerminals(id, period.query)
|
||||
if (isLoading || !response)
|
||||
return <Loading />
|
||||
const noDataFlag = !response.data || response.data.length === 0
|
||||
return <Chart
|
||||
@@ -429,8 +441,8 @@ export const WorkflowDailyTerminalsChart: FC<IBizChartProps> = ({ id, period })
|
||||
export const WorkflowCostChart: FC<IBizChartProps> = ({ id, period }) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
const { data: response } = useSWR({ url: `/apps/${id}/workflow/statistics/token-costs`, params: period.query }, getAppTokenCosts)
|
||||
if (!response)
|
||||
const { data: response, isLoading } = useWorkflowTokenCosts(id, period.query)
|
||||
if (isLoading || !response)
|
||||
return <Loading />
|
||||
const noDataFlag = !response.data || response.data.length === 0
|
||||
return <Chart
|
||||
@@ -443,8 +455,8 @@ export const WorkflowCostChart: FC<IBizChartProps> = ({ id, period }) => {
|
||||
|
||||
export const AvgUserInteractions: FC<IBizChartProps> = ({ id, period }) => {
|
||||
const { t } = useTranslation()
|
||||
const { data: response } = useSWR({ url: `/apps/${id}/workflow/statistics/average-app-interactions`, params: period.query }, getAppStatistics)
|
||||
if (!response)
|
||||
const { data: response, isLoading } = useWorkflowAverageInteractions(id, period.query)
|
||||
if (isLoading || !response)
|
||||
return <Loading />
|
||||
const noDataFlag = !response.data || response.data.length === 0
|
||||
return <Chart
|
||||
|
||||
@@ -23,7 +23,7 @@ const Empty = () => {
|
||||
return (
|
||||
<>
|
||||
<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'>
|
||||
{t('app.newApp.noAppsFound')}
|
||||
</span>
|
||||
|
||||
@@ -4,7 +4,6 @@ import { useCallback, useEffect, useRef, useState } from 'react'
|
||||
import {
|
||||
useRouter,
|
||||
} from 'next/navigation'
|
||||
import useSWRInfinite from 'swr/infinite'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useDebounceFn } from 'ahooks'
|
||||
import {
|
||||
@@ -19,8 +18,6 @@ import AppCard from './app-card'
|
||||
import NewAppCard from './new-app-card'
|
||||
import useAppsQueryState from './hooks/use-apps-query-state'
|
||||
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 { NEED_REFRESH_APP_LIST_KEY } from '@/config'
|
||||
import { CheckModal } from '@/hooks/use-pay'
|
||||
@@ -35,6 +32,7 @@ import Empty from './empty'
|
||||
import Footer from './footer'
|
||||
import { useGlobalPublicStore } from '@/context/global-public-context'
|
||||
import { AppModeEnum } from '@/types/app'
|
||||
import { useInfiniteAppList } from '@/service/use-apps'
|
||||
|
||||
const TagManagementModal = dynamic(() => import('@/app/components/base/tag-management'), {
|
||||
ssr: false,
|
||||
@@ -43,30 +41,6 @@ const CreateFromDSLModal = dynamic(() => import('@/app/components/app/create-fro
|
||||
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 { t } = useTranslation()
|
||||
const { systemFeatures } = useGlobalPublicStore()
|
||||
@@ -102,16 +76,24 @@ const List = () => {
|
||||
enabled: isCurrentWorkspaceEditor,
|
||||
})
|
||||
|
||||
const { data, isLoading, error, setSize, mutate } = useSWRInfinite(
|
||||
(pageIndex: number, previousPageData: AppListResponse) => getKey(pageIndex, previousPageData, activeTab, isCreatedByMe, tagIDs, searchKeywords),
|
||||
fetchAppList,
|
||||
{
|
||||
revalidateFirstPage: true,
|
||||
shouldRetryOnError: false,
|
||||
dedupingInterval: 500,
|
||||
errorRetryCount: 3,
|
||||
},
|
||||
)
|
||||
const appListQueryParams = {
|
||||
page: 1,
|
||||
limit: 30,
|
||||
name: searchKeywords,
|
||||
tag_ids: tagIDs,
|
||||
is_created_by_me: isCreatedByMe,
|
||||
...(activeTab !== 'all' ? { mode: activeTab as AppModeEnum } : {}),
|
||||
}
|
||||
|
||||
const {
|
||||
data,
|
||||
isLoading,
|
||||
isFetchingNextPage,
|
||||
fetchNextPage,
|
||||
hasNextPage,
|
||||
error,
|
||||
refetch,
|
||||
} = useInfiniteAppList(appListQueryParams, { enabled: !isCurrentWorkspaceDatasetOperator })
|
||||
|
||||
const anchorRef = useRef<HTMLDivElement>(null)
|
||||
const options = [
|
||||
@@ -126,9 +108,9 @@ const List = () => {
|
||||
useEffect(() => {
|
||||
if (localStorage.getItem(NEED_REFRESH_APP_LIST_KEY) === '1') {
|
||||
localStorage.removeItem(NEED_REFRESH_APP_LIST_KEY)
|
||||
mutate()
|
||||
refetch()
|
||||
}
|
||||
}, [mutate, t])
|
||||
}, [refetch])
|
||||
|
||||
useEffect(() => {
|
||||
if (isCurrentWorkspaceDatasetOperator)
|
||||
@@ -136,7 +118,9 @@ const List = () => {
|
||||
}, [router, isCurrentWorkspaceDatasetOperator])
|
||||
|
||||
useEffect(() => {
|
||||
const hasMore = data?.at(-1)?.has_more ?? true
|
||||
if (isCurrentWorkspaceDatasetOperator)
|
||||
return
|
||||
const hasMore = hasNextPage ?? true
|
||||
let observer: IntersectionObserver | undefined
|
||||
|
||||
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
|
||||
|
||||
observer = new IntersectionObserver((entries) => {
|
||||
if (entries[0].isIntersecting && !isLoading && !error && hasMore)
|
||||
setSize((size: number) => size + 1)
|
||||
if (entries[0].isIntersecting && !isLoading && !isFetchingNextPage && !error && hasMore)
|
||||
fetchNextPage()
|
||||
}, {
|
||||
root: containerRef.current,
|
||||
rootMargin: `${dynamicMargin}px`,
|
||||
@@ -161,7 +145,7 @@ const List = () => {
|
||||
observer.observe(anchorRef.current)
|
||||
}
|
||||
return () => observer?.disconnect()
|
||||
}, [isLoading, setSize, data, error])
|
||||
}, [isLoading, isFetchingNextPage, fetchNextPage, error, hasNextPage, isCurrentWorkspaceDatasetOperator])
|
||||
|
||||
const { run: handleSearch } = useDebounceFn(() => {
|
||||
setSearchKeywords(keywords)
|
||||
@@ -185,6 +169,9 @@ const List = () => {
|
||||
setQuery(prev => ({ ...prev, isCreatedByMe: newValue }))
|
||||
}, [isCreatedByMe, setQuery])
|
||||
|
||||
const pages = data?.pages ?? []
|
||||
const hasAnyApp = (pages[0]?.total ?? 0) > 0
|
||||
|
||||
return (
|
||||
<>
|
||||
<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>
|
||||
{(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'>
|
||||
{isCurrentWorkspaceEditor
|
||||
&& <NewAppCard ref={newAppCardRef} onSuccess={mutate} selectedAppType={activeTab} />}
|
||||
{data.map(({ data: apps }) => apps.map(app => (
|
||||
<AppCard key={app.id} app={app} onRefresh={mutate} />
|
||||
&& <NewAppCard ref={newAppCardRef} onSuccess={refetch} selectedAppType={activeTab} />}
|
||||
{pages.map(({ data: apps }) => apps.map(app => (
|
||||
<AppCard key={app.id} app={app} onRefresh={refetch} />
|
||||
)))}
|
||||
</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'>
|
||||
{isCurrentWorkspaceEditor
|
||||
&& <NewAppCard ref={newAppCardRef} className='z-10' onSuccess={mutate} selectedAppType={activeTab} />}
|
||||
&& <NewAppCard ref={newAppCardRef} className='z-10' onSuccess={refetch} selectedAppType={activeTab} />}
|
||||
<Empty />
|
||||
</div>}
|
||||
|
||||
@@ -261,7 +248,7 @@ const List = () => {
|
||||
onSuccess={() => {
|
||||
setShowCreateFromDSLModal(false)
|
||||
setDroppedDSLFile(undefined)
|
||||
mutate()
|
||||
refetch()
|
||||
}}
|
||||
droppedFile={droppedDSLFile}
|
||||
/>
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
'use client'
|
||||
import useSWR from 'swr'
|
||||
import { produce } from 'immer'
|
||||
import React, { Fragment } from 'react'
|
||||
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 { useFeatures, useFeaturesStore } from '@/app/components/base/features/hooks'
|
||||
import type { Item } from '@/app/components/base/select'
|
||||
import { fetchAppVoices } from '@/service/apps'
|
||||
import Tooltip from '@/app/components/base/tooltip'
|
||||
import Switch from '@/app/components/base/switch'
|
||||
import AudioBtn from '@/app/components/base/audio-btn'
|
||||
@@ -17,6 +15,7 @@ import { languages } from '@/i18n-config/language'
|
||||
import { TtsAutoPlay } from '@/types/app'
|
||||
import type { OnFeaturesChange } from '@/app/components/base/features/types'
|
||||
import classNames from '@/utils/classnames'
|
||||
import { useAppVoices } from '@/service/use-apps'
|
||||
|
||||
type VoiceParamConfigProps = {
|
||||
onClose: () => void
|
||||
@@ -39,7 +38,7 @@ const VoiceParamConfig = ({
|
||||
const localLanguagePlaceholder = languageItem?.name || t('common.placeholder.select')
|
||||
|
||||
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)
|
||||
if (voiceItems && !voiceItem)
|
||||
voiceItem = voiceItems[0]
|
||||
|
||||
@@ -5,7 +5,7 @@ import {
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { RiDeleteBinLine } from '@remixicon/react'
|
||||
import { PlusIcon, XMarkIcon } from '@heroicons/react/20/solid'
|
||||
import useSWR, { useSWRConfig } from 'swr'
|
||||
import useSWR from 'swr'
|
||||
import SecretKeyGenerateModal from './secret-key-generate'
|
||||
import s from './style.module.css'
|
||||
import ActionButton from '@/app/components/base/action-button'
|
||||
@@ -15,7 +15,6 @@ import CopyFeedback from '@/app/components/base/copy-feedback'
|
||||
import {
|
||||
createApikey as createAppApikey,
|
||||
delApikey as delAppApikey,
|
||||
fetchApiKeysList as fetchAppApiKeysList,
|
||||
} from '@/service/apps'
|
||||
import {
|
||||
createApikey as createDatasetApikey,
|
||||
@@ -27,6 +26,7 @@ import Loading from '@/app/components/base/loading'
|
||||
import Confirm from '@/app/components/base/confirm'
|
||||
import useTimestamp from '@/hooks/use-timestamp'
|
||||
import { useAppContext } from '@/context/app-context'
|
||||
import { useAppApiKeys, useInvalidateAppApiKeys } from '@/service/use-apps'
|
||||
|
||||
type ISecretKeyModalProps = {
|
||||
isShow: boolean
|
||||
@@ -45,12 +45,14 @@ const SecretKeyModal = ({
|
||||
const [showConfirmDelete, setShowConfirmDelete] = useState(false)
|
||||
const [isVisible, setVisible] = useState(false)
|
||||
const [newKey, setNewKey] = useState<CreateApiKeyResponse | undefined>(undefined)
|
||||
const { mutate } = useSWRConfig()
|
||||
const commonParams = appId
|
||||
? { url: `/apps/${appId}/api-keys`, params: {} }
|
||||
: { url: '/datasets/api-keys', params: {} }
|
||||
const fetchApiKeysList = appId ? fetchAppApiKeysList : fetchDatasetApiKeysList
|
||||
const { data: apiKeysList } = useSWR(commonParams, fetchApiKeysList)
|
||||
const invalidateAppApiKeys = useInvalidateAppApiKeys()
|
||||
const { data: appApiKeys, isLoading: isAppApiKeysLoading } = useAppApiKeys(appId, { enabled: !!appId && isShow })
|
||||
const { data: datasetApiKeys, isLoading: isDatasetApiKeysLoading, mutate: mutateDatasetApiKeys } = useSWR(
|
||||
!appId && isShow ? { url: '/datasets/api-keys', params: {} } : null,
|
||||
fetchDatasetApiKeysList,
|
||||
)
|
||||
const apiKeysList = appId ? appApiKeys : datasetApiKeys
|
||||
const isApiKeysLoading = appId ? isAppApiKeysLoading : isDatasetApiKeysLoading
|
||||
|
||||
const [delKeyID, setDelKeyId] = useState('')
|
||||
|
||||
@@ -64,7 +66,10 @@ const SecretKeyModal = ({
|
||||
? { url: `/apps/${appId}/api-keys/${delKeyID}`, params: {} }
|
||||
: { url: `/datasets/api-keys/${delKeyID}`, params: {} }
|
||||
await delApikey(params)
|
||||
mutate(commonParams)
|
||||
if (appId)
|
||||
invalidateAppApiKeys(appId)
|
||||
else
|
||||
mutateDatasetApiKeys()
|
||||
}
|
||||
|
||||
const onCreate = async () => {
|
||||
@@ -75,7 +80,10 @@ const SecretKeyModal = ({
|
||||
const res = await createApikey(params)
|
||||
setVisible(true)
|
||||
setNewKey(res)
|
||||
mutate(commonParams)
|
||||
if (appId)
|
||||
invalidateAppApiKeys(appId)
|
||||
else
|
||||
mutateDatasetApiKeys()
|
||||
}
|
||||
|
||||
const generateToken = (token: string) => {
|
||||
@@ -88,7 +96,7 @@ const SecretKeyModal = ({
|
||||
<XMarkIcon className="h-6 w-6 cursor-pointer text-text-tertiary" onClick={onClose} />
|
||||
</div>
|
||||
<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 && (
|
||||
<div className='mt-4 flex grow flex-col overflow-hidden'>
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useParams } from 'next/navigation'
|
||||
import useSWRInfinite from 'swr/infinite'
|
||||
import { flatten } from 'lodash-es'
|
||||
import { produce } from 'immer'
|
||||
import {
|
||||
@@ -12,33 +11,13 @@ import {
|
||||
} from '@remixicon/react'
|
||||
import Nav from '../nav'
|
||||
import type { NavItem } from '../nav/nav-selector'
|
||||
import { fetchAppList } from '@/service/apps'
|
||||
import CreateAppTemplateDialog from '@/app/components/app/create-app-dialog'
|
||||
import CreateAppModal from '@/app/components/app/create-app-modal'
|
||||
import CreateFromDSLModal from '@/app/components/app/create-from-dsl-modal'
|
||||
import type { AppListResponse } from '@/models/app'
|
||||
import { useAppContext } from '@/context/app-context'
|
||||
import { useStore as useAppStore } from '@/app/components/app/store'
|
||||
import { AppModeEnum } from '@/types/app'
|
||||
|
||||
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
|
||||
}
|
||||
import { useInfiniteAppList } from '@/service/use-apps'
|
||||
|
||||
const AppNav = () => {
|
||||
const { t } = useTranslation()
|
||||
@@ -50,17 +29,21 @@ const AppNav = () => {
|
||||
const [showCreateFromDSLModal, setShowCreateFromDSLModal] = useState(false)
|
||||
const [navItems, setNavItems] = useState<NavItem[]>([])
|
||||
|
||||
const { data: appsData, setSize, mutate } = useSWRInfinite(
|
||||
appId
|
||||
? (pageIndex: number, previousPageData: AppListResponse) => getKey(pageIndex, previousPageData, 'all', '')
|
||||
: () => null,
|
||||
fetchAppList,
|
||||
{ revalidateFirstPage: false },
|
||||
)
|
||||
const {
|
||||
data: appsData,
|
||||
fetchNextPage,
|
||||
hasNextPage,
|
||||
refetch,
|
||||
} = useInfiniteAppList({
|
||||
page: 1,
|
||||
limit: 30,
|
||||
name: '',
|
||||
}, { enabled: !!appId })
|
||||
|
||||
const handleLoadMore = useCallback(() => {
|
||||
setSize(size => size + 1)
|
||||
}, [setSize])
|
||||
if (hasNextPage)
|
||||
fetchNextPage()
|
||||
}, [fetchNextPage, hasNextPage])
|
||||
|
||||
const openModal = (state: string) => {
|
||||
if (state === 'blank')
|
||||
@@ -73,7 +56,7 @@ const AppNav = () => {
|
||||
|
||||
useEffect(() => {
|
||||
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 link = ((isCurrentWorkspaceEditor, app) => {
|
||||
if (!isCurrentWorkspaceEditor) {
|
||||
@@ -132,17 +115,17 @@ const AppNav = () => {
|
||||
<CreateAppModal
|
||||
show={showNewAppDialog}
|
||||
onClose={() => setShowNewAppDialog(false)}
|
||||
onSuccess={() => mutate()}
|
||||
onSuccess={() => refetch()}
|
||||
/>
|
||||
<CreateAppTemplateDialog
|
||||
show={showNewAppTemplateDialog}
|
||||
onClose={() => setShowNewAppTemplateDialog(false)}
|
||||
onSuccess={() => mutate()}
|
||||
onSuccess={() => refetch()}
|
||||
/>
|
||||
<CreateFromDSLModal
|
||||
show={showCreateFromDSLModal}
|
||||
onClose={() => setShowCreateFromDSLModal(false)}
|
||||
onSuccess={() => mutate()}
|
||||
onSuccess={() => refetch()}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
|
||||
@@ -15,32 +15,10 @@ import type {
|
||||
OffsetOptions,
|
||||
Placement,
|
||||
} from '@floating-ui/react'
|
||||
import useSWRInfinite from 'swr/infinite'
|
||||
import { fetchAppList } from '@/service/apps'
|
||||
import type { AppListResponse } from '@/models/app'
|
||||
import { useInfiniteAppList } from '@/service/use-apps'
|
||||
|
||||
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 = {
|
||||
value?: {
|
||||
app_id: string
|
||||
@@ -72,30 +50,32 @@ const AppSelector: FC<Props> = ({
|
||||
const [searchText, setSearchText] = useState('')
|
||||
const [isLoadingMore, setIsLoadingMore] = useState(false)
|
||||
|
||||
const { data, isLoading, setSize } = useSWRInfinite(
|
||||
(pageIndex: number, previousPageData: AppListResponse) => getKey(pageIndex, previousPageData, searchText),
|
||||
fetchAppList,
|
||||
{
|
||||
revalidateFirstPage: true,
|
||||
shouldRetryOnError: false,
|
||||
dedupingInterval: 500,
|
||||
errorRetryCount: 3,
|
||||
},
|
||||
)
|
||||
const {
|
||||
data,
|
||||
isLoading,
|
||||
isFetchingNextPage,
|
||||
fetchNextPage,
|
||||
hasNextPage,
|
||||
} = useInfiniteAppList({
|
||||
page: 1,
|
||||
limit: PAGE_SIZE,
|
||||
name: searchText,
|
||||
})
|
||||
|
||||
const pages = data?.pages ?? []
|
||||
const displayedApps = useMemo(() => {
|
||||
if (!data) return []
|
||||
return data.flatMap(({ data: apps }) => apps)
|
||||
}, [data])
|
||||
if (!pages.length) return []
|
||||
return pages.flatMap(({ data: apps }) => apps)
|
||||
}, [pages])
|
||||
|
||||
const hasMore = data?.at(-1)?.has_more ?? true
|
||||
const hasMore = hasNextPage ?? true
|
||||
|
||||
const handleLoadMore = useCallback(async () => {
|
||||
if (isLoadingMore || !hasMore) return
|
||||
if (isLoadingMore || isFetchingNextPage || !hasMore) return
|
||||
|
||||
setIsLoadingMore(true)
|
||||
try {
|
||||
await setSize((size: number) => size + 1)
|
||||
await fetchNextPage()
|
||||
}
|
||||
finally {
|
||||
// Add a small delay to ensure state updates are complete
|
||||
@@ -103,7 +83,7 @@ const AppSelector: FC<Props> = ({
|
||||
setIsLoadingMore(false)
|
||||
}, 300)
|
||||
}
|
||||
}, [isLoadingMore, hasMore, setSize])
|
||||
}, [isLoadingMore, isFetchingNextPage, hasMore, fetchNextPage])
|
||||
|
||||
const handleTriggerClick = () => {
|
||||
if (disabled) return
|
||||
@@ -185,7 +165,7 @@ const AppSelector: FC<Props> = ({
|
||||
onSelect={handleSelectApp}
|
||||
scope={scope || 'all'}
|
||||
apps={displayedApps}
|
||||
isLoading={isLoading || isLoadingMore}
|
||||
isLoading={isLoading || isLoadingMore || isFetchingNextPage}
|
||||
hasMore={hasMore}
|
||||
onLoadMore={handleLoadMore}
|
||||
searchText={searchText}
|
||||
|
||||
@@ -1,15 +1,14 @@
|
||||
import type { Fetcher } from 'swr'
|
||||
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 { CommonResponse } from '@/models/common'
|
||||
import type { AppIconType, AppModeEnum, ModelConfig } from '@/types/app'
|
||||
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 })
|
||||
}
|
||||
|
||||
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}`)
|
||||
}
|
||||
|
||||
@@ -18,24 +17,74 @@ export const fetchAppDetailDirect = async ({ url, id }: { url: string; id: strin
|
||||
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)
|
||||
}
|
||||
|
||||
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 } })
|
||||
}
|
||||
|
||||
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 }
|
||||
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 } })
|
||||
}
|
||||
|
||||
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({
|
||||
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()}`)
|
||||
}
|
||||
|
||||
// TODO: delete
|
||||
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 }) => {
|
||||
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> => {
|
||||
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: {} })
|
||||
}
|
||||
|
||||
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 } })
|
||||
}
|
||||
|
||||
export const deleteApp: Fetcher<CommonResponse, string> = (appID) => {
|
||||
export const deleteApp = (appID: string): Promise<CommonResponse> => {
|
||||
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 })
|
||||
}
|
||||
|
||||
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 })
|
||||
}
|
||||
|
||||
// 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 })
|
||||
}
|
||||
|
||||
export const updateAppSiteAccessToken: Fetcher<UpdateAppSiteCodeResponse, { url: string }> = ({ url }) => {
|
||||
export const updateAppSiteAccessToken = ({ url }: { url: string }): Promise<UpdateAppSiteCodeResponse> => {
|
||||
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 })
|
||||
}
|
||||
|
||||
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 })
|
||||
}
|
||||
|
||||
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 })
|
||||
}
|
||||
|
||||
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 })
|
||||
}
|
||||
|
||||
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 })
|
||||
}
|
||||
|
||||
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 })
|
||||
}
|
||||
|
||||
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 })
|
||||
}
|
||||
|
||||
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 })
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
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 })
|
||||
}
|
||||
|
||||
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 })
|
||||
}
|
||||
|
||||
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 })
|
||||
}
|
||||
|
||||
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'
|
||||
return get<AppVoicesListResponse>(`apps/${appId}/text-to-audio/voices?language=${language}`)
|
||||
}
|
||||
|
||||
// Tracing
|
||||
export const fetchTracingStatus: Fetcher<TracingStatus, { appId: string }> = ({ appId }) => {
|
||||
return get(`/apps/${appId}/trace`)
|
||||
export const fetchTracingStatus = ({ appId }: { appId: string }): Promise<TracingStatus> => {
|
||||
return get<TracingStatus>(`/apps/${appId}/trace`)
|
||||
}
|
||||
|
||||
export const updateTracingStatus: Fetcher<CommonResponse, { appId: string; body: Record<string, any> }> = ({ appId, body }) => {
|
||||
return post(`/apps/${appId}/trace`, { body })
|
||||
export const updateTracingStatus = ({ appId, body }: { appId: string; body: Record<string, any> }): Promise<CommonResponse> => {
|
||||
return post<CommonResponse>(`/apps/${appId}/trace`, { body })
|
||||
}
|
||||
|
||||
// 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>(
|
||||
`apps/${appId}/workflows/triggers/webhook`,
|
||||
{ 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 }) => {
|
||||
return get(`/apps/${appId}/trace-config`, {
|
||||
export const fetchTracingConfig = ({ appId, provider }: { appId: string; provider: TracingProvider }): Promise<TracingConfig & { has_not_configured: true }> => {
|
||||
return get<TracingConfig & { has_not_configured: true }>(`/apps/${appId}/trace-config`, {
|
||||
params: {
|
||||
tracing_provider: provider,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export const addTracingConfig: Fetcher<CommonResponse, { appId: string; body: TracingConfig }> = ({ appId, body }) => {
|
||||
return post(`/apps/${appId}/trace-config`, { body })
|
||||
export const addTracingConfig = ({ appId, body }: { appId: string; body: TracingConfig }): Promise<CommonResponse> => {
|
||||
return post<CommonResponse>(`/apps/${appId}/trace-config`, { body })
|
||||
}
|
||||
|
||||
export const updateTracingConfig: Fetcher<CommonResponse, { appId: string; body: TracingConfig }> = ({ appId, body }) => {
|
||||
return patch(`/apps/${appId}/trace-config`, { body })
|
||||
export const updateTracingConfig = ({ appId, body }: { appId: string; body: TracingConfig }): Promise<CommonResponse> => {
|
||||
return patch<CommonResponse>(`/apps/${appId}/trace-config`, { body })
|
||||
}
|
||||
|
||||
export const removeTracingConfig: Fetcher<CommonResponse, { appId: string; provider: TracingProvider }> = ({ appId, provider }) => {
|
||||
return del(`/apps/${appId}/trace-config?tracing_provider=${provider}`)
|
||||
export const removeTracingConfig = ({ appId, provider }: { appId: string; provider: TracingProvider }): Promise<CommonResponse> => {
|
||||
return del<CommonResponse>(`/apps/${appId}/trace-config?tracing_provider=${provider}`)
|
||||
}
|
||||
|
||||
@@ -1,38 +1,85 @@
|
||||
'use client'
|
||||
import type { FC } from 'react'
|
||||
import React from 'react'
|
||||
import useSWR, { useSWRConfig } from 'swr'
|
||||
import { createApp, fetchAppDetail, fetchAppList, getAppDailyConversations, getAppDailyEndUsers, updateAppApiStatus, updateAppModelConfig, updateAppRateLimit, updateAppSiteAccessToken, updateAppSiteConfig, updateAppSiteStatus } from '../apps'
|
||||
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
|
||||
import { createApp, updateAppApiStatus, updateAppModelConfig, updateAppRateLimit, updateAppSiteAccessToken, updateAppSiteConfig, updateAppSiteStatus } from '../apps'
|
||||
import Loading from '@/app/components/base/loading'
|
||||
import { AppModeEnum } from '@/types/app'
|
||||
import {
|
||||
useAppDailyConversations,
|
||||
useAppDailyEndUsers,
|
||||
useAppDetail,
|
||||
useAppList,
|
||||
} from '../use-apps'
|
||||
|
||||
const Service: FC = () => {
|
||||
const { data: appList, error: appListError } = useSWR({ url: '/apps', params: { page: 1 } }, fetchAppList)
|
||||
const { data: firstApp, error: appDetailError } = useSWR({ url: '/apps', id: '1' }, fetchAppDetail)
|
||||
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 appId = '1'
|
||||
const queryClient = useQueryClient()
|
||||
|
||||
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 () => {
|
||||
await createApp({
|
||||
const { data: updateAppSiteStatusRes, error: err1, isLoading: isUpdatingSiteStatus } = useQuery({
|
||||
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)}`,
|
||||
mode: AppModeEnum.CHAT,
|
||||
})
|
||||
// reload app list
|
||||
mutate({ url: '/apps', params: { page: 1 } })
|
||||
}),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: ['apps', 'list'],
|
||||
})
|
||||
},
|
||||
})
|
||||
|
||||
const handleCreateApp = async () => {
|
||||
await mutateCreateApp()
|
||||
}
|
||||
|
||||
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 (
|
||||
|
||||
@@ -1,31 +1,63 @@
|
||||
import { get, post } from './base'
|
||||
import type { App } from '@/types/app'
|
||||
import type { AppListResponse } from '@/models/app'
|
||||
import type {
|
||||
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 { useQuery } from '@tanstack/react-query'
|
||||
import {
|
||||
useInfiniteQuery,
|
||||
useQuery,
|
||||
useQueryClient,
|
||||
} from '@tanstack/react-query'
|
||||
import type { GeneratorType } from '@/app/components/app/configuration/config/automatic/types'
|
||||
|
||||
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']
|
||||
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) => {
|
||||
return useQuery({
|
||||
@@ -39,3 +71,142 @@ export const useGenerateRuleTemplate = (type: GeneratorType, disabled?: boolean)
|
||||
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