mirror of
https://github.com/andy-stack/vaultkeeper-ai.git
synced 2026-07-22 06:42:03 +00:00
Replace console.error/log with Exception.log helper, improve OpenAI error handling with retryable errors, add type-safe model validation, and add new AI models (Claude Opus 4.5, Gemini 3 Pro Preview, GPT-5.1)
231 lines
No EOL
7.5 KiB
TypeScript
231 lines
No EOL
7.5 KiB
TypeScript
import { AIProviderModel, fromModel } from "Enums/ApiProvider";
|
|
import { Copy } from "Enums/Copy";
|
|
import { Selector } from "Enums/Selector";
|
|
import type VaultkeeperAIPlugin from "main";
|
|
import { PluginSettingTab, Setting, setIcon, setTooltip } from "obsidian";
|
|
import { Resolve } from "Services/DependencyService";
|
|
import type { SettingsService } from "Services/SettingsService";
|
|
import { Services } from "Services/Services";
|
|
import { RegisterAiProvider } from "Services/ServiceRegistration";
|
|
|
|
export class VaultkeeperAISettingTab extends PluginSettingTab {
|
|
private readonly settingsService: SettingsService;
|
|
|
|
private apiKeySetting: Setting | null = null;
|
|
private apiKeyInputEl: HTMLInputElement | null = null;
|
|
|
|
constructor() {
|
|
const plugin = Resolve<VaultkeeperAIPlugin>(Services.VaultkeeperAIPlugin);
|
|
super(plugin.app, plugin);
|
|
this.settingsService = Resolve<SettingsService>(Services.SettingsService);
|
|
}
|
|
|
|
display() {
|
|
const { containerEl } = this;
|
|
|
|
containerEl.empty();
|
|
|
|
/* Model Selection Setting */
|
|
new Setting(containerEl)
|
|
.setName(Copy.SettingModel)
|
|
.setDesc(Copy.SettingModelDesc)
|
|
.addDropdown((dropdown) => {
|
|
const select = dropdown.selectEl;
|
|
|
|
// Claude models group
|
|
const claudeGroup = select.createEl("optgroup", { attr: { label: Copy.ProviderClaude } });
|
|
claudeGroup.createEl("option", {
|
|
value: AIProviderModel.ClaudeSonnet_4_5,
|
|
text: Copy.ClaudeSonnet_4_5
|
|
});
|
|
claudeGroup.createEl("option", {
|
|
value: AIProviderModel.ClaudeSonnet_4,
|
|
text: Copy.ClaudeSonnet_4
|
|
});
|
|
claudeGroup.createEl("option", {
|
|
value: AIProviderModel.ClaudeSonnet_3_7,
|
|
text: Copy.ClaudeSonnet_3_7
|
|
});
|
|
claudeGroup.createEl("option", {
|
|
value: AIProviderModel.ClaudeOpus_4_5,
|
|
text: Copy.ClaudeOpus_4_5
|
|
});
|
|
claudeGroup.createEl("option", {
|
|
value: AIProviderModel.ClaudeOpus_4_1,
|
|
text: Copy.ClaudeOpus_4_1
|
|
});
|
|
claudeGroup.createEl("option", {
|
|
value: AIProviderModel.ClaudeOpus_4,
|
|
text: Copy.ClaudeOpus_4
|
|
});
|
|
claudeGroup.createEl("option", {
|
|
value: AIProviderModel.ClaudeHaiku_4_5,
|
|
text: Copy.ClaudeHaiku_4_5
|
|
});
|
|
|
|
// OpenAI models group
|
|
const openaiGroup = select.createEl("optgroup", { attr: { label: Copy.ProviderOpenAI } });
|
|
openaiGroup.createEl("option", {
|
|
value: AIProviderModel.GPT_5_1,
|
|
text: Copy.GPT_5_1
|
|
});
|
|
openaiGroup.createEl("option", {
|
|
value: AIProviderModel.GPT_5,
|
|
text: Copy.GPT_5
|
|
});
|
|
openaiGroup.createEl("option", {
|
|
value: AIProviderModel.GPT_5_Mini,
|
|
text: Copy.GPT_5_Mini
|
|
});
|
|
openaiGroup.createEl("option", {
|
|
value: AIProviderModel.GPT_5_Nano,
|
|
text: Copy.GPT_5_Nano
|
|
});
|
|
openaiGroup.createEl("option", {
|
|
value: AIProviderModel.GPT_5_Pro,
|
|
text: Copy.GPT_5_Pro
|
|
});
|
|
openaiGroup.createEl("option", {
|
|
value: AIProviderModel.GPT_4o,
|
|
text: Copy.GPT_4o
|
|
});
|
|
openaiGroup.createEl("option", {
|
|
value: AIProviderModel.GPT_4o_Mini,
|
|
text: Copy.GPT_4o_Mini
|
|
});
|
|
openaiGroup.createEl("option", {
|
|
value: AIProviderModel.GPT_4_1,
|
|
text: Copy.GPT_4_1
|
|
});
|
|
openaiGroup.createEl("option", {
|
|
value: AIProviderModel.GPT_4_1_Mini,
|
|
text: Copy.GPT_4_1_Mini
|
|
});
|
|
|
|
// Gemini models group
|
|
const geminiGroup = select.createEl("optgroup", { attr: { label: Copy.ProviderGemini } });
|
|
geminiGroup.createEl("option", {
|
|
value: AIProviderModel.GeminiFlash_2_5_Lite,
|
|
text: Copy.GeminiFlash_2_5_Lite
|
|
});
|
|
geminiGroup.createEl("option", {
|
|
value: AIProviderModel.GeminiFlash_2_5,
|
|
text: Copy.GeminiFlash_2_5
|
|
});
|
|
geminiGroup.createEl("option", {
|
|
value: AIProviderModel.GeminiPro_2_5,
|
|
text: Copy.GeminiPro_2_5
|
|
});
|
|
geminiGroup.createEl("option", {
|
|
value: AIProviderModel.GeminiPro_3_Preview,
|
|
text: Copy.GeminiPro_3_Preview
|
|
});
|
|
|
|
dropdown.setValue(this.settingsService.settings.model);
|
|
dropdown.onChange(async (value) => {
|
|
this.settingsService.settings.model = value;
|
|
await this.settingsService.saveSettings(() => RegisterAiProvider());
|
|
if (this.apiKeyInputEl) {
|
|
this.apiKeyInputEl.value = this.settingsService.getApiKeyForCurrentModel();
|
|
this.highlightApiKey();
|
|
}
|
|
});
|
|
});
|
|
|
|
/* API Key Setting */
|
|
this.apiKeySetting = new Setting(containerEl)
|
|
.setName(Copy.SettingApiKey)
|
|
.setDesc(Copy.SettingApiKeyDesc)
|
|
.addText(text => {
|
|
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());
|
|
this.highlightApiKey();
|
|
});
|
|
text.inputEl.type = "password";
|
|
this.apiKeyInputEl = text.inputEl;
|
|
})
|
|
.addExtraButton(button => {
|
|
button
|
|
.setTooltip(Copy.TooltipShowApiKey)
|
|
.onClick(() => {
|
|
if (this.apiKeyInputEl && this.apiKeyInputEl.type === "password") {
|
|
this.apiKeyInputEl.type = "text";
|
|
setIcon(button.extraSettingsEl, "eye-off");
|
|
setTooltip(button.extraSettingsEl, Copy.TooltipHideApiKey);
|
|
} else if (this.apiKeyInputEl) {
|
|
this.apiKeyInputEl.type = "password";
|
|
setIcon(button.extraSettingsEl, "eye");
|
|
setTooltip(button.extraSettingsEl, Copy.TooltipShowApiKey);
|
|
}
|
|
});
|
|
setIcon(button.extraSettingsEl, "eye");
|
|
});
|
|
this.highlightApiKey();
|
|
|
|
/* Exclusions Setting */
|
|
new Setting(containerEl)
|
|
.setName(Copy.SettingFileExclusions)
|
|
.setDesc(Copy.SettingFileExclusionsDesc)
|
|
.addTextArea(text => {
|
|
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();
|
|
});
|
|
text.inputEl.classList.add(Selector.AIExclusionsInput);
|
|
});
|
|
|
|
/* Context Header */
|
|
new Setting(containerEl)
|
|
.setHeading()
|
|
.setName(Copy.SettingContext);
|
|
|
|
/* Search Results Limit Setting */
|
|
new Setting(containerEl)
|
|
.setName(Copy.SettingSearchResultsLimit)
|
|
.setDesc(Copy.SettingSearchResultsLimitDesc)
|
|
.addSlider(slider => {
|
|
slider
|
|
.setLimits(5, 40, 1)
|
|
.setValue(this.settingsService.settings.searchResultsLimit)
|
|
.setDynamicTooltip()
|
|
.onChange(async (value) => {
|
|
this.settingsService.settings.searchResultsLimit = value;
|
|
await this.settingsService.saveSettings();
|
|
});
|
|
});
|
|
|
|
/* Snippet Size Limit Setting */
|
|
new Setting(containerEl)
|
|
.setName(Copy.SettingSnippetSizeLimit)
|
|
.setDesc(Copy.SettingSnippetSizeLimitDesc)
|
|
.addSlider(slider => {
|
|
slider
|
|
.setLimits(50, 1000, 10)
|
|
.setValue(this.settingsService.settings.snippetSizeLimit)
|
|
.setDynamicTooltip()
|
|
.onChange(async (value) => {
|
|
this.settingsService.settings.snippetSizeLimit = value;
|
|
await this.settingsService.saveSettings();
|
|
});
|
|
});
|
|
}
|
|
|
|
private highlightApiKey() {
|
|
if (this.apiKeySetting) {
|
|
const currentApiKey = this.settingsService.getApiKeyForCurrentModel();
|
|
if (currentApiKey.trim() === "") {
|
|
this.apiKeySetting.settingEl.removeClass(Selector.ApiKeySettingOk);
|
|
this.apiKeySetting.settingEl.addClass(Selector.ApiKeySettingError);
|
|
} else {
|
|
this.apiKeySetting.settingEl.removeClass(Selector.ApiKeySettingError);
|
|
this.apiKeySetting.settingEl.addClass(Selector.ApiKeySettingOk);
|
|
}
|
|
}
|
|
}
|
|
} |