feat: enchance prompt and code (#23633)

Co-authored-by: stream <stream@dify.ai>
Co-authored-by: Stream <1542763342@qq.com>
Co-authored-by: Stream <Stream_2@qq.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
This commit is contained in:
Joel
2025-08-18 12:29:12 +08:00
committed by GitHub
parent a7fe0e3f87
commit de9c5f10b3
66 changed files with 2654 additions and 275 deletions

View File

@@ -13,7 +13,7 @@ import Tooltip from '@/app/components/base/tooltip'
import { AppType } from '@/types/app'
import { getNewVar, getVars } from '@/utils/var'
import AutomaticBtn from '@/app/components/app/configuration/config/automatic/automatic-btn'
import type { AutomaticRes } from '@/service/debug'
import type { GenRes } from '@/service/debug'
import GetAutomaticResModal from '@/app/components/app/configuration/config/automatic/get-automatic-res'
import PromptEditor from '@/app/components/base/prompt-editor'
import ConfigContext from '@/context/debug-configuration'
@@ -61,6 +61,7 @@ const Prompt: FC<ISimplePromptInput> = ({
const { eventEmitter } = useEventEmitterContextContext()
const {
appId,
modelConfig,
dataSets,
setModelConfig,
@@ -139,21 +140,21 @@ const Prompt: FC<ISimplePromptInput> = ({
}
const [showAutomatic, { setTrue: showAutomaticTrue, setFalse: showAutomaticFalse }] = useBoolean(false)
const handleAutomaticRes = (res: AutomaticRes) => {
const handleAutomaticRes = (res: GenRes) => {
// put eventEmitter in first place to prevent overwrite the configs.prompt_variables.But another problem is that prompt won't hight the prompt_variables.
eventEmitter?.emit({
type: PROMPT_EDITOR_UPDATE_VALUE_BY_EVENT_EMITTER,
payload: res.prompt,
payload: res.modified,
} as any)
const newModelConfig = produce(modelConfig, (draft) => {
draft.configs.prompt_template = res.prompt
draft.configs.prompt_variables = res.variables.map(key => ({ key, name: key, type: 'string', required: true }))
draft.configs.prompt_template = res.modified
draft.configs.prompt_variables = (res.variables || []).map(key => ({ key, name: key, type: 'string', required: true }))
})
setModelConfig(newModelConfig)
setPrevPromptConfig(modelConfig.configs)
if (mode !== AppType.completion) {
setIntroduction(res.opening_statement)
setIntroduction(res.opening_statement || '')
const newFeatures = produce(features, (draft) => {
draft.opening = {
...draft.opening,
@@ -272,10 +273,13 @@ const Prompt: FC<ISimplePromptInput> = ({
{showAutomatic && (
<GetAutomaticResModal
flowId={appId}
mode={mode as AppType}
isShow={showAutomatic}
onClose={showAutomaticFalse}
onFinished={handleAutomaticRes}
currentPrompt={promptTemplate}
isBasicMode
/>
)}
</div>

View File

@@ -2,7 +2,7 @@
import type { FC } from 'react'
import React, { useCallback, useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { useBoolean } from 'ahooks'
import { useBoolean, useSessionStorageState } from 'ahooks'
import {
RiDatabase2Line,
RiFileExcel2Line,
@@ -14,24 +14,18 @@ import {
RiTranslate,
RiUser2Line,
} from '@remixicon/react'
import cn from 'classnames'
import s from './style.module.css'
import Modal from '@/app/components/base/modal'
import Button from '@/app/components/base/button'
import Textarea from '@/app/components/base/textarea'
import Toast from '@/app/components/base/toast'
import { generateRule } from '@/service/debug'
import ConfigPrompt from '@/app/components/app/configuration/config-prompt'
import { generateBasicAppFistTimeRule, generateRule } from '@/service/debug'
import type { CompletionParams, Model } from '@/types/app'
import { AppType } from '@/types/app'
import ConfigVar from '@/app/components/app/configuration/config-var'
import GroupName from '@/app/components/app/configuration/base/group-name'
import type { AppType } from '@/types/app'
import Loading from '@/app/components/base/loading'
import Confirm from '@/app/components/base/confirm'
import { LoveMessage } from '@/app/components/base/icons/src/vender/features'
// type
import type { AutomaticRes } from '@/service/debug'
import type { GenRes } from '@/service/debug'
import { Generator } from '@/app/components/base/icons/src/vender/other'
import ModelParameterModal from '@/app/components/header/account-setting/model-provider-page/model-parameter-modal'
@@ -39,13 +33,25 @@ import { ModelTypeEnum } from '@/app/components/header/account-setting/model-pro
import { useModelListAndDefaultModelAndCurrentProviderAndModel } from '@/app/components/header/account-setting/model-provider-page/hooks'
import type { ModelModeType } from '@/types/app'
import type { FormValue } from '@/app/components/header/account-setting/model-provider-page/declarations'
import InstructionEditorInWorkflow from './instruction-editor-in-workflow'
import InstructionEditorInBasic from './instruction-editor'
import { GeneratorType } from './types'
import Result from './result'
import useGenData from './use-gen-data'
import IdeaOutput from './idea-output'
import ResPlaceholder from './res-placeholder'
import { useGenerateRuleTemplate } from '@/service/use-apps'
const i18nPrefix = 'appDebug.generate'
export type IGetAutomaticResProps = {
mode: AppType
isShow: boolean
onClose: () => void
onFinished: (res: AutomaticRes) => void
isInLLMNode?: boolean
onFinished: (res: GenRes) => void
flowId?: string
nodeId?: string
currentPrompt?: string
isBasicMode?: boolean
}
const TryLabel: FC<{
@@ -68,7 +74,10 @@ const GetAutomaticRes: FC<IGetAutomaticResProps> = ({
mode,
isShow,
onClose,
isInLLMNode,
flowId,
nodeId,
currentPrompt,
isBasicMode,
onFinished,
}) => {
const { t } = useTranslation()
@@ -123,13 +132,27 @@ const GetAutomaticRes: FC<IGetAutomaticResProps> = ({
},
]
const [instruction, setInstruction] = useState<string>('')
const [instructionFromSessionStorage, setInstruction] = useSessionStorageState<string>(`improve-instruction-${flowId}${isBasicMode ? '' : `-${nodeId}`}`)
const instruction = instructionFromSessionStorage || ''
const [ideaOutput, setIdeaOutput] = useState<string>('')
const [editorKey, setEditorKey] = useState(`${flowId}-0`)
const handleChooseTemplate = useCallback((key: string) => {
return () => {
const template = t(`appDebug.generate.template.${key}.instruction`)
setInstruction(template)
setEditorKey(`${flowId}-${Date.now()}`)
}
}, [t])
const { data: instructionTemplate } = useGenerateRuleTemplate(GeneratorType.prompt, isBasicMode)
useEffect(() => {
if (!instruction && instructionTemplate)
setInstruction(instructionTemplate.data)
setEditorKey(`${flowId}-${Date.now()}`)
}, [instructionTemplate])
const isValid = () => {
if (instruction.trim() === '') {
Toast.notify({
@@ -143,7 +166,10 @@ const GetAutomaticRes: FC<IGetAutomaticResProps> = ({
return true
}
const [isLoading, { setTrue: setLoadingTrue, setFalse: setLoadingFalse }] = useBoolean(false)
const [res, setRes] = useState<AutomaticRes | null>(null)
const storageKey = `${flowId}${isBasicMode ? '' : `-${nodeId}`}`
const { addVersion, current, currentVersionIndex, setCurrentVersionIndex, versions } = useGenData({
storageKey,
})
useEffect(() => {
if (defaultModel) {
@@ -170,16 +196,6 @@ const GetAutomaticRes: FC<IGetAutomaticResProps> = ({
</div>
)
const renderNoData = (
<div className='flex h-full w-0 grow flex-col items-center justify-center space-y-3 px-8'>
<Generator className='h-14 w-14 text-text-tertiary' />
<div className='text-center text-[13px] font-normal leading-5 text-text-tertiary'>
<div>{t('appDebug.generate.noDataLine1')}</div>
<div>{t('appDebug.generate.noDataLine2')}</div>
</div>
</div>
)
const handleModelChange = useCallback((newValue: { modelId: string; provider: string; mode?: string; features?: string[] }) => {
const newModel = {
...model,
@@ -207,28 +223,59 @@ const GetAutomaticRes: FC<IGetAutomaticResProps> = ({
return
setLoadingTrue()
try {
const { error, ...res } = await generateRule({
instruction,
model_config: model,
no_variable: !!isInLLMNode,
})
setRes(res)
if (error) {
Toast.notify({
type: 'error',
message: error,
let apiRes: GenRes
let hasError = false
if (isBasicMode || !currentPrompt) {
const { error, ...res } = await generateBasicAppFistTimeRule({
instruction,
model_config: model,
no_variable: false,
})
apiRes = {
...res,
modified: res.prompt,
} as GenRes
if (error) {
hasError = true
Toast.notify({
type: 'error',
message: error,
})
}
}
else {
const { error, ...res } = await generateRule({
flow_id: flowId,
node_id: nodeId,
current: currentPrompt,
instruction,
ideal_output: ideaOutput,
model_config: model,
})
apiRes = res
if (error) {
hasError = true
Toast.notify({
type: 'error',
message: error,
})
}
}
if (!hasError)
addVersion(apiRes)
}
finally {
setLoadingFalse()
}
}
const [showConfirmOverwrite, setShowConfirmOverwrite] = React.useState(false)
const [isShowConfirmOverwrite, {
setTrue: showConfirmOverwrite,
setFalse: hideShowConfirmOverwrite,
}] = useBoolean(false)
const isShowAutoPromptResPlaceholder = () => {
return !isLoading && !res
return !isLoading && !current
}
return (
@@ -236,15 +283,14 @@ const GetAutomaticRes: FC<IGetAutomaticResProps> = ({
isShow={isShow}
onClose={onClose}
className='min-w-[1140px] !p-0'
closable
>
<div className='flex h-[680px] flex-wrap'>
<div className='h-full w-[570px] shrink-0 overflow-y-auto border-r border-divider-regular p-6'>
<div className='mb-8'>
<div className='mb-5'>
<div className={`text-lg font-bold leading-[28px] ${s.textGradient}`}>{t('appDebug.generate.title')}</div>
<div className='mt-1 text-[13px] font-normal text-text-tertiary'>{t('appDebug.generate.description')}</div>
</div>
<div className='mb-8'>
<div>
<ModelParameterModal
popupClassName='!w-[520px]'
portalToFollowElemContentClassName='z-[1000]'
@@ -258,116 +304,99 @@ const GetAutomaticRes: FC<IGetAutomaticResProps> = ({
hideDebugWithMultipleModel
/>
</div>
<div >
<div className='flex items-center'>
<div className='mr-3 shrink-0 text-xs font-semibold uppercase leading-[18px] text-text-tertiary'>{t('appDebug.generate.tryIt')}</div>
<div className='h-px grow' style={{
background: 'linear-gradient(to right, rgba(243, 244, 246, 1), rgba(243, 244, 246, 0))',
}}></div>
</div>
<div className='flex flex-wrap'>
{tryList.map(item => (
<TryLabel
key={item.key}
Icon={item.icon}
text={t(`appDebug.generate.template.${item.key}.name`)}
onClick={handleChooseTemplate(item.key)}
/>
))}
</div>
</div>
{/* inputs */}
<div className='mt-6'>
<div className='text-[0px]'>
<div className='mb-2 text-sm font-medium leading-5 text-text-primary'>{t('appDebug.generate.instruction')}</div>
<Textarea
className="h-[200px] resize-none"
placeholder={t('appDebug.generate.instructionPlaceHolder') as string}
value={instruction}
onChange={e => setInstruction(e.target.value)} />
{isBasicMode && (
<div className='mt-4'>
<div className='flex items-center'>
<div className='mr-3 shrink-0 text-xs font-semibold uppercase leading-[18px] text-text-tertiary'>{t('appDebug.generate.tryIt')}</div>
<div className='h-px grow' style={{
background: 'linear-gradient(to right, rgba(243, 244, 246, 1), rgba(243, 244, 246, 0))',
}}></div>
</div>
<div className='flex flex-wrap'>
{tryList.map(item => (
<TryLabel
key={item.key}
Icon={item.icon}
text={t(`appDebug.generate.template.${item.key}.name`)}
onClick={handleChooseTemplate(item.key)}
/>
))}
</div>
</div>
)}
<div className='mt-5 flex justify-end'>
{/* inputs */}
<div className='mt-4'>
<div>
<div className='system-sm-semibold-uppercase mb-1.5 text-text-secondary'>{t('appDebug.generate.instruction')}</div>
{isBasicMode ? (
<InstructionEditorInBasic
editorKey={editorKey}
generatorType={GeneratorType.prompt}
value={instruction}
onChange={setInstruction}
availableVars={[]}
availableNodes={[]}
isShowCurrentBlock={!!currentPrompt}
isShowLastRunBlock={false}
/>
) : (
<InstructionEditorInWorkflow
editorKey={editorKey}
generatorType={GeneratorType.prompt}
value={instruction}
onChange={setInstruction}
nodeId={nodeId || ''}
isShowCurrentBlock={!!currentPrompt}
/>
)}
</div>
<IdeaOutput
value={ideaOutput}
onChange={setIdeaOutput}
/>
<div className='mt-7 flex justify-end space-x-2'>
<Button onClick={onClose}>{t(`${i18nPrefix}.dismiss`)}</Button>
<Button
className='flex space-x-1'
variant='primary'
onClick={onGenerate}
disabled={isLoading}
>
<Generator className='h-4 w-4 text-white' />
<span className='text-xs font-semibold text-white'>{t('appDebug.generate.generate')}</span>
<Generator className='h-4 w-4' />
<span className='text-xs font-semibold'>{t('appDebug.generate.generate')}</span>
</Button>
</div>
</div>
</div>
{(!isLoading && res) && (
<div className='h-full w-0 grow p-6 pb-0'>
<div className='mb-3 shrink-0 text-base font-semibold leading-[160%] text-text-secondary'>{t('appDebug.generate.resTitle')}</div>
<div className={cn('max-h-[555px] overflow-y-auto', !isInLLMNode && 'pb-2')}>
<ConfigPrompt
mode={mode}
promptTemplate={res?.prompt || ''}
promptVariables={[]}
readonly
noTitle={isInLLMNode}
gradientBorder
editorHeight={isInLLMNode ? 524 : 0}
noResize={isInLLMNode}
/>
{!isInLLMNode && (
<>
{(res?.variables?.length && res?.variables?.length > 0)
? (
<ConfigVar
promptVariables={res?.variables.map(key => ({ key, name: key, type: 'string', required: true })) || []}
readonly
/>
)
: ''}
{(mode !== AppType.completion && res?.opening_statement) && (
<div className='mt-7'>
<GroupName name={t('appDebug.feature.groupChat.title')} />
<div
className='mb-1 rounded-xl border-l-[0.5px] border-t-[0.5px] border-effects-highlight bg-background-section-burn p-3'
>
<div className='mb-2 flex items-center gap-2'>
<div className='shrink-0 rounded-lg border-[0.5px] border-divider-subtle bg-util-colors-blue-light-blue-light-500 p-1 shadow-xs'>
<LoveMessage className='h-4 w-4 text-text-primary-on-surface' />
</div>
<div className='system-sm-semibold flex grow items-center text-text-secondary'>
{t('appDebug.feature.conversationOpener.title')}
</div>
</div>
<div className='system-xs-regular min-h-8 text-text-tertiary'>{res.opening_statement}</div>
</div>
</div>
)}
</>
)}
</div>
<div className='flex justify-end bg-background-default py-4'>
<Button onClick={onClose}>{t('common.operation.cancel')}</Button>
<Button variant='primary' className='ml-2' onClick={() => {
setShowConfirmOverwrite(true)
}}>{t('appDebug.generate.apply')}</Button>
</div>
{(!isLoading && current) && (
<div className='h-full w-0 grow bg-background-default-subtle p-6 pb-0'>
<Result
current={current!}
isBasicMode={isBasicMode}
nodeId={nodeId!}
currentVersionIndex={currentVersionIndex || 0}
setCurrentVersionIndex={setCurrentVersionIndex}
versions={versions || []}
onApply={showConfirmOverwrite}
generatorType={GeneratorType.prompt}
/>
</div>
)}
{isLoading && renderLoading}
{isShowAutoPromptResPlaceholder() && renderNoData}
{showConfirmOverwrite && (
{isShowAutoPromptResPlaceholder() && <ResPlaceholder />}
{isShowConfirmOverwrite && (
<Confirm
title={t('appDebug.generate.overwriteTitle')}
content={t('appDebug.generate.overwriteMessage')}
isShow={showConfirmOverwrite}
isShow
onConfirm={() => {
setShowConfirmOverwrite(false)
onFinished(res!)
hideShowConfirmOverwrite()
onFinished(current!)
}}
onCancel={() => setShowConfirmOverwrite(false)}
onCancel={hideShowConfirmOverwrite}
/>
)}
</div>

View File

@@ -0,0 +1,48 @@
'use client'
import { ArrowDownRoundFill } from '@/app/components/base/icons/src/vender/solid/general'
import { useBoolean } from 'ahooks'
import type { FC } from 'react'
import React from 'react'
import cn from '@/utils/classnames'
import Textarea from '@/app/components/base/textarea'
import { useTranslation } from 'react-i18next'
const i18nPrefix = 'appDebug.generate'
type Props = {
value: string
onChange: (value: string) => void
}
const IdeaOutput: FC<Props> = ({
value,
onChange,
}) => {
const { t } = useTranslation()
const [isFoldIdeaOutput, {
toggle: toggleFoldIdeaOutput,
}] = useBoolean(true)
return (
<div className='mt-4 text-[0px]'>
<div
className='mb-1.5 flex cursor-pointer items-center text-sm font-medium leading-5 text-text-primary'
onClick={toggleFoldIdeaOutput}
>
<div className='system-sm-semibold-uppercase mr-1 text-text-secondary'>{t(`${i18nPrefix}.idealOutput`)}</div>
<div className='system-xs-regular text-text-tertiary'>({t(`${i18nPrefix}.optional`)})</div>
<ArrowDownRoundFill className={cn('size text-text-quaternary', isFoldIdeaOutput && 'relative top-[1px] rotate-[-90deg]')} />
</div>
{!isFoldIdeaOutput && (
<Textarea
className="h-[80px]"
placeholder={t(`${i18nPrefix}.idealOutputPlaceholder`)}
value={value}
onChange={e => onChange(e.target.value)}
/>
)}
</div>
)
}
export default React.memo(IdeaOutput)

View File

@@ -0,0 +1,58 @@
'use client'
import type { FC } from 'react'
import React, { useCallback } from 'react'
import type { GeneratorType } from './types'
import type { ValueSelector, Var } from '@/app/components/workflow/types'
import { VarType } from '@/app/components/workflow/types'
import useAvailableVarList from '@/app/components/workflow/nodes/_base/hooks/use-available-var-list'
import InstructionEditor from './instruction-editor'
import { useWorkflowVariableType } from '@/app/components/workflow/hooks'
import { useWorkflowStore } from '@/app/components/workflow/store'
type Props = {
nodeId: string
value: string
editorKey: string
onChange: (text: string) => void
generatorType: GeneratorType
isShowCurrentBlock: boolean
}
const InstructionEditorInWorkflow: FC<Props> = ({
nodeId,
value,
editorKey,
onChange,
generatorType,
isShowCurrentBlock,
}) => {
const workflowStore = useWorkflowStore()
const filterVar = useCallback((payload: Var, selector: ValueSelector) => {
const { nodesWithInspectVars } = workflowStore.getState()
const nodeId = selector?.[0]
return !!nodesWithInspectVars.find(node => node.nodeId === nodeId) && payload.type !== VarType.file && payload.type !== VarType.arrayFile
}, [workflowStore])
const {
availableVars,
availableNodes,
} = useAvailableVarList(nodeId, {
onlyLeafNodeVar: false,
filterVar,
})
const getVarType = useWorkflowVariableType()
return (
<InstructionEditor
value={value}
onChange={onChange}
editorKey={editorKey}
generatorType={generatorType}
availableVars={availableVars}
availableNodes={availableNodes}
getVarType={getVarType}
isShowCurrentBlock={isShowCurrentBlock}
isShowLastRunBlock
/>
)
}
export default React.memo(InstructionEditorInWorkflow)

View File

@@ -0,0 +1,117 @@
'use client'
import type { FC } from 'react'
import React from 'react'
import PromptEditor from '@/app/components/base/prompt-editor'
import type { GeneratorType } from './types'
import cn from '@/utils/classnames'
import type { Node, NodeOutPutVar, ValueSelector } from '@/app/components/workflow/types'
import { BlockEnum } from '@/app/components/workflow/types'
import { useTranslation } from 'react-i18next'
import { Type } from '@/app/components/workflow/nodes/llm/types'
import { PROMPT_EDITOR_INSERT_QUICKLY } from '@/app/components/base/prompt-editor/plugins/update-block'
import { useEventEmitterContextContext } from '@/context/event-emitter'
type Props = {
editorKey: string
value: string
onChange: (text: string) => void
generatorType: GeneratorType
availableVars: NodeOutPutVar[]
availableNodes: Node[]
getVarType?: (params: {
nodeId: string,
valueSelector: ValueSelector,
}) => Type
isShowCurrentBlock: boolean
isShowLastRunBlock: boolean
}
const i18nPrefix = 'appDebug.generate'
const InstructionEditor: FC<Props> = ({
editorKey,
generatorType,
value,
onChange,
availableVars,
availableNodes,
getVarType = () => Type.string,
isShowCurrentBlock,
isShowLastRunBlock,
}) => {
const { t } = useTranslation()
const { eventEmitter } = useEventEmitterContextContext()
const isCode = generatorType === 'code'
const placeholder = isCode ? <div className='system-sm-regular whitespace-break-spaces !leading-6 text-text-placeholder'>
{t(`${i18nPrefix}.codeGenInstructionPlaceHolderLine`)}
</div> : (
<div className='system-sm-regular text-text-placeholder'>
<div className='leading-6'>{t(`${i18nPrefix}.instructionPlaceHolderTitle`)}</div>
<div className='mt-2'>
<div>{t(`${i18nPrefix}.instructionPlaceHolderLine1`)}</div>
<div>{t(`${i18nPrefix}.instructionPlaceHolderLine2`)}</div>
<div>{t(`${i18nPrefix}.instructionPlaceHolderLine3`)}</div>
</div>
</div>
)
const handleInsertVariable = () => {
eventEmitter?.emit({ type: PROMPT_EDITOR_INSERT_QUICKLY, instanceId: editorKey } as any)
}
return (
<div className='relative'>
<PromptEditor
wrapperClassName='border !border-components-input-bg-normal bg-components-input-bg-normal hover:!border-components-input-bg-hover rounded-[10px] px-4 pt-3'
key={editorKey}
instanceId={editorKey}
placeholder={placeholder}
placeholderClassName='px-4 pt-3'
className={cn('min-h-[240px] pb-8')}
value={value}
workflowVariableBlock={{
show: true,
variables: availableVars,
getVarType,
workflowNodesMap: availableNodes.reduce((acc, node) => {
acc[node.id] = {
title: node.data.title,
type: node.data.type,
width: node.width,
height: node.height,
position: node.position,
}
if (node.data.type === BlockEnum.Start) {
acc.sys = {
title: t('workflow.blocks.start'),
type: BlockEnum.Start,
}
}
return acc
}, {} as any),
}}
currentBlock={{
show: isShowCurrentBlock,
generatorType,
}}
errorMessageBlock={{
show: isCode,
}}
lastRunBlock={{
show: isShowLastRunBlock,
}}
onChange={onChange}
editable
isSupportFileVar={false}
/>
<div className='system-xs-regular absolute bottom-0 left-4 flex h-8 items-center space-x-0.5 text-components-input-text-placeholder'>
<span>{t('appDebug.generate.press')}</span>
<span className='system-kbd flex h-4 w-3.5 items-center justify-center rounded-[4px] bg-components-kbd-bg-gray text-text-placeholder'>/</span>
<span>{t('appDebug.generate.to')}</span>
<span onClick={handleInsertVariable} className='!ml-1 cursor-pointer hover:border-b hover:border-dotted hover:border-text-tertiary hover:text-text-tertiary'>{t('appDebug.generate.insertContext')}</span>
</div>
</div>
)
}
export default React.memo(InstructionEditor)

View File

@@ -0,0 +1,55 @@
'use client'
import useAvailableVarList from '@/app/components/workflow/nodes/_base/hooks/use-available-var-list'
import type { FC } from 'react'
import React from 'react'
import PromptRes from './prompt-res'
import { Type } from '@/app/components/workflow/nodes/llm/types'
import { BlockEnum } from '@/app/components/workflow/types'
import { useTranslation } from 'react-i18next'
type Props = {
value: string
nodeId: string
}
const PromptResInWorkflow: FC<Props> = ({
value,
nodeId,
}) => {
const { t } = useTranslation()
const {
availableVars,
availableNodes,
} = useAvailableVarList(nodeId, {
onlyLeafNodeVar: false,
filterVar: _payload => true,
})
return (
<PromptRes
value={value}
workflowVariableBlock={{
show: true,
variables: availableVars || [],
getVarType: () => Type.string,
workflowNodesMap: availableNodes.reduce((acc, node) => {
acc[node.id] = {
title: node.data.title,
type: node.data.type,
width: node.width,
height: node.height,
position: node.position,
}
if (node.data.type === BlockEnum.Start) {
acc.sys = {
title: t('workflow.blocks.start'),
type: BlockEnum.Start,
}
}
return acc
}, {} as any),
}}
>
</PromptRes>
)
}
export default React.memo(PromptResInWorkflow)

View File

@@ -0,0 +1,31 @@
'use client'
import PromptEditor from '@/app/components/base/prompt-editor'
import type { WorkflowVariableBlockType } from '@/app/components/base/prompt-editor/types'
import type { FC } from 'react'
import React, { useEffect } from 'react'
type Props = {
value: string
workflowVariableBlock: WorkflowVariableBlockType
}
const keyIdPrefix = 'prompt-res-editor'
const PromptRes: FC<Props> = ({
value,
workflowVariableBlock,
}) => {
const [editorKey, setEditorKey] = React.useState<string>('keyIdPrefix-0')
useEffect(() => {
setEditorKey(`${keyIdPrefix}-${Date.now()}`)
}, [value])
return (
<PromptEditor
key={editorKey}
value={value}
editable={false}
className='h-full bg-transparent pt-0'
workflowVariableBlock={workflowVariableBlock}
/>
)
}
export default React.memo(PromptRes)

View File

@@ -0,0 +1,54 @@
import { RiArrowDownSLine, RiSparklingFill } from '@remixicon/react'
import { useBoolean } from 'ahooks'
import React from 'react'
import cn from '@/utils/classnames'
import { Markdown } from '@/app/components/base/markdown'
import { useTranslation } from 'react-i18next'
import s from './style.module.css'
type Props = {
message: string
className?: string
}
const PromptToast = ({
message,
className,
}: Props) => {
const { t } = useTranslation()
const [isFold, {
toggle: toggleFold,
}] = useBoolean(false)
// const message = `
// list1list1list1list1list1list1list1list1list1list1list1list1list1list1list1list1list1list1list1list1list1list1list1list1list1list1list1list1list1list1
// # h1
// **strong text** ~~strikethrough~~
// * list1list1list1list1list1list1list1list1list1list1list1list1list1list1list1
// * list2
// xxxx
// ## h2
// \`\`\`python
// print('Hello, World!')
// \`\`\`
// `
return (
<div className={cn('rounded-xl border-[0.5px] border-components-panel-border bg-background-section-burn pl-4 shadow-xs', className)}>
<div className='my-3 flex h-4 items-center justify-between pr-3'>
<div className='flex items-center space-x-1'>
<RiSparklingFill className='size-3.5 text-components-input-border-active-prompt-1' />
<span className={cn(s.optimizationNoteText, 'system-xs-semibold-uppercase')}>{t('appDebug.generate.optimizationNote')}</span>
</div>
<RiArrowDownSLine className={cn('size-4 cursor-pointer text-text-tertiary', isFold && 'rotate-[-90deg]')} onClick={toggleFold} />
</div>
{!isFold && (
<div className='pb-4 pr-4'>
<Markdown className="!text-sm" content={message} />
</div>
)}
</div>
)
}
export default PromptToast

View File

@@ -0,0 +1,18 @@
'use client'
import { Generator } from '@/app/components/base/icons/src/vender/other'
import type { FC } from 'react'
import React from 'react'
import { useTranslation } from 'react-i18next'
const ResPlaceholder: FC = () => {
const { t } = useTranslation()
return (
<div className='flex h-full w-0 grow flex-col items-center justify-center space-y-3 px-8'>
<Generator className='size-8 text-text-quaternary' />
<div className='text-center text-[13px] font-normal leading-5 text-text-tertiary'>
<div>{t('appDebug.generate.newNoDataLine1')}</div>
</div>
</div>
)
}
export default React.memo(ResPlaceholder)

View File

@@ -0,0 +1,97 @@
'use client'
import type { FC } from 'react'
import React from 'react'
import { useTranslation } from 'react-i18next'
import { GeneratorType } from './types'
import PromptToast from './prompt-toast'
import Button from '@/app/components/base/button'
import VersionSelector from './version-selector'
import type { GenRes } from '@/service/debug'
import { RiClipboardLine } from '@remixicon/react'
import copy from 'copy-to-clipboard'
import Toast from '@/app/components/base/toast'
import CodeEditor from '@/app/components/workflow/nodes/llm/components/json-schema-config-modal/code-editor'
import PromptRes from './prompt-res'
import PromptResInWorkflow from './prompt-res-in-workflow'
type Props = {
isBasicMode?: boolean
nodeId?: string
current: GenRes
currentVersionIndex: number
setCurrentVersionIndex: (index: number) => void
versions: GenRes[]
onApply: () => void
generatorType: GeneratorType
}
const Result: FC<Props> = ({
isBasicMode,
nodeId,
current,
currentVersionIndex,
setCurrentVersionIndex,
versions,
onApply,
generatorType,
}) => {
const { t } = useTranslation()
const isGeneratorPrompt = generatorType === GeneratorType.prompt
return (
<div className='flex h-full flex-col'>
<div className='mb-3 flex shrink-0 items-center justify-between'>
<div>
<div className='shrink-0 text-base font-semibold leading-[160%] text-text-secondary'>{t('appDebug.generate.resTitle')}</div>
<VersionSelector
versionLen={versions.length}
value={currentVersionIndex}
onChange={setCurrentVersionIndex}
/>
</div>
<div className='flex items-center space-x-2'>
<Button className='px-2' onClick={() => {
copy(current.modified)
Toast.notify({ type: 'success', message: t('common.actionMsg.copySuccessfully') })
}}>
<RiClipboardLine className='h-4 w-4 text-text-secondary' />
</Button>
<Button variant='primary' onClick={onApply}>
{t('appDebug.generate.apply')}
</Button>
</div>
</div>
<div className='flex grow flex-col overflow-y-auto'>
{
current?.message && (
<PromptToast message={current.message} className='mb-3 shrink-0' />
)
}
<div className='grow pb-6'>
{isGeneratorPrompt ? (
isBasicMode ? (
<PromptRes
value={current?.modified}
workflowVariableBlock={{
show: false,
}}
/>
) : (<PromptResInWorkflow
value={current?.modified || ''}
nodeId={nodeId!}
/>)
) : (
<CodeEditor
editorWrapperClassName='h-full'
className='bg-transparent pt-0'
value={current?.modified}
readOnly
hideTopMenu
/>
)}
</div>
</div>
</div>
)
}
export default React.memo(Result)

View File

@@ -3,5 +3,11 @@
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
text-fill-color: transparent;
}
.optimizationNoteText {
background: linear-gradient(263deg, rgba(21, 90, 239, 0.95) -20.92%, rgba(11, 165, 236, 0.95) 87.04%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}

View File

@@ -0,0 +1,4 @@
export enum GeneratorType {
prompt = 'prompt',
code = 'code',
}

View File

@@ -0,0 +1,36 @@
import type { GenRes } from '@/service/debug'
import { useSessionStorageState } from 'ahooks'
import { useCallback } from 'react'
type Params = {
storageKey: string
}
const keyPrefix = 'gen-data-'
const useGenData = ({ storageKey }: Params) => {
const [versions, setVersions] = useSessionStorageState<GenRes[]>(`${keyPrefix}${storageKey}-versions`, {
defaultValue: [],
})
const [currentVersionIndex, setCurrentVersionIndex] = useSessionStorageState<number>(`${keyPrefix}${storageKey}-version-index`, {
defaultValue: 0,
})
const current = versions?.[currentVersionIndex || 0]
const addVersion = useCallback((version: GenRes) => {
setCurrentVersionIndex(() => versions?.length || 0)
setVersions((prev) => {
return [...prev!, version]
})
}, [setVersions, setCurrentVersionIndex, versions?.length])
return {
versions,
addVersion,
currentVersionIndex,
setCurrentVersionIndex,
current,
}
}
export default useGenData

View File

@@ -0,0 +1,103 @@
import React, { useCallback } from 'react'
import { PortalToFollowElem, PortalToFollowElemContent, PortalToFollowElemTrigger } from '@/app/components/base/portal-to-follow-elem'
import { useBoolean } from 'ahooks'
import cn from '@/utils/classnames'
import { RiArrowDownSLine, RiCheckLine } from '@remixicon/react'
import { useTranslation } from 'react-i18next'
type Option = {
label: string
value: number
}
type VersionSelectorProps = {
versionLen: number;
value: number;
onChange: (index: number) => void;
}
const VersionSelector: React.FC<VersionSelectorProps> = ({ versionLen, value, onChange }) => {
const { t } = useTranslation()
const [isOpen, {
setFalse: handleOpenFalse,
toggle: handleOpenToggle,
set: handleOpenSet,
}] = useBoolean(false)
const moreThanOneVersion = versionLen > 1
const handleOpen = useCallback((value: boolean) => {
if (moreThanOneVersion)
handleOpenSet(value)
}, [moreThanOneVersion, handleOpenToggle])
const handleToggle = useCallback(() => {
if (moreThanOneVersion)
handleOpenToggle()
}, [moreThanOneVersion, handleOpenToggle])
const versions = Array.from({ length: versionLen }, (_, index) => ({
label: `${t('appDebug.generate.version')} ${index + 1}${index === versionLen - 1 ? ` · ${t('appDebug.generate.latest')}` : ''}`,
value: index,
}))
const isLatest = value === versionLen - 1
return (
<PortalToFollowElem
placement={'bottom-start'}
offset={{
mainAxis: 4,
crossAxis: -12,
}}
open={isOpen}
onOpenChange={handleOpen}
>
<PortalToFollowElemTrigger
onClick={handleToggle}
asChild
>
<div className={cn('system-xs-medium flex items-center text-text-tertiary', isOpen && 'text-text-secondary', moreThanOneVersion && 'cursor-pointer')}>
<div>{t('appDebug.generate.version')} {value + 1}{isLatest && ` · ${t('appDebug.generate.latest')}`}</div>
{moreThanOneVersion && <RiArrowDownSLine className='size-3 ' />}
</div>
</PortalToFollowElemTrigger >
<PortalToFollowElemContent className={cn(
'z-[99]',
)}>
<div
className={cn(
'w-[208px] rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg-blur p-1 shadow-lg',
)}
>
<div className={cn('system-xs-medium-uppercase flex h-[22px] items-center px-3 pl-3 text-text-tertiary')}>
{t('appDebug.generate.versions')}
</div>
{
versions.map(option => (
<div
key={option.value}
className={cn(
'system-sm-medium flex h-7 cursor-pointer items-center rounded-lg px-2 text-text-secondary hover:bg-state-base-hover',
)}
title={option.label}
onClick={() => {
onChange(option.value)
handleOpenFalse()
}}
>
<div className='mr-1 grow truncate px-1 pl-1'>
{option.label}
</div>
{
value === option.value && <RiCheckLine className='h-4 w-4 shrink-0 text-text-accent' />
}
</div>
))
}
</div>
</PortalToFollowElemContent>
</PortalToFollowElem >
)
}
export default VersionSelector

View File

@@ -1,16 +1,13 @@
import type { FC } from 'react'
import React, { useCallback, useEffect } from 'react'
import cn from 'classnames'
import React, { useCallback, useEffect, useState } from 'react'
import useBoolean from 'ahooks/lib/useBoolean'
import { useTranslation } from 'react-i18next'
import ConfigPrompt from '../../config-prompt'
import { languageMap } from '../../../../workflow/nodes/_base/components/editor/code-editor/index'
import { generateRuleCode } from '@/service/debug'
import type { CodeGenRes } from '@/service/debug'
import { generateRule } from '@/service/debug'
import type { GenRes } from '@/service/debug'
import type { ModelModeType } from '@/types/app'
import type { AppType, CompletionParams, Model } from '@/types/app'
import Modal from '@/app/components/base/modal'
import Textarea from '@/app/components/base/textarea'
import Button from '@/app/components/base/button'
import { Generator } from '@/app/components/base/icons/src/vender/other'
import Toast from '@/app/components/base/toast'
@@ -21,17 +18,33 @@ import { useModelListAndDefaultModelAndCurrentProviderAndModel } from '@/app/com
import { ModelTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations'
import ModelParameterModal from '@/app/components/header/account-setting/model-provider-page/model-parameter-modal'
import type { FormValue } from '@/app/components/header/account-setting/model-provider-page/declarations'
import IdeaOutput from '../automatic/idea-output'
import { GeneratorType } from '../automatic/types'
import InstructionEditor from '../automatic/instruction-editor-in-workflow'
import useGenData from '../automatic/use-gen-data'
import Result from '../automatic/result'
import ResPlaceholder from '../automatic/res-placeholder'
import { useGenerateRuleTemplate } from '@/service/use-apps'
import { useSessionStorageState } from 'ahooks'
import s from '../automatic/style.module.css'
const i18nPrefix = 'appDebug.generate'
export type IGetCodeGeneratorResProps = {
flowId: string
nodeId: string
currentCode?: string
mode: AppType
isShow: boolean
codeLanguages: CodeLanguage
onClose: () => void
onFinished: (res: CodeGenRes) => void
onFinished: (res: GenRes) => void
}
export const GetCodeGeneratorResModal: FC<IGetCodeGeneratorResProps> = (
{
flowId,
nodeId,
currentCode,
mode,
isShow,
codeLanguages,
@@ -61,9 +74,25 @@ export const GetCodeGeneratorResModal: FC<IGetCodeGeneratorResProps> = (
const {
defaultModel,
} = useModelListAndDefaultModelAndCurrentProviderAndModel(ModelTypeEnum.textGeneration)
const [instruction, setInstruction] = React.useState<string>('')
const [instructionFromSessionStorage, setInstruction] = useSessionStorageState<string>(`improve-instruction-${flowId}-${nodeId}`)
const instruction = instructionFromSessionStorage || ''
const [ideaOutput, setIdeaOutput] = useState<string>('')
const [isLoading, { setTrue: setLoadingTrue, setFalse: setLoadingFalse }] = useBoolean(false)
const [res, setRes] = React.useState<CodeGenRes | null>(null)
const storageKey = `${flowId}-${nodeId}`
const { addVersion, current, currentVersionIndex, setCurrentVersionIndex, versions } = useGenData({
storageKey,
})
const [editorKey, setEditorKey] = useState(`${flowId}-0`)
const { data: instructionTemplate } = useGenerateRuleTemplate(GeneratorType.code)
useEffect(() => {
if (!instruction && instructionTemplate)
setInstruction(instructionTemplate.data)
setEditorKey(`${flowId}-${Date.now()}`)
}, [instructionTemplate])
const isValid = () => {
if (instruction.trim() === '') {
Toast.notify({
@@ -97,7 +126,6 @@ export const GetCodeGeneratorResModal: FC<IGetCodeGeneratorResProps> = (
localStorage.setItem('auto-gen-model', JSON.stringify(newModel))
}, [model, setModel])
const isInLLMNode = true
const onGenerate = async () => {
if (!isValid())
return
@@ -105,25 +133,35 @@ export const GetCodeGeneratorResModal: FC<IGetCodeGeneratorResProps> = (
return
setLoadingTrue()
try {
const { error, ...res } = await generateRuleCode({
const { error, ...res } = await generateRule({
flow_id: flowId,
node_id: nodeId,
current: currentCode,
instruction,
model_config: model,
no_variable: !!isInLLMNode,
code_language: languageMap[codeLanguages] || 'javascript',
ideal_output: ideaOutput,
language: languageMap[codeLanguages] || 'javascript',
})
setRes(res)
if (error) {
Toast.notify({
type: 'error',
message: error,
})
}
else {
addVersion(res)
}
}
finally {
setLoadingFalse()
}
}
const [showConfirmOverwrite, setShowConfirmOverwrite] = React.useState(false)
const [isShowConfirmOverwrite, {
setTrue: showConfirmOverwrite,
setFalse: hideShowConfirmOverwrite,
}] = useBoolean(false)
useEffect(() => {
if (defaultModel) {
@@ -155,30 +193,20 @@ export const GetCodeGeneratorResModal: FC<IGetCodeGeneratorResProps> = (
<div className='text-[13px] text-text-tertiary'>{t('appDebug.codegen.loading')}</div>
</div>
)
const renderNoData = (
<div className='flex h-full w-0 grow flex-col items-center justify-center space-y-3 px-8'>
<Generator className='h-14 w-14 text-text-tertiary' />
<div className='text-center text-[13px] font-normal leading-5 text-text-tertiary'>
<div>{t('appDebug.codegen.noDataLine1')}</div>
<div>{t('appDebug.codegen.noDataLine2')}</div>
</div>
</div>
)
return (
<Modal
isShow={isShow}
onClose={onClose}
className='min-w-[1140px] !p-0'
closable
>
<div className='relative flex h-[680px] flex-wrap'>
<div className='h-full w-[570px] shrink-0 overflow-y-auto border-r border-divider-regular p-8'>
<div className='mb-8'>
<div className={'text-lg font-bold leading-[28px] text-text-primary'}>{t('appDebug.codegen.title')}</div>
<div className='h-full w-[570px] shrink-0 overflow-y-auto border-r border-divider-regular p-6'>
<div className='mb-5'>
<div className={`text-lg font-bold leading-[28px] ${s.textGradient}`}>{t('appDebug.codegen.title')}</div>
<div className='mt-1 text-[13px] font-normal text-text-tertiary'>{t('appDebug.codegen.description')}</div>
</div>
<div className='mb-8'>
<div className='mb-4'>
<ModelParameterModal
popupClassName='!w-[520px]'
portalToFollowElemContentClassName='z-[1000]'
@@ -194,84 +222,60 @@ export const GetCodeGeneratorResModal: FC<IGetCodeGeneratorResProps> = (
</div>
<div>
<div className='text-[0px]'>
<div className='mb-2 text-sm font-medium leading-5 text-text-primary'>{t('appDebug.codegen.instruction')}</div>
<Textarea
className="h-[200px] resize-none"
placeholder={t('appDebug.codegen.instructionPlaceholder') || ''}
<div className='system-sm-semibold-uppercase mb-1.5 text-text-secondary'>{t('appDebug.codegen.instruction')}</div>
<InstructionEditor
editorKey={editorKey}
value={instruction}
onChange={e => setInstruction(e.target.value)}
onChange={setInstruction}
nodeId={nodeId}
generatorType={GeneratorType.code}
isShowCurrentBlock={!!currentCode}
/>
</div>
<IdeaOutput
value={ideaOutput}
onChange={setIdeaOutput}
/>
<div className='mt-5 flex justify-end'>
<div className='mt-7 flex justify-end space-x-2'>
<Button onClick={onClose}>{t(`${i18nPrefix}.dismiss`)}</Button>
<Button
className='flex space-x-1'
variant='primary'
onClick={onGenerate}
disabled={isLoading}
>
<Generator className='h-4 w-4 text-white' />
<span className='text-xs font-semibold text-white'>{t('appDebug.codegen.generate')}</span>
<Generator className='h-4 w-4' />
<span className='text-xs font-semibold '>{t('appDebug.codegen.generate')}</span>
</Button>
</div>
</div>
</div>
{isLoading && renderLoading}
{!isLoading && !res && renderNoData}
{(!isLoading && res) && (
<div className='h-full w-0 grow p-6 pb-0'>
<div className='mb-3 shrink-0 text-base font-semibold leading-[160%] text-text-secondary'>{t('appDebug.codegen.resTitle')}</div>
<div className={cn('max-h-[555px] overflow-y-auto', !isInLLMNode && 'pb-2')}>
<ConfigPrompt
mode={mode}
promptTemplate={res?.code || ''}
promptVariables={[]}
readonly
noTitle={isInLLMNode}
gradientBorder
editorHeight={isInLLMNode ? 524 : 0}
noResize={isInLLMNode}
/>
{!isInLLMNode && (
<>
{res?.code && (
<div className='mt-4'>
<h3 className='mb-2 text-sm font-medium text-text-primary'>{t('appDebug.codegen.generatedCode')}</h3>
<pre className='overflow-x-auto rounded-lg bg-gray-50 p-4'>
<code className={`language-${res.language}`}>
{res.code}
</code>
</pre>
</div>
)}
{res?.error && (
<div className='mt-4 rounded-lg bg-red-50 p-4'>
<p className='text-sm text-red-600'>{res.error}</p>
</div>
)}
</>
)}
</div>
<div className='flex justify-end bg-background-default py-4'>
<Button onClick={onClose}>{t('common.operation.cancel')}</Button>
<Button variant='primary' className='ml-2' onClick={() => {
setShowConfirmOverwrite(true)
}}>{t('appDebug.codegen.apply')}</Button>
</div>
{!isLoading && !current && <ResPlaceholder />}
{(!isLoading && current) && (
<div className='h-full w-0 grow bg-background-default-subtle p-6 pb-0'>
<Result
current={current!}
currentVersionIndex={currentVersionIndex || 0}
setCurrentVersionIndex={setCurrentVersionIndex}
versions={versions || []}
onApply={showConfirmOverwrite}
generatorType={GeneratorType.code}
/>
</div>
)}
</div>
{showConfirmOverwrite && (
{isShowConfirmOverwrite && (
<Confirm
title={t('appDebug.codegen.overwriteConfirmTitle')}
content={t('appDebug.codegen.overwriteConfirmMessage')}
isShow={showConfirmOverwrite}
isShow
onConfirm={() => {
setShowConfirmOverwrite(false)
onFinished(res!)
hideShowConfirmOverwrite()
onFinished(current!)
}}
onCancel={() => setShowConfirmOverwrite(false)}
onCancel={hideShowConfirmOverwrite}
/>
)}
</Modal>

View File

@@ -0,0 +1,6 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M12 3C12.5523 3 13 3.44772 13 4C13 4.55228 12.5523 5 12 5H5.59962C5.30336 5 5.14096 5.00122 5.02443 5.01074C5.01998 5.01111 5.01573 5.01135 5.01173 5.01172C5.01136 5.01572 5.01112 5.01996 5.01076 5.02441C5.00124 5.14095 5.00001 5.30334 5.00001 5.59961V18.4004C5.00001 18.6967 5.00124 18.8591 5.01076 18.9756C5.01109 18.9797 5.01139 18.9836 5.01173 18.9873C5.01575 18.9877 5.01995 18.9889 5.02443 18.9893C5.14096 18.9988 5.30336 19 5.59962 19H18.4004C18.6967 19 18.8591 18.9988 18.9756 18.9893C18.9797 18.9889 18.9836 18.9876 18.9873 18.9873C18.9877 18.9836 18.9889 18.9797 18.9893 18.9756C18.9988 18.8591 19 18.6967 19 18.4004V12C19 11.4477 19.4477 11 20 11C20.5523 11 21 11.4477 21 12V18.4004C21 18.6638 21.0011 18.9219 20.9834 19.1387C20.9647 19.3672 20.9205 19.6369 20.7822 19.9082C20.5905 20.2845 20.2845 20.5905 19.9082 20.7822C19.6369 20.9205 19.3672 20.9647 19.1387 20.9834C18.9219 21.0011 18.6638 21 18.4004 21H5.59962C5.33625 21 5.07815 21.0011 4.86134 20.9834C4.63284 20.9647 4.36307 20.9204 4.09181 20.7822C3.71579 20.5906 3.40963 20.2847 3.21779 19.9082C3.07958 19.6369 3.03531 19.3672 3.01662 19.1387C2.9989 18.9219 3.00001 18.6638 3.00001 18.4004V5.59961C3.00001 5.33623 2.9989 5.07814 3.01662 4.86133C3.03531 4.63283 3.07958 4.36305 3.21779 4.0918C3.40953 3.71555 3.71557 3.40952 4.09181 3.21777C4.36306 3.07957 4.63285 3.0353 4.86134 3.0166C5.07815 2.99889 5.33624 3 5.59962 3H12Z" fill="black"/>
<path d="M13.293 9.29297C13.6835 8.90244 14.3165 8.90244 14.707 9.29297L16.707 11.293C17.0975 11.6835 17.0975 12.3165 16.707 12.707L14.707 14.707C14.3165 15.0975 13.6835 15.0975 13.293 14.707C12.9025 14.3165 12.9025 13.6835 13.293 13.293L14.586 12L13.293 10.707C12.9025 10.3165 12.9025 9.68349 13.293 9.29297Z" fill="black"/>
<path d="M9.29298 9.29297C9.68351 8.90244 10.3165 8.90244 10.707 9.29297C11.0975 9.6835 11.0975 10.3165 10.707 10.707L9.41408 12L10.707 13.293L10.7754 13.3691C11.0957 13.7619 11.0731 14.3409 10.707 14.707C10.341 15.0731 9.76192 15.0957 9.36915 14.7754L9.29298 14.707L7.29298 12.707C6.90246 12.3165 6.90246 11.6835 7.29298 11.293L9.29298 9.29297Z" fill="black"/>
<path d="M18.501 2C18.7487 2.00048 18.9564 2.18654 18.9844 2.43262C19.1561 3.94883 20.0165 4.87694 21.5557 5.01367C21.8075 5.03614 22.0003 5.24816 22 5.50098C21.9995 5.75356 21.8063 5.96437 21.5547 5.98633C20.0372 6.11768 19.1176 7.03726 18.9863 8.55469C18.9644 8.80629 18.7536 8.99948 18.501 9C18.2483 9.00028 18.0362 8.80743 18.0137 8.55566C17.877 7.01647 16.9488 6.15613 15.4326 5.98438C15.1866 5.95634 15.0006 5.74863 15 5.50098C14.9997 5.25311 15.1855 5.04417 15.4317 5.01563C16.9697 4.83823 17.8382 3.96966 18.0156 2.43164C18.0442 2.18545 18.2531 1.99975 18.501 2Z" fill="black"/>
</svg>

After

Width:  |  Height:  |  Size: 2.7 KiB

View File

@@ -0,0 +1,6 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M15.3691 3.22448C15.7619 2.90422 16.3409 2.92682 16.707 3.29284L20.707 7.29284C21.0975 7.68334 21.0975 8.31637 20.707 8.7069L8.70703 20.7069C8.5195 20.8944 8.26521 20.9999 8 20.9999H4C3.44771 20.9999 3 20.5522 3 19.9999V15.9999L3.00488 15.9012C3.0276 15.6723 3.12886 15.4569 3.29297 15.2928L15.293 3.29284L15.3691 3.22448ZM5 16.4139V18.9999H7.58593L18.5859 7.99987L16 5.41394L5 16.4139Z" fill="black"/>
<path d="M18.2451 15.1581C18.3502 14.9478 18.6498 14.9478 18.7549 15.1581L19.4082 16.4637C19.4358 16.5189 19.4809 16.5641 19.5361 16.5917L20.8428 17.245C21.0525 17.3502 21.0524 17.6495 20.8428 17.7548L19.5361 18.4081C19.4809 18.4357 19.4358 18.4808 19.4082 18.536L18.7549 19.8426C18.6497 20.0525 18.3503 20.0525 18.2451 19.8426L17.5918 18.536C17.5642 18.4808 17.5191 18.4357 17.4639 18.4081L16.1572 17.7548C15.9476 17.6495 15.9475 17.3502 16.1572 17.245L17.4639 16.5917C17.5191 16.5641 17.5642 16.5189 17.5918 16.4637L18.2451 15.1581Z" fill="black"/>
<path d="M4.24511 5.15808C4.35024 4.94783 4.64975 4.94783 4.75488 5.15808L5.4082 6.46374C5.4358 6.51895 5.48092 6.56406 5.53613 6.59167L6.84277 7.24499C7.05248 7.3502 7.05238 7.64946 6.84277 7.75476L5.53613 8.40808C5.48092 8.43568 5.4358 8.4808 5.4082 8.53601L4.75488 9.84265C4.64966 10.0525 4.35033 10.0525 4.24511 9.84265L3.59179 8.53601C3.56418 8.4808 3.51907 8.43568 3.46386 8.40808L2.15722 7.75476C1.94764 7.64945 1.94755 7.35021 2.15722 7.24499L3.46386 6.59167C3.51907 6.56406 3.56418 6.51895 3.59179 6.46374L4.24511 5.15808Z" fill="black"/>
<path d="M8.73535 2.16394C8.84432 1.94601 9.15568 1.94601 9.26465 2.16394L9.74414 3.12292C9.77275 3.18014 9.81973 3.22712 9.87695 3.25573L10.8369 3.73522C11.0546 3.84424 11.0545 4.15544 10.8369 4.26452L9.87695 4.74401C9.81973 4.77262 9.77275 4.81961 9.74414 4.87683L9.26465 5.83679C9.15565 6.05458 8.84435 6.05457 8.73535 5.83679L8.25586 4.87683C8.22725 4.81961 8.18026 4.77262 8.12304 4.74401L7.16308 4.26452C6.94547 4.15546 6.94539 3.84422 7.16308 3.73522L8.12304 3.25573C8.18026 3.22712 8.22725 3.18014 8.25586 3.12292L8.73535 2.16394Z" fill="black"/>
</svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

@@ -0,0 +1,53 @@
{
"icon": {
"type": "element",
"isRootNode": true,
"name": "svg",
"attributes": {
"width": "24",
"height": "24",
"viewBox": "0 0 24 24",
"fill": "none",
"xmlns": "http://www.w3.org/2000/svg"
},
"children": [
{
"type": "element",
"name": "path",
"attributes": {
"d": "M12 3C12.5523 3 13 3.44772 13 4C13 4.55228 12.5523 5 12 5H5.59962C5.30336 5 5.14096 5.00122 5.02443 5.01074C5.01998 5.01111 5.01573 5.01135 5.01173 5.01172C5.01136 5.01572 5.01112 5.01996 5.01076 5.02441C5.00124 5.14095 5.00001 5.30334 5.00001 5.59961V18.4004C5.00001 18.6967 5.00124 18.8591 5.01076 18.9756C5.01109 18.9797 5.01139 18.9836 5.01173 18.9873C5.01575 18.9877 5.01995 18.9889 5.02443 18.9893C5.14096 18.9988 5.30336 19 5.59962 19H18.4004C18.6967 19 18.8591 18.9988 18.9756 18.9893C18.9797 18.9889 18.9836 18.9876 18.9873 18.9873C18.9877 18.9836 18.9889 18.9797 18.9893 18.9756C18.9988 18.8591 19 18.6967 19 18.4004V12C19 11.4477 19.4477 11 20 11C20.5523 11 21 11.4477 21 12V18.4004C21 18.6638 21.0011 18.9219 20.9834 19.1387C20.9647 19.3672 20.9205 19.6369 20.7822 19.9082C20.5905 20.2845 20.2845 20.5905 19.9082 20.7822C19.6369 20.9205 19.3672 20.9647 19.1387 20.9834C18.9219 21.0011 18.6638 21 18.4004 21H5.59962C5.33625 21 5.07815 21.0011 4.86134 20.9834C4.63284 20.9647 4.36307 20.9204 4.09181 20.7822C3.71579 20.5906 3.40963 20.2847 3.21779 19.9082C3.07958 19.6369 3.03531 19.3672 3.01662 19.1387C2.9989 18.9219 3.00001 18.6638 3.00001 18.4004V5.59961C3.00001 5.33623 2.9989 5.07814 3.01662 4.86133C3.03531 4.63283 3.07958 4.36305 3.21779 4.0918C3.40953 3.71555 3.71557 3.40952 4.09181 3.21777C4.36306 3.07957 4.63285 3.0353 4.86134 3.0166C5.07815 2.99889 5.33624 3 5.59962 3H12Z",
"fill": "currentColor"
},
"children": []
},
{
"type": "element",
"name": "path",
"attributes": {
"d": "M13.293 9.29297C13.6835 8.90244 14.3165 8.90244 14.707 9.29297L16.707 11.293C17.0975 11.6835 17.0975 12.3165 16.707 12.707L14.707 14.707C14.3165 15.0975 13.6835 15.0975 13.293 14.707C12.9025 14.3165 12.9025 13.6835 13.293 13.293L14.586 12L13.293 10.707C12.9025 10.3165 12.9025 9.68349 13.293 9.29297Z",
"fill": "currentColor"
},
"children": []
},
{
"type": "element",
"name": "path",
"attributes": {
"d": "M9.29298 9.29297C9.68351 8.90244 10.3165 8.90244 10.707 9.29297C11.0975 9.6835 11.0975 10.3165 10.707 10.707L9.41408 12L10.707 13.293L10.7754 13.3691C11.0957 13.7619 11.0731 14.3409 10.707 14.707C10.341 15.0731 9.76192 15.0957 9.36915 14.7754L9.29298 14.707L7.29298 12.707C6.90246 12.3165 6.90246 11.6835 7.29298 11.293L9.29298 9.29297Z",
"fill": "currentColor"
},
"children": []
},
{
"type": "element",
"name": "path",
"attributes": {
"d": "M18.501 2C18.7487 2.00048 18.9564 2.18654 18.9844 2.43262C19.1561 3.94883 20.0165 4.87694 21.5557 5.01367C21.8075 5.03614 22.0003 5.24816 22 5.50098C21.9995 5.75356 21.8063 5.96437 21.5547 5.98633C20.0372 6.11768 19.1176 7.03726 18.9863 8.55469C18.9644 8.80629 18.7536 8.99948 18.501 9C18.2483 9.00028 18.0362 8.80743 18.0137 8.55566C17.877 7.01647 16.9488 6.15613 15.4326 5.98438C15.1866 5.95634 15.0006 5.74863 15 5.50098C14.9997 5.25311 15.1855 5.04417 15.4317 5.01563C16.9697 4.83823 17.8382 3.96966 18.0156 2.43164C18.0442 2.18545 18.2531 1.99975 18.501 2Z",
"fill": "currentColor"
},
"children": []
}
]
},
"name": "CodeAssistant"
}

View File

@@ -0,0 +1,20 @@
// GENERATE BY script
// DON NOT EDIT IT MANUALLY
import * as React from 'react'
import data from './CodeAssistant.json'
import IconBase from '@/app/components/base/icons/IconBase'
import type { IconData } from '@/app/components/base/icons/IconBase'
const Icon = (
{
ref,
...props
}: React.SVGProps<SVGSVGElement> & {
ref?: React.RefObject<React.MutableRefObject<HTMLOrSVGElement>>;
},
) => <IconBase {...props} ref={ref} data={data as IconData} />
Icon.displayName = 'CodeAssistant'
export default Icon

View File

@@ -0,0 +1,55 @@
{
"icon": {
"type": "element",
"isRootNode": true,
"name": "svg",
"attributes": {
"width": "24",
"height": "24",
"viewBox": "0 0 24 24",
"fill": "none",
"xmlns": "http://www.w3.org/2000/svg"
},
"children": [
{
"type": "element",
"name": "path",
"attributes": {
"fill-rule": "evenodd",
"clip-rule": "evenodd",
"d": "M15.3691 3.22448C15.7619 2.90422 16.3409 2.92682 16.707 3.29284L20.707 7.29284C21.0975 7.68334 21.0975 8.31637 20.707 8.7069L8.70703 20.7069C8.5195 20.8944 8.26521 20.9999 8 20.9999H4C3.44771 20.9999 3 20.5522 3 19.9999V15.9999L3.00488 15.9012C3.0276 15.6723 3.12886 15.4569 3.29297 15.2928L15.293 3.29284L15.3691 3.22448ZM5 16.4139V18.9999H7.58593L18.5859 7.99987L16 5.41394L5 16.4139Z",
"fill": "currentColor"
},
"children": []
},
{
"type": "element",
"name": "path",
"attributes": {
"d": "M18.2451 15.1581C18.3502 14.9478 18.6498 14.9478 18.7549 15.1581L19.4082 16.4637C19.4358 16.5189 19.4809 16.5641 19.5361 16.5917L20.8428 17.245C21.0525 17.3502 21.0524 17.6495 20.8428 17.7548L19.5361 18.4081C19.4809 18.4357 19.4358 18.4808 19.4082 18.536L18.7549 19.8426C18.6497 20.0525 18.3503 20.0525 18.2451 19.8426L17.5918 18.536C17.5642 18.4808 17.5191 18.4357 17.4639 18.4081L16.1572 17.7548C15.9476 17.6495 15.9475 17.3502 16.1572 17.245L17.4639 16.5917C17.5191 16.5641 17.5642 16.5189 17.5918 16.4637L18.2451 15.1581Z",
"fill": "currentColor"
},
"children": []
},
{
"type": "element",
"name": "path",
"attributes": {
"d": "M4.24511 5.15808C4.35024 4.94783 4.64975 4.94783 4.75488 5.15808L5.4082 6.46374C5.4358 6.51895 5.48092 6.56406 5.53613 6.59167L6.84277 7.24499C7.05248 7.3502 7.05238 7.64946 6.84277 7.75476L5.53613 8.40808C5.48092 8.43568 5.4358 8.4808 5.4082 8.53601L4.75488 9.84265C4.64966 10.0525 4.35033 10.0525 4.24511 9.84265L3.59179 8.53601C3.56418 8.4808 3.51907 8.43568 3.46386 8.40808L2.15722 7.75476C1.94764 7.64945 1.94755 7.35021 2.15722 7.24499L3.46386 6.59167C3.51907 6.56406 3.56418 6.51895 3.59179 6.46374L4.24511 5.15808Z",
"fill": "currentColor"
},
"children": []
},
{
"type": "element",
"name": "path",
"attributes": {
"d": "M8.73535 2.16394C8.84432 1.94601 9.15568 1.94601 9.26465 2.16394L9.74414 3.12292C9.77275 3.18014 9.81973 3.22712 9.87695 3.25573L10.8369 3.73522C11.0546 3.84424 11.0545 4.15544 10.8369 4.26452L9.87695 4.74401C9.81973 4.77262 9.77275 4.81961 9.74414 4.87683L9.26465 5.83679C9.15565 6.05458 8.84435 6.05457 8.73535 5.83679L8.25586 4.87683C8.22725 4.81961 8.18026 4.77262 8.12304 4.74401L7.16308 4.26452C6.94547 4.15546 6.94539 3.84422 7.16308 3.73522L8.12304 3.25573C8.18026 3.22712 8.22725 3.18014 8.25586 3.12292L8.73535 2.16394Z",
"fill": "currentColor"
},
"children": []
}
]
},
"name": "MagicEdit"
}

View File

@@ -0,0 +1,20 @@
// GENERATE BY script
// DON NOT EDIT IT MANUALLY
import * as React from 'react'
import data from './MagicEdit.json'
import IconBase from '@/app/components/base/icons/IconBase'
import type { IconData } from '@/app/components/base/icons/IconBase'
const Icon = (
{
ref,
...props
}: React.SVGProps<SVGSVGElement> & {
ref?: React.RefObject<React.MutableRefObject<HTMLOrSVGElement>>;
},
) => <IconBase {...props} ref={ref} data={data as IconData} />
Icon.displayName = 'MagicEdit'
export default Icon

View File

@@ -3,6 +3,7 @@ export { default as Bookmark } from './Bookmark'
export { default as CheckDone01 } from './CheckDone01'
export { default as Check } from './Check'
export { default as ChecklistSquare } from './ChecklistSquare'
export { default as CodeAssistant } from './CodeAssistant'
export { default as DotsGrid } from './DotsGrid'
export { default as Edit02 } from './Edit02'
export { default as Edit04 } from './Edit04'
@@ -14,6 +15,7 @@ export { default as LinkExternal02 } from './LinkExternal02'
export { default as LogIn04 } from './LogIn04'
export { default as LogOut01 } from './LogOut01'
export { default as LogOut04 } from './LogOut04'
export { default as MagicEdit } from './MagicEdit'
export { default as Menu01 } from './Menu01'
export { default as Pin01 } from './Pin01'
export { default as Pin02 } from './Pin02'

View File

@@ -3,6 +3,10 @@ import { SupportUploadFileTypes, type ValueSelector } from '../../workflow/types
export const CONTEXT_PLACEHOLDER_TEXT = '{{#context#}}'
export const HISTORY_PLACEHOLDER_TEXT = '{{#histories#}}'
export const QUERY_PLACEHOLDER_TEXT = '{{#query#}}'
export const CURRENT_PLACEHOLDER_TEXT = '{{#current#}}'
export const ERROR_MESSAGE_PLACEHOLDER_TEXT = '{{#error_message#}}'
export const LAST_RUN_PLACEHOLDER_TEXT = '{{#last_run#}}'
export const PRE_PROMPT_PLACEHOLDER_TEXT = '{{#pre_prompt#}}'
export const UPDATE_DATASETS_EVENT_EMITTER = 'prompt-editor-context-block-update-datasets'
export const UPDATE_HISTORY_EVENT_EMITTER = 'prompt-editor-history-block-update-role'

View File

@@ -1,7 +1,7 @@
'use client'
import type { FC } from 'react'
import { useEffect } from 'react'
import React, { useEffect } from 'react'
import type {
EditorState,
} from 'lexical'
@@ -39,6 +39,22 @@ import {
WorkflowVariableBlockNode,
WorkflowVariableBlockReplacementBlock,
} from './plugins/workflow-variable-block'
import {
CurrentBlock,
CurrentBlockNode,
CurrentBlockReplacementBlock,
} from './plugins/current-block'
import {
ErrorMessageBlock,
ErrorMessageBlockNode,
ErrorMessageBlockReplacementBlock,
} from './plugins/error-message-block'
import {
LastRunBlock,
LastRunBlockNode,
LastRunReplacementBlock,
} from './plugins/last-run-block'
import VariableBlock from './plugins/variable-block'
import VariableValueBlock from './plugins/variable-value-block'
import { VariableValueBlockNode } from './plugins/variable-value-block/node'
@@ -48,8 +64,11 @@ import UpdateBlock from './plugins/update-block'
import { textToEditorState } from './utils'
import type {
ContextBlockType,
CurrentBlockType,
ErrorMessageBlockType,
ExternalToolBlockType,
HistoryBlockType,
LastRunBlockType,
QueryBlockType,
VariableBlockType,
WorkflowVariableBlockType,
@@ -66,7 +85,7 @@ export type PromptEditorProps = {
compact?: boolean
wrapperClassName?: string
className?: string
placeholder?: string | JSX.Element
placeholder?: string | React.JSX.Element
placeholderClassName?: string
style?: React.CSSProperties
value?: string
@@ -80,6 +99,9 @@ export type PromptEditorProps = {
variableBlock?: VariableBlockType
externalToolBlock?: ExternalToolBlockType
workflowVariableBlock?: WorkflowVariableBlockType
currentBlock?: CurrentBlockType
errorMessageBlock?: ErrorMessageBlockType
lastRunBlock?: LastRunBlockType
isSupportFileVar?: boolean
}
@@ -102,6 +124,9 @@ const PromptEditor: FC<PromptEditorProps> = ({
variableBlock,
externalToolBlock,
workflowVariableBlock,
currentBlock,
errorMessageBlock,
lastRunBlock,
isSupportFileVar,
}) => {
const { eventEmitter } = useEventEmitterContextContext()
@@ -119,6 +144,9 @@ const PromptEditor: FC<PromptEditorProps> = ({
QueryBlockNode,
WorkflowVariableBlockNode,
VariableValueBlockNode,
CurrentBlockNode,
ErrorMessageBlockNode,
LastRunBlockNode, // LastRunBlockNode is used for error message block replacement
],
editorState: textToEditorState(value || ''),
onError: (error: Error) => {
@@ -178,6 +206,9 @@ const PromptEditor: FC<PromptEditorProps> = ({
variableBlock={variableBlock}
externalToolBlock={externalToolBlock}
workflowVariableBlock={workflowVariableBlock}
currentBlock={currentBlock}
errorMessageBlock={errorMessageBlock}
lastRunBlock={lastRunBlock}
isSupportFileVar={isSupportFileVar}
/>
<ComponentPickerBlock
@@ -188,6 +219,9 @@ const PromptEditor: FC<PromptEditorProps> = ({
variableBlock={variableBlock}
externalToolBlock={externalToolBlock}
workflowVariableBlock={workflowVariableBlock}
currentBlock={currentBlock}
errorMessageBlock={errorMessageBlock}
lastRunBlock={lastRunBlock}
isSupportFileVar={isSupportFileVar}
/>
{
@@ -230,6 +264,35 @@ const PromptEditor: FC<PromptEditorProps> = ({
</>
)
}
{
currentBlock?.show && (
<>
<CurrentBlock {...currentBlock} />
<CurrentBlockReplacementBlock {...currentBlock} />
</>
)
}
{
errorMessageBlock?.show && (
<>
<ErrorMessageBlock {...errorMessageBlock} />
<ErrorMessageBlockReplacementBlock {...errorMessageBlock} />
</>
)
}
{
lastRunBlock?.show && (
<>
<LastRunBlock {...lastRunBlock} />
<LastRunReplacementBlock {...lastRunBlock} />
</>
)
}
{
isSupportFileVar && (
<VariableValueBlock />
)
}
<OnChangePlugin onChange={handleEditorChange} />
<OnBlurBlock onBlur={onBlur} onFocus={onFocus} />
<UpdateBlock instanceId={instanceId} />

View File

@@ -4,8 +4,11 @@ import { $insertNodes } from 'lexical'
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext'
import type {
ContextBlockType,
CurrentBlockType,
ErrorMessageBlockType,
ExternalToolBlockType,
HistoryBlockType,
LastRunBlockType,
QueryBlockType,
VariableBlockType,
WorkflowVariableBlockType,
@@ -27,6 +30,7 @@ import { BracketsX } from '@/app/components/base/icons/src/vender/line/developme
import { UserEdit02 } from '@/app/components/base/icons/src/vender/solid/users'
import { ArrowUpRight } from '@/app/components/base/icons/src/vender/line/arrows'
import AppIcon from '@/app/components/base/app-icon'
import { VarType } from '@/app/components/workflow/types'
export const usePromptOptions = (
contextBlock?: ContextBlockType,
@@ -267,17 +271,61 @@ export const useOptions = (
variableBlock?: VariableBlockType,
externalToolBlockType?: ExternalToolBlockType,
workflowVariableBlockType?: WorkflowVariableBlockType,
currentBlockType?: CurrentBlockType,
errorMessageBlockType?: ErrorMessageBlockType,
lastRunBlockType?: LastRunBlockType,
queryString?: string,
) => {
const promptOptions = usePromptOptions(contextBlock, queryBlock, historyBlock)
const variableOptions = useVariableOptions(variableBlock, queryString)
const externalToolOptions = useExternalToolOptions(externalToolBlockType, queryString)
const workflowVariableOptions = useMemo(() => {
if (!workflowVariableBlockType?.show)
return []
return workflowVariableBlockType.variables || []
}, [workflowVariableBlockType])
const res = workflowVariableBlockType.variables || []
if(errorMessageBlockType?.show && res.findIndex(v => v.nodeId === 'error_message') === -1) {
res.unshift({
nodeId: 'error_message',
title: 'error_message',
isFlat: true,
vars: [
{
variable: 'error_message',
type: VarType.string,
},
],
})
}
if(lastRunBlockType?.show && res.findIndex(v => v.nodeId === 'last_run') === -1) {
res.unshift({
nodeId: 'last_run',
title: 'last_run',
isFlat: true,
vars: [
{
variable: 'last_run',
type: VarType.object,
},
],
})
}
if(currentBlockType?.show && res.findIndex(v => v.nodeId === 'current') === -1) {
const title = currentBlockType.generatorType === 'prompt' ? 'current_prompt' : 'current_code'
res.unshift({
nodeId: 'current',
title,
isFlat: true,
vars: [
{
variable: 'current',
type: VarType.string,
},
],
})
}
return res
}, [workflowVariableBlockType?.show, workflowVariableBlockType?.variables, errorMessageBlockType?.show, lastRunBlockType?.show, currentBlockType?.show, currentBlockType?.generatorType])
return useMemo(() => {
return {

View File

@@ -17,8 +17,11 @@ import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext
import { LexicalTypeaheadMenuPlugin } from '@lexical/react/LexicalTypeaheadMenuPlugin'
import type {
ContextBlockType,
CurrentBlockType,
ErrorMessageBlockType,
ExternalToolBlockType,
HistoryBlockType,
LastRunBlockType,
QueryBlockType,
VariableBlockType,
WorkflowVariableBlockType,
@@ -32,6 +35,10 @@ import type { PickerBlockMenuOption } from './menu'
import VarReferenceVars from '@/app/components/workflow/nodes/_base/components/variable/var-reference-vars'
import { useEventEmitterContextContext } from '@/context/event-emitter'
import { KEY_ESCAPE_COMMAND } from 'lexical'
import { INSERT_CURRENT_BLOCK_COMMAND } from '../current-block'
import { GeneratorType } from '@/app/components/app/configuration/config/automatic/types'
import { INSERT_ERROR_MESSAGE_BLOCK_COMMAND } from '../error-message-block'
import { INSERT_LAST_RUN_BLOCK_COMMAND } from '../last-run-block'
type ComponentPickerProps = {
triggerString: string
@@ -41,6 +48,9 @@ type ComponentPickerProps = {
variableBlock?: VariableBlockType
externalToolBlock?: ExternalToolBlockType
workflowVariableBlock?: WorkflowVariableBlockType
currentBlock?: CurrentBlockType
errorMessageBlock?: ErrorMessageBlockType
lastRunBlock?: LastRunBlockType
isSupportFileVar?: boolean
}
const ComponentPicker = ({
@@ -51,6 +61,9 @@ const ComponentPicker = ({
variableBlock,
externalToolBlock,
workflowVariableBlock,
currentBlock,
errorMessageBlock,
lastRunBlock,
isSupportFileVar,
}: ComponentPickerProps) => {
const { eventEmitter } = useEventEmitterContextContext()
@@ -87,6 +100,9 @@ const ComponentPicker = ({
variableBlock,
externalToolBlock,
workflowVariableBlock,
currentBlock,
errorMessageBlock,
lastRunBlock,
)
const onSelectOption = useCallback(
@@ -112,12 +128,23 @@ const ComponentPicker = ({
if (needRemove)
needRemove.remove()
})
if (variables[1] === 'sys.query' || variables[1] === 'sys.files')
const isFlat = variables.length === 1
if(isFlat) {
const varName = variables[0]
if(varName === 'current')
editor.dispatchCommand(INSERT_CURRENT_BLOCK_COMMAND, currentBlock?.generatorType)
else if (varName === 'error_message')
editor.dispatchCommand(INSERT_ERROR_MESSAGE_BLOCK_COMMAND, null)
else if (varName === 'last_run')
editor.dispatchCommand(INSERT_LAST_RUN_BLOCK_COMMAND, null)
}
else if (variables[1] === 'sys.query' || variables[1] === 'sys.files') {
editor.dispatchCommand(INSERT_WORKFLOW_VARIABLE_BLOCK_COMMAND, [variables[1]])
else
}
else {
editor.dispatchCommand(INSERT_WORKFLOW_VARIABLE_BLOCK_COMMAND, variables)
}, [editor, checkForTriggerMatch, triggerString])
}
}, [editor, currentBlock?.generatorType, checkForTriggerMatch, triggerString])
const handleClose = useCallback(() => {
const escapeEvent = new KeyboardEvent('keydown', { key: 'Escape' })
@@ -166,6 +193,7 @@ const ComponentPicker = ({
onClose={handleClose}
onBlur={handleClose}
autoFocus={false}
isInCodeGeneratorInstructionEditor={currentBlock?.generatorType === GeneratorType.code}
/>
</div>
)
@@ -206,7 +234,7 @@ const ComponentPicker = ({
}
</>
)
}, [allFlattenOptions.length, workflowVariableBlock?.show, refs, isPositioned, floatingStyles, queryString, workflowVariableOptions, handleSelectWorkflowVariable, handleClose, isSupportFileVar])
}, [allFlattenOptions.length, workflowVariableBlock?.show, floatingStyles, isPositioned, refs, workflowVariableOptions, isSupportFileVar, handleClose, currentBlock?.generatorType, handleSelectWorkflowVariable, queryString])
return (
<LexicalTypeaheadMenuPlugin

View File

@@ -0,0 +1,44 @@
import { type FC, useEffect } from 'react'
import { GeneratorType } from '@/app/components/app/configuration/config/automatic/types'
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext'
import { useSelectOrDelete } from '../../hooks'
import { CurrentBlockNode, DELETE_CURRENT_BLOCK_COMMAND } from '.'
import cn from '@/utils/classnames'
import { CodeAssistant, MagicEdit } from '../../../icons/src/vender/line/general'
type CurrentBlockComponentProps = {
nodeKey: string
generatorType: GeneratorType
}
const CurrentBlockComponent: FC<CurrentBlockComponentProps> = ({
nodeKey,
generatorType,
}) => {
const [editor] = useLexicalComposerContext()
const [ref, isSelected] = useSelectOrDelete(nodeKey, DELETE_CURRENT_BLOCK_COMMAND)
const Icon = generatorType === GeneratorType.prompt ? MagicEdit : CodeAssistant
useEffect(() => {
if (!editor.hasNodes([CurrentBlockNode]))
throw new Error('WorkflowVariableBlockPlugin: WorkflowVariableBlock not registered on editor')
}, [editor])
return (
<div
className={cn(
'group/wrap relative mx-0.5 flex h-[18px] select-none items-center rounded-[5px] border pl-0.5 pr-[3px] text-util-colors-violet-violet-600 hover:border-state-accent-solid hover:bg-state-accent-hover',
isSelected ? ' border-state-accent-solid bg-state-accent-hover' : ' border-components-panel-border-subtle bg-components-badge-white-to-dark',
)}
onClick={(e) => {
e.stopPropagation()
}}
ref={ref}
>
<Icon className='mr-0.5 h-[14px] w-[14px]' />
<div className='text-xs font-medium'>{generatorType === GeneratorType.prompt ? 'current_prompt' : 'current_code'}</div>
</div>
)
}
export default CurrentBlockComponent

View File

@@ -0,0 +1,62 @@
import {
memo,
useCallback,
useEffect,
} from 'react'
import { $applyNodeReplacement } from 'lexical'
import { mergeRegister } from '@lexical/utils'
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext'
import { decoratorTransform } from '../../utils'
import { CURRENT_PLACEHOLDER_TEXT } from '../../constants'
import type { CurrentBlockType } from '../../types'
import {
$createCurrentBlockNode,
CurrentBlockNode,
} from './node'
import { CustomTextNode } from '../custom-text/node'
const REGEX = new RegExp(CURRENT_PLACEHOLDER_TEXT)
const CurrentBlockReplacementBlock = ({
generatorType,
onInsert,
}: CurrentBlockType) => {
const [editor] = useLexicalComposerContext()
useEffect(() => {
if (!editor.hasNodes([CurrentBlockNode]))
throw new Error('CurrentBlockNodePlugin: CurrentBlockNode not registered on editor')
}, [editor])
const createCurrentBlockNode = useCallback((): CurrentBlockNode => {
if (onInsert)
onInsert()
return $applyNodeReplacement($createCurrentBlockNode(generatorType))
}, [onInsert, generatorType])
const getMatch = useCallback((text: string) => {
const matchArr = REGEX.exec(text)
if (matchArr === null)
return null
const startOffset = matchArr.index
const endOffset = startOffset + CURRENT_PLACEHOLDER_TEXT.length
return {
end: endOffset,
start: startOffset,
}
}, [])
useEffect(() => {
REGEX.lastIndex = 0
return mergeRegister(
editor.registerNodeTransform(CustomTextNode, textNode => decoratorTransform(textNode, getMatch, createCurrentBlockNode)),
)
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [])
return null
}
export default memo(CurrentBlockReplacementBlock)

View File

@@ -0,0 +1,66 @@
import {
memo,
useEffect,
} from 'react'
import {
$insertNodes,
COMMAND_PRIORITY_EDITOR,
createCommand,
} from 'lexical'
import { mergeRegister } from '@lexical/utils'
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext'
import type { CurrentBlockType } from '../../types'
import {
$createCurrentBlockNode,
CurrentBlockNode,
} from './node'
export const INSERT_CURRENT_BLOCK_COMMAND = createCommand('INSERT_CURRENT_BLOCK_COMMAND')
export const DELETE_CURRENT_BLOCK_COMMAND = createCommand('DELETE_CURRENT_BLOCK_COMMAND')
const CurrentBlock = memo(({
generatorType,
onInsert,
onDelete,
}: CurrentBlockType) => {
const [editor] = useLexicalComposerContext()
useEffect(() => {
if (!editor.hasNodes([CurrentBlockNode]))
throw new Error('CURRENTBlockPlugin: CURRENTBlock not registered on editor')
return mergeRegister(
editor.registerCommand(
INSERT_CURRENT_BLOCK_COMMAND,
() => {
const currentBlockNode = $createCurrentBlockNode(generatorType)
$insertNodes([currentBlockNode])
if (onInsert)
onInsert()
return true
},
COMMAND_PRIORITY_EDITOR,
),
editor.registerCommand(
DELETE_CURRENT_BLOCK_COMMAND,
() => {
if (onDelete)
onDelete()
return true
},
COMMAND_PRIORITY_EDITOR,
),
)
}, [editor, generatorType, onDelete, onInsert])
return null
})
CurrentBlock.displayName = 'CurrentBlock'
export { CurrentBlock }
export { CurrentBlockNode } from './node'
export { default as CurrentBlockReplacementBlock } from './current-block-replacement-block'

View File

@@ -0,0 +1,78 @@
import type { LexicalNode, NodeKey, SerializedLexicalNode } from 'lexical'
import { DecoratorNode } from 'lexical'
import CurrentBlockComponent from './component'
import type { GeneratorType } from '@/app/components/app/configuration/config/automatic/types'
export type SerializedNode = SerializedLexicalNode & { generatorType: GeneratorType; }
export class CurrentBlockNode extends DecoratorNode<React.JSX.Element> {
__generatorType: GeneratorType
static getType(): string {
return 'current-block'
}
static clone(node: CurrentBlockNode): CurrentBlockNode {
return new CurrentBlockNode(node.__generatorType, node.getKey())
}
isInline(): boolean {
return true
}
constructor(generatorType: GeneratorType, key?: NodeKey) {
super(key)
this.__generatorType = generatorType
}
createDOM(): HTMLElement {
const div = document.createElement('div')
div.classList.add('inline-flex', 'items-center', 'align-middle')
return div
}
updateDOM(): false {
return false
}
decorate(): React.JSX.Element {
return (
<CurrentBlockComponent
nodeKey={this.getKey()}
generatorType={this.getGeneratorType()}
/>
)
}
getGeneratorType(): GeneratorType {
const self = this.getLatest()
return self.__generatorType
}
static importJSON(serializedNode: SerializedNode): CurrentBlockNode {
const node = $createCurrentBlockNode(serializedNode.generatorType)
return node
}
exportJSON(): SerializedNode {
return {
type: 'current-block',
version: 1,
generatorType: this.getGeneratorType(),
}
}
getTextContent(): string {
return '{{#current#}}'
}
}
export function $createCurrentBlockNode(type: GeneratorType): CurrentBlockNode {
return new CurrentBlockNode(type)
}
export function $isCurrentBlockNode(
node: CurrentBlockNode | LexicalNode | null | undefined,
): boolean {
return node instanceof CurrentBlockNode
}

View File

@@ -0,0 +1,40 @@
import { type FC, useEffect } from 'react'
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext'
import { useSelectOrDelete } from '../../hooks'
import { DELETE_ERROR_MESSAGE_COMMAND, ErrorMessageBlockNode } from '.'
import cn from '@/utils/classnames'
import { Variable02 } from '../../../icons/src/vender/solid/development'
type Props = {
nodeKey: string
}
const ErrorMessageBlockComponent: FC<Props> = ({
nodeKey,
}) => {
const [editor] = useLexicalComposerContext()
const [ref, isSelected] = useSelectOrDelete(nodeKey, DELETE_ERROR_MESSAGE_COMMAND)
useEffect(() => {
if (!editor.hasNodes([ErrorMessageBlockNode]))
throw new Error('WorkflowVariableBlockPlugin: WorkflowVariableBlock not registered on editor')
}, [editor])
return (
<div
className={cn(
'group/wrap relative mx-0.5 flex h-[18px] select-none items-center rounded-[5px] border pl-0.5 pr-[3px] text-util-colors-orange-dark-orange-dark-600 hover:border-state-accent-solid hover:bg-state-accent-hover',
isSelected ? ' border-state-accent-solid bg-state-accent-hover' : ' border-components-panel-border-subtle bg-components-badge-white-to-dark',
)}
onClick={(e) => {
e.stopPropagation()
}}
ref={ref}
>
<Variable02 className='mr-0.5 h-[14px] w-[14px]' />
<div className='text-xs font-medium'>error_message</div>
</div>
)
}
export default ErrorMessageBlockComponent

View File

@@ -0,0 +1,61 @@
import {
memo,
useCallback,
useEffect,
} from 'react'
import { $applyNodeReplacement } from 'lexical'
import { mergeRegister } from '@lexical/utils'
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext'
import { decoratorTransform } from '../../utils'
import { ERROR_MESSAGE_PLACEHOLDER_TEXT } from '../../constants'
import type { ErrorMessageBlockType } from '../../types'
import {
$createErrorMessageBlockNode,
ErrorMessageBlockNode,
} from './node'
import { CustomTextNode } from '../custom-text/node'
const REGEX = new RegExp(ERROR_MESSAGE_PLACEHOLDER_TEXT)
const ErrorMessageBlockReplacementBlock = ({
onInsert,
}: ErrorMessageBlockType) => {
const [editor] = useLexicalComposerContext()
useEffect(() => {
if (!editor.hasNodes([ErrorMessageBlockNode]))
throw new Error('ErrorMessageBlockNodePlugin: ErrorMessageBlockNode not registered on editor')
}, [editor])
const createErrorMessageBlockNode = useCallback((): ErrorMessageBlockNode => {
if (onInsert)
onInsert()
return $applyNodeReplacement($createErrorMessageBlockNode())
}, [onInsert])
const getMatch = useCallback((text: string) => {
const matchArr = REGEX.exec(text)
if (matchArr === null)
return null
const startOffset = matchArr.index
const endOffset = startOffset + ERROR_MESSAGE_PLACEHOLDER_TEXT.length
return {
end: endOffset,
start: startOffset,
}
}, [])
useEffect(() => {
REGEX.lastIndex = 0
return mergeRegister(
editor.registerNodeTransform(CustomTextNode, textNode => decoratorTransform(textNode, getMatch, createErrorMessageBlockNode)),
)
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [])
return null
}
export default memo(ErrorMessageBlockReplacementBlock)

View File

@@ -0,0 +1,65 @@
import {
memo,
useEffect,
} from 'react'
import {
$insertNodes,
COMMAND_PRIORITY_EDITOR,
createCommand,
} from 'lexical'
import { mergeRegister } from '@lexical/utils'
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext'
import type { ErrorMessageBlockType } from '../../types'
import {
$createErrorMessageBlockNode,
ErrorMessageBlockNode,
} from './node'
export const INSERT_ERROR_MESSAGE_BLOCK_COMMAND = createCommand('INSERT_ERROR_MESSAGE_BLOCK_COMMAND')
export const DELETE_ERROR_MESSAGE_COMMAND = createCommand('DELETE_ERROR_MESSAGE_COMMAND')
const ErrorMessageBlock = memo(({
onInsert,
onDelete,
}: ErrorMessageBlockType) => {
const [editor] = useLexicalComposerContext()
useEffect(() => {
if (!editor.hasNodes([ErrorMessageBlockNode]))
throw new Error('ERROR_MESSAGEBlockPlugin: ERROR_MESSAGEBlock not registered on editor')
return mergeRegister(
editor.registerCommand(
INSERT_ERROR_MESSAGE_BLOCK_COMMAND,
() => {
const Node = $createErrorMessageBlockNode()
$insertNodes([Node])
if (onInsert)
onInsert()
return true
},
COMMAND_PRIORITY_EDITOR,
),
editor.registerCommand(
DELETE_ERROR_MESSAGE_COMMAND,
() => {
if (onDelete)
onDelete()
return true
},
COMMAND_PRIORITY_EDITOR,
),
)
}, [editor, onDelete, onInsert])
return null
})
ErrorMessageBlock.displayName = 'ErrorMessageBlock'
export { ErrorMessageBlock }
export { ErrorMessageBlockNode } from './node'
export { default as ErrorMessageBlockReplacementBlock } from './error-message-block-replacement-block'

View File

@@ -0,0 +1,67 @@
import type { LexicalNode, NodeKey, SerializedLexicalNode } from 'lexical'
import { DecoratorNode } from 'lexical'
import ErrorMessageBlockComponent from './component'
export type SerializedNode = SerializedLexicalNode
export class ErrorMessageBlockNode extends DecoratorNode<React.JSX.Element> {
static getType(): string {
return 'error-message-block'
}
static clone(node: ErrorMessageBlockNode): ErrorMessageBlockNode {
return new ErrorMessageBlockNode(node.getKey())
}
isInline(): boolean {
return true
}
constructor(key?: NodeKey) {
super(key)
}
createDOM(): HTMLElement {
const div = document.createElement('div')
div.classList.add('inline-flex', 'items-center', 'align-middle')
return div
}
updateDOM(): false {
return false
}
decorate(): React.JSX.Element {
return (
<ErrorMessageBlockComponent
nodeKey={this.getKey()}
/>
)
}
static importJSON(): ErrorMessageBlockNode {
const node = $createErrorMessageBlockNode()
return node
}
exportJSON(): SerializedNode {
return {
type: 'error-message-block',
version: 1,
}
}
getTextContent(): string {
return '{{#error_message#}}'
}
}
export function $createErrorMessageBlockNode(): ErrorMessageBlockNode {
return new ErrorMessageBlockNode()
}
export function $isErrorMessageBlockNode(
node: ErrorMessageBlockNode | LexicalNode | null | undefined,
): boolean {
return node instanceof ErrorMessageBlockNode
}

View File

@@ -0,0 +1,40 @@
import { type FC, useEffect } from 'react'
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext'
import { useSelectOrDelete } from '../../hooks'
import { DELETE_LAST_RUN_COMMAND, LastRunBlockNode } from '.'
import cn from '@/utils/classnames'
import { Variable02 } from '../../../icons/src/vender/solid/development'
type Props = {
nodeKey: string
}
const LastRunBlockComponent: FC<Props> = ({
nodeKey,
}) => {
const [editor] = useLexicalComposerContext()
const [ref, isSelected] = useSelectOrDelete(nodeKey, DELETE_LAST_RUN_COMMAND)
useEffect(() => {
if (!editor.hasNodes([LastRunBlockNode]))
throw new Error('WorkflowVariableBlockPlugin: WorkflowVariableBlock not registered on editor')
}, [editor])
return (
<div
className={cn(
'group/wrap relative mx-0.5 flex h-[18px] select-none items-center rounded-[5px] border pl-0.5 pr-[3px] text-text-accent hover:border-state-accent-solid hover:bg-state-accent-hover',
isSelected ? ' border-state-accent-solid bg-state-accent-hover' : ' border-components-panel-border-subtle bg-components-badge-white-to-dark',
)}
onClick={(e) => {
e.stopPropagation()
}}
ref={ref}
>
<Variable02 className='mr-0.5 h-[14px] w-[14px]' />
<div className='text-xs font-medium'>last_run</div>
</div>
)
}
export default LastRunBlockComponent

View File

@@ -0,0 +1,65 @@
import {
memo,
useEffect,
} from 'react'
import {
$insertNodes,
COMMAND_PRIORITY_EDITOR,
createCommand,
} from 'lexical'
import { mergeRegister } from '@lexical/utils'
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext'
import type { LastRunBlockType } from '../../types'
import {
$createLastRunBlockNode,
LastRunBlockNode,
} from './node'
export const INSERT_LAST_RUN_BLOCK_COMMAND = createCommand('INSERT_LAST_RUN_BLOCK_COMMAND')
export const DELETE_LAST_RUN_COMMAND = createCommand('DELETE_LAST_RUN_COMMAND')
const LastRunBlock = memo(({
onInsert,
onDelete,
}: LastRunBlockType) => {
const [editor] = useLexicalComposerContext()
useEffect(() => {
if (!editor.hasNodes([LastRunBlockNode]))
throw new Error('Last_RunBlockPlugin: Last_RunBlock not registered on editor')
return mergeRegister(
editor.registerCommand(
INSERT_LAST_RUN_BLOCK_COMMAND,
() => {
const Node = $createLastRunBlockNode()
$insertNodes([Node])
if (onInsert)
onInsert()
return true
},
COMMAND_PRIORITY_EDITOR,
),
editor.registerCommand(
DELETE_LAST_RUN_COMMAND,
() => {
if (onDelete)
onDelete()
return true
},
COMMAND_PRIORITY_EDITOR,
),
)
}, [editor, onDelete, onInsert])
return null
})
LastRunBlock.displayName = 'LastRunBlock'
export { LastRunBlock }
export { LastRunBlockNode } from './node'
export { default as LastRunReplacementBlock } from './last-run-block-replacement-block'

View File

@@ -0,0 +1,61 @@
import {
memo,
useCallback,
useEffect,
} from 'react'
import { $applyNodeReplacement } from 'lexical'
import { mergeRegister } from '@lexical/utils'
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext'
import { decoratorTransform } from '../../utils'
import { LAST_RUN_PLACEHOLDER_TEXT } from '../../constants'
import type { LastRunBlockType } from '../../types'
import {
$createLastRunBlockNode,
LastRunBlockNode,
} from './node'
import { CustomTextNode } from '../custom-text/node'
const REGEX = new RegExp(LAST_RUN_PLACEHOLDER_TEXT)
const LastRunReplacementBlock = ({
onInsert,
}: LastRunBlockType) => {
const [editor] = useLexicalComposerContext()
useEffect(() => {
if (!editor.hasNodes([LastRunBlockNode]))
throw new Error('LastRunMessageBlockNodePlugin: LastRunMessageBlockNode not registered on editor')
}, [editor])
const createLastRunBlockNode = useCallback((): LastRunBlockNode => {
if (onInsert)
onInsert()
return $applyNodeReplacement($createLastRunBlockNode())
}, [onInsert])
const getMatch = useCallback((text: string) => {
const matchArr = REGEX.exec(text)
if (matchArr === null)
return null
const startOffset = matchArr.index
const endOffset = startOffset + LAST_RUN_PLACEHOLDER_TEXT.length
return {
end: endOffset,
start: startOffset,
}
}, [])
useEffect(() => {
REGEX.lastIndex = 0
return mergeRegister(
editor.registerNodeTransform(CustomTextNode, textNode => decoratorTransform(textNode, getMatch, createLastRunBlockNode)),
)
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [])
return null
}
export default memo(LastRunReplacementBlock)

View File

@@ -0,0 +1,67 @@
import type { LexicalNode, NodeKey, SerializedLexicalNode } from 'lexical'
import { DecoratorNode } from 'lexical'
import LastRunBlockComponent from './component'
export type SerializedNode = SerializedLexicalNode
export class LastRunBlockNode extends DecoratorNode<React.JSX.Element> {
static getType(): string {
return 'last-run-block'
}
static clone(node: LastRunBlockNode): LastRunBlockNode {
return new LastRunBlockNode(node.getKey())
}
isInline(): boolean {
return true
}
constructor(key?: NodeKey) {
super(key)
}
createDOM(): HTMLElement {
const div = document.createElement('div')
div.classList.add('inline-flex', 'items-center', 'align-middle')
return div
}
updateDOM(): false {
return false
}
decorate(): React.JSX.Element {
return (
<LastRunBlockComponent
nodeKey={this.getKey()}
/>
)
}
static importJSON(): LastRunBlockNode {
const node = $createLastRunBlockNode()
return node
}
exportJSON(): SerializedNode {
return {
type: 'last-run-block',
version: 1,
}
}
getTextContent(): string {
return '{{#last_run#}}'
}
}
export function $createLastRunBlockNode(): LastRunBlockNode {
return new LastRunBlockNode()
}
export function $isLastRunBlockNode(
node: LastRunBlockNode | LexicalNode | null | undefined,
): boolean {
return node instanceof LastRunBlockNode
}

View File

@@ -1,3 +1,4 @@
import type { GeneratorType } from '../../app/configuration/config/automatic/types'
import type { Type } from '../../workflow/nodes/llm/types'
import type { Dataset } from './plugins/context-block'
import type { RoleName } from './plugins/history-block'
@@ -75,3 +76,22 @@ export type MenuTextMatch = {
matchingString: string
replaceableString: string
}
export type CurrentBlockType = {
show?: boolean
generatorType: GeneratorType
onInsert?: () => void
onDelete?: () => void
}
export type ErrorMessageBlockType = {
show?: boolean
onInsert?: () => void
onDelete?: () => void
}
export type LastRunBlockType = {
show?: boolean
onInsert?: () => void
onDelete?: () => void
}

View File

@@ -5,6 +5,7 @@ export const useConfigsMap = () => {
const appId = useStore(s => s.appId)
return useMemo(() => {
return {
flowId: appId,
conversationVarsUrl: `apps/${appId}/workflows/draft/conversation-variables`,
systemVarsUrl: `apps/${appId}/workflows/draft/system-variables`,
}

View File

@@ -35,7 +35,6 @@ export const useWorkflowRun = () => {
const invalidAllLastRun = useInvalidAllLastRun(appId as string)
const configsMap = useConfigsMap()
const { fetchInspectVars } = useSetWorkflowVarsWithValue({
flowId: appId as string,
...configsMap,
})

View File

@@ -49,6 +49,7 @@ type CommonHooksFnMap = {
resetConversationVar: (varId: string) => Promise<void>
invalidateConversationVarValues: () => void
configsMap?: {
flowId: string
conversationVarsUrl: string
systemVarsUrl: string
}

View File

@@ -10,6 +10,7 @@ import type {
} from '@/app/components/workflow/types'
import { useIsChatMode } from './use-workflow'
import { useStoreApi } from 'reactflow'
import type { Type } from '../nodes/llm/types'
export const useWorkflowVariables = () => {
const { t } = useTranslation()
@@ -106,7 +107,7 @@ export const useWorkflowVariableType = () => {
isChatMode,
isConstant: false,
})
return type
return type as unknown as Type
}
return getVarType

View File

@@ -87,6 +87,7 @@ export const AgentStrategy = memo((props: AgentStrategyProps) => {
headerClassName='bg-transparent px-0 text-text-secondary system-sm-semibold-uppercase'
containerBackgroundClassName='bg-transparent'
gradientBorder={false}
nodeId={nodeId}
isSupportPromptGenerator={!!def.auto_generate?.type}
titleTooltip={schema.tooltip && renderI18nObject(schema.tooltip)}
editorContainerClassName='px-0'

View File

@@ -7,25 +7,32 @@ import type { CodeLanguage } from '../../code/types'
import { Generator } from '@/app/components/base/icons/src/vender/other'
import { ActionButton } from '@/app/components/base/action-button'
import { AppType } from '@/types/app'
import type { CodeGenRes } from '@/service/debug'
import type { GenRes } from '@/service/debug'
import { GetCodeGeneratorResModal } from '@/app/components/app/configuration/config/code-generator/get-code-generator-res'
import { useHooksStore } from '../../../hooks-store'
type Props = {
nodeId: string
currentCode?: string
className?: string
onGenerated?: (prompt: string) => void
codeLanguages: CodeLanguage
}
const CodeGenerateBtn: FC<Props> = ({
nodeId,
currentCode,
className,
codeLanguages,
onGenerated,
}) => {
const [showAutomatic, { setTrue: showAutomaticTrue, setFalse: showAutomaticFalse }] = useBoolean(false)
const handleAutomaticRes = useCallback((res: CodeGenRes) => {
onGenerated?.(res.code)
const handleAutomaticRes = useCallback((res: GenRes) => {
onGenerated?.(res.modified)
showAutomaticFalse()
}, [onGenerated, showAutomaticFalse])
const configsMap = useHooksStore(s => s.configsMap)
return (
<div className={cn(className)}>
<ActionButton
@@ -40,6 +47,9 @@ const CodeGenerateBtn: FC<Props> = ({
codeLanguages={codeLanguages}
onClose={showAutomaticFalse}
onFinished={handleAutomaticRes}
flowId={configsMap?.flowId || ''}
nodeId={nodeId}
currentCode={currentCode}
/>
)}
</div>

View File

@@ -16,8 +16,10 @@ import useToggleExpend from '@/app/components/workflow/nodes/_base/hooks/use-tog
import type { FileEntity } from '@/app/components/base/file-uploader/types'
import FileListInLog from '@/app/components/base/file-uploader/file-list-in-log'
import ActionButton from '@/app/components/base/action-button'
import type { Node, NodeOutPutVar } from '@/app/components/workflow/types'
type Props = {
nodeId?: string
className?: string
title: React.JSX.Element | string
headerRight?: React.JSX.Element
@@ -35,9 +37,12 @@ type Props = {
showFileList?: boolean
showCodeGenerator?: boolean
tip?: React.JSX.Element
nodesOutputVars?: NodeOutPutVar[]
availableNodes?: Node[]
}
const Base: FC<Props> = ({
nodeId,
className,
title,
headerRight,
@@ -86,7 +91,12 @@ const Base: FC<Props> = ({
{headerRight}
{showCodeGenerator && codeLanguages && (
<div className='ml-1'>
<CodeGeneratorButton onGenerated={onGenerated} codeLanguages={codeLanguages} />
<CodeGeneratorButton
onGenerated={onGenerated}
codeLanguages={codeLanguages}
currentCode={value}
nodeId={nodeId!}
/>
</div>
)}
<ActionButton className='ml-1' onClick={handleCopy}>

View File

@@ -20,6 +20,7 @@ loader.config({ paths: { vs: `${basePath}/vs` } })
const CODE_EDITOR_LINE_HEIGHT = 18
export type Props = {
nodeId?: string
value?: string | object
placeholder?: React.JSX.Element | string
onChange?: (value: string) => void
@@ -47,6 +48,7 @@ export const languageMap = {
}
const CodeEditor: FC<Props> = ({
nodeId,
value = '',
placeholder = '',
onChange = noop,
@@ -175,6 +177,7 @@ const CodeEditor: FC<Props> = ({
</div>
: (
<Base
nodeId={nodeId}
className='relative'
title={title}
value={outPutValue}

View File

@@ -41,6 +41,7 @@ type Props = {
className?: string
headerClassName?: string
instanceId?: string
nodeId?: string
title: string | React.JSX.Element
value: string
onChange: (value: string) => void
@@ -83,6 +84,7 @@ const Editor: FC<Props> = ({
className,
headerClassName,
instanceId,
nodeId,
title,
value,
onChange,
@@ -159,7 +161,13 @@ const Editor: FC<Props> = ({
<div className='flex items-center'>
<div className='text-xs font-medium leading-[18px] text-text-tertiary'>{value?.length || 0}</div>
{isSupportPromptGenerator && (
<PromptGeneratorBtn className='ml-[5px]' onGenerated={onGenerated} modelConfig={modelConfig} />
<PromptGeneratorBtn
nodeId={nodeId!}
className='ml-[5px]'
onGenerated={onGenerated}
modelConfig={modelConfig}
currentPrompt={value}
/>
)}
<div className='ml-2 mr-2 h-3 w-px bg-divider-regular'></div>

View File

@@ -20,7 +20,9 @@ import { varTypeToStructType } from './utils'
import type { Field } from '@/app/components/workflow/nodes/llm/types'
import { FILE_STRUCT } from '@/app/components/workflow/constants'
import { noop } from 'lodash-es'
import { CodeAssistant, MagicEdit } from '@/app/components/base/icons/src/vender/line/general'
import { VariableIconWithColor } from '@/app/components/workflow/nodes/_base/components/variable/variable-label'
import { Variable02 } from '@/app/components/base/icons/src/vender/solid/development'
type ObjectChildrenProps = {
nodeId: string
@@ -44,7 +46,10 @@ type ItemProps = {
isSupportFileVar?: boolean
isException?: boolean
isLoopVar?: boolean
isFlat?: boolean
isInCodeGeneratorInstructionEditor?: boolean
zIndex?: number
className?: string
}
const objVarTypes = [VarType.object, VarType.file]
@@ -59,7 +64,10 @@ const Item: FC<ItemProps> = ({
isSupportFileVar,
isException,
isLoopVar,
isFlat,
isInCodeGeneratorInstructionEditor,
zIndex,
className,
}) => {
const isStructureOutput = itemData.type === VarType.object && (itemData.children as StructuredOutput)?.schema?.properties
const isFile = itemData.type === VarType.file && !isStructureOutput
@@ -67,6 +75,29 @@ const Item: FC<ItemProps> = ({
const isSys = itemData.variable.startsWith('sys.')
const isEnv = itemData.variable.startsWith('env.')
const isChatVar = itemData.variable.startsWith('conversation.')
const flatVarIcon = useMemo(() => {
if (!isFlat)
return null
const variable = itemData.variable
let Icon
switch (variable) {
case 'current':
Icon = isInCodeGeneratorInstructionEditor ? CodeAssistant : MagicEdit
return <Icon className='h-3.5 w-3.5 shrink-0 text-util-colors-violet-violet-600' />
case 'error_message':
return <Variable02 className='h-3.5 w-3.5 shrink-0 text-util-colors-orange-dark-orange-dark-600' />
default:
return <Variable02 className='h-3.5 w-3.5 shrink-0 text-text-accent' />
}
}, [isFlat, isInCodeGeneratorInstructionEditor, itemData.variable])
const varName = useMemo(() => {
if (!isFlat)
return itemData.variable
if (itemData.variable === 'current')
return isInCodeGeneratorInstructionEditor ? 'current_code' : 'current_prompt'
return itemData.variable
}, [isFlat, isInCodeGeneratorInstructionEditor, itemData.variable])
const objStructuredOutput: StructuredOutput | null = useMemo(() => {
if (!isObj) return null
@@ -122,7 +153,10 @@ const Item: FC<ItemProps> = ({
if (!isSupportFileVar && isFile)
return
if (isSys || isEnv || isChatVar) { // system variable | environment variable | conversation variable
if (isFlat) {
onChange([itemData.variable], itemData)
}
else if (isSys || isEnv || isChatVar) { // system variable | environment variable | conversation variable
onChange([...objPath, ...itemData.variable.split('.')], itemData)
}
else {
@@ -147,18 +181,22 @@ const Item: FC<ItemProps> = ({
className={cn(
(isObj || isStructureOutput) ? ' pr-1' : 'pr-[18px]',
isHovering && ((isObj || isStructureOutput) ? 'bg-components-panel-on-panel-item-bg-hover' : 'bg-state-base-hover'),
'relative flex h-6 w-full cursor-pointer items-center rounded-md pl-3')
'relative flex h-6 w-full cursor-pointer items-center rounded-md pl-3',
className,
)
}
onClick={handleChosen}
onMouseDown={e => e.preventDefault()}
>
<div className='flex w-0 grow items-center'>
<VariableIconWithColor
{!isFlat && <VariableIconWithColor
variableCategory={variableCategory}
isExceptionVariable={isException}
/>
/>}
{isFlat && flatVarIcon}
{!isEnv && !isChatVar && (
<div title={itemData.variable} className='system-sm-medium ml-1 w-0 grow truncate text-text-secondary'>{itemData.variable}</div>
<div title={itemData.variable} className='system-sm-medium ml-1 w-0 grow truncate text-text-secondary'>{varName}</div>
)}
{isEnv && (
<div title={itemData.variable} className='system-sm-medium ml-1 w-0 grow truncate text-text-secondary'>{itemData.variable.replace('env.', '')}</div>
@@ -264,6 +302,7 @@ type Props = {
onClose?: () => void
onBlur?: () => void
zIndex?: number
isInCodeGeneratorInstructionEditor?: boolean
autoFocus?: boolean
}
const VarReferenceVars: FC<Props> = ({
@@ -277,6 +316,7 @@ const VarReferenceVars: FC<Props> = ({
onClose,
onBlur,
zIndex,
isInCodeGeneratorInstructionEditor,
autoFocus = true,
}) => {
const { t } = useTranslation()
@@ -319,7 +359,7 @@ const VarReferenceVars: FC<Props> = ({
{
!hideSearch && (
<>
<div className={cn('var-search-input-wrapper mx-2 mb-1 mt-2', searchBoxClassName)} onClick={e => e.stopPropagation()}>
<div className={cn('var-search-input-wrapper mx-2 mb-2 mt-2', searchBoxClassName)} onClick={e => e.stopPropagation()}>
<Input
className='var-search-input'
showLeftIcon
@@ -345,11 +385,13 @@ const VarReferenceVars: FC<Props> = ({
{
filteredVars.map((item, i) => (
<div key={i}>
<div
className='system-xs-medium-uppercase truncate px-3 leading-[22px] text-text-tertiary'
title={item.title}
>{item.title}</div>
<div key={i} className={cn(!item.isFlat && 'mt-3', i === 0 && item.isFlat && 'mt-2')}>
{!item.isFlat && (
<div
className='system-xs-medium-uppercase truncate px-3 leading-[22px] text-text-tertiary'
title={item.title}
>{item.title}</div>
)}
{item.vars.map((v, j) => (
<Item
key={j}
@@ -362,13 +404,22 @@ const VarReferenceVars: FC<Props> = ({
isSupportFileVar={isSupportFileVar}
isException={v.isException}
isLoopVar={item.isLoop}
isFlat={item.isFlat}
isInCodeGeneratorInstructionEditor={isInCodeGeneratorInstructionEditor}
zIndex={zIndex}
/>
))}
{item.isFlat && !filteredVars[i + 1]?.isFlat && !!filteredVars.find(item => !item.isFlat) && (
<div className='relative mt-[14px] flex items-center space-x-1'>
<div className='h-0 w-3 shrink-0 border border-divider-subtle'></div>
<div className='system-2xs-semibold-uppercase text-text-tertiary'>{t('workflow.debug.lastOutput')}</div>
<div className='h-0 shrink-0 grow border border-divider-subtle'></div>
</div>
)}
</div>))
}
</div>
: <div className='pl-3 text-xs font-medium uppercase leading-[18px] text-gray-500'>{t('workflow.common.noVar')}</div>}
: <div className='mt-2 pl-3 text-xs font-medium uppercase leading-[18px] text-gray-500'>{t('workflow.common.noVar')}</div>}
</>
)
}

View File

@@ -1,18 +1,19 @@
import { useEffect, useState } from 'react'
type Params = {
ref: React.RefObject<HTMLDivElement>
ref?: React.RefObject<HTMLDivElement | null>
hasFooter?: boolean
isInNode?: boolean
}
const useToggleExpend = ({ ref, hasFooter = true, isInNode }: Params) => {
const [isExpand, setIsExpand] = useState(false)
const [wrapHeight, setWrapHeight] = useState(ref.current?.clientHeight)
const [wrapHeight, setWrapHeight] = useState(ref?.current?.clientHeight)
const editorExpandHeight = isExpand ? wrapHeight! - (hasFooter ? 56 : 29) : 0
useEffect(() => {
if (!ref?.current) return
setWrapHeight(ref.current?.clientHeight)
// eslint-disable-next-line react-hooks/exhaustive-deps
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [isExpand])
const wrapClassName = (() => {

View File

@@ -89,6 +89,7 @@ const Panel: FC<NodePanelProps<CodeNodeType>> = ({
</Field>
<Split />
<CodeEditor
nodeId={id}
isInNode
readOnly={readOnly}
title={

View File

@@ -1,7 +1,6 @@
'use client'
import type { FC } from 'react'
import React, { useCallback, useEffect, useState } from 'react'
import { uniqueId } from 'lodash-es'
import React, { useCallback } from 'react'
import { useTranslation } from 'react-i18next'
import type { ModelConfig, PromptItem, Variable } from '../../../types'
import { EditionType } from '../../../types'
@@ -14,11 +13,13 @@ import { PromptRole } from '@/models/debug'
const i18nPrefix = 'workflow.nodes.llm'
type Props = {
instanceId: string
className?: string
headerClassName?: string
canNotChooseSystemRole?: boolean
readOnly: boolean
id: string
nodeId: string
canRemove: boolean
isChatModel: boolean
isChatApp: boolean
@@ -58,11 +59,13 @@ const roleOptions = [
const roleOptionsWithoutSystemRole = roleOptions.filter(item => item.value !== PromptRole.system)
const ConfigPromptItem: FC<Props> = ({
instanceId,
className,
headerClassName,
canNotChooseSystemRole,
readOnly,
id,
nodeId,
canRemove,
handleChatModeMessageRoleChange,
isChatModel,
@@ -84,10 +87,6 @@ const ConfigPromptItem: FC<Props> = ({
const {
setControlPromptEditorRerenderKey,
} = workflowStore.getState()
const [instanceId, setInstanceId] = useState(uniqueId())
useEffect(() => {
setInstanceId(`${id}-${uniqueId()}`)
}, [id])
const handleGenerated = useCallback((prompt: string) => {
onPromptChange(prompt)
@@ -136,6 +135,7 @@ const ConfigPromptItem: FC<Props> = ({
hasSetBlockStatus={hasSetBlockStatus}
nodesOutputVars={availableVars}
availableNodes={availableNodes}
nodeId={nodeId}
isSupportPromptGenerator={payload.role === PromptRole.system}
onGenerated={handleGenerated}
modelConfig={modelConfig}

View File

@@ -182,12 +182,14 @@ const ConfigPrompt: FC<Props> = ({
<div key={item.id || index} className='group relative'>
{canDrag && <DragHandle className='absolute left-[-14px] top-2 hidden h-3.5 w-3.5 text-text-quaternary group-hover:block' />}
<ConfigPromptItem
instanceId={item.role === PromptRole.system ? `${nodeId}-chat-workflow-llm-prompt-editor` : `${nodeId}-chat-workflow-llm-prompt-editor-${index}`}
className={cn(canDrag && 'handle')}
headerClassName={cn(canDrag && 'cursor-grab')}
canNotChooseSystemRole={!canChooseSystemRole}
canRemove={payload.length > 1 && !(index === 0 && item.role === PromptRole.system)}
readOnly={readOnly}
id={item.id!}
nodeId={nodeId}
handleChatModeMessageRoleChange={handleChatModeMessageRoleChange(index)}
isChatModel={isChatModel}
isChatApp={isChatApp}

View File

@@ -7,24 +7,30 @@ import { Generator } from '@/app/components/base/icons/src/vender/other'
import { ActionButton } from '@/app/components/base/action-button'
import GetAutomaticResModal from '@/app/components/app/configuration/config/automatic/get-automatic-res'
import { AppType } from '@/types/app'
import type { AutomaticRes } from '@/service/debug'
import type { GenRes } from '@/service/debug'
import type { ModelConfig } from '@/app/components/workflow/types'
import { useHooksStore } from '../../../hooks-store'
type Props = {
className?: string
onGenerated?: (prompt: string) => void
modelConfig?: ModelConfig
nodeId: string
currentPrompt?: string
}
const PromptGeneratorBtn: FC<Props> = ({
className,
onGenerated,
nodeId,
currentPrompt,
}) => {
const [showAutomatic, { setTrue: showAutomaticTrue, setFalse: showAutomaticFalse }] = useBoolean(false)
const handleAutomaticRes = useCallback((res: AutomaticRes) => {
onGenerated?.(res.prompt)
const handleAutomaticRes = useCallback((res: GenRes) => {
onGenerated?.(res.modified)
showAutomaticFalse()
}, [onGenerated, showAutomaticFalse])
const configsMap = useHooksStore(s => s.configsMap)
return (
<div className={cn(className)}>
<ActionButton
@@ -38,7 +44,9 @@ const PromptGeneratorBtn: FC<Props> = ({
isShow={showAutomatic}
onClose={showAutomaticFalse}
onFinished={handleAutomaticRes}
isInLLMNode
flowId={configsMap?.flowId || ''}
nodeId={nodeId}
currentPrompt={currentPrompt}
/>
)}
</div>

View File

@@ -296,6 +296,7 @@ export type NodeOutPutVar = {
vars: Var[]
isStartNode?: boolean
isLoop?: boolean
isFlat?: boolean
}
export type Block = {

View File

@@ -176,6 +176,7 @@ const Panel: FC = () => {
{/* right */}
<div className='w-0 grow'>
<Right
nodeId={currentFocusNodeId!}
isValueFetching={isCurrentNodeVarValueFetching}
currentNodeVar={currentNodeInfo as currentVarType}
handleOpenMenu={() => setShowLeftPanel(true)}

View File

@@ -3,9 +3,10 @@ import {
RiArrowGoBackLine,
RiCloseLine,
RiMenuLine,
RiSparklingFill,
} from '@remixicon/react'
import { useStore } from '../store'
import type { BlockEnum } from '../types'
import { BlockEnum } from '../types'
import useCurrentVars from '../hooks/use-inspect-vars-crud'
import Empty from './empty'
import ValueContent from './value-content'
@@ -18,15 +19,31 @@ import Loading from '@/app/components/base/loading'
import type { currentVarType } from './panel'
import { VarInInspectType } from '@/types/workflow'
import cn from '@/utils/classnames'
import useNodeInfo from '../nodes/_base/hooks/use-node-info'
import { useBoolean } from 'ahooks'
import GetAutomaticResModal from '@/app/components/app/configuration/config/automatic/get-automatic-res'
import GetCodeGeneratorResModal from '../../app/configuration/config/code-generator/get-code-generator-res'
import { AppType } from '@/types/app'
import { useHooksStore } from '../hooks-store'
import { useCallback, useMemo } from 'react'
import { useNodesInteractions } from '../hooks'
import { CodeLanguage } from '../nodes/code/types'
import useNodeCrud from '../nodes/_base/hooks/use-node-crud'
import type { GenRes } from '@/service/debug'
import produce from 'immer'
import { PROMPT_EDITOR_UPDATE_VALUE_BY_EVENT_EMITTER } from '../../base/prompt-editor/plugins/update-block'
import { useEventEmitterContextContext } from '@/context/event-emitter'
import { VariableIconWithColor } from '@/app/components/workflow/nodes/_base/components/variable/variable-label'
type Props = {
nodeId: string
currentNodeVar?: currentVarType
handleOpenMenu: () => void
isValueFetching?: boolean
}
const Right = ({
nodeId,
currentNodeVar,
handleOpenMenu,
isValueFetching,
@@ -73,6 +90,67 @@ const Right = ({
return String(value)
}
const configsMap = useHooksStore(s => s.configsMap)
const { eventEmitter } = useEventEmitterContextContext()
const { handleNodeSelect } = useNodesInteractions()
const { node } = useNodeInfo(nodeId)
const { setInputs } = useNodeCrud(nodeId, node?.data)
const blockType = node?.data?.type
const isCodeBlock = blockType === BlockEnum.Code
const canShowPromptGenerator = [BlockEnum.LLM, BlockEnum.Code].includes(blockType)
const currentPrompt = useMemo(() => {
if (!canShowPromptGenerator)
return ''
if (blockType === BlockEnum.LLM)
return node?.data?.prompt_template?.text || node?.data?.prompt_template?.[0].text
// if (blockType === BlockEnum.Agent) {
// return node?.data?.agent_parameters?.instruction?.value
// }
if (blockType === BlockEnum.Code)
return node?.data?.code
}, [canShowPromptGenerator])
const [isShowPromptGenerator, {
setTrue: doShowPromptGenerator,
setFalse: handleHidePromptGenerator,
}] = useBoolean(false)
const handleShowPromptGenerator = useCallback(() => {
handleNodeSelect(nodeId)
doShowPromptGenerator()
}, [doShowPromptGenerator, handleNodeSelect, nodeId])
const handleUpdatePrompt = useCallback((res: GenRes) => {
const newInputs = produce(node?.data, (draft: any) => {
switch (blockType) {
case BlockEnum.LLM:
if (draft?.prompt_template) {
if (Array.isArray(draft.prompt_template))
draft.prompt_template[0].text = res.modified
else
draft.prompt_template.text = res.modified
}
break
// Agent is a plugin, may has many instructions, can not locate which one to update
// case BlockEnum.Agent:
// if (draft?.agent_parameters?.instruction) {
// draft.agent_parameters.instruction.value = res.modified
// }
// break
case BlockEnum.Code:
draft.code = res.modified
break
}
})
setInputs(newInputs)
eventEmitter?.emit({
type: PROMPT_EDITOR_UPDATE_VALUE_BY_EVENT_EMITTER,
instanceId: `${nodeId}-chat-workflow-llm-prompt-editor`,
payload: res.modified,
} as any)
handleHidePromptGenerator()
}, [setInputs, blockType, nodeId, node?.data, handleHidePromptGenerator])
return (
<div className={cn('flex h-full flex-col')}>
{/* header */}
@@ -112,6 +190,16 @@ const Right = ({
<div className='flex shrink-0 items-center gap-1'>
{currentNodeVar && (
<>
{canShowPromptGenerator && (
<Tooltip popupContent={t('appDebug.generate.optimizePromptTooltip')}>
<div
className='cursor-pointer rounded-md p-1 hover:bg-state-accent-active'
onClick={handleShowPromptGenerator}
>
<RiSparklingFill className='size-4 text-components-input-border-active-prompt-1' />
</div>
</Tooltip>
)}
{currentNodeVar.var.edited && (
<Badge>
<span className='ml-[2.5px] mr-[4.5px] h-[3px] w-[3px] rounded bg-text-accent-secondary'></span>
@@ -152,6 +240,28 @@ const Right = ({
)}
{currentNodeVar && !isValueFetching && <ValueContent currentVar={currentNodeVar.var} handleValueChange={handleValueChange} />}
</div>
{isShowPromptGenerator && (
isCodeBlock
? <GetCodeGeneratorResModal
isShow
mode={AppType.chat}
onClose={handleHidePromptGenerator}
flowId={configsMap?.flowId || ''}
nodeId={nodeId}
currentCode={currentPrompt}
codeLanguages={node?.data?.code_languages || CodeLanguage.python3}
onFinished={handleUpdatePrompt}
/>
: <GetAutomaticResModal
mode={AppType.chat}
isShow
onClose={handleHidePromptGenerator}
onFinished={handleUpdatePrompt}
flowId={configsMap?.flowId || ''}
nodeId={nodeId}
currentPrompt={currentPrompt}
/>
)}
</div>
)
}