mirror of
https://github.com/langgenius/dify.git
synced 2026-04-28 06:28:05 +08:00
feat: combine 2 export
This commit is contained in:
@ -1,8 +1,9 @@
|
||||
'use client'
|
||||
|
||||
import type { AppIconSelection } from '../../base/app-icon-picker'
|
||||
import { RiArrowRightLine, RiArrowRightSLine, RiCheckLine, RiCommandLine, RiCornerDownLeftLine, RiExchange2Fill } from '@remixicon/react'
|
||||
import type { RuntimeMode } from '@/types/app'
|
||||
|
||||
import { RiArrowRightLine, RiArrowRightSLine, RiCheckLine, RiCommandLine, RiCornerDownLeftLine, RiExchange2Fill } from '@remixicon/react'
|
||||
import { useDebounceFn, useKeyPress } from 'ahooks'
|
||||
import Image from 'next/image'
|
||||
import { useRouter } from 'next/navigation'
|
||||
@ -39,8 +40,6 @@ type CreateAppProps = {
|
||||
defaultAppMode?: AppModeEnum
|
||||
}
|
||||
|
||||
type RuntimeMode = 'sandboxed' | 'classic'
|
||||
|
||||
type RuntimeOption = {
|
||||
label: string
|
||||
value: RuntimeMode
|
||||
|
||||
@ -42,6 +42,7 @@ const createMockApp = (overrides: Partial<App> = {}): App => ({
|
||||
icon_url: null,
|
||||
use_icon_as_answer_icon: false,
|
||||
mode: AppModeEnum.CHAT,
|
||||
runtime_type: 'classic' as const,
|
||||
enable_site: true,
|
||||
enable_api: true,
|
||||
api_rpm: 60,
|
||||
|
||||
@ -89,6 +89,7 @@ const createMockApp = (overrides: Partial<App> = {}): App => ({
|
||||
icon_url: null,
|
||||
use_icon_as_answer_icon: false,
|
||||
mode: AppModeEnum.COMPLETION,
|
||||
runtime_type: 'classic' as const,
|
||||
enable_site: true,
|
||||
enable_api: true,
|
||||
api_rpm: 60,
|
||||
|
||||
@ -69,6 +69,7 @@ const createMockApp = (overrides: Partial<App> = {}): App => ({
|
||||
icon_url: null,
|
||||
use_icon_as_answer_icon: false,
|
||||
mode: 'workflow' as AppModeEnum,
|
||||
runtime_type: 'classic' as const,
|
||||
enable_site: true,
|
||||
enable_api: true,
|
||||
api_rpm: 60,
|
||||
|
||||
@ -170,6 +170,7 @@ const createMockApp = (overrides: Partial<App> = {}): App => ({
|
||||
icon_url: null,
|
||||
use_icon_as_answer_icon: false,
|
||||
mode: 'workflow' as AppModeEnum,
|
||||
runtime_type: 'classic' as const,
|
||||
enable_site: true,
|
||||
enable_api: true,
|
||||
api_rpm: 60,
|
||||
|
||||
@ -101,6 +101,7 @@ const createMockApp = (overrides: Partial<App> = {}): App => ({
|
||||
icon_url: null,
|
||||
use_icon_as_answer_icon: false,
|
||||
mode: 'workflow' as AppModeEnum,
|
||||
runtime_type: 'classic' as const,
|
||||
enable_site: true,
|
||||
enable_api: true,
|
||||
api_rpm: 60,
|
||||
|
||||
@ -2,13 +2,13 @@ import type { Mock } from 'vitest'
|
||||
import { fireEvent, render, screen, waitFor } from '@testing-library/react'
|
||||
import * as React from 'react'
|
||||
import { AccessMode } from '@/models/access-control'
|
||||
|
||||
// Mock API services - import for direct manipulation
|
||||
import * as appsService from '@/service/apps'
|
||||
|
||||
import * as exploreService from '@/service/explore'
|
||||
import * as workflowService from '@/service/workflow'
|
||||
import { AppModeEnum } from '@/types/app'
|
||||
|
||||
import { AppModeEnum } from '@/types/app'
|
||||
// Import component after mocks
|
||||
import AppCard from './app-card'
|
||||
|
||||
@ -212,6 +212,7 @@ const createMockApp = (overrides: Record<string, any> = {}) => ({
|
||||
name: 'Test App',
|
||||
description: 'Test app description',
|
||||
mode: AppModeEnum.CHAT,
|
||||
runtime_type: 'classic' as const,
|
||||
icon: '🤖',
|
||||
icon_type: 'emoji' as const,
|
||||
icon_background: '#FFEAD5',
|
||||
|
||||
@ -157,6 +157,14 @@ const AppCard = ({ app, onRefresh }: AppCardProps) => {
|
||||
|
||||
const onExport = async (include = false) => {
|
||||
try {
|
||||
const isDownLoadBundle = app.runtime_type === 'sandboxed'
|
||||
if (isDownLoadBundle) {
|
||||
await exportAppBundle({
|
||||
appID: app.id,
|
||||
include,
|
||||
})
|
||||
return
|
||||
}
|
||||
const { data } = await exportAppConfig({
|
||||
appID: app.id,
|
||||
include,
|
||||
@ -165,12 +173,15 @@ const AppCard = ({ app, onRefresh }: AppCardProps) => {
|
||||
const file = new Blob([data], { type: 'application/yaml' })
|
||||
const url = URL.createObjectURL(file)
|
||||
a.href = url
|
||||
a.download = `${app.name}.yml`
|
||||
a.download = `${app.name}.${isDownLoadBundle ? 'zip' : 'yaml'}`
|
||||
a.click()
|
||||
URL.revokeObjectURL(url)
|
||||
}
|
||||
catch {
|
||||
notify({ type: 'error', message: t('exportFailed', { ns: 'app' }) })
|
||||
notify({
|
||||
type: 'error',
|
||||
message: t('exportFailed', { ns: 'app' }),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -189,41 +200,11 @@ const AppCard = ({ app, onRefresh }: AppCardProps) => {
|
||||
setSecretEnvList(list)
|
||||
}
|
||||
catch {
|
||||
notify({ type: 'error', message: t('exportFailed', { ns: 'app' }) })
|
||||
}
|
||||
}
|
||||
|
||||
const onExportBundle = async (include = false) => {
|
||||
try {
|
||||
await exportAppBundle({
|
||||
appID: app.id,
|
||||
include,
|
||||
notify({
|
||||
type: 'error',
|
||||
message: t('exportFailed', { ns: 'app' }),
|
||||
})
|
||||
}
|
||||
catch {
|
||||
notify({ type: 'error', message: t('exportBundleFailed', { ns: 'app' }) })
|
||||
}
|
||||
}
|
||||
|
||||
const [secretEnvListForBundle, setSecretEnvListForBundle] = useState<EnvironmentVariable[]>([])
|
||||
|
||||
const exportBundleCheck = async () => {
|
||||
if (app.mode !== AppModeEnum.WORKFLOW && app.mode !== AppModeEnum.ADVANCED_CHAT) {
|
||||
onExportBundle()
|
||||
return
|
||||
}
|
||||
try {
|
||||
const workflowDraft = await fetchWorkflowDraft(`/apps/${app.id}/workflows/draft`)
|
||||
const list = (workflowDraft.environment_variables || []).filter(env => env.value_type === 'secret')
|
||||
if (list.length === 0) {
|
||||
onExportBundle()
|
||||
return
|
||||
}
|
||||
setSecretEnvListForBundle(list)
|
||||
}
|
||||
catch {
|
||||
notify({ type: 'error', message: t('exportBundleFailed', { ns: 'app' }) })
|
||||
}
|
||||
}
|
||||
|
||||
const onSwitch = () => {
|
||||
@ -261,12 +242,6 @@ const AppCard = ({ app, onRefresh }: AppCardProps) => {
|
||||
e.preventDefault()
|
||||
exportCheck()
|
||||
}
|
||||
const onClickExportBundle = async (e: React.MouseEvent<HTMLButtonElement>) => {
|
||||
e.stopPropagation()
|
||||
props.onClick?.()
|
||||
e.preventDefault()
|
||||
exportBundleCheck()
|
||||
}
|
||||
const onClickSwitch = async (e: React.MouseEvent<HTMLButtonElement>) => {
|
||||
e.stopPropagation()
|
||||
props.onClick?.()
|
||||
@ -317,9 +292,6 @@ const AppCard = ({ app, onRefresh }: AppCardProps) => {
|
||||
<button type="button" className="mx-1 flex h-8 cursor-pointer items-center gap-2 rounded-lg px-3 hover:bg-state-base-hover" onClick={onClickExport}>
|
||||
<span className="system-sm-regular text-text-secondary">{t('export', { ns: 'app' })}</span>
|
||||
</button>
|
||||
<button type="button" className="mx-1 flex h-8 cursor-pointer items-center gap-2 rounded-lg px-3 hover:bg-state-base-hover" onClick={onClickExportBundle}>
|
||||
<span className="system-sm-regular text-text-secondary">{t('exportBundle', { ns: 'app' })}</span>
|
||||
</button>
|
||||
{(app.mode === AppModeEnum.COMPLETION || app.mode === AppModeEnum.CHAT) && (
|
||||
<>
|
||||
<Divider className="my-1" />
|
||||
@ -556,13 +528,6 @@ const AppCard = ({ app, onRefresh }: AppCardProps) => {
|
||||
onClose={() => setSecretEnvList([])}
|
||||
/>
|
||||
)}
|
||||
{secretEnvListForBundle.length > 0 && (
|
||||
<DSLExportConfirmModal
|
||||
envList={secretEnvListForBundle}
|
||||
onConfirm={onExportBundle}
|
||||
onClose={() => setSecretEnvListForBundle([])}
|
||||
/>
|
||||
)}
|
||||
{showAccessControl && (
|
||||
<AccessControl app={app} onConfirm={onUpdateAccessControl} onClose={() => setShowAccessControl(false)} />
|
||||
)}
|
||||
|
||||
@ -19,6 +19,7 @@ const createMockApp = (overrides: Partial<App> = {}): App => ({
|
||||
icon_url: null,
|
||||
use_icon_as_answer_icon: false,
|
||||
mode: AppModeEnum.COMPLETION,
|
||||
runtime_type: 'classic' as const,
|
||||
enable_site: true,
|
||||
enable_api: true,
|
||||
api_rpm: 60,
|
||||
|
||||
@ -45,10 +45,8 @@
|
||||
"editAppTitle": "Edit App Info",
|
||||
"editDone": "App info updated",
|
||||
"editFailed": "Failed to update app info",
|
||||
"export": "Export DSL",
|
||||
"exportBundle": "Export Bundle",
|
||||
"exportBundleFailed": "Export bundle failed.",
|
||||
"exportFailed": "Export DSL failed.",
|
||||
"export": "Export",
|
||||
"exportFailed": "Export failed.",
|
||||
"gotoAnything.actions.accountDesc": "Navigate to account page",
|
||||
"gotoAnything.actions.communityDesc": "Open Discord community",
|
||||
"gotoAnything.actions.docDesc": "Open help documentation",
|
||||
|
||||
@ -45,10 +45,8 @@
|
||||
"editAppTitle": "编辑应用信息",
|
||||
"editDone": "应用信息已更新",
|
||||
"editFailed": "更新应用信息失败",
|
||||
"export": "导出 DSL",
|
||||
"exportBundle": "导出 Bundle",
|
||||
"exportBundleFailed": "导出 Bundle 失败",
|
||||
"exportFailed": "导出 DSL 失败",
|
||||
"export": "导出",
|
||||
"exportFailed": "导出 失败",
|
||||
"gotoAnything.actions.accountDesc": "导航到账户页面",
|
||||
"gotoAnything.actions.communityDesc": "打开 Discord 社区",
|
||||
"gotoAnything.actions.docDesc": "打开帮助文档",
|
||||
|
||||
@ -317,6 +317,7 @@ export type SiteConfig = {
|
||||
}
|
||||
|
||||
export type AppIconType = 'image' | 'emoji' | 'link'
|
||||
export type RuntimeMode = 'sandboxed' | 'classic'
|
||||
|
||||
/**
|
||||
* App
|
||||
@ -347,6 +348,7 @@ export type App = {
|
||||
|
||||
/** Mode */
|
||||
mode: AppModeEnum
|
||||
runtime_type: RuntimeMode
|
||||
/** Enable web app */
|
||||
enable_site: boolean
|
||||
/** Enable web API */
|
||||
|
||||
Reference in New Issue
Block a user