Skip to content

Commit 8860571

Browse files
Add WebGPU indicator
1 parent 8cf5413 commit 8860571

File tree

3 files changed

+61
-2
lines changed

3 files changed

+61
-2
lines changed

src/styles.css

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,17 @@ body {
4444
.muted { color: var(--muted); }
4545
.brand::after { content: ' • v0.1'; color: var(--muted); font-weight: 400; margin-left: 6px; }
4646

47+
.badge {
48+
display: inline-flex; align-items: center; gap: 8px;
49+
padding: 4px 8px; border-radius: 999px; font-size: 12px;
50+
background: var(--panel-2); border: 1px solid var(--border);
51+
}
52+
.badge.ok { color: var(--ok); }
53+
.badge.warn { color: var(--danger); }
54+
.dot { width: 8px; height: 8px; border-radius: 50%; }
55+
.dot.ok { background: var(--ok); }
56+
.dot.warn { background: var(--danger); }
57+
4758
.container {
4859
display: grid;
4960
grid-template-columns: 280px 1fr;

src/ui/App.tsx

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,11 @@ import { useChatStore } from '../ui/chat/useChatStore';
33
import { ChatPanel } from '../ui/chat/ChatPanel';
44
import { useEngine } from '../ui/model/useEngine';
55
import { usePrebuiltModels } from '../ui/model/usePrebuiltModels';
6+
import { useWebGPU } from '../ui/model/useWebGPU';
67

78
export default function App() {
89
const modelList = usePrebuiltModels();
10+
const webgpu = useWebGPU();
911
const [selectedModel, setSelectedModel] = useState<string>(modelList[0]?.id || '');
1012
const engine = useEngine();
1113
const chat = useChatStore(selectedModel);
@@ -28,9 +30,13 @@ export default function App() {
2830
return (
2931
<div className="app">
3032
<header className="topbar">
31-
<div className="row">
33+
<div className="row" style={{ gap: 12 }}>
3234
<div className="brand">Web LLM Starter</div>
33-
<div className="muted">React + WebGPU</div>
35+
<div className={`badge ${webgpu.supported ? 'ok' : 'warn'}`} title={webgpu.supported ? `Adapter: ${webgpu.adapter}` : (webgpu.error || 'WebGPU unsupported')}>
36+
<span className={`dot ${webgpu.supported ? 'ok' : 'warn'}`} />
37+
WebGPU {webgpu.supported ? 'On' : 'Off'}
38+
</div>
39+
{webgpu.supported && <div className="muted" title={webgpu.features.join(', ') || undefined}>{webgpu.adapter}</div>}
3440
</div>
3541
<div className="row">
3642
<span className="muted">Tok/s</span>
@@ -54,6 +60,11 @@ export default function App() {
5460
<option key={m.id} value={m.id}>{m.label}</option>
5561
))}
5662
</select>
63+
{!webgpu.supported && (
64+
<div className="muted" style={{ fontSize: 12 }}>
65+
WebGPU not detected. You can still try, but performance may be limited.
66+
</div>
67+
)}
5768

5869
{engine.state !== 'ready' ? (
5970
<button

src/ui/model/useWebGPU.ts

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import { useEffect, useState } from 'react';
2+
3+
export type WebGPUInfo = {
4+
supported: boolean;
5+
adapter: string;
6+
features: string[];
7+
error?: string;
8+
};
9+
10+
export function useWebGPU(): WebGPUInfo {
11+
const [info, setInfo] = useState<WebGPUInfo>({ supported: false, adapter: '', features: [] });
12+
13+
useEffect(() => {
14+
const supported = typeof navigator !== 'undefined' && 'gpu' in navigator;
15+
if (!supported) {
16+
setInfo({ supported: false, adapter: '', features: [], error: 'WebGPU not detected' });
17+
return;
18+
}
19+
(async () => {
20+
try {
21+
const adapter: GPUAdapter | null = await (navigator as any).gpu.requestAdapter();
22+
if (!adapter) {
23+
setInfo({ supported: false, adapter: '', features: [], error: 'No compatible adapter' });
24+
return;
25+
}
26+
const features = Array.from((adapter.features as any)?.values?.() ?? []);
27+
const name = (adapter as any).name || 'GPU';
28+
setInfo({ supported: true, adapter: String(name), features: features.map(String) });
29+
} catch (e) {
30+
setInfo({ supported: false, adapter: '', features: [], error: (e as Error).message });
31+
}
32+
})();
33+
}, []);
34+
35+
return info;
36+
}
37+

0 commit comments

Comments
 (0)