From b6fbec066dbe89789f2e76b69fa2b6fb9e0bc738 Mon Sep 17 00:00:00 2001 From: JzoNg Date: Mon, 20 Apr 2026 15:47:34 +0800 Subject: [PATCH] fix(web): nav icons --- .../assets/vender/line/others/dhs.svg | 5 +++ .../assets/vender/line/others/dvs.svg | 5 +++ .../assets/vender/line/others/evaluation.svg | 3 ++ .../custom-public/icons.json | 2 +- .../custom-vender/icons.json | 17 ++++++++- .../custom-vender/info.json | 2 +- .../(appDetailLayout)/[appId]/layout-main.tsx | 24 +++++++----- .../[datasetId]/layout-main.tsx | 12 +++--- .../components/app/app-publisher/sections.tsx | 2 +- .../__tests__/snippet-layout.spec.tsx | 38 +++++++++++++++---- .../__tests__/use-snippet-publish.spec.ts | 10 +++++ .../components/hooks/use-snippet-publish.ts | 14 ++++++- .../snippets/components/snippet-layout.tsx | 38 +++++++++++++------ .../workflow/selection-contextmenu.tsx | 4 +- web/models/snippet.ts | 2 + web/service/use-snippets.ts | 3 +- 16 files changed, 140 insertions(+), 41 deletions(-) create mode 100644 packages/iconify-collections/assets/vender/line/others/dhs.svg create mode 100644 packages/iconify-collections/assets/vender/line/others/dvs.svg create mode 100644 packages/iconify-collections/assets/vender/line/others/evaluation.svg diff --git a/packages/iconify-collections/assets/vender/line/others/dhs.svg b/packages/iconify-collections/assets/vender/line/others/dhs.svg new file mode 100644 index 0000000000..54e8eff8c2 --- /dev/null +++ b/packages/iconify-collections/assets/vender/line/others/dhs.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/packages/iconify-collections/assets/vender/line/others/dvs.svg b/packages/iconify-collections/assets/vender/line/others/dvs.svg new file mode 100644 index 0000000000..3b1c9f2f4c --- /dev/null +++ b/packages/iconify-collections/assets/vender/line/others/dvs.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/packages/iconify-collections/assets/vender/line/others/evaluation.svg b/packages/iconify-collections/assets/vender/line/others/evaluation.svg new file mode 100644 index 0000000000..3856b8b176 --- /dev/null +++ b/packages/iconify-collections/assets/vender/line/others/evaluation.svg @@ -0,0 +1,3 @@ + + + diff --git a/packages/iconify-collections/custom-public/icons.json b/packages/iconify-collections/custom-public/icons.json index 7c7d110be8..7e258d97fc 100644 --- a/packages/iconify-collections/custom-public/icons.json +++ b/packages/iconify-collections/custom-public/icons.json @@ -1,6 +1,6 @@ { "prefix": "custom-public", - "lastModified": 1776313052, + "lastModified": 1776670225, "icons": { "avatar-user": { "body": "", diff --git a/packages/iconify-collections/custom-vender/icons.json b/packages/iconify-collections/custom-vender/icons.json index d588db650e..e4d2bd3272 100644 --- a/packages/iconify-collections/custom-vender/icons.json +++ b/packages/iconify-collections/custom-vender/icons.json @@ -1,6 +1,6 @@ { "prefix": "custom-vender", - "lastModified": 1776313052, + "lastModified": 1776670225, "icons": { "features-citations": { "body": "" @@ -513,12 +513,27 @@ "width": 14, "height": 14 }, + "line-others-dhs": { + "body": "", + "width": 18, + "height": 18 + }, "line-others-drag-handle": { "body": "" }, + "line-others-dvs": { + "body": "", + "width": 18, + "height": 18 + }, "line-others-env": { "body": "" }, + "line-others-evaluation": { + "body": "", + "width": 18, + "height": 18 + }, "line-others-global-variable": { "body": "" }, diff --git a/packages/iconify-collections/custom-vender/info.json b/packages/iconify-collections/custom-vender/info.json index ea5f666503..52df22b171 100644 --- a/packages/iconify-collections/custom-vender/info.json +++ b/packages/iconify-collections/custom-vender/info.json @@ -1,7 +1,7 @@ { "prefix": "custom-vender", "name": "Dify Custom Vender", - "total": 278, + "total": 281, "version": "0.0.0-private", "author": { "name": "LangGenius, Inc.", diff --git a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/layout-main.tsx b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/layout-main.tsx index 7f8d6d535e..5c3a237aca 100644 --- a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/layout-main.tsx +++ b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/layout-main.tsx @@ -8,8 +8,6 @@ import { RiDashboard2Line, RiFileList3Fill, RiFileList3Line, - RiFlaskFill, - RiFlaskLine, RiTerminalBoxFill, RiTerminalBoxLine, RiTerminalWindowFill, @@ -43,6 +41,10 @@ type IAppDetailLayoutProps = { appId: string } +const EvaluationIcon = ({ className }: { className?: string }) => { + return +} + const AppDetailLayout: FC = (props) => { const { children, @@ -80,14 +82,6 @@ const AppDetailLayout: FC = (props) => { icon: RiTerminalWindowLine, selectedIcon: RiTerminalWindowFill, }) - if (canAccessSnippetsAndEvaluation) { - navConfig.push({ - name: t('appMenus.evaluation', { ns: 'common' }), - href: `/app/${appId}/evaluation`, - icon: RiFlaskLine, - selectedIcon: RiFlaskFill, - }) - } } navConfig.push({ @@ -114,6 +108,16 @@ const AppDetailLayout: FC = (props) => { icon: RiDashboard2Line, selectedIcon: RiDashboard2Fill, }) + + if (isCurrentWorkspaceEditor && canAccessSnippetsAndEvaluation) { + navConfig.push({ + name: t('appMenus.evaluation', { ns: 'common' }), + href: `/app/${appId}/evaluation`, + icon: EvaluationIcon, + selectedIcon: EvaluationIcon, + }) + } + return navConfig }, [canAccessSnippetsAndEvaluation, t]) diff --git a/web/app/(commonLayout)/datasets/(datasetDetailLayout)/[datasetId]/layout-main.tsx b/web/app/(commonLayout)/datasets/(datasetDetailLayout)/[datasetId]/layout-main.tsx index c5719d6a61..179557b40e 100644 --- a/web/app/(commonLayout)/datasets/(datasetDetailLayout)/[datasetId]/layout-main.tsx +++ b/web/app/(commonLayout)/datasets/(datasetDetailLayout)/[datasetId]/layout-main.tsx @@ -7,8 +7,6 @@ import { RiEqualizer2Line, RiFileTextFill, RiFileTextLine, - RiFlaskFill, - RiFlaskLine, RiFocus2Fill, RiFocus2Line, } from '@remixicon/react' @@ -34,6 +32,10 @@ type IAppDetailLayoutProps = { datasetId: string } +const EvaluationIcon = ({ className }: { className?: string }) => { + return +} + const DatasetDetailLayout: FC = (props) => { const { children, @@ -106,16 +108,16 @@ const DatasetDetailLayout: FC = (props) => { selectedIcon: PipelineFill as RemixiconComponentType, disabled: false, }, + ...baseNavigation, ...(isRagPipelineDataset && canAccessSnippetsAndEvaluation ? [{ name: t('datasetMenus.evaluation', { ns: 'common' }), href: `/datasets/${datasetId}/evaluation`, - icon: RiFlaskLine, - selectedIcon: RiFlaskFill, + icon: EvaluationIcon, + selectedIcon: EvaluationIcon, disabled: isButtonDisabledWithPipeline, }] : []), - ...baseNavigation, ] } diff --git a/web/app/components/app/app-publisher/sections.tsx b/web/app/components/app/app-publisher/sections.tsx index 51b1ac2f7b..3bcad6a533 100644 --- a/web/app/components/app/app-publisher/sections.tsx +++ b/web/app/components/app/app-publisher/sections.tsx @@ -205,7 +205,7 @@ export const PublisherSummarySection = ({ + ) + : ( + + {name} + + ) ), })) @@ -63,6 +71,7 @@ const createSnippet = (overrides: Partial = {}): SnippetDetail => usage: '42', icon: 'emoji', iconBackground: '#ffffff', + is_published: true, ...overrides, }) @@ -104,5 +113,20 @@ describe('SnippetLayout', () => { expect(screen.getByRole('link', { name: 'snippet.sectionEvaluation' })).toHaveAttribute('href', '/snippets/snippet-1/evaluation') expect(screen.getByRole('link', { name: 'snippet.sectionEvaluation' })).toHaveAttribute('aria-current', 'page') }) + + it('should disable the evaluation menu when the snippet is unpublished', () => { + render( + +
content
+
, + ) + + expect(screen.getByRole('button', { name: 'snippet.sectionEvaluation' })).toBeDisabled() + expect(screen.queryByRole('link', { name: 'snippet.sectionEvaluation' })).not.toBeInTheDocument() + }) }) }) diff --git a/web/app/components/snippets/components/hooks/__tests__/use-snippet-publish.spec.ts b/web/app/components/snippets/components/hooks/__tests__/use-snippet-publish.spec.ts index b3a9139053..9f13053d2d 100644 --- a/web/app/components/snippets/components/hooks/__tests__/use-snippet-publish.spec.ts +++ b/web/app/components/snippets/components/hooks/__tests__/use-snippet-publish.spec.ts @@ -6,6 +6,7 @@ const mockMutateAsync = vi.fn() const mockSetPublishMenuOpen = vi.fn() const mockUseKeyPress = vi.fn() const mockSetPublishedAt = vi.fn() +const mockSetQueryData = vi.fn() let isPublishMenuOpen = false let isPending = false @@ -22,6 +23,12 @@ vi.mock('@langgenius/dify-ui/toast', () => ({ }, })) +vi.mock('@tanstack/react-query', () => ({ + useQueryClient: () => ({ + setQueryData: mockSetQueryData, + }), +})) + vi.mock('@/service/use-snippet-workflows', () => ({ usePublishSnippetWorkflowMutation: () => ({ mutateAsync: mockMutateAsync, @@ -72,6 +79,9 @@ describe('useSnippetPublish', () => { expect(mockMutateAsync).toHaveBeenCalledWith({ params: { snippetId: 'snippet-1' }, }) + expect(mockSetQueryData).toHaveBeenCalledTimes(1) + const updateSnippetDetail = mockSetQueryData.mock.calls[0][1] as (old: { is_published: boolean }) => { is_published: boolean } + expect(updateSnippetDetail({ is_published: false })).toEqual({ is_published: true }) expect(mockSetPublishedAt).toHaveBeenCalledWith(1_712_345_678) expect(mockSetPublishMenuOpen).toHaveBeenCalledWith(false) expect(toast.success).toHaveBeenCalledWith('snippet.publishSuccess') diff --git a/web/app/components/snippets/components/hooks/use-snippet-publish.ts b/web/app/components/snippets/components/hooks/use-snippet-publish.ts index 8ceb4ea335..bb803ccbac 100644 --- a/web/app/components/snippets/components/hooks/use-snippet-publish.ts +++ b/web/app/components/snippets/components/hooks/use-snippet-publish.ts @@ -1,10 +1,13 @@ +import type { Snippet as SnippetContract } from '@/types/snippet' import { toast } from '@langgenius/dify-ui/toast' +import { useQueryClient } from '@tanstack/react-query' import { useKeyPress } from 'ahooks' import { useCallback } from 'react' import { useTranslation } from 'react-i18next' import { useShallow } from 'zustand/react/shallow' import { useWorkflowStore } from '@/app/components/workflow/store' import { getKeyboardKeyCodeBySystem } from '@/app/components/workflow/utils' +import { consoleQuery } from '@/service/client' import { usePublishSnippetWorkflowMutation } from '@/service/use-snippet-workflows' import { useSnippetDetailStore } from '../../store' @@ -17,6 +20,7 @@ export const useSnippetPublish = ({ }: UseSnippetPublishOptions) => { const { t } = useTranslation('snippet') const workflowStore = useWorkflowStore() + const queryClient = useQueryClient() const publishSnippetMutation = usePublishSnippetWorkflowMutation(snippetId) const { isPublishMenuOpen, @@ -31,6 +35,14 @@ export const useSnippetPublish = ({ const publishedWorkflow = await publishSnippetMutation.mutateAsync({ params: { snippetId }, }) + queryClient.setQueryData( + consoleQuery.snippets.detail.queryKey({ + input: { + params: { snippetId }, + }, + }), + old => old ? { ...old, is_published: true } : old, + ) workflowStore.getState().setPublishedAt(publishedWorkflow.created_at) setPublishMenuOpen(false) toast.success(t('publishSuccess')) @@ -38,7 +50,7 @@ export const useSnippetPublish = ({ catch (error) { toast.error(error instanceof Error ? error.message : t('publishFailed')) } - }, [publishSnippetMutation, setPublishMenuOpen, snippetId, t, workflowStore]) + }, [publishSnippetMutation, queryClient, setPublishMenuOpen, snippetId, t, workflowStore]) useKeyPress(`${getKeyboardKeyCodeBySystem('ctrl')}.shift.p`, (event) => { if (publishSnippetMutation.isPending) diff --git a/web/app/components/snippets/components/snippet-layout.tsx b/web/app/components/snippets/components/snippet-layout.tsx index 2d03f3a065..b9cfaccfb7 100644 --- a/web/app/components/snippets/components/snippet-layout.tsx +++ b/web/app/components/snippets/components/snippet-layout.tsx @@ -1,14 +1,9 @@ 'use client' -import type { ReactNode } from 'react' +import type { FC, 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 { cn } from '@langgenius/dify-ui/cn' import { useEffect } from 'react' import { useTranslation } from 'react-i18next' import AppSideBar from '@/app/components/app-sidebar' @@ -25,14 +20,34 @@ type SnippetLayoutProps = { snippetId: string } +const SidebarCssIcon: FC<{ iconClassName: string, className?: string }> = ({ iconClassName, className }) => { + return +} + +const OrchestrateIcon = ({ className }: { className?: string }) => { + return +} + +const OrchestrateSelectedIcon = ({ className }: { className?: string }) => { + return +} + +const EvaluationIcon = ({ className }: { className?: string }) => { + return +} + +const EvaluationSelectedIcon = ({ className }: { className?: string }) => { + return +} + const ORCHESTRATE_ICONS: { normal: NavIcon, selected: NavIcon } = { - normal: RiTerminalWindowLine, - selected: RiTerminalWindowFill, + normal: OrchestrateIcon, + selected: OrchestrateSelectedIcon, } const EVALUATION_ICONS: { normal: NavIcon, selected: NavIcon } = { - normal: RiFlaskLine, - selected: RiFlaskFill, + normal: EvaluationIcon, + selected: EvaluationSelectedIcon, } const SnippetLayout = ({ @@ -74,6 +89,7 @@ const SnippetLayout = ({ iconMap={EVALUATION_ICONS} href={`/snippets/${snippetId}/evaluation`} active={section === 'evaluation'} + disabled={!snippet.is_published} /> )} diff --git a/web/app/components/workflow/selection-contextmenu.tsx b/web/app/components/workflow/selection-contextmenu.tsx index 9a5034e9a5..17e3904b8e 100644 --- a/web/app/components/workflow/selection-contextmenu.tsx +++ b/web/app/components/workflow/selection-contextmenu.tsx @@ -75,8 +75,8 @@ const alignMenuItems: AlignMenuItem[] = [ { alignType: AlignType.Top, icon: 'i-ri-align-item-top-line', translationKey: 'operator.alignTop' }, { alignType: AlignType.Middle, icon: 'i-ri-align-item-vertical-center-line', iconClassName: 'rotate-90', translationKey: 'operator.alignMiddle' }, { alignType: AlignType.Bottom, icon: 'i-ri-align-item-bottom-line', translationKey: 'operator.alignBottom' }, - { alignType: AlignType.DistributeHorizontal, icon: 'i-ri-align-justify-line', translationKey: 'operator.distributeHorizontal' }, - { alignType: AlignType.DistributeVertical, icon: 'i-ri-align-justify-line', iconClassName: 'rotate-90', translationKey: 'operator.distributeVertical' }, + { alignType: AlignType.DistributeHorizontal, icon: 'i-custom-vender-line-others-dhs', translationKey: 'operator.distributeHorizontal' }, + { alignType: AlignType.DistributeVertical, icon: 'i-custom-vender-line-others-dvs', iconClassName: 'rotate-90', translationKey: 'operator.distributeVertical' }, ] const getAlignableNodes = (nodes: Node[], selectedNodes: Node[]) => { diff --git a/web/models/snippet.ts b/web/models/snippet.ts index 5471782ab0..efb41f45da 100644 --- a/web/models/snippet.ts +++ b/web/models/snippet.ts @@ -13,6 +13,7 @@ export type SnippetListItem = { usage: string icon: string iconBackground: string + is_published?: boolean status?: string } @@ -25,6 +26,7 @@ export type SnippetDetail = { usage: string icon: string iconBackground: string + is_published?: boolean status?: string } diff --git a/web/service/use-snippets.ts b/web/service/use-snippets.ts index dffc3d6631..0600d9ae11 100644 --- a/web/service/use-snippets.ts +++ b/web/service/use-snippets.ts @@ -29,7 +29,7 @@ type SnippetListParams = { is_published?: boolean } -type SnippetSummary = Pick +type SnippetSummary = Pick const DEFAULT_SNIPPET_LIST_PARAMS = { page: 1, @@ -75,6 +75,7 @@ const toSnippetListItem = (snippet: SnippetSummary): SnippetListItemUIModel => { usage: String(snippet.use_count ?? 0), icon: getSnippetIcon(snippet.icon_info), iconBackground: getSnippetIconBackground(snippet.icon_info), + is_published: snippet.is_published, status: undefined, } }