chrome-devtools-frontend 1.0.1596535 → 1.0.1597624
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/agents/prompts/ui-widgets.md +7 -8
- package/docs/ui_engineering.md +10 -11
- package/front_end/core/host/AidaClient.ts +4 -0
- package/front_end/core/host/InspectorFrontendHostAPI.ts +1 -0
- package/front_end/core/host/UserMetrics.ts +12 -0
- package/front_end/core/root/Runtime.ts +5 -0
- package/front_end/core/sdk/CPUThrottlingManager.ts +14 -13
- package/front_end/core/sdk/CSSMatchedStyles.ts +2 -0
- package/front_end/core/sdk/CSSPropertyParserMatchers.ts +28 -0
- package/front_end/core/sdk/PageResourceLoader.ts +22 -1
- package/front_end/devtools_compatibility.js +2 -1
- package/front_end/models/ai_assistance/AiConversation.ts +29 -8
- package/front_end/models/ai_assistance/ChangeManager.ts +16 -0
- package/front_end/models/ai_assistance/ExtensionScope.ts +11 -3
- package/front_end/models/ai_assistance/agents/AccessibilityAgent.ts +127 -0
- package/front_end/models/ai_assistance/agents/AiAgent.ts +26 -3
- package/front_end/models/ai_assistance/agents/ContextSelectionAgent.snapshot.txt +1 -1
- package/front_end/models/ai_assistance/agents/ContextSelectionAgent.ts +11 -8
- package/front_end/models/ai_assistance/agents/StylingAgent.snapshot.txt +24 -0
- package/front_end/models/ai_assistance/agents/StylingAgent.ts +323 -16
- package/front_end/models/ai_assistance/ai_assistance.ts +2 -0
- package/front_end/models/ai_assistance/data_formatters/PerformanceInsightFormatter.ts +27 -0
- package/front_end/models/ai_assistance/data_formatters/PerformanceTraceFormatter.snapshot.txt +21 -0
- package/front_end/models/greendev/Prototypes.ts +7 -1
- package/front_end/models/trace/Processor.ts +1 -0
- package/front_end/models/trace/handlers/PageLoadMetricsHandler.ts +33 -0
- package/front_end/models/trace/insights/CharacterSet.ts +172 -0
- package/front_end/models/trace/insights/Models.ts +1 -0
- package/front_end/models/trace/insights/types.ts +1 -0
- package/front_end/models/trace/types/TraceEvents.ts +17 -0
- package/front_end/panels/ai_assistance/AiAssistancePanel.ts +51 -36
- package/front_end/panels/ai_assistance/PatchWidget.ts +6 -6
- package/front_end/panels/ai_assistance/components/ChatMessage.ts +93 -74
- package/front_end/panels/ai_assistance/components/ChatView.ts +6 -11
- package/front_end/panels/ai_assistance/components/MarkdownRendererWithCodeBlock.ts +18 -9
- package/front_end/panels/ai_assistance/components/StylingAgentMarkdownRenderer.ts +200 -0
- package/front_end/panels/ai_assistance/components/chatMessage.css +11 -8
- package/front_end/panels/application/AppManifestView.ts +3 -4
- package/front_end/panels/application/DeviceBoundSessionsView.ts +18 -22
- package/front_end/panels/application/FrameDetailsView.ts +9 -15
- package/front_end/panels/application/OriginTrialTreeView.ts +2 -3
- package/front_end/panels/application/ReportingApiView.ts +13 -17
- package/front_end/panels/application/ServiceWorkerCacheViews.ts +1 -1
- package/front_end/panels/application/components/BackForwardCacheView.ts +3 -3
- package/front_end/panels/application/preloading/components/UsedPreloadingView.ts +2 -3
- package/front_end/panels/browser_debugger/DOMBreakpointsSidebarPane.ts +3 -2
- package/front_end/panels/changes/ChangesView.ts +6 -4
- package/front_end/panels/common/ExtensionServer.ts +15 -0
- package/front_end/panels/console/ConsolePinPane.ts +3 -3
- package/front_end/panels/coverage/CoverageListView.ts +1 -1
- package/front_end/panels/css_overview/CSSOverviewPanel.ts +11 -15
- package/front_end/panels/developer_resources/DeveloperResourcesView.ts +3 -5
- package/front_end/panels/elements/ElementsTreeElement.ts +55 -47
- package/front_end/panels/elements/ElementsTreeOutline.ts +149 -28
- package/front_end/panels/elements/EventListenersWidget.ts +3 -2
- package/front_end/panels/elements/StandaloneStylesContainer.ts +21 -6
- package/front_end/panels/elements/StylePropertyTreeElement.ts +49 -4
- package/front_end/panels/layer_viewer/Layers3DView.ts +5 -4
- package/front_end/panels/lighthouse/LighthousePanel.ts +8 -0
- package/front_end/panels/linear_memory_inspector/components/LinearMemoryInspector.ts +5 -6
- package/front_end/panels/linear_memory_inspector/components/LinearMemoryValueInterpreter.ts +6 -11
- package/front_end/panels/network/RequestCookiesView.ts +3 -4
- package/front_end/panels/network/RequestInitiatorView.ts +7 -5
- package/front_end/panels/network/RequestResponseView.ts +10 -15
- package/front_end/panels/protocol_monitor/ProtocolMonitor.ts +3 -4
- package/front_end/panels/recorder/components/RecordingView.ts +31 -36
- package/front_end/panels/recorder/components/StepEditor.ts +6 -7
- package/front_end/panels/search/SearchView.ts +2 -3
- package/front_end/panels/settings/SettingsScreen.ts +3 -2
- package/front_end/panels/settings/WorkspaceSettingsTab.ts +2 -5
- package/front_end/panels/timeline/components/LiveMetricsView.ts +5 -8
- package/front_end/panels/timeline/components/insights/Cache.ts +8 -10
- package/front_end/panels/timeline/components/insights/CharacterSet.ts +38 -0
- package/front_end/panels/timeline/components/insights/DOMSize.ts +16 -20
- package/front_end/panels/timeline/components/insights/DocumentLatency.ts +2 -6
- package/front_end/panels/timeline/components/insights/DuplicatedJavaScript.ts +3 -4
- package/front_end/panels/timeline/components/insights/FontDisplay.ts +3 -4
- package/front_end/panels/timeline/components/insights/ForcedReflow.ts +5 -7
- package/front_end/panels/timeline/components/insights/INPBreakdown.ts +3 -4
- package/front_end/panels/timeline/components/insights/ImageDelivery.ts +3 -4
- package/front_end/panels/timeline/components/insights/ImageRef.ts +2 -4
- package/front_end/panels/timeline/components/insights/InsightRenderer.ts +2 -0
- package/front_end/panels/timeline/components/insights/LCPBreakdown.ts +5 -7
- package/front_end/panels/timeline/components/insights/LCPDiscovery.ts +2 -4
- package/front_end/panels/timeline/components/insights/LegacyJavaScript.ts +3 -4
- package/front_end/panels/timeline/components/insights/ModernHTTP.ts +3 -4
- package/front_end/panels/timeline/components/insights/NetworkDependencyTree.ts +7 -11
- package/front_end/panels/timeline/components/insights/NodeLink.ts +2 -4
- package/front_end/panels/timeline/components/insights/RenderBlocking.ts +3 -4
- package/front_end/panels/timeline/components/insights/SlowCSSSelector.ts +7 -10
- package/front_end/panels/timeline/components/insights/ThirdParties.ts +5 -7
- package/front_end/panels/timeline/components/insights/insights.ts +2 -0
- package/front_end/panels/web_audio/WebAudioView.ts +3 -4
- package/front_end/ui/components/settings/SettingCheckbox.ts +2 -0
- package/front_end/ui/legacy/UIUtils.ts +5 -5
- package/front_end/ui/legacy/Widget.ts +33 -2
- package/front_end/ui/legacy/components/data_grid/DataGridElement.ts +3 -3
- package/front_end/ui/legacy/components/object_ui/ObjectPropertiesSection.ts +8 -8
- package/front_end/ui/visual_logging/Debugging.ts +0 -32
- package/front_end/ui/visual_logging/KnownContextValues.ts +3 -0
- package/package.json +1 -1
|
@@ -13,7 +13,8 @@ import * as Root from '../../../core/root/root.js';
|
|
|
13
13
|
import * as SDK from '../../../core/sdk/sdk.js';
|
|
14
14
|
import type * as Protocol from '../../../generated/protocol.js';
|
|
15
15
|
import type {
|
|
16
|
-
AiWidget, ComputedStyleAiWidget, CoreVitalsAiWidget} from
|
|
16
|
+
AiWidget, ComputedStyleAiWidget, CoreVitalsAiWidget, StylePropertiesAiWidget} from
|
|
17
|
+
'../../../models/ai_assistance/agents/AiAgent.js';
|
|
17
18
|
import * as AiAssistanceModel from '../../../models/ai_assistance/ai_assistance.js';
|
|
18
19
|
import * as ComputedStyle from '../../../models/computed_style/computed_style.js';
|
|
19
20
|
import * as Marked from '../../../third_party/marked/marked.js';
|
|
@@ -34,6 +35,7 @@ import {walkthroughTitle, WalkthroughView} from './WalkthroughView.js';
|
|
|
34
35
|
|
|
35
36
|
const {html, Directives: {ref, ifDefined}} = Lit;
|
|
36
37
|
const lockedString = i18n.i18n.lockedString;
|
|
38
|
+
const {widget} = UI.Widget;
|
|
37
39
|
|
|
38
40
|
const REPORT_URL = 'https://crbug.com/364805393' as Platform.DevToolsPath.UrlString;
|
|
39
41
|
const SCROLL_ROUNDING_OFFSET = 1;
|
|
@@ -100,6 +102,10 @@ const UIStringsNotTranslate = {
|
|
|
100
102
|
* @description The error message when the LLM gets stuck in a loop (max steps reached).
|
|
101
103
|
*/
|
|
102
104
|
maxStepsError: 'Seems like I am stuck with the investigation. It would be better if you start over.',
|
|
105
|
+
/**
|
|
106
|
+
* @description The error message when the LLM selects context from a different origin.
|
|
107
|
+
*/
|
|
108
|
+
crossOriginError: 'I have selected the new context but you will have to start a new chat.',
|
|
103
109
|
/**
|
|
104
110
|
* @description Displayed when the user stop the response
|
|
105
111
|
*/
|
|
@@ -120,10 +126,6 @@ const UIStringsNotTranslate = {
|
|
|
120
126
|
* @description Gemini (do not translate)
|
|
121
127
|
*/
|
|
122
128
|
gemini: 'Gemini',
|
|
123
|
-
/**
|
|
124
|
-
* @description The fallback text when we can't find the user full name
|
|
125
|
-
*/
|
|
126
|
-
you: 'You',
|
|
127
129
|
/**
|
|
128
130
|
* @description The fallback text when a step has no title yet
|
|
129
131
|
*/
|
|
@@ -156,10 +158,6 @@ const UIStringsNotTranslate = {
|
|
|
156
158
|
* @description Alt text for the image input (displayed in the chat messages) that has been sent to the model.
|
|
157
159
|
*/
|
|
158
160
|
imageInputSentToTheModel: 'Image input sent to the model',
|
|
159
|
-
/**
|
|
160
|
-
* @description Alt text for the account avatar.
|
|
161
|
-
*/
|
|
162
|
-
accountAvatar: 'Account avatar',
|
|
163
161
|
/**
|
|
164
162
|
* @description Title for the link which wraps the image input rendered in chat messages.
|
|
165
163
|
*/
|
|
@@ -212,7 +210,12 @@ export interface StepPart {
|
|
|
212
210
|
step: Step;
|
|
213
211
|
}
|
|
214
212
|
|
|
215
|
-
export
|
|
213
|
+
export interface WidgetPart {
|
|
214
|
+
type: 'widget';
|
|
215
|
+
widgets: AiWidget[];
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
export type ModelMessagePart = AnswerPart|StepPart|WidgetPart;
|
|
216
219
|
|
|
217
220
|
export interface UserChatMessage {
|
|
218
221
|
entity: ChatMessageEntity.USER;
|
|
@@ -272,7 +275,6 @@ export interface MessageInput {
|
|
|
272
275
|
isReadOnly: boolean;
|
|
273
276
|
isLastMessage: boolean;
|
|
274
277
|
canShowFeedbackForm: boolean;
|
|
275
|
-
userInfo: Pick<Host.InspectorFrontendHostAPI.SyncInformation, 'accountImage'|'accountFullName'|'accountGivenName'>;
|
|
276
278
|
markdownRenderer: MarkdownLitRenderer;
|
|
277
279
|
onSuggestionClick: (suggestion: string) => void;
|
|
278
280
|
onFeedbackSubmit: (rpcId: Host.AidaClient.RpcGlobalId, rate: Host.AidaClient.Rating, feedback?: string) => void;
|
|
@@ -289,14 +291,6 @@ export interface MessageInput {
|
|
|
289
291
|
export const DEFAULT_VIEW = (input: ChatMessageViewInput, output: ViewOutput, target: HTMLElement): void => {
|
|
290
292
|
const message = input.message;
|
|
291
293
|
if (message.entity === ChatMessageEntity.USER) {
|
|
292
|
-
const givenName = AiAssistanceModel.AiUtils.isGeminiBranding() ? input.userInfo.accountGivenName : '';
|
|
293
|
-
const name = givenName || input.userInfo.accountFullName || lockedString(UIStringsNotTranslate.you);
|
|
294
|
-
const image = input.userInfo.accountImage ?
|
|
295
|
-
html`<img src="data:image/png;base64, ${input.userInfo.accountImage}" alt=${
|
|
296
|
-
UIStringsNotTranslate.accountAvatar} />` :
|
|
297
|
-
html`<devtools-icon
|
|
298
|
-
name="profile"
|
|
299
|
-
></devtools-icon>`;
|
|
300
294
|
const imageInput = message.imageInput && 'inlineData' in message.imageInput ?
|
|
301
295
|
renderImageChatMessage(message.imageInput.inlineData) :
|
|
302
296
|
Lit.nothing;
|
|
@@ -308,12 +302,6 @@ export const DEFAULT_VIEW = (input: ChatMessageViewInput, output: ViewOutput, ta
|
|
|
308
302
|
class="chat-message query ${input.isLastMessage ? 'is-last-message' : ''}"
|
|
309
303
|
jslog=${VisualLogging.section('question')}
|
|
310
304
|
>
|
|
311
|
-
<div class="message-info">
|
|
312
|
-
${image}
|
|
313
|
-
<div class="message-name">
|
|
314
|
-
<h2>${name}</h2>
|
|
315
|
-
</div>
|
|
316
|
-
</div>
|
|
317
305
|
${imageInput}
|
|
318
306
|
<div class="message-content">${renderTextAsMarkdown(message.text, input.markdownRenderer)}</div>
|
|
319
307
|
</section>
|
|
@@ -349,6 +337,9 @@ export const DEFAULT_VIEW = (input: ChatMessageViewInput, output: ViewOutput, ta
|
|
|
349
337
|
if (part.type === 'answer') {
|
|
350
338
|
return html`<p>${renderTextAsMarkdown(part.text, input.markdownRenderer, { animate: !input.isReadOnly && input.isLoading && isLastPart && input.isLastMessage })}</p>`;
|
|
351
339
|
}
|
|
340
|
+
if (part.type === 'widget') {
|
|
341
|
+
return html`${Lit.Directives.until(renderWidgets(part.widgets, {wrapperClass: 'main-widgets-wrapper'}))}`;
|
|
342
|
+
}
|
|
352
343
|
if (!aiAssistanceV2 && part.type === 'step') {
|
|
353
344
|
return renderStep({
|
|
354
345
|
step: part.step,
|
|
@@ -569,7 +560,7 @@ function renderWalkthroughUI(input: ChatMessageViewInput, steps: Step[]): Lit.Li
|
|
|
569
560
|
// clang-format off
|
|
570
561
|
const walkthroughInline = input.walkthrough.isInlined ? html`
|
|
571
562
|
<div class="walkthrough-container">
|
|
572
|
-
|
|
563
|
+
${widget(WalkthroughView, {
|
|
573
564
|
message: input.message as ModelChatMessage,
|
|
574
565
|
isLoading: input.isLoading && input.isLastMessage,
|
|
575
566
|
markdownRenderer: input.markdownRenderer,
|
|
@@ -577,7 +568,7 @@ function renderWalkthroughUI(input: ChatMessageViewInput, steps: Step[]): Lit.Li
|
|
|
577
568
|
isExpanded,
|
|
578
569
|
onToggle: input.walkthrough.onToggle,
|
|
579
570
|
onOpen: input.walkthrough.onOpen,
|
|
580
|
-
})}
|
|
571
|
+
})}
|
|
581
572
|
</div>
|
|
582
573
|
` : Lit.nothing;
|
|
583
574
|
|
|
@@ -624,7 +615,6 @@ export function renderStep({step, isLoading, markdownRenderer, isLast}: {
|
|
|
624
615
|
markdownRenderer: MarkdownLitRenderer,
|
|
625
616
|
isLast: boolean,
|
|
626
617
|
}): Lit.LitTemplate {
|
|
627
|
-
const shouldRenderWidgets = Boolean(step.widgets?.length && Root.Runtime.hostConfig.devToolsAiAssistanceV2?.enabled);
|
|
628
618
|
const stepClasses = Lit.Directives.classMap({
|
|
629
619
|
step: true,
|
|
630
620
|
empty: !step.thought && !step.code && !step.contextDetails && !step.requestApproval,
|
|
@@ -648,11 +638,8 @@ export function renderStep({step, isLoading, markdownRenderer, isLast}: {
|
|
|
648
638
|
</summary>
|
|
649
639
|
${renderStepDetails({step, markdownRenderer, isLast})}
|
|
650
640
|
</details>
|
|
651
|
-
${
|
|
652
|
-
|
|
653
|
-
${Lit.Directives.until(renderStepWidgets(step))}
|
|
654
|
-
</div>` : Lit.nothing
|
|
655
|
-
}`;
|
|
641
|
+
${Lit.Directives.until(renderWidgets(step.widgets, {wrapperClass: 'step-widgets-wrapper'}))}
|
|
642
|
+
`;
|
|
656
643
|
// clang-format on
|
|
657
644
|
}
|
|
658
645
|
|
|
@@ -661,25 +648,31 @@ interface WidgetMakerResponse {
|
|
|
661
648
|
revealable: unknown;
|
|
662
649
|
}
|
|
663
650
|
|
|
664
|
-
const
|
|
651
|
+
const nodeCache = new Map<Protocol.DOM.BackendNodeId, SDK.DOMModel.DOMNode>();
|
|
652
|
+
|
|
653
|
+
async function resolveNode(backendNodeId: Protocol.DOM.BackendNodeId): Promise<SDK.DOMModel.DOMNode|null> {
|
|
654
|
+
const cachedNode = nodeCache.get(backendNodeId);
|
|
655
|
+
if (cachedNode) {
|
|
656
|
+
return cachedNode;
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
const target = SDK.TargetManager.TargetManager.instance().primaryPageTarget();
|
|
660
|
+
if (!target) {
|
|
661
|
+
return null;
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
const node = new SDK.DOMModel.DeferredDOMNode(target, backendNodeId);
|
|
665
|
+
const resolved = await node.resolvePromise();
|
|
666
|
+
if (resolved) {
|
|
667
|
+
nodeCache.set(backendNodeId, resolved);
|
|
668
|
+
}
|
|
669
|
+
return resolved;
|
|
670
|
+
}
|
|
665
671
|
|
|
666
672
|
async function makeComputedStyleWidget(widgetData: ComputedStyleAiWidget): Promise<WidgetMakerResponse|null> {
|
|
667
|
-
|
|
673
|
+
const domNodeForId = await resolveNode(widgetData.data.backendNodeId);
|
|
668
674
|
if (!domNodeForId) {
|
|
669
|
-
|
|
670
|
-
if (!target) {
|
|
671
|
-
return null;
|
|
672
|
-
}
|
|
673
|
-
const node = new SDK.DOMModel.DeferredDOMNode(
|
|
674
|
-
target,
|
|
675
|
-
widgetData.data.backendNodeId,
|
|
676
|
-
);
|
|
677
|
-
const resolved = await node.resolvePromise();
|
|
678
|
-
if (!resolved) {
|
|
679
|
-
return null;
|
|
680
|
-
}
|
|
681
|
-
domNodeForId = resolved;
|
|
682
|
-
computedStyleNodeCache.set(widgetData.data.backendNodeId, resolved);
|
|
675
|
+
return null;
|
|
683
676
|
}
|
|
684
677
|
const styles = new ComputedStyle.ComputedStyleModel.ComputedStyle(domNodeForId, widgetData.data.computedStyles);
|
|
685
678
|
|
|
@@ -712,6 +705,27 @@ async function makeCoreVitalsWidget(widgetData: CoreVitalsAiWidget): Promise<Wid
|
|
|
712
705
|
};
|
|
713
706
|
}
|
|
714
707
|
|
|
708
|
+
async function makeStylePropertiesWidget(widgetData: StylePropertiesAiWidget): Promise<WidgetMakerResponse|null> {
|
|
709
|
+
const domNodeForId = await resolveNode(widgetData.data.backendNodeId);
|
|
710
|
+
if (!domNodeForId) {
|
|
711
|
+
return null;
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
const widgetConfig = UI.Widget.widgetConfig(Elements.StandaloneStylesContainer.StandaloneStylesContainer, {
|
|
715
|
+
domNode: domNodeForId,
|
|
716
|
+
filter: widgetData.data.selector ? new RegExp(widgetData.data.selector) : null,
|
|
717
|
+
});
|
|
718
|
+
|
|
719
|
+
// clang-format off
|
|
720
|
+
const widget = html`<devtools-widget
|
|
721
|
+
class="styling-preview-widget"
|
|
722
|
+
.widgetConfig=${widgetConfig}
|
|
723
|
+
></devtools-widget>`;
|
|
724
|
+
// clang-format on
|
|
725
|
+
|
|
726
|
+
return {renderedWidget: widget, revealable: domNodeForId};
|
|
727
|
+
}
|
|
728
|
+
|
|
715
729
|
function renderWidgetResponse(response: WidgetMakerResponse|null): Lit.LitTemplate {
|
|
716
730
|
if (response === null) {
|
|
717
731
|
return Lit.nothing;
|
|
@@ -743,38 +757,43 @@ function renderWidgetResponse(response: WidgetMakerResponse|null): Lit.LitTempla
|
|
|
743
757
|
}
|
|
744
758
|
|
|
745
759
|
/**
|
|
746
|
-
* Renders AI-defined UI widgets
|
|
747
|
-
* When a ModelChatMessage contains a WidgetPart,
|
|
748
|
-
* iterates through the
|
|
749
|
-
* the appropriate rendering logic based on
|
|
760
|
+
* Renders AI-defined UI widgets.
|
|
761
|
+
* When a ModelChatMessage contains a WidgetPart, or a Step has widgets,
|
|
762
|
+
* the ChatMessage component iterates through the \`widgets\` array.
|
|
763
|
+
* For each widget, it determines the appropriate rendering logic based on
|
|
764
|
+
* the \`widgetData.name\`.
|
|
750
765
|
*
|
|
751
|
-
* Currently,
|
|
752
|
-
*
|
|
753
|
-
* data and configuration for the
|
|
754
|
-
* and
|
|
755
|
-
*
|
|
756
|
-
* custom element, which dynamically instantiates and displays the specified
|
|
757
|
-
* UI.Widget subclass with the provided configuration.
|
|
766
|
+
* Currently, 'COMPUTED_STYLES', 'CORE_VITALS' and 'STYLE_PROPERTIES' widgets are supported.
|
|
767
|
+
* For these, the corresponding \`make...Widget\` functions are called to construct the necessary
|
|
768
|
+
* data and configuration for the UI components. The widget is then rendered using the
|
|
769
|
+
* \`<devtools-widget>\` custom element, which dynamically instantiates and displays the
|
|
770
|
+
* specified UI.Widget subclass with the provided configuration.
|
|
758
771
|
*
|
|
759
772
|
* This allows for a flexible and extensible system where new widget types
|
|
760
773
|
* can be added to the AI responses and rendered in DevTools by adding
|
|
761
|
-
* corresponding
|
|
774
|
+
* corresponding \`make...Widget\` functions and handling them here.
|
|
762
775
|
*/
|
|
763
|
-
async function
|
|
764
|
-
|
|
776
|
+
async function renderWidgets(
|
|
777
|
+
widgets: AiWidget[]|undefined, options: {wrapperClass?: string} = {}): Promise<Lit.LitTemplate> {
|
|
778
|
+
if (!Root.Runtime.hostConfig.devToolsAiAssistanceV2?.enabled || !widgets || widgets.length === 0) {
|
|
765
779
|
return Lit.nothing;
|
|
766
780
|
}
|
|
767
|
-
const ui = await Promise.all(
|
|
781
|
+
const ui = await Promise.all(widgets.map(async widgetData => {
|
|
782
|
+
let response: WidgetMakerResponse|null = null;
|
|
768
783
|
if (widgetData.name === 'COMPUTED_STYLES') {
|
|
769
|
-
|
|
770
|
-
|
|
784
|
+
response = await makeComputedStyleWidget(widgetData);
|
|
785
|
+
} else if (widgetData.name === 'CORE_VITALS') {
|
|
786
|
+
response = await makeCoreVitalsWidget(widgetData);
|
|
787
|
+
} else if (widgetData.name === 'STYLE_PROPERTIES') {
|
|
788
|
+
response = await makeStylePropertiesWidget(widgetData);
|
|
771
789
|
}
|
|
772
|
-
|
|
773
|
-
const response = await makeCoreVitalsWidget(widgetData);
|
|
774
|
-
return renderWidgetResponse(response);
|
|
775
|
-
}
|
|
776
|
-
return Lit.nothing;
|
|
790
|
+
return renderWidgetResponse(response);
|
|
777
791
|
}));
|
|
792
|
+
|
|
793
|
+
if (options.wrapperClass) {
|
|
794
|
+
return html`<div class=${options.wrapperClass}>${ui}</div>`;
|
|
795
|
+
}
|
|
796
|
+
|
|
778
797
|
return html`${ui}`;
|
|
779
798
|
}
|
|
780
799
|
|
|
@@ -829,6 +848,9 @@ function renderError(message: ModelChatMessage): Lit.LitTemplate {
|
|
|
829
848
|
case AiAssistanceModel.AiAgent.ErrorType.MAX_STEPS:
|
|
830
849
|
errorMessage = UIStringsNotTranslate.maxStepsError;
|
|
831
850
|
break;
|
|
851
|
+
case AiAssistanceModel.AiAgent.ErrorType.CROSS_ORIGIN:
|
|
852
|
+
errorMessage = UIStringsNotTranslate.crossOriginError;
|
|
853
|
+
break;
|
|
832
854
|
case AiAssistanceModel.AiAgent.ErrorType.ABORT:
|
|
833
855
|
return html`<p class="aborted" jslog=${VisualLogging.section('aborted')}>${
|
|
834
856
|
lockedString(UIStringsNotTranslate.stoppedResponse)}</p>`;
|
|
@@ -1019,8 +1041,6 @@ export class ChatMessage extends UI.Widget.Widget {
|
|
|
1019
1041
|
isReadOnly = false;
|
|
1020
1042
|
canShowFeedbackForm = false;
|
|
1021
1043
|
isLastMessage = false;
|
|
1022
|
-
userInfo:
|
|
1023
|
-
Pick<Host.InspectorFrontendHostAPI.SyncInformation, 'accountImage'|'accountFullName'|'accountGivenName'> = {};
|
|
1024
1044
|
markdownRenderer!: MarkdownLitRenderer;
|
|
1025
1045
|
onSuggestionClick: (suggestion: string) => void = () => {};
|
|
1026
1046
|
onFeedbackSubmit:
|
|
@@ -1067,7 +1087,6 @@ export class ChatMessage extends UI.Widget.Widget {
|
|
|
1067
1087
|
isLoading: this.isLoading,
|
|
1068
1088
|
isReadOnly: this.isReadOnly,
|
|
1069
1089
|
canShowFeedbackForm: this.canShowFeedbackForm,
|
|
1070
|
-
userInfo: this.userInfo,
|
|
1071
1090
|
markdownRenderer: this.markdownRenderer,
|
|
1072
1091
|
isLastMessage: this.isLastMessage,
|
|
1073
1092
|
onSuggestionClick: this.onSuggestionClick,
|
|
@@ -25,6 +25,7 @@ const {
|
|
|
25
25
|
repeat,
|
|
26
26
|
classMap,
|
|
27
27
|
} = Directives;
|
|
28
|
+
const {widget} = UI.Widget;
|
|
28
29
|
|
|
29
30
|
/*
|
|
30
31
|
* Strings that don't need to be translated at this time.
|
|
@@ -67,9 +68,8 @@ export interface Props {
|
|
|
67
68
|
messages: Message[];
|
|
68
69
|
context: AiAssistanceModel.AiAgent.ConversationContext<unknown>|null;
|
|
69
70
|
isContextSelected: boolean;
|
|
70
|
-
isLoading: boolean;
|
|
71
71
|
canShowFeedbackForm: boolean;
|
|
72
|
-
|
|
72
|
+
isLoading: boolean;
|
|
73
73
|
conversationType: AiAssistanceModel.AiHistoryStorage.ConversationType;
|
|
74
74
|
isReadOnly: boolean;
|
|
75
75
|
blockedByCrossOrigin: boolean;
|
|
@@ -91,7 +91,6 @@ export interface Props {
|
|
|
91
91
|
}
|
|
92
92
|
|
|
93
93
|
interface ChatWidgetInput extends Props {
|
|
94
|
-
accountGivenName: string;
|
|
95
94
|
handleScroll: (ev: Event) => void;
|
|
96
95
|
handleSuggestionClick: (title: string) => void;
|
|
97
96
|
handleMessageContainerRef: (el: Element|undefined) => void;
|
|
@@ -116,12 +115,11 @@ const DEFAULT_VIEW: View = (input, output, target) => {
|
|
|
116
115
|
${input.messages.length > 0 ? html`
|
|
117
116
|
<div class="messages-container" ${ref(input.handleMessageContainerRef)}>
|
|
118
117
|
${repeat(input.messages, message =>
|
|
119
|
-
|
|
118
|
+
widget(ChatMessage, {
|
|
120
119
|
message,
|
|
121
120
|
isLoading: input.isLoading && input.messages.at(-1) === message,
|
|
122
121
|
isReadOnly: input.isReadOnly,
|
|
123
122
|
canShowFeedbackForm: input.canShowFeedbackForm,
|
|
124
|
-
userInfo: input.userInfo,
|
|
125
123
|
markdownRenderer: input.markdownRenderer,
|
|
126
124
|
isLastMessage: input.messages.at(-1) === message,
|
|
127
125
|
onSuggestionClick: input.handleSuggestionClick,
|
|
@@ -130,14 +128,12 @@ const DEFAULT_VIEW: View = (input, output, target) => {
|
|
|
130
128
|
walkthrough: {
|
|
131
129
|
...input.walkthrough,
|
|
132
130
|
}
|
|
133
|
-
})
|
|
131
|
+
})
|
|
134
132
|
)}
|
|
135
|
-
${input.isLoading ? nothing :
|
|
136
|
-
.widgetConfig=${UI.Widget.widgetConfig(PatchWidget, {
|
|
133
|
+
${input.isLoading ? nothing : widget(PatchWidget, {
|
|
137
134
|
changeSummary: input.changeSummary ?? '',
|
|
138
135
|
changeManager: input.changeManager,
|
|
139
136
|
})}
|
|
140
|
-
></devtools-widget>`}
|
|
141
137
|
</div>
|
|
142
138
|
` : html`
|
|
143
139
|
<div class="empty-state-container">
|
|
@@ -149,7 +145,7 @@ const DEFAULT_VIEW: View = (input, output, target) => {
|
|
|
149
145
|
</div>
|
|
150
146
|
${AiAssistanceModel.AiUtils.isGeminiBranding() ?
|
|
151
147
|
html`
|
|
152
|
-
<h1 class='greeting'>Hello
|
|
148
|
+
<h1 class='greeting'>Hello</h1>
|
|
153
149
|
<p class='cta'>${lockedString(UIStringsNotTranslate.emptyStateTextGemini)}</p>
|
|
154
150
|
` : html`<h1>${lockedString(UIStringsNotTranslate.emptyStateText)}</h1>`
|
|
155
151
|
}
|
|
@@ -354,7 +350,6 @@ export class ChatView extends HTMLElement {
|
|
|
354
350
|
this.#view(
|
|
355
351
|
{
|
|
356
352
|
...this.#props,
|
|
357
|
-
accountGivenName: this.#props.userInfo.accountGivenName ?? '',
|
|
358
353
|
handleScroll: this.#handleScroll,
|
|
359
354
|
handleSuggestionClick: this.#handleSuggestionClick,
|
|
360
355
|
handleMessageContainerRef: this.#handleMessageContainerRef,
|
|
@@ -33,16 +33,21 @@ export class MarkdownRendererWithCodeBlock extends MarkdownView.MarkdownView.Mar
|
|
|
33
33
|
}}>${Platform.StringUtilities.trimEndWithMaxLength(label, 100)}</devtools-link>`;
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
-
#renderLink(
|
|
36
|
+
#renderLink(
|
|
37
|
+
href: string,
|
|
38
|
+
fallbackText: string,
|
|
39
|
+
): Lit.LitTemplate|null {
|
|
37
40
|
if (href.startsWith('#req-')) {
|
|
38
|
-
const request =
|
|
39
|
-
|
|
41
|
+
const request = Logs.NetworkLog.NetworkLog.instance().requests().find(
|
|
42
|
+
req => req.requestId() === href.substring(5),
|
|
43
|
+
);
|
|
40
44
|
|
|
41
45
|
if (request) {
|
|
42
46
|
return this.#revealableLink(request, request.url());
|
|
43
47
|
}
|
|
44
|
-
|
|
45
|
-
}
|
|
48
|
+
return html`${fallbackText}`;
|
|
49
|
+
}
|
|
50
|
+
if (href.startsWith('#file-')) {
|
|
46
51
|
const file = AiAssistanceModel.ContextSelectionAgent.ContextSelectionAgent.getUISourceCodes().find(
|
|
47
52
|
file => AiAssistanceModel.ContextSelectionAgent.ContextSelectionAgent.uiSourceCodeId.get(file) ===
|
|
48
53
|
Number(href.substring(6)));
|
|
@@ -50,13 +55,14 @@ export class MarkdownRendererWithCodeBlock extends MarkdownView.MarkdownView.Mar
|
|
|
50
55
|
if (file) {
|
|
51
56
|
return this.#revealableLink(file, file.name());
|
|
52
57
|
}
|
|
58
|
+
return html`${fallbackText}`;
|
|
53
59
|
}
|
|
54
60
|
return null;
|
|
55
61
|
}
|
|
56
62
|
|
|
57
63
|
override templateForToken(token: Marked.Marked.MarkedToken): Lit.LitTemplate|null {
|
|
58
64
|
if (token.type === 'link') {
|
|
59
|
-
const link = this.#renderLink(token.href);
|
|
65
|
+
const link = this.#renderLink(token.href, token.text);
|
|
60
66
|
if (link) {
|
|
61
67
|
return link;
|
|
62
68
|
}
|
|
@@ -73,9 +79,12 @@ export class MarkdownRendererWithCodeBlock extends MarkdownView.MarkdownView.Mar
|
|
|
73
79
|
if (token.type === 'codespan') {
|
|
74
80
|
// LLM likes outputting the link inside a codespan block.
|
|
75
81
|
// Remove the codespan and render the link directly
|
|
76
|
-
const matches = token.text.match(/^\[
|
|
77
|
-
if (matches?.[
|
|
78
|
-
const link = this.#renderLink(
|
|
82
|
+
const matches = token.text.match(/^\[(.*)\]\((.+)\)$/);
|
|
83
|
+
if (matches?.[2]) {
|
|
84
|
+
const link = this.#renderLink(
|
|
85
|
+
matches[2],
|
|
86
|
+
matches[1],
|
|
87
|
+
);
|
|
79
88
|
if (link) {
|
|
80
89
|
return link;
|
|
81
90
|
}
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
// Copyright 2026 The Chromium Authors
|
|
2
|
+
// Use of this source code is governed by a BSD-style license that can be
|
|
3
|
+
// found in the LICENSE file.
|
|
4
|
+
|
|
5
|
+
import * as SDK from '../../../core/sdk/sdk.js';
|
|
6
|
+
import type * as Protocol from '../../../generated/protocol.js';
|
|
7
|
+
import * as Marked from '../../../third_party/marked/marked.js';
|
|
8
|
+
import type * as MarkdownView from '../../../ui/components/markdown_view/MarkdownView.js';
|
|
9
|
+
import * as Lit from '../../../ui/lit/lit.js';
|
|
10
|
+
import * as PanelsCommon from '../../common/common.js';
|
|
11
|
+
|
|
12
|
+
import {MarkdownRendererWithCodeBlock} from './MarkdownRendererWithCodeBlock.js';
|
|
13
|
+
|
|
14
|
+
const {html} = Lit.StaticHtml;
|
|
15
|
+
const {ref, createRef} = Lit.Directives;
|
|
16
|
+
|
|
17
|
+
export class StylingAgentMarkdownRenderer extends MarkdownRendererWithCodeBlock {
|
|
18
|
+
constructor(
|
|
19
|
+
private mainFrameId = '',
|
|
20
|
+
) {
|
|
21
|
+
super();
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
#renderTableFromJson(data: Array<Record<string, string>>): Lit.LitTemplate|null {
|
|
25
|
+
if (!Array.isArray(data) || data.length === 0 || typeof data[0] !== 'object' || data[0] === null) {
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const headers = Object.keys(data[0]);
|
|
30
|
+
const requiredKeys = ['Problem', 'Element', 'NodeId', 'Details'];
|
|
31
|
+
if (!requiredKeys.every(key => headers.includes(key))) {
|
|
32
|
+
return null; // Not the expected JSON structure
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const problemIndex = headers.indexOf('Problem');
|
|
36
|
+
if (problemIndex > -1) {
|
|
37
|
+
const problemHeader = headers.splice(problemIndex, 1);
|
|
38
|
+
headers.unshift(...problemHeader);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return html`
|
|
42
|
+
<table style="width: 100%;">
|
|
43
|
+
<thead>
|
|
44
|
+
<tr>
|
|
45
|
+
${headers.map(header => html`<th style="text-align: left;">${header === 'NodeId' ? '' : header}</th>`)}
|
|
46
|
+
</tr>
|
|
47
|
+
</thead>
|
|
48
|
+
<tbody>
|
|
49
|
+
${data.flatMap(row => {
|
|
50
|
+
return html`
|
|
51
|
+
<tr>
|
|
52
|
+
${headers.map(header => {
|
|
53
|
+
if (header === 'NodeId') {
|
|
54
|
+
return html`<td>${this.#renderLinkifiedText(row[header])}</td>`;
|
|
55
|
+
}
|
|
56
|
+
if (header === 'Details') {
|
|
57
|
+
// eslint-disable-next-line @devtools/no-a-tags-in-lit
|
|
58
|
+
return html`<td><a href="#" @click=${this.#toggleDetailsRow}>Details</a></td>`;
|
|
59
|
+
}
|
|
60
|
+
return html`<td>${row[header]}</td>`;
|
|
61
|
+
})}
|
|
62
|
+
</tr>
|
|
63
|
+
<tr class="details-row" style="display: none;">
|
|
64
|
+
<td colspan=${headers.length} style="background-color: #f0f0f0; padding: 1em;">
|
|
65
|
+
<devtools-markdown-view .data=${{
|
|
66
|
+
tokens: Marked.Marked.lexer(row['Details']),
|
|
67
|
+
renderer: new StylingAgentMarkdownRenderer(this.mainFrameId),
|
|
68
|
+
} as MarkdownView.MarkdownViewData}></devtools-markdown-view>
|
|
69
|
+
</td>
|
|
70
|
+
</tr>
|
|
71
|
+
`;
|
|
72
|
+
})}
|
|
73
|
+
</tbody>
|
|
74
|
+
</table>
|
|
75
|
+
<br><div>To investigate these problems, please click one of the provided links (above), to set as context, and ask me further questions about the problem.</div>
|
|
76
|
+
`;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
override templateForToken(token: Marked.Marked.MarkedToken): Lit.LitTemplate|null {
|
|
80
|
+
if (token.type === 'code') {
|
|
81
|
+
try {
|
|
82
|
+
const data = JSON.parse(token.text);
|
|
83
|
+
const table = this.#renderTableFromJson(data);
|
|
84
|
+
if (table) {
|
|
85
|
+
return table;
|
|
86
|
+
}
|
|
87
|
+
} catch {
|
|
88
|
+
// Not a JSON object, fallback to default rendering.
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (token.type === 'link' && token.href.startsWith('#')) {
|
|
93
|
+
let nodeId = undefined;
|
|
94
|
+
if (token.href.startsWith('#node-')) {
|
|
95
|
+
nodeId = Number(token.href.replace('#node-', '')) as Protocol.DOM.BackendNodeId;
|
|
96
|
+
} else if (token.href.startsWith('#')) {
|
|
97
|
+
// So often does it ignore requests to prepend nodes with node-, frustratingly.
|
|
98
|
+
nodeId = Number(token.href.replace('#', '')) as Protocol.DOM.BackendNodeId;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (nodeId) {
|
|
102
|
+
const templateRef = createRef();
|
|
103
|
+
void this.#linkifyNode(nodeId, token.text).then(node => {
|
|
104
|
+
if (!templateRef.value || !node) {
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
templateRef.value.textContent = '';
|
|
109
|
+
templateRef.value.append(node);
|
|
110
|
+
});
|
|
111
|
+
return html`<span ${ref(templateRef)}>${token.text}</span>`;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return super.templateForToken(token);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
#toggleDetailsRow(e: Event): void {
|
|
119
|
+
e.preventDefault();
|
|
120
|
+
const link = e.target as HTMLAnchorElement;
|
|
121
|
+
const currentRow = link.closest('tr');
|
|
122
|
+
if (!currentRow) {
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
const detailsRow = currentRow.nextElementSibling as HTMLTableRowElement | null;
|
|
126
|
+
if (detailsRow?.classList.contains('details-row')) {
|
|
127
|
+
if (detailsRow.style.display === 'none') {
|
|
128
|
+
detailsRow.style.display = 'table-row';
|
|
129
|
+
link.textContent = 'Hide';
|
|
130
|
+
} else {
|
|
131
|
+
detailsRow.style.display = 'none';
|
|
132
|
+
link.textContent = 'Details';
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
#renderLinkifiedText(text: string): Lit.LitTemplate {
|
|
138
|
+
if (text.indexOf(',') === -1) {
|
|
139
|
+
const nodeId = Number(text) as Protocol.DOM.BackendNodeId;
|
|
140
|
+
if (isNaN(nodeId)) {
|
|
141
|
+
// Not a number, return as is.
|
|
142
|
+
return html`${text}`;
|
|
143
|
+
}
|
|
144
|
+
return this.#renderSingleLink(nodeId);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Check for comma separated list.
|
|
148
|
+
const nodeIdsStr = text.split(',').map(s => s.trim()).filter(Boolean);
|
|
149
|
+
return html`${nodeIdsStr.map(idStr => {
|
|
150
|
+
const nodeId = Number(idStr) as Protocol.DOM.BackendNodeId;
|
|
151
|
+
if (isNaN(nodeId)) {
|
|
152
|
+
return html`<div>${idStr}</div>`;
|
|
153
|
+
}
|
|
154
|
+
return html`<div>${this.#renderSingleLink(nodeId)}</div>`;
|
|
155
|
+
})}`;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
#renderSingleLink(nodeId: Protocol.DOM.BackendNodeId): Lit.LitTemplate {
|
|
159
|
+
const label = `link`;
|
|
160
|
+
const templateRef = createRef();
|
|
161
|
+
void this.#linkifyNode(nodeId, label).then(node => {
|
|
162
|
+
if (!templateRef.value) {
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
templateRef.value.textContent = '';
|
|
166
|
+
if (node) {
|
|
167
|
+
templateRef.value.append(node);
|
|
168
|
+
} else {
|
|
169
|
+
// Fallback to plain text if linkification fails
|
|
170
|
+
templateRef.value.append(document.createTextNode(label));
|
|
171
|
+
}
|
|
172
|
+
});
|
|
173
|
+
// Placeholder for async link
|
|
174
|
+
return html`<span ${ref(templateRef)}>${label}</span>`;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
async #linkifyNode(backendNodeId: Protocol.DOM.BackendNodeId, label: string): Promise<Node|undefined> {
|
|
178
|
+
if (backendNodeId === undefined) {
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const target = SDK.TargetManager.TargetManager.instance().primaryPageTarget();
|
|
183
|
+
const domModel = target?.model(SDK.DOMModel.DOMModel);
|
|
184
|
+
if (!domModel) {
|
|
185
|
+
return undefined;
|
|
186
|
+
}
|
|
187
|
+
const domNodesMap = await domModel.pushNodesByBackendIdsToFrontend(new Set([backendNodeId]));
|
|
188
|
+
const node = domNodesMap?.get(backendNodeId);
|
|
189
|
+
if (!node) {
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
if (node.frameId() !== this.mainFrameId) {
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const linkedNode = PanelsCommon.DOMLinkifier.Linkifier.instance().linkify(node, {textContent: label});
|
|
198
|
+
return linkedNode;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
@@ -143,14 +143,6 @@
|
|
|
143
143
|
gap: var(--sys-size-4);
|
|
144
144
|
font: var(--sys-typescale-body4-bold);
|
|
145
145
|
|
|
146
|
-
img {
|
|
147
|
-
border: 0;
|
|
148
|
-
border-radius: var(--sys-shape-corner-full);
|
|
149
|
-
display: block;
|
|
150
|
-
height: var(--sys-size-9);
|
|
151
|
-
width: var(--sys-size-9);
|
|
152
|
-
}
|
|
153
|
-
|
|
154
146
|
h2 {
|
|
155
147
|
font: var(--sys-typescale-body4-bold);
|
|
156
148
|
}
|
|
@@ -362,6 +354,17 @@
|
|
|
362
354
|
width: fit-content;
|
|
363
355
|
}
|
|
364
356
|
|
|
357
|
+
.styling-preview-widget {
|
|
358
|
+
width: 100%;
|
|
359
|
+
min-height: 100px;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
.main-widgets-wrapper {
|
|
363
|
+
display: flex;
|
|
364
|
+
flex-direction: column;
|
|
365
|
+
gap: var(--sys-size-5);
|
|
366
|
+
}
|
|
367
|
+
|
|
365
368
|
.step-widgets-wrapper {
|
|
366
369
|
width: fit-content;
|
|
367
370
|
display: flex;
|