feat: enchance prompt and code (#23633)
Co-authored-by: stream <stream@dify.ai> Co-authored-by: Stream <1542763342@qq.com> Co-authored-by: Stream <Stream_2@qq.com> Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import type { FC } from 'react'
|
||||
import React, { useCallback, useEffect, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useBoolean } from 'ahooks'
|
||||
import { useBoolean, useSessionStorageState } from 'ahooks'
|
||||
import {
|
||||
RiDatabase2Line,
|
||||
RiFileExcel2Line,
|
||||
@@ -14,24 +14,18 @@ import {
|
||||
RiTranslate,
|
||||
RiUser2Line,
|
||||
} from '@remixicon/react'
|
||||
import cn from 'classnames'
|
||||
import s from './style.module.css'
|
||||
import Modal from '@/app/components/base/modal'
|
||||
import Button from '@/app/components/base/button'
|
||||
import Textarea from '@/app/components/base/textarea'
|
||||
import Toast from '@/app/components/base/toast'
|
||||
import { generateRule } from '@/service/debug'
|
||||
import ConfigPrompt from '@/app/components/app/configuration/config-prompt'
|
||||
import { generateBasicAppFistTimeRule, generateRule } from '@/service/debug'
|
||||
import type { CompletionParams, Model } from '@/types/app'
|
||||
import { AppType } from '@/types/app'
|
||||
import ConfigVar from '@/app/components/app/configuration/config-var'
|
||||
import GroupName from '@/app/components/app/configuration/base/group-name'
|
||||
import type { AppType } from '@/types/app'
|
||||
import Loading from '@/app/components/base/loading'
|
||||
import Confirm from '@/app/components/base/confirm'
|
||||
import { LoveMessage } from '@/app/components/base/icons/src/vender/features'
|
||||
|
||||
// type
|
||||
import type { AutomaticRes } from '@/service/debug'
|
||||
import type { GenRes } from '@/service/debug'
|
||||
import { Generator } from '@/app/components/base/icons/src/vender/other'
|
||||
import ModelParameterModal from '@/app/components/header/account-setting/model-provider-page/model-parameter-modal'
|
||||
|
||||
@@ -39,13 +33,25 @@ import { ModelTypeEnum } from '@/app/components/header/account-setting/model-pro
|
||||
import { useModelListAndDefaultModelAndCurrentProviderAndModel } from '@/app/components/header/account-setting/model-provider-page/hooks'
|
||||
import type { ModelModeType } from '@/types/app'
|
||||
import type { FormValue } from '@/app/components/header/account-setting/model-provider-page/declarations'
|
||||
import InstructionEditorInWorkflow from './instruction-editor-in-workflow'
|
||||
import InstructionEditorInBasic from './instruction-editor'
|
||||
import { GeneratorType } from './types'
|
||||
import Result from './result'
|
||||
import useGenData from './use-gen-data'
|
||||
import IdeaOutput from './idea-output'
|
||||
import ResPlaceholder from './res-placeholder'
|
||||
import { useGenerateRuleTemplate } from '@/service/use-apps'
|
||||
|
||||
const i18nPrefix = 'appDebug.generate'
|
||||
export type IGetAutomaticResProps = {
|
||||
mode: AppType
|
||||
isShow: boolean
|
||||
onClose: () => void
|
||||
onFinished: (res: AutomaticRes) => void
|
||||
isInLLMNode?: boolean
|
||||
onFinished: (res: GenRes) => void
|
||||
flowId?: string
|
||||
nodeId?: string
|
||||
currentPrompt?: string
|
||||
isBasicMode?: boolean
|
||||
}
|
||||
|
||||
const TryLabel: FC<{
|
||||
@@ -68,7 +74,10 @@ const GetAutomaticRes: FC<IGetAutomaticResProps> = ({
|
||||
mode,
|
||||
isShow,
|
||||
onClose,
|
||||
isInLLMNode,
|
||||
flowId,
|
||||
nodeId,
|
||||
currentPrompt,
|
||||
isBasicMode,
|
||||
onFinished,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
@@ -123,13 +132,27 @@ const GetAutomaticRes: FC<IGetAutomaticResProps> = ({
|
||||
},
|
||||
]
|
||||
|
||||
const [instruction, setInstruction] = useState<string>('')
|
||||
const [instructionFromSessionStorage, setInstruction] = useSessionStorageState<string>(`improve-instruction-${flowId}${isBasicMode ? '' : `-${nodeId}`}`)
|
||||
const instruction = instructionFromSessionStorage || ''
|
||||
const [ideaOutput, setIdeaOutput] = useState<string>('')
|
||||
|
||||
const [editorKey, setEditorKey] = useState(`${flowId}-0`)
|
||||
const handleChooseTemplate = useCallback((key: string) => {
|
||||
return () => {
|
||||
const template = t(`appDebug.generate.template.${key}.instruction`)
|
||||
setInstruction(template)
|
||||
setEditorKey(`${flowId}-${Date.now()}`)
|
||||
}
|
||||
}, [t])
|
||||
|
||||
const { data: instructionTemplate } = useGenerateRuleTemplate(GeneratorType.prompt, isBasicMode)
|
||||
useEffect(() => {
|
||||
if (!instruction && instructionTemplate)
|
||||
setInstruction(instructionTemplate.data)
|
||||
|
||||
setEditorKey(`${flowId}-${Date.now()}`)
|
||||
}, [instructionTemplate])
|
||||
|
||||
const isValid = () => {
|
||||
if (instruction.trim() === '') {
|
||||
Toast.notify({
|
||||
@@ -143,7 +166,10 @@ const GetAutomaticRes: FC<IGetAutomaticResProps> = ({
|
||||
return true
|
||||
}
|
||||
const [isLoading, { setTrue: setLoadingTrue, setFalse: setLoadingFalse }] = useBoolean(false)
|
||||
const [res, setRes] = useState<AutomaticRes | null>(null)
|
||||
const storageKey = `${flowId}${isBasicMode ? '' : `-${nodeId}`}`
|
||||
const { addVersion, current, currentVersionIndex, setCurrentVersionIndex, versions } = useGenData({
|
||||
storageKey,
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
if (defaultModel) {
|
||||
@@ -170,16 +196,6 @@ const GetAutomaticRes: FC<IGetAutomaticResProps> = ({
|
||||
</div>
|
||||
)
|
||||
|
||||
const renderNoData = (
|
||||
<div className='flex h-full w-0 grow flex-col items-center justify-center space-y-3 px-8'>
|
||||
<Generator className='h-14 w-14 text-text-tertiary' />
|
||||
<div className='text-center text-[13px] font-normal leading-5 text-text-tertiary'>
|
||||
<div>{t('appDebug.generate.noDataLine1')}</div>
|
||||
<div>{t('appDebug.generate.noDataLine2')}</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
const handleModelChange = useCallback((newValue: { modelId: string; provider: string; mode?: string; features?: string[] }) => {
|
||||
const newModel = {
|
||||
...model,
|
||||
@@ -207,28 +223,59 @@ const GetAutomaticRes: FC<IGetAutomaticResProps> = ({
|
||||
return
|
||||
setLoadingTrue()
|
||||
try {
|
||||
const { error, ...res } = await generateRule({
|
||||
instruction,
|
||||
model_config: model,
|
||||
no_variable: !!isInLLMNode,
|
||||
})
|
||||
setRes(res)
|
||||
if (error) {
|
||||
Toast.notify({
|
||||
type: 'error',
|
||||
message: error,
|
||||
let apiRes: GenRes
|
||||
let hasError = false
|
||||
if (isBasicMode || !currentPrompt) {
|
||||
const { error, ...res } = await generateBasicAppFistTimeRule({
|
||||
instruction,
|
||||
model_config: model,
|
||||
no_variable: false,
|
||||
})
|
||||
apiRes = {
|
||||
...res,
|
||||
modified: res.prompt,
|
||||
} as GenRes
|
||||
if (error) {
|
||||
hasError = true
|
||||
Toast.notify({
|
||||
type: 'error',
|
||||
message: error,
|
||||
})
|
||||
}
|
||||
}
|
||||
else {
|
||||
const { error, ...res } = await generateRule({
|
||||
flow_id: flowId,
|
||||
node_id: nodeId,
|
||||
current: currentPrompt,
|
||||
instruction,
|
||||
ideal_output: ideaOutput,
|
||||
model_config: model,
|
||||
})
|
||||
apiRes = res
|
||||
if (error) {
|
||||
hasError = true
|
||||
Toast.notify({
|
||||
type: 'error',
|
||||
message: error,
|
||||
})
|
||||
}
|
||||
}
|
||||
if (!hasError)
|
||||
addVersion(apiRes)
|
||||
}
|
||||
finally {
|
||||
setLoadingFalse()
|
||||
}
|
||||
}
|
||||
|
||||
const [showConfirmOverwrite, setShowConfirmOverwrite] = React.useState(false)
|
||||
const [isShowConfirmOverwrite, {
|
||||
setTrue: showConfirmOverwrite,
|
||||
setFalse: hideShowConfirmOverwrite,
|
||||
}] = useBoolean(false)
|
||||
|
||||
const isShowAutoPromptResPlaceholder = () => {
|
||||
return !isLoading && !res
|
||||
return !isLoading && !current
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -236,15 +283,14 @@ const GetAutomaticRes: FC<IGetAutomaticResProps> = ({
|
||||
isShow={isShow}
|
||||
onClose={onClose}
|
||||
className='min-w-[1140px] !p-0'
|
||||
closable
|
||||
>
|
||||
<div className='flex h-[680px] flex-wrap'>
|
||||
<div className='h-full w-[570px] shrink-0 overflow-y-auto border-r border-divider-regular p-6'>
|
||||
<div className='mb-8'>
|
||||
<div className='mb-5'>
|
||||
<div className={`text-lg font-bold leading-[28px] ${s.textGradient}`}>{t('appDebug.generate.title')}</div>
|
||||
<div className='mt-1 text-[13px] font-normal text-text-tertiary'>{t('appDebug.generate.description')}</div>
|
||||
</div>
|
||||
<div className='mb-8'>
|
||||
<div>
|
||||
<ModelParameterModal
|
||||
popupClassName='!w-[520px]'
|
||||
portalToFollowElemContentClassName='z-[1000]'
|
||||
@@ -258,116 +304,99 @@ const GetAutomaticRes: FC<IGetAutomaticResProps> = ({
|
||||
hideDebugWithMultipleModel
|
||||
/>
|
||||
</div>
|
||||
<div >
|
||||
<div className='flex items-center'>
|
||||
<div className='mr-3 shrink-0 text-xs font-semibold uppercase leading-[18px] text-text-tertiary'>{t('appDebug.generate.tryIt')}</div>
|
||||
<div className='h-px grow' style={{
|
||||
background: 'linear-gradient(to right, rgba(243, 244, 246, 1), rgba(243, 244, 246, 0))',
|
||||
}}></div>
|
||||
</div>
|
||||
<div className='flex flex-wrap'>
|
||||
{tryList.map(item => (
|
||||
<TryLabel
|
||||
key={item.key}
|
||||
Icon={item.icon}
|
||||
text={t(`appDebug.generate.template.${item.key}.name`)}
|
||||
onClick={handleChooseTemplate(item.key)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
{/* inputs */}
|
||||
<div className='mt-6'>
|
||||
<div className='text-[0px]'>
|
||||
<div className='mb-2 text-sm font-medium leading-5 text-text-primary'>{t('appDebug.generate.instruction')}</div>
|
||||
<Textarea
|
||||
className="h-[200px] resize-none"
|
||||
placeholder={t('appDebug.generate.instructionPlaceHolder') as string}
|
||||
value={instruction}
|
||||
onChange={e => setInstruction(e.target.value)} />
|
||||
{isBasicMode && (
|
||||
<div className='mt-4'>
|
||||
<div className='flex items-center'>
|
||||
<div className='mr-3 shrink-0 text-xs font-semibold uppercase leading-[18px] text-text-tertiary'>{t('appDebug.generate.tryIt')}</div>
|
||||
<div className='h-px grow' style={{
|
||||
background: 'linear-gradient(to right, rgba(243, 244, 246, 1), rgba(243, 244, 246, 0))',
|
||||
}}></div>
|
||||
</div>
|
||||
<div className='flex flex-wrap'>
|
||||
{tryList.map(item => (
|
||||
<TryLabel
|
||||
key={item.key}
|
||||
Icon={item.icon}
|
||||
text={t(`appDebug.generate.template.${item.key}.name`)}
|
||||
onClick={handleChooseTemplate(item.key)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className='mt-5 flex justify-end'>
|
||||
{/* inputs */}
|
||||
<div className='mt-4'>
|
||||
<div>
|
||||
<div className='system-sm-semibold-uppercase mb-1.5 text-text-secondary'>{t('appDebug.generate.instruction')}</div>
|
||||
{isBasicMode ? (
|
||||
<InstructionEditorInBasic
|
||||
editorKey={editorKey}
|
||||
generatorType={GeneratorType.prompt}
|
||||
value={instruction}
|
||||
onChange={setInstruction}
|
||||
availableVars={[]}
|
||||
availableNodes={[]}
|
||||
isShowCurrentBlock={!!currentPrompt}
|
||||
isShowLastRunBlock={false}
|
||||
/>
|
||||
) : (
|
||||
<InstructionEditorInWorkflow
|
||||
editorKey={editorKey}
|
||||
generatorType={GeneratorType.prompt}
|
||||
value={instruction}
|
||||
onChange={setInstruction}
|
||||
nodeId={nodeId || ''}
|
||||
isShowCurrentBlock={!!currentPrompt}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<IdeaOutput
|
||||
value={ideaOutput}
|
||||
onChange={setIdeaOutput}
|
||||
/>
|
||||
|
||||
<div className='mt-7 flex justify-end space-x-2'>
|
||||
<Button onClick={onClose}>{t(`${i18nPrefix}.dismiss`)}</Button>
|
||||
<Button
|
||||
className='flex space-x-1'
|
||||
variant='primary'
|
||||
onClick={onGenerate}
|
||||
disabled={isLoading}
|
||||
>
|
||||
<Generator className='h-4 w-4 text-white' />
|
||||
<span className='text-xs font-semibold text-white'>{t('appDebug.generate.generate')}</span>
|
||||
<Generator className='h-4 w-4' />
|
||||
<span className='text-xs font-semibold'>{t('appDebug.generate.generate')}</span>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{(!isLoading && res) && (
|
||||
<div className='h-full w-0 grow p-6 pb-0'>
|
||||
<div className='mb-3 shrink-0 text-base font-semibold leading-[160%] text-text-secondary'>{t('appDebug.generate.resTitle')}</div>
|
||||
<div className={cn('max-h-[555px] overflow-y-auto', !isInLLMNode && 'pb-2')}>
|
||||
<ConfigPrompt
|
||||
mode={mode}
|
||||
promptTemplate={res?.prompt || ''}
|
||||
promptVariables={[]}
|
||||
readonly
|
||||
noTitle={isInLLMNode}
|
||||
gradientBorder
|
||||
editorHeight={isInLLMNode ? 524 : 0}
|
||||
noResize={isInLLMNode}
|
||||
/>
|
||||
{!isInLLMNode && (
|
||||
<>
|
||||
{(res?.variables?.length && res?.variables?.length > 0)
|
||||
? (
|
||||
<ConfigVar
|
||||
promptVariables={res?.variables.map(key => ({ key, name: key, type: 'string', required: true })) || []}
|
||||
readonly
|
||||
/>
|
||||
)
|
||||
: ''}
|
||||
|
||||
{(mode !== AppType.completion && res?.opening_statement) && (
|
||||
<div className='mt-7'>
|
||||
<GroupName name={t('appDebug.feature.groupChat.title')} />
|
||||
<div
|
||||
className='mb-1 rounded-xl border-l-[0.5px] border-t-[0.5px] border-effects-highlight bg-background-section-burn p-3'
|
||||
>
|
||||
<div className='mb-2 flex items-center gap-2'>
|
||||
<div className='shrink-0 rounded-lg border-[0.5px] border-divider-subtle bg-util-colors-blue-light-blue-light-500 p-1 shadow-xs'>
|
||||
<LoveMessage className='h-4 w-4 text-text-primary-on-surface' />
|
||||
</div>
|
||||
<div className='system-sm-semibold flex grow items-center text-text-secondary'>
|
||||
{t('appDebug.feature.conversationOpener.title')}
|
||||
</div>
|
||||
</div>
|
||||
<div className='system-xs-regular min-h-8 text-text-tertiary'>{res.opening_statement}</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className='flex justify-end bg-background-default py-4'>
|
||||
<Button onClick={onClose}>{t('common.operation.cancel')}</Button>
|
||||
<Button variant='primary' className='ml-2' onClick={() => {
|
||||
setShowConfirmOverwrite(true)
|
||||
}}>{t('appDebug.generate.apply')}</Button>
|
||||
</div>
|
||||
{(!isLoading && current) && (
|
||||
<div className='h-full w-0 grow bg-background-default-subtle p-6 pb-0'>
|
||||
<Result
|
||||
current={current!}
|
||||
isBasicMode={isBasicMode}
|
||||
nodeId={nodeId!}
|
||||
currentVersionIndex={currentVersionIndex || 0}
|
||||
setCurrentVersionIndex={setCurrentVersionIndex}
|
||||
versions={versions || []}
|
||||
onApply={showConfirmOverwrite}
|
||||
generatorType={GeneratorType.prompt}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{isLoading && renderLoading}
|
||||
{isShowAutoPromptResPlaceholder() && renderNoData}
|
||||
{showConfirmOverwrite && (
|
||||
{isShowAutoPromptResPlaceholder() && <ResPlaceholder />}
|
||||
{isShowConfirmOverwrite && (
|
||||
<Confirm
|
||||
title={t('appDebug.generate.overwriteTitle')}
|
||||
content={t('appDebug.generate.overwriteMessage')}
|
||||
isShow={showConfirmOverwrite}
|
||||
isShow
|
||||
onConfirm={() => {
|
||||
setShowConfirmOverwrite(false)
|
||||
onFinished(res!)
|
||||
hideShowConfirmOverwrite()
|
||||
onFinished(current!)
|
||||
}}
|
||||
onCancel={() => setShowConfirmOverwrite(false)}
|
||||
onCancel={hideShowConfirmOverwrite}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -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 isCode = generatorType === 'code'
|
||||
const placeholder = isCode ? <div className='system-sm-regular whitespace-break-spaces !leading-6 text-text-placeholder'>
|
||||
{t(`${i18nPrefix}.codeGenInstructionPlaceHolderLine`)}
|
||||
</div> : (
|
||||
<div className='system-sm-regular text-text-placeholder'>
|
||||
<div className='leading-6'>{t(`${i18nPrefix}.instructionPlaceHolderTitle`)}</div>
|
||||
<div className='mt-2'>
|
||||
<div>{t(`${i18nPrefix}.instructionPlaceHolderLine1`)}</div>
|
||||
<div>{t(`${i18nPrefix}.instructionPlaceHolderLine2`)}</div>
|
||||
<div>{t(`${i18nPrefix}.instructionPlaceHolderLine3`)}</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
const handleInsertVariable = () => {
|
||||
eventEmitter?.emit({ type: PROMPT_EDITOR_INSERT_QUICKLY, instanceId: editorKey } as any)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='relative'>
|
||||
<PromptEditor
|
||||
wrapperClassName='border !border-components-input-bg-normal bg-components-input-bg-normal hover:!border-components-input-bg-hover rounded-[10px] px-4 pt-3'
|
||||
key={editorKey}
|
||||
instanceId={editorKey}
|
||||
placeholder={placeholder}
|
||||
placeholderClassName='px-4 pt-3'
|
||||
className={cn('min-h-[240px] pb-8')}
|
||||
value={value}
|
||||
workflowVariableBlock={{
|
||||
show: true,
|
||||
variables: availableVars,
|
||||
getVarType,
|
||||
workflowNodesMap: availableNodes.reduce((acc, node) => {
|
||||
acc[node.id] = {
|
||||
title: node.data.title,
|
||||
type: node.data.type,
|
||||
width: node.width,
|
||||
height: node.height,
|
||||
position: node.position,
|
||||
}
|
||||
if (node.data.type === BlockEnum.Start) {
|
||||
acc.sys = {
|
||||
title: t('workflow.blocks.start'),
|
||||
type: BlockEnum.Start,
|
||||
}
|
||||
}
|
||||
return acc
|
||||
}, {} as any),
|
||||
}}
|
||||
currentBlock={{
|
||||
show: isShowCurrentBlock,
|
||||
generatorType,
|
||||
}}
|
||||
errorMessageBlock={{
|
||||
show: isCode,
|
||||
}}
|
||||
lastRunBlock={{
|
||||
show: isShowLastRunBlock,
|
||||
}}
|
||||
onChange={onChange}
|
||||
editable
|
||||
isSupportFileVar={false}
|
||||
/>
|
||||
<div className='system-xs-regular absolute bottom-0 left-4 flex h-8 items-center space-x-0.5 text-components-input-text-placeholder'>
|
||||
<span>{t('appDebug.generate.press')}</span>
|
||||
<span className='system-kbd flex h-4 w-3.5 items-center justify-center rounded-[4px] bg-components-kbd-bg-gray text-text-placeholder'>/</span>
|
||||
<span>{t('appDebug.generate.to')}</span>
|
||||
<span onClick={handleInsertVariable} className='!ml-1 cursor-pointer hover:border-b hover:border-dotted hover:border-text-tertiary hover:text-text-tertiary'>{t('appDebug.generate.insertContext')}</span>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
export default React.memo(InstructionEditor)
|
||||
@@ -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,54 @@
|
||||
import { RiArrowDownSLine, RiSparklingFill } from '@remixicon/react'
|
||||
import { useBoolean } from 'ahooks'
|
||||
import React from 'react'
|
||||
import cn from '@/utils/classnames'
|
||||
import { Markdown } from '@/app/components/base/markdown'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import s from './style.module.css'
|
||||
|
||||
type Props = {
|
||||
message: string
|
||||
className?: string
|
||||
}
|
||||
const PromptToast = ({
|
||||
message,
|
||||
className,
|
||||
}: Props) => {
|
||||
const { t } = useTranslation()
|
||||
const [isFold, {
|
||||
toggle: toggleFold,
|
||||
}] = useBoolean(false)
|
||||
// const message = `
|
||||
// list1list1list1list1list1list1list1list1list1list1list1list1list1list1list1list1list1list1list1list1list1list1list1list1list1list1list1list1list1list1
|
||||
// # h1
|
||||
// **strong text** ~~strikethrough~~
|
||||
|
||||
// * list1list1list1list1list1list1list1list1list1list1list1list1list1list1list1
|
||||
// * list2
|
||||
|
||||
// xxxx
|
||||
|
||||
// ## h2
|
||||
// \`\`\`python
|
||||
// print('Hello, World!')
|
||||
// \`\`\`
|
||||
// `
|
||||
return (
|
||||
<div className={cn('rounded-xl border-[0.5px] border-components-panel-border bg-background-section-burn pl-4 shadow-xs', className)}>
|
||||
<div className='my-3 flex h-4 items-center justify-between pr-3'>
|
||||
<div className='flex items-center space-x-1'>
|
||||
<RiSparklingFill className='size-3.5 text-components-input-border-active-prompt-1' />
|
||||
<span className={cn(s.optimizationNoteText, 'system-xs-semibold-uppercase')}>{t('appDebug.generate.optimizationNote')}</span>
|
||||
</div>
|
||||
<RiArrowDownSLine className={cn('size-4 cursor-pointer text-text-tertiary', isFold && 'rotate-[-90deg]')} onClick={toggleFold} />
|
||||
</div>
|
||||
{!isFold && (
|
||||
<div className='pb-4 pr-4'>
|
||||
<Markdown className="!text-sm" content={message} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default PromptToast
|
||||
@@ -0,0 +1,18 @@
|
||||
'use client'
|
||||
import { Generator } from '@/app/components/base/icons/src/vender/other'
|
||||
import type { FC } from 'react'
|
||||
import React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
const ResPlaceholder: FC = () => {
|
||||
const { t } = useTranslation()
|
||||
return (
|
||||
<div className='flex h-full w-0 grow flex-col items-center justify-center space-y-3 px-8'>
|
||||
<Generator className='size-8 text-text-quaternary' />
|
||||
<div className='text-center text-[13px] font-normal leading-5 text-text-tertiary'>
|
||||
<div>{t('appDebug.generate.newNoDataLine1')}</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
export default React.memo(ResPlaceholder)
|
||||
@@ -0,0 +1,97 @@
|
||||
'use client'
|
||||
import type { FC } from 'react'
|
||||
import React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { GeneratorType } from './types'
|
||||
import PromptToast from './prompt-toast'
|
||||
import Button from '@/app/components/base/button'
|
||||
import VersionSelector from './version-selector'
|
||||
import type { GenRes } from '@/service/debug'
|
||||
import { RiClipboardLine } from '@remixicon/react'
|
||||
import copy from 'copy-to-clipboard'
|
||||
import Toast from '@/app/components/base/toast'
|
||||
import CodeEditor from '@/app/components/workflow/nodes/llm/components/json-schema-config-modal/code-editor'
|
||||
import PromptRes from './prompt-res'
|
||||
import PromptResInWorkflow from './prompt-res-in-workflow'
|
||||
|
||||
type Props = {
|
||||
isBasicMode?: boolean
|
||||
nodeId?: string
|
||||
current: GenRes
|
||||
currentVersionIndex: number
|
||||
setCurrentVersionIndex: (index: number) => void
|
||||
versions: GenRes[]
|
||||
onApply: () => void
|
||||
generatorType: GeneratorType
|
||||
}
|
||||
|
||||
const Result: FC<Props> = ({
|
||||
isBasicMode,
|
||||
nodeId,
|
||||
current,
|
||||
currentVersionIndex,
|
||||
setCurrentVersionIndex,
|
||||
versions,
|
||||
onApply,
|
||||
generatorType,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
const isGeneratorPrompt = generatorType === GeneratorType.prompt
|
||||
|
||||
return (
|
||||
<div className='flex h-full flex-col'>
|
||||
<div className='mb-3 flex shrink-0 items-center justify-between'>
|
||||
<div>
|
||||
<div className='shrink-0 text-base font-semibold leading-[160%] text-text-secondary'>{t('appDebug.generate.resTitle')}</div>
|
||||
<VersionSelector
|
||||
versionLen={versions.length}
|
||||
value={currentVersionIndex}
|
||||
onChange={setCurrentVersionIndex}
|
||||
/>
|
||||
</div>
|
||||
<div className='flex items-center space-x-2'>
|
||||
<Button className='px-2' onClick={() => {
|
||||
copy(current.modified)
|
||||
Toast.notify({ type: 'success', message: t('common.actionMsg.copySuccessfully') })
|
||||
}}>
|
||||
<RiClipboardLine className='h-4 w-4 text-text-secondary' />
|
||||
</Button>
|
||||
<Button variant='primary' onClick={onApply}>
|
||||
{t('appDebug.generate.apply')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div className='flex grow flex-col overflow-y-auto'>
|
||||
{
|
||||
current?.message && (
|
||||
<PromptToast message={current.message} className='mb-3 shrink-0' />
|
||||
)
|
||||
}
|
||||
<div className='grow pb-6'>
|
||||
{isGeneratorPrompt ? (
|
||||
isBasicMode ? (
|
||||
<PromptRes
|
||||
value={current?.modified}
|
||||
workflowVariableBlock={{
|
||||
show: false,
|
||||
}}
|
||||
/>
|
||||
) : (<PromptResInWorkflow
|
||||
value={current?.modified || ''}
|
||||
nodeId={nodeId!}
|
||||
/>)
|
||||
) : (
|
||||
<CodeEditor
|
||||
editorWrapperClassName='h-full'
|
||||
className='bg-transparent pt-0'
|
||||
value={current?.modified}
|
||||
readOnly
|
||||
hideTopMenu
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
export default React.memo(Result)
|
||||
@@ -3,5 +3,11 @@
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
text-fill-color: transparent;
|
||||
}
|
||||
|
||||
.optimizationNoteText {
|
||||
background: linear-gradient(263deg, rgba(21, 90, 239, 0.95) -20.92%, rgba(11, 165, 236, 0.95) 87.04%);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
}
|
||||
|
||||
@@ -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,103 @@
|
||||
import React, { useCallback } from 'react'
|
||||
import { PortalToFollowElem, PortalToFollowElemContent, PortalToFollowElemTrigger } from '@/app/components/base/portal-to-follow-elem'
|
||||
import { useBoolean } from 'ahooks'
|
||||
import cn from '@/utils/classnames'
|
||||
import { RiArrowDownSLine, RiCheckLine } from '@remixicon/react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
type Option = {
|
||||
label: string
|
||||
value: number
|
||||
}
|
||||
|
||||
type VersionSelectorProps = {
|
||||
versionLen: number;
|
||||
value: number;
|
||||
onChange: (index: number) => void;
|
||||
}
|
||||
|
||||
const VersionSelector: React.FC<VersionSelectorProps> = ({ versionLen, value, onChange }) => {
|
||||
const { t } = useTranslation()
|
||||
const [isOpen, {
|
||||
setFalse: handleOpenFalse,
|
||||
toggle: handleOpenToggle,
|
||||
set: handleOpenSet,
|
||||
}] = useBoolean(false)
|
||||
|
||||
const moreThanOneVersion = versionLen > 1
|
||||
const handleOpen = useCallback((value: boolean) => {
|
||||
if (moreThanOneVersion)
|
||||
handleOpenSet(value)
|
||||
}, [moreThanOneVersion, handleOpenToggle])
|
||||
const handleToggle = useCallback(() => {
|
||||
if (moreThanOneVersion)
|
||||
handleOpenToggle()
|
||||
}, [moreThanOneVersion, handleOpenToggle])
|
||||
|
||||
const versions = Array.from({ length: versionLen }, (_, index) => ({
|
||||
label: `${t('appDebug.generate.version')} ${index + 1}${index === versionLen - 1 ? ` · ${t('appDebug.generate.latest')}` : ''}`,
|
||||
value: index,
|
||||
}))
|
||||
|
||||
const isLatest = value === versionLen - 1
|
||||
|
||||
return (
|
||||
<PortalToFollowElem
|
||||
placement={'bottom-start'}
|
||||
offset={{
|
||||
mainAxis: 4,
|
||||
crossAxis: -12,
|
||||
}}
|
||||
open={isOpen}
|
||||
onOpenChange={handleOpen}
|
||||
>
|
||||
<PortalToFollowElemTrigger
|
||||
onClick={handleToggle}
|
||||
asChild
|
||||
>
|
||||
|
||||
<div className={cn('system-xs-medium flex items-center text-text-tertiary', isOpen && 'text-text-secondary', moreThanOneVersion && 'cursor-pointer')}>
|
||||
<div>{t('appDebug.generate.version')} {value + 1}{isLatest && ` · ${t('appDebug.generate.latest')}`}</div>
|
||||
{moreThanOneVersion && <RiArrowDownSLine className='size-3 ' />}
|
||||
</div>
|
||||
</PortalToFollowElemTrigger >
|
||||
<PortalToFollowElemContent className={cn(
|
||||
'z-[99]',
|
||||
)}>
|
||||
<div
|
||||
className={cn(
|
||||
'w-[208px] rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg-blur p-1 shadow-lg',
|
||||
)}
|
||||
>
|
||||
<div className={cn('system-xs-medium-uppercase flex h-[22px] items-center px-3 pl-3 text-text-tertiary')}>
|
||||
{t('appDebug.generate.versions')}
|
||||
</div>
|
||||
{
|
||||
versions.map(option => (
|
||||
<div
|
||||
key={option.value}
|
||||
className={cn(
|
||||
'system-sm-medium flex h-7 cursor-pointer items-center rounded-lg px-2 text-text-secondary hover:bg-state-base-hover',
|
||||
)}
|
||||
title={option.label}
|
||||
onClick={() => {
|
||||
onChange(option.value)
|
||||
handleOpenFalse()
|
||||
}}
|
||||
>
|
||||
<div className='mr-1 grow truncate px-1 pl-1'>
|
||||
{option.label}
|
||||
</div>
|
||||
{
|
||||
value === option.value && <RiCheckLine className='h-4 w-4 shrink-0 text-text-accent' />
|
||||
}
|
||||
</div>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
</PortalToFollowElemContent>
|
||||
</PortalToFollowElem >
|
||||
)
|
||||
}
|
||||
|
||||
export default VersionSelector
|
||||
@@ -1,16 +1,13 @@
|
||||
import type { FC } from 'react'
|
||||
import React, { useCallback, useEffect } from 'react'
|
||||
import cn from 'classnames'
|
||||
import React, { useCallback, useEffect, useState } from 'react'
|
||||
import useBoolean from 'ahooks/lib/useBoolean'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import ConfigPrompt from '../../config-prompt'
|
||||
import { languageMap } from '../../../../workflow/nodes/_base/components/editor/code-editor/index'
|
||||
import { generateRuleCode } from '@/service/debug'
|
||||
import type { CodeGenRes } from '@/service/debug'
|
||||
import { generateRule } from '@/service/debug'
|
||||
import type { GenRes } from '@/service/debug'
|
||||
import type { ModelModeType } from '@/types/app'
|
||||
import type { AppType, CompletionParams, Model } from '@/types/app'
|
||||
import Modal from '@/app/components/base/modal'
|
||||
import Textarea from '@/app/components/base/textarea'
|
||||
import Button from '@/app/components/base/button'
|
||||
import { Generator } from '@/app/components/base/icons/src/vender/other'
|
||||
import Toast from '@/app/components/base/toast'
|
||||
@@ -21,17 +18,33 @@ import { useModelListAndDefaultModelAndCurrentProviderAndModel } from '@/app/com
|
||||
import { ModelTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations'
|
||||
import ModelParameterModal from '@/app/components/header/account-setting/model-provider-page/model-parameter-modal'
|
||||
import type { FormValue } from '@/app/components/header/account-setting/model-provider-page/declarations'
|
||||
import IdeaOutput from '../automatic/idea-output'
|
||||
import { GeneratorType } from '../automatic/types'
|
||||
import InstructionEditor from '../automatic/instruction-editor-in-workflow'
|
||||
import useGenData from '../automatic/use-gen-data'
|
||||
import Result from '../automatic/result'
|
||||
import ResPlaceholder from '../automatic/res-placeholder'
|
||||
import { useGenerateRuleTemplate } from '@/service/use-apps'
|
||||
import { useSessionStorageState } from 'ahooks'
|
||||
import s from '../automatic/style.module.css'
|
||||
|
||||
const i18nPrefix = 'appDebug.generate'
|
||||
export type IGetCodeGeneratorResProps = {
|
||||
flowId: string
|
||||
nodeId: string
|
||||
currentCode?: string
|
||||
mode: AppType
|
||||
isShow: boolean
|
||||
codeLanguages: CodeLanguage
|
||||
onClose: () => void
|
||||
onFinished: (res: CodeGenRes) => void
|
||||
onFinished: (res: GenRes) => void
|
||||
}
|
||||
|
||||
export const GetCodeGeneratorResModal: FC<IGetCodeGeneratorResProps> = (
|
||||
{
|
||||
flowId,
|
||||
nodeId,
|
||||
currentCode,
|
||||
mode,
|
||||
isShow,
|
||||
codeLanguages,
|
||||
@@ -61,9 +74,25 @@ export const GetCodeGeneratorResModal: FC<IGetCodeGeneratorResProps> = (
|
||||
const {
|
||||
defaultModel,
|
||||
} = useModelListAndDefaultModelAndCurrentProviderAndModel(ModelTypeEnum.textGeneration)
|
||||
const [instruction, setInstruction] = React.useState<string>('')
|
||||
const [instructionFromSessionStorage, setInstruction] = useSessionStorageState<string>(`improve-instruction-${flowId}-${nodeId}`)
|
||||
const instruction = instructionFromSessionStorage || ''
|
||||
|
||||
const [ideaOutput, setIdeaOutput] = useState<string>('')
|
||||
|
||||
const [isLoading, { setTrue: setLoadingTrue, setFalse: setLoadingFalse }] = useBoolean(false)
|
||||
const [res, setRes] = React.useState<CodeGenRes | null>(null)
|
||||
const storageKey = `${flowId}-${nodeId}`
|
||||
const { addVersion, current, currentVersionIndex, setCurrentVersionIndex, versions } = useGenData({
|
||||
storageKey,
|
||||
})
|
||||
const [editorKey, setEditorKey] = useState(`${flowId}-0`)
|
||||
const { data: instructionTemplate } = useGenerateRuleTemplate(GeneratorType.code)
|
||||
useEffect(() => {
|
||||
if (!instruction && instructionTemplate)
|
||||
setInstruction(instructionTemplate.data)
|
||||
|
||||
setEditorKey(`${flowId}-${Date.now()}`)
|
||||
}, [instructionTemplate])
|
||||
|
||||
const isValid = () => {
|
||||
if (instruction.trim() === '') {
|
||||
Toast.notify({
|
||||
@@ -97,7 +126,6 @@ export const GetCodeGeneratorResModal: FC<IGetCodeGeneratorResProps> = (
|
||||
localStorage.setItem('auto-gen-model', JSON.stringify(newModel))
|
||||
}, [model, setModel])
|
||||
|
||||
const isInLLMNode = true
|
||||
const onGenerate = async () => {
|
||||
if (!isValid())
|
||||
return
|
||||
@@ -105,25 +133,35 @@ export const GetCodeGeneratorResModal: FC<IGetCodeGeneratorResProps> = (
|
||||
return
|
||||
setLoadingTrue()
|
||||
try {
|
||||
const { error, ...res } = await generateRuleCode({
|
||||
const { error, ...res } = await generateRule({
|
||||
flow_id: flowId,
|
||||
node_id: nodeId,
|
||||
current: currentCode,
|
||||
instruction,
|
||||
model_config: model,
|
||||
no_variable: !!isInLLMNode,
|
||||
code_language: languageMap[codeLanguages] || 'javascript',
|
||||
ideal_output: ideaOutput,
|
||||
language: languageMap[codeLanguages] || 'javascript',
|
||||
})
|
||||
setRes(res)
|
||||
|
||||
if (error) {
|
||||
Toast.notify({
|
||||
type: 'error',
|
||||
message: error,
|
||||
})
|
||||
}
|
||||
else {
|
||||
addVersion(res)
|
||||
}
|
||||
}
|
||||
finally {
|
||||
setLoadingFalse()
|
||||
}
|
||||
}
|
||||
const [showConfirmOverwrite, setShowConfirmOverwrite] = React.useState(false)
|
||||
|
||||
const [isShowConfirmOverwrite, {
|
||||
setTrue: showConfirmOverwrite,
|
||||
setFalse: hideShowConfirmOverwrite,
|
||||
}] = useBoolean(false)
|
||||
|
||||
useEffect(() => {
|
||||
if (defaultModel) {
|
||||
@@ -155,30 +193,20 @@ export const GetCodeGeneratorResModal: FC<IGetCodeGeneratorResProps> = (
|
||||
<div className='text-[13px] text-text-tertiary'>{t('appDebug.codegen.loading')}</div>
|
||||
</div>
|
||||
)
|
||||
const renderNoData = (
|
||||
<div className='flex h-full w-0 grow flex-col items-center justify-center space-y-3 px-8'>
|
||||
<Generator className='h-14 w-14 text-text-tertiary' />
|
||||
<div className='text-center text-[13px] font-normal leading-5 text-text-tertiary'>
|
||||
<div>{t('appDebug.codegen.noDataLine1')}</div>
|
||||
<div>{t('appDebug.codegen.noDataLine2')}</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
return (
|
||||
<Modal
|
||||
isShow={isShow}
|
||||
onClose={onClose}
|
||||
className='min-w-[1140px] !p-0'
|
||||
closable
|
||||
>
|
||||
<div className='relative flex h-[680px] flex-wrap'>
|
||||
<div className='h-full w-[570px] shrink-0 overflow-y-auto border-r border-divider-regular p-8'>
|
||||
<div className='mb-8'>
|
||||
<div className={'text-lg font-bold leading-[28px] text-text-primary'}>{t('appDebug.codegen.title')}</div>
|
||||
<div className='h-full w-[570px] shrink-0 overflow-y-auto border-r border-divider-regular p-6'>
|
||||
<div className='mb-5'>
|
||||
<div className={`text-lg font-bold leading-[28px] ${s.textGradient}`}>{t('appDebug.codegen.title')}</div>
|
||||
<div className='mt-1 text-[13px] font-normal text-text-tertiary'>{t('appDebug.codegen.description')}</div>
|
||||
</div>
|
||||
<div className='mb-8'>
|
||||
<div className='mb-4'>
|
||||
<ModelParameterModal
|
||||
popupClassName='!w-[520px]'
|
||||
portalToFollowElemContentClassName='z-[1000]'
|
||||
@@ -194,84 +222,60 @@ export const GetCodeGeneratorResModal: FC<IGetCodeGeneratorResProps> = (
|
||||
</div>
|
||||
<div>
|
||||
<div className='text-[0px]'>
|
||||
<div className='mb-2 text-sm font-medium leading-5 text-text-primary'>{t('appDebug.codegen.instruction')}</div>
|
||||
<Textarea
|
||||
className="h-[200px] resize-none"
|
||||
placeholder={t('appDebug.codegen.instructionPlaceholder') || ''}
|
||||
<div className='system-sm-semibold-uppercase mb-1.5 text-text-secondary'>{t('appDebug.codegen.instruction')}</div>
|
||||
<InstructionEditor
|
||||
editorKey={editorKey}
|
||||
value={instruction}
|
||||
onChange={e => setInstruction(e.target.value)}
|
||||
onChange={setInstruction}
|
||||
nodeId={nodeId}
|
||||
generatorType={GeneratorType.code}
|
||||
isShowCurrentBlock={!!currentCode}
|
||||
/>
|
||||
</div>
|
||||
<IdeaOutput
|
||||
value={ideaOutput}
|
||||
onChange={setIdeaOutput}
|
||||
/>
|
||||
|
||||
<div className='mt-5 flex justify-end'>
|
||||
<div className='mt-7 flex justify-end space-x-2'>
|
||||
<Button onClick={onClose}>{t(`${i18nPrefix}.dismiss`)}</Button>
|
||||
<Button
|
||||
className='flex space-x-1'
|
||||
variant='primary'
|
||||
onClick={onGenerate}
|
||||
disabled={isLoading}
|
||||
>
|
||||
<Generator className='h-4 w-4 text-white' />
|
||||
<span className='text-xs font-semibold text-white'>{t('appDebug.codegen.generate')}</span>
|
||||
<Generator className='h-4 w-4' />
|
||||
<span className='text-xs font-semibold '>{t('appDebug.codegen.generate')}</span>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{isLoading && renderLoading}
|
||||
{!isLoading && !res && renderNoData}
|
||||
{(!isLoading && res) && (
|
||||
<div className='h-full w-0 grow p-6 pb-0'>
|
||||
<div className='mb-3 shrink-0 text-base font-semibold leading-[160%] text-text-secondary'>{t('appDebug.codegen.resTitle')}</div>
|
||||
<div className={cn('max-h-[555px] overflow-y-auto', !isInLLMNode && 'pb-2')}>
|
||||
<ConfigPrompt
|
||||
mode={mode}
|
||||
promptTemplate={res?.code || ''}
|
||||
promptVariables={[]}
|
||||
readonly
|
||||
noTitle={isInLLMNode}
|
||||
gradientBorder
|
||||
editorHeight={isInLLMNode ? 524 : 0}
|
||||
noResize={isInLLMNode}
|
||||
/>
|
||||
{!isInLLMNode && (
|
||||
<>
|
||||
{res?.code && (
|
||||
<div className='mt-4'>
|
||||
<h3 className='mb-2 text-sm font-medium text-text-primary'>{t('appDebug.codegen.generatedCode')}</h3>
|
||||
<pre className='overflow-x-auto rounded-lg bg-gray-50 p-4'>
|
||||
<code className={`language-${res.language}`}>
|
||||
{res.code}
|
||||
</code>
|
||||
</pre>
|
||||
</div>
|
||||
)}
|
||||
{res?.error && (
|
||||
<div className='mt-4 rounded-lg bg-red-50 p-4'>
|
||||
<p className='text-sm text-red-600'>{res.error}</p>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className='flex justify-end bg-background-default py-4'>
|
||||
<Button onClick={onClose}>{t('common.operation.cancel')}</Button>
|
||||
<Button variant='primary' className='ml-2' onClick={() => {
|
||||
setShowConfirmOverwrite(true)
|
||||
}}>{t('appDebug.codegen.apply')}</Button>
|
||||
</div>
|
||||
{!isLoading && !current && <ResPlaceholder />}
|
||||
{(!isLoading && current) && (
|
||||
<div className='h-full w-0 grow bg-background-default-subtle p-6 pb-0'>
|
||||
<Result
|
||||
current={current!}
|
||||
currentVersionIndex={currentVersionIndex || 0}
|
||||
setCurrentVersionIndex={setCurrentVersionIndex}
|
||||
versions={versions || []}
|
||||
onApply={showConfirmOverwrite}
|
||||
generatorType={GeneratorType.code}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{showConfirmOverwrite && (
|
||||
{isShowConfirmOverwrite && (
|
||||
<Confirm
|
||||
title={t('appDebug.codegen.overwriteConfirmTitle')}
|
||||
content={t('appDebug.codegen.overwriteConfirmMessage')}
|
||||
isShow={showConfirmOverwrite}
|
||||
isShow
|
||||
onConfirm={() => {
|
||||
setShowConfirmOverwrite(false)
|
||||
onFinished(res!)
|
||||
hideShowConfirmOverwrite()
|
||||
onFinished(current!)
|
||||
}}
|
||||
onCancel={() => setShowConfirmOverwrite(false)}
|
||||
onCancel={hideShowConfirmOverwrite}
|
||||
/>
|
||||
)}
|
||||
</Modal>
|
||||
|
||||
Reference in New Issue
Block a user