From c20090217352ae51aebabbb50f4bf9da47676b2c Mon Sep 17 00:00:00 2001 From: twwu Date: Mon, 31 Aug 2026 14:33:00 +0800 Subject: [PATCH] refactor(web): move app builder button to canvas header --- .../__tests__/dify-builder-button.spec.tsx | 36 ++++++++ .../header/__tests__/header-layouts.spec.tsx | 19 ++++ .../workflow/header/dify-builder-button.tsx | 13 +-- .../workflow/header/header-in-normal.tsx | 9 +- web/i18n/en-US/workflow.json | 88 +++++++++---------- 5 files changed, 109 insertions(+), 56 deletions(-) create mode 100644 web/app/components/workflow/header/__tests__/dify-builder-button.spec.tsx diff --git a/web/app/components/workflow/header/__tests__/dify-builder-button.spec.tsx b/web/app/components/workflow/header/__tests__/dify-builder-button.spec.tsx new file mode 100644 index 00000000000..288af9225e1 --- /dev/null +++ b/web/app/components/workflow/header/__tests__/dify-builder-button.spec.tsx @@ -0,0 +1,36 @@ +import { screen } from '@testing-library/react' +import userEvent from '@testing-library/user-event' +import { renderWorkflowComponent } from '../../__tests__/workflow-test-env' +import DifyBuilderButton from '../dify-builder-button' + +describe('DifyBuilderButton', () => { + it('should expose a visible label and toggle the builder panel', async () => { + const user = userEvent.setup() + const { store } = renderWorkflowComponent() + const button = screen.getByRole('button', { + name: 'workflow.difyBuilder.buttonTooltip', + }) + + expect(button).toHaveTextContent('workflow.difyBuilder.buttonTooltip') + expect(button).toHaveAttribute('aria-pressed', 'false') + + await user.click(button) + + expect(store.getState().showDifyBuilderPanel).toBe(true) + expect(button).toHaveAttribute('aria-pressed', 'true') + }) + + it('should remain closed when the button is disabled', async () => { + const user = userEvent.setup() + const { store } = renderWorkflowComponent() + const button = screen.getByRole('button', { + name: 'workflow.difyBuilder.buttonTooltip', + }) + + expect(button).toBeDisabled() + + await user.click(button) + + expect(store.getState().showDifyBuilderPanel).toBe(false) + }) +}) diff --git a/web/app/components/workflow/header/__tests__/header-layouts.spec.tsx b/web/app/components/workflow/header/__tests__/header-layouts.spec.tsx index 630f665df81..1623808ea5f 100644 --- a/web/app/components/workflow/header/__tests__/header-layouts.spec.tsx +++ b/web/app/components/workflow/header/__tests__/header-layouts.spec.tsx @@ -259,6 +259,25 @@ describe('Header layout components', () => { expect(screen.queryByTestId('env-button')).not.toBeInTheDocument() expect(screen.queryByTestId('global-variable-button')).not.toBeInTheDocument() }) + + it('should place App Builder at the end of the canvas actions', () => { + renderWorkflowComponent( + middle-action, + }} + controls={{ + showDifyBuilderButton: true, + }} + />, + ) + + const appBuilderButton = screen.getByRole('button', { + name: 'workflow.difyBuilder.buttonTooltip', + }) + + expect(screen.getAllByRole('button').at(-1)).toBe(appBuilderButton) + }) }) describe('HeaderInRestoring', () => { diff --git a/web/app/components/workflow/header/dify-builder-button.tsx b/web/app/components/workflow/header/dify-builder-button.tsx index 7852f46b06d..954f0bb8999 100644 --- a/web/app/components/workflow/header/dify-builder-button.tsx +++ b/web/app/components/workflow/header/dify-builder-button.tsx @@ -1,14 +1,11 @@ import { Button } from '@langgenius/dify-ui/button' -import { cn } from '@langgenius/dify-ui/cn' import { Tooltip, TooltipContent, TooltipTrigger } from '@langgenius/dify-ui/tooltip' import { memo } from 'react' import { useTranslation } from 'react-i18next' import { useStore } from '@/app/components/workflow/store' -import useTheme from '@/hooks/use-theme' const DifyBuilderButton = ({ disabled }: { disabled: boolean }) => { const { t } = useTranslation() - const { theme } = useTheme() const showDifyBuilderPanel = useStore((s) => s.showDifyBuilderPanel) const setShowDifyBuilderPanel = useStore((s) => s.setShowDifyBuilderPanel) const label = t(($) => $['difyBuilder.buttonTooltip'], { ns: 'workflow' }) @@ -24,20 +21,16 @@ const DifyBuilderButton = ({ disabled }: { disabled: boolean }) => { } /> diff --git a/web/app/components/workflow/header/header-in-normal.tsx b/web/app/components/workflow/header/header-in-normal.tsx index 203b7256d20..71ae90e1ade 100644 --- a/web/app/components/workflow/header/header-in-normal.tsx +++ b/web/app/components/workflow/header/header-in-normal.tsx @@ -9,6 +9,7 @@ import { useNodesInteractions } from '../hooks/use-nodes-interactions' import { useNodesReadOnly } from '../hooks/use-workflow' import { useWorkflowRun } from '../hooks/use-workflow-run' import { useStore, useWorkflowStore } from '../store' +import DifyBuilderButton from './dify-builder-button' import EditingTitle from './editing-title' import EnvButton from './env-button' import GlobalVariableButton from './global-variable-button' @@ -16,7 +17,6 @@ import OnlineUsers from './online-users' import RunAndHistory from './run-and-history' import ScrollToSelectedNodeButton from './scroll-to-selected-node-button' import { VersionHistoryButton } from './version-history-button' -import DifyBuilderButton from './dify-builder-button' export type HeaderInNormalProps = { components?: { @@ -93,7 +93,6 @@ const HeaderInNormal = ({ components, controls, runAndHistoryProps }: HeaderInNo {components?.left} - {showDifyBuilderButton && } {showContextButtons && (
{components?.chatVariableTrigger} @@ -103,6 +102,12 @@ const HeaderInNormal = ({ components, controls, runAndHistoryProps }: HeaderInNo )} {components?.middle} {canReleaseAndVersion && } + {showDifyBuilderButton && ( + <> + + + + )}
) diff --git a/web/i18n/en-US/workflow.json b/web/i18n/en-US/workflow.json index 451ed72a3cf..30a6b19605b 100644 --- a/web/i18n/en-US/workflow.json +++ b/web/i18n/en-US/workflow.json @@ -297,50 +297,6 @@ "common.workflowProcessRunning": "Workflow Process running", "common.workflowProcessSucceeded": "Workflow Process succeeded", "common.workflowSaveStatus": "Workflow save status", - "difyBuilder.actionApproveRepair": "Approve repair", - "difyBuilder.actionProvideTestdata": "Provide mock test data", - "difyBuilder.actionPublish": "Publish", - "difyBuilder.actionReFix": "Re-fix", - "difyBuilder.actionRunVerify": "Run verify", - "difyBuilder.actionUndo": "Undo", - "difyBuilder.actionsTitle": "Actions", - "difyBuilder.appLabel": "App", - "difyBuilder.buttonTooltip": "Dify Builder", - "difyBuilder.checklistFixAction_one": "Fix {{count}} checklist issue", - "difyBuilder.checklistFixAction_other": "Fix {{count}} checklist issues", - "difyBuilder.checklistFixSummary_one": "{{count}} configuration issue can be auto-fixed", - "difyBuilder.checklistFixSummary_other": "{{count}} configuration issues can be auto-fixed", - "difyBuilder.checklistFixTitle": "Checklist issues", - "difyBuilder.checklistRecheckAction": "Recheck checklist", - "difyBuilder.checklistRecheckDescription": "The proposed fix was applied to the draft. Recheck the checklist to confirm it resolved the issue.", - "difyBuilder.checklistRecheckTitle": "Checklist recheck", - "difyBuilder.checklistUnfixableNote_one": "{{count}} issue can't be auto-fixed yet — resolve it manually before publishing", - "difyBuilder.checklistUnfixableNote_other": "{{count}} issues can't be auto-fixed yet — resolve them manually before publishing", - "difyBuilder.contextMissing": "Open this panel from an app's workflow editor to use Dify Builder.", - "difyBuilder.errorPrefix": "Error", - "difyBuilder.messagePlaceholder": "Send a message to the difyBuilder", - "difyBuilder.messageSend": "Send", - "difyBuilder.panelDescription": "Diagnose and repair a failed workflow run with AI-assisted fixes.", - "difyBuilder.panelTitle": "Dify Builder", - "difyBuilder.progressEmpty": "Progress will appear here once a fix starts.", - "difyBuilder.progressTitle": "Progress", - "difyBuilder.runToFixAutoEmpty": "No failed run found yet. Enter a run ID manually.", - "difyBuilder.runToFixAutoLabel": "Latest failed run", - "difyBuilder.runToFixAutoLoading": "Looking for the latest failed run…", - "difyBuilder.runToFixManualLabel": "Or enter a run ID", - "difyBuilder.runToFixManualPlaceholder": "Workflow run ID", - "difyBuilder.runToFixTitle": "Run to fix", - "difyBuilder.sessionCanvasReadOnlyLabel": "Canvas read-only", - "difyBuilder.sessionEmpty": "No active fix session yet.", - "difyBuilder.sessionIdLabel": "Session", - "difyBuilder.sessionInterruptedLabel": "Interrupted", - "difyBuilder.sessionRunStatusLabel": "Run status", - "difyBuilder.sessionStateLabel": "State", - "difyBuilder.sessionVersionLabel": "Version", - "difyBuilder.startFix": "Start Fix", - "difyBuilder.starting": "Starting…", - "difyBuilder.useThisRun": "Fix this run", - "difyBuilder.workspaceLabel": "Workspace", "customWebhook": "Custom Webhook", "debug.copyLastRun": "Copy Last Run", "debug.copyLastRunError": "Failed to copy last run inputs", @@ -391,6 +347,50 @@ "debug.variableInspect.trigger.running": "Caching running status", "debug.variableInspect.trigger.stop": "Stop run", "debug.variableInspect.view": "View log", + "difyBuilder.actionApproveRepair": "Approve repair", + "difyBuilder.actionProvideTestdata": "Provide mock test data", + "difyBuilder.actionPublish": "Publish", + "difyBuilder.actionReFix": "Re-fix", + "difyBuilder.actionRunVerify": "Run verify", + "difyBuilder.actionUndo": "Undo", + "difyBuilder.actionsTitle": "Actions", + "difyBuilder.appLabel": "App", + "difyBuilder.buttonTooltip": "App Builder", + "difyBuilder.checklistFixAction_one": "Fix {{count}} checklist issue", + "difyBuilder.checklistFixAction_other": "Fix {{count}} checklist issues", + "difyBuilder.checklistFixSummary_one": "{{count}} configuration issue can be auto-fixed", + "difyBuilder.checklistFixSummary_other": "{{count}} configuration issues can be auto-fixed", + "difyBuilder.checklistFixTitle": "Checklist issues", + "difyBuilder.checklistRecheckAction": "Recheck checklist", + "difyBuilder.checklistRecheckDescription": "The proposed fix was applied to the draft. Recheck the checklist to confirm it resolved the issue.", + "difyBuilder.checklistRecheckTitle": "Checklist recheck", + "difyBuilder.checklistUnfixableNote_one": "{{count}} issue can't be auto-fixed yet — resolve it manually before publishing", + "difyBuilder.checklistUnfixableNote_other": "{{count}} issues can't be auto-fixed yet — resolve them manually before publishing", + "difyBuilder.contextMissing": "Open this panel from an app's workflow editor to use Dify Builder.", + "difyBuilder.errorPrefix": "Error", + "difyBuilder.messagePlaceholder": "Send a message to the difyBuilder", + "difyBuilder.messageSend": "Send", + "difyBuilder.panelDescription": "Diagnose and repair a failed workflow run with AI-assisted fixes.", + "difyBuilder.panelTitle": "Dify Builder", + "difyBuilder.progressEmpty": "Progress will appear here once a fix starts.", + "difyBuilder.progressTitle": "Progress", + "difyBuilder.runToFixAutoEmpty": "No failed run found yet. Enter a run ID manually.", + "difyBuilder.runToFixAutoLabel": "Latest failed run", + "difyBuilder.runToFixAutoLoading": "Looking for the latest failed run…", + "difyBuilder.runToFixManualLabel": "Or enter a run ID", + "difyBuilder.runToFixManualPlaceholder": "Workflow run ID", + "difyBuilder.runToFixTitle": "Run to fix", + "difyBuilder.sessionCanvasReadOnlyLabel": "Canvas read-only", + "difyBuilder.sessionEmpty": "No active fix session yet.", + "difyBuilder.sessionIdLabel": "Session", + "difyBuilder.sessionInterruptedLabel": "Interrupted", + "difyBuilder.sessionRunStatusLabel": "Run status", + "difyBuilder.sessionStateLabel": "State", + "difyBuilder.sessionVersionLabel": "Version", + "difyBuilder.startFix": "Start Fix", + "difyBuilder.starting": "Starting…", + "difyBuilder.useThisRun": "Fix this run", + "difyBuilder.workspaceLabel": "Workspace", "difyTeam": "Dify Team", "entryNodeStatus.disabled": "START • DISABLED", "entryNodeStatus.enabled": "START",