Enhance note adoption process with confidence evaluation and settings
- Introduced a new function to evaluate adoption confidence based on file creation time and content criteria. - Updated the adoption confirmation logic to determine when to show confirmation based on user settings. - Added new settings for adoption confirmation mode and high-confidence time window in the settings interface. - Improved the user experience by providing clearer options for managing note adoption behavior.
This commit is contained in:
parent
90eafb62f4
commit
a6ce268b04
35
src/main.ts
35
src/main.ts
|
|
@ -39,6 +39,7 @@ import {
|
||||||
isAdoptCandidate,
|
isAdoptCandidate,
|
||||||
matchesPendingHint,
|
matchesPendingHint,
|
||||||
mergeFrontmatter,
|
mergeFrontmatter,
|
||||||
|
evaluateAdoptionConfidence,
|
||||||
type PendingCreateHint,
|
type PendingCreateHint,
|
||||||
} from "./unresolvedLink/adoptHelpers";
|
} from "./unresolvedLink/adoptHelpers";
|
||||||
import { AdoptNoteModal } from "./ui/AdoptNoteModal";
|
import { AdoptNoteModal } from "./ui/AdoptNoteModal";
|
||||||
|
|
@ -745,21 +746,31 @@ export default class MindnetCausalAssistantPlugin extends Plugin {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if matches pending hint (high confidence)
|
// Evaluate confidence level
|
||||||
const highConfidence = matchesPendingHint(file, this.pendingCreateHint);
|
const confidence = evaluateAdoptionConfidence(
|
||||||
|
file,
|
||||||
|
content,
|
||||||
|
this.settings.adoptMaxChars,
|
||||||
|
this.pendingCreateHint,
|
||||||
|
this.settings.highConfidenceWindowMs
|
||||||
|
);
|
||||||
|
|
||||||
if (this.settings.debugLogging) {
|
if (this.settings.debugLogging) {
|
||||||
console.log(`[Mindnet] Adopt candidate detected: ${file.path}`, {
|
console.log(`[Mindnet] Adopt candidate detected: ${file.path}`, {
|
||||||
highConfidence,
|
confidence,
|
||||||
pendingHint: this.pendingCreateHint,
|
pendingHint: this.pendingCreateHint,
|
||||||
|
fileCtime: file.stat.ctime,
|
||||||
|
timeSinceCreation: Date.now() - file.stat.ctime,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clear pending hint (used once)
|
// Clear pending hint (used once)
|
||||||
this.pendingCreateHint = null;
|
this.pendingCreateHint = null;
|
||||||
|
|
||||||
// Show confirm modal if not high confidence
|
// Determine if we need to show confirmation
|
||||||
if (!highConfidence) {
|
const shouldShowConfirm = this.shouldShowAdoptionConfirm(confidence);
|
||||||
|
|
||||||
|
if (shouldShowConfirm) {
|
||||||
const adoptModal = new AdoptNoteModal(this.app, file.basename);
|
const adoptModal = new AdoptNoteModal(this.app, file.basename);
|
||||||
const result = await adoptModal.show();
|
const result = await adoptModal.show();
|
||||||
if (!result.adopt) {
|
if (!result.adopt) {
|
||||||
|
|
@ -797,6 +808,20 @@ export default class MindnetCausalAssistantPlugin extends Plugin {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine if adoption confirmation should be shown based on mode and confidence.
|
||||||
|
*/
|
||||||
|
private shouldShowAdoptionConfirm(confidence: "high" | "low"): boolean {
|
||||||
|
if (this.settings.adoptConfirmMode === "always") {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (this.settings.adoptConfirmMode === "never") {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// onlyLowConfidence: show only for low confidence
|
||||||
|
return confidence === "low";
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adopt a note: convert to Mindnet format and start wizard.
|
* Adopt a note: convert to Mindnet format and start wizard.
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,8 @@ export interface MindnetSettings {
|
||||||
debugLogging: boolean; // Enable debug logging for unresolved link handling
|
debugLogging: boolean; // Enable debug logging for unresolved link handling
|
||||||
adoptNewNotesInEditor: boolean; // Auto-adopt newly created notes in editor
|
adoptNewNotesInEditor: boolean; // Auto-adopt newly created notes in editor
|
||||||
adoptMaxChars: number; // Max content length to consider note as adopt-candidate
|
adoptMaxChars: number; // Max content length to consider note as adopt-candidate
|
||||||
|
adoptConfirmMode: "always" | "onlyLowConfidence" | "never"; // When to show adoption confirmation
|
||||||
|
highConfidenceWindowMs: number; // Time window in ms for high-confidence adoption
|
||||||
// Semantic mapping builder settings
|
// Semantic mapping builder settings
|
||||||
mappingWrapperCalloutType: string; // default: "abstract"
|
mappingWrapperCalloutType: string; // default: "abstract"
|
||||||
mappingWrapperTitle: string; // default: "🕸️ Semantic Mapping"
|
mappingWrapperTitle: string; // default: "🕸️ Semantic Mapping"
|
||||||
|
|
@ -44,6 +46,8 @@ export interface MindnetSettings {
|
||||||
debugLogging: false,
|
debugLogging: false,
|
||||||
adoptNewNotesInEditor: true,
|
adoptNewNotesInEditor: true,
|
||||||
adoptMaxChars: 200,
|
adoptMaxChars: 200,
|
||||||
|
adoptConfirmMode: "onlyLowConfidence",
|
||||||
|
highConfidenceWindowMs: 3000,
|
||||||
mappingWrapperCalloutType: "abstract",
|
mappingWrapperCalloutType: "abstract",
|
||||||
mappingWrapperTitle: "🕸️ Semantic Mapping",
|
mappingWrapperTitle: "🕸️ Semantic Mapping",
|
||||||
mappingWrapperFolded: true,
|
mappingWrapperFolded: true,
|
||||||
|
|
|
||||||
145
src/tests/unresolvedLink/adoptConfidence.test.ts
Normal file
145
src/tests/unresolvedLink/adoptConfidence.test.ts
Normal file
|
|
@ -0,0 +1,145 @@
|
||||||
|
import { describe, it, expect } from "vitest";
|
||||||
|
import {
|
||||||
|
evaluateAdoptionConfidence,
|
||||||
|
type PendingCreateHint,
|
||||||
|
} from "../../unresolvedLink/adoptHelpers";
|
||||||
|
import { TFile } from "obsidian";
|
||||||
|
|
||||||
|
describe("evaluateAdoptionConfidence", () => {
|
||||||
|
it("returns high confidence for recent file with matching hint", () => {
|
||||||
|
const file = {
|
||||||
|
basename: "test-note",
|
||||||
|
stat: { ctime: Date.now() - 1000 }, // 1 second ago
|
||||||
|
} as TFile;
|
||||||
|
const content = "Some content";
|
||||||
|
const hint: PendingCreateHint = {
|
||||||
|
basename: "test-note",
|
||||||
|
sourcePath: "source.md",
|
||||||
|
timestamp: Date.now() - 500,
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = evaluateAdoptionConfidence(file, content, 200, hint, 3000);
|
||||||
|
expect(result).toBe("high");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("returns low confidence for old file", () => {
|
||||||
|
const file = {
|
||||||
|
basename: "test-note",
|
||||||
|
stat: { ctime: Date.now() - 10000 }, // 10 seconds ago
|
||||||
|
} as TFile;
|
||||||
|
const content = "Some content";
|
||||||
|
const hint: PendingCreateHint = {
|
||||||
|
basename: "test-note",
|
||||||
|
sourcePath: "source.md",
|
||||||
|
timestamp: Date.now() - 500,
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = evaluateAdoptionConfidence(file, content, 200, hint, 3000);
|
||||||
|
expect(result).toBe("low");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("returns low confidence for file with id", () => {
|
||||||
|
const file = {
|
||||||
|
basename: "test-note",
|
||||||
|
stat: { ctime: Date.now() - 1000 },
|
||||||
|
} as TFile;
|
||||||
|
const content = `---
|
||||||
|
id: note_123
|
||||||
|
---
|
||||||
|
Body`;
|
||||||
|
const hint: PendingCreateHint = {
|
||||||
|
basename: "test-note",
|
||||||
|
sourcePath: "source.md",
|
||||||
|
timestamp: Date.now() - 500,
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = evaluateAdoptionConfidence(file, content, 200, hint, 3000);
|
||||||
|
expect(result).toBe("low");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("returns low confidence for file exceeding maxChars", () => {
|
||||||
|
const file = {
|
||||||
|
basename: "test-note",
|
||||||
|
stat: { ctime: Date.now() - 1000 },
|
||||||
|
} as TFile;
|
||||||
|
const content = "x".repeat(300); // Exceeds maxChars
|
||||||
|
const hint: PendingCreateHint = {
|
||||||
|
basename: "test-note",
|
||||||
|
sourcePath: "source.md",
|
||||||
|
timestamp: Date.now() - 500,
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = evaluateAdoptionConfidence(file, content, 200, hint, 3000);
|
||||||
|
expect(result).toBe("low");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("returns high confidence for very recent file even without hint", () => {
|
||||||
|
const file = {
|
||||||
|
basename: "test-note",
|
||||||
|
stat: { ctime: Date.now() - 500 }, // Very recent (0.5 seconds)
|
||||||
|
} as TFile;
|
||||||
|
const content = "Some content";
|
||||||
|
|
||||||
|
const result = evaluateAdoptionConfidence(file, content, 200, null, 3000);
|
||||||
|
expect(result).toBe("high");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("returns low confidence for recent file without hint if not very recent", () => {
|
||||||
|
const file = {
|
||||||
|
basename: "test-note",
|
||||||
|
stat: { ctime: Date.now() - 2500 }, // 2.5 seconds ago (not very recent)
|
||||||
|
} as TFile;
|
||||||
|
const content = "Some content";
|
||||||
|
|
||||||
|
const result = evaluateAdoptionConfidence(file, content, 200, null, 3000);
|
||||||
|
expect(result).toBe("low");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("returns high confidence when hint matches even if file is slightly older", () => {
|
||||||
|
const file = {
|
||||||
|
basename: "test-note",
|
||||||
|
stat: { ctime: Date.now() - 2500 }, // 2.5 seconds ago
|
||||||
|
} as TFile;
|
||||||
|
const content = "Some content";
|
||||||
|
const hint: PendingCreateHint = {
|
||||||
|
basename: "test-note",
|
||||||
|
sourcePath: "source.md",
|
||||||
|
timestamp: Date.now() - 2000, // Hint is 2 seconds ago, within window
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = evaluateAdoptionConfidence(file, content, 200, hint, 3000);
|
||||||
|
expect(result).toBe("high");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("adoptConfirmMode behavior", () => {
|
||||||
|
// Pure function to test confirm mode logic
|
||||||
|
function shouldShowAdoptionConfirm(
|
||||||
|
mode: "always" | "onlyLowConfidence" | "never",
|
||||||
|
confidence: "high" | "low"
|
||||||
|
): boolean {
|
||||||
|
if (mode === "always") {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (mode === "never") {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// onlyLowConfidence: show only for low confidence
|
||||||
|
return confidence === "low";
|
||||||
|
}
|
||||||
|
|
||||||
|
it("always mode shows confirm for both high and low confidence", () => {
|
||||||
|
expect(shouldShowAdoptionConfirm("always", "high")).toBe(true);
|
||||||
|
expect(shouldShowAdoptionConfirm("always", "low")).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("never mode never shows confirm", () => {
|
||||||
|
expect(shouldShowAdoptionConfirm("never", "high")).toBe(false);
|
||||||
|
expect(shouldShowAdoptionConfirm("never", "low")).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("onlyLowConfidence mode shows confirm only for low confidence", () => {
|
||||||
|
expect(shouldShowAdoptionConfirm("onlyLowConfidence", "high")).toBe(false);
|
||||||
|
expect(shouldShowAdoptionConfirm("onlyLowConfidence", "low")).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -347,6 +347,41 @@ export class MindnetSettingTab extends PluginSettingTab {
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Adopt confirm mode
|
||||||
|
new Setting(containerEl)
|
||||||
|
.setName("Adopt confirm mode")
|
||||||
|
.setDesc("When to show adoption confirmation: always, onlyLowConfidence (skip for high-confidence), or never")
|
||||||
|
.addDropdown((dropdown) =>
|
||||||
|
dropdown
|
||||||
|
.addOption("always", "Always ask")
|
||||||
|
.addOption("onlyLowConfidence", "Only for low confidence")
|
||||||
|
.addOption("never", "Never ask")
|
||||||
|
.setValue(this.plugin.settings.adoptConfirmMode)
|
||||||
|
.onChange(async (value) => {
|
||||||
|
if (value === "always" || value === "onlyLowConfidence" || value === "never") {
|
||||||
|
this.plugin.settings.adoptConfirmMode = value;
|
||||||
|
await this.plugin.saveSettings();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
// High confidence window
|
||||||
|
new Setting(containerEl)
|
||||||
|
.setName("High confidence window (ms)")
|
||||||
|
.setDesc("Time window in milliseconds for high-confidence adoption (default: 3000)")
|
||||||
|
.addText((text) =>
|
||||||
|
text
|
||||||
|
.setPlaceholder("3000")
|
||||||
|
.setValue(String(this.plugin.settings.highConfidenceWindowMs))
|
||||||
|
.onChange(async (value) => {
|
||||||
|
const numValue = parseInt(value, 10);
|
||||||
|
if (!isNaN(numValue) && numValue > 0) {
|
||||||
|
this.plugin.settings.highConfidenceWindowMs = numValue;
|
||||||
|
await this.plugin.saveSettings();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
// Semantic Mapping Builder section
|
// Semantic Mapping Builder section
|
||||||
containerEl.createEl("h2", { text: "Semantic Mapping Builder" });
|
containerEl.createEl("h2", { text: "Semantic Mapping Builder" });
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -38,7 +38,7 @@ export function isAdoptCandidate(
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if a created file matches a pending create hint.
|
* Check if a created file matches a pending create hint.
|
||||||
* Returns true if basename matches and within time window (3 seconds).
|
* Returns true if basename matches and within time window.
|
||||||
*/
|
*/
|
||||||
export function matchesPendingHint(
|
export function matchesPendingHint(
|
||||||
file: TFile,
|
file: TFile,
|
||||||
|
|
@ -57,6 +57,46 @@ export function matchesPendingHint(
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Evaluate confidence level for adoption.
|
||||||
|
* Returns "high" if file is recently created and matches criteria, "low" otherwise.
|
||||||
|
*/
|
||||||
|
export function evaluateAdoptionConfidence(
|
||||||
|
file: TFile,
|
||||||
|
content: string,
|
||||||
|
maxChars: number,
|
||||||
|
hint: PendingCreateHint | null,
|
||||||
|
timeWindowMs: number
|
||||||
|
): "high" | "low" {
|
||||||
|
// Check if file is recently created (within time window)
|
||||||
|
const fileCtime = file.stat.ctime;
|
||||||
|
const timeSinceCreation = Date.now() - fileCtime;
|
||||||
|
const isRecent = timeSinceCreation >= 0 && timeSinceCreation <= timeWindowMs;
|
||||||
|
|
||||||
|
if (!isRecent) {
|
||||||
|
return "low";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if adopt candidate (no id, content length OK)
|
||||||
|
if (!isAdoptCandidate(content, maxChars)) {
|
||||||
|
return "low";
|
||||||
|
}
|
||||||
|
|
||||||
|
// High confidence if pending hint matches (optional but recommended)
|
||||||
|
if (matchesPendingHint(file, hint, timeWindowMs)) {
|
||||||
|
return "high";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Also high confidence if file is very recent (within window) and adopt candidate
|
||||||
|
// This handles cases where pending hint might not be set
|
||||||
|
if (isRecent && timeSinceCreation <= 2000) {
|
||||||
|
// Very recent (within 2 seconds) = high confidence
|
||||||
|
return "high";
|
||||||
|
}
|
||||||
|
|
||||||
|
return "low";
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Merge new frontmatter into existing content.
|
* Merge new frontmatter into existing content.
|
||||||
* Preserves existing frontmatter fields and body content.
|
* Preserves existing frontmatter fields and body content.
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user