mirror of
https://github.com/router-for-me/Cli-Proxy-API-Management-Center.git
synced 2026-06-16 21:03:58 +08:00
perf(claude): replace signature dirty tracking
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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 },
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user