Compare commits

...

36 Commits

Author SHA1 Message Date
Joel 7ead579335 chore: use markdown render notice 8 months ago
Joel ae50d3e22f chore: add markdown render tip 8 months ago
Joel 91b0672001 fix: can overwrite the gened prompt 8 months ago
Joel 0066cfe4ed chore: remove agent can show prompt gen 8 months ago
Joel a44a200245 fix: can update versions 8 months ago
Joel 5ad4465c9c fix: can not gen in second time 8 months ago
Joel c78130990d chore: text 8 months ago
Joel 07288bda8b feat: bottom to show vars 8 months ago
Joel ab45f3ce26 feat: instruction template 8 months ago
Joel 71d08d0580 feat: handle is show current prompt 8 months ago
Joel 534ece0ad0 chore: gen template 8 months ago
Joel f438af1aa1 fix: cannot choose no value vaiable 8 months ago
Joel 4e27d2c35f fix: can set the template value 8 months ago
Joel 525e64467d feat: prompt res 8 months ago
Joel 98c70ff86d chore: no data placeholder 8 months ago
Joel 219c96aee1 feat: code gen result highlight 8 months ago
Joel df95b6eba0 chore: remove debug code 8 months ago
Joel f60792bcc7 feat: can generate code 8 months ago
Joel ba8129cf73 feat: code generate use prompt editor 8 months ago
Joel 635e92d762 chore: copy 8 months ago
Joel e2512b0af9 fix: can not choose error message in code gen 8 months ago
Joel a5653f253a chore: version show 8 months ago
Joel ed737beb4a chore: can set to value 8 months ago
Joel a85908386e feat: version select picker and versions storage 8 months ago
Joel 677716346a chore: change currentprompt 8 months ago
Joel f14678a0d8 feat: call gen api 8 months ago
Joel 945a424fa8 feat: new result 8 months ago
Joel 424a563055 chore: i18n 8 months ago
Joel d82abbef23 chore: 18n 8 months ago
Joel 7907235124 feat: gener instrument left 8 months ago
Joel 5d232ac1bc chore: last run divide 8 months ago
Joel 4c76a5f57b feat: support choose last run 8 months ago
Joel 3160e5e562 feat: add error message 8 months ago
Joel da560e5950 chore: current ui fix 8 months ago
Joel 6075ca5f59 feat: can choose current and show current 8 months ago
Joel 7d80cb6d95 feat: current block 8 months ago

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

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

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

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

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

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

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

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

@ -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 { Dataset } from './plugins/context-block'
import type { RoleName } from './plugins/history-block'
@ -75,3 +76,22 @@ export type MenuTextMatch = {
matchingString: string
replaceableString: string
}
export type CurrentBlockType = {
show?: boolean
generatorType: GeneratorType
onInsert?: () => void
onDelete?: () => void
}
export type ErrorMessageBlockType = {
show?: boolean
onInsert?: () => void
onDelete?: () => void
}
export type LastRunBlockType = {
show?: boolean
onInsert?: () => void
onDelete?: () => void
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

@ -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.',
tryIt: 'Try it',
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',
resTitle: 'Generated Prompt',
noDataLine1: 'Describe your use case on the left,',
noDataLine2: 'the orchestration preview will show here.',
newNoDataLine1: 'Write a instruction in the left column, and click Generate to see response. ',
newNoDataLine2: 'Learn about prompt design',
apply: 'Apply',
loading: 'Orchestrating the application for you...',
overwriteTitle: 'Override existing configuration?',
@ -295,6 +302,10 @@ const translation = {
instruction: 'Use advanced copyediting techniques to improve your writings',
},
},
press: 'Press',
to: 'to ',
insertContext: 'insert context',
optimizePromptTooltip: 'Optimize in Prompt Generator',
},
resetConfig: {
title: 'Confirm reset?',

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

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

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

@ -3,12 +3,21 @@ import type { IOnCompleted, IOnData, IOnError, IOnFile, IOnMessageEnd, IOnMessag
import type { ChatPromptConfig, CompletionPromptConfig } from '@/models/debug'
import type { ModelModeType } from '@/types/app'
import type { ModelParameterRule } from '@/app/components/header/account-setting/model-provider-page/declarations'
export type AutomaticRes = {
export type BasicAppFirstRes = {
prompt: string
variables: string[]
opening_statement: 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 = {
code: string
language: string[]
@ -71,13 +80,14 @@ export const fetchConversationMessages = (appId: string, conversation_id: string
})
}
export const generateRule = (body: Record<string, any>) => {
return post<AutomaticRes>('/rule-generate', {
export const generateBasicAppFistTimeRule = (body: Record<string, any>) => {
return post<BasicAppFirstRes>('/rule-generate', {
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,
})
}

@ -1,8 +1,9 @@
import { get } from './base'
import { get, post } from './base'
import type { App } from '@/types/app'
import type { AppListResponse } from '@/models/app'
import { useInvalid } from './use-base'
import { useQuery } from '@tanstack/react-query'
import type { GeneratorType } from '@/app/components/app/configuration/config/automatic/types'
const NAME_SPACE = 'apps'
@ -25,3 +26,16 @@ export const useAppDetail = (appID: string) => {
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