Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/components/layout/LandingPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ function LandingPage() {
<span className={styles.logoText}>MoE Visualizer</span>
</div>
<nav className={styles.nav}>
<a href="/docs">Docs</a>
<a href="/visualizer">Demo</a>
</nav>
</div>
Expand Down
25 changes: 25 additions & 0 deletions src/components/layout/VisualizerPage.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
.nav {
display: flex;
gap: var(--spacing-lg);
align-items: center;
}

.nav a {
Expand All @@ -61,6 +62,30 @@
color: var(--color-text);
}

.metricsButton {
padding: var(--spacing-xs) var(--spacing-md);
background: linear-gradient(135deg, var(--color-primary), var(--color-secondary));
color: white;
font-size: 0.875rem;
font-weight: 600;
border: none;
border-radius: var(--radius-md);
cursor: pointer;
transition: all var(--transition-fast);
display: flex;
align-items: center;
gap: var(--spacing-xs);
}

.metricsButton:hover {
transform: translateY(-2px);
box-shadow: var(--shadow-glow);
}

.metricsButton:active {
transform: translateY(0);
}

/* Main Content */
.main {
flex: 1;
Expand Down
18 changes: 18 additions & 0 deletions src/components/layout/VisualizerPage.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
import { useState } from 'react'
import { Link } from 'react-router-dom'
import { useMoeStore } from '../../store/moeStore'
import { useSimulationStore } from '../../store/simulationStore'
import ExpertNetwork from '../visualizers/ExpertNetwork'
import AnimationPanel from '../visualizers/AnimationPanel'
import StatusLegend from '../common/StatusLegend'
import { MetricsPanel } from '../visualizers/MetricsPanel'
import styles from './VisualizerPage.module.css'

function VisualizerPage() {
// Metrics panel state
const [isMetricsPanelOpen, setIsMetricsPanelOpen] = useState(false)

// Get values and setters from the store
const numExperts = useMoeStore(state => state.numExperts)
const topK = useMoeStore(state => state.topK)
Expand All @@ -33,6 +38,13 @@ function VisualizerPage() {
</Link>
<nav className={styles.nav}>
<Link to="/">Home</Link>
<Link to="/docs">Docs</Link>
<button
className={styles.metricsButton}
onClick={() => setIsMetricsPanelOpen(true)}
>
Metrics
</button>
</nav>
</div>
</header>
Expand Down Expand Up @@ -117,6 +129,12 @@ function VisualizerPage() {
</div>
</div>
</main>

{/* Metrics Sidebar */}
<MetricsPanel
isOpen={isMetricsPanelOpen}
onClose={() => setIsMetricsPanelOpen(false)}
/>
</div>
)
}
Expand Down
2 changes: 1 addition & 1 deletion src/components/visualizers/AnimationPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ function AnimationPanel() {
const topK = useMoeStore(state => state.topK)
const addToken = useSimulationStore(state => state.addToken)

const MAX_TOKENS = 20
const MAX_TOKENS = 50

const [input, setInput] = useState('')
const setAnimationState = useSimulationStore(state => state.setAnimationState)
Expand Down
2 changes: 1 addition & 1 deletion src/components/visualizers/ExpertNetwork.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ function ExpertNetwork() {
if (!expert) return null

const weight = token.routingWeights[index]
const strokeWidth = 1 + weight * 3 // 1-4px based on weight
const strokeWidth = 1 + weight * 2

// Add curve based on index to separate overlapping lines
const curveOffset = (index - (token.targetExperts.length - 1) / 2) * 15
Expand Down
212 changes: 212 additions & 0 deletions src/components/visualizers/MetricsPanel.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,212 @@
.backdrop {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
backdrop-filter: blur(4px);
z-index: 1000;
animation: fadeIn 0.3s ease-out;
}

@keyframes fadeIn {
from {
opacity: 0;
}
to {
opacity: 1;
}
}

.panel {
position: fixed;
top: 0;
right: 0;
bottom: 0;
width: 400px;
max-width: 90vw;
background: var(--color-surface);
border-left: 1px solid var(--color-surface-light);
box-shadow: var(--shadow-xl);
z-index: 1001;
display: flex;
flex-direction: column;
transform: translateX(100%);
transition: transform 0.3s ease-out;
}

.panel.open {
transform: translateX(0);
}

.header {
display: flex;
justify-content: space-between;
align-items: center;
padding: var(--spacing-lg);
border-bottom: 1px solid var(--color-surface-light);
background: var(--color-background);
}

.header h2 {
margin: 0;
font-size: 1.5rem;
color: var(--color-text);
font-weight: 600;
}

.closeButton {
width: 32px;
height: 32px;
border-radius: var(--radius-sm);
border: none;
background: var(--color-surface-light);
color: var(--color-text);
font-size: 1.5rem;
line-height: 1;
cursor: pointer;
transition: all var(--transition-fast);
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
}

.closeButton:hover {
background: var(--color-surface);
color: var(--color-primary);
transform: scale(1.1);
}

.content {
flex: 1;
padding: var(--spacing-xl) var(--spacing-lg);
overflow-y: auto;
}

/* Sections */
.section {
margin-bottom: var(--spacing-2xl);
}

.sectionTitle {
font-size: 1.125rem;
font-weight: 600;
color: var(--color-text);
margin: 0 0 var(--spacing-md) 0;
padding-bottom: var(--spacing-xs);
border-bottom: 2px solid var(--color-surface-light);
}

/* Metric Cards */
.metricCard {
background: var(--color-background);
border: 1px solid var(--color-surface-light);
border-radius: var(--radius-md);
padding: var(--spacing-md);
margin-bottom: var(--spacing-md);
}

.metricHeader {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: var(--spacing-xs);
}

.metricLabel {
font-size: 1.2rem;
font-weight: 600;
color: var(--color-text-secondary);
}

.metricValue {
font-size: 1.25rem;
font-weight: 700;
color: var(--color-text);
font-family: monospace;
}

.metricValueBadge {
font-size: 1.25rem;
font-weight: 700;
color: white;
font-family: monospace;
padding: var(--spacing-xs) var(--spacing-md);
border-radius: var(--radius-md);
transition: background-color 0.3s ease;
}

.metricDescription {
font-size: 0.8rem;
color: var(--color-text-secondary);
font-style: italic;
}

.metricDescription sub {
font-size: 0.65rem;
}

/* Expert Utilization Bars */
.expertBars {
display: flex;
flex-direction: column;
gap: var(--spacing-sm);
}

.expertBar {
display: flex;
flex-direction: column;
gap: 4px;
}

.expertBarHeader {
display: flex;
align-items: center;
gap: var(--spacing-xs);
font-size: 0.875rem;
}

.expertDot {
width: 10px;
height: 10px;
border-radius: 50%;
flex-shrink: 0;
}

.expertName {
color: var(--color-text-secondary);
font-weight: 500;
flex: 1;
}

.expertValue {
color: var(--color-text);
font-weight: 700;
font-family: monospace;
font-size: 0.8rem;
}

.expertBarTrack {
width: 100%;
height: 6px;
background: var(--color-surface-light);
border-radius: 3px;
overflow: hidden;
}

.expertBarFill {
height: 100%;
transition: width 0.3s ease;
border-radius: 3px;
}

/* Mobile responsiveness */
@media (max-width: 768px) {
.panel {
width: 100%;
max-width: 100vw;
}
}

Loading