Implemented the accept and discard changes buttons

This commit is contained in:
FBarrca 2025-01-04 21:25:42 +01:00
parent 140dda4e18
commit 8589bfb9df
3 changed files with 171 additions and 28 deletions

View file

@ -6,7 +6,7 @@ import { commandEffect, selectionOverlayWidget } from "./modules/WidgetExtension
import { ChatApiManager } from "./api";
import { AIResponseField } from "./modules/AIExtension";
import { selectionHighlightField, selectionInfoField, setSelectionInfoEffect } from "./modules/SelectionSate";
import { diffField } from "./modules/diffExtension";
import { diffExtension, diffField } from "./modules/diffExtension";
export default class MyPlugin extends Plugin {
settings: MyPluginSettings = DEFAULT_SETTINGS;
@ -20,7 +20,8 @@ export default class MyPlugin extends Plugin {
AIResponseField,
selectionInfoField,
selectionHighlightField,
diffField,
diffExtension
]);
// Add command to show tooltip

View file

@ -22,7 +22,6 @@ import { selectionInfoField, SelectionInfo } from "./SelectionSate";
export const commandEffect = StateEffect.define<null>();
export const dismmisTooltipEffect = StateEffect.define<null>();
export const acceptTooltipEffect = StateEffect.define<null>();
export const discardTooltipEffect = StateEffect.define<null>();
export const reloadTooltipEffect = StateEffect.define<null>();
class CursorOverlayWidget extends WidgetType {
@ -35,9 +34,15 @@ class CursorOverlayWidget extends WidgetType {
private innerDom = document.createElement("div");
private textFieldView?: EditorView;
// Primary Action Buttons
private submitButton!: HTMLButtonElement;
private loaderElement!: HTMLElement;
//Secondary Action Buttons
private acceptButton!: HTMLButtonElement;
private discardButton!: HTMLButtonElement;
private reloadButton!: HTMLButtonElement;
constructor(chatApiManager: ChatApiManager, selectionInfo: SelectionInfo | null) {
super();
this.chatApiManager = chatApiManager;
@ -108,6 +113,14 @@ class CursorOverlayWidget extends WidgetType {
}
}
private acceptChange() {
if (this.outerEditorView) {
this.outerEditorView.dispatch({
effects: acceptTooltipEffect.of(null),
});
}
}
private createPencilIcon() {
if (!this.innerDom.querySelector(".cm-pencil-icon")) {
const icon = document.createElement("div");
@ -185,7 +198,9 @@ class CursorOverlayWidget extends WidgetType {
.callSelection(userPrompt, selectedText)
.then((aiResponse) => {
// Optionally handle or display aiResponse
// we dont use it here as we are dispatching the effect from the method
// For this implementation, we'll assume the AI response is handled via state effects
// and we'll transition to the action buttons stage.
this.showActionButtons();
})
.catch((error) => {
console.error("Error calling AI:", error);
@ -194,6 +209,7 @@ class CursorOverlayWidget extends WidgetType {
// Hide loader
this.toggleLoading(false);
});
}
/**
@ -209,6 +225,100 @@ class CursorOverlayWidget extends WidgetType {
this.loaderElement.style.display = "none";
}
}
/**
* Transitions the widget to show Accept, Discard, and Reload buttons.
*/
private showActionButtons() {
// Clear existing inner content
this.submitButton.style.display = "none";
// Create Accept, Discard, and Reload buttons
this.createAcceptButton();
this.createDiscardButton();
this.createReloadButton();
}
/**
* Creates the Accept button.
*/
private createAcceptButton() {
this.acceptButton = document.createElement("button");
this.acceptButton.textContent = "Accept";
this.acceptButton.className = "accept-button tooltip-button primary-action";
setIcon(this.acceptButton, "check");
this.acceptButton.onclick = () => {
this.acceptAction();
};
this.innerDom.appendChild(this.acceptButton);
}
/**
* Creates the Discard button.
*/
private createDiscardButton() {
this.discardButton = document.createElement("button");
this.discardButton.textContent = "Discard";
this.discardButton.className = "discard-button tooltip-button neutral-action";
setIcon(this.discardButton, "cross");
this.discardButton.onclick = () => {
this.discardAction();
};
this.innerDom.appendChild(this.discardButton);
}
/**
* Creates the Reload button.
*/
private createReloadButton() {
this.reloadButton = document.createElement("button");
this.reloadButton.textContent = "Reload";
this.reloadButton.className = "reload-button tooltip-button neutral-action";
setIcon(this.reloadButton, "rotate-ccw"); // Assuming "reload" is a valid icon
this.reloadButton.onclick = () => {
this.reloadAction();
};
this.innerDom.appendChild(this.reloadButton);
}
/**
* Handles the Accept action.
* Confirms the result, applies changes, and closes the tooltip.
*/
private acceptAction() {
console.log("Accept button clicked");
this.acceptChange();
// After applying changes, dismiss the tooltip
this.dismissTooltip();
}
/**
* Handles the Discard action.
* Rejects the result and closes the tooltip without applying changes.
*/
private discardAction() {
console.log("Discard button clicked");
// Simply dismiss the tooltip without applying any changes
this.dismissTooltip();
}
/**
* Handles the Reload action.
* Reprocesses the query based on current edits.
*/
private reloadAction() {
console.log("Reload button clicked");
// Optionally, you might want to re-open the input field or re-submit the current prompt.
// For this example, we'll transition back to the input stage.
this.submitAction();
}
}
/**

View file

@ -1,7 +1,6 @@
// modules/diffExtension.ts
import {
EditorState,
StateEffect,
StateField,
RangeSetBuilder,
} from "@codemirror/state";
@ -10,10 +9,12 @@ import {
DecorationSet,
EditorView,
WidgetType,
ViewPlugin,
ViewUpdate,
} from "@codemirror/view";
import { diffWords, Change } from "diff";
import { dismmisTooltipEffect } from "./WidgetExtension";
import { acceptTooltipEffect, dismmisTooltipEffect } from "./WidgetExtension";
import { AIResponseField, setAIResponseEffect } from "./AIExtension";
import { selectionInfoField } from "./SelectionSate";
@ -72,14 +73,8 @@ function generateDiffView(state: EditorState): DecorationSet {
aiResponse: response,
contextText: context,
});
console.log("AI Response:", response, "Context Text:", context);
console.log(
"AI Response:",
response,
"Context Text:",
context
)
// Compute the word-level differences using the 'diff' package
const diffResult: Change[] = diffWords(contextText, aiText);
// Initialize RangeSetBuilder for efficient decoration construction
@ -131,26 +126,43 @@ function generateDiffView(state: EditorState): DecorationSet {
}
/**
* Defines a StateField to manage the diff decorations.
* This function takes the current editor state and the view, and applies the AI-suggested changes.
*
* @param state - The current editor state.
* @param view - The EditorView instance.
*/
function applyDiffChanges(state: EditorState, view: EditorView): void {
try {
console.log("Applying diff changes");
// Grab the AI text and selection info (original context)
const response = state.field(AIResponseField);
const context = state.field(selectionInfoField);
const aiText: string = response?.airesponse ?? "";
const selectionFrom = context?.from ?? 0;
const selectionTo = context?.to ?? 0;
// Dispatch the transaction to apply the AI changes
view.dispatch({
changes: { from: selectionFrom, to: selectionTo, insert: aiText },
});
} catch (error) {
console.error("Error applying diff changes:", error);
}
}
/**
* Our main diff field that reacts to AI response effects, accept tooltip, etc.
*/
export const diffField = StateField.define<DecorationSet>({
create(): DecorationSet {
return Decoration.none;
},
update(decorations: DecorationSet, tr): DecorationSet {
// Check if the AI response effect is present
const hasAIResponseEffect = tr.effects.some(e => e.is(setAIResponseEffect));
if (hasAIResponseEffect) {
const effect = tr.effects.find(e => e.is(setAIResponseEffect));
if (effect && effect !== null) {
console.debug("Received AI response");
console.log("AI Response:", effect);
return generateDiffView(tr.state);
} else {
console.error("Error in AI response effect");
return Decoration.none;
}
update(decorations: DecorationSet, tr) {
// Check if we got the AI response effect
if (tr.effects.some(e => e.is(setAIResponseEffect))) {
return generateDiffView(tr.state);
}
// Check if the dismiss tooltip effect is present
@ -165,9 +177,29 @@ export const diffField = StateField.define<DecorationSet>({
provide: (field) => EditorView.decorations.from(field),
});
/**
* Plugin to handle applying diff changes when the accept tooltip effect is triggered.
*/
const applyDiffPlugin = ViewPlugin.fromClass(class {
update(update: ViewUpdate) {
// Iterate through all transactions in the update
for (const transaction of update.transactions) {
for (const effect of transaction.effects) {
if (effect.is(acceptTooltipEffect)) {
// Apply the diff changes by dispatching the transaction
setTimeout(() => {
applyDiffChanges(update.state, update.view);
}, 0);
}
}
}
}
});
/**
* Exported extension to be included in the EditorView.
*/
export const diffExtension = [
diffField,
applyDiffPlugin,
];