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" } ) ;
2026-01-17 10:54:32 +01:00
// ============================================
// 1. Dictionary & Schema Configuration
// ============================================
containerEl . createEl ( "h3" , { text : "📚 Dictionary & Schema" } ) ;
containerEl . createEl ( "p" , {
text : "Konfigurationsdateien für Edge-Vokabular, Graph-Schema und Interview-Profile." ,
cls : "setting-item-description" ,
} ) ;
2026-01-15 11:34:29 +01:00
// Edge vocabulary path
new Setting ( containerEl )
. setName ( "Edge vocabulary path" )
2026-01-17 10:54:32 +01:00
. setDesc (
"Pfad zur Edge-Vokabular-Datei (Markdown). Definiert verfügbare Edge-Typen und deren Beschreibungen. Wird für die semantische Mapping-Erstellung verwendet."
)
2026-01-15 11:34:29 +01:00
. 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" )
2026-01-17 10:54:32 +01:00
. setDesc (
"Pfad zur Graph-Schema-Datei (Markdown). Definiert typische und verbotene Edge-Typen für verschiedene Quelltypen. Wird für Empfehlungen beim Mapping verwendet."
)
2026-01-15 11:34:29 +01:00
. 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 ( ) ;
} )
2026-01-17 07:42:17 +01:00
)
. 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 } ` ) ;
}
} )
2026-01-15 11:34:29 +01:00
) ;
2026-01-17 10:54:32 +01:00
// Interview config path
2026-01-15 11:34:29 +01:00
new Setting ( containerEl )
2026-01-17 10:54:32 +01:00
. setName ( "Interview config path" )
. setDesc (
"Pfad zur Interview-Konfigurationsdatei (YAML). Definiert verfügbare Interview-Profile mit ihren Schritten und Einstellungen."
)
. addText ( ( text ) = >
text
. setPlaceholder ( "_system/dictionary/interview_config.yaml" )
. setValue ( this . plugin . settings . interviewConfigPath )
2026-01-15 11:34:29 +01:00
. onChange ( async ( value ) = > {
2026-01-17 10:54:32 +01:00
this . plugin . settings . interviewConfigPath = value ;
2026-01-15 11:34:29 +01:00
await this . plugin . saveSettings ( ) ;
} )
2026-01-17 10:54:32 +01:00
)
. 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 } ` ) ;
}
} )
2026-01-15 11:34:29 +01:00
) ;
2026-01-18 14:50:17 +01:00
// Chain roles path
new Setting ( containerEl )
. setName ( "Chain roles path" )
. setDesc (
"Pfad zur Chain-Roles-Konfigurationsdatei (YAML). Definiert Rollen für Chain-Intelligence mit ihren Edge-Typen."
)
. addText ( ( text ) = >
text
. setPlaceholder ( "_system/dictionary/chain_roles.yaml" )
. setValue ( this . plugin . settings . chainRolesPath )
. onChange ( async ( value ) = > {
this . plugin . settings . chainRolesPath = value ;
await this . plugin . saveSettings ( ) ;
// Trigger reload if path changes
if ( this . plugin . settings . chainRolesPath ) {
// Reload will happen on next file modify or can be triggered manually
}
} )
)
. addButton ( ( button ) = >
button
. setButtonText ( "Validate" )
. setCta ( )
. onClick ( async ( ) = > {
try {
const { ChainRolesLoader } = await import ( "../dictionary/ChainRolesLoader" ) ;
const result = await ChainRolesLoader . load (
this . app ,
this . plugin . settings . chainRolesPath
) ;
if ( result . errors . length > 0 ) {
new Notice (
` Chain roles loaded with ${ result . errors . length } error(s). Check console. `
) ;
console . warn ( "Chain roles errors:" , result . errors ) ;
} else {
const roleCount = Object . keys ( result . data ? . roles || { } ) . length ;
new Notice ( ` Chain roles file found ( ${ roleCount } role(s)) ` ) ;
}
} catch ( e ) {
const msg = e instanceof Error ? e.message : String ( e ) ;
new Notice ( ` Failed to load chain roles: ${ msg } ` ) ;
}
} )
) ;
// Chain templates path
new Setting ( containerEl )
. setName ( "Chain templates path" )
. setDesc (
"Pfad zur Chain-Templates-Konfigurationsdatei (YAML). Definiert Templates für Chain-Intelligence mit Slots und Constraints."
)
. addText ( ( text ) = >
text
. setPlaceholder ( "_system/dictionary/chain_templates.yaml" )
. setValue ( this . plugin . settings . chainTemplatesPath )
. onChange ( async ( value ) = > {
this . plugin . settings . chainTemplatesPath = value ;
await this . plugin . saveSettings ( ) ;
// Trigger reload if path changes
if ( this . plugin . settings . chainTemplatesPath ) {
// Reload will happen on next file modify or can be triggered manually
}
} )
)
. addButton ( ( button ) = >
button
. setButtonText ( "Validate" )
. setCta ( )
. onClick ( async ( ) = > {
try {
const { ChainTemplatesLoader } = await import ( "../dictionary/ChainTemplatesLoader" ) ;
const result = await ChainTemplatesLoader . load (
this . app ,
this . plugin . settings . chainTemplatesPath
) ;
if ( result . errors . length > 0 ) {
new Notice (
` Chain templates loaded with ${ result . errors . length } error(s). Check console. `
) ;
console . warn ( "Chain templates errors:" , result . errors ) ;
} else {
const templateCount = result . data ? . templates ? . length || 0 ;
new Notice ( ` Chain templates file found ( ${ templateCount } template(s)) ` ) ;
}
} catch ( e ) {
const msg = e instanceof Error ? e.message : String ( e ) ;
new Notice ( ` Failed to load chain templates: ${ msg } ` ) ;
}
} )
) ;
2026-01-18 21:46:20 +01:00
// Template matching profile
new Setting ( containerEl )
. setName ( "Template matching profile" )
. setDesc (
"Profil für Template Matching: 'discovery' (weniger strikt, mehr Findings) oder 'decisioning' (strikter, weniger Findings). Wird aus chain_templates.yaml defaults.profiles geladen."
)
. addDropdown ( ( dropdown ) = >
dropdown
. addOption ( "discovery" , "Discovery" )
. addOption ( "decisioning" , "Decisioning" )
. setValue ( this . plugin . settings . templateMatchingProfile )
. onChange ( async ( value ) = > {
this . plugin . settings . templateMatchingProfile = value as "discovery" | "decisioning" ;
await this . plugin . saveSettings ( ) ;
} )
) ;
2026-01-17 10:54:32 +01:00
// ============================================
// 2. Graph Traversal & Linting
// ============================================
containerEl . createEl ( "h3" , { text : "🔍 Graph Traversal & Linting" } ) ;
containerEl . createEl ( "p" , {
text : "Einstellungen für Graph-Durchquerung und Lint-Validierung." ,
cls : "setting-item-description" ,
} ) ;
// Max hops
2026-01-15 11:34:29 +01:00
new Setting ( containerEl )
. setName ( "Max hops" )
2026-01-17 10:54:32 +01:00
. setDesc (
"Maximale Anzahl von Hops (Schritten) bei der Graph-Durchquerung. Bestimmt, wie tief der Graph durchsucht wird."
)
2026-01-15 11:34:29 +01:00
. 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
2026-01-17 10:54:32 +01:00
// Chain direction
2026-01-15 12:27:16 +01:00
new Setting ( containerEl )
. setName ( "Chain direction" )
2026-01-17 10:54:32 +01:00
. setDesc (
"Richtung für Chain-Traversal: 'Forward' (vorwärts), 'Backward' (rückwärts) oder 'Both' (beide Richtungen)."
)
2026-01-15 12:27:16 +01:00
. 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
2026-01-17 10:54:32 +01:00
// Strict mode
2026-01-16 12:27:44 +01:00
new Setting ( containerEl )
2026-01-17 10:54:32 +01:00
. setName ( "Strict mode" )
. setDesc (
"Aktiviert den strikten Validierungsmodus. Erzwingt strengere Regeln bei der Lint-Validierung."
)
. addToggle ( ( toggle ) = >
toggle
. setValue ( this . plugin . settings . strictMode )
2026-01-16 12:27:44 +01:00
. onChange ( async ( value ) = > {
2026-01-17 10:54:32 +01:00
this . plugin . settings . strictMode = value ;
2026-01-16 12:27:44 +01:00
await this . plugin . saveSettings ( ) ;
} )
2026-01-17 10:54:32 +01:00
) ;
// Show canonical hints
new Setting ( containerEl )
. setName ( "Show canonical hints" )
. setDesc (
"Zeigt INFO-Hinweise mit kanonischer Edge-Typ-Auflösung in den Lint-Ergebnissen an. Hilfreich für die Debugging von Edge-Typ-Zuordnungen."
2026-01-16 12:27:44 +01:00
)
2026-01-17 10:54:32 +01:00
. addToggle ( ( toggle ) = >
toggle
. setValue ( this . plugin . settings . showCanonicalHints )
. onChange ( async ( value ) = > {
this . plugin . settings . showCanonicalHints = value ;
await this . plugin . saveSettings ( ) ;
2026-01-16 12:27:44 +01:00
} )
) ;
2026-01-17 10:54:32 +01:00
// ============================================
// 3. Interview & Note Creation
// ============================================
containerEl . createEl ( "h3" , { text : "📝 Interview & Note Creation" } ) ;
containerEl . createEl ( "p" , {
text : "Einstellungen für Interview-Wizard und Notiz-Erstellung." ,
cls : "setting-item-description" ,
} ) ;
// Auto-start interview on create
2026-01-16 12:27:44 +01:00
new Setting ( containerEl )
. setName ( "Auto-start interview on create" )
2026-01-17 10:54:32 +01:00
. setDesc (
"Startet automatisch den Interview-Wizard, wenn eine neue Notiz über ein Profil erstellt wird."
)
2026-01-16 12:27:44 +01:00
. addToggle ( ( toggle ) = >
toggle
. setValue ( this . plugin . settings . autoStartInterviewOnCreate )
. onChange ( async ( value ) = > {
this . plugin . settings . autoStartInterviewOnCreate = value ;
await this . plugin . saveSettings ( ) ;
} )
) ;
2026-01-17 10:54:32 +01:00
// Default notes folder
new Setting ( containerEl )
. setName ( "Default notes folder" )
. setDesc (
"Standard-Ordner für neue Notizen (vault-relativer Pfad, leer für Root). Profil-spezifische Defaults haben Vorrang."
)
. addText ( ( text ) = >
text
. setPlaceholder ( "" )
. setValue ( this . plugin . settings . defaultNotesFolder )
. onChange ( async ( value ) = > {
this . plugin . settings . defaultNotesFolder = value || "" ;
await this . plugin . saveSettings ( ) ;
} )
) ;
// ============================================
// 4. Unresolved Link Handling
// ============================================
containerEl . createEl ( "h3" , { text : "🔗 Unresolved Link Handling" } ) ;
containerEl . createEl ( "p" , {
text : "Konfiguration für das Verhalten bei Klicks auf nicht aufgelöste Links." ,
cls : "setting-item-description" ,
} ) ;
// Intercept unresolved links
2026-01-16 12:27:44 +01:00
new Setting ( containerEl )
. setName ( "Intercept unresolved link clicks" )
2026-01-17 10:54:32 +01:00
. setDesc (
"Aktiviert die Abfangen von Klicks auf nicht aufgelöste interne Links. Öffnet die Profilauswahl, wenn auf einen nicht existierenden Link geklickt wird."
)
2026-01-16 12:27:44 +01:00
. 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
2026-01-17 10:54:32 +01:00
// Auto-start interview on unresolved click
2026-01-17 10:07:07 +01:00
new Setting ( containerEl )
. setName ( "Auto-start interview on unresolved link click" )
2026-01-17 10:54:32 +01:00
. setDesc (
"Startet automatisch den Interview-Wizard, wenn eine Notiz aus einem nicht aufgelösten Link erstellt wird. Erfordert, dass 'Intercept unresolved link clicks' aktiviert ist."
)
2026-01-17 10:07:07 +01:00
. addToggle ( ( toggle ) = >
toggle
. setValue ( this . plugin . settings . autoStartOnUnresolvedClick )
. onChange ( async ( value ) = > {
this . plugin . settings . autoStartOnUnresolvedClick = value ;
await this . plugin . saveSettings ( ) ;
} )
) ;
// Bypass modifier (Reading View)
new Setting ( containerEl )
. setName ( "Bypass modifier (Reading View)" )
2026-01-17 10:54:32 +01:00
. setDesc (
"Modifier-Taste zum Umgehen des Link-Intercepts in der Leseansicht. Wenn diese Taste gedrückt wird, wird der Standard-Link-Klick ausgeführt (z.B. zum Erstellen einer leeren Notiz)."
)
2026-01-17 10:07:07 +01:00
. addDropdown ( ( dropdown ) = >
dropdown
. addOption ( "Alt" , "Alt" )
. addOption ( "Ctrl" , "Ctrl/Cmd" )
. addOption ( "Shift" , "Shift" )
. addOption ( "None" , "None" )
. setValue ( this . plugin . settings . bypassModifier )
. onChange ( async ( value ) = > {
if ( value === "Alt" || value === "Ctrl" || value === "Shift" || value === "None" ) {
this . plugin . settings . bypassModifier = value ;
await this . plugin . saveSettings ( ) ;
}
} )
) ;
// Editor follow modifier (Live Preview/Source)
new Setting ( containerEl )
. setName ( "Editor follow modifier (Live Preview/Source)" )
2026-01-17 10:54:32 +01:00
. setDesc (
"Modifier-Taste, die im Editor (Live Preview/Source) gedrückt werden muss, um den Link-Intercept zu aktivieren. Standard: Ctrl/Cmd. Verhindert versehentliche Intercepts beim normalen Bearbeiten."
)
2026-01-17 10:07:07 +01:00
. addDropdown ( ( dropdown ) = >
dropdown
. addOption ( "Alt" , "Alt" )
. addOption ( "Ctrl" , "Ctrl/Cmd" )
. addOption ( "Shift" , "Shift" )
. addOption ( "None" , "None" )
. setValue ( this . plugin . settings . editorFollowModifier )
. onChange ( async ( value ) = > {
if ( value === "Alt" || value === "Ctrl" || value === "Shift" || value === "None" ) {
this . plugin . settings . editorFollowModifier = value ;
await this . plugin . saveSettings ( ) ;
}
} )
) ;
2026-01-17 10:54:32 +01:00
// ============================================
// 5. Templater Compatibility
// ============================================
containerEl . createEl ( "h3" , { text : "⚙️ Templater Compatibility" } ) ;
containerEl . createEl ( "p" , {
text : "Einstellungen für die Kompatibilität mit dem Templater-Plugin." ,
cls : "setting-item-description" ,
} ) ;
2026-01-17 10:07:07 +01:00
// Wait for first modify after create
new Setting ( containerEl )
. setName ( "Wait for first modify after create" )
2026-01-17 10:54:32 +01:00
. setDesc (
"Wartet, bis Templater die Datei modifiziert hat, bevor der Wizard gestartet wird. Empfohlen, wenn Templater verwendet wird, um Konflikte zu vermeiden."
)
2026-01-17 10:07:07 +01:00
. addToggle ( ( toggle ) = >
toggle
. setValue ( this . plugin . settings . waitForFirstModifyAfterCreate )
. onChange ( async ( value ) = > {
this . plugin . settings . waitForFirstModifyAfterCreate = value ;
await this . plugin . saveSettings ( ) ;
} )
) ;
// Modify timeout
new Setting ( containerEl )
. setName ( "Modify timeout (ms)" )
2026-01-17 10:54:32 +01:00
. setDesc (
"Timeout in Millisekunden für das Warten auf ein File-Modify-Event. Wenn Templater nicht innerhalb dieser Zeit reagiert, wird der Wizard trotzdem gestartet."
)
2026-01-17 10:07:07 +01:00
. addText ( ( text ) = >
text
. setPlaceholder ( "1200" )
. setValue ( String ( this . plugin . settings . waitForModifyTimeoutMs ) )
. onChange ( async ( value ) = > {
const numValue = parseInt ( value , 10 ) ;
if ( ! isNaN ( numValue ) && numValue > 0 ) {
this . plugin . settings . waitForModifyTimeoutMs = numValue ;
await this . plugin . saveSettings ( ) ;
}
} )
) ;
2026-01-17 10:54:32 +01:00
// ============================================
// 6. Note Adoption
// ============================================
containerEl . createEl ( "h3" , { text : "🔄 Note Adoption" } ) ;
containerEl . createEl ( "p" , {
text : "Einstellungen für die automatische Übernahme neu erstellter Notizen im Editor." ,
cls : "setting-item-description" ,
} ) ;
2026-01-17 10:07:07 +01:00
// Adopt new notes in editor
new Setting ( containerEl )
. setName ( "Adopt new notes in editor" )
2026-01-17 10:54:32 +01:00
. setDesc (
"Übernimmt automatisch neu erstellte Notizen im Editor und konvertiert sie in das Mindnet-Format (mit Frontmatter-ID). Nützlich, wenn Obsidian eine Notiz direkt erstellt, bevor unser Intercept greift."
)
2026-01-17 10:07:07 +01:00
. addToggle ( ( toggle ) = >
toggle
. setValue ( this . plugin . settings . adoptNewNotesInEditor )
. onChange ( async ( value ) = > {
this . plugin . settings . adoptNewNotesInEditor = value ;
await this . plugin . saveSettings ( ) ;
} )
) ;
// Adopt max chars
new Setting ( containerEl )
. setName ( "Adopt max chars" )
2026-01-17 10:54:32 +01:00
. setDesc (
"Maximale Inhaltslänge (in Zeichen), um eine Notiz als Übernahme-Kandidat zu betrachten. Leere oder sehr kurze Notizen werden bevorzugt übernommen."
)
2026-01-17 10:07:07 +01:00
. addText ( ( text ) = >
text
. setPlaceholder ( "200" )
. setValue ( String ( this . plugin . settings . adoptMaxChars ) )
. onChange ( async ( value ) = > {
const numValue = parseInt ( value , 10 ) ;
if ( ! isNaN ( numValue ) && numValue > 0 ) {
this . plugin . settings . adoptMaxChars = numValue ;
await this . plugin . saveSettings ( ) ;
}
} )
) ;
2026-01-17 10:11:30 +01:00
// Adopt confirm mode
new Setting ( containerEl )
. setName ( "Adopt confirm mode" )
2026-01-17 10:54:32 +01:00
. setDesc (
"Wann die Übernahme-Bestätigung angezeigt wird: 'Always ask' (immer fragen), 'Only for low confidence' (nur bei niedriger Konfidenz, überspringt bei hoher Konfidenz), oder 'Never ask' (nie fragen, automatisch übernehmen)."
)
2026-01-17 10:11:30 +01:00
. addDropdown ( ( dropdown ) = >
dropdown
. addOption ( "always" , "Always ask" )
. addOption ( "onlyLowConfidence" , "Only for low confidence" )
. addOption ( "never" , "Never ask" )
. setValue ( this . plugin . settings . adoptConfirmMode )
. onChange ( async ( value ) = > {
if ( value === "always" || value === "onlyLowConfidence" || value === "never" ) {
this . plugin . settings . adoptConfirmMode = value ;
await this . plugin . saveSettings ( ) ;
}
} )
) ;
// High confidence window
new Setting ( containerEl )
. setName ( "High confidence window (ms)" )
2026-01-17 10:54:32 +01:00
. setDesc (
"Zeitfenster in Millisekunden für die hohe Konfidenz-Übernahme. Notizen, die innerhalb dieses Zeitfensters nach einem Klick erstellt wurden, werden mit hoher Konfidenz als Übernahme-Kandidaten betrachtet."
)
2026-01-17 10:11:30 +01:00
. addText ( ( text ) = >
text
. setPlaceholder ( "3000" )
. setValue ( String ( this . plugin . settings . highConfidenceWindowMs ) )
. onChange ( async ( value ) = > {
const numValue = parseInt ( value , 10 ) ;
if ( ! isNaN ( numValue ) && numValue > 0 ) {
this . plugin . settings . highConfidenceWindowMs = numValue ;
await this . plugin . saveSettings ( ) ;
}
} )
) ;
2026-01-17 10:54:32 +01:00
// ============================================
// 7. Semantic Mapping Builder
// ============================================
containerEl . createEl ( "h3" , { text : "🕸️ Semantic Mapping Builder" } ) ;
containerEl . createEl ( "p" , {
text : "Einstellungen für den Semantic Mapping Builder, der automatisch Mapping-Blöcke in Notizen erstellt." ,
cls : "setting-item-description" ,
} ) ;
2026-01-17 07:27:11 +01:00
// Wrapper callout type
new Setting ( containerEl )
. setName ( "Mapping wrapper callout type" )
2026-01-17 10:54:32 +01:00
. setDesc (
"Callout-Typ für den Mapping-Wrapper (z.B. 'abstract', 'info', 'note'). Bestimmt das visuelle Erscheinungsbild des Mapping-Blocks."
)
2026-01-17 07:27:11 +01:00
. 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" )
2026-01-17 10:54:32 +01:00
. setDesc (
"Titel-Text für den Mapping-Wrapper-Callout. Wird als Überschrift des Mapping-Blocks angezeigt."
)
2026-01-17 07:27:11 +01:00
. 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" )
2026-01-17 10:54:32 +01:00
. setDesc (
"Startet mit dem Mapping-Wrapper-Callout eingeklappt (collapsed). Reduziert visuellen Lärm in der Notiz."
)
2026-01-17 07:27:11 +01:00
. addToggle ( ( toggle ) = >
toggle
. setValue ( this . plugin . settings . mappingWrapperFolded )
. onChange ( async ( value ) = > {
this . plugin . settings . mappingWrapperFolded = value ;
await this . plugin . saveSettings ( ) ;
} )
) ;
// Unassigned handling
new Setting ( containerEl )
. setName ( "Unassigned handling" )
2026-01-17 10:54:32 +01:00
. setDesc (
"Wie Links ohne bestehende Mappings behandelt werden: 'Prompt (interactive)' (interaktive Auswahl), 'None (skip unmapped)' (überspringen), 'Use default edge type' (Standard-Typ verwenden), oder 'Use advisor' (zukünftig: E1-Engine)."
)
2026-01-17 07:27:11 +01:00
. 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 ( ) ;
}
} )
) ;
2026-01-17 10:54:32 +01:00
// Default edge type
new Setting ( containerEl )
. setName ( "Default edge type" )
. setDesc (
"Standard-Edge-Typ für nicht zugeordnete Links. Wird nur verwendet, wenn 'Unassigned handling' auf 'Use default edge type' gesetzt ist."
)
. addText ( ( text ) = >
text
. setPlaceholder ( "" )
. setValue ( this . plugin . settings . defaultEdgeType )
. onChange ( async ( value ) = > {
this . plugin . settings . defaultEdgeType = value ;
await this . plugin . saveSettings ( ) ;
} )
) ;
2026-01-17 07:27:11 +01:00
// Allow overwrite existing mappings
new Setting ( containerEl )
. setName ( "Allow overwrite existing mappings" )
2026-01-17 10:54:32 +01:00
. setDesc (
"Wenn aktiviert, wird vor dem Überschreiben bestehender Edge-Typ-Zuordnungen eine Bestätigung angefordert. Wenn deaktiviert, werden bestehende Mappings immer beibehalten."
)
2026-01-17 07:27:11 +01:00
. addToggle ( ( toggle ) = >
toggle
. setValue ( this . plugin . settings . allowOverwriteExistingMappings )
. onChange ( async ( value ) = > {
this . plugin . settings . allowOverwriteExistingMappings = value ;
await this . plugin . saveSettings ( ) ;
} )
) ;
2026-01-17 08:01:04 +01:00
2026-01-17 11:54:14 +01:00
// Inline micro edge suggester
new Setting ( containerEl )
. setName ( "Inline micro edge suggester enabled" )
. setDesc (
"Aktiviert den Inline-Micro-Edge-Suggester. Zeigt nach dem Einfügen eines Links über den Entity Picker sofort eine Edge-Typ-Auswahl an (nur wenn Profil edging.mode=inline_micro)."
)
. addToggle ( ( toggle ) = >
toggle
. setValue ( this . plugin . settings . inlineMicroEnabled )
. onChange ( async ( value ) = > {
this . plugin . settings . inlineMicroEnabled = value ;
await this . plugin . saveSettings ( ) ;
} )
) ;
// Inline max alternatives
new Setting ( containerEl )
. setName ( "Inline max alternatives" )
. setDesc (
"Maximale Anzahl von alternativen Edge-Typen, die im Inline-Micro-Modal angezeigt werden (Standard: 6)."
)
. addText ( ( text ) = >
text
. setPlaceholder ( "6" )
. setValue ( String ( this . plugin . settings . inlineMaxAlternatives ) )
. onChange ( async ( value ) = > {
const numValue = parseInt ( value , 10 ) ;
if ( ! isNaN ( numValue ) && numValue > 0 ) {
this . plugin . settings . inlineMaxAlternatives = numValue ;
await this . plugin . saveSettings ( ) ;
}
} )
) ;
2026-01-17 10:54:32 +01:00
// ============================================
// 8. Debug & Development
// ============================================
containerEl . createEl ( "h3" , { text : "🐛 Debug & Development" } ) ;
containerEl . createEl ( "p" , {
text : "Einstellungen für Debugging und Entwicklung." ,
cls : "setting-item-description" ,
} ) ;
// Debug logging
2026-01-17 08:01:04 +01:00
new Setting ( containerEl )
2026-01-17 10:54:32 +01:00
. setName ( "Debug logging" )
. setDesc (
"Aktiviert ausführliches Debug-Logging für das Unresolved-Link-Handling. Logs erscheinen in der Browser-Konsole (F12). Nützlich für die Fehlersuche."
)
. addToggle ( ( toggle ) = >
toggle
. setValue ( this . plugin . settings . debugLogging )
2026-01-17 08:01:04 +01:00
. onChange ( async ( value ) = > {
2026-01-17 10:54:32 +01:00
this . plugin . settings . debugLogging = value ;
2026-01-17 08:01:04 +01:00
await this . plugin . saveSettings ( ) ;
} )
) ;
2026-01-18 13:45:38 +01:00
// ============================================
// 9. Export
// ============================================
containerEl . createEl ( "h3" , { text : "📤 Export" } ) ;
containerEl . createEl ( "p" , {
text : "Einstellungen für den Graph-Export." ,
cls : "setting-item-description" ,
} ) ;
// Export path
new Setting ( containerEl )
. setName ( "Export path" )
. setDesc (
"Pfad für die exportierte Graph-JSON-Datei. Die Datei enthält alle Knoten (Nodes) und Kanten (Edges) aus dem Vault."
)
. addText ( ( text ) = >
text
. setPlaceholder ( "_system/exports/graph_export.json" )
. setValue ( this . plugin . settings . exportPath )
. onChange ( async ( value ) = > {
this . plugin . settings . exportPath = value || "_system/exports/graph_export.json" ;
await this . plugin . saveSettings ( ) ;
} )
)
. addButton ( ( button ) = >
button
. setButtonText ( "Export now" )
. setCta ( )
. onClick ( async ( ) = > {
try {
// Load vocabulary using the same method as the command
const { VocabularyLoader } = await import ( "../vocab/VocabularyLoader" ) ;
const { parseEdgeVocabulary } = await import ( "../vocab/parseEdgeVocabulary" ) ;
const { Vocabulary } = await import ( "../vocab/Vocabulary" ) ;
const text = await VocabularyLoader . loadText (
this . app ,
this . plugin . settings . edgeVocabularyPath
) ;
const parsed = parseEdgeVocabulary ( text ) ;
const vocabulary = new Vocabulary ( parsed ) ;
const { exportGraph } = await import ( "../export/exportGraph" ) ;
await exportGraph ( this . app , vocabulary , this . plugin . settings . exportPath ) ;
new Notice ( ` Graph exported to ${ this . plugin . settings . exportPath } ` ) ;
console . log ( ` Graph exported: ${ this . plugin . settings . exportPath } ` ) ;
} catch ( e ) {
const msg = e instanceof Error ? e.message : String ( e ) ;
new Notice ( ` Failed to export graph: ${ msg } ` ) ;
console . error ( e ) ;
}
} )
) ;
2026-01-18 21:10:33 +01:00
// ============================================
// 5. Fix Actions Settings
// ============================================
containerEl . createEl ( "h3" , { text : "🔧 Fix Actions" } ) ;
containerEl . createEl ( "p" , {
text : "Einstellungen für automatische Fix-Aktionen bei Chain Inspector Findings." ,
cls : "setting-item-description" ,
} ) ;
// Create missing note mode
new Setting ( containerEl )
. setName ( "Create missing note mode" )
. setDesc (
"Verhalten beim Erstellen fehlender Noten: 'Skeleton only' (nur Frontmatter), 'Create and open profile picker' (mit Profil-Auswahl), 'Create and start wizard' (mit Wizard)."
)
. addDropdown ( ( dropdown ) = >
dropdown
. addOption ( "skeleton_only" , "Skeleton only" )
. addOption ( "create_and_open_profile_picker" , "Create and open profile picker" )
. addOption ( "create_and_start_wizard" , "Create and start wizard" )
. setValue ( this . plugin . settings . fixActions . createMissingNote . mode )
. onChange ( async ( value ) = > {
if (
value === "skeleton_only" ||
value === "create_and_open_profile_picker" ||
value === "create_and_start_wizard"
) {
this . plugin . settings . fixActions . createMissingNote . mode = value ;
await this . plugin . saveSettings ( ) ;
}
} )
) ;
// Default type strategy
new Setting ( containerEl )
. setName ( "Default type strategy" )
. setDesc (
"Strategie für Note-Typ-Zuweisung: 'Profile picker' (immer Picker zeigen), 'Inference then picker' (heuristische Vorauswahl), 'Default concept no prompt' (Standard 'concept' ohne Prompt)."
)
. addDropdown ( ( dropdown ) = >
dropdown
. addOption ( "profile_picker" , "Profile picker" )
. addOption ( "inference_then_picker" , "Inference then picker" )
. addOption ( "default_concept_no_prompt" , "Default concept no prompt" )
. setValue ( this . plugin . settings . fixActions . createMissingNote . defaultTypeStrategy )
. onChange ( async ( value ) = > {
if (
value === "profile_picker" ||
value === "inference_then_picker" ||
value === "default_concept_no_prompt"
) {
this . plugin . settings . fixActions . createMissingNote . defaultTypeStrategy = value ;
await this . plugin . saveSettings ( ) ;
}
} )
) ;
// Include zones
new Setting ( containerEl )
. setName ( "Include zones" )
. setDesc (
"Welche Zonen in neu erstellten Noten einfügen: 'None' (keine), 'Note links only' (nur Note-Verbindungen), 'Candidates only' (nur Kandidaten), 'Both' (beide)."
)
. addDropdown ( ( dropdown ) = >
dropdown
. addOption ( "none" , "None" )
. addOption ( "note_links_only" , "Note links only" )
. addOption ( "candidates_only" , "Candidates only" )
. addOption ( "both" , "Both" )
. setValue ( this . plugin . settings . fixActions . createMissingNote . includeZones )
. onChange ( async ( value ) = > {
if (
value === "none" ||
value === "note_links_only" ||
value === "candidates_only" ||
value === "both"
) {
this . plugin . settings . fixActions . createMissingNote . includeZones = value ;
await this . plugin . saveSettings ( ) ;
}
} )
) ;
// Create missing heading level
new Setting ( containerEl )
. setName ( "Create missing heading level" )
. setDesc (
"Heading-Level für neu erstellte Headings (1-6). Standard: 2 (H2)."
)
. addText ( ( text ) = >
text
. setPlaceholder ( "2" )
. setValue ( String ( this . plugin . settings . fixActions . createMissingHeading . level ) )
. onChange ( async ( value ) = > {
const numValue = parseInt ( value , 10 ) ;
if ( ! isNaN ( numValue ) && numValue >= 1 && numValue <= 6 ) {
this . plugin . settings . fixActions . createMissingHeading . level = numValue ;
await this . plugin . saveSettings ( ) ;
}
} )
) ;
// Promote candidate keep original
new Setting ( containerEl )
. setName ( "Promote candidate: Keep original" )
. setDesc (
"Wenn aktiviert, bleibt das ursprüngliche Candidate-Edge im Kandidaten-Bereich erhalten, wenn es zu einem expliziten Edge befördert wird."
)
. addToggle ( ( toggle ) = >
toggle
. setValue ( this . plugin . settings . fixActions . promoteCandidate . keepOriginal )
. onChange ( async ( value ) = > {
this . plugin . settings . fixActions . promoteCandidate . keepOriginal = value ;
await this . plugin . saveSettings ( ) ;
} )
) ;
2026-01-15 11:34:29 +01:00
}
}