Skip to content
Merged
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
6 changes: 3 additions & 3 deletions agentic/workflows/modules/agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ def parse_json(output: str, target_type: Type[T] = None) -> Any:
return [target_type.model_validate(item) for item in parsed]
return target_type.model_validate(parsed)
return parsed
except (json.JSONDecodeError, ValueError):
except json.JSONDecodeError, ValueError:
pass

# Strategy 2: Strip markdown code fences
Expand All @@ -162,7 +162,7 @@ def parse_json(output: str, target_type: Type[T] = None) -> Any:
return [target_type.model_validate(item) for item in parsed]
return target_type.model_validate(parsed)
return parsed
except (json.JSONDecodeError, ValueError):
except json.JSONDecodeError, ValueError:
pass

# Strategy 3: Find first JSON array or object in output
Expand All @@ -182,7 +182,7 @@ def parse_json(output: str, target_type: Type[T] = None) -> Any:
return [target_type.model_validate(item) for item in parsed]
return target_type.model_validate(parsed)
return parsed
except (json.JSONDecodeError, ValueError):
except json.JSONDecodeError, ValueError:
continue

raise json.JSONDecodeError("No valid JSON found in output", output, 0)
Expand Down
2 changes: 1 addition & 1 deletion agentic/workflows/modules/orchestrator.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,5 +45,5 @@ def extract_run_id(stdout: str) -> str | None:
try:
data = json.loads(stdout.strip())
return data.get("run_id")
except (json.JSONDecodeError, ValueError):
except json.JSONDecodeError, ValueError:
return None
2 changes: 1 addition & 1 deletion agentic/workflows/modules/state.py
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,7 @@ def from_stdin(cls) -> Optional["WorkflowState"]:
state = cls(run_id=run_id, prompt=data.get("prompt", ""))
state.data = data
return state
except (json.JSONDecodeError, EOFError):
except json.JSONDecodeError, EOFError:
return None

def to_stdout(self) -> None:
Expand Down
25 changes: 17 additions & 8 deletions core/images.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@
import subprocess

_HAS_PNGQUANT = subprocess.run(["pngquant", "--version"], capture_output=True).returncode == 0
except (FileNotFoundError, subprocess.SubprocessError):
except FileNotFoundError, subprocess.SubprocessError:
_HAS_PNGQUANT = False


Expand Down Expand Up @@ -196,7 +196,7 @@ def create_comparison_image(
# Header bar
header_text = f"{library} · {spec_id}" if library and spec_id else library or spec_id
if header_text:
header_font = _get_font(28, weight=700)
header_font = _get_font(28, weight=700, local_only=True)
bbox = draw.textbbox((0, 0), header_text, font=header_font)
text_w = bbox[2] - bbox[0]
text_h = bbox[3] - bbox[1]
Expand All @@ -208,7 +208,7 @@ def create_comparison_image(
)

# Labels
label_font = _get_font(22, weight=400)
label_font = _get_font(22, weight=400, local_only=True)
before_label = "BEFORE (current)"
after_label = "AFTER (updated)"

Expand Down Expand Up @@ -242,7 +242,7 @@ def create_comparison_image(
canvas.paste(before_img, (bx, by))
else:
# Gray placeholder
placeholder_font = _get_font(20, weight=400)
placeholder_font = _get_font(20, weight=400, local_only=True)
placeholder_text = "No previous version"
pb = draw.textbbox((0, 0), placeholder_text, font=placeholder_font)
pw = pb[2] - pb[0]
Expand Down Expand Up @@ -276,9 +276,12 @@ def _fit_image(img: Image.Image, max_width: int, max_height: int) -> Image.Image
# =============================================================================


def _get_monolisa_font_path() -> Path | None:
def _get_monolisa_font_path(local_only: bool = False) -> Path | None:
"""Get path to MonoLisa font, downloading from GCS if needed.

Args:
local_only: If True, only return the font if already cached locally.

Returns:
Path to font file, or None if unavailable.
"""
Expand All @@ -288,6 +291,9 @@ def _get_monolisa_font_path() -> Path | None:
if cached_font.exists():
return cached_font

if local_only:
return None

# Try to download from GCS
try:
from google.cloud import storage
Expand All @@ -305,17 +311,20 @@ def _get_monolisa_font_path() -> Path | None:
return None


def _get_font(size: int = 32, weight: int = 700) -> ImageFont.FreeTypeFont | ImageFont.ImageFont:
def _get_font(
size: int = 32, weight: int = 700, local_only: bool = False
) -> ImageFont.FreeTypeFont | ImageFont.ImageFont:
"""Get a suitable font for text rendering.

Tries to load MonoLisa from GCS cache, falls back to system fonts.

Args:
size: Font size in pixels
weight: Font weight (100-900, default 700 for bold like website)
local_only: If True, skip GCS download and only use locally cached or system fonts.
"""
# Try MonoLisa first (downloaded from GCS)
monolisa_path = _get_monolisa_font_path()
# Try MonoLisa (from local cache, or download from GCS if allowed)
monolisa_path = _get_monolisa_font_path(local_only=local_only)
if monolisa_path:
try:
font = ImageFont.truetype(str(monolisa_path), size)
Expand Down
62 changes: 33 additions & 29 deletions plots/area-basic/implementations/highcharts.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
""" pyplots.ai
area-basic: Basic Area Chart
Library: highcharts 1.10.3 | Python 3.14.2
Quality: 74/100 | Created: 2025-12-23
Quality: 91/100 | Created: 2025-12-23
"""

import tempfile
Expand Down Expand Up @@ -31,14 +31,15 @@
chart = Chart(container="container")
chart.options = HighchartsOptions()

# Chart configuration
# Chart configuration — generous bottom margin to ensure x-axis title renders fully
chart.options.chart = {
"type": "area",
"width": 4800,
"height": 2700,
"backgroundColor": "#ffffff",
"marginBottom": 200,
"marginLeft": 200,
"marginBottom": 300,
"marginLeft": 220,
"spacingBottom": 40,
}

# Title
Expand All @@ -47,39 +48,58 @@
"style": {"fontSize": "72px", "fontWeight": "bold"},
}

# X-axis
# Subtitle for data context
chart.options.subtitle = {
"text": "Daily Website Visitors Over One Month",
"style": {"fontSize": "42px", "color": "#666666"},
}

# X-axis — explicit margin and offset to prevent title clipping
chart.options.x_axis = {
"title": {"text": "Day of Month", "style": {"fontSize": "48px"}},
"labels": {"style": {"fontSize": "36px"}},
"title": {"text": "Day of Month", "style": {"fontSize": "48px"}, "margin": 30},
"labels": {"style": {"fontSize": "36px"}, "y": 45},
"gridLineWidth": 1,
"gridLineColor": "rgba(0, 0, 0, 0.1)",
"tickInterval": 1,
}

# Y-axis — set min close to data range to avoid wasted whitespace
# Y-axis — min near data floor to maximize visual resolution of the data range
chart.options.y_axis = {
"title": {"text": "Daily Visitors (count)", "style": {"fontSize": "48px"}},
"labels": {"style": {"fontSize": "36px"}},
"gridLineWidth": 1,
"gridLineColor": "rgba(0, 0, 0, 0.1)",
"min": 1000,
"min": 1500,
"startOnTick": False,
}

# Plot options with semi-transparent fill and gradient
chart.options.plot_options = {
"area": {
"fillColor": {
"linearGradient": {"x1": 0, "y1": 0, "x2": 0, "y2": 1},
"stops": [[0, "rgba(48, 105, 152, 0.5)"], [1, "rgba(48, 105, 152, 0.1)"]],
"stops": [[0, "rgba(48, 105, 152, 0.5)"], [1, "rgba(48, 105, 152, 0.05)"]],
},
"lineWidth": 4,
"marker": {"enabled": True, "radius": 8, "fillColor": "#306998"},
"marker": {"enabled": True, "radius": 6, "fillColor": "#306998"},
"color": "#306998",
"tooltip": {"headerFormat": "<b>Day {point.x}</b><br/>", "pointFormat": "Visitors: {point.y:,.0f}"},
}
}

# Legend
chart.options.legend = {"enabled": False}
# Legend — enabled with styling for single series identification
chart.options.legend = {
"enabled": True,
"itemStyle": {"fontSize": "36px", "fontWeight": "normal"},
"align": "right",
"verticalAlign": "top",
"layout": "horizontal",
"x": -40,
"y": 60,
}

# Credits off
chart.options.credits = {"enabled": False}

# Add series
series = AreaSeries()
Expand Down Expand Up @@ -111,22 +131,6 @@
f.write(html_content)
temp_path = f.name

# Also save HTML for interactive version
with open("plot.html", "w", encoding="utf-8") as f:
# For standalone HTML, use CDN link
standalone_html = f"""<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<script src="https://code.highcharts.com/highcharts.js"></script>
</head>
<body style="margin:0;">
<div id="container" style="width: 100%; height: 100vh;"></div>
<script>{html_str}</script>
</body>
</html>"""
f.write(standalone_html)

chrome_options = Options()
chrome_options.add_argument("--headless")
chrome_options.add_argument("--no-sandbox")
Expand Down
Loading