feat: add Grok provider integration and update settings

This commit is contained in:
Ahmet Ildirim 2026-02-26 20:25:18 +01:00
parent b26e61432d
commit 5a1b01b9eb
11 changed files with 155 additions and 8 deletions

View file

@ -10,6 +10,7 @@ Inscribe is an AI-powered inline autocompletion plugin for Obsidian. It will gen
Inscribe supports following providers:
- Ollama (local)
- Gemini
- Grok
- OpenAI
- OpenAI compatible
@ -48,4 +49,3 @@ Using per-path profile config, you can enable completions only for specific path
If you find this plugin useful:
[![Buy Me A Coffee](https://www.buymeacoffee.com/assets/img/custom_images/orange_img.png)](https://buymeacoffee.com/ahmetildirim)

4
package-lock.json generated
View file

@ -1,12 +1,12 @@
{
"name": "obsidian-inscribe",
"version": "1.1.3",
"version": "1.1.5",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "obsidian-inscribe",
"version": "1.1.3",
"version": "1.1.5",
"license": "MIT",
"dependencies": {
"@google/genai": "^1.41.0",

View file

@ -5,12 +5,14 @@ import { OllamaProvider } from "./ollama";
import { OpenAIProvider } from "./openai";
import { OpenAICompatibleProvider } from "./openai-compat";
import { GeminiProvider } from "./gemini";
import { GrokProvider } from "./grok";
interface Providers {
[ProviderType.OLLAMA]: OllamaProvider,
[ProviderType.OPENAI]: OpenAIProvider,
[ProviderType.OPENAI_COMPATIBLE]: OpenAICompatibleProvider,
[ProviderType.GEMINI]: GeminiProvider,
[ProviderType.GROK]: GrokProvider,
}
export class ProviderFactory {
@ -44,6 +46,7 @@ export class ProviderFactory {
[ProviderType.OPENAI]: new OpenAIProvider(settings.providers.openai),
[ProviderType.OPENAI_COMPATIBLE]: new OpenAICompatibleProvider(settings.providers.openai_compatible),
[ProviderType.GEMINI]: new GeminiProvider(settings.providers.gemini),
[ProviderType.GROK]: new GrokProvider(settings.providers.grok),
}
}
}

View file

@ -0,0 +1,2 @@
export * from "./provider";
export * from "./settings";

View file

@ -0,0 +1,83 @@
import { Provider } from "..";
import { Editor } from "obsidian";
import OpenAI from "openai";
import { ProfileOptions } from "src/settings/settings";
import { GrokSettings } from ".";
export class GrokProvider implements Provider {
private static readonly BASE_URL = "https://api.x.ai/v1";
client: OpenAI;
settings: GrokSettings;
aborted: boolean = false;
abortcontroller?: AbortController;
constructor(settings: GrokSettings) {
this.settings = settings;
this.client = new OpenAI({
baseURL: GrokProvider.BASE_URL,
apiKey: this.settings.apiKey,
dangerouslyAllowBrowser: true,
});
}
async *generate(editor: Editor, prompt: string, options: ProfileOptions): AsyncGenerator<string> {
this.aborted = false;
this.abortcontroller = new AbortController();
const initialPosition = editor.getCursor();
const stream = await this.client.chat.completions.create({
model: options.model,
messages: [
{ role: "system", content: options.systemPrompt },
{ role: "user", content: prompt }
],
temperature: options.temperature,
stream: true,
}, { signal: this.abortcontroller.signal });
let completion = "";
for await (const chunk of stream) {
if (this.aborted) {
return;
}
if (this.cursorMoved(editor, initialPosition)) {
this.abort();
return;
}
const content = chunk.choices[0]?.delta?.content || "";
completion += content;
yield completion;
}
}
async abort() {
if (this.aborted) return;
this.aborted = true;
this.abortcontroller?.abort();
}
async fetchModels(): Promise<string[]> {
if (!this.client) {
return this.settings.models;
}
const models = await this.client.models.list();
return models.data.map(model => model.id);
}
async connectionTest(): Promise<boolean> {
try {
await this.client.models.list();
return true;
} catch (error) {
console.error("Error testing connection:", error);
return false;
}
}
private cursorMoved(editor: Editor, initialPosition: { line: number, ch: number }): boolean {
const currentPosition = editor.getCursor();
return currentPosition.line !== initialPosition.line || currentPosition.ch !== initialPosition.ch;
}
}

View file

@ -0,0 +1,11 @@
import { ProviderType } from "..";
export interface GrokSettings {
integration: ProviderType;
name: string;
description: string;
apiKey: string;
models: string[];
configured: boolean;
temperature_range: { min: number; max: number };
}

View file

@ -2,5 +2,7 @@ export * from "./provider";
export * from "./factory";
export * from "./ollama";
export * from "./openai";
export * from "./openai-compat";
export * from "./gemini";
export * from "./grok";

View file

@ -6,6 +6,7 @@ export enum ProviderType {
OPENAI = "openai",
OPENAI_COMPATIBLE = "openai_compatible",
GEMINI = "gemini",
GROK = "grok",
}
// Completer interface for ai integrations

View file

@ -36,6 +36,9 @@ export class ProviderSettingsModal extends Modal {
case ProviderType.GEMINI:
this.renderGeminiSettings();
break;
case ProviderType.GROK:
this.renderGrokSettings();
break;
default:
contentEl.createEl('h2', { text: 'Unknown provider type' });
break;
@ -137,6 +140,27 @@ export class ProviderSettingsModal extends Modal {
this.renderConnectionStatus(this.plugin.settings.providers.gemini);
}
async renderGrokSettings() {
const { contentEl } = this;
contentEl.empty();
this.setTitle('Grok');
new Setting(contentEl)
.setName("Grok API key")
.setDesc("The API key for xAI Grok")
.addText((text) => {
text
.setValue(this.plugin.settings.providers.grok.apiKey)
.onChange(async (value) => {
this.plugin.settings.providers.grok.apiKey = value;
await this.plugin.saveSettings();
});
});
this.renderModelSettings(this.plugin.settings.providers.grok);
this.renderConnectionStatus(this.plugin.settings.providers.grok);
}
private renderModelSettings(provider: { models: string[] }) {
new Setting(this.contentEl)
.setName("Available models")

View file

@ -4,6 +4,7 @@ import { OllamaSettings } from "src/providers/ollama";
import { OpenAISettings } from "src/providers/openai";
import { OpenAICompatibleSettings } from "src/providers/openai-compat";
import { GeminiSettings } from "src/providers/gemini";
import { GrokSettings } from "src/providers/grok";
// Completion options for a profile
export interface ProfileOptions {
@ -54,6 +55,7 @@ export interface Settings {
openai: OpenAISettings,
openai_compatible: OpenAICompatibleSettings,
gemini: GeminiSettings,
grok: GrokSettings,
},
// profiles
profiles: Profiles,
@ -121,6 +123,15 @@ export const DEFAULT_SETTINGS: Settings = {
configured: false,
temperature_range: { min: 0, max: 2 },
},
grok: {
integration: ProviderType.GROK,
name: "Grok",
description: "Use xAI Grok models to generate text.",
apiKey: "api-key",
models: ["grok-3-mini", "grok-3"],
configured: false,
temperature_range: { min: 0, max: 1 },
},
},
profiles: {
default: {

View file

@ -369,6 +369,15 @@ class ProvidersSection {
ProviderType.GEMINI,
this.plugin.settings.providers.gemini.configured));
// Grok Provider
new Setting(this.container)
.setName("Grok")
.setDesc("xAI Grok API")
.addButton((button: ButtonComponent) => this.createConfigureButton(
button,
ProviderType.GROK,
this.plugin.settings.providers.grok.configured));
// OpenAI Compatible Provider
new Setting(this.container)
.setName("OpenAI compatible API")
@ -444,6 +453,7 @@ class ProfilesSection {
.addOption(ProviderType.OLLAMA, "Ollama")
.addOption(ProviderType.OPENAI, "OpenAI")
.addOption(ProviderType.GEMINI, "Gemini")
.addOption(ProviderType.GROK, "Grok")
.addOption(ProviderType.OPENAI_COMPATIBLE, "OpenAI Compatible")
.setValue(profile.provider)
.onChange(async (value: ProviderType) => {