This commit is contained in:
jZonG 2025-04-28 15:46:01 +08:00
parent 9afcdfa8d4
commit b288258e4d
8 changed files with 258 additions and 101 deletions

View File

@ -0,0 +1,128 @@
import { useState } from 'react'
import { useTranslation } from 'react-i18next'
import {
RiArrowRightSLine,
// RiErrorWarningFill,
// RiLoader2Line,
} from '@remixicon/react'
import { useStore } from '../store'
// import { BlockEnum } from '../types'
// import Button from '@/app/components/base/button'
// import ActionButton from '@/app/components/base/action-button'
// import Tooltip from '@/app/components/base/tooltip'
// import BlockIcon from '@/app/components/workflow/block-icon'
import {
// BubbleX,
Env,
} from '@/app/components/base/icons/src/vender/line/others'
// import { Variable02 } from '@/app/components/base/icons/src/vender/solid/development'
// import useCurrentVars from '../hooks/use-inspect-vars-crud'
import type { currentVarType } from './panel'
import cn from '@/utils/classnames'
type Props = {
isEnv?: boolean
isChatVar?: boolean
isSystem?: boolean
currentVar?: currentVarType
handleSelect: (state: any) => void
}
const Group = ({
isEnv,
isChatVar,
isSystem,
currentVar,
handleSelect,
}: Props) => {
const { t } = useTranslation()
const environmentVariables = useStore(s => s.environmentVariables)
// const {
// conversationVars,
// systemVars,
// nodesWithInspectVars,
// } = useCurrentVars()
const [isCollapsed, setIsCollapsed] = useState(false)
const handleSelectVar = (varItem: any, type?: string) => {
if (type === 'env') {
handleSelect({
nodeId: 'env',
nodeTitle: 'env',
nodeType: 'env',
var: {
...varItem,
type: 'env',
...(varItem.value_type === 'secret' ? { value: '******************' } : {}),
},
})
return
}
if (type === 'chat') {
handleSelect({
nodeId: 'conversation',
nodeTitle: 'conversation',
nodeType: 'conversation',
var: {
...varItem,
type: 'conversation',
},
})
return
}
if (type === 'system') {
handleSelect({
nodeId: 'sys',
nodeTitle: 'sys',
nodeType: 'sys',
var: varItem,
})
return
}
handleSelect({
nodeId: varItem.nodeId,
nodeTitle: varItem.nodeTitle,
nodeType: varItem.nodeType,
var: varItem.var,
})
}
return (
<div className='p-0.5'>
{/* node item */}
<div className='flex h-6 items-center gap-0.5'>
<RiArrowRightSLine className={cn('h-3 w-3 text-text-tertiary', !isCollapsed && 'rotate-90')} />
<div className='flex grow cursor-pointer items-center gap-1' onClick={() => setIsCollapsed(!isCollapsed)}>
<div className='system-xs-medium-uppercase truncate text-text-tertiary'>
{isEnv && t('workflow.debug.variableInspect.envNode')}
{isChatVar && t('workflow.debug.variableInspect.chatNode')}
{isSystem && t('workflow.debug.variableInspect.systemNode')}
</div>
</div>
</div>
{/* var item list */}
{!isCollapsed && (
<div className='px-0.5'>
{environmentVariables.length > 0 && environmentVariables.map(env => (
<div
key={env.id}
className={cn(
'relative flex cursor-pointer items-center gap-1 rounded-md px-3 py-1 hover:bg-state-base-hover',
env.id === currentVar?.var.id && 'bg-state-base-hover-alt hover:bg-state-base-hover-alt',
)}
onClick={() => handleSelectVar(env, 'env')}
>
<Env className='h-4 w-4 shrink-0 text-util-colors-violet-violet-600' />
<div className='system-sm-medium grow truncate text-text-secondary'>{env.name}</div>
<div className='system-xs-regular shrink-0 text-text-tertiary'>{env.value_type}</div>
</div>
))}
</div>
)}
</div>
)
}
export default Group

View File

@ -5,27 +5,35 @@ import {
RiErrorWarningFill,
RiLoader2Line,
} from '@remixicon/react'
// import { useStore } from '../store'
import { useStore } from '../store'
import { BlockEnum } from '../types'
import Button from '@/app/components/base/button'
// import ActionButton from '@/app/components/base/action-button'
// import Tooltip from '@/app/components/base/tooltip'
// import ActionButton from '@/app/components/base/action-button'
// import Tooltip from '@/app/components/base/tooltip'
import BlockIcon from '@/app/components/workflow/block-icon'
import { BubbleX, Env } from '@/app/components/base/icons/src/vender/line/others'
import { BubbleX } from '@/app/components/base/icons/src/vender/line/others'
import { Variable02 } from '@/app/components/base/icons/src/vender/solid/development'
import Group from './group'
import useCurrentVars from '../hooks/use-inspect-vars-crud'
import type { currentVarType } from './panel'
import cn from '@/utils/classnames'
type Props = {
handleMenuClick: (state: any) => void
currentNodeVar?: currentVarType
handleVarSelect: (state: any) => void
}
const Left = ({ handleMenuClick }: Props) => {
const Left = ({
currentNodeVar,
handleVarSelect,
}: Props) => {
const { t } = useTranslation()
// const bottomPanelWidth = useStore(s => s.bottomPanelWidth)
// const setShowVariableInspectPanel = useStore(s => s.setShowVariableInspectPanel)
const environmentVariables = useStore(s => s.environmentVariables)
const {
conversationVars,
systemVars,
nodesWithInspectVars,
deleteAllInspectorVars,
} = useCurrentVars()
@ -42,29 +50,13 @@ const Left = ({ handleMenuClick }: Props) => {
{/* content */}
<div className='grow overflow-y-auto py-1'>
{/* group ENV */}
<div className='p-0.5'>
{/* node item */}
<div className='flex h-6 items-center gap-0.5'>
<RiArrowRightSLine className='h-3 w-3 rotate-90 text-text-tertiary' />
<div className='flex grow cursor-pointer items-center gap-1'>
<div className='system-xs-medium-uppercase truncate text-text-tertiary'>{t('workflow.env.envPanelTitle')}</div>
</div>
</div>
{/* var item list */}
<div className='px-0.5'>
<div className={cn('relative flex cursor-pointer items-center gap-1 rounded-md px-3 py-1 hover:bg-state-base-hover')} onClick={handleMenuClick}>
<Env className='h-4 w-4 shrink-0 text-util-colors-violet-violet-600' />
<div className='system-sm-medium grow truncate text-text-secondary'>SECRET_KEY</div>
<div className='system-xs-regular shrink-0 text-text-tertiary'>string</div>
</div>
<div className={cn('relative flex cursor-pointer items-center gap-1 rounded-md px-3 py-1 hover:bg-state-base-hover', selectedNode && 'bg-state-base-hover-alt hover:bg-state-base-hover-alt')}>
{selectedNode && <span className='absolute left-1.5 top-[10.5px] h-[3px] w-[3px] rounded-full bg-text-accent-secondary'></span>}
<Env className='h-4 w-4 shrink-0 text-util-colors-violet-violet-600' />
<div className='system-sm-medium grow truncate text-text-secondary'>PORT</div>
<div className='system-xs-regular shrink-0 text-text-tertiary'>number</div>
</div>
</div>
</div>
{environmentVariables.length > 0 && (
<Group
isEnv
currentVar={currentNodeVar}
handleSelect={handleVarSelect}
/>
)}
{/* group CHAT VAR */}
<div className='p-0.5'>
{/* node item */}

View File

@ -10,14 +10,23 @@ import Empty from './empty'
import Left from './left'
import Right from './right'
import ActionButton from '@/app/components/base/action-button'
import type { VarInInspect } from '@/types/workflow'
import cn from '@/utils/classnames'
export type currentVarType = {
nodeId: string
nodeTitle: string
nodeType: string
var: VarInInspect
}
const Panel: FC = () => {
const { t } = useTranslation()
const bottomPanelWidth = useStore(s => s.bottomPanelWidth)
const setShowVariableInspectPanel = useStore(s => s.setShowVariableInspectPanel)
const [showLeftPanel, setShowLeftPanel] = useState(true)
const [currentNodeVar, setCurrentNodeVar] = useState<currentVarType>()
const environmentVariables = useStore(s => s.environmentVariables)
const {
@ -60,11 +69,17 @@ const Panel: FC = () => {
: 'block',
)}
>
<Left handleMenuClick={setShowLeftPanel} />
<Left
currentNodeVar={currentNodeVar}
handleVarSelect={setCurrentNodeVar}
/>
</div>
{/* right */}
<div className='w-0 grow'>
<Right handleOpenMenu={() => setShowLeftPanel(true)} />
<Right
currentNodeVar={currentNodeVar}
handleOpenMenu={() => setShowLeftPanel(true)}
/>
</div>
</div>
)

View File

@ -6,7 +6,7 @@ import {
RiMenuLine,
} from '@remixicon/react'
import { useStore } from '../store'
import { BlockEnum } from '../types'
import type { BlockEnum } from '../types'
import useCurrentVars from '../hooks/use-inspect-vars-crud'
import Empty from './empty'
import ValueContent from './value-content'
@ -16,43 +16,29 @@ import CopyFeedback from '@/app/components/base/copy-feedback'
import Tooltip from '@/app/components/base/tooltip'
import BlockIcon from '@/app/components/workflow/block-icon'
import { BubbleX, Env } from '@/app/components/base/icons/src/vender/line/others'
import type { currentVarType } from './panel'
import cn from '@/utils/classnames'
export const currentVar = {
id: 'var-jfkldjjfkldaf-dfhekdfj',
type: 'node',
// type: 'conversation',
// type: 'environment',
name: 'out_put',
var_type: 'string',
// var_type: 'number',
// var_type: 'object',
// var_type: 'array[string]',
// var_type: 'array[number]',
// var_type: 'array[object]',
// var_type: 'file',
// var_type: 'array[file]',
value: 'tuituitui',
edited: true,
}
type Props = {
currentNodeVar?: currentVarType
handleOpenMenu: () => void
}
const Right = ({ handleOpenMenu }: Props) => {
const Right = ({
currentNodeVar,
handleOpenMenu,
}: Props) => {
const { t } = useTranslation()
const bottomPanelWidth = useStore(s => s.bottomPanelWidth)
const setShowVariableInspectPanel = useStore(s => s.setShowVariableInspectPanel)
const current = currentVar
const {
resetToLastRunVar,
} = useCurrentVars()
const resetValue = () => {
resetToLastRunVar('node_id', current.name)
if (!currentNodeVar) return
resetToLastRunVar(currentNodeVar.nodeId, currentNodeVar.var.name)
}
return (
@ -65,48 +51,48 @@ const Right = ({ handleOpenMenu }: Props) => {
</ActionButton>
)}
<div className='flex w-0 grow items-center gap-1'>
{current && (
{currentNodeVar && (
<>
{current.type === 'environment' && (
{currentNodeVar.nodeType === 'env' && (
<Env className='h-4 w-4 shrink-0 text-util-colors-violet-violet-600' />
)}
{current.type === 'conversation' && (
{currentNodeVar.nodeType === 'conversation' && (
<BubbleX className='h-4 w-4 shrink-0 text-util-colors-teal-teal-700' />
)}
{current.type === 'node' && (
{currentNodeVar.nodeType !== 'env' && currentNodeVar.nodeType !== 'conversation' && currentNodeVar.nodeType !== 'sys' && (
<>
<BlockIcon
className='shrink-0'
type={BlockEnum.LLM}
type={currentNodeVar.nodeType as BlockEnum}
size='xs'
/>
<div className='system-sm-regular shrink-0 text-text-secondary'>LLM</div>
<div className='system-sm-regular shrink-0 text-text-secondary'>{currentNodeVar.nodeTitle}</div>
<div className='system-sm-regular shrink-0 text-text-quaternary'>/</div>
</>
)}
<div title={current.name} className='system-sm-semibold truncate text-text-secondary'>{current.name}</div>
<div className='system-xs-medium ml-1 shrink-0 text-text-tertiary'>{current.var_type}</div>
<div title={currentNodeVar.var.name} className='system-sm-semibold truncate text-text-secondary'>{currentNodeVar.var.name}</div>
<div className='system-xs-medium ml-1 shrink-0 text-text-tertiary'>{currentNodeVar.var.value_type}</div>
</>
)}
</div>
<div className='flex shrink-0 items-center gap-1'>
{current && (
{currentNodeVar && (
<>
{current.edited && (
{currentNodeVar.var.edited && (
<Badge>
<span className='ml-[2.5px] mr-[4.5px] h-[3px] w-[3px] rounded bg-text-accent-secondary'></span>
<span className='system-2xs-semibold-uupercase'>{t('workflow.debug.variableInspect.edited')}</span>
</Badge>
)}
{current.edited && (
{currentNodeVar.var.edited && (
<Tooltip popupContent={t('workflow.debug.variableInspect.reset')}>
<ActionButton onClick={resetValue}>
<RiArrowGoBackLine className='h-4 w-4' />
</ActionButton>
</Tooltip>
)}
{(current.type !== 'environment' || current.var_type !== 'secret') && (
<CopyFeedback content={current.value ? JSON.stringify(current.value) : ''} />
{currentNodeVar.var.value_type !== 'secret' && (
<CopyFeedback content={currentNodeVar.var.value ? JSON.stringify(currentNodeVar.var.value) : ''} />
)}
</>
)}
@ -117,8 +103,8 @@ const Right = ({ handleOpenMenu }: Props) => {
</div>
{/* content */}
<div className='grow p-2'>
{!current && <Empty />}
{current && <ValueContent />}
{!currentNodeVar && <Empty />}
{currentNodeVar && <ValueContent currentVar={currentNodeVar.var} />}
</div>
</div>
)

View File

@ -19,22 +19,24 @@ import { JSON_SCHEMA_MAX_DEPTH } from '@/config'
import { TransferMethod } from '@/types/app'
import { FILE_EXTS } from '@/app/components/base/prompt-editor/constants'
import { SupportUploadFileTypes } from '@/app/components/workflow/types'
import type { VarInInspect } from '@/types/workflow'
import { VarInInspectType } from '@/types/workflow'
import cn from '@/utils/classnames'
export const currentVar = {
export const MOCK_DATA = {
id: 'var-jfkldjjfkldaf-dfhekdfj',
type: 'node',
// type: 'conversation',
// type: 'environment',
name: 'out_put',
// var_type: 'string',
// var_type: 'number',
// var_type: 'object',
// var_type: 'array[string]',
// var_type: 'array[number]',
// var_type: 'array[object]',
// var_type: 'file',
var_type: 'array[file]',
// value_type: 'string',
// value_type: 'number',
// value_type: 'object',
// value_type: 'array[string]',
// value_type: 'array[number]',
// value_type: 'array[object]',
// value_type: 'file',
value_type: 'array[file]',
// value: 'tuituitui',
// value: ['aaa', 'bbb', 'ccc'],
// value: {
@ -46,34 +48,62 @@ export const currentVar = {
edited: true,
}
const ValueContent = () => {
const current = currentVar
type Props = {
currentVar: VarInInspect
}
const ValueContent = ({
// currentVar = MOCK_DATA as any, // TODO remove this line
currentVar,
}: Props) => {
const contentContainerRef = useRef<HTMLDivElement>(null)
const errorMessageRef = useRef<HTMLDivElement>(null)
const [editorHeight, setEditorHeight] = useState(0)
const showTextEditor = current.var_type === 'secret' || current.var_type === 'string' || current.var_type === 'number'
const showJSONEditor = current.var_type === 'object' || current.var_type === 'array[string]' || current.var_type === 'array[number]' || current.var_type === 'array[object]'
const showFileEditor = current.var_type === 'file' || current.var_type === 'array[file]'
const showTextEditor = currentVar.value_type === 'secret' || currentVar.value_type === 'string' || currentVar.value_type === 'number'
const showJSONEditor = currentVar.value_type === 'object' || currentVar.value_type === 'array[string]' || currentVar.value_type === 'array[number]' || currentVar.value_type === 'array[object]'
const showFileEditor = currentVar.value_type === 'file' || currentVar.value_type === 'array[file]'
const [value, setValue] = useState<any>(current.value ? JSON.stringify(current.value) : '')
const [jsonSchema, setJsonSchema] = useState(current.value || null)
const [json, setJson] = useState(JSON.stringify(jsonSchema, null, 2))
const [value, setValue] = useState<any>()
const [jsonSchema, setJsonSchema] = useState()
const [json, setJson] = useState('')
const [parseError, setParseError] = useState<Error | null>(null)
const [validationError, setValidationError] = useState<string>('')
const fileFeature = useFeatures(s => s.features.file)
const [fileValue, setFileValue] = useState<any>(
current.var_type === 'array[file]'
? current.value || []
: current.value
? [current.value]
currentVar.value_type === 'array[file]'
? currentVar.value || []
: currentVar.value
? [currentVar.value]
: [],
)
// update default value when id changed
useEffect(() => {
if (showTextEditor) {
if (!currentVar.value)
return setValue('')
if (currentVar.value_type === 'number')
return setValue(JSON.stringify(currentVar.value))
setValue(currentVar.value)
}
if (showJSONEditor) {
setJsonSchema(currentVar.value || null)
setJson(currentVar.value ? JSON.stringify(currentVar.value, null, 2) : '')
}
if (showFileEditor) {
setFileValue(currentVar.value_type === 'array[file]'
? currentVar.value || []
: currentVar.value
? [currentVar.value]
: [])
}
}, [currentVar, showTextEditor, showJSONEditor, showFileEditor])
const handleTextChange = (value: string) => {
if (current.var_type === 'string')
if (currentVar.value_type === 'string')
setValue(value)
if (current.var_type === 'number') {
if (currentVar.value_type === 'number') {
if (/^-?\d+(\.)?(\d+)?$/.test(value))
setValue(value)
}
@ -119,7 +149,7 @@ const ValueContent = () => {
const handleEditorChange = (value: string) => {
setJson(value)
if (jsonValueValidate(value, current.var_type)) {
if (jsonValueValidate(value, currentVar.value_type)) {
const parsed = JSON.parse(value)
setJsonSchema(parsed)
// TODO call api of value update
@ -131,10 +161,10 @@ const ValueContent = () => {
setFileValue(value)
// TODO check every file upload progress
// invoke update api after every file uploaded
if (current.var_type === 'file') {
if (currentVar.value_type === 'file') {
// TODO call api of value update
}
if (current.var_type === 'array[file]') {
if (currentVar.value_type === 'array[file]') {
// TODO call api of value update
}
}
@ -164,8 +194,8 @@ const ValueContent = () => {
<div className={cn('grow')} style={{ height: `${editorHeight}px` }}>
{showTextEditor && (
<Textarea
readOnly={current.type === 'environment'}
disabled={current.type === 'environment'}
readOnly={currentVar.type === VarInInspectType.environment}
disabled={currentVar.type === VarInInspectType.environment}
className='h-full'
value={value as any}
onChange={debounce(e => handleTextChange(e.target.value))}
@ -198,7 +228,7 @@ const ValueContent = () => {
...FILE_EXTS[SupportUploadFileTypes.video],
],
allowed_file_upload_methods: [TransferMethod.local_file, TransferMethod.remote_url],
number_limits: current.var_type === 'file' ? 1 : (fileFeature as any).fileUploadConfig?.workflow_file_upload_limit || 5,
number_limits: currentVar.value_type === 'file' ? 1 : (fileFeature as any).fileUploadConfig?.workflow_file_upload_limit || 5,
fileUploadConfig: (fileFeature as any).fileUploadConfig,
}}
/>

View File

@ -930,6 +930,9 @@ const translation = {
cached: 'View cached variables',
clear: 'Clear',
},
envNode: 'Environment',
chatNode: 'Conversation',
systemNode: 'System',
},
},
}

View File

@ -931,6 +931,9 @@ const translation = {
cached: '查看缓存',
clear: '清除',
},
envNode: '环境变量',
chatNode: '会话变量',
systemNode: '系统变量',
},
},
}

View File

@ -374,7 +374,7 @@ export type NodeRunResult = NodeTracing
// Var Inspect
export enum VarInInspectType {
conversation = 'conversation',
environment = 'environment',
environment = 'env',
node = 'node',
system = 'sys',
}