Cycle through chat history in chat input on Obsidian (#861)

* Add ability to cycle through the chat history in the chat input on Obsidian (similar to terminal history navigation)
* Add mod key shortcut to cycle through chat history in chat input
* Add shortcut help text in chat input placeholder

---------

Co-authored-by: Debanjum Singh Solanky <debanjum@gmail.com>
This commit is contained in:
Shantanu Sakpal 2024-08-13 12:25:25 +05:30 committed by GitHub
parent 05c0aa3882
commit b5bcce7f85
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 72 additions and 5 deletions

2
.gitignore vendored
View file

@ -37,6 +37,8 @@ src/interface/obsidian/main.js
# Exclude sourcemaps # Exclude sourcemaps
*.map *.map
# IntelliJ
.idea
# obsidian # obsidian
data.json data.json

View file

@ -1,4 +1,4 @@
import { ItemView, MarkdownRenderer, Scope, WorkspaceLeaf, request, requestUrl, setIcon } from 'obsidian'; import {ItemView, MarkdownRenderer, Scope, WorkspaceLeaf, request, requestUrl, setIcon, Platform} from 'obsidian';
import * as DOMPurify from 'dompurify'; import * as DOMPurify from 'dompurify';
import { KhojSetting } from 'src/settings'; import { KhojSetting } from 'src/settings';
import { KhojPaneView } from 'src/pane_view'; import { KhojPaneView } from 'src/pane_view';
@ -45,6 +45,10 @@ export class KhojChatView extends KhojPaneView {
waitingForLocation: boolean; waitingForLocation: boolean;
location: Location; location: Location;
keyPressTimeout: NodeJS.Timeout | null = null; 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
private currentUserInput: string = ""; // Stores the current user input that is being typed in chat
private startingMessage: string = "Message";
chatMessageState: ChatMessageState; chatMessageState: ChatMessageState;
constructor(leaf: WorkspaceLeaf, setting: KhojSetting) { constructor(leaf: WorkspaceLeaf, setting: KhojSetting) {
@ -96,6 +100,14 @@ export class KhojChatView extends KhojPaneView {
// Clear text after extracting message to send // Clear text after extracting message to send
let user_message = input_el.value.trim(); let user_message = input_el.value.trim();
// Store the message in the array if it's not empty
if (user_message) {
this.userMessages.push(user_message);
// Update starting message after sending a new message
const modifierKey = Platform.isMacOS ? '⌘' : '^';
this.startingMessage = `(${modifierKey}+↑/↓) for prev messages`;
input_el.placeholder = this.startingMessage;
}
input_el.value = ""; input_el.value = "";
this.autoResize(); this.autoResize();
@ -147,7 +159,10 @@ export class KhojChatView extends KhojPaneView {
}, },
}) })
chatInput.addEventListener('input', (_) => { this.onChatInput() }); chatInput.addEventListener('input', (_) => { this.onChatInput() });
chatInput.addEventListener('keydown', (event) => { this.incrementalChat(event) }); chatInput.addEventListener('keydown', (event) => {
this.incrementalChat(event);
this.handleArrowKeys(event);
});
// Add event listeners for long press keybinding // Add event listeners for long press keybinding
this.contentEl.addEventListener('keydown', this.handleKeyDown.bind(this)); this.contentEl.addEventListener('keydown', this.handleKeyDown.bind(this));
@ -181,7 +196,8 @@ export class KhojChatView extends KhojPaneView {
// Get chat history from Khoj backend and set chat input state // Get chat history from Khoj backend and set chat input state
let getChatHistorySucessfully = await this.getChatHistory(chatBodyEl); let getChatHistorySucessfully = await this.getChatHistory(chatBodyEl);
let placeholderText = getChatHistorySucessfully ? "Message" : "Configure Khoj to enable chat";
let placeholderText : string = getChatHistorySucessfully ? this.startingMessage : "Configure Khoj to enable chat";
chatInput.placeholder = placeholderText; chatInput.placeholder = placeholderText;
chatInput.disabled = !getChatHistorySucessfully; chatInput.disabled = !getChatHistorySucessfully;
@ -627,10 +643,19 @@ export class KhojChatView extends KhojPaneView {
chatBodyEl.innerHTML = ""; chatBodyEl.innerHTML = "";
chatBodyEl.dataset.conversationId = ""; chatBodyEl.dataset.conversationId = "";
chatBodyEl.dataset.conversationTitle = ""; chatBodyEl.dataset.conversationTitle = "";
this.userMessages = [];
this.startingMessage = "Message";
// Update the placeholder of the chat input
const chatInput = this.contentEl.querySelector('.khoj-chat-input') as HTMLTextAreaElement;
if (chatInput) {
chatInput.placeholder = this.startingMessage;
}
this.renderMessage(chatBodyEl, "Hey 👋🏾, what's up?", "khoj"); this.renderMessage(chatBodyEl, "Hey 👋🏾, what's up?", "khoj");
} }
async toggleChatSessions(forceShow: boolean = false): Promise<boolean> { async toggleChatSessions(forceShow: boolean = false): Promise<boolean> {
this.userMessages = []; // clear user previous message history
let chatBodyEl = this.contentEl.getElementsByClassName("khoj-chat-body")[0] as HTMLElement; let chatBodyEl = this.contentEl.getElementsByClassName("khoj-chat-body")[0] as HTMLElement;
if (!forceShow && this.contentEl.getElementsByClassName("side-panel")?.length > 0) { if (!forceShow && this.contentEl.getElementsByClassName("side-panel")?.length > 0) {
chatBodyEl.innerHTML = ""; chatBodyEl.innerHTML = "";
@ -846,7 +871,6 @@ export class KhojChatView extends KhojPaneView {
chatBodyEl.dataset.conversationId = responseJson.response.conversation_id; chatBodyEl.dataset.conversationId = responseJson.response.conversation_id;
chatBodyEl.dataset.conversationTitle = responseJson.response.slug || `New conversation 🌱`; chatBodyEl.dataset.conversationTitle = responseJson.response.slug || `New conversation 🌱`;
let chatLogs = responseJson.response?.conversation_id ? responseJson.response.chat ?? [] : responseJson.response; let chatLogs = responseJson.response?.conversation_id ? responseJson.response.chat ?? [] : responseJson.response;
chatLogs.forEach((chatLog: any) => { chatLogs.forEach((chatLog: any) => {
this.renderMessageWithReferences( this.renderMessageWithReferences(
@ -859,7 +883,23 @@ export class KhojChatView extends KhojPaneView {
chatLog.intent?.type, chatLog.intent?.type,
chatLog.intent?.["inferred-queries"], chatLog.intent?.["inferred-queries"],
); );
// push the user messages to the chat history
if(chatLog.by === "you"){
this.userMessages.push(chatLog.message);
}
}); });
// Update starting message after loading history
const modifierKey: string = Platform.isMacOS ? '⌘' : '^';
this.startingMessage = this.userMessages.length > 0
? `(${modifierKey}+↑/↓) for prev messages`
: "Message";
// Update the placeholder of the chat input
const chatInput = this.contentEl.querySelector('.khoj-chat-input') as HTMLTextAreaElement;
if (chatInput) {
chatInput.placeholder = this.startingMessage;
}
} }
} catch (err) { } catch (err) {
let errorMsg = "Unable to get response from Khoj server ❤️‍🩹. Ensure server is running or contact developers for help at [team@khoj.dev](mailto:team@khoj.dev) or in [Discord](https://discord.gg/BDgyabRM6e)"; let errorMsg = "Unable to get response from Khoj server ❤️‍🩹. Ensure server is running or contact developers for help at [team@khoj.dev](mailto:team@khoj.dev) or in [Discord](https://discord.gg/BDgyabRM6e)";
@ -1228,7 +1268,9 @@ export class KhojChatView extends KhojPaneView {
onChatInput() { onChatInput() {
const chatInput = <HTMLTextAreaElement>this.contentEl.getElementsByClassName("khoj-chat-input")[0]; const chatInput = <HTMLTextAreaElement>this.contentEl.getElementsByClassName("khoj-chat-input")[0];
chatInput.value = chatInput.value.trimStart(); chatInput.value = chatInput.value.trimStart();
this.currentMessageIndex = -1;
// store the current input
this.currentUserInput = chatInput.value;
this.autoResize(); this.autoResize();
} }
@ -1360,4 +1402,27 @@ export class KhojChatView extends KhojPaneView {
return referencesDiv; return referencesDiv;
} }
// function to loop through the user's past messages
handleArrowKeys(event: KeyboardEvent) {
const chatInput = event.target as HTMLTextAreaElement;
const isModKey = Platform.isMacOS ? event.metaKey : event.ctrlKey;
if (isModKey && event.key === 'ArrowUp') {
event.preventDefault();
if (this.currentMessageIndex < this.userMessages.length - 1) {
this.currentMessageIndex++;
chatInput.value = this.userMessages[this.userMessages.length - 1 - this.currentMessageIndex];
}
} else if (isModKey && event.key === 'ArrowDown') {
event.preventDefault();
if (this.currentMessageIndex > 0) {
this.currentMessageIndex--;
chatInput.value = this.userMessages[this.userMessages.length - 1 - this.currentMessageIndex];
} else if (this.currentMessageIndex === 0) {
this.currentMessageIndex = -1;
chatInput.value = this.currentUserInput;
}
}
}
} }