Enhance interview configuration and wizard functionality
- Added parsing for new edging configuration options in interview profiles, including mode, wrapper callout type, title, and folded state. - Updated the InterviewWizardModal to support post-run edging, allowing for semantic mapping based on the new edging settings. - Enhanced the unresolved link handler to pass plugin instance and settings for improved graph schema loading and post-run edging functionality. - Improved error handling and user notifications for semantic mapping processes, ensuring better feedback during execution.
This commit is contained in:
parent
a6ce268b04
commit
5ed06e6b9a
|
|
@ -118,6 +118,25 @@ function parseProfile(
|
|||
profile.defaults = raw.defaults as Record<string, unknown>;
|
||||
}
|
||||
|
||||
// Parse edging config
|
||||
if (raw.edging && typeof raw.edging === "object") {
|
||||
const edgingRaw = raw.edging as Record<string, unknown>;
|
||||
profile.edging = {};
|
||||
|
||||
if (edgingRaw.mode === "none" || edgingRaw.mode === "post_run" || edgingRaw.mode === "inline_micro") {
|
||||
profile.edging.mode = edgingRaw.mode;
|
||||
}
|
||||
if (typeof edgingRaw.wrapperCalloutType === "string") {
|
||||
profile.edging.wrapperCalloutType = edgingRaw.wrapperCalloutType;
|
||||
}
|
||||
if (typeof edgingRaw.wrapperTitle === "string") {
|
||||
profile.edging.wrapperTitle = edgingRaw.wrapperTitle;
|
||||
}
|
||||
if (typeof edgingRaw.wrapperFolded === "boolean") {
|
||||
profile.edging.wrapperFolded = edgingRaw.wrapperFolded;
|
||||
}
|
||||
}
|
||||
|
||||
// Parse steps
|
||||
if (Array.isArray(raw.steps)) {
|
||||
for (let i = 0; i < raw.steps.length; i++) {
|
||||
|
|
|
|||
|
|
@ -15,6 +15,12 @@ export interface InterviewProfile {
|
|||
group?: string;
|
||||
defaults?: Record<string, unknown>;
|
||||
steps: InterviewStep[];
|
||||
edging?: {
|
||||
mode?: "none" | "post_run" | "inline_micro"; // Semantic mapping mode (default: "none")
|
||||
wrapperCalloutType?: string; // Override wrapper callout type
|
||||
wrapperTitle?: string; // Override wrapper title
|
||||
wrapperFolded?: boolean; // Override wrapper folded state
|
||||
};
|
||||
}
|
||||
|
||||
export type InterviewStep =
|
||||
|
|
|
|||
|
|
@ -489,7 +489,8 @@ export default class MindnetCausalAssistantPlugin extends Plugin {
|
|||
},
|
||||
async (wizardResult: WizardResult) => {
|
||||
new Notice("Interview saved and changes applied");
|
||||
}
|
||||
},
|
||||
this // Pass plugin instance for graph schema loading
|
||||
);
|
||||
} catch (e) {
|
||||
const msg = e instanceof Error ? e.message : String(e);
|
||||
|
|
@ -887,7 +888,8 @@ export default class MindnetCausalAssistantPlugin extends Plugin {
|
|||
},
|
||||
async (wizardResult: WizardResult) => {
|
||||
new Notice("Interview saved and changes applied");
|
||||
}
|
||||
},
|
||||
this // Pass plugin instance for graph schema loading
|
||||
);
|
||||
} catch (e) {
|
||||
const msg = e instanceof Error ? e.message : String(e);
|
||||
|
|
|
|||
142
src/tests/interview/postRunEdging.test.ts
Normal file
142
src/tests/interview/postRunEdging.test.ts
Normal file
|
|
@ -0,0 +1,142 @@
|
|||
import { describe, it, expect, vi, beforeEach } from "vitest";
|
||||
import type { InterviewProfile } from "../../interview/types";
|
||||
|
||||
describe("Post-run edging integration", () => {
|
||||
let mockApp: any;
|
||||
let mockFile: any;
|
||||
let mockSettings: any;
|
||||
let mockBuildSemanticMappings: any;
|
||||
|
||||
beforeEach(() => {
|
||||
mockApp = {
|
||||
vault: {
|
||||
read: vi.fn().mockResolvedValue("Content"),
|
||||
},
|
||||
};
|
||||
mockFile = {
|
||||
path: "test.md",
|
||||
basename: "test",
|
||||
};
|
||||
mockSettings = {
|
||||
mappingWrapperCalloutType: "abstract",
|
||||
mappingWrapperTitle: "🕸️ Semantic Mapping",
|
||||
mappingWrapperFolded: true,
|
||||
unassignedHandling: "prompt",
|
||||
allowOverwriteExistingMappings: false,
|
||||
};
|
||||
mockBuildSemanticMappings = vi.fn().mockResolvedValue({
|
||||
sectionsProcessed: 2,
|
||||
sectionsWithMappings: 2,
|
||||
totalLinks: 5,
|
||||
existingMappingsKept: 3,
|
||||
newMappingsAssigned: 2,
|
||||
unmappedLinksSkipped: 0,
|
||||
});
|
||||
});
|
||||
|
||||
it("should invoke builder when profile has edging.mode=post_run", async () => {
|
||||
const profile: InterviewProfile = {
|
||||
key: "test-profile",
|
||||
label: "Test Profile",
|
||||
note_type: "note",
|
||||
steps: [],
|
||||
edging: {
|
||||
mode: "post_run",
|
||||
},
|
||||
};
|
||||
|
||||
// This is a unit test for the hook path
|
||||
// In real implementation, this would be called from InterviewWizardModal
|
||||
const shouldRunEdging = profile.edging?.mode === "post_run";
|
||||
expect(shouldRunEdging).toBe(true);
|
||||
});
|
||||
|
||||
it("should not invoke builder when profile has edging.mode=none", () => {
|
||||
const profile: InterviewProfile = {
|
||||
key: "test-profile",
|
||||
label: "Test Profile",
|
||||
note_type: "note",
|
||||
steps: [],
|
||||
edging: {
|
||||
mode: "none",
|
||||
},
|
||||
};
|
||||
|
||||
const shouldRunEdging = profile.edging?.mode === "post_run";
|
||||
expect(shouldRunEdging).toBe(false);
|
||||
});
|
||||
|
||||
it("should not invoke builder when profile has no edging config", () => {
|
||||
const profile: InterviewProfile = {
|
||||
key: "test-profile",
|
||||
label: "Test Profile",
|
||||
note_type: "note",
|
||||
steps: [],
|
||||
};
|
||||
|
||||
const shouldRunEdging = profile.edging?.mode === "post_run";
|
||||
expect(shouldRunEdging).toBe(false);
|
||||
});
|
||||
|
||||
it("should use profile edging config over settings", () => {
|
||||
const profile: InterviewProfile = {
|
||||
key: "test-profile",
|
||||
label: "Test Profile",
|
||||
note_type: "note",
|
||||
steps: [],
|
||||
edging: {
|
||||
mode: "post_run",
|
||||
wrapperCalloutType: "info",
|
||||
wrapperTitle: "Custom Title",
|
||||
wrapperFolded: false,
|
||||
},
|
||||
};
|
||||
|
||||
const settings = {
|
||||
mappingWrapperCalloutType: "abstract",
|
||||
mappingWrapperTitle: "Default Title",
|
||||
mappingWrapperFolded: true,
|
||||
};
|
||||
|
||||
// Profile config should override settings
|
||||
const finalCalloutType = profile.edging?.wrapperCalloutType || settings.mappingWrapperCalloutType;
|
||||
const finalTitle = profile.edging?.wrapperTitle || settings.mappingWrapperTitle;
|
||||
const finalFolded = profile.edging?.wrapperFolded !== undefined
|
||||
? profile.edging.wrapperFolded
|
||||
: settings.mappingWrapperFolded;
|
||||
|
||||
expect(finalCalloutType).toBe("info");
|
||||
expect(finalTitle).toBe("Custom Title");
|
||||
expect(finalFolded).toBe(false);
|
||||
});
|
||||
|
||||
it("should use settings defaults when profile edging config is partial", () => {
|
||||
const profile: InterviewProfile = {
|
||||
key: "test-profile",
|
||||
label: "Test Profile",
|
||||
note_type: "note",
|
||||
steps: [],
|
||||
edging: {
|
||||
mode: "post_run",
|
||||
wrapperCalloutType: "info",
|
||||
// wrapperTitle and wrapperFolded not specified
|
||||
},
|
||||
};
|
||||
|
||||
const settings = {
|
||||
mappingWrapperCalloutType: "abstract",
|
||||
mappingWrapperTitle: "Default Title",
|
||||
mappingWrapperFolded: true,
|
||||
};
|
||||
|
||||
const finalCalloutType = profile.edging?.wrapperCalloutType || settings.mappingWrapperCalloutType;
|
||||
const finalTitle = profile.edging?.wrapperTitle || settings.mappingWrapperTitle;
|
||||
const finalFolded = profile.edging?.wrapperFolded !== undefined
|
||||
? profile.edging.wrapperFolded
|
||||
: settings.mappingWrapperFolded;
|
||||
|
||||
expect(finalCalloutType).toBe("info"); // From profile
|
||||
expect(finalTitle).toBe("Default Title"); // From settings (fallback)
|
||||
expect(finalFolded).toBe(true); // From settings (fallback)
|
||||
});
|
||||
});
|
||||
|
|
@ -29,6 +29,8 @@ import { renderProfileToMarkdown, type RenderAnswers } from "../interview/render
|
|||
import { NoteIndex } from "../entityPicker/noteIndex";
|
||||
import { EntityPickerModal, type EntityPickerResult } from "./EntityPickerModal";
|
||||
import { insertWikilinkIntoTextarea } from "../entityPicker/wikilink";
|
||||
import { buildSemanticMappings, type BuildResult } from "../mapping/semanticMappingBuilder";
|
||||
import type { MindnetSettings } from "../settings";
|
||||
import {
|
||||
type LoopRuntimeState,
|
||||
createLoopState,
|
||||
|
|
@ -57,7 +59,10 @@ export class InterviewWizardModal extends Modal {
|
|||
fileContent: string;
|
||||
onSubmit: (result: WizardResult) => void;
|
||||
onSaveAndExit: (result: WizardResult) => void;
|
||||
profile: InterviewProfile;
|
||||
profileKey: string;
|
||||
settings?: MindnetSettings; // Optional settings for post-run edging
|
||||
plugin?: { ensureGraphSchemaLoaded?: () => Promise<import("../mapping/graphSchema").GraphSchema | null> }; // Optional plugin instance
|
||||
// Store current input values to save on navigation
|
||||
private currentInputValues: Map<string, string> = new Map();
|
||||
// Store preview mode state per step key
|
||||
|
|
@ -71,7 +76,9 @@ export class InterviewWizardModal extends Modal {
|
|||
file: TFile,
|
||||
fileContent: string,
|
||||
onSubmit: (result: WizardResult) => void,
|
||||
onSaveAndExit: (result: WizardResult) => void
|
||||
onSaveAndExit: (result: WizardResult) => void,
|
||||
settings?: MindnetSettings,
|
||||
plugin?: { ensureGraphSchemaLoaded?: () => Promise<import("../mapping/graphSchema").GraphSchema | null> }
|
||||
) {
|
||||
super(app);
|
||||
|
||||
|
|
@ -81,7 +88,10 @@ export class InterviewWizardModal extends Modal {
|
|||
throw new Error("Profile is required");
|
||||
}
|
||||
|
||||
this.profile = profile;
|
||||
this.profileKey = profile.key;
|
||||
this.settings = settings;
|
||||
this.plugin = plugin;
|
||||
|
||||
// Log profile info
|
||||
const stepKinds = profile.steps?.map(s => s.type) || [];
|
||||
|
|
@ -89,6 +99,9 @@ export class InterviewWizardModal extends Modal {
|
|||
key: profile.key,
|
||||
stepCount: profile.steps?.length,
|
||||
kinds: stepKinds,
|
||||
edgingMode: profile.edging?.mode || "none",
|
||||
edging: profile.edging, // Full edging object for debugging
|
||||
profileKeys: Object.keys(profile), // All keys in profile
|
||||
});
|
||||
|
||||
// Validate steps - only throw if profile.steps is actually empty
|
||||
|
|
@ -2255,7 +2268,7 @@ export class InterviewWizardModal extends Modal {
|
|||
.setButtonText(isReview ? "Apply & Finish" : "Next")
|
||||
.setCta()
|
||||
.setDisabled((!canGoNext(this.state) && !isReview) || !canProceedLoop);
|
||||
button.onClick(() => {
|
||||
button.onClick(async () => {
|
||||
if (isReview) {
|
||||
console.log("=== FINISH WIZARD (Apply & Finish) ===");
|
||||
// Save current step data before finishing
|
||||
|
|
@ -2264,6 +2277,20 @@ export class InterviewWizardModal extends Modal {
|
|||
this.saveCurrentStepData(currentStep);
|
||||
}
|
||||
this.applyPatches();
|
||||
|
||||
// Run semantic mapping builder if edging mode is post_run
|
||||
console.log("[Mindnet] Checking edging mode:", {
|
||||
profileKey: this.profile.key,
|
||||
edgingMode: this.profile.edging?.mode,
|
||||
hasEdging: !!this.profile.edging,
|
||||
});
|
||||
if (this.profile.edging?.mode === "post_run") {
|
||||
console.log("[Mindnet] Starting post-run edging");
|
||||
await this.runPostRunEdging();
|
||||
} else {
|
||||
console.log("[Mindnet] Post-run edging skipped (mode:", this.profile.edging?.mode || "none", ")");
|
||||
}
|
||||
|
||||
this.onSubmit({ applied: true, patches: this.state.patches });
|
||||
this.close();
|
||||
} else {
|
||||
|
|
@ -2289,6 +2316,64 @@ export class InterviewWizardModal extends Modal {
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Run semantic mapping builder after interview finish (post_run mode).
|
||||
*/
|
||||
private async runPostRunEdging(): Promise<void> {
|
||||
console.log("[Mindnet] runPostRunEdging called", {
|
||||
hasSettings: !!this.settings,
|
||||
hasPlugin: !!this.plugin,
|
||||
file: this.file.path,
|
||||
});
|
||||
|
||||
if (!this.settings) {
|
||||
console.warn("[Mindnet] Cannot run post-run edging: settings not provided");
|
||||
new Notice("Edging: Settings nicht verfügbar");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
console.log("[Mindnet] Starting semantic mapping builder");
|
||||
// Create settings override from profile edging config
|
||||
const edgingSettings: MindnetSettings = {
|
||||
...this.settings,
|
||||
mappingWrapperCalloutType: this.profile.edging?.wrapperCalloutType || this.settings.mappingWrapperCalloutType,
|
||||
mappingWrapperTitle: this.profile.edging?.wrapperTitle || this.settings.mappingWrapperTitle,
|
||||
mappingWrapperFolded: this.profile.edging?.wrapperFolded !== undefined
|
||||
? this.profile.edging.wrapperFolded
|
||||
: this.settings.mappingWrapperFolded,
|
||||
// Use prompt mode for unmapped links (default behavior)
|
||||
unassignedHandling: "prompt",
|
||||
// Don't overwrite existing mappings unless explicitly allowed
|
||||
allowOverwriteExistingMappings: false,
|
||||
};
|
||||
|
||||
// Run semantic mapping builder
|
||||
const result: BuildResult = await buildSemanticMappings(
|
||||
this.app,
|
||||
this.file,
|
||||
edgingSettings,
|
||||
false, // allowOverwrite: false (respect existing)
|
||||
this.plugin
|
||||
);
|
||||
|
||||
// Show summary notice
|
||||
const summary = [
|
||||
`Edger: Sections updated ${result.sectionsWithMappings}`,
|
||||
`kept ${result.existingMappingsKept}`,
|
||||
`changed ${result.newMappingsAssigned}`,
|
||||
];
|
||||
if (result.unmappedLinksSkipped > 0) {
|
||||
summary.push(`skipped ${result.unmappedLinksSkipped}`);
|
||||
}
|
||||
new Notice(summary.join(", "));
|
||||
} catch (e) {
|
||||
const msg = e instanceof Error ? e.message : String(e);
|
||||
new Notice(`Failed to run semantic mapping builder: ${msg}`);
|
||||
console.error("[Mindnet] Failed to run post-run edging:", e);
|
||||
}
|
||||
}
|
||||
|
||||
goNext(): void {
|
||||
const currentStep = getCurrentStep(this.state);
|
||||
|
||||
|
|
|
|||
|
|
@ -47,7 +47,8 @@ export async function startWizardAfterCreate(
|
|||
content: string,
|
||||
isUnresolvedClick: boolean,
|
||||
onWizardComplete: (result: any) => void,
|
||||
onWizardSave: (result: any) => void
|
||||
onWizardSave: (result: any) => void,
|
||||
pluginInstance?: { ensureGraphSchemaLoaded?: () => Promise<import("../mapping/graphSchema").GraphSchema | null> }
|
||||
): Promise<void> {
|
||||
// Determine if wizard should start
|
||||
const shouldStartInterview = isUnresolvedClick
|
||||
|
|
@ -106,7 +107,9 @@ export async function startWizardAfterCreate(
|
|||
file,
|
||||
content,
|
||||
onWizardComplete,
|
||||
onWizardSave
|
||||
onWizardSave,
|
||||
settings, // Pass settings for post-run edging
|
||||
pluginInstance // Pass plugin instance for graph schema loading
|
||||
).open();
|
||||
} catch (e) {
|
||||
const msg = e instanceof Error ? e.message : String(e);
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user