Skip to content

Commit 197c9c9

Browse files
yuluo-yxrootfs
andauthored
docs: add mermaid modal (#288)
* docs: add mermaid modal Signed-off-by: yuluo-yx <[email protected]> * fix Signed-off-by: yuluo-yx <[email protected]> * fix Signed-off-by: yuluo-yx <[email protected]> * fix: fix lit Signed-off-by: yuluo-yx <[email protected]> * fix Signed-off-by: yuluo-yx <[email protected]> * fix Signed-off-by: yuluo-yx <[email protected]> * Fix the issue where the top scroll bar is not visible when the chart is enlarged. Signed-off-by: yuluo-yx <[email protected]> * fix lint Signed-off-by: yuluo-yx <[email protected]> --------- Signed-off-by: yuluo-yx <[email protected]> Co-authored-by: Huamin Chen <[email protected]>
1 parent 294a307 commit 197c9c9

File tree

3 files changed

+633
-21
lines changed

3 files changed

+633
-21
lines changed

website/docs/overview/architecture/system-architecture.md

Lines changed: 23 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,10 @@ The Semantic Router implements a sophisticated Mixture-of-Models (MoM) architect
44

55
## High-Level Architecture Overview
66

7-
```mermaid
8-
graph TB
7+
import ZoomableMermaid from '@site/src/components/ZoomableMermaid';
8+
9+
<ZoomableMermaid title="System Architecture Overview" defaultZoom={5.5}>
10+
{`graph TB
911
subgraph "Client Layer"
1012
Client1[Web Application]
1113
Client2[Mobile App]
@@ -62,8 +64,8 @@ graph TB
6264

6365
ExtProc --> Prometheus
6466
Prometheus --> Grafana
65-
ExtProc --> Logs
66-
```
67+
ExtProc --> Logs`}
68+
</ZoomableMermaid>
6769

6870
## Core Components
6971

@@ -113,7 +115,7 @@ http_filters:
113115
type OpenAIRouter struct {
114116
Config *config.RouterConfig
115117
CategoryDescriptions []string
116-
Classifier *classification.Classifier // ModernBERT-based
118+
Classifier *classification.Classifier // ModernBERT-based
117119
PIIChecker *pii.PolicyChecker // Privacy protection
118120
Cache *cache.SemanticCache // Performance optimization
119121
ToolsDatabase *tools.ToolsDatabase // Tool selection
@@ -125,8 +127,8 @@ type OpenAIRouter struct {
125127

126128
**Processing Pipeline**:
127129

128-
```mermaid
129-
sequenceDiagram
130+
<ZoomableMermaid title="Processing Pipeline" defaultZoom={3.5}>
131+
{`sequenceDiagram
130132
participant E as Envoy
131133
participant R as Router
132134
participant C as Classifier
@@ -152,17 +154,17 @@ sequenceDiagram
152154
E->>R: Response from model
153155
R->>Ca: Cache semantic representation
154156
R->>E: Final response
155-
end
156-
```
157+
end`}
158+
</ZoomableMermaid>
157159

158160
### 3. Classification System - Decision Engine
159161

160162
The classification system uses ModernBERT models for multiple classification tasks:
161163

162164
#### Category Classification
163165

164-
```mermaid
165-
graph LR
166+
<ZoomableMermaid title="Category Classification System" defaultZoom={4.5}>
167+
{`graph LR
166168
Query[User Query] --> Tokenizer[ModernBERT Tokenizer]
167169
Tokenizer --> Encoder[ModernBERT Encoder<br/>768-dim embeddings]
168170
Encoder --> ClassifierHead[Classification Head<br/>Category Prediction]
@@ -182,8 +184,8 @@ graph LR
182184
Decision --> Code
183185
Decision --> General
184186
Decision --> Science
185-
Decision --> Business
186-
```
187+
Decision --> Business`}
188+
</ZoomableMermaid>
187189

188190
#### Multi-Task Architecture
189191

@@ -262,8 +264,8 @@ graph TB
262264

263265
### Response Processing Flow
264266

265-
```mermaid
266-
sequenceDiagram
267+
<ZoomableMermaid title="Response Processing Flow" defaultZoom={4.5}>
268+
{`sequenceDiagram
267269
participant C as Client
268270
participant E as Envoy
269271
participant R as Router
@@ -285,8 +287,8 @@ sequenceDiagram
285287
R->>Me: Record routing metrics
286288

287289
R->>E: Processed Response
288-
E->>C: Final Response to Client
289-
```
290+
E->>C: Final Response to Client`}
291+
</ZoomableMermaid>
290292

291293
## Threading and Concurrency Model
292294

@@ -514,8 +516,8 @@ func (cb *CircuitBreaker) Call(operation func() error) error {
514516

515517
### Fallback Strategies
516518

517-
```mermaid
518-
graph TB
519+
<ZoomableMermaid title="Fallback Strategies" defaultZoom={2.5}>
520+
{`graph TB
519521
Request[Incoming Request] --> PrimaryRoute[Primary Routing Decision]
520522

521523
PrimaryRoute --> ModelA{Model A<br/>Available?}
@@ -534,8 +536,8 @@ graph TB
534536
ProcessA --> Success[Successful Response]
535537
ProcessB --> Success
536538
ProcessGeneral --> Success
537-
ReturnCached --> Success
538-
```
539+
ReturnCached --> Success`}
540+
</ZoomableMermaid>
539541

540542
## Monitoring and Observability
541543

Lines changed: 235 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,235 @@
1+
import React, { useState, useRef, useEffect, useCallback } from 'react'
2+
import { createPortal } from 'react-dom'
3+
import Mermaid from '@theme/Mermaid'
4+
import styles from './styles.module.css'
5+
6+
const ZoomableMermaid = ({ children, title, defaultZoom = 1.2 }) => {
7+
const [isModalOpen, setIsModalOpen] = useState(false)
8+
const [isHovered, setIsHovered] = useState(false)
9+
const [zoomLevel, setZoomLevel] = useState(defaultZoom) // Use defaultZoom prop
10+
const modalRef = useRef(null)
11+
const containerRef = useRef(null)
12+
13+
const openModal = useCallback(() => {
14+
setIsModalOpen(true)
15+
setZoomLevel(defaultZoom) // Reset to default zoom when opening
16+
document.body.style.overflow = 'hidden'
17+
}, [defaultZoom])
18+
19+
const closeModal = useCallback(() => {
20+
setIsModalOpen(false)
21+
document.body.style.overflow = 'unset'
22+
// Return focus to the original container
23+
if (containerRef.current) {
24+
containerRef.current.focus()
25+
}
26+
}, [])
27+
28+
const zoomIn = useCallback(() => {
29+
setZoomLevel(prev => Math.min(prev + 0.2, 5.0)) // Max 500%
30+
}, [])
31+
32+
const zoomOut = useCallback(() => {
33+
setZoomLevel(prev => Math.max(prev - 0.2, 0.5)) // Min 50%
34+
}, [])
35+
36+
const resetZoom = useCallback(() => {
37+
setZoomLevel(defaultZoom) // Reset to custom default instead of hardcoded 1.2
38+
}, [defaultZoom])
39+
40+
useEffect(() => {
41+
const handleEscape = (e) => {
42+
if (e.key === 'Escape' && isModalOpen) {
43+
closeModal()
44+
}
45+
}
46+
47+
const handleClickOutside = (e) => {
48+
if (modalRef.current && !modalRef.current.contains(e.target)) {
49+
closeModal()
50+
}
51+
}
52+
53+
const handleKeydown = (e) => {
54+
if (!isModalOpen) return
55+
56+
if (e.key === '=' || e.key === '+') {
57+
e.preventDefault()
58+
zoomIn()
59+
}
60+
else if (e.key === '-') {
61+
e.preventDefault()
62+
zoomOut()
63+
}
64+
else if (e.key === '0') {
65+
e.preventDefault()
66+
resetZoom()
67+
}
68+
}
69+
70+
if (isModalOpen) {
71+
document.addEventListener('keydown', handleEscape)
72+
document.addEventListener('mousedown', handleClickOutside)
73+
document.addEventListener('keydown', handleKeydown)
74+
75+
// Focus the modal content when opened
76+
setTimeout(() => {
77+
if (modalRef.current) {
78+
modalRef.current.focus()
79+
}
80+
}, 100)
81+
}
82+
83+
return () => {
84+
document.removeEventListener('keydown', handleEscape)
85+
document.removeEventListener('mousedown', handleClickOutside)
86+
document.removeEventListener('keydown', handleKeydown)
87+
}
88+
}, [isModalOpen, closeModal, zoomIn, zoomOut, resetZoom])
89+
90+
// Cleanup on unmount
91+
useEffect(() => {
92+
return () => {
93+
document.body.style.overflow = 'unset'
94+
}
95+
}, [])
96+
97+
const handleKeyDown = (e) => {
98+
if (e.key === 'Enter' || e.key === ' ') {
99+
e.preventDefault()
100+
openModal()
101+
}
102+
}
103+
104+
const modalContent = (
105+
<div
106+
className={styles.modal}
107+
role="dialog"
108+
aria-modal="true"
109+
aria-labelledby={title ? 'modal-title' : undefined}
110+
aria-describedby="modal-description"
111+
>
112+
<div
113+
className={styles.modalContent}
114+
ref={modalRef}
115+
tabIndex={-1}
116+
>
117+
<div className={styles.modalHeader}>
118+
{title && (
119+
<h3 id="modal-title" className={styles.modalTitle}>
120+
{title}
121+
</h3>
122+
)}
123+
<div className={styles.modalControls}>
124+
<span className={styles.zoomIndicator}>
125+
{Math.round(zoomLevel * 100)}
126+
%
127+
</span>
128+
<button
129+
className={styles.zoomButton}
130+
onClick={zoomOut}
131+
disabled={zoomLevel <= 0.5}
132+
aria-label="Reduce the size of the chart"
133+
type="button"
134+
title="Reduce (Shortcut key: -)"
135+
>
136+
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
137+
<circle cx="11" cy="11" r="8" />
138+
<path d="M8 11h6" />
139+
<path d="m21 21-4.35-4.35" />
140+
</svg>
141+
</button>
142+
<button
143+
className={styles.resetButton}
144+
onClick={resetZoom}
145+
aria-label={`Reset to default zoom level ${Math.round(defaultZoom * 100)}%`}
146+
type="button"
147+
title={`Reset to default zoom level ${Math.round(defaultZoom * 100)}% (Shortcut key: 0)`}
148+
>
149+
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
150+
<path d="M3 3l18 18" />
151+
<path d="m19 4-7 7-7-7" />
152+
<path d="m5 20 7-7 7 7" />
153+
</svg>
154+
</button>
155+
<button
156+
className={styles.zoomButton}
157+
onClick={zoomIn}
158+
disabled={zoomLevel >= 5.0}
159+
aria-label="Enlarge the chart"
160+
type="button"
161+
title="Enlarge (Shortcut key: +)"
162+
>
163+
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
164+
<circle cx="11" cy="11" r="8" />
165+
<path d="M8 11h6" />
166+
<path d="M11 8v6" />
167+
<path d="m21 21-4.35-4.35" />
168+
</svg>
169+
</button>
170+
<button
171+
className={styles.closeButton}
172+
onClick={closeModal}
173+
aria-label="Close the zoomed view"
174+
type="button"
175+
>
176+
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
177+
<line x1="18" y1="6" x2="6" y2="18" />
178+
<line x1="6" y1="6" x2="18" y2="18" />
179+
</svg>
180+
</button>
181+
</div>
182+
</div>
183+
<div
184+
className={styles.modalBody}
185+
id="modal-description"
186+
aria-label="Enlarged Mermaid diagram"
187+
>
188+
<div
189+
className={styles.diagramContainer}
190+
style={{
191+
transform: `scale(${zoomLevel})`,
192+
// Ensure scaling is from the center of the diagram.
193+
// Fix the issue where the top scroll bar is not visible when the chart is enlarged.
194+
transformOrigin: 'center top',
195+
}}
196+
>
197+
<Mermaid value={children} />
198+
</div>
199+
</div>
200+
</div>
201+
</div>
202+
)
203+
204+
return (
205+
<>
206+
<div
207+
ref={containerRef}
208+
className={`${styles.mermaidContainer} ${isHovered ? styles.hovered : ''}`}
209+
onClick={openModal}
210+
onMouseEnter={() => setIsHovered(true)}
211+
onMouseLeave={() => setIsHovered(false)}
212+
role="button"
213+
tabIndex={0}
214+
onKeyDown={handleKeyDown}
215+
aria-label={`Click to enlarge ${title || 'Mermaid diagram'}`}
216+
aria-expanded={isModalOpen}
217+
>
218+
<div className={styles.zoomHint} aria-hidden="true">
219+
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
220+
<circle cx="11" cy="11" r="8" />
221+
<path d="m21 21-4.35-4.35" />
222+
<path d="M11 8v6" />
223+
<path d="M8 11h6" />
224+
</svg>
225+
<span>Click to enlarge</span>
226+
</div>
227+
<Mermaid value={children} />
228+
</div>
229+
230+
{isModalOpen && createPortal(modalContent, document.body)}
231+
</>
232+
)
233+
}
234+
235+
export default ZoomableMermaid

0 commit comments

Comments
 (0)