mirror of
https://github.com/fbarrca/obsidian-inlineAI.git
synced 2026-07-22 11:50:24 +00:00
Implemented the accept and discard changes buttons
This commit is contained in:
parent
140dda4e18
commit
8589bfb9df
3 changed files with 171 additions and 28 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
];
|
||||
|
|
|
|||
Loading…
Reference in a new issue