From 37d59222cf03c8ec681a59c75b247b0da35bba91 Mon Sep 17 00:00:00 2001 From: yyh Date: Thu, 26 Mar 2026 13:32:18 +0800 Subject: [PATCH] Revert "Refactor variable inspect panel layout and scrolling" This reverts commit 9ab18b3ef6a7e24679f61440ca3c34530df97c98. --- .../variable-inspect/__tests__/panel.spec.tsx | 56 ---- .../artifacts-empty-state.tsx | 40 --- .../variable-inspect/artifacts-left-pane.tsx | 27 -- .../variable-inspect/artifacts-right-pane.tsx | 118 --------- .../variable-inspect/artifacts-tab.spec.tsx | 48 ++-- .../variable-inspect/artifacts-tab.tsx | 241 ++++++++++++++++++ .../workflow/variable-inspect/group.tsx | 59 ++--- .../hooks/use-artifacts-inspect-state.ts | 141 ---------- .../hooks/use-inspect-shell.ts | 19 -- .../workflow/variable-inspect/index.tsx | 5 +- .../variable-inspect/inspect-layout.tsx | 46 ++++ .../variable-inspect/inspect-scroll-area.tsx | 30 --- .../variable-inspect/inspect-shell.tsx | 102 -------- .../workflow/variable-inspect/left.tsx | 101 ++++---- .../workflow/variable-inspect/listening.tsx | 59 +++-- .../workflow/variable-inspect/panel.tsx | 175 +++---------- .../workflow/variable-inspect/right.tsx | 120 ++++----- .../workflow/variable-inspect/split-panel.tsx | 62 +++++ .../workflow/variable-inspect/tab-header.tsx | 10 +- .../workflow/variable-inspect/trigger.tsx | 31 +-- .../workflow/variable-inspect/types.ts | 20 -- ...les-inspect-state.ts => variables-tab.tsx} | 135 +++++----- web/eslint-suppressions.json | 84 +++--- 23 files changed, 735 insertions(+), 994 deletions(-) delete mode 100644 web/app/components/workflow/variable-inspect/artifacts-empty-state.tsx delete mode 100644 web/app/components/workflow/variable-inspect/artifacts-left-pane.tsx delete mode 100644 web/app/components/workflow/variable-inspect/artifacts-right-pane.tsx create mode 100644 web/app/components/workflow/variable-inspect/artifacts-tab.tsx delete mode 100644 web/app/components/workflow/variable-inspect/hooks/use-artifacts-inspect-state.ts delete mode 100644 web/app/components/workflow/variable-inspect/hooks/use-inspect-shell.ts create mode 100644 web/app/components/workflow/variable-inspect/inspect-layout.tsx delete mode 100644 web/app/components/workflow/variable-inspect/inspect-scroll-area.tsx delete mode 100644 web/app/components/workflow/variable-inspect/inspect-shell.tsx create mode 100644 web/app/components/workflow/variable-inspect/split-panel.tsx rename web/app/components/workflow/variable-inspect/{hooks/use-variables-inspect-state.ts => variables-tab.tsx} (58%) diff --git a/web/app/components/workflow/variable-inspect/__tests__/panel.spec.tsx b/web/app/components/workflow/variable-inspect/__tests__/panel.spec.tsx index 7cf5fdb889..b2c2b68216 100644 --- a/web/app/components/workflow/variable-inspect/__tests__/panel.spec.tsx +++ b/web/app/components/workflow/variable-inspect/__tests__/panel.spec.tsx @@ -17,33 +17,17 @@ const { mockEmit, mockFetchInspectVarValue, mockHandleNodeSelect, - mockDownloadUrlOptions, - mockTreeOptions, - mockUseQuery, mockResetConversationVar, mockResetToLastRunVar, mockSetInputs, - flatData, - isLoading, } = vi.hoisted(() => ({ mockEditInspectVarValue: vi.fn(), mockEmit: vi.fn(), mockFetchInspectVarValue: vi.fn(), mockHandleNodeSelect: vi.fn(), - mockDownloadUrlOptions: vi.fn().mockReturnValue({ - queryKey: ['sandboxFile', 'downloadFile'], - queryFn: vi.fn(), - }), - mockTreeOptions: vi.fn().mockReturnValue({ - queryKey: ['sandboxFile', 'listFiles'], - queryFn: vi.fn(), - }), - mockUseQuery: vi.fn(), mockResetConversationVar: vi.fn(), mockResetToLastRunVar: vi.fn(), mockSetInputs: vi.fn(), - flatData: [] as unknown[], - isLoading: false, })) let inspectVarsState: InspectVarsState @@ -105,21 +89,6 @@ vi.mock('../../hooks-store', () => ({ }), })) -vi.mock('@tanstack/react-query', async importOriginal => ({ - ...await importOriginal(), - useQuery: (options: { queryKey?: unknown }) => mockUseQuery(options), -})) - -vi.mock('@/service/use-sandbox-file', async importOriginal => ({ - ...(await importOriginal()), - sandboxFileDownloadUrlOptions: (...args: unknown[]) => mockDownloadUrlOptions(...args), - sandboxFilesTreeOptions: (...args: unknown[]) => mockTreeOptions(...args), - useDownloadSandboxFile: () => ({ - mutateAsync: vi.fn(), - isPending: false, - }), -})) - vi.mock('@/context/event-emitter', () => ({ useEventEmitterContextContext: () => ({ eventEmitter: { @@ -128,17 +97,6 @@ vi.mock('@/context/event-emitter', () => ({ }), })) -vi.mock('@/app/components/base/features/hooks', async importOriginal => ({ - ...(await importOriginal()), - useFeatures: (selector: (state: { features: { sandbox: { enabled: boolean } } }) => unknown) => selector({ - features: { - sandbox: { - enabled: true, - }, - }, - }), -})) - const createEnvironmentVariable = (overrides: Partial = {}): EnvironmentVariable => ({ id: 'env-1', name: 'API_KEY', @@ -166,20 +124,6 @@ const renderPanel = (initialStoreState: Record = {}) => { describe('VariableInspect Panel', () => { beforeEach(() => { vi.clearAllMocks() - mockUseQuery.mockImplementation((options: { queryKey?: unknown }) => { - const treeKey = mockTreeOptions.mock.results.at(-1)?.value?.queryKey - if (treeKey && options.queryKey === treeKey) { - return { - data: flatData, - isLoading, - } - } - - return { - data: undefined, - isLoading: false, - } - }) inspectVarsState = { conversationVars: [], systemVars: [], diff --git a/web/app/components/workflow/variable-inspect/artifacts-empty-state.tsx b/web/app/components/workflow/variable-inspect/artifacts-empty-state.tsx deleted file mode 100644 index f304094458..0000000000 --- a/web/app/components/workflow/variable-inspect/artifacts-empty-state.tsx +++ /dev/null @@ -1,40 +0,0 @@ -import type { DocPathWithoutLang } from '@/types/doc-paths' -import { useTranslation } from 'react-i18next' -import SearchLinesSparkle from '@/app/components/base/icons/src/vender/knowledge/SearchLinesSparkle' -import { useDocLink } from '@/context/i18n' - -const fileSystemArtifactsLocalizedPathMap = { - 'zh-Hans': '/use-dify/build/file-system#产物' as DocPathWithoutLang, - 'zh_Hans': '/use-dify/build/file-system#产物' as DocPathWithoutLang, - 'ja-JP': '/use-dify/build/file-system#アーティファクト' as DocPathWithoutLang, - 'ja_JP': '/use-dify/build/file-system#アーティファクト' as DocPathWithoutLang, -} - -type Props = { - description: string -} - -export default function ArtifactsEmptyState({ description }: Props) { - const { t } = useTranslation('workflow') - const docLink = useDocLink() - - return ( -
-
-
-
-
{t('debug.variableInspect.tabArtifacts.emptyTitle')}
-
{description}
- - {t('debug.variableInspect.tabArtifacts.emptyLink')} - -
-
- ) -} diff --git a/web/app/components/workflow/variable-inspect/artifacts-left-pane.tsx b/web/app/components/workflow/variable-inspect/artifacts-left-pane.tsx deleted file mode 100644 index 08baf6cf29..0000000000 --- a/web/app/components/workflow/variable-inspect/artifacts-left-pane.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import type { ArtifactsInspectView } from './hooks/use-artifacts-inspect-state' -import ArtifactsTree from '@/app/components/workflow/skill/file-tree/artifacts/artifacts-tree' - -type Props = Pick< - ArtifactsInspectView, - 'handleFileSelect' | 'handleTreeDownload' | 'isDownloading' | 'selectedFilePath' | 'treeData' -> - -export default function ArtifactsLeftPane({ - treeData, - handleTreeDownload, - handleFileSelect, - selectedFilePath, - isDownloading, -}: Props) { - return ( -
- -
- ) -} diff --git a/web/app/components/workflow/variable-inspect/artifacts-right-pane.tsx b/web/app/components/workflow/variable-inspect/artifacts-right-pane.tsx deleted file mode 100644 index 2ca302a1c8..0000000000 --- a/web/app/components/workflow/variable-inspect/artifacts-right-pane.tsx +++ /dev/null @@ -1,118 +0,0 @@ -import type { ArtifactsInspectView } from './hooks/use-artifacts-inspect-state' -import { useTranslation } from 'react-i18next' -import ActionButton from '@/app/components/base/action-button' -import Loading from '@/app/components/base/loading' -import ReadOnlyFilePreview from '@/app/components/workflow/skill/viewer/read-only-file-preview' -import { cn } from '@/utils/classnames' -import ArtifactsEmptyState from './artifacts-empty-state' -import useInspectShell from './hooks/use-inspect-shell' - -type Props = Pick< - ArtifactsInspectView, - 'downloadUrlData' | 'handleSelectedFileDownload' | 'isDownloadUrlLoading' | 'pathSegments' | 'selectedFile' | 'selectedFilePath' -> - -function formatFileSize(bytes: number | null): string { - if (bytes === null || bytes === 0) - return '0 B' - const units = ['B', 'KB', 'MB', 'GB'] - const i = Math.floor(Math.log(bytes) / Math.log(1024)) - return `${(bytes / 1024 ** i).toFixed(i === 0 ? 0 : 1)} ${units[i]}` -} - -export default function ArtifactsRightPane({ - downloadUrlData, - handleSelectedFileDownload, - isDownloadUrlLoading, - pathSegments, - selectedFile, - selectedFilePath, -}: Props) { - const { t } = useTranslation('workflow') - const { isNarrow, onClose, openLeftPane } = useInspectShell() - const file = selectedFilePath ? selectedFile : null - - return ( - <> -
-
- {isNarrow - ? ( - - - ) - : null} - {file - ? ( - <> -
-
- {pathSegments.map(seg => ( - - {!seg.isFirst ? / : null} - - {seg.part} - - - ))} -
- - {formatFileSize(file.size)} - -
-
- - -
- - ) - : null} -
- - -
-
- {file - ? ( -
- {isDownloadUrlLoading - ?
- : downloadUrlData?.download_url - ? ( - - ) - : ( -
-

- {t('debug.variableInspect.tabArtifacts.previewNotAvailable')} -

-
- )} -
- ) - : ( -
- -
- )} -
- - ) -} 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 fd3c345e4a..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,6 +1,7 @@ import type { SandboxFileNode } from '@/types/sandbox-file' -import { act, renderHook, waitFor } from '@testing-library/react' -import { useArtifactsInspectView } from './hooks/use-artifacts-inspect-state' +import { fireEvent, render, screen, waitFor } from '@testing-library/react' +import ArtifactsTab from './artifacts-tab' +import { InspectTab } from './types' type MockStoreState = { appId: string | undefined @@ -10,6 +11,7 @@ type MockStoreState = { } } isResponding: boolean + bottomPanelWidth: number } const mocks = vi.hoisted(() => ({ @@ -17,6 +19,7 @@ const mocks = vi.hoisted(() => ({ appId: 'app-1', workflowRunningData: undefined, isResponding: false, + bottomPanelWidth: 640, } as MockStoreState, flatData: [] as SandboxFileNode[], isLoading: false, @@ -38,7 +41,7 @@ vi.mock('../store', () => ({ vi.mock('@tanstack/react-query', async importOriginal => ({ ...await importOriginal(), - useQuery: (options: { queryKey?: unknown }) => mocks.mockUseQuery(options), + useQuery: (options: unknown) => mocks.mockUseQuery(options), })) vi.mock('@/service/use-sandbox-file', async importOriginal => ({ @@ -51,6 +54,20 @@ vi.mock('@/service/use-sandbox-file', async importOriginal => ({ }), })) +vi.mock('@/context/i18n', () => ({ + useDocLink: () => (path: string) => path, +})) + +vi.mock('@/app/components/base/features/hooks', () => ({ + useFeatures: (selector: (state: { features: { sandbox: { enabled: boolean } } }) => unknown) => selector({ + features: { + sandbox: { + enabled: true, + }, + }, + }), +})) + vi.mock('@/utils/download', () => ({ downloadUrl: vi.fn(), })) @@ -64,12 +81,13 @@ const createFlatFileNode = (overrides: Partial = {}): SandboxFi ...overrides, }) -describe('useArtifactsInspectState', () => { +describe('ArtifactsTab', () => { beforeEach(() => { vi.clearAllMocks() mocks.storeState.appId = 'app-1' mocks.storeState.workflowRunningData = undefined mocks.storeState.isResponding = false + mocks.storeState.bottomPanelWidth = 640 mocks.flatData = [createFlatFileNode()] mocks.isLoading = false @@ -81,7 +99,6 @@ describe('useArtifactsInspectState', () => { isLoading: mocks.isLoading, } } - return { data: undefined, isLoading: false, @@ -90,24 +107,23 @@ describe('useArtifactsInspectState', () => { }) it('should stop using stale file path for download url query after files are cleared', async () => { - const { result, rerender } = renderHook(() => useArtifactsInspectView()) + const headerProps = { + activeTab: InspectTab.Artifacts, + onTabChange: vi.fn(), + onClose: vi.fn(), + } - act(() => { - result.current.handleFileSelect({ - name: 'a.txt', - path: 'a.txt', - node_type: 'file', - size: 128, - extension: 'txt', - } as never) - }) + const { rerender } = render() + + fireEvent.click(screen.getByRole('button', { name: 'a.txt' })) await waitFor(() => { expect(mocks.mockDownloadUrlOptions).toHaveBeenCalledWith('app-1', 'a.txt') }) mocks.flatData = [] - rerender() + + rerender() await waitFor(() => { const lastCall = mocks.mockDownloadUrlOptions.mock.calls.at(-1) diff --git a/web/app/components/workflow/variable-inspect/artifacts-tab.tsx b/web/app/components/workflow/variable-inspect/artifacts-tab.tsx new file mode 100644 index 0000000000..0ee451fc9a --- /dev/null +++ b/web/app/components/workflow/variable-inspect/artifacts-tab.tsx @@ -0,0 +1,241 @@ +import type { InspectHeaderProps } from './inspect-layout' +import type { DocPathWithoutLang } from '@/types/doc-paths' +import type { SandboxFileTreeNode } from '@/types/sandbox-file' +import { useQuery } from '@tanstack/react-query' +import { useCallback, useMemo, useState } from 'react' +import { useTranslation } from 'react-i18next' +import ActionButton from '@/app/components/base/action-button' +import SearchLinesSparkle from '@/app/components/base/icons/src/vender/knowledge/SearchLinesSparkle' +import { FileDownload01 } from '@/app/components/base/icons/src/vender/line/files' +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, sandboxFilesTreeOptions, useDownloadSandboxFile } from '@/service/use-sandbox-file' +import { cn } from '@/utils/classnames' +import { downloadUrl } from '@/utils/download' +import { buildTreeFromFlatList } from '../skill/file-tree/artifacts/utils' +import { useStore } from '../store' +import { WorkflowRunningStatus } from '../types' +import InspectLayout from './inspect-layout' +import SplitPanel from './split-panel' + +const fileSystemArtifactsLocalizedPathMap = { + 'zh-Hans': '/use-dify/build/file-system#产物' as DocPathWithoutLang, + 'zh_Hans': '/use-dify/build/file-system#产物' as DocPathWithoutLang, + 'ja-JP': '/use-dify/build/file-system#アーティファクト' as DocPathWithoutLang, + 'ja_JP': '/use-dify/build/file-system#アーティファクト' as DocPathWithoutLang, +} + +const ArtifactsEmpty = ({ description }: { description: string }) => { + const { t } = useTranslation('workflow') + const docLink = useDocLink() + + return ( +
+
+
+
+
{t('debug.variableInspect.tabArtifacts.emptyTitle')}
+
{description}
+ + {t('debug.variableInspect.tabArtifacts.emptyLink')} + +
+
+ ) +} + +const formatFileSize = (bytes: number | null): string => { + if (bytes === null || bytes === 0) + return '0 B' + const units = ['B', 'KB', 'MB', 'GB'] + const i = Math.floor(Math.log(bytes) / Math.log(1024)) + return `${(bytes / 1024 ** i).toFixed(i === 0 ? 0 : 1)} ${units[i]}` +} + +const ArtifactsTab = (headerProps: InspectHeaderProps) => { + const { t } = useTranslation('workflow') + const appId = useStore(s => s.appId) + const isWorkflowRunning = useStore( + s => s.workflowRunningData?.result?.status === WorkflowRunningStatus.Running, + ) + const isResponding = useStore(s => s.isResponding) + + 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(() => { + if (!selectedFile) + return undefined + + const selectedExists = flatData?.some( + node => !node.is_dir && node.path === selectedFile.path, + ) ?? false + + return selectedExists ? selectedFile.path : undefined + }, [flatData, selectedFile]) + + const { data: downloadUrlData, isLoading: isDownloadUrlLoading } = useQuery({ + ...sandboxFileDownloadUrlOptions(appId, selectedFilePath), + retry: false, + }) + + const handleFileSelect = useCallback((node: SandboxFileTreeNode) => { + if (node.node_type === 'file') + setSelectedFile(node) + }, []) + + const handleTreeDownload = useCallback(async (node: SandboxFileTreeNode) => { + try { + const ticket = await fetchDownloadUrl(node.path) + downloadUrl({ url: ticket.download_url, fileName: node.name }) + } + catch (error) { + console.error('Download failed:', error) + } + }, [fetchDownloadUrl]) + + const handleSelectedFileDownload = useCallback(() => { + if (downloadUrlData?.download_url && selectedFile) + downloadUrl({ url: downloadUrlData.download_url, fileName: selectedFile.name }) + }, [downloadUrlData, selectedFile]) + + if (isLoading) { + return ( + +
+ +
+
+ ) + } + + if (!hasFiles) { + return ( + +
+ +
+
+ ) + } + + const file = selectedFilePath ? selectedFile : null + const parts = file?.path.split('/') ?? [] + let cumPath = '' + const pathSegments = parts.map((part, i) => { + cumPath += (cumPath ? '/' : '') + part + return { part, key: cumPath, isFirst: i === 0, isLast: i === parts.length - 1 } + }) + + return ( + + + + )} + > + {({ isNarrow, onOpenMenu, onClose: handleClose }) => ( + <> +
+
+ {isNarrow && ( + + + )} + {file && ( + <> +
+
+ {pathSegments!.map(seg => ( + + {!seg.isFirst && /} + + {seg.part} + + + ))} +
+ + {formatFileSize(file.size)} + +
+
+ + + +
+ + )} +
+ + +
+
+ {file + ? ( +
+ {isDownloadUrlLoading + ?
+ : downloadUrlData?.download_url + ? ( + + ) + : ( +
+

+ {t('debug.variableInspect.tabArtifacts.previewNotAvailable')} +

+
+ )} +
+ ) + : ( +
+ +
+ )} +
+ + )} +
+ ) +} + +export default ArtifactsTab diff --git a/web/app/components/workflow/variable-inspect/group.tsx b/web/app/components/workflow/variable-inspect/group.tsx index aeec4f20a8..07f986f417 100644 --- a/web/app/components/workflow/variable-inspect/group.tsx +++ b/web/app/components/workflow/variable-inspect/group.tsx @@ -1,9 +1,18 @@ -import type { CurrentVarInInspect } from './types' +import type { currentVarType } from './variables-tab' import type { NodeWithVar, VarInInspect } from '@/types/workflow' +import { + RiArrowRightSLine, + RiDeleteBinLine, + RiFileList3Line, + RiLoader2Line, + // RiErrorWarningFill, +} from '@remixicon/react' import { useState } from 'react' import { useTranslation } from 'react-i18next' +// import Button from '@/app/components/base/button' import ActionButton from '@/app/components/base/action-button' -import { Tooltip, TooltipContent, TooltipTrigger } from '@/app/components/base/ui/tooltip' +import { AtSign } from '@/app/components/base/icons/src/vender/workflow' +import Tooltip from '@/app/components/base/tooltip' import BlockIcon from '@/app/components/workflow/block-icon' import { VariableIconWithColor } from '@/app/components/workflow/nodes/_base/components/variable/variable-label' import { VarInInspectType } from '@/types/workflow' @@ -13,15 +22,15 @@ import { formatVarTypeLabel } from './utils' type Props = { nodeData?: NodeWithVar - currentVar?: CurrentVarInInspect + currentVar?: currentVarType varType: VarInInspectType varList: VarInInspect[] - handleSelect: (state: CurrentVarInInspect) => void + handleSelect: (state: currentVarType) => void handleView?: () => void handleClear?: () => void } -export default function Group({ +const Group = ({ nodeData, currentVar, varType, @@ -29,7 +38,7 @@ export default function Group({ handleSelect, handleView, handleClear, -}: Props) { +}: Props) => { const { t } = useTranslation() const [isCollapsed, setIsCollapsed] = useState(false) @@ -95,10 +104,10 @@ export default function Group({
{nodeData?.isSingRunRunning && ( -
setIsCollapsed(!isCollapsed)}> @@ -123,29 +132,15 @@ export default function Group({
{nodeData && !nodeData.isSingRunRunning && (
- - - - - - - )} - /> - {t('debug.variableInspect.view', { ns: 'workflow' })} + + + + - - - - - - - )} - /> - {t('debug.variableInspect.clearNode', { ns: 'workflow' })} + + + +
)} @@ -167,7 +162,7 @@ export default function Group({ onClick={() => handleSelectVar(varItem, varType)} > {isAgentAliasVar - ? @ + ? : ( ) } + +export default Group diff --git a/web/app/components/workflow/variable-inspect/hooks/use-artifacts-inspect-state.ts b/web/app/components/workflow/variable-inspect/hooks/use-artifacts-inspect-state.ts deleted file mode 100644 index 8b2336f1b9..0000000000 --- a/web/app/components/workflow/variable-inspect/hooks/use-artifacts-inspect-state.ts +++ /dev/null @@ -1,141 +0,0 @@ -import type { SandboxFileTreeNode } from '@/types/sandbox-file' -import { useQuery } from '@tanstack/react-query' -import { useCallback, useMemo, useState } from 'react' -import { sandboxFileDownloadUrlOptions, sandboxFilesTreeOptions, useDownloadSandboxFile } from '@/service/use-sandbox-file' -import { downloadUrl } from '@/utils/download' -import { buildTreeFromFlatList } from '../../skill/file-tree/artifacts/utils' -import { useStore } from '../../store' -import { WorkflowRunningStatus } from '../../types' - -type PathSegment = { - part: string - key: string - isFirst: boolean - isLast: boolean -} - -export type ArtifactsInspectStatus = 'loading' | 'empty' | 'split' - -export type ArtifactsInspectView = { - downloadUrlData?: { download_url?: string } - handleFileSelect: (node: SandboxFileTreeNode) => void - handleSelectedFileDownload: () => void - handleTreeDownload: (node: SandboxFileTreeNode) => Promise - isDownloadUrlLoading: boolean - isDownloading: boolean - pathSegments: PathSegment[] - selectedFile: SandboxFileTreeNode | null - selectedFilePath?: string - status: ArtifactsInspectStatus - treeData?: SandboxFileTreeNode[] -} - -export const useArtifactsInspectView = (): ArtifactsInspectView => { - const appId = useStore(s => s.appId) - const isWorkflowRunning = useStore( - s => s.workflowRunningData?.result?.status === WorkflowRunningStatus.Running, - ) - const isResponding = useStore(s => s.isResponding) - - 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(() => { - if (!selectedFile) - return undefined - - const selectedExists = flatData?.some(node => !node.is_dir && node.path === selectedFile.path) ?? false - return selectedExists ? selectedFile.path : undefined - }, [flatData, selectedFile]) - - const { data: downloadUrlData, isLoading: isDownloadUrlLoading } = useQuery({ - ...sandboxFileDownloadUrlOptions(appId, selectedFilePath), - retry: false, - }) - - const handleFileSelect = useCallback((node: SandboxFileTreeNode) => { - if (node.node_type === 'file') - setSelectedFile(node) - }, []) - - const handleTreeDownload = useCallback(async (node: SandboxFileTreeNode) => { - try { - const ticket = await fetchDownloadUrl(node.path) - downloadUrl({ url: ticket.download_url, fileName: node.name }) - } - catch (error) { - console.error('Download failed:', error) - } - }, [fetchDownloadUrl]) - - const handleSelectedFileDownload = useCallback(() => { - if (downloadUrlData?.download_url && selectedFile) - downloadUrl({ url: downloadUrlData.download_url, fileName: selectedFile.name }) - }, [downloadUrlData, selectedFile]) - - const pathSegments = useMemo(() => { - const parts = selectedFilePath ? selectedFilePath.split('/') : [] - let cumPath = '' - return parts.map((part, index) => { - cumPath += `${cumPath ? '/' : ''}${part}` - return { - part, - key: cumPath, - isFirst: index === 0, - isLast: index === parts.length - 1, - } - }) - }, [selectedFilePath]) - - if (isLoading) { - return { - downloadUrlData, - handleFileSelect, - handleSelectedFileDownload, - handleTreeDownload, - isDownloadUrlLoading, - isDownloading, - pathSegments, - selectedFile, - selectedFilePath, - status: 'loading', - treeData, - } - } - - if (!hasFiles) { - return { - downloadUrlData, - handleFileSelect, - handleSelectedFileDownload, - handleTreeDownload, - isDownloadUrlLoading, - isDownloading, - pathSegments, - selectedFile, - selectedFilePath, - status: 'empty', - treeData, - } - } - - return { - downloadUrlData, - handleFileSelect, - handleSelectedFileDownload, - handleTreeDownload, - isDownloadUrlLoading, - isDownloading, - pathSegments, - selectedFile, - selectedFilePath, - status: 'split', - treeData, - } -} diff --git a/web/app/components/workflow/variable-inspect/hooks/use-inspect-shell.ts b/web/app/components/workflow/variable-inspect/hooks/use-inspect-shell.ts deleted file mode 100644 index 8f8093b976..0000000000 --- a/web/app/components/workflow/variable-inspect/hooks/use-inspect-shell.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { createContext, use } from 'react' - -type InspectShellContextValue = { - closeLeftPane: () => void - isNarrow: boolean - onClose: () => void - openLeftPane: () => void -} - -export const InspectShellContext = createContext(null) - -export default function useInspectShell() { - const context = use(InspectShellContext) - - if (!context) - throw new Error('useInspectShell must be used within InspectShell') - - return context -} diff --git a/web/app/components/workflow/variable-inspect/index.tsx b/web/app/components/workflow/variable-inspect/index.tsx index ea5d463feb..775c761eca 100644 --- a/web/app/components/workflow/variable-inspect/index.tsx +++ b/web/app/components/workflow/variable-inspect/index.tsx @@ -1,3 +1,4 @@ +import type { FC } from 'react' import { debounce } from 'es-toolkit/compat' import { useCallback, @@ -8,7 +9,7 @@ import { useResizePanel } from '../nodes/_base/hooks/use-resize-panel' import { useStore } from '../store' import Panel from './panel' -export default function VariableInspectPanel() { +const VariableInspectPanel: FC = () => { const showVariableInspectPanel = useStore(s => s.showVariableInspectPanel) const workflowCanvasHeight = useStore(s => s.workflowCanvasHeight) const variableInspectPanelHeight = useStore(s => s.variableInspectPanelHeight) @@ -57,3 +58,5 @@ export default function VariableInspectPanel() {
) } + +export default VariableInspectPanel diff --git a/web/app/components/workflow/variable-inspect/inspect-layout.tsx b/web/app/components/workflow/variable-inspect/inspect-layout.tsx new file mode 100644 index 0000000000..15560db083 --- /dev/null +++ b/web/app/components/workflow/variable-inspect/inspect-layout.tsx @@ -0,0 +1,46 @@ +import type { FC, ReactNode } from 'react' +import type { InspectTab } from './types' +import { RiCloseLine } from '@remixicon/react' +import ActionButton from '@/app/components/base/action-button' +import TabHeader from './tab-header' + +export type InspectHeaderProps = { + activeTab: InspectTab + onTabChange: (tab: InspectTab) => void + onClose: () => void + headerActions?: ReactNode +} + +type InspectLayoutProps = InspectHeaderProps & { + children: ReactNode +} + +const InspectLayout: FC = ({ + activeTab, + onTabChange, + onClose, + headerActions, + children, +}) => { + return ( +
+
+
+ + {headerActions} + +
+
+ + + +
+
+
+ {children} +
+
+ ) +} + +export default InspectLayout diff --git a/web/app/components/workflow/variable-inspect/inspect-scroll-area.tsx b/web/app/components/workflow/variable-inspect/inspect-scroll-area.tsx deleted file mode 100644 index 5ef2a8e5ad..0000000000 --- a/web/app/components/workflow/variable-inspect/inspect-scroll-area.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import type { ReactNode } from 'react' -import { ScrollArea } from '@/app/components/base/ui/scroll-area' -import { cn } from '@/utils/classnames' - -type InspectScrollAreaProps = { - children: ReactNode - className?: string - contentClassName?: string - label?: string -} - -export default function InspectScrollArea({ - children, - className, - contentClassName, - label, -}: InspectScrollAreaProps) { - return ( - - {children} - - ) -} diff --git a/web/app/components/workflow/variable-inspect/inspect-shell.tsx b/web/app/components/workflow/variable-inspect/inspect-shell.tsx deleted file mode 100644 index 1914cb9a68..0000000000 --- a/web/app/components/workflow/variable-inspect/inspect-shell.tsx +++ /dev/null @@ -1,102 +0,0 @@ -import type { ReactNode } from 'react' -import type { InspectHeaderProps } from './types' -import { useMemo, useState } from 'react' -import ActionButton from '@/app/components/base/action-button' -import { cn } from '@/utils/classnames' -import { useStore } from '../store' -import useInspectShell, { InspectShellContext } from './hooks/use-inspect-shell' -import TabHeader from './tab-header' - -type InspectShellProps = InspectHeaderProps & { - children: ReactNode - left?: ReactNode -} - -function SinglePaneCloseButton() { - const { onClose } = useInspectShell() - - return ( -
- - -
- ) -} - -export default function InspectShell({ - activeTab, - onTabChange, - onClose, - headerActions, - left, - children, -}: InspectShellProps) { - const bottomPanelWidth = useStore(s => s.bottomPanelWidth) - const isNarrow = bottomPanelWidth < 488 - const [showLeftPane, setShowLeftPane] = useState(true) - const hasLeftPane = !!left - - const contextValue = useMemo(() => ({ - closeLeftPane: () => setShowLeftPane(false), - isNarrow: hasLeftPane - ? isNarrow - : false, - onClose, - openLeftPane: () => setShowLeftPane(true), - }), [hasLeftPane, isNarrow, onClose]) - - return ( - - {hasLeftPane - ? ( -
-
-
- - {headerActions} - -
- {isNarrow && showLeftPane && ( -
setShowLeftPane(false)} - /> - )} -
- {left} -
-
-
- {children} -
-
- ) - : ( -
-
-
- - {headerActions} - -
- -
-
- {children} -
-
- )} - - ) -} diff --git a/web/app/components/workflow/variable-inspect/left.tsx b/web/app/components/workflow/variable-inspect/left.tsx index 74df6d20dc..fc25601d72 100644 --- a/web/app/components/workflow/variable-inspect/left.tsx +++ b/web/app/components/workflow/variable-inspect/left.tsx @@ -1,14 +1,18 @@ -import type { CurrentVarInInspect } from './types' +import type { currentVarType } from './variables-tab' + import type { VarInInspect } from '@/types/workflow' import { VarInInspectType } from '@/types/workflow' +import { cn } from '@/utils/classnames' import useCurrentVars from '../hooks/use-inspect-vars-crud' import { useNodesInteractions } from '../hooks/use-nodes-interactions' import { useStore } from '../store' +// import ActionButton from '@/app/components/base/action-button' +// import Tooltip from '@/app/components/base/tooltip' import Group from './group' type Props = { - currentNodeVar?: CurrentVarInInspect - handleVarSelect: (state: CurrentVarInInspect) => void + currentNodeVar?: currentVarType + handleVarSelect: (state: currentVarType) => void } const Left = ({ @@ -36,48 +40,55 @@ const Left = ({ } return ( -
- {environmentVariables.length > 0 && ( - - )} - {conversationVars.length > 0 && ( - - )} - {systemVars.length > 0 && ( - - )} - {showDivider && ( -
-
-
- )} - {visibleNodesWithInspectVars.length > 0 && visibleNodesWithInspectVars.map(group => ( - handleNodeSelect(group.nodeId, false, true)} - handleClear={() => handleClearNode(group.nodeId)} - /> - ))} +
+
+ {/* group ENV */} + {environmentVariables.length > 0 && ( + + )} + {/* group CHAT VAR */} + {conversationVars.length > 0 && ( + + )} + {/* group SYSTEM VAR */} + {systemVars.length > 0 && ( + + )} + {/* divider */} + {showDivider && ( +
+
+
+ )} + {/* group nodes */} + {visibleNodesWithInspectVars.length > 0 && visibleNodesWithInspectVars.map(group => ( + handleNodeSelect(group.nodeId, false, true)} + handleClear={() => handleClearNode(group.nodeId)} + /> + ))} +
) } diff --git a/web/app/components/workflow/variable-inspect/listening.tsx b/web/app/components/workflow/variable-inspect/listening.tsx index 69421cf303..130936147b 100644 --- a/web/app/components/workflow/variable-inspect/listening.tsx +++ b/web/app/components/workflow/variable-inspect/listening.tsx @@ -1,4 +1,5 @@ import type { TFunction } from 'i18next' +import type { FC } from 'react' import type { Node } from 'reactflow' import type { ScheduleTriggerNodeType } from '@/app/components/workflow/nodes/trigger-schedule/types' import type { WebhookTriggerNodeType } from '@/app/components/workflow/nodes/trigger-webhook/types' @@ -7,7 +8,8 @@ import { useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' import { useStoreApi } from 'reactflow' import Button from '@/app/components/base/button' -import { Tooltip, TooltipContent, TooltipTrigger } from '@/app/components/base/ui/tooltip' +import { StopCircle } from '@/app/components/base/icons/src/vender/line/mediaAndDevices' +import Tooltip from '@/app/components/base/tooltip' import BlockIcon from '@/app/components/workflow/block-icon' import { useGetToolIcon } from '@/app/components/workflow/hooks/use-tool-icon' import { getNextExecutionTime } from '@/app/components/workflow/nodes/trigger-schedule/utils/execution-time-calculator' @@ -73,10 +75,10 @@ export type ListeningProps = { message?: string } -export default function Listening({ +const Listening: FC = ({ onStop, message, -}: ListeningProps) { +}) => { const { t } = useTranslation() const store = useStoreApi() @@ -177,33 +179,28 @@ export default function Listening({
{t('nodes.triggerWebhook.debugUrlTitle', { ns: 'workflow' })}
- - { - copy(webhookDebugUrl) - setDebugUrlCopied(true) - }} - > - - {webhookDebugUrl} - - - )} - /> - +
)} @@ -214,10 +211,12 @@ export default function Listening({ variant="primary" onClick={onStop} > -
) } + +export default Listening diff --git a/web/app/components/workflow/variable-inspect/panel.tsx b/web/app/components/workflow/variable-inspect/panel.tsx index 5a2533a7af..32803e5a13 100644 --- a/web/app/components/workflow/variable-inspect/panel.tsx +++ b/web/app/components/workflow/variable-inspect/panel.tsx @@ -1,164 +1,71 @@ -import type { ReactNode } from 'react' -import type { InspectTab as InspectTabType } from './types' +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 Loading from '@/app/components/base/loading' +import { sandboxFilesTreeOptions } from '@/service/use-sandbox-file' import useCurrentVars from '../hooks/use-inspect-vars-crud' import { useStore } from '../store' -import ArtifactsEmptyState from './artifacts-empty-state' -import ArtifactsLeftPane from './artifacts-left-pane' -import ArtifactsRightPane from './artifacts-right-pane' -import Empty from './empty' -import { - useArtifactsInspectView, -} from './hooks/use-artifacts-inspect-state' -import { - useVariablesInspectView, -} from './hooks/use-variables-inspect-state' -import InspectScrollArea from './inspect-scroll-area' -import InspectShell from './inspect-shell' -import Left from './left' -import Listening from './listening' -import Right from './right' +import ArtifactsTab from './artifacts-tab' import { InspectTab } from './types' +import VariablesTab from './variables-tab' -export default function Panel() { +const VariablesPanel: FC<{ onClose: () => void }> = ({ onClose }) => { const { t } = useTranslation('workflow') const setCurrentFocusNodeId = useStore(s => s.setCurrentFocusNodeId) - const setShowVariableInspectPanel = useStore(s => s.setShowVariableInspectPanel) - const environmentVariables = useStore(s => s.environmentVariables) + const appId = useStore(s => s.appId) const sandboxEnabled = useFeatures(s => s.features.sandbox?.enabled) ?? false - const [activeTab, setActiveTab] = useState(InspectTab.Variables) - - const { - conversationVars, - systemVars, - nodesWithInspectVars, - deleteAllInspectorVars, - } = useCurrentVars() - - const variablesState = useVariablesInspectView() - const artifactsState = useArtifactsInspectView() + const [activeTab, setActiveTab] = useState(InspectTab.Variables) const resolvedTab = (!sandboxEnabled && activeTab === InspectTab.Artifacts) ? InspectTab.Variables : activeTab + const environmentVariables = useStore(s => s.environmentVariables) + const { conversationVars, systemVars, nodesWithInspectVars, deleteAllInspectorVars } = useCurrentVars() + const isVariablesEmpty = useMemo(() => { return [...environmentVariables, ...conversationVars, ...systemVars, ...nodesWithInspectVars].length === 0 - }, [conversationVars, environmentVariables, nodesWithInspectVars, systemVars]) + }, [environmentVariables, conversationVars, systemVars, nodesWithInspectVars]) - const hasArtifacts = artifactsState.status === 'split' - const hasData = !isVariablesEmpty || hasArtifacts + const { data: sandboxFiles } = useQuery(sandboxFilesTreeOptions(sandboxEnabled ? appId : undefined)) + const hasArtifacts = (sandboxFiles?.length ?? 0) > 0 const handleClear = useCallback(() => { deleteAllInspectorVars() setCurrentFocusNodeId('') }, [deleteAllInspectorVars, setCurrentFocusNodeId]) + const hasData = !isVariablesEmpty || hasArtifacts + const headerActions = hasData + ? ( + + ) + : undefined + + const headerProps = { + activeTab: resolvedTab, + onTabChange: setActiveTab, + onClose, + headerActions, + } + + return resolvedTab === InspectTab.Variables + ? + : +} + +const Panel: FC = () => { + const setShowVariableInspectPanel = useStore(s => s.setShowVariableInspectPanel) + const handleClose = useCallback(() => { setShowVariableInspectPanel(false) }, [setShowVariableInspectPanel]) - const headerActions = ( - - ) - - const headerProps = { - activeTab: resolvedTab, - headerActions, - onClose: handleClose, - onTabChange: setActiveTab, - } - - let leftPane: ReactNode | undefined - let body: ReactNode - - if (resolvedTab === InspectTab.Variables) { - if (variablesState.status === 'listening') { - body = ( -
- -
- ) - } - else if (variablesState.status === 'empty') { - body = ( -
- -
- ) - } - else { - leftPane = ( - - - - ) - body = ( - - ) - } - } - else if (artifactsState.status === 'loading') { - body = ( -
- -
- ) - } - else if (artifactsState.status === 'empty') { - body = ( -
- -
- ) - } - else { - leftPane = ( - - - - ) - body = ( - - ) - } - - return ( - - {body} - - ) + return } + +export default Panel diff --git a/web/app/components/workflow/variable-inspect/right.tsx b/web/app/components/workflow/variable-inspect/right.tsx index 74b9cb27a6..7aab66f41b 100644 --- a/web/app/components/workflow/variable-inspect/right.tsx +++ b/web/app/components/workflow/variable-inspect/right.tsx @@ -1,6 +1,15 @@ -import type { CurrentVarInInspect } from './types' +import type { FC } from 'react' +import type { SplitRightProps } from './split-panel' import type { VarInspectValue } from './value-types' +import type { currentVarType } from './variables-tab' import type { GenRes } from '@/service/debug' +import { + RiArrowGoBackLine, + RiCloseLine, + RiFileDownloadFill, + RiMenuLine, + RiSparklingFill, +} from '@remixicon/react' import { useBoolean } from 'ahooks' import { produce } from 'immer' import { useCallback, useMemo } from 'react' @@ -10,7 +19,7 @@ import ActionButton from '@/app/components/base/action-button' import Badge from '@/app/components/base/badge' import CopyFeedback from '@/app/components/base/copy-feedback' import Loading from '@/app/components/base/loading' -import { Tooltip, TooltipContent, TooltipTrigger } from '@/app/components/base/ui/tooltip' +import Tooltip from '@/app/components/base/tooltip' import BlockIcon from '@/app/components/workflow/block-icon' import { VariableIconWithColor } from '@/app/components/workflow/nodes/_base/components/variable/variable-label' import { useEventEmitterContextContext } from '@/context/event-emitter' @@ -26,23 +35,24 @@ import useNodeInfo from '../nodes/_base/hooks/use-node-info' import { CodeLanguage } from '../nodes/code/types' import { BlockEnum } from '../types' import Empty from './empty' -import useInspectShell from './hooks/use-inspect-shell' import { formatVarTypeLabel } from './utils' import ValueContent from './value-content' -type Props = { +type Props = SplitRightProps & { nodeId: string - currentNodeVar?: CurrentVarInInspect + currentNodeVar?: currentVarType isValueFetching?: boolean } -export default function Right({ +const Right: FC = ({ nodeId, currentNodeVar, isValueFetching, -}: Props) { + isNarrow, + onOpenMenu, + onClose, +}) => { const { t } = useTranslation() - const { isNarrow, onClose, openLeftPane } = useInspectShell() const toolIcon = useToolIcon(currentNodeVar?.nodeData) const currentVar = currentNodeVar?.var const currentNodeType = currentNodeVar?.nodeType @@ -101,7 +111,7 @@ export default function Right({ return node?.data?.prompt_template?.text || node?.data?.prompt_template?.[0].text if (blockType === BlockEnum.Code) return node?.data?.code - }, [blockType, canShowPromptGenerator, node?.data?.code, node?.data?.prompt_template]) + }, [canShowPromptGenerator]) const [isShowPromptGenerator, { setTrue: doShowPromptGenerator, @@ -135,7 +145,7 @@ export default function Right({ payload: res.modified, }) handleHidePromptGenerator() - }, [eventEmitter, setInputs, blockType, nodeId, node?.data, handleHidePromptGenerator]) + }, [setInputs, blockType, nodeId, node?.data, handleHidePromptGenerator]) const schemaType = currentVar?.schemaType const valueType = currentVar?.value_type @@ -149,13 +159,11 @@ export default function Right({ <>
- {isNarrow - ? ( - - - ) - : null} + {isNarrow && ( + + + + )}
{currentVar && ( <> @@ -204,70 +212,44 @@ export default function Right({ {currentVar && ( <> {canShowPromptGenerator && ( - - - - )} - /> - {t('debug.variableInspect.exportToolTip', { ns: 'workflow' })} + + window.open(fullContent?.download_url, '_blank')} + aria-label={t('debug.variableInspect.exportToolTip', { ns: 'workflow' })} + > + + )} {!isTruncated && currentVar.edited && ( - {t('debug.variableInspect.edited', { ns: 'workflow' })} + {t('debug.variableInspect.edited', { ns: 'workflow' })} )} {!isTruncated && currentVar.edited && currentVar.type !== VarInInspectType.conversation && ( - - - - - - )} - /> - {t('debug.variableInspect.reset', { ns: 'workflow' })} + + + + )} {!isTruncated && currentVar.edited && currentVar.type === VarInInspectType.conversation && ( - - - - - - )} - /> - {t('debug.variableInspect.resetConversationVar', { ns: 'workflow' })} + + + + )} {currentVar.value_type !== 'secret' && ( @@ -278,7 +260,7 @@ export default function Right({
-
@@ -328,3 +310,5 @@ export default function Right({ ) } + +export default Right diff --git a/web/app/components/workflow/variable-inspect/split-panel.tsx b/web/app/components/workflow/variable-inspect/split-panel.tsx new file mode 100644 index 0000000000..bc2ec164c4 --- /dev/null +++ b/web/app/components/workflow/variable-inspect/split-panel.tsx @@ -0,0 +1,62 @@ +import type { FC, ReactNode } from 'react' +import type { InspectHeaderProps } from './inspect-layout' +import { useState } from 'react' +import { cn } from '@/utils/classnames' +import { useStore } from '../store' +import TabHeader from './tab-header' + +export type SplitRightProps = { + isNarrow: boolean + onOpenMenu: () => void + onClose: () => void +} + +type SplitPanelProps = InspectHeaderProps & { + left: ReactNode + children: (rightProps: SplitRightProps) => ReactNode +} + +const SplitPanel: FC = ({ + activeTab, + onTabChange, + onClose, + headerActions, + left, + children, +}) => { + const bottomPanelWidth = useStore(s => s.bottomPanelWidth) + const isNarrow = bottomPanelWidth < 488 + const [showLeftPanel, setShowLeftPanel] = useState(true) + + return ( +
+
+
+ + {headerActions} + +
+ {isNarrow && showLeftPanel && ( +
setShowLeftPanel(false)} /> + )} +
+ {left} +
+
+
+ {children({ isNarrow, onOpenMenu: () => setShowLeftPanel(true), onClose })} +
+
+ ) +} + +export default SplitPanel diff --git a/web/app/components/workflow/variable-inspect/tab-header.tsx b/web/app/components/workflow/variable-inspect/tab-header.tsx index 6ca73c4097..98c0e501dd 100644 --- a/web/app/components/workflow/variable-inspect/tab-header.tsx +++ b/web/app/components/workflow/variable-inspect/tab-header.tsx @@ -1,4 +1,4 @@ -import type { ReactNode } from 'react' +import type { FC, ReactNode } from 'react' import { useMemo } from 'react' import { useTranslation } from 'react-i18next' import { useFeatures } from '@/app/components/base/features/hooks' @@ -16,11 +16,11 @@ type TabHeaderProps = { children?: ReactNode } -export default function TabHeader({ +const TabHeader: FC = ({ activeTab, onTabChange, children, -}: TabHeaderProps) { +}) => { const { t } = useTranslation('workflow') const sandboxEnabled = useFeatures(s => s.features.sandbox?.enabled) ?? false @@ -46,7 +46,9 @@ export default function TabHeader({ {t(tab.labelKey)} ))} -
{children}
+
{children}
) } + +export default TabHeader diff --git a/web/app/components/workflow/variable-inspect/trigger.tsx b/web/app/components/workflow/variable-inspect/trigger.tsx index 3a0050c6a9..30e0b459a2 100644 --- a/web/app/components/workflow/variable-inspect/trigger.tsx +++ b/web/app/components/workflow/variable-inspect/trigger.tsx @@ -1,8 +1,10 @@ +import type { FC } from 'react' import type { CommonNodeType } from '@/app/components/workflow/types' +import { RiLoader2Line, RiStopCircleFill } from '@remixicon/react' import { useMemo } from 'react' import { useTranslation } from 'react-i18next' import { useNodes } from 'reactflow' -import { Tooltip, TooltipContent, TooltipTrigger } from '@/app/components/base/ui/tooltip' +import Tooltip from '@/app/components/base/tooltip' import { NodeRunningStatus, WorkflowRunningStatus } from '@/app/components/workflow/types' import { EVENT_WORKFLOW_STOP } from '@/app/components/workflow/variable-inspect/types' import { useEventEmitterContextContext } from '@/context/event-emitter' @@ -11,7 +13,7 @@ import useCurrentVars from '../hooks/use-inspect-vars-crud' import { useNodesReadOnly } from '../hooks/use-workflow' import { useStore } from '../store' -export default function VariableInspectTrigger() { +const VariableInspectTrigger: FC = () => { const { t } = useTranslation() const { eventEmitter } = useEventEmitterContextContext() @@ -102,22 +104,19 @@ export default function VariableInspectTrigger() { className="flex h-6 cursor-pointer items-center gap-1 rounded-md border-[0.5px] border-effects-highlight bg-components-actionbar-bg px-2 text-text-accent shadow-lg backdrop-blur-sm system-xs-medium hover:bg-components-actionbar-bg-accent" onClick={() => setShowVariableInspectPanel(true)} > -
{isPreviewRunning && ( - - -