diff --git a/src/App.tsx b/src/App.tsx index 548cfcc5..c2938e3d 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,5 +1,5 @@ import { useEffect } from 'react'; -import { HashRouter, Route, Routes } from 'react-router-dom'; +import { Outlet, RouterProvider, createHashRouter } from 'react-router-dom'; import { LoginPage } from '@/pages/LoginPage'; import { NotificationContainer } from '@/components/common/NotificationContainer'; import { ConfirmationModal } from '@/components/common/ConfirmationModal'; @@ -7,6 +7,33 @@ import { MainLayout } from '@/components/layout/MainLayout'; import { ProtectedRoute } from '@/router/ProtectedRoute'; import { useLanguageStore, useThemeStore } from '@/stores'; +function RootShell() { + return ( + <> + + + + + ); +} + +const router = createHashRouter([ + { + element: , + children: [ + { path: '/login', element: }, + { + path: '/*', + element: ( + + + + ), + }, + ], + }, +]); + function App() { const initializeTheme = useThemeStore((state) => state.initializeTheme); const language = useLanguageStore((state) => state.language); @@ -26,23 +53,7 @@ function App() { document.documentElement.lang = language; }, [language]); - return ( - - - - - } /> - - - - } - /> - - - ); + return ; } export default App; diff --git a/src/hooks/useUnsavedChangesGuard.ts b/src/hooks/useUnsavedChangesGuard.ts index 4eddb866..14e2cf2a 100644 --- a/src/hooks/useUnsavedChangesGuard.ts +++ b/src/hooks/useUnsavedChangesGuard.ts @@ -1,6 +1,6 @@ import { useCallback, useEffect, useMemo, useRef } from 'react'; import type { BlockerFunction } from 'react-router'; -import { useBlocker } from 'react-router'; +import { useBlocker, useLocation } from 'react-router'; import { useNotificationStore } from '@/stores'; type ConfirmationVariant = 'danger' | 'primary' | 'secondary'; @@ -23,9 +23,18 @@ export function useUnsavedChangesGuard(options: UseUnsavedChangesGuardOptions) { const { enabled = true, shouldBlock, dialog } = options; const { showConfirmation } = useNotificationStore(); const lastBlockedRef = useRef(''); + const allowNextNavigationRef = useRef(false); + const location = useLocation(); + + const allowNextNavigation = useCallback(() => { + allowNextNavigationRef.current = true; + }, []); const shouldBlockFunction = useCallback( (args) => { + if (allowNextNavigationRef.current) { + return false; + } if (!enabled) return false; return typeof shouldBlock === 'function' ? shouldBlock(args) : shouldBlock; }, @@ -34,6 +43,11 @@ export function useUnsavedChangesGuard(options: UseUnsavedChangesGuardOptions) { const blocker = useBlocker(shouldBlockFunction); + useEffect(() => { + if (!allowNextNavigationRef.current) return; + allowNextNavigationRef.current = false; + }, [location.key]); + const blockedKey = useMemo(() => { if (blocker.state !== 'blocked' || !blocker.location) return ''; return `${blocker.location.pathname}${blocker.location.search}${blocker.location.hash}`; @@ -60,5 +74,6 @@ export function useUnsavedChangesGuard(options: UseUnsavedChangesGuardOptions) { onCancel: () => blocker.reset(), }); }, [blockedKey, blocker, dialog, showConfirmation]); -} + return { allowNextNavigation }; +} diff --git a/src/pages/AiProvidersAmpcodeEditPage.tsx b/src/pages/AiProvidersAmpcodeEditPage.tsx index a416e8a6..fc98a271 100644 --- a/src/pages/AiProvidersAmpcodeEditPage.tsx +++ b/src/pages/AiProvidersAmpcodeEditPage.tsx @@ -134,7 +134,7 @@ export function AiProvidersAmpcodeEditPage() { const isDirty = baselineSignature !== currentSignature; const canGuard = !loading && !saving; - useUnsavedChangesGuard({ + const { allowNextNavigation } = useUnsavedChangesGuard({ enabled: canGuard, shouldBlock: ({ currentLocation, nextLocation }) => isDirty && currentLocation.pathname !== nextLocation.pathname, @@ -234,6 +234,8 @@ export function AiProvidersAmpcodeEditPage() { updateConfigValue('ampcode', next); clearCache('ampcode'); showNotification(t('notification.ampcode_updated'), 'success'); + allowNextNavigation(); + setBaselineSignature(buildAmpcodeSignature(form)); handleBack(); } catch (err: unknown) { const message = getErrorMessage(err); diff --git a/src/pages/AiProvidersClaudeEditLayout.tsx b/src/pages/AiProvidersClaudeEditLayout.tsx index cc9296c9..1f811866 100644 --- a/src/pages/AiProvidersClaudeEditLayout.tsx +++ b/src/pages/AiProvidersClaudeEditLayout.tsx @@ -139,8 +139,6 @@ export function AiProvidersClaudeEditLayout() { const [configs, setConfigs] = useState(() => config?.claudeApiKeys ?? []); const [loading, setLoading] = useState(() => !isCacheValid('claude-api-key')); const [saving, setSaving] = useState(false); - const [baselineDraftKey, setBaselineDraftKey] = useState(''); - const [baselineSignature, setBaselineSignature] = useState(''); const draftKey = useMemo(() => { if (invalidIndexParam) return `claude:invalid:${params.index ?? 'unknown'}`; @@ -149,9 +147,10 @@ export function AiProvidersClaudeEditLayout() { }, [editIndex, invalidIndexParam, params.index]); const draft = useClaudeEditDraftStore((state) => state.drafts[draftKey]); - const ensureDraft = useClaudeEditDraftStore((state) => state.ensureDraft); + const acquireDraft = useClaudeEditDraftStore((state) => state.acquireDraft); + const releaseDraft = useClaudeEditDraftStore((state) => state.releaseDraft); const initDraft = useClaudeEditDraftStore((state) => state.initDraft); - const clearDraft = useClaudeEditDraftStore((state) => state.clearDraft); + const setDraftBaselineSignature = useClaudeEditDraftStore((state) => state.setDraftBaselineSignature); const setDraftForm = useClaudeEditDraftStore((state) => state.setDraftForm); const setDraftTestModel = useClaudeEditDraftStore((state) => state.setDraftTestModel); const setDraftTestStatus = useClaudeEditDraftStore((state) => state.setDraftTestStatus); @@ -203,8 +202,9 @@ export function AiProvidersClaudeEditLayout() { ); useEffect(() => { - ensureDraft(draftKey); - }, [draftKey, ensureDraft]); + acquireDraft(draftKey); + return () => releaseDraft(draftKey); + }, [acquireDraft, draftKey, releaseDraft]); const handleBack = useCallback(() => { const state = location.state as LocationState; @@ -215,12 +215,6 @@ export function AiProvidersClaudeEditLayout() { navigate('/ai-providers', { replace: true }); }, [location.state, navigate]); - useEffect(() => { - return () => { - clearDraft(draftKey); - }; - }, [clearDraft, draftKey]); - useEffect(() => { let cancelled = false; const hasValidCache = isCacheValid('claude-api-key'); @@ -260,7 +254,9 @@ export function AiProvidersClaudeEditLayout() { excludedText: excludedModelsToText(initialData.excludedModels), }; const available = seededForm.modelEntries.map((entry) => entry.name.trim()).filter(Boolean); + const baselineSignature = buildClaudeSignature(seededForm); initDraft(draftKey, { + baselineSignature, form: seededForm, testModel: available[0] || '', testStatus: 'idle', @@ -269,8 +265,10 @@ export function AiProvidersClaudeEditLayout() { return; } + const emptyForm = buildEmptyForm(); initDraft(draftKey, { - form: buildEmptyForm(), + baselineSignature: buildClaudeSignature(emptyForm), + form: emptyForm, testModel: '', testStatus: 'idle', testMessage: '', @@ -279,15 +277,8 @@ export function AiProvidersClaudeEditLayout() { const resolvedLoading = !draft?.initialized; const currentSignature = useMemo(() => buildClaudeSignature(form), [form]); - - useEffect(() => { - if (resolvedLoading) return; - if (baselineDraftKey === draftKey) return; - setBaselineDraftKey(draftKey); - setBaselineSignature(currentSignature); - }, [baselineDraftKey, currentSignature, draftKey, resolvedLoading]); - - const isDirty = baselineDraftKey === draftKey && baselineSignature !== currentSignature; + const baselineSignature = draft?.baselineSignature ?? ''; + const isDirty = Boolean(draft?.initialized) && baselineSignature !== currentSignature; const editorRootPath = useMemo(() => { if (hasIndexParam) { return `/ai-providers/claude/${params.index ?? ''}`; @@ -296,7 +287,7 @@ export function AiProvidersClaudeEditLayout() { }, [hasIndexParam, params.index]); const canGuard = !resolvedLoading && !saving && !invalidIndexParam && !invalidIndex; - useUnsavedChangesGuard({ + const { allowNextNavigation } = useUnsavedChangesGuard({ enabled: canGuard, shouldBlock: ({ nextLocation }) => { const nextPath = nextLocation.pathname; @@ -405,6 +396,8 @@ export function AiProvidersClaudeEditLayout() { editIndex !== null ? t('notification.claude_config_updated') : t('notification.claude_config_added'), 'success' ); + allowNextNavigation(); + setDraftBaselineSignature(draftKey, buildClaudeSignature(form)); handleBack(); } catch (err: unknown) { showNotification(`${t('notification.update_failed')}: ${getErrorMessage(err)}`, 'error'); @@ -412,8 +405,10 @@ export function AiProvidersClaudeEditLayout() { setSaving(false); } }, [ + allowNextNavigation, clearCache, configs, + draftKey, disableControls, editIndex, form, @@ -421,6 +416,7 @@ export function AiProvidersClaudeEditLayout() { invalidIndex, invalidIndexParam, resolvedLoading, + setDraftBaselineSignature, saving, showNotification, t, diff --git a/src/pages/AiProvidersCodexEditPage.tsx b/src/pages/AiProvidersCodexEditPage.tsx index 7d98bbbf..6691fd25 100644 --- a/src/pages/AiProvidersCodexEditPage.tsx +++ b/src/pages/AiProvidersCodexEditPage.tsx @@ -202,7 +202,7 @@ export function AiProvidersCodexEditPage() { const isDirty = baselineSignature !== currentSignature; const canGuard = !loading && !saving && !invalidIndexParam && !invalidIndex; - useUnsavedChangesGuard({ + const { allowNextNavigation } = useUnsavedChangesGuard({ enabled: canGuard, shouldBlock: ({ currentLocation, nextLocation }) => isDirty && currentLocation.pathname !== nextLocation.pathname, @@ -381,6 +381,8 @@ export function AiProvidersCodexEditPage() { editIndex !== null ? t('notification.codex_config_updated') : t('notification.codex_config_added'), 'success' ); + allowNextNavigation(); + setBaselineSignature(buildCodexSignature(form)); handleBack(); } catch (err: unknown) { const message = err instanceof Error ? err.message : ''; @@ -390,6 +392,7 @@ export function AiProvidersCodexEditPage() { setSaving(false); } }, [ + allowNextNavigation, canSave, clearCache, configs, diff --git a/src/pages/AiProvidersGeminiEditPage.tsx b/src/pages/AiProvidersGeminiEditPage.tsx index 3b77294c..84fe6e9a 100644 --- a/src/pages/AiProvidersGeminiEditPage.tsx +++ b/src/pages/AiProvidersGeminiEditPage.tsx @@ -335,7 +335,7 @@ export function AiProvidersGeminiEditPage() { const isDirty = baselineSignature !== currentSignature; const canGuard = !loading && !saving && !invalidIndexParam && !invalidIndex; - useUnsavedChangesGuard({ + const { allowNextNavigation } = useUnsavedChangesGuard({ enabled: canGuard, shouldBlock: ({ currentLocation, nextLocation }) => isDirty && currentLocation.pathname !== nextLocation.pathname, @@ -382,6 +382,8 @@ export function AiProvidersGeminiEditPage() { editIndex !== null ? t('notification.gemini_key_updated') : t('notification.gemini_key_added'), 'success' ); + allowNextNavigation(); + setBaselineSignature(buildGeminiSignature(form)); handleBack(); } catch (err: unknown) { const message = err instanceof Error ? err.message : ''; @@ -391,6 +393,7 @@ export function AiProvidersGeminiEditPage() { setSaving(false); } }, [ + allowNextNavigation, canSave, clearCache, configs, diff --git a/src/pages/AiProvidersOpenAIEditLayout.tsx b/src/pages/AiProvidersOpenAIEditLayout.tsx index aaa46b1e..2875aaa7 100644 --- a/src/pages/AiProvidersOpenAIEditLayout.tsx +++ b/src/pages/AiProvidersOpenAIEditLayout.tsx @@ -154,8 +154,6 @@ export function AiProvidersOpenAIEditLayout() { () => !isCacheValid('openai-compatibility') ); const [saving, setSaving] = useState(false); - const [baselineDraftKey, setBaselineDraftKey] = useState(''); - const [baselineSignature, setBaselineSignature] = useState(''); const draftKey = useMemo(() => { if (invalidIndexParam) return `openai:invalid:${params.index ?? 'unknown'}`; @@ -164,9 +162,10 @@ export function AiProvidersOpenAIEditLayout() { }, [editIndex, invalidIndexParam, params.index]); const draft = useOpenAIEditDraftStore((state) => state.drafts[draftKey]); - const ensureDraft = useOpenAIEditDraftStore((state) => state.ensureDraft); + const acquireDraft = useOpenAIEditDraftStore((state) => state.acquireDraft); + const releaseDraft = useOpenAIEditDraftStore((state) => state.releaseDraft); const initDraft = useOpenAIEditDraftStore((state) => state.initDraft); - const clearDraft = useOpenAIEditDraftStore((state) => state.clearDraft); + const setDraftBaselineSignature = useOpenAIEditDraftStore((state) => state.setDraftBaselineSignature); const setDraftForm = useOpenAIEditDraftStore((state) => state.setDraftForm); const setDraftTestModel = useOpenAIEditDraftStore((state) => state.setDraftTestModel); const setDraftTestStatus = useOpenAIEditDraftStore((state) => state.setDraftTestStatus); @@ -236,8 +235,9 @@ export function AiProvidersOpenAIEditLayout() { ); useEffect(() => { - ensureDraft(draftKey); - }, [draftKey, ensureDraft]); + acquireDraft(draftKey); + return () => releaseDraft(draftKey); + }, [acquireDraft, draftKey, releaseDraft]); const handleBack = useCallback(() => { const state = location.state as LocationState; @@ -248,12 +248,6 @@ export function AiProvidersOpenAIEditLayout() { navigate('/ai-providers', { replace: true }); }, [location.state, navigate]); - useEffect(() => { - return () => { - clearDraft(draftKey); - }; - }, [clearDraft, draftKey]); - useEffect(() => { let cancelled = false; const hasValidCache = isCacheValid('openai-compatibility'); @@ -305,7 +299,9 @@ export function AiProvidersOpenAIEditLayout() { initialData.testModel && available.includes(initialData.testModel) ? initialData.testModel : available[0] || ''; + const baselineSignature = buildOpenAISignature(seededForm, initialTestModel); initDraft(draftKey, { + baselineSignature, form: seededForm, testModel: initialTestModel, testStatus: 'idle', @@ -313,8 +309,10 @@ export function AiProvidersOpenAIEditLayout() { keyTestStatuses: [], }); } else { + const emptyForm = buildEmptyForm(); initDraft(draftKey, { - form: buildEmptyForm(), + baselineSignature: buildOpenAISignature(emptyForm, ''), + form: emptyForm, testModel: '', testStatus: 'idle', testMessage: '', @@ -376,6 +374,35 @@ export function AiProvidersOpenAIEditLayout() { [setForm, showNotification, t] ); + const resolvedLoading = !draft?.initialized; + const currentSignature = useMemo(() => buildOpenAISignature(form, testModel), [form, testModel]); + const baselineSignature = draft?.baselineSignature ?? ''; + const isDirty = Boolean(draft?.initialized) && baselineSignature !== currentSignature; + const editorRootPath = useMemo(() => { + if (hasIndexParam) { + return `/ai-providers/openai/${params.index ?? ''}`; + } + return '/ai-providers/openai/new'; + }, [hasIndexParam, params.index]); + const canGuard = !resolvedLoading && !saving && !invalidIndexParam && !invalidIndex; + + const { allowNextNavigation } = useUnsavedChangesGuard({ + enabled: canGuard, + shouldBlock: ({ nextLocation }) => { + const nextPath = nextLocation.pathname; + const isWithinRoot = + nextPath === editorRootPath || nextPath.startsWith(`${editorRootPath}/`); + return isDirty && !isWithinRoot; + }, + dialog: { + title: t('common.unsaved_changes_title'), + message: t('common.unsaved_changes_message'), + confirmText: t('common.leave'), + cancelText: t('common.stay'), + variant: 'danger', + }, + }); + const handleSave = useCallback(async () => { const name = form.name.trim(); const baseUrl = form.baseUrl.trim(); @@ -430,6 +457,8 @@ export function AiProvidersOpenAIEditLayout() { : t('notification.openai_provider_added'), 'success' ); + allowNextNavigation(); + setDraftBaselineSignature(draftKey, buildOpenAISignature(form, testModel)); handleBack(); } catch (err: unknown) { showNotification(`${t('notification.update_failed')}: ${getErrorMessage(err)}`, 'error'); @@ -437,52 +466,19 @@ export function AiProvidersOpenAIEditLayout() { setSaving(false); } }, [ + allowNextNavigation, + draftKey, editIndex, fetchConfig, form, handleBack, providers, - testModel, + setDraftBaselineSignature, showNotification, t, + testModel, ]); - const resolvedLoading = !draft?.initialized; - const currentSignature = useMemo(() => buildOpenAISignature(form, testModel), [form, testModel]); - - useEffect(() => { - if (resolvedLoading) return; - if (baselineDraftKey === draftKey) return; - setBaselineDraftKey(draftKey); - setBaselineSignature(currentSignature); - }, [baselineDraftKey, currentSignature, draftKey, resolvedLoading]); - - const isDirty = baselineDraftKey === draftKey && baselineSignature !== currentSignature; - const editorRootPath = useMemo(() => { - if (hasIndexParam) { - return `/ai-providers/openai/${params.index ?? ''}`; - } - return '/ai-providers/openai/new'; - }, [hasIndexParam, params.index]); - const canGuard = !resolvedLoading && !saving && !invalidIndexParam && !invalidIndex; - - useUnsavedChangesGuard({ - enabled: canGuard, - shouldBlock: ({ nextLocation }) => { - const nextPath = nextLocation.pathname; - const isWithinRoot = - nextPath === editorRootPath || nextPath.startsWith(`${editorRootPath}/`); - return isDirty && !isWithinRoot; - }, - dialog: { - title: t('common.unsaved_changes_title'), - message: t('common.unsaved_changes_message'), - confirmText: t('common.leave'), - cancelText: t('common.stay'), - variant: 'danger', - }, - }); - return ( isDirty && currentLocation.pathname !== nextLocation.pathname, @@ -234,6 +234,8 @@ export function AiProvidersVertexEditPage() { editIndex !== null ? t('notification.vertex_config_updated') : t('notification.vertex_config_added'), 'success' ); + allowNextNavigation(); + setBaselineSignature(buildVertexSignature(form)); handleBack(); } catch (err: unknown) { const message = err instanceof Error ? err.message : ''; @@ -243,6 +245,7 @@ export function AiProvidersVertexEditPage() { setSaving(false); } }, [ + allowNextNavigation, canSave, clearCache, configs, diff --git a/src/stores/useClaudeEditDraftStore.ts b/src/stores/useClaudeEditDraftStore.ts index b284d2b2..d7ab7aa1 100644 --- a/src/stores/useClaudeEditDraftStore.ts +++ b/src/stores/useClaudeEditDraftStore.ts @@ -16,6 +16,7 @@ export type ClaudeTestStatus = 'idle' | 'loading' | 'success' | 'error'; type ClaudeEditDraft = { initialized: boolean; + baselineSignature: string; form: ProviderFormState; testModel: string; testStatus: ClaudeTestStatus; @@ -24,11 +25,15 @@ type ClaudeEditDraft = { interface ClaudeEditDraftState { drafts: Record; + refCounts: Record; + acquireDraft: (key: string) => void; + releaseDraft: (key: string) => void; ensureDraft: (key: string) => void; initDraft: ( key: string, draft: Omit ) => void; + setDraftBaselineSignature: (key: string, signature: string) => void; setDraftForm: ( key: string, action: SetStateAction @@ -59,6 +64,7 @@ const buildEmptyForm = (): ProviderFormState => ({ const buildEmptyDraft = (): ClaudeEditDraft => ({ initialized: false, + baselineSignature: '', form: buildEmptyForm(), testModel: '', testStatus: 'idle', @@ -67,6 +73,35 @@ const buildEmptyDraft = (): ClaudeEditDraft => ({ export const useClaudeEditDraftStore = create((set, get) => ({ drafts: {}, + refCounts: {}, + + acquireDraft: (key) => { + if (!key) return; + set((state) => { + const existingDraft = state.drafts[key]; + const currentCount = state.refCounts[key] ?? 0; + return { + drafts: existingDraft ? state.drafts : { ...state.drafts, [key]: buildEmptyDraft() }, + refCounts: { ...state.refCounts, [key]: currentCount + 1 }, + }; + }); + }, + + releaseDraft: (key) => { + if (!key) return; + set((state) => { + const currentCount = state.refCounts[key]; + if (!currentCount) return state; + if (currentCount > 1) { + return { refCounts: { ...state.refCounts, [key]: currentCount - 1 } }; + } + const nextCounts = { ...state.refCounts }; + delete nextCounts[key]; + const nextDrafts = { ...state.drafts }; + delete nextDrafts[key]; + return { refCounts: nextCounts, drafts: nextDrafts }; + }); + }, ensureDraft: (key) => { if (!key) return; @@ -89,6 +124,19 @@ export const useClaudeEditDraftStore = create((set, get) = })); }, + setDraftBaselineSignature: (key, signature) => { + if (!key) return; + set((state) => { + const existing = state.drafts[key] ?? buildEmptyDraft(); + return { + drafts: { + ...state.drafts, + [key]: { ...existing, initialized: true, baselineSignature: signature }, + }, + }; + }); + }, + setDraftForm: (key, action) => { if (!key) return; set((state) => { diff --git a/src/stores/useOpenAIEditDraftStore.ts b/src/stores/useOpenAIEditDraftStore.ts index 4c8566c0..cff52a0a 100644 --- a/src/stores/useOpenAIEditDraftStore.ts +++ b/src/stores/useOpenAIEditDraftStore.ts @@ -22,6 +22,7 @@ export type KeyTestStatus = { export type OpenAIEditDraft = { initialized: boolean; + baselineSignature: string; form: OpenAIFormState; testModel: string; testStatus: OpenAITestStatus; @@ -31,8 +32,12 @@ export type OpenAIEditDraft = { interface OpenAIEditDraftState { drafts: Record; + refCounts: Record; + acquireDraft: (key: string) => void; + releaseDraft: (key: string) => void; ensureDraft: (key: string) => void; initDraft: (key: string, draft: Omit) => void; + setDraftBaselineSignature: (key: string, signature: string) => void; setDraftForm: (key: string, action: SetStateAction) => void; setDraftTestModel: (key: string, action: SetStateAction) => void; setDraftTestStatus: (key: string, action: SetStateAction) => void; @@ -57,6 +62,7 @@ const buildEmptyForm = (): OpenAIFormState => ({ const buildEmptyDraft = (): OpenAIEditDraft => ({ initialized: false, + baselineSignature: '', form: buildEmptyForm(), testModel: '', testStatus: 'idle', @@ -66,6 +72,35 @@ const buildEmptyDraft = (): OpenAIEditDraft => ({ export const useOpenAIEditDraftStore = create((set, get) => ({ drafts: {}, + refCounts: {}, + + acquireDraft: (key) => { + if (!key) return; + set((state) => { + const existingDraft = state.drafts[key]; + const currentCount = state.refCounts[key] ?? 0; + return { + drafts: existingDraft ? state.drafts : { ...state.drafts, [key]: buildEmptyDraft() }, + refCounts: { ...state.refCounts, [key]: currentCount + 1 }, + }; + }); + }, + + releaseDraft: (key) => { + if (!key) return; + set((state) => { + const currentCount = state.refCounts[key]; + if (!currentCount) return state; + if (currentCount > 1) { + return { refCounts: { ...state.refCounts, [key]: currentCount - 1 } }; + } + const nextCounts = { ...state.refCounts }; + delete nextCounts[key]; + const nextDrafts = { ...state.drafts }; + delete nextDrafts[key]; + return { refCounts: nextCounts, drafts: nextDrafts }; + }); + }, ensureDraft: (key) => { if (!key) return; @@ -88,6 +123,19 @@ export const useOpenAIEditDraftStore = create((set, get) = })); }, + setDraftBaselineSignature: (key, signature) => { + if (!key) return; + set((state) => { + const existing = state.drafts[key] ?? buildEmptyDraft(); + return { + drafts: { + ...state.drafts, + [key]: { ...existing, initialized: true, baselineSignature: signature }, + }, + }; + }); + }, + setDraftForm: (key, action) => { if (!key) return; set((state) => { @@ -186,4 +234,3 @@ export const useOpenAIEditDraftStore = create((set, get) = }); }, })); -