AI visual tagger reimagined (plantUML diagram generation with Copilot and more)

AI visual tagger reimagined

I am a lazy tagger. I don’t want to be put on the spot having to come with a tag for something. Especially when I know I’m intuitive enough to vary them. And that’s exactly the problem with the following approach as well:

If we allow AI to tag notes: can it be configured somehow to force it to stick to a pre-defined set…? How to be congruent around out tags…?

Wait, this write-up is not even about tags…

It’s about making plantUML diagrams and generating nodes as properties that we can query.

The problem remains…how can we make the same nodes appear on various content?

I mean it’s completely futile to do all this manual work when we can simple use content search…there’s no point doing this…?

Well, actually, this is why I was hesitant to put this up in the first place…

But there can be takeaways still:

  • Nice diagrams in your notes, for free
  • You can use the AI generated node content to inspire you to add your own tags or rename the nodes
  • You are reviewing your content and looking at the diagram, you may…get something out of it…?

I don’t know…
As I said to the person asking for my setup, my main motivation was not the queries that I’d make afterwards, but the actual diagrams…

Anyway, here goes…


Plugins needed

  • Copilot
  • plantUML
  • Templater
  • Datacore (to be installed with BRAT, which you need to install beforehand)

Optional:

  • MetaEdit

Workflow

Simple enough:
We generate a diagram based on note content with a prompt (you’ll need to tweak, or translate to your own language, etc., then we cut the diagram to be able to put the nodes in the frontmatter of the file.

Copilot plantUML diagram generation

First of all, you need a prompt for Copilot:

Based on the content of the {activeNote}, you will create a PlantUML diagram using a **predefined PKM (Personal Knowledge Management) dictionary** to ensure generic and reusable node names.

**PKM Dictionary:**  
```
pkm_generic_terms = {
  "concept": ["idea", "principle", "theory", "notion", "understanding"],
  "source": ["book", "article", "paper", "website", "podcast", "video", "document"],
  "project": ["goal", "task", "objective", "initiative", "endeavor"],
  "person": ["author", "researcher", "expert", "contact", "individual"],
  "event": ["meeting", "conference", "workshop", "presentation", "session"],
  "resource": ["tool", "software", "method", "technique", "instrument"],
  "question": ["query", "problem", "issue", "challenge", "inquiry"],
  "principle": ["rule", "guideline", "law", "tenet", "maxim"],
  "process": ["workflow", "method", "procedure", "system", "approach"],
  "connection": ["link", "relationship", "association", "interaction", "relation"],
  "collection": ["group", "set", "category", "list", "compilation"],
  "analysis": ["evaluation", "interpretation", "examination", "assessment"],
  "creation": ["generation", "production", "development", "making"],
  "organization": ["structure", "arrangement", "classification", "order"],
  "learning": ["acquisition", "understanding", "study", "knowledge gain"],
  "application": ["use", "implementation", "practice", "employment"],
  "attribute": ["property", "characteristic", "feature", "aspect", "quality"],
  "context": ["setting", "environment", "situation", "background", "circumstance"],
  "evidence": ["proof", "data", "support", "justification", "validation"],
  "example": ["instance", "illustration", "case", "sample", "demonstration"],
  "summary": ["overview", "abstract", "synopsis", "digest", "recap"]
}
```

**Diagram Creation Process:**

1.  **Concept Identification:** Thoroughly read the content of the {activeNote} and identify the main idea and related concepts.
2.  **Head Element:** Designate the main idea as the 'head' (parent) element, represented as a rectangle in the PlantUML diagram.
3.  **Child Elements (Cards):** Connect related ideas as 'children' elements, represented as cards.
4.  **Generic Node Names:** For each identified concept (for both head and children elements), map it to the **most appropriate generic term from the PKM Dictionary above**. Use this generic term as the node name in your PlantUML diagram. For example, if a concept in the note is about a "research paper," map it to the generic term `"source"` from the dictionary and use "source" as the node name. If the main idea is about "understanding a concept," map it to `"concept"` and use "concept" as the head node name.
5.  **Relationship Labels:** Use short labels to describe the relationships between elements, indicating the type of connection where logically inferable from the content.
6.  **Hierarchical Structure:** Children elements can also have siblings or children if logically derivable from the content, creating a hierarchical diagram.

**PlantUML Diagram Styles:**

-   Always generate `plantuml-svg` diagrams.
-   Use `left to right direction` if there are not too many elements, otherwise use `top to bottom direction`.
-   Set `skinparam BackGroundColor transparent` for a transparent background.
-   Set the rectangle color to `#ccbe78` using `BackgroundColor #ccbe78`.
-   Set the card color to `#f9f5d7` using `BackgroundColor #f9f5d7`.
-   If there are too many elements, adjust layout using `skinparam nodesep` and `skinparam ranksep`.

**Handling Note Structure and Complexity:**

-   **Multiple Heading 1 Sections:** If there are multiple Heading 1 titles, treat each section as a separate context, but attempt to integrate everything into a single diagram if logically feasible.
-   **Relationship Definitions:** When defining relationships, use `--` for connections without arrowheads.
-   **Identifier Naming:** Use Latin alphabet characters for identifiers after `as` (e.g., `concept_node as concept`) and for back-references to ensure proper diagram generation.
-   **Diagram Size Limitation:** In longer notes, prioritize key concepts for the diagram if there are many ideas, aiming for a diagram with around 10-12 elements maximum to avoid excessive complexity. Prioritize concepts based on their frequency or importance in the text.

**Example Templates for Guidance:**  (Keep these example templates as they are helpful)

````
```plantuml-svg
@startuml
left to right direction
skinparam BackGroundColor transparent
skinparam rectangle {
    BackgroundColor #ccbe78
}
skinparam card {
    roundCorner 15
    BackgroundColor #f9f5d7
}

rectangle "<b>MAIN IDEA</b>" as head
card Idea1 as idea1
card Idea2 as idea2
card Idea3 as idea3
card Idea4 as idea4
card Idea5 as idea5
card Idea6 as idea6

head -up-> idea1
idea1 -right-> idea2 : Label with explanation
idea2 -right-> idea3
idea3 --> idea4 : Longer label with explanation\nusing line break
idea4 -right-> idea5
head ----> idea6
@enduml
```
````

````
```plantuml-svg
@startuml
left to right direction
skinparam BackGroundColor transparent
skinparam rectangle {
    BackgroundColor #ccbe78
}
skinparam card {
    roundCorner 15
    BackgroundColor #f9f5d7
}

rectangle "<b>Main Concept</b>" as head
rectangle "Secondary Main Concept" as secondary_head
card Idea1 as idea1
card Idea2 as idea2
card Idea3 as idea3
card Idea4 as idea4

head --> idea1 : Type of relation
idea1 --> secondary_head
secondary_head ---> idea2 : Relation detail 1
secondary_head ---> idea3 : Relation detail 2
secondary_head ---> idea4 : Relation detail 3
@enduml
```
````

**Important Considerations:**

-   **Dictionary Usage is Key:**  Always use the provided PKM Dictionary to map concepts to generic node names. This ensures consistency and reusability across diagrams.
-   **Relationship Basis:** Ensure relationships are created based on concepts within the content and not based on Wikilinks.
-   **Output Format:** Always use `plantuml-svg` and enclose the code block with four backticks.
-   **Diagram Type:** Avoid sequence diagrams or any other types; stick to the described guidelines/templates.
-   **Confidence and Accuracy:** Create only confident relationships, remembering "less is more". Do not misrepresent the content's intent.
  • I tried to create a PKM-sy prompt, but I’m not sure this is the one. You’ll need to tweak it.
  • Colour codes ccbe78 and f9f5d7 need to be changed to go well with your theme.

Former prompt with no PKM in mind:

Based on the content of the {activeNote}, you will create a PlantUML diagram.

After thoroughly reading the content, identify the main idea as the 'head' (parent) element, represented as a rectangle. Connect related ideas to it as 'children' elements, represented as cards.  Use short labels to describe the relationships, indicating the type of connection where it's logically inferable from the content. Children can also have siblings or children if logically derivable from the content.

When organizing relationships, avoid using ellipses or any non-standard PlantUML syntax.

Use the following PlantUML styles:
- Always generate `plantuml-svg` diagrams.
- Use `left to right direction` if there are not too many elements, otherwise use `top to bottom direction`.
- Set `skinparam BackGroundColor transparent` for a transparent background.
- Set the rectangle color to `#ccbe78` using `BackgroundColor #ccbe78`.
- Set the card color to `#f9f5d7` using `BackgroundColor #f9f5d7`.
- If there are too many elements, adjust layout using `skinparam nodesep` and `skinparam ranksep`.

If there are multiple Heading 1 titles in a note, treat each section under a Heading 1 as a separate context, but attempt to integrate everything into a single diagram if logically feasible.

When defining relationships at the bottom of the diagram, use `--` for connections without arrowheads.

Use Latin alphabet characters for identifiers after `as` and for back-references to ensure proper diagram generation.

In longer notes, consider prioritizing key concepts for the diagram if there are many ideas, aiming for a diagram with around 10-12 elements maximum to avoid excessive complexity. Prioritize concepts based on their frequency or importance in the text.

Here are example templates for guidance:  
````
```plantuml-svg
@startuml
left to right direction
skinparam BackGroundColor transparent
skinparam rectangle {
    BackgroundColor #ccbe78
}
skinparam card {
    roundCorner 15
    BackgroundColor #f9f5d7
}

rectangle "<b>MAIN IDEA</b>" as head
card Idea1 as idea1
card Idea2 as idea2
card Idea3 as idea3
card Idea4 as idea4
card Idea5 as idea5
card Idea6 as idea6

head -up-> idea1
idea1 -right-> idea2 : Label with explanation
idea2 -right-> idea3
idea3 --> idea4 : Longer label with explanation\nusing line break
idea4 -right-> idea5
head ----> idea6
@enduml
```
````

````
```plantuml-svg
@startuml
left to right direction
skinparam BackGroundColor transparent
skinparam rectangle {
    BackgroundColor #ccbe78
}
skinparam card {
    roundCorner 15
    BackgroundColor #f9f5d7
}

rectangle "<b>Main Concept</b>" as head
rectangle "Secondary Main Concept" as secondary_head
card Idea1 as idea1
card Idea2 as idea2
card Idea3 as idea3
card Idea4 as idea4

head --> idea1 : Type of relation
idea1 --> secondary_head
secondary_head ---> idea2 : Relation detail 1
secondary_head ---> idea3 : Relation detail 2
secondary_head ---> idea4 : Relation detail 3
@enduml
```
````

- Ensure relationships are created based on concepts within the content and not based on Wikilinks.   
- Always use `plantuml-svg` and enclose the code block with four backticks.  
- Avoid sequence diagrams or any other types, and stick to the described guidelines/templates.  
- Create only confident relationships, remembering "less is more".  
- Do not misrepresent the content's intent.

You call the prompt when the note is loaded in your main editor tab group and Copilot in your sidebar. You need to configure the plugin first.
You can use this guide of mine to help you with that.
Use the same Google Gemini Flash Thinking model as talked about there.

Adding node values to the frontmatter

Add the created diagram to your note and cut the diagram with the backticked fence and all.
Here you need to do something first.
Save this script named ‘Paste Army Knife’ or something:

<%*
// CONFIG - Customize these settings as needed
const CONFIG = {
    updateDateModified: true, // Set to false if you don't want to update the date modified field
    dateModifiedKey: 'date_modified', // Change this to your preferred frontmatter key for date modified
    plantUMLNodesKey: 'plantuml_nodes', // Change this to your preferred frontmatter key for PlantUML nodes
    // Add excluded words configuration
    excludedWords: new Set([
        'a', 'an', 'and', 'as', 'at', 'by', 'for', 'from', 'in',
        'of', 'on', 'or', 'the', 'to', 'with', 'are', 'is', 'be'
    ])
};
let clipboard = await tp.system.clipboard();
// Function to extract nodes from plantuml-svg code block
function extractPlantUMLNodes(clipboard) {
    let nodes = [];
    const pattern = /(?:card|rectangle)\s+(?:"([^"]+)"|([^"\s]+))(?:\s+as\s+\w+|\s*{)?/gm;
    
    function cleanAndAddNode(text) {
        // Keep phrases together and clean them as a unit
        text = text.toLowerCase()
            .replace(/['"]/g, '')
            .replace(/[()]/g, '')
            .replace(/\\n/g, ' ')
            .replace(/:\s*/, ' ')
            .replace(/,+$/, '')
            .replace(/\s+/g, ' ')
            .trim();
            
        // Don't add empty, single-character, duplicate nodes, or excluded words
        if (text && 
            text.length > 1 && 
            !nodes.includes(text) && 
            !CONFIG.excludedWords.has(text)) {
            nodes.push(text);
        }
    }

    function processContent(content) {
        // Clean up the content first
        content = content
            .replace(/<b>(.*?)<\/b>/gi, '$1')
            .replace(/\*\*(.*?)\*\*/g, '$1')
            .trim();

        // Split by both newlines and commas
        const lines = content.split(/\\n/).map(line => line.trim()).filter(Boolean);
        
        lines.forEach(line => {
            // Handle each line
            if (line.includes(':')) {
                // Split label and content
                const [label, ...rest] = line.split(':');
                if (label) cleanAndAddNode(label);
                
                const content = rest.join(':').trim();
                if (content) {
                    if (content.includes(',')) {
                        // Split by commas and preserve phrases
                        content.split(/,\s*/)
                            .filter(Boolean)
                            .forEach(cleanAndAddNode);
                    } else {
                        cleanAndAddNode(content);
                    }
                }
            } else if (line.includes('(')) {
                // Handle bracketed content
                const mainPart = line.split('(')[0].trim();
                if (mainPart) cleanAndAddNode(mainPart);
                
                const bracketMatch = line.match(/\((.*?)\)/);
                if (bracketMatch) {
                    // Process bracketed content preserving phrases
                    bracketMatch[1].split(/,\s*/)
                        .map(item => item
                            .replace(/\\n/g, ' ')
                            .trim())
                        .filter(Boolean)
                        .forEach(cleanAndAddNode);
                }
            } else {
                // Keep complete line as one phrase
                cleanAndAddNode(line);
            }
        });
    }

    let match;
    while ((match = pattern.exec(clipboard)) !== null) {
        const content = match[1] || match[2];
        if (content) processContent(content);
    }

    return [...new Set(nodes)];
}
// Function to merge new nodes into YAML frontmatter
async function addNodesToFrontMatter(nodes) {
    await app.fileManager.processFrontMatter(tp.config.target_file, frontmatter => {
        // Handle PlantUML nodes
        if (!frontmatter[CONFIG.plantUMLNodesKey]) {
            frontmatter[CONFIG.plantUMLNodesKey] = []; // Initialize if missing
        }
        nodes.forEach(node => {
            // Force the node value to lowercase
            const lowercasedNode = node.toLowerCase();
            if (!frontmatter[CONFIG.plantUMLNodesKey].includes(lowercasedNode)) {
                frontmatter[CONFIG.plantUMLNodesKey].push(lowercasedNode);
            }
        });
        // Update the modified date if configured to do so
        if (CONFIG.updateDateModified) {
            const modDateTime = tp.date.now("YYYY-MM-DDTHH:mm");
            frontmatter[CONFIG.dateModifiedKey] = modDateTime;
        }
    });
}

// Separate YAML update and content paste
if (/```plantuml-svg\n[\s\S]+?\n```/gm.test(clipboard)) {
    const diagramBlock = clipboard.match(/```plantuml-svg\n[\s\S]+?\n```/gm)?.[0] || clipboard;
    
    // First, update the YAML
    const nodes = extractPlantUMLNodes(diagramBlock);
    await addNodesToFrontMatter(nodes);
    
    // Then, after a small delay to ensure YAML is updated, add the content
    await new Promise(resolve => setTimeout(resolve, 100));
    
    // Clean up the diagram formatting
    const cleanDiagram = diagramBlock.replace(/\n{3,4}/g, '\n\n');
    tR += cleanDiagram;
} else if (/(?:https?:\/\/|www\.|ftp:|obsidian:|zotero:|file:)[\w\-\.~:/?#[\]@!$&'()*+,;=]+/gi.test(clipboard)) {
    clipboard = clipboard.replace(
        /(?:https?:\/\/|www\.|ftp:|obsidian:|zotero:|file:)[\w\-\.~:/?#[\]@!$&'()*+,;=]+/gi,
        match => {
            let url = match;
            if (match.startsWith('www.')) {
                url = 'https://' + match;
            }
            return `[${match}](${url})`;
        }
    );
    tR += clipboard;
} else {
    tR += clipboard;
}
_%>

You see CONFIG settings on top. It is straightforward. You can set your own property names. And if you want date modified on the note, add/keep true and add your own prop name which you normally used (modified, date modified, whatever). The value added will be YYYY-MM-DDTHH:mm format datetime value, which is expected by DataView as well.
You can add more words to the excluded words array, in your own language as well.

You register the script in Templater and add a shortcut. I use WIN+ALT+P, as it is not taken on Linux or Windows (don’t know about Mac), where P is for Paste.
You can use the script for another use case: if you have a link in the clipboard, it pastes a clickable link in the note (clickable in Live Preview as well).
Before the } else { part, you can add more else if’s to add more functionality to do with frontmatter additions, or whatever. I deleted from this script several pieces of my own unrelated functionality.

  • In fact, I no longer use Templater for scripts anymore but CodeScript Toolkit, but I was not going to add a lesser known plugin in here.

When you hit the shortcut, the same diagram will be pasted back into your note but now the property of your choice will be added values, which you can query.

  • Currently, only node content are taken into consideration. If you want relationships or any other tweaks to go well with the PKM kind of prompt, you’ll need to modify the Templater script with Claude.Ai or otherwise.

Queries

Search Modal

I take it you are using my pre-defined property. If not, you need to edit the front part of the query in the quotes.
["plantuml_nodes": /dog – this will list any notes with prop value containing ‘dog’
["plantuml_nodes": /(^|[^a-zA-Z0-9_])(cat|dog)(?=$|[^a-zA-Z0-9_])/ – this will return notes with prop values that are exactly cat or dog; meaning cats or doggy will not be found

Datacore

Query:
datacore_query.zip (8.5 KB)

  • Add this to any file, for example to a Dashboard note or anywhere.
  • There is a CONFIG part on top of this script as well. You can add more properties for columns, uncomment tags, etc. You may not want Customizable Query with Date Filtering for title, either.

So if you add anything in your filter bar, make sure you Group By the plantUML prop; you may have to change to ‘Desc’ as well.

MetaEdit

You can in the DC query update values if you have MetaEdit plugin installed.

  • Courtesy of @Dusk; see here, for general help about Datacore as well.

If I remember I forgot anything, I’ll come back here to rectify or answer questions.

2 Likes