mirror of
https://github.com/andy-stack/vaultkeeper-ai.git
synced 2026-07-22 06:42:03 +00:00
Replace activeDocument/activeWindow globals with Obsidian API equivalents (createFragment, createDiv, createSpan, window), update officeparser to use async API, switch diff2html import path and disable highlight option, prefix unused parameters with underscore, and update dependencies including @anthropic-ai/sdk, @google/genai, officeparser, and various dev dependencies
235 lines
No EOL
9.1 KiB
TypeScript
235 lines
No EOL
9.1 KiB
TypeScript
import { Resolve } from "./DependencyService";
|
|
import { Services } from "./Services";
|
|
import type { QuickAgent } from "./AIServices/QuickAgent";
|
|
import type VaultkeeperAIPlugin from "main";
|
|
import { FuzzySuggestModal, MarkdownView, Menu, Notice, setIcon, TFile, type Editor, type EventRef, type MarkdownFileInfo } from "obsidian";
|
|
import { FileSystemService } from "./FileSystemService";
|
|
import { BeautifyPrompt } from "AIPrompts/QuickActionPrompts/Beautify";
|
|
import Spinner from "Components/Spinner.svelte";
|
|
import { mount } from "svelte";
|
|
import { ApplyTemplatePrompt } from "AIPrompts/QuickActionPrompts/ApplyTemplatePrompt";
|
|
import { Copy } from "Enums/Copy";
|
|
import type { WorkSpaceService } from "./WorkSpaceService";
|
|
import type { SettingsService } from "./SettingsService";
|
|
import type { EventService } from "./EventService";
|
|
import { Event } from "Enums/Event";
|
|
import { openPluginSettings } from "Helpers/Helpers";
|
|
|
|
export class QuickActionsService {
|
|
|
|
private plugin: VaultkeeperAIPlugin;
|
|
private workSpaceService: WorkSpaceService;
|
|
private fileSystemService: FileSystemService;
|
|
private settingsService: SettingsService;
|
|
private eventService: EventService;
|
|
|
|
private editorMenuEventRef: EventRef | null = null;
|
|
private layoutChangeEventRef: EventRef | null = null;
|
|
|
|
public constructor() {
|
|
this.plugin = Resolve<VaultkeeperAIPlugin>(Services.VaultkeeperAIPlugin);
|
|
this.workSpaceService = Resolve<WorkSpaceService>(Services.WorkSpaceService);
|
|
this.fileSystemService = Resolve<FileSystemService>(Services.FileSystemService);
|
|
this.settingsService = Resolve<SettingsService>(Services.SettingsService);
|
|
this.eventService = Resolve<EventService>(Services.EventService);
|
|
|
|
this.plugin.registerEvent(
|
|
this.eventService.on(Event.QuickActionsSettingsChanged, () => {
|
|
this.updateRegistrations();
|
|
})
|
|
);
|
|
|
|
this.registerEditorMenuActions();
|
|
this.registerViewActions();
|
|
}
|
|
|
|
/* Action Definitions */
|
|
|
|
private async beautify(_menu: Menu, editor: Editor, view: MarkdownView | MarkdownFileInfo) {
|
|
const file = view.file;
|
|
if (!file) {
|
|
return;
|
|
}
|
|
|
|
const selection = editor.getSelection();
|
|
const content = await this.fileSystemService.readFile(file);
|
|
|
|
if (content instanceof Error || (selection.trim() === "" && content.trim() === "")) {
|
|
return; // Either an excluded file or nothing to beautify
|
|
}
|
|
|
|
const notice = this.showNotice("Beautifying content...");
|
|
try {
|
|
if (selection.length > 0) {
|
|
const result = await this.newAction(BeautifyPrompt, selection);
|
|
if (result) {
|
|
await this.fileSystemService.patchFile(file, [selection], [result], false, false);
|
|
}
|
|
} else {
|
|
const result = await this.newAction(BeautifyPrompt, content);
|
|
if (result) {
|
|
await this.fileSystemService.writeToFile(file, result, false, false);
|
|
}
|
|
}
|
|
} finally {
|
|
notice.hide();
|
|
}
|
|
}
|
|
|
|
private async applyTemplate(_menu: Menu, _editor: Editor, view: MarkdownView | MarkdownFileInfo) {
|
|
const file = view.file;
|
|
if (!file) {
|
|
return;
|
|
}
|
|
|
|
const content = await this.fileSystemService.readFile(file);
|
|
if (content instanceof Error || content.trim() === "") {
|
|
return; // Either an excluded file or nothing to apply a template to
|
|
}
|
|
|
|
this.userSelectFile(this.plugin, async (templateFile) => {
|
|
const templateContent = await this.fileSystemService.readFile(templateFile);
|
|
if (templateContent instanceof Error || templateContent.trim() === "") {
|
|
return; // Either an excluded file or the template is empty
|
|
}
|
|
|
|
const notice = this.showNotice("Applying template...");
|
|
try {
|
|
const context = `${Copy.ApplyTemplateTemplateSeparator}\n${templateContent}\n${Copy.ApplyTemplateContentSeparator}\n${content}`;
|
|
const result = await this.newAction(ApplyTemplatePrompt, context);
|
|
|
|
if (result && result.trim() !== Copy.ApplyTemplateCancelled.toString()) {
|
|
await this.fileSystemService.writeToFile(file, result, false, false);
|
|
}
|
|
} finally {
|
|
notice.hide();
|
|
}
|
|
});
|
|
}
|
|
|
|
/* Action Registration */
|
|
|
|
private registerEditorMenuActions() {
|
|
if (!this.settingsService.settings.enableContextMenuActions) {
|
|
if (this.editorMenuEventRef) {
|
|
this.plugin.app.workspace.offref(this.editorMenuEventRef);
|
|
this.editorMenuEventRef = null;
|
|
}
|
|
return;
|
|
}
|
|
if (this.editorMenuEventRef) {
|
|
return;
|
|
}
|
|
|
|
this.editorMenuEventRef = this.plugin.app.workspace.on("editor-menu", (menu, editor, view) => {
|
|
menu.addItem((item) => {
|
|
item.setTitle("Beautify")
|
|
.setIcon("palette")
|
|
.onClick(async () => this.beautify(menu, editor, view));
|
|
});
|
|
menu.addItem((item) => {
|
|
item.setTitle("Apply template")
|
|
.setIcon("notepad-text-dashed")
|
|
.onClick(async () => this.applyTemplate(menu, editor, view));
|
|
});
|
|
});
|
|
this.plugin.registerEvent(this.editorMenuEventRef);
|
|
}
|
|
|
|
private registerViewActions() {
|
|
if (!this.settingsService.settings.enableToolbarActions) {
|
|
if (this.layoutChangeEventRef) {
|
|
this.plugin.app.workspace.offref(this.layoutChangeEventRef);
|
|
this.layoutChangeEventRef = null;
|
|
// Remove any existing toolbar buttons
|
|
this.plugin.app.workspace.containerEl
|
|
.querySelectorAll(".vault-keeper-ai-actions")
|
|
.forEach(el => el.remove());
|
|
}
|
|
return;
|
|
}
|
|
if (this.layoutChangeEventRef) {
|
|
return;
|
|
}
|
|
|
|
this.layoutChangeEventRef = this.plugin.app.workspace.on("layout-change", () => {
|
|
this.injectToolbarButton();
|
|
});
|
|
this.plugin.registerEvent(this.layoutChangeEventRef);
|
|
this.injectToolbarButton();
|
|
}
|
|
|
|
private injectToolbarButton() {
|
|
const actionsView = this.workSpaceService.getViewActionsView();
|
|
const view = this.workSpaceService.getActiveViewOfType(MarkdownView);
|
|
if (!actionsView || !view || actionsView.querySelector(".vault-keeper-ai-actions")) {
|
|
return;
|
|
}
|
|
const button = createEl("button", { cls: "clickable-icon view-action vault-keeper-ai-actions" });
|
|
button.setAttribute('aria-label', 'AI quick actions');
|
|
button.addEventListener('click', (evt) => {
|
|
const view = this.workSpaceService.getActiveViewOfType(MarkdownView);
|
|
if (view) {
|
|
const { editor } = view;
|
|
const menu = new Menu();
|
|
menu.addItem((item) =>
|
|
item.setTitle("Beautify")
|
|
.setIcon("palette")
|
|
.onClick(async () => this.beautify(menu, editor, view))
|
|
);
|
|
menu.addItem((item) =>
|
|
item.setTitle("Apply template")
|
|
.setIcon("notepad-text-dashed")
|
|
.onClick(async () => this.applyTemplate(menu, editor, view))
|
|
);
|
|
menu.showAtMouseEvent(evt);
|
|
}
|
|
});
|
|
setIcon(button, "sparkles");
|
|
actionsView.prepend(button);
|
|
}
|
|
|
|
private updateRegistrations() {
|
|
this.registerEditorMenuActions();
|
|
this.registerViewActions();
|
|
}
|
|
|
|
/* Helpers */
|
|
|
|
private userSelectFile(plugin: VaultkeeperAIPlugin, onSelected: (file: TFile) => Promise<void>): void {
|
|
const fileSystemService = this.fileSystemService;
|
|
|
|
new (class extends FuzzySuggestModal<TFile> {
|
|
getItems() { return fileSystemService.getMarkdownFiles(); }
|
|
getItemText(f: TFile) { return f.path; }
|
|
onChooseItem(f: TFile) { void onSelected(f); }
|
|
})(plugin.app).open();
|
|
}
|
|
|
|
private async newAction(action: string, context: string): Promise<string | null> {
|
|
if (this.settingsService.getApiKeyForCurrentModel().trim() == "") {
|
|
openPluginSettings(this.plugin);
|
|
return null;
|
|
}
|
|
const agent = Resolve<QuickAgent>(Services.QuickAgent);
|
|
agent.resolveAIProvider();
|
|
return agent.quickAction(action, context);
|
|
}
|
|
|
|
private showNotice(message: string): Notice {
|
|
const fragment = createFragment();
|
|
const container = activeDocument.createDiv();
|
|
|
|
container.addClass("quick-action-notice");
|
|
mount(Spinner, { target: container, props: {
|
|
width: "var(--size-4-4)",
|
|
height: "var(--size-4-4)",
|
|
background: "var(--background-modifier-message)"
|
|
}});
|
|
|
|
container.createSpan({ text: message });
|
|
fragment.appendChild(container);
|
|
|
|
return new Notice(fragment, 0);
|
|
}
|
|
} |