refactor(web): move app builder button to canvas header

This commit is contained in:
twwu authored and Wu Tianwei committed 2026-08-31 18:18:30 +08:00
1 parent cb53f02905
commit c200902173
5 files changed
+109 -56

No files matched your search

@@ -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(<DifyBuilderButton disabled={false} />)
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(<DifyBuilderButton disabled />)
const button = screen.getByRole('button', {
name: 'workflow.difyBuilder.buttonTooltip',
})
expect(button).toBeDisabled()
await user.click(button)
expect(store.getState().showDifyBuilderPanel).toBe(false)
})
})
@@ -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(
<HeaderInNormal
components={{
middle: <button type="button">middle-action</button>,
}}
controls={{
showDifyBuilderButton: true,
}}
/>,
)
const appBuilderButton = screen.getByRole('button', {
name: 'workflow.difyBuilder.buttonTooltip',
})
expect(screen.getAllByRole('button').at(-1)).toBe(appBuilderButton)
})
})
describe('HeaderInRestoring', () => {
@@ -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 }) => {
<Button
aria-label={label}
aria-pressed={showDifyBuilderPanel}
className={cn(
'rounded-lg border border-transparent p-2',
theme === 'dark' &&
showDifyBuilderPanel &&
'border-black/5 bg-white/10 backdrop-blur-xs',
)}
className="dify-blue-glass-surface relative h-8 shrink-0 rounded-lg p-2 text-text-accent! data-disabled:text-components-button-ghost-text-disabled!"
variant="ghost"
disabled={disabled}
onClick={handleClick}
>
<span
aria-hidden
className="i-ri-magic-line size-4 text-components-button-secondary-text"
className="i-ri-chat-ai-line size-4 shrink-0 drop-shadow-[0_0_4px_var(--color-components-main-nav-glass-text-glow)]"
/>
<span className="system-xs-semibold-uppercase">{label}</span>
</Button>
}
/>
@@ -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}
<Divider type="vertical" className="mx-auto h-3.5" />
<RunAndHistory {...runAndHistoryProps} />
{showDifyBuilderButton && <DifyBuilderButton disabled={nodesReadOnly} />}
{showContextButtons && (
<div className="shrink-0 cursor-pointer rounded-lg border-[0.5px] border-components-button-secondary-border bg-components-button-secondary-bg shadow-xs backdrop-blur-[10px]">
{components?.chatVariableTrigger}
@@ -103,6 +102,12 @@ const HeaderInNormal = ({ components, controls, runAndHistoryProps }: HeaderInNo
)}
{components?.middle}
{canReleaseAndVersion && <VersionHistoryButton onClick={onStartRestoring} />}
{showDifyBuilderButton && (
<>
<Divider type="vertical" className="mx-0 h-4" />
<DifyBuilderButton disabled={nodesReadOnly} />
</>
)}
</div>
</div>
)
+44 -44
View File
@@ -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",