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(); }) ); // Default notes folder new Setting(containerEl) .setName("Default notes folder") .setDesc("Default folder for new notes (vault-relative path, empty for root). Profile defaults take precedence.") .addText((text) => text .setPlaceholder("") .setValue(this.plugin.settings.defaultNotesFolder) .onChange(async (value) => { this.plugin.settings.defaultNotesFolder = value || ""; await this.plugin.saveSettings(); }) ); } }