Skip to content

Commit ea35ec1

Browse files
authored
feat: add topology for vllm dash (#409)
Signed-off-by: bitliu <bitliu@tencent.com>
1 parent ad70b7f commit ea35ec1

File tree

8 files changed

+1387
-12
lines changed

8 files changed

+1387
-12
lines changed

dashboard/frontend/package-lock.json

Lines changed: 527 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dashboard/frontend/package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@
1212
"dependencies": {
1313
"react": "^18.3.1",
1414
"react-dom": "^18.3.1",
15-
"react-router-dom": "^6.28.0"
15+
"react-router-dom": "^6.28.0",
16+
"reactflow": "^11.11.4"
1617
},
1718
"devDependencies": {
1819
"@types/react": "^18.3.18",
@@ -26,4 +27,4 @@
2627
"typescript": "^5.9.3",
2728
"vite": "^5.4.11"
2829
}
29-
}
30+
}

dashboard/frontend/src/App.tsx

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import LandingPage from './pages/LandingPage'
55
import MonitoringPage from './pages/MonitoringPage'
66
import ConfigPage from './pages/ConfigPage'
77
import PlaygroundPage from './pages/PlaygroundPage'
8+
import TopologyPage from './pages/TopologyPage'
89
import { ConfigSection } from './components/ConfigNav'
910

1011
const App: React.FC = () => {
@@ -105,6 +106,17 @@ const App: React.FC = () => {
105106
</Layout>
106107
}
107108
/>
109+
<Route
110+
path="/topology"
111+
element={
112+
<Layout
113+
configSection={configSection}
114+
onConfigSectionChange={(section) => setConfigSection(section as ConfigSection)}
115+
>
116+
<TopologyPage />
117+
</Layout>
118+
}
119+
/>
108120
</Routes>
109121
</BrowserRouter>
110122
)

dashboard/frontend/src/components/ConfigNav.tsx

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ export type ConfigSection =
66
| 'prompt-guard'
77
| 'similarity-cache'
88
| 'intelligent-routing'
9+
| 'topology'
910
| 'tools-selection'
1011
| 'observability'
1112
| 'classification-api'
@@ -41,6 +42,12 @@ const ConfigNav: React.FC<ConfigNavProps> = ({ activeSection, onSectionChange })
4142
title: 'Intelligent Routing',
4243
description: 'Classify BERT, categories & reasoning'
4344
},
45+
{
46+
id: 'topology' as ConfigSection,
47+
icon: '🗺️',
48+
title: 'Topology',
49+
description: 'Visualize routing chain-of-thought'
50+
},
4451
{
4552
id: 'tools-selection' as ConfigSection,
4653
icon: '🔧',

dashboard/frontend/src/components/Layout.tsx

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -56,16 +56,26 @@ const Layout: React.FC<LayoutProps> = ({ children, configSection, onConfigSectio
5656
{ id: 'prompt-guard', icon: '🛡️', title: 'Prompt Guard' },
5757
{ id: 'similarity-cache', icon: '⚡', title: 'Similarity Cache' },
5858
{ id: 'intelligent-routing', icon: '🧠', title: 'Intelligent Routing' },
59+
{ id: 'topology', icon: '🗺️', title: 'Topology' },
5960
{ id: 'tools-selection', icon: '🔧', title: 'Tools Selection' },
6061
{ id: 'observability', icon: '👁️', title: 'Observability' },
6162
{ id: 'classification-api', icon: '🔌', title: 'Classification API' }
6263
].map((section) => (
6364
<button
6465
key={section.id}
65-
className={`${styles.navLink} ${isConfigPage && configSection === section.id ? styles.navLinkActive : ''}`}
66+
className={`${styles.navLink} ${
67+
(section.id === 'topology' && location.pathname === '/topology') ||
68+
(isConfigPage && configSection === section.id)
69+
? styles.navLinkActive
70+
: ''
71+
}`}
6672
onClick={() => {
67-
onConfigSectionChange(section.id)
68-
navigate('/config')
73+
if (section.id === 'topology') {
74+
navigate('/topology')
75+
} else {
76+
onConfigSectionChange(section.id)
77+
navigate('/config')
78+
}
6979
}}
7080
>
7181
<span className={styles.navIcon}>{section.icon}</span>
@@ -95,7 +105,7 @@ const Layout: React.FC<LayoutProps> = ({ children, configSection, onConfigSectio
95105
{theme === 'light' ? '🌙' : '☀️'}
96106
</button>
97107
<a
98-
href="https://github.com/vllm-project/vllm"
108+
href="https://github.com/vllm-project/semantic-router"
99109
target="_blank"
100110
rel="noopener noreferrer"
101111
className={styles.iconButton}
@@ -107,7 +117,7 @@ const Layout: React.FC<LayoutProps> = ({ children, configSection, onConfigSectio
107117
</svg>
108118
</a>
109119
<a
110-
href="https://docs.vllm.ai"
120+
href="https://vllm-semantic-router.com"
111121
target="_blank"
112122
rel="noopener noreferrer"
113123
className={styles.iconButton}
Lines changed: 258 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,258 @@
1+
.container {
2+
display: flex;
3+
flex-direction: column;
4+
height: 100%;
5+
width: 100%;
6+
background: var(--bg-primary);
7+
overflow: hidden;
8+
}
9+
10+
.header {
11+
padding: 1.5rem 2rem;
12+
background: var(--bg-secondary);
13+
border-bottom: 1px solid var(--border-color);
14+
display: flex;
15+
align-items: center;
16+
justify-content: space-between;
17+
flex-wrap: wrap;
18+
gap: 1rem;
19+
}
20+
21+
.title {
22+
font-size: 1.75rem;
23+
font-weight: 600;
24+
color: var(--text-primary);
25+
margin: 0;
26+
}
27+
28+
.subtitle {
29+
font-size: 0.95rem;
30+
color: var(--text-secondary);
31+
margin: 0.25rem 0 0 0;
32+
flex: 1;
33+
}
34+
35+
.refreshButton {
36+
padding: 0.5rem 1rem;
37+
background: var(--accent-color);
38+
color: white;
39+
border: none;
40+
border-radius: 6px;
41+
font-size: 0.9rem;
42+
cursor: pointer;
43+
transition: all 0.2s;
44+
font-weight: 500;
45+
}
46+
47+
.refreshButton:hover {
48+
background: var(--accent-hover);
49+
transform: translateY(-1px);
50+
}
51+
52+
.flowContainer {
53+
flex: 1;
54+
width: 100%;
55+
height: 100%;
56+
position: relative;
57+
background: var(--bg-primary);
58+
}
59+
60+
/* ReactFlow custom styling */
61+
.flowContainer :global(.react-flow) {
62+
background: var(--bg-primary);
63+
}
64+
65+
.flowContainer :global(.react-flow__node) {
66+
border-radius: 8px;
67+
padding: 12px 16px;
68+
font-size: 13px;
69+
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
70+
transition: all 0.2s;
71+
}
72+
73+
.flowContainer :global(.react-flow__node:hover) {
74+
transform: scale(1.05);
75+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.25);
76+
}
77+
78+
.flowContainer :global(.react-flow__edge-path) {
79+
stroke-width: 2;
80+
stroke: var(--text-secondary);
81+
}
82+
83+
.flowContainer :global(.react-flow__edge.animated .react-flow__edge-path) {
84+
stroke: var(--accent-color);
85+
stroke-width: 2.5;
86+
}
87+
88+
.flowContainer :global(.react-flow__controls) {
89+
background: var(--bg-secondary);
90+
border: 1px solid var(--border-color);
91+
border-radius: 8px;
92+
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
93+
}
94+
95+
.flowContainer :global(.react-flow__controls-button) {
96+
background: var(--bg-secondary);
97+
border-bottom: 1px solid var(--border-color);
98+
color: var(--text-primary);
99+
}
100+
101+
.flowContainer :global(.react-flow__controls-button:hover) {
102+
background: var(--bg-hover);
103+
}
104+
105+
.flowContainer :global(.react-flow__minimap) {
106+
background: var(--bg-secondary);
107+
border: 1px solid var(--border-color);
108+
border-radius: 8px;
109+
}
110+
111+
.flowContainer :global(.react-flow__background) {
112+
background: var(--bg-primary);
113+
}
114+
115+
.flowContainer :global(.react-flow__attribution) {
116+
background: var(--bg-secondary);
117+
padding: 4px 8px;
118+
border-radius: 4px;
119+
font-size: 10px;
120+
color: var(--text-secondary);
121+
}
122+
123+
/* Legend */
124+
.legend {
125+
position: absolute;
126+
top: 80px;
127+
right: 20px;
128+
background: var(--bg-secondary);
129+
border: 1px solid var(--border-color);
130+
border-radius: 8px;
131+
padding: 1rem;
132+
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
133+
max-width: 200px;
134+
z-index: 10;
135+
}
136+
137+
.legend h3 {
138+
margin: 0 0 0.75rem 0;
139+
font-size: 0.9rem;
140+
font-weight: 600;
141+
color: var(--text-primary);
142+
}
143+
144+
.legendItems {
145+
display: flex;
146+
flex-direction: column;
147+
gap: 0.5rem;
148+
}
149+
150+
.legendItem {
151+
display: flex;
152+
align-items: center;
153+
gap: 0.5rem;
154+
font-size: 0.8rem;
155+
color: var(--text-secondary);
156+
}
157+
158+
.legendColor {
159+
width: 16px;
160+
height: 16px;
161+
border-radius: 4px;
162+
flex-shrink: 0;
163+
border: 1px solid rgba(0, 0, 0, 0.2);
164+
}
165+
166+
/* Loading state */
167+
.loading {
168+
display: flex;
169+
flex-direction: column;
170+
align-items: center;
171+
justify-content: center;
172+
height: 100%;
173+
color: var(--text-secondary);
174+
}
175+
176+
.spinner {
177+
width: 48px;
178+
height: 48px;
179+
border: 4px solid var(--border-color);
180+
border-top-color: var(--accent-color);
181+
border-radius: 50%;
182+
animation: spin 1s linear infinite;
183+
margin-bottom: 1rem;
184+
}
185+
186+
@keyframes spin {
187+
to {
188+
transform: rotate(360deg);
189+
}
190+
}
191+
192+
.loading p {
193+
font-size: 1rem;
194+
margin: 0;
195+
}
196+
197+
/* Error state */
198+
.error {
199+
display: flex;
200+
flex-direction: column;
201+
align-items: center;
202+
justify-content: center;
203+
height: 100%;
204+
color: var(--text-secondary);
205+
padding: 2rem;
206+
text-align: center;
207+
}
208+
209+
.errorIcon {
210+
font-size: 3rem;
211+
margin-bottom: 1rem;
212+
}
213+
214+
.error p {
215+
font-size: 1rem;
216+
margin: 0 0 1.5rem 0;
217+
color: var(--text-primary);
218+
}
219+
220+
.retryButton {
221+
padding: 0.75rem 1.5rem;
222+
background: var(--accent-color);
223+
color: white;
224+
border: none;
225+
border-radius: 6px;
226+
font-size: 0.95rem;
227+
cursor: pointer;
228+
transition: all 0.2s;
229+
font-weight: 500;
230+
}
231+
232+
.retryButton:hover {
233+
background: var(--accent-hover);
234+
transform: translateY(-1px);
235+
}
236+
237+
/* Responsive adjustments */
238+
@media (max-width: 768px) {
239+
.header {
240+
flex-direction: column;
241+
align-items: flex-start;
242+
}
243+
244+
.legend {
245+
position: static;
246+
margin: 1rem;
247+
max-width: none;
248+
}
249+
250+
.title {
251+
font-size: 1.5rem;
252+
}
253+
254+
.subtitle {
255+
font-size: 0.85rem;
256+
}
257+
}
258+

0 commit comments

Comments
 (0)