mirror of
https://github.com/langgenius/dify.git
synced 2026-05-03 08:58:09 +08:00
Merge branch 'fix/chore-fix' into dev/plugin-deploy
This commit is contained in:
@ -22,6 +22,7 @@ export const useWorkflowTemplate = () => {
|
||||
...nodesInitialData.llm,
|
||||
memory: {
|
||||
window: { enabled: false, size: 10 },
|
||||
query_prompt_template: '{{#sys.query#}}',
|
||||
},
|
||||
selected: true,
|
||||
},
|
||||
|
||||
@ -16,6 +16,7 @@ import { InputVarType, NodeRunningStatus } from '@/app/components/workflow/types
|
||||
import ResultPanel from '@/app/components/workflow/run/result-panel'
|
||||
import Toast from '@/app/components/base/toast'
|
||||
import { TransferMethod } from '@/types/app'
|
||||
import { getProcessedFiles } from '@/app/components/base/file-uploader/utils'
|
||||
|
||||
const i18nPrefix = 'workflow.singleRun'
|
||||
|
||||
@ -39,6 +40,11 @@ function formatValue(value: string | any, type: InputVarType) {
|
||||
return JSON.parse(item)
|
||||
})
|
||||
}
|
||||
if (type === InputVarType.multiFiles)
|
||||
return getProcessedFiles(value)
|
||||
|
||||
if (type === InputVarType.singleFile)
|
||||
return getProcessedFiles([value])[0]
|
||||
|
||||
return value
|
||||
}
|
||||
|
||||
@ -160,6 +160,7 @@ const CodeEditor: FC<Props> = ({
|
||||
hideSearch
|
||||
vars={availableVars}
|
||||
onChange={handleSelectVar}
|
||||
isSupportFileVar={false}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@ -128,7 +128,7 @@ const PanelOperatorPopup = ({
|
||||
className='flex items-center justify-between px-3 h-8 text-sm text-gray-700 rounded-lg cursor-pointer hover:bg-gray-50'
|
||||
onClick={() => {
|
||||
onClosePopup()
|
||||
handleNodesCopy()
|
||||
handleNodesCopy(id)
|
||||
}}
|
||||
>
|
||||
{t('workflow.common.copy')}
|
||||
|
||||
@ -18,6 +18,7 @@ type Props = {
|
||||
isSupportConstantValue?: boolean
|
||||
onlyLeafNodeVar?: boolean
|
||||
filterVar?: (payload: Var, valueSelector: ValueSelector) => boolean
|
||||
isSupportFileVar?: boolean
|
||||
}
|
||||
|
||||
const VarList: FC<Props> = ({
|
||||
@ -29,6 +30,7 @@ const VarList: FC<Props> = ({
|
||||
isSupportConstantValue,
|
||||
onlyLeafNodeVar,
|
||||
filterVar,
|
||||
isSupportFileVar = true,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
@ -94,6 +96,7 @@ const VarList: FC<Props> = ({
|
||||
defaultVarKindType={item.variable_type}
|
||||
onlyLeafNodeVar={onlyLeafNodeVar}
|
||||
filterVar={filterVar}
|
||||
isSupportFileVar={isSupportFileVar}
|
||||
/>
|
||||
{!readonly && (
|
||||
<RemoveButton
|
||||
|
||||
@ -59,6 +59,7 @@ type Props = {
|
||||
isInTable?: boolean
|
||||
onRemove?: () => void
|
||||
typePlaceHolder?: string
|
||||
isSupportFileVar?: boolean
|
||||
}
|
||||
|
||||
const VarReferencePicker: FC<Props> = ({
|
||||
@ -81,6 +82,7 @@ const VarReferencePicker: FC<Props> = ({
|
||||
isInTable,
|
||||
onRemove,
|
||||
typePlaceHolder,
|
||||
isSupportFileVar = true,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
const store = useStoreApi()
|
||||
@ -382,6 +384,7 @@ const VarReferencePicker: FC<Props> = ({
|
||||
vars={outputVars}
|
||||
onChange={handleVarReferenceChange}
|
||||
itemWidth={isAddBtnTrigger ? 260 : triggerWidth}
|
||||
isSupportFileVar={isSupportFileVar}
|
||||
/>
|
||||
)}
|
||||
</PortalToFollowElemContent>
|
||||
|
||||
@ -8,11 +8,13 @@ type Props = {
|
||||
vars: NodeOutPutVar[]
|
||||
onChange: (value: ValueSelector, varDetail: Var) => void
|
||||
itemWidth?: number
|
||||
isSupportFileVar?: boolean
|
||||
}
|
||||
const VarReferencePopup: FC<Props> = ({
|
||||
vars,
|
||||
onChange,
|
||||
itemWidth,
|
||||
isSupportFileVar = true,
|
||||
}) => {
|
||||
// max-h-[300px] overflow-y-auto todo: use portal to handle long list
|
||||
return (
|
||||
@ -24,7 +26,7 @@ const VarReferencePopup: FC<Props> = ({
|
||||
vars={vars}
|
||||
onChange={onChange}
|
||||
itemWidth={itemWidth}
|
||||
isSupportFileVar
|
||||
isSupportFileVar={isSupportFileVar}
|
||||
/>
|
||||
</div >
|
||||
)
|
||||
|
||||
@ -89,6 +89,7 @@ const Panel: FC<NodePanelProps<CodeNodeType>> = ({
|
||||
list={inputs.variables}
|
||||
onChange={handleVarListChange}
|
||||
filterVar={filterVar}
|
||||
isSupportFileVar={false}
|
||||
/>
|
||||
</Field>
|
||||
<Split />
|
||||
|
||||
154
web/app/components/workflow/nodes/http/components/curl-panel.tsx
Normal file
154
web/app/components/workflow/nodes/http/components/curl-panel.tsx
Normal file
@ -0,0 +1,154 @@
|
||||
'use client'
|
||||
import type { FC } from 'react'
|
||||
import React, { useCallback, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { BodyType, type HttpNodeType, Method } from '../types'
|
||||
import Modal from '@/app/components/base/modal'
|
||||
import Button from '@/app/components/base/button'
|
||||
import Toast from '@/app/components/base/toast'
|
||||
import { useNodesInteractions } from '@/app/components/workflow/hooks'
|
||||
|
||||
type Props = {
|
||||
nodeId: string
|
||||
isShow: boolean
|
||||
onHide: () => void
|
||||
handleCurlImport: (node: HttpNodeType) => void
|
||||
}
|
||||
|
||||
const parseCurl = (curlCommand: string): { node: HttpNodeType | null; error: string | null } => {
|
||||
if (!curlCommand.trim().toLowerCase().startsWith('curl'))
|
||||
return { node: null, error: 'Invalid cURL command. Command must start with "curl".' }
|
||||
|
||||
const node: Partial<HttpNodeType> = {
|
||||
title: 'HTTP Request',
|
||||
desc: 'Imported from cURL',
|
||||
method: Method.get,
|
||||
url: '',
|
||||
headers: '',
|
||||
params: '',
|
||||
body: { type: BodyType.none, data: '' },
|
||||
}
|
||||
const args = curlCommand.match(/(?:[^\s"']+|"[^"]*"|'[^']*')+/g) || []
|
||||
|
||||
for (let i = 1; i < args.length; i++) {
|
||||
const arg = args[i].replace(/^['"]|['"]$/g, '')
|
||||
switch (arg) {
|
||||
case '-X':
|
||||
case '--request':
|
||||
if (i + 1 >= args.length)
|
||||
return { node: null, error: 'Missing HTTP method after -X or --request.' }
|
||||
node.method = (args[++i].replace(/^['"]|['"]$/g, '') as Method) || Method.get
|
||||
break
|
||||
case '-H':
|
||||
case '--header':
|
||||
if (i + 1 >= args.length)
|
||||
return { node: null, error: 'Missing header value after -H or --header.' }
|
||||
node.headers += (node.headers ? '\n' : '') + args[++i].replace(/^['"]|['"]$/g, '')
|
||||
break
|
||||
case '-d':
|
||||
case '--data':
|
||||
case '--data-raw':
|
||||
case '--data-binary':
|
||||
if (i + 1 >= args.length)
|
||||
return { node: null, error: 'Missing data value after -d, --data, --data-raw, or --data-binary.' }
|
||||
node.body = { type: BodyType.rawText, data: args[++i].replace(/^['"]|['"]$/g, '') }
|
||||
break
|
||||
case '-F':
|
||||
case '--form': {
|
||||
if (i + 1 >= args.length)
|
||||
return { node: null, error: 'Missing form data after -F or --form.' }
|
||||
if (node.body?.type !== BodyType.formData)
|
||||
node.body = { type: BodyType.formData, data: '' }
|
||||
const formData = args[++i].replace(/^['"]|['"]$/g, '')
|
||||
const [key, ...valueParts] = formData.split('=')
|
||||
if (!key)
|
||||
return { node: null, error: 'Invalid form data format.' }
|
||||
let value = valueParts.join('=')
|
||||
|
||||
// To support command like `curl -F "file=@/path/to/file;type=application/zip"`
|
||||
// the `;type=application/zip` should translate to `Content-Type: application/zip`
|
||||
const typeMatch = value.match(/^(.+?);type=(.+)$/)
|
||||
if (typeMatch) {
|
||||
const [, actualValue, mimeType] = typeMatch
|
||||
value = actualValue
|
||||
node.headers += `${node.headers ? '\n' : ''}Content-Type: ${mimeType}`
|
||||
}
|
||||
|
||||
node.body.data += `${node.body.data ? '\n' : ''}${key}:${value}`
|
||||
break
|
||||
}
|
||||
case '--json':
|
||||
if (i + 1 >= args.length)
|
||||
return { node: null, error: 'Missing JSON data after --json.' }
|
||||
node.body = { type: BodyType.json, data: args[++i].replace(/^['"]|['"]$/g, '') }
|
||||
break
|
||||
default:
|
||||
if (arg.startsWith('http') && !node.url)
|
||||
node.url = arg
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if (!node.url)
|
||||
return { node: null, error: 'Missing URL or url not start with http.' }
|
||||
|
||||
// Extract query params from URL
|
||||
const urlParts = node.url?.split('?') || []
|
||||
if (urlParts.length > 1) {
|
||||
node.url = urlParts[0]
|
||||
node.params = urlParts[1].replace(/&/g, '\n').replace(/=/g, ': ')
|
||||
}
|
||||
|
||||
return { node: node as HttpNodeType, error: null }
|
||||
}
|
||||
|
||||
const CurlPanel: FC<Props> = ({ nodeId, isShow, onHide, handleCurlImport }) => {
|
||||
const [inputString, setInputString] = useState('')
|
||||
const { handleNodeSelect } = useNodesInteractions()
|
||||
const { t } = useTranslation()
|
||||
|
||||
const handleSave = useCallback(() => {
|
||||
const { node, error } = parseCurl(inputString)
|
||||
if (error) {
|
||||
Toast.notify({
|
||||
type: 'error',
|
||||
message: error,
|
||||
})
|
||||
return
|
||||
}
|
||||
if (!node)
|
||||
return
|
||||
|
||||
onHide()
|
||||
handleCurlImport(node)
|
||||
// Close the panel then open it again to make the panel re-render
|
||||
handleNodeSelect(nodeId, true)
|
||||
setTimeout(() => {
|
||||
handleNodeSelect(nodeId)
|
||||
}, 0)
|
||||
}, [onHide, nodeId, inputString, handleNodeSelect, handleCurlImport])
|
||||
|
||||
return (
|
||||
<Modal
|
||||
title={t('workflow.nodes.http.curl.title')}
|
||||
isShow={isShow}
|
||||
onClose={onHide}
|
||||
className='!w-[400px] !max-w-[400px] !p-4'
|
||||
>
|
||||
<div>
|
||||
<textarea
|
||||
value={inputString}
|
||||
className='w-full my-3 p-3 text-sm text-gray-900 border-0 rounded-lg grow bg-gray-100 focus:outline-none focus:ring-1 focus:ring-inset focus:ring-gray-200 h-40'
|
||||
onChange={e => setInputString(e.target.value)}
|
||||
placeholder={t('workflow.nodes.http.curl.placeholder')!}
|
||||
/>
|
||||
</div>
|
||||
<div className='mt-4 flex justify-end space-x-2'>
|
||||
<Button className='!w-[95px]' onClick={onHide} >{t('common.operation.cancel')}</Button>
|
||||
<Button className='!w-[95px]' variant='primary' onClick={handleSave} > {t('common.operation.save')}</Button>
|
||||
</div>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
|
||||
export default React.memo(CurlPanel)
|
||||
@ -8,11 +8,13 @@ import EditBody from './components/edit-body'
|
||||
import AuthorizationModal from './components/authorization'
|
||||
import type { HttpNodeType } from './types'
|
||||
import Timeout from './components/timeout'
|
||||
import CurlPanel from './components/curl-panel'
|
||||
import cn from '@/utils/classnames'
|
||||
import Field from '@/app/components/workflow/nodes/_base/components/field'
|
||||
import Split from '@/app/components/workflow/nodes/_base/components/split'
|
||||
import OutputVars, { VarItem } from '@/app/components/workflow/nodes/_base/components/output-vars'
|
||||
import { Settings01 } from '@/app/components/base/icons/src/vender/line/general'
|
||||
import { FileArrow01 } from '@/app/components/base/icons/src/vender/line/files'
|
||||
import type { NodePanelProps } from '@/app/components/workflow/types'
|
||||
import BeforeRunForm from '@/app/components/workflow/nodes/_base/components/before-run-form'
|
||||
import ResultPanel from '@/app/components/workflow/run/result-panel'
|
||||
@ -53,6 +55,10 @@ const Panel: FC<NodePanelProps<HttpNodeType>> = ({
|
||||
inputVarValues,
|
||||
setInputVarValues,
|
||||
runResult,
|
||||
isShowCurlPanel,
|
||||
showCurlPanel,
|
||||
hideCurlPanel,
|
||||
handleCurlImport,
|
||||
} = useConfig(id, data)
|
||||
// To prevent prompt editor in body not update data.
|
||||
if (!isDataReady)
|
||||
@ -64,14 +70,25 @@ const Panel: FC<NodePanelProps<HttpNodeType>> = ({
|
||||
<Field
|
||||
title={t(`${i18nPrefix}.api`)}
|
||||
operations={
|
||||
<div
|
||||
onClick={showAuthorization}
|
||||
className={cn(!readOnly && 'cursor-pointer hover:bg-gray-50', 'flex items-center h-6 space-x-1 px-2 rounded-md ')}
|
||||
>
|
||||
{!readOnly && <Settings01 className='w-3 h-3 text-gray-500' />}
|
||||
<div className='text-xs font-medium text-gray-500'>
|
||||
{t(`${i18nPrefix}.authorization.authorization`)}
|
||||
<span className='ml-1 text-gray-700'>{t(`${i18nPrefix}.authorization.${inputs.authorization.type}`)}</span>
|
||||
<div className='flex'>
|
||||
<div
|
||||
onClick={showAuthorization}
|
||||
className={cn(!readOnly && 'cursor-pointer hover:bg-gray-50', 'flex items-center h-6 space-x-1 px-2 rounded-md ')}
|
||||
>
|
||||
{!readOnly && <Settings01 className='w-3 h-3 text-gray-500' />}
|
||||
<div className='text-xs font-medium text-gray-500'>
|
||||
{t(`${i18nPrefix}.authorization.authorization`)}
|
||||
<span className='ml-1 text-gray-700'>{t(`${i18nPrefix}.authorization.${inputs.authorization.type}`)}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
onClick={showCurlPanel}
|
||||
className={cn(!readOnly && 'cursor-pointer hover:bg-gray-50', 'flex items-center h-6 space-x-1 px-2 rounded-md ')}
|
||||
>
|
||||
{!readOnly && <FileArrow01 className='w-3 h-3 text-gray-500' />}
|
||||
<div className='text-xs font-medium text-gray-500'>
|
||||
{t(`${i18nPrefix}.curl.title`)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
@ -180,7 +197,15 @@ const Panel: FC<NodePanelProps<HttpNodeType>> = ({
|
||||
result={<ResultPanel {...runResult} showSteps={false} />}
|
||||
/>
|
||||
)}
|
||||
</div >
|
||||
{(isShowCurlPanel && !readOnly) && (
|
||||
<CurlPanel
|
||||
nodeId={id}
|
||||
isShow
|
||||
onHide={hideCurlPanel}
|
||||
handleCurlImport={handleCurlImport}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@ -164,6 +164,23 @@ const useConfig = (id: string, payload: HttpNodeType) => {
|
||||
setRunInputData(newPayload)
|
||||
}, [setRunInputData])
|
||||
|
||||
// curl import panel
|
||||
const [isShowCurlPanel, {
|
||||
setTrue: showCurlPanel,
|
||||
setFalse: hideCurlPanel,
|
||||
}] = useBoolean(false)
|
||||
|
||||
const handleCurlImport = useCallback((newNode: HttpNodeType) => {
|
||||
const newInputs = produce(inputs, (draft: HttpNodeType) => {
|
||||
draft.method = newNode.method
|
||||
draft.url = newNode.url
|
||||
draft.headers = newNode.headers
|
||||
draft.params = newNode.params
|
||||
draft.body = newNode.body
|
||||
})
|
||||
setInputs(newInputs)
|
||||
}, [inputs, setInputs])
|
||||
|
||||
return {
|
||||
readOnly,
|
||||
isDataReady,
|
||||
@ -203,6 +220,11 @@ const useConfig = (id: string, payload: HttpNodeType) => {
|
||||
inputVarValues,
|
||||
setInputVarValues,
|
||||
runResult,
|
||||
// curl import
|
||||
isShowCurlPanel,
|
||||
showCurlPanel,
|
||||
hideCurlPanel,
|
||||
handleCurlImport,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -144,6 +144,7 @@ const ConfigPromptItem: FC<Props> = ({
|
||||
onEditionTypeChange={onEditionTypeChange}
|
||||
varList={varList}
|
||||
handleAddVariable={handleAddVariable}
|
||||
isSupportFileVar
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
@ -67,6 +67,7 @@ const Panel: FC<NodePanelProps<LLMNodeType>> = ({
|
||||
handleStop,
|
||||
varInputs,
|
||||
runResult,
|
||||
filterJinjia2InputVar,
|
||||
} = useConfig(id, data)
|
||||
|
||||
const model = inputs.model
|
||||
@ -194,7 +195,8 @@ const Panel: FC<NodePanelProps<LLMNodeType>> = ({
|
||||
list={inputs.prompt_config?.jinja2_variables || []}
|
||||
onChange={handleVarListChange}
|
||||
onVarNameChange={handleVarNameChange}
|
||||
filterVar={filterVar}
|
||||
filterVar={filterJinjia2InputVar}
|
||||
isSupportFileVar={false}
|
||||
/>
|
||||
</Field>
|
||||
)}
|
||||
@ -233,6 +235,7 @@ const Panel: FC<NodePanelProps<LLMNodeType>> = ({
|
||||
hasSetBlockStatus={hasSetBlockStatus}
|
||||
nodesOutputVars={availableVars}
|
||||
availableNodes={availableNodesWithParent}
|
||||
isSupportFileVar
|
||||
/>
|
||||
|
||||
{inputs.memory.query_prompt_template && !inputs.memory.query_prompt_template.includes('{{#sys.query#}}') && (
|
||||
|
||||
@ -278,11 +278,15 @@ const useConfig = (id: string, payload: LLMNodeType) => {
|
||||
}, [inputs, setInputs])
|
||||
|
||||
const filterInputVar = useCallback((varPayload: Var) => {
|
||||
return [VarType.number, VarType.string, VarType.secret, VarType.arrayString, VarType.arrayNumber, VarType.file, VarType.arrayFile].includes(varPayload.type)
|
||||
}, [])
|
||||
|
||||
const filterJinjia2InputVar = useCallback((varPayload: Var) => {
|
||||
return [VarType.number, VarType.string, VarType.secret, VarType.arrayString, VarType.arrayNumber].includes(varPayload.type)
|
||||
}, [])
|
||||
|
||||
const filterMemoryPromptVar = useCallback((varPayload: Var) => {
|
||||
return [VarType.arrayObject, VarType.array, VarType.number, VarType.string, VarType.secret, VarType.arrayString, VarType.arrayNumber].includes(varPayload.type)
|
||||
return [VarType.arrayObject, VarType.array, VarType.number, VarType.string, VarType.secret, VarType.arrayString, VarType.arrayNumber, VarType.file, VarType.arrayFile].includes(varPayload.type)
|
||||
}, [])
|
||||
|
||||
const {
|
||||
@ -406,6 +410,7 @@ const useConfig = (id: string, payload: LLMNodeType) => {
|
||||
handleRun,
|
||||
handleStop,
|
||||
runResult,
|
||||
filterJinjia2InputVar,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -64,6 +64,7 @@ const Panel: FC<NodePanelProps<TemplateTransformNodeType>> = ({
|
||||
onChange={handleVarListChange}
|
||||
onVarNameChange={handleVarNameChange}
|
||||
filterVar={filterVar}
|
||||
isSupportFileVar={false}
|
||||
/>
|
||||
</Field>
|
||||
<Split />
|
||||
|
||||
@ -10,8 +10,9 @@ import {
|
||||
import { useContext } from 'use-context-selector'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import {
|
||||
RiAlertLine,
|
||||
RiAlertFill,
|
||||
RiCloseLine,
|
||||
RiFileDownloadLine,
|
||||
} from '@remixicon/react'
|
||||
import { WORKFLOW_DATA_UPDATE } from './constants'
|
||||
import {
|
||||
@ -21,11 +22,19 @@ import {
|
||||
initialEdges,
|
||||
initialNodes,
|
||||
} from './utils'
|
||||
import {
|
||||
importDSL,
|
||||
importDSLConfirm,
|
||||
} from '@/service/apps'
|
||||
import { fetchWorkflowDraft } from '@/service/workflow'
|
||||
import {
|
||||
DSLImportMode,
|
||||
DSLImportStatus,
|
||||
} from '@/models/app'
|
||||
import Uploader from '@/app/components/app/create-from-dsl-modal/uploader'
|
||||
import Button from '@/app/components/base/button'
|
||||
import Modal from '@/app/components/base/modal'
|
||||
import { ToastContext } from '@/app/components/base/toast'
|
||||
import { updateWorkflowDraftFromDSL } from '@/service/workflow'
|
||||
import { useEventEmitterContextContext } from '@/context/event-emitter'
|
||||
import { useStore as useAppStore } from '@/app/components/app/store'
|
||||
import { FILE_EXTS } from '@/app/components/base/prompt-editor/constants'
|
||||
@ -50,6 +59,10 @@ const UpdateDSLModal = ({
|
||||
const [loading, setLoading] = useState(false)
|
||||
const { eventEmitter } = useEventEmitterContextContext()
|
||||
const { mutateAsync, mutate } = useMutationCheckDependenciesBeforeImportDSL()
|
||||
const [show, setShow] = useState(true)
|
||||
const [showErrorModal, setShowErrorModal] = useState(false)
|
||||
const [versions, setVersions] = useState<{ importedVersion: string; systemVersion: string }>()
|
||||
const [importId, setImportId] = useState<string>()
|
||||
|
||||
const readFile = (file: File) => {
|
||||
const reader = new FileReader()
|
||||
@ -68,6 +81,51 @@ const UpdateDSLModal = ({
|
||||
setFileContent('')
|
||||
}
|
||||
|
||||
const handleWorkflowUpdate = async (app_id: string) => {
|
||||
const {
|
||||
graph,
|
||||
features,
|
||||
hash,
|
||||
} = await fetchWorkflowDraft(`/apps/${app_id}/workflows/draft`)
|
||||
|
||||
const { nodes, edges, viewport } = graph
|
||||
const newFeatures = {
|
||||
file: {
|
||||
image: {
|
||||
enabled: !!features.file_upload?.image?.enabled,
|
||||
number_limits: features.file_upload?.image?.number_limits || 3,
|
||||
transfer_methods: features.file_upload?.image?.transfer_methods || ['local_file', 'remote_url'],
|
||||
},
|
||||
enabled: !!(features.file_upload?.enabled || features.file_upload?.image?.enabled),
|
||||
allowed_file_types: features.file_upload?.allowed_file_types || [SupportUploadFileTypes.image],
|
||||
allowed_file_extensions: features.file_upload?.allowed_file_extensions || FILE_EXTS[SupportUploadFileTypes.image].map(ext => `.${ext}`),
|
||||
allowed_file_upload_methods: features.file_upload?.allowed_file_upload_methods || features.file_upload?.image?.transfer_methods || ['local_file', 'remote_url'],
|
||||
number_limits: features.file_upload?.number_limits || features.file_upload?.image?.number_limits || 3,
|
||||
},
|
||||
opening: {
|
||||
enabled: !!features.opening_statement,
|
||||
opening_statement: features.opening_statement,
|
||||
suggested_questions: features.suggested_questions,
|
||||
},
|
||||
suggested: features.suggested_questions_after_answer || { enabled: false },
|
||||
speech2text: features.speech_to_text || { enabled: false },
|
||||
text2speech: features.text_to_speech || { enabled: false },
|
||||
citation: features.retriever_resource || { enabled: false },
|
||||
moderation: features.sensitive_word_avoidance || { enabled: false },
|
||||
}
|
||||
|
||||
eventEmitter?.emit({
|
||||
type: WORKFLOW_DATA_UPDATE,
|
||||
payload: {
|
||||
nodes: initialNodes(nodes, edges),
|
||||
edges: initialEdges(edges, nodes),
|
||||
viewport,
|
||||
features: newFeatures,
|
||||
hash,
|
||||
},
|
||||
} as any)
|
||||
}
|
||||
|
||||
const isCreatingRef = useRef(false)
|
||||
const handleImport: MouseEventHandler = useCallback(async () => {
|
||||
if (isCreatingRef.current)
|
||||
@ -109,21 +167,6 @@ const UpdateDSLModal = ({
|
||||
citation: features.retriever_resource || { enabled: false },
|
||||
moderation: features.sensitive_word_avoidance || { enabled: false },
|
||||
}
|
||||
eventEmitter?.emit({
|
||||
type: WORKFLOW_DATA_UPDATE,
|
||||
payload: {
|
||||
nodes: initialNodes(nodes, edges),
|
||||
edges: initialEdges(edges, nodes),
|
||||
viewport,
|
||||
features: newFeatures,
|
||||
hash,
|
||||
},
|
||||
} as any)
|
||||
if (onImport)
|
||||
onImport()
|
||||
notify({ type: 'success', message: t('workflow.common.importSuccess') })
|
||||
setLoading(false)
|
||||
onCancel()
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
@ -133,52 +176,119 @@ const UpdateDSLModal = ({
|
||||
isCreatingRef.current = false
|
||||
}, [currentFile, fileContent, onCancel, notify, t, eventEmitter, appDetail, onImport, mutateAsync])
|
||||
|
||||
const onUpdateDSLConfirm: MouseEventHandler = async () => {
|
||||
try {
|
||||
if (!importId)
|
||||
return
|
||||
const response = await importDSLConfirm({
|
||||
import_id: importId,
|
||||
})
|
||||
|
||||
const { status, app_id } = response
|
||||
|
||||
if (status === DSLImportStatus.COMPLETED) {
|
||||
if (!app_id) {
|
||||
notify({ type: 'error', message: t('workflow.common.importFailure') })
|
||||
return
|
||||
}
|
||||
handleWorkflowUpdate(app_id)
|
||||
if (onImport)
|
||||
onImport()
|
||||
notify({ type: 'success', message: t('workflow.common.importSuccess') })
|
||||
setLoading(false)
|
||||
onCancel()
|
||||
}
|
||||
else if (status === DSLImportStatus.FAILED) {
|
||||
setLoading(false)
|
||||
notify({ type: 'error', message: t('workflow.common.importFailure') })
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
setLoading(false)
|
||||
notify({ type: 'error', message: t('workflow.common.importFailure') })
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal
|
||||
className='p-6 w-[520px] rounded-2xl'
|
||||
isShow={true}
|
||||
onClose={() => {}}
|
||||
>
|
||||
<div className='flex items-center justify-between mb-6'>
|
||||
<div className='text-2xl font-semibold text-[#101828]'>{t('workflow.common.importDSL')}</div>
|
||||
<div className='flex items-center justify-center w-[22px] h-[22px] cursor-pointer' onClick={onCancel}>
|
||||
<RiCloseLine className='w-5 h-5 text-gray-500' />
|
||||
<>
|
||||
<Modal
|
||||
className='p-6 w-[520px] rounded-2xl'
|
||||
isShow={show}
|
||||
onClose={onCancel}
|
||||
>
|
||||
<div className='flex items-center justify-between mb-3'>
|
||||
<div className='title-2xl-semi-bold text-text-primary'>{t('workflow.common.importDSL')}</div>
|
||||
<div className='flex items-center justify-center w-[22px] h-[22px] cursor-pointer' onClick={onCancel}>
|
||||
<RiCloseLine className='w-[18px] h-[18px] text-text-tertiary' />
|
||||
</div>
|
||||
</div>
|
||||
<div className='flex relative p-2 mb-2 gap-0.5 flex-grow rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg-blur shadow-xs overflow-hidden'>
|
||||
<div className='absolute top-0 left-0 w-full h-full opacity-40 bg-[linear-gradient(92deg,rgba(247,144,9,0.25)_0%,rgba(255,255,255,0.00)_100%)]' />
|
||||
<div className='flex p-1 justify-center items-start'>
|
||||
<RiAlertFill className='w-4 h-4 flex-shrink-0 text-text-warning-secondary' />
|
||||
</div>
|
||||
<div className='flex py-1 flex-col items-start gap-0.5 flex-grow'>
|
||||
<div className='text-text-primary system-xs-medium whitespace-pre-line'>{t('workflow.common.importDSLTip')}</div>
|
||||
<div className='flex pt-1 pb-0.5 items-start gap-1 self-stretch'>
|
||||
<Button
|
||||
size='small'
|
||||
variant='secondary'
|
||||
className='z-[1000]'
|
||||
onClick={onBackup}
|
||||
>
|
||||
<RiFileDownloadLine className='w-3.5 h-3.5 text-components-button-secondary-text' />
|
||||
<div className='flex px-[3px] justify-center items-center gap-1'>
|
||||
{t('workflow.common.backupCurrentDraft')}
|
||||
</div>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className='flex mb-4 px-4 py-3 bg-[#FFFAEB] rounded-xl border border-[#FEDF89]'>
|
||||
<RiAlertLine className='shrink-0 mt-0.5 mr-2 w-4 h-4 text-[#F79009]' />
|
||||
<div>
|
||||
<div className='mb-2 text-sm font-medium text-[#354052]'>{t('workflow.common.importDSLTip')}</div>
|
||||
<div className='pt-2 text-text-primary system-md-semibold'>
|
||||
{t('workflow.common.chooseDSL')}
|
||||
</div>
|
||||
<div className='flex w-full py-4 flex-col justify-center items-start gap-4 self-stretch'>
|
||||
<Uploader
|
||||
file={currentFile}
|
||||
updateFile={handleFile}
|
||||
className='!mt-0 w-full'
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className='flex pt-5 gap-2 items-center justify-end self-stretch'>
|
||||
<Button onClick={onCancel}>{t('app.newApp.Cancel')}</Button>
|
||||
<Button
|
||||
variant='secondary-accent'
|
||||
onClick={onBackup}
|
||||
disabled={!currentFile || loading}
|
||||
variant='warning'
|
||||
onClick={handleImport}
|
||||
loading={loading}
|
||||
>
|
||||
{t('workflow.common.backupCurrentDraft')}
|
||||
{t('workflow.common.overwriteAndImport')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div className='mb-8'>
|
||||
<div className='mb-1 text-[13px] font-semibold text-[#354052]'>
|
||||
{t('workflow.common.chooseDSL')}
|
||||
</Modal>
|
||||
<Modal
|
||||
isShow={showErrorModal}
|
||||
onClose={() => setShowErrorModal(false)}
|
||||
className='w-[480px]'
|
||||
>
|
||||
<div className='flex pb-4 flex-col items-start gap-2 self-stretch'>
|
||||
<div className='text-text-primary title-2xl-semi-bold'>{t('app.newApp.appCreateDSLErrorTitle')}</div>
|
||||
<div className='flex flex-grow flex-col text-text-secondary system-md-regular'>
|
||||
<div>{t('app.newApp.appCreateDSLErrorPart1')}</div>
|
||||
<div>{t('app.newApp.appCreateDSLErrorPart2')}</div>
|
||||
<br />
|
||||
<div>{t('app.newApp.appCreateDSLErrorPart3')}<span className='system-md-medium'>{versions?.importedVersion}</span></div>
|
||||
<div>{t('app.newApp.appCreateDSLErrorPart4')}<span className='system-md-medium'>{versions?.systemVersion}</span></div>
|
||||
</div>
|
||||
</div>
|
||||
<Uploader
|
||||
file={currentFile}
|
||||
updateFile={handleFile}
|
||||
className='!mt-0'
|
||||
/>
|
||||
</div>
|
||||
<div className='flex justify-end'>
|
||||
<Button className='mr-2' onClick={onCancel}>{t('app.newApp.Cancel')}</Button>
|
||||
<Button
|
||||
disabled={!currentFile || loading}
|
||||
variant='warning'
|
||||
onClick={handleImport}
|
||||
loading={loading}
|
||||
>
|
||||
{t('workflow.common.overwriteAndImport')}
|
||||
</Button>
|
||||
</div>
|
||||
</Modal>
|
||||
<div className='flex pt-6 justify-end items-start gap-2 self-stretch'>
|
||||
<Button variant='secondary' onClick={() => setShowErrorModal(false)}>{t('app.newApp.Cancel')}</Button>
|
||||
<Button variant='primary' destructive onClick={onUpdateDSLConfirm}>{t('app.newApp.Confirm')}</Button>
|
||||
</div>
|
||||
</Modal>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user