import type { DuplicateAppModalProps } from '@/app/components/app/duplicate-modal' import type { CreateAppModalProps } from '@/app/components/explore/create-app-modal' import type { EnvironmentVariable } from '@/app/components/workflow/types' import { useCallback, useState } from 'react' import { useTranslation } from 'react-i18next' import { useStore as useAppStore } from '@/app/components/app/store' import { toast } from '@/app/components/base/ui/toast' import { NEED_REFRESH_APP_LIST_KEY } from '@/config' import { useProviderContext } from '@/context/provider-context' import { useRouter } from '@/next/navigation' import { copyApp, deleteApp, exportAppConfig, 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 { downloadBlob } from '@/utils/download' export type AppInfoModalType = 'edit' | 'duplicate' | 'delete' | 'switch' | 'importDSL' | 'exportWarning' | null type UseAppInfoActionsParams = { onDetailExpand?: (expand: boolean) => void } export function useAppInfoActions({ onDetailExpand }: UseAppInfoActionsParams) { const { t } = useTranslation() const { replace } = useRouter() const { onPlanInfoChanged } = useProviderContext() const appDetail = useAppStore(state => state.appDetail) const setAppDetail = useAppStore(state => state.setAppDetail) const invalidateAppList = useInvalidateAppList() const [panelOpen, setPanelOpen] = useState(false) const [activeModal, setActiveModal] = useState(null) const [secretEnvList, setSecretEnvList] = useState([]) const closePanel = useCallback(() => { setPanelOpen(false) onDetailExpand?.(false) }, [onDetailExpand]) const openModal = useCallback((modal: Exclude) => { closePanel() setActiveModal(modal) }, [closePanel]) const closeModal = useCallback(() => { setActiveModal(null) }, []) const onEdit: CreateAppModalProps['onConfirm'] = useCallback(async ({ name, icon_type, icon, icon_background, description, use_icon_as_answer_icon, max_active_requests, }) => { if (!appDetail) return try { const app = await updateAppInfo({ appID: appDetail.id, name, icon_type, icon, icon_background, description, use_icon_as_answer_icon, max_active_requests, }) closeModal() toast(t('editDone', { ns: 'app' }), { type: 'success' }) setAppDetail(app) } catch { toast(t('editFailed', { ns: 'app' }), { type: 'error' }) } }, [appDetail, closeModal, setAppDetail, t]) const onCopy: DuplicateAppModalProps['onConfirm'] = useCallback(async ({ name, icon_type, icon, icon_background, }) => { if (!appDetail) return try { const newApp = await copyApp({ appID: appDetail.id, name, icon_type, icon, icon_background, mode: appDetail.mode, }) closeModal() toast(t('newApp.appCreated', { ns: 'app' }), { type: 'success' }) localStorage.setItem(NEED_REFRESH_APP_LIST_KEY, '1') onPlanInfoChanged() getRedirection(true, newApp, replace) } catch { toast(t('newApp.appCreateFailed', { ns: 'app' }), { type: 'error' }) } }, [appDetail, closeModal, onPlanInfoChanged, replace, t]) const onExport = useCallback(async (include = false) => { if (!appDetail) return try { const { data } = await exportAppConfig({ appID: appDetail.id, include }) const file = new Blob([data], { type: 'application/yaml' }) downloadBlob({ data: file, fileName: `${appDetail.name}.yml` }) } catch { toast(t('exportFailed', { ns: 'app' }), { type: 'error' }) } }, [appDetail, t]) const exportCheck = useCallback(async () => { if (!appDetail) return if (appDetail.mode !== AppModeEnum.WORKFLOW && appDetail.mode !== AppModeEnum.ADVANCED_CHAT) { onExport() return } setActiveModal('exportWarning') }, [appDetail, onExport]) const handleConfirmExport = useCallback(async () => { if (!appDetail) return closeModal() try { const workflowDraft = await fetchWorkflowDraft(`/apps/${appDetail.id}/workflows/draft`) const list = (workflowDraft.environment_variables || []).filter(env => env.value_type === 'secret') if (list.length === 0) { onExport() return } setSecretEnvList(list) } catch { toast(t('exportFailed', { ns: 'app' }), { type: 'error' }) } }, [appDetail, closeModal, onExport, t]) const onConfirmDelete = useCallback(async () => { if (!appDetail) return try { await deleteApp(appDetail.id) toast(t('appDeleted', { ns: 'app' }), { type: 'success' }) invalidateAppList() onPlanInfoChanged() setAppDetail() replace('/apps') } catch (e: unknown) { toast(`${t('appDeleteFailed', { ns: 'app' })}${e instanceof Error && e.message ? `: ${e.message}` : ''}`, { type: 'error' }) } closeModal() }, [appDetail, closeModal, invalidateAppList, onPlanInfoChanged, replace, setAppDetail, t]) return { appDetail, panelOpen, setPanelOpen, closePanel, activeModal, openModal, closeModal, secretEnvList, setSecretEnvList, onEdit, onCopy, onExport, exportCheck, handleConfirmExport, onConfirmDelete, } }