diff --git a/web/app/components/base/prompt-editor/__tests__/sandbox-placeholder.spec.tsx b/web/app/components/base/prompt-editor/__tests__/sandbox-placeholder.spec.tsx
new file mode 100644
index 0000000000..22d69cdd11
--- /dev/null
+++ b/web/app/components/base/prompt-editor/__tests__/sandbox-placeholder.spec.tsx
@@ -0,0 +1,59 @@
+import type { ReactElement } from 'react'
+import { render, screen } from '@testing-library/react'
+import SandboxPlaceholder from '../sandbox-placeholder'
+
+vi.mock('react-i18next', () => ({
+ Trans: ({ i18nKey, components = [] }: {
+ i18nKey: string
+ components?: ReactElement[]
+ }) => (
+
+ {components}
+
+ ),
+}))
+
+describe('SandboxPlaceholder', () => {
+ beforeEach(() => {
+ vi.clearAllMocks()
+ })
+
+ // Rendering branches for sandbox availability and tool-block support.
+ describe('Rendering', () => {
+ it('should render nothing when sandbox is not supported', () => {
+ const { container } = render()
+
+ expect(container).toBeEmptyDOMElement()
+ expect(screen.queryByTestId('sandbox-placeholder-trans')).not.toBeInTheDocument()
+ })
+
+ it('should render slash and insert tokens when tool blocks are disabled', () => {
+ const { container } = render(
+ ,
+ )
+
+ expect(screen.getByTestId('sandbox-placeholder-trans')).toHaveAttribute('data-i18n-key', 'promptEditor.placeholderSandboxNoTools')
+
+ const spans = container.querySelectorAll('span')
+ expect(spans).toHaveLength(2)
+ expect(spans[0]).toHaveClass('inline-flex', 'bg-components-kbd-bg-gray', 'system-kbd')
+ expect(spans[1]).toHaveClass('border-b', 'border-dotted', 'border-current')
+ })
+
+ it('should render slash insert at and tools tokens when tool blocks are enabled', () => {
+ const { container } = render()
+
+ expect(screen.getByTestId('sandbox-placeholder-trans')).toHaveAttribute('data-i18n-key', 'promptEditor.placeholderSandbox')
+
+ const spans = container.querySelectorAll('span')
+ expect(spans).toHaveLength(4)
+ expect(spans[0]).toHaveClass('inline-flex', 'bg-components-kbd-bg-gray', 'system-kbd')
+ expect(spans[1]).toHaveClass('border-b', 'border-dotted', 'border-current')
+ expect(spans[2]).toHaveClass('inline-flex', 'bg-components-kbd-bg-gray', 'system-kbd')
+ expect(spans[3]).toHaveClass('border-b', 'border-dotted', 'border-current')
+ })
+ })
+})
diff --git a/web/app/components/base/prompt-editor/index.tsx b/web/app/components/base/prompt-editor/index.tsx
index 9f202a3d62..141b960249 100644
--- a/web/app/components/base/prompt-editor/index.tsx
+++ b/web/app/components/base/prompt-editor/index.tsx
@@ -39,7 +39,6 @@ import {
} from 'lexical'
import * as React from 'react'
import { useEffect, useState } from 'react'
-import { Trans } from 'react-i18next'
import { WorkflowContext } from '@/app/components/workflow/context'
import { HooksStoreContext } from '@/app/components/workflow/hooks-store/provider'
import { FileReferenceNode } from '@/app/components/workflow/skill/editor/skill-editor/plugins/file-reference-block/node'
@@ -118,6 +117,7 @@ import {
WorkflowVariableBlockNode,
WorkflowVariableBlockReplacementBlock,
} from './plugins/workflow-variable-block'
+import SandboxPlaceholder from './sandbox-placeholder'
import { textToEditorState } from './utils'
const ValueSyncPlugin: FC<{ value?: string }> = ({ value }) => {
@@ -342,50 +342,6 @@ const PromptEditorContent: FC = ({
enabled: Boolean(isSupportSandbox),
}), [isSupportSandbox])
- const sandboxPlaceHolder = React.useMemo(() => {
- if (!isSupportSandbox)
- return null
- const i18nKey = disableToolBlocks
- ? 'promptEditor.placeholderSandboxNoTools'
- : 'promptEditor.placeholderSandbox'
- const components = disableToolBlocks
- ? [
- ,
- ,
- ]
- : [
- ,
- ,
- ,
- ,
- ]
- return (
-
- )
- }, [disableToolBlocks, isSupportSandbox])
-
const [floatingAnchorElem, setFloatingAnchorElem] = useState(null)
const onRef = (floatingAnchorElement: HTMLDivElement | null) => {
@@ -415,7 +371,12 @@ const PromptEditorContent: FC = ({
)}
placeholder={(
+ )}
className={cn('truncate', placeholderClassName)}
compact={compact}
/>
diff --git a/web/app/components/base/prompt-editor/sandbox-placeholder.tsx b/web/app/components/base/prompt-editor/sandbox-placeholder.tsx
new file mode 100644
index 0000000000..5fb348bee0
--- /dev/null
+++ b/web/app/components/base/prompt-editor/sandbox-placeholder.tsx
@@ -0,0 +1,57 @@
+import type { FC, PropsWithChildren, ReactElement } from 'react'
+import { Trans } from 'react-i18next'
+
+type SandboxPlaceholderTokenProps = PropsWithChildren<{
+ variant: 'kbd' | 'action'
+}>
+
+const SandboxPlaceholderToken: FC = ({ variant, children }) => {
+ if (variant === 'kbd') {
+ return (
+
+ {children}
+
+ )
+ }
+
+ return (
+
+ {children}
+
+ )
+}
+
+type SandboxPlaceholderProps = {
+ disableToolBlocks?: boolean
+ isSupportSandbox?: boolean
+}
+
+const SandboxPlaceholder: FC = ({
+ disableToolBlocks,
+ isSupportSandbox,
+}) => {
+ if (!isSupportSandbox)
+ return null
+
+ const components: ReactElement[] = [
+ ,
+ ,
+ ]
+
+ if (!disableToolBlocks) {
+ components.push(
+ ,
+ ,
+ )
+ }
+
+ return (
+
+ )
+}
+
+export default SandboxPlaceholder