chore: snapshot working tree - pty_exited notifications + in-flight inference WIP
feat(booterm): structured pty_exited WS notifications. Plan-validated, impl-validated, code-reviewed green (contracts build clean, contracts test 29/29, booterm + web typecheck clean). wip: in-progress inference/provider refactor (agents.ts, provider.ts, new llama-providers.ts, removed llama-args-validator), plus arena, dispatcher, compaction, schema changes. openspec: pty-exit-notifications complete; x-agent-flags planned (not yet implemented).
This commit is contained in:
107
apps/web/src/components/control/VramGauge.tsx
Normal file
107
apps/web/src/components/control/VramGauge.tsx
Normal file
@@ -0,0 +1,107 @@
|
||||
import { useEffect, useRef } from 'react';
|
||||
import * as echarts from 'echarts/core';
|
||||
import { GaugeChart } from 'echarts/charts';
|
||||
import { CanvasRenderer } from 'echarts/renderers';
|
||||
import type { EChartsType } from 'echarts/core';
|
||||
import { buildEChartsTheme } from './buildEChartsTheme';
|
||||
|
||||
echarts.use([GaugeChart, CanvasRenderer]);
|
||||
|
||||
interface VramGaugeProps {
|
||||
used: number; // MB
|
||||
total: number; // MB
|
||||
size?: number;
|
||||
}
|
||||
|
||||
export function VramGauge({ used, total, size = 120 }: VramGaugeProps) {
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const chartRef = useRef<EChartsType | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (!containerRef.current) return;
|
||||
|
||||
if (!chartRef.current) {
|
||||
const theme = buildEChartsTheme();
|
||||
chartRef.current = echarts.init(containerRef.current, theme);
|
||||
}
|
||||
|
||||
const chart = chartRef.current;
|
||||
const root = getComputedStyle(document.documentElement);
|
||||
const get = (prop: string) => root.getPropertyValue(prop).trim();
|
||||
|
||||
const pct = total > 0 ? Math.round((used / total) * 100) : 0;
|
||||
|
||||
// Derive gauge progress color from CSS custom properties
|
||||
// Green -> Amber -> Red as utilization increases
|
||||
let color = get('--glow-green');
|
||||
if (pct > 80) color = get('--glow-red');
|
||||
else if (pct > 60) color = get('--glow-amber');
|
||||
|
||||
chart.setOption({
|
||||
backgroundColor: 'transparent',
|
||||
series: [
|
||||
{
|
||||
type: 'gauge',
|
||||
startAngle: 220,
|
||||
endAngle: -40,
|
||||
min: 0,
|
||||
max: total,
|
||||
radius: '90%',
|
||||
center: ['50%', '55%'],
|
||||
pointer: { show: false },
|
||||
progress: {
|
||||
show: true,
|
||||
overlap: false,
|
||||
roundCap: true,
|
||||
clip: false,
|
||||
itemStyle: { color },
|
||||
width: 8,
|
||||
},
|
||||
axisLine: {
|
||||
lineStyle: {
|
||||
width: 8,
|
||||
color: [[1, get('--border')]],
|
||||
},
|
||||
},
|
||||
axisTick: { show: false },
|
||||
splitLine: { show: false },
|
||||
axisLabel: { show: false },
|
||||
title: {
|
||||
show: true,
|
||||
offsetCenter: ['0%', '-10%'],
|
||||
fontSize: 11,
|
||||
color: get('--muted-foreground'),
|
||||
fontFamily: 'Inter',
|
||||
},
|
||||
detail: {
|
||||
show: true,
|
||||
offsetCenter: ['0%', '10%'],
|
||||
fontSize: 18,
|
||||
fontWeight: 'bold',
|
||||
color: get('--foreground'),
|
||||
fontFamily: 'Orbitron',
|
||||
formatter: () => `${used} / ${total} MB`,
|
||||
},
|
||||
data: [{ value: used, name: 'VRAM' }],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const observer = new ResizeObserver(() => chart.resize());
|
||||
observer.observe(containerRef.current);
|
||||
|
||||
return () => {
|
||||
observer.disconnect();
|
||||
chart.dispose();
|
||||
chartRef.current = null;
|
||||
};
|
||||
}, [used, total]);
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={containerRef}
|
||||
className="flex items-center justify-center"
|
||||
style={{ width: size, height: size }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user