mirror of
https://github.com/khoaliber/khoj.git
synced 2026-03-02 13:18:18 +00:00
Fix and improve file read, write handling in Obsidian
Fixes - Fix to allow khoj to delete content in obsidian write mode - Do not throw error when no edit blocks in write mode on obsidian - Limit retries to fix invalid edit blocks in obsidian write mode Improvements - Only show 3 recent files as context in obsidian file read, write mode - Persist open file access mode setting across restarts in obsidian - Make khoj obsidian keyboard shortcuts toggle voice chat, chat history - Do not show <SYSTEM> instructions in chat session title on obsidian Closes #1209
This commit is contained in:
@@ -1,9 +1,9 @@
|
||||
import { ItemView, MarkdownRenderer, Scope, WorkspaceLeaf, request, requestUrl, setIcon, Platform, TFile } from 'obsidian';
|
||||
import * as DOMPurify from 'isomorphic-dompurify';
|
||||
import { KhojSetting } from 'src/settings';
|
||||
import { KhojPaneView } from 'src/pane_view';
|
||||
import { KhojView, createCopyParentText, getLinkToEntry, pasteTextAtCursor } from 'src/utils';
|
||||
import { KhojSearchModal } from 'src/search_modal';
|
||||
import Khoj from 'src/main';
|
||||
import { FileInteractions, EditBlock } from 'src/interact_with_files';
|
||||
|
||||
export interface ChatJsonResult {
|
||||
@@ -67,12 +67,12 @@ interface Agent {
|
||||
|
||||
export class KhojChatView extends KhojPaneView {
|
||||
result: string;
|
||||
setting: KhojSetting;
|
||||
waitingForLocation: boolean;
|
||||
location: Location = { timezone: Intl.DateTimeFormat().resolvedOptions().timeZone };
|
||||
keyPressTimeout: NodeJS.Timeout | null = null;
|
||||
userMessages: string[] = []; // Store user sent messages for input history cycling
|
||||
currentMessageIndex: number = -1; // Track current message index in userMessages array
|
||||
voiceChatActive: boolean = false; // Flag to track if voice chat is active
|
||||
private currentUserInput: string = ""; // Stores the current user input that is being typed in chat
|
||||
private startingMessage: string = this.getLearningMoment();
|
||||
chatMessageState: ChatMessageState;
|
||||
@@ -101,10 +101,13 @@ export class KhojChatView extends KhojPaneView {
|
||||
// 2. Higher invalid edit blocks than tolerable
|
||||
private maxEditRetries: number = 1; // Maximum retries for edit blocks
|
||||
|
||||
constructor(leaf: WorkspaceLeaf, setting: KhojSetting) {
|
||||
super(leaf, setting);
|
||||
constructor(leaf: WorkspaceLeaf, plugin: Khoj) {
|
||||
super(leaf, plugin);
|
||||
this.fileInteractions = new FileInteractions(this.app);
|
||||
|
||||
// Initialize file access mode from persisted settings
|
||||
this.fileAccessMode = this.setting.fileAccessMode ?? 'read';
|
||||
|
||||
this.waitingForLocation = true;
|
||||
|
||||
fetch("https://ipapi.co/json")
|
||||
@@ -129,7 +132,7 @@ export class KhojChatView extends KhojPaneView {
|
||||
this.scope = new Scope(this.app.scope);
|
||||
this.scope.register(["Ctrl", "Alt"], 'n', (_) => this.createNewConversation(this.currentAgent));
|
||||
this.scope.register(["Ctrl", "Alt"], 'o', async (_) => await this.toggleChatSessions());
|
||||
this.scope.register(["Ctrl", "Alt"], 'v', (_) => this.speechToText(new KeyboardEvent('keydown')));
|
||||
this.scope.register(["Ctrl", "Alt"], 'v', (_) => this.speechToText(this.voiceChatActive ? new KeyboardEvent('keyup') : new KeyboardEvent('keydown')));
|
||||
this.scope.register(["Ctrl"], 'f', (_) => new KhojSearchModal(this.app, this.setting).open());
|
||||
this.scope.register(["Ctrl"], 'r', (_) => { this.activateView(KhojView.SIMILAR); });
|
||||
}
|
||||
@@ -272,29 +275,48 @@ export class KhojChatView extends KhojPaneView {
|
||||
text: "File Access",
|
||||
attr: {
|
||||
class: "khoj-input-row-button clickable-icon",
|
||||
title: "Toggle file access mode (Read Only)",
|
||||
title: "Toggle open file access",
|
||||
},
|
||||
});
|
||||
setIcon(fileAccessButton, "file-search");
|
||||
fileAccessButton.addEventListener('click', () => {
|
||||
// Set initial icon based on persisted setting
|
||||
switch (this.fileAccessMode) {
|
||||
case 'none':
|
||||
setIcon(fileAccessButton, "file-x");
|
||||
fileAccessButton.title = "Toggle open file access (No Access)";
|
||||
break;
|
||||
case 'write':
|
||||
setIcon(fileAccessButton, "file-edit");
|
||||
fileAccessButton.title = "Toggle open file access (Read & Write)";
|
||||
break;
|
||||
case 'read':
|
||||
default:
|
||||
setIcon(fileAccessButton, "file-search");
|
||||
fileAccessButton.title = "Toggle open file access (Read Only)";
|
||||
break;
|
||||
}
|
||||
fileAccessButton.addEventListener('click', async () => {
|
||||
// Cycle through modes: none -> read -> write -> none
|
||||
switch (this.fileAccessMode) {
|
||||
case 'none':
|
||||
this.fileAccessMode = 'read';
|
||||
setIcon(fileAccessButton, "file-search");
|
||||
fileAccessButton.title = "Toggle file access mode (Read Only)";
|
||||
fileAccessButton.title = "Toggle open file access (Read Only)";
|
||||
break;
|
||||
case 'read':
|
||||
this.fileAccessMode = 'write';
|
||||
setIcon(fileAccessButton, "file-edit");
|
||||
fileAccessButton.title = "Toggle file access mode (Read & Write)";
|
||||
fileAccessButton.title = "Toggle open file access (Read & Write)";
|
||||
break;
|
||||
case 'write':
|
||||
this.fileAccessMode = 'none';
|
||||
setIcon(fileAccessButton, "file-x");
|
||||
fileAccessButton.title = "Toggle file access mode (No Access)";
|
||||
fileAccessButton.title = "Toggle open file access (No Access)";
|
||||
break;
|
||||
}
|
||||
|
||||
// Persist the updated mode to settings
|
||||
this.setting.fileAccessMode = this.fileAccessMode;
|
||||
await this.plugin.saveSettings();
|
||||
});
|
||||
|
||||
let chatInput = inputRow.createEl("textarea", {
|
||||
@@ -319,7 +341,7 @@ export class KhojChatView extends KhojPaneView {
|
||||
attr: {
|
||||
id: "khoj-transcribe",
|
||||
class: "khoj-transcribe khoj-input-row-button clickable-icon ",
|
||||
title: "Start Voice Chat (Ctrl+Alt+V)",
|
||||
title: "Hold to Voice Chat (Ctrl+Alt+V)",
|
||||
},
|
||||
})
|
||||
transcribe.addEventListener('mousedown', (event) => { this.startSpeechToText(event) });
|
||||
@@ -1037,7 +1059,7 @@ export class KhojChatView extends KhojPaneView {
|
||||
if (incomingConversationId == conversationId) {
|
||||
conversationSessionEl.classList.add("selected-conversation");
|
||||
}
|
||||
const conversationTitle = conversation["slug"] || `New conversation 🌱`;
|
||||
const conversationTitle = conversation["slug"].split("<SYSTEM>")[0].trim() || `New conversation 🌱`;
|
||||
const conversationSessionTitleEl = conversationSessionEl.createDiv("conversation-session-title");
|
||||
conversationSessionTitleEl.textContent = conversationTitle;
|
||||
conversationSessionTitleEl.addEventListener('click', () => {
|
||||
@@ -1190,7 +1212,7 @@ export class KhojChatView extends KhojPaneView {
|
||||
chatUrl += `&conversation_id=${chatBodyEl.dataset.conversationId}`;
|
||||
}
|
||||
|
||||
console.log("Fetching chat history from:", chatUrl);
|
||||
console.debug("Fetching chat history from:", chatUrl);
|
||||
|
||||
try {
|
||||
let response = await fetch(chatUrl, {
|
||||
@@ -1199,7 +1221,7 @@ export class KhojChatView extends KhojPaneView {
|
||||
});
|
||||
|
||||
let responseJson: any = await response.json();
|
||||
console.log("Chat history response:", responseJson);
|
||||
console.debug("Chat history response:", responseJson);
|
||||
|
||||
chatBodyEl.dataset.conversationId = responseJson.conversation_id;
|
||||
|
||||
@@ -1221,7 +1243,7 @@ export class KhojChatView extends KhojPaneView {
|
||||
|
||||
// Update current agent from conversation history
|
||||
if (responseJson.response.agent?.slug) {
|
||||
console.log("Found agent in conversation history:", responseJson.response.agent);
|
||||
console.debug("Found agent in conversation history:", responseJson.response.agent);
|
||||
this.currentAgent = responseJson.response.agent.slug;
|
||||
// Update the agent selector if it exists
|
||||
const agentSelect = this.contentEl.querySelector('.khoj-header-agent-select') as HTMLSelectElement;
|
||||
@@ -1352,18 +1374,25 @@ export class KhojChatView extends KhojPaneView {
|
||||
if (this.fileAccessMode === 'write') {
|
||||
const editBlocks = this.parseEditBlocks(this.chatMessageState.rawResponse);
|
||||
|
||||
// Check for errors and retry if needed
|
||||
if (editBlocks.length > 0 && editBlocks[0].hasError && this.editRetryCount < this.maxEditRetries) {
|
||||
await this.handleEditRetry(editBlocks[0]);
|
||||
return;
|
||||
}
|
||||
|
||||
// Reset retry count on success
|
||||
this.editRetryCount = 0;
|
||||
|
||||
// Apply edits if there are any
|
||||
if (editBlocks.length > 0) {
|
||||
await this.applyEditBlocks(editBlocks);
|
||||
const firstBlock = editBlocks[0];
|
||||
if (firstBlock.hasError) {
|
||||
// Only retry if we have remaining attempts; do NOT reset counter on failure
|
||||
if (this.editRetryCount < this.maxEditRetries) {
|
||||
await this.handleEditRetry(firstBlock);
|
||||
return; // Wait for retry response
|
||||
} else {
|
||||
// Exhausted retries; surface error and do not attempt further automatic retries
|
||||
console.warn('[Khoj] Max edit retries reached. Aborting further retries.');
|
||||
}
|
||||
} else {
|
||||
// Successful parse => reset counter and apply edits
|
||||
this.editRetryCount = 0;
|
||||
await this.applyEditBlocks(editBlocks);
|
||||
}
|
||||
} else {
|
||||
// No edit blocks => reset counter just in case
|
||||
this.editRetryCount = 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1720,6 +1749,7 @@ export class KhojChatView extends KhojPaneView {
|
||||
|
||||
// Toggle recording
|
||||
if (!this.mediaRecorder || this.mediaRecorder.state === 'inactive' || event.type === 'touchstart' || event.type === 'mousedown' || event.type === 'keydown') {
|
||||
this.voiceChatActive = true;
|
||||
navigator.mediaDevices
|
||||
.getUserMedia({ audio: true })
|
||||
?.then(handleRecording)
|
||||
@@ -1727,6 +1757,7 @@ export class KhojChatView extends KhojPaneView {
|
||||
this.flashStatusInChatInput("⛔️ Failed to access microphone");
|
||||
});
|
||||
} else if (this.mediaRecorder?.state === 'recording' || event.type === 'touchend' || event.type === 'touchcancel' || event.type === 'mouseup' || event.type === 'keyup') {
|
||||
this.voiceChatActive = false;
|
||||
this.mediaRecorder.stop();
|
||||
this.mediaRecorder.stream.getTracks().forEach(track => track.stop());
|
||||
this.mediaRecorder = undefined;
|
||||
@@ -2403,7 +2434,7 @@ export class KhojChatView extends KhojPaneView {
|
||||
// Add retry count
|
||||
retryBadge.createSpan({
|
||||
cls: "retry-count",
|
||||
text: `Attempt ${this.editRetryCount}/3`
|
||||
text: `Attempt ${this.editRetryCount}/${this.maxEditRetries}`
|
||||
});
|
||||
|
||||
// Add error details as a tooltip
|
||||
@@ -2418,7 +2449,7 @@ export class KhojChatView extends KhojPaneView {
|
||||
retryBadge.scrollIntoView({ behavior: "smooth", block: "center" });
|
||||
|
||||
// Create a retry prompt for the LLM
|
||||
const retryPrompt = `/general I noticed some issues with the edit block. Please fix the following and provide a corrected version (retry ${this.editRetryCount}/3):\n\n${errorDetails}\n\nPlease provide a new edit block that fixes these issues. Make sure to follow the exact format required.`;
|
||||
const retryPrompt = `/general I noticed some issues with the edit block. Please fix the following and provide a corrected version (retry ${this.editRetryCount}/${this.maxEditRetries}):\n\n${errorDetails}\n\nPlease provide a new edit block that fixes these issues. Make sure to follow the exact format required.`;
|
||||
|
||||
// Send retry request without displaying the user message
|
||||
await this.getChatResponse(retryPrompt, "", false, false);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { App, TFile } from 'obsidian';
|
||||
import { App, MarkdownView, TFile } from 'obsidian';
|
||||
import { diffWords } from 'diff';
|
||||
|
||||
/**
|
||||
@@ -55,6 +55,7 @@ export class FileInteractions {
|
||||
private app: App;
|
||||
private readonly EDIT_BLOCK_START = '<khoj_edit>';
|
||||
private readonly EDIT_BLOCK_END = '</khoj_edit>';
|
||||
private readonly CONTEXT_FILES_LIMIT = 3;
|
||||
|
||||
/**
|
||||
* Constructor for FileInteractions
|
||||
@@ -65,6 +66,26 @@ export class FileInteractions {
|
||||
this.app = app;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get N open, recently viewed markdown files.
|
||||
*/
|
||||
private getRecentActiveMarkdownFiles(N: number): TFile[] {
|
||||
const seen = new Set<string>();
|
||||
const recentActiveFiles = this.app.workspace.getLeavesOfType('markdown')
|
||||
.sort((a, b) => (b as any).activeTime - (a as any).activeTime) // Sort by leaf activeTime (note: undocumented prop)
|
||||
.map(leaf => (leaf.view as MarkdownView)?.file)
|
||||
// Dedupe by file path
|
||||
.filter((file): file is TFile => {
|
||||
if (!file || seen.has(file.path)) return false;
|
||||
seen.add(file.path);
|
||||
return true;
|
||||
})
|
||||
.slice(0, N);
|
||||
|
||||
console.log(`Using ${recentActiveFiles.length} recently viewed md files for context: ${recentActiveFiles.map(file => file.path).join(', ')}`);
|
||||
return recentActiveFiles;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the content of all open files
|
||||
*
|
||||
@@ -75,9 +96,9 @@ export class FileInteractions {
|
||||
// Only proceed if we have read or write access
|
||||
if (fileAccessMode === 'none') return '';
|
||||
|
||||
// Get all open markdown leaves
|
||||
const leaves = this.app.workspace.getLeavesOfType('markdown');
|
||||
if (leaves.length === 0) return '';
|
||||
// Get recently viewed markdown files
|
||||
const recentFiles = this.getRecentActiveMarkdownFiles(this.CONTEXT_FILES_LIMIT);
|
||||
if (recentFiles.length === 0) return '';
|
||||
|
||||
// Instructions in write access mode
|
||||
let editInstructions: string = '';
|
||||
@@ -274,11 +295,7 @@ For context, the user is currently working on the following files:
|
||||
|
||||
`;
|
||||
|
||||
for (const leaf of leaves) {
|
||||
const view = leaf.view as any;
|
||||
const file = view?.file;
|
||||
if (!file || file.extension !== 'md') continue;
|
||||
|
||||
for (const file of recentFiles) {
|
||||
// Read file content
|
||||
let fileContent: string;
|
||||
try {
|
||||
@@ -415,8 +432,16 @@ For context, the user is currently working on the following files:
|
||||
}
|
||||
|
||||
// Try parse SEARCH/REPLACE format for complete edit blocks
|
||||
// Regex: file_path\n<<<<<<< SEARCH\nsearch_content\n=======\nreplacement_content\n>>>>>>> REPLACE
|
||||
const newFormatRegex = /^([^\n]+)\n<<<<<<< SEARCH\n([\s\S]*?)\n=======\n([\s\S]*?)\n>>>>>>> REPLACE\s*$/;
|
||||
// Supports empty SEARCH (new file / replace whole file) and empty REPLACE (deletion)
|
||||
// Regex structure:
|
||||
// file_path (group 1)
|
||||
// <<<<<<< SEARCH literal marker
|
||||
// search_content (group 2, can be empty)
|
||||
// ======= divider
|
||||
// replacement_content (group 3, can be empty => deletion)
|
||||
// >>>>>>> REPLACE end marker
|
||||
// Note: The trailing newline before the end marker is optional to allow zero-length replacement
|
||||
const newFormatRegex = /^([^\n]+)\n<<<<<<< SEARCH\n([\s\S]*?)\n=======\n([\s\S]*?)\n?>>>>>>> REPLACE\s*$/;
|
||||
const newFormatMatch = newFormatRegex.exec(cleanContent);
|
||||
|
||||
let editData: EditBlock | null = null;
|
||||
@@ -430,32 +455,25 @@ For context, the user is currently working on the following files:
|
||||
|
||||
// Validate required fields
|
||||
let error: { type: 'missing_field' | 'invalid_format' | 'preprocessing' | 'unknown', message: string, details?: string } | null = null;
|
||||
if (!editData) {
|
||||
error = {
|
||||
type: 'invalid_format',
|
||||
message: 'Invalid edit block format',
|
||||
details: 'The edit block does not match the expected format'
|
||||
};
|
||||
}
|
||||
else if (!editData.file) {
|
||||
if (editData && !editData.file) {
|
||||
error = {
|
||||
type: 'missing_field',
|
||||
message: 'Missing "file" field in edit block',
|
||||
details: 'The "file" field is required and should contain the target file name'
|
||||
};
|
||||
}
|
||||
else if (editData.find === undefined || editData.find === null) {
|
||||
else if (editData && (editData.find === undefined || editData.find === null)) {
|
||||
error = {
|
||||
type: 'missing_field',
|
||||
message: 'Missing "find" field markers',
|
||||
details: 'The "find" field is required and should contain the content to find in the file'
|
||||
details: 'The "find" field is required. It should contain the content to find in the file or be empty for new files'
|
||||
};
|
||||
}
|
||||
else if (!editData.replace) {
|
||||
else if (editData && editData.replace === undefined) {
|
||||
error = {
|
||||
type: 'missing_field',
|
||||
message: 'Missing "replace" field in edit block',
|
||||
details: 'The "replace" field is required and should contain the replacement text'
|
||||
details: 'The "replace" field is required. It should contain the content to replace or be empty to indicate deletion'
|
||||
};
|
||||
}
|
||||
|
||||
@@ -507,7 +525,7 @@ For context, the user is currently working on the following files:
|
||||
}
|
||||
|
||||
if (!editData) {
|
||||
console.error("No edit data parsed");
|
||||
console.debug("No edit data parsed");
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -684,10 +702,8 @@ For context, the user is currently working on the following files:
|
||||
// Track current content for each file as we apply edits
|
||||
const currentFileContents = new Map<string, string>();
|
||||
|
||||
// Get all open markdown files
|
||||
const files = this.app.workspace.getLeavesOfType('markdown')
|
||||
.map(leaf => (leaf.view as any)?.file)
|
||||
.filter(file => file && file.extension === 'md');
|
||||
// Get recently viewed markdown file(s) to edit
|
||||
const files = this.getRecentActiveMarkdownFiles(this.CONTEXT_FILES_LIMIT);
|
||||
|
||||
// Track success/failure for each edit
|
||||
const editResults: { block: EditBlock, success: boolean, error?: string }[] = [];
|
||||
@@ -883,6 +899,10 @@ For context, the user is currently working on the following files:
|
||||
|
||||
// Parse the block content
|
||||
const { editData, cleanContent, error, inProgress } = this.parseEditBlock(content, isComplete);
|
||||
if (!editData && !error) {
|
||||
// If no edit data and no error, skip this block
|
||||
continue;
|
||||
}
|
||||
|
||||
// Escape content for HTML display
|
||||
const diff = diffWords(editData?.find || '', editData?.replace || '');
|
||||
@@ -898,7 +918,7 @@ For context, the user is currently working on the following files:
|
||||
).join('').trim();
|
||||
|
||||
let htmlRender = '';
|
||||
if (error || !editData) {
|
||||
if (error) {
|
||||
// Error block
|
||||
console.error("Error parsing khoj-edit block:", error);
|
||||
console.error("Content causing error:", content);
|
||||
@@ -913,7 +933,7 @@ For context, the user is currently working on the following files:
|
||||
<pre><code class="language-md error">${diffContent}</code></pre>
|
||||
</div>
|
||||
</details>`;
|
||||
} else if (inProgress) {
|
||||
} else if (editData && inProgress) {
|
||||
// In-progress block
|
||||
htmlRender = `<details class="khoj-edit-accordion in-progress">
|
||||
<summary>📄 ${editData.file} <span class="khoj-edit-status">In Progress</span></summary>
|
||||
@@ -921,7 +941,7 @@ For context, the user is currently working on the following files:
|
||||
<pre><code class="language-md">${diffContent}</code></pre>
|
||||
</div>
|
||||
</details>`;
|
||||
} else {
|
||||
} else if (editData) {
|
||||
// Success block
|
||||
// Find the actual file that will be modified
|
||||
const targetFile = this.findBestMatchingFile(editData.file, files);
|
||||
|
||||
@@ -3,8 +3,8 @@ import { KhojSetting, KhojSettingTab, DEFAULT_SETTINGS } from 'src/settings'
|
||||
import { KhojSearchModal } from 'src/search_modal'
|
||||
import { KhojChatView } from 'src/chat_view'
|
||||
import { KhojSimilarView } from 'src/similar_view'
|
||||
import { updateContentIndex, canConnectToBackend, KhojView, jumpToPreviousView } from './utils';
|
||||
import { KhojPaneView } from './pane_view';
|
||||
import { updateContentIndex, canConnectToBackend, KhojView } from 'src/utils';
|
||||
import { KhojPaneView } from 'src/pane_view';
|
||||
|
||||
|
||||
export default class Khoj extends Plugin {
|
||||
@@ -73,7 +73,7 @@ export default class Khoj extends Plugin {
|
||||
this.activateView(KhojView.CHAT).then(() => {
|
||||
const chatView = this.app.workspace.getActiveViewOfType(KhojChatView);
|
||||
if (chatView) {
|
||||
chatView.toggleChatSessions(true);
|
||||
chatView.toggleChatSessions();
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -88,8 +88,9 @@ export default class Khoj extends Plugin {
|
||||
this.activateView(KhojView.CHAT).then(() => {
|
||||
const chatView = this.app.workspace.getActiveViewOfType(KhojChatView);
|
||||
if (chatView) {
|
||||
// Trigger speech to text functionality
|
||||
chatView.speechToText(new KeyboardEvent('keydown'));
|
||||
// Toggle speech to text functionality
|
||||
const toggleEvent = chatView.voiceChatActive ? 'keyup' : 'keydown';
|
||||
chatView.speechToText(new KeyboardEvent(toggleEvent));
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -136,8 +137,8 @@ export default class Khoj extends Plugin {
|
||||
});
|
||||
|
||||
// Register views
|
||||
this.registerView(KhojView.CHAT, (leaf) => new KhojChatView(leaf, this.settings));
|
||||
this.registerView(KhojView.SIMILAR, (leaf) => new KhojSimilarView(leaf, this.settings));
|
||||
this.registerView(KhojView.CHAT, (leaf) => new KhojChatView(leaf, this));
|
||||
this.registerView(KhojView.SIMILAR, (leaf) => new KhojSimilarView(leaf, this));
|
||||
|
||||
// Create an icon in the left ribbon.
|
||||
this.addRibbonIcon('message-circle', 'Khoj', (_: MouseEvent) => {
|
||||
|
||||
@@ -1,14 +1,17 @@
|
||||
import { ItemView, WorkspaceLeaf } from 'obsidian';
|
||||
import { KhojSetting } from 'src/settings';
|
||||
import { KhojView, populateHeaderPane } from './utils';
|
||||
import Khoj from 'src/main';
|
||||
|
||||
export abstract class KhojPaneView extends ItemView {
|
||||
setting: KhojSetting;
|
||||
plugin: Khoj;
|
||||
|
||||
constructor(leaf: WorkspaceLeaf, setting: KhojSetting) {
|
||||
constructor(leaf: WorkspaceLeaf, plugin: Khoj) {
|
||||
super(leaf);
|
||||
|
||||
this.setting = setting;
|
||||
this.setting = plugin.settings;
|
||||
this.plugin = plugin;
|
||||
|
||||
// Register Modal Keybindings to send user message
|
||||
// this.scope.register([], 'Enter', async () => { await this.chat() });
|
||||
|
||||
@@ -38,6 +38,7 @@ export interface KhojSetting {
|
||||
syncFolders: string[];
|
||||
syncInterval: number;
|
||||
autoVoiceResponse: boolean;
|
||||
fileAccessMode: 'none' | 'read' | 'write';
|
||||
selectedChatModelId: string | null; // Mirrors server's selected_chat_model_config
|
||||
availableChatModels: ModelOption[];
|
||||
}
|
||||
@@ -58,6 +59,7 @@ export const DEFAULT_SETTINGS: KhojSetting = {
|
||||
syncFolders: [],
|
||||
syncInterval: 60,
|
||||
autoVoiceResponse: true,
|
||||
fileAccessMode: 'read',
|
||||
selectedChatModelId: null, // Will be populated from server
|
||||
availableChatModels: [],
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { WorkspaceLeaf, TFile, MarkdownRenderer, Notice, setIcon } from 'obsidian';
|
||||
import { KhojSetting } from 'src/settings';
|
||||
import { KhojPaneView } from 'src/pane_view';
|
||||
import { KhojView, getLinkToEntry, supportedBinaryFileTypes } from 'src/utils';
|
||||
import Khoj from 'src/main';
|
||||
|
||||
export interface SimilarResult {
|
||||
entry: string;
|
||||
@@ -11,7 +11,6 @@ export interface SimilarResult {
|
||||
|
||||
export class KhojSimilarView extends KhojPaneView {
|
||||
static iconName: string = "search";
|
||||
setting: KhojSetting;
|
||||
currentController: AbortController | null = null;
|
||||
isLoading: boolean = false;
|
||||
loadingEl: HTMLElement;
|
||||
@@ -21,9 +20,8 @@ export class KhojSimilarView extends KhojPaneView {
|
||||
fileWatcher: any;
|
||||
component: any;
|
||||
|
||||
constructor(leaf: WorkspaceLeaf, setting: KhojSetting) {
|
||||
super(leaf, setting);
|
||||
this.setting = setting;
|
||||
constructor(leaf: WorkspaceLeaf, plugin: Khoj) {
|
||||
super(leaf, plugin);
|
||||
this.component = this;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user