From 1f9211cbc5a05b01d2fdf1c49a85f669baa97a84 Mon Sep 17 00:00:00 2001 From: Lars Date: Sat, 17 Jan 2026 07:42:17 +0100 Subject: [PATCH] Add graph schema loading and validation functionality - Introduced methods for loading and reloading graph schema, including debounced reload on file changes. - Enhanced the semantic mapping builder to utilize the new graph schema loading mechanism for improved caching and live updates. - Added a validation button in the settings interface to check the existence and validity of the graph schema file, providing user feedback on the schema status. - Improved error handling for graph schema loading to ensure clear notifications for users in case of issues. --- src/main.ts | 77 +++++++++++++++++++++- src/mapping/semanticMappingBuilder.ts | 21 ++++-- src/schema/GraphSchemaLoader.ts | 30 +++++++++ src/tests/schema/GraphSchemaLoader.test.ts | 15 +++++ src/ui/MindnetSettingTab.ts | 29 ++++++++ 5 files changed, 164 insertions(+), 8 deletions(-) create mode 100644 src/schema/GraphSchemaLoader.ts create mode 100644 src/tests/schema/GraphSchemaLoader.test.ts diff --git a/src/main.ts b/src/main.ts index 0024e90..979cb1c 100644 --- a/src/main.ts +++ b/src/main.ts @@ -20,6 +20,8 @@ import { InterviewWizardModal, type WizardResult } from "./ui/InterviewWizardMod import { extractTargetFromAnchor } from "./interview/extractTargetFromAnchor"; import { buildSemanticMappings } from "./mapping/semanticMappingBuilder"; import { ConfirmOverwriteModal } from "./ui/ConfirmOverwriteModal"; +import { GraphSchemaLoader } from "./schema/GraphSchemaLoader"; +import type { GraphSchema } from "./mapping/graphSchema"; export default class MindnetCausalAssistantPlugin extends Plugin { settings: MindnetSettings; @@ -27,6 +29,8 @@ export default class MindnetCausalAssistantPlugin extends Plugin { private reloadDebounceTimer: number | null = null; private interviewConfig: InterviewConfig | null = null; private interviewConfigReloadDebounceTimer: number | null = null; + private graphSchema: GraphSchema | null = null; + private graphSchemaReloadDebounceTimer: number | null = null; async onload(): Promise { await this.loadSettings(); @@ -127,6 +131,22 @@ export default class MindnetCausalAssistantPlugin extends Plugin { this.interviewConfigReloadDebounceTimer = null; }, 200); } + + // Check if modified file matches graph schema path + const normalizedGraphSchemaPath = normalizeVaultPath(this.settings.graphSchemaPath); + if (normalizedFilePath === normalizedGraphSchemaPath || + normalizedFilePath === `/${normalizedGraphSchemaPath}` || + normalizedFilePath.endsWith(`/${normalizedGraphSchemaPath}`)) { + // Debounce reload + if (this.graphSchemaReloadDebounceTimer !== null) { + window.clearTimeout(this.graphSchemaReloadDebounceTimer); + } + + this.graphSchemaReloadDebounceTimer = window.setTimeout(async () => { + await this.reloadGraphSchema(); + this.graphSchemaReloadDebounceTimer = null; + }, 200); + } }) ); @@ -384,7 +404,8 @@ export default class MindnetCausalAssistantPlugin extends Plugin { this.app, activeFile, this.settings, - allowOverwrite + allowOverwrite, + this ); // Show summary @@ -667,4 +688,58 @@ export default class MindnetCausalAssistantPlugin extends Plugin { console.error(e); } } + + /** + * Ensure graph schema is loaded. Auto-loads if not present. + * Returns GraphSchema instance or null on failure. + */ + public async ensureGraphSchemaLoaded(): Promise { + if (this.graphSchema) { + return this.graphSchema; + } + + try { + this.graphSchema = await GraphSchemaLoader.load( + this.app, + this.settings.graphSchemaPath + ); + + const ruleCount = this.graphSchema.schema.size; + console.log(`Graph schema auto-loaded: ${ruleCount} source types`); + return this.graphSchema; + } catch (e) { + const msg = e instanceof Error ? e.message : String(e); + if (msg.includes("not found") || msg.includes("file not found")) { + console.warn("Graph schema not found. Check the path in plugin settings."); + } else { + console.error(`Failed to load graph schema: ${msg}`); + } + return null; + } + } + + /** + * Reload graph schema from file. Used by manual command and live reload. + */ + public async reloadGraphSchema(): Promise { + try { + this.graphSchema = await GraphSchemaLoader.load( + this.app, + this.settings.graphSchemaPath + ); + + const ruleCount = this.graphSchema.schema.size; + console.log(`Graph schema reloaded: ${ruleCount} source types`); + new Notice(`Graph schema reloaded: ${ruleCount} rules`); + } catch (e) { + const msg = e instanceof Error ? e.message : String(e); + if (msg.includes("not found") || msg.includes("file not found")) { + new Notice("Graph schema not found. Configure path in plugin settings."); + } else { + new Notice(`Failed to reload graph schema: ${msg}`); + } + console.error(e); + this.graphSchema = null; // Clear cache on error + } + } } diff --git a/src/mapping/semanticMappingBuilder.ts b/src/mapping/semanticMappingBuilder.ts index 497cd09..4bc9d2e 100644 --- a/src/mapping/semanticMappingBuilder.ts +++ b/src/mapping/semanticMappingBuilder.ts @@ -39,7 +39,8 @@ export async function buildSemanticMappings( app: App, file: TFile, settings: MindnetSettings, - allowOverwrite: boolean + allowOverwrite: boolean, + plugin?: { ensureGraphSchemaLoaded?: () => Promise } ): Promise { const content = await app.vault.read(file); const lines = content.split(/\r?\n/); @@ -64,12 +65,18 @@ export async function buildSemanticMappings( } // Load graph schema (optional, continue if not found) - try { - const schemaText = await VocabularyLoader.loadText(app, settings.graphSchemaPath); - graphSchema = parseGraphSchema(schemaText); - } catch (e) { - console.warn(`Graph schema not available: ${e instanceof Error ? e.message : String(e)}`); - // Continue without schema + if (plugin && plugin.ensureGraphSchemaLoaded) { + // Use plugin's ensureGraphSchemaLoaded for caching and live reload + graphSchema = await plugin.ensureGraphSchemaLoaded(); + } else { + // Fallback: direct load (no caching) + try { + const schemaText = await VocabularyLoader.loadText(app, settings.graphSchemaPath); + graphSchema = parseGraphSchema(schemaText); + } catch (e) { + console.warn(`Graph schema not available: ${e instanceof Error ? e.message : String(e)}`); + // Continue without schema + } } } diff --git a/src/schema/GraphSchemaLoader.ts b/src/schema/GraphSchemaLoader.ts new file mode 100644 index 0000000..8d81fcd --- /dev/null +++ b/src/schema/GraphSchemaLoader.ts @@ -0,0 +1,30 @@ +/** + * Graph Schema loader with caching. + */ + +import { App, Notice } from "obsidian"; +import { normalizeVaultPath } from "../settings"; +import { parseGraphSchema, type GraphSchema } from "../mapping/graphSchema"; +import { VocabularyLoader } from "../vocab/VocabularyLoader"; + +export class GraphSchemaLoader { + /** + * Load graph schema from vault. + */ + static async load(app: App, vaultRelativePath: string): Promise { + const text = await VocabularyLoader.loadText(app, vaultRelativePath); + return parseGraphSchema(text); + } + + /** + * Validate that graph schema file exists in vault. + */ + static async validate(app: App, vaultRelativePath: string): Promise { + try { + await VocabularyLoader.loadText(app, vaultRelativePath); + return true; + } catch (e) { + return false; + } + } +} diff --git a/src/tests/schema/GraphSchemaLoader.test.ts b/src/tests/schema/GraphSchemaLoader.test.ts new file mode 100644 index 0000000..91c74d2 --- /dev/null +++ b/src/tests/schema/GraphSchemaLoader.test.ts @@ -0,0 +1,15 @@ +/** + * Unit tests for GraphSchemaLoader. + */ + +import { describe, it, expect } from "vitest"; +import { GraphSchemaLoader } from "../../schema/GraphSchemaLoader"; +import { normalizeVaultPath } from "../../settings"; + +describe("GraphSchemaLoader", () => { + it("should normalize vault paths correctly", () => { + expect(normalizeVaultPath("_system/dictionary/graph_schema.md")).toBe("_system/dictionary/graph_schema.md"); + expect(normalizeVaultPath("_system\\dictionary\\graph_schema.md")).toBe("_system/dictionary/graph_schema.md"); + expect(normalizeVaultPath(" _system/dictionary/graph_schema.md ")).toBe("_system/dictionary/graph_schema.md"); + }); +}); diff --git a/src/ui/MindnetSettingTab.ts b/src/ui/MindnetSettingTab.ts index 947c59e..67a74e0 100644 --- a/src/ui/MindnetSettingTab.ts +++ b/src/ui/MindnetSettingTab.ts @@ -64,6 +64,35 @@ export class MindnetSettingTab extends PluginSettingTab { 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