Skip to content

Commit

Permalink
add: edit and regen button for user messages
Browse files Browse the repository at this point in the history
  • Loading branch information
longy2k committed Jan 18, 2024
1 parent 6fd5041 commit 24cdfb3
Show file tree
Hide file tree
Showing 13 changed files with 889 additions and 255 deletions.
2 changes: 1 addition & 1 deletion manifest.json
@@ -1,7 +1,7 @@
{
"id": "bmo-chatbot",
"name": "BMO Chatbot",
"version": "1.8.2",
"version": "1.8.3",
"minAppVersion": "1.0.0",
"description": "Generate and brainstorm ideas while creating your notes using Large Language Models (LLMs) such as OpenAI's \"gpt-3.5-turbo\" and \"gpt-4.\"",
"author": "Longy2k",
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
@@ -1,6 +1,6 @@
{
"name": "bmo-chatbot",
"version": "1.8.2",
"version": "1.8.3",
"description": "Generate and brainstorm ideas while creating your notes using Large Language Models (LLMs) such as OpenAI's \"gpt-3.5-turbo\" and \"gpt-4.\"",
"main": "main.js",
"scripts": {
Expand Down
756 changes: 655 additions & 101 deletions src/components/FetchModel.ts

Large diffs are not rendered by default.

239 changes: 166 additions & 73 deletions src/components/chat/Buttons.ts
@@ -1,7 +1,7 @@
import { Modal, Notice, setIcon } from "obsidian";
import { BMOSettings, checkActiveFile } from "src/main";
import { ANTHROPIC_MODELS, OPENAI_MODELS, activeEditor, filenameMessageHistoryJSON, lastCursorPosition, lastCursorPositionFile, messageHistory } from "src/view";
import { fetchOpenAIAPI, fetchOpenAIBaseAPI, ollamaFetchData, ollamaFetchDataStream, requestUrlAnthropicAPI, openAIRestAPIFetchData } from "../FetchModel";
import { fetchOpenAIAPI, fetchOpenAIBaseAPI, ollamaFetchData, ollamaFetchDataStream, requestUrlAnthropicAPI, openAIRestAPIFetchData, openAIRestAPIFetchDataStream } from "../FetchModel";

export function regenerateUserButton(settings: BMOSettings, referenceCurrentNote: string) {
const regenerateButton = document.createElement("button");
Expand All @@ -10,98 +10,191 @@ export function regenerateUserButton(settings: BMOSettings, referenceCurrentNote
regenerateButton.classList.add("regenerate-button");
regenerateButton.title = "regenerate";

regenerateButton.addEventListener("click", async function () {
const messageContainerEl = document.querySelector('#messageContainer');
if (messageContainerEl) {
const botMessages = messageContainerEl.querySelectorAll(".botMessage");
const lastBotMessage = botMessages[botMessages.length - 1];
const messageBlock = lastBotMessage.querySelector('.messageBlock');
const lastBotMessageToolBarDiv = lastBotMessage.querySelector(".botMessageToolBar");
if (lastBotMessageToolBarDiv) {
const buttonContainerDiv = lastBotMessageToolBarDiv.querySelector(".button-container");
if (buttonContainerDiv) {
// Remove the button container div
buttonContainerDiv.remove();
let lastClickedElement: HTMLElement | null = null;

regenerateButton.addEventListener("click", async function (event) {
event.stopPropagation();
lastClickedElement = event.target as HTMLElement;

while (lastClickedElement && !lastClickedElement.classList.contains('userMessage')) {
lastClickedElement = lastClickedElement.parentElement;
}

let index = -1;

if (lastClickedElement) {
const userMessages = Array.from(document.querySelectorAll('#messageContainer .userMessage'));
index = userMessages.indexOf(lastClickedElement) * 2;
}

if (index !== -1) {
deleteMessage(index+1);
if (OPENAI_MODELS.includes(settings.model)) {
try {
await fetchOpenAIAPI(settings, referenceCurrentNote, index);
}
catch (error) {
new Notice('Error occurred while fetching completion: ' + error.message);
console.log(error.message);
}
}
if (messageBlock) {
messageBlock.innerHTML = '';
messageHistory.pop();

const loadingEl = document.createElement("span");
loadingEl.setAttribute("id", "loading");
loadingEl.style.display = "inline-block";
loadingEl.textContent = "...";

// Define a function to update the loading animation
const updateLoadingAnimation = () => {
const loadingEl = document.querySelector('#loading');
if (!loadingEl) {
return;
}
loadingEl.textContent += ".";
// If the loading animation has reached three dots, reset it to one dot
if (loadingEl.textContent?.length && loadingEl.textContent.length > 3) {
loadingEl.textContent = ".";
}
};
else if (settings.openAIBaseModels.includes(settings.model)) {
try {
await fetchOpenAIBaseAPI(settings, referenceCurrentNote, index);
}
catch (error) {
new Notice('Error occurred while fetching completion: ' + error.message);
console.log(error.message);
}
}
else if (ANTHROPIC_MODELS.includes(settings.model)) {
try {
await requestUrlAnthropicAPI(settings, referenceCurrentNote, index);
}
catch (error) {
console.error('Error:', error);
}
}
else if (settings.ollamaRestAPIUrl && settings.ollamaModels.includes(settings.model)) {
if (settings.allowOllamaStream) {
await ollamaFetchDataStream(settings, referenceCurrentNote, index);
}
else {
await ollamaFetchData(settings, referenceCurrentNote, index);
}
}
else if (settings.openAIRestAPIUrl && settings.openAIRestAPIModels.includes(settings.model)){
if (settings.allowOpenAIRestAPIStream) {
await openAIRestAPIFetchDataStream(settings, referenceCurrentNote, index);
}
else {
await openAIRestAPIFetchData(settings, referenceCurrentNote, index);
}
}
}
else {
new Notice("No models detected.");
}
});
return regenerateButton;
}

// Dispaly loading animation
lastBotMessage.appendChild(loadingEl);
loadingEl.scrollIntoView({ behavior: 'smooth', block: 'end' });
export function displayEditButton (settings: BMOSettings, referenceCurrentNoteContent: string, userP: HTMLParagraphElement) {
const editButton = document.createElement("button");
editButton.textContent = "edit";
setIcon(editButton, "edit"); // Assuming setIcon is defined elsewhere
editButton.classList.add("edit-button");
editButton.title = "edit";

const loadingAnimationIntervalId = setInterval(updateLoadingAnimation, 500);
lastBotMessage.scrollIntoView({ behavior: "smooth", block: "end" });
let lastClickedElement: HTMLElement | null = null;

// Fetch OpenAI API
if (OPENAI_MODELS.includes(settings.model)) {
try {
await fetchOpenAIAPI(settings, referenceCurrentNote);
}
catch (error) {
new Notice('Error occurred while fetching completion: ' + error.message);
console.log(error.message);
}
}
else if (settings.openAIBaseModels.includes(settings.model)) {
try {
await fetchOpenAIBaseAPI(settings, referenceCurrentNote);
}
catch (error) {
new Notice('Error occurred while fetching completion: ' + error.message);
console.log(error.message);
editButton.addEventListener("click", function (event) {
const editContainer = document.createElement("div");
editContainer.classList.add("edit-container");
const textArea = document.createElement("textarea");
textArea.classList.add("edit-textarea");
textArea.value = userP.textContent ?? ""; // Check if userP.textContent is null and provide a default value

editContainer.appendChild(textArea);

const textareaEditButton = document.createElement("button");
textareaEditButton.textContent = "Edit";
textareaEditButton.classList.add("textarea-edit-button");
textareaEditButton.title = "edit";

const cancelButton = document.createElement("button");
cancelButton.textContent = "Cancel";
cancelButton.classList.add("textarea-cancel-button");
cancelButton.title = "cancel";

event.stopPropagation();
lastClickedElement = event.target as HTMLElement;

while (lastClickedElement && !lastClickedElement.classList.contains('userMessage')) {
lastClickedElement = lastClickedElement.parentElement;
}

textareaEditButton.addEventListener("click", async function () {
userP.textContent = textArea.value;
editContainer.replaceWith(userP);

if (lastClickedElement) {
const userMessages = Array.from(document.querySelectorAll('#messageContainer .userMessage'));

const index = userMessages.indexOf(lastClickedElement) * 2;

if (index !== -1) {
messageHistory[index].content = textArea.value;
deleteMessage(index+1);
// Fetch OpenAI API
if (OPENAI_MODELS.includes(settings.model)) {
try {
await fetchOpenAIAPI(settings, referenceCurrentNoteContent, index);
}
catch (error) {
new Notice('Error occurred while fetching completion: ' + error.message);
console.log(error.message);
}
}
}
else if (ANTHROPIC_MODELS.includes(settings.model)) {
try {
await requestUrlAnthropicAPI(settings, referenceCurrentNote);
else if (settings.openAIBaseModels.includes(settings.model)) {
try {
await fetchOpenAIBaseAPI(settings, referenceCurrentNoteContent, index);
}
catch (error) {
new Notice('Error occurred while fetching completion: ' + error.message);
console.log(error.message);
}
}
catch (error) {
console.error('Error:', error);
else if (ANTHROPIC_MODELS.includes(settings.model)) {
try {
await requestUrlAnthropicAPI(settings, referenceCurrentNoteContent, index);
}
catch (error) {
console.error('Error:', error);
}
}
}
else if (settings.ollamaRestAPIUrl && settings.ollamaModels.includes(settings.model)) {
if (settings.allowOllamaStream) {
await ollamaFetchDataStream(settings, referenceCurrentNote);
else if (settings.ollamaRestAPIUrl && settings.ollamaModels.includes(settings.model)) {
if (settings.allowOllamaStream) {
await ollamaFetchDataStream(settings, referenceCurrentNoteContent, index);
}
else {
await ollamaFetchData(settings, referenceCurrentNoteContent, index);
}
}
else {
await ollamaFetchData(settings, referenceCurrentNote);
else if (settings.openAIRestAPIUrl && settings.openAIRestAPIModels.includes(settings.model)){
if (settings.allowOpenAIRestAPIStream) {
await openAIRestAPIFetchDataStream(settings, referenceCurrentNoteContent, index);
}
else {
await openAIRestAPIFetchData(settings, referenceCurrentNoteContent, index);
}
}
}
else if (settings.openAIRestAPIUrl && settings.openAIRestAPIModels.includes(settings.model)){
await openAIRestAPIFetchData(settings, referenceCurrentNote);
}
else {
new Notice("No models detected.");
}

clearInterval(loadingAnimationIntervalId);

}

});

cancelButton.addEventListener("click", function () {
editContainer.replaceWith(userP);
});

editContainer.appendChild(textareaEditButton);
editContainer.appendChild(cancelButton);

if (userP.parentNode !== null) {
userP.parentNode.replaceChild(editContainer, userP);
}
});
return regenerateButton;

return editButton;
}


export function displayUserCopyButton (userP: HTMLParagraphElement) {
const copyButton = document.createElement("button");
copyButton.textContent = "copy";
Expand Down
4 changes: 2 additions & 2 deletions src/components/chat/Commands.ts
Expand Up @@ -54,7 +54,7 @@ export function executeCommand(input: string, settings: BMOSettings, plugin: BMO
}

// Function to create and append a bot message
function createBotMessage(settings: BMOSettings): HTMLDivElement {
export function createBotMessage(settings: BMOSettings): HTMLDivElement {
const messageContainer = document.querySelector("#messageContainer");
const botMessage = document.createElement("div");
botMessage.classList.add("botMessage");
Expand Down Expand Up @@ -100,6 +100,7 @@ function displayMessage(messageBlock: HTMLDivElement, messageHtml: string, setti

export async function commandFalse(settings: BMOSettings, plugin: BMOGPT) {
const messageBlock = createBotMessage(settings);


const formattedSettings = `
<div class="formattedSettings">
Expand Down Expand Up @@ -537,7 +538,6 @@ export async function commandSave(settings: BMOSettings) {
---\n` + fileContent;
}
markdownContent += fileContent;
// console.log(fileContent);
} else {
// YAML front matter
markdownContent +=
Expand Down
32 changes: 18 additions & 14 deletions src/components/chat/Message.ts
Expand Up @@ -3,7 +3,7 @@ import { displayAppendButton, displayBotCopyButton } from "./Buttons";
import { BMOSettings } from "src/main";

// Add a new message to the messageHistory array and save it to the file
export async function addMessage(input: string, messageType: 'userMessage' | 'botMessage', settings: BMOSettings) {
export async function addMessage(input: string, messageType: 'userMessage' | 'botMessage', settings: BMOSettings, index?: number) {
const messageObj: { role: string; content: string } = {
role: "",
content: ""
Expand All @@ -16,23 +16,27 @@ export async function addMessage(input: string, messageType: 'userMessage' | 'bo
messageObj.role = 'assistant';
messageObj.content = input.trim();

const botMessageToolBarDiv = document.querySelectorAll(".botMessageToolBar");
const lastBotMessageToolBarDiv = botMessageToolBarDiv[botMessageToolBarDiv.length - 1];
if (botMessageToolBarDiv.length > 0) {
if (!messageObj.content.includes('div class="formattedSettings"')) {
const buttonContainerDiv = document.createElement("div");
const copyBotButton = displayBotCopyButton(messageObj, settings);
const appendButton = displayAppendButton(messageObj);
buttonContainerDiv.className = "button-container";
lastBotMessageToolBarDiv.appendChild(buttonContainerDiv);
buttonContainerDiv.appendChild(copyBotButton);
buttonContainerDiv.appendChild(appendButton);
}
const messageContainerElDivs = document.querySelectorAll('#messageContainer div.userMessage, #messageContainer div.botMessage');
const targetUserMessage = messageContainerElDivs[index ?? messageHistory.length - 1];
const targetBotMessage = targetUserMessage.nextElementSibling;

if (!messageObj.content.includes('div class="formattedSettings"')) {
const botMessageToolBarDiv = targetBotMessage?.querySelector(".botMessageToolBar");
const buttonContainerDiv = document.createElement("div");
const copyBotButton = displayBotCopyButton(messageObj, settings);
const appendButton = displayAppendButton(messageObj);
buttonContainerDiv.className = "button-container";
buttonContainerDiv.appendChild(copyBotButton);
buttonContainerDiv.appendChild(appendButton);
botMessageToolBarDiv?.appendChild(buttonContainerDiv);
}

}

messageHistory.push(messageObj);

messageHistory.splice((index ?? messageHistory.length)+1, 0, messageObj);

// messageHistory.push(messageObj);

const jsonString = JSON.stringify(messageHistory, null, 4);

Expand Down
4 changes: 2 additions & 2 deletions src/components/settings/AppearanceSettings.ts
Expand Up @@ -47,7 +47,7 @@ export function addAppearanceSettings(containerEl: HTMLElement, plugin: BMOGPT,
const defaultUserMessageBackgroundColor = getComputedStyle(document.body).getPropertyValue(DEFAULT_SETTINGS.userMessageBackgroundColor).trim();

new Setting(containerEl)
.setName('Background color for User Messages')
.setName('Background Color for User Messages')
.setDesc('Modify the background color of the userMessage element.')
.addButton(button => button
.setButtonText("Restore Default")
Expand Down Expand Up @@ -98,7 +98,7 @@ export function addAppearanceSettings(containerEl: HTMLElement, plugin: BMOGPT,
const defaultBotMessageBackgroundColor = getComputedStyle(document.body).getPropertyValue(DEFAULT_SETTINGS.botMessageBackgroundColor).trim();

new Setting(containerEl)
.setName('Background color for Bot Messages')
.setName('Background Color for Bot Messages')
.setDesc('Modify the background color of the botMessage element.')
.addButton(button => button
.setButtonText("Restore Default")
Expand Down

0 comments on commit 24cdfb3

Please sign in to comment.