chrome-devtools-frontend
Version:
Chrome DevTools UI
160 lines (144 loc) • 5.57 kB
text/typescript
// Copyright 2025 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import './CollapsibleAssistanceContentWidget.js';
import './PerformanceAgentFlameChart.js';
import * as AiAssistanceModel from '../../../models/ai_assistance/ai_assistance.js';
import * as Logs from '../../../models/logs/logs.js';
import * as NetworkTimeCalculator from '../../../models/network_time_calculator/network_time_calculator.js';
import * as Trace from '../../../models/trace/trace.js';
import * as UI from '../../../ui/legacy/legacy.js';
import * as Lit from '../../../ui/lit/lit.js';
import * as Network from '../../network/network.js';
import * as Insights from '../../timeline/components/insights/insights.js';
import artifactsViewerStyles from './artifactsViewer.css.js';
import type * as PerformanceAgentFlameChart from './PerformanceAgentFlameChart.js';
const {html, render} = Lit;
export interface ViewInput {
artifacts: AiAssistanceModel.ArtifactsManager.Artifact[];
parsedTrace: Trace.TraceModel.ParsedTrace;
}
export function renderArtifact(
artifact: AiAssistanceModel.ArtifactsManager.Artifact, parsedTrace: Trace.TraceModel.ParsedTrace): Lit.LitTemplate {
switch (artifact.type) {
// clang-format off
case 'insight': {
const insightRenderer = new Insights.InsightRenderer.InsightRenderer();
const componentName = artifact.insightType;
const insightSet = parsedTrace.insights?.values().next().value;
const insightModel = insightSet?.model[componentName as Trace.Insights.Types.InsightKeys];
if (!insightModel) {
return Lit.nothing;
}
return html`
<devtools-collapsible-assistance-content-widget .data=${{headerText: `Insight - ${componentName}`}}>
${insightRenderer.renderInsightToWidgetElement(parsedTrace, insightSet, insightModel, componentName, {
selected: true,
isAIAssistanceContext: true,
})}
</devtools-collapsible-assistance-content-widget>`;
}
case 'network-request': {
const networkRequest = artifact.request;
if ('args' in networkRequest && Trace.Types.Events.isSyntheticNetworkRequest(networkRequest)) {
const calculator = new NetworkTimeCalculator.NetworkTimeCalculator(true);
const sdkRequest = Logs.NetworkLog.NetworkLog.instance()
.requestsForId(networkRequest.args.data.requestId)
.find(r => r.url() === networkRequest.args.data.url) ??
null;
if (!sdkRequest) {
return Lit.nothing;
}
return html`
<devtools-collapsible-assistance-content-widget
.data=${{headerText: `Network Request: ${
sdkRequest.url().length > 80 ? sdkRequest.url().slice(0, 80) + '...' : sdkRequest.url()}`}}>
<devtools-widget class="actions" .widgetConfig=${UI.Widget.widgetConfig(Network.RequestTimingView.RequestTimingView, {
request: sdkRequest,
calculator,
})}></devtools-widget>
</devtools-collapsible-assistance-content-widget>`;
}
return Lit.nothing;
}
case 'flamechart': {
return html`
<devtools-collapsible-assistance-content-widget .data=${{headerText: `Flamechart`}}>
<devtools-performance-agent-flame-chart .data=${{
parsedTrace,
start: artifact.start,
end: artifact.end,
} as PerformanceAgentFlameChart.PerformanceAgentFlameChartData}>
</devtools-performance-agent-flame-chart>
</devtools-collapsible-assistance-content-widget>`;
}
default:
return Lit.nothing;
// clang-format on
}
}
export const DEFAULT_VIEW = (
input: ViewInput,
_output: Record<string, unknown>,
target: HTMLElement,
): void => {
// clang-format off
render(
html`
<style>${artifactsViewerStyles}</style>
<div class="artifacts-viewer">
${input.artifacts.map(artifact => renderArtifact(artifact, input.parsedTrace))}
</div>
`,
target
);
// clang-format on
};
export type View = typeof DEFAULT_VIEW;
export class ArtifactsViewer extends UI.Widget.Widget {
#view: View;
#parsedTrace: Trace.TraceModel.ParsedTrace|null;
constructor(element?: HTMLElement, view = DEFAULT_VIEW) {
super(element);
this.#view = view;
this.#parsedTrace = null;
}
override wasShown(): void {
super.wasShown();
AiAssistanceModel.ArtifactsManager.ArtifactsManager.instance().addEventListener(
AiAssistanceModel.ArtifactsManager.ArtifactAddedEvent.eventName,
() => {
if (this.#parsedTrace) {
this.performUpdate();
}
},
);
UI.Context.Context.instance().addFlavorChangeListener(
AiAssistanceModel.AIContext.AgentFocus,
({data}) => {
this.#parsedTrace = data.parsedTrace;
if (this.#parsedTrace) {
this.performUpdate();
}
},
);
const focus = UI.Context.Context.instance().flavor(AiAssistanceModel.AIContext.AgentFocus);
if (focus) {
this.#parsedTrace = focus.parsedTrace;
this.performUpdate();
}
}
override performUpdate(): void {
if (!this.#parsedTrace) {
return;
}
this.#view(
{
artifacts: AiAssistanceModel.ArtifactsManager.ArtifactsManager.instance().artifacts,
parsedTrace: this.#parsedTrace,
},
{},
this.contentElement,
);
}
}