From 5fc1cfba60a8b735e8e6cf7bec54dce8efbbadab Mon Sep 17 00:00:00 2001 From: Andrew Beal Date: Tue, 26 May 2026 18:51:08 +0100 Subject: [PATCH] refactor: optimize settings change notifications and cleanup subscriptions Remove unused dispose method and settings subscription from BaseAIClass. Update SettingsService to track which settings changed and notify subscribers with specific changed keys. Components now only react to relevant setting changes. Export RegisterAiProvider and call it when AI provider settings change to ensure provider is re-registered with new configuration. --- AIClasses/BaseAIClass.ts | 9 --------- Components/ChatInput.svelte | 12 +++++++----- Components/ChatModeSelector.svelte | 6 +++++- Services/ServiceRegistration.ts | 2 +- Services/SettingsService.ts | 19 ++++++++++++------- Views/VaultkeeperAISettingTab.ts | 5 +++++ 6 files changed, 30 insertions(+), 23 deletions(-) diff --git a/AIClasses/BaseAIClass.ts b/AIClasses/BaseAIClass.ts index 934c5da..b96002b 100644 --- a/AIClasses/BaseAIClass.ts +++ b/AIClasses/BaseAIClass.ts @@ -27,8 +27,6 @@ export abstract class BaseAIClass implements IAIClass { protected readonly settingsService: SettingsService; protected readonly streamingService: StreamingService; - private readonly settingsSubscription: object; - private _systemPrompt: string = ""; private _userInstruction: string = ""; private _agentType: AgentType = AgentType.Main; @@ -42,16 +40,9 @@ export abstract class BaseAIClass implements IAIClass { this.settingsService = Resolve(Services.SettingsService); this.streamingService = Resolve(Services.StreamingService); - this.settingsSubscription = this.settingsService.subscribeToSettingsChanged(() => { - this.apiKey = this.settingsService.getApiKeyForProvider(provider); - }); this.apiKey = this.settingsService.getApiKeyForProvider(provider); } - public dispose(): void { - this.settingsService.unsubscribe(this.settingsSubscription); - } - public get currentProvider(): AIProvider { return this.provider; } diff --git a/Components/ChatInput.svelte b/Components/ChatInput.svelte index 4f22a67..62c5e90 100644 --- a/Components/ChatInput.svelte +++ b/Components/ChatInput.svelte @@ -73,11 +73,13 @@ const diffClosedRef: EventRef = eventService.on(Event.DiffClosed, () => { inputMode = InputMode.Normal; focusInput(); }); const rateLimitCountdownRef: EventRef = eventService.on(Event.RateLimitCountdown, (delayMs: number) => { startCountdown(delayMs); }); - const settingsSubscription: object = settingsService.subscribeToSettingsChanged(() => { - chatMode = settingsService.settings.chatMode; - editsAllowed = chatModeAllowsEdits(chatMode); - if (chatModeButton){ - setIcon(chatModeButton, iconForChatMode(chatMode)); + const settingsSubscription: object = settingsService.subscribeToSettingsChanged(changed => { + if (changed.includes("chatMode")) { + chatMode = settingsService.settings.chatMode; + editsAllowed = chatModeAllowsEdits(chatMode); + if (chatModeButton) { + setIcon(chatModeButton, iconForChatMode(chatMode)); + } } }); diff --git a/Components/ChatModeSelector.svelte b/Components/ChatModeSelector.svelte index d6df2b7..0a31ce6 100644 --- a/Components/ChatModeSelector.svelte +++ b/Components/ChatModeSelector.svelte @@ -12,7 +12,11 @@ const settingsService: SettingsService = Resolve(Services.SettingsService); - const settingsSubscription: object = settingsService.subscribeToSettingsChanged(() => currentChatMode = settingsService.settings.chatMode); + const settingsSubscription: object = settingsService.subscribeToSettingsChanged(changed => { + if (changed.includes("chatMode")) { + currentChatMode = settingsService.settings.chatMode; + } + }); onDestroy(() => settingsService.unsubscribe(settingsSubscription)); diff --git a/Services/ServiceRegistration.ts b/Services/ServiceRegistration.ts index d875bd0..089da2d 100644 --- a/Services/ServiceRegistration.ts +++ b/Services/ServiceRegistration.ts @@ -102,7 +102,7 @@ export function RegisterDependencies() { RegisterAiProvider(); } -function RegisterAiProvider() { +export function RegisterAiProvider() { const settingsService = Resolve(Services.SettingsService); const provider = fromModel(settingsService.settings.model); diff --git a/Services/SettingsService.ts b/Services/SettingsService.ts index 65593e8..ea51752 100644 --- a/Services/SettingsService.ts +++ b/Services/SettingsService.ts @@ -81,12 +81,15 @@ export interface IVaultkeeperAISettings { hideDrawerElements: boolean; } +type SettingKey = keyof IVaultkeeperAISettings; +type SettingsChangedCallback = ((changedKeys: SettingKey[]) => void) | ((changedKeys: SettingKey[]) => Promise); + export class SettingsService { public readonly settings: Readonly; private readonly plugin: VaultkeeperAIPlugin; - private readonly subscribers: WeakMap void) | (() => Promise)> = new WeakMap(); + private readonly subscribers: WeakMap = new WeakMap(); private readonly subscriberRefs: Set> = new Set(); private settingsSnapshot: string; @@ -98,7 +101,7 @@ export class SettingsService { this.ensureValidModels(); } - public subscribeToSettingsChanged(callback: (() => void) | (() => Promise)): object { + public subscribeToSettingsChanged(callback: SettingsChangedCallback): object { const token = {}; this.subscribers.set(token, callback); this.subscriberRefs.add(new WeakRef(token)); @@ -150,17 +153,19 @@ export class SettingsService { } private async saveSettings() { + const oldSettings = JSON.parse(this.settingsSnapshot) as IVaultkeeperAISettings; await this.plugin.saveData(this.settings); - const snapshot = JSON.stringify(this.settings); - if (this.settingsSnapshot !== snapshot) { - this.settingsSnapshot = snapshot; + const changedKeys = (Object.keys(this.settings) as SettingKey[]) + .filter(key => JSON.stringify(this.settings[key]) !== JSON.stringify(oldSettings[key])); + if (changedKeys.length > 0) { + this.settingsSnapshot = JSON.stringify(this.settings); for (const ref of this.subscriberRefs) { const subscriber = ref.deref(); - if (!subscriber) { + if (!subscriber) { this.subscriberRefs.delete(ref); continue; } - await this.subscribers.get(subscriber)?.(); + await this.subscribers.get(subscriber)?.(changedKeys); } } } diff --git a/Views/VaultkeeperAISettingTab.ts b/Views/VaultkeeperAISettingTab.ts index ceb4c6a..39fb90b 100644 --- a/Views/VaultkeeperAISettingTab.ts +++ b/Views/VaultkeeperAISettingTab.ts @@ -11,6 +11,7 @@ import type { SettingsService } from "Services/SettingsService"; import { Services } from "Services/Services"; import { closePluginSettings } from "Helpers/Helpers"; import type { MemoriesService } from "Services/MemoriesService"; +import { RegisterAiProvider } from "Services/ServiceRegistration"; export class VaultkeeperAISettingTab extends PluginSettingTab { private readonly plugin: VaultkeeperAIPlugin; @@ -63,6 +64,7 @@ export class VaultkeeperAISettingTab extends PluginSettingTab { } this.updateFileDisclaimer(); await this.updateModelDropdowns(); + RegisterAiProvider(); }); }); @@ -87,6 +89,7 @@ export class VaultkeeperAISettingTab extends PluginSettingTab { await this.settingsService.updateSettings(settings => { settings.planningModel = value; }); + RegisterAiProvider(); }); }); @@ -105,6 +108,7 @@ export class VaultkeeperAISettingTab extends PluginSettingTab { await this.settingsService.updateSettings(settings => { settings.quickActionModel = value; }); + RegisterAiProvider(); }); }); @@ -120,6 +124,7 @@ export class VaultkeeperAISettingTab extends PluginSettingTab { await this.settingsService.setApiKeyForProvider(fromModel(settings.model), value); }); this.highlightApiKey(); + RegisterAiProvider(); }); text.inputEl.type = "password"; this.apiKeyInputEl = text.inputEl;