Merge branch 'feat/pull-a-variable' into feat/support-agent-sandbox

This commit is contained in:
Novice
2026-01-20 09:54:41 +08:00
163 changed files with 16676 additions and 740 deletions

View File

@ -0,0 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 12 12" fill="none">
<path d="M2.91992 1.6875C3.23055 1.68754 3.48242 1.93937 3.48242 2.25C3.48242 2.56063 3.23055 2.81246 2.91992 2.8125C2.63855 2.8125 2.41064 3.04041 2.41064 3.32178V5.46436C2.41061 5.61344 2.35148 5.75637 2.24609 5.86182L2.10791 6L2.24609 6.13818C2.35148 6.24363 2.41061 6.38656 2.41064 6.53564V8.67822C2.41064 8.95959 2.63855 9.1875 2.91992 9.1875C3.23055 9.18754 3.48242 9.43937 3.48242 9.75C3.48242 10.0606 3.23055 10.3125 2.91992 10.3125C2.01723 10.3125 1.28564 9.58091 1.28564 8.67822V6.76855L0.914551 6.39795C0.809062 6.29246 0.75 6.14918 0.75 6C0.75 5.85082 0.809062 5.70754 0.914551 5.60205L1.28564 5.23145V3.32178C1.28564 2.41909 2.01723 1.6875 2.91992 1.6875Z" fill="currentColor"/>
<path d="M9.08008 1.6875C9.98276 1.68751 10.7144 2.41909 10.7144 3.32178V5.23145L11.085 5.60205C11.1904 5.70754 11.25 5.85082 11.25 6C11.25 6.14918 11.1904 6.29246 11.085 6.39795L10.7144 6.76855V8.67822C10.7144 9.58107 9.98213 10.3125 9.08008 10.3125C8.76942 10.3125 8.51758 10.0607 8.51758 9.75C8.51758 9.43934 8.76942 9.1875 9.08008 9.1875C9.36113 9.18749 9.58936 8.95943 9.58936 8.67822V6.53564C9.58939 6.38654 9.64849 6.24363 9.75391 6.13818L9.89209 6L9.75391 5.86182C9.64849 5.75637 9.58939 5.61346 9.58936 5.46436V3.32178C9.58936 3.04041 9.36144 2.81251 9.08008 2.8125C8.76942 2.8125 8.51758 2.56066 8.51758 2.25C8.51758 1.93934 8.76942 1.6875 9.08008 1.6875Z" fill="currentColor"/>
<path d="M5.24707 5.07715C5.36302 5.07715 5.46712 5.14866 5.50879 5.25684L5.8335 6.10059C5.88932 6.24563 6.00388 6.36018 6.14893 6.41602L6.99268 6.74072C7.10086 6.78238 7.17236 6.88648 7.17236 7.00244C7.17229 7.11832 7.10078 7.22202 6.99268 7.26367L6.14893 7.58838C6.00378 7.64424 5.88929 7.75912 5.8335 7.9043L5.50879 8.74756C5.46715 8.8558 5.36307 8.92725 5.24707 8.92725C5.13116 8.92717 5.02746 8.85572 4.98584 8.74756L4.66113 7.9043C4.60526 7.75904 4.49046 7.6442 4.34521 7.58838L3.50195 7.26367C3.39378 7.22205 3.32234 7.11835 3.32227 7.00244C3.32227 6.88645 3.39371 6.78236 3.50195 6.74072L4.34521 6.41602C4.49039 6.36022 4.60523 6.24573 4.66113 6.10059L4.98584 5.25684C5.02749 5.14874 5.13121 5.07723 5.24707 5.07715Z" fill="currentColor"/>
<path d="M6.89746 2.87744C6.98013 2.87754 7.05427 2.92822 7.08398 3.00537L7.29053 3.54297C7.34635 3.68816 7.46125 3.80302 7.60645 3.85889L8.14404 4.06543C8.22123 4.0952 8.27246 4.16966 8.27246 4.25244C8.27236 4.33513 8.22116 4.40922 8.14404 4.43896L7.60645 4.64551C7.46125 4.70138 7.34635 4.81624 7.29053 4.96143L7.08398 5.49902C7.05428 5.57614 6.98014 5.62734 6.89746 5.62744C6.81468 5.62744 6.74019 5.57622 6.71045 5.49902L6.50391 4.96143C6.44808 4.81624 6.33318 4.70138 6.18799 4.64551L5.65039 4.43896C5.57328 4.40922 5.52256 4.33513 5.52246 4.25244C5.52246 4.16966 5.5732 4.0952 5.65039 4.06543L6.18799 3.85889C6.33318 3.80302 6.44808 3.68816 6.50391 3.54297L6.71045 3.00537C6.74019 2.92814 6.81469 2.87744 6.89746 2.87744Z" fill="currentColor"/>
</svg>

After

Width:  |  Height:  |  Size: 2.9 KiB

View File

@ -0,0 +1,53 @@
{
"icon": {
"type": "element",
"isRootNode": true,
"name": "svg",
"attributes": {
"width": "12",
"height": "12",
"viewBox": "0 0 12 12",
"fill": "none",
"xmlns": "http://www.w3.org/2000/svg"
},
"children": [
{
"type": "element",
"name": "path",
"attributes": {
"d": "M2.91992 1.6875C3.23055 1.68754 3.48242 1.93937 3.48242 2.25C3.48242 2.56063 3.23055 2.81246 2.91992 2.8125C2.63855 2.8125 2.41064 3.04041 2.41064 3.32178V5.46436C2.41061 5.61344 2.35148 5.75637 2.24609 5.86182L2.10791 6L2.24609 6.13818C2.35148 6.24363 2.41061 6.38656 2.41064 6.53564V8.67822C2.41064 8.95959 2.63855 9.1875 2.91992 9.1875C3.23055 9.18754 3.48242 9.43937 3.48242 9.75C3.48242 10.0606 3.23055 10.3125 2.91992 10.3125C2.01723 10.3125 1.28564 9.58091 1.28564 8.67822V6.76855L0.914551 6.39795C0.809062 6.29246 0.75 6.14918 0.75 6C0.75 5.85082 0.809062 5.70754 0.914551 5.60205L1.28564 5.23145V3.32178C1.28564 2.41909 2.01723 1.6875 2.91992 1.6875Z",
"fill": "currentColor"
},
"children": []
},
{
"type": "element",
"name": "path",
"attributes": {
"d": "M9.08008 1.6875C9.98276 1.68751 10.7144 2.41909 10.7144 3.32178V5.23145L11.085 5.60205C11.1904 5.70754 11.25 5.85082 11.25 6C11.25 6.14918 11.1904 6.29246 11.085 6.39795L10.7144 6.76855V8.67822C10.7144 9.58107 9.98213 10.3125 9.08008 10.3125C8.76942 10.3125 8.51758 10.0607 8.51758 9.75C8.51758 9.43934 8.76942 9.1875 9.08008 9.1875C9.36113 9.18749 9.58936 8.95943 9.58936 8.67822V6.53564C9.58939 6.38654 9.64849 6.24363 9.75391 6.13818L9.89209 6L9.75391 5.86182C9.64849 5.75637 9.58939 5.61346 9.58936 5.46436V3.32178C9.58936 3.04041 9.36144 2.81251 9.08008 2.8125C8.76942 2.8125 8.51758 2.56066 8.51758 2.25C8.51758 1.93934 8.76942 1.6875 9.08008 1.6875Z",
"fill": "currentColor"
},
"children": []
},
{
"type": "element",
"name": "path",
"attributes": {
"d": "M5.24707 5.07715C5.36302 5.07715 5.46712 5.14866 5.50879 5.25684L5.8335 6.10059C5.88932 6.24563 6.00388 6.36018 6.14893 6.41602L6.99268 6.74072C7.10086 6.78238 7.17236 6.88648 7.17236 7.00244C7.17229 7.11832 7.10078 7.22202 6.99268 7.26367L6.14893 7.58838C6.00378 7.64424 5.88929 7.75912 5.8335 7.9043L5.50879 8.74756C5.46715 8.8558 5.36307 8.92725 5.24707 8.92725C5.13116 8.92717 5.02746 8.85572 4.98584 8.74756L4.66113 7.9043C4.60526 7.75904 4.49046 7.6442 4.34521 7.58838L3.50195 7.26367C3.39378 7.22205 3.32234 7.11835 3.32227 7.00244C3.32227 6.88645 3.39371 6.78236 3.50195 6.74072L4.34521 6.41602C4.49039 6.36022 4.60523 6.24573 4.66113 6.10059L4.98584 5.25684C5.02749 5.14874 5.13121 5.07723 5.24707 5.07715Z",
"fill": "currentColor"
},
"children": []
},
{
"type": "element",
"name": "path",
"attributes": {
"d": "M6.89746 2.87744C6.98013 2.87754 7.05427 2.92822 7.08398 3.00537L7.29053 3.54297C7.34635 3.68816 7.46125 3.80302 7.60645 3.85889L8.14404 4.06543C8.22123 4.0952 8.27246 4.16966 8.27246 4.25244C8.27236 4.33513 8.22116 4.40922 8.14404 4.43896L7.60645 4.64551C7.46125 4.70138 7.34635 4.81624 7.29053 4.96143L7.08398 5.49902C7.05428 5.57614 6.98014 5.62734 6.89746 5.62744C6.81468 5.62744 6.74019 5.57622 6.71045 5.49902L6.50391 4.96143C6.44808 4.81624 6.33318 4.70138 6.18799 4.64551L5.65039 4.43896C5.57328 4.40922 5.52256 4.33513 5.52246 4.25244C5.52246 4.16966 5.5732 4.0952 5.65039 4.06543L6.18799 3.85889C6.33318 3.80302 6.44808 3.68816 6.50391 3.54297L6.71045 3.00537C6.74019 2.92814 6.81469 2.87744 6.89746 2.87744Z",
"fill": "currentColor"
},
"children": []
}
]
},
"name": "AssembleVariables"
}

View File

@ -0,0 +1,20 @@
// GENERATE BY script
// DON NOT EDIT IT MANUALLY
import type { IconData } from '@/app/components/base/icons/IconBase'
import * as React from 'react'
import IconBase from '@/app/components/base/icons/IconBase'
import data from './AssembleVariables.json'
const Icon = (
{
ref,
...props
}: React.SVGProps<SVGSVGElement> & {
ref?: React.RefObject<React.RefObject<HTMLOrSVGElement>>
},
) => <IconBase {...props} ref={ref} data={data as IconData} />
Icon.displayName = 'AssembleVariables'
export default Icon

View File

@ -0,0 +1,26 @@
{
"icon": {
"type": "element",
"isRootNode": true,
"name": "svg",
"attributes": {
"width": "12",
"height": "12",
"viewBox": "0 0 12 12",
"fill": "none",
"xmlns": "http://www.w3.org/2000/svg"
},
"children": [
{
"type": "element",
"name": "path",
"attributes": {
"d": "M5.14286 5.14286V3.42857L8 5.71429L5.14286 8V6.28571H0V5.14286H5.14286ZM0.83303 7.42857H2.04658C2.72474 9.10389 4.36721 10.28571 6.28571 10.28571C8.81049 10.28571 10.85717 8.23903 10.85717 5.71429C10.85717 3.18956 8.81049 1.14285 6.28571 1.14285C4.36721 1.14285 2.72474 2.32467 2.04658 4H0.83303C1.56118 1.68165 3.72706 0 6.28571 0C9.4416 0 12 2.55837 12 5.71429C12 8.87014 9.4416 11.42854 6.28571 11.42854C3.72706 11.42854 1.56118 9.74691 0.83303 7.42857Z",
"fill": "currentColor"
},
"children": []
}
]
},
"name": "AssembleVariablesAlt"
}

View File

@ -0,0 +1,20 @@
// GENERATE BY script
// DON NOT EDIT IT MANUALLY
import type { IconData } from '@/app/components/base/icons/IconBase'
import * as React from 'react'
import IconBase from '@/app/components/base/icons/IconBase'
import data from './AssembleVariablesAlt.json'
const Icon = (
{
ref,
...props
}: React.SVGProps<SVGSVGElement> & {
ref?: React.RefObject<React.RefObject<HTMLOrSVGElement>>
},
) => <IconBase {...props} ref={ref} data={data as IconData} />
Icon.displayName = 'AssembleVariablesAlt'
export default Icon

View File

@ -1,3 +1,5 @@
export { default as AssembleVariables } from './AssembleVariables'
export { default as AssembleVariablesAlt } from './AssembleVariablesAlt'
export { default as AtSign } from './AtSign'
export { default as Bookmark } from './Bookmark'
export { default as Check } from './Check'

View File

@ -38,13 +38,16 @@ export const getInputVars = (text: string): ValueSelector[] => {
if (!text || typeof text !== 'string')
return []
const allVars = text.match(/\{\{#([^#]*)#\}\}/g)
const allVars = text.match(/\{\{[@#]([^@#]*)[@#]\}\}/g)
if (allVars && allVars?.length > 0) {
// {{#context#}}, {{#query#}} is not input vars
const inputVars = allVars
.filter(item => item.includes('.'))
.map((item) => {
const valueSelector = item.replace('{{#', '').replace('#}}', '').split('.')
const valueSelector = item
.replace(/^\{\{[@#]/, '')
.replace(/[@#]\}\}$/, '')
.split('.')
if (valueSelector[1] === 'sys' && /^\d+$/.test(valueSelector[0]))
return valueSelector.slice(1)

View File

@ -155,14 +155,13 @@ export type TriggerFn = (
text: string,
editor: LexicalEditor,
) => MenuTextMatch | null
export const PUNCTUATION = '\\.,\\+\\*\\?\\$\\@\\|#{}\\(\\)\\^\\-\\[\\]\\\\/!%\'"~=<>_:;'
export function useBasicTypeaheadTriggerMatch(
trigger: string,
{ minLength = 1, maxLength = 75 }: { minLength?: number, maxLength?: number },
): TriggerFn {
return useCallback(
(text: string) => {
const validChars = `[${PUNCTUATION}\\s]`
const validChars = '[^\\n]'
const TypeaheadTriggerRegex = new RegExp(
'(.*)('
+ `[${trigger}]`

View File

@ -5,6 +5,7 @@ import type {
} from 'lexical'
import type { FC } from 'react'
import type {
AgentBlockType,
ContextBlockType,
CurrentBlockType,
ErrorMessageBlockType,
@ -103,6 +104,7 @@ export type PromptEditorProps = {
currentBlock?: CurrentBlockType
errorMessageBlock?: ErrorMessageBlockType
lastRunBlock?: LastRunBlockType
agentBlock?: AgentBlockType
isSupportFileVar?: boolean
}
@ -128,6 +130,7 @@ const PromptEditor: FC<PromptEditorProps> = ({
currentBlock,
errorMessageBlock,
lastRunBlock,
agentBlock,
isSupportFileVar,
}) => {
const { eventEmitter } = useEventEmitterContextContext()
@ -139,6 +142,7 @@ const PromptEditor: FC<PromptEditorProps> = ({
{
replace: TextNode,
with: (node: TextNode) => new CustomTextNode(node.__text),
withKlass: CustomTextNode,
},
ContextBlockNode,
HistoryBlockNode,
@ -212,6 +216,22 @@ const PromptEditor: FC<PromptEditorProps> = ({
lastRunBlock={lastRunBlock}
isSupportFileVar={isSupportFileVar}
/>
{(!agentBlock || agentBlock.show) && (
<ComponentPickerBlock
triggerString="@"
contextBlock={contextBlock}
historyBlock={historyBlock}
queryBlock={queryBlock}
variableBlock={variableBlock}
externalToolBlock={externalToolBlock}
workflowVariableBlock={workflowVariableBlock}
currentBlock={currentBlock}
errorMessageBlock={errorMessageBlock}
lastRunBlock={lastRunBlock}
agentBlock={agentBlock}
isSupportFileVar={isSupportFileVar}
/>
)}
<ComponentPickerBlock
triggerString="{"
contextBlock={contextBlock}

View File

@ -32,6 +32,8 @@ import { PickerBlockMenuOption } from './menu'
import { PromptMenuItem } from './prompt-option'
import { VariableMenuItem } from './variable-option'
const escapeRegExp = (value: string) => value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
export const usePromptOptions = (
contextBlock?: ContextBlockType,
queryBlock?: QueryBlockType,
@ -154,7 +156,7 @@ export const useVariableOptions = (
if (!queryString)
return baseOptions
const regex = new RegExp(queryString, 'i')
const regex = new RegExp(escapeRegExp(queryString), 'i')
return baseOptions.filter(option => regex.test(option.key))
}, [editor, queryString, variableBlock])
@ -232,7 +234,7 @@ export const useExternalToolOptions = (
if (!queryString)
return baseToolOptions
const regex = new RegExp(queryString, 'i')
const regex = new RegExp(escapeRegExp(queryString), 'i')
return baseToolOptions.filter(option => regex.test(option.key))
}, [editor, queryString, externalToolBlockType])

View File

@ -1,6 +1,7 @@
import type { MenuRenderFn } from '@lexical/react/LexicalTypeaheadMenuPlugin'
import type { TextNode } from 'lexical'
import type {
AgentBlockType,
ContextBlockType,
CurrentBlockType,
ErrorMessageBlockType,
@ -12,6 +13,8 @@ import type {
WorkflowVariableBlockType,
} from '../../types'
import type { PickerBlockMenuOption } from './menu'
import type { AgentNode } from '@/app/components/base/prompt-editor/types'
import type { ValueSelector } from '@/app/components/workflow/types'
import {
flip,
offset,
@ -20,16 +23,25 @@ import {
} from '@floating-ui/react'
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext'
import { LexicalTypeaheadMenuPlugin } from '@lexical/react/LexicalTypeaheadMenuPlugin'
import { KEY_ESCAPE_COMMAND } from 'lexical'
import {
$getRoot,
$getSelection,
$insertNodes,
$isRangeSelection,
KEY_ESCAPE_COMMAND,
} from 'lexical'
import {
Fragment,
memo,
useCallback,
useMemo,
useState,
} from 'react'
import ReactDOM from 'react-dom'
import { GeneratorType } from '@/app/components/app/configuration/config/automatic/types'
import AgentNodeList from '@/app/components/workflow/nodes/_base/components/agent-node-list'
import VarReferenceVars from '@/app/components/workflow/nodes/_base/components/variable/var-reference-vars'
import { BlockEnum } from '@/app/components/workflow/types'
import { useEventEmitterContextContext } from '@/context/event-emitter'
import { useBasicTypeaheadTriggerMatch } from '../../hooks'
import { $splitNodeContainingQuery } from '../../utils'
@ -38,6 +50,7 @@ import { INSERT_ERROR_MESSAGE_BLOCK_COMMAND } from '../error-message-block'
import { INSERT_LAST_RUN_BLOCK_COMMAND } from '../last-run-block'
import { INSERT_VARIABLE_VALUE_BLOCK_COMMAND } from '../variable-block'
import { INSERT_WORKFLOW_VARIABLE_BLOCK_COMMAND } from '../workflow-variable-block'
import { $createWorkflowVariableBlockNode } from '../workflow-variable-block/node'
import { useOptions } from './hooks'
type ComponentPickerProps = {
@ -51,6 +64,7 @@ type ComponentPickerProps = {
currentBlock?: CurrentBlockType
errorMessageBlock?: ErrorMessageBlockType
lastRunBlock?: LastRunBlockType
agentBlock?: AgentBlockType
isSupportFileVar?: boolean
}
const ComponentPicker = ({
@ -64,6 +78,7 @@ const ComponentPicker = ({
currentBlock,
errorMessageBlock,
lastRunBlock,
agentBlock,
isSupportFileVar,
}: ComponentPickerProps) => {
const { eventEmitter } = useEventEmitterContextContext()
@ -78,11 +93,26 @@ const ComponentPicker = ({
],
})
const [editor] = useLexicalComposerContext()
const useExternalSearch = triggerString === '/' || triggerString === '@'
const checkForTriggerMatch = useBasicTypeaheadTriggerMatch(triggerString, {
minLength: 0,
maxLength: 0,
maxLength: useExternalSearch ? 75 : 0,
})
const getMatchFromSelection = useCallback(() => {
const selection = $getSelection()
if (!$isRangeSelection(selection) || !selection.isCollapsed())
return null
const anchor = selection.anchor
if (anchor.type !== 'text')
return null
const anchorNode = anchor.getNode()
if (!anchorNode.isSimpleText())
return null
const text = anchorNode.getTextContent().slice(0, anchor.offset)
return checkForTriggerMatch(text, editor)
}, [checkForTriggerMatch, editor])
const [queryString, setQueryString] = useState<string | null>(null)
eventEmitter?.useSubscription((v: any) => {
@ -103,6 +133,7 @@ const ComponentPicker = ({
currentBlock,
errorMessageBlock,
lastRunBlock,
useExternalSearch ? (queryString ?? undefined) : undefined,
)
const onSelectOption = useCallback(
@ -124,7 +155,10 @@ const ComponentPicker = ({
const handleSelectWorkflowVariable = useCallback((variables: string[]) => {
editor.update(() => {
const needRemove = $splitNodeContainingQuery(checkForTriggerMatch(triggerString, editor)!)
const match = getMatchFromSelection()
if (!match)
return
const needRemove = $splitNodeContainingQuery(match)
if (needRemove)
needRemove.remove()
})
@ -144,19 +178,68 @@ const ComponentPicker = ({
else {
editor.dispatchCommand(INSERT_WORKFLOW_VARIABLE_BLOCK_COMMAND, variables)
}
}, [editor, currentBlock?.generatorType, checkForTriggerMatch, triggerString])
}, [editor, currentBlock?.generatorType, getMatchFromSelection])
const handleClose = useCallback(() => {
const escapeEvent = new KeyboardEvent('keydown', { key: 'Escape' })
editor.dispatchCommand(KEY_ESCAPE_COMMAND, escapeEvent)
}, [editor])
const handleSelectAssembleVariables = useCallback((): ValueSelector | null => {
editor.update(() => {
const match = getMatchFromSelection()
if (!match)
return
const needRemove = $splitNodeContainingQuery(match)
if (needRemove)
needRemove.remove()
})
const assembleVariables = workflowVariableBlock?.onAssembleVariables?.()
if (assembleVariables && assembleVariables.length)
editor.dispatchCommand(INSERT_WORKFLOW_VARIABLE_BLOCK_COMMAND, assembleVariables)
handleClose()
return assembleVariables ?? null
}, [editor, getMatchFromSelection, workflowVariableBlock, handleClose])
const handleSelectAgent = useCallback((agent: { id: string, title: string }) => {
editor.update(() => {
const match = getMatchFromSelection()
if (!match)
return
const needRemove = $splitNodeContainingQuery(match)
if (needRemove)
needRemove.remove()
const root = $getRoot()
const firstChild = root.getFirstChild()
if (firstChild) {
const selection = firstChild.selectStart()
if (selection) {
const workflowVariableBlockNode = $createWorkflowVariableBlockNode([agent.id, 'text'], {}, undefined)
$insertNodes([workflowVariableBlockNode])
}
}
})
agentBlock?.onSelect?.(agent)
handleClose()
}, [editor, getMatchFromSelection, agentBlock, handleClose])
const isAgentTrigger = triggerString === '@' && agentBlock?.show
const showAssembleVariables = triggerString === '/'
const agentNodes: AgentNode[] = useMemo(() => agentBlock?.agentNodes || [], [agentBlock?.agentNodes])
const renderMenu = useCallback<MenuRenderFn<PickerBlockMenuOption>>((
anchorElementRef,
{ options, selectedIndex, selectOptionAndCleanUp, setHighlightedIndex },
) => {
if (!(anchorElementRef.current && (allFlattenOptions.length || workflowVariableBlock?.show)))
return null
if (isAgentTrigger) {
if (!(anchorElementRef.current && agentNodes.length))
return null
}
else {
if (!(anchorElementRef.current && (allFlattenOptions.length || workflowVariableBlock?.show)))
return null
}
setTimeout(() => {
if (anchorElementRef.current)
@ -167,9 +250,6 @@ const ComponentPicker = ({
<>
{
ReactDOM.createPortal(
// The `LexicalMenu` will try to calculate the position of the floating menu based on the first child.
// Since we use floating ui, we need to wrap it with a div to prevent the position calculation being affected.
// See https://github.com/facebook/lexical/blob/ac97dfa9e14a73ea2d6934ff566282d7f758e8bb/packages/lexical-react/src/shared/LexicalMenu.ts#L493
<div className="h-0 w-0">
<div
className="w-[260px] rounded-lg border-[0.5px] border-components-panel-border bg-components-panel-bg-blur p-1 shadow-lg"
@ -179,56 +259,81 @@ const ComponentPicker = ({
}}
ref={refs.setFloating}
>
{
workflowVariableBlock?.show && (
<div className="p-1">
<VarReferenceVars
searchBoxClassName="mt-1"
vars={workflowVariableOptions}
onChange={(variables: string[]) => {
handleSelectWorkflowVariable(variables)
}}
maxHeightClass="max-h-[34vh]"
isSupportFileVar={isSupportFileVar}
{isAgentTrigger
? (
<AgentNodeList
nodes={agentNodes.map(node => ({
...node,
type: BlockEnum.Agent || BlockEnum.LLM,
}))}
onSelect={handleSelectAgent}
onClose={handleClose}
onBlur={handleClose}
showManageInputField={workflowVariableBlock.showManageInputField}
onManageInputField={workflowVariableBlock.onManageInputField}
maxHeightClass="max-h-[34vh]"
autoFocus={false}
isInCodeGeneratorInstructionEditor={currentBlock?.generatorType === GeneratorType.code}
hideSearch={useExternalSearch}
externalSearchText={useExternalSearch ? (queryString ?? '') : undefined}
enableKeyboardNavigation={useExternalSearch}
/>
</div>
)
}
{
workflowVariableBlock?.show && !!options.length && (
<div className="my-1 h-px w-full -translate-x-1 bg-divider-subtle"></div>
)
}
<div>
{
options.map((option, index) => (
<Fragment key={option.key}>
)
: (
<>
{
// Divider
index !== 0 && options.at(index - 1)?.group !== option.group && (
workflowVariableBlock?.show && (
<div className="p-1">
<VarReferenceVars
searchBoxClassName="mt-1"
vars={workflowVariableOptions}
onChange={(variables: string[]) => {
handleSelectWorkflowVariable(variables)
}}
maxHeightClass="max-h-[34vh]"
isSupportFileVar={isSupportFileVar}
onClose={handleClose}
onBlur={handleClose}
showManageInputField={workflowVariableBlock.showManageInputField}
onManageInputField={workflowVariableBlock.onManageInputField}
showAssembleVariables={showAssembleVariables}
onAssembleVariables={showAssembleVariables ? handleSelectAssembleVariables : undefined}
autoFocus={false}
isInCodeGeneratorInstructionEditor={currentBlock?.generatorType === GeneratorType.code}
hideSearch={useExternalSearch}
externalSearchText={useExternalSearch ? (queryString ?? '') : undefined}
enableKeyboardNavigation={useExternalSearch}
/>
</div>
)
}
{
workflowVariableBlock?.show && !!options.length && (
<div className="my-1 h-px w-full -translate-x-1 bg-divider-subtle"></div>
)
}
{option.renderMenuOption({
queryString,
isSelected: selectedIndex === index,
onSelect: () => {
selectOptionAndCleanUp(option)
},
onSetHighlight: () => {
setHighlightedIndex(index)
},
})}
</Fragment>
))
}
</div>
<div>
{
options.map((option, index) => (
<Fragment key={option.key}>
{
index !== 0 && options.at(index - 1)?.group !== option.group && (
<div className="my-1 h-px w-full -translate-x-1 bg-divider-subtle"></div>
)
}
{option.renderMenuOption({
queryString,
isSelected: selectedIndex === index,
onSelect: () => {
selectOptionAndCleanUp(option)
},
onSetHighlight: () => {
setHighlightedIndex(index)
},
})}
</Fragment>
))
}
</div>
</>
)}
</div>
</div>,
anchorElementRef.current,
@ -236,7 +341,7 @@ const ComponentPicker = ({
}
</>
)
}, [allFlattenOptions.length, workflowVariableBlock?.show, floatingStyles, isPositioned, refs, workflowVariableOptions, isSupportFileVar, handleClose, currentBlock?.generatorType, handleSelectWorkflowVariable, queryString, workflowVariableBlock?.showManageInputField, workflowVariableBlock?.onManageInputField])
}, [isAgentTrigger, agentNodes, allFlattenOptions.length, workflowVariableBlock?.show, floatingStyles, isPositioned, refs, handleSelectAgent, handleClose, workflowVariableOptions, isSupportFileVar, currentBlock?.generatorType, handleSelectWorkflowVariable, queryString, workflowVariableBlock?.showManageInputField, workflowVariableBlock?.onManageInputField, showAssembleVariables, handleSelectAssembleVariables, useExternalSearch])
return (
<LexicalTypeaheadMenuPlugin

View File

@ -21,6 +21,7 @@ import {
VariableLabelInEditor,
} from '@/app/components/workflow/nodes/_base/components/variable/variable-label'
import { Type } from '@/app/components/workflow/nodes/llm/types'
import { BlockEnum } from '@/app/components/workflow/types'
import { isExceptionVariable } from '@/app/components/workflow/utils'
import { useSelectOrDelete } from '../../hooks'
import {
@ -66,6 +67,8 @@ const WorkflowVariableBlockComponent = ({
)()
const [localWorkflowNodesMap, setLocalWorkflowNodesMap] = useState<WorkflowNodesMap>(workflowNodesMap)
const node = localWorkflowNodesMap![variables[isRagVar ? 1 : 0]]
const isContextVariable = (node?.type === BlockEnum.Agent || node?.type === BlockEnum.LLM)
&& variables[variablesLength - 1] === 'context'
const isException = isExceptionVariable(varName, node?.type)
const variableValid = useMemo(() => {
@ -134,6 +137,9 @@ const WorkflowVariableBlockComponent = ({
})
}, [node, reactflow, store])
if (isContextVariable)
return <span className="hidden" ref={ref} />
const Item = (
<VariableLabelInEditor
nodeType={node?.type}

View File

@ -2,6 +2,7 @@ import type { LexicalNode, NodeKey, SerializedLexicalNode } from 'lexical'
import type { GetVarType, WorkflowVariableBlockType } from '../../types'
import type { Var } from '@/app/components/workflow/types'
import { DecoratorNode } from 'lexical'
import { BlockEnum } from '@/app/components/workflow/types'
import WorkflowVariableBlockComponent from './component'
export type WorkflowNodesMap = WorkflowVariableBlockType['workflowNodesMap']
@ -120,7 +121,12 @@ export class WorkflowVariableBlockNode extends DecoratorNode<React.JSX.Element>
}
getTextContent(): string {
return `{{#${this.getVariables().join('.')}#}}`
const variables = this.getVariables()
const node = this.getWorkflowNodesMap()?.[variables[0]]
const isContextVariable = (node?.type === BlockEnum.Agent || node?.type === BlockEnum.LLM)
&& variables[variables.length - 1] === 'context'
const marker = isContextVariable ? '@' : '#'
return `{{${marker}${variables.join('.')}${marker}}}`
}
}
export function $createWorkflowVariableBlockNode(variables: string[], workflowNodesMap: WorkflowNodesMap, getVarType?: GetVarType, environmentVariables?: Var[], conversationVariables?: Var[], ragVariables?: Var[]): WorkflowVariableBlockNode {

View File

@ -71,6 +71,19 @@ export type WorkflowVariableBlockType = {
getVarType?: GetVarType
showManageInputField?: boolean
onManageInputField?: () => void
showAssembleVariables?: boolean
onAssembleVariables?: () => ValueSelector | null
}
export type AgentNode = {
id: string
title: string
}
export type AgentBlockType = {
show?: boolean
agentNodes?: AgentNode[]
onSelect?: (agent: AgentNode) => void
}
export type MenuTextMatch = {