From 19e17ee410fd63b582337070fa0bff44ab425509 Mon Sep 17 00:00:00 2001 From: yuluo-yx Date: Mon, 29 Sep 2025 20:52:01 +0800 Subject: [PATCH 1/8] docs: add mermaid modal Signed-off-by: yuluo-yx --- website/docs/mermaid-enhancement-guide.md | 63 +++++ .../architecture/system-architecture.md | 52 ++-- .../src/components/ZoomableMermaid/index.js | 141 ++++++++++ .../ZoomableMermaid/styles.module.css | 251 ++++++++++++++++++ 4 files changed, 482 insertions(+), 25 deletions(-) create mode 100644 website/docs/mermaid-enhancement-guide.md create mode 100644 website/src/components/ZoomableMermaid/index.js create mode 100644 website/src/components/ZoomableMermaid/styles.module.css diff --git a/website/docs/mermaid-enhancement-guide.md b/website/docs/mermaid-enhancement-guide.md new file mode 100644 index 00000000..85d53ed9 --- /dev/null +++ b/website/docs/mermaid-enhancement-guide.md @@ -0,0 +1,63 @@ +# 可放大 Mermaid 图表组件使用指南 + +## 概述 + +`ZoomableMermaid` 组件为 Docusaurus 网站提供了可点击放大的 Mermaid 图表功能。用户可以点击图表在模态框中查看放大版本,提供更好的阅读体验。 + +## 使用方法 + +### 基本用法 + +在任何 Markdown 文档中,首先导入组件: + +```jsx +import ZoomableMermaid from '@site/src/components/ZoomableMermaid'; +``` + +然后使用组件包装 Mermaid 图表代码: + +```jsx + +{`graph TB + A[开始] --> B[处理] + B --> C[结束]`} + +``` + +### 参数说明 + +- `children` (string, 必需): Mermaid 图表的代码字符串 +- `title` (string, 可选): 图表标题,会显示在模态框顶部 + +### 功能特性 + +- **点击放大**: 点击图表在模态框中查看放大版本 +- **键盘支持**: 支持 Enter 和空格键打开模态框,Escape 键关闭 +- **无障碍访问**: 完整的 ARIA 属性支持 +- **响应式设计**: 在各种屏幕尺寸下都能正常工作 +- **主题支持**: 自动适应明暗主题 +- **动画效果**: 平滑的打开/关闭动画 + +### 示例 + + +{`graph LR + A[用户访问] --> B{检查权限} + B -->|有权限| C[显示内容] + B -->|无权限| D[显示错误]`} + + +## 实现原理 + +组件基于以下技术栈: + +1. **React Hooks**: 使用 `useState`、`useRef` 和 `useEffect` 管理状态 +2. **CSS Modules**: 样式隔离和主题支持 +3. **事件处理**: 键盘、鼠标和触摸事件 +4. **无障碍访问**: ARIA 属性和焦点管理 + +## 注意事项 + +- 确保 Mermaid 代码字符串使用模板字符串语法 (backticks) +- 在 JSX 中使用时,需要将代码包装在 `{``}` 中 +- 组件会自动处理焦点管理和页面滚动 \ No newline at end of file diff --git a/website/docs/overview/architecture/system-architecture.md b/website/docs/overview/architecture/system-architecture.md index 71420ed6..2193011f 100644 --- a/website/docs/overview/architecture/system-architecture.md +++ b/website/docs/overview/architecture/system-architecture.md @@ -4,8 +4,10 @@ The Semantic Router implements a sophisticated Mixture-of-Models (MoM) architect ## High-Level Architecture Overview -```mermaid -graph TB +import ZoomableMermaid from '@site/src/components/ZoomableMermaid'; + + +{`graph TB subgraph "Client Layer" Client1[Web Application] Client2[Mobile App] @@ -62,8 +64,8 @@ graph TB ExtProc --> Prometheus Prometheus --> Grafana - ExtProc --> Logs -``` + ExtProc --> Logs`} + ## Core Components @@ -113,7 +115,7 @@ http_filters: type OpenAIRouter struct { Config *config.RouterConfig CategoryDescriptions []string - Classifier *classification.Classifier // ModernBERT-based + Classifier *classification.Classifier // ModernBERT-based PIIChecker *pii.PolicyChecker // Privacy protection Cache *cache.SemanticCache // Performance optimization ToolsDatabase *tools.ToolsDatabase // Tool selection @@ -125,8 +127,8 @@ type OpenAIRouter struct { **Processing Pipeline**: -```mermaid -sequenceDiagram + +{`sequenceDiagram participant E as Envoy participant R as Router participant C as Classifier @@ -152,8 +154,8 @@ sequenceDiagram E->>R: Response from model R->>Ca: Cache semantic representation R->>E: Final response - end -``` + end`} + ### 3. Classification System - Decision Engine @@ -161,8 +163,8 @@ The classification system uses ModernBERT models for multiple classification tas #### Category Classification -```mermaid -graph LR + +{`graph LR Query[User Query] --> Tokenizer[ModernBERT Tokenizer] Tokenizer --> Encoder[ModernBERT Encoder
768-dim embeddings] Encoder --> ClassifierHead[Classification Head
Category Prediction] @@ -182,8 +184,8 @@ graph LR Decision --> Code Decision --> General Decision --> Science - Decision --> Business -``` + Decision --> Business`} +
#### Multi-Task Architecture @@ -214,8 +216,8 @@ class SemanticRouter: ### Request Processing Flow -```mermaid -graph TB + +{`graph TB Start([Client Request]) --> EnvoyReceive[Envoy Receives Request] EnvoyReceive --> ExtProcSend[Send to ExtProc
Headers + Body] @@ -257,13 +259,13 @@ graph TB style JailbreakCheck fill:#f44336 style CategoryClassification fill:#4caf50 style CacheCheck fill:#2196f3 - style RoutingDecision fill:#9c27b0 -``` + style RoutingDecision fill:#9c27b0`} +
### Response Processing Flow -```mermaid -sequenceDiagram + +{`sequenceDiagram participant C as Client participant E as Envoy participant R as Router @@ -285,8 +287,8 @@ sequenceDiagram R->>Me: Record routing metrics R->>E: Processed Response - E->>C: Final Response to Client -``` + E->>C: Final Response to Client`} + ## Threading and Concurrency Model @@ -514,8 +516,8 @@ func (cb *CircuitBreaker) Call(operation func() error) error { ### Fallback Strategies -```mermaid -graph TB + +{`graph TB Request[Incoming Request] --> PrimaryRoute[Primary Routing Decision] PrimaryRoute --> ModelA{Model A
Available?} @@ -534,8 +536,8 @@ graph TB ProcessA --> Success[Successful Response] ProcessB --> Success ProcessGeneral --> Success - ReturnCached --> Success -``` + ReturnCached --> Success`} +
## Monitoring and Observability diff --git a/website/src/components/ZoomableMermaid/index.js b/website/src/components/ZoomableMermaid/index.js new file mode 100644 index 00000000..5a1ccff0 --- /dev/null +++ b/website/src/components/ZoomableMermaid/index.js @@ -0,0 +1,141 @@ +import React, { useState, useRef, useEffect, useCallback } from 'react'; +import Mermaid from '@theme/Mermaid'; +import styles from './styles.module.css'; + +const ZoomableMermaid = ({ children, title }) => { + const [isModalOpen, setIsModalOpen] = useState(false); + const [isHovered, setIsHovered] = useState(false); + const modalRef = useRef(null); + const containerRef = useRef(null); + + const openModal = useCallback(() => { + setIsModalOpen(true); + document.body.style.overflow = 'hidden'; + }, []); + + const closeModal = useCallback(() => { + setIsModalOpen(false); + document.body.style.overflow = 'unset'; + // Return focus to the original container + if (containerRef.current) { + containerRef.current.focus(); + } + }, []); + + useEffect(() => { + const handleEscape = (e) => { + if (e.key === 'Escape' && isModalOpen) { + closeModal(); + } + }; + + const handleClickOutside = (e) => { + if (modalRef.current && !modalRef.current.contains(e.target)) { + closeModal(); + } + }; + + if (isModalOpen) { + document.addEventListener('keydown', handleEscape); + document.addEventListener('mousedown', handleClickOutside); + + // Focus the modal content when opened + setTimeout(() => { + if (modalRef.current) { + modalRef.current.focus(); + } + }, 100); + } + + return () => { + document.removeEventListener('keydown', handleEscape); + document.removeEventListener('mousedown', handleClickOutside); + }; + }, [isModalOpen, closeModal]); + + // Cleanup on unmount + useEffect(() => { + return () => { + document.body.style.overflow = 'unset'; + }; + }, []); + + const handleKeyDown = (e) => { + if (e.key === 'Enter' || e.key === ' ') { + e.preventDefault(); + openModal(); + } + }; + + return ( + <> +
setIsHovered(true)} + onMouseLeave={() => setIsHovered(false)} + role="button" + tabIndex={0} + onKeyDown={handleKeyDown} + aria-label={`点击放大查看 ${title || 'Mermaid 图表'}`} + aria-expanded={isModalOpen} + > + + +
+ + {isModalOpen && ( +
+
+
+ {title && ( + + )} + +
+ +
+
+ )} + + ); +}; + +export default ZoomableMermaid; \ No newline at end of file diff --git a/website/src/components/ZoomableMermaid/styles.module.css b/website/src/components/ZoomableMermaid/styles.module.css new file mode 100644 index 00000000..466e5750 --- /dev/null +++ b/website/src/components/ZoomableMermaid/styles.module.css @@ -0,0 +1,251 @@ +.mermaidContainer { + position: relative; + cursor: pointer; + border-radius: 12px; + overflow: hidden; + transition: all 0.3s ease; + background: var(--tech-card-bg); + border: 1px solid var(--tech-border); + box-shadow: var(--tech-shadow); +} + +.mermaidContainer:hover { + transform: translateY(-2px); + box-shadow: 0 12px 32px rgba(9, 105, 218, 0.15); + border-color: var(--tech-border-accent); +} + +.mermaidContainer:focus { + outline: 2px solid var(--tech-primary-blue); + outline-offset: 2px; +} + +.zoomHint { + position: absolute; + top: 12px; + right: 12px; + background: rgba(9, 105, 218, 0.9); + color: white; + padding: 6px 10px; + border-radius: 6px; + font-size: 12px; + display: flex; + align-items: center; + gap: 4px; + opacity: 0; + transform: translateY(-4px); + transition: all 0.3s ease; + z-index: 10; + backdrop-filter: blur(10px); + font-weight: 500; + box-shadow: 0 4px 12px rgba(9, 105, 218, 0.3); +} + +.mermaidContainer:hover .zoomHint, +.mermaidContainer:focus .zoomHint { + opacity: 1; + transform: translateY(0); +} + +.modal { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: rgba(0, 0, 0, 0.8); + backdrop-filter: blur(5px); + display: flex; + align-items: center; + justify-content: center; + z-index: 9999; + padding: 20px; + animation: fadeIn 0.3s ease; +} + +.modalContent { + background: var(--tech-card-bg); + border-radius: 16px; + max-width: 95vw; + max-height: 95vh; + width: 100%; + overflow: hidden; + box-shadow: 0 24px 64px rgba(0, 0, 0, 0.3); + border: 1px solid var(--tech-border); + display: flex; + flex-direction: column; + animation: slideIn 0.3s ease; +} + +.modalHeader { + display: flex; + justify-content: space-between; + align-items: center; + padding: 20px 24px 16px; + border-bottom: 1px solid var(--tech-border); + background: var(--tech-surface-bg); + flex-shrink: 0; +} + +.modalTitle { + margin: 0; + color: var(--tech-text-primary); + font-size: 18px; + font-weight: 600; +} + +.closeButton { + background: none; + border: none; + cursor: pointer; + padding: 8px; + border-radius: 8px; + color: var(--tech-text-secondary); + transition: all 0.2s ease; + display: flex; + align-items: center; + justify-content: center; + width: 40px; + height: 40px; +} + +.closeButton:hover { + background: rgba(244, 67, 54, 0.1); + color: #f44336; + transform: scale(1.1); +} + +.closeButton:focus { + outline: 2px solid var(--tech-primary-blue); + outline-offset: 2px; +} + +.modalBody { + padding: 24px; + overflow: auto; + flex: 1; + display: flex; + align-items: center; + justify-content: center; + min-height: 0; +} + +.modalBody .mermaid { + background: transparent !important; + border: none !important; + box-shadow: none !important; + max-width: 100%; + max-height: 100%; + margin: 0 !important; + padding: 0 !important; +} + +/* Ensure Mermaid diagrams in modal are properly sized */ +.modalBody .mermaid svg { + max-width: 100% !important; + height: auto !important; +} + +@keyframes fadeIn { + from { + opacity: 0; + } + to { + opacity: 1; + } +} + +@keyframes slideIn { + from { + opacity: 0; + transform: scale(0.9) translateY(20px); + } + to { + opacity: 1; + transform: scale(1) translateY(0); + } +} + +/* Dark theme support */ +[data-theme='dark'] .zoomHint { + background: rgba(88, 166, 255, 0.9); + box-shadow: 0 4px 12px rgba(88, 166, 255, 0.3); +} + +[data-theme='dark'] .modal { + background: rgba(0, 0, 0, 0.9); +} + +/* Mobile responsive */ +@media (max-width: 768px) { + .modal { + padding: 10px; + } + + .modalContent { + max-width: 100%; + max-height: 100%; + border-radius: 12px; + } + + .modalHeader { + padding: 16px 20px 12px; + } + + .modalTitle { + font-size: 16px; + } + + .modalBody { + padding: 16px; + } + + .zoomHint { + font-size: 11px; + padding: 4px 8px; + top: 8px; + right: 8px; + } + + .closeButton { + width: 36px; + height: 36px; + } +} + +/* High contrast mode support */ +@media (prefers-contrast: high) { + .mermaidContainer { + border-width: 2px; + } + + .zoomHint { + background: #000; + color: #fff; + } + + [data-theme='dark'] .zoomHint { + background: #fff; + color: #000; + } +} + +/* Reduced motion support */ +@media (prefers-reduced-motion: reduce) { + .mermaidContainer, + .zoomHint, + .modal, + .modalContent, + .closeButton { + transition: none; + animation: none; + } + + .modal { + animation: none; + } + + .modalContent { + animation: none; + } +} \ No newline at end of file From 0766888c47b1597e8d1c5609d87c432304d84220 Mon Sep 17 00:00:00 2001 From: yuluo-yx Date: Mon, 29 Sep 2025 20:56:24 +0800 Subject: [PATCH 2/8] fix Signed-off-by: yuluo-yx --- .../src/components/ZoomableMermaid/index.js | 85 ++++++++++--------- .../ZoomableMermaid/styles.module.css | 67 +++++++++++---- 2 files changed, 96 insertions(+), 56 deletions(-) diff --git a/website/src/components/ZoomableMermaid/index.js b/website/src/components/ZoomableMermaid/index.js index 5a1ccff0..d0f6610a 100644 --- a/website/src/components/ZoomableMermaid/index.js +++ b/website/src/components/ZoomableMermaid/index.js @@ -1,4 +1,5 @@ import React, { useState, useRef, useEffect, useCallback } from 'react'; +import { createPortal } from 'react-dom'; import Mermaid from '@theme/Mermaid'; import styles from './styles.module.css'; @@ -67,6 +68,48 @@ const ZoomableMermaid = ({ children, title }) => { } }; + const modalContent = ( +
+
+
+ {title && ( + + )} + +
+ +
+
+ ); + return ( <>
{
- {isModalOpen && ( -
-
-
- {title && ( - - )} - -
- -
-
- )} + {isModalOpen && createPortal(modalContent, document.body)} ); }; diff --git a/website/src/components/ZoomableMermaid/styles.module.css b/website/src/components/ZoomableMermaid/styles.module.css index 466e5750..bca3dd30 100644 --- a/website/src/components/ZoomableMermaid/styles.module.css +++ b/website/src/components/ZoomableMermaid/styles.module.css @@ -53,24 +53,27 @@ left: 0; right: 0; bottom: 0; - background: rgba(0, 0, 0, 0.8); + width: 100vw; + height: 100vh; + background: rgba(0, 0, 0, 0.9); backdrop-filter: blur(5px); display: flex; align-items: center; justify-content: center; - z-index: 9999; - padding: 20px; + z-index: 99999; + padding: 0; animation: fadeIn 0.3s ease; } .modalContent { background: var(--tech-card-bg); border-radius: 16px; - max-width: 95vw; - max-height: 95vh; - width: 100%; + width: 95vw; + height: 95vh; + max-width: none; + max-height: none; overflow: hidden; - box-shadow: 0 24px 64px rgba(0, 0, 0, 0.3); + box-shadow: 0 24px 64px rgba(0, 0, 0, 0.5); border: 1px solid var(--tech-border); display: flex; flex-direction: column; @@ -128,22 +131,27 @@ align-items: center; justify-content: center; min-height: 0; + background: var(--tech-surface-bg); } .modalBody .mermaid { background: transparent !important; border: none !important; box-shadow: none !important; - max-width: 100%; - max-height: 100%; + max-width: none !important; + max-height: none !important; margin: 0 !important; padding: 0 !important; + transform: scale(1.5); + transform-origin: center; } /* Ensure Mermaid diagrams in modal are properly sized */ .modalBody .mermaid svg { - max-width: 100% !important; + max-width: none !important; + width: auto !important; height: auto !important; + display: block !important; } @keyframes fadeIn { @@ -173,23 +181,47 @@ } [data-theme='dark'] .modal { - background: rgba(0, 0, 0, 0.9); + background: rgba(0, 0, 0, 0.95); +} + +/* Override any potential Docusaurus container constraints */ +.modal { + position: fixed !important; + top: 0 !important; + left: 0 !important; + right: 0 !important; + bottom: 0 !important; + width: 100vw !important; + height: 100vh !important; + margin: 0 !important; + padding: 0 !important; + z-index: 99999 !important; +} + +.modalContent { + position: relative !important; + margin: auto !important; } /* Mobile responsive */ @media (max-width: 768px) { .modal { - padding: 10px; + padding: 0; + width: 100vw; + height: 100vh; } .modalContent { - max-width: 100%; - max-height: 100%; - border-radius: 12px; + width: 100vw; + height: 100vh; + max-width: none; + max-height: none; + border-radius: 0; } .modalHeader { padding: 16px 20px 12px; + flex-shrink: 0; } .modalTitle { @@ -198,6 +230,11 @@ .modalBody { padding: 16px; + flex: 1; + } + + .modalBody .mermaid { + transform: scale(1.2); } .zoomHint { From d803b06fbbcefa21e5774188e4e1248341442560 Mon Sep 17 00:00:00 2001 From: yuluo-yx Date: Mon, 29 Sep 2025 21:12:04 +0800 Subject: [PATCH 3/8] fix Signed-off-by: yuluo-yx --- .../architecture/system-architecture.md | 6 +- .../src/components/ZoomableMermaid/index.js | 115 ++++++++++++-- .../ZoomableMermaid/styles.module.css | 148 ++++++++++++++---- 3 files changed, 219 insertions(+), 50 deletions(-) diff --git a/website/docs/overview/architecture/system-architecture.md b/website/docs/overview/architecture/system-architecture.md index 2193011f..cc9e387d 100644 --- a/website/docs/overview/architecture/system-architecture.md +++ b/website/docs/overview/architecture/system-architecture.md @@ -6,7 +6,7 @@ The Semantic Router implements a sophisticated Mixture-of-Models (MoM) architect import ZoomableMermaid from '@site/src/components/ZoomableMermaid'; - + {`graph TB subgraph "Client Layer" Client1[Web Application] @@ -216,7 +216,7 @@ class SemanticRouter: ### Request Processing Flow - + {`graph TB Start([Client Request]) --> EnvoyReceive[Envoy Receives Request] @@ -516,7 +516,7 @@ func (cb *CircuitBreaker) Call(operation func() error) error { ### Fallback Strategies - + {`graph TB Request[Incoming Request] --> PrimaryRoute[Primary Routing Decision] diff --git a/website/src/components/ZoomableMermaid/index.js b/website/src/components/ZoomableMermaid/index.js index d0f6610a..168d29fe 100644 --- a/website/src/components/ZoomableMermaid/index.js +++ b/website/src/components/ZoomableMermaid/index.js @@ -3,16 +3,18 @@ import { createPortal } from 'react-dom'; import Mermaid from '@theme/Mermaid'; import styles from './styles.module.css'; -const ZoomableMermaid = ({ children, title }) => { +const ZoomableMermaid = ({ children, title, defaultZoom = 1.2 }) => { const [isModalOpen, setIsModalOpen] = useState(false); const [isHovered, setIsHovered] = useState(false); + const [zoomLevel, setZoomLevel] = useState(defaultZoom); // Use defaultZoom prop const modalRef = useRef(null); const containerRef = useRef(null); const openModal = useCallback(() => { setIsModalOpen(true); + setZoomLevel(defaultZoom); // Reset to default zoom when opening document.body.style.overflow = 'hidden'; - }, []); + }, [defaultZoom]); const closeModal = useCallback(() => { setIsModalOpen(false); @@ -23,6 +25,18 @@ const ZoomableMermaid = ({ children, title }) => { } }, []); + const zoomIn = useCallback(() => { + setZoomLevel(prev => Math.min(prev + 0.2, 3.0)); // Max 300% + }, []); + + const zoomOut = useCallback(() => { + setZoomLevel(prev => Math.max(prev - 0.2, 0.5)); // Min 50% + }, []); + + const resetZoom = useCallback(() => { + setZoomLevel(defaultZoom); // Reset to custom default instead of hardcoded 1.2 + }, [defaultZoom]); + useEffect(() => { const handleEscape = (e) => { if (e.key === 'Escape' && isModalOpen) { @@ -36,9 +50,25 @@ const ZoomableMermaid = ({ children, title }) => { } }; + const handleKeydown = (e) => { + if (!isModalOpen) return; + + if (e.key === '=' || e.key === '+') { + e.preventDefault(); + zoomIn(); + } else if (e.key === '-') { + e.preventDefault(); + zoomOut(); + } else if (e.key === '0') { + e.preventDefault(); + resetZoom(); + } + }; + if (isModalOpen) { document.addEventListener('keydown', handleEscape); document.addEventListener('mousedown', handleClickOutside); + document.addEventListener('keydown', handleKeydown); // Focus the modal content when opened setTimeout(() => { @@ -51,8 +81,9 @@ const ZoomableMermaid = ({ children, title }) => { return () => { document.removeEventListener('keydown', handleEscape); document.removeEventListener('mousedown', handleClickOutside); + document.removeEventListener('keydown', handleKeydown); }; - }, [isModalOpen, closeModal]); + }, [isModalOpen, closeModal, zoomIn, zoomOut, resetZoom]); // Cleanup on unmount useEffect(() => { @@ -77,7 +108,7 @@ const ZoomableMermaid = ({ children, title }) => { aria-describedby="modal-description" >
@@ -87,24 +118,76 @@ const ZoomableMermaid = ({ children, title }) => { {title} )} - +
+ + {Math.round(zoomLevel * 100)}% + + + + + +
diff --git a/website/src/components/ZoomableMermaid/styles.module.css b/website/src/components/ZoomableMermaid/styles.module.css index bca3dd30..f6f56fd1 100644 --- a/website/src/components/ZoomableMermaid/styles.module.css +++ b/website/src/components/ZoomableMermaid/styles.module.css @@ -48,28 +48,29 @@ } .modal { - position: fixed; - top: 0; - left: 0; - right: 0; - bottom: 0; - width: 100vw; - height: 100vh; + position: fixed !important; + top: 0 !important; + left: 0 !important; + right: 0 !important; + bottom: 0 !important; + width: 100vw !important; + height: 100vh !important; background: rgba(0, 0, 0, 0.9); backdrop-filter: blur(5px); display: flex; align-items: center; justify-content: center; - z-index: 99999; - padding: 0; + z-index: 99999 !important; + padding: 0 !important; + margin: 0 !important; animation: fadeIn 0.3s ease; } .modalContent { background: var(--tech-card-bg); border-radius: 16px; - width: 95vw; - height: 95vh; + width: 70vw; + height: 70vh; max-width: none; max-height: none; overflow: hidden; @@ -78,6 +79,8 @@ display: flex; flex-direction: column; animation: slideIn 0.3s ease; + position: relative !important; + margin: auto !important; } .modalHeader { @@ -97,19 +100,91 @@ font-weight: 600; } +.modalControls { + display: flex; + align-items: center; + gap: 8px; +} + +.zoomIndicator { + font-size: 12px; + color: var(--tech-text-secondary); + font-weight: 500; + min-width: 35px; + text-align: center; +} + +.zoomButton { + background: none; + border: none; + cursor: pointer; + padding: 6px; + border-radius: 6px; + color: var(--tech-text-secondary); + transition: all 0.2s ease; + display: flex; + align-items: center; + justify-content: center; + width: 30px; + height: 30px; +} + +.zoomButton:hover:not(:disabled) { + background: rgba(9, 105, 218, 0.1); + color: var(--tech-primary-blue); + transform: scale(1.05); +} + +.zoomButton:disabled { + opacity: 0.4; + cursor: not-allowed; +} + +.zoomButton:focus:not(:disabled) { + outline: 2px solid var(--tech-primary-blue); + outline-offset: 2px; +} + +.resetButton { + background: none; + border: none; + cursor: pointer; + padding: 6px; + border-radius: 6px; + color: var(--tech-text-secondary); + transition: all 0.2s ease; + display: flex; + align-items: center; + justify-content: center; + width: 30px; + height: 30px; +} + +.resetButton:hover { + background: rgba(156, 39, 176, 0.1); + color: var(--tech-accent-purple); + transform: scale(1.05); +} + +.resetButton:focus { + outline: 2px solid var(--tech-primary-blue); + outline-offset: 2px; +} + .closeButton { background: none; border: none; cursor: pointer; - padding: 8px; - border-radius: 8px; + padding: 6px; + border-radius: 6px; color: var(--tech-text-secondary); transition: all 0.2s ease; display: flex; align-items: center; justify-content: center; - width: 40px; - height: 40px; + width: 30px; + height: 30px; + margin-left: 4px; } .closeButton:hover { @@ -134,7 +209,14 @@ background: var(--tech-surface-bg); } -.modalBody .mermaid { +.diagramContainer { + transition: transform 0.3s ease; + display: flex; + align-items: center; + justify-content: center; +} + +.diagramContainer .mermaid { background: transparent !important; border: none !important; box-shadow: none !important; @@ -142,12 +224,10 @@ max-height: none !important; margin: 0 !important; padding: 0 !important; - transform: scale(1.5); - transform-origin: center; } /* Ensure Mermaid diagrams in modal are properly sized */ -.modalBody .mermaid svg { +.diagramContainer .mermaid svg { max-width: none !important; width: auto !important; height: auto !important; @@ -212,11 +292,9 @@ } .modalContent { - width: 100vw; - height: 100vh; - max-width: none; - max-height: none; - border-radius: 0; + width: 95vw !important; + height: 90vh !important; + border-radius: 12px; } .modalHeader { @@ -233,10 +311,6 @@ flex: 1; } - .modalBody .mermaid { - transform: scale(1.2); - } - .zoomHint { font-size: 11px; padding: 4px 8px; @@ -244,9 +318,21 @@ right: 8px; } - .closeButton { - width: 36px; - height: 36px; + .closeButton, + .zoomButton, + .resetButton { + width: 28px; + height: 28px; + padding: 4px; + } + + .modalControls { + gap: 6px; + } + + .zoomIndicator { + font-size: 11px; + min-width: 30px; } } From 1f909b1b7bb3937fbe4237285934ee7c15da30ec Mon Sep 17 00:00:00 2001 From: yuluo-yx Date: Mon, 29 Sep 2025 22:13:37 +0800 Subject: [PATCH 4/8] fix: fix lit Signed-off-by: yuluo-yx --- website/docs/mermaid-enhancement-guide.md | 63 ------- .../architecture/system-architecture.md | 2 +- .../src/components/ZoomableMermaid/index.js | 175 +++++++++--------- 3 files changed, 90 insertions(+), 150 deletions(-) delete mode 100644 website/docs/mermaid-enhancement-guide.md diff --git a/website/docs/mermaid-enhancement-guide.md b/website/docs/mermaid-enhancement-guide.md deleted file mode 100644 index 85d53ed9..00000000 --- a/website/docs/mermaid-enhancement-guide.md +++ /dev/null @@ -1,63 +0,0 @@ -# 可放大 Mermaid 图表组件使用指南 - -## 概述 - -`ZoomableMermaid` 组件为 Docusaurus 网站提供了可点击放大的 Mermaid 图表功能。用户可以点击图表在模态框中查看放大版本,提供更好的阅读体验。 - -## 使用方法 - -### 基本用法 - -在任何 Markdown 文档中,首先导入组件: - -```jsx -import ZoomableMermaid from '@site/src/components/ZoomableMermaid'; -``` - -然后使用组件包装 Mermaid 图表代码: - -```jsx - -{`graph TB - A[开始] --> B[处理] - B --> C[结束]`} - -``` - -### 参数说明 - -- `children` (string, 必需): Mermaid 图表的代码字符串 -- `title` (string, 可选): 图表标题,会显示在模态框顶部 - -### 功能特性 - -- **点击放大**: 点击图表在模态框中查看放大版本 -- **键盘支持**: 支持 Enter 和空格键打开模态框,Escape 键关闭 -- **无障碍访问**: 完整的 ARIA 属性支持 -- **响应式设计**: 在各种屏幕尺寸下都能正常工作 -- **主题支持**: 自动适应明暗主题 -- **动画效果**: 平滑的打开/关闭动画 - -### 示例 - - -{`graph LR - A[用户访问] --> B{检查权限} - B -->|有权限| C[显示内容] - B -->|无权限| D[显示错误]`} - - -## 实现原理 - -组件基于以下技术栈: - -1. **React Hooks**: 使用 `useState`、`useRef` 和 `useEffect` 管理状态 -2. **CSS Modules**: 样式隔离和主题支持 -3. **事件处理**: 键盘、鼠标和触摸事件 -4. **无障碍访问**: ARIA 属性和焦点管理 - -## 注意事项 - -- 确保 Mermaid 代码字符串使用模板字符串语法 (backticks) -- 在 JSX 中使用时,需要将代码包装在 `{``}` 中 -- 组件会自动处理焦点管理和页面滚动 \ No newline at end of file diff --git a/website/docs/overview/architecture/system-architecture.md b/website/docs/overview/architecture/system-architecture.md index e9bf30be..fe7101d4 100644 --- a/website/docs/overview/architecture/system-architecture.md +++ b/website/docs/overview/architecture/system-architecture.md @@ -6,7 +6,7 @@ The Semantic Router implements a sophisticated Mixture-of-Models (MoM) architect import ZoomableMermaid from '@site/src/components/ZoomableMermaid'; - + {`graph TB subgraph "Client Layer" Client1[Web Application] diff --git a/website/src/components/ZoomableMermaid/index.js b/website/src/components/ZoomableMermaid/index.js index 168d29fe..e8001f76 100644 --- a/website/src/components/ZoomableMermaid/index.js +++ b/website/src/components/ZoomableMermaid/index.js @@ -1,113 +1,115 @@ -import React, { useState, useRef, useEffect, useCallback } from 'react'; -import { createPortal } from 'react-dom'; -import Mermaid from '@theme/Mermaid'; -import styles from './styles.module.css'; +import React, { useState, useRef, useEffect, useCallback } from 'react' +import { createPortal } from 'react-dom' +import Mermaid from '@theme/Mermaid' +import styles from './styles.module.css' const ZoomableMermaid = ({ children, title, defaultZoom = 1.2 }) => { - const [isModalOpen, setIsModalOpen] = useState(false); - const [isHovered, setIsHovered] = useState(false); - const [zoomLevel, setZoomLevel] = useState(defaultZoom); // Use defaultZoom prop - const modalRef = useRef(null); - const containerRef = useRef(null); + const [isModalOpen, setIsModalOpen] = useState(false) + const [isHovered, setIsHovered] = useState(false) + const [zoomLevel, setZoomLevel] = useState(defaultZoom) // Use defaultZoom prop + const modalRef = useRef(null) + const containerRef = useRef(null) const openModal = useCallback(() => { - setIsModalOpen(true); - setZoomLevel(defaultZoom); // Reset to default zoom when opening - document.body.style.overflow = 'hidden'; - }, [defaultZoom]); + setIsModalOpen(true) + setZoomLevel(defaultZoom) // Reset to default zoom when opening + document.body.style.overflow = 'hidden' + }, [defaultZoom]) const closeModal = useCallback(() => { - setIsModalOpen(false); - document.body.style.overflow = 'unset'; + setIsModalOpen(false) + document.body.style.overflow = 'unset' // Return focus to the original container if (containerRef.current) { - containerRef.current.focus(); + containerRef.current.focus() } - }, []); + }, []) const zoomIn = useCallback(() => { - setZoomLevel(prev => Math.min(prev + 0.2, 3.0)); // Max 300% - }, []); + setZoomLevel(prev => Math.min(prev + 0.2, 3.0)) // Max 300% + }, []) const zoomOut = useCallback(() => { - setZoomLevel(prev => Math.max(prev - 0.2, 0.5)); // Min 50% - }, []); + setZoomLevel(prev => Math.max(prev - 0.2, 0.5)) // Min 50% + }, []) const resetZoom = useCallback(() => { - setZoomLevel(defaultZoom); // Reset to custom default instead of hardcoded 1.2 - }, [defaultZoom]); + setZoomLevel(defaultZoom) // Reset to custom default instead of hardcoded 1.2 + }, [defaultZoom]) useEffect(() => { const handleEscape = (e) => { if (e.key === 'Escape' && isModalOpen) { - closeModal(); + closeModal() } - }; + } const handleClickOutside = (e) => { if (modalRef.current && !modalRef.current.contains(e.target)) { - closeModal(); + closeModal() } - }; + } const handleKeydown = (e) => { - if (!isModalOpen) return; - + if (!isModalOpen) return + if (e.key === '=' || e.key === '+') { - e.preventDefault(); - zoomIn(); - } else if (e.key === '-') { - e.preventDefault(); - zoomOut(); - } else if (e.key === '0') { - e.preventDefault(); - resetZoom(); + e.preventDefault() + zoomIn() + } + else if (e.key === '-') { + e.preventDefault() + zoomOut() + } + else if (e.key === '0') { + e.preventDefault() + resetZoom() } - }; + } if (isModalOpen) { - document.addEventListener('keydown', handleEscape); - document.addEventListener('mousedown', handleClickOutside); - document.addEventListener('keydown', handleKeydown); - + document.addEventListener('keydown', handleEscape) + document.addEventListener('mousedown', handleClickOutside) + document.addEventListener('keydown', handleKeydown) + // Focus the modal content when opened setTimeout(() => { if (modalRef.current) { - modalRef.current.focus(); + modalRef.current.focus() } - }, 100); + }, 100) } return () => { - document.removeEventListener('keydown', handleEscape); - document.removeEventListener('mousedown', handleClickOutside); - document.removeEventListener('keydown', handleKeydown); - }; - }, [isModalOpen, closeModal, zoomIn, zoomOut, resetZoom]); + document.removeEventListener('keydown', handleEscape) + document.removeEventListener('mousedown', handleClickOutside) + document.removeEventListener('keydown', handleKeydown) + } + }, [isModalOpen, closeModal, zoomIn, zoomOut, resetZoom]) // Cleanup on unmount useEffect(() => { return () => { - document.body.style.overflow = 'unset'; - }; - }, []); + document.body.style.overflow = 'unset' + } + }, []) const handleKeyDown = (e) => { if (e.key === 'Enter' || e.key === ' ') { - e.preventDefault(); - openModal(); + e.preventDefault() + openModal() } - }; + } const modalContent = ( -
-
{ )}
- {Math.round(zoomLevel * 100)}% + {Math.round(zoomLevel * 100)} + % - - - -
-
- ); + ) return ( <> -
{ > @@ -221,7 +224,7 @@ const ZoomableMermaid = ({ children, title, defaultZoom = 1.2 }) => { {isModalOpen && createPortal(modalContent, document.body)} - ); -}; + ) +} -export default ZoomableMermaid; \ No newline at end of file +export default ZoomableMermaid From 290e1f35b507440eb1fd975216501a3c9039827c Mon Sep 17 00:00:00 2001 From: yuluo-yx Date: Mon, 29 Sep 2025 22:27:46 +0800 Subject: [PATCH 5/8] fix Signed-off-by: yuluo-yx --- .../src/components/ZoomableMermaid/index.js | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/website/src/components/ZoomableMermaid/index.js b/website/src/components/ZoomableMermaid/index.js index e8001f76..dd4d33c3 100644 --- a/website/src/components/ZoomableMermaid/index.js +++ b/website/src/components/ZoomableMermaid/index.js @@ -129,9 +129,9 @@ const ZoomableMermaid = ({ children, title, defaultZoom = 1.2 }) => { className={styles.zoomButton} onClick={zoomOut} disabled={zoomLevel <= 0.5} - aria-label="缩小图表" + aria-label="Reduce the size of the chart" type="button" - title="缩小 (快捷键: -)" + title="Reduce (Shortcut key: -)" > @@ -142,9 +142,9 @@ const ZoomableMermaid = ({ children, title, defaultZoom = 1.2 }) => {