feat: llm support jinja fe (#4260)
This commit is contained in:
@@ -3,33 +3,28 @@ import type { FC } from 'react'
|
||||
import React, { useEffect, useRef, useState } from 'react'
|
||||
import { useBoolean } from 'ahooks'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import cn from 'classnames'
|
||||
import type { Props as EditorProps } from '.'
|
||||
import Editor from '.'
|
||||
import VarReferenceVars from '@/app/components/workflow/nodes/_base/components/variable/var-reference-vars'
|
||||
import useAvailableVarList from '@/app/components/workflow/nodes/_base/hooks/use-available-var-list'
|
||||
import type { Variable } from '@/app/components/workflow/types'
|
||||
import type { NodeOutPutVar, Variable } from '@/app/components/workflow/types'
|
||||
|
||||
const TO_WINDOW_OFFSET = 8
|
||||
|
||||
type Props = {
|
||||
nodeId: string
|
||||
availableVars: NodeOutPutVar[]
|
||||
varList: Variable[]
|
||||
onAddVar: (payload: Variable) => void
|
||||
onAddVar?: (payload: Variable) => void
|
||||
} & EditorProps
|
||||
|
||||
const CodeEditor: FC<Props> = ({
|
||||
nodeId,
|
||||
availableVars,
|
||||
varList,
|
||||
onAddVar,
|
||||
...editorProps
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
const { availableVars } = useAvailableVarList(nodeId, {
|
||||
onlyLeafNodeVar: false,
|
||||
filterVar: () => true,
|
||||
})
|
||||
|
||||
const isLeftBraceRef = useRef(false)
|
||||
|
||||
const editorRef = useRef(null)
|
||||
@@ -76,7 +71,8 @@ const CodeEditor: FC<Props> = ({
|
||||
if (popupPosition.y + height > window.innerHeight - TO_WINDOW_OFFSET)
|
||||
newPopupPosition.y = window.innerHeight - height - TO_WINDOW_OFFSET
|
||||
|
||||
setPopupPosition(newPopupPosition)
|
||||
if (newPopupPosition.x !== popupPosition.x || newPopupPosition.y !== popupPosition.y)
|
||||
setPopupPosition(newPopupPosition)
|
||||
}
|
||||
}, [isShowVarPicker, popupPosition])
|
||||
|
||||
@@ -124,7 +120,7 @@ const CodeEditor: FC<Props> = ({
|
||||
value_selector: varValue,
|
||||
}
|
||||
|
||||
onAddVar(newVar)
|
||||
onAddVar?.(newVar)
|
||||
}
|
||||
const editor: any = editorRef.current
|
||||
const monaco: any = monacoRef.current
|
||||
@@ -143,7 +139,7 @@ const CodeEditor: FC<Props> = ({
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className={cn(editorProps.isExpand && 'h-full')}>
|
||||
<Editor
|
||||
{...editorProps}
|
||||
onMount={onEditorMounted}
|
||||
|
@@ -1,20 +1,23 @@
|
||||
'use client'
|
||||
import type { FC } from 'react'
|
||||
import Editor, { loader } from '@monaco-editor/react'
|
||||
|
||||
import React, { useRef } from 'react'
|
||||
import React, { useEffect, useRef, useState } from 'react'
|
||||
import cn from 'classnames'
|
||||
import Base from '../base'
|
||||
import { CodeLanguage } from '@/app/components/workflow/nodes/code/types'
|
||||
|
||||
import './style.css'
|
||||
|
||||
// load file from local instead of cdn https://github.com/suren-atoyan/monaco-react/issues/482
|
||||
loader.config({ paths: { vs: '/vs' } })
|
||||
|
||||
const CODE_EDITOR_LINE_HEIGHT = 18
|
||||
|
||||
export type Props = {
|
||||
value?: string | object
|
||||
placeholder?: string
|
||||
onChange?: (value: string) => void
|
||||
title: JSX.Element
|
||||
title?: JSX.Element
|
||||
language: CodeLanguage
|
||||
headerRight?: JSX.Element
|
||||
readOnly?: boolean
|
||||
@@ -22,6 +25,8 @@ export type Props = {
|
||||
height?: number
|
||||
isInNode?: boolean
|
||||
onMount?: (editor: any, monaco: any) => void
|
||||
noWrapper?: boolean
|
||||
isExpand?: boolean
|
||||
}
|
||||
|
||||
const languageMap = {
|
||||
@@ -30,11 +35,20 @@ const languageMap = {
|
||||
[CodeLanguage.json]: 'json',
|
||||
}
|
||||
|
||||
const DEFAULT_THEME = {
|
||||
base: 'vs',
|
||||
inherit: true,
|
||||
rules: [],
|
||||
colors: {
|
||||
'editor.background': '#F2F4F7', // #00000000 transparent. But it will has a blue border
|
||||
},
|
||||
}
|
||||
|
||||
const CodeEditor: FC<Props> = ({
|
||||
value = '',
|
||||
placeholder = '',
|
||||
onChange = () => { },
|
||||
title,
|
||||
title = '',
|
||||
headerRight,
|
||||
language,
|
||||
readOnly,
|
||||
@@ -42,16 +56,37 @@ const CodeEditor: FC<Props> = ({
|
||||
height,
|
||||
isInNode,
|
||||
onMount,
|
||||
noWrapper,
|
||||
isExpand,
|
||||
}) => {
|
||||
const [isFocus, setIsFocus] = React.useState(false)
|
||||
const [isMounted, setIsMounted] = React.useState(false)
|
||||
const minHeight = height || 200
|
||||
const [editorContentHeight, setEditorContentHeight] = useState(56)
|
||||
|
||||
const valueRef = useRef(value)
|
||||
useEffect(() => {
|
||||
valueRef.current = value
|
||||
}, [value])
|
||||
|
||||
const editorRef = useRef<any>(null)
|
||||
const resizeEditorToContent = () => {
|
||||
if (editorRef.current) {
|
||||
const contentHeight = editorRef.current.getContentHeight() // Math.max(, minHeight)
|
||||
setEditorContentHeight(contentHeight)
|
||||
}
|
||||
}
|
||||
|
||||
const handleEditorChange = (value: string | undefined) => {
|
||||
onChange(value || '')
|
||||
setTimeout(() => {
|
||||
resizeEditorToContent()
|
||||
}, 10)
|
||||
}
|
||||
|
||||
const editorRef = useRef(null)
|
||||
const handleEditorDidMount = (editor: any, monaco: any) => {
|
||||
editorRef.current = editor
|
||||
resizeEditorToContent()
|
||||
|
||||
editor.onDidFocusEditorText(() => {
|
||||
setIsFocus(true)
|
||||
@@ -60,6 +95,8 @@ const CodeEditor: FC<Props> = ({
|
||||
setIsFocus(false)
|
||||
})
|
||||
|
||||
monaco.editor.defineTheme('default-theme', DEFAULT_THEME)
|
||||
|
||||
monaco.editor.defineTheme('blur-theme', {
|
||||
base: 'vs',
|
||||
inherit: true,
|
||||
@@ -78,7 +115,10 @@ const CodeEditor: FC<Props> = ({
|
||||
},
|
||||
})
|
||||
|
||||
monaco.editor.setTheme('default-theme') // Fix: sometimes not load the default theme
|
||||
|
||||
onMount?.(editor, monaco)
|
||||
setIsMounted(true)
|
||||
}
|
||||
|
||||
const outPutValue = (() => {
|
||||
@@ -92,43 +132,63 @@ const CodeEditor: FC<Props> = ({
|
||||
}
|
||||
})()
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Base
|
||||
className='relative'
|
||||
title={title}
|
||||
const theme = (() => {
|
||||
if (noWrapper)
|
||||
return 'default-theme'
|
||||
|
||||
return isFocus ? 'focus-theme' : 'blur-theme'
|
||||
})()
|
||||
|
||||
const main = (
|
||||
<>
|
||||
{/* https://www.npmjs.com/package/@monaco-editor/react */}
|
||||
<Editor
|
||||
// className='min-h-[100%]' // h-full
|
||||
// language={language === CodeLanguage.javascript ? 'javascript' : 'python'}
|
||||
language={languageMap[language] || 'javascript'}
|
||||
theme={isMounted ? theme : 'default-theme'} // sometimes not load the default theme
|
||||
value={outPutValue}
|
||||
headerRight={headerRight}
|
||||
isFocus={isFocus && !readOnly}
|
||||
minHeight={height || 200}
|
||||
isInNode={isInNode}
|
||||
>
|
||||
<>
|
||||
{/* https://www.npmjs.com/package/@monaco-editor/react */}
|
||||
<Editor
|
||||
className='h-full'
|
||||
// language={language === CodeLanguage.javascript ? 'javascript' : 'python'}
|
||||
language={languageMap[language] || 'javascript'}
|
||||
theme={isFocus ? 'focus-theme' : 'blur-theme'}
|
||||
onChange={handleEditorChange}
|
||||
// https://microsoft.github.io/monaco-editor/typedoc/interfaces/editor.IEditorOptions.html
|
||||
options={{
|
||||
readOnly,
|
||||
domReadOnly: true,
|
||||
quickSuggestions: false,
|
||||
minimap: { enabled: false },
|
||||
lineNumbersMinChars: 1, // would change line num width
|
||||
wordWrap: 'on', // auto line wrap
|
||||
// lineNumbers: (num) => {
|
||||
// return <div>{num}</div>
|
||||
// }
|
||||
}}
|
||||
onMount={handleEditorDidMount}
|
||||
/>
|
||||
{!outPutValue && <div className='pointer-events-none absolute left-[36px] top-0 leading-[18px] text-[13px] font-normal text-gray-300'>{placeholder}</div>}
|
||||
</>
|
||||
)
|
||||
|
||||
return (
|
||||
<div className={cn(isExpand && 'h-full')}>
|
||||
{noWrapper
|
||||
? <div className='relative no-wrapper' style={{
|
||||
height: isExpand ? '100%' : (editorContentHeight) / 2 + CODE_EDITOR_LINE_HEIGHT, // In IDE, the last line can always be in lop line. So there is some blank space in the bottom.
|
||||
minHeight: CODE_EDITOR_LINE_HEIGHT,
|
||||
}}>
|
||||
{main}
|
||||
</div>
|
||||
: (
|
||||
<Base
|
||||
className='relative'
|
||||
title={title}
|
||||
value={outPutValue}
|
||||
onChange={handleEditorChange}
|
||||
// https://microsoft.github.io/monaco-editor/typedoc/interfaces/editor.IEditorOptions.html
|
||||
options={{
|
||||
readOnly,
|
||||
domReadOnly: true,
|
||||
quickSuggestions: false,
|
||||
minimap: { enabled: false },
|
||||
lineNumbersMinChars: 1, // would change line num width
|
||||
wordWrap: 'on', // auto line wrap
|
||||
// lineNumbers: (num) => {
|
||||
// return <div>{num}</div>
|
||||
// }
|
||||
}}
|
||||
onMount={handleEditorDidMount}
|
||||
/>
|
||||
{!outPutValue && <div className='pointer-events-none absolute left-[36px] top-0 leading-[18px] text-[13px] font-normal text-gray-300'>{placeholder}</div>}
|
||||
</>
|
||||
</Base>
|
||||
headerRight={headerRight}
|
||||
isFocus={isFocus && !readOnly}
|
||||
minHeight={minHeight}
|
||||
isInNode={isInNode}
|
||||
>
|
||||
{main}
|
||||
</Base>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
@@ -2,6 +2,10 @@
|
||||
padding-left: 10px;
|
||||
}
|
||||
|
||||
.no-wrapper .margin-view-overlays {
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
/* hide readonly tooltip */
|
||||
.monaco-editor-overlaymessage {
|
||||
display: none !important;
|
||||
|
@@ -1,16 +1,19 @@
|
||||
'use client'
|
||||
import type { FC } from 'react'
|
||||
import React, { useCallback, useEffect, useRef, useState } from 'react'
|
||||
import React, { useCallback, useRef } from 'react'
|
||||
import cn from 'classnames'
|
||||
import copy from 'copy-to-clipboard'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useBoolean } from 'ahooks'
|
||||
import {
|
||||
BlockEnum,
|
||||
type Node,
|
||||
type NodeOutPutVar,
|
||||
import { BlockEnum, EditionType } from '../../../../types'
|
||||
import type {
|
||||
Node,
|
||||
NodeOutPutVar,
|
||||
Variable,
|
||||
} from '../../../../types'
|
||||
|
||||
import Wrap from '../editor/wrap'
|
||||
import { CodeLanguage } from '../../../code/types'
|
||||
import ToggleExpandBtn from '@/app/components/workflow/nodes/_base/components/toggle-expand-btn'
|
||||
import useToggleExpend from '@/app/components/workflow/nodes/_base/hooks/use-toggle-expend'
|
||||
import PromptEditor from '@/app/components/base/prompt-editor'
|
||||
@@ -21,6 +24,10 @@ import { useEventEmitterContextContext } from '@/context/event-emitter'
|
||||
import { PROMPT_EDITOR_INSERT_QUICKLY } from '@/app/components/base/prompt-editor/plugins/update-block'
|
||||
import { Variable02 } from '@/app/components/base/icons/src/vender/solid/development'
|
||||
import TooltipPlus from '@/app/components/base/tooltip-plus'
|
||||
import CodeEditor from '@/app/components/workflow/nodes/_base/components/editor/code-editor/editor-support-vars'
|
||||
import Switch from '@/app/components/base/switch'
|
||||
import { Jinja } from '@/app/components/base/icons/src/vender/workflow'
|
||||
|
||||
type Props = {
|
||||
className?: string
|
||||
headerClassName?: string
|
||||
@@ -42,6 +49,12 @@ type Props = {
|
||||
}
|
||||
nodesOutputVars?: NodeOutPutVar[]
|
||||
availableNodes?: Node[]
|
||||
// for jinja
|
||||
isSupportJinja?: boolean
|
||||
editionType?: EditionType
|
||||
onEditionTypeChange?: (editionType: EditionType) => void
|
||||
varList?: Variable[]
|
||||
handleAddVariable?: (payload: any) => void
|
||||
}
|
||||
|
||||
const Editor: FC<Props> = ({
|
||||
@@ -61,6 +74,11 @@ const Editor: FC<Props> = ({
|
||||
hasSetBlockStatus,
|
||||
nodesOutputVars,
|
||||
availableNodes = [],
|
||||
isSupportJinja,
|
||||
editionType,
|
||||
onEditionTypeChange,
|
||||
varList = [],
|
||||
handleAddVariable,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
const { eventEmitter } = useEventEmitterContextContext()
|
||||
@@ -85,20 +103,6 @@ const Editor: FC<Props> = ({
|
||||
setTrue: setFocus,
|
||||
setFalse: setBlur,
|
||||
}] = useBoolean(false)
|
||||
const hideTooltipRunId = useRef(0)
|
||||
|
||||
const [isShowInsertToolTip, setIsShowInsertTooltip] = useState(false)
|
||||
useEffect(() => {
|
||||
if (isFocus) {
|
||||
clearTimeout(hideTooltipRunId.current)
|
||||
setIsShowInsertTooltip(true)
|
||||
}
|
||||
else {
|
||||
hideTooltipRunId.current = setTimeout(() => {
|
||||
setIsShowInsertTooltip(false)
|
||||
}, 100) as any
|
||||
}
|
||||
}, [isFocus])
|
||||
|
||||
const handleInsertVariable = () => {
|
||||
setFocus()
|
||||
@@ -116,6 +120,29 @@ const Editor: FC<Props> = ({
|
||||
<div className='w-px h-3 ml-2 mr-2 bg-gray-200'></div>
|
||||
{/* Operations */}
|
||||
<div className='flex items-center space-x-2'>
|
||||
{isSupportJinja && (
|
||||
<TooltipPlus
|
||||
popupContent={
|
||||
<div>
|
||||
<div>{t('workflow.common.enableJinja')}</div>
|
||||
<a className='text-[#155EEF]' target='_blank' href='https://jinja.palletsprojects.com/en/2.10.x/'>{t('workflow.common.learnMore')}</a>
|
||||
</div>
|
||||
}
|
||||
hideArrow
|
||||
>
|
||||
<div className={cn(editionType === EditionType.jinja2 && 'border-black/5 bg-white', 'mb-1 flex h-[22px] items-center px-1.5 rounded-[5px] border border-transparent hover:border-black/5 space-x-0.5')}>
|
||||
<Jinja className='w-6 h-3 text-gray-300' />
|
||||
<Switch
|
||||
size='sm'
|
||||
defaultValue={editionType === EditionType.jinja2}
|
||||
onChange={(checked) => {
|
||||
onEditionTypeChange?.(checked ? EditionType.jinja2 : EditionType.basic)
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</TooltipPlus>
|
||||
|
||||
)}
|
||||
{!readOnly && (
|
||||
<TooltipPlus
|
||||
popupContent={`${t('workflow.common.insertVarTip')}`}
|
||||
@@ -142,57 +169,75 @@ const Editor: FC<Props> = ({
|
||||
|
||||
{/* Min: 80 Max: 560. Header: 24 */}
|
||||
<div className={cn('pb-2', isExpand && 'flex flex-col grow')}>
|
||||
<div className={cn(isExpand ? 'grow' : 'max-h-[536px]', 'relative px-3 min-h-[56px] overflow-y-auto')}>
|
||||
<PromptEditor
|
||||
instanceId={instanceId}
|
||||
compact
|
||||
className='min-h-[56px]'
|
||||
style={isExpand ? { height: editorExpandHeight - 5 } : {}}
|
||||
value={value}
|
||||
contextBlock={{
|
||||
show: justVar ? false : isShowContext,
|
||||
selectable: !hasSetBlockStatus?.context,
|
||||
canNotAddContext: true,
|
||||
}}
|
||||
historyBlock={{
|
||||
show: justVar ? false : isShowHistory,
|
||||
selectable: !hasSetBlockStatus?.history,
|
||||
history: {
|
||||
user: 'Human',
|
||||
assistant: 'Assistant',
|
||||
},
|
||||
}}
|
||||
queryBlock={{
|
||||
show: false, // use [sys.query] instead of query block
|
||||
selectable: false,
|
||||
}}
|
||||
workflowVariableBlock={{
|
||||
show: true,
|
||||
variables: nodesOutputVars || [],
|
||||
workflowNodesMap: availableNodes.reduce((acc, node) => {
|
||||
acc[node.id] = {
|
||||
title: node.data.title,
|
||||
type: node.data.type,
|
||||
}
|
||||
if (node.data.type === BlockEnum.Start) {
|
||||
acc.sys = {
|
||||
title: t('workflow.blocks.start'),
|
||||
type: BlockEnum.Start,
|
||||
}
|
||||
}
|
||||
return acc
|
||||
}, {} as any),
|
||||
}}
|
||||
onChange={onChange}
|
||||
onBlur={setBlur}
|
||||
onFocus={setFocus}
|
||||
editable={!readOnly}
|
||||
/>
|
||||
{/* to patch Editor not support dynamic change editable status */}
|
||||
{readOnly && <div className='absolute inset-0 z-10'></div>}
|
||||
</div>
|
||||
{!(isSupportJinja && editionType === EditionType.jinja2)
|
||||
? (
|
||||
<div className={cn(isExpand ? 'grow' : 'max-h-[536px]', 'relative px-3 min-h-[56px] overflow-y-auto')}>
|
||||
<PromptEditor
|
||||
instanceId={instanceId}
|
||||
compact
|
||||
className='min-h-[56px]'
|
||||
style={isExpand ? { height: editorExpandHeight - 5 } : {}}
|
||||
value={value}
|
||||
contextBlock={{
|
||||
show: justVar ? false : isShowContext,
|
||||
selectable: !hasSetBlockStatus?.context,
|
||||
canNotAddContext: true,
|
||||
}}
|
||||
historyBlock={{
|
||||
show: justVar ? false : isShowHistory,
|
||||
selectable: !hasSetBlockStatus?.history,
|
||||
history: {
|
||||
user: 'Human',
|
||||
assistant: 'Assistant',
|
||||
},
|
||||
}}
|
||||
queryBlock={{
|
||||
show: false, // use [sys.query] instead of query block
|
||||
selectable: false,
|
||||
}}
|
||||
workflowVariableBlock={{
|
||||
show: true,
|
||||
variables: nodesOutputVars || [],
|
||||
workflowNodesMap: availableNodes.reduce((acc, node) => {
|
||||
acc[node.id] = {
|
||||
title: node.data.title,
|
||||
type: node.data.type,
|
||||
}
|
||||
if (node.data.type === BlockEnum.Start) {
|
||||
acc.sys = {
|
||||
title: t('workflow.blocks.start'),
|
||||
type: BlockEnum.Start,
|
||||
}
|
||||
}
|
||||
return acc
|
||||
}, {} as any),
|
||||
}}
|
||||
onChange={onChange}
|
||||
onBlur={setBlur}
|
||||
onFocus={setFocus}
|
||||
editable={!readOnly}
|
||||
/>
|
||||
{/* to patch Editor not support dynamic change editable status */}
|
||||
{readOnly && <div className='absolute inset-0 z-10'></div>}
|
||||
</div>
|
||||
)
|
||||
: (
|
||||
<div className={cn(isExpand ? 'grow' : 'max-h-[536px]', 'relative px-3 min-h-[56px] overflow-y-auto')}>
|
||||
<CodeEditor
|
||||
availableVars={nodesOutputVars || []}
|
||||
varList={varList}
|
||||
onAddVar={handleAddVariable}
|
||||
isInNode
|
||||
readOnly={readOnly}
|
||||
language={CodeLanguage.python3}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
noWrapper
|
||||
isExpand={isExpand}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</Wrap>
|
||||
|
@@ -3,7 +3,8 @@ import type { FC } from 'react'
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import { uniqueId } from 'lodash-es'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import type { PromptItem } from '../../../types'
|
||||
import type { PromptItem, Variable } from '../../../types'
|
||||
import { EditionType } from '../../../types'
|
||||
import Editor from '@/app/components/workflow/nodes/_base/components/prompt/editor'
|
||||
import TypeSelector from '@/app/components/workflow/nodes/_base/components/selector'
|
||||
import TooltipPlus from '@/app/components/base/tooltip-plus'
|
||||
@@ -24,6 +25,7 @@ type Props = {
|
||||
payload: PromptItem
|
||||
handleChatModeMessageRoleChange: (role: PromptRole) => void
|
||||
onPromptChange: (p: string) => void
|
||||
onEditionTypeChange: (editionType: EditionType) => void
|
||||
onRemove: () => void
|
||||
isShowContext: boolean
|
||||
hasSetBlockStatus: {
|
||||
@@ -33,6 +35,8 @@ type Props = {
|
||||
}
|
||||
availableVars: any
|
||||
availableNodes: any
|
||||
varList: Variable[]
|
||||
handleAddVariable: (payload: any) => void
|
||||
}
|
||||
|
||||
const roleOptions = [
|
||||
@@ -64,17 +68,21 @@ const ConfigPromptItem: FC<Props> = ({
|
||||
isChatApp,
|
||||
payload,
|
||||
onPromptChange,
|
||||
onEditionTypeChange,
|
||||
onRemove,
|
||||
isShowContext,
|
||||
hasSetBlockStatus,
|
||||
availableVars,
|
||||
availableNodes,
|
||||
varList,
|
||||
handleAddVariable,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
const [instanceId, setInstanceId] = useState(uniqueId())
|
||||
useEffect(() => {
|
||||
setInstanceId(`${id}-${uniqueId()}`)
|
||||
}, [id])
|
||||
|
||||
return (
|
||||
<Editor
|
||||
className={className}
|
||||
@@ -107,7 +115,7 @@ const ConfigPromptItem: FC<Props> = ({
|
||||
</TooltipPlus>
|
||||
</div>
|
||||
}
|
||||
value={payload.text}
|
||||
value={payload.edition_type === EditionType.jinja2 ? (payload.jinja2_text || '') : payload.text}
|
||||
onChange={onPromptChange}
|
||||
readOnly={readOnly}
|
||||
showRemove={canRemove}
|
||||
@@ -118,6 +126,11 @@ const ConfigPromptItem: FC<Props> = ({
|
||||
hasSetBlockStatus={hasSetBlockStatus}
|
||||
nodesOutputVars={availableVars}
|
||||
availableNodes={availableNodes}
|
||||
isSupportJinja
|
||||
editionType={payload.edition_type}
|
||||
onEditionTypeChange={onEditionTypeChange}
|
||||
varList={varList}
|
||||
handleAddVariable={handleAddVariable}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
@@ -6,8 +6,8 @@ import produce from 'immer'
|
||||
import { ReactSortable } from 'react-sortablejs'
|
||||
import { v4 as uuid4 } from 'uuid'
|
||||
import cn from 'classnames'
|
||||
import type { PromptItem, ValueSelector, Var } from '../../../types'
|
||||
import { PromptRole } from '../../../types'
|
||||
import type { PromptItem, ValueSelector, Var, Variable } from '../../../types'
|
||||
import { EditionType, PromptRole } from '../../../types'
|
||||
import useAvailableVarList from '../../_base/hooks/use-available-var-list'
|
||||
import ConfigPromptItem from './config-prompt-item'
|
||||
import Editor from '@/app/components/workflow/nodes/_base/components/prompt/editor'
|
||||
@@ -30,6 +30,8 @@ type Props = {
|
||||
history: boolean
|
||||
query: boolean
|
||||
}
|
||||
varList?: Variable[]
|
||||
handleAddVariable: (payload: any) => void
|
||||
}
|
||||
|
||||
const ConfigPrompt: FC<Props> = ({
|
||||
@@ -42,10 +44,12 @@ const ConfigPrompt: FC<Props> = ({
|
||||
onChange,
|
||||
isShowContext,
|
||||
hasSetBlockStatus,
|
||||
varList = [],
|
||||
handleAddVariable,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
const payloadWithIds = (isChatModel && Array.isArray(payload))
|
||||
? payload.map((item, i) => {
|
||||
? payload.map((item) => {
|
||||
const id = uuid4()
|
||||
return {
|
||||
id: item.id || id,
|
||||
@@ -67,7 +71,16 @@ const ConfigPrompt: FC<Props> = ({
|
||||
const handleChatModePromptChange = useCallback((index: number) => {
|
||||
return (prompt: string) => {
|
||||
const newPrompt = produce(payload as PromptItem[], (draft) => {
|
||||
draft[index].text = prompt
|
||||
draft[index][draft[index].edition_type === EditionType.jinja2 ? 'jinja2_text' : 'text'] = prompt
|
||||
})
|
||||
onChange(newPrompt)
|
||||
}
|
||||
}, [onChange, payload])
|
||||
|
||||
const handleChatModeEditionTypeChange = useCallback((index: number) => {
|
||||
return (editionType: EditionType) => {
|
||||
const newPrompt = produce(payload as PromptItem[], (draft) => {
|
||||
draft[index].edition_type = editionType
|
||||
})
|
||||
onChange(newPrompt)
|
||||
}
|
||||
@@ -106,7 +119,14 @@ const ConfigPrompt: FC<Props> = ({
|
||||
|
||||
const handleCompletionPromptChange = useCallback((prompt: string) => {
|
||||
const newPrompt = produce(payload as PromptItem, (draft) => {
|
||||
draft.text = prompt
|
||||
draft[draft.edition_type === EditionType.jinja2 ? 'jinja2_text' : 'text'] = prompt
|
||||
})
|
||||
onChange(newPrompt)
|
||||
}, [onChange, payload])
|
||||
|
||||
const handleCompletionEditionTypeChange = useCallback((editionType: EditionType) => {
|
||||
const newPrompt = produce(payload as PromptItem, (draft) => {
|
||||
draft.edition_type = editionType
|
||||
})
|
||||
onChange(newPrompt)
|
||||
}, [onChange, payload])
|
||||
@@ -161,11 +181,14 @@ const ConfigPrompt: FC<Props> = ({
|
||||
isChatApp={isChatApp}
|
||||
payload={item}
|
||||
onPromptChange={handleChatModePromptChange(index)}
|
||||
onEditionTypeChange={handleChatModeEditionTypeChange(index)}
|
||||
onRemove={handleRemove(index)}
|
||||
isShowContext={isShowContext}
|
||||
hasSetBlockStatus={hasSetBlockStatus}
|
||||
availableVars={availableVars}
|
||||
availableNodes={availableNodes}
|
||||
varList={varList}
|
||||
handleAddVariable={handleAddVariable}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -187,7 +210,7 @@ const ConfigPrompt: FC<Props> = ({
|
||||
<Editor
|
||||
instanceId={`${nodeId}-chat-workflow-llm-prompt-editor`}
|
||||
title={<span className='capitalize'>{t(`${i18nPrefix}.prompt`)}</span>}
|
||||
value={(payload as PromptItem).text}
|
||||
value={(payload as PromptItem).edition_type === EditionType.basic ? (payload as PromptItem).text : ((payload as PromptItem).jinja2_text || '')}
|
||||
onChange={handleCompletionPromptChange}
|
||||
readOnly={readOnly}
|
||||
isChatModel={isChatModel}
|
||||
@@ -196,6 +219,11 @@ const ConfigPrompt: FC<Props> = ({
|
||||
hasSetBlockStatus={hasSetBlockStatus}
|
||||
nodesOutputVars={availableVars}
|
||||
availableNodes={availableNodes}
|
||||
isSupportJinja
|
||||
editionType={(payload as PromptItem).edition_type}
|
||||
varList={varList}
|
||||
onEditionTypeChange={handleCompletionEditionTypeChange}
|
||||
handleAddVariable={handleAddVariable}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
@@ -1,7 +1,6 @@
|
||||
import { BlockEnum } from '../../types'
|
||||
import { type NodeDefault, PromptRole } from '../../types'
|
||||
import { BlockEnum, EditionType } from '../../types'
|
||||
import { type NodeDefault, type PromptItem, 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'
|
||||
@@ -16,7 +15,6 @@ const nodeDefault: NodeDefault<LLMNodeType> = {
|
||||
temperature: 0.7,
|
||||
},
|
||||
},
|
||||
variables: [],
|
||||
prompt_template: [{
|
||||
role: PromptRole.system,
|
||||
text: '',
|
||||
@@ -57,6 +55,23 @@ const nodeDefault: NodeDefault<LLMNodeType> = {
|
||||
if (isChatModel && !!payload.memory.query_prompt_template && !payload.memory.query_prompt_template.includes('{{#sys.query#}}'))
|
||||
errorMessages = t('workflow.nodes.llm.sysQueryInUser')
|
||||
}
|
||||
|
||||
if (!errorMessages) {
|
||||
const isChatModel = payload.model.mode === 'chat'
|
||||
const isShowVars = (() => {
|
||||
if (isChatModel)
|
||||
return (payload.prompt_template as PromptItem[]).some(item => item.edition_type === EditionType.jinja2)
|
||||
return (payload.prompt_template as PromptItem).edition_type === EditionType.jinja2
|
||||
})()
|
||||
if (isShowVars && payload.prompt_config?.jinja2_variables) {
|
||||
payload.prompt_config?.jinja2_variables.forEach((i) => {
|
||||
if (!errorMessages && !i.variable)
|
||||
errorMessages = t(`${i18nPrefix}.fieldRequired`, { field: t(`${i18nPrefix}.fields.variable`) })
|
||||
if (!errorMessages && !i.value_selector.length)
|
||||
errorMessages = t(`${i18nPrefix}.fieldRequired`, { field: t(`${i18nPrefix}.fields.variableValue`) })
|
||||
})
|
||||
}
|
||||
}
|
||||
return {
|
||||
isValid: !errorMessages,
|
||||
errorMessage: errorMessages,
|
||||
|
@@ -7,6 +7,8 @@ import useConfig from './use-config'
|
||||
import ResolutionPicker from './components/resolution-picker'
|
||||
import type { LLMNodeType } from './types'
|
||||
import ConfigPrompt from './components/config-prompt'
|
||||
import VarList from '@/app/components/workflow/nodes/_base/components/variable/var-list'
|
||||
import AddButton2 from '@/app/components/base/button/add-button'
|
||||
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'
|
||||
@@ -44,7 +46,12 @@ const Panel: FC<NodePanelProps<LLMNodeType>> = ({
|
||||
filterVar,
|
||||
availableVars,
|
||||
availableNodes,
|
||||
isShowVars,
|
||||
handlePromptChange,
|
||||
handleAddEmptyVariable,
|
||||
handleAddVariable,
|
||||
handleVarListChange,
|
||||
handleVarNameChange,
|
||||
handleSyeQueryChange,
|
||||
handleMemoryChange,
|
||||
handleVisionResolutionEnabledChange,
|
||||
@@ -169,9 +176,29 @@ const Panel: FC<NodePanelProps<LLMNodeType>> = ({
|
||||
payload={inputs.prompt_template}
|
||||
onChange={handlePromptChange}
|
||||
hasSetBlockStatus={hasSetBlockStatus}
|
||||
varList={inputs.prompt_config?.jinja2_variables || []}
|
||||
handleAddVariable={handleAddVariable}
|
||||
/>
|
||||
)}
|
||||
|
||||
{isShowVars && (
|
||||
<Field
|
||||
title={t('workflow.nodes.templateTransform.inputVars')}
|
||||
operations={
|
||||
!readOnly ? <AddButton2 onClick={handleAddEmptyVariable} /> : undefined
|
||||
}
|
||||
>
|
||||
<VarList
|
||||
nodeId={id}
|
||||
readonly={readOnly}
|
||||
list={inputs.prompt_config?.jinja2_variables || []}
|
||||
onChange={handleVarListChange}
|
||||
onVarNameChange={handleVarNameChange}
|
||||
filterVar={filterVar}
|
||||
/>
|
||||
</Field>
|
||||
)}
|
||||
|
||||
{/* Memory put place examples. */}
|
||||
{isChatMode && isChatModel && !!inputs.memory && (
|
||||
<div className='mt-4'>
|
||||
|
@@ -3,8 +3,10 @@ import type { CommonNodeType, Memory, ModelConfig, PromptItem, ValueSelector, Va
|
||||
|
||||
export type LLMNodeType = CommonNodeType & {
|
||||
model: ModelConfig
|
||||
variables: Variable[]
|
||||
prompt_template: PromptItem[] | PromptItem
|
||||
prompt_config?: {
|
||||
jinja2_variables?: Variable[]
|
||||
}
|
||||
memory?: Memory
|
||||
context: {
|
||||
enabled: boolean
|
||||
|
@@ -1,8 +1,7 @@
|
||||
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 { EditionType, VarType } from '../../types'
|
||||
import type { Memory, PromptItem, ValueSelector, Var, Variable } from '../../types'
|
||||
import { useStore } from '../../store'
|
||||
import {
|
||||
useIsChatMode,
|
||||
@@ -18,7 +17,6 @@ import {
|
||||
} 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'
|
||||
|
||||
@@ -29,20 +27,21 @@ const useConfig = (id: string, payload: LLMNodeType) => {
|
||||
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 inputRef = useRef(inputs)
|
||||
|
||||
const setInputs = useCallback((newInputs: LLMNodeType) => {
|
||||
if (newInputs.memory && !newInputs.memory.role_prefix) {
|
||||
const newPayload = produce(newInputs, (draft) => {
|
||||
draft.memory!.role_prefix = defaultRolePrefix
|
||||
})
|
||||
doSetInputs(newPayload)
|
||||
inputRef.current = newPayload
|
||||
return
|
||||
}
|
||||
doSetInputs(newInputs)
|
||||
inputRef.current = newInputs
|
||||
}, [doSetInputs, defaultRolePrefix])
|
||||
const inputRef = useRef(inputs)
|
||||
useEffect(() => {
|
||||
inputRef.current = inputs
|
||||
}, [inputs])
|
||||
|
||||
// model
|
||||
const model = inputs.model
|
||||
const modelMode = inputs.model?.mode
|
||||
@@ -178,11 +177,80 @@ const useConfig = (id: string, payload: LLMNodeType) => {
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [isShowVisionConfig, modelChanged])
|
||||
|
||||
// variables
|
||||
const { handleVarListChange, handleAddVariable } = useVarList<LLMNodeType>({
|
||||
inputs,
|
||||
setInputs,
|
||||
})
|
||||
const isShowVars = (() => {
|
||||
if (isChatModel)
|
||||
return (inputs.prompt_template as PromptItem[]).some(item => item.edition_type === EditionType.jinja2)
|
||||
|
||||
return (inputs.prompt_template as PromptItem).edition_type === EditionType.jinja2
|
||||
})()
|
||||
const handleAddEmptyVariable = useCallback(() => {
|
||||
const newInputs = produce(inputRef.current, (draft) => {
|
||||
if (!draft.prompt_config) {
|
||||
draft.prompt_config = {
|
||||
jinja2_variables: [],
|
||||
}
|
||||
}
|
||||
if (!draft.prompt_config.jinja2_variables)
|
||||
draft.prompt_config.jinja2_variables = []
|
||||
|
||||
draft.prompt_config.jinja2_variables.push({
|
||||
variable: '',
|
||||
value_selector: [],
|
||||
})
|
||||
})
|
||||
setInputs(newInputs)
|
||||
}, [setInputs])
|
||||
|
||||
const handleAddVariable = useCallback((payload: Variable) => {
|
||||
const newInputs = produce(inputRef.current, (draft) => {
|
||||
if (!draft.prompt_config) {
|
||||
draft.prompt_config = {
|
||||
jinja2_variables: [],
|
||||
}
|
||||
}
|
||||
if (!draft.prompt_config.jinja2_variables)
|
||||
draft.prompt_config.jinja2_variables = []
|
||||
|
||||
draft.prompt_config.jinja2_variables.push(payload)
|
||||
})
|
||||
setInputs(newInputs)
|
||||
}, [setInputs])
|
||||
|
||||
const handleVarListChange = useCallback((newList: Variable[]) => {
|
||||
const newInputs = produce(inputRef.current, (draft) => {
|
||||
if (!draft.prompt_config) {
|
||||
draft.prompt_config = {
|
||||
jinja2_variables: [],
|
||||
}
|
||||
}
|
||||
if (!draft.prompt_config.jinja2_variables)
|
||||
draft.prompt_config.jinja2_variables = []
|
||||
|
||||
draft.prompt_config.jinja2_variables = newList
|
||||
})
|
||||
setInputs(newInputs)
|
||||
}, [setInputs])
|
||||
|
||||
const handleVarNameChange = useCallback((oldName: string, newName: string) => {
|
||||
const newInputs = produce(inputRef.current, (draft) => {
|
||||
if (isChatModel) {
|
||||
const promptTemplate = draft.prompt_template as PromptItem[]
|
||||
promptTemplate.filter(item => item.edition_type === EditionType.jinja2).forEach((item) => {
|
||||
item.jinja2_text = (item.jinja2_text || '').replaceAll(`{{ ${oldName} }}`, `{{ ${newName} }}`)
|
||||
})
|
||||
}
|
||||
else {
|
||||
if ((draft.prompt_template as PromptItem).edition_type !== EditionType.jinja2)
|
||||
return
|
||||
|
||||
const promptTemplate = draft.prompt_template as PromptItem
|
||||
promptTemplate.jinja2_text = (promptTemplate.jinja2_text || '').replaceAll(`{{ ${oldName} }}`, `{{ ${newName} }}`)
|
||||
}
|
||||
})
|
||||
setInputs(newInputs)
|
||||
}, [isChatModel, setInputs])
|
||||
|
||||
// context
|
||||
const handleContextVarChange = useCallback((newVar: ValueSelector | string) => {
|
||||
@@ -194,11 +262,11 @@ const useConfig = (id: string, payload: LLMNodeType) => {
|
||||
}, [inputs, setInputs])
|
||||
|
||||
const handlePromptChange = useCallback((newPrompt: PromptItem[] | PromptItem) => {
|
||||
const newInputs = produce(inputs, (draft) => {
|
||||
const newInputs = produce(inputRef.current, (draft) => {
|
||||
draft.prompt_template = newPrompt
|
||||
})
|
||||
setInputs(newInputs)
|
||||
}, [inputs, setInputs])
|
||||
}, [setInputs])
|
||||
|
||||
const handleMemoryChange = useCallback((newMemory?: Memory) => {
|
||||
const newInputs = produce(inputs, (draft) => {
|
||||
@@ -286,6 +354,7 @@ const useConfig = (id: string, payload: LLMNodeType) => {
|
||||
runInputData,
|
||||
setRunInputData,
|
||||
runResult,
|
||||
toVarInputs,
|
||||
} = useOneStepRun<LLMNodeType>({
|
||||
id,
|
||||
data: inputs,
|
||||
@@ -295,23 +364,6 @@ const useConfig = (id: string, payload: LLMNodeType) => {
|
||||
},
|
||||
})
|
||||
|
||||
// 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)
|
||||
@@ -348,7 +400,7 @@ const useConfig = (id: string, payload: LLMNodeType) => {
|
||||
}, [runInputData, setRunInputData])
|
||||
|
||||
const allVarStrArr = (() => {
|
||||
const arr = isChatModel ? (inputs.prompt_template as PromptItem[]).map(item => item.text) : [(inputs.prompt_template as PromptItem).text]
|
||||
const arr = isChatModel ? (inputs.prompt_template as PromptItem[]).filter(item => item.edition_type !== EditionType.jinja2).map(item => item.text) : [(inputs.prompt_template as PromptItem).text]
|
||||
if (isChatMode && isChatModel && !!inputs.memory) {
|
||||
arr.push('{{#sys.query#}}')
|
||||
arr.push(inputs.memory.query_prompt_template)
|
||||
@@ -357,7 +409,13 @@ const useConfig = (id: string, payload: LLMNodeType) => {
|
||||
return arr
|
||||
})()
|
||||
|
||||
const varInputs = getInputVars(allVarStrArr)
|
||||
const varInputs = (() => {
|
||||
const vars = getInputVars(allVarStrArr)
|
||||
if (isShowVars)
|
||||
return [...vars, ...toVarInputs(inputs.prompt_config?.jinja2_variables || [])]
|
||||
|
||||
return vars
|
||||
})()
|
||||
|
||||
return {
|
||||
readOnly,
|
||||
@@ -370,8 +428,11 @@ const useConfig = (id: string, payload: LLMNodeType) => {
|
||||
isShowVisionConfig,
|
||||
handleModelChanged,
|
||||
handleCompletionParamsChange,
|
||||
isShowVars,
|
||||
handleVarListChange,
|
||||
handleVarNameChange,
|
||||
handleAddVariable,
|
||||
handleAddEmptyVariable,
|
||||
handleContextVarChange,
|
||||
filterInputVar,
|
||||
filterVar,
|
||||
|
@@ -26,6 +26,7 @@ const Panel: FC<NodePanelProps<TemplateTransformNodeType>> = ({
|
||||
const {
|
||||
readOnly,
|
||||
inputs,
|
||||
availableVars,
|
||||
handleVarListChange,
|
||||
handleVarNameChange,
|
||||
handleAddVariable,
|
||||
@@ -65,7 +66,7 @@ const Panel: FC<NodePanelProps<TemplateTransformNodeType>> = ({
|
||||
</Field>
|
||||
<Split />
|
||||
<CodeEditor
|
||||
nodeId={id}
|
||||
availableVars={availableVars}
|
||||
varList={inputs.variables}
|
||||
onAddVar={handleAddVariable}
|
||||
isInNode
|
||||
|
@@ -10,6 +10,7 @@ import useOneStepRun from '@/app/components/workflow/nodes/_base/hooks/use-one-s
|
||||
import {
|
||||
useNodesReadOnly,
|
||||
} from '@/app/components/workflow/hooks'
|
||||
import useAvailableVarList from '@/app/components/workflow/nodes/_base/hooks/use-available-var-list'
|
||||
|
||||
const useConfig = (id: string, payload: TemplateTransformNodeType) => {
|
||||
const { nodesReadOnly: readOnly } = useNodesReadOnly()
|
||||
@@ -22,6 +23,11 @@ const useConfig = (id: string, payload: TemplateTransformNodeType) => {
|
||||
inputsRef.current = newPayload
|
||||
}, [doSetInputs])
|
||||
|
||||
const { availableVars } = useAvailableVarList(id, {
|
||||
onlyLeafNodeVar: false,
|
||||
filterVar: () => true,
|
||||
})
|
||||
|
||||
const { handleAddVariable: handleAddEmptyVariable } = useVarList<TemplateTransformNodeType>({
|
||||
inputs,
|
||||
setInputs,
|
||||
@@ -108,6 +114,7 @@ const useConfig = (id: string, payload: TemplateTransformNodeType) => {
|
||||
return {
|
||||
readOnly,
|
||||
inputs,
|
||||
availableVars,
|
||||
handleVarListChange,
|
||||
handleVarNameChange,
|
||||
handleAddVariable,
|
||||
|
Reference in New Issue
Block a user