Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Auto save settings #874

Open
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

zeroliu
Copy link
Contributor

@zeroliu zeroliu commented Nov 28, 2024

Goal: Enable Auto-Saving for Settings

This PR introduces auto-saving functionality for settings by leveraging the Jotai state management library.

Implementation Details:

  1. Atomizing settings:
  • settings is now an atom, enabling managers and components to observe changes reactively.
  • A new global access method, getSettings(), provides read access to settings, allowing significant cleanup of constructors.
  • Adding subscribeToSettingsChange in managers that need to reset on settings change.
  1. Enforcing Controlled Updates:
  • Direct updates to settings have been removed since such changes were not observable and failed to refresh dependent components.
  • Updates must now use setSettings or updateSetting, ensuring proper state management.
  • Additionally, getSettings() now returns a readonly value, enabling TypeScript to catch unintended direct modifications.
  1. Simplified State Management:
  • The langChainParams field has been removed.
  • In its place, chatModel and chainType have been converted into separate atoms, as they are not general settings. These individual atoms allow components and managers to observe and modify them independently.
  • The rests are replaced with settings.

Notes:

  • Global Reactivity: Any change to a settings field triggers resets or re-renders for all managers and components that rely on settings. While this is acceptable in most cases, it may lead to inefficiencies in edge scenarios.
  • Future Improvements: Observing changes at a more granular level is feasible but introduces complexity. Unlike React’s dependency arrays, manually selecting keys to observe lacks systematic checks and can lead to errors.

@zeroliu zeroliu marked this pull request as draft November 28, 2024 01:10
@zeroliu zeroliu changed the title Auto save settings [WIP] Auto save settings Nov 28, 2024
@logancyang logancyang linked an issue Nov 28, 2024 that may be closed by this pull request
@zeroliu zeroliu force-pushed the zero/auto-save-settings branch 3 times, most recently from 2528c14 to d6df88e Compare November 29, 2024 00:23
}
return BrevilabsClient.instance;
}

private checkLicenseKey() {
if (!this.licenseKey) {
if (!getSettings().plusLicenseKey) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change ensures BrevilabsClient always uses the latest license key

subscribeToChainTypeChange(() =>
this.setChain(getChainType(), {
debug: getSettings().debug,
refreshIndex:
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This chunk of code re-implements reindex on chain type change

): void {
this.langChainParams[key] = value;
this.createChainWithNewModel();
subscribeToModelKeyChange(() => this.createChainWithNewModel());
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

chain needs to be recreated when model key or settings change.

Here it listens to all settings key changes, which is not necessary. But it will cover all edge cases in case someone adds new settings dependency in the future.

this.chatModelManager.setChatModel(customModel);
// Must update the chatModel for chain because ChainFactory always
// retrieves the old chain without the chatModel change if it exists!
// Create a new chain with the new chatModel
this.createChain(this.getLangChainParams().chainType, {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

createChain and setChain has no functionality differences

@@ -138,30 +120,15 @@ export default class ChainManager {
customModel = BUILTIN_CHAT_MODELS[0];
newModelKey = customModel.name + "|" + customModel.provider;
}
this.setLangChainParam("modelKey", newModelKey);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is done within setChain

@@ -174,10 +141,7 @@ export default class ChainManager {
this.validateChainType(chainType);

// Handle index refresh if needed
if (
options.refreshIndex &&
(chainType === ChainType.VAULT_QA_CHAIN || chainType === ChainType.COPILOT_PLUS_CHAIN)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This check is done when setting refreshIndex

@@ -188,25 +152,14 @@ export default class ChainManager {

switch (chainType) {
case ChainType.LLM_CHAIN: {
// For initial load of the plugin
if (options.forceNewCreation) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

forceNewCreation is always true

@@ -261,9 +246,7 @@ export default class ChatModelManager {

const modelConfig = this.getModelConfig(model);

// MUST update it since chatModelManager is a singleton.
this.getLangChainParams().modelKey = modelKey;
new Notice(`Setting model: ${modelConfig.modelName}`);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am removing this notice because buildModelMap is called on any settings value change, which can be annoying for it to show when unrelated settings values are set

@@ -72,12 +56,6 @@ class VectorStoreManager {
console.error("Failed to initialize Copilot database:", error);
});

// Initialize the rate limiter
VectorDBManager.initialize({
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is no longer needed

encryptionService: EncryptionService,
getLangChainParams: () => LangChainParams
) {
constructor(app: App) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no subscription to settings needed in this manager because all the methods always use the latest settings value

@@ -24,42 +55,11 @@ export interface ModelConfig {
enableCors?: boolean;
}

export interface LangChainParams {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LangChainParams have been replaced with settings, modelKey and chainType.

src/aiState.ts Outdated
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is no longer needed as components are interacting with the hooks exposed from aiParams.ts

@@ -11,7 +11,7 @@ import { getAIResponse } from "@/langchainStream";
import ChainManager from "@/LLMProviders/chainManager";
import CopilotPlugin from "@/main";
import { Mention } from "@/mentions/Mention";
import { useSettingsValueContext } from "@/settings/contexts/SettingsValueContext";
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

settings has become global. We don't need settings value context any more

@zeroliu zeroliu force-pushed the zero/auto-save-settings branch 2 times, most recently from b55e3cf to 1089023 Compare November 29, 2024 00:53
Comment on lines -35 to -37
if (!usageStrategy) {
console.warn("PromptUsageStrategy not initialize");
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This code was very fragile. Many callers do not have access a usage strategy instance. If they ever get called before main.ts, it will not work. With the settings refactoring, this class can instantiate its usageStrategy now.

@@ -60,13 +56,13 @@ export class CustomPromptProcessor {
}

// Clean up promptUsageTimestamps
this.usageStrategy?.removeUnusedPrompts(prompts.map((prompt) => prompt.title)).save();
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

save is done automatically when any setting value changes now

}

export default class EncryptionService {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we don't need a class if all the classes are static and there is no class state.

src/main.ts Outdated

async onload(): Promise<void> {
await this.loadSettings();
this.settingsUnsubscriber = subscribeToSettingsChange(() => {
this.saveData(getSettings());
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

auto save on any settings value change

@@ -379,17 +340,6 @@ export default class CopilotPlugin extends Plugin {
},
});

// Index vault to Copilot index on startup and after loading all commands
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is already done in VectorStoreManager post init job

@@ -550,27 +506,6 @@ export default class CopilotPlugin extends Plugin {
return Array.from(modelMap.values());
}

mergeAllActiveModelsWithCoreModels(): void {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

moved to settings/model.ts

@@ -276,24 +275,6 @@ export function areEmbeddingModelsSame(
return model1 === model2;
}

export function sanitizeSettings(settings: CopilotSettings): CopilotSettings {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

moved to settings/model


private static getRateLimiter(): RateLimiter {
if (!this.config) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

config will always be set now

@zeroliu zeroliu force-pushed the zero/auto-save-settings branch 2 times, most recently from b45bf47 to dbd2ccb Compare November 29, 2024 01:46
@@ -98,13 +92,13 @@ export class BrevilabsClient {
method,
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${this.licenseKey}`,
Authorization: `Bearer ${getDecryptedKey(getSettings().plusLicenseKey)}`,
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Found an existing bug! plus license key was not working with encryption enabled

@@ -507,8 +464,9 @@ export default class CopilotPlugin extends Plugin {
active: true,
});
}
} else {
this.app.workspace.revealLeaf(leaves[0]);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Found an existing bug! Reveal leaf will throw if the leaf is newly created

@zeroliu zeroliu marked this pull request as ready for review November 29, 2024 01:52
@zeroliu zeroliu changed the title [WIP] Auto save settings Auto save settings Nov 29, 2024
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Created a base class for confirm modal

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Migrate the existing modal to use the new confirm modal. Also refine the styling of this modal

<div style={{ display: "flex", flexDirection: "column", gap: "1rem" }}>
<h1 style={{ display: "flex", alignItems: "center", justifyContent: "space-between" }}>
Copilot Settings
<button onClick={() => new ResetSettingsConfirmModal(app, () => resetSettings()).open()}>
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

added a modal to confirm the change here. This button becomes more powerful than before and it can get annoying if people accidentally click on it and lose all of their keys

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Autosave settings after settings value are changed
1 participant