Spaces:
Running
Running
File size: 7,002 Bytes
0ed8124 c3633b1 6c62075 0ed8124 6c62075 0ed8124 7142bcd 0ed8124 7142bcd 0ed8124 6c62075 c3633b1 6c62075 c3633b1 6c62075 0ed8124 6c62075 0ed8124 6c62075 0ed8124 c3633b1 6c62075 0ed8124 6c62075 0ed8124 6c62075 0ed8124 c3633b1 0ed8124 6c62075 0ed8124 7142bcd 0ed8124 6c62075 0ed8124 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 | import type { ArtifactDocument } from '../../lib/agent-tools';
import type { TelemetrySnapshot, ToolEvent } from '../../lib/contracts';
import { formatBytes, formatPercent } from '../../lib/format';
interface InspectorPanelProps {
hidden?: boolean;
telemetry: TelemetrySnapshot;
toolEvents: ToolEvent[];
artifacts: ArtifactDocument[];
onSelectArtifact: (artifactId: string) => void;
}
export function InspectorPanel({ hidden = false, telemetry, toolEvents, artifacts, onSelectArtifact }: InspectorPanelProps) {
const contextRatio = telemetry.contextLimit > 0 ? telemetry.contextUsed / telemetry.contextLimit : 0;
const prefillRatio = telemetry.promptTotal > 0 ? telemetry.promptProcessed / telemetry.promptTotal : 0;
const activeRate = telemetry.inferencePhase === 'prefill'
? telemetry.prefillTokensPerSecond
: telemetry.decodeTokensPerSecond || telemetry.tokensPerSecond;
const speedRatio = Math.min(1, Math.max(0, activeRate / 50));
const gaugeLabel = telemetry.inferencePhase === 'prefill'
? 'Prefill'
: telemetry.inferencePhase === 'decode'
? 'Decode'
: 'Average';
const samplingLabel = telemetry.inferencePhase === 'idle'
? 'Ready'
: telemetry.inferencePhase === 'complete' ? 'Average' : 'Live';
return (
<aside className="inspector" aria-label="Runtime inspector" hidden={hidden}>
<section className="inspector-section telemetry-section" aria-labelledby="telemetry-title">
<div className="section-heading">
<h2 id="telemetry-title">Telemetry</h2>
<span className={`sampling-label phase-${telemetry.inferencePhase}`}>{samplingLabel}</span>
</div>
<div className="speed-gauge">
<div className="gauge-ring" style={{ '--gauge': `${Math.round(speedRatio * 100)}%` } as React.CSSProperties}>
<div>
<strong>{activeRate.toFixed(1)}</strong>
<small><span>{gaugeLabel}</span><span>tok/s</span></small>
</div>
</div>
<dl>
<div><dt>Backend</dt><dd>{telemetry.backend}</dd></div>
<div><dt>First token</dt><dd>{telemetry.timeToFirstTokenMs} ms</dd></div>
<div><dt>Graph splits</dt><dd>{telemetry.graphSplits ?? '—'}</dd></div>
</dl>
</div>
<div className="telemetry-live-grid" aria-label="Live inference rates">
<TelemetryMetric
label="Prefill"
value={`${telemetry.prefillTokensPerSecond.toFixed(1)} tok/s`}
detail={telemetry.promptTotal > 0
? `${telemetry.promptProcessed.toLocaleString()} / ${telemetry.promptTotal.toLocaleString()} tokens`
: 'No prompt yet'}
ratio={prefillRatio}
active={telemetry.inferencePhase === 'prefill'}
progressLabel={telemetry.promptTotal > 0 ? formatPercent(prefillRatio) : 'Waiting'}
/>
<TelemetryMetric
label="Decode"
value={`${telemetry.decodeTokensPerSecond.toFixed(1)} tok/s`}
detail={`${telemetry.completionTokens.toLocaleString()} generated tokens`}
ratio={telemetry.decodeTokensPerSecond / 50}
active={telemetry.inferencePhase === 'decode'}
progressLabel="Rate / 50 tok/s"
/>
</div>
<div className="device-card">
<span className="device-icon" aria-hidden="true"><i /><i /><i /></span>
<div>
<small>Active adapter</small>
<strong>{telemetry.device}</strong>
</div>
<span>{telemetry.gpuLayers}<small>layers</small></span>
</div>
<Meter label="Context" value={contextRatio} detail={`${telemetry.contextUsed.toLocaleString()} / ${telemetry.contextLimit.toLocaleString()}`} />
<div className="memory-grid">
<div><small>Model allocation</small><strong>{formatBytes(telemetry.modelMemoryBytes)}</strong></div>
<div><small>KV allocation</small><strong>{formatBytes(telemetry.kvMemoryBytes)}</strong></div>
</div>
</section>
<section className="inspector-section tool-log-section" aria-labelledby="tool-log-title">
<div className="section-heading compact">
<h2 id="tool-log-title">Tool log</h2>
</div>
<ol className="event-log">
{toolEvents.length === 0 && <li className="event-empty">No tool calls in this session.</li>}
{toolEvents.map((event) => (
<li key={event.id}>
<span className={`event-pip event-${event.state}`} />
<time>{event.timestamp}</time>
<div><strong>{event.label}</strong><small>{event.detail}</small></div>
</li>
))}
</ol>
</section>
<section className="inspector-section artifact-index-section" aria-labelledby="artifact-index-title">
<div className="section-heading compact">
<h2 id="artifact-index-title">Artifacts</h2>
<span className="artifact-count" aria-label={`${artifacts.length} artifacts`}>{artifacts.length}</span>
</div>
{artifacts.length === 0 ? (
<p className="artifact-empty">Generated HTML artifacts will be indexed here and rendered in the conversation.</p>
) : (
<ol className="artifact-index">
{artifacts.map((artifact, index) => (
<li key={artifact.id}>
<button type="button" onClick={() => onSelectArtifact(artifact.id)}>
<span>{String(index + 1).padStart(2, '0')}</span>
<strong>{artifact.title}</strong>
<svg viewBox="0 0 20 20" aria-hidden="true"><path d="m7 4 6 6-6 6" /></svg>
</button>
</li>
))}
</ol>
)}
</section>
</aside>
);
}
function TelemetryMetric({
label,
value,
detail,
ratio,
active,
progressLabel,
}: {
label: string;
value: string;
detail: string;
ratio: number;
active: boolean;
progressLabel: string;
}) {
const percentage = Math.round(Math.min(1, Math.max(0, ratio)) * 100);
return (
<div className={`telemetry-metric${active ? ' is-active' : ''}`}>
<div><span>{label}</span><strong>{value}</strong></div>
<small>{detail}</small>
<div className="telemetry-meter" role="meter" aria-label={`${label}: ${progressLabel}`} aria-valuemin={0} aria-valuemax={100} aria-valuenow={percentage}>
<span style={{ width: `${percentage}%` }} />
</div>
</div>
);
}
function Meter({ label, value, detail }: { label: string; value: number; detail: string }) {
const percentage = Math.round(Math.min(1, Math.max(0, value)) * 100);
return (
<div className="meter">
<div><span>{label}</span><small>{detail}</small></div>
<div className="meter-track" role="meter" aria-label={label} aria-valuemin={0} aria-valuemax={100} aria-valuenow={percentage}>
<span style={{ width: `${percentage}%` }} />
</div>
</div>
);
}
|