FEAT: NEW WORKFLOW ENGINE (#3160)

Co-authored-by: Joel <iamjoel007@gmail.com>
Co-authored-by: Yeuoly <admin@srmxy.cn>
Co-authored-by: JzoNg <jzongcode@gmail.com>
Co-authored-by: StyleZhang <jasonapring2015@outlook.com>
Co-authored-by: jyong <jyong@dify.ai>
Co-authored-by: nite-knite <nkCoding@gmail.com>
Co-authored-by: jyong <718720800@qq.com>
This commit is contained in:
takatost
2024-04-08 18:51:46 +08:00
committed by GitHub
parent 2fb9850af5
commit 7753ba2d37
1161 changed files with 103836 additions and 10327 deletions

View File

@@ -0,0 +1,185 @@
'use client'
import type { FC } from 'react'
import React, { useCallback } from 'react'
import { useTranslation } from 'react-i18next'
import produce from 'immer'
import type { PromptItem, ValueSelector, Var } from '../../../types'
import { PromptRole } from '../../../types'
import useAvailableVarList from '../../_base/hooks/use-available-var-list'
import Editor from '@/app/components/workflow/nodes/_base/components/prompt/editor'
import AddButton from '@/app/components/workflow/nodes/_base/components/add-button'
import TypeSelector from '@/app/components/workflow/nodes/_base/components/selector'
import TooltipPlus from '@/app/components/base/tooltip-plus'
import { HelpCircle } from '@/app/components/base/icons/src/vender/line/general'
const i18nPrefix = 'workflow.nodes.llm'
type Props = {
readOnly: boolean
nodeId: string
filterVar: (payload: Var, selector: ValueSelector) => boolean
isChatModel: boolean
isChatApp: boolean
payload: PromptItem | PromptItem[]
onChange: (payload: PromptItem | PromptItem[]) => void
isShowContext: boolean
hasSetBlockStatus: {
context: boolean
history: boolean
query: boolean
}
}
const ConfigPrompt: FC<Props> = ({
readOnly,
nodeId,
filterVar,
isChatModel,
isChatApp,
payload,
onChange,
isShowContext,
hasSetBlockStatus,
}) => {
const { t } = useTranslation()
const {
availableVars,
availableNodes,
} = useAvailableVarList(nodeId, {
onlyLeafNodeVar: false,
filterVar,
})
const handleChatModePromptChange = useCallback((index: number) => {
return (prompt: string) => {
const newPrompt = produce(payload as PromptItem[], (draft) => {
draft[index].text = prompt
})
onChange(newPrompt)
}
}, [onChange, payload])
const roleOptions = [
{
label: 'system',
value: PromptRole.system,
},
{
label: 'user',
value: PromptRole.user,
},
{
label: 'assistant',
value: PromptRole.assistant,
},
]
const handleChatModeMessageRoleChange = useCallback((index: number) => {
return (role: PromptRole) => {
const newPrompt = produce(payload as PromptItem[], (draft) => {
draft[index].role = role
})
onChange(newPrompt)
}
}, [onChange, payload])
const handleAddPrompt = useCallback(() => {
const newPrompt = produce(payload as PromptItem[], (draft) => {
const isLastItemUser = draft[draft.length - 1].role === PromptRole.user
draft.push({ role: isLastItemUser ? PromptRole.assistant : PromptRole.user, text: '' })
})
onChange(newPrompt)
}, [onChange, payload])
const handleRemove = useCallback((index: number) => {
return () => {
const newPrompt = produce(payload as PromptItem[], (draft) => {
draft.splice(index, 1)
})
onChange(newPrompt)
}
}, [onChange, payload])
const handleCompletionPromptChange = useCallback((prompt: string) => {
const newPrompt = produce(payload as PromptItem, (draft) => {
draft.text = prompt
})
onChange(newPrompt)
}, [onChange, payload])
// console.log(getInputVars((payload as PromptItem).text))
return (
<div>
{(isChatModel && Array.isArray(payload))
? (
<div>
<div className='space-y-2'>
{
(payload as PromptItem[]).map((item, index) => {
return (
<Editor
instanceId={`${nodeId}-chat-workflow-llm-prompt-editor-${item.role}-${index}`}
key={index}
title={
<div className='relative left-1 flex items-center'>
<TypeSelector
value={item.role as string}
options={roleOptions}
onChange={handleChatModeMessageRoleChange(index)}
triggerClassName='text-xs font-semibold text-gray-700 uppercase'
itemClassName='text-[13px] font-medium text-gray-700'
/>
<TooltipPlus
popupContent={
<div className='max-w-[180px]'>{t(`${i18nPrefix}.roleDescription.${item.role}`)}</div>
}
>
<HelpCircle className='w-3.5 h-3.5 text-gray-400' />
</TooltipPlus>
</div>
}
value={item.text}
onChange={handleChatModePromptChange(index)}
readOnly={readOnly}
showRemove={(payload as PromptItem[]).length > 1}
onRemove={handleRemove(index)}
isChatModel={isChatModel}
isChatApp={isChatApp}
isShowContext={isShowContext}
hasSetBlockStatus={hasSetBlockStatus}
nodesOutputVars={availableVars}
availableNodes={availableNodes}
/>
)
})
}
</div>
<AddButton
className='mt-2'
text={t(`${i18nPrefix}.addMessage`)}
onClick={handleAddPrompt}
/>
</div>
)
: (
<div>
<Editor
instanceId={`${nodeId}-chat-workflow-llm-prompt-editor`}
title={<span className='capitalize'>{t(`${i18nPrefix}.prompt`)}</span>}
value={(payload as PromptItem).text}
onChange={handleCompletionPromptChange}
readOnly={readOnly}
isChatModel={isChatModel}
isChatApp={isChatApp}
isShowContext={isShowContext}
hasSetBlockStatus={hasSetBlockStatus}
nodesOutputVars={availableVars}
availableNodes={availableNodes}
/>
</div>
)}
</div>
)
}
export default React.memo(ConfigPrompt)

View File

@@ -0,0 +1,65 @@
'use client'
import type { FC } from 'react'
import React, { useCallback } from 'react'
import { useTranslation } from 'react-i18next'
import cn from 'classnames'
import { Resolution } from '@/types/app'
const i18nPrefix = 'workflow.nodes.llm'
type ItemProps = {
title: string
value: Resolution
onSelect: (value: Resolution) => void
isSelected: boolean
}
const Item: FC<ItemProps> = ({ title, value, onSelect, isSelected }) => {
const handleSelect = useCallback(() => {
if (isSelected)
return
onSelect(value)
}, [value, onSelect, isSelected])
return (
<div
className={cn(isSelected ? 'bg-white border-[2px] border-primary-400 shadow-xs' : 'bg-gray-25 border border-gray-100', 'flex items-center h-8 px-3 rounded-xl text-[13px] font-normal text-gray-900 cursor-pointer')}
onClick={handleSelect}
>
{title}
</div>
)
}
type Props = {
value: Resolution
onChange: (value: Resolution) => void
}
const ResolutionPicker: FC<Props> = ({
value,
onChange,
}) => {
const { t } = useTranslation()
return (
<div className='flex items-center'>
<div className='mr-2 text-xs font-medium text-gray-500 uppercase'>{t(`${i18nPrefix}.resolution.name`)}</div>
<div className='flex items-center space-x-1'>
<Item
title={t(`${i18nPrefix}.resolution.high`)}
value={Resolution.high}
onSelect={onChange}
isSelected={value === Resolution.high}
/>
<Item
title={t(`${i18nPrefix}.resolution.low`)}
value={Resolution.low}
onSelect={onChange}
isSelected={value === Resolution.low}
/>
</div>
</div>
)
}
export default React.memo(ResolutionPicker)

View File

@@ -0,0 +1,60 @@
import { BlockEnum } from '../../types'
import { type NodeDefault, PromptRole } from '../../types'
import type { LLMNodeType } from './types'
import type { PromptItem } from '@/models/debug'
import { ALL_CHAT_AVAILABLE_BLOCKS, ALL_COMPLETION_AVAILABLE_BLOCKS } from '@/app/components/workflow/constants'
const i18nPrefix = 'workflow.errorMsg'
const nodeDefault: NodeDefault<LLMNodeType> = {
defaultValue: {
model: {
provider: '',
name: '',
mode: 'chat',
completion_params: {
temperature: 0.7,
},
},
variables: [],
prompt_template: [{
role: PromptRole.system,
text: '',
}],
context: {
enabled: false,
variable_selector: [],
},
vision: {
enabled: false,
},
},
getAvailablePrevNodes(isChatMode: boolean) {
const nodes = isChatMode
? ALL_CHAT_AVAILABLE_BLOCKS
: ALL_COMPLETION_AVAILABLE_BLOCKS.filter(type => type !== BlockEnum.End)
return nodes
},
getAvailableNextNodes(isChatMode: boolean) {
const nodes = isChatMode ? ALL_CHAT_AVAILABLE_BLOCKS : ALL_COMPLETION_AVAILABLE_BLOCKS
return nodes
},
checkValid(payload: LLMNodeType, t: any) {
let errorMessages = ''
if (!errorMessages && !payload.model.provider)
errorMessages = t(`${i18nPrefix}.fieldRequired`, { field: t(`${i18nPrefix}.fields.model`) })
if (!errorMessages && !payload.memory) {
const isChatModel = payload.model.mode === 'chat'
const isPromptyEmpty = isChatModel ? !(payload.prompt_template as PromptItem[]).some(t => t.text !== '') : (payload.prompt_template as PromptItem).text === ''
if (isPromptyEmpty)
errorMessages = t(`${i18nPrefix}.fieldRequired`, { field: t('workflow.nodes.llm.prompt') })
}
return {
isValid: !errorMessages,
errorMessage: errorMessages,
}
},
}
export default nodeDefault

View File

@@ -0,0 +1,35 @@
import type { FC } from 'react'
import React from 'react'
import type { LLMNodeType } from './types'
import {
useTextGenerationCurrentProviderAndModelAndModelList,
} from '@/app/components/header/account-setting/model-provider-page/hooks'
import ModelSelector from '@/app/components/header/account-setting/model-provider-page/model-selector'
import type { NodeProps } from '@/app/components/workflow/types'
const Node: FC<NodeProps<LLMNodeType>> = ({
data,
}) => {
const { provider, name: modelId } = data.model || {}
const {
textGenerationModelList,
} = useTextGenerationCurrentProviderAndModelAndModelList()
const hasSetModel = provider && modelId
if (!hasSetModel)
return null
return (
<div className='mb-1 px-3 py-1'>
{hasSetModel && (
<ModelSelector
defaultModel={{ provider, model: modelId }}
modelList={textGenerationModelList}
readonly
/>
)}
</div>
)
}
export default React.memo(Node)

View File

@@ -0,0 +1,279 @@
import type { FC } from 'react'
import React, { useMemo } from 'react'
import { useStoreApi } from 'reactflow'
import { useTranslation } from 'react-i18next'
import { BlockEnum } from '../../types'
import MemoryConfig from '../_base/components/memory-config'
import VarReferencePicker from '../_base/components/variable/var-reference-picker'
import useConfig from './use-config'
import ResolutionPicker from './components/resolution-picker'
import type { LLMNodeType } from './types'
import ConfigPrompt from './components/config-prompt'
import Field from '@/app/components/workflow/nodes/_base/components/field'
import Split from '@/app/components/workflow/nodes/_base/components/split'
import ModelParameterModal from '@/app/components/header/account-setting/model-provider-page/model-parameter-modal'
import OutputVars, { VarItem } from '@/app/components/workflow/nodes/_base/components/output-vars'
import { Resolution } from '@/types/app'
import { InputVarType, type NodePanelProps } from '@/app/components/workflow/types'
import BeforeRunForm from '@/app/components/workflow/nodes/_base/components/before-run-form'
import type { Props as FormProps } from '@/app/components/workflow/nodes/_base/components/before-run-form/form'
import ResultPanel from '@/app/components/workflow/run/result-panel'
import TooltipPlus from '@/app/components/base/tooltip-plus'
import { HelpCircle } from '@/app/components/base/icons/src/vender/line/general'
import Editor from '@/app/components/workflow/nodes/_base/components/prompt/editor'
const i18nPrefix = 'workflow.nodes.llm'
const Panel: FC<NodePanelProps<LLMNodeType>> = ({
id,
data,
}) => {
const { t } = useTranslation()
const store = useStoreApi()
const startNode = useMemo(() => {
const nodes = store.getState().getNodes()
return nodes.find(node => node.data.type === BlockEnum.Start)
}, [store])
const {
readOnly,
inputs,
isChatModel,
isChatMode,
isCompletionModel,
shouldShowContextTip,
isShowVisionConfig,
handleModelChanged,
hasSetBlockStatus,
handleCompletionParamsChange,
handleContextVarChange,
filterInputVar,
filterVar,
handlePromptChange,
handleMemoryChange,
handleVisionResolutionChange,
isShowSingleRun,
hideSingleRun,
inputVarValues,
setInputVarValues,
visionFiles,
setVisionFiles,
contexts,
setContexts,
runningStatus,
handleRun,
handleStop,
varInputs,
runResult,
} = useConfig(id, data)
const model = inputs.model
const singleRunForms = (() => {
const forms: FormProps[] = []
if (varInputs.length > 0) {
forms.push(
{
label: t(`${i18nPrefix}.singleRun.variable`)!,
inputs: varInputs,
values: inputVarValues,
onChange: setInputVarValues,
},
)
}
if (inputs.context?.variable_selector && inputs.context?.variable_selector.length > 0) {
forms.push(
{
label: t(`${i18nPrefix}.context`)!,
inputs: [{
label: '',
variable: '#context#',
type: InputVarType.contexts,
required: false,
}],
values: { '#context#': contexts },
onChange: keyValue => setContexts((keyValue as any)['#context#']),
},
)
}
if (isShowVisionConfig) {
forms.push(
{
label: t(`${i18nPrefix}.vision`)!,
inputs: [{
label: t(`${i18nPrefix}.files`)!,
variable: '#files#',
type: InputVarType.files,
required: false,
}],
values: { '#files#': visionFiles },
onChange: keyValue => setVisionFiles((keyValue as any)['#files#']),
},
)
}
return forms
})()
return (
<div className='mt-2'>
<div className='px-4 pb-4 space-y-4'>
<Field
title={t(`${i18nPrefix}.model`)}
>
<ModelParameterModal
popupClassName='!w-[387px]'
isInWorkflow
isAdvancedMode={true}
mode={model?.mode}
provider={model?.provider}
completionParams={model?.completion_params}
modelId={model?.name}
setModel={handleModelChanged}
onCompletionParamsChange={handleCompletionParamsChange}
hideDebugWithMultipleModel
debugWithMultipleModel={false}
readonly={readOnly}
/>
</Field>
{/* knowledge */}
<Field
title={t(`${i18nPrefix}.context`)}
tooltip={t(`${i18nPrefix}.contextTooltip`)!}
>
<>
<VarReferencePicker
readonly={readOnly}
nodeId={id}
isShowNodeName
value={inputs.context?.variable_selector || []}
onChange={handleContextVarChange}
filterVar={filterVar}
/>
{shouldShowContextTip && (
<div className='leading-[18px] text-xs font-normal text-[#DC6803]'>{t(`${i18nPrefix}.notSetContextInPromptTip`)}</div>
)}
</>
</Field>
{/* Prompt */}
{model.name && (
<ConfigPrompt
readOnly={readOnly}
nodeId={id}
filterVar={filterInputVar}
isChatModel={isChatModel}
isChatApp={isChatMode}
isShowContext
payload={inputs.prompt_template}
onChange={handlePromptChange}
hasSetBlockStatus={hasSetBlockStatus}
/>
)}
{/* Memory put place examples. */}
{isChatMode && isChatModel && !!inputs.memory && (
<div className='mt-4'>
<div className='flex justify-between items-center h-8 pl-3 pr-2 rounded-lg bg-gray-100'>
<div className='flex items-center space-x-1'>
<div className='text-xs font-semibold text-gray-700 uppercase'>{t('workflow.nodes.common.memories.title')}</div>
<TooltipPlus
popupContent={t('workflow.nodes.common.memories.tip')}
>
<HelpCircle className='w-3.5 h-3.5 text-gray-400' />
</TooltipPlus>
</div>
<div className='flex items-center h-[18px] px-1 rounded-[5px] border border-black/8 text-xs font-semibold text-gray-500 uppercase'>{t('workflow.nodes.common.memories.builtIn')}</div>
</div>
{/* Readonly User Query */}
<div className='mt-4'>
<Editor
title={<div className='flex items-center space-x-1'>
<div className='text-xs font-semibold text-gray-700 uppercase'>user</div>
<TooltipPlus
popupContent={
<div className='max-w-[180px]'>{t('workflow.nodes.llm.roleDescription.user')}</div>
}
>
<HelpCircle className='w-3.5 h-3.5 text-gray-400' />
</TooltipPlus>
</div>}
value={'{{#sys.query#}}'}
onChange={() => { }}
readOnly
isShowContext={false}
isChatApp
isChatModel={false}
hasSetBlockStatus={{
query: false,
history: true,
context: true,
}}
availableNodes={[startNode!]}
/>
</div>
</div>
)}
{/* Memory */}
{isChatMode && (
<>
<Split />
<MemoryConfig
readonly={readOnly}
config={{ data: inputs.memory }}
onChange={handleMemoryChange}
canSetRoleName={isCompletionModel}
/>
</>
)}
{/* Vision: GPT4-vision and so on */}
{isShowVisionConfig && (
<>
<Split />
<Field
title={t(`${i18nPrefix}.vision`)}
tooltip={t('appDebug.vision.description')!}
operations={
<ResolutionPicker
value={inputs.vision.configs?.detail || Resolution.high}
onChange={handleVisionResolutionChange}
/>
}
/>
</>
)}
</div>
<Split />
<div className='px-4 pt-4 pb-2'>
<OutputVars>
<>
<VarItem
name='text'
type='string'
description={t(`${i18nPrefix}.outputVars.output`)}
/>
</>
</OutputVars>
</div>
{isShowSingleRun && (
<BeforeRunForm
nodeName={inputs.title}
onHide={hideSingleRun}
forms={singleRunForms}
runningStatus={runningStatus}
onRun={handleRun}
onStop={handleStop}
result={<ResultPanel {...runResult} showSteps={false} />}
/>
)}
</div>
)
}
export default React.memo(Panel)

View File

@@ -0,0 +1,19 @@
import type { Resolution } from '@/types/app'
import type { CommonNodeType, Memory, ModelConfig, PromptItem, ValueSelector, Variable } from '@/app/components/workflow/types'
export type LLMNodeType = CommonNodeType & {
model: ModelConfig
variables: Variable[]
prompt_template: PromptItem[] | PromptItem
memory?: Memory
context: {
enabled: boolean
variable_selector: ValueSelector
}
vision: {
enabled: boolean
configs?: {
detail: Resolution
}
}
}

View File

@@ -0,0 +1,316 @@
import { useCallback, useEffect, useRef, useState } from 'react'
import produce from 'immer'
import useVarList from '../_base/hooks/use-var-list'
import { VarType } from '../../types'
import type { Memory, ValueSelector, Var } from '../../types'
import { useStore } from '../../store'
import {
useIsChatMode,
useNodesReadOnly,
} from '../../hooks'
import type { LLMNodeType } from './types'
import { Resolution } from '@/types/app'
import { useModelListAndDefaultModelAndCurrentProviderAndModel, useTextGenerationCurrentProviderAndModelAndModelList } from '@/app/components/header/account-setting/model-provider-page/hooks'
import {
ModelFeatureEnum,
ModelTypeEnum,
} from '@/app/components/header/account-setting/model-provider-page/declarations'
import useNodeCrud from '@/app/components/workflow/nodes/_base/hooks/use-node-crud'
import useOneStepRun from '@/app/components/workflow/nodes/_base/hooks/use-one-step-run'
import type { PromptItem } from '@/models/debug'
import { RETRIEVAL_OUTPUT_STRUCT } from '@/app/components/workflow/constants'
import { checkHasContextBlock, checkHasHistoryBlock, checkHasQueryBlock } from '@/app/components/base/prompt-editor/constants'
const useConfig = (id: string, payload: LLMNodeType) => {
const { nodesReadOnly: readOnly } = useNodesReadOnly()
const isChatMode = useIsChatMode()
const defaultConfig = useStore(s => s.nodesDefaultConfigs)[payload.type]
const [defaultRolePrefix, setDefaultRolePrefix] = useState<{ user: string; assistant: string }>({ user: '', assistant: '' })
const { inputs, setInputs: doSetInputs } = useNodeCrud<LLMNodeType>(id, payload)
const setInputs = useCallback((newInputs: LLMNodeType) => {
if (newInputs.memory && !newInputs.memory.role_prefix) {
const newPayload = produce(newInputs, (draft) => {
draft.memory!.role_prefix = defaultRolePrefix
})
doSetInputs(newPayload)
return
}
doSetInputs(newInputs)
}, [doSetInputs, defaultRolePrefix])
const inputRef = useRef(inputs)
useEffect(() => {
inputRef.current = inputs
}, [inputs])
// model
const model = inputs.model
const modelMode = inputs.model?.mode
const isChatModel = modelMode === 'chat'
const isCompletionModel = !isChatModel
const hasSetBlockStatus = (() => {
const promptTemplate = inputs.prompt_template
const hasSetContext = isChatModel ? (promptTemplate as PromptItem[]).some(item => checkHasContextBlock(item.text)) : checkHasContextBlock((promptTemplate as PromptItem).text)
if (!isChatMode) {
return {
history: false,
query: false,
context: hasSetContext,
}
}
if (isChatModel) {
return {
history: false,
query: (promptTemplate as PromptItem[]).some(item => checkHasQueryBlock(item.text)),
context: hasSetContext,
}
}
else {
return {
history: checkHasHistoryBlock((promptTemplate as PromptItem).text),
query: checkHasQueryBlock((promptTemplate as PromptItem).text),
context: hasSetContext,
}
}
})()
const shouldShowContextTip = !hasSetBlockStatus.context && inputs.context.enabled
const appendDefaultPromptConfig = useCallback((draft: LLMNodeType, defaultConfig: any, passInIsChatMode?: boolean) => {
const promptTemplates = defaultConfig.prompt_templates
if (passInIsChatMode === undefined ? isChatModel : passInIsChatMode) {
draft.prompt_template = promptTemplates.chat_model.prompts
}
else {
draft.prompt_template = promptTemplates.completion_model.prompt
setDefaultRolePrefix({
user: promptTemplates.completion_model.conversation_histories_role.user_prefix,
assistant: promptTemplates.completion_model.conversation_histories_role.assistant_prefix,
})
}
}, [isChatModel])
useEffect(() => {
const isReady = defaultConfig && Object.keys(defaultConfig).length > 0
if (isReady && !inputs.prompt_template) {
const newInputs = produce(inputs, (draft) => {
appendDefaultPromptConfig(draft, defaultConfig)
})
setInputs(newInputs)
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [defaultConfig, isChatModel])
const {
currentProvider,
currentModel,
} = useModelListAndDefaultModelAndCurrentProviderAndModel(ModelTypeEnum.textGeneration)
const handleModelChanged = useCallback((model: { provider: string; modelId: string; mode?: string }) => {
const newInputs = produce(inputRef.current, (draft) => {
draft.model.provider = model.provider
draft.model.name = model.modelId
draft.model.mode = model.mode!
const isModeChange = model.mode !== inputRef.current.model.mode
if (isModeChange && defaultConfig && Object.keys(defaultConfig).length > 0)
appendDefaultPromptConfig(draft, defaultConfig, model.mode === 'chat')
})
setInputs(newInputs)
}, [setInputs, defaultConfig, appendDefaultPromptConfig])
useEffect(() => {
if (currentProvider?.provider && currentModel?.model && !model.provider) {
handleModelChanged({
provider: currentProvider?.provider,
modelId: currentModel?.model,
mode: currentModel?.model_properties?.mode as string,
})
}
}, [model.provider, currentProvider, currentModel, handleModelChanged])
const handleCompletionParamsChange = useCallback((newParams: Record<string, any>) => {
const newInputs = produce(inputs, (draft) => {
draft.model.completion_params = newParams
})
setInputs(newInputs)
}, [inputs, setInputs])
const {
currentModel: currModel,
} = useTextGenerationCurrentProviderAndModelAndModelList(
{
provider: model.provider,
model: model.name,
},
)
const isShowVisionConfig = !!currModel?.features?.includes(ModelFeatureEnum.vision)
// variables
const { handleVarListChange, handleAddVariable } = useVarList<LLMNodeType>({
inputs,
setInputs,
})
// context
const handleContextVarChange = useCallback((newVar: ValueSelector | string) => {
const newInputs = produce(inputs, (draft) => {
draft.context.variable_selector = newVar as ValueSelector || []
draft.context.enabled = !!(newVar && newVar.length > 0)
})
setInputs(newInputs)
}, [inputs, setInputs])
const handlePromptChange = useCallback((newPrompt: PromptItem[] | PromptItem) => {
const newInputs = produce(inputs, (draft) => {
draft.prompt_template = newPrompt
})
setInputs(newInputs)
}, [inputs, setInputs])
const handleMemoryChange = useCallback((newMemory?: Memory) => {
const newInputs = produce(inputs, (draft) => {
draft.memory = newMemory
})
setInputs(newInputs)
}, [inputs, setInputs])
const handleVisionResolutionChange = useCallback((newResolution: Resolution) => {
const newInputs = produce(inputs, (draft) => {
if (!draft.vision.configs) {
draft.vision.configs = {
detail: Resolution.high,
}
}
draft.vision.configs.detail = newResolution
})
setInputs(newInputs)
}, [inputs, setInputs])
const filterInputVar = useCallback((varPayload: Var) => {
return [VarType.number, VarType.string].includes(varPayload.type)
}, [])
const filterVar = useCallback((varPayload: Var) => {
return [VarType.arrayObject, VarType.array, VarType.string].includes(varPayload.type)
}, [])
// single run
const {
isShowSingleRun,
hideSingleRun,
getInputVars,
runningStatus,
handleRun,
handleStop,
runInputData,
setRunInputData,
runResult,
} = useOneStepRun<LLMNodeType>({
id,
data: inputs,
defaultRunInputData: {
'#context#': [RETRIEVAL_OUTPUT_STRUCT],
'#files#': [],
},
})
// const handleRun = (submitData: Record<string, any>) => {
// console.log(submitData)
// const res = produce(submitData, (draft) => {
// debugger
// if (draft.contexts) {
// draft['#context#'] = draft.contexts
// delete draft.contexts
// }
// if (draft.visionFiles) {
// draft['#files#'] = draft.visionFiles
// delete draft.visionFiles
// }
// })
// doHandleRun(res)
// }
const inputVarValues = (() => {
const vars: Record<string, any> = {}
Object.keys(runInputData)
.filter(key => !['#context#', '#files#'].includes(key))
.forEach((key) => {
vars[key] = runInputData[key]
})
return vars
})()
const setInputVarValues = useCallback((newPayload: Record<string, any>) => {
const newVars = {
...newPayload,
'#context#': runInputData['#context#'],
'#files#': runInputData['#files#'],
}
setRunInputData(newVars)
}, [runInputData, setRunInputData])
const contexts = runInputData['#context#']
const setContexts = useCallback((newContexts: string[]) => {
setRunInputData({
...runInputData,
'#context#': newContexts,
})
}, [runInputData, setRunInputData])
const visionFiles = runInputData['#files#']
const setVisionFiles = useCallback((newFiles: any[]) => {
setRunInputData({
...runInputData,
'#files#': newFiles,
})
}, [runInputData, setRunInputData])
const allVarStrArr = (() => {
const arr = isChatModel ? (inputs.prompt_template as PromptItem[]).map(item => item.text) : [(inputs.prompt_template as PromptItem).text]
if (isChatMode && isChatModel && !!inputs.memory)
arr.push('{{#sys.query#}}')
return arr
})()
const varInputs = getInputVars(allVarStrArr)
return {
readOnly,
isChatMode,
inputs,
isChatModel,
isCompletionModel,
hasSetBlockStatus,
shouldShowContextTip,
isShowVisionConfig,
handleModelChanged,
handleCompletionParamsChange,
handleVarListChange,
handleAddVariable,
handleContextVarChange,
filterInputVar,
filterVar,
handlePromptChange,
handleMemoryChange,
handleVisionResolutionChange,
isShowSingleRun,
hideSingleRun,
inputVarValues,
setInputVarValues,
visionFiles,
setVisionFiles,
contexts,
setContexts,
varInputs,
runningStatus,
handleRun,
handleStop,
runResult,
}
}
export default useConfig

View File

@@ -0,0 +1,5 @@
import type { LLMNodeType } from './types'
export const checkNodeValid = (payload: LLMNodeType) => {
return true
}