Skip to content

Commit 4840b94

Browse files
authored
added 24/7 tooltip (#18)
1 parent 896cb08 commit 4840b94

3 files changed

Lines changed: 159 additions & 25 deletions

File tree

frontend/src/components/ui/ModelCard.svelte

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<script lang="ts">
22
import { getModelLogo } from '../../lib/modelLogos';
3-
import { getModelMetricsUrl } from '../../lib/modelMetrics';
3+
import { getModelMetricsUrl, getModelTier } from '../../lib/modelMetrics';
44
55
interface ModelCardProps {
66
entry: {
@@ -19,6 +19,7 @@
1919
// Get the logo URL for this model
2020
const logoUrl = getModelLogo(entry.data.title);
2121
const metricsUrl = getModelMetricsUrl(entry.data.title);
22+
const tier = getModelTier(entry.data.title);
2223
2324
let copied = false;
2425
@@ -82,6 +83,21 @@ class="relative group flex flex-nowrap py-3 px-4 pr-10 rounded-lg border border-
8283
Metrics
8384
</a>
8485
{/if}
86+
{#if tier === "L2"}
87+
<span
88+
class="uptime-badge"
89+
title="This service is running on CSCS L2 Kubernetes"
90+
>
91+
24/7
92+
</span>
93+
{:else if tier === "slurm"}
94+
<span
95+
class="slurm-badge"
96+
title="Model-launch Slurm job"
97+
>
98+
Slurm
99+
</span>
100+
{/if}
85101
{#if entry.data.instanceCount > 1}
86102
<span class="instance-count" title="Number of launched instances for higher throughput">
87103
x{entry.data.instanceCount}
@@ -133,6 +149,28 @@ class="relative group flex flex-nowrap py-3 px-4 pr-10 rounded-lg border border-
133149
background-color: #15803d;
134150
}
135151
152+
.uptime-badge {
153+
background-color: #2563eb;
154+
color: white;
155+
font-weight: bold;
156+
font-size: 0.75em;
157+
padding: 0 6px;
158+
border-radius: 4px;
159+
flex-shrink: 0;
160+
cursor: help;
161+
}
162+
163+
.slurm-badge {
164+
background-color: #9333ea;
165+
color: white;
166+
font-weight: bold;
167+
font-size: 0.75em;
168+
padding: 0 6px;
169+
border-radius: 4px;
170+
flex-shrink: 0;
171+
cursor: help;
172+
}
173+
136174
@keyframes check-bounce {
137175
0% {
138176
transform: scale(1);

frontend/src/components/ui/ModelList.svelte

Lines changed: 99 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,16 @@
22
import { onMount } from "svelte";
33
import ModelCard from "./ModelCard.svelte";
44
import { getApiUrl } from "../../lib/config";
5+
import { getModelTier } from "../../lib/modelMetrics";
56
67
let models = [];
78
let modelCount = 0;
89
let loading = true;
910
let error = null;
11+
12+
let search = "";
13+
let activeFilter = "all"; // "all" | "24/7" | "slurm"
14+
1015
onMount(async () => {
1116
try {
1217
const response = await fetch(`${getApiUrl()}/v1/models_detailed`);
@@ -45,6 +50,20 @@
4550
loading = false;
4651
}
4752
});
53+
54+
$: filteredModels = models.filter((m) => {
55+
const title = m.data.title;
56+
57+
if (search.trim()) {
58+
const q = search.trim().toLowerCase();
59+
const haystack = (title + " " + m.data.devices.join(" ")).toLowerCase();
60+
if (!haystack.includes(q)) return false;
61+
}
62+
63+
if (activeFilter === "24/7") return getModelTier(title) === "L2";
64+
if (activeFilter === "slurm") return getModelTier(title) === "slurm";
65+
return true;
66+
});
4867
</script>
4968

5069
<div>
@@ -59,6 +78,35 @@
5978
Access state-of-the-art language models from leading AI research organizations
6079
</p>
6180
</div>
81+
82+
{#if !loading && !error}
83+
<div class="mb-6 flex flex-col sm:flex-row gap-3 sm:items-center sm:justify-between">
84+
<div class="flex flex-wrap gap-2">
85+
<button
86+
class="pill"
87+
class:active={activeFilter === "all"}
88+
on:click={() => (activeFilter = "all")}
89+
>All</button>
90+
<button
91+
class="pill"
92+
class:active={activeFilter === "24/7"}
93+
on:click={() => (activeFilter = "24/7")}
94+
>24/7</button>
95+
<button
96+
class="pill"
97+
class:active={activeFilter === "slurm"}
98+
on:click={() => (activeFilter = "slurm")}
99+
>Slurm</button>
100+
</div>
101+
<input
102+
type="text"
103+
bind:value={search}
104+
placeholder="Search by model name or GPU..."
105+
class="search-input"
106+
/>
107+
</div>
108+
{/if}
109+
62110
{#if loading}
63111
<div class="loading">Loading...</div>
64112
{:else if error}
@@ -67,13 +115,61 @@
67115
</div>
68116
{:else}
69117
<div class="model-list space-y-2">
70-
{#each models as model}
118+
{#each filteredModels as model (model.data.title)}
71119
<ModelCard entry={model} />
72120
{/each}
121+
{#if filteredModels.length === 0}
122+
<div class="text-center text-slate-500 dark:text-slate-400 py-6">
123+
No models match your filters.
124+
</div>
125+
{/if}
73126
</div>
74127
{/if}
75128
</div>
76129

77130
<style>
78-
/* Optional styling */
79-
</style>
131+
.search-input {
132+
width: 100%;
133+
max-width: 420px;
134+
padding: 0.5rem 0.75rem;
135+
border-radius: 6px;
136+
border: 1px solid rgba(0, 0, 0, 0.15);
137+
background-color: transparent;
138+
color: inherit;
139+
font-size: 0.875rem;
140+
outline: none;
141+
transition: border-color 0.15s ease;
142+
}
143+
:global(.dark) .search-input {
144+
border-color: rgba(255, 255, 255, 0.2);
145+
}
146+
.search-input:focus {
147+
border-color: #6366f1;
148+
}
149+
150+
.pill {
151+
padding: 0.3rem 0.75rem;
152+
border-radius: 9999px;
153+
font-size: 0.8rem;
154+
font-weight: 600;
155+
border: 1px solid rgba(0, 0, 0, 0.15);
156+
background-color: transparent;
157+
color: inherit;
158+
cursor: pointer;
159+
transition: background-color 0.15s ease, border-color 0.15s ease, color 0.15s ease;
160+
}
161+
:global(.dark) .pill {
162+
border-color: rgba(255, 255, 255, 0.2);
163+
}
164+
.pill:hover {
165+
background-color: rgba(0, 0, 0, 0.05);
166+
}
167+
:global(.dark) .pill:hover {
168+
background-color: rgba(255, 255, 255, 0.08);
169+
}
170+
.pill.active {
171+
background-color: #6366f1;
172+
border-color: #6366f1;
173+
color: white;
174+
}
175+
</style>

frontend/src/lib/modelMetrics.ts

Lines changed: 21 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,28 @@
1-
const SGLANG_BASE = "https://metrics.swissai.svc.cscs.ch/d/sglang-monitoring/sglang-monitoring?orgId=1&from=now-15m&to=now&timezone=browser&refresh=5s&var-model=";
2-
const VLLM_BASE = "https://metrics.swissai.svc.cscs.ch/d/vllm-master-v2/vllm-monitoring-v2?orgId=1&from=now-15m&to=now&timezone=browser&refresh=5s&var-model_name=";
1+
const METRICS_BASE = "https://metrics.swissai.svc.cscs.ch/d/inference-unified/inference-monitoring-vllm-2b-sglang?orgId=1&from=now-15m&to=now&timezone=browser&var-datasource=PBFA97CFB590B2093&refresh=30s&var-model_name=";
32

4-
type Engine = "sglang" | "vllm";
3+
export type HostingTier = "L2" | "slurm";
54

6-
const modelEngines: Record<string, Engine> = {
7-
"swiss-ai/Apertus-8B-Instruct-2509": "sglang",
8-
"zai-org/GLM-4.7-Flash": "sglang",
9-
"Snowflake/snowflake-arctic-embed-l-v2.0": "vllm",
10-
"cais/HarmBench-Llama-2-13b-cls": "vllm",
11-
"meta-llama/Llama-3.3-70B-Instruct": "sglang",
12-
"meta-llama/Llama-Guard-4-12B": "vllm",
13-
"swiss-ai/Apertus-70B-Instruct-2509": "vllm",
14-
"Qwen/Qwen3.5-27B": "vllm",
5+
type ModelConfig = {
6+
metrics?: boolean;
7+
tier?: HostingTier;
8+
};
9+
10+
const models: Record<string, ModelConfig> = {
11+
"swiss-ai/Apertus-8B-Instruct-2509": { tier: "L2" },
12+
"zai-org/GLM-4.7-Flash": { tier: "L2" },
13+
"Snowflake/snowflake-arctic-embed-l-v2.0": { tier: "L2" },
14+
"cais/HarmBench-Llama-2-13b-cls": { tier: "L2" },
15+
"meta-llama/Llama-3.3-70B-Instruct": { tier: "L2" },
16+
"meta-llama/Llama-Guard-4-12B": { tier: "L2" },
17+
"swiss-ai/Apertus-70B-Instruct-2509": { tier: "L2" },
18+
"Qwen/Qwen3.5-27B": { tier: "L2" },
1519
};
1620

17-
/**
18-
* Get the metrics dashboard URL for a model, or null if none exists
19-
*/
2021
export function getModelMetricsUrl(modelName: string): string | null {
21-
const engine = modelEngines[modelName];
22-
if (!engine) return null;
22+
if (models[modelName]?.metrics === false) return null;
23+
return `${METRICS_BASE}${encodeURIComponent(modelName)}`;
24+
}
2325

24-
const encoded = encodeURIComponent(modelName);
25-
return engine === "sglang"
26-
? `${SGLANG_BASE}${encoded}`
27-
: `${VLLM_BASE}${encoded}`;
26+
export function getModelTier(modelName: string): HostingTier {
27+
return models[modelName]?.tier ?? "slurm";
2828
}

0 commit comments

Comments
 (0)