335 lines
12 KiB
Python
335 lines
12 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
SVG Rebuilder - Converts all SVG files to match the style guide standards
|
|
Following the guidelines from botserver/prompts/dev/svg-diagram-style-guide.md
|
|
"""
|
|
|
|
import os
|
|
import re
|
|
from pathlib import Path
|
|
from typing import Dict, List, Tuple
|
|
|
|
# Style guide constants
|
|
COLORS = {
|
|
"blue": "#4A90E2", # Input/User elements, External/API
|
|
"orange": "#F5A623", # Processing/Scripts, Storage/Data
|
|
"purple": "#BD10E0", # AI/ML/Decision
|
|
"green": "#7ED321", # Execution/Action
|
|
"cyan": "#50E3C2", # Output/Response
|
|
"gray": "#666", # Arrows/text
|
|
"dark": "#333", # Labels
|
|
}
|
|
|
|
SVG_TEMPLATE = """<svg width="800" height="{height}" xmlns="http://www.w3.org/2000/svg">
|
|
<defs>
|
|
<marker id="arrow" markerWidth="10" markerHeight="10" refX="9" refY="3" orient="auto" markerUnits="strokeWidth">
|
|
<path d="M0,0 L0,6 L9,3 z" fill="#666"/>
|
|
</marker>
|
|
</defs>
|
|
|
|
<!-- Title -->
|
|
<text x="400" y="25" text-anchor="middle" font-family="Arial, sans-serif" font-size="16" font-weight="600" fill="#333">{title}</text>
|
|
|
|
{content}
|
|
|
|
<!-- Description -->
|
|
<text x="400" y="{desc_y}" text-anchor="middle" font-family="Arial, sans-serif" font-size="12" fill="#666">
|
|
{description}
|
|
</text>
|
|
</svg>"""
|
|
|
|
|
|
def create_box(x: int, y: int, width: int, height: int, color: str, label: str) -> str:
|
|
"""Create a standard box component"""
|
|
center_x = x + width // 2
|
|
center_y = y + height // 2 + 5
|
|
return f'''<rect x="{x}" y="{y}" width="{width}" height="{height}" fill="none" stroke="{color}" stroke-width="2" rx="5"/>
|
|
<text x="{center_x}" y="{center_y}" text-anchor="middle" font-family="Arial, sans-serif" font-size="14" fill="#333">{label}</text>'''
|
|
|
|
|
|
def create_arrow(
|
|
x1: int, y1: int, x2: int, y2: int, dashed: bool = False, opacity: float = 1.0
|
|
) -> str:
|
|
"""Create an arrow between two points"""
|
|
dash_attr = ' stroke-dasharray="3,3"' if dashed else ""
|
|
opacity_attr = f' opacity="{opacity}"' if opacity < 1.0 else ""
|
|
return f'<line x1="{x1}" y1="{y1}" x2="{x2}" y2="{y2}" marker-end="url(#arrow)"{dash_attr}{opacity_attr}/>'
|
|
|
|
|
|
def create_curved_arrow(
|
|
points: List[Tuple[int, int]], dashed: bool = False, opacity: float = 1.0
|
|
) -> str:
|
|
"""Create a curved arrow path"""
|
|
dash_attr = ' stroke-dasharray="3,3"' if dashed else ""
|
|
opacity_attr = f' opacity="{opacity}"' if opacity < 1.0 else ""
|
|
|
|
if len(points) < 3:
|
|
return ""
|
|
|
|
path = f"M{points[0][0]},{points[0][1]}"
|
|
if len(points) == 3:
|
|
path += f" Q{points[1][0]},{points[1][1]} {points[2][0]},{points[2][1]}"
|
|
else:
|
|
for i in range(1, len(points)):
|
|
path += f" L{points[i][0]},{points[i][1]}"
|
|
|
|
return f'<path d="{path}" marker-end="url(#arrow)"{dash_attr}{opacity_attr}/>'
|
|
|
|
|
|
def rebuild_conversation_flow() -> str:
|
|
"""Rebuild conversation flow diagram"""
|
|
boxes = []
|
|
arrows = []
|
|
|
|
# Main flow boxes
|
|
boxes.append(create_box(20, 60, 100, 40, COLORS["blue"], "User Input"))
|
|
boxes.append(create_box(160, 60, 100, 40, COLORS["orange"], "ASIC Script"))
|
|
boxes.append(create_box(300, 60, 100, 40, COLORS["purple"], "LM Decision"))
|
|
boxes.append(create_box(440, 60, 100, 40, COLORS["green"], "Bot Executor"))
|
|
boxes.append(create_box(580, 60, 100, 40, COLORS["cyan"], "Bot Response"))
|
|
|
|
# Parallel processes
|
|
boxes.append(create_box(360, 160, 120, 40, COLORS["blue"], "Search Knowledge"))
|
|
boxes.append(create_box(500, 160, 100, 40, COLORS["orange"], "Call API"))
|
|
|
|
# Main flow arrows
|
|
arrows.append(create_arrow(120, 80, 160, 80))
|
|
arrows.append(create_arrow(260, 80, 300, 80))
|
|
arrows.append(create_arrow(400, 80, 440, 80))
|
|
arrows.append(create_arrow(540, 80, 580, 80))
|
|
|
|
# Branch arrows
|
|
arrows.append(create_arrow(490, 100, 420, 160, dashed=True, opacity=0.6))
|
|
arrows.append(create_arrow(490, 100, 550, 160, dashed=True, opacity=0.6))
|
|
|
|
# Feedback loops
|
|
arrows.append(
|
|
create_curved_arrow(
|
|
[(420, 200), (420, 240), (630, 240), (630, 100)], dashed=True, opacity=0.4
|
|
)
|
|
)
|
|
arrows.append(
|
|
create_curved_arrow(
|
|
[(550, 200), (550, 230), (620, 230), (620, 100)], dashed=True, opacity=0.4
|
|
)
|
|
)
|
|
|
|
content = (
|
|
"\n ".join(boxes)
|
|
+ '\n\n <g stroke="#666" stroke-width="2" fill="none">\n '
|
|
+ "\n ".join(arrows)
|
|
+ "\n </g>"
|
|
)
|
|
|
|
return SVG_TEMPLATE.format(
|
|
height=320,
|
|
title="The Flow",
|
|
content=content,
|
|
desc_y=300,
|
|
description="The AI handles everything else - understanding intent, collecting information, executing tools, answering from documents. Zero configuration.",
|
|
)
|
|
|
|
|
|
def rebuild_architecture() -> str:
|
|
"""Rebuild architecture diagram"""
|
|
boxes = []
|
|
arrows = []
|
|
|
|
# Top layer
|
|
boxes.append(create_box(20, 60, 100, 40, COLORS["blue"], "Web Server"))
|
|
boxes.append(create_box(160, 60, 120, 40, COLORS["orange"], "BASIC Interpreter"))
|
|
boxes.append(create_box(320, 60, 100, 40, COLORS["purple"], "LLM Integration"))
|
|
boxes.append(create_box(460, 60, 120, 40, COLORS["green"], "Package Manager"))
|
|
boxes.append(create_box(620, 60, 100, 40, COLORS["cyan"], "Console UI"))
|
|
|
|
# Middle layer
|
|
boxes.append(
|
|
create_box(
|
|
250, 160, 300, 40, COLORS["blue"], "Session Manager (Tokio Async Runtime)"
|
|
)
|
|
)
|
|
|
|
# Data layer
|
|
boxes.append(create_box(20, 260, 100, 40, COLORS["orange"], "PostgreSQL"))
|
|
boxes.append(create_box(160, 260, 100, 40, COLORS["purple"], "Valkey Cache"))
|
|
boxes.append(create_box(300, 260, 100, 40, COLORS["green"], "Qdrant Vectors"))
|
|
boxes.append(create_box(440, 260, 100, 40, COLORS["cyan"], "Object Storage"))
|
|
boxes.append(create_box(580, 260, 100, 40, COLORS["blue"], "Channels"))
|
|
boxes.append(create_box(700, 260, 80, 40, COLORS["orange"], "External API"))
|
|
|
|
# Connection arrows (simplified)
|
|
for x in [70, 220, 370, 520, 670]:
|
|
arrows.append(
|
|
create_curved_arrow(
|
|
[(x, 100), (x, 130), (400, 130), (400, 160)], opacity=0.6
|
|
)
|
|
)
|
|
|
|
for x in [70, 210, 350, 490, 630]:
|
|
arrows.append(create_arrow(400, 200, x, 260, opacity=0.6))
|
|
|
|
# External API connection
|
|
arrows.append(
|
|
create_curved_arrow(
|
|
[(740, 260), (740, 220), (550, 180)], dashed=True, opacity=0.4
|
|
)
|
|
)
|
|
|
|
content = (
|
|
"\n ".join(boxes)
|
|
+ '\n\n <g stroke="#666" stroke-width="2" fill="none">\n '
|
|
+ "\n ".join(arrows)
|
|
+ "\n </g>"
|
|
)
|
|
|
|
# Add storage detail box
|
|
detail_box = """
|
|
<g transform="translate(20, 330)">
|
|
<rect width="760" height="50" fill="none" stroke="#666" stroke-width="1" rx="5" opacity="0.3"/>
|
|
<text x="10" y="25" font-family="Arial, sans-serif" font-size="12" fill="#666">Storage Contents:</text>
|
|
<text x="130" y="25" font-family="Arial, sans-serif" font-size="12" fill="#666">.gbkb (Documents)</text>
|
|
<text x="280" y="25" font-family="Arial, sans-serif" font-size="12" fill="#666">.gbdialog (Scripts)</text>
|
|
<text x="430" y="25" font-family="Arial, sans-serif" font-size="12" fill="#666">.gbot (Configs)</text>
|
|
<text x="560" y="25" font-family="Arial, sans-serif" font-size="12" fill="#666">Templates</text>
|
|
<text x="660" y="25" font-family="Arial, sans-serif" font-size="12" fill="#666">User Assets</text>
|
|
</g>"""
|
|
|
|
content += detail_box
|
|
|
|
return SVG_TEMPLATE.format(
|
|
height=400,
|
|
title="General Bots Architecture",
|
|
content=content,
|
|
desc_y=45,
|
|
description="Single binary with everything included - no external dependencies",
|
|
)
|
|
|
|
|
|
def rebuild_package_system_flow() -> str:
|
|
"""Rebuild package system flow diagram"""
|
|
boxes = []
|
|
arrows = []
|
|
|
|
# Main flow
|
|
boxes.append(create_box(20, 60, 100, 40, COLORS["blue"], "User Request"))
|
|
boxes.append(create_box(160, 60, 100, 40, COLORS["orange"], "start.bas"))
|
|
boxes.append(create_box(300, 60, 100, 40, COLORS["purple"], "LLM Engine"))
|
|
boxes.append(create_box(440, 60, 100, 40, COLORS["cyan"], "Bot Response"))
|
|
|
|
# Supporting components
|
|
boxes.append(create_box(240, 160, 120, 40, COLORS["blue"], "Vector Search"))
|
|
boxes.append(create_box(240, 240, 120, 40, COLORS["orange"], ".gbkb docs"))
|
|
|
|
# Main flow arrows
|
|
arrows.append(create_arrow(120, 80, 160, 80))
|
|
arrows.append(create_arrow(260, 80, 300, 80))
|
|
arrows.append(create_arrow(400, 80, 440, 80))
|
|
|
|
# Bidirectional between start.bas and LLM
|
|
arrows.append(
|
|
create_curved_arrow(
|
|
[(210, 100), (210, 120), (300, 120), (350, 120), (350, 100)],
|
|
dashed=True,
|
|
opacity=0.6,
|
|
)
|
|
)
|
|
arrows.append(
|
|
create_curved_arrow(
|
|
[(350, 60), (350, 40), (260, 40), (210, 40), (210, 60)],
|
|
dashed=True,
|
|
opacity=0.6,
|
|
)
|
|
)
|
|
|
|
# LLM to Vector Search
|
|
arrows.append(create_arrow(350, 100, 300, 160, opacity=0.6))
|
|
|
|
# Vector Search to .gbkb docs
|
|
arrows.append(create_arrow(300, 200, 300, 240, opacity=0.6))
|
|
|
|
# Feedback from Vector Search to LLM
|
|
arrows.append(
|
|
create_curved_arrow(
|
|
[(240, 180), (200, 140), (300, 100)], dashed=True, opacity=0.4
|
|
)
|
|
)
|
|
|
|
content = (
|
|
"\n ".join(boxes)
|
|
+ '\n\n <g stroke="#666" stroke-width="2" fill="none">\n '
|
|
+ "\n ".join(arrows)
|
|
+ "\n </g>"
|
|
)
|
|
|
|
# Add BASIC commands and package structure boxes
|
|
detail_boxes = """
|
|
<g transform="translate(580, 60)">
|
|
<rect width="200" height="120" fill="none" stroke="#7ED321" stroke-width="2" rx="5"/>
|
|
<text x="100" y="25" text-anchor="middle" font-family="Arial, sans-serif" font-size="14" fill="#333">BASIC Commands</text>
|
|
<text x="10" y="50" font-family="monospace" font-size="12" fill="#666">USE KB "docs"</text>
|
|
<text x="10" y="70" font-family="monospace" font-size="12" fill="#666">answer = HEAR</text>
|
|
<text x="10" y="90" font-family="monospace" font-size="12" fill="#666">result = LLM()</text>
|
|
<text x="10" y="110" font-family="monospace" font-size="12" fill="#666">TALK result</text>
|
|
</g>
|
|
|
|
<g transform="translate(580, 210)">
|
|
<rect width="200" height="140" fill="none" stroke="#4A90E2" stroke-width="2" rx="5"/>
|
|
<text x="100" y="25" text-anchor="middle" font-family="Arial, sans-serif" font-size="14" fill="#333">Package Structure</text>
|
|
<text x="10" y="50" font-family="monospace" font-size="12" fill="#666">my-bot.gbai/</text>
|
|
<text x="20" y="70" font-family="monospace" font-size="12" fill="#666">├─ .gbdialog/</text>
|
|
<text x="20" y="90" font-family="monospace" font-size="12" fill="#666">├─ .gbkb/</text>
|
|
<text x="20" y="110" font-family="monospace" font-size="12" fill="#666">└─ .gbot/</text>
|
|
</g>"""
|
|
|
|
content += detail_boxes
|
|
|
|
# Add connection lines to detail boxes
|
|
content += """
|
|
<g stroke="#666" stroke-width="2" fill="none">
|
|
<path d="M210,60 Q395,20 580,80" stroke-dasharray="2,2" opacity="0.3"/>
|
|
<path d="M300,280 Q440,330 580,310" stroke-dasharray="2,2" opacity="0.3"/>
|
|
</g>"""
|
|
|
|
# Add labels
|
|
labels = """
|
|
<text x="180" y="35" font-family="Arial, sans-serif" font-size="11" fill="#666">Commands</text>
|
|
<text x="180" y="125" font-family="Arial, sans-serif" font-size="11" fill="#666">Results</text>
|
|
<text x="325" y="135" font-family="Arial, sans-serif" font-size="11" fill="#666">Query</text>
|
|
<text x="250" y="135" font-family="Arial, sans-serif" font-size="11" fill="#666">Context</text>"""
|
|
|
|
content += labels
|
|
|
|
return SVG_TEMPLATE.format(
|
|
height=400,
|
|
title="Package System Flow",
|
|
content=content,
|
|
desc_y=380,
|
|
description="BASIC scripts orchestrate LLM decisions, vector search, and responses with zero configuration",
|
|
)
|
|
|
|
|
|
def main():
|
|
"""Main function to rebuild all SVGs"""
|
|
svgs_to_rebuild = {
|
|
"docs/src/assets/conversation-flow.svg": rebuild_conversation_flow(),
|
|
"docs/src/assets/architecture.svg": rebuild_architecture(),
|
|
"docs/src/assets/package-system-flow.svg": rebuild_package_system_flow(),
|
|
}
|
|
|
|
for filepath, content in svgs_to_rebuild.items():
|
|
full_path = Path(filepath)
|
|
if full_path.parent.exists():
|
|
with open(full_path, "w") as f:
|
|
f.write(content)
|
|
print(f"Rebuilt: {filepath}")
|
|
else:
|
|
print(f"Skipping (directory not found): {filepath}")
|
|
|
|
print(f"\nRebuilt {len(svgs_to_rebuild)} SVG files according to style guide")
|
|
print(
|
|
"Note: This is a demonstration script. Extend it to rebuild all 28 SVG files."
|
|
)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|