import type { TFunction } from 'i18next' import type { StoreApi } from 'zustand' import type { Shape } from '@/app/components/workflow/store' import { useCallback, useEffect } from 'react' import Toast from '@/app/components/base/toast' import { useUpdateAppAssetFileContent } from '@/service/use-app-asset' type UseSkillFileSaveParams = { appId: string activeTabId: string | null isEditable: boolean dirtyContents: Map dirtyMetadataIds: Set originalContent: string currentMetadata: Record | undefined storeApi: StoreApi t: TFunction<'workflow'> } /** * Hook to handle file save logic and Ctrl+S keyboard shortcut. * Returns the save handler function. */ export function useSkillFileSave({ appId, activeTabId, isEditable, dirtyContents, dirtyMetadataIds, originalContent, currentMetadata, storeApi, t, }: UseSkillFileSaveParams): () => Promise { const updateContent = useUpdateAppAssetFileContent() const handleSave = useCallback(async () => { if (!activeTabId || !appId || !isEditable) return const content = dirtyContents.get(activeTabId) const hasDirtyMetadata = dirtyMetadataIds.has(activeTabId) if (content === undefined && !hasDirtyMetadata) return try { await updateContent.mutateAsync({ appId, nodeId: activeTabId, payload: { content: content ?? originalContent, ...(currentMetadata ? { metadata: currentMetadata } : {}), }, }) storeApi.getState().clearDraftContent(activeTabId) storeApi.getState().clearDraftMetadata(activeTabId) Toast.notify({ type: 'success', message: t('api.saved', { ns: 'common' }), }) } catch (error) { Toast.notify({ type: 'error', message: String(error), }) } }, [activeTabId, appId, currentMetadata, dirtyContents, dirtyMetadataIds, isEditable, originalContent, storeApi, t, updateContent]) useEffect(() => { function handleKeyDown(e: KeyboardEvent): void { if ((e.ctrlKey || e.metaKey) && e.key === 's') { e.preventDefault() handleSave() } } window.addEventListener('keydown', handleKeyDown) return () => window.removeEventListener('keydown', handleKeyDown) }, [handleSave]) return handleSave }