feat(workflow): merge tool agent insertions into slash menu

This commit is contained in:
CodingOnStar
2026-03-26 10:29:07 +08:00
parent d66b0d2d11
commit 12be211a6d
9 changed files with 371 additions and 49 deletions

View File

@ -631,4 +631,77 @@ describe('ComponentPicker (component-picker-block/index.tsx)', () => {
// With a single option group, the only divider should be the workflow-var/options separator.
expect(document.querySelectorAll('.bg-divider-subtle')).toHaveLength(1)
})
it('renders agent entries in the slash menu and routes selection through workflowVariableBlock.onSelectAgent', async () => {
const captures: Captures = { editor: null, eventEmitter: null }
const onSelectAgent = vi.fn()
const workflowVariableBlock = makeWorkflowVariableBlock({
agentNodes: [{ id: 'agent-1', title: 'Agent One' }],
onSelectAgent,
showAssembleVariables: true,
onAssembleVariables: vi.fn(() => ['tool-ext', 'result']),
}, [
makeWorkflowVarNode('node-1', 'Node 1', [
makeWorkflowNodeVar('output', VarType.string),
]),
])
render((
<MinimalEditor
triggerString="/"
workflowVariableBlock={workflowVariableBlock}
captures={captures}
/>
))
const editor = await waitForEditor(captures)
const dispatchSpy = vi.spyOn(editor, 'dispatchCommand')
await setEditorText(editor, '/', true)
await flushNextTick()
const agentButton = await screen.findByRole('button', { name: 'Agent One' })
const assembleButton = await screen.findByRole('button', { name: 'workflow.nodes.tool.assembleVariables' })
expect(agentButton.compareDocumentPosition(assembleButton) & Node.DOCUMENT_POSITION_FOLLOWING).toBeTruthy()
fireEvent.click(agentButton)
expect(dispatchSpy).toHaveBeenCalledWith(INSERT_WORKFLOW_VARIABLE_BLOCK_COMMAND, ['agent-1', 'context'])
expect(onSelectAgent).toHaveBeenCalledWith({ id: 'agent-1', title: 'Agent One' })
await waitFor(() => {
expect(readEditorText(editor)).not.toContain('/')
})
})
it('does not render an at-menu when triggerString is @ but agentBlock is not provided', async () => {
const captures: Captures = { editor: null, eventEmitter: null }
const workflowVariableBlock = makeWorkflowVariableBlock({
agentNodes: [{ id: 'agent-1', title: 'Agent One' }],
onSelectAgent: vi.fn(),
}, [
makeWorkflowVarNode('node-1', 'Node 1', [
makeWorkflowNodeVar('output', VarType.string),
]),
])
render((
<MinimalEditor
triggerString="@"
workflowVariableBlock={workflowVariableBlock}
captures={captures}
/>
))
const editor = await waitForEditor(captures)
await setEditorText(editor, '@', true)
await waitFor(() => {
expect(screen.queryByText('Agent One')).not.toBeInTheDocument()
expect(screen.queryByPlaceholderText('workflow.common.searchVar')).not.toBeInTheDocument()
})
})
})

View File

@ -254,13 +254,14 @@ const ComponentPicker = ({
root.selectStart()
})
editor.dispatchCommand(INSERT_WORKFLOW_VARIABLE_BLOCK_COMMAND, [agent.id, 'context'])
workflowVariableBlock?.onSelectAgent?.(agent)
agentBlock?.onSelect?.(agent)
editor.update(() => {
const root = $getRoot()
root.selectEnd()
})
handleClose()
}, [editor, getMatchFromSelection, agentBlock, handleClose])
}, [editor, getMatchFromSelection, workflowVariableBlock, agentBlock, handleClose])
const handleSelectContext = useCallback(() => {
if (!contextBlock?.selectable)
@ -279,7 +280,9 @@ const ComponentPicker = ({
const isAgentTrigger = triggerString === '@' && agentBlock?.show
const showAssembleVariables = triggerString === '/' && workflowVariableBlock?.showAssembleVariables && !!workflowVariableBlock?.onAssembleVariables
const agentNodes: AgentNode[] = useMemo(() => agentBlock?.agentNodes || [], [agentBlock?.agentNodes])
const agentNodes: AgentNode[] = useMemo(() => {
return workflowVariableBlock?.agentNodes || agentBlock?.agentNodes || []
}, [workflowVariableBlock?.agentNodes, agentBlock?.agentNodes])
const handleOpen = useCallback(() => {
if (isSupportSandbox && triggerString === '/')
setActiveTab('variables')
@ -289,6 +292,9 @@ const ComponentPicker = ({
anchorElementRef,
{ options, selectedIndex, selectOptionAndCleanUp, setHighlightedIndex },
) => {
if (triggerString === '@' && !agentBlock?.show)
return null
if (isAgentTrigger) {
if (!(anchorElementRef.current && agentNodes.length))
return null
@ -462,6 +468,8 @@ const ComponentPicker = ({
onBlur={handleClose}
showManageInputField={workflowVariableBlock.showManageInputField}
onManageInputField={workflowVariableBlock.onManageInputField}
agentNodes={triggerString === '/' ? workflowVariableBlock.agentNodes : undefined}
onSelectAgent={triggerString === '/' && workflowVariableBlock.agentNodes?.length ? handleSelectAgent : undefined}
showAssembleVariables={showAssembleVariables}
onAssembleVariables={showAssembleVariables ? handleSelectAssembleVariables : undefined}
autoFocus={false}
@ -510,7 +518,7 @@ const ComponentPicker = ({
}
</>
)
}, [isAgentTrigger, isSupportSandbox, triggerString, allFlattenOptions.length, workflowVariableBlock?.show, workflowVariableBlock?.showManageInputField, workflowVariableBlock?.onManageInputField, floatingStyles, isPositioned, refs, agentNodes, handleSelectAgent, handleClose, useExternalSearch, queryString, workflowVariableOptions, isSupportFileVar, showAssembleVariables, handleSelectAssembleVariables, currentBlock?.generatorType, t, activeTab, handleSelectWorkflowVariable, handleSelectFileReference, contextBlock?.show, contextBlock?.selectable, handleSelectContext])
}, [isAgentTrigger, isSupportSandbox, triggerString, allFlattenOptions.length, workflowVariableBlock?.show, workflowVariableBlock?.showManageInputField, workflowVariableBlock?.onManageInputField, workflowVariableBlock?.agentNodes, floatingStyles, isPositioned, refs, agentNodes, handleSelectAgent, handleClose, useExternalSearch, queryString, workflowVariableOptions, isSupportFileVar, showAssembleVariables, handleSelectAssembleVariables, currentBlock?.generatorType, t, activeTab, handleSelectWorkflowVariable, handleSelectFileReference, contextBlock?.show, contextBlock?.selectable, handleSelectContext, agentBlock?.show])
return (
<>

View File

@ -79,6 +79,8 @@ export type WorkflowVariableBlockType = {
getVarType?: GetVarType
showManageInputField?: boolean
onManageInputField?: () => void
agentNodes?: AgentNode[]
onSelectAgent?: (agent: AgentNode) => void
showAssembleVariables?: boolean
onAssembleVariables?: () => ValueSelector | null
}

View File

@ -223,4 +223,83 @@ describe('VarReferenceVars', () => {
fireEvent.click(screen.getByText('asset'))
expect(onChange).not.toHaveBeenCalled()
})
it('should render agent entries before assemble variables and normal variables', () => {
const onSelectAgent = vi.fn()
render(
<VarReferenceVars
hideSearch
vars={baseVars}
onChange={vi.fn()}
agentNodes={[{ id: 'agent-1', title: 'Agent One' }]}
onSelectAgent={onSelectAgent}
showAssembleVariables
onAssembleVariables={() => null}
/>,
)
const agentButton = screen.getByRole('button', { name: 'Agent One' })
const assembleButton = screen.getByRole('button', { name: 'workflow.nodes.tool.assembleVariables' })
const variableLabel = screen.getByText('valid_name')
expect(agentButton.compareDocumentPosition(assembleButton) & Node.DOCUMENT_POSITION_FOLLOWING).toBeTruthy()
expect(assembleButton.compareDocumentPosition(variableLabel) & Node.DOCUMENT_POSITION_FOLLOWING).toBeTruthy()
fireEvent.click(agentButton)
expect(onSelectAgent).toHaveBeenCalledWith({ id: 'agent-1', title: 'Agent One' })
})
it('should filter agent entries and variables with the shared search box', () => {
render(
<VarReferenceVars
vars={baseVars}
onChange={vi.fn()}
agentNodes={[{ id: 'agent-1', title: 'Agent One' }]}
onSelectAgent={vi.fn()}
/>,
)
const searchInput = screen.getByPlaceholderText('workflow.common.searchVar')
fireEvent.change(searchInput, { target: { value: 'agent' } })
expect(screen.getByRole('button', { name: 'Agent One' })).toBeInTheDocument()
expect(screen.queryByText('valid_name')).not.toBeInTheDocument()
fireEvent.change(searchInput, { target: { value: 'valid' } })
expect(screen.getByText('valid_name')).toBeInTheDocument()
expect(screen.queryByRole('button', { name: 'Agent One' })).not.toBeInTheDocument()
})
it('should include agents, assemble variables, and variables in keyboard navigation order', () => {
const onSelectAgent = vi.fn()
const onAssembleVariables = vi.fn(() => null)
const onChange = vi.fn()
render(
<VarReferenceVars
hideSearch
enableKeyboardNavigation
vars={baseVars}
onChange={onChange}
agentNodes={[{ id: 'agent-1', title: 'Agent One' }]}
onSelectAgent={onSelectAgent}
showAssembleVariables
onAssembleVariables={onAssembleVariables}
/>,
)
fireEvent.keyDown(document, { key: 'Enter' })
expect(onSelectAgent).toHaveBeenCalledWith({ id: 'agent-1', title: 'Agent One' })
fireEvent.keyDown(document, { key: 'ArrowDown' })
fireEvent.keyDown(document, { key: 'Enter' })
expect(onAssembleVariables).toHaveBeenCalledTimes(1)
fireEvent.keyDown(document, { key: 'ArrowDown' })
fireEvent.keyDown(document, { key: 'Enter' })
expect(onChange).toHaveBeenCalledWith(['node-a', 'valid_name'], expect.objectContaining({
variable: 'valid_name',
}))
})
})

View File

@ -1,6 +1,7 @@
'use client'
import type { FC } from 'react'
import type { StructuredOutput } from '../../../llm/types'
import type { AgentNode } from '@/app/components/base/prompt-editor/types'
import type { Field } from '@/app/components/workflow/nodes/llm/types'
import type { NodeOutPutVar, ValueSelector, Var } from '@/app/components/workflow/types'
import { useHover, useLatest } from 'ahooks'
@ -11,6 +12,7 @@ import { useTranslation } from 'react-i18next'
import { ChevronRight } from '@/app/components/base/icons/src/vender/line/arrows'
import { AssembleVariables, CodeAssistant, MagicEdit } from '@/app/components/base/icons/src/vender/line/general'
import { Variable02 } from '@/app/components/base/icons/src/vender/solid/development'
import { Agent } from '@/app/components/base/icons/src/vender/workflow'
import Input from '@/app/components/base/input'
import {
PortalToFollowElem,
@ -102,6 +104,11 @@ const matchesNestedVar = (itemData: Var, query: string): boolean => {
return false
}
type KeyboardItem
= | { type: 'agent', agent: AgentNode }
| { type: 'assemble' }
| { type: 'variable', node: NodeOutPutVar, itemData: Var }
type ItemProps = {
nodeId: string
title: string
@ -339,6 +346,8 @@ type Props = {
isInCodeGeneratorInstructionEditor?: boolean
showManageInputField?: boolean
onManageInputField?: () => void
agentNodes?: AgentNode[]
onSelectAgent?: (agent: AgentNode) => void
showAssembleVariables?: boolean
onAssembleVariables?: () => ValueSelector | null
autoFocus?: boolean
@ -360,6 +369,8 @@ const VarReferenceVars: FC<Props> = ({
isInCodeGeneratorInstructionEditor,
showManageInputField,
onManageInputField,
agentNodes,
onSelectAgent,
showAssembleVariables,
onAssembleVariables,
autoFocus = true,
@ -388,6 +399,14 @@ const VarReferenceVars: FC<Props> = ({
onClose?.()
}
const filteredAgentNodes = useMemo(() => {
if (!agentNodes?.length || !onSelectAgent)
return []
if (!normalizedSearchTextTrimmed)
return agentNodes
return agentNodes.filter(node => node.title.toLowerCase().includes(normalizedSearchTextLower))
}, [agentNodes, normalizedSearchTextLower, normalizedSearchTextTrimmed, onSelectAgent])
const validatedVars = useMemo(() => {
const result: NodeOutPutVar[] = []
vars.forEach((node) => {
@ -435,23 +454,33 @@ const VarReferenceVars: FC<Props> = ({
})
return items
}, [filteredVars])
const showAgentSection = filteredAgentNodes.length > 0
const showAssembleEntry = !!(showAssembleVariables && onAssembleVariables)
const keyboardItems = useMemo<KeyboardItem[]>(() => {
const items: KeyboardItem[] = []
filteredAgentNodes.forEach(agent => items.push({ type: 'agent', agent }))
if (showAssembleEntry)
items.push({ type: 'assemble' })
flatItems.forEach(item => items.push({ type: 'variable', ...item }))
return items
}, [filteredAgentNodes, flatItems, showAssembleEntry])
const [activeIndex, setActiveIndex] = useState(-1)
const itemRefs = useRef<Array<HTMLDivElement | null>>([])
const itemRefsRef = useRef<Array<HTMLElement | null>>([])
const lastInteractionRef = useRef<'keyboard' | 'mouse' | 'filter' | null>(null)
const resolvedActiveIndex = useMemo(() => {
if (!enableKeyboardNavigation || flatItems.length === 0)
if (!enableKeyboardNavigation || keyboardItems.length === 0)
return -1
if (activeIndex < 0 || activeIndex >= flatItems.length)
if (activeIndex < 0 || activeIndex >= keyboardItems.length)
return 0
return activeIndex
}, [activeIndex, enableKeyboardNavigation, flatItems.length])
const flatItemsRef = useLatest(flatItems)
}, [activeIndex, enableKeyboardNavigation, keyboardItems.length])
const keyboardItemsRef = useLatest(keyboardItems)
const activeIndexRef = useLatest(resolvedActiveIndex)
const onCloseRef = useLatest(onClose)
useEffect(() => {
itemRefs.current = []
}, [flatItems.length])
itemRefsRef.current = []
}, [keyboardItems.length])
const handleHighlightIndex = useCallback((index: number, source: 'keyboard' | 'mouse' | 'filter') => {
lastInteractionRef.current = source
@ -459,26 +488,38 @@ const VarReferenceVars: FC<Props> = ({
}, [])
useEffect(() => {
if (!enableKeyboardNavigation || flatItems.length === 0) {
if (!enableKeyboardNavigation || keyboardItems.length === 0) {
lastInteractionRef.current = 'filter'
return
}
if (activeIndex < 0 || activeIndex >= flatItems.length)
if (activeIndex < 0 || activeIndex >= keyboardItems.length)
lastInteractionRef.current = 'filter'
}, [activeIndex, enableKeyboardNavigation, flatItems.length])
}, [activeIndex, enableKeyboardNavigation, keyboardItems.length])
useEffect(() => {
if (!enableKeyboardNavigation || resolvedActiveIndex < 0)
return
if (lastInteractionRef.current !== 'keyboard')
return
const target = itemRefs.current[resolvedActiveIndex]
const target = itemRefsRef.current[resolvedActiveIndex]
if (target)
target.scrollIntoView({ block: 'nearest' })
lastInteractionRef.current = null
}, [enableKeyboardNavigation, flatItems.length, resolvedActiveIndex])
}, [enableKeyboardNavigation, keyboardItems.length, resolvedActiveIndex])
const handleSelectItem = useCallback((item: KeyboardItem) => {
if (item.type === 'agent') {
onSelectAgent?.(item.agent)
onClose?.()
return
}
if (item.type === 'assemble') {
onAssembleVariables?.()
onClose?.()
return
}
const handleSelectItem = useCallback((item: { node: NodeOutPutVar, itemData: Var }) => {
const isStructureOutput = item.itemData.type === VarType.object
&& (item.itemData.children as StructuredOutput | undefined)?.schema?.properties
const isFile = item.itemData.type === VarType.file && !isStructureOutput
@ -500,13 +541,13 @@ const VarReferenceVars: FC<Props> = ({
onChange(valueSelector, item.itemData)
onClose?.()
}, [isSupportFileVar, onChange, onClose])
}, [isSupportFileVar, onChange, onClose, onSelectAgent, onAssembleVariables])
useEffect(() => {
if (!enableKeyboardNavigation)
return
const handleDocumentKeyDown = (event: KeyboardEvent) => {
const items = flatItemsRef.current
const items = keyboardItemsRef.current
if (!items.length)
return
if (!['ArrowDown', 'ArrowUp', 'Enter', 'Escape'].includes(event.key))
@ -538,9 +579,10 @@ const VarReferenceVars: FC<Props> = ({
return () => {
document.removeEventListener('keydown', handleDocumentKeyDown, true)
}
}, [activeIndexRef, enableKeyboardNavigation, flatItemsRef, handleHighlightIndex, handleSelectItem, onCloseRef])
}, [activeIndexRef, enableKeyboardNavigation, keyboardItemsRef, handleHighlightIndex, handleSelectItem, onCloseRef])
let runningIndex = -1
const assembleIndex = filteredAgentNodes.length
let runningIndex = filteredAgentNodes.length + (showAssembleEntry ? 1 : 0) - 1
return (
<>
@ -572,13 +614,66 @@ const VarReferenceVars: FC<Props> = ({
)
}
{
showAssembleVariables && onAssembleVariables && (
showAgentSection && (
<div className="border-t border-divider-subtle pt-1">
<div className="px-3 pb-1 text-text-tertiary system-xs-medium-uppercase">
{t('nodes.tool.agentPopupHeader', { ns: 'workflow' })}
</div>
{filteredAgentNodes.map((agent) => {
runningIndex += 1
const itemIndex = runningIndex
return (
<button
key={agent.id}
type="button"
ref={enableKeyboardNavigation
? (element) => {
itemRefsRef.current[itemIndex] = element
}
: undefined}
className={cn(
'flex h-6 w-full items-center rounded-md pl-3 pr-[18px] text-text-secondary hover:bg-state-base-hover',
enableKeyboardNavigation && itemIndex === resolvedActiveIndex && 'bg-state-base-hover',
)}
onClick={() => handleSelectItem({ type: 'agent', agent })}
onFocus={enableKeyboardNavigation ? () => handleHighlightIndex(itemIndex, 'mouse') : undefined}
onMouseDown={e => e.preventDefault()}
onMouseEnter={enableKeyboardNavigation ? () => handleHighlightIndex(itemIndex, 'mouse') : undefined}
>
<span className="mr-1 flex h-4 w-4 items-center justify-center rounded bg-util-colors-indigo-indigo-500">
<Agent className="h-3 w-3 text-text-primary-on-surface" />
</span>
<span className="truncate system-sm-medium" title={agent.title}>
{agent.title}
</span>
</button>
)
})}
</div>
)
}
{
showAssembleEntry && (
<div className="flex items-center border-t border-divider-subtle pt-1">
<button
type="button"
className="flex h-6 w-full items-center rounded-md pl-3 pr-[18px] text-text-secondary hover:bg-state-base-hover"
ref={enableKeyboardNavigation
? (element) => {
itemRefsRef.current[assembleIndex] = element
}
: undefined}
className={cn(
'flex h-6 w-full items-center rounded-md pl-3 pr-[18px] text-text-secondary hover:bg-state-base-hover',
enableKeyboardNavigation && assembleIndex === resolvedActiveIndex && 'bg-state-base-hover',
)}
onClick={handleAssembleVariables}
onFocus={enableKeyboardNavigation
? () => handleHighlightIndex(assembleIndex, 'mouse')
: undefined}
onMouseDown={e => e.preventDefault()}
onMouseEnter={enableKeyboardNavigation
? () => handleHighlightIndex(assembleIndex, 'mouse')
: undefined}
>
<span className="mr-1 flex h-4 w-4 items-center justify-center rounded bg-util-colors-blue-blue-500">
<AssembleVariables className="h-3 w-3 text-text-primary-on-surface" />
@ -628,7 +723,7 @@ const VarReferenceVars: FC<Props> = ({
onSetHighlight={enableKeyboardNavigation ? () => handleHighlightIndex(itemIndex, 'mouse') : undefined}
registerRef={enableKeyboardNavigation
? (element) => {
itemRefs.current[itemIndex] = element
itemRefsRef.current[itemIndex] = element
}
: undefined}
/>
@ -646,7 +741,7 @@ const VarReferenceVars: FC<Props> = ({
}
</div>
)
: <div className="mt-2 pl-3 text-xs font-medium uppercase leading-[18px] text-gray-500">{t('common.noVar', { ns: 'workflow' })}</div>}
: !showAgentSection && !showAssembleEntry && <div className="mt-2 pl-3 text-xs font-medium uppercase leading-[18px] text-gray-500">{t('common.noVar', { ns: 'workflow' })}</div>}
{
showManageInputField && (
<ManageInputField

View File

@ -12,8 +12,7 @@ import type {
ContextGenerateResponse,
} from '@/service/debug'
import type { CompletionParams, Model, ModelModeType } from '@/types/app'
import { useSessionStorageState } from 'ahooks'
import useBoolean from 'ahooks/lib/useBoolean'
import { useBoolean, useSessionStorageState } from 'ahooks'
import { useCallback, useMemo, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { toast } from '@/app/components/base/ui/toast'

View File

@ -0,0 +1,86 @@
import type { LexicalComposerContextWithEditor } from '@lexical/react/LexicalComposerContext'
import type { LexicalEditor } from 'lexical'
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext'
import { createEvent, fireEvent, render, screen } from '@testing-library/react'
import { $insertNodes, FOCUS_COMMAND } from 'lexical'
import Placeholder from '../placeholder'
const mockEditorUpdate = vi.fn((callback: () => void) => callback())
const mockDispatchCommand = vi.fn()
const mockInsertNodes = vi.fn()
const mockTextNode = vi.fn()
const mockEditor = {
update: mockEditorUpdate,
dispatchCommand: mockDispatchCommand,
} as unknown as LexicalEditor
const lexicalContextValue: LexicalComposerContextWithEditor = [
mockEditor,
{ getTheme: () => undefined },
]
vi.mock('@lexical/react/LexicalComposerContext', () => ({
useLexicalComposerContext: vi.fn(),
}))
vi.mock('lexical', () => ({
$insertNodes: vi.fn(),
FOCUS_COMMAND: 'focus-command',
}))
vi.mock('@/app/components/base/prompt-editor/plugins/custom-text/node', () => ({
CustomTextNode: class MockCustomTextNode {
value: string
constructor(value: string) {
this.value = value
mockTextNode(value)
}
},
}))
describe('Tool mixed variable placeholder', () => {
beforeEach(() => {
vi.clearAllMocks()
vi.mocked(useLexicalComposerContext).mockReturnValue(lexicalContextValue)
vi.mocked($insertNodes).mockImplementation(nodes => mockInsertNodes(nodes))
})
it('should insert an empty text node and focus the editor when the placeholder background is clicked', () => {
const parentClick = vi.fn()
render(
<div onClick={parentClick}>
<Placeholder />
</div>,
)
fireEvent.click(screen.getByText('workflow.nodes.tool.insertPlaceholder1'))
expect(parentClick).not.toHaveBeenCalled()
expect(mockTextNode).toHaveBeenCalledWith('')
expect(mockInsertNodes).toHaveBeenCalledTimes(1)
expect(mockDispatchCommand).toHaveBeenCalledWith(FOCUS_COMMAND, expect.any(FocusEvent))
})
it('should render only the slash insertion hint', () => {
render(<Placeholder />)
expect(screen.getByText('workflow.nodes.tool.insertPlaceholder2')).toBeInTheDocument()
expect(screen.queryByText('workflow.nodes.tool.insertPlaceholder3')).not.toBeInTheDocument()
expect(screen.queryByText('@')).not.toBeInTheDocument()
})
it('should insert a slash shortcut from the highlighted action and prevent the native mouse down behavior', () => {
render(<Placeholder />)
const shortcut = screen.getByText('workflow.nodes.tool.insertPlaceholder2')
const event = createEvent.mouseDown(shortcut)
fireEvent(shortcut, event)
expect(event.defaultPrevented).toBe(true)
expect(mockTextNode).toHaveBeenCalledWith('/')
expect(mockDispatchCommand).toHaveBeenCalledWith(FOCUS_COMMAND, expect.any(FocusEvent))
})
})

View File

@ -8,13 +8,11 @@ import { cn } from '@/utils/classnames'
type PlaceholderProps = {
disableVariableInsertion?: boolean
hasSelectedAgent?: boolean
hideBadge?: boolean
}
const Placeholder = ({
disableVariableInsertion = false,
hasSelectedAgent = false,
hideBadge = false,
}: PlaceholderProps) => {
const { t } = useTranslation()
@ -54,21 +52,6 @@ const Placeholder = ({
>
{t('nodes.tool.insertPlaceholder2', { ns: 'workflow' })}
</div>
{!hasSelectedAgent && (
<>
<div className="mx-0.5 flex h-4 w-4 items-center justify-center rounded bg-components-kbd-bg-gray text-text-placeholder system-kbd">@</div>
<div
className="cursor-pointer text-components-input-text-placeholder underline decoration-dotted decoration-auto underline-offset-auto system-sm-regular hover:text-text-tertiary"
onMouseDown={((e) => {
e.preventDefault()
e.stopPropagation()
handleInsert('@')
})}
>
{t('nodes.tool.insertPlaceholder3', { ns: 'workflow' })}
</div>
</>
)}
</>
)}
</div>

View File

@ -414,15 +414,12 @@ const MixedVariableTextInput = ({
workflowNodesMap,
showManageInputField,
onManageInputField,
agentNodes,
onSelectAgent: handleAgentSelect,
showAssembleVariables: !disableVariableInsertion && !!toolNodeId && !!paramKey,
onAssembleVariables: handleAssembleSelect,
}}
agentBlock={{
show: agentNodes.length > 0 && !detectedAgentFromValue,
agentNodes,
onSelect: handleAgentSelect,
}}
placeholder={<Placeholder disableVariableInsertion={disableVariableInsertion} hasSelectedAgent={!!detectedAgentFromValue} />}
placeholder={<Placeholder disableVariableInsertion={disableVariableInsertion} />}
onChange={(text) => {
const hasPlaceholder = new RegExp(AGENT_CONTEXT_VAR_PATTERN.source).test(text)
if (hasPlaceholder)