Merge branch 'feat/enchance-prompt-and-code-fe' into deploy/dev

deploy/dev
Joel 9 months ago
commit d6af356248

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

@ -14,24 +14,18 @@ import {
RiTranslate, RiTranslate,
RiUser2Line, RiUser2Line,
} from '@remixicon/react' } from '@remixicon/react'
import cn from 'classnames'
import s from './style.module.css' import s from './style.module.css'
import Modal from '@/app/components/base/modal' import Modal from '@/app/components/base/modal'
import Button from '@/app/components/base/button' import Button from '@/app/components/base/button'
import Textarea from '@/app/components/base/textarea'
import Toast from '@/app/components/base/toast' import Toast from '@/app/components/base/toast'
import { generateRule } from '@/service/debug' import { generateBasicAppFistTimeRule, generateRule } from '@/service/debug'
import ConfigPrompt from '@/app/components/app/configuration/config-prompt'
import type { CompletionParams, Model } from '@/types/app' import type { CompletionParams, Model } from '@/types/app'
import { AppType } from '@/types/app' import type { AppType } from '@/types/app'
import ConfigVar from '@/app/components/app/configuration/config-var'
import GroupName from '@/app/components/app/configuration/base/group-name'
import Loading from '@/app/components/base/loading' import Loading from '@/app/components/base/loading'
import Confirm from '@/app/components/base/confirm' import Confirm from '@/app/components/base/confirm'
import { LoveMessage } from '@/app/components/base/icons/src/vender/features'
// type // type
import type { AutomaticRes } from '@/service/debug' import type { GenRes } from '@/service/debug'
import { Generator } from '@/app/components/base/icons/src/vender/other' import { Generator } from '@/app/components/base/icons/src/vender/other'
import ModelParameterModal from '@/app/components/header/account-setting/model-provider-page/model-parameter-modal' 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 { useModelListAndDefaultModelAndCurrentProviderAndModel } from '@/app/components/header/account-setting/model-provider-page/hooks'
import type { ModelModeType } from '@/types/app' import type { ModelModeType } from '@/types/app'
import type { FormValue } from '@/app/components/header/account-setting/model-provider-page/declarations' 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 = { export type IGetAutomaticResProps = {
mode: AppType mode: AppType
isShow: boolean isShow: boolean
onClose: () => void onClose: () => void
onFinished: (res: AutomaticRes) => void onFinished: (res: GenRes) => void
isInLLMNode?: boolean flowId?: string
nodeId?: string
currentPrompt?: string
isBasicMode?: boolean
} }
const TryLabel: FC<{ const TryLabel: FC<{
@ -68,7 +74,10 @@ const GetAutomaticRes: FC<IGetAutomaticResProps> = ({
mode, mode,
isShow, isShow,
onClose, onClose,
isInLLMNode, flowId,
nodeId,
currentPrompt,
isBasicMode,
onFinished, onFinished,
}) => { }) => {
const { t } = useTranslation() const { t } = useTranslation()
@ -124,12 +133,25 @@ const GetAutomaticRes: FC<IGetAutomaticResProps> = ({
] ]
const [instruction, setInstruction] = useState<string>('') const [instruction, setInstruction] = useState<string>('')
const [ideaOutput, setIdeaOutput] = useState<string>('')
const [editorKey, setEditorKey] = useState(`${flowId}-0`)
const handleChooseTemplate = useCallback((key: string) => { const handleChooseTemplate = useCallback((key: string) => {
return () => { return () => {
const template = t(`appDebug.generate.template.${key}.instruction`) const template = t(`appDebug.generate.template.${key}.instruction`)
setInstruction(template) setInstruction(template)
setEditorKey(`${flowId}-${Date.now()}`)
} }
}, [t]) }, [t])
const { data: instructionTemplate } = useGenerateRuleTemplate(GeneratorType.prompt, isBasicMode)
useEffect(() => {
if (!instruction && instructionTemplate) {
setInstruction(instructionTemplate.data)
setEditorKey(`${flowId}-${Date.now()}`)
}
}, [instructionTemplate])
const isValid = () => { const isValid = () => {
if (instruction.trim() === '') { if (instruction.trim() === '') {
Toast.notify({ Toast.notify({
@ -143,7 +165,10 @@ const GetAutomaticRes: FC<IGetAutomaticResProps> = ({
return true return true
} }
const [isLoading, { setTrue: setLoadingTrue, setFalse: setLoadingFalse }] = useBoolean(false) 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(() => { useEffect(() => {
if (defaultModel) { if (defaultModel) {
@ -170,16 +195,6 @@ const GetAutomaticRes: FC<IGetAutomaticResProps> = ({
</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.generate.noDataLine1')}</div>
<div>{t('appDebug.generate.noDataLine2')}</div>
</div>
</div>
)
const handleModelChange = useCallback((newValue: { modelId: string; provider: string; mode?: string; features?: string[] }) => { const handleModelChange = useCallback((newValue: { modelId: string; provider: string; mode?: string; features?: string[] }) => {
const newModel = { const newModel = {
...model, ...model,
@ -207,28 +222,59 @@ const GetAutomaticRes: FC<IGetAutomaticResProps> = ({
return return
setLoadingTrue() setLoadingTrue()
try { try {
const { error, ...res } = await generateRule({ let apiRes: GenRes
instruction, let hasError = false
model_config: model, if (isBasicMode && !currentPrompt) {
no_variable: !!isInLLMNode, const { error, ...res } = await generateBasicAppFistTimeRule({
}) instruction,
setRes(res) model_config: model,
if (error) { no_variable: false,
Toast.notify({ })
type: 'error', apiRes = {
message: error, ...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,
idea_output: ideaOutput,
model_config: model,
}) })
apiRes = res
if (error) {
hasError = true
Toast.notify({
type: 'error',
message: error,
})
}
} }
if (!hasError)
addVersion(apiRes)
} }
finally { finally {
setLoadingFalse() setLoadingFalse()
} }
} }
const [showConfirmOverwrite, setShowConfirmOverwrite] = React.useState(false) const [isShowConfirmOverwrite, {
setTrue: showConfirmOverwrite,
setFalse: hideShowConfirmOverwrite,
}] = useBoolean(false)
const isShowAutoPromptResPlaceholder = () => { const isShowAutoPromptResPlaceholder = () => {
return !isLoading && !res return !isLoading && !current
} }
return ( return (
@ -236,15 +282,14 @@ const GetAutomaticRes: FC<IGetAutomaticResProps> = ({
isShow={isShow} isShow={isShow}
onClose={onClose} onClose={onClose}
className='min-w-[1140px] !p-0' className='min-w-[1140px] !p-0'
closable
> >
<div className='flex h-[680px] flex-wrap'> <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='h-full w-[570px] shrink-0 overflow-y-auto border-r border-divider-regular p-6'>
<div className='mb-8'> <div className='mb-4'>
<div className={`text-lg font-bold leading-[28px] ${s.textGradient}`}>{t('appDebug.generate.title')}</div> <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 className='mt-1 text-[13px] font-normal text-text-tertiary'>{t('appDebug.generate.description')}</div>
</div> </div>
<div className='mb-8'> <div>
<ModelParameterModal <ModelParameterModal
popupClassName='!w-[520px]' popupClassName='!w-[520px]'
portalToFollowElemContentClassName='z-[1000]' portalToFollowElemContentClassName='z-[1000]'
@ -258,116 +303,99 @@ const GetAutomaticRes: FC<IGetAutomaticResProps> = ({
hideDebugWithMultipleModel hideDebugWithMultipleModel
/> />
</div> </div>
<div > {isBasicMode && (
<div className='flex items-center'> <div className='mt-4'>
<div className='mr-3 shrink-0 text-xs font-semibold uppercase leading-[18px] text-text-tertiary'>{t('appDebug.generate.tryIt')}</div> <div className='flex items-center'>
<div className='h-px grow' style={{ <div className='mr-3 shrink-0 text-xs font-semibold uppercase leading-[18px] text-text-tertiary'>{t('appDebug.generate.tryIt')}</div>
background: 'linear-gradient(to right, rgba(243, 244, 246, 1), rgba(243, 244, 246, 0))', <div className='h-px grow' style={{
}}></div> 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>
<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 */} {/* inputs */}
<div className='mt-6'> <div className='mt-4'>
<div className='text-[0px]'> <div>
<div className='mb-2 text-sm font-medium leading-5 text-text-primary'>{t('appDebug.generate.instruction')}</div> <div className='system-sm-semibold-uppercase mb-1.5 text-text-secondary'>{t('appDebug.generate.instruction')}</div>
<Textarea {isBasicMode ? (
className="h-[200px] resize-none" <InstructionEditorInBasic
placeholder={t('appDebug.generate.instructionPlaceHolder') as string} editorKey={editorKey}
value={instruction} generatorType={GeneratorType.prompt}
onChange={e => setInstruction(e.target.value)} /> 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> </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 <Button
className='flex space-x-1' className='flex space-x-1'
variant='primary' variant='primary'
onClick={onGenerate} onClick={onGenerate}
disabled={isLoading} disabled={isLoading}
> >
<Generator className='h-4 w-4 text-white' /> <Generator className='h-4 w-4' />
<span className='text-xs font-semibold text-white'>{t('appDebug.generate.generate')}</span> <span className='text-xs font-semibold'>{t('appDebug.generate.generate')}</span>
</Button> </Button>
</div> </div>
</div> </div>
</div> </div>
{(!isLoading && res) && ( {(!isLoading && current) && (
<div className='h-full w-0 grow p-6 pb-0'> <div className='h-full w-0 grow bg-background-default-subtle p-6 pb-0'>
<div className='mb-3 shrink-0 text-base font-semibold leading-[160%] text-text-secondary'>{t('appDebug.generate.resTitle')}</div> <Result
<div className={cn('max-h-[555px] overflow-y-auto', !isInLLMNode && 'pb-2')}> current={current!}
<ConfigPrompt isBasicMode={isBasicMode}
mode={mode} nodeId={nodeId!}
promptTemplate={res?.prompt || ''} currentVersionIndex={currentVersionIndex || 0}
promptVariables={[]} setCurrentVersionIndex={setCurrentVersionIndex}
readonly versions={versions || []}
noTitle={isInLLMNode} onApply={showConfirmOverwrite}
gradientBorder generatorType={GeneratorType.prompt}
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>
</div> </div>
)} )}
{isLoading && renderLoading} {isLoading && renderLoading}
{isShowAutoPromptResPlaceholder() && renderNoData} {isShowAutoPromptResPlaceholder() && <ResPlaceholder />}
{showConfirmOverwrite && ( {isShowConfirmOverwrite && (
<Confirm <Confirm
title={t('appDebug.generate.overwriteTitle')} title={t('appDebug.generate.overwriteTitle')}
content={t('appDebug.generate.overwriteMessage')} content={t('appDebug.generate.overwriteMessage')}
isShow={showConfirmOverwrite} isShow
onConfirm={() => { onConfirm={() => {
setShowConfirmOverwrite(false) hideShowConfirmOverwrite()
onFinished(res!) onFinished(current!)
}} }}
onCancel={() => setShowConfirmOverwrite(false)} onCancel={hideShowConfirmOverwrite}
/> />
)} )}
</div> </div>

@ -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)

@ -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)

@ -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 isBasicMode = !!getVarType
// const [controlPromptEditorRerenderKey] =
const isCode = generatorType === 'code'
const placeholder = (
<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-3 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)

@ -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)

@ -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)

@ -0,0 +1,51 @@
import { RiCloseLine, RiInformation2Fill } from '@remixicon/react'
import { useBoolean } from 'ahooks'
import React from 'react'
import cn from '@/utils/classnames'
import { Markdown } from '@/app/components/base/markdown'
type Props = {
message: string
className?: string
}
const PromptToast = ({
message,
className,
}: Props) => {
const [isHide, {
setTrue: hide,
}] = useBoolean(false)
// const message = `
// # h1
// **strong text** ~~strikethrough~~
// * list1
// * list2
// xxxx
// ## h2
// \`\`\`python
// print('Hello, World!')
// \`\`\`
// `
if (isHide)
return
return (
<div className={cn('relative flex items-center p-2 ', className)}>
{/* Background Effect */}
<div className="pointer-events-none absolute inset-0 rounded-lg bg-[linear-gradient(92deg,rgba(11,165,236,0.25)_0%,rgba(255,255,255,0.00)_100%)] opacity-40 shadow-md"></div>
<div className='relative flex h-full w-full justify-between'>
<div className="flex h-full w-0 grow gap-1">
<RiInformation2Fill className="mt-[3px] size-4 shrink-0 text-text-accent" />
<Markdown className="w-0 grow text-sm" content={message} />
</div>
<div className='relative top-[-1px] shrink-0 cursor-pointer p-0.5' onClick={hide}>
<RiCloseLine className='size-5 text-text-tertiary' />
</div>
</div>
</div>
)
}
export default PromptToast

@ -0,0 +1,20 @@
'use client'
import { Generator } from '@/app/components/base/icons/src/vender/other'
import Link from 'next/link'
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>
<Link className='text-text-accent' href='//todo' target='_blank'>{t('appDebug.generate.newNoDataLine2')}</Link>
</div>
</div>
)
}
export default React.memo(ResPlaceholder)

@ -0,0 +1,96 @@
'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'
import cn from '@/utils/classnames'
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>
{
current?.message && (
<PromptToast message={current.message} className='mt-4 shrink-0' />
)
}
<div className={cn('mt-3 grow', isGeneratorPrompt && 'overflow-y-auto')}>
{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>
)
}
export default React.memo(Result)

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

@ -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

@ -0,0 +1,101 @@
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'
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 [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: `Version ${index + 1}${index === versionLen - 1 ? ' · Latest' : ''}`,
value: index,
}))
const isLatest = value === versionLen - 1
return (
<PortalToFollowElem
placement={'bottom-start'}
offset={{
mainAxis: 4,
crossAxis: -8,
}}
open={isOpen}
onOpenChange={handleOpen}
>
<PortalToFollowElemTrigger
onClick={handleToggle}
asChild
>
<div className={cn('system-xs-medium flex items-center text-text-secondary', moreThanOneVersion && 'cursor-pointer')}>
<div>Version {value + 1}{isLatest && ' · 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='system-xs-medium-uppercase flex h-[22px] items-center px-3 pl-3 text-text-tertiary'>
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

@ -1,16 +1,13 @@
import type { FC } from 'react' import type { FC } from 'react'
import React, { useCallback, useEffect } from 'react' import React, { useCallback, useEffect, useState } from 'react'
import cn from 'classnames'
import useBoolean from 'ahooks/lib/useBoolean' import useBoolean from 'ahooks/lib/useBoolean'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import ConfigPrompt from '../../config-prompt'
import { languageMap } from '../../../../workflow/nodes/_base/components/editor/code-editor/index' import { languageMap } from '../../../../workflow/nodes/_base/components/editor/code-editor/index'
import { generateRuleCode } from '@/service/debug' import { generateRule } from '@/service/debug'
import type { CodeGenRes } from '@/service/debug' import type { GenRes } from '@/service/debug'
import type { ModelModeType } from '@/types/app' import type { ModelModeType } from '@/types/app'
import type { AppType, CompletionParams, Model } from '@/types/app' import type { AppType, CompletionParams, Model } from '@/types/app'
import Modal from '@/app/components/base/modal' import Modal from '@/app/components/base/modal'
import Textarea from '@/app/components/base/textarea'
import Button from '@/app/components/base/button' import Button from '@/app/components/base/button'
import { Generator } from '@/app/components/base/icons/src/vender/other' import { Generator } from '@/app/components/base/icons/src/vender/other'
import Toast from '@/app/components/base/toast' import Toast from '@/app/components/base/toast'
@ -21,17 +18,31 @@ import { useModelListAndDefaultModelAndCurrentProviderAndModel } from '@/app/com
import { ModelTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations' 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 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 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'
const i18nPrefix = 'appDebug.generate'
export type IGetCodeGeneratorResProps = { export type IGetCodeGeneratorResProps = {
flowId: string
nodeId: string
currentCode?: string
mode: AppType mode: AppType
isShow: boolean isShow: boolean
codeLanguages: CodeLanguage codeLanguages: CodeLanguage
onClose: () => void onClose: () => void
onFinished: (res: CodeGenRes) => void onFinished: (res: GenRes) => void
} }
export const GetCodeGeneratorResModal: FC<IGetCodeGeneratorResProps> = ( export const GetCodeGeneratorResModal: FC<IGetCodeGeneratorResProps> = (
{ {
flowId,
nodeId,
currentCode,
mode, mode,
isShow, isShow,
codeLanguages, codeLanguages,
@ -61,9 +72,23 @@ export const GetCodeGeneratorResModal: FC<IGetCodeGeneratorResProps> = (
const { const {
defaultModel, defaultModel,
} = useModelListAndDefaultModelAndCurrentProviderAndModel(ModelTypeEnum.textGeneration) } = useModelListAndDefaultModelAndCurrentProviderAndModel(ModelTypeEnum.textGeneration)
const [instruction, setInstruction] = React.useState<string>('') const [instruction, setInstruction] = useState<string>('')
const [ideaOutput, setIdeaOutput] = useState<string>('')
const [isLoading, { setTrue: setLoadingTrue, setFalse: setLoadingFalse }] = useBoolean(false) 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 = () => { const isValid = () => {
if (instruction.trim() === '') { if (instruction.trim() === '') {
Toast.notify({ Toast.notify({
@ -97,7 +122,6 @@ export const GetCodeGeneratorResModal: FC<IGetCodeGeneratorResProps> = (
localStorage.setItem('auto-gen-model', JSON.stringify(newModel)) localStorage.setItem('auto-gen-model', JSON.stringify(newModel))
}, [model, setModel]) }, [model, setModel])
const isInLLMNode = true
const onGenerate = async () => { const onGenerate = async () => {
if (!isValid()) if (!isValid())
return return
@ -105,25 +129,35 @@ export const GetCodeGeneratorResModal: FC<IGetCodeGeneratorResProps> = (
return return
setLoadingTrue() setLoadingTrue()
try { try {
const { error, ...res } = await generateRuleCode({ const { error, ...res } = await generateRule({
flow_id: flowId,
node_id: nodeId,
current: currentCode,
instruction, instruction,
model_config: model, model_config: model,
no_variable: !!isInLLMNode, idea_output: ideaOutput,
code_language: languageMap[codeLanguages] || 'javascript', language: languageMap[codeLanguages] || 'javascript',
}) })
setRes(res)
if (error) { if (error) {
Toast.notify({ Toast.notify({
type: 'error', type: 'error',
message: error, message: error,
}) })
} }
else {
addVersion(res)
}
} }
finally { finally {
setLoadingFalse() setLoadingFalse()
} }
} }
const [showConfirmOverwrite, setShowConfirmOverwrite] = React.useState(false)
const [isShowConfirmOverwrite, {
setTrue: showConfirmOverwrite,
setFalse: hideShowConfirmOverwrite,
}] = useBoolean(false)
useEffect(() => { useEffect(() => {
if (defaultModel) { if (defaultModel) {
@ -155,26 +189,16 @@ export const GetCodeGeneratorResModal: FC<IGetCodeGeneratorResProps> = (
<div className='text-[13px] text-text-tertiary'>{t('appDebug.codegen.loading')}</div> <div className='text-[13px] text-text-tertiary'>{t('appDebug.codegen.loading')}</div>
</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 ( return (
<Modal <Modal
isShow={isShow} isShow={isShow}
onClose={onClose} onClose={onClose}
className='min-w-[1140px] !p-0' className='min-w-[1140px] !p-0'
closable
> >
<div className='relative flex h-[680px] flex-wrap'> <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='h-full w-[570px] shrink-0 overflow-y-auto border-r border-divider-regular p-6'>
<div className='mb-8'> <div className='mb-4'>
<div className={'text-lg font-bold leading-[28px] text-text-primary'}>{t('appDebug.codegen.title')}</div> <div className={'text-lg font-bold leading-[28px] text-text-primary'}>{t('appDebug.codegen.title')}</div>
<div className='mt-1 text-[13px] font-normal text-text-tertiary'>{t('appDebug.codegen.description')}</div> <div className='mt-1 text-[13px] font-normal text-text-tertiary'>{t('appDebug.codegen.description')}</div>
</div> </div>
@ -194,84 +218,60 @@ export const GetCodeGeneratorResModal: FC<IGetCodeGeneratorResProps> = (
</div> </div>
<div> <div>
<div className='text-[0px]'> <div className='text-[0px]'>
<div className='mb-2 text-sm font-medium leading-5 text-text-primary'>{t('appDebug.codegen.instruction')}</div> <div className='system-sm-semibold-uppercase mb-1.5 text-text-secondary'>{t('appDebug.codegen.instruction')}</div>
<Textarea <InstructionEditor
className="h-[200px] resize-none" editorKey={editorKey}
placeholder={t('appDebug.codegen.instructionPlaceholder') || ''}
value={instruction} value={instruction}
onChange={e => setInstruction(e.target.value)} onChange={setInstruction}
nodeId={nodeId}
generatorType={GeneratorType.code}
isShowCurrentBlock={!!currentCode}
/> />
</div> </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 <Button
className='flex space-x-1' className='flex space-x-1'
variant='primary' variant='primary'
onClick={onGenerate} onClick={onGenerate}
disabled={isLoading} disabled={isLoading}
> >
<Generator className='h-4 w-4 text-white' /> <Generator className='h-4 w-4' />
<span className='text-xs font-semibold text-white'>{t('appDebug.codegen.generate')}</span> <span className='text-xs font-semibold '>{t('appDebug.codegen.generate')}</span>
</Button> </Button>
</div> </div>
</div> </div>
</div> </div>
{isLoading && renderLoading} {isLoading && renderLoading}
{!isLoading && !res && renderNoData} {!isLoading && !current && <ResPlaceholder />}
{(!isLoading && res) && ( {(!isLoading && current) && (
<div className='h-full w-0 grow p-6 pb-0'> <div className='h-full w-0 grow bg-background-default-subtle p-6 pb-0'>
<div className='mb-3 shrink-0 text-base font-semibold leading-[160%] text-text-secondary'>{t('appDebug.codegen.resTitle')}</div> <Result
<div className={cn('max-h-[555px] overflow-y-auto', !isInLLMNode && 'pb-2')}> current={current!}
<ConfigPrompt currentVersionIndex={currentVersionIndex || 0}
mode={mode} setCurrentVersionIndex={setCurrentVersionIndex}
promptTemplate={res?.code || ''} versions={versions || []}
promptVariables={[]} onApply={showConfirmOverwrite}
readonly generatorType={GeneratorType.code}
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>
</div> </div>
)} )}
</div> </div>
{showConfirmOverwrite && ( {isShowConfirmOverwrite && (
<Confirm <Confirm
title={t('appDebug.codegen.overwriteConfirmTitle')} title={t('appDebug.codegen.overwriteConfirmTitle')}
content={t('appDebug.codegen.overwriteConfirmMessage')} content={t('appDebug.codegen.overwriteConfirmMessage')}
isShow={showConfirmOverwrite} isShow
onConfirm={() => { onConfirm={() => {
setShowConfirmOverwrite(false) hideShowConfirmOverwrite()
onFinished(res!) onFinished(current!)
}} }}
onCancel={() => setShowConfirmOverwrite(false)} onCancel={hideShowConfirmOverwrite}
/> />
)} )}
</Modal> </Modal>

@ -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

@ -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

@ -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"
}

@ -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

@ -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"
}

@ -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

@ -3,6 +3,7 @@ export { default as Bookmark } from './Bookmark'
export { default as CheckDone01 } from './CheckDone01' export { default as CheckDone01 } from './CheckDone01'
export { default as Check } from './Check' export { default as Check } from './Check'
export { default as ChecklistSquare } from './ChecklistSquare' export { default as ChecklistSquare } from './ChecklistSquare'
export { default as CodeAssistant } from './CodeAssistant'
export { default as DotsGrid } from './DotsGrid' export { default as DotsGrid } from './DotsGrid'
export { default as Edit02 } from './Edit02' export { default as Edit02 } from './Edit02'
export { default as Edit04 } from './Edit04' 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 LogIn04 } from './LogIn04'
export { default as LogOut01 } from './LogOut01' export { default as LogOut01 } from './LogOut01'
export { default as LogOut04 } from './LogOut04' export { default as LogOut04 } from './LogOut04'
export { default as MagicEdit } from './MagicEdit'
export { default as Menu01 } from './Menu01' export { default as Menu01 } from './Menu01'
export { default as Pin01 } from './Pin01' export { default as Pin01 } from './Pin01'
export { default as Pin02 } from './Pin02' export { default as Pin02 } from './Pin02'

@ -3,6 +3,10 @@ import { SupportUploadFileTypes, type ValueSelector } from '../../workflow/types
export const CONTEXT_PLACEHOLDER_TEXT = '{{#context#}}' export const CONTEXT_PLACEHOLDER_TEXT = '{{#context#}}'
export const HISTORY_PLACEHOLDER_TEXT = '{{#histories#}}' export const HISTORY_PLACEHOLDER_TEXT = '{{#histories#}}'
export const QUERY_PLACEHOLDER_TEXT = '{{#query#}}' 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 PRE_PROMPT_PLACEHOLDER_TEXT = '{{#pre_prompt#}}'
export const UPDATE_DATASETS_EVENT_EMITTER = 'prompt-editor-context-block-update-datasets' export const UPDATE_DATASETS_EVENT_EMITTER = 'prompt-editor-context-block-update-datasets'
export const UPDATE_HISTORY_EVENT_EMITTER = 'prompt-editor-history-block-update-role' export const UPDATE_HISTORY_EVENT_EMITTER = 'prompt-editor-history-block-update-role'

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

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

@ -17,8 +17,11 @@ import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext
import { LexicalTypeaheadMenuPlugin } from '@lexical/react/LexicalTypeaheadMenuPlugin' import { LexicalTypeaheadMenuPlugin } from '@lexical/react/LexicalTypeaheadMenuPlugin'
import type { import type {
ContextBlockType, ContextBlockType,
CurrentBlockType,
ErrorMessageBlockType,
ExternalToolBlockType, ExternalToolBlockType,
HistoryBlockType, HistoryBlockType,
LastRunBlockType,
QueryBlockType, QueryBlockType,
VariableBlockType, VariableBlockType,
WorkflowVariableBlockType, WorkflowVariableBlockType,
@ -32,6 +35,10 @@ import type { PickerBlockMenuOption } from './menu'
import VarReferenceVars from '@/app/components/workflow/nodes/_base/components/variable/var-reference-vars' import VarReferenceVars from '@/app/components/workflow/nodes/_base/components/variable/var-reference-vars'
import { useEventEmitterContextContext } from '@/context/event-emitter' import { useEventEmitterContextContext } from '@/context/event-emitter'
import { KEY_ESCAPE_COMMAND } from 'lexical' 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 = { type ComponentPickerProps = {
triggerString: string triggerString: string
@ -41,6 +48,9 @@ type ComponentPickerProps = {
variableBlock?: VariableBlockType variableBlock?: VariableBlockType
externalToolBlock?: ExternalToolBlockType externalToolBlock?: ExternalToolBlockType
workflowVariableBlock?: WorkflowVariableBlockType workflowVariableBlock?: WorkflowVariableBlockType
currentBlock?: CurrentBlockType
errorMessageBlock?: ErrorMessageBlockType
lastRunBlock?: LastRunBlockType
isSupportFileVar?: boolean isSupportFileVar?: boolean
} }
const ComponentPicker = ({ const ComponentPicker = ({
@ -51,6 +61,9 @@ const ComponentPicker = ({
variableBlock, variableBlock,
externalToolBlock, externalToolBlock,
workflowVariableBlock, workflowVariableBlock,
currentBlock,
errorMessageBlock,
lastRunBlock,
isSupportFileVar, isSupportFileVar,
}: ComponentPickerProps) => { }: ComponentPickerProps) => {
const { eventEmitter } = useEventEmitterContextContext() const { eventEmitter } = useEventEmitterContextContext()
@ -87,6 +100,9 @@ const ComponentPicker = ({
variableBlock, variableBlock,
externalToolBlock, externalToolBlock,
workflowVariableBlock, workflowVariableBlock,
currentBlock,
errorMessageBlock,
lastRunBlock,
) )
const onSelectOption = useCallback( const onSelectOption = useCallback(
@ -112,12 +128,23 @@ const ComponentPicker = ({
if (needRemove) if (needRemove)
needRemove.remove() needRemove.remove()
}) })
const isFlat = variables.length === 1
if (variables[1] === 'sys.query' || variables[1] === 'sys.files') 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]]) editor.dispatchCommand(INSERT_WORKFLOW_VARIABLE_BLOCK_COMMAND, [variables[1]])
else }
else {
editor.dispatchCommand(INSERT_WORKFLOW_VARIABLE_BLOCK_COMMAND, variables) editor.dispatchCommand(INSERT_WORKFLOW_VARIABLE_BLOCK_COMMAND, variables)
}, [editor, checkForTriggerMatch, triggerString]) }
}, [editor, currentBlock?.generatorType, checkForTriggerMatch, triggerString])
const handleClose = useCallback(() => { const handleClose = useCallback(() => {
const escapeEvent = new KeyboardEvent('keydown', { key: 'Escape' }) const escapeEvent = new KeyboardEvent('keydown', { key: 'Escape' })
@ -166,6 +193,7 @@ const ComponentPicker = ({
onClose={handleClose} onClose={handleClose}
onBlur={handleClose} onBlur={handleClose}
autoFocus={false} autoFocus={false}
isInCodeGeneratorInstructionEditor={currentBlock?.generatorType === GeneratorType.code}
/> />
</div> </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 ( return (
<LexicalTypeaheadMenuPlugin <LexicalTypeaheadMenuPlugin

@ -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

@ -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)

@ -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'

@ -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
}

@ -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

@ -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)

@ -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'

@ -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
}

@ -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

@ -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'

@ -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)

@ -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
}

@ -1,3 +1,4 @@
import type { GeneratorType } from '../../app/configuration/config/automatic/types'
import type { Type } from '../../workflow/nodes/llm/types' import type { Type } from '../../workflow/nodes/llm/types'
import type { Dataset } from './plugins/context-block' import type { Dataset } from './plugins/context-block'
import type { RoleName } from './plugins/history-block' import type { RoleName } from './plugins/history-block'
@ -75,3 +76,22 @@ export type MenuTextMatch = {
matchingString: string matchingString: string
replaceableString: 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
}

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

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

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

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

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

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

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

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

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

@ -23,6 +23,7 @@ import type { Field } from '@/app/components/workflow/nodes/llm/types'
import { FILE_STRUCT } from '@/app/components/workflow/constants' import { FILE_STRUCT } from '@/app/components/workflow/constants'
import { Loop } from '@/app/components/base/icons/src/vender/workflow' import { Loop } from '@/app/components/base/icons/src/vender/workflow'
import { noop } from 'lodash-es' import { noop } from 'lodash-es'
import { CodeAssistant, MagicEdit } from '@/app/components/base/icons/src/vender/line/general'
type ObjectChildrenProps = { type ObjectChildrenProps = {
nodeId: string nodeId: string
@ -46,7 +47,10 @@ type ItemProps = {
isSupportFileVar?: boolean isSupportFileVar?: boolean
isException?: boolean isException?: boolean
isLoopVar?: boolean isLoopVar?: boolean
isFlat?: boolean
isInCodeGeneratorInstructionEditor?: boolean
zIndex?: number zIndex?: number
className?: string
} }
const objVarTypes = [VarType.object, VarType.file] const objVarTypes = [VarType.object, VarType.file]
@ -61,7 +65,10 @@ const Item: FC<ItemProps> = ({
isSupportFileVar, isSupportFileVar,
isException, isException,
isLoopVar, isLoopVar,
isFlat,
isInCodeGeneratorInstructionEditor,
zIndex, zIndex,
className,
}) => { }) => {
const isStructureOutput = itemData.type === VarType.object && (itemData.children as StructuredOutput)?.schema?.properties const isStructureOutput = itemData.type === VarType.object && (itemData.children as StructuredOutput)?.schema?.properties
const isFile = itemData.type === VarType.file && !isStructureOutput const isFile = itemData.type === VarType.file && !isStructureOutput
@ -69,6 +76,29 @@ const Item: FC<ItemProps> = ({
const isSys = itemData.variable.startsWith('sys.') const isSys = itemData.variable.startsWith('sys.')
const isEnv = itemData.variable.startsWith('env.') const isEnv = itemData.variable.startsWith('env.')
const isChatVar = itemData.variable.startsWith('conversation.') 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(() => { const objStructuredOutput: StructuredOutput | null = useMemo(() => {
if (!isObj) return null if (!isObj) return null
@ -125,7 +155,10 @@ const Item: FC<ItemProps> = ({
if (!isSupportFileVar && isFile) if (!isSupportFileVar && isFile)
return 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) onChange([...objPath, ...itemData.variable.split('.')], itemData)
} }
else { else {
@ -144,18 +177,21 @@ const Item: FC<ItemProps> = ({
className={cn( className={cn(
(isObj || isStructureOutput) ? ' pr-1' : 'pr-[18px]', (isObj || isStructureOutput) ? ' pr-1' : 'pr-[18px]',
isHovering && ((isObj || isStructureOutput) ? 'bg-components-panel-on-panel-item-bg-hover' : 'bg-state-base-hover'), 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} onClick={handleChosen}
onMouseDown={e => e.preventDefault()} onMouseDown={e => e.preventDefault()}
> >
<div className='flex w-0 grow items-center'> <div className='flex w-0 grow items-center'>
{!isEnv && !isChatVar && !isLoopVar && <Variable02 className={cn('h-3.5 w-3.5 shrink-0 text-text-accent', isException && 'text-text-warning')} />} {!isFlat && !isEnv && !isChatVar && !isLoopVar && <Variable02 className={cn('h-3.5 w-3.5 shrink-0 text-text-accent', isException && 'text-text-warning')} />}
{isEnv && <Env className='h-3.5 w-3.5 shrink-0 text-util-colors-violet-violet-600' />} {isEnv && <Env className='h-3.5 w-3.5 shrink-0 text-util-colors-violet-violet-600' />}
{isChatVar && <BubbleX className='h-3.5 w-3.5 shrink-0 text-util-colors-teal-teal-700' />} {isChatVar && <BubbleX className='h-3.5 w-3.5 shrink-0 text-util-colors-teal-teal-700' />}
{isLoopVar && <Loop className='h-3.5 w-3.5 shrink-0 text-util-colors-cyan-cyan-500' />} {isLoopVar && <Loop className='h-3.5 w-3.5 shrink-0 text-util-colors-cyan-cyan-500' />}
{isFlat && flatVarIcon}
{!isEnv && !isChatVar && ( {!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 && ( {isEnv && (
<div title={itemData.variable} className='system-sm-medium ml-1 w-0 grow truncate text-text-secondary'>{itemData.variable.replace('env.', '')}</div> <div title={itemData.variable} className='system-sm-medium ml-1 w-0 grow truncate text-text-secondary'>{itemData.variable.replace('env.', '')}</div>
@ -263,6 +299,7 @@ type Props = {
onClose?: () => void onClose?: () => void
onBlur?: () => void onBlur?: () => void
zIndex?: number zIndex?: number
isInCodeGeneratorInstructionEditor?: boolean
autoFocus?: boolean autoFocus?: boolean
} }
const VarReferenceVars: FC<Props> = ({ const VarReferenceVars: FC<Props> = ({
@ -276,6 +313,7 @@ const VarReferenceVars: FC<Props> = ({
onClose, onClose,
onBlur, onBlur,
zIndex, zIndex,
isInCodeGeneratorInstructionEditor,
autoFocus = true, autoFocus = true,
}) => { }) => {
const { t } = useTranslation() const { t } = useTranslation()
@ -318,7 +356,7 @@ const VarReferenceVars: FC<Props> = ({
{ {
!hideSearch && ( !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 <Input
className='var-search-input' className='var-search-input'
showLeftIcon showLeftIcon
@ -344,11 +382,13 @@ const VarReferenceVars: FC<Props> = ({
{ {
filteredVars.map((item, i) => ( filteredVars.map((item, i) => (
<div key={i}> <div key={i} className={cn(!item.isFlat && 'mt-3', i === 0 && item.isFlat && 'mt-2')}>
<div {!item.isFlat && (
className='system-xs-medium-uppercase truncate px-3 leading-[22px] text-text-tertiary' <div
title={item.title} className='system-xs-medium-uppercase truncate px-3 leading-[22px] text-text-tertiary'
>{item.title}</div> title={item.title}
>{item.title}</div>
)}
{item.vars.map((v, j) => ( {item.vars.map((v, j) => (
<Item <Item
key={j} key={j}
@ -361,13 +401,22 @@ const VarReferenceVars: FC<Props> = ({
isSupportFileVar={isSupportFileVar} isSupportFileVar={isSupportFileVar}
isException={v.isException} isException={v.isException}
isLoopVar={item.isLoop} isLoopVar={item.isLoop}
isFlat={item.isFlat}
isInCodeGeneratorInstructionEditor={isInCodeGeneratorInstructionEditor}
zIndex={zIndex} 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> </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>}
</> </>
) )
} }

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

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

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

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

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

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

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

@ -3,9 +3,10 @@ import {
RiArrowGoBackLine, RiArrowGoBackLine,
RiCloseLine, RiCloseLine,
RiMenuLine, RiMenuLine,
RiSparklingFill,
} from '@remixicon/react' } from '@remixicon/react'
import { useStore } from '../store' import { useStore } from '../store'
import type { BlockEnum } from '../types' import { BlockEnum } from '../types'
import useCurrentVars from '../hooks/use-inspect-vars-crud' import useCurrentVars from '../hooks/use-inspect-vars-crud'
import Empty from './empty' import Empty from './empty'
import ValueContent from './value-content' import ValueContent from './value-content'
@ -20,14 +21,30 @@ import Loading from '@/app/components/base/loading'
import type { currentVarType } from './panel' import type { currentVarType } from './panel'
import { VarInInspectType } from '@/types/workflow' import { VarInInspectType } from '@/types/workflow'
import cn from '@/utils/classnames' 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'
type Props = { type Props = {
nodeId: string
currentNodeVar?: currentVarType currentNodeVar?: currentVarType
handleOpenMenu: () => void handleOpenMenu: () => void
isValueFetching?: boolean isValueFetching?: boolean
} }
const Right = ({ const Right = ({
nodeId,
currentNodeVar, currentNodeVar,
handleOpenMenu, handleOpenMenu,
isValueFetching, isValueFetching,
@ -74,6 +91,67 @@ const Right = ({
return String(value) 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 ( return (
<div className={cn('flex h-full flex-col')}> <div className={cn('flex h-full flex-col')}>
{/* header */} {/* header */}
@ -114,6 +192,16 @@ const Right = ({
<div className='flex shrink-0 items-center gap-1'> <div className='flex shrink-0 items-center gap-1'>
{currentNodeVar && ( {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 && ( {currentNodeVar.var.edited && (
<Badge> <Badge>
<span className='ml-[2.5px] mr-[4.5px] h-[3px] w-[3px] rounded bg-text-accent-secondary'></span> <span className='ml-[2.5px] mr-[4.5px] h-[3px] w-[3px] rounded bg-text-accent-secondary'></span>
@ -154,6 +242,28 @@ const Right = ({
)} )}
{currentNodeVar && !isValueFetching && <ValueContent currentVar={currentNodeVar.var} handleValueChange={handleValueChange} />} {currentNodeVar && !isValueFetching && <ValueContent currentVar={currentNodeVar.var} handleValueChange={handleValueChange} />}
</div> </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> </div>
) )
} }

@ -248,11 +248,18 @@ const translation = {
description: 'The Prompt Generator uses the configured model to optimize prompts for higher quality and better structure. Please write clear and detailed instructions.', description: 'The Prompt Generator uses the configured model to optimize prompts for higher quality and better structure. Please write clear and detailed instructions.',
tryIt: 'Try it', tryIt: 'Try it',
instruction: 'Instructions', instruction: 'Instructions',
instructionPlaceHolder: 'Write clear and specific instructions.', instructionPlaceHolderTitle: 'Describe how you would like to improve this Prompt. For example:',
instructionPlaceHolderLine1: 'Make the output more concise, retaining the core points.',
instructionPlaceHolderLine2: 'The output format is incorrect, please strictly follow the JSON format.',
instructionPlaceHolderLine3: 'The tone is too harsh, please make it more friendly.',
idealOutput: 'Ideal Output',
idealOutputPlaceholder: 'Describe your ideal response format, length, tone, and content requirements...',
optional: 'Optional',
dismiss: 'Dismiss',
generate: 'Generate', generate: 'Generate',
resTitle: 'Generated Prompt', resTitle: 'Generated Prompt',
noDataLine1: 'Describe your use case on the left,', newNoDataLine1: 'Write a instruction in the left column, and click Generate to see response. ',
noDataLine2: 'the orchestration preview will show here.', newNoDataLine2: 'Learn about prompt design',
apply: 'Apply', apply: 'Apply',
loading: 'Orchestrating the application for you...', loading: 'Orchestrating the application for you...',
overwriteTitle: 'Override existing configuration?', overwriteTitle: 'Override existing configuration?',
@ -295,6 +302,10 @@ const translation = {
instruction: 'Use advanced copyediting techniques to improve your writings', instruction: 'Use advanced copyediting techniques to improve your writings',
}, },
}, },
press: 'Press',
to: 'to ',
insertContext: 'insert context',
optimizePromptTooltip: 'Optimize in Prompt Generator',
}, },
resetConfig: { resetConfig: {
title: 'Confirm reset?', title: 'Confirm reset?',

@ -966,6 +966,7 @@ const translation = {
chatNode: 'Conversation', chatNode: 'Conversation',
systemNode: 'System', systemNode: 'System',
}, },
lastOutput: 'Last Output',
}, },
} }

@ -236,6 +236,8 @@ const translation = {
apply: '应用', apply: '应用',
applyChanges: '应用更改', applyChanges: '应用更改',
resTitle: '生成的代码', resTitle: '生成的代码',
newNoDataLine1: '在左侧描述您的用例,点击生成查看响应。',
newNoDataLine2: '了解提示词设计',
overwriteConfirmTitle: '是否覆盖现有代码?', overwriteConfirmTitle: '是否覆盖现有代码?',
overwriteConfirmMessage: '此操作将覆盖现有代码。您确定要继续吗?', overwriteConfirmMessage: '此操作将覆盖现有代码。您确定要继续吗?',
}, },
@ -244,11 +246,18 @@ const translation = {
description: '提示词生成器使用配置的模型来优化提示词,以获得更高的质量和更好的结构。请写出清晰详细的说明。', description: '提示词生成器使用配置的模型来优化提示词,以获得更高的质量和更好的结构。请写出清晰详细的说明。',
tryIt: '试一试', tryIt: '试一试',
instruction: '指令', instruction: '指令',
instructionPlaceHolder: '写下清晰、具体的说明。', instructionPlaceHolderTitle: '描述您希望如何改进此提示词。例如:',
instructionPlaceHolderLine1: '使输出更简洁,保留核心要点。',
instructionPlaceHolderLine2: '输出格式不正确,请严格遵循 JSON 格式。',
instructionPlaceHolderLine3: '语气过于生硬,请使其更友好。',
idealOutput: '理想输出',
idealOutputPlaceholder: '描述您理想的回复格式、长度、语气和内容要求……',
optional: '可选',
dismiss: '取消',
generate: '生成', generate: '生成',
resTitle: '生成的提示词', resTitle: '生成的提示词',
noDataLine1: '在左侧描述您的用例,', newNoDataLine1: '在左侧描述您的用例,点击生成查看结果。',
noDataLine2: '编排预览将在此处显示。', newNoDataLine2: '了解提示词设计',
apply: '应用', apply: '应用',
noData: '在左侧描述您的用例,编排预览将在此处显示。', noData: '在左侧描述您的用例,编排预览将在此处显示。',
loading: '为您编排应用程序中…', loading: '为您编排应用程序中…',
@ -292,6 +301,10 @@ const translation = {
instruction: '用地道的编辑技巧改进我的文章', instruction: '用地道的编辑技巧改进我的文章',
}, },
}, },
press: '输入',
to: '来',
insertContext: '插入上下文',
optimizePromptTooltip: '在提示词生成器中优化',
}, },
resetConfig: { resetConfig: {
title: '确认重置?', title: '确认重置?',

@ -967,6 +967,7 @@ const translation = {
chatNode: '会话变量', chatNode: '会话变量',
systemNode: '系统变量', systemNode: '系统变量',
}, },
lastOutput: '上次输出',
}, },
} }

@ -3,12 +3,21 @@ import type { IOnCompleted, IOnData, IOnError, IOnFile, IOnMessageEnd, IOnMessag
import type { ChatPromptConfig, CompletionPromptConfig } from '@/models/debug' import type { ChatPromptConfig, CompletionPromptConfig } from '@/models/debug'
import type { ModelModeType } from '@/types/app' import type { ModelModeType } from '@/types/app'
import type { ModelParameterRule } from '@/app/components/header/account-setting/model-provider-page/declarations' import type { ModelParameterRule } from '@/app/components/header/account-setting/model-provider-page/declarations'
export type AutomaticRes = { export type BasicAppFirstRes = {
prompt: string prompt: string
variables: string[] variables: string[]
opening_statement: string opening_statement: string
error?: string error?: string
} }
export type GenRes = {
modified: string
message?: string // tip for human
variables?: string[] // only for basic app first time rule
opening_statement?: string // only for basic app first time rule
error?: string
}
export type CodeGenRes = { export type CodeGenRes = {
code: string code: string
language: string[] language: string[]
@ -71,13 +80,14 @@ export const fetchConversationMessages = (appId: string, conversation_id: string
}) })
} }
export const generateRule = (body: Record<string, any>) => { export const generateBasicAppFistTimeRule = (body: Record<string, any>) => {
return post<AutomaticRes>('/rule-generate', { return post<BasicAppFirstRes>('/rule-generate', {
body, body,
}) })
} }
export const generateRuleCode = (body: Record<string, any>) => {
return post<CodeGenRes>('/rule-code-generate', { export const generateRule = (body: Record<string, any>) => {
return post<GenRes>('/instruction-generate', {
body, body,
}) })
} }

@ -1,8 +1,9 @@
import { get } from './base' import { get, post } from './base'
import type { App } from '@/types/app' import type { App } from '@/types/app'
import type { AppListResponse } from '@/models/app' import type { AppListResponse } from '@/models/app'
import { useInvalid } from './use-base' import { useInvalid } from './use-base'
import { useQuery } from '@tanstack/react-query' import { useQuery } from '@tanstack/react-query'
import type { GeneratorType } from '@/app/components/app/configuration/config/automatic/types'
const NAME_SPACE = 'apps' const NAME_SPACE = 'apps'
@ -25,3 +26,16 @@ export const useAppDetail = (appID: string) => {
queryFn: () => get<App>(`/apps/${appID}`), queryFn: () => get<App>(`/apps/${appID}`),
}) })
} }
export const useGenerateRuleTemplate = (type: GeneratorType, disabled?: boolean) => {
return useQuery({
queryKey: [NAME_SPACE, 'generate-rule-template', type],
queryFn: () => post<{ data: string }>('instruction-generate/template', {
body: {
type,
},
}),
enabled: !disabled,
retry: 0,
})
}

Loading…
Cancel
Save