chore: refactor sandbox placeholder

This commit is contained in:
Joel
2026-03-26 13:54:25 +08:00
parent 37d59222cf
commit 1c01bd773b
3 changed files with 123 additions and 46 deletions

View File

@ -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[]
}) => (
<div data-i18n-key={i18nKey} data-testid="sandbox-placeholder-trans">
{components}
</div>
),
}))
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(<SandboxPlaceholder isSupportSandbox={false} />)
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(
<SandboxPlaceholder
disableToolBlocks
isSupportSandbox
/>,
)
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(<SandboxPlaceholder isSupportSandbox />)
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')
})
})
})

View File

@ -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<PromptEditorContentProps> = ({
enabled: Boolean(isSupportSandbox),
}), [isSupportSandbox])
const sandboxPlaceHolder = React.useMemo(() => {
if (!isSupportSandbox)
return null
const i18nKey = disableToolBlocks
? 'promptEditor.placeholderSandboxNoTools'
: 'promptEditor.placeholderSandbox'
const components = disableToolBlocks
? [
<span
key="slash"
className="inline-flex h-5 min-w-5 items-center justify-center rounded-[4px] bg-components-kbd-bg-gray px-1 text-text-tertiary system-kbd"
/>,
<span
key="insert"
className="border-b border-dotted border-current"
/>,
]
: [
<span
key="slash"
className="inline-flex h-5 min-w-5 items-center justify-center rounded-[4px] bg-components-kbd-bg-gray px-1 text-text-tertiary system-kbd"
/>,
<span
key="insert"
className="border-b border-dotted border-current"
/>,
<span
key="at"
className="inline-flex h-5 min-w-5 items-center justify-center rounded-[4px] bg-components-kbd-bg-gray px-1 text-text-tertiary system-kbd"
/>,
<span
key="tools"
className="border-b border-dotted border-current"
/>,
]
return (
<Trans
i18nKey={i18nKey}
ns="common"
components={components}
/>
)
}, [disableToolBlocks, isSupportSandbox])
const [floatingAnchorElem, setFloatingAnchorElem] = useState<HTMLDivElement | null>(null)
const onRef = (floatingAnchorElement: HTMLDivElement | null) => {
@ -415,7 +371,12 @@ const PromptEditorContent: FC<PromptEditorContentProps> = ({
)}
placeholder={(
<Placeholder
value={placeholder || sandboxPlaceHolder}
value={placeholder || (
<SandboxPlaceholder
disableToolBlocks={disableToolBlocks}
isSupportSandbox={isSupportSandbox}
/>
)}
className={cn('truncate', placeholderClassName)}
compact={compact}
/>

View File

@ -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<SandboxPlaceholderTokenProps> = ({ variant, children }) => {
if (variant === 'kbd') {
return (
<span className="inline-flex h-5 min-w-5 items-center justify-center rounded-[4px] bg-components-kbd-bg-gray px-1 text-text-tertiary system-kbd">
{children}
</span>
)
}
return (
<span className="border-b border-dotted border-current">
{children}
</span>
)
}
type SandboxPlaceholderProps = {
disableToolBlocks?: boolean
isSupportSandbox?: boolean
}
const SandboxPlaceholder: FC<SandboxPlaceholderProps> = ({
disableToolBlocks,
isSupportSandbox,
}) => {
if (!isSupportSandbox)
return null
const components: ReactElement[] = [
<SandboxPlaceholderToken key="slash" variant="kbd" />,
<SandboxPlaceholderToken key="insert" variant="action" />,
]
if (!disableToolBlocks) {
components.push(
<SandboxPlaceholderToken key="at" variant="kbd" />,
<SandboxPlaceholderToken key="tools" variant="action" />,
)
}
return (
<Trans
i18nKey={disableToolBlocks ? 'promptEditor.placeholderSandboxNoTools' : 'promptEditor.placeholderSandbox'}
ns="common"
components={components}
/>
)
}
export default SandboxPlaceholder