feat: add code generator (#9051)
Co-authored-by: crazywoola <100913391+crazywoola@users.noreply.github.com>pull/9722/head
parent
0e965b6529
commit
a7ee51e5d8
@ -0,0 +1,200 @@
|
|||||||
|
import type { FC } from 'react'
|
||||||
|
import React from 'react'
|
||||||
|
import cn from 'classnames'
|
||||||
|
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 { ModelModeType } from '@/types/app'
|
||||||
|
import type { AppType, Model } from '@/types/app'
|
||||||
|
import Modal from '@/app/components/base/modal'
|
||||||
|
import Button from '@/app/components/base/button'
|
||||||
|
import { Generator } from '@/app/components/base/icons/src/vender/other'
|
||||||
|
import Toast from '@/app/components/base/toast'
|
||||||
|
import Loading from '@/app/components/base/loading'
|
||||||
|
import Confirm from '@/app/components/base/confirm'
|
||||||
|
import type { CodeLanguage } from '@/app/components/workflow/nodes/code/types'
|
||||||
|
export type IGetCodeGeneratorResProps = {
|
||||||
|
mode: AppType
|
||||||
|
isShow: boolean
|
||||||
|
codeLanguages: CodeLanguage
|
||||||
|
onClose: () => void
|
||||||
|
onFinished: (res: CodeGenRes) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
export const GetCodeGeneratorResModal: FC<IGetCodeGeneratorResProps> = (
|
||||||
|
{
|
||||||
|
mode,
|
||||||
|
isShow,
|
||||||
|
codeLanguages,
|
||||||
|
onClose,
|
||||||
|
onFinished,
|
||||||
|
|
||||||
|
},
|
||||||
|
) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
const [instruction, setInstruction] = React.useState<string>('')
|
||||||
|
const [isLoading, { setTrue: setLoadingTrue, setFalse: setLoadingFalse }] = useBoolean(false)
|
||||||
|
const [res, setRes] = React.useState<CodeGenRes | null>(null)
|
||||||
|
const isValid = () => {
|
||||||
|
if (instruction.trim() === '') {
|
||||||
|
Toast.notify({
|
||||||
|
type: 'error',
|
||||||
|
message: t('common.errorMsg.fieldRequired', {
|
||||||
|
field: t('appDebug.code.instruction'),
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
const model: Model = {
|
||||||
|
provider: 'openai',
|
||||||
|
name: 'gpt-4o-mini',
|
||||||
|
mode: ModelModeType.chat,
|
||||||
|
completion_params: {
|
||||||
|
temperature: 0.7,
|
||||||
|
max_tokens: 0,
|
||||||
|
top_p: 0,
|
||||||
|
echo: false,
|
||||||
|
stop: [],
|
||||||
|
presence_penalty: 0,
|
||||||
|
frequency_penalty: 0,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
const isInLLMNode = true
|
||||||
|
const onGenerate = async () => {
|
||||||
|
if (!isValid())
|
||||||
|
return
|
||||||
|
if (isLoading)
|
||||||
|
return
|
||||||
|
setLoadingTrue()
|
||||||
|
try {
|
||||||
|
const { error, ...res } = await generateRuleCode({
|
||||||
|
instruction,
|
||||||
|
model_config: model,
|
||||||
|
no_variable: !!isInLLMNode,
|
||||||
|
code_language: languageMap[codeLanguages] || 'javascript',
|
||||||
|
})
|
||||||
|
setRes(res)
|
||||||
|
if (error) {
|
||||||
|
Toast.notify({
|
||||||
|
type: 'error',
|
||||||
|
message: error,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
setLoadingFalse()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const [showConfirmOverwrite, setShowConfirmOverwrite] = React.useState(false)
|
||||||
|
|
||||||
|
const renderLoading = (
|
||||||
|
<div className='w-0 grow flex flex-col items-center justify-center h-full space-y-3'>
|
||||||
|
<Loading />
|
||||||
|
<div className='text-[13px] text-gray-400'>{t('appDebug.codegen.loading')}</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
isShow={isShow}
|
||||||
|
onClose={onClose}
|
||||||
|
className='!p-0 min-w-[1140px]'
|
||||||
|
closable
|
||||||
|
>
|
||||||
|
<div className='relative flex h-[680px] flex-wrap'>
|
||||||
|
<div className='w-[570px] shrink-0 p-8 h-full overflow-y-auto border-r border-gray-100'>
|
||||||
|
<div className='mb-8'>
|
||||||
|
<div className={'leading-[28px] text-lg font-bold'}>{t('appDebug.codegen.title')}</div>
|
||||||
|
<div className='mt-1 text-[13px] font-normal text-gray-500'>{t('appDebug.codegen.description')}</div>
|
||||||
|
</div>
|
||||||
|
<div className='mt-6'>
|
||||||
|
<div className='text-[0px]'>
|
||||||
|
<div className='mb-2 leading-5 text-sm font-medium text-gray-900'>{t('appDebug.codegen.instruction')}</div>
|
||||||
|
<textarea
|
||||||
|
className="w-full h-[200px] overflow-y-auto px-3 py-2 text-sm bg-gray-50 rounded-lg"
|
||||||
|
placeholder={t('appDebug.codegen.instructionPlaceholder') || ''}
|
||||||
|
value={instruction}
|
||||||
|
onChange={e => setInstruction(e.target.value)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='mt-5 flex justify-end'>
|
||||||
|
<Button
|
||||||
|
className='flex space-x-1'
|
||||||
|
variant='primary'
|
||||||
|
onClick={onGenerate}
|
||||||
|
disabled={isLoading}
|
||||||
|
>
|
||||||
|
<Generator className='w-4 h-4 text-white' />
|
||||||
|
<span className='text-xs font-semibold text-white'>{t('appDebug.codegen.generate')}</span>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{isLoading && renderLoading}
|
||||||
|
{(!isLoading && res) && (
|
||||||
|
<div className='w-0 grow p-6 pb-0 h-full'>
|
||||||
|
<div className='shrink-0 mb-3 leading-[160%] text-base font-semibold text-gray-800'>{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-gray-900'>{t('appDebug.codegen.generatedCode')}</h3>
|
||||||
|
<pre className='p-4 bg-gray-50 rounded-lg overflow-x-auto'>
|
||||||
|
<code className={`language-${res.language}`}>
|
||||||
|
{res.code}
|
||||||
|
</code>
|
||||||
|
</pre>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{res?.error && (
|
||||||
|
<div className='mt-4 p-4 bg-red-50 rounded-lg'>
|
||||||
|
<p className='text-sm text-red-600'>{res.error}</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='flex justify-end py-4 bg-white'>
|
||||||
|
<Button onClick={onClose}>{t('common.operation.cancel')}</Button>
|
||||||
|
<Button variant='primary' className='ml-2' onClick={() => {
|
||||||
|
setShowConfirmOverwrite(true)
|
||||||
|
}}>{t('appDebug.codegen.apply')}</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
{showConfirmOverwrite && (
|
||||||
|
<Confirm
|
||||||
|
title={t('appDebug.codegen.overwriteConfirmTitle')}
|
||||||
|
content={t('appDebug.codegen.overwriteConfirmMessage')}
|
||||||
|
isShow={showConfirmOverwrite}
|
||||||
|
onConfirm={() => {
|
||||||
|
setShowConfirmOverwrite(false)
|
||||||
|
onFinished(res!)
|
||||||
|
}}
|
||||||
|
onCancel={() => setShowConfirmOverwrite(false)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Modal>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default React.memo(GetCodeGeneratorResModal)
|
||||||
@ -0,0 +1,48 @@
|
|||||||
|
'use client'
|
||||||
|
import type { FC } from 'react'
|
||||||
|
import React, { useCallback } from 'react'
|
||||||
|
import { useBoolean } from 'ahooks'
|
||||||
|
import cn from 'classnames'
|
||||||
|
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 { GetCodeGeneratorResModal } from '@/app/components/app/configuration/config/code-generator/get-code-generator-res'
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
className?: string
|
||||||
|
onGenerated?: (prompt: string) => void
|
||||||
|
codeLanguages: CodeLanguage
|
||||||
|
}
|
||||||
|
|
||||||
|
const CodeGenerateBtn: FC<Props> = ({
|
||||||
|
className,
|
||||||
|
codeLanguages,
|
||||||
|
onGenerated,
|
||||||
|
}) => {
|
||||||
|
const [showAutomatic, { setTrue: showAutomaticTrue, setFalse: showAutomaticFalse }] = useBoolean(false)
|
||||||
|
const handleAutomaticRes = useCallback((res: CodeGenRes) => {
|
||||||
|
onGenerated?.(res.code)
|
||||||
|
showAutomaticFalse()
|
||||||
|
}, [onGenerated, showAutomaticFalse])
|
||||||
|
return (
|
||||||
|
<div className={cn(className)}>
|
||||||
|
<ActionButton
|
||||||
|
className='hover:bg-[#155EFF]/8'
|
||||||
|
onClick={showAutomaticTrue}>
|
||||||
|
<Generator className='w-4 h-4 text-primary-600' />
|
||||||
|
</ActionButton>
|
||||||
|
{showAutomatic && (
|
||||||
|
<GetCodeGeneratorResModal
|
||||||
|
mode={AppType.chat}
|
||||||
|
isShow={showAutomatic}
|
||||||
|
codeLanguages={codeLanguages}
|
||||||
|
onClose={showAutomaticFalse}
|
||||||
|
onFinished={handleAutomaticRes}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
export default React.memo(CodeGenerateBtn)
|
||||||
Loading…
Reference in New Issue