EmpowerHer / docs /render_system_architecture_png.py
Disini Ruhansa Kodagoda Hettige
Update chatbot responses
23b0ad9
from PIL import Image, ImageDraw, ImageFont
WIDTH = 2200
HEIGHT = 1500
BG = "#f6f3ed"
TEXT = "#1d2430"
ACCENT = "#1f5f8b"
LINE = "#476072"
BORDER = "#d7c8b6"
LAYER_COLORS = {
"presentation": "#eaf4ff",
"application": "#fdf2e2",
"service": "#eef8ee",
"intelligence": "#f7eefb",
"data": "#fff8dc",
}
def get_font(size: int, bold: bool = False):
candidates = []
if bold:
candidates.extend(
[
"C:/Windows/Fonts/arialbd.ttf",
"C:/Windows/Fonts/calibrib.ttf",
"C:/Windows/Fonts/segoeuib.ttf",
]
)
candidates.extend(
[
"C:/Windows/Fonts/arial.ttf",
"C:/Windows/Fonts/calibri.ttf",
"C:/Windows/Fonts/segoeui.ttf",
]
)
for path in candidates:
try:
return ImageFont.truetype(path, size)
except OSError:
continue
return ImageFont.load_default()
TITLE_FONT = get_font(42, True)
SUBTITLE_FONT = get_font(24)
LAYER_FONT = get_font(28, True)
BOX_TITLE_FONT = get_font(23, True)
BOX_TEXT_FONT = get_font(20)
def wrap_text(draw, text, font, max_width):
words = text.split()
lines = []
current = ""
for word in words:
trial = word if not current else f"{current} {word}"
if draw.textbbox((0, 0), trial, font=font)[2] <= max_width:
current = trial
else:
if current:
lines.append(current)
current = word
if current:
lines.append(current)
return lines
def draw_box(draw, rect, title, body, fill):
x1, y1, x2, y2 = rect
draw.rounded_rectangle(rect, radius=22, fill=fill, outline=BORDER, width=3)
title_lines = wrap_text(draw, title, BOX_TITLE_FONT, x2 - x1 - 30)
body_lines = wrap_text(draw, body, BOX_TEXT_FONT, x2 - x1 - 34)
lines = [(line, BOX_TITLE_FONT) for line in title_lines] + [(line, BOX_TEXT_FONT) for line in body_lines]
heights = []
for line, font in lines:
bbox = draw.textbbox((0, 0), line, font=font)
heights.append((bbox[3] - bbox[1]) + 6)
total_h = sum(heights) - 6
y = y1 + ((y2 - y1) - total_h) / 2
for (line, font), h in zip(lines, heights):
bbox = draw.textbbox((0, 0), line, font=font)
w = bbox[2] - bbox[0]
x = x1 + ((x2 - x1) - w) / 2
draw.text((x, y), line, font=font, fill=TEXT)
y += h
def draw_layer(draw, rect, title, fill):
x1, y1, x2, y2 = rect
draw.rounded_rectangle(rect, radius=28, fill=fill, outline=BORDER, width=4)
draw.text((x1 + 24, y1 + 16), title, font=LAYER_FONT, fill=ACCENT)
def draw_arrow(draw, start, end, width=5):
draw.line([start, end], fill=LINE, width=width)
ex, ey = end
sx, sy = start
dx = ex - sx
dy = ey - sy
if abs(dx) >= abs(dy):
sign = 1 if dx >= 0 else -1
pts = [(ex, ey), (ex - 16 * sign, ey - 8), (ex - 16 * sign, ey + 8)]
else:
sign = 1 if dy >= 0 else -1
pts = [(ex, ey), (ex - 8, ey - 16 * sign), (ex + 8, ey - 16 * sign)]
draw.polygon(pts, fill=LINE)
def draw_poly_arrow(draw, points, width=5):
for start, end in zip(points, points[1:]):
draw.line([start, end], fill=LINE, width=width)
draw_arrow(draw, points[-2], points[-1], width)
def main():
img = Image.new("RGB", (WIDTH, HEIGHT), BG)
draw = ImageDraw.Draw(img)
draw.text((70, 40), "6.3 System Architecture Diagram", font=TITLE_FONT, fill=ACCENT)
draw.text((72, 95), "High-level layered architecture of the EmpowerHer prototype", font=SUBTITLE_FONT, fill=TEXT)
layers = {
"presentation": (120, 170, 2080, 360),
"application": (120, 395, 2080, 600),
"service": (120, 635, 2080, 870),
"intelligence": (120, 905, 2080, 1160),
"data": (120, 1190, 2080, 1420),
}
draw_layer(draw, layers["presentation"], "Presentation Layer", LAYER_COLORS["presentation"])
draw_layer(draw, layers["application"], "Application Layer", LAYER_COLORS["application"])
draw_layer(draw, layers["service"], "Service / Business Logic Layer", LAYER_COLORS["service"])
draw_layer(draw, layers["intelligence"], "Intelligence Layer", LAYER_COLORS["intelligence"])
draw_layer(draw, layers["data"], "Data / Knowledge Layer", LAYER_COLORS["data"])
boxes = {
"user": (160, 225, 520, 325),
"frontend": (720, 215, 1080, 330),
"api": (540, 445, 900, 560),
"static": (1220, 445, 1580, 560),
"chat": (330, 700, 740, 820),
"rules": (1020, 690, 1590, 830),
"emotion": (180, 965, 560, 1095),
"llm": (700, 965, 1080, 1095),
"retriever": (1220, 955, 1640, 1105),
"kb": (330, 1255, 760, 1365),
"history": (1040, 1255, 1470, 1365),
}
draw_box(draw, boxes["user"], "End User", "Teenage girl using the chatbot through a browser", "#fffdf8")
draw_box(draw, boxes["frontend"], "React Frontend", "Chat interface for sending messages and showing replies", "#fffdf8")
draw_box(draw, boxes["api"], "Flask Web App / API", "Serves the system and exposes the /chat endpoint", "#fffdf8")
draw_box(draw, boxes["static"], "Static Asset Serving", "Delivers the built frontend from the backend container", "#fffdf8")
draw_box(draw, boxes["chat"], "ChatService", "Central orchestration of the chatbot request pipeline", "#fffdf8")
draw_box(draw, boxes["rules"], "Rule-Based Logic", "Intent detection, topic detection, safety checks, follow-up handling, templates", "#fffdf8")
draw_box(draw, boxes["emotion"], "Emotion Classifier", "GoEmotions RoBERTa for emotion detection", "#fffdf8")
draw_box(draw, boxes["llm"], "Response Generator", "Flan-T5 model for empathetic response generation", "#fffdf8")
draw_box(draw, boxes["retriever"], "KnowledgeBaseRetriever", "Searches local documents using TF-IDF or embeddings", "#fffdf8")
draw_box(draw, boxes["kb"], "Local Knowledge Base", "Menstrual-health guidance stored as local text files", "#fffdf8")
draw_box(draw, boxes["history"], "Conversation History", "Prior messages used for follow-up context", "#fffdf8")
draw_arrow(draw, (520, 275), (720, 275))
draw_poly_arrow(draw, [(900, 330), (900, 390), (720, 390), (720, 445)])
draw_poly_arrow(draw, [(1080, 275), (1400, 275), (1400, 445)])
draw_arrow(draw, (720, 560), (720, 700))
draw_poly_arrow(draw, [(900, 505), (1020, 505), (1020, 740)])
draw_poly_arrow(draw, [(535, 820), (535, 905), (370, 905), (370, 965)])
draw_poly_arrow(draw, [(740, 760), (860, 760), (860, 965)])
draw_poly_arrow(draw, [(1380, 830), (1380, 905), (1430, 905), (1430, 955)])
draw_poly_arrow(draw, [(1300, 830), (1300, 1190), (1255, 1190), (1255, 1255)])
draw_poly_arrow(draw, [(1430, 1105), (1430, 1190), (545, 1190), (545, 1255)])
draw_poly_arrow(draw, [(370, 1095), (370, 1135), (535, 1135), (535, 820)])
draw_poly_arrow(draw, [(890, 1095), (890, 1135), (535, 1135), (535, 820)])
draw_poly_arrow(draw, [(1430, 1105), (1430, 1135), (535, 1135), (535, 820)])
draw_poly_arrow(draw, [(740, 760), (900, 760), (900, 505)])
draw_poly_arrow(draw, [(1580, 505), (1680, 505), (1680, 275), (1080, 275)])
out_path = "d:/FYP IMPLEMENTATION/EmpowerHer_Chatbot/docs/system_architecture_diagram.png"
img.save(out_path, format="PNG")
print(out_path)
if __name__ == "__main__":
main()