-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy patharchitecture-diagram.py
More file actions
262 lines (213 loc) · 12.1 KB
/
Copy patharchitecture-diagram.py
File metadata and controls
262 lines (213 loc) · 12.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
#!/usr/bin/env python3
"""Generate architecture diagram for Encore - AI Music Tutor"""
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
import matplotlib.patches as mpatches
from matplotlib.patches import FancyBboxPatch, FancyArrowPatch
import numpy as np
fig, ax = plt.subplots(1, 1, figsize=(20, 14))
ax.set_xlim(0, 20)
ax.set_ylim(0, 14)
ax.set_aspect('equal')
ax.axis('off')
fig.patch.set_facecolor('#0f172a')
# --- Color Palette ---
GOOGLE_BLUE = '#4285F4'
GOOGLE_RED = '#EA4335'
GOOGLE_YELLOW = '#FBBC05'
GOOGLE_GREEN = '#34A853'
FIREBASE_ORANGE = '#FF9100'
NEXT_BLACK = '#1a1a2e'
PYTHON_BLUE = '#306998'
CLIENT_BG = '#1e293b'
GOOGLE_BG = '#1a2744'
BACKEND_BG = '#1a2e1a'
EXTERNAL_BG = '#2e1a2e'
TEXT_WHITE = '#e2e8f0'
TEXT_LIGHT = '#94a3b8'
BORDER_SUBTLE = '#334155'
ACCENT_CYAN = '#22d3ee'
def draw_rounded_box(x, y, w, h, color, alpha=0.25, border_color=None, lw=1.5):
"""Draw a rounded rectangle"""
bc = border_color or color
box = FancyBboxPatch((x, y), w, h, boxstyle="round,pad=0.15",
facecolor=color, edgecolor=bc, linewidth=lw, alpha=alpha,
zorder=2)
ax.add_patch(box)
return box
def draw_component(x, y, w, h, label, sublabel=None, color=GOOGLE_BLUE, icon=None, fontsize=9):
"""Draw a component box with label"""
box = FancyBboxPatch((x, y), w, h, boxstyle="round,pad=0.08",
facecolor=color, edgecolor='white', linewidth=0.8, alpha=0.85,
zorder=5)
ax.add_patch(box)
cx, cy = x + w/2, y + h/2
if sublabel:
cy += 0.12
if icon:
ax.text(cx, cy + 0.05, icon, fontsize=fontsize+2, ha='center', va='center',
color='white', fontweight='bold', zorder=6)
cy -= 0.2
ax.text(cx, cy, label, fontsize=fontsize, ha='center', va='center',
color='white', fontweight='bold', zorder=6, fontfamily='sans-serif')
if sublabel:
ax.text(cx, cy - 0.28, sublabel, fontsize=6.5, ha='center', va='center',
color='#cbd5e1', zorder=6, fontfamily='sans-serif', style='italic')
def draw_arrow(x1, y1, x2, y2, color='white', style='->', lw=1.2, alpha=0.6):
"""Draw an arrow between points"""
ax.annotate('', xy=(x2, y2), xytext=(x1, y1),
arrowprops=dict(arrowstyle=style, color=color, lw=lw, alpha=alpha),
zorder=4)
def draw_label_arrow(x1, y1, x2, y2, label, color='white', lw=1.2, offset=(0, 0.12)):
"""Draw arrow with a label"""
draw_arrow(x1, y1, x2, y2, color=color, lw=lw)
mx, my = (x1+x2)/2 + offset[0], (y1+y2)/2 + offset[1]
ax.text(mx, my, label, fontsize=6, ha='center', va='center',
color=TEXT_LIGHT, zorder=7, fontfamily='sans-serif',
bbox=dict(boxstyle='round,pad=0.15', facecolor='#0f172a', edgecolor='none', alpha=0.8))
# ============================================================
# TITLE
# ============================================================
ax.text(10, 13.5, 'Encore — AI Music Tutor', fontsize=22, ha='center', va='center',
color='white', fontweight='bold', fontfamily='sans-serif', zorder=10)
ax.text(10, 13.1, 'System Architecture · Google APIs & SDKs highlighted',
fontsize=10, ha='center', va='center', color=TEXT_LIGHT, fontfamily='sans-serif', zorder=10)
# ============================================================
# SECTION: Google Cloud / AI (top-right, large)
# ============================================================
draw_rounded_box(10.5, 6.8, 9, 5.8, GOOGLE_BG, alpha=0.35, border_color=GOOGLE_BLUE, lw=2)
ax.text(15, 12.25, 'Google Cloud & AI', fontsize=13, ha='center', va='center',
color=GOOGLE_BLUE, fontweight='bold', fontfamily='sans-serif', zorder=6)
# Gemini Live API
draw_component(11.2, 10.5, 3.2, 1.1, 'Gemini Live API', 'gemini-2.5-flash-native-audio', color='#1a56db')
ax.text(12.8, 11.85, '★ Primary AI Engine', fontsize=7, ha='center', va='center',
color=GOOGLE_YELLOW, fontweight='bold', zorder=7)
# @google/genai SDK
draw_component(15.2, 10.5, 3.5, 1.1, '@google/genai SDK', 'v1.49.0 · TypeScript', color='#2563eb')
# Google ADK
draw_component(11.2, 8.8, 3.2, 1.1, 'Google ADK', 'Agent Development Kit · Python', color='#1e40af')
# Gemini model for ADK
draw_component(15.2, 8.8, 3.5, 1.1, 'gemini-2.0-flash-live', 'Backend Agent Model', color='#1e3a8a')
# Firebase section inside Google
draw_rounded_box(11, 7, 8.2, 1.5, '#3d1e00', alpha=0.4, border_color=FIREBASE_ORANGE, lw=1.5)
ax.text(11.5, 8.2, 'Firebase', fontsize=10, ha='left', va='center',
color=FIREBASE_ORANGE, fontweight='bold', fontfamily='sans-serif', zorder=6)
draw_component(11.3, 7.15, 1.8, 0.7, 'Auth', 'Anonymous', color='#c2410c', fontsize=8)
draw_component(13.3, 7.15, 1.9, 0.7, 'Firestore', 'Sessions DB', color='#c2410c', fontsize=8)
draw_component(15.4, 7.15, 2.0, 0.7, 'Cloud Storage', 'Audio/Video', color='#c2410c', fontsize=8)
draw_component(17.6, 7.15, 1.4, 0.7, 'Hosting', '', color='#c2410c', fontsize=8)
# ============================================================
# SECTION: Frontend (left side)
# ============================================================
draw_rounded_box(0.5, 6.8, 9.5, 5.8, CLIENT_BG, alpha=0.4, border_color=ACCENT_CYAN, lw=2)
ax.text(5.25, 12.25, 'Frontend · Next.js 15 + React 19', fontsize=13, ha='center', va='center',
color=ACCENT_CYAN, fontweight='bold', fontfamily='sans-serif', zorder=6)
# UI Components
draw_component(1, 10.8, 2.3, 0.85, 'Studio Page', 'Live Coaching UI', color='#0e7490')
draw_component(3.6, 10.8, 2.2, 0.85, 'Dog Avatar', 'Animated SVG Coach', color='#0e7490')
draw_component(6.1, 10.8, 1.8, 0.85, 'Transcript', 'Chat Display', color='#0e7490')
draw_component(8.2, 10.8, 1.5, 0.85, 'Waveform', 'Visualizer', color='#0e7490')
# Core libs
draw_component(1, 9.3, 2.8, 0.95, 'Gemini Live Client', 'WebSocket · Bidirectional Audio', color='#155e75')
draw_component(4.1, 9.3, 2.5, 0.95, 'Audio Manager', 'Mic Capture · PCM Codec', color='#155e75')
draw_component(6.9, 9.3, 2.8, 0.95, 'Session Context', 'State Machine · useReducer', color='#155e75')
# Services
draw_component(1, 7.7, 2.2, 0.85, 'Melody ID', 'Song Recognition', color='#164e63')
draw_component(3.5, 7.7, 2.5, 0.85, 'Sheet Music', 'IMSLP / Musescore', color='#164e63')
draw_component(6.3, 7.7, 1.8, 0.85, 'Scoring', 'Performance', color='#164e63')
draw_component(8.3, 7.7, 1.4, 0.85, 'History', 'localStorage', color='#164e63')
# PWA badge
ax.text(1.2, 7.05, 'PWA', fontsize=8, ha='center', va='center',
color=ACCENT_CYAN, fontweight='bold', zorder=6,
bbox=dict(boxstyle='round,pad=0.15', facecolor='#083344', edgecolor=ACCENT_CYAN, alpha=0.8))
# ============================================================
# SECTION: Backend (bottom-left)
# ============================================================
draw_rounded_box(0.5, 1.2, 9.5, 5, BACKEND_BG, alpha=0.35, border_color=GOOGLE_GREEN, lw=2)
ax.text(5.25, 5.85, 'Backend · FastAPI + Google ADK', fontsize=13, ha='center', va='center',
color=GOOGLE_GREEN, fontweight='bold', fontfamily='sans-serif', zorder=6)
draw_component(1, 4.3, 3.5, 1.0, 'FastAPI Server', 'REST API · CORS · uvicorn', color='#166534')
draw_component(5.2, 4.3, 4.2, 1.0, 'ADK Agent', 'google-adk · Tool Orchestration', color='#14532d')
# Tools
ax.text(5.25, 3.7, 'Agent Tools', fontsize=9, ha='center', va='center',
color=GOOGLE_GREEN, fontweight='bold', fontfamily='sans-serif', zorder=6)
draw_component(1, 2.5, 2.0, 0.75, 'identify_song()', '', color='#15803d', fontsize=7.5)
draw_component(3.2, 2.5, 2.5, 0.75, 'search_sheet_music()', '', color='#15803d', fontsize=7.5)
draw_component(5.9, 2.5, 2.3, 0.75, 'score_performance()', '', color='#15803d', fontsize=7.5)
draw_component(1, 1.5, 2.5, 0.75, 'get_practice_history()', '', color='#15803d', fontsize=7.5)
draw_component(3.7, 1.5, 2.7, 0.75, 'save_session_result()', '', color='#15803d', fontsize=7.5)
# Docker badge
ax.text(8.8, 1.6, 'Docker', fontsize=8, ha='center', va='center',
color=GOOGLE_GREEN, fontweight='bold', zorder=6,
bbox=dict(boxstyle='round,pad=0.15', facecolor='#052e16', edgecolor=GOOGLE_GREEN, alpha=0.8))
ax.text(8.8, 1.2, 'Cloud Run', fontsize=7, ha='center', va='center',
color=TEXT_LIGHT, zorder=6)
# ============================================================
# SECTION: External Music Services (bottom-right)
# ============================================================
draw_rounded_box(10.5, 1.2, 9, 5, EXTERNAL_BG, alpha=0.3, border_color='#a855f7', lw=2)
ax.text(15, 5.85, 'External Music Resources', fontsize=13, ha='center', va='center',
color='#a855f7', fontweight='bold', fontfamily='sans-serif', zorder=6)
draw_component(11.2, 4.3, 2.5, 1.0, 'IMSLP', 'Public Domain Scores', color='#6b21a8')
draw_component(14.2, 4.3, 2.8, 1.0, 'Musescore', 'Community Sheet Music', color='#6b21a8')
draw_component(17.5, 4.3, 1.8, 1.0, '8notes', 'Free Previews', color='#6b21a8')
# User's device
draw_rounded_box(11.2, 1.5, 7.5, 2.2, '#1e1b2e', alpha=0.4, border_color='#8b5cf6', lw=1)
ax.text(15, 3.35, "User's Device", fontsize=10, ha='center', va='center',
color='#8b5cf6', fontweight='bold', fontfamily='sans-serif', zorder=6)
draw_component(11.5, 1.7, 2.2, 0.85, 'Microphone', '16kHz PCM Audio', color='#581c87', fontsize=8)
draw_component(14, 1.7, 2.0, 0.85, 'Camera', 'Video Feed', color='#581c87', fontsize=8)
draw_component(16.3, 1.7, 2.2, 0.85, 'Speaker', '24kHz Playback', color='#581c87', fontsize=8)
# ============================================================
# ARROWS / DATA FLOW
# ============================================================
# Frontend Gemini Client -> Gemini Live API (WebSocket)
draw_label_arrow(4.0, 9.8, 11.2, 11.05, 'WebSocket\nBidirectional Audio', color=GOOGLE_YELLOW, lw=2, offset=(0, 0.3))
# Frontend Gemini Client uses @google/genai SDK
draw_arrow(4.0, 10.0, 15.2, 10.8, color=GOOGLE_BLUE, lw=1, style='->', alpha=0.4)
# Gemini Live -> ADK Agent (tool calls)
draw_label_arrow(12.8, 10.5, 12.8, 9.9, 'Function\nCalling', color=GOOGLE_BLUE, lw=1.5, offset=(0.8, 0))
# GenAI SDK -> ADK
draw_arrow(16.9, 10.5, 16.9, 9.9, color=GOOGLE_BLUE, lw=1, alpha=0.4)
# Frontend -> Backend (tool execution)
draw_label_arrow(5.25, 6.8, 5.25, 5.85, 'POST /tools/execute', color=GOOGLE_GREEN, lw=1.5, offset=(1.5, 0))
# Backend ADK -> Google ADK cloud
draw_label_arrow(7.5, 4.8, 11.2, 9.3, 'Agent\nOrchestration', color=GOOGLE_BLUE, lw=1.2, offset=(-0.5, 0.2))
# Backend -> Firebase
draw_label_arrow(7.5, 5.3, 14.2, 7.15, 'Read/Write\nSessions', color=FIREBASE_ORANGE, lw=1, offset=(0, 0.2))
# Backend tools -> External music
draw_label_arrow(4.45, 2.87, 11.2, 4.3, 'Search\nQueries', color='#a855f7', lw=1, offset=(0, 0.2))
# User device -> Frontend
draw_label_arrow(15, 3.35, 5.25, 6.8, 'Audio/Video\nStreams', color='#8b5cf6', lw=1.5, offset=(0, 0.25))
# Frontend -> Firebase Auth
draw_label_arrow(8.5, 7.2, 11.3, 7.5, 'Auth', color=FIREBASE_ORANGE, lw=1, offset=(0, 0.15))
# ============================================================
# LEGEND
# ============================================================
legend_y = 0.6
ax.text(1, legend_y, 'Legend:', fontsize=8, ha='left', va='center',
color=TEXT_WHITE, fontweight='bold', fontfamily='sans-serif', zorder=10)
legend_items = [
(GOOGLE_BLUE, 'Google AI / Cloud'),
(FIREBASE_ORANGE, 'Firebase Services'),
(ACCENT_CYAN, 'Frontend (Next.js)'),
(GOOGLE_GREEN, 'Backend (FastAPI)'),
('#a855f7', 'External Services'),
]
lx = 2.5
for color, label in legend_items:
box = FancyBboxPatch((lx, legend_y - 0.15), 0.3, 0.3, boxstyle="round,pad=0.03",
facecolor=color, edgecolor='white', linewidth=0.5, alpha=0.8, zorder=10)
ax.add_patch(box)
ax.text(lx + 0.5, legend_y, label, fontsize=7.5, ha='left', va='center',
color=TEXT_LIGHT, fontfamily='sans-serif', zorder=10)
lx += 2.8
# Google star
ax.text(17, legend_y, '★ = Google API/SDK', fontsize=8, ha='left', va='center',
color=GOOGLE_YELLOW, fontweight='bold', fontfamily='sans-serif', zorder=10)
# Save
plt.tight_layout(pad=0.5)
plt.savefig('/Users/timothy/dev/encore/.claude/worktrees/friendly-liskov/architecture-diagram.png',
dpi=200, facecolor='#0f172a', edgecolor='none', bbox_inches='tight')
print("Saved architecture-diagram.png")