From 5c7a6db6b3858bb3138bb9d0053840f1d2a723df Mon Sep 17 00:00:00 2001 From: Joel Date: Wed, 12 Mar 2025 15:58:33 +0800 Subject: [PATCH 1/4] feat: prompt var popup --- .../workflow-variable-block/component.tsx | 31 +++++++++++++++++-- .../variable/var-full-path-panel.tsx | 5 ++- 2 files changed, 32 insertions(+), 4 deletions(-) diff --git a/web/app/components/base/prompt-editor/plugins/workflow-variable-block/component.tsx b/web/app/components/base/prompt-editor/plugins/workflow-variable-block/component.tsx index fc6e589807..47bb297789 100644 --- a/web/app/components/base/prompt-editor/plugins/workflow-variable-block/component.tsx +++ b/web/app/components/base/prompt-editor/plugins/workflow-variable-block/component.tsx @@ -11,6 +11,7 @@ import { mergeRegister } from '@lexical/utils' import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext' import { RiErrorWarningFill, + RiMoreLine, } from '@remixicon/react' import { useSelectOrDelete } from '../../hooks' import type { WorkflowNodesMap } from './node' @@ -27,6 +28,8 @@ import { Line3 } from '@/app/components/base/icons/src/public/common' import { isConversationVar, isENV, isSystemVar } from '@/app/components/workflow/nodes/_base/components/variable/utils' import Tooltip from '@/app/components/base/tooltip' import { isExceptionVariable } from '@/app/components/workflow/utils' +import VarFullPathPanel from '@/app/components/workflow/nodes/_base/components/variable/var-full-path-panel' +import { Type } from '@/app/components/workflow/nodes/llm/types' type WorkflowVariableBlockComponentProps = { nodeKey: string @@ -43,10 +46,11 @@ const WorkflowVariableBlockComponent = ({ const [editor] = useLexicalComposerContext() const [ref, isSelected] = useSelectOrDelete(nodeKey, DELETE_WORKFLOW_VARIABLE_BLOCK_COMMAND) const variablesLength = variables.length + const isShowAPart = variablesLength > 2 const varName = ( () => { const isSystem = isSystemVar(variables) - const varName = variablesLength >= 3 ? (variables).slice(-2).join('.') : variables[variablesLength - 1] + const varName = variables[variablesLength - 1] return `${isSystem ? 'sys.' : ''}${varName}` } )() @@ -76,7 +80,7 @@ const WorkflowVariableBlockComponent = ({ const Item = (
)} + {isShowAPart && ( +
+ + +
+ )} +
{!isEnv && !isChatVar && } {isEnv && } @@ -126,7 +137,21 @@ const WorkflowVariableBlockComponent = ({ ) } - return Item + return ( + } + disabled={!isShowAPart} + > + {Item} + + ) } export default memo(WorkflowVariableBlockComponent) diff --git a/web/app/components/workflow/nodes/_base/components/variable/var-full-path-panel.tsx b/web/app/components/workflow/nodes/_base/components/variable/var-full-path-panel.tsx index dde7069acf..67179feef0 100644 --- a/web/app/components/workflow/nodes/_base/components/variable/var-full-path-panel.tsx +++ b/web/app/components/workflow/nodes/_base/components/variable/var-full-path-panel.tsx @@ -6,16 +6,19 @@ import { Type } from '../../../llm/types' import { PickerPanelMain as Panel } from '@/app/components/workflow/nodes/_base/components/variable/object-child-tree-panel/picker' import BlockIcon from '@/app/components/workflow/block-icon' import { BlockEnum } from '@/app/components/workflow/types' + type Props = { nodeName: string path: string[] varType: TypeWithArray + nodeType?: BlockEnum } const VarFullPathPanel: FC = ({ nodeName, path, varType, + nodeType = BlockEnum.LLM, }) => { const schema: StructuredOutput = (() => { const schema: StructuredOutput['schema'] = { @@ -41,7 +44,7 @@ const VarFullPathPanel: FC = ({ return (
- +
{nodeName}
Date: Fri, 14 Mar 2025 14:22:12 +0800 Subject: [PATCH 2/4] fix: popup panel show detail --- .../variable/var-full-path-panel.tsx | 4 +- .../variable/var-reference-picker.tsx | 44 ++++++++++++++----- 2 files changed, 36 insertions(+), 12 deletions(-) diff --git a/web/app/components/workflow/nodes/_base/components/variable/var-full-path-panel.tsx b/web/app/components/workflow/nodes/_base/components/variable/var-full-path-panel.tsx index 67179feef0..8742fae526 100644 --- a/web/app/components/workflow/nodes/_base/components/variable/var-full-path-panel.tsx +++ b/web/app/components/workflow/nodes/_base/components/variable/var-full-path-panel.tsx @@ -28,7 +28,7 @@ const VarFullPathPanel: FC = ({ additionalProperties: false, } let current = schema - for (let i = 0; i < path.length; i++) { + for (let i = 1; i < path.length; i++) { const isLast = i === path.length - 1 const name = path[i] current.properties[name] = { @@ -49,7 +49,7 @@ const VarFullPathPanel: FC = ({
diff --git a/web/app/components/workflow/nodes/_base/components/variable/var-reference-picker.tsx b/web/app/components/workflow/nodes/_base/components/variable/var-reference-picker.tsx index c7ef4598f7..b776983803 100644 --- a/web/app/components/workflow/nodes/_base/components/variable/var-reference-picker.tsx +++ b/web/app/components/workflow/nodes/_base/components/variable/var-reference-picker.tsx @@ -6,6 +6,7 @@ import { RiArrowDownSLine, RiCloseLine, RiErrorWarningFill, + RiMoreLine, } from '@remixicon/react' import produce from 'immer' import { useStoreApi } from 'reactflow' @@ -37,6 +38,8 @@ import AddButton from '@/app/components/base/button/add-button' import Badge from '@/app/components/base/badge' import Tooltip from '@/app/components/base/tooltip' import { isExceptionVariable } from '@/app/components/workflow/utils' +import VarFullPathPanel from './var-full-path-panel' +import { Type } from '../../../llm/types' const TRIGGER_DEFAULT_WIDTH = 227 @@ -156,16 +159,15 @@ const VarReferencePicker: FC = ({ return getNodeInfoById(availableNodes, outputVarNodeId)?.data }, [value, hasValue, isConstant, isIterationVar, iterationNode, availableNodes, outputVarNodeId, startNode]) - const varName = useMemo(() => { - if (hasValue) { - const isSystem = isSystemVar(value as ValueSelector) - let varName = '' - if (Array.isArray(value)) - varName = value.length >= 3 ? (value as ValueSelector).slice(-2).join('.') : value[value.length - 1] + const isShowAPart = (value as ValueSelector).length > 2 - return `${isSystem ? 'sys.' : ''}${varName}` - } - return '' + const varName = useMemo(() => { + if (!hasValue) + return '' + + const isSystem = isSystemVar(value as ValueSelector) + const varName = Array.isArray(value) ? value[(value as ValueSelector).length - 1] : '' + return `${isSystem ? 'sys.' : ''}${varName}` }, [hasValue, value]) const varKindTypes = [ @@ -253,6 +255,22 @@ const VarReferencePicker: FC = ({ const WrapElem = isSupportConstantValue ? 'div' : PortalToFollowElemTrigger const VarPickerWrap = !isSupportConstantValue ? 'div' : PortalToFollowElemTrigger + + const tooltipPopup = useMemo(() => { + if (isValidVar && isShowAPart) { + return ( + ) + } + if (!isValidVar && hasValue) + return t('workflow.errorMsg.invalidVariable') + + return null + }, [isValidVar, isShowAPart, hasValue, t, outputVarNode?.title, outputVarNode?.type, value]) return (
= ({ className='grow h-full' >
- +
{hasValue ? ( @@ -336,6 +354,12 @@ const VarReferencePicker: FC = ({
)} + {isShowAPart && ( +
+ + +
+ )}
{!hasValue && } {isEnv && } From 84eb6a47154eafa4dfd8b67d99bae1a0f21d5e6a Mon Sep 17 00:00:00 2001 From: Joel Date: Fri, 14 Mar 2025 14:53:02 +0800 Subject: [PATCH 3/4] feat: old attr to new obj attr --- .../nodes/_base/components/variable/utils.ts | 10 ++ .../variable/var-reference-vars.tsx | 136 +++++------------- 2 files changed, 42 insertions(+), 104 deletions(-) diff --git a/web/app/components/workflow/nodes/_base/components/variable/utils.ts b/web/app/components/workflow/nodes/_base/components/variable/utils.ts index 15d0a16d2b..187b2f18e5 100644 --- a/web/app/components/workflow/nodes/_base/components/variable/utils.ts +++ b/web/app/components/workflow/nodes/_base/components/variable/utils.ts @@ -67,6 +67,16 @@ const structTypeToVarType = (type: Type): VarType => { } as any)[type] || VarType.string } +export const varTypeToStructType = (type: VarType): Type => { + return ({ + [VarType.string]: Type.string, + [VarType.number]: Type.number, + [VarType.boolean]: Type.boolean, + [VarType.object]: Type.object, + [VarType.array]: Type.array, + } as any)[type] || Type.string +} + const findExceptVarInStructuredProperties = (properties: Record, filterVar: (payload: Var, selector: ValueSelector) => boolean): Record => { const res = produce(properties, (draft) => { Object.keys(properties).forEach((key) => { diff --git a/web/app/components/workflow/nodes/_base/components/variable/var-reference-vars.tsx b/web/app/components/workflow/nodes/_base/components/variable/var-reference-vars.tsx index 64047dda84..e45bb12f07 100644 --- a/web/app/components/workflow/nodes/_base/components/variable/var-reference-vars.tsx +++ b/web/app/components/workflow/nodes/_base/components/variable/var-reference-vars.tsx @@ -1,6 +1,6 @@ 'use client' import type { FC } from 'react' -import React, { useEffect, useRef, useState } from 'react' +import React, { useEffect, useMemo, useRef, useState } from 'react' import { useHover } from 'ahooks' import { useTranslation } from 'react-i18next' import cn from '@/utils/classnames' @@ -15,20 +15,12 @@ import { import Input from '@/app/components/base/input' import { BubbleX, Env } from '@/app/components/base/icons/src/vender/line/others' import { checkKeys } from '@/utils/var' -import { FILE_STRUCT } from '@/app/components/workflow/constants' import type { StructuredOutput } from '../../../llm/types' +import { Type } from '../../../llm/types' import PickerStructurePanel from '@/app/components/workflow/nodes/_base/components/variable/object-child-tree-panel/picker' - -type ObjectChildrenProps = { - nodeId: string - title: string - data: Var[] - objPath: string[] - onChange: (value: ValueSelector, item: Var) => void - onHovering?: (value: boolean) => void - itemWidth?: number - isSupportFileVar?: boolean -} +import { varTypeToStructType } from './utils' +import type { Field } from '@/app/components/workflow/nodes/llm/types' +import { FILE_STRUCT } from '@/app/components/workflow/constants' type ItemProps = { nodeId: string @@ -49,7 +41,6 @@ const Item: FC = ({ itemData, onChange, onHovering, - itemWidth, isSupportFileVar, isException, }) => { @@ -59,6 +50,31 @@ const Item: FC = ({ const isSys = itemData.variable.startsWith('sys.') const isEnv = itemData.variable.startsWith('env.') const isChatVar = itemData.variable.startsWith('conversation.') + + const objStructuredOutput: StructuredOutput | null = useMemo(() => { + if (!isObj) return null + const properties: Record = {}; + (isFile ? FILE_STRUCT : (itemData.children as Var[])).forEach((c) => { + properties[c.variable] = { + type: varTypeToStructType(c.type), + } + }) + return { + schema: { + type: Type.object, + properties, + required: [], + additionalProperties: false, + }, + } + }, [isFile, isObj, itemData.children]) + + const structuredOutput = (() => { + if (isStructureOutput) + return itemData.children as StructuredOutput + return objStructuredOutput + })() + const itemRef = useRef(null) const [isItemHovering, setIsItemHovering] = useState(false) const _ = useHover(itemRef, { @@ -136,109 +152,21 @@ const Item: FC = ({ - {isStructureOutput && ( + {(isStructureOutput || isObj) && ( { onChange(valueSelector, itemData) }} /> )} - {(isObj && !isFile) && ( - // eslint-disable-next-line ts/no-use-before-define - - )} - {isFile && ( - // eslint-disable-next-line ts/no-use-before-define - - )} ) } -const ObjectChildren: FC = ({ - title, - nodeId, - objPath, - data, - onChange, - onHovering, - itemWidth, - isSupportFileVar, -}) => { - const currObjPath = objPath - const itemRef = useRef(null) - const [isItemHovering, setIsItemHovering] = useState(false) - const _ = useHover(itemRef, { - onChange: (hovering) => { - if (hovering) { - setIsItemHovering(true) - } - else { - setTimeout(() => { - setIsItemHovering(false) - }, 100) - } - }, - }) - const [isChildrenHovering, setIsChildrenHovering] = useState(false) - const isHovering = isItemHovering || isChildrenHovering - useEffect(() => { - onHovering && onHovering(isHovering) - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [isHovering]) - useEffect(() => { - onHovering && onHovering(isItemHovering) - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [isItemHovering]) - // absolute top-[-2px] - return ( -
-
{title}.{currObjPath.join('.')}
- { - (data && data.length > 0) - && data.map((v, i) => ( - - )) - } -
- ) -} - type Props = { hideSearch?: boolean searchBoxClassName?: string From 8adf0fa698b1656f45f26b518df7bc74ed28ff81 Mon Sep 17 00:00:00 2001 From: Joel Date: Fri, 14 Mar 2025 16:03:23 +0800 Subject: [PATCH 4/4] feat: var picker can show the right var --- .../[appId]/overview/tracing/panel.tsx | 2 +- .../object-child-tree-panel/picker/field.tsx | 8 ++++---- .../nodes/_base/components/variable/utils.ts | 20 +++++++++++++++++++ .../variable/var-reference-picker.tsx | 7 +++---- 4 files changed, 28 insertions(+), 9 deletions(-) diff --git a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/panel.tsx b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/panel.tsx index 6df1466df8..eb0e7371bb 100644 --- a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/panel.tsx +++ b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/panel.tsx @@ -82,7 +82,7 @@ const Panel: FC = () => { ? LangfuseIcon : inUseTracingProvider === TracingProvider.opik ? OpikIcon - : null + : LangsmithIcon const [langSmithConfig, setLangSmithConfig] = useState(null) const [langFuseConfig, setLangFuseConfig] = useState(null) diff --git a/web/app/components/workflow/nodes/_base/components/variable/object-child-tree-panel/picker/field.tsx b/web/app/components/workflow/nodes/_base/components/variable/object-child-tree-panel/picker/field.tsx index 7c8ad80f88..b01badca57 100644 --- a/web/app/components/workflow/nodes/_base/components/variable/object-child-tree-panel/picker/field.tsx +++ b/web/app/components/workflow/nodes/_base/components/variable/object-child-tree-panel/picker/field.tsx @@ -58,11 +58,11 @@ const Field: FC = ({ {depth <= MAX_DEPTH && payload.type === Type.object && payload.properties && (
- {Object.keys(payload.properties).map(name => ( + {Object.keys(payload.properties).map(propName => ( v.variable === (valueSelector as ValueSelector).join('.'))?.type } else { + const targetVar = curr.find((v: any) => v.variable === valueSelector[1]) + if (!targetVar) + return VarType.string + + const isStructuredOutputVar = !!targetVar.children.schema?.properties + if (isStructuredOutputVar) { + let currProperties = targetVar.children.schema; + (valueSelector as ValueSelector).slice(2).forEach((key, i) => { + const isLast = i === valueSelector.length - 3 + if (!currProperties) + return + + currProperties = currProperties.properties[key] + if (isLast) + type = structTypeToVarType(currProperties?.type) + }) + return type + } + (valueSelector as ValueSelector).slice(1).forEach((key, i) => { const isLast = i === valueSelector.length - 2 if (Array.isArray(curr)) diff --git a/web/app/components/workflow/nodes/_base/components/variable/var-reference-picker.tsx b/web/app/components/workflow/nodes/_base/components/variable/var-reference-picker.tsx index b776983803..64319044e4 100644 --- a/web/app/components/workflow/nodes/_base/components/variable/var-reference-picker.tsx +++ b/web/app/components/workflow/nodes/_base/components/variable/var-reference-picker.tsx @@ -13,7 +13,7 @@ import { useStoreApi } from 'reactflow' import RemoveButton from '../remove-button' import useAvailableVarList from '../../hooks/use-available-var-list' import VarReferencePopup from './var-reference-popup' -import { getNodeInfoById, isConversationVar, isENV, isSystemVar } from './utils' +import { getNodeInfoById, isConversationVar, isENV, isSystemVar, varTypeToStructType } from './utils' import ConstantField from './constant-field' import cn from '@/utils/classnames' import type { Node, NodeOutPutVar, ValueSelector, Var } from '@/app/components/workflow/types' @@ -39,7 +39,6 @@ import Badge from '@/app/components/base/badge' import Tooltip from '@/app/components/base/tooltip' import { isExceptionVariable } from '@/app/components/workflow/utils' import VarFullPathPanel from './var-full-path-panel' -import { Type } from '../../../llm/types' const TRIGGER_DEFAULT_WIDTH = 227 @@ -262,7 +261,7 @@ const VarReferencePicker: FC = ({ ) } @@ -270,7 +269,7 @@ const VarReferencePicker: FC = ({ return t('workflow.errorMsg.invalidVariable') return null - }, [isValidVar, isShowAPart, hasValue, t, outputVarNode?.title, outputVarNode?.type, value]) + }, [isValidVar, isShowAPart, hasValue, t, outputVarNode?.title, outputVarNode?.type, value, type]) return (