feat: combine 2 export

This commit is contained in:
Joel
2026-01-23 15:50:06 +08:00
parent c4714d757d
commit b5d843b1fd
12 changed files with 33 additions and 64 deletions

View File

@ -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

View File

@ -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,

View File

@ -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,

View File

@ -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,

View File

@ -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,

View File

@ -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,

View File

@ -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',

View File

@ -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)} />
)}

View File

@ -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,

View File

@ -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",

View File

@ -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": "打开帮助文档",

View File

@ -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 */