'use client'
|
import type { FC } from 'react'
|
import React, { useCallback, useRef } from 'react'
|
import {
|
RiDeleteBinLine,
|
} from '@remixicon/react'
|
import copy from 'copy-to-clipboard'
|
import { useTranslation } from 'react-i18next'
|
import { useBoolean } from 'ahooks'
|
import { BlockEnum, EditionType } from '../../../../types'
|
import type {
|
ModelConfig,
|
Node,
|
NodeOutPutVar,
|
Variable,
|
} from '../../../../types'
|
|
import Wrap from '../editor/wrap'
|
import { CodeLanguage } from '../../../code/types'
|
import PromptGeneratorBtn from '../../../llm/components/prompt-generator-btn'
|
import cn from '@/utils/classnames'
|
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'
|
import {
|
Clipboard,
|
ClipboardCheck,
|
} from '@/app/components/base/icons/src/vender/line/files'
|
import s from '@/app/components/app/configuration/config-prompt/style.module.css'
|
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 ActionButton from '@/app/components/base/action-button'
|
import Tooltip from '@/app/components/base/tooltip'
|
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'
|
import { useStore } from '@/app/components/workflow/store'
|
|
type Props = {
|
className?: string
|
headerClassName?: string
|
instanceId?: string
|
title: string | JSX.Element
|
value: string
|
onChange: (value: string) => void
|
readOnly?: boolean
|
showRemove?: boolean
|
onRemove?: () => void
|
justVar?: boolean
|
isChatModel?: boolean
|
isChatApp?: boolean
|
isShowContext?: boolean
|
hasSetBlockStatus?: {
|
context: boolean
|
history: boolean
|
query: boolean
|
}
|
nodesOutputVars?: NodeOutPutVar[]
|
availableNodes?: Node[]
|
isSupportFileVar?: boolean
|
isSupportPromptGenerator?: boolean
|
onGenerated?: (prompt: string) => void
|
modelConfig?: ModelConfig
|
// for jinja
|
isSupportJinja?: boolean
|
editionType?: EditionType
|
onEditionTypeChange?: (editionType: EditionType) => void
|
varList?: Variable[]
|
handleAddVariable?: (payload: any) => void
|
}
|
|
const Editor: FC<Props> = ({
|
className,
|
headerClassName,
|
instanceId,
|
title,
|
value,
|
onChange,
|
readOnly,
|
showRemove,
|
onRemove,
|
justVar,
|
isChatModel,
|
isChatApp,
|
isShowContext,
|
hasSetBlockStatus,
|
nodesOutputVars,
|
availableNodes = [],
|
isSupportFileVar,
|
isSupportPromptGenerator,
|
isSupportJinja,
|
editionType,
|
onEditionTypeChange,
|
varList = [],
|
handleAddVariable,
|
onGenerated,
|
modelConfig,
|
}) => {
|
const { t } = useTranslation()
|
const { eventEmitter } = useEventEmitterContextContext()
|
const controlPromptEditorRerenderKey = useStore(s => s.controlPromptEditorRerenderKey)
|
|
const isShowHistory = !isChatModel && isChatApp
|
|
const ref = useRef<HTMLDivElement>(null)
|
const {
|
wrapClassName,
|
wrapStyle,
|
isExpand,
|
setIsExpand,
|
editorExpandHeight,
|
} = useToggleExpend({ ref, isInNode: true })
|
const [isCopied, setIsCopied] = React.useState(false)
|
const handleCopy = useCallback(() => {
|
copy(value)
|
setIsCopied(true)
|
}, [value])
|
|
const [isFocus, {
|
setTrue: setFocus,
|
setFalse: setBlur,
|
}] = useBoolean(false)
|
|
const handleInsertVariable = () => {
|
setFocus()
|
eventEmitter?.emit({ type: PROMPT_EDITOR_INSERT_QUICKLY, instanceId } as any)
|
}
|
|
return (
|
<Wrap className={cn(className, wrapClassName)} style={wrapStyle} isInNode isExpand={isExpand}>
|
<div ref={ref} className={cn(isFocus ? s.gradientBorder : 'bg-gray-100', isExpand && 'h-full', '!rounded-[9px] p-0.5')}>
|
<div className={cn(isFocus ? 'bg-gray-50' : 'bg-gray-100', isExpand && 'h-full flex flex-col', 'rounded-lg')}>
|
<div className={cn(headerClassName, 'pt-1 pl-3 pr-2 flex justify-between items-center')}>
|
<div className='leading-4 text-xs font-semibold text-gray-700 uppercase'>{title}</div>
|
<div className='flex items-center'>
|
<div className='leading-[18px] text-xs font-medium text-gray-500'>{value?.length || 0}</div>
|
{isSupportPromptGenerator && (
|
<PromptGeneratorBtn className='ml-[5px]' onGenerated={onGenerated} modelConfig={modelConfig} />
|
)}
|
|
<div className='w-px h-3 ml-2 mr-2 bg-gray-200'></div>
|
{/* Operations */}
|
<div className='flex items-center space-x-[2px]'>
|
{isSupportJinja && (
|
<Tooltip
|
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>
|
}
|
needsDelay
|
>
|
<div className={cn(editionType === EditionType.jinja2 && 'border-black/5 bg-white', '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>
|
</Tooltip>
|
|
)}
|
{!readOnly && (
|
<Tooltip
|
popupContent={`${t('workflow.common.insertVarTip')}`}
|
>
|
<ActionButton onClick={handleInsertVariable}>
|
<Variable02 className='w-4 h-4' />
|
</ActionButton>
|
</Tooltip>
|
)}
|
{showRemove && (
|
<ActionButton onClick={onRemove}>
|
<RiDeleteBinLine className='w-4 h-4' />
|
</ActionButton>
|
)}
|
{!isCopied
|
? (
|
<ActionButton onClick={handleCopy}>
|
<Clipboard className='w-4 h-4' />
|
</ActionButton>
|
)
|
: (
|
<ActionButton>
|
<ClipboardCheck className='w-4 h-4' />
|
</ActionButton>
|
)
|
}
|
<ToggleExpandBtn isExpand={isExpand} onExpandChange={setIsExpand} />
|
</div>
|
|
</div>
|
</div>
|
|
{/* Min: 80 Max: 560. Header: 24 */}
|
<div className={cn('pb-2', isExpand && 'flex flex-col grow')}>
|
{!(isSupportJinja && editionType === EditionType.jinja2)
|
? (
|
<div className={cn(isExpand ? 'grow' : 'max-h-[536px]', 'relative px-3 min-h-[56px] overflow-y-auto')}>
|
<PromptEditor
|
key={controlPromptEditorRerenderKey}
|
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}
|
isSupportFileVar={isSupportFileVar}
|
/>
|
{/* 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>
|
|
)
|
}
|
export default React.memo(Editor)
|