9b7ec36828
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.
101 lines
2.6 KiB
TypeScript
101 lines
2.6 KiB
TypeScript
import axios from "axios";
|
|
import { PostProcessingProvider } from "./SettingsManager";
|
|
|
|
export interface ChatCompletionOptions {
|
|
apiKey: string;
|
|
model: string;
|
|
endpoint?: string;
|
|
provider?: "anthropic" | "openai" | "openrouter" | "groq" | "custom";
|
|
temperature?: number;
|
|
maxTokens?: number;
|
|
}
|
|
|
|
/**
|
|
* Unified AI service for all NibbleAI modules.
|
|
* Supports OpenAI-compatible chat completions and Anthropic native API.
|
|
*/
|
|
export class AIService {
|
|
/**
|
|
* Call a chat completion API (OpenAI-compatible or Anthropic).
|
|
* @param userMessage - The user's input text
|
|
* @param systemPrompt - System-level instructions
|
|
* @param options - API configuration
|
|
* @returns The model's response text
|
|
*/
|
|
static async callChatCompletion(
|
|
userMessage: string,
|
|
systemPrompt: string,
|
|
options: ChatCompletionOptions
|
|
): Promise<string> {
|
|
const provider = options.provider || "openai";
|
|
const endpoint =
|
|
options.endpoint || "https://openrouter.ai/api/v1/chat/completions";
|
|
|
|
if (provider === "anthropic") {
|
|
return AIService.callAnthropic(userMessage, systemPrompt, options);
|
|
}
|
|
|
|
return AIService.callOpenAI(userMessage, systemPrompt, {
|
|
...options,
|
|
endpoint,
|
|
});
|
|
}
|
|
|
|
private static async callOpenAI(
|
|
userMessage: string,
|
|
systemPrompt: string,
|
|
options: ChatCompletionOptions
|
|
): Promise<string> {
|
|
const response = await axios.post(
|
|
options.endpoint ||
|
|
"https://openrouter.ai/api/v1/chat/completions",
|
|
{
|
|
model: options.model,
|
|
messages: [
|
|
{ role: "system", content: systemPrompt },
|
|
{ role: "user", content: userMessage },
|
|
],
|
|
temperature: options.temperature ?? 0.3,
|
|
max_tokens: options.maxTokens ?? 2000,
|
|
},
|
|
{
|
|
headers: {
|
|
Authorization: `Bearer ${options.apiKey}`,
|
|
"Content-Type": "application/json",
|
|
...(options.endpoint?.includes("openrouter.ai")
|
|
? {
|
|
"HTTP-Referer": "https://obsidian.md",
|
|
"X-Title": "Obsidian NibbleAI",
|
|
}
|
|
: {}),
|
|
},
|
|
}
|
|
);
|
|
return response.data.choices[0].message.content.trim();
|
|
}
|
|
|
|
private static async callAnthropic(
|
|
userMessage: string,
|
|
systemPrompt: string,
|
|
options: ChatCompletionOptions
|
|
): Promise<string> {
|
|
const response = await axios.post(
|
|
options.endpoint || "https://api.anthropic.com/v1/messages",
|
|
{
|
|
model: options.model,
|
|
max_tokens: options.maxTokens ?? 8192,
|
|
system: systemPrompt,
|
|
messages: [{ role: "user", content: userMessage }],
|
|
},
|
|
{
|
|
headers: {
|
|
"x-api-key": options.apiKey,
|
|
"anthropic-version": "2023-06-01",
|
|
"anthropic-dangerous-direct-browser-access": "true",
|
|
"Content-Type": "application/json",
|
|
},
|
|
}
|
|
);
|
|
return response.data.content[0].text;
|
|
}
|
|
} |