freeplane-and-yaml 0.1.4__py3-none-any.whl → 0.3.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- freeplane_and_yaml/__init__.py +10 -0
- freeplane_and_yaml/cli.py +5 -2
- freeplane_and_yaml/convert_pdf_to_mindmap.py +75 -0
- freeplane_and_yaml/llm_adapter.py +37 -0
- freeplane_and_yaml/openai_adapter.py +110 -0
- freeplane_and_yaml/pdf2mindmap.py +85 -0
- freeplane_and_yaml/schema/mindmap-schema.json +73 -0
- freeplane_and_yaml/text_to_mindmap.py +54 -0
- freeplane_and_yaml/yaml_utils.py +26 -0
- freeplane_and_yaml-0.3.0.dist-info/METADATA +103 -0
- freeplane_and_yaml-0.3.0.dist-info/RECORD +16 -0
- freeplane_and_yaml-0.3.0.dist-info/entry_points.txt +3 -0
- freeplane_and_yaml-0.1.4.dist-info/METADATA +0 -187
- freeplane_and_yaml-0.1.4.dist-info/RECORD +0 -9
- freeplane_and_yaml-0.1.4.dist-info/entry_points.txt +0 -3
- {freeplane_and_yaml-0.1.4.dist-info → freeplane_and_yaml-0.3.0.dist-info}/LICENSE +0 -0
- {freeplane_and_yaml-0.1.4.dist-info → freeplane_and_yaml-0.3.0.dist-info}/WHEEL +0 -0
- {freeplane_and_yaml-0.1.4.dist-info → freeplane_and_yaml-0.3.0.dist-info}/top_level.txt +0 -0
freeplane_and_yaml/__init__.py
CHANGED
freeplane_and_yaml/cli.py
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
import sys
|
2
2
|
import os
|
3
|
-
from
|
3
|
+
from freeplane_and_yaml.convert import convert_yaml_file # Import your conversion logic
|
4
4
|
|
5
5
|
|
6
6
|
def main():
|
@@ -31,10 +31,13 @@ def main():
|
|
31
31
|
base_name = os.path.basename(input_file).replace(".yaml", ".mm")
|
32
32
|
output_file = os.path.join(output_dir, base_name)
|
33
33
|
|
34
|
-
# Perform the
|
34
|
+
# Perform the conversio
|
35
35
|
try:
|
36
36
|
convert_yaml_file(input_file, output_file)
|
37
37
|
print(f"Conversion complete: {output_file}")
|
38
38
|
except Exception as e:
|
39
39
|
print(f"Error during conversion: {e}")
|
40
40
|
sys.exit(1)
|
41
|
+
|
42
|
+
if __name__ == "__main__":
|
43
|
+
main()
|
@@ -0,0 +1,75 @@
|
|
1
|
+
import os
|
2
|
+
import sys
|
3
|
+
import tempfile
|
4
|
+
import pymupdf4llm
|
5
|
+
from .text_to_mindmap import TextToMindMap
|
6
|
+
from .openai_adapter import OpenAIAdapter
|
7
|
+
|
8
|
+
def convert_pdf_to_mindmap(base_name, input_file, mm_output, model, text_output, yaml_output):
|
9
|
+
# Step 1: Extract text from the PDF
|
10
|
+
print(f"Extracting text from {input_file}...")
|
11
|
+
# Ensure the file exists
|
12
|
+
if not os.path.isfile(input_file):
|
13
|
+
raise FileNotFoundError(f"PDF file not found: {input_file}")
|
14
|
+
# Extract text using PyMuPDF4LLM directly
|
15
|
+
extracted_text = pymupdf4llm.to_markdown(input_file)
|
16
|
+
# Save the extracted text if requested
|
17
|
+
if text_output:
|
18
|
+
with open(text_output, 'w', encoding='utf-8') as f:
|
19
|
+
f.write(extracted_text)
|
20
|
+
print(f"Saved extracted text to: {text_output}")
|
21
|
+
# If the text is empty, report an error
|
22
|
+
if not extracted_text.strip():
|
23
|
+
print("Error: No text could be extracted from the PDF", file=sys.stderr)
|
24
|
+
sys.exit(1)
|
25
|
+
# Step 2: Create the OpenAI adapter
|
26
|
+
# Create the adapter with specified model or default
|
27
|
+
adapter = OpenAIAdapter(model=model)
|
28
|
+
# Step 3: Create a temporary text file for the extracted content
|
29
|
+
with tempfile.NamedTemporaryFile(mode='w', suffix='.txt', delete=False) as temp_file:
|
30
|
+
temp_file.write(extracted_text)
|
31
|
+
temp_file_path = temp_file.name
|
32
|
+
try:
|
33
|
+
# Step 4: Process the text with the text-to-mindmap service
|
34
|
+
print("Converting extracted text to mind map format...")
|
35
|
+
service = TextToMindMap(adapter)
|
36
|
+
|
37
|
+
# First try to get the YAML directly to inspect it
|
38
|
+
with open(temp_file_path, 'r', encoding='utf-8') as f:
|
39
|
+
text_content = f.read()
|
40
|
+
|
41
|
+
# Limit the text size to avoid timeouts
|
42
|
+
MAX_CHARS = 40000 # Large documents may cause timeouts
|
43
|
+
if len(text_content) > MAX_CHARS:
|
44
|
+
print(f"Document is large ({len(text_content)} chars). Truncating to {MAX_CHARS} chars to avoid timeouts.")
|
45
|
+
text_content = text_content[:MAX_CHARS] + "\n\n[Content truncated due to length...]"
|
46
|
+
|
47
|
+
try:
|
48
|
+
print(f"Sending document for processing...")
|
49
|
+
yaml_content = adapter.generate_mind_map_yaml(text_content)
|
50
|
+
|
51
|
+
# Save raw output for debugging
|
52
|
+
output_dir = os.path.dirname(mm_output)
|
53
|
+
debug_yaml_path = os.path.join(output_dir, f"{base_name}.raw.yaml")
|
54
|
+
with open(debug_yaml_path, 'w', encoding='utf-8') as f:
|
55
|
+
f.write(yaml_content)
|
56
|
+
print(f"Saved raw YAML for debugging to: {debug_yaml_path}")
|
57
|
+
|
58
|
+
# Now continue with the regular process
|
59
|
+
service.convert_text_file(temp_file_path, yaml_output, mm_output)
|
60
|
+
except Exception as e:
|
61
|
+
print(f"YAML conversion error: {e}")
|
62
|
+
print("Saving debug information and continuing...")
|
63
|
+
# Continue with original code path anyway
|
64
|
+
service.convert_text_file(temp_file_path, yaml_output, mm_output)
|
65
|
+
|
66
|
+
print("\nConversion successful!")
|
67
|
+
print(f"YAML output: {yaml_output}")
|
68
|
+
print(f"Mind map: {mm_output}")
|
69
|
+
if text_output:
|
70
|
+
print(f"Text output: {text_output}")
|
71
|
+
|
72
|
+
finally:
|
73
|
+
# Clean up the temporary file
|
74
|
+
if os.path.exists(temp_file_path):
|
75
|
+
os.unlink(temp_file_path)
|
@@ -0,0 +1,37 @@
|
|
1
|
+
from abc import ABC, abstractmethod
|
2
|
+
from typing import Dict, Any
|
3
|
+
import os
|
4
|
+
|
5
|
+
# Default schema path - using a path that works both in development and when installed
|
6
|
+
# First try the package-relative path (for installed package)
|
7
|
+
DEFAULT_SCHEMA_PATH = os.path.join(os.path.dirname(__file__), 'schema', 'mindmap-schema.json')
|
8
|
+
|
9
|
+
# If that doesn't exist, try the development path
|
10
|
+
if not os.path.exists(DEFAULT_SCHEMA_PATH):
|
11
|
+
DEFAULT_SCHEMA_PATH = os.path.abspath(
|
12
|
+
os.path.join(
|
13
|
+
os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))),
|
14
|
+
"src", "schema", "mindmap-schema.json"
|
15
|
+
)
|
16
|
+
)
|
17
|
+
|
18
|
+
|
19
|
+
class LLMAdapter(ABC):
|
20
|
+
"""
|
21
|
+
Abstract base class for LLM adapters.
|
22
|
+
This follows the hexagonal architecture pattern to isolate the LLM service from our core logic.
|
23
|
+
"""
|
24
|
+
|
25
|
+
@abstractmethod
|
26
|
+
def generate_mind_map_yaml(self, text: str) -> str:
|
27
|
+
"""
|
28
|
+
Generate a mind map in YAML format from the provided text,
|
29
|
+
following the schema defined in DEFAULT_SCHEMA_PATH.
|
30
|
+
|
31
|
+
Args:
|
32
|
+
text: The text content to convert to a mind map
|
33
|
+
|
34
|
+
Returns:
|
35
|
+
A string containing the YAML representation of the mind map
|
36
|
+
"""
|
37
|
+
pass
|
@@ -0,0 +1,110 @@
|
|
1
|
+
import os
|
2
|
+
from openai import OpenAI
|
3
|
+
from typing import Dict, Any
|
4
|
+
from dotenv import load_dotenv
|
5
|
+
from .llm_adapter import LLMAdapter, DEFAULT_SCHEMA_PATH
|
6
|
+
from .yaml_utils import extract_yaml_from_markdown
|
7
|
+
|
8
|
+
# Load environment variables from .env file
|
9
|
+
load_dotenv()
|
10
|
+
|
11
|
+
class OpenAIAdapter(LLMAdapter):
|
12
|
+
"""
|
13
|
+
Adapter for OpenAI API.
|
14
|
+
|
15
|
+
This class provides an implementation of the LLMAdapter for OpenAI models.
|
16
|
+
It makes API calls to OpenAI to generate mind maps from text.
|
17
|
+
"""
|
18
|
+
|
19
|
+
DEFAULT_MODEL = "gpt-4o" # Default OpenAI model to use
|
20
|
+
|
21
|
+
def __init__(self, api_key: str = None, model: str = None):
|
22
|
+
"""
|
23
|
+
Initialize the adapter with API credentials.
|
24
|
+
|
25
|
+
Args:
|
26
|
+
api_key: OpenAI API key, defaults to reading from OPENAI_API_KEY environment variable
|
27
|
+
model: OpenAI model to use, defaults to DEFAULT_MODEL
|
28
|
+
"""
|
29
|
+
self.api_key = api_key or os.environ.get("OPENAI_API_KEY")
|
30
|
+
if not self.api_key:
|
31
|
+
raise ValueError(
|
32
|
+
"No API key provided. Pass an API key to the constructor or "
|
33
|
+
"set the OPENAI_API_KEY environment variable in the .env file."
|
34
|
+
)
|
35
|
+
|
36
|
+
self.model = model or self.DEFAULT_MODEL
|
37
|
+
self.client = OpenAI(api_key=self.api_key)
|
38
|
+
|
39
|
+
def generate_mind_map_yaml(self, text: str) -> str:
|
40
|
+
"""
|
41
|
+
Generate a mind map in YAML format using OpenAI.
|
42
|
+
|
43
|
+
Args:
|
44
|
+
text: The text content to convert to a mind map
|
45
|
+
|
46
|
+
Returns:
|
47
|
+
A string containing the YAML representation of the mind map
|
48
|
+
"""
|
49
|
+
# Read the schema content
|
50
|
+
with open(DEFAULT_SCHEMA_PATH, 'r') as f:
|
51
|
+
schema = f.read()
|
52
|
+
|
53
|
+
# Make the API call to OpenAI
|
54
|
+
yaml_content = self._call_openai_api(text, schema)
|
55
|
+
|
56
|
+
return yaml_content
|
57
|
+
|
58
|
+
def _call_openai_api(self, text: str, schema: str) -> str:
|
59
|
+
"""
|
60
|
+
Call the OpenAI API with the text and schema.
|
61
|
+
|
62
|
+
Args:
|
63
|
+
text: The text content to convert to a mind map
|
64
|
+
schema: The JSON schema content
|
65
|
+
|
66
|
+
Returns:
|
67
|
+
The generated YAML content
|
68
|
+
"""
|
69
|
+
try:
|
70
|
+
# Build system prompt and user prompt
|
71
|
+
system_prompt = "You are an expert at creating structured mind maps in YAML format. Your task is to organize the provided text into a hierarchical structure that follows the schema exactly. Every node must include both a title and a descriptive note providing context or details for that node. Only output valid YAML without any explanations, markdown code blocks, or extra text."
|
72
|
+
|
73
|
+
user_prompt = f"""I'd like you to summarise a document as a yaml file following a schema.
|
74
|
+
|
75
|
+
Please respond with ONLY the YAML content that follows the schema, without any additional text or explanations.
|
76
|
+
The YAML should have a root node with an appropriate title and organized children nodes that capture
|
77
|
+
the key points and structure of the text. Aim to have one node per 20 lines of input text.
|
78
|
+
|
79
|
+
IMPORTANT: Every node (including the root and ALL child nodes) should include both a title AND a note.
|
80
|
+
The note should provide additional context, explanation, or details about that specific node.
|
81
|
+
|
82
|
+
Here is the schema to follow:
|
83
|
+
|
84
|
+
{schema}
|
85
|
+
|
86
|
+
Here is the document to summarize:
|
87
|
+
|
88
|
+
{text}"""
|
89
|
+
|
90
|
+
# Call the OpenAI API using the new client interface
|
91
|
+
response = self.client.chat.completions.create(
|
92
|
+
model=self.model,
|
93
|
+
messages=[
|
94
|
+
{"role": "system", "content": system_prompt},
|
95
|
+
{"role": "user", "content": user_prompt}
|
96
|
+
],
|
97
|
+
max_tokens=16000, # Maximum allowed for this model
|
98
|
+
temperature=0.2, # Low temperature for more deterministic output
|
99
|
+
)
|
100
|
+
|
101
|
+
# Extract the response content
|
102
|
+
content = response.choices[0].message.content
|
103
|
+
|
104
|
+
# Use the utility function to extract YAML from markdown
|
105
|
+
content = extract_yaml_from_markdown(content)
|
106
|
+
|
107
|
+
return content
|
108
|
+
|
109
|
+
except Exception as e:
|
110
|
+
raise RuntimeError(f"Error calling OpenAI API: {str(e)}") from e
|
@@ -0,0 +1,85 @@
|
|
1
|
+
#!/usr/bin/env python3
|
2
|
+
"""
|
3
|
+
Convert PDF files to mind maps using PyMuPDF4LLM and AI.
|
4
|
+
"""
|
5
|
+
|
6
|
+
import os
|
7
|
+
import sys
|
8
|
+
import argparse
|
9
|
+
import tempfile
|
10
|
+
import pymupdf4llm
|
11
|
+
from dotenv import load_dotenv
|
12
|
+
|
13
|
+
from freeplane_and_yaml.convert_pdf_to_mindmap import convert_pdf_to_mindmap
|
14
|
+
from .text_to_mindmap import TextToMindMap
|
15
|
+
from .openai_adapter import OpenAIAdapter
|
16
|
+
|
17
|
+
# Load environment variables
|
18
|
+
load_dotenv()
|
19
|
+
|
20
|
+
|
21
|
+
def main():
|
22
|
+
"""Main entry point for the pdf2mindmap command-line tool."""
|
23
|
+
parser = argparse.ArgumentParser(
|
24
|
+
description="Convert a PDF file to a mind map using PyMuPDF4LLM and OpenAI"
|
25
|
+
)
|
26
|
+
parser.add_argument(
|
27
|
+
"input_file",
|
28
|
+
help="Path to the input PDF file"
|
29
|
+
)
|
30
|
+
parser.add_argument(
|
31
|
+
"output_dir",
|
32
|
+
help="Directory to save the output files"
|
33
|
+
)
|
34
|
+
parser.add_argument(
|
35
|
+
"--model",
|
36
|
+
help="OpenAI model to use (defaults to gpt-4o)"
|
37
|
+
)
|
38
|
+
parser.add_argument(
|
39
|
+
"--save-text",
|
40
|
+
action="store_true",
|
41
|
+
help="Save the extracted text to a file"
|
42
|
+
)
|
43
|
+
|
44
|
+
args = parser.parse_args()
|
45
|
+
|
46
|
+
# Validate input file
|
47
|
+
if not os.path.isfile(args.input_file):
|
48
|
+
print(f"Error: Input PDF file '{args.input_file}' does not exist", file=sys.stderr)
|
49
|
+
sys.exit(1)
|
50
|
+
|
51
|
+
# Make sure it's a PDF file
|
52
|
+
if not args.input_file.lower().endswith('.pdf'):
|
53
|
+
print(f"Error: Input file must be a PDF file: {args.input_file}", file=sys.stderr)
|
54
|
+
sys.exit(1)
|
55
|
+
|
56
|
+
# Validate output directory
|
57
|
+
if not os.path.isdir(args.output_dir):
|
58
|
+
try:
|
59
|
+
os.makedirs(args.output_dir, exist_ok=True)
|
60
|
+
print(f"Created output directory: {args.output_dir}")
|
61
|
+
except Exception as e:
|
62
|
+
print(f"Error: Could not create output directory '{args.output_dir}': {e}", file=sys.stderr)
|
63
|
+
sys.exit(1)
|
64
|
+
input_file = args.input_file
|
65
|
+
output_dir = args.output_dir
|
66
|
+
save_text = args.save_text
|
67
|
+
# Generate output file paths
|
68
|
+
base_name = os.path.splitext(os.path.basename(input_file))[0]
|
69
|
+
yaml_output = os.path.join(output_dir, f"{base_name}.yaml")
|
70
|
+
mm_output = os.path.join(output_dir, f"{base_name}.mm")
|
71
|
+
text_output = os.path.join(output_dir, f"{base_name}.txt") if save_text else None
|
72
|
+
model = args.model
|
73
|
+
|
74
|
+
try:
|
75
|
+
convert_pdf_to_mindmap(base_name, input_file, mm_output, model, text_output, yaml_output)
|
76
|
+
|
77
|
+
except Exception as e:
|
78
|
+
print(f"Error during conversion: {e}", file=sys.stderr)
|
79
|
+
sys.exit(1)
|
80
|
+
|
81
|
+
|
82
|
+
|
83
|
+
|
84
|
+
if __name__ == "__main__":
|
85
|
+
main()
|
@@ -0,0 +1,73 @@
|
|
1
|
+
{
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
3
|
+
"title": "Mind Map Schema",
|
4
|
+
"description": "Schema for Freeplane-compatible mind map YAML format",
|
5
|
+
|
6
|
+
"definitions": {
|
7
|
+
"node": {
|
8
|
+
"type": "object",
|
9
|
+
"required": ["title"],
|
10
|
+
"properties": {
|
11
|
+
"title": {
|
12
|
+
"type": "string",
|
13
|
+
"description": "The display text for the node"
|
14
|
+
},
|
15
|
+
"note": {
|
16
|
+
"type": "string",
|
17
|
+
"description": "Rich text note attached to the node"
|
18
|
+
},
|
19
|
+
"children": {
|
20
|
+
"type": "object",
|
21
|
+
"description": "Child nodes of this node",
|
22
|
+
"patternProperties": {
|
23
|
+
"^[a-zA-Z0-9_]+$": {
|
24
|
+
"$ref": "#/definitions/node"
|
25
|
+
}
|
26
|
+
},
|
27
|
+
"additionalProperties": false
|
28
|
+
}
|
29
|
+
},
|
30
|
+
"additionalProperties": false
|
31
|
+
}
|
32
|
+
},
|
33
|
+
|
34
|
+
"type": "object",
|
35
|
+
"required": ["root"],
|
36
|
+
"properties": {
|
37
|
+
"root": {
|
38
|
+
"allOf": [
|
39
|
+
{ "$ref": "#/definitions/node" },
|
40
|
+
{
|
41
|
+
"required": ["children"],
|
42
|
+
"description": "The root node must have at least one child"
|
43
|
+
}
|
44
|
+
]
|
45
|
+
}
|
46
|
+
},
|
47
|
+
"additionalProperties": false,
|
48
|
+
|
49
|
+
"examples": [
|
50
|
+
{
|
51
|
+
"root": {
|
52
|
+
"title": "Example Mind Map",
|
53
|
+
"note": "This is the root node",
|
54
|
+
"children": {
|
55
|
+
"topic1": {
|
56
|
+
"title": "First Topic",
|
57
|
+
"note": "Note for first topic",
|
58
|
+
"children": {
|
59
|
+
"subtopic1": {
|
60
|
+
"title": "Subtopic 1",
|
61
|
+
"note": "Note for subtopic"
|
62
|
+
}
|
63
|
+
}
|
64
|
+
},
|
65
|
+
"topic2": {
|
66
|
+
"title": "Second Topic",
|
67
|
+
"note": "Note for second topic"
|
68
|
+
}
|
69
|
+
}
|
70
|
+
}
|
71
|
+
}
|
72
|
+
]
|
73
|
+
}
|
@@ -0,0 +1,54 @@
|
|
1
|
+
import os
|
2
|
+
from typing import Optional
|
3
|
+
from .llm_adapter import LLMAdapter
|
4
|
+
from .convert import converter
|
5
|
+
|
6
|
+
|
7
|
+
class TextToMindMap:
|
8
|
+
"""
|
9
|
+
Service for converting text files to mind maps via LLM-generated YAML.
|
10
|
+
|
11
|
+
This class follows the hexagonal architecture pattern, with the LLM adapter
|
12
|
+
injected through the constructor, allowing for easy testing and flexibility
|
13
|
+
in switching between different LLM providers.
|
14
|
+
"""
|
15
|
+
|
16
|
+
def __init__(self, llm_adapter: LLMAdapter):
|
17
|
+
"""
|
18
|
+
Initialize the service with an LLM adapter.
|
19
|
+
|
20
|
+
Args:
|
21
|
+
llm_adapter: An implementation of LLMAdapter
|
22
|
+
"""
|
23
|
+
self.llm_adapter = llm_adapter
|
24
|
+
|
25
|
+
def convert_text_file(self, input_file_path: str, output_yaml_path: str, output_mm_path: str = None) -> str:
|
26
|
+
"""
|
27
|
+
Convert a text file to a mind map.
|
28
|
+
|
29
|
+
Args:
|
30
|
+
input_file_path: Path to the text file to convert
|
31
|
+
output_yaml_path: Path where the YAML file should be saved
|
32
|
+
output_mm_path: Optional path where the Freeplane .mm file should be saved
|
33
|
+
|
34
|
+
Returns:
|
35
|
+
The YAML content generated from the text
|
36
|
+
"""
|
37
|
+
# Read the text file
|
38
|
+
with open(input_file_path, 'r', encoding='utf-8') as f:
|
39
|
+
text_content = f.read()
|
40
|
+
|
41
|
+
# Convert text to YAML using the adapter
|
42
|
+
yaml_content = self.llm_adapter.generate_mind_map_yaml(text_content)
|
43
|
+
|
44
|
+
# Save the YAML output
|
45
|
+
with open(output_yaml_path, 'w', encoding='utf-8') as f:
|
46
|
+
f.write(yaml_content)
|
47
|
+
|
48
|
+
# Optionally convert to Freeplane .mm format
|
49
|
+
if output_mm_path:
|
50
|
+
mm_content = converter(yaml_content)
|
51
|
+
with open(output_mm_path, 'w', encoding='utf-8') as f:
|
52
|
+
f.write(mm_content)
|
53
|
+
|
54
|
+
return yaml_content
|
@@ -0,0 +1,26 @@
|
|
1
|
+
"""
|
2
|
+
Utility functions for YAML handling.
|
3
|
+
"""
|
4
|
+
|
5
|
+
def extract_yaml_from_markdown(content):
|
6
|
+
"""
|
7
|
+
Extract YAML content from markdown-formatted text.
|
8
|
+
|
9
|
+
Args:
|
10
|
+
content: The markdown text containing YAML code blocks
|
11
|
+
|
12
|
+
Returns:
|
13
|
+
The extracted YAML content
|
14
|
+
"""
|
15
|
+
# Remove any initial text before the YAML content
|
16
|
+
if "```yaml" in content:
|
17
|
+
content = content.split("```yaml", 1)[1]
|
18
|
+
if "```" in content:
|
19
|
+
content = content.split("```", 1)[0]
|
20
|
+
elif "```" in content:
|
21
|
+
content = content.split("```", 1)[1]
|
22
|
+
if "```" in content:
|
23
|
+
content = content.split("```", 1)[0]
|
24
|
+
|
25
|
+
# Clean up and return
|
26
|
+
return content.strip()
|
@@ -0,0 +1,103 @@
|
|
1
|
+
Metadata-Version: 2.1
|
2
|
+
Name: freeplane-and-yaml
|
3
|
+
Version: 0.3.0
|
4
|
+
Summary: A tool to convert PDF files to Freeplane mind maps using OpenAI
|
5
|
+
Home-page: https://github.com/romilly/freeplane-and-yaml
|
6
|
+
Author: Romilly Cocking
|
7
|
+
Author-email: romilly.cocking@gmail.com
|
8
|
+
License: MIT
|
9
|
+
Platform: UNKNOWN
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
11
|
+
Classifier: License :: OSI Approved :: MIT License
|
12
|
+
Classifier: Operating System :: OS Independent
|
13
|
+
Requires-Python: >=3.9
|
14
|
+
Description-Content-Type: text/markdown
|
15
|
+
License-File: LICENSE
|
16
|
+
Requires-Dist: PyYAML
|
17
|
+
Requires-Dist: openai
|
18
|
+
Requires-Dist: pymupdf4llm
|
19
|
+
Requires-Dist: python-dotenv
|
20
|
+
|
21
|
+
# PDF to Freeplane Mind Map Converter
|
22
|
+
|
23
|
+
A Python tool that converts PDF documents into Freeplane mind maps using OpenAI. This allows you to automatically generate structured mind maps from PDF documents.
|
24
|
+
|
25
|
+
You can read about how it got written using AI on [medium](https://medium.com/@romillyc/build-your-own-mind-map-tools-with-ai-b193564f2464?sk=b353aa7d16d6412e4aae8f3eab0ec554).
|
26
|
+
That's a _friend link_ so you can read it even if you're not a subscriber.
|
27
|
+
|
28
|
+
## Installation
|
29
|
+
|
30
|
+
This project requires Python and should be run in a virtual environment:
|
31
|
+
|
32
|
+
```bash
|
33
|
+
# Create and activate virtual environment in the directory of your choice
|
34
|
+
python -m venv venv
|
35
|
+
source venv/bin/activate # On Windows use: venv\Scripts\activate
|
36
|
+
pip install freeplane-and-yaml
|
37
|
+
```
|
38
|
+
|
39
|
+
You'll need to set up your OpenAI API key in a `.env` file or as an environment variable:
|
40
|
+
|
41
|
+
```bash
|
42
|
+
# Create a .env file in your project directory
|
43
|
+
echo "OPENAI_API_KEY=your_api_key_here" > .env
|
44
|
+
```
|
45
|
+
|
46
|
+
|
47
|
+
## Usage
|
48
|
+
|
49
|
+
### Converting PDF Documents to Mind Maps
|
50
|
+
|
51
|
+
To convert a PDF document to a Freeplane mind map:
|
52
|
+
|
53
|
+
```bash
|
54
|
+
# Convert a PDF file to a mind map
|
55
|
+
pdf2mindmap path/to/document.pdf output_directory
|
56
|
+
|
57
|
+
# Use a specific OpenAI model (default is gpt-4o-mini)
|
58
|
+
pdf2mindmap path/to/document.pdf output_directory --model gpt-4-turbo
|
59
|
+
|
60
|
+
# Also save the extracted text
|
61
|
+
pdf2mindmap path/to/document.pdf output_directory --save-text
|
62
|
+
```
|
63
|
+
|
64
|
+
The tool performs these steps:
|
65
|
+
1. Extracts text from the PDF using PyMuPDF4LLM (preserving structure as markdown)
|
66
|
+
2. Processes the text with OpenAI to generate a structured mind map in YAML format
|
67
|
+
3. Converts the YAML to a Freeplane mind map (.mm file)
|
68
|
+
4. Saves all files in the output directory
|
69
|
+
|
70
|
+
This is useful for:
|
71
|
+
- Summarizing academic papers
|
72
|
+
- Converting product requirements documents (PRDs)
|
73
|
+
- Creating structured summaries of technical documentation
|
74
|
+
- Organizing research notes or book content
|
75
|
+
|
76
|
+
|
77
|
+
The generated `.mm` file can be opened in Freeplane. When you first open the file, Freeplane will show this warning dialog because the file wasn't created by Freeplane itself:
|
78
|
+
|
79
|
+

|
80
|
+
|
81
|
+
This is normal and expected — click OK to load the mind map.
|
82
|
+
|
83
|
+
Here's an example of how the output looks:
|
84
|
+
|
85
|
+

|
86
|
+
|
87
|
+
## Features
|
88
|
+
|
89
|
+
- Converts PDF documents to Freeplane mind maps using OpenAI
|
90
|
+
- Uses PyMuPDF4LLM for high-quality text extraction from PDFs
|
91
|
+
- Creates hierarchical mind maps with node titles and notes
|
92
|
+
- Automatically alternates between right and left positions for top-level nodes
|
93
|
+
- Generates unique IDs for each node
|
94
|
+
- Produces clean, well-structured YAML as an intermediate format
|
95
|
+
- Supports different OpenAI models (default is gpt-4o-mini)
|
96
|
+
|
97
|
+
## License
|
98
|
+
|
99
|
+
_Apologies to readers from the USA. This README uses UK spelling._
|
100
|
+
|
101
|
+
This project is licensed under the MIT Licence — see the [LICENCE](LICENSE) file for details.
|
102
|
+
|
103
|
+
|
@@ -0,0 +1,16 @@
|
|
1
|
+
freeplane_and_yaml/__init__.py,sha256=x8BtsOuvatRJqbtZkqU93yf-h4OQJK2tkvi3WnIB5YM,226
|
2
|
+
freeplane_and_yaml/cli.py,sha256=ETgW_67QglX7QhDJ4NQANiV134_V4vT3I70o--zwFNw,1271
|
3
|
+
freeplane_and_yaml/convert.py,sha256=dyQLt2FlmCgsQOnutnp1DyLXzqk-guazL8TZwTH0vHI,3440
|
4
|
+
freeplane_and_yaml/convert_pdf_to_mindmap.py,sha256=TR620sdvw9-4jKIeoK0eQko3kAD9LPH83TKndKJHYY4,3294
|
5
|
+
freeplane_and_yaml/llm_adapter.py,sha256=Sd-ibim34Nunj1A45i4om0JkwhldjC_eFvkX1jhDhPg,1250
|
6
|
+
freeplane_and_yaml/openai_adapter.py,sha256=1RKv_6Hb_9s9xqvLq3Vmu4g9We2vdbY8YfcTqiMuOLA,4312
|
7
|
+
freeplane_and_yaml/pdf2mindmap.py,sha256=17WnRxP_w5eqI40IX7y9JIRsQEONGNrSm00PQKok7uA,2543
|
8
|
+
freeplane_and_yaml/text_to_mindmap.py,sha256=f46jQFAvOb4u1tKRSROGdFAyqG62Smww-mBWS1ke1fM,1898
|
9
|
+
freeplane_and_yaml/yaml_utils.py,sha256=W_K3kstknOFibZbMDnkxuTqVQO1hKGfNBOLLa9_GErM,718
|
10
|
+
freeplane_and_yaml/schema/mindmap-schema.json,sha256=vCFcGjWcZTeTNozGChsRjEw-jYsYDiPxr-qVML8M_XE,1734
|
11
|
+
freeplane_and_yaml-0.3.0.dist-info/LICENSE,sha256=gCmvBRHqGZibjt4wyMG81SeYWMKuhoGFVQh_Kn1wZ98,1072
|
12
|
+
freeplane_and_yaml-0.3.0.dist-info/METADATA,sha256=ZV85Aw2fyntEKziT2E-RjvzV6PcTG99Afaz4oW2XQCw,3527
|
13
|
+
freeplane_and_yaml-0.3.0.dist-info/WHEEL,sha256=tZoeGjtWxWRfdplE7E3d45VPlLNQnvbKiYnx7gwAy8A,92
|
14
|
+
freeplane_and_yaml-0.3.0.dist-info/entry_points.txt,sha256=-k40S1cR7VRZViC202mFFI2OsUxmPPBzBiRh0wsMmHE,69
|
15
|
+
freeplane_and_yaml-0.3.0.dist-info/top_level.txt,sha256=UY6T4vy985r4DAfWpM1D_n6t0cEis5SxtfkPPd-xDhQ,19
|
16
|
+
freeplane_and_yaml-0.3.0.dist-info/RECORD,,
|
@@ -1,187 +0,0 @@
|
|
1
|
-
Metadata-Version: 2.1
|
2
|
-
Name: freeplane-and-yaml
|
3
|
-
Version: 0.1.4
|
4
|
-
Summary: A tool to convert YAML files to Freeplane MM format
|
5
|
-
Home-page: https://github.com/romilly/freeplane-and-yaml
|
6
|
-
Author: Romilly Cocking
|
7
|
-
Author-email: romilly.cocking@gmail.com
|
8
|
-
License: MIT
|
9
|
-
Platform: UNKNOWN
|
10
|
-
Classifier: Programming Language :: Python :: 3
|
11
|
-
Classifier: License :: OSI Approved :: MIT License
|
12
|
-
Classifier: Operating System :: OS Independent
|
13
|
-
Requires-Python: >=3.9
|
14
|
-
Description-Content-Type: text/markdown
|
15
|
-
License-File: LICENSE
|
16
|
-
Requires-Dist: PyYAML
|
17
|
-
|
18
|
-
# YAML to Freeplane Converter
|
19
|
-
|
20
|
-
A Python tool that converts YAML files into Freeplane mind maps. This allows you to generate mind maps programmatically from structured YAML data.
|
21
|
-
|
22
|
-
The YAML file can be created using [Claude AI](https://claude.ai/chat/).
|
23
|
-
A suitable prompt is given [below](https://github.com/romilly/freeplane-and-yaml?tab=readme-ov-file#converting-documents-to-yaml-using-claude-ai).
|
24
|
-
|
25
|
-
You can read about how it got written using AI on [medium](https://medium.com/@romillyc/build-your-own-mind-map-tools-with-ai-b193564f2464?sk=b353aa7d16d6412e4aae8f3eab0ec554).
|
26
|
-
That's a _friend link_ so you can read it even if you're not a subscriber.
|
27
|
-
|
28
|
-
## Installation
|
29
|
-
|
30
|
-
This project requires Python and should be run in a virtual environment:
|
31
|
-
|
32
|
-
```bash
|
33
|
-
# Create and activate virtual environment in the direcoty of your choice
|
34
|
-
python -m venv venv
|
35
|
-
source venv/bin/activate # On Windows use: venv\Scripts\activate
|
36
|
-
pip install freeplane-and-yaml
|
37
|
-
```
|
38
|
-
|
39
|
-
|
40
|
-
## Usage
|
41
|
-
|
42
|
-
Your YAML file should follow this [schema](https://raw.githubusercontent.com/romilly/freeplane-and-yaml/refs/heads/main/src/schema/mindmap-schema.json). It includes an example.
|
43
|
-
|
44
|
-
```json
|
45
|
-
{
|
46
|
-
"$schema": "http://json-schema.org/draft-07/schema#",
|
47
|
-
"title": "Mind Map Schema",
|
48
|
-
"description": "Schema for Freeplane-compatible mind map YAML format",
|
49
|
-
|
50
|
-
"definitions": {
|
51
|
-
"node": {
|
52
|
-
"type": "object",
|
53
|
-
"required": ["title"],
|
54
|
-
"properties": {
|
55
|
-
"title": {
|
56
|
-
"type": "string",
|
57
|
-
"description": "The display text for the node"
|
58
|
-
},
|
59
|
-
"note": {
|
60
|
-
"type": "string",
|
61
|
-
"description": "Rich text note attached to the node"
|
62
|
-
},
|
63
|
-
"children": {
|
64
|
-
"type": "object",
|
65
|
-
"description": "Child nodes of this node",
|
66
|
-
"patternProperties": {
|
67
|
-
"^[a-zA-Z0-9_]+$": {
|
68
|
-
"$ref": "#/definitions/node"
|
69
|
-
}
|
70
|
-
},
|
71
|
-
"additionalProperties": false
|
72
|
-
}
|
73
|
-
},
|
74
|
-
"additionalProperties": false
|
75
|
-
}
|
76
|
-
},
|
77
|
-
|
78
|
-
"type": "object",
|
79
|
-
"required": ["root"],
|
80
|
-
"properties": {
|
81
|
-
"root": {
|
82
|
-
"allOf": [
|
83
|
-
{ "$ref": "#/definitions/node" },
|
84
|
-
{
|
85
|
-
"required": ["children"],
|
86
|
-
"description": "The root node must have at least one child"
|
87
|
-
}
|
88
|
-
]
|
89
|
-
}
|
90
|
-
},
|
91
|
-
"additionalProperties": false,
|
92
|
-
|
93
|
-
"examples": [
|
94
|
-
{
|
95
|
-
"root": {
|
96
|
-
"title": "Example Mind Map",
|
97
|
-
"note": "This is the root node",
|
98
|
-
"children": {
|
99
|
-
"topic1": {
|
100
|
-
"title": "First Topic",
|
101
|
-
"note": "Note for first topic",
|
102
|
-
"children": {
|
103
|
-
"subtopic1": {
|
104
|
-
"title": "Subtopic 1",
|
105
|
-
"note": "Note for subtopic"
|
106
|
-
}
|
107
|
-
}
|
108
|
-
},
|
109
|
-
"topic2": {
|
110
|
-
"title": "Second Topic",
|
111
|
-
"note": "Note for second topic"
|
112
|
-
}
|
113
|
-
}
|
114
|
-
}
|
115
|
-
}
|
116
|
-
]
|
117
|
-
}
|
118
|
-
|
119
|
-
```
|
120
|
-
|
121
|
-
### Converting YAML to Mind Map
|
122
|
-
|
123
|
-
To convert a YAML file to a Freeplane mind map:
|
124
|
-
|
125
|
-
```bash
|
126
|
-
|
127
|
-
# Convert YAML and store mind map in temp
|
128
|
-
convert data/marr.yaml temp
|
129
|
-
```
|
130
|
-
|
131
|
-
### YAML Schema requirements explained
|
132
|
-
|
133
|
-
As the schema specifies, The YAML must conform to these rules:
|
134
|
-
- Must have a root node with a title and at least one child
|
135
|
-
- Each node requires a title
|
136
|
-
- Notes are optional
|
137
|
-
- Child node keys must be alphanumeric (including underscores)
|
138
|
-
- No additional properties are allowed beyond title, note, and children
|
139
|
-
|
140
|
-
For full schema details, see above.
|
141
|
-
|
142
|
-
### Converting Documents to YAML using Claude AI
|
143
|
-
|
144
|
-
You can use Claude Sonnet to automatically convert documents (PDFs, articles, specifications, etc.) into the required YAML format. Here's the workflow:
|
145
|
-
|
146
|
-
1. Share your document and the schema (above) with Claude Sonnet.
|
147
|
-
2. Use this prompt:
|
148
|
-
```
|
149
|
-
I've uploaded a document and a schema file. I'd like you to summarise the document as a yaml file following the schema that I uploaded.
|
150
|
-
```
|
151
|
-
3. Claude will generate a YAML file that follows the schema
|
152
|
-
4. Save Claude's output as a .yaml file
|
153
|
-
5. Convert it to a mind map using this tool
|
154
|
-
|
155
|
-
This workflow is useful for:
|
156
|
-
- Summarizing academic papers
|
157
|
-
- Converting product requirements documents (PRDs)
|
158
|
-
- Creating structured summaries of technical documentation
|
159
|
-
- Organizing research notes
|
160
|
-
|
161
|
-
|
162
|
-
The generated `.mm` file can be opened in Freeplane. When you first open the file, Freeplane will show this warning dialog because the file wasn't created by Freeplane itself:
|
163
|
-
|
164
|
-

|
165
|
-
|
166
|
-
This is normal and expected — click OK to load the mind map.
|
167
|
-
|
168
|
-
Here's an example of how the output looks:
|
169
|
-
|
170
|
-

|
171
|
-
|
172
|
-
## Features
|
173
|
-
|
174
|
-
- Converts YAML structured data to Freeplane mind map format
|
175
|
-
- Supports hierarchical node structure
|
176
|
-
- Includes node titles and optional notes
|
177
|
-
- Automatically alternates between right and left positions for top-level nodes
|
178
|
-
- Generates unique IDs for each node
|
179
|
-
- Validates input against JSON schema
|
180
|
-
|
181
|
-
## License
|
182
|
-
|
183
|
-
_Apologies to readers from the USA. This README uses UK spelling._
|
184
|
-
|
185
|
-
This project is licensed under the MIT Licence — see the [LICENCE](LICENSE) file for details.
|
186
|
-
|
187
|
-
|
@@ -1,9 +0,0 @@
|
|
1
|
-
freeplane_and_yaml/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
2
|
-
freeplane_and_yaml/cli.py,sha256=qeUJXw2MqCf6Kv7c-K63-srGj339beL4JTrAXi5_-3c,1213
|
3
|
-
freeplane_and_yaml/convert.py,sha256=dyQLt2FlmCgsQOnutnp1DyLXzqk-guazL8TZwTH0vHI,3440
|
4
|
-
freeplane_and_yaml-0.1.4.dist-info/LICENSE,sha256=gCmvBRHqGZibjt4wyMG81SeYWMKuhoGFVQh_Kn1wZ98,1072
|
5
|
-
freeplane_and_yaml-0.1.4.dist-info/METADATA,sha256=BVBe-V68MU9x5nvP1OZQD5cZHcjZqXgOtpZrOG7_7l4,5656
|
6
|
-
freeplane_and_yaml-0.1.4.dist-info/WHEEL,sha256=tZoeGjtWxWRfdplE7E3d45VPlLNQnvbKiYnx7gwAy8A,92
|
7
|
-
freeplane_and_yaml-0.1.4.dist-info/entry_points.txt,sha256=kWuCk_RZlzcO9h7yk7uJ4dtJiccQxBocEuQPVt6qzxo,57
|
8
|
-
freeplane_and_yaml-0.1.4.dist-info/top_level.txt,sha256=UY6T4vy985r4DAfWpM1D_n6t0cEis5SxtfkPPd-xDhQ,19
|
9
|
-
freeplane_and_yaml-0.1.4.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|