From 1654ba670aa6288a6a29993de11542aaee8f0203 Mon Sep 17 00:00:00 2001 From: yyh Date: Wed, 22 Jul 2026 17:29:36 +0800 Subject: [PATCH 1/2] fix(web): improve DSL export feedback --- packages/dify-ui/src/toast/index.stories.tsx | 89 +++--- packages/dify-ui/src/toast/index.tsx | 20 +- .../__tests__/app-info-detail-panel.spec.tsx | 25 +- .../__tests__/app-info-modals.spec.tsx | 35 +-- .../__tests__/use-app-info-actions.spec.ts | 108 +++---- .../app-info/app-info-detail-panel.tsx | 5 +- .../app-sidebar/app-info/app-info-modals.tsx | 31 +- .../app-sidebar/app-info/app-operations.tsx | 13 +- .../components/app-sidebar/app-info/index.tsx | 3 + .../app-info/use-app-info-actions.ts | 64 ++--- .../app/__tests__/use-export-app-dsl.spec.tsx | 266 ++++++++++++++++++ web/app/components/app/use-export-app-dsl.ts | 148 ++++++++++ .../apps/__tests__/app-card.spec.tsx | 110 ++++---- web/app/components/apps/app-card.tsx | 162 ++++++----- .../workflow/dsl-export-confirm-modal.tsx | 2 +- .../__tests__/navigation.spec.tsx | 30 +- .../agent-v2/agent-detail/sidebar-actions.tsx | 24 +- .../__tests__/agent-roster-list.spec.tsx | 45 +-- .../roster/components/agent-roster-list.tsx | 24 +- 19 files changed, 775 insertions(+), 429 deletions(-) create mode 100644 web/app/components/app/__tests__/use-export-app-dsl.spec.tsx create mode 100644 web/app/components/app/use-export-app-dsl.ts diff --git a/packages/dify-ui/src/toast/index.stories.tsx b/packages/dify-ui/src/toast/index.stories.tsx index 301252bbc38263..8997874324fa47 100644 --- a/packages/dify-ui/src/toast/index.stories.tsx +++ b/packages/dify-ui/src/toast/index.stories.tsx @@ -2,6 +2,7 @@ import type { Meta, StoryObj } from '@storybook/react-vite' import * as React from 'react' import { expect, within } from 'storybook/test' import { toast, ToastHost } from '.' +import { Button } from '../button' const longToastTitle = 'operation error S3: PutObject, exceeded maximum number of attempts, 3, StatusCode: 0, RequestID: , HostID: , request send failed' @@ -155,58 +156,62 @@ const StackExamples = () => { } const PromiseExamples = () => { - const createPromiseToast = () => { - const request = new Promise((resolve) => { - window.setTimeout(() => resolve('The deployment is now available in production.'), 1400) - }) + const [pendingExample, setPendingExample] = React.useState<'success' | 'error' | null>(null) - void toast.promise(request, { - loading: { - type: 'info', - title: 'Deploying workflow', - description: 'Provisioning runtime and publishing the latest version.', - }, - success: (result) => ({ - type: 'success', - title: 'Deployment complete', - description: result, - }), - error: () => ({ - type: 'error', - title: 'Deployment failed', - description: 'The release could not be completed.', - }), - }) - } + const exportDsl = async (outcome: 'success' | 'error') => { + if (pendingExample) return - const createRejectingPromiseToast = () => { - const request = new Promise((_, reject) => { - window.setTimeout(() => reject(new Error('intentional story failure')), 1200) + setPendingExample(outcome) + const request = new Promise((resolve, reject) => { + window.setTimeout(() => { + if (outcome === 'success') resolve('customer-support-agent.yml') + else reject(new Error('The DSL could not be generated.')) + }, 1400) }) - void toast.promise(request, { - loading: 'Validating model credentials…', - success: 'Credentials verified', - error: () => ({ - type: 'error', - title: 'Credentials rejected', - description: 'The model provider returned an authentication error.', - }), - }) + await toast + .promise(request, { + loading: { + title: 'Preparing DSL export', + description: 'Collecting the app configuration and generating a YAML file.', + }, + success: (fileName) => ({ + title: 'Download started', + description: `${fileName} was sent to your browser.`, + timeout: 3000, + }), + error: () => ({ + title: 'Export failed', + description: 'The DSL could not be generated. Try again.', + }), + }) + .catch(() => undefined) + + setPendingExample(null) } return ( - - + + ) } diff --git a/packages/dify-ui/src/toast/index.tsx b/packages/dify-ui/src/toast/index.tsx index 9aad5890c17e5a..3fa8d3b9f0444f 100644 --- a/packages/dify-ui/src/toast/index.tsx +++ b/packages/dify-ui/src/toast/index.tsx @@ -16,6 +16,11 @@ type ToastToneStyle = { } const TOAST_TONE_STYLES = { + loading: { + iconClassName: 'i-ri-loader-2-line animate-spin text-text-accent motion-reduce:animate-none', + gradientClassName: + 'from-components-badge-status-light-normal-halo to-background-gradient-mask-transparent', + }, success: { iconClassName: 'i-ri-checkbox-circle-fill text-text-success', gradientClassName: @@ -41,7 +46,8 @@ const TOAST_TONE_STYLES = { const toastCloseLabel = 'Close notification' const toastViewportLabel = 'Notifications' -type ToastType = keyof typeof TOAST_TONE_STYLES +type ToastRenderType = keyof typeof TOAST_TONE_STYLES +type ToastType = Exclude type ToastAddOptions = Omit< ToastManagerAddOptions, @@ -96,12 +102,12 @@ type ToastApi = { const toastManager = BaseToast.createToastManager() -function isToastType(type: string): type is ToastType { +function isToastRenderType(type: string): type is ToastRenderType { return Object.prototype.hasOwnProperty.call(TOAST_TONE_STYLES, type) } -function getToastType(type?: string): ToastType | undefined { - return type && isToastType(type) ? type : undefined +function getToastRenderType(type?: string): ToastRenderType | undefined { + return type && isToastRenderType(type) ? type : undefined } function addToast(options: ToastAddOptions) { @@ -145,19 +151,19 @@ export const toast: ToastApi = Object.assign(showToast, { promise: promiseToast, }) -function ToastIcon({ type }: { type?: ToastType }) { +function ToastIcon({ type }: { type?: ToastRenderType }) { return type ? (