diff --git a/src/interface/obsidian/src/main.ts b/src/interface/obsidian/src/main.ts index f8cf621f..23082895 100644 --- a/src/interface/obsidian/src/main.ts +++ b/src/interface/obsidian/src/main.ts @@ -1,4 +1,4 @@ -import { Plugin } from 'obsidian'; +import { Notice, Plugin } from 'obsidian'; import { KhojSetting, KhojSettingTab, DEFAULT_SETTINGS } from 'src/settings' import { KhojModal } from 'src/modal' import { configureKhojBackend } from './utils'; @@ -14,18 +14,22 @@ export default class Khoj extends Plugin { this.addCommand({ id: 'search', name: 'Search', - callback: () => { - new KhojModal(this.app, this.settings).open(); + checkCallback: (checking) => { + if (!checking && this.settings.connectedToBackend) + new KhojModal(this.app, this.settings).open(); + return this.settings.connectedToBackend; } }); // Create an icon in the left ribbon. this.addRibbonIcon('search', 'Khoj', (_: MouseEvent) => { // Called when the user clicks the icon. - new KhojModal(this.app, this.settings).open(); + this.settings.connectedToBackend + ? new KhojModal(this.app, this.settings).open() + : new Notice(`❗️Ensure Khoj backend is running and Khoj URL is pointing to it in the plugin settings`); }); - // Add a settings tab so the user can configure various aspects of the plugin + // Add a settings tab so the user can configure khoj this.addSettingTab(new KhojSettingTab(this.app, this)); } @@ -41,7 +45,7 @@ export default class Khoj extends Plugin { } async saveSettings() { - await this.saveData(this.settings) - .then(() => configureKhojBackend(this.settings)); + await configureKhojBackend(this.settings) + .then(() => this.saveData(this.settings)); } } diff --git a/src/interface/obsidian/src/modal.ts b/src/interface/obsidian/src/modal.ts index c9eeadde..af4455a6 100644 --- a/src/interface/obsidian/src/modal.ts +++ b/src/interface/obsidian/src/modal.ts @@ -1,4 +1,4 @@ -import { App, SuggestModal, Notice, request, MarkdownRenderer } from 'obsidian'; +import { App, SuggestModal, Notice, request, MarkdownRenderer, Instruction, Platform } from 'obsidian'; import { KhojSetting } from 'src/settings'; import { getVaultAbsolutePath } from 'src/utils'; @@ -9,16 +9,54 @@ export interface SearchResult { export class KhojModal extends SuggestModal { setting: KhojSetting; + rerank: boolean; constructor(app: App, setting: KhojSetting) { super(app); this.setting = setting; + this.rerank = false; + + // Register Modal Keybindings to Rerank Results + this.scope.register(['Mod'], 'Enter', async () => { + this.rerank = true + let results = await this.getSuggestions(this.inputEl.value); + + this.resultContainerEl.empty(); + results.forEach((result) => { + this.renderSuggestion(result, this.resultContainerEl); + }); + this.rerank = false + }); + + // Add Hints to Modal for available Keybindings + const modalInstructions: Instruction[] = [ + { + command: '↑↓', + purpose: 'to navigate', + }, + { + command: '↵', + purpose: 'to open', + }, + { + command: Platform.isMacOS ? 'cmd ↵': 'ctrl ↵', + purpose: 'to rerank', + }, + { + command: 'esc', + purpose: 'to dismiss', + }, + ] + this.setInstructions(modalInstructions); + + // Set Placeholder Text for Modal + this.setPlaceholder('Search with Khoj...'); } async getSuggestions(query: string): Promise { // Query Khoj backend for search results - var searchUrl = `${this.setting.khojUrl}/api/search?q=${query}&n=${this.setting.resultsCount}&t=markdown` - var results = await request(searchUrl) + let searchUrl = `${this.setting.khojUrl}/api/search?q=${query}&n=${this.setting.resultsCount}&r=${this.rerank}&t=markdown` + let results = await request(searchUrl) .then(response => JSON.parse(response)) .then(data => { return data.map((result: any) => { diff --git a/src/interface/obsidian/src/settings.ts b/src/interface/obsidian/src/settings.ts index 941c8b4d..d502d962 100644 --- a/src/interface/obsidian/src/settings.ts +++ b/src/interface/obsidian/src/settings.ts @@ -1,4 +1,4 @@ -import { App, PluginSettingTab, Setting } from 'obsidian'; +import { App, PluginSettingTab, request, Setting } from 'obsidian'; import Khoj from 'src/main'; import { getVaultAbsolutePath } from 'src/utils'; @@ -6,12 +6,14 @@ export interface KhojSetting { resultsCount: number; khojUrl: string; obsidianVaultPath: string; + connectedToBackend: boolean; } export const DEFAULT_SETTINGS: KhojSetting = { resultsCount: 6, khojUrl: 'http://localhost:8000', - obsidianVaultPath: getVaultAbsolutePath() + obsidianVaultPath: getVaultAbsolutePath(), + connectedToBackend: false, } export class KhojSettingTab extends PluginSettingTab { @@ -25,8 +27,15 @@ export class KhojSettingTab extends PluginSettingTab { display(): void { const { containerEl } = this; containerEl.empty(); + + // Add notice if unable to connect to khoj backend + if (!this.plugin.settings.connectedToBackend) { + containerEl.createEl('small', { text: '❗Ensure Khoj backend is running and Khoj URL is correctly set below' }); + } + + // Add khoj settings configurable from the plugin settings tab new Setting(containerEl) - .setName('Vault Paths') + .setName('Vault Path') .setDesc('The Obsidian Vault to search with Khoj') .addText(text => text .setValue(`${this.plugin.settings.obsidianVaultPath}`) @@ -44,7 +53,7 @@ export class KhojSettingTab extends PluginSettingTab { await this.plugin.saveSettings(); })); new Setting(containerEl) - .setName('Number of Results') + .setName('Results Count') .setDesc('The number of search results to show') .addText(text => text .setPlaceholder('6') @@ -53,5 +62,14 @@ export class KhojSettingTab extends PluginSettingTab { this.plugin.settings.resultsCount = parseInt(value); await this.plugin.saveSettings(); })); + new Setting(containerEl) + .setName('Index Vault') + .setDesc('Manually force Khoj to re-index your Obsidian Vault') + .addButton(button => button + .setButtonText('Update') + .onClick(async () => { + await request(`${this.plugin.settings.khojUrl}/api/update?t=markdown&force=true`); + } + )); } } diff --git a/src/interface/obsidian/src/utils.ts b/src/interface/obsidian/src/utils.ts index d46a58dd..0cc67faa 100644 --- a/src/interface/obsidian/src/utils.ts +++ b/src/interface/obsidian/src/utils.ts @@ -13,16 +13,25 @@ export async function configureKhojBackend(setting: KhojSetting) { let mdInVault = `${setting.obsidianVaultPath}/**/*.md`; let khojConfigUrl = `${setting.khojUrl}/api/config/data`; - // Load khoj app config from backend API, Update Markdown config and save - let khoj_configured = await request(khojConfigUrl) - .then(response => response !== "null"); + // Check if khoj backend is configured, show error if backend is not running + let khoj_already_configured = await request(khojConfigUrl) + .then(response => { + setting.connectedToBackend = true; + return response !== "null" + }) + .catch(error => { + setting.connectedToBackend = false; + new Notice(`❗️Ensure Khoj backend is running and Khoj URL is pointing to it in the plugin settings.\n\n${error}`); + }) + // Short-circuit configuring khoj if unable to connect to khoj backend + if (!setting.connectedToBackend) return; // Get current config if khoj backend configured, else get default config from khoj backend - await request(khoj_configured ? khojConfigUrl : `${khojConfigUrl}/default`) + await request(khoj_already_configured ? khojConfigUrl : `${khojConfigUrl}/default`) .then(response => JSON.parse(response)) .then(data => { // If khoj backend not configured yet - if (!khoj_configured) { + if (!khoj_already_configured) { // Create khoj content-type config with only markdown configured let khojObsidianPluginPath = `${setting.obsidianVaultPath}/${this.app.vault.configDir}/plugins/khoj/`; data["content-type"] = { @@ -70,9 +79,10 @@ export async function configureKhojBackend(setting: KhojSetting) { updateKhojBackend(setting.khojUrl, data); console.log(`Khoj: Updated markdown config in khoj backend config:\n${JSON.stringify(data["content-type"]["markdown"])}`) } + new Notice(`✅ Successfully Setup Khoj`); }) .catch(error => { - new Notice(`Ensure Khoj backend is running and Khoj URL is correct in the Khoj plugin settings.\nError: ${error}`); + new Notice(`❗️Failed to configure Khoj backend. Contact developer on Github. \n\nError: ${error}`); }) }