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:
@@ -75,11 +75,15 @@ Open **Settings** → **NibbleAI** and add at least one API key:
|
|||||||
|
|
||||||
| Key | Used For |
|
| Key | Used For |
|
||||||
|-----|----------|
|
|-----|----------|
|
||||||
| **Transcription API Key** | Whisper transcription (OpenAI, Groq, Azure) |
|
| **Groq API Key** | Whisper transcription, and as a chat provider for proofreading/title/property/post-processing |
|
||||||
| **OpenAI API Key** | Proofreading, title generation, property filling, post-processing *(recommended: OpenRouter)* |
|
| **OpenAI API Key** | Whisper transcription, and as a chat provider for proofreading/title/property/post-processing |
|
||||||
| **Anthropic API Key** | Post-processing with Claude models |
|
| **Anthropic API Key** | Claude models for proofreading/title/property/post-processing |
|
||||||
|
| **OpenRouter API Key** | Access to hundreds of models for proofreading/title/property/post-processing |
|
||||||
|
| **Custom API Key + URL** | Any OpenAI-compatible endpoint (self-hosted, Azure, etc.) |
|
||||||
|
|
||||||
> **Tip:** For proofreading, title generation, and property filling, an [OpenRouter](https://openrouter.ai/keys) API key with a free model like `google/gemini-2.5-flash-lite` costs nothing and works great.
|
Every LLM-backed feature (Proofreading, Title Generation, Property Filler, Post-Processing) shows the same provider dropdown — pick any provider that has a key configured above.
|
||||||
|
|
||||||
|
> **Tip:** An [OpenRouter](https://openrouter.ai/keys) API key with a free model like `google/gemini-2.5-flash-lite` costs nothing and works great across all four AI features.
|
||||||
|
|
||||||
### 2. Try the Features
|
### 2. Try the Features
|
||||||
|
|
||||||
@@ -172,13 +176,14 @@ Securely stored in your system keychain via Obsidian's SecretStorage. Never writ
|
|||||||
| Setting | Default | Description |
|
| Setting | Default | Description |
|
||||||
|---------|---------|-------------|
|
|---------|---------|-------------|
|
||||||
| Enable | Off | Clean up transcripts with an LLM |
|
| Enable | Off | Clean up transcripts with an LLM |
|
||||||
| Provider | Anthropic | Anthropic, OpenAI, or Custom |
|
| Provider | Anthropic | Any configured provider: Anthropic, OpenAI, Groq, OpenRouter, or Custom |
|
||||||
| Model | `claude-sonnet-4-20250514` | LLM for cleanup |
|
| Model | `claude-sonnet-4-20250514` | LLM for cleanup |
|
||||||
| Auto-generate title | Off | Create filenames from content |
|
| Auto-generate title | Off | Create filenames from content |
|
||||||
|
|
||||||
### Proofreading
|
### Proofreading
|
||||||
| Setting | Default | Description |
|
| Setting | Default | Description |
|
||||||
|---------|---------|-------------|
|
|---------|---------|-------------|
|
||||||
|
| Provider | OpenRouter | Any configured provider: Anthropic, OpenAI, Groq, OpenRouter, or Custom |
|
||||||
| Model | `openai/gpt-4o-mini` | Model for proofreading |
|
| Model | `openai/gpt-4o-mini` | Model for proofreading |
|
||||||
| Temperature | `0.2` | Lower = more consistent |
|
| Temperature | `0.2` | Lower = more consistent |
|
||||||
| Max tokens | `2000` | Maximum response length |
|
| Max tokens | `2000` | Maximum response length |
|
||||||
@@ -187,12 +192,16 @@ Securely stored in your system keychain via Obsidian's SecretStorage. Never writ
|
|||||||
### Title Generation
|
### Title Generation
|
||||||
| Setting | Default | Description |
|
| Setting | Default | Description |
|
||||||
|---------|---------|-------------|
|
|---------|---------|-------------|
|
||||||
|
| Provider | OpenRouter | Any configured provider: Anthropic, OpenAI, Groq, OpenRouter, or Custom |
|
||||||
|
| Model | `openai/gpt-4o-mini` | Model for generating titles |
|
||||||
| Mode | Rename file | Rename file or set frontmatter property |
|
| Mode | Rename file | Rename file or set frontmatter property |
|
||||||
| Max title length | 8 words | Maximum words in generated title |
|
| Max title length | 8 words | Maximum words in generated title |
|
||||||
|
|
||||||
### Property Filler
|
### Property Filler
|
||||||
| Setting | Default | Description |
|
| Setting | Default | Description |
|
||||||
|---------|---------|-------------|
|
|---------|---------|-------------|
|
||||||
|
| Provider | OpenRouter | Any configured provider: Anthropic, OpenAI, Groq, OpenRouter, or Custom |
|
||||||
|
| Model | `openai/gpt-4o-mini` | Model for generating property values |
|
||||||
| Custom prompt | *(built-in)* | Template with `{property}` placeholder |
|
| Custom prompt | *(built-in)* | Template with `{property}` placeholder |
|
||||||
| Overwrite existing | Off | Replace existing property values |
|
| Overwrite existing | Off | Replace existing property values |
|
||||||
|
|
||||||
@@ -205,7 +214,8 @@ NibbleAI/
|
|||||||
├── main.ts # Plugin entry point, commands, events
|
├── main.ts # Plugin entry point, commands, events
|
||||||
├── src/
|
├── src/
|
||||||
│ ├── SettingsManager.ts # Settings + SecretStorage management
|
│ ├── SettingsManager.ts # Settings + SecretStorage management
|
||||||
│ ├── AIService.ts # Unified AI client (OpenAI + Anthropic)
|
│ ├── AIService.ts # Unified AI client (OpenAI-compatible + Anthropic-native)
|
||||||
|
│ ├── ProviderResolver.ts # Shared provider → API key/endpoint resolution
|
||||||
│ ├── ModelBrowser.ts # OpenRouter model suggest modal
|
│ ├── ModelBrowser.ts # OpenRouter model suggest modal
|
||||||
│ ├── PostProcessor.ts # LLM post-processing for transcripts
|
│ ├── PostProcessor.ts # LLM post-processing for transcripts
|
||||||
│ ├── NibbleAISettingsTab.ts # Settings UI
|
│ ├── NibbleAISettingsTab.ts # Settings UI
|
||||||
|
|||||||
+1
-1
@@ -5,7 +5,7 @@ export interface ChatCompletionOptions {
|
|||||||
apiKey: string;
|
apiKey: string;
|
||||||
model: string;
|
model: string;
|
||||||
endpoint?: string;
|
endpoint?: string;
|
||||||
provider?: "anthropic" | "openai" | "openrouter" | "custom";
|
provider?: "anthropic" | "openai" | "openrouter" | "groq" | "custom";
|
||||||
temperature?: number;
|
temperature?: number;
|
||||||
maxTokens?: number;
|
maxTokens?: number;
|
||||||
}
|
}
|
||||||
|
|||||||
+144
-82
@@ -3,9 +3,8 @@ import { App, PluginSettingTab, Setting, setIcon } from "obsidian";
|
|||||||
import {
|
import {
|
||||||
SettingsManager,
|
SettingsManager,
|
||||||
PostProcessingProvider,
|
PostProcessingProvider,
|
||||||
AIProvider,
|
|
||||||
TranscriptionProvider,
|
TranscriptionProvider,
|
||||||
PROVIDER_URLS,
|
CHAT_PROVIDERS,
|
||||||
PROVIDER_DEFAULT_MODELS,
|
PROVIDER_DEFAULT_MODELS,
|
||||||
} from "./SettingsManager";
|
} from "./SettingsManager";
|
||||||
import { ModelSuggestModal } from "./ModelBrowser";
|
import { ModelSuggestModal } from "./ModelBrowser";
|
||||||
@@ -106,10 +105,14 @@ export class NibbleAISettingsTab extends PluginSettingTab {
|
|||||||
text: "API keys are stored securely in your system keychain.",
|
text: "API keys are stored securely in your system keychain.",
|
||||||
cls: "setting-item-description",
|
cls: "setting-item-description",
|
||||||
});
|
});
|
||||||
|
containerEl.createEl("p", {
|
||||||
|
text: "Each AI feature below (Proofreading, Title Generation, Property Filler, Post-Processing) lets you pick which of these providers to use, provided its key is set here.",
|
||||||
|
cls: "setting-item-description",
|
||||||
|
});
|
||||||
|
|
||||||
this.createApiKeySetting(containerEl,
|
this.createApiKeySetting(containerEl,
|
||||||
"Groq API Key",
|
"Groq API Key",
|
||||||
"For Whisper transcription via Groq (groq.com)",
|
"For Whisper transcription and Groq chat models (groq.com)",
|
||||||
"gsk_...",
|
"gsk_...",
|
||||||
this.plugin.settings.groqApiKey,
|
this.plugin.settings.groqApiKey,
|
||||||
async (value) => {
|
async (value) => {
|
||||||
@@ -139,7 +142,7 @@ export class NibbleAISettingsTab extends PluginSettingTab {
|
|||||||
);
|
);
|
||||||
this.createApiKeySetting(containerEl,
|
this.createApiKeySetting(containerEl,
|
||||||
"OpenRouter API Key",
|
"OpenRouter API Key",
|
||||||
"For OpenRouter models — get one at openrouter.ai/keys. Used for proofreading, title generation, property filling, and post-processing.",
|
"For OpenRouter models — get one at openrouter.ai/keys.",
|
||||||
"sk-or-v1-...",
|
"sk-or-v1-...",
|
||||||
this.plugin.settings.openRouterApiKey,
|
this.plugin.settings.openRouterApiKey,
|
||||||
async (value) => {
|
async (value) => {
|
||||||
@@ -147,6 +150,26 @@ export class NibbleAISettingsTab extends PluginSettingTab {
|
|||||||
await this.save();
|
await this.save();
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
this.createApiKeySetting(containerEl,
|
||||||
|
"Custom API Key",
|
||||||
|
"For a self-hosted or OpenAI-compatible endpoint not listed above",
|
||||||
|
"sk-...xxxx",
|
||||||
|
this.plugin.settings.customApiKey,
|
||||||
|
async (value) => {
|
||||||
|
this.plugin.settings.customApiKey = value;
|
||||||
|
await this.save();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
this.createTextSetting(containerEl,
|
||||||
|
"Custom API URL",
|
||||||
|
"Chat completions endpoint for the custom provider",
|
||||||
|
"https://api.example.com/v1/chat/completions",
|
||||||
|
this.plugin.settings.customApiUrl,
|
||||||
|
async (value) => {
|
||||||
|
this.plugin.settings.customApiUrl = value;
|
||||||
|
await this.save();
|
||||||
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Transcription Tab ──
|
// ── Transcription Tab ──
|
||||||
@@ -328,29 +351,22 @@ export class NibbleAISettingsTab extends PluginSettingTab {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (this.plugin.settings.postProcessing) {
|
if (this.plugin.settings.postProcessing) {
|
||||||
this.createPostProcessingProviderSetting(containerEl);
|
this.createChatProviderSetting(containerEl,
|
||||||
|
"Select the LLM provider for cleaning up transcriptions",
|
||||||
|
this.plugin.settings.postProcessingProvider,
|
||||||
|
async (provider) => {
|
||||||
|
this.plugin.settings.postProcessingProvider = provider;
|
||||||
|
if (provider !== "custom") {
|
||||||
|
this.plugin.settings.postProcessingModel =
|
||||||
|
PROVIDER_DEFAULT_MODELS[provider];
|
||||||
|
}
|
||||||
|
await this.save();
|
||||||
|
this.display();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
if (this.plugin.settings.postProcessingProvider === "custom") {
|
if (this.plugin.settings.postProcessingProvider === "custom") {
|
||||||
this.createTextSetting(containerEl,
|
this.createCustomProviderFields(containerEl);
|
||||||
"API URL",
|
|
||||||
"Custom endpoint for chat completions",
|
|
||||||
"https://api.example.com/v1/chat/completions",
|
|
||||||
this.plugin.settings.postProcessingUrl,
|
|
||||||
async (value) => {
|
|
||||||
this.plugin.settings.postProcessingUrl = value;
|
|
||||||
await this.save();
|
|
||||||
}
|
|
||||||
);
|
|
||||||
this.createApiKeySetting(containerEl,
|
|
||||||
"API Key",
|
|
||||||
"API key for the custom endpoint",
|
|
||||||
"sk-...xxxx",
|
|
||||||
this.plugin.settings.customApiKey,
|
|
||||||
async (value) => {
|
|
||||||
this.plugin.settings.customApiKey = value;
|
|
||||||
await this.save();
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.createModelSetting(containerEl,
|
this.createModelSetting(containerEl,
|
||||||
@@ -424,15 +440,23 @@ export class NibbleAISettingsTab extends PluginSettingTab {
|
|||||||
cls: "setting-item-description",
|
cls: "setting-item-description",
|
||||||
});
|
});
|
||||||
|
|
||||||
this.createProviderSetting(containerEl,
|
this.createChatProviderSetting(containerEl,
|
||||||
"Provider",
|
|
||||||
"Select the AI provider for proofreading",
|
"Select the AI provider for proofreading",
|
||||||
this.plugin.settings.proofreadingProvider,
|
this.plugin.settings.proofreadingProvider,
|
||||||
async (value) => {
|
async (provider) => {
|
||||||
this.plugin.settings.proofreadingProvider = value as AIProvider;
|
this.plugin.settings.proofreadingProvider = provider;
|
||||||
|
if (provider !== "custom") {
|
||||||
|
this.plugin.settings.proofreadingModel =
|
||||||
|
PROVIDER_DEFAULT_MODELS[provider];
|
||||||
|
}
|
||||||
await this.save();
|
await this.save();
|
||||||
|
this.display();
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (this.plugin.settings.proofreadingProvider === "custom") {
|
||||||
|
this.createCustomProviderFields(containerEl);
|
||||||
|
}
|
||||||
this.createModelSetting(containerEl,
|
this.createModelSetting(containerEl,
|
||||||
"Model",
|
"Model",
|
||||||
"Model for fixing punctuation and grammar",
|
"Model for fixing punctuation and grammar",
|
||||||
@@ -508,15 +532,23 @@ export class NibbleAISettingsTab extends PluginSettingTab {
|
|||||||
cls: "setting-item-description",
|
cls: "setting-item-description",
|
||||||
});
|
});
|
||||||
|
|
||||||
this.createProviderSetting(containerEl,
|
this.createChatProviderSetting(containerEl,
|
||||||
"Provider",
|
|
||||||
"Select the AI provider for title generation",
|
"Select the AI provider for title generation",
|
||||||
this.plugin.settings.titleGenProvider,
|
this.plugin.settings.titleGenProvider,
|
||||||
async (value) => {
|
async (provider) => {
|
||||||
this.plugin.settings.titleGenProvider = value as AIProvider;
|
this.plugin.settings.titleGenProvider = provider;
|
||||||
|
if (provider !== "custom") {
|
||||||
|
this.plugin.settings.titleGenModel =
|
||||||
|
PROVIDER_DEFAULT_MODELS[provider];
|
||||||
|
}
|
||||||
await this.save();
|
await this.save();
|
||||||
|
this.display();
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (this.plugin.settings.titleGenProvider === "custom") {
|
||||||
|
this.createCustomProviderFields(containerEl);
|
||||||
|
}
|
||||||
this.createModelSetting(containerEl,
|
this.createModelSetting(containerEl,
|
||||||
"Model",
|
"Model",
|
||||||
"Model for generating titles",
|
"Model for generating titles",
|
||||||
@@ -577,15 +609,23 @@ export class NibbleAISettingsTab extends PluginSettingTab {
|
|||||||
cls: "setting-item-description",
|
cls: "setting-item-description",
|
||||||
});
|
});
|
||||||
|
|
||||||
this.createProviderSetting(containerEl,
|
this.createChatProviderSetting(containerEl,
|
||||||
"Provider",
|
|
||||||
"Select the AI provider for property generation",
|
"Select the AI provider for property generation",
|
||||||
this.plugin.settings.propertyGenProvider,
|
this.plugin.settings.propertyGenProvider,
|
||||||
async (value) => {
|
async (provider) => {
|
||||||
this.plugin.settings.propertyGenProvider = value as AIProvider;
|
this.plugin.settings.propertyGenProvider = provider;
|
||||||
|
if (provider !== "custom") {
|
||||||
|
this.plugin.settings.propertyGenModel =
|
||||||
|
PROVIDER_DEFAULT_MODELS[provider];
|
||||||
|
}
|
||||||
await this.save();
|
await this.save();
|
||||||
|
this.display();
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (this.plugin.settings.propertyGenProvider === "custom") {
|
||||||
|
this.createCustomProviderFields(containerEl);
|
||||||
|
}
|
||||||
this.createModelSetting(containerEl,
|
this.createModelSetting(containerEl,
|
||||||
"Model",
|
"Model",
|
||||||
"Model for generating property values",
|
"Model for generating property values",
|
||||||
@@ -748,30 +788,53 @@ export class NibbleAISettingsTab extends PluginSettingTab {
|
|||||||
infoEl.style.flex = "1 1 100%";
|
infoEl.style.flex = "1 1 100%";
|
||||||
}
|
}
|
||||||
|
|
||||||
private createProviderSetting(
|
/**
|
||||||
|
* Shared provider dropdown for every LLM-backed feature (Post-Processing,
|
||||||
|
* Proofreading, Title Generation, Property Filler). Reads from the single
|
||||||
|
* CHAT_PROVIDERS registry so all four features always offer the exact
|
||||||
|
* same providers, and disables any provider whose API key isn't set yet.
|
||||||
|
*/
|
||||||
|
private createChatProviderSetting(
|
||||||
containerEl: HTMLElement,
|
containerEl: HTMLElement,
|
||||||
name: string,
|
|
||||||
desc: string,
|
desc: string,
|
||||||
value: string,
|
currentProvider: PostProcessingProvider,
|
||||||
onChange: (value: string) => Promise<void>
|
onChange: (provider: PostProcessingProvider) => Promise<void>
|
||||||
): void {
|
): void {
|
||||||
const providers: Record<string, string> = {
|
const configured = new Set(
|
||||||
openrouter: "OpenRouter",
|
CHAT_PROVIDERS.filter((p) => !!this.plugin.settings[p.apiKeyField]).map(
|
||||||
openai: "OpenAI",
|
(p) => p.id
|
||||||
anthropic: "Anthropic",
|
)
|
||||||
custom: "Custom",
|
);
|
||||||
};
|
|
||||||
|
|
||||||
new Setting(containerEl)
|
new Setting(containerEl)
|
||||||
.setName(name)
|
.setName("Provider")
|
||||||
.setDesc(desc)
|
.setDesc(desc)
|
||||||
.addDropdown((dropdown) => {
|
.addDropdown((dropdown) => {
|
||||||
for (const [val, label] of Object.entries(providers)) {
|
for (const p of CHAT_PROVIDERS) {
|
||||||
dropdown.addOption(val, label);
|
dropdown.addOption(
|
||||||
|
p.id,
|
||||||
|
configured.has(p.id) ? p.label : `${p.label} (no API key)`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
dropdown.setValue(currentProvider || "openrouter");
|
||||||
|
dropdown.onChange(async (value) => {
|
||||||
|
await onChange(value as PostProcessingProvider);
|
||||||
|
});
|
||||||
|
for (const p of CHAT_PROVIDERS) {
|
||||||
|
if (configured.has(p.id)) continue;
|
||||||
|
const optionEl = dropdown.selectEl.querySelector(
|
||||||
|
`option[value="${p.id}"]`
|
||||||
|
) as HTMLOptionElement | null;
|
||||||
|
if (optionEl) optionEl.disabled = true;
|
||||||
}
|
}
|
||||||
dropdown.setValue(value || "openrouter");
|
|
||||||
dropdown.onChange(async (val) => await onChange(val));
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (configured.size === 0) {
|
||||||
|
containerEl.createEl("p", {
|
||||||
|
text: "⚠️ No AI provider configured yet — add an API key in the API Keys tab.",
|
||||||
|
cls: "setting-item-description",
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private createModelSetting(
|
private createModelSetting(
|
||||||
@@ -860,36 +923,35 @@ export class NibbleAISettingsTab extends PluginSettingTab {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private createPostProcessingProviderSetting(containerEl: HTMLElement): void {
|
/**
|
||||||
const providers: Record<PostProcessingProvider, string> = {
|
* Custom endpoint + key fields, shown whenever a feature's provider is
|
||||||
anthropic: "Anthropic",
|
* set to "Custom". This slot is shared across all four AI features.
|
||||||
openai: "OpenAI",
|
*/
|
||||||
openrouter: "OpenRouter",
|
private createCustomProviderFields(containerEl: HTMLElement): void {
|
||||||
custom: "Custom",
|
containerEl.createEl("p", {
|
||||||
};
|
text: "Custom provider connection \u2014 shared across Post-Processing, Proofreading, Title Generation, and Property Filler.",
|
||||||
|
cls: "setting-item-description",
|
||||||
new Setting(containerEl)
|
});
|
||||||
.setName("Provider")
|
this.createTextSetting(containerEl,
|
||||||
.setDesc("Select the LLM provider")
|
"Custom API URL",
|
||||||
.addDropdown((dropdown) => {
|
"OpenAI-compatible chat completions endpoint",
|
||||||
for (const [value, label] of Object.entries(providers)) {
|
"https://api.example.com/v1/chat/completions",
|
||||||
dropdown.addOption(value, label);
|
this.plugin.settings.customApiUrl,
|
||||||
}
|
async (value) => {
|
||||||
dropdown
|
this.plugin.settings.customApiUrl = value;
|
||||||
.setValue(this.plugin.settings.postProcessingProvider)
|
await this.save();
|
||||||
.onChange(async (value) => {
|
}
|
||||||
const provider = value as PostProcessingProvider;
|
);
|
||||||
this.plugin.settings.postProcessingProvider = provider;
|
this.createApiKeySetting(containerEl,
|
||||||
if (provider !== "custom") {
|
"Custom API Key",
|
||||||
this.plugin.settings.postProcessingUrl =
|
"API key for the custom endpoint",
|
||||||
PROVIDER_URLS[provider];
|
"sk-...xxxx",
|
||||||
this.plugin.settings.postProcessingModel =
|
this.plugin.settings.customApiKey,
|
||||||
PROVIDER_DEFAULT_MODELS[provider];
|
async (value) => {
|
||||||
}
|
this.plugin.settings.customApiKey = value;
|
||||||
await this.save();
|
await this.save();
|
||||||
this.display();
|
}
|
||||||
});
|
);
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async createAudioDeviceSetting(containerEl: HTMLElement): Promise<void> {
|
private async createAudioDeviceSetting(containerEl: HTMLElement): Promise<void> {
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
import { CHAT_PROVIDERS, PROVIDER_URLS, PluginSettings, PostProcessingProvider } from "./SettingsManager";
|
||||||
|
|
||||||
|
export interface ResolvedChatProvider {
|
||||||
|
apiKey: string;
|
||||||
|
endpoint: string;
|
||||||
|
provider: PostProcessingProvider;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolves the API key + endpoint for a chat-completion provider selection.
|
||||||
|
* Shared by every AI feature that talks to an LLM (Post-Processing,
|
||||||
|
* Proofreading, Title Generation, Property Filler) so they all treat
|
||||||
|
* providers — including "no key configured" — identically.
|
||||||
|
*/
|
||||||
|
export function resolveChatProvider(
|
||||||
|
settings: PluginSettings,
|
||||||
|
provider: PostProcessingProvider
|
||||||
|
): ResolvedChatProvider | null {
|
||||||
|
const info = CHAT_PROVIDERS.find((p) => p.id === provider);
|
||||||
|
if (!info) return null;
|
||||||
|
|
||||||
|
const apiKey = settings[info.apiKeyField];
|
||||||
|
const endpoint = provider === "custom" ? settings.customApiUrl : PROVIDER_URLS[provider];
|
||||||
|
|
||||||
|
if (!apiKey || !endpoint) return null;
|
||||||
|
return { apiKey, endpoint, provider };
|
||||||
|
}
|
||||||
+50
-12
@@ -1,6 +1,6 @@
|
|||||||
import { Plugin } from "obsidian";
|
import { Plugin } from "obsidian";
|
||||||
|
|
||||||
const SECRET_IDS: Record<keyof ApiKeysSettings, string> = {
|
const SECRET_IDS: Partial<Record<keyof ApiKeysSettings, string>> = {
|
||||||
groqApiKey: "groq-api-key",
|
groqApiKey: "groq-api-key",
|
||||||
openAiApiKey: "openai-api-key",
|
openAiApiKey: "openai-api-key",
|
||||||
anthropicApiKey: "anthropic-api-key",
|
anthropicApiKey: "anthropic-api-key",
|
||||||
@@ -8,7 +8,7 @@ const SECRET_IDS: Record<keyof ApiKeysSettings, string> = {
|
|||||||
openRouterApiKey: "openrouter-api-key",
|
openRouterApiKey: "openrouter-api-key",
|
||||||
};
|
};
|
||||||
|
|
||||||
export type PostProcessingProvider = "anthropic" | "openai" | "openrouter" | "custom";
|
export type PostProcessingProvider = "anthropic" | "openai" | "openrouter" | "groq" | "custom";
|
||||||
|
|
||||||
export type AIProvider = PostProcessingProvider;
|
export type AIProvider = PostProcessingProvider;
|
||||||
|
|
||||||
@@ -16,6 +16,7 @@ export const PROVIDER_URLS: Record<PostProcessingProvider, string> = {
|
|||||||
anthropic: "https://api.anthropic.com/v1/messages",
|
anthropic: "https://api.anthropic.com/v1/messages",
|
||||||
openai: "https://api.openai.com/v1/chat/completions",
|
openai: "https://api.openai.com/v1/chat/completions",
|
||||||
openrouter: "https://openrouter.ai/api/v1/chat/completions",
|
openrouter: "https://openrouter.ai/api/v1/chat/completions",
|
||||||
|
groq: "https://api.groq.com/openai/v1/chat/completions",
|
||||||
custom: "",
|
custom: "",
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -23,9 +24,30 @@ export const PROVIDER_DEFAULT_MODELS: Record<PostProcessingProvider, string> = {
|
|||||||
anthropic: "claude-sonnet-4-20250514",
|
anthropic: "claude-sonnet-4-20250514",
|
||||||
openai: "gpt-4o-mini",
|
openai: "gpt-4o-mini",
|
||||||
openrouter: "openai/gpt-4o-mini",
|
openrouter: "openai/gpt-4o-mini",
|
||||||
|
groq: "llama-3.3-70b-versatile",
|
||||||
custom: "",
|
custom: "",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export interface ChatProviderInfo {
|
||||||
|
id: PostProcessingProvider;
|
||||||
|
label: string;
|
||||||
|
apiKeyField: keyof ApiKeysSettings;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Single source of truth for which chat-completion providers exist and which
|
||||||
|
* API key each one uses. UI dropdowns and provider resolution both read from
|
||||||
|
* this list so every AI feature (Post-Processing, Proofreading, Title
|
||||||
|
* Generation, Property Filler) offers the exact same providers.
|
||||||
|
*/
|
||||||
|
export const CHAT_PROVIDERS: ChatProviderInfo[] = [
|
||||||
|
{ id: "anthropic", label: "Anthropic", apiKeyField: "anthropicApiKey" },
|
||||||
|
{ id: "openai", label: "OpenAI", apiKeyField: "openAiApiKey" },
|
||||||
|
{ id: "groq", label: "Groq", apiKeyField: "groqApiKey" },
|
||||||
|
{ id: "openrouter", label: "OpenRouter", apiKeyField: "openRouterApiKey" },
|
||||||
|
{ id: "custom", label: "Custom", apiKeyField: "customApiKey" },
|
||||||
|
];
|
||||||
|
|
||||||
export type TranscriptionProvider = "groq" | "openai" | "azure" | "custom";
|
export type TranscriptionProvider = "groq" | "openai" | "azure" | "custom";
|
||||||
|
|
||||||
export const TRANSCRIPTION_PROVIDER_URLS: Record<TranscriptionProvider, string> = {
|
export const TRANSCRIPTION_PROVIDER_URLS: Record<TranscriptionProvider, string> = {
|
||||||
@@ -42,6 +64,7 @@ export interface ApiKeysSettings {
|
|||||||
openAiApiKey: string;
|
openAiApiKey: string;
|
||||||
anthropicApiKey: string;
|
anthropicApiKey: string;
|
||||||
customApiKey: string;
|
customApiKey: string;
|
||||||
|
customApiUrl: string;
|
||||||
openRouterApiKey: string;
|
openRouterApiKey: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -74,7 +97,6 @@ export interface OutputSettings {
|
|||||||
export interface PostProcessingSettings {
|
export interface PostProcessingSettings {
|
||||||
postProcessing: boolean;
|
postProcessing: boolean;
|
||||||
postProcessingProvider: PostProcessingProvider;
|
postProcessingProvider: PostProcessingProvider;
|
||||||
postProcessingUrl: string;
|
|
||||||
postProcessingModel: string;
|
postProcessingModel: string;
|
||||||
postProcessingPrompt: string;
|
postProcessingPrompt: string;
|
||||||
autoGenerateTitle: boolean;
|
autoGenerateTitle: boolean;
|
||||||
@@ -128,6 +150,7 @@ export const DEFAULT_API_KEYS: ApiKeysSettings = {
|
|||||||
openAiApiKey: "",
|
openAiApiKey: "",
|
||||||
anthropicApiKey: "",
|
anthropicApiKey: "",
|
||||||
customApiKey: "",
|
customApiKey: "",
|
||||||
|
customApiUrl: "",
|
||||||
openRouterApiKey: "",
|
openRouterApiKey: "",
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -160,7 +183,6 @@ export const DEFAULT_OUTPUT: OutputSettings = {
|
|||||||
export const DEFAULT_POST_PROCESSING: PostProcessingSettings = {
|
export const DEFAULT_POST_PROCESSING: PostProcessingSettings = {
|
||||||
postProcessing: false,
|
postProcessing: false,
|
||||||
postProcessingProvider: "anthropic",
|
postProcessingProvider: "anthropic",
|
||||||
postProcessingUrl: "https://api.anthropic.com/v1/messages",
|
|
||||||
postProcessingModel: "claude-sonnet-4-20250514",
|
postProcessingModel: "claude-sonnet-4-20250514",
|
||||||
postProcessingPrompt:
|
postProcessingPrompt:
|
||||||
'You are a transcription editor. Clean up the following voice transcription: fix grammar, remove filler words (um, uh, like) and repetitions, and improve readability. Format the text in markdown. If there are action items or to-dos, format them as task lists with "[ ]". Preserve the original meaning and language. Return only the polished text, nothing else.',
|
'You are a transcription editor. Clean up the following voice transcription: fix grammar, remove filler words (um, uh, like) and repetitions, and improve readability. Format the text in markdown. If there are action items or to-dos, format them as task lists with "[ ]". Preserve the original meaning and language. Return only the polished text, nothing else.',
|
||||||
@@ -274,19 +296,31 @@ export class SettingsManager {
|
|||||||
|
|
||||||
private migratePostProcessingProvider(settings: PluginSettings): boolean {
|
private migratePostProcessingProvider(settings: PluginSettings): boolean {
|
||||||
if (settings.postProcessingProvider) return false;
|
if (settings.postProcessingProvider) return false;
|
||||||
|
const legacyUrl = (settings as PluginSettings & { postProcessingUrl?: string })
|
||||||
|
.postProcessingUrl;
|
||||||
|
if (!legacyUrl) return false;
|
||||||
|
|
||||||
for (const [provider, url] of Object.entries(PROVIDER_URLS)) {
|
for (const [provider, url] of Object.entries(PROVIDER_URLS)) {
|
||||||
if (url && settings.postProcessingUrl === url) {
|
if (url && legacyUrl === url) {
|
||||||
settings.postProcessingProvider =
|
settings.postProcessingProvider = provider as PostProcessingProvider;
|
||||||
provider as PostProcessingProvider;
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (settings.postProcessingUrl) {
|
settings.postProcessingProvider = "custom";
|
||||||
settings.postProcessingProvider = "custom";
|
return true;
|
||||||
return true;
|
}
|
||||||
}
|
|
||||||
return false;
|
private migrateCustomApiUrl(settings: PluginSettings): boolean {
|
||||||
|
// One-time migration: the old `postProcessingUrl` field is now the
|
||||||
|
// shared `customApiUrl` used by every feature's "Custom" provider.
|
||||||
|
if (settings.customApiUrl) return false;
|
||||||
|
const legacyUrl = (settings as PluginSettings & { postProcessingUrl?: string })
|
||||||
|
.postProcessingUrl;
|
||||||
|
if (!legacyUrl) return false;
|
||||||
|
if (Object.values(PROVIDER_URLS).includes(legacyUrl)) return false;
|
||||||
|
|
||||||
|
settings.customApiUrl = legacyUrl;
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
async loadSettings(): Promise<PluginSettings> {
|
async loadSettings(): Promise<PluginSettings> {
|
||||||
@@ -300,6 +334,10 @@ export class SettingsManager {
|
|||||||
await this.plugin.saveData(settings);
|
await this.plugin.saveData(settings);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.migrateCustomApiUrl(settings)) {
|
||||||
|
await this.plugin.saveData(settings);
|
||||||
|
}
|
||||||
|
|
||||||
if (this.migrateKeysFromDataJson(settings)) {
|
if (this.migrateKeysFromDataJson(settings)) {
|
||||||
await this.plugin.saveData(settings);
|
await this.plugin.saveData(settings);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { Editor, Notice } from "obsidian";
|
import { Editor, Notice } from "obsidian";
|
||||||
import { AIService } from "../AIService";
|
import { AIService } from "../AIService";
|
||||||
import { AIProvider, PROVIDER_URLS } from "../SettingsManager";
|
import { resolveChatProvider } from "../ProviderResolver";
|
||||||
import NibbleAI from "main";
|
import NibbleAI from "main";
|
||||||
|
|
||||||
export class Proofreader {
|
export class Proofreader {
|
||||||
@@ -10,33 +10,6 @@ export class Proofreader {
|
|||||||
this.plugin = plugin;
|
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> {
|
async fixSelection(editor: Editor): Promise<void> {
|
||||||
const selection = editor.getSelection();
|
const selection = editor.getSelection();
|
||||||
if (!selection || selection.trim().length === 0) {
|
if (!selection || selection.trim().length === 0) {
|
||||||
@@ -44,7 +17,10 @@ export class Proofreader {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const config = this.resolveProvider();
|
const config = resolveChatProvider(
|
||||||
|
this.plugin.settings,
|
||||||
|
this.plugin.settings.proofreadingProvider || "openrouter"
|
||||||
|
);
|
||||||
if (!config) {
|
if (!config) {
|
||||||
new Notice(
|
new Notice(
|
||||||
"⚠️ No API key for selected provider. Check Settings \u2192 NibbleAI \u2192 API Keys."
|
"⚠️ No API key for selected provider. Check Settings \u2192 NibbleAI \u2192 API Keys."
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { Notice, MarkdownView, Modal, App, Setting, TFile } from "obsidian";
|
import { Notice, MarkdownView, Modal, App, Setting, TFile } from "obsidian";
|
||||||
import { AIService } from "../AIService";
|
import { AIService } from "../AIService";
|
||||||
import { AIProvider, PROVIDER_URLS } from "../SettingsManager";
|
import { resolveChatProvider } from "../ProviderResolver";
|
||||||
import NibbleAI from "main";
|
import NibbleAI from "main";
|
||||||
|
|
||||||
class PropertyPromptModal extends Modal {
|
class PropertyPromptModal extends Modal {
|
||||||
@@ -80,31 +80,6 @@ export class PropertyFiller {
|
|||||||
this.plugin = plugin;
|
this.plugin = plugin;
|
||||||
}
|
}
|
||||||
|
|
||||||
private resolveProvider(): { apiKey: string; endpoint: string; provider: AIProvider } | null {
|
|
||||||
const provider = this.plugin.settings.propertyGenProvider || "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 || !endpoint) return null;
|
|
||||||
return { apiKey, endpoint, provider };
|
|
||||||
}
|
|
||||||
|
|
||||||
async fillProperty(): Promise<void> {
|
async fillProperty(): Promise<void> {
|
||||||
new PropertyPromptModal(
|
new PropertyPromptModal(
|
||||||
this.plugin.app,
|
this.plugin.app,
|
||||||
@@ -139,7 +114,10 @@ export class PropertyFiller {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const config = this.resolveProvider();
|
const config = resolveChatProvider(
|
||||||
|
this.plugin.settings,
|
||||||
|
this.plugin.settings.propertyGenProvider || "openrouter"
|
||||||
|
);
|
||||||
if (!config) {
|
if (!config) {
|
||||||
new Notice(
|
new Notice(
|
||||||
"⚠️ No API key for selected provider. Check Settings \u2192 NibbleAI \u2192 API Keys."
|
"⚠️ No API key for selected provider. Check Settings \u2192 NibbleAI \u2192 API Keys."
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { Notice, TFile, MarkdownView } from "obsidian";
|
import { Notice, TFile, MarkdownView } from "obsidian";
|
||||||
import { AIService } from "../AIService";
|
import { AIService } from "../AIService";
|
||||||
import { AIProvider, PROVIDER_URLS } from "../SettingsManager";
|
import { resolveChatProvider } from "../ProviderResolver";
|
||||||
import NibbleAI from "main";
|
import NibbleAI from "main";
|
||||||
|
|
||||||
export class TitleGenerator {
|
export class TitleGenerator {
|
||||||
@@ -10,31 +10,6 @@ export class TitleGenerator {
|
|||||||
this.plugin = plugin;
|
this.plugin = plugin;
|
||||||
}
|
}
|
||||||
|
|
||||||
private resolveProvider(): { apiKey: string; endpoint: string; provider: AIProvider } | null {
|
|
||||||
const provider = this.plugin.settings.titleGenProvider || "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 || !endpoint) return null;
|
|
||||||
return { apiKey, endpoint, provider };
|
|
||||||
}
|
|
||||||
|
|
||||||
async generateTitle(): Promise<void> {
|
async generateTitle(): Promise<void> {
|
||||||
const activeView =
|
const activeView =
|
||||||
this.plugin.app.workspace.getActiveViewOfType(MarkdownView);
|
this.plugin.app.workspace.getActiveViewOfType(MarkdownView);
|
||||||
@@ -55,7 +30,10 @@ export class TitleGenerator {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const config = this.resolveProvider();
|
const config = resolveChatProvider(
|
||||||
|
this.plugin.settings,
|
||||||
|
this.plugin.settings.titleGenProvider || "openrouter"
|
||||||
|
);
|
||||||
if (!config) {
|
if (!config) {
|
||||||
new Notice(
|
new Notice(
|
||||||
"⚠️ No API key for selected provider. Check Settings \u2192 NibbleAI \u2192 API Keys."
|
"⚠️ No API key for selected provider. Check Settings \u2192 NibbleAI \u2192 API Keys."
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import {
|
|||||||
resolveTemplate,
|
resolveTemplate,
|
||||||
} from "./utils";
|
} from "./utils";
|
||||||
import { PostProcessor } from "../PostProcessor";
|
import { PostProcessor } from "../PostProcessor";
|
||||||
|
import { resolveChatProvider } from "../ProviderResolver";
|
||||||
|
|
||||||
export class AudioHandler {
|
export class AudioHandler {
|
||||||
private plugin: NibbleAI;
|
private plugin: NibbleAI;
|
||||||
@@ -16,19 +17,6 @@ export class AudioHandler {
|
|||||||
this.plugin = plugin;
|
this.plugin = plugin;
|
||||||
}
|
}
|
||||||
|
|
||||||
private getPostProcessingApiKey(): string {
|
|
||||||
switch (this.plugin.settings.postProcessingProvider) {
|
|
||||||
case "anthropic":
|
|
||||||
return this.plugin.settings.anthropicApiKey;
|
|
||||||
case "openai":
|
|
||||||
return this.plugin.settings.openAiApiKey;
|
|
||||||
case "openrouter":
|
|
||||||
return this.plugin.settings.openRouterApiKey;
|
|
||||||
case "custom":
|
|
||||||
return this.plugin.settings.customApiKey;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private getTranscriptionUrl(): string {
|
private getTranscriptionUrl(): string {
|
||||||
switch (this.plugin.settings.transcriptionProvider) {
|
switch (this.plugin.settings.transcriptionProvider) {
|
||||||
case "groq":
|
case "groq":
|
||||||
@@ -173,8 +161,11 @@ export class AudioHandler {
|
|||||||
|
|
||||||
// Post-process with LLM if enabled
|
// Post-process with LLM if enabled
|
||||||
if (this.plugin.settings.postProcessing) {
|
if (this.plugin.settings.postProcessing) {
|
||||||
const ppApiKey = this.getPostProcessingApiKey();
|
const resolved = resolveChatProvider(
|
||||||
if (!ppApiKey) {
|
this.plugin.settings,
|
||||||
|
this.plugin.settings.postProcessingProvider
|
||||||
|
);
|
||||||
|
if (!resolved) {
|
||||||
new Notice(
|
new Notice(
|
||||||
"✘ Add your post-processing API key in settings"
|
"✘ Add your post-processing API key in settings"
|
||||||
);
|
);
|
||||||
@@ -185,10 +176,10 @@ export class AudioHandler {
|
|||||||
new Notice("Post-processing...");
|
new Notice("Post-processing...");
|
||||||
}
|
}
|
||||||
const processor = new PostProcessor({
|
const processor = new PostProcessor({
|
||||||
apiKey: ppApiKey,
|
apiKey: resolved.apiKey,
|
||||||
model: this.plugin.settings.postProcessingModel,
|
model: this.plugin.settings.postProcessingModel,
|
||||||
url: this.plugin.settings.postProcessingUrl,
|
url: resolved.endpoint,
|
||||||
provider: this.plugin.settings.postProcessingProvider,
|
provider: resolved.provider,
|
||||||
});
|
});
|
||||||
finalText = await processor.process(
|
finalText = await processor.process(
|
||||||
originalText,
|
originalText,
|
||||||
@@ -209,14 +200,17 @@ export class AudioHandler {
|
|||||||
this.plugin.settings.autoGenerateTitle &&
|
this.plugin.settings.autoGenerateTitle &&
|
||||||
this.plugin.settings.createNoteFile
|
this.plugin.settings.createNoteFile
|
||||||
) {
|
) {
|
||||||
const ppApiKey = this.getPostProcessingApiKey();
|
const resolved = resolveChatProvider(
|
||||||
if (ppApiKey) {
|
this.plugin.settings,
|
||||||
|
this.plugin.settings.postProcessingProvider
|
||||||
|
);
|
||||||
|
if (resolved) {
|
||||||
try {
|
try {
|
||||||
const processor = new PostProcessor({
|
const processor = new PostProcessor({
|
||||||
apiKey: ppApiKey,
|
apiKey: resolved.apiKey,
|
||||||
model: this.plugin.settings.postProcessingModel,
|
model: this.plugin.settings.postProcessingModel,
|
||||||
url: this.plugin.settings.postProcessingUrl,
|
url: resolved.endpoint,
|
||||||
provider: this.plugin.settings.postProcessingProvider,
|
provider: resolved.provider,
|
||||||
});
|
});
|
||||||
const title = await processor.process(
|
const title = await processor.process(
|
||||||
finalText,
|
finalText,
|
||||||
|
|||||||
Reference in New Issue
Block a user