fix(ai-providers): prompt on unsaved edits and preserve editor drafts

This commit is contained in:
Supra4E8C
2026-02-22 02:10:25 +08:00
parent 86a5b94cac
commit 8e9235f66c
10 changed files with 222 additions and 98 deletions
+29 -18
View File
@@ -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 (
<>
<NotificationContainer />
<ConfirmationModal />
<Outlet />
</>
);
}
const router = createHashRouter([
{
element: <RootShell />,
children: [
{ path: '/login', element: <LoginPage /> },
{
path: '/*',
element: (
<ProtectedRoute>
<MainLayout />
</ProtectedRoute>
),
},
],
},
]);
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 (
<HashRouter>
<NotificationContainer />
<ConfirmationModal />
<Routes>
<Route path="/login" element={<LoginPage />} />
<Route
path="/*"
element={
<ProtectedRoute>
<MainLayout />
</ProtectedRoute>
}
/>
</Routes>
</HashRouter>
);
return <RouterProvider router={router} />;
}
export default App;
+17 -2
View File
@@ -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<string>('');
const allowNextNavigationRef = useRef(false);
const location = useLocation();
const allowNextNavigation = useCallback(() => {
allowNextNavigationRef.current = true;
}, []);
const shouldBlockFunction = useCallback<BlockerFunction>(
(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 };
}
+3 -1
View File
@@ -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);
+19 -23
View File
@@ -139,8 +139,6 @@ export function AiProvidersClaudeEditLayout() {
const [configs, setConfigs] = useState<ProviderKeyConfig[]>(() => config?.claudeApiKeys ?? []);
const [loading, setLoading] = useState(() => !isCacheValid('claude-api-key'));
const [saving, setSaving] = useState(false);
const [baselineDraftKey, setBaselineDraftKey] = useState<string>('');
const [baselineSignature, setBaselineSignature] = useState<string>('');
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,
+4 -1
View File
@@ -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,
+4 -1
View File
@@ -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,
+46 -50
View File
@@ -154,8 +154,6 @@ export function AiProvidersOpenAIEditLayout() {
() => !isCacheValid('openai-compatibility')
);
const [saving, setSaving] = useState(false);
const [baselineDraftKey, setBaselineDraftKey] = useState<string>('');
const [baselineSignature, setBaselineSignature] = useState<string>('');
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 (
<Outlet
context={{
+4 -1
View File
@@ -180,7 +180,7 @@ export function AiProvidersVertexEditPage() {
const isDirty = baselineSignature !== currentSignature;
const canGuard = !loading && !saving && !invalidIndexParam && !invalidIndex;
useUnsavedChangesGuard({
const { allowNextNavigation } = useUnsavedChangesGuard({
enabled: canGuard,
shouldBlock: ({ currentLocation, nextLocation }) =>
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,
+48
View File
@@ -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<string, ClaudeEditDraft>;
refCounts: Record<string, number>;
acquireDraft: (key: string) => void;
releaseDraft: (key: string) => void;
ensureDraft: (key: string) => void;
initDraft: (
key: string,
draft: Omit<ClaudeEditDraft, 'initialized'>
) => void;
setDraftBaselineSignature: (key: string, signature: string) => void;
setDraftForm: (
key: string,
action: SetStateAction<ProviderFormState>
@@ -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<ClaudeEditDraftState>((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<ClaudeEditDraftState>((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) => {
+48 -1
View File
@@ -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<string, OpenAIEditDraft>;
refCounts: Record<string, number>;
acquireDraft: (key: string) => void;
releaseDraft: (key: string) => void;
ensureDraft: (key: string) => void;
initDraft: (key: string, draft: Omit<OpenAIEditDraft, 'initialized'>) => void;
setDraftBaselineSignature: (key: string, signature: string) => void;
setDraftForm: (key: string, action: SetStateAction<OpenAIFormState>) => void;
setDraftTestModel: (key: string, action: SetStateAction<string>) => void;
setDraftTestStatus: (key: string, action: SetStateAction<OpenAITestStatus>) => 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<OpenAIEditDraftState>((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<OpenAIEditDraftState>((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<OpenAIEditDraftState>((set, get) =
});
},
}));