2026-01-15 11:34:29 +01:00
|
|
|
import { App, Notice, PluginSettingTab, Setting } from "obsidian";
|
|
|
|
|
import type MindnetCausalAssistantPlugin from "../main";
|
|
|
|
|
import { VocabularyLoader } from "../vocab/VocabularyLoader";
|
|
|
|
|
|
|
|
|
|
export class MindnetSettingTab extends PluginSettingTab {
|
|
|
|
|
plugin: MindnetCausalAssistantPlugin;
|
|
|
|
|
|
|
|
|
|
constructor(app: App, plugin: MindnetCausalAssistantPlugin) {
|
|
|
|
|
super(app, plugin);
|
|
|
|
|
this.plugin = plugin;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
display(): void {
|
|
|
|
|
const { containerEl } = this;
|
|
|
|
|
|
|
|
|
|
containerEl.empty();
|
|
|
|
|
|
|
|
|
|
containerEl.createEl("h2", { text: "Mindnet Settings" });
|
|
|
|
|
|
|
|
|
|
// Edge vocabulary path
|
|
|
|
|
new Setting(containerEl)
|
|
|
|
|
.setName("Edge vocabulary path")
|
|
|
|
|
.setDesc("Vault-relative path to the edge vocabulary markdown file")
|
|
|
|
|
.addText((text) =>
|
|
|
|
|
text
|
|
|
|
|
.setPlaceholder("_system/dictionary/edge_vocabulary.md")
|
|
|
|
|
.setValue(this.plugin.settings.edgeVocabularyPath)
|
|
|
|
|
.onChange(async (value) => {
|
|
|
|
|
this.plugin.settings.edgeVocabularyPath = value;
|
|
|
|
|
await this.plugin.saveSettings();
|
|
|
|
|
})
|
|
|
|
|
)
|
|
|
|
|
.addButton((button) =>
|
|
|
|
|
button
|
|
|
|
|
.setButtonText("Validate")
|
|
|
|
|
.setCta()
|
|
|
|
|
.onClick(async () => {
|
|
|
|
|
try {
|
|
|
|
|
const text = await VocabularyLoader.loadText(
|
|
|
|
|
this.app,
|
|
|
|
|
this.plugin.settings.edgeVocabularyPath
|
|
|
|
|
);
|
|
|
|
|
new Notice(
|
|
|
|
|
`Edge vocabulary file found (${text.length} characters)`
|
|
|
|
|
);
|
|
|
|
|
} catch (e) {
|
|
|
|
|
const msg = e instanceof Error ? e.message : String(e);
|
|
|
|
|
new Notice(
|
|
|
|
|
`Failed to load edge vocabulary: ${msg}`
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// Graph schema path
|
|
|
|
|
new Setting(containerEl)
|
|
|
|
|
.setName("Graph schema path")
|
|
|
|
|
.setDesc("Vault-relative path to the graph schema markdown file")
|
|
|
|
|
.addText((text) =>
|
|
|
|
|
text
|
|
|
|
|
.setPlaceholder("_system/dictionary/graph_schema.md")
|
|
|
|
|
.setValue(this.plugin.settings.graphSchemaPath)
|
|
|
|
|
.onChange(async (value) => {
|
|
|
|
|
this.plugin.settings.graphSchemaPath = value;
|
|
|
|
|
await this.plugin.saveSettings();
|
|
|
|
|
})
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// Strict mode toggle
|
|
|
|
|
new Setting(containerEl)
|
|
|
|
|
.setName("Strict mode")
|
|
|
|
|
.setDesc("Enable strict validation mode")
|
|
|
|
|
.addToggle((toggle) =>
|
|
|
|
|
toggle
|
|
|
|
|
.setValue(this.plugin.settings.strictMode)
|
|
|
|
|
.onChange(async (value) => {
|
|
|
|
|
this.plugin.settings.strictMode = value;
|
|
|
|
|
await this.plugin.saveSettings();
|
|
|
|
|
})
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// Max hops number input
|
|
|
|
|
new Setting(containerEl)
|
|
|
|
|
.setName("Max hops")
|
|
|
|
|
.setDesc("Maximum number of hops for graph traversal")
|
|
|
|
|
.addText((text) =>
|
|
|
|
|
text
|
|
|
|
|
.setPlaceholder("3")
|
|
|
|
|
.setValue(String(this.plugin.settings.maxHops))
|
|
|
|
|
.onChange(async (value) => {
|
|
|
|
|
const numValue = parseInt(value, 10);
|
|
|
|
|
if (!isNaN(numValue) && numValue > 0) {
|
|
|
|
|
this.plugin.settings.maxHops = numValue;
|
|
|
|
|
await this.plugin.saveSettings();
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
);
|
2026-01-15 12:27:16 +01:00
|
|
|
|
|
|
|
|
// Show canonical hints toggle
|
|
|
|
|
new Setting(containerEl)
|
|
|
|
|
.setName("Show canonical hints")
|
|
|
|
|
.setDesc("Show INFO findings with canonical edge type resolution in lint results")
|
|
|
|
|
.addToggle((toggle) =>
|
|
|
|
|
toggle
|
|
|
|
|
.setValue(this.plugin.settings.showCanonicalHints)
|
|
|
|
|
.onChange(async (value) => {
|
|
|
|
|
this.plugin.settings.showCanonicalHints = value;
|
|
|
|
|
await this.plugin.saveSettings();
|
|
|
|
|
})
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// Chain direction dropdown
|
|
|
|
|
new Setting(containerEl)
|
|
|
|
|
.setName("Chain direction")
|
|
|
|
|
.setDesc("Direction for chain traversal: forward, backward, or both")
|
|
|
|
|
.addDropdown((dropdown) =>
|
|
|
|
|
dropdown
|
|
|
|
|
.addOption("forward", "Forward")
|
|
|
|
|
.addOption("backward", "Backward")
|
|
|
|
|
.addOption("both", "Both")
|
|
|
|
|
.setValue(this.plugin.settings.chainDirection)
|
|
|
|
|
.onChange(async (value) => {
|
|
|
|
|
if (value === "forward" || value === "backward" || value === "both") {
|
|
|
|
|
this.plugin.settings.chainDirection = value;
|
|
|
|
|
await this.plugin.saveSettings();
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
);
|
2026-01-16 12:27:44 +01:00
|
|
|
|
|
|
|
|
// Interview config path
|
|
|
|
|
new Setting(containerEl)
|
|
|
|
|
.setName("Interview config path")
|
|
|
|
|
.setDesc("Vault-relative path to the interview config YAML file")
|
|
|
|
|
.addText((text) =>
|
|
|
|
|
text
|
|
|
|
|
.setPlaceholder("_system/dictionary/interview_config.yaml")
|
|
|
|
|
.setValue(this.plugin.settings.interviewConfigPath)
|
|
|
|
|
.onChange(async (value) => {
|
|
|
|
|
this.plugin.settings.interviewConfigPath = value;
|
|
|
|
|
await this.plugin.saveSettings();
|
|
|
|
|
})
|
|
|
|
|
)
|
|
|
|
|
.addButton((button) =>
|
|
|
|
|
button
|
|
|
|
|
.setButtonText("Validate")
|
|
|
|
|
.setCta()
|
|
|
|
|
.onClick(async () => {
|
|
|
|
|
try {
|
|
|
|
|
const { InterviewConfigLoader } = await import("../interview/InterviewConfigLoader");
|
|
|
|
|
const result = await InterviewConfigLoader.loadConfig(
|
|
|
|
|
this.app,
|
|
|
|
|
this.plugin.settings.interviewConfigPath
|
|
|
|
|
);
|
|
|
|
|
if (result.errors.length > 0) {
|
|
|
|
|
new Notice(
|
|
|
|
|
`Interview config loaded with ${result.errors.length} error(s). Check console.`
|
|
|
|
|
);
|
|
|
|
|
console.warn("Interview config errors:", result.errors);
|
|
|
|
|
} else {
|
|
|
|
|
new Notice(
|
|
|
|
|
`Interview config file found (${result.config.profiles.length} profile(s))`
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
} catch (e) {
|
|
|
|
|
const msg = e instanceof Error ? e.message : String(e);
|
|
|
|
|
new Notice(`Failed to load interview config: ${msg}`);
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// Auto-start interview toggle
|
|
|
|
|
new Setting(containerEl)
|
|
|
|
|
.setName("Auto-start interview on create")
|
|
|
|
|
.setDesc("Automatically start interview wizard when creating a note from profile")
|
|
|
|
|
.addToggle((toggle) =>
|
|
|
|
|
toggle
|
|
|
|
|
.setValue(this.plugin.settings.autoStartInterviewOnCreate)
|
|
|
|
|
.onChange(async (value) => {
|
|
|
|
|
this.plugin.settings.autoStartInterviewOnCreate = value;
|
|
|
|
|
await this.plugin.saveSettings();
|
|
|
|
|
})
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// Intercept unresolved links toggle
|
|
|
|
|
new Setting(containerEl)
|
|
|
|
|
.setName("Intercept unresolved link clicks")
|
|
|
|
|
.setDesc("Open profile selection when clicking unresolved internal links")
|
|
|
|
|
.addToggle((toggle) =>
|
|
|
|
|
toggle
|
|
|
|
|
.setValue(this.plugin.settings.interceptUnresolvedLinkClicks)
|
|
|
|
|
.onChange(async (value) => {
|
|
|
|
|
this.plugin.settings.interceptUnresolvedLinkClicks = value;
|
|
|
|
|
await this.plugin.saveSettings();
|
|
|
|
|
})
|
|
|
|
|
);
|
2026-01-17 07:27:11 +01:00
|
|
|
|
|
|
|
|
// Semantic Mapping Builder section
|
|
|
|
|
containerEl.createEl("h2", { text: "Semantic Mapping Builder" });
|
|
|
|
|
|
|
|
|
|
// Wrapper callout type
|
|
|
|
|
new Setting(containerEl)
|
|
|
|
|
.setName("Mapping wrapper callout type")
|
|
|
|
|
.setDesc("Callout type for the mapping wrapper (e.g., 'abstract', 'info', 'note')")
|
|
|
|
|
.addText((text) =>
|
|
|
|
|
text
|
|
|
|
|
.setPlaceholder("abstract")
|
|
|
|
|
.setValue(this.plugin.settings.mappingWrapperCalloutType)
|
|
|
|
|
.onChange(async (value) => {
|
|
|
|
|
this.plugin.settings.mappingWrapperCalloutType = value || "abstract";
|
|
|
|
|
await this.plugin.saveSettings();
|
|
|
|
|
})
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// Wrapper title
|
|
|
|
|
new Setting(containerEl)
|
|
|
|
|
.setName("Mapping wrapper title")
|
|
|
|
|
.setDesc("Title text for the mapping wrapper callout")
|
|
|
|
|
.addText((text) =>
|
|
|
|
|
text
|
|
|
|
|
.setPlaceholder("🕸️ Semantic Mapping")
|
|
|
|
|
.setValue(this.plugin.settings.mappingWrapperTitle)
|
|
|
|
|
.onChange(async (value) => {
|
|
|
|
|
this.plugin.settings.mappingWrapperTitle = value || "🕸️ Semantic Mapping";
|
|
|
|
|
await this.plugin.saveSettings();
|
|
|
|
|
})
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// Wrapper folded
|
|
|
|
|
new Setting(containerEl)
|
|
|
|
|
.setName("Mapping wrapper folded")
|
|
|
|
|
.setDesc("Start with mapping wrapper callout folded (collapsed)")
|
|
|
|
|
.addToggle((toggle) =>
|
|
|
|
|
toggle
|
|
|
|
|
.setValue(this.plugin.settings.mappingWrapperFolded)
|
|
|
|
|
.onChange(async (value) => {
|
|
|
|
|
this.plugin.settings.mappingWrapperFolded = value;
|
|
|
|
|
await this.plugin.saveSettings();
|
|
|
|
|
})
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// Default edge type
|
|
|
|
|
new Setting(containerEl)
|
|
|
|
|
.setName("Default edge type")
|
|
|
|
|
.setDesc("Default edge type for unmapped links (if unassignedHandling is 'defaultType')")
|
|
|
|
|
.addText((text) =>
|
|
|
|
|
text
|
|
|
|
|
.setPlaceholder("")
|
|
|
|
|
.setValue(this.plugin.settings.defaultEdgeType)
|
|
|
|
|
.onChange(async (value) => {
|
|
|
|
|
this.plugin.settings.defaultEdgeType = value;
|
|
|
|
|
await this.plugin.saveSettings();
|
|
|
|
|
})
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// Unassigned handling
|
|
|
|
|
new Setting(containerEl)
|
|
|
|
|
.setName("Unassigned handling")
|
|
|
|
|
.setDesc("How to handle links without existing mappings: prompt (interactive), none, defaultType, or advisor")
|
|
|
|
|
.addDropdown((dropdown) =>
|
|
|
|
|
dropdown
|
|
|
|
|
.addOption("prompt", "Prompt (interactive)")
|
|
|
|
|
.addOption("none", "None (skip unmapped)")
|
|
|
|
|
.addOption("defaultType", "Use default edge type")
|
|
|
|
|
.addOption("advisor", "Use advisor (future: E1 engine)")
|
|
|
|
|
.setValue(this.plugin.settings.unassignedHandling)
|
|
|
|
|
.onChange(async (value) => {
|
|
|
|
|
if (value === "prompt" || value === "none" || value === "defaultType" || value === "advisor") {
|
|
|
|
|
this.plugin.settings.unassignedHandling = value;
|
|
|
|
|
await this.plugin.saveSettings();
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// Allow overwrite existing mappings
|
|
|
|
|
new Setting(containerEl)
|
|
|
|
|
.setName("Allow overwrite existing mappings")
|
|
|
|
|
.setDesc("If enabled, will prompt to confirm before overwriting existing edge type assignments")
|
|
|
|
|
.addToggle((toggle) =>
|
|
|
|
|
toggle
|
|
|
|
|
.setValue(this.plugin.settings.allowOverwriteExistingMappings)
|
|
|
|
|
.onChange(async (value) => {
|
|
|
|
|
this.plugin.settings.allowOverwriteExistingMappings = value;
|
|
|
|
|
await this.plugin.saveSettings();
|
|
|
|
|
})
|
|
|
|
|
);
|
2026-01-15 11:34:29 +01:00
|
|
|
}
|
|
|
|
|
}
|