Unify AI provider selection across all features

Every LLM-backed feature (Post-Processing, Proofreading, Title
Generation, Property Filler) now reads from one CHAT_PROVIDERS
registry instead of 5 hand-duplicated provider lists, so they always
offer the exact same providers in the exact same order.

- Add Groq as a 5th chat-completion provider (was transcription-only
  despite its OpenAI-compatible API and an already-collected key).
- Provider dropdowns disable options with no API key configured
  (shown as e.g. "Anthropic (no API key)") instead of silently
  failing at runtime when picked.
- Collapse 3 byte-for-byte duplicated resolveProvider() methods
  (Proofreader, TitleGenerator, PropertyFiller) plus AudioHandler's
  getPostProcessingApiKey() into one resolveChatProvider() helper
  (src/ProviderResolver.ts).
- Fix Custom provider: previously Proofreading/Title/Property silently
  reused Post-Processing's postProcessingUrl with no visible field to
  set it. Renamed to a shared customApiUrl field with its own UI
  (createCustomProviderFields), now exposed on every tab that offers
  Custom, plus rows on the API Keys tab. Migrates old data.json values
  automatically.
- Update README settings reference and getting-started sections to
  match.

No behavior change for existing single-provider setups; migration
handles the postProcessingUrl -> customApiUrl rename transparently.
This commit is contained in:
2026-09-07 17:14:33 -04:00
parent 3abd9d9b3b
commit 9b7ec36828
9 changed files with 270 additions and 207 deletions
+5 -29
View File
@@ -1,6 +1,6 @@
import { Editor, Notice } from "obsidian";
import { AIService } from "../AIService";
import { AIProvider, PROVIDER_URLS } from "../SettingsManager";
import { resolveChatProvider } from "../ProviderResolver";
import NibbleAI from "main";
export class Proofreader {
@@ -10,33 +10,6 @@ export class Proofreader {
this.plugin = plugin;
}
private resolveProvider(): { apiKey: string; endpoint: string; provider: AIProvider } | null {
const provider = this.plugin.settings.proofreadingProvider || "openrouter";
let apiKey = "";
let endpoint = PROVIDER_URLS[provider] || "";
switch (provider) {
case "openai":
apiKey = this.plugin.settings.openAiApiKey;
break;
case "anthropic":
apiKey = this.plugin.settings.anthropicApiKey;
break;
case "openrouter":
apiKey = this.plugin.settings.openRouterApiKey;
break;
case "custom":
apiKey = this.plugin.settings.customApiKey;
endpoint = this.plugin.settings.postProcessingUrl || "";
break;
}
if (!apiKey) return null;
if (!endpoint) return null;
return { apiKey, endpoint, provider };
}
async fixSelection(editor: Editor): Promise<void> {
const selection = editor.getSelection();
if (!selection || selection.trim().length === 0) {
@@ -44,7 +17,10 @@ export class Proofreader {
return;
}
const config = this.resolveProvider();
const config = resolveChatProvider(
this.plugin.settings,
this.plugin.settings.proofreadingProvider || "openrouter"
);
if (!config) {
new Notice(
"⚠️ No API key for selected provider. Check Settings \u2192 NibbleAI \u2192 API Keys."