From 871a2a149f572a7aa29cfc31e75388601cfd01dd Mon Sep 17 00:00:00 2001 From: JzoNg Date: Mon, 30 Mar 2026 10:32:59 +0800 Subject: [PATCH] refactor(web): split snippet index --- .../snippets/[snippetId]/evaluation/page.tsx | 4 +- .../snippets/[snippetId]/orchestrate/page.tsx | 2 +- .../snippets/__tests__/index.spec.tsx | 178 +++--------------- .../snippet-evaluation-page.spec.tsx | 107 +++++++++++ .../__tests__/snippet-main.spec.tsx | 44 +---- .../components/hooks/use-snippet-publish.ts | 5 +- .../snippets/components/snippet-layout.tsx | 88 +++++++++ .../snippets/components/snippet-main.tsx | 126 +++---------- web/app/components/snippets/index.tsx | 55 +++--- .../snippets/snippet-evaluation-page.tsx | 46 +++++ 10 files changed, 332 insertions(+), 323 deletions(-) create mode 100644 web/app/components/snippets/__tests__/snippet-evaluation-page.spec.tsx create mode 100644 web/app/components/snippets/components/snippet-layout.tsx create mode 100644 web/app/components/snippets/snippet-evaluation-page.tsx diff --git a/web/app/(commonLayout)/snippets/[snippetId]/evaluation/page.tsx b/web/app/(commonLayout)/snippets/[snippetId]/evaluation/page.tsx index 293945ad206..adc4bb6903e 100644 --- a/web/app/(commonLayout)/snippets/[snippetId]/evaluation/page.tsx +++ b/web/app/(commonLayout)/snippets/[snippetId]/evaluation/page.tsx @@ -1,11 +1,11 @@ -import SnippetPage from '@/app/components/snippets' +import SnippetEvaluationPage from '@/app/components/snippets/snippet-evaluation-page' const Page = async (props: { params: Promise<{ snippetId: string }> }) => { const { snippetId } = await props.params - return + return } export default Page diff --git a/web/app/(commonLayout)/snippets/[snippetId]/orchestrate/page.tsx b/web/app/(commonLayout)/snippets/[snippetId]/orchestrate/page.tsx index 702dfcab543..8a39dc710b1 100644 --- a/web/app/(commonLayout)/snippets/[snippetId]/orchestrate/page.tsx +++ b/web/app/(commonLayout)/snippets/[snippetId]/orchestrate/page.tsx @@ -5,7 +5,7 @@ const Page = async (props: { }) => { const { snippetId } = await props.params - return + return } export default Page diff --git a/web/app/components/snippets/__tests__/index.spec.tsx b/web/app/components/snippets/__tests__/index.spec.tsx index 041783b9873..8d63394a820 100644 --- a/web/app/components/snippets/__tests__/index.spec.tsx +++ b/web/app/components/snippets/__tests__/index.spec.tsx @@ -1,44 +1,16 @@ -import type { HeaderProps } from '@/app/components/workflow/header' import type { SnippetDetailPayload } from '@/models/snippet' -import { fireEvent, render, screen } from '@testing-library/react' -import { PipelineInputVarType } from '@/models/pipeline' +import { render, screen } from '@testing-library/react' import SnippetPage from '..' -import { useSnippetDetailStore } from '../store' const mockUseSnippetInit = vi.fn() -const mockPublishSnippetMutateAsync = vi.fn() +const mockSetAppSidebarExpand = vi.fn() vi.mock('../hooks/use-snippet-init', () => ({ useSnippetInit: (snippetId: string) => mockUseSnippetInit(snippetId), })) -vi.mock('../hooks/use-configs-map', () => ({ - useConfigsMap: () => ({ - flowId: 'snippet-1', - flowType: 'snippet', - fileSettings: {}, - }), -})) - -vi.mock('../hooks/use-nodes-sync-draft', () => ({ - useNodesSyncDraft: () => ({ - doSyncWorkflowDraft: vi.fn(), - syncInputFieldsDraft: vi.fn(), - syncWorkflowDraftWhenPageClose: vi.fn(), - }), -})) - -vi.mock('../hooks/use-snippet-refresh-draft', () => ({ - useSnippetRefreshDraft: () => ({ - handleRefreshWorkflowDraft: vi.fn(), - }), -})) - -vi.mock('@/service/use-snippet-workflows', () => ({ - usePublishSnippetWorkflowMutation: () => ({ - mutateAsync: mockPublishSnippetMutateAsync, - isPending: false, - }), +vi.mock('../components/snippet-main', () => ({ + default: ({ snippetId }: { snippetId: string }) =>
{snippetId}
, })) vi.mock('@/next/navigation', () => ({ @@ -48,41 +20,14 @@ vi.mock('@/next/navigation', () => ({ }), })) -vi.mock('@/service/use-snippets', async (importOriginal) => { - const actual = await importOriginal() - - return { - ...actual, - useUpdateSnippetMutation: () => ({ - mutate: vi.fn(), - isPending: false, - }), - useExportSnippetMutation: () => ({ - mutateAsync: vi.fn(), - isPending: false, - }), - useDeleteSnippetMutation: () => ({ - mutate: vi.fn(), - isPending: false, - }), - } -}) - -vi.mock('@/service/use-common', () => ({ - useFileUploadConfig: () => ({ - data: undefined, - }), -})) - vi.mock('@/hooks/use-breakpoints', () => ({ default: () => 'desktop', MediaType: { mobile: 'mobile', desktop: 'desktop' }, })) -vi.mock('@/app/components/rag-pipeline/components/panel/input-field/hooks', () => ({ - useFloatingRight: () => ({ - floatingRight: false, - floatingRightWidth: 400, +vi.mock('@/app/components/app/store', () => ({ + useStore: (selector: (state: { setAppSidebarExpand: typeof mockSetAppSidebarExpand }) => unknown) => selector({ + setAppSidebarExpand: mockSetAppSidebarExpand, }), })) @@ -90,26 +35,6 @@ vi.mock('@/app/components/workflow', () => ({ default: ({ children }: { children: React.ReactNode }) => (
{children}
), - WorkflowWithInnerContext: ({ children, viewport }: { children: React.ReactNode, viewport?: { zoom?: number } }) => ( -
- {viewport?.zoom ?? 'none'} - {children} -
- ), -})) - -vi.mock('@/app/components/workflow/header', () => ({ - default: (props: HeaderProps) => { - const CustomRunMode = props.normal?.runAndHistoryProps?.components?.RunMode - - return ( -
- {props.normal?.components?.left} - {CustomRunMode && } - {props.normal?.components?.middle} -
- ) - }, })) vi.mock('@/app/components/workflow/context', () => ({ @@ -118,6 +43,16 @@ vi.mock('@/app/components/workflow/context', () => ({ ), })) +vi.mock('@/app/components/workflow/utils', async (importOriginal) => { + const actual = await importOriginal() + + return { + ...actual, + initialNodes: (nodes: unknown[]) => nodes, + initialEdges: (edges: unknown[]) => edges, + } +}) + vi.mock('@/app/components/app-sidebar', () => ({ default: ({ renderHeader, @@ -139,27 +74,12 @@ vi.mock('@/app/components/app-sidebar/nav-link', () => ({ ), })) -vi.mock('@/app/components/workflow/panel', () => ({ - default: ({ components }: { components?: { left?: React.ReactNode, right?: React.ReactNode } }) => ( -
-
{components?.left}
-
{components?.right}
-
- ), +vi.mock('@/app/components/app-sidebar/snippet-info', () => ({ + default: () =>
, })) -vi.mock('@/app/components/workflow/utils', async (importOriginal) => { - const actual = await importOriginal() - - return { - ...actual, - initialNodes: (nodes: unknown[]) => nodes, - initialEdges: (edges: unknown[]) => edges, - } -}) - -vi.mock('react-sortablejs', () => ({ - ReactSortable: ({ children }: { children: React.ReactNode }) =>
{children}
, +vi.mock('@/app/components/evaluation', () => ({ + default: ({ resourceId }: { resourceId: string }) =>
{resourceId}
, })) const mockSnippetDetail: SnippetDetailPayload = { @@ -179,20 +99,10 @@ const mockSnippetDetail: SnippetDetailPayload = { nodes: [], edges: [], }, - inputFields: [ - { - type: PipelineInputVarType.textInput, - label: 'Blog URL', - variable: 'blog_url', - required: true, - options: [], - placeholder: 'Paste a source article URL', - max_length: 256, - }, - ], + inputFields: [], uiMeta: { - inputFieldCount: 1, - checklistCount: 2, + inputFieldCount: 0, + checklistCount: 0, autoSavedAt: 'Auto-saved · a few seconds ago', }, } @@ -200,55 +110,23 @@ const mockSnippetDetail: SnippetDetailPayload = { describe('SnippetPage', () => { beforeEach(() => { vi.clearAllMocks() - useSnippetDetailStore.getState().reset() - mockPublishSnippetMutateAsync.mockResolvedValue(undefined) mockUseSnippetInit.mockReturnValue({ data: mockSnippetDetail, isLoading: false, }) }) - it('should render the snippet detail shell', () => { + it('should render the orchestrate route shell with independent main content', () => { render() - expect(screen.getByText('Tone Rewriter')).toBeInTheDocument() - expect(screen.getByText('A static snippet mock.')).toBeInTheDocument() expect(screen.getByTestId('app-sidebar')).toBeInTheDocument() + expect(screen.getByTestId('snippet-info')).toBeInTheDocument() expect(screen.getByTestId('workflow-context-provider')).toBeInTheDocument() expect(screen.getByTestId('workflow-default-context')).toBeInTheDocument() - expect(screen.getByTestId('workflow-inner-context')).toBeInTheDocument() - expect(screen.getByTestId('workflow-viewport-zoom').textContent).toBe('1') + expect(screen.getByTestId('snippet-main')).toHaveTextContent('snippet-1') }) - it('should open the input field panel and editor', () => { - render() - - fireEvent.click(screen.getAllByRole('button', { name: /snippet\.inputFieldButton/i })[0]) - expect(screen.getAllByText('snippet.panelTitle').length).toBeGreaterThan(0) - - fireEvent.click(screen.getAllByRole('button', { name: /datasetPipeline\.inputFieldPanel\.addInputField/i })[0]) - expect(screen.getAllByText('datasetPipeline.inputFieldPanel.addInputField').length).toBeGreaterThan(1) - }) - - it('should toggle the publish menu', () => { - render() - - fireEvent.click(screen.getByRole('button', { name: /snippet\.publishButton/i })) - expect(screen.getByText('snippet.publishMenuCurrentDraft')).toBeInTheDocument() - }) - - it('should publish the snippet when clicking publish in the menu', async () => { - render() - - fireEvent.click(screen.getByRole('button', { name: /snippet\.publishButton/i })) - fireEvent.click(screen.getAllByRole('button', { name: /snippet\.publishButton/i })[1]) - - expect(mockPublishSnippetMutateAsync).toHaveBeenCalledWith({ - params: { snippetId: 'snippet-1' }, - }) - }) - - it('should render loading fallback when snippet data is unavailable', () => { + it('should render loading fallback when orchestrate data is unavailable', () => { mockUseSnippetInit.mockReturnValue({ data: null, isLoading: false, diff --git a/web/app/components/snippets/__tests__/snippet-evaluation-page.spec.tsx b/web/app/components/snippets/__tests__/snippet-evaluation-page.spec.tsx new file mode 100644 index 00000000000..21595ac9633 --- /dev/null +++ b/web/app/components/snippets/__tests__/snippet-evaluation-page.spec.tsx @@ -0,0 +1,107 @@ +import type { Snippet } from '@/types/snippet' +import { render, screen } from '@testing-library/react' +import SnippetEvaluationPage from '../snippet-evaluation-page' + +const mockUseSnippetApiDetail = vi.fn() +const mockSetAppSidebarExpand = vi.fn() + +vi.mock('@/service/use-snippets', async (importOriginal) => { + const actual = await importOriginal() + + return { + ...actual, + useSnippetApiDetail: (snippetId: string) => mockUseSnippetApiDetail(snippetId), + useUpdateSnippetMutation: () => ({ + mutate: vi.fn(), + isPending: false, + }), + useExportSnippetMutation: () => ({ + mutateAsync: vi.fn(), + isPending: false, + }), + useDeleteSnippetMutation: () => ({ + mutate: vi.fn(), + isPending: false, + }), + } +}) + +vi.mock('@/next/navigation', () => ({ + useRouter: () => ({ + replace: vi.fn(), + push: vi.fn(), + }), +})) + +vi.mock('@/hooks/use-breakpoints', () => ({ + default: () => 'desktop', + MediaType: { mobile: 'mobile', desktop: 'desktop' }, +})) + +vi.mock('@/app/components/app/store', () => ({ + useStore: (selector: (state: { setAppSidebarExpand: typeof mockSetAppSidebarExpand }) => unknown) => selector({ + setAppSidebarExpand: mockSetAppSidebarExpand, + }), +})) + +vi.mock('@/app/components/app-sidebar', () => ({ + default: ({ + renderHeader, + renderNavigation, + }: { + renderHeader?: (modeState: string) => React.ReactNode + renderNavigation?: (modeState: string) => React.ReactNode + }) => ( +
+
{renderHeader?.('expand')}
+
{renderNavigation?.('expand')}
+
+ ), +})) + +vi.mock('@/app/components/app-sidebar/nav-link', () => ({ + default: ({ name, onClick }: { name: string, onClick?: () => void }) => ( + + ), +})) + +vi.mock('@/app/components/evaluation', () => ({ + default: ({ resourceId }: { resourceId: string }) =>
{resourceId}
, +})) + +const mockSnippetApiDetail: Snippet = { + id: 'snippet-1', + name: 'Tone Rewriter', + description: 'A static snippet mock.', + type: 'node', + is_published: false, + version: 'draft', + use_count: 19, + icon_info: { + icon_type: 'emoji', + icon: '🪄', + icon_background: '#E0EAFF', + }, + input_fields: [], + created_at: 1_711_609_600, + updated_at: 1_711_616_800, + author: 'Evan', +} + +describe('SnippetEvaluationPage', () => { + beforeEach(() => { + vi.clearAllMocks() + mockUseSnippetApiDetail.mockReturnValue({ + data: mockSnippetApiDetail, + isLoading: false, + }) + }) + + it('should fetch evaluation route data independently from snippet init', () => { + render() + + expect(mockUseSnippetApiDetail).toHaveBeenCalledWith('snippet-1') + expect(screen.getByTestId('app-sidebar')).toBeInTheDocument() + expect(screen.getByTestId('evaluation')).toHaveTextContent('snippet-1') + }) +}) diff --git a/web/app/components/snippets/components/__tests__/snippet-main.spec.tsx b/web/app/components/snippets/components/__tests__/snippet-main.spec.tsx index ff3a3961efb..c6a6b5a2316 100644 --- a/web/app/components/snippets/components/__tests__/snippet-main.spec.tsx +++ b/web/app/components/snippets/components/__tests__/snippet-main.spec.tsx @@ -1,10 +1,9 @@ import type { WorkflowProps } from '@/app/components/workflow' -import type { SnippetDetailPayload, SnippetInputField, SnippetSection } from '@/models/snippet' +import type { SnippetDetailPayload, SnippetInputField } from '@/models/snippet' import { fireEvent, render, screen, waitFor } from '@testing-library/react' import { PipelineInputVarType } from '@/models/pipeline' import SnippetMain from '../snippet-main' -const mockSetAppSidebarExpand = vi.fn() const mockSyncInputFieldsDraft = vi.fn() const mockCloseEditor = vi.fn() const mockOpenEditor = vi.fn() @@ -40,17 +39,6 @@ const mockInspectVarsCrud = { } let capturedHooksStore: Record | undefined -vi.mock('@/hooks/use-breakpoints', () => ({ - default: () => 'desktop', - MediaType: { mobile: 'mobile', desktop: 'desktop' }, -})) - -vi.mock('@/app/components/app/store', () => ({ - useStore: (selector: (state: { setAppSidebarExpand: typeof mockSetAppSidebarExpand }) => unknown) => selector({ - setAppSidebarExpand: mockSetAppSidebarExpand, - }), -})) - vi.mock('@/app/components/snippets/store', () => ({ useSnippetDetailStore: (selector: (state: { editingField: SnippetInputField | null @@ -135,33 +123,6 @@ vi.mock('@/app/components/snippets/hooks/use-snippet-start-run', () => ({ }), })) -vi.mock('@/app/components/app-sidebar', () => ({ - default: ({ - renderHeader, - renderNavigation, - }: { - renderHeader?: (modeState: string) => React.ReactNode - renderNavigation?: (modeState: string) => React.ReactNode - }) => ( -
-
{renderHeader?.('expand')}
-
{renderNavigation?.('expand')}
-
- ), -})) - -vi.mock('@/app/components/app-sidebar/nav-link', () => ({ - default: ({ name }: { name: string }) =>
{name}
, -})) - -vi.mock('@/app/components/app-sidebar/snippet-info', () => ({ - default: () =>
, -})) - -vi.mock('@/app/components/evaluation', () => ({ - default: () =>
, -})) - vi.mock('@/app/components/workflow', () => ({ WorkflowWithInnerContext: ({ children, @@ -237,12 +198,11 @@ const payload: SnippetDetailPayload = { }, } -const renderSnippetMain = (section: SnippetSection = 'orchestrate') => { +const renderSnippetMain = () => { return render( { const { t } = useTranslation('snippet') const publishSnippetMutation = usePublishSnippetWorkflowMutation(snippetId) @@ -41,7 +38,7 @@ export const useSnippetPublish = ({ }, [publishSnippetMutation, setPublishMenuOpen, snippetId, t]) useKeyPress(`${getKeyboardKeyCodeBySystem('ctrl')}.shift.p`, (event) => { - if (section !== 'orchestrate' || publishSnippetMutation.isPending) + if (publishSnippetMutation.isPending) return event.preventDefault() diff --git a/web/app/components/snippets/components/snippet-layout.tsx b/web/app/components/snippets/components/snippet-layout.tsx new file mode 100644 index 00000000000..37900f39693 --- /dev/null +++ b/web/app/components/snippets/components/snippet-layout.tsx @@ -0,0 +1,88 @@ +'use client' + +import type { ReactNode } from 'react' +import type { NavIcon } from '@/app/components/app-sidebar/nav-link' +import type { SnippetDetail, SnippetSection } from '@/models/snippet' +import { + RiFlaskFill, + RiFlaskLine, + RiTerminalWindowFill, + RiTerminalWindowLine, +} from '@remixicon/react' +import { useEffect } from 'react' +import { useTranslation } from 'react-i18next' +import AppSideBar from '@/app/components/app-sidebar' +import NavLink from '@/app/components/app-sidebar/nav-link' +import SnippetInfo from '@/app/components/app-sidebar/snippet-info' +import { useStore as useAppStore } from '@/app/components/app/store' +import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints' + +type SnippetLayoutProps = { + children: ReactNode + section: SnippetSection + snippet: SnippetDetail + snippetId: string +} + +const ORCHESTRATE_ICONS: { normal: NavIcon, selected: NavIcon } = { + normal: RiTerminalWindowLine, + selected: RiTerminalWindowFill, +} + +const EVALUATION_ICONS: { normal: NavIcon, selected: NavIcon } = { + normal: RiFlaskLine, + selected: RiFlaskFill, +} + +const SnippetLayout = ({ + children, + section, + snippet, + snippetId, +}: SnippetLayoutProps) => { + const { t } = useTranslation('snippet') + const media = useBreakpoints() + const isMobile = media === MediaType.mobile + const setAppSidebarExpand = useAppStore(state => state.setAppSidebarExpand) + + useEffect(() => { + const localeMode = localStorage.getItem('app-detail-collapse-or-expand') || 'expand' + const mode = isMobile ? 'collapse' : 'expand' + setAppSidebarExpand(isMobile ? mode : localeMode) + }, [isMobile, setAppSidebarExpand]) + + return ( +
+ } + renderNavigation={mode => ( + <> + + + + )} + /> + +
+
+ {children} +
+
+
+ ) +} + +export default SnippetLayout diff --git a/web/app/components/snippets/components/snippet-main.tsx b/web/app/components/snippets/components/snippet-main.tsx index 46740c344aa..f65ec8c6f88 100644 --- a/web/app/components/snippets/components/snippet-main.tsx +++ b/web/app/components/snippets/components/snippet-main.tsx @@ -1,29 +1,15 @@ 'use client' -import type { NavIcon } from '@/app/components/app-sidebar/nav-link' import type { WorkflowProps } from '@/app/components/workflow' -import type { SnippetDetailPayload, SnippetSection } from '@/models/snippet' -import { - RiFlaskFill, - RiFlaskLine, - RiTerminalWindowFill, - RiTerminalWindowLine, -} from '@remixicon/react' +import type { SnippetDetailPayload } from '@/models/snippet' import { useEffect, useMemo, } from 'react' -import { useTranslation } from 'react-i18next' -import AppSideBar from '@/app/components/app-sidebar' -import NavLink from '@/app/components/app-sidebar/nav-link' -import SnippetInfo from '@/app/components/app-sidebar/snippet-info' -import { useStore as useAppStore } from '@/app/components/app/store' -import Evaluation from '@/app/components/evaluation' import { WorkflowWithInnerContext } from '@/app/components/workflow' import { useAvailableNodesMetaData } from '@/app/components/workflow-app/hooks' import { useSetWorkflowVarsWithValue } from '@/app/components/workflow/hooks/use-fetch-workflow-inspect-vars' import { BlockEnum } from '@/app/components/workflow/types' -import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints' import { useConfigsMap } from '../hooks/use-configs-map' import { useInspectVarsCrud } from '../hooks/use-inspect-vars-crud' import { useNodesSyncDraft } from '../hooks/use-nodes-sync-draft' @@ -38,31 +24,16 @@ import SnippetChildren from './snippet-children' type SnippetMainProps = { payload: SnippetDetailPayload snippetId: string - section: SnippetSection } & Pick -const ORCHESTRATE_ICONS: { normal: NavIcon, selected: NavIcon } = { - normal: RiTerminalWindowLine, - selected: RiTerminalWindowFill, -} - -const EVALUATION_ICONS: { normal: NavIcon, selected: NavIcon } = { - normal: RiFlaskLine, - selected: RiFlaskFill, -} - const SnippetMain = ({ payload, snippetId, - section, nodes, edges, viewport, }: SnippetMainProps) => { - const { t } = useTranslation('snippet') - const { graph, snippet, uiMeta } = payload - const media = useBreakpoints() - const isMobile = media === MediaType.mobile + const { graph, uiMeta } = payload const { doSyncWorkflowDraft, syncWorkflowDraftWhenPageClose, @@ -114,7 +85,6 @@ const SnippetMain = ({ nodesMap, } }, [workflowAvailableNodesMetaData]) - const setAppSidebarExpand = useAppStore(state => state.setAppSidebarExpand) const reset = useSnippetDetailStore(state => state.reset) const { editingField, @@ -139,7 +109,6 @@ const SnippetMain = ({ setPublishMenuOpen, } = useSnippetPublish({ snippetId, - section, }) const { handleStartWorkflowRun, @@ -153,12 +122,6 @@ const SnippetMain = ({ reset() }, [reset, snippetId]) - useEffect(() => { - const localeMode = localStorage.getItem('app-detail-collapse-or-expand') || 'expand' - const mode = isMobile ? 'collapse' : 'expand' - setAppSidebarExpand(isMobile ? mode : localeMode) - }, [isMobile, setAppSidebarExpand]) - const hooksStore = useMemo(() => { return { doSyncWorkflowDraft, @@ -220,67 +183,32 @@ const SnippetMain = ({ ]) return ( -
- } - renderNavigation={mode => ( - <> - - - - )} + + - -
-
- {section === 'evaluation' - ? ( - - ) - : ( - - - - )} -
-
-
+ ) } diff --git a/web/app/components/snippets/index.tsx b/web/app/components/snippets/index.tsx index 706fdf7a27a..6d3bcb570e5 100644 --- a/web/app/components/snippets/index.tsx +++ b/web/app/components/snippets/index.tsx @@ -1,6 +1,5 @@ 'use client' -import type { SnippetSection } from '@/models/snippet' import { useMemo } from 'react' import Loading from '@/app/components/base/loading' import WorkflowWithDefaultContext from '@/app/components/workflow' @@ -9,18 +8,23 @@ import { initialEdges, initialNodes, } from '@/app/components/workflow/utils' +import SnippetLayout from './components/snippet-layout' import SnippetMain from './components/snippet-main' import { useSnippetInit } from './hooks/use-snippet-init' type SnippetPageProps = { snippetId: string - section?: SnippetSection } -const SnippetPage = ({ - snippetId, - section = 'orchestrate', -}: SnippetPageProps) => { +const SnippetPageLoading = () => { + return ( +
+ +
+ ) +} + +const SnippetPage = ({ snippetId }: SnippetPageProps) => { const { data, isLoading } = useSnippetInit(snippetId) const nodesData = useMemo(() => { if (!data) @@ -36,35 +40,36 @@ const SnippetPage = ({ }, [data]) if (!data || isLoading) { - return ( -
- -
- ) + return } return ( - - - + nodes={nodesData} + > + + + ) } -const SnippetPageWrapper = (props: SnippetPageProps) => { +const SnippetPageWrapper = ({ snippetId }: SnippetPageProps) => { return ( - + ) } diff --git a/web/app/components/snippets/snippet-evaluation-page.tsx b/web/app/components/snippets/snippet-evaluation-page.tsx new file mode 100644 index 00000000000..e52c857b3d5 --- /dev/null +++ b/web/app/components/snippets/snippet-evaluation-page.tsx @@ -0,0 +1,46 @@ +'use client' + +import { useMemo } from 'react' +import Loading from '@/app/components/base/loading' +import Evaluation from '@/app/components/evaluation' +import { buildSnippetDetailPayload, useSnippetApiDetail } from '@/service/use-snippets' +import { getSnippetDetailMock } from '@/service/use-snippets.mock' +import SnippetLayout from './components/snippet-layout' + +type SnippetEvaluationPageProps = { + snippetId: string +} + +const SnippetEvaluationPage = ({ snippetId }: SnippetEvaluationPageProps) => { + const snippetApiDetail = useSnippetApiDetail(snippetId) + const mockSnippet = useMemo(() => getSnippetDetailMock(snippetId)?.snippet, [snippetId]) + const snippet = useMemo(() => { + if (snippetApiDetail.data) + return buildSnippetDetailPayload(snippetApiDetail.data).snippet + + if (!snippetApiDetail.isLoading) + return mockSnippet + + return undefined + }, [mockSnippet, snippetApiDetail.data, snippetApiDetail.isLoading]) + + if (!snippet || snippetApiDetail.isLoading) { + return ( +
+ +
+ ) + } + + return ( + + + + ) +} + +export default SnippetEvaluationPage