mirror of
https://github.com/andy-stack/vaultkeeper-ai.git
synced 2026-07-22 16:30:27 +00:00
- Add clickable artifact cards with keyboard support - Create new ArtifactView for displaying file diffs - Update button styles with solid backgrounds and faster transitions - Add auto-cleanup of stale diff and plan approval views on startup - Fix artifact deletion to preserve original file path
141 lines
No EOL
4.5 KiB
TypeScript
141 lines
No EOL
4.5 KiB
TypeScript
import * as Diff from 'diff';
|
|
import type VaultkeeperAIPlugin from 'main';
|
|
import { Resolve } from './DependencyService';
|
|
import { Services } from './Services';
|
|
import type { EventService } from './EventService';
|
|
import { Event } from 'Enums/Event';
|
|
import type { Diff2HtmlUIConfig } from 'diff2html/lib/ui/js/diff2html-ui-base';
|
|
import { ColorSchemeType, OutputFormatType } from 'diff2html/lib/types';
|
|
import { Component } from 'obsidian';
|
|
import { AbortService } from './AbortService';
|
|
import type { Artifact } from 'Conversations/Artifact';
|
|
|
|
interface DiffResult {
|
|
accepted: boolean;
|
|
suggestion?: string;
|
|
}
|
|
|
|
export class DiffService extends Component {
|
|
|
|
private readonly plugin: VaultkeeperAIPlugin;
|
|
private readonly eventService: EventService;
|
|
private readonly abortService: AbortService;
|
|
|
|
private diffResolve?: (result: DiffResult) => void;
|
|
|
|
private ongoingDiff: boolean = false;
|
|
|
|
public constructor() {
|
|
super();
|
|
this.plugin = Resolve<VaultkeeperAIPlugin>(Services.VaultkeeperAIPlugin);
|
|
this.eventService = Resolve<EventService>(Services.EventService);
|
|
this.abortService = Resolve<AbortService>(Services.AbortService);
|
|
|
|
this.registerEvent(this.eventService.on(Event.DiffClosed, () => {
|
|
this.cancelPendingDiff();
|
|
}));
|
|
}
|
|
|
|
public showArtifactDiff(artifact: Artifact): void {
|
|
const diffString = this.createDiffString(artifact.filePath,
|
|
artifact.filePath, artifact.originalContent, artifact.updatedContent);
|
|
|
|
const outputFormat: OutputFormatType = "line-by-line";
|
|
|
|
const config: Diff2HtmlUIConfig = {
|
|
drawFileList: false,
|
|
matching: "words",
|
|
outputFormat: outputFormat,
|
|
highlight: false,
|
|
fileListToggle: false,
|
|
fileContentToggle: false,
|
|
synchronisedScroll: true,
|
|
colorScheme: ColorSchemeType.AUTO
|
|
};
|
|
|
|
void this.plugin.activateArtifactView(artifact, diffString, config);
|
|
}
|
|
|
|
public async requestDiff(oldFileName: string, newFileName: string, oldContent: string, newContent: string): Promise<DiffResult> {
|
|
const diffString = this.createDiffString(oldFileName, newFileName, oldContent, newContent);
|
|
|
|
const outputFormat: OutputFormatType = "line-by-line";
|
|
|
|
const config: Diff2HtmlUIConfig = {
|
|
drawFileList: false,
|
|
matching: "words",
|
|
outputFormat: outputFormat,
|
|
highlight: false,
|
|
fileListToggle: false,
|
|
fileContentToggle: false,
|
|
synchronisedScroll: true,
|
|
colorScheme: ColorSchemeType.AUTO
|
|
};
|
|
|
|
this.ongoingDiff = true;
|
|
|
|
const signal = this.abortService.signal();
|
|
return new Promise<DiffResult>((resolve, reject) => {
|
|
if (signal.aborted) {
|
|
this.finishDiff();
|
|
reject(this.abortService.reason());
|
|
return;
|
|
}
|
|
|
|
const abortHandler = () => {
|
|
this.finishDiff();
|
|
reject(this.abortService.reason());
|
|
};
|
|
signal.addEventListener("abort", abortHandler, { once: true });
|
|
|
|
this.diffResolve = (result: DiffResult) => {
|
|
signal.removeEventListener("abort", abortHandler);
|
|
resolve(result);
|
|
};
|
|
|
|
void this.plugin.activateDiffView(diffString, config);
|
|
this.eventService.trigger(Event.DiffOpened);
|
|
});
|
|
}
|
|
|
|
public onAccept() {
|
|
if (this.diffResolve) {
|
|
this.diffResolve({ accepted: true });
|
|
}
|
|
this.finishDiff();
|
|
}
|
|
|
|
public onReject() {
|
|
if (this.diffResolve) {
|
|
this.diffResolve({ accepted: false });
|
|
}
|
|
this.finishDiff();
|
|
}
|
|
|
|
public onSuggest(suggestion: string) {
|
|
if (this.diffResolve) {
|
|
this.diffResolve({ accepted: false, suggestion: suggestion });
|
|
}
|
|
this.finishDiff();
|
|
}
|
|
|
|
private cancelPendingDiff() {
|
|
if (this.ongoingDiff) {
|
|
if (this.diffResolve) {
|
|
this.diffResolve({ accepted: false });
|
|
}
|
|
this.finishDiff();
|
|
}
|
|
}
|
|
|
|
private createDiffString(oldFileName: string, newFileName: string, oldContent: string, newContent: string): string {
|
|
return Diff.createTwoFilesPatch(oldFileName, newFileName, oldContent, newContent, undefined, undefined, { context: Infinity });
|
|
}
|
|
|
|
private finishDiff() {
|
|
this.ongoingDiff = false;
|
|
this.diffResolve = undefined;
|
|
this.eventService.trigger(Event.DiffClosed);
|
|
}
|
|
|
|
} |