mindnet_obsidian/src/ui/MindnetSettingTab.ts

317 lines
11 KiB
TypeScript
Raw Normal View History

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();
})
)
.addButton((button) =>
button
.setButtonText("Validate")
.setCta()
.onClick(async () => {
try {
const { GraphSchemaLoader } = await import("../schema/GraphSchemaLoader");
const isValid = await GraphSchemaLoader.validate(
this.app,
this.plugin.settings.graphSchemaPath
);
if (isValid) {
const schema = await GraphSchemaLoader.load(
this.app,
this.plugin.settings.graphSchemaPath
);
const ruleCount = schema.schema.size;
new Notice(
`Graph schema file found (${ruleCount} source types)`
);
} else {
new Notice("Graph schema file not found or invalid");
}
} catch (e) {
const msg = e instanceof Error ? e.message : String(e);
new Notice(`Failed to validate graph schema: ${msg}`);
}
})
);
// 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();
}
})
);
// 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();
}
})
);
// 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();
})
);
// 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();
})
);
}
}