perf(claude): replace signature dirty tracking

This commit is contained in:
Supra4E8C
2026-04-03 01:33:11 +08:00
Unverified
parent 5527c1e5d4
commit b88eb47918
2 changed files with 97 additions and 27 deletions
+74 -22
View File
@@ -9,8 +9,10 @@ import type { ProviderKeyConfig } from '@/types';
import type { ModelInfo } from '@/utils/models';
import type { ModelEntry, ProviderFormState } from '@/components/providers/types';
import { buildHeaderObject, headersToEntries, normalizeHeaderEntries } from '@/utils/headers';
import { areKeyValueEntriesEqual, areModelEntriesEqual, areStringArraysEqual } from '@/utils/compare';
import { excludedModelsToText, parseExcludedModels } from '@/components/providers/utils';
import { modelsToEntries } from '@/components/ui/modelInputListUtils';
import type { ClaudeEditBaseline } from '@/stores/useClaudeEditDraftStore';
type LocationState = { fromAiProviders?: boolean } | null;
@@ -89,19 +91,28 @@ const normalizeCloakConfig = (cloak: ProviderFormState['cloak']) => {
};
};
const buildClaudeSignature = (form: ProviderFormState) =>
JSON.stringify({
apiKey: String(form.apiKey ?? '').trim(),
priority:
form.priority !== undefined && Number.isFinite(form.priority) ? Math.trunc(form.priority) : null,
prefix: String(form.prefix ?? '').trim(),
baseUrl: String(form.baseUrl ?? '').trim(),
proxyUrl: String(form.proxyUrl ?? '').trim(),
headers: normalizeHeaderEntries(form.headers),
models: normalizeClaudeModelEntries(form.modelEntries),
excludedModels: parseExcludedModels(form.excludedText ?? ''),
cloak: normalizeCloakConfig(form.cloak),
});
const buildClaudeBaseline = (form: ProviderFormState): ClaudeEditBaseline => ({
apiKey: String(form.apiKey ?? '').trim(),
priority:
form.priority !== undefined && Number.isFinite(form.priority) ? Math.trunc(form.priority) : null,
prefix: String(form.prefix ?? '').trim(),
baseUrl: String(form.baseUrl ?? '').trim(),
proxyUrl: String(form.proxyUrl ?? '').trim(),
headers: normalizeHeaderEntries(form.headers),
models: normalizeClaudeModelEntries(form.modelEntries),
excludedModels: parseExcludedModels(form.excludedText ?? ''),
cloak: normalizeCloakConfig(form.cloak),
});
const areCloakConfigsEqual = (left: ClaudeEditBaseline['cloak'], right: ClaudeEditBaseline['cloak']) => {
if (left === right) return true;
if (!left || !right) return false;
if (left.mode !== right.mode || left.strictMode !== right.strictMode) return false;
if (left.sensitiveWords === null || right.sensitiveWords === null) {
return left.sensitiveWords === right.sensitiveWords;
}
return areStringArraysEqual(left.sensitiveWords, right.sensitiveWords);
};
export function AiProvidersClaudeEditLayout() {
const { t } = useTranslation();
@@ -137,7 +148,7 @@ export function AiProvidersClaudeEditLayout() {
const acquireDraft = useClaudeEditDraftStore((state) => state.acquireDraft);
const releaseDraft = useClaudeEditDraftStore((state) => state.releaseDraft);
const initDraft = useClaudeEditDraftStore((state) => state.initDraft);
const setDraftBaselineSignature = useClaudeEditDraftStore((state) => state.setDraftBaselineSignature);
const setDraftBaseline = useClaudeEditDraftStore((state) => state.setDraftBaseline);
const setDraftForm = useClaudeEditDraftStore((state) => state.setDraftForm);
const setDraftTestModel = useClaudeEditDraftStore((state) => state.setDraftTestModel);
const setDraftTestStatus = useClaudeEditDraftStore((state) => state.setDraftTestStatus);
@@ -241,9 +252,9 @@ export function AiProvidersClaudeEditLayout() {
excludedText: excludedModelsToText(initialData.excludedModels),
};
const available = seededForm.modelEntries.map((entry) => entry.name.trim()).filter(Boolean);
const baselineSignature = buildClaudeSignature(seededForm);
const baseline = buildClaudeBaseline(seededForm);
initDraft(draftKey, {
baselineSignature,
baseline,
form: seededForm,
testModel: available[0] || '',
testStatus: 'idle',
@@ -254,7 +265,7 @@ export function AiProvidersClaudeEditLayout() {
const emptyForm = buildEmptyForm();
initDraft(draftKey, {
baselineSignature: buildClaudeSignature(emptyForm),
baseline: buildClaudeBaseline(emptyForm),
form: emptyForm,
testModel: '',
testStatus: 'idle',
@@ -263,9 +274,50 @@ export function AiProvidersClaudeEditLayout() {
}, [draft?.initialized, draftKey, initDraft, initialData, loading]);
const resolvedLoading = !draft?.initialized;
const currentSignature = useMemo(() => buildClaudeSignature(form), [form]);
const baselineSignature = draft?.baselineSignature ?? '';
const isDirty = Boolean(draft?.initialized) && baselineSignature !== currentSignature;
const baseline = draft?.baseline ?? null;
const normalizedHeaders = useMemo(() => normalizeHeaderEntries(form.headers), [form.headers]);
const normalizedModels = useMemo(
() => normalizeClaudeModelEntries(form.modelEntries),
[form.modelEntries]
);
const normalizedExcludedModels = useMemo(
() => parseExcludedModels(form.excludedText ?? ''),
[form.excludedText]
);
const normalizedCloak = useMemo(() => normalizeCloakConfig(form.cloak), [form.cloak]);
const normalizedPriority = useMemo(() => {
return form.priority !== undefined && Number.isFinite(form.priority)
? Math.trunc(form.priority)
: null;
}, [form.priority]);
const isHeadersDirty = useMemo(() => {
if (!baseline) return false;
return !areKeyValueEntriesEqual(baseline.headers, normalizedHeaders);
}, [baseline, normalizedHeaders]);
const isModelsDirty = useMemo(() => {
if (!baseline) return false;
return !areModelEntriesEqual(baseline.models, normalizedModels);
}, [baseline, normalizedModels]);
const isExcludedModelsDirty = useMemo(() => {
if (!baseline) return false;
return !areStringArraysEqual(baseline.excludedModels, normalizedExcludedModels);
}, [baseline, normalizedExcludedModels]);
const isCloakDirty = useMemo(() => {
if (!baseline) return false;
return !areCloakConfigsEqual(baseline.cloak, normalizedCloak);
}, [baseline, normalizedCloak]);
const isDirty =
Boolean(draft?.initialized) &&
baseline !== null &&
(baseline.apiKey !== form.apiKey.trim() ||
baseline.priority !== normalizedPriority ||
baseline.prefix !== String(form.prefix ?? '').trim() ||
baseline.baseUrl !== String(form.baseUrl ?? '').trim() ||
baseline.proxyUrl !== String(form.proxyUrl ?? '').trim() ||
isHeadersDirty ||
isModelsDirty ||
isExcludedModelsDirty ||
isCloakDirty);
const editorRootPath = useMemo(() => {
if (hasIndexParam) {
return `/ai-providers/claude/${params.index ?? ''}`;
@@ -384,7 +436,7 @@ export function AiProvidersClaudeEditLayout() {
'success'
);
allowNextNavigation();
setDraftBaselineSignature(draftKey, buildClaudeSignature(form));
setDraftBaseline(draftKey, buildClaudeBaseline(form));
handleBack();
} catch (err: unknown) {
showNotification(`${t('notification.update_failed')}: ${getErrorMessage(err)}`, 'error');
@@ -403,7 +455,7 @@ export function AiProvidersClaudeEditLayout() {
invalidIndex,
invalidIndexParam,
resolvedLoading,
setDraftBaselineSignature,
setDraftBaseline,
saving,
showNotification,
t,
+23 -5
View File
@@ -14,9 +14,27 @@ import type { ProviderFormState } from '@/components/providers/types';
export type ClaudeTestStatus = 'idle' | 'loading' | 'success' | 'error';
export type ClaudeCloakBaseline = {
mode: string;
strictMode: boolean;
sensitiveWords: string[] | null;
} | null;
export type ClaudeEditBaseline = {
apiKey: string;
priority: number | null;
prefix: string;
baseUrl: string;
proxyUrl: string;
headers: Array<{ key: string; value: string }>;
models: Array<{ name: string; alias: string }>;
excludedModels: string[];
cloak: ClaudeCloakBaseline;
};
type ClaudeEditDraft = {
initialized: boolean;
baselineSignature: string;
baseline: ClaudeEditBaseline | null;
form: ProviderFormState;
testModel: string;
testStatus: ClaudeTestStatus;
@@ -33,7 +51,7 @@ interface ClaudeEditDraftState {
key: string,
draft: Omit<ClaudeEditDraft, 'initialized'>
) => void;
setDraftBaselineSignature: (key: string, signature: string) => void;
setDraftBaseline: (key: string, baseline: ClaudeEditBaseline) => void;
setDraftForm: (
key: string,
action: SetStateAction<ProviderFormState>
@@ -64,7 +82,7 @@ const buildEmptyForm = (): ProviderFormState => ({
const buildEmptyDraft = (): ClaudeEditDraft => ({
initialized: false,
baselineSignature: '',
baseline: null,
form: buildEmptyForm(),
testModel: '',
testStatus: 'idle',
@@ -124,14 +142,14 @@ export const useClaudeEditDraftStore = create<ClaudeEditDraftState>((set, get) =
}));
},
setDraftBaselineSignature: (key, signature) => {
setDraftBaseline: (key, baseline) => {
if (!key) return;
set((state) => {
const existing = state.drafts[key] ?? buildEmptyDraft();
return {
drafts: {
...state.drafts,
[key]: { ...existing, initialized: true, baselineSignature: signature },
[key]: { ...existing, initialized: true, baseline },
},
};
});