diff --git a/web/app/components/workflow/run/__tests__/output-panel.spec.tsx b/web/app/components/workflow/run/__tests__/output-panel.spec.tsx
index bd0613cd591..09e6247d904 100644
--- a/web/app/components/workflow/run/__tests__/output-panel.spec.tsx
+++ b/web/app/components/workflow/run/__tests__/output-panel.spec.tsx
@@ -21,8 +21,16 @@ vi.mock('@/app/components/base/markdown', () => ({
}))
vi.mock('@/app/components/workflow/run/status-container', () => ({
- default: ({ status, children }: { status: string; children?: React.ReactNode }) => (
-
+ default: ({
+ status,
+ children,
+ copyContent,
+ }: {
+ status: string
+ children?: React.ReactNode
+ copyContent?: string
+ }) => (
+
{children}
),
@@ -62,6 +70,10 @@ describe('OutputPanel', () => {
render(
)
expect(screen.getByTestId('status-container')).toHaveAttribute('data-status', 'failed')
+ expect(screen.getByTestId('status-container')).toHaveAttribute(
+ 'data-copy-content',
+ 'Execution failed',
+ )
expect(screen.getByText('Execution failed')).toBeInTheDocument()
})
diff --git a/web/app/components/workflow/run/__tests__/status-container.spec.tsx b/web/app/components/workflow/run/__tests__/status-container.spec.tsx
index 36b44fef80a..b27dc19328a 100644
--- a/web/app/components/workflow/run/__tests__/status-container.spec.tsx
+++ b/web/app/components/workflow/run/__tests__/status-container.spec.tsx
@@ -1,8 +1,18 @@
import { render, screen } from '@testing-library/react'
+import userEvent from '@testing-library/user-event'
import useTheme from '@/hooks/use-theme'
import { Theme } from '@/types/app'
import StatusContainer from '../status-container'
+const copy = vi.fn()
+
+vi.mock('foxact/use-clipboard', () => ({
+ useClipboard: () => ({
+ copied: false,
+ copy,
+ }),
+}))
+
vi.mock('@/hooks/use-theme', () => ({
default: vi.fn(),
}))
@@ -34,4 +44,19 @@ describe('StatusContainer', () => {
).toBeInTheDocument()
})
})
+
+ it('copies the supplied content from the status action', async () => {
+ const user = userEvent.setup()
+ render(
+
+ Execution failed
+ ,
+ )
+
+ await user.click(
+ screen.getByRole('button', { name: 'appOverview.overview.appInfo.embedded.copy' }),
+ )
+
+ expect(copy).toHaveBeenCalledWith('Execution failed')
+ })
})
diff --git a/web/app/components/workflow/run/output-panel.tsx b/web/app/components/workflow/run/output-panel.tsx
index 4fedf87956f..1c1c5463ab5 100644
--- a/web/app/components/workflow/run/output-panel.tsx
+++ b/web/app/components/workflow/run/output-panel.tsx
@@ -53,7 +53,9 @@ const OutputPanel: FC
= ({ isRunning, outputs, error, height }
)}
{!isRunning && error && (
- {error}
+
+ {error}
+
)}
{!isRunning && !outputs && (
diff --git a/web/app/components/workflow/run/status-container.tsx b/web/app/components/workflow/run/status-container.tsx
index 9ed9668b062..8a7038d4870 100644
--- a/web/app/components/workflow/run/status-container.tsx
+++ b/web/app/components/workflow/run/status-container.tsx
@@ -1,22 +1,26 @@
'use client'
import type { FC } from 'react'
import { cn } from '@langgenius/dify-ui/cn'
+import CopyFeedback from '@/app/components/base/copy-feedback'
import useTheme from '@/hooks/use-theme'
import { Theme } from '@/types/app'
type Props = {
readonly status: string
readonly children?: React.ReactNode
+ readonly copyContent?: string
}
-const StatusContainer: FC = ({ status, children }) => {
+const StatusContainer: FC = ({ status, children, copyContent }) => {
const { theme } = useTheme()
+ const isCopyable = copyContent !== undefined
return (
= ({ status, children }) => {
>
{children}
+ {isCopyable && (
+
+
+
+ )}
)
}