mirror of
https://github.com/andy-stack/vaultkeeper-ai.git
synced 2026-07-22 06:42:03 +00:00
Refactor SettingsService to use immutable settings with update pattern
- Change settings from mutable to readonly with updateSettings method - Add settings change subscription system for reactive updates - Remove direct property mutations throughout codebase - Make API key reactive to provider changes via subscription - Consolidate all settings updates through single async method - Remove unused RegisterAiProvider export and AIToolDefinitions.isGated
This commit is contained in:
parent
d2edd167dc
commit
da2d777087
6 changed files with 141 additions and 108 deletions
|
|
@ -19,8 +19,9 @@ import { AIToolUsageMode } from "Enums/AIToolUsageMode";
|
|||
|
||||
export abstract class BaseAIClass implements IAIClass {
|
||||
|
||||
protected apiKey: string;
|
||||
|
||||
protected readonly provider: AIProvider;
|
||||
protected readonly apiKey: string;
|
||||
protected readonly abortService: AbortService;
|
||||
protected readonly aiFileService: IAIFileService;
|
||||
protected readonly settingsService: SettingsService;
|
||||
|
|
@ -39,6 +40,9 @@ export abstract class BaseAIClass implements IAIClass {
|
|||
this.settingsService = Resolve<SettingsService>(Services.SettingsService);
|
||||
this.streamingService = Resolve<StreamingService>(Services.StreamingService);
|
||||
|
||||
this.settingsService.subscribeToSettingsChanged(this, () => {
|
||||
this.apiKey = this.settingsService.getApiKeyForProvider(provider);
|
||||
});
|
||||
this.apiKey = this.settingsService.getApiKeyForProvider(provider);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -25,15 +25,12 @@ import { MoveVaultFolder } from "./Tools/MoveVaultFolder";
|
|||
import { ChatMode, chatModeAllowsEdits } from "Enums/ChatMode";
|
||||
|
||||
export abstract class AIToolDefinitions {
|
||||
|
||||
public static isGated: boolean = false;
|
||||
|
||||
// Definitions list provides a list of function definitions that does not include any planning functions (used as reference in planning agent prompt)
|
||||
private static readonly definitionsList = [SearchVaultFiles, ReadVaultFiles, ListVaultFiles, GetWebViewerContent,
|
||||
WriteVaultFile, PatchVaultFile, DeleteVaultFiles, MoveVaultFiles, CreateVaultFolder, DeleteVaultFolder, MoveVaultFolder];
|
||||
|
||||
public static agentDefinitions(chatMode: ChatMode, memories: boolean, updateMemories: boolean, webViewer: boolean): IAIToolDefinition[] {
|
||||
this.isGated = false;
|
||||
|
||||
if (chatMode === ChatMode.Planning) {
|
||||
return [ExecuteWorkflow];
|
||||
|
|
|
|||
|
|
@ -102,7 +102,7 @@ export function RegisterDependencies() {
|
|||
RegisterAiProvider();
|
||||
}
|
||||
|
||||
export function RegisterAiProvider() {
|
||||
function RegisterAiProvider() {
|
||||
const settingsService = Resolve<SettingsService>(Services.SettingsService);
|
||||
const provider = fromModel(settingsService.settings.model);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,10 +1,22 @@
|
|||
import type VaultkeeperAIPlugin from "main";
|
||||
import { Resolve } from "./DependencyService";
|
||||
import { Services } from "./Services";
|
||||
import { AIProvider, AIProviderModel, DEFAULT_MODEL_BY_PROVIDER, DEFAULT_PLANNING_MODEL_BY_PROVIDER, fromModel, isvalidProvider, isValidProviderModel, modelMatchesProvider } from "Enums/ApiProvider";
|
||||
import { ChatMode } from "Enums/ChatMode";
|
||||
import {
|
||||
AIProvider,
|
||||
AIProviderModel,
|
||||
DEFAULT_MODEL_BY_PROVIDER,
|
||||
DEFAULT_PLANNING_MODEL_BY_PROVIDER,
|
||||
fromModel,
|
||||
isvalidProvider,
|
||||
isValidProviderModel,
|
||||
modelMatchesProvider
|
||||
} from "Enums/ApiProvider";
|
||||
|
||||
const DEFAULT_SETTINGS: IVaultkeeperAISettings = {
|
||||
firstTimeStart: true,
|
||||
|
||||
chatMode: ChatMode.ReadOnly,
|
||||
userInstruction: "",
|
||||
|
||||
provider: AIProvider.Claude,
|
||||
|
|
@ -37,6 +49,8 @@ const DEFAULT_SETTINGS: IVaultkeeperAISettings = {
|
|||
|
||||
export interface IVaultkeeperAISettings {
|
||||
firstTimeStart: boolean;
|
||||
|
||||
chatMode: ChatMode;
|
||||
userInstruction: string;
|
||||
|
||||
provider: AIProvider;
|
||||
|
|
@ -69,21 +83,31 @@ export interface IVaultkeeperAISettings {
|
|||
|
||||
export class SettingsService {
|
||||
|
||||
public readonly settings: Readonly<IVaultkeeperAISettings>;
|
||||
|
||||
private readonly plugin: VaultkeeperAIPlugin;
|
||||
|
||||
public readonly settings: IVaultkeeperAISettings;
|
||||
private readonly subscribers: Map<object, (() => void) | (() => Promise<void>)> = new Map();
|
||||
|
||||
private settingsSnapshot: string;
|
||||
|
||||
public constructor(loadedSettings: Partial<IVaultkeeperAISettings>) {
|
||||
this.plugin = Resolve<VaultkeeperAIPlugin>(Services.VaultkeeperAIPlugin);
|
||||
this.settings = Object.assign({}, DEFAULT_SETTINGS, loadedSettings);
|
||||
this.settingsSnapshot = JSON.stringify(this.settings);
|
||||
this.ensureValidModels();
|
||||
}
|
||||
|
||||
public async saveSettings(onSave?: () => void) {
|
||||
await this.plugin.saveData(this.settings);
|
||||
if (onSave) {
|
||||
onSave();
|
||||
}
|
||||
public subscribeToSettingsChanged(subscriber: object, callback: (() => void) | (() => Promise<void>)): void {
|
||||
this.subscribers.set(subscriber, callback);
|
||||
}
|
||||
|
||||
public unsubscribe(subscriber: object): void {
|
||||
this.subscribers.delete(subscriber);
|
||||
}
|
||||
|
||||
public async updateSettings(updateAction: ((settings: IVaultkeeperAISettings) => void) | ((settings: IVaultkeeperAISettings) => Promise<void>)) {
|
||||
await updateAction(this.settings);
|
||||
await this.saveSettings();
|
||||
}
|
||||
|
||||
public getApiKeyForCurrentModel(): string {
|
||||
|
|
@ -104,51 +128,54 @@ export class SettingsService {
|
|||
}
|
||||
}
|
||||
|
||||
public setApiKeyForProvider(provider: AIProvider, key: string) {
|
||||
public async setApiKeyForProvider(provider: AIProvider, key: string) {
|
||||
switch (provider) {
|
||||
case AIProvider.Claude:
|
||||
this.settings.apiKeys.claude = key;
|
||||
await this.updateSettings(settings => settings.apiKeys.claude = key);
|
||||
break;
|
||||
case AIProvider.OpenAI:
|
||||
this.settings.apiKeys.openai = key;
|
||||
await this.updateSettings(settings => settings.apiKeys.openai = key);
|
||||
break;
|
||||
case AIProvider.Gemini:
|
||||
this.settings.apiKeys.gemini = key;
|
||||
await this.updateSettings(settings => settings.apiKeys.gemini = key);
|
||||
break;
|
||||
case AIProvider.Mistral:
|
||||
this.settings.apiKeys.mistral = key;
|
||||
await this.updateSettings(settings => settings.apiKeys.mistral = key);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private async saveSettings() {
|
||||
await this.plugin.saveData(this.settings);
|
||||
const snapshot = JSON.stringify(this.settings);
|
||||
if (this.settingsSnapshot !== snapshot) {
|
||||
this.settingsSnapshot = snapshot;
|
||||
for (const callback of this.subscribers.values()) {
|
||||
await callback();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private ensureValidModels(): void {
|
||||
let changed = false;
|
||||
void this.updateSettings(settings => {
|
||||
let provider = settings.provider;
|
||||
|
||||
let provider = this.settings.provider;
|
||||
|
||||
if (!isvalidProvider(provider)) {
|
||||
provider = DEFAULT_SETTINGS.provider;
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (!isValidProviderModel(this.settings.model) || !modelMatchesProvider(this.settings.model, provider)) {
|
||||
this.settings.model = DEFAULT_MODEL_BY_PROVIDER[provider];
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (!isValidProviderModel(this.settings.planningModel) || !modelMatchesProvider(this.settings.planningModel, provider)) {
|
||||
this.settings.planningModel = DEFAULT_PLANNING_MODEL_BY_PROVIDER[provider];
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (!isValidProviderModel(this.settings.quickActionModel) || !modelMatchesProvider(this.settings.quickActionModel, provider)) {
|
||||
this.settings.quickActionModel = DEFAULT_MODEL_BY_PROVIDER[provider];
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (changed) {
|
||||
void this.saveSettings();
|
||||
}
|
||||
if (!isvalidProvider(provider)) {
|
||||
provider = DEFAULT_SETTINGS.provider;
|
||||
}
|
||||
|
||||
if (!isValidProviderModel(this.settings.model) || !modelMatchesProvider(this.settings.model, provider)) {
|
||||
settings.model = DEFAULT_MODEL_BY_PROVIDER[provider];
|
||||
}
|
||||
|
||||
if (!isValidProviderModel(this.settings.planningModel) || !modelMatchesProvider(this.settings.planningModel, provider)) {
|
||||
settings.planningModel = DEFAULT_PLANNING_MODEL_BY_PROVIDER[provider];
|
||||
}
|
||||
|
||||
if (!isValidProviderModel(this.settings.quickActionModel) || !modelMatchesProvider(this.settings.quickActionModel, provider)) {
|
||||
settings.quickActionModel = DEFAULT_MODEL_BY_PROVIDER[provider];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -9,7 +9,6 @@ import { Resolve } from "Services/DependencyService";
|
|||
import type { EventService } from "Services/EventService";
|
||||
import type { SettingsService } from "Services/SettingsService";
|
||||
import { Services } from "Services/Services";
|
||||
import { RegisterAiProvider } from "Services/ServiceRegistration";
|
||||
import { closePluginSettings } from "Helpers/Helpers";
|
||||
import type { MemoriesService } from "Services/MemoriesService";
|
||||
|
||||
|
|
@ -54,9 +53,10 @@ export class VaultkeeperAISettingTab extends PluginSettingTab {
|
|||
if (!isValidProviderModel(value)) {
|
||||
return;
|
||||
}
|
||||
this.settingsService.settings.model = value;
|
||||
this.settingsService.settings.provider = fromModel(value);
|
||||
await this.settingsService.saveSettings(() => RegisterAiProvider());
|
||||
await this.settingsService.updateSettings(settings => {
|
||||
settings.model = value;
|
||||
settings.provider = fromModel(value);
|
||||
});
|
||||
if (this.apiKeyInputEl) {
|
||||
this.apiKeyInputEl.value = this.settingsService.getApiKeyForCurrentModel();
|
||||
this.highlightApiKey();
|
||||
|
|
@ -84,8 +84,9 @@ export class VaultkeeperAISettingTab extends PluginSettingTab {
|
|||
if (!isValidProviderModel(value)) {
|
||||
return;
|
||||
}
|
||||
this.settingsService.settings.planningModel = value;
|
||||
await this.settingsService.saveSettings();
|
||||
await this.settingsService.updateSettings(settings => {
|
||||
settings.planningModel = value;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -101,8 +102,9 @@ export class VaultkeeperAISettingTab extends PluginSettingTab {
|
|||
if (!isValidProviderModel(value)) {
|
||||
return;
|
||||
}
|
||||
this.settingsService.settings.quickActionModel = value;
|
||||
await this.settingsService.saveSettings();
|
||||
await this.settingsService.updateSettings(settings => {
|
||||
settings.quickActionModel = value;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -114,9 +116,9 @@ export class VaultkeeperAISettingTab extends PluginSettingTab {
|
|||
text.setPlaceholder(Copy.PlaceholderEnterApiKey)
|
||||
.setValue(this.settingsService.getApiKeyForCurrentModel())
|
||||
.onChange(async (value) => {
|
||||
const provider = fromModel(this.settingsService.settings.model);
|
||||
this.settingsService.setApiKeyForProvider(provider, value);
|
||||
await this.settingsService.saveSettings(() => RegisterAiProvider());
|
||||
await this.settingsService.updateSettings(async settings => {
|
||||
await this.settingsService.setApiKeyForProvider(fromModel(settings.model), value);
|
||||
});
|
||||
this.highlightApiKey();
|
||||
});
|
||||
text.inputEl.type = "password";
|
||||
|
|
@ -162,8 +164,9 @@ export class VaultkeeperAISettingTab extends PluginSettingTab {
|
|||
text.setPlaceholder(Copy.PlaceholderFileExclusions)
|
||||
.setValue(this.settingsService.settings.exclusions.join("\n"))
|
||||
.onChange(async (value) => {
|
||||
this.settingsService.settings.exclusions = value.split("\n").map(line => line.trim()).filter(line => line.length > 0);
|
||||
await this.settingsService.saveSettings();
|
||||
await this.settingsService.updateSettings(settings => {
|
||||
settings.exclusions = value.split("\n").map(line => line.trim()).filter(line => line.length > 0);
|
||||
});
|
||||
});
|
||||
text.inputEl.classList.add(Selector.AIExclusionsInput);
|
||||
});
|
||||
|
|
@ -183,8 +186,9 @@ export class VaultkeeperAISettingTab extends PluginSettingTab {
|
|||
.setValue(this.settingsService.settings.searchResultsLimit)
|
||||
.setDynamicTooltip()
|
||||
.onChange(async (value) => {
|
||||
this.settingsService.settings.searchResultsLimit = value;
|
||||
await this.settingsService.saveSettings();
|
||||
await this.settingsService.updateSettings(settings => {
|
||||
settings.searchResultsLimit = value;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -198,8 +202,9 @@ export class VaultkeeperAISettingTab extends PluginSettingTab {
|
|||
.setValue(this.settingsService.settings.snippetSizeLimit)
|
||||
.setDynamicTooltip()
|
||||
.onChange(async (value) => {
|
||||
this.settingsService.settings.snippetSizeLimit = value;
|
||||
await this.settingsService.saveSettings();
|
||||
await this.settingsService.updateSettings(settings => {
|
||||
settings.snippetSizeLimit = value;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -216,8 +221,9 @@ export class VaultkeeperAISettingTab extends PluginSettingTab {
|
|||
toggle
|
||||
.setValue(this.settingsService.settings.enableWebViewer)
|
||||
.onChange(async (value) => {
|
||||
this.settingsService.settings.enableWebViewer = value;
|
||||
await this.settingsService.saveSettings();
|
||||
await this.settingsService.updateSettings(settings => {
|
||||
settings.enableWebViewer = value;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -234,8 +240,9 @@ export class VaultkeeperAISettingTab extends PluginSettingTab {
|
|||
toggle
|
||||
.setValue(this.settingsService.settings.enableMemories)
|
||||
.onChange(async (value) => {
|
||||
this.settingsService.settings.enableMemories = value;
|
||||
await this.settingsService.saveSettings();
|
||||
await this.settingsService.updateSettings(settings => {
|
||||
settings.enableMemories = value;
|
||||
});
|
||||
this.updateAllowUpdatingMemoriesSetting();
|
||||
});
|
||||
});
|
||||
|
|
@ -249,8 +256,9 @@ export class VaultkeeperAISettingTab extends PluginSettingTab {
|
|||
toggle
|
||||
.setValue(this.settingsService.settings.allowUpdatingMemories)
|
||||
.onChange(async (value) => {
|
||||
this.settingsService.settings.allowUpdatingMemories = value;
|
||||
await this.settingsService.saveSettings();
|
||||
await this.settingsService.updateSettings(settings => {
|
||||
settings.allowUpdatingMemories = value;
|
||||
});
|
||||
})
|
||||
});
|
||||
this.updateAllowUpdatingMemoriesSetting();
|
||||
|
|
@ -282,10 +290,10 @@ export class VaultkeeperAISettingTab extends PluginSettingTab {
|
|||
toggle
|
||||
.setValue(this.settingsService.settings.enableContextMenuActions)
|
||||
.onChange(async (value) => {
|
||||
this.settingsService.settings.enableContextMenuActions = value;
|
||||
await this.settingsService.saveSettings(() =>
|
||||
this.eventService.trigger(Event.QuickActionsSettingsChanged)
|
||||
);
|
||||
await this.settingsService.updateSettings(settings => {
|
||||
settings.enableContextMenuActions = value;
|
||||
});
|
||||
this.eventService.trigger(Event.QuickActionsSettingsChanged);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -297,10 +305,10 @@ export class VaultkeeperAISettingTab extends PluginSettingTab {
|
|||
toggle
|
||||
.setValue(this.settingsService.settings.enableToolbarActions)
|
||||
.onChange(async (value) => {
|
||||
this.settingsService.settings.enableToolbarActions = value;
|
||||
await this.settingsService.saveSettings(() =>
|
||||
this.eventService.trigger(Event.QuickActionsSettingsChanged)
|
||||
);
|
||||
await this.settingsService.updateSettings(settings => {
|
||||
settings.enableToolbarActions = value;
|
||||
});
|
||||
this.eventService.trigger(Event.QuickActionsSettingsChanged);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -317,8 +325,9 @@ export class VaultkeeperAISettingTab extends PluginSettingTab {
|
|||
toggle
|
||||
.setValue(this.settingsService.settings.hideDrawerElements)
|
||||
.onChange(async (value) => {
|
||||
this.settingsService.settings.hideDrawerElements = value;
|
||||
await this.settingsService.saveSettings();
|
||||
await this.settingsService.updateSettings(settings => {
|
||||
settings.hideDrawerElements = value;
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
@ -369,38 +378,33 @@ export class VaultkeeperAISettingTab extends PluginSettingTab {
|
|||
}
|
||||
|
||||
private async updateModelDropdowns(): Promise<void> {
|
||||
const currentProvider = fromModel(this.settingsService.settings.model);
|
||||
let shouldSave = false;
|
||||
await this.settingsService.updateSettings(settings => {
|
||||
const currentProvider = fromModel(settings.model);
|
||||
|
||||
if (this.planningModelDropdown) {
|
||||
const planningProvider = fromModel(this.settingsService.settings.planningModel);
|
||||
this.planningModelDropdown.selectEl.empty();
|
||||
this.populateModelDropdown(this.planningModelDropdown, currentProvider);
|
||||
|
||||
if (planningProvider !== currentProvider) {
|
||||
this.settingsService.settings.planningModel = this.settingsService.settings.model;
|
||||
shouldSave = true;
|
||||
if (this.planningModelDropdown) {
|
||||
const planningProvider = fromModel(settings.planningModel);
|
||||
this.planningModelDropdown.selectEl.empty();
|
||||
this.populateModelDropdown(this.planningModelDropdown, currentProvider);
|
||||
|
||||
if (planningProvider !== currentProvider) {
|
||||
settings.planningModel = settings.model;
|
||||
}
|
||||
|
||||
this.planningModelDropdown.setValue(settings.planningModel);
|
||||
}
|
||||
|
||||
this.planningModelDropdown.setValue(this.settingsService.settings.planningModel);
|
||||
}
|
||||
|
||||
if (this.quickActionModelDropdown) {
|
||||
const quickActionProvider = fromModel(this.settingsService.settings.quickActionModel);
|
||||
this.quickActionModelDropdown.selectEl.empty();
|
||||
this.populateModelDropdown(this.quickActionModelDropdown);
|
||||
|
||||
if (quickActionProvider !== currentProvider) {
|
||||
this.settingsService.settings.quickActionModel = this.settingsService.settings.model;
|
||||
shouldSave = true;
|
||||
if (this.quickActionModelDropdown) {
|
||||
const quickActionProvider = fromModel(settings.quickActionModel);
|
||||
this.quickActionModelDropdown.selectEl.empty();
|
||||
this.populateModelDropdown(this.quickActionModelDropdown);
|
||||
|
||||
if (quickActionProvider !== currentProvider) {
|
||||
settings.quickActionModel = settings.model;
|
||||
}
|
||||
|
||||
this.quickActionModelDropdown.setValue(settings.quickActionModel);
|
||||
}
|
||||
|
||||
this.quickActionModelDropdown.setValue(this.settingsService.settings.quickActionModel);
|
||||
}
|
||||
|
||||
if (shouldSave) {
|
||||
await this.settingsService.saveSettings();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private highlightApiKey() {
|
||||
|
|
|
|||
7
main.ts
7
main.ts
|
|
@ -94,11 +94,12 @@ export default class VaultkeeperAIPlugin extends Plugin {
|
|||
if (!settingsService.settings.firstTimeStart) {
|
||||
return;
|
||||
}
|
||||
settingsService.settings.firstTimeStart = false;
|
||||
await settingsService.saveSettings();
|
||||
|
||||
await settingsService.updateSettings(settings => {
|
||||
settings.firstTimeStart = false;
|
||||
});
|
||||
|
||||
const vaultService: VaultService = Resolve<VaultService>(Services.VaultService);
|
||||
|
||||
await vaultService.create(Path.ExampleUserInstructions, Copy.EXAMPLE_USER_INSTRUCTION, true, false);
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue