feat: Refactor app export to support sandboxed bundle format

This commit is contained in:
zhsama
2026-01-29 23:36:19 +08:00
parent 9b62be2eb1
commit 20a4a83129
5 changed files with 47 additions and 30 deletions

View File

@ -27,12 +27,13 @@ import { webSocketClient } from '@/app/components/workflow/collaboration/core/we
import { NEED_REFRESH_APP_LIST_KEY } from '@/config'
import { useAppContext } from '@/context/app-context'
import { useProviderContext } from '@/context/provider-context'
import { copyApp, deleteApp, exportAppConfig, fetchAppDetail, updateAppInfo } from '@/service/apps'
import { copyApp, deleteApp, exportAppBundle, exportAppConfig, fetchAppDetail, updateAppInfo } from '@/service/apps'
import { useInvalidateAppList } from '@/service/use-apps'
import { fetchWorkflowDraft } from '@/service/workflow'
import { AppModeEnum } from '@/types/app'
import { getRedirection } from '@/utils/app-redirection'
import { cn } from '@/utils/classnames'
import { downloadBlob } from '@/utils/download'
import AppIcon from '../base/app-icon'
import AppOperations from './app-operations'
@ -78,6 +79,7 @@ const AppInfo = ({ expand, onlyShowDetail = false, openState = false, onDetailEx
const [showImportDSLModal, setShowImportDSLModal] = useState<boolean>(false)
const [secretEnvList, setSecretEnvList] = useState<EnvironmentVariable[]>([])
const [showExportWarning, setShowExportWarning] = useState(false)
const [exportSandboxed, setExportSandboxed] = useState(false)
const emitAppMetaUpdate = useCallback(() => {
if (!appDetail?.id)
@ -153,21 +155,23 @@ const AppInfo = ({ expand, onlyShowDetail = false, openState = false, onDetailEx
}
}
const onExport = async (include = false) => {
const onExport = async (include = false, sandboxed = false) => {
if (!appDetail)
return
try {
if (sandboxed) {
await exportAppBundle({
appID: appDetail.id,
include,
})
return
}
const { data } = await exportAppConfig({
appID: appDetail.id,
include,
})
const a = document.createElement('a')
const file = new Blob([data], { type: 'application/yaml' })
const url = URL.createObjectURL(file)
a.href = url
a.download = `${appDetail.name}.yml`
a.click()
URL.revokeObjectURL(url)
downloadBlob({ data: file, fileName: `${appDetail.name}.yaml` })
}
catch {
notify({ type: 'error', message: t('exportFailed', { ns: 'app' }) })
@ -178,7 +182,7 @@ const AppInfo = ({ expand, onlyShowDetail = false, openState = false, onDetailEx
if (!appDetail)
return
if (appDetail.mode !== AppModeEnum.WORKFLOW && appDetail.mode !== AppModeEnum.ADVANCED_CHAT) {
onExport()
onExport(false, false)
return
}
@ -192,11 +196,13 @@ const AppInfo = ({ expand, onlyShowDetail = false, openState = false, onDetailEx
try {
const workflowDraft = await fetchWorkflowDraft(`/apps/${appDetail.id}/workflows/draft`)
const list = (workflowDraft.environment_variables || []).filter(env => env.value_type === 'secret')
const sandboxed = workflowDraft.features?.sandbox?.enabled === true
if (list.length === 0) {
onExport()
onExport(false, sandboxed)
return
}
setSecretEnvList(list)
setExportSandboxed(sandboxed)
}
catch {
notify({ type: 'error', message: t('exportFailed', { ns: 'app' }) })
@ -490,7 +496,7 @@ const AppInfo = ({ expand, onlyShowDetail = false, openState = false, onDetailEx
{secretEnvList.length > 0 && (
<DSLExportConfirmModal
envList={secretEnvList}
onConfirm={onExport}
onConfirm={include => onExport(include, exportSandboxed)}
onClose={() => setSecretEnvList([])}
/>
)}

View File

@ -73,6 +73,7 @@ type WorkflowChildrenProps = {
const WorkflowChildren = ({ headerLeftSlot }: WorkflowChildrenProps) => {
const { eventEmitter } = useEventEmitterContextContext()
const [secretEnvList, setSecretEnvList] = useState<EnvironmentVariable[]>([])
const [exportSandboxed, setExportSandboxed] = useState(false)
const showFeaturesPanel = useStore(s => s.showFeaturesPanel)
const showImportDSLModal = useStore(s => s.showImportDSLModal)
const setShowImportDSLModal = useStore(s => s.setShowImportDSLModal)
@ -93,8 +94,10 @@ const WorkflowChildren = ({ headerLeftSlot }: WorkflowChildrenProps) => {
} = useDSL()
eventEmitter?.useSubscription((v: any) => {
if (v.type === DSL_EXPORT_CHECK)
if (v.type === DSL_EXPORT_CHECK) {
setSecretEnvList(v.payload.data as EnvironmentVariable[])
setExportSandboxed(v.payload.sandboxed || false)
}
})
const autoGenerateWebhookUrl = useAutoGenerateWebhookUrl()
@ -188,7 +191,7 @@ const WorkflowChildren = ({ headerLeftSlot }: WorkflowChildrenProps) => {
secretEnvList.length > 0 && (
<DSLExportConfirmModal
envList={secretEnvList}
onConfirm={handleExportDSL!}
onConfirm={include => handleExportDSL!(include, undefined, exportSandboxed)}
onClose={() => setSecretEnvList([])}
/>
)

View File

@ -9,8 +9,9 @@ import {
DSL_EXPORT_CHECK,
} from '@/app/components/workflow/constants'
import { useEventEmitterContextContext } from '@/context/event-emitter'
import { exportAppConfig } from '@/service/apps'
import { exportAppBundle, exportAppConfig } from '@/service/apps'
import { fetchWorkflowDraft } from '@/service/workflow'
import { downloadBlob } from '@/utils/download'
import { useNodesSyncDraft } from './use-nodes-sync-draft'
export const useDSL = () => {
@ -22,7 +23,7 @@ export const useDSL = () => {
const appDetail = useAppStore(s => s.appDetail)
const handleExportDSL = useCallback(async (include = false, workflowId?: string) => {
const handleExportDSL = useCallback(async (include = false, workflowId?: string, sandboxed = false) => {
if (!appDetail)
return
@ -32,18 +33,23 @@ export const useDSL = () => {
try {
setExporting(true)
await doSyncWorkflowDraft()
const { data } = await exportAppConfig({
appID: appDetail.id,
include,
workflowID: workflowId,
})
const a = document.createElement('a')
const file = new Blob([data], { type: 'application/yaml' })
const url = URL.createObjectURL(file)
a.href = url
a.download = `${appDetail.name}.yml`
a.click()
URL.revokeObjectURL(url)
if (sandboxed) {
await exportAppBundle({
appID: appDetail.id,
include,
workflowID: workflowId,
})
}
else {
const { data } = await exportAppConfig({
appID: appDetail.id,
include,
workflowID: workflowId,
})
const file = new Blob([data], { type: 'application/yaml' })
downloadBlob({ data: file, fileName: `${appDetail.name}.yaml` })
}
}
catch {
notify({ type: 'error', message: t('exportFailed', { ns: 'app' }) })
@ -58,15 +64,17 @@ export const useDSL = () => {
return
try {
const workflowDraft = await fetchWorkflowDraft(`/apps/${appDetail?.id}/workflows/draft`)
const sandboxed = workflowDraft.features?.sandbox?.enabled === true
const list = (workflowDraft.environment_variables || []).filter(env => env.value_type === 'secret')
if (list.length === 0) {
handleExportDSL()
handleExportDSL(false, undefined, sandboxed)
return
}
eventEmitter?.emit({
type: DSL_EXPORT_CHECK,
payload: {
data: list,
sandboxed,
},
} as any)
}

View File

@ -61,7 +61,7 @@ export type CommonHooksFnMap = {
availableNodesMetaData?: AvailableNodesMetaData
getWorkflowRunAndTraceUrl: (runId?: string) => { runUrl: string, traceUrl: string }
exportCheck?: () => Promise<void>
handleExportDSL?: (include?: boolean, flowId?: string) => Promise<void>
handleExportDSL?: (include?: boolean, flowId?: string, sandboxed?: boolean) => Promise<void>
fetchInspectVars: (params: { passInVars?: boolean, vars?: VarInInspect[], passedInAllPluginInfoList?: Record<string, ToolWithProvider[]>, passedInSchemaTypeDefinitions?: SchemaTypeDefinition[] }) => Promise<void>
hasNodeInspectVars: (nodeId: string) => boolean
hasSetInspectVar: (nodeId: string, name: string, sysVars: VarInInspect[], conversationVars: VarInInspect[]) => boolean

View File

@ -126,7 +126,7 @@ export const VersionHistoryPanel = ({
})
break
case VersionHistoryContextMenuOptions.exportDSL:
handleExportDSL?.(false, item.id)
handleExportDSL?.(false, item.id, item.features?.sandbox?.enabled === true)
break
}
}, [t, handleExportDSL])