diff --git a/web/app/components/header/account-setting/sandbox-provider-page/index.tsx b/web/app/components/header/account-setting/sandbox-provider-page/index.tsx index e1d80cdd5f..e4699d75e7 100644 --- a/web/app/components/header/account-setting/sandbox-provider-page/index.tsx +++ b/web/app/components/header/account-setting/sandbox-provider-page/index.tsx @@ -1,11 +1,12 @@ 'use client' import type { SandboxProvider } from '@/types/sandbox-provider' +import { useQuery } from '@tanstack/react-query' import { memo, useState } from 'react' import { useTranslation } from 'react-i18next' import Loading from '@/app/components/base/loading' import { useAppContext } from '@/context/app-context' -import { useGetSandboxProviderList } from '@/service/use-sandbox-provider' +import { consoleQuery } from '@/service/client' import ConfigModal from './config-modal' import ProviderCard from './provider-card' import SwitchModal from './switch-modal' @@ -13,7 +14,7 @@ import SwitchModal from './switch-modal' const SandboxProviderPage = () => { const { t } = useTranslation() const { isCurrentWorkspaceManager, isLoadingCurrentWorkspace } = useAppContext() - const { data: providers, isLoading } = useGetSandboxProviderList() + const { data: providers, isLoading } = useQuery(consoleQuery.sandboxProvider.getSandboxProviderList.queryOptions()) const [configModalProvider, setConfigModalProvider] = useState(null) const [switchModalProvider, setSwitchModalProvider] = useState(null) diff --git a/web/app/components/workflow/variable-inspect/artifacts-tab.spec.tsx b/web/app/components/workflow/variable-inspect/artifacts-tab.spec.tsx index 78ca2be526..404f418ab4 100644 --- a/web/app/components/workflow/variable-inspect/artifacts-tab.spec.tsx +++ b/web/app/components/workflow/variable-inspect/artifacts-tab.spec.tsx @@ -1,4 +1,4 @@ -import type { SandboxFileNode, SandboxFileTreeNode } from '@/types/sandbox-file' +import type { SandboxFileNode } from '@/types/sandbox-file' import { fireEvent, render, screen, waitFor } from '@testing-library/react' import ArtifactsTab from './artifacts-tab' import { InspectTab } from './types' @@ -21,9 +21,7 @@ const mocks = vi.hoisted(() => ({ isResponding: false, bottomPanelWidth: 640, } as MockStoreState, - treeData: undefined as SandboxFileTreeNode[] | undefined, flatData: [] as SandboxFileNode[], - hasFiles: false, isLoading: false, fetchDownloadUrl: vi.fn(), mockUseQuery: vi.fn(), @@ -31,6 +29,10 @@ const mocks = vi.hoisted(() => ({ queryKey: ['sandboxFile', 'downloadFile'], queryFn: vi.fn(), }), + mockTreeOptions: vi.fn().mockReturnValue({ + queryKey: ['sandboxFile', 'listFiles'], + queryFn: vi.fn(), + }), })) vi.mock('../store', () => ({ @@ -42,14 +44,10 @@ vi.mock('@tanstack/react-query', async importOriginal => ({ useQuery: (options: unknown) => mocks.mockUseQuery(options), })) -vi.mock('@/service/use-sandbox-file', () => ({ +vi.mock('@/service/use-sandbox-file', async importOriginal => ({ + ...(await importOriginal()), sandboxFileDownloadUrlOptions: (...args: unknown[]) => mocks.mockDownloadUrlOptions(...args), - useSandboxFilesTree: () => ({ - data: mocks.treeData, - flatData: mocks.flatData, - hasFiles: mocks.hasFiles, - isLoading: mocks.isLoading, - }), + sandboxFilesTreeOptions: (...args: unknown[]) => mocks.mockTreeOptions(...args), useDownloadSandboxFile: () => ({ mutateAsync: mocks.fetchDownloadUrl, isPending: false, @@ -74,18 +72,6 @@ vi.mock('@/utils/download', () => ({ downloadUrl: vi.fn(), })) -const createTreeFileNode = (overrides: Partial = {}): SandboxFileTreeNode => ({ - id: 'a.txt', - name: 'a.txt', - path: 'a.txt', - node_type: 'file', - size: 128, - mtime: 1700000000, - extension: 'txt', - children: [], - ...overrides, -}) - const createFlatFileNode = (overrides: Partial = {}): SandboxFileNode => ({ path: 'a.txt', is_dir: false, @@ -103,13 +89,20 @@ describe('ArtifactsTab', () => { mocks.storeState.isResponding = false mocks.storeState.bottomPanelWidth = 640 - mocks.treeData = [createTreeFileNode()] mocks.flatData = [createFlatFileNode()] - mocks.hasFiles = true mocks.isLoading = false - mocks.mockUseQuery.mockReturnValue({ - data: undefined, - isLoading: false, + mocks.mockUseQuery.mockImplementation((options: { queryKey?: unknown }) => { + const treeKey = mocks.mockTreeOptions.mock.results.at(-1)?.value?.queryKey + if (treeKey && options.queryKey === treeKey) { + return { + data: mocks.flatData, + isLoading: mocks.isLoading, + } + } + return { + data: undefined, + isLoading: false, + } }) }) @@ -128,9 +121,7 @@ describe('ArtifactsTab', () => { expect(mocks.mockDownloadUrlOptions).toHaveBeenCalledWith('app-1', 'a.txt') }) - mocks.treeData = undefined mocks.flatData = [] - mocks.hasFiles = false rerender() diff --git a/web/app/components/workflow/variable-inspect/artifacts-tab.tsx b/web/app/components/workflow/variable-inspect/artifacts-tab.tsx index e90808062a..595ba1018a 100644 --- a/web/app/components/workflow/variable-inspect/artifacts-tab.tsx +++ b/web/app/components/workflow/variable-inspect/artifacts-tab.tsx @@ -11,7 +11,7 @@ import Loading from '@/app/components/base/loading' import ArtifactsTree from '@/app/components/workflow/skill/file-tree/artifacts/artifacts-tree' import ReadOnlyFilePreview from '@/app/components/workflow/skill/viewer/read-only-file-preview' import { useDocLink } from '@/context/i18n' -import { sandboxFileDownloadUrlOptions, useDownloadSandboxFile, useSandboxFilesTree } from '@/service/use-sandbox-file' +import { buildTreeFromFlatList, sandboxFileDownloadUrlOptions, sandboxFilesTreeOptions, useDownloadSandboxFile } from '@/service/use-sandbox-file' import { cn } from '@/utils/classnames' import { downloadUrl } from '@/utils/download' import { useStore } from '../store' @@ -67,10 +67,12 @@ const ArtifactsTab = (headerProps: InspectHeaderProps) => { ) const isResponding = useStore(s => s.isResponding) - const { data: treeData, flatData, hasFiles, isLoading } = useSandboxFilesTree(appId, { - enabled: !!appId, + const { data: flatData, isLoading } = useQuery({ + ...sandboxFilesTreeOptions(appId), refetchInterval: (isWorkflowRunning || isResponding) ? 5000 : false, }) + const treeData = useMemo(() => flatData ? buildTreeFromFlatList(flatData) : undefined, [flatData]) + const hasFiles = (flatData?.length ?? 0) > 0 const { mutateAsync: fetchDownloadUrl, isPending: isDownloading } = useDownloadSandboxFile(appId) const [selectedFile, setSelectedFile] = useState(null) const selectedFilePath = useMemo(() => { diff --git a/web/app/components/workflow/variable-inspect/panel.tsx b/web/app/components/workflow/variable-inspect/panel.tsx index fabce360d3..32803e5a13 100644 --- a/web/app/components/workflow/variable-inspect/panel.tsx +++ b/web/app/components/workflow/variable-inspect/panel.tsx @@ -1,9 +1,10 @@ import type { FC } from 'react' +import { useQuery } from '@tanstack/react-query' import { useCallback, useMemo, useState } from 'react' import { useTranslation } from 'react-i18next' import Button from '@/app/components/base/button' import { useFeatures } from '@/app/components/base/features/hooks' -import { useSandboxFilesTree } from '@/service/use-sandbox-file' +import { sandboxFilesTreeOptions } from '@/service/use-sandbox-file' import useCurrentVars from '../hooks/use-inspect-vars-crud' import { useStore } from '../store' import ArtifactsTab from './artifacts-tab' @@ -28,9 +29,8 @@ const VariablesPanel: FC<{ onClose: () => void }> = ({ onClose }) => { return [...environmentVariables, ...conversationVars, ...systemVars, ...nodesWithInspectVars].length === 0 }, [environmentVariables, conversationVars, systemVars, nodesWithInspectVars]) - const { hasFiles: hasArtifacts } = useSandboxFilesTree(appId, { - enabled: !!appId && sandboxEnabled, - }) + const { data: sandboxFiles } = useQuery(sandboxFilesTreeOptions(sandboxEnabled ? appId : undefined)) + const hasArtifacts = (sandboxFiles?.length ?? 0) > 0 const handleClear = useCallback(() => { deleteAllInspectorVars() diff --git a/web/contract/console/billing.ts b/web/contract/console/billing.ts index 08e1d0668f..d286ff3b42 100644 --- a/web/contract/console/billing.ts +++ b/web/contract/console/billing.ts @@ -6,7 +6,6 @@ export const invoicesContract = base path: '/billing/invoices', method: 'GET', }) - .input(type()) .output(type<{ url: string }>()) export const bindPartnerStackContract = base diff --git a/web/contract/console/sandbox-provider.ts b/web/contract/console/sandbox-provider.ts index a4a618f6bb..5a71941d05 100644 --- a/web/contract/console/sandbox-provider.ts +++ b/web/contract/console/sandbox-provider.ts @@ -7,7 +7,6 @@ export const getSandboxProviderListContract = base path: '/workspaces/current/sandbox-providers', method: 'GET', }) - .input(type()) .output(type()) export const saveSandboxProviderConfigContract = base diff --git a/web/contract/console/system.ts b/web/contract/console/system.ts index bce0a8226e..73e920b804 100644 --- a/web/contract/console/system.ts +++ b/web/contract/console/system.ts @@ -7,5 +7,4 @@ export const systemFeaturesContract = base path: '/system-features', method: 'GET', }) - .input(type()) .output(type()) diff --git a/web/service/use-sandbox-file.ts b/web/service/use-sandbox-file.ts index c4734c889c..4fd59bc235 100644 --- a/web/service/use-sandbox-file.ts +++ b/web/service/use-sandbox-file.ts @@ -14,6 +14,14 @@ export function sandboxFileDownloadUrlOptions(appId: string | undefined, path: s }) } +export function sandboxFilesTreeOptions(appId: string | undefined) { + return consoleQuery.sandboxFile.listFiles.queryOptions({ + input: appId + ? { params: { appId }, query: { recursive: true } } + : skipToken, + }) +} + type InvalidateSandboxFilesOptions = { refetchDownloadFile?: boolean } @@ -47,7 +55,7 @@ export function useDownloadSandboxFile(appId: string | undefined) { }) } -function buildTreeFromFlatList(nodes: SandboxFileNode[]): SandboxFileTreeNode[] { +export function buildTreeFromFlatList(nodes: SandboxFileNode[]): SandboxFileTreeNode[] { const nodeMap = new Map() const roots: SandboxFileTreeNode[] = [] @@ -86,25 +94,8 @@ function buildTreeFromFlatList(nodes: SandboxFileNode[]): SandboxFileTreeNode[] return roots } -type UseSandboxFilesTreeOptions = { - enabled?: boolean - refetchInterval?: number | false -} - -export function useSandboxFilesTree( - appId: string | undefined, - options?: UseSandboxFilesTreeOptions, -) { - const input = appId && (options?.enabled ?? true) - ? { params: { appId }, query: { recursive: true } } - : skipToken - - const { data, isLoading, error } = useQuery({ - ...consoleQuery.sandboxFile.listFiles.queryOptions({ - input, - }), - refetchInterval: options?.refetchInterval, - }) +export function useSandboxFilesTree(appId: string | undefined) { + const { data, isLoading, error } = useQuery(sandboxFilesTreeOptions(appId)) const treeData = useMemo(() => { if (!data) @@ -112,14 +103,10 @@ export function useSandboxFilesTree( return buildTreeFromFlatList(data) }, [data]) - const hasFiles = useMemo(() => { - return (data?.length ?? 0) > 0 - }, [data]) - return { data: treeData, flatData: data, - hasFiles, + hasFiles: (data?.length ?? 0) > 0, isLoading, error, } diff --git a/web/service/use-sandbox-provider.ts b/web/service/use-sandbox-provider.ts index b038b80ed4..3381dbca33 100644 --- a/web/service/use-sandbox-provider.ts +++ b/web/service/use-sandbox-provider.ts @@ -1,17 +1,9 @@ import { useMutation, - useQuery, useQueryClient, } from '@tanstack/react-query' import { consoleClient, consoleQuery } from '@/service/client' -export const useGetSandboxProviderList = () => { - return useQuery({ - queryKey: consoleQuery.sandboxProvider.getSandboxProviderList.queryKey(), - queryFn: () => consoleClient.sandboxProvider.getSandboxProviderList(), - }) -} - export const useSaveSandboxProviderConfig = () => { const queryClient = useQueryClient() return useMutation({