Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,13 +38,38 @@ View the [Change Logs](https://github.com/csgoh/roadmapper/wiki/Change-Logs) to
* drawsvg >= 2.3.0
* rich >= 13.7.1
* ruff >= 0.2.1
* openai >= 1.0.0



Any feedback or suggestions are welcome. Please feel free to create an issue or pull request.
<br/>
<hr>

## AI Roadmap Generation
You can now generate roadmap code from natural language descriptions using the `generate_roadmap_code` function.

```python
from roadmapper import generate_roadmap_code

description = """
Create a roadmap for a new product launch.
The timeline should be monthly starting from 2024-01-01.
Add a group for 'Development'.
Add a task 'MVP' from 2024-01-01 to 2024-04-01 with a milestone 'Alpha Release' on 2024-03-01.
Add a task 'Beta' from 2024-04-01 to 2024-06-01 with a milestone 'Public Beta' on 2024-05-15.
"""

api_key = "your-openai-api-key"
code = generate_roadmap_code(description, api_key=api_key)
print(code)

# You can then execute the code:
exec(code)
```
<br/>
<hr>


## Installation
### Install from PyPI
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ classifiers = [
"Programming Language :: Python :: 3.10",
"Operating System :: OS Independent",
]
dependencies = ['Pillow>=10.0.0', 'python-dateutil>=2.8.2', 'drawsvg>=2.2.0']
dependencies = ['Pillow>=10.0.0', 'python-dateutil>=2.8.2', 'drawsvg>=2.2.0', 'openai>=1.0.0']


[project.urls]
Expand Down
Binary file modified requirements.txt
Binary file not shown.
5 changes: 5 additions & 0 deletions src/roadmapper/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
from roadmapper.roadmap import Roadmap
from roadmapper.timelinemode import TimelineMode
from roadmapper.ai import generate_roadmap_code

__all__ = ["Roadmap", "TimelineMode", "generate_roadmap_code"]
116 changes: 116 additions & 0 deletions src/roadmapper/ai.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
# MIT License

# Copyright (c) 2024 CS Goh

# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:

# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.

# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.

import re
from openai import OpenAI


def generate_roadmap_code(
prompt: str, api_key: str, model: str = "gpt-4o"
) -> str:
"""
Generates Python code for a roadmap using the roadmapper library based on a natural language description.

Args:
prompt (str): A description of the roadmap you want to generate.
api_key (str): Your OpenAI API key.
model (str, optional): The OpenAI model to use. Defaults to "gpt-4o".

Returns:
str: Valid Python code that uses roadmapper to generate the roadmap.
"""

system_prompt = """
You are an expert Python developer specialized in using the `roadmapper` library.
Your task is to convert the user's natural language description of a roadmap into valid Python code using the `roadmapper` library.

### Instructions:
1. Return ONLY valid Python code.
2. Do not include markdown formatting (like ```python ... ```) if possible, or I will strip it.
3. The code must be complete and runnable.
4. Use the provided examples to understand the API.

### Roadmapper API Examples:

```python
from roadmapper.roadmap import Roadmap
from roadmapper.timelinemode import TimelineMode

# Create roadmap
roadmap = Roadmap(1200, 400, colour_theme="BLUEMOUNTAIN")
roadmap.set_title("My Demo Roadmap")
roadmap.set_subtitle("Matariki Technologies Ltd")
roadmap.set_timeline(TimelineMode.MONTHLY, start="2025-01-01", number_of_items=18)
roadmap.set_footer("Generated by Roadmapper")

# Add Logo (optional)
# roadmap.add_logo("logo.png", position="top-right", width=50, height=50)

# Add Group
group = roadmap.add_group("Core Product Work Stream")

# Add Task
task = group.add_task("Base Functionality", "2025-01-01", "2025-10-31")
task.add_milestone("v.1.0", "2025-02-15")

# Add Parallel Task
parallel_task = task.add_parallel_task("Enhancements", "2025-11-15", "2026-03-31")
parallel_task.add_milestone("v.2.0", "2026-03-30")

# Draw and Save
roadmap.draw()
roadmap.save("roadmap.png")
```

### Timeline Modes:
- TimelineMode.WEEKLY
- TimelineMode.MONTHLY
- TimelineMode.QUARTERLY
- TimelineMode.HALF_YEARLY
- TimelineMode.YEARLY

### Colour Themes:
- "DEFAULT", "BLUEMOUNTAIN", "ORANGE", "GREYWOOLF", "TEAL"

### Output Format:
Provide only the python code. No explanations.
"""

client = OpenAI(api_key=api_key)

response = client.chat.completions.create(
model=model,
messages=[
{"role": "system", "content": system_prompt},
{"role": "user", "content": prompt},
],
temperature=0.2,
)

content = response.choices[0].message.content

# Strip markdown code blocks if present
content = re.sub(r"^```python\n", "", content)
content = re.sub(r"^```\n", "", content)
content = re.sub(r"\n```$", "", content)

return content.strip()
35 changes: 35 additions & 0 deletions tests/test_ai.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import unittest
from unittest.mock import MagicMock, patch
from roadmapper.ai import generate_roadmap_code


class TestAI(unittest.TestCase):
@patch("roadmapper.ai.OpenAI")
def test_generate_roadmap_code(self, mock_openai):
# Setup mock
mock_client = MagicMock()
mock_openai.return_value = mock_client

mock_completion = MagicMock()
mock_completion.choices = [
MagicMock(message=MagicMock(content="```python\nprint('Hello World')\n```"))
]
mock_client.chat.completions.create.return_value = mock_completion

# Execute
code = generate_roadmap_code("Create a roadmap", "fake-api-key")

# Verify
self.assertEqual(code, "print('Hello World')")
mock_client.chat.completions.create.assert_called_once()

# Verify prompt structure
args, kwargs = mock_client.chat.completions.create.call_args
self.assertEqual(kwargs["model"], "gpt-4o")
self.assertEqual(len(kwargs["messages"]), 2)
self.assertEqual(kwargs["messages"][0]["role"], "system")
self.assertEqual(kwargs["messages"][1]["role"], "user")
self.assertIn("Roadmapper API Examples", kwargs["messages"][0]["content"])

if __name__ == "__main__":
unittest.main()
Loading