Enhance Search Modal, Error State Handling in Khoj Obsidian Plugin

### Search Modal Enhancements
  - b52cd85 Allow Reranking results using Keybinding from Khoj Search Modal
  - 580f4ac Add hints to Modal for available Keybindings
  - da49ea2 Add placeholder text to modal in Khoj Obsidian plugin
  
### Handle Failure to Connect to Khoj Backend
Load plugin but warn on failure to connect to Khoj backend

- f046a95 Track connectedToBackend as a setting. Use it across obsidian plugin to:
  - Disable command if not connected to backend
  - Trigger warning notice on clicking Khoj ribbon if not connected to backend
  - Show warning at top of Khoj Obsidian plugin settings pane
- 768e874 Load obsidian plugin even if fail to connect to backend but show warning
  - Allows user to see reason for failure to try resolve it
  - Allows user to update Khoj URL settings to point to URL of Khoj server
  
### Miscellaneous
- 7991ab7 Add button in Obsidian plugin settings to force re-indexing your vault
  - Useful if index gets corrupted
This commit is contained in:
Debanjum
2023-01-10 23:20:32 -03:00
committed by GitHub
4 changed files with 90 additions and 20 deletions

View File

@@ -1,4 +1,4 @@
import { Plugin } from 'obsidian'; import { Notice, Plugin } from 'obsidian';
import { KhojSetting, KhojSettingTab, DEFAULT_SETTINGS } from 'src/settings' import { KhojSetting, KhojSettingTab, DEFAULT_SETTINGS } from 'src/settings'
import { KhojModal } from 'src/modal' import { KhojModal } from 'src/modal'
import { configureKhojBackend } from './utils'; import { configureKhojBackend } from './utils';
@@ -14,18 +14,22 @@ export default class Khoj extends Plugin {
this.addCommand({ this.addCommand({
id: 'search', id: 'search',
name: 'Search', name: 'Search',
callback: () => { checkCallback: (checking) => {
if (!checking && this.settings.connectedToBackend)
new KhojModal(this.app, this.settings).open(); new KhojModal(this.app, this.settings).open();
return this.settings.connectedToBackend;
} }
}); });
// Create an icon in the left ribbon. // Create an icon in the left ribbon.
this.addRibbonIcon('search', 'Khoj', (_: MouseEvent) => { this.addRibbonIcon('search', 'Khoj', (_: MouseEvent) => {
// Called when the user clicks the icon. // 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)); this.addSettingTab(new KhojSettingTab(this.app, this));
} }
@@ -41,7 +45,7 @@ export default class Khoj extends Plugin {
} }
async saveSettings() { async saveSettings() {
await this.saveData(this.settings) await configureKhojBackend(this.settings)
.then(() => configureKhojBackend(this.settings)); .then(() => this.saveData(this.settings));
} }
} }

View File

@@ -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 { KhojSetting } from 'src/settings';
import { getVaultAbsolutePath } from 'src/utils'; import { getVaultAbsolutePath } from 'src/utils';
@@ -9,16 +9,54 @@ export interface SearchResult {
export class KhojModal extends SuggestModal<SearchResult> { export class KhojModal extends SuggestModal<SearchResult> {
setting: KhojSetting; setting: KhojSetting;
rerank: boolean;
constructor(app: App, setting: KhojSetting) { constructor(app: App, setting: KhojSetting) {
super(app); super(app);
this.setting = setting; 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<SearchResult[]> { async getSuggestions(query: string): Promise<SearchResult[]> {
// Query Khoj backend for search results // Query Khoj backend for search results
var searchUrl = `${this.setting.khojUrl}/api/search?q=${query}&n=${this.setting.resultsCount}&t=markdown` let searchUrl = `${this.setting.khojUrl}/api/search?q=${query}&n=${this.setting.resultsCount}&r=${this.rerank}&t=markdown`
var results = await request(searchUrl) let results = await request(searchUrl)
.then(response => JSON.parse(response)) .then(response => JSON.parse(response))
.then(data => { .then(data => {
return data.map((result: any) => { return data.map((result: any) => {

View File

@@ -1,4 +1,4 @@
import { App, PluginSettingTab, Setting } from 'obsidian'; import { App, PluginSettingTab, request, Setting } from 'obsidian';
import Khoj from 'src/main'; import Khoj from 'src/main';
import { getVaultAbsolutePath } from 'src/utils'; import { getVaultAbsolutePath } from 'src/utils';
@@ -6,12 +6,14 @@ export interface KhojSetting {
resultsCount: number; resultsCount: number;
khojUrl: string; khojUrl: string;
obsidianVaultPath: string; obsidianVaultPath: string;
connectedToBackend: boolean;
} }
export const DEFAULT_SETTINGS: KhojSetting = { export const DEFAULT_SETTINGS: KhojSetting = {
resultsCount: 6, resultsCount: 6,
khojUrl: 'http://localhost:8000', khojUrl: 'http://localhost:8000',
obsidianVaultPath: getVaultAbsolutePath() obsidianVaultPath: getVaultAbsolutePath(),
connectedToBackend: false,
} }
export class KhojSettingTab extends PluginSettingTab { export class KhojSettingTab extends PluginSettingTab {
@@ -25,8 +27,15 @@ export class KhojSettingTab extends PluginSettingTab {
display(): void { display(): void {
const { containerEl } = this; const { containerEl } = this;
containerEl.empty(); 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) new Setting(containerEl)
.setName('Vault Paths') .setName('Vault Path')
.setDesc('The Obsidian Vault to search with Khoj') .setDesc('The Obsidian Vault to search with Khoj')
.addText(text => text .addText(text => text
.setValue(`${this.plugin.settings.obsidianVaultPath}`) .setValue(`${this.plugin.settings.obsidianVaultPath}`)
@@ -44,7 +53,7 @@ export class KhojSettingTab extends PluginSettingTab {
await this.plugin.saveSettings(); await this.plugin.saveSettings();
})); }));
new Setting(containerEl) new Setting(containerEl)
.setName('Number of Results') .setName('Results Count')
.setDesc('The number of search results to show') .setDesc('The number of search results to show')
.addText(text => text .addText(text => text
.setPlaceholder('6') .setPlaceholder('6')
@@ -53,5 +62,14 @@ export class KhojSettingTab extends PluginSettingTab {
this.plugin.settings.resultsCount = parseInt(value); this.plugin.settings.resultsCount = parseInt(value);
await this.plugin.saveSettings(); 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`);
}
));
} }
} }

View File

@@ -13,16 +13,25 @@ export async function configureKhojBackend(setting: KhojSetting) {
let mdInVault = `${setting.obsidianVaultPath}/**/*.md`; let mdInVault = `${setting.obsidianVaultPath}/**/*.md`;
let khojConfigUrl = `${setting.khojUrl}/api/config/data`; let khojConfigUrl = `${setting.khojUrl}/api/config/data`;
// Load khoj app config from backend API, Update Markdown config and save // Check if khoj backend is configured, show error if backend is not running
let khoj_configured = await request(khojConfigUrl) let khoj_already_configured = await request(khojConfigUrl)
.then(response => response !== "null"); .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 // 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(response => JSON.parse(response))
.then(data => { .then(data => {
// If khoj backend not configured yet // If khoj backend not configured yet
if (!khoj_configured) { if (!khoj_already_configured) {
// Create khoj content-type config with only markdown configured // Create khoj content-type config with only markdown configured
let khojObsidianPluginPath = `${setting.obsidianVaultPath}/${this.app.vault.configDir}/plugins/khoj/`; let khojObsidianPluginPath = `${setting.obsidianVaultPath}/${this.app.vault.configDir}/plugins/khoj/`;
data["content-type"] = { data["content-type"] = {
@@ -70,9 +79,10 @@ export async function configureKhojBackend(setting: KhojSetting) {
updateKhojBackend(setting.khojUrl, data); updateKhojBackend(setting.khojUrl, data);
console.log(`Khoj: Updated markdown config in khoj backend config:\n${JSON.stringify(data["content-type"]["markdown"])}`) console.log(`Khoj: Updated markdown config in khoj backend config:\n${JSON.stringify(data["content-type"]["markdown"])}`)
} }
new Notice(`✅ Successfully Setup Khoj`);
}) })
.catch(error => { .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}`);
}) })
} }