chrome-devtools-frontend 1.0.1605219 → 1.0.1605390
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/front_end/Images/src/dots-circle.svg +10 -0
- package/front_end/core/sdk/WebMCPModel.ts +57 -2
- package/front_end/models/ai_assistance/agents/AiAgent.ts +9 -1
- package/front_end/models/ai_assistance/agents/ConversationSummaryAgent.ts +10 -9
- package/front_end/models/ai_assistance/agents/ExecuteJavascript.ts +301 -0
- package/front_end/models/ai_assistance/agents/PerformanceAgent.ts +19 -8
- package/front_end/models/ai_assistance/agents/StylingAgent.ts +12 -264
- package/front_end/panels/ai_assistance/components/ChatMessage.ts +132 -48
- package/front_end/panels/ai_assistance/components/ChatView.ts +2 -13
- package/front_end/panels/ai_assistance/components/ExportForAgentsDialog.ts +1 -1
- package/front_end/panels/ai_assistance/components/chatMessage.css +43 -1
- package/front_end/panels/ai_assistance/components/chatView.css +0 -8
- package/front_end/panels/application/WebMCPView.ts +363 -13
- package/front_end/panels/application/webMCPView.css +25 -0
- package/front_end/panels/elements/ElementsTreeOutline.ts +87 -0
- package/front_end/panels/elements/elementsTreeOutline.css +21 -0
- package/front_end/panels/profiler/HeapDetachedElementsView.ts +0 -4
- package/front_end/panels/profiler/HeapProfileView.ts +0 -4
- package/front_end/panels/profiler/HeapProfilerPanel.ts +4 -13
- package/front_end/panels/profiler/HeapSnapshotView.ts +12 -16
- package/front_end/panels/profiler/ProfileHeader.ts +0 -11
- package/front_end/panels/profiler/ProfileTypeRegistry.ts +4 -12
- package/front_end/panels/profiler/ProfileView.ts +1 -6
- package/front_end/panels/profiler/ProfilesPanel.ts +60 -7
- package/front_end/panels/timeline/EventsTimelineTreeView.ts +1 -1
- package/front_end/panels/timeline/ThirdPartyTreeView.ts +2 -2
- package/front_end/panels/timeline/TimelinePanel.ts +11 -0
- package/front_end/panels/timeline/TimelineTreeView.ts +136 -47
- package/front_end/panels/timeline/components/CWVMetrics.ts +18 -2
- package/front_end/panels/timeline/timeline-meta.ts +11 -0
- package/front_end/panels/timeline/utils/Helpers.ts +5 -0
- package/front_end/ui/legacy/FilterBar.ts +2 -0
- package/front_end/ui/legacy/SplitWidget.ts +33 -27
- package/front_end/ui/legacy/Widget.ts +36 -28
- package/front_end/ui/visual_logging/KnownContextValues.ts +6 -0
- package/package.json +1 -1
|
@@ -4,7 +4,6 @@
|
|
|
4
4
|
|
|
5
5
|
import * as Host from '../../../core/host/host.js';
|
|
6
6
|
import * as i18n from '../../../core/i18n/i18n.js';
|
|
7
|
-
import * as Platform from '../../../core/platform/platform.js';
|
|
8
7
|
import * as Root from '../../../core/root/root.js';
|
|
9
8
|
import * as SDK from '../../../core/sdk/sdk.js';
|
|
10
9
|
import * as Protocol from '../../../generated/protocol.js';
|
|
@@ -13,9 +12,8 @@ import * as Annotations from '../../annotations/annotations.js';
|
|
|
13
12
|
import * as Emulation from '../../emulation/emulation.js';
|
|
14
13
|
import {ChangeManager} from '../ChangeManager.js';
|
|
15
14
|
import {debugLog} from '../debug.js';
|
|
16
|
-
import {EvaluateAction, formatError, SideEffectError} from '../EvaluateAction.js';
|
|
17
15
|
import {ExtensionScope} from '../ExtensionScope.js';
|
|
18
|
-
import {AI_ASSISTANCE_CSS_CLASS_NAME
|
|
16
|
+
import {AI_ASSISTANCE_CSS_CLASS_NAME} from '../injected.js';
|
|
19
17
|
|
|
20
18
|
import {
|
|
21
19
|
type AgentOptions as BaseAgentOptions,
|
|
@@ -25,12 +23,12 @@ import {
|
|
|
25
23
|
ConversationContext,
|
|
26
24
|
type ConversationSuggestions,
|
|
27
25
|
type FunctionCallHandlerResult,
|
|
28
|
-
type FunctionHandlerOptions,
|
|
29
26
|
type MultimodalInput,
|
|
30
27
|
MultimodalInputType,
|
|
31
28
|
type RequestOptions,
|
|
32
29
|
ResponseType
|
|
33
30
|
} from './AiAgent.js';
|
|
31
|
+
import {executeJavaScriptFunction, executeJsCode, JavascriptExecutor} from './ExecuteJavascript.js';
|
|
34
32
|
|
|
35
33
|
/*
|
|
36
34
|
* Strings that don't need to be translated at this time.
|
|
@@ -145,51 +143,6 @@ const MULTIMODAL_ENHANCEMENT_PROMPTS: Record<MultimodalInputType, string> = {
|
|
|
145
143
|
[MultimodalInputType.UPLOADED_IMAGE]: promptForUploadedImage + considerationsForMultimodalInputEvaluation,
|
|
146
144
|
};
|
|
147
145
|
|
|
148
|
-
async function executeJsCode(
|
|
149
|
-
functionDeclaration: string,
|
|
150
|
-
{throwOnSideEffect, contextNode}: {throwOnSideEffect: boolean, contextNode: SDK.DOMModel.DOMNode|null}):
|
|
151
|
-
Promise<string> {
|
|
152
|
-
if (!contextNode) {
|
|
153
|
-
throw new Error('Cannot execute JavaScript because of missing context node');
|
|
154
|
-
}
|
|
155
|
-
const target = contextNode.domModel().target();
|
|
156
|
-
|
|
157
|
-
if (!target) {
|
|
158
|
-
throw new Error('Target is not found for executing code');
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
const resourceTreeModel = target.model(SDK.ResourceTreeModel.ResourceTreeModel);
|
|
162
|
-
const frameId = contextNode.frameId() ?? resourceTreeModel?.mainFrame?.id;
|
|
163
|
-
|
|
164
|
-
if (!frameId) {
|
|
165
|
-
throw new Error('Main frame is not found for executing code');
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
const runtimeModel = target.model(SDK.RuntimeModel.RuntimeModel);
|
|
169
|
-
const pageAgent = target.pageAgent();
|
|
170
|
-
|
|
171
|
-
// This returns previously created world if it exists for the frame.
|
|
172
|
-
const {executionContextId} = await pageAgent.invoke_createIsolatedWorld({frameId, worldName: FREESTYLER_WORLD_NAME});
|
|
173
|
-
const executionContext = runtimeModel?.executionContext(executionContextId);
|
|
174
|
-
if (!executionContext) {
|
|
175
|
-
throw new Error('Execution context is not found for executing code');
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
if (executionContext.debuggerModel.selectedCallFrame()) {
|
|
179
|
-
return formatError('Cannot evaluate JavaScript because the execution is paused on a breakpoint.');
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
const remoteObject = await contextNode.resolveToObject(undefined, executionContextId);
|
|
183
|
-
if (!remoteObject) {
|
|
184
|
-
throw new Error('Cannot execute JavaScript because remote object cannot be resolved');
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
return await EvaluateAction.execute(functionDeclaration, [remoteObject], executionContext, {throwOnSideEffect});
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
const MAX_OBSERVATION_BYTE_LENGTH = 25_000;
|
|
191
|
-
const OBSERVATION_TIMEOUT = 5_000;
|
|
192
|
-
|
|
193
146
|
export const AI_ASSISTANCE_FILTER_REGEX = `\\.${AI_ASSISTANCE_CSS_CLASS_NAME}-.*&`;
|
|
194
147
|
|
|
195
148
|
type CreateExtensionScopeFunction = (changes: ChangeManager) => {
|
|
@@ -310,6 +263,7 @@ export class StylingAgent extends AiAgent<SDK.DOMModel.DOMNode> {
|
|
|
310
263
|
}
|
|
311
264
|
|
|
312
265
|
#execJs: typeof executeJsCode;
|
|
266
|
+
#javascriptExecutor: JavascriptExecutor;
|
|
313
267
|
|
|
314
268
|
#changes: ChangeManager;
|
|
315
269
|
#createExtensionScope: CreateExtensionScopeFunction;
|
|
@@ -326,6 +280,14 @@ export class StylingAgent extends AiAgent<SDK.DOMModel.DOMNode> {
|
|
|
326
280
|
opts.createExtensionScope ?? ((changes: ChangeManager) => {
|
|
327
281
|
return new ExtensionScope(changes, this.sessionId, this.context?.getItem() ?? null, this.#currentTurnId);
|
|
328
282
|
});
|
|
283
|
+
this.#javascriptExecutor = new JavascriptExecutor(
|
|
284
|
+
{
|
|
285
|
+
executionMode: this.executionMode,
|
|
286
|
+
getContextNode: () => this.#getSelectedNode(),
|
|
287
|
+
createExtensionScope: this.#createExtensionScope.bind(this),
|
|
288
|
+
changes: this.#changes,
|
|
289
|
+
},
|
|
290
|
+
this.#execJs);
|
|
329
291
|
|
|
330
292
|
this.declareFunction<{
|
|
331
293
|
elements: number[],
|
|
@@ -378,91 +340,7 @@ export class StylingAgent extends AiAgent<SDK.DOMModel.DOMNode> {
|
|
|
378
340
|
},
|
|
379
341
|
});
|
|
380
342
|
|
|
381
|
-
this.declareFunction
|
|
382
|
-
title: string,
|
|
383
|
-
explanation: string,
|
|
384
|
-
code: string,
|
|
385
|
-
}>('executeJavaScript', {
|
|
386
|
-
description:
|
|
387
|
-
`This function allows you to run JavaScript code on the inspected page to access the element styles and page content.
|
|
388
|
-
Call this function to gather additional information or modify the page state. Call this function enough times to investigate the user request.`,
|
|
389
|
-
parameters: {
|
|
390
|
-
type: Host.AidaClient.ParametersTypes.OBJECT,
|
|
391
|
-
description: '',
|
|
392
|
-
nullable: false,
|
|
393
|
-
properties: {
|
|
394
|
-
code: {
|
|
395
|
-
type: Host.AidaClient.ParametersTypes.STRING,
|
|
396
|
-
description:
|
|
397
|
-
`JavaScript code snippet to run on the inspected page. Make sure the code is formatted for readability.
|
|
398
|
-
|
|
399
|
-
# Instructions
|
|
400
|
-
|
|
401
|
-
* To return data, define a top-level \`data\` variable and populate it with data you want to get. Only JSON-serializable objects can be assigned to \`data\`.
|
|
402
|
-
* If you modify styles on an element, ALWAYS call the pre-defined global \`async setElementStyles(el: Element, styles: object)\` function. This function is an internal mechanism for you and should never be presented as a command/advice to the user.
|
|
403
|
-
* **CRITICAL** Only get styles that might be relevant to the user request.
|
|
404
|
-
* **CRITICAL** Never assume a selector for the elements unless you verified your knowledge.
|
|
405
|
-
* **CRITICAL** Consider that \`data\` variable from the previous function calls are not available in a new function call.
|
|
406
|
-
|
|
407
|
-
For example, the code to change element styles:
|
|
408
|
-
|
|
409
|
-
\`\`\`
|
|
410
|
-
await setElementStyles($0, {
|
|
411
|
-
color: 'blue',
|
|
412
|
-
});
|
|
413
|
-
\`\`\`
|
|
414
|
-
|
|
415
|
-
For example, the code to get overlapping elements:
|
|
416
|
-
|
|
417
|
-
\`\`\`
|
|
418
|
-
const data = {
|
|
419
|
-
overlappingElements: Array.from(document.querySelectorAll('*'))
|
|
420
|
-
.filter(el => {
|
|
421
|
-
const rect = el.getBoundingClientRect();
|
|
422
|
-
const popupRect = $0.getBoundingClientRect();
|
|
423
|
-
return (
|
|
424
|
-
el !== $0 &&
|
|
425
|
-
rect.left < popupRect.right &&
|
|
426
|
-
rect.right > popupRect.left &&
|
|
427
|
-
rect.top < popupRect.bottom &&
|
|
428
|
-
rect.bottom > popupRect.top
|
|
429
|
-
);
|
|
430
|
-
})
|
|
431
|
-
.map(el => ({
|
|
432
|
-
tagName: el.tagName,
|
|
433
|
-
id: el.id,
|
|
434
|
-
className: el.className,
|
|
435
|
-
zIndex: window.getComputedStyle(el)['z-index']
|
|
436
|
-
}))
|
|
437
|
-
};
|
|
438
|
-
\`\`\`
|
|
439
|
-
`,
|
|
440
|
-
},
|
|
441
|
-
explanation: {
|
|
442
|
-
type: Host.AidaClient.ParametersTypes.STRING,
|
|
443
|
-
description: 'Explain why you want to run this code',
|
|
444
|
-
},
|
|
445
|
-
title: {
|
|
446
|
-
type: Host.AidaClient.ParametersTypes.STRING,
|
|
447
|
-
description: 'Provide a summary of what the code does. For example, "Checking related element styles".',
|
|
448
|
-
},
|
|
449
|
-
},
|
|
450
|
-
required: ['code', 'explanation', 'title']
|
|
451
|
-
},
|
|
452
|
-
displayInfoFromArgs: params => {
|
|
453
|
-
return {
|
|
454
|
-
title: params.title,
|
|
455
|
-
thought: params.explanation,
|
|
456
|
-
action: params.code,
|
|
457
|
-
};
|
|
458
|
-
},
|
|
459
|
-
handler: async (
|
|
460
|
-
params,
|
|
461
|
-
options,
|
|
462
|
-
) => {
|
|
463
|
-
return await this.executeAction(params.code, options);
|
|
464
|
-
},
|
|
465
|
-
});
|
|
343
|
+
this.declareFunction('executeJavaScript', executeJavaScriptFunction(this.#javascriptExecutor));
|
|
466
344
|
|
|
467
345
|
if (Annotations.AnnotationRepository.annotationsEnabled()) {
|
|
468
346
|
this.declareFunction<{
|
|
@@ -528,68 +406,6 @@ const data = {
|
|
|
528
406
|
});
|
|
529
407
|
}
|
|
530
408
|
|
|
531
|
-
async generateObservation(
|
|
532
|
-
action: string,
|
|
533
|
-
{
|
|
534
|
-
throwOnSideEffect,
|
|
535
|
-
}: {
|
|
536
|
-
throwOnSideEffect: boolean,
|
|
537
|
-
},
|
|
538
|
-
): Promise<{
|
|
539
|
-
observation: string,
|
|
540
|
-
sideEffect: boolean,
|
|
541
|
-
canceled: boolean,
|
|
542
|
-
}> {
|
|
543
|
-
const functionDeclaration = `async function ($0) {
|
|
544
|
-
try {
|
|
545
|
-
${action}
|
|
546
|
-
;
|
|
547
|
-
return ((typeof data !== "undefined") ? data : undefined);
|
|
548
|
-
} catch (error) {
|
|
549
|
-
return error;
|
|
550
|
-
}
|
|
551
|
-
}`;
|
|
552
|
-
try {
|
|
553
|
-
const result = await Promise.race([
|
|
554
|
-
this.#execJs(
|
|
555
|
-
functionDeclaration,
|
|
556
|
-
{
|
|
557
|
-
throwOnSideEffect,
|
|
558
|
-
contextNode: this.context?.getItem() || null,
|
|
559
|
-
},
|
|
560
|
-
),
|
|
561
|
-
new Promise<never>((_, reject) => {
|
|
562
|
-
setTimeout(
|
|
563
|
-
() => reject(new Error('Script execution exceeded the maximum allowed time.')), OBSERVATION_TIMEOUT);
|
|
564
|
-
}),
|
|
565
|
-
]);
|
|
566
|
-
const byteCount = Platform.StringUtilities.countWtf8Bytes(result);
|
|
567
|
-
Host.userMetrics.freestylerEvalResponseSize(byteCount);
|
|
568
|
-
if (byteCount > MAX_OBSERVATION_BYTE_LENGTH) {
|
|
569
|
-
throw new Error('Output exceeded the maximum allowed length.');
|
|
570
|
-
}
|
|
571
|
-
return {
|
|
572
|
-
observation: result,
|
|
573
|
-
sideEffect: false,
|
|
574
|
-
canceled: false,
|
|
575
|
-
};
|
|
576
|
-
} catch (error) {
|
|
577
|
-
if (error instanceof SideEffectError) {
|
|
578
|
-
return {
|
|
579
|
-
observation: error.message,
|
|
580
|
-
sideEffect: true,
|
|
581
|
-
canceled: false,
|
|
582
|
-
};
|
|
583
|
-
}
|
|
584
|
-
|
|
585
|
-
return {
|
|
586
|
-
observation: `Error: ${error.message}`,
|
|
587
|
-
sideEffect: false,
|
|
588
|
-
canceled: false,
|
|
589
|
-
};
|
|
590
|
-
}
|
|
591
|
-
}
|
|
592
|
-
|
|
593
409
|
static async describeElement(element: SDK.DOMModel.DOMNode): Promise<string> {
|
|
594
410
|
let output = `* Element's uid is ${element.backendNodeId()}.
|
|
595
411
|
* Its selector is \`${element.simpleSelector()}\``;
|
|
@@ -742,74 +558,6 @@ const data = {
|
|
|
742
558
|
};
|
|
743
559
|
}
|
|
744
560
|
|
|
745
|
-
async executeAction(action: string, options?: FunctionHandlerOptions): Promise<FunctionCallHandlerResult<unknown>> {
|
|
746
|
-
debugLog(`Action to execute: ${action}`);
|
|
747
|
-
|
|
748
|
-
if (options?.approved === false) {
|
|
749
|
-
return {
|
|
750
|
-
error: 'Error: User denied code execution with side effects.',
|
|
751
|
-
};
|
|
752
|
-
}
|
|
753
|
-
|
|
754
|
-
if (this.executionMode === Root.Runtime.HostConfigFreestylerExecutionMode.NO_SCRIPTS) {
|
|
755
|
-
return {
|
|
756
|
-
error: 'Error: JavaScript execution is currently disabled.',
|
|
757
|
-
};
|
|
758
|
-
}
|
|
759
|
-
|
|
760
|
-
const selectedNode = this.#getSelectedNode();
|
|
761
|
-
if (!selectedNode) {
|
|
762
|
-
return {error: 'Error: no selected node found.'};
|
|
763
|
-
}
|
|
764
|
-
const target = selectedNode.domModel().target();
|
|
765
|
-
if (target.model(SDK.DebuggerModel.DebuggerModel)?.selectedCallFrame()) {
|
|
766
|
-
return {
|
|
767
|
-
error: 'Error: Cannot evaluate JavaScript because the execution is paused on a breakpoint.',
|
|
768
|
-
};
|
|
769
|
-
}
|
|
770
|
-
|
|
771
|
-
const scope = this.#createExtensionScope(this.#changes);
|
|
772
|
-
await scope.install();
|
|
773
|
-
try {
|
|
774
|
-
let throwOnSideEffect = true;
|
|
775
|
-
if (options?.approved) {
|
|
776
|
-
throwOnSideEffect = false;
|
|
777
|
-
}
|
|
778
|
-
|
|
779
|
-
const result = await this.generateObservation(action, {throwOnSideEffect});
|
|
780
|
-
debugLog(`Action result: ${JSON.stringify(result)}`);
|
|
781
|
-
if (result.sideEffect) {
|
|
782
|
-
if (this.executionMode === Root.Runtime.HostConfigFreestylerExecutionMode.SIDE_EFFECT_FREE_SCRIPTS_ONLY) {
|
|
783
|
-
return {
|
|
784
|
-
error: 'Error: JavaScript execution that modifies the page is currently disabled.',
|
|
785
|
-
};
|
|
786
|
-
}
|
|
787
|
-
|
|
788
|
-
if (options?.signal?.aborted) {
|
|
789
|
-
return {
|
|
790
|
-
error: 'Error: evaluation has been cancelled',
|
|
791
|
-
};
|
|
792
|
-
}
|
|
793
|
-
|
|
794
|
-
return {
|
|
795
|
-
requiresApproval: true,
|
|
796
|
-
description: lockedString('This code may modify page content. Continue?'),
|
|
797
|
-
};
|
|
798
|
-
}
|
|
799
|
-
if (result.canceled) {
|
|
800
|
-
return {
|
|
801
|
-
error: result.observation,
|
|
802
|
-
};
|
|
803
|
-
}
|
|
804
|
-
|
|
805
|
-
return {
|
|
806
|
-
result: result.observation,
|
|
807
|
-
};
|
|
808
|
-
} finally {
|
|
809
|
-
await scope.uninstall();
|
|
810
|
-
}
|
|
811
|
-
}
|
|
812
|
-
|
|
813
561
|
async addElementAnnotation(elementId: string, annotationMessage: string):
|
|
814
562
|
Promise<FunctionCallHandlerResult<unknown>> {
|
|
815
563
|
if (!Annotations.AnnotationRepository.annotationsEnabled()) {
|
|
@@ -13,8 +13,9 @@ 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, DomTreeAiWidget, LcpBreakdownAiWidget,
|
|
17
|
-
StylePropertiesAiWidget,
|
|
16
|
+
AiWidget, BottomUpTreeAiWidget, ComputedStyleAiWidget, CoreVitalsAiWidget, DomTreeAiWidget, LcpBreakdownAiWidget,
|
|
17
|
+
PerformanceTraceAiWidget, StylePropertiesAiWidget,
|
|
18
|
+
TimelineRangeSummaryAiWidget} from '../../../models/ai_assistance/agents/AiAgent.js';
|
|
18
19
|
import * as AiAssistanceModel from '../../../models/ai_assistance/ai_assistance.js';
|
|
19
20
|
import * as ComputedStyle from '../../../models/computed_style/computed_style.js';
|
|
20
21
|
import * as Trace from '../../../models/trace/trace.js';
|
|
@@ -193,7 +194,15 @@ const UIStringsNotTranslate = {
|
|
|
193
194
|
/**
|
|
194
195
|
* @description Title for the performance summary widget.
|
|
195
196
|
*/
|
|
196
|
-
performanceSummary: 'Performance summary'
|
|
197
|
+
performanceSummary: 'Performance summary',
|
|
198
|
+
/**
|
|
199
|
+
* @description The title of the button that allows exporting the conversation for agents.
|
|
200
|
+
*/
|
|
201
|
+
exportForAgents: 'Copy for your coding agent',
|
|
202
|
+
/**
|
|
203
|
+
* @description Title for the bottom up thread activity widget.
|
|
204
|
+
*/
|
|
205
|
+
bottomUpTree: 'Bottom-up thread activity',
|
|
197
206
|
} as const;
|
|
198
207
|
|
|
199
208
|
export interface Step {
|
|
@@ -260,6 +269,7 @@ export interface RatingViewInput {
|
|
|
260
269
|
export interface ActionViewInput {
|
|
261
270
|
onReportClick: () => void;
|
|
262
271
|
onCopyResponseClick: () => void;
|
|
272
|
+
onExportClick?: () => void;
|
|
263
273
|
showActions: boolean;
|
|
264
274
|
}
|
|
265
275
|
|
|
@@ -293,11 +303,13 @@ export interface MessageInput {
|
|
|
293
303
|
isLoading: boolean;
|
|
294
304
|
isReadOnly: boolean;
|
|
295
305
|
isLastMessage: boolean;
|
|
306
|
+
isFirstMessage: boolean;
|
|
296
307
|
canShowFeedbackForm: boolean;
|
|
297
308
|
markdownRenderer: MarkdownLitRenderer;
|
|
298
309
|
onSuggestionClick: (suggestion: string) => void;
|
|
299
310
|
onFeedbackSubmit: (rpcId: Host.AidaClient.RpcGlobalId, rate: Host.AidaClient.Rating, feedback?: string) => void;
|
|
300
311
|
onCopyResponseClick: (message: ModelChatMessage) => void;
|
|
312
|
+
onExportClick?: () => void;
|
|
301
313
|
walkthrough: {
|
|
302
314
|
onOpen: (message: ModelChatMessage) => void,
|
|
303
315
|
isExpanded: boolean,
|
|
@@ -309,22 +321,37 @@ export interface MessageInput {
|
|
|
309
321
|
}
|
|
310
322
|
|
|
311
323
|
export const DEFAULT_VIEW = (input: ChatMessageViewInput, output: ViewOutput, target: HTMLElement): void => {
|
|
324
|
+
const hasAiV2 = Boolean(Root.Runtime.hostConfig.devToolsAiAssistanceV2?.enabled);
|
|
312
325
|
const message = input.message;
|
|
326
|
+
|
|
313
327
|
if (message.entity === ChatMessageEntity.USER) {
|
|
314
328
|
const imageInput = message.imageInput && 'inlineData' in message.imageInput ?
|
|
315
329
|
renderImageChatMessage(message.imageInput.inlineData) :
|
|
316
330
|
Lit.nothing;
|
|
331
|
+
|
|
332
|
+
const messageClasses = Lit.Directives.classMap({
|
|
333
|
+
'chat-message': true,
|
|
334
|
+
query: true,
|
|
335
|
+
'is-last-message': input.isLastMessage,
|
|
336
|
+
'is-first-message': input.isFirstMessage,
|
|
337
|
+
'ai-v2': hasAiV2,
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
const userQueryWrapperClasses = Lit.Directives.classMap({
|
|
341
|
+
// Don't need to style at all unless we are on the V2 flag.
|
|
342
|
+
// Once we ship this can be removed entirely.
|
|
343
|
+
'user-query-wrapper': hasAiV2
|
|
344
|
+
});
|
|
317
345
|
// clang-format off
|
|
318
346
|
Lit.render(html`
|
|
319
347
|
<style>${Input.textInputStyles}</style>
|
|
320
348
|
<style>${chatMessageStyles}</style>
|
|
321
|
-
<
|
|
322
|
-
class
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
</section>
|
|
349
|
+
<div class=${userQueryWrapperClasses}>
|
|
350
|
+
<section class=${messageClasses} jslog=${VisualLogging.section('question')}>
|
|
351
|
+
${imageInput}
|
|
352
|
+
<div class="message-content">${renderTextAsMarkdown(message.text, input.markdownRenderer)}</div>
|
|
353
|
+
</section>
|
|
354
|
+
</div>
|
|
328
355
|
`, target);
|
|
329
356
|
// clang-format on
|
|
330
357
|
return;
|
|
@@ -332,48 +359,53 @@ export const DEFAULT_VIEW = (input: ChatMessageViewInput, output: ViewOutput, ta
|
|
|
332
359
|
|
|
333
360
|
const steps = message.parts.filter(part => part.type === 'step').map(part => part.step);
|
|
334
361
|
const icon = AiAssistanceModel.AiUtils.getIconName();
|
|
335
|
-
const aiAssistanceV2 = Root.Runtime.hostConfig.devToolsAiAssistanceV2?.enabled;
|
|
336
362
|
|
|
363
|
+
const messageClasses = Lit.Directives.classMap({
|
|
364
|
+
'chat-message': true,
|
|
365
|
+
answer: true,
|
|
366
|
+
'is-last-message': input.isLastMessage,
|
|
367
|
+
'is-first-message': input.isFirstMessage,
|
|
368
|
+
'ai-v2': hasAiV2,
|
|
369
|
+
});
|
|
337
370
|
// clang-format off
|
|
338
371
|
Lit.render(html`
|
|
339
372
|
<style>${Input.textInputStyles}</style>
|
|
340
373
|
<style>${chatMessageStyles}</style>
|
|
341
|
-
<section
|
|
342
|
-
|
|
343
|
-
jslog=${VisualLogging.section('answer')}
|
|
344
|
-
>
|
|
345
|
-
${aiAssistanceV2 ? Lit.nothing : html`
|
|
374
|
+
<section class=${messageClasses} jslog=${VisualLogging.section('answer')}>
|
|
375
|
+
${hasAiV2 ? Lit.nothing : html`
|
|
346
376
|
<div class="message-info">
|
|
347
377
|
<devtools-icon name=${icon}></devtools-icon>
|
|
348
378
|
<div class="message-name">
|
|
349
379
|
<h2>${AiAssistanceModel.AiUtils.isGeminiBranding() ? lockedString(UIStringsNotTranslate.gemini) : lockedString(UIStringsNotTranslate.ai)}</h2>
|
|
350
380
|
</div>
|
|
351
381
|
</div>`}
|
|
352
|
-
${
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
382
|
+
${hasAiV2 ? renderWalkthroughUI(input, steps) : Lit.nothing}
|
|
383
|
+
<div class="answer-body-wrapper">
|
|
384
|
+
${Lit.Directives.repeat(
|
|
385
|
+
message.parts,
|
|
386
|
+
(_, index) => index,
|
|
387
|
+
(part, index) => {
|
|
388
|
+
const isLastPart = index === message.parts.length - 1;
|
|
389
|
+
if (part.type === 'answer') {
|
|
390
|
+
return html`<p>${renderTextAsMarkdown(part.text, input.markdownRenderer, { animate: !input.isReadOnly && input.isLoading && isLastPart && input.isLastMessage })}</p>`;
|
|
391
|
+
}
|
|
392
|
+
if (part.type === 'widget') {
|
|
393
|
+
return html`${Lit.Directives.until(renderWidgets(part.widgets, {wrapperClass: 'main-widgets-wrapper'}))}`;
|
|
394
|
+
}
|
|
395
|
+
if (!hasAiV2 && part.type === 'step') {
|
|
396
|
+
return renderStep({
|
|
397
|
+
step: part.step,
|
|
398
|
+
isLoading: input.isLoading,
|
|
399
|
+
markdownRenderer: input.markdownRenderer,
|
|
400
|
+
isLast: isLastPart,
|
|
401
|
+
});
|
|
402
|
+
}
|
|
403
|
+
return Lit.nothing;
|
|
404
|
+
},
|
|
405
|
+
)}
|
|
406
|
+
${renderError(message)}
|
|
407
|
+
${input.showActions ? renderActions(input, output) : Lit.nothing}
|
|
408
|
+
</div>
|
|
377
409
|
</section>
|
|
378
410
|
`, target);
|
|
379
411
|
// clang-format on
|
|
@@ -739,10 +771,9 @@ async function makeComputedStyleWidget(widgetData: ComputedStyleAiWidget): Promi
|
|
|
739
771
|
};
|
|
740
772
|
}
|
|
741
773
|
|
|
742
|
-
async function
|
|
774
|
+
async function makeCoreWebVitalsWidget(widgetData: CoreVitalsAiWidget): Promise<WidgetMakerResponse|null> {
|
|
743
775
|
// clang-format off
|
|
744
|
-
const renderedWidget = html`<devtools-widget
|
|
745
|
-
class="core-vitals-widget" ${widget(TimelineComponents.CWVMetrics.CWVMetrics, {data: widgetData.data})}>
|
|
776
|
+
const renderedWidget = html`<devtools-widget class="core-vitals-widget" ${widget(TimelineComponents.CWVMetrics.CWVMetrics, {data: widgetData.data, skipBottomBorder: true})}>
|
|
746
777
|
</devtools-widget>`;
|
|
747
778
|
// clang-format on
|
|
748
779
|
|
|
@@ -802,6 +833,33 @@ async function makeLcpBreakdownWidget(widgetData: LcpBreakdownAiWidget): Promise
|
|
|
802
833
|
};
|
|
803
834
|
}
|
|
804
835
|
|
|
836
|
+
async function makeBottomUpTimelineTreeWidget(widgetData: BottomUpTreeAiWidget): Promise<WidgetMakerResponse|null> {
|
|
837
|
+
const bottomUpRootNode = AiAssistanceModel.AIQueries.AIQueries.mainThreadActivityBottomUp(
|
|
838
|
+
widgetData.data.bounds, widgetData.data.parsedTrace);
|
|
839
|
+
if (!bottomUpRootNode) {
|
|
840
|
+
return null;
|
|
841
|
+
}
|
|
842
|
+
const events = bottomUpRootNode.events;
|
|
843
|
+
const startTime = Trace.Helpers.Timing.microToMilli(widgetData.data.bounds.min);
|
|
844
|
+
const endTime = Trace.Helpers.Timing.microToMilli(widgetData.data.bounds.max);
|
|
845
|
+
|
|
846
|
+
const renderedWidget = html`<devtools-widget
|
|
847
|
+
class="bottom-up-timeline-tree-widget"
|
|
848
|
+
${widget(Timeline.TimelineTreeView.BottomUpTimelineTreeView, {
|
|
849
|
+
selectedEvents: events,
|
|
850
|
+
parsedTrace: widgetData.data.parsedTrace,
|
|
851
|
+
startTime,
|
|
852
|
+
endTime,
|
|
853
|
+
compactMode: true,
|
|
854
|
+
})}></devtools-widget>`;
|
|
855
|
+
|
|
856
|
+
return {
|
|
857
|
+
renderedWidget,
|
|
858
|
+
revealable: new TimelineUtils.Helpers.RevealableBottomUpProfile(widgetData.data.bounds),
|
|
859
|
+
title: lockedString(UIStringsNotTranslate.bottomUpTree)
|
|
860
|
+
};
|
|
861
|
+
}
|
|
862
|
+
|
|
805
863
|
function renderWidgetResponse(response: WidgetMakerResponse|null): Lit.LitTemplate {
|
|
806
864
|
if (response === null) {
|
|
807
865
|
return Lit.nothing;
|
|
@@ -911,6 +969,7 @@ async function makeDomTreeWidget(widgetData: DomTreeAiWidget): Promise<WidgetMak
|
|
|
911
969
|
rootDOMNode: root,
|
|
912
970
|
visibleWidth: 400,
|
|
913
971
|
wrap: true,
|
|
972
|
+
maxRows: 10,
|
|
914
973
|
})}></devtools-widget>
|
|
915
974
|
`;
|
|
916
975
|
// clang-format on
|
|
@@ -951,7 +1010,7 @@ async function renderWidgets(
|
|
|
951
1010
|
response = await makeComputedStyleWidget(widgetData);
|
|
952
1011
|
break;
|
|
953
1012
|
case 'CORE_VITALS':
|
|
954
|
-
response = await
|
|
1013
|
+
response = await makeCoreWebVitalsWidget(widgetData);
|
|
955
1014
|
break;
|
|
956
1015
|
case 'STYLE_PROPERTIES':
|
|
957
1016
|
response = await makeStylePropertiesWidget(widgetData);
|
|
@@ -968,6 +1027,9 @@ async function renderWidgets(
|
|
|
968
1027
|
case 'TIMELINE_RANGE_SUMMARY':
|
|
969
1028
|
response = await makeTimelineRangeSummaryWidget(widgetData);
|
|
970
1029
|
break;
|
|
1030
|
+
case 'BOTTOM_UP_TREE':
|
|
1031
|
+
response = await makeBottomUpTimelineTreeWidget(widgetData);
|
|
1032
|
+
break;
|
|
971
1033
|
default:
|
|
972
1034
|
Platform.assertNever(widgetData, 'Unknown AiWidget name');
|
|
973
1035
|
}
|
|
@@ -1066,9 +1128,14 @@ function renderImageChatMessage(inlineData: Host.AidaClient.MediaBlob): Lit.LitT
|
|
|
1066
1128
|
}
|
|
1067
1129
|
|
|
1068
1130
|
function renderActions(input: ChatMessageViewInput, output: ViewOutput): Lit.LitTemplate {
|
|
1131
|
+
const aiAssistanceV2 = Root.Runtime.hostConfig.devToolsAiAssistanceV2?.enabled;
|
|
1132
|
+
const rowClasses = Lit.Directives.classMap({
|
|
1133
|
+
'ai-assistance-feedback-row': true,
|
|
1134
|
+
'not-v2': !aiAssistanceV2,
|
|
1135
|
+
});
|
|
1069
1136
|
// clang-format off
|
|
1070
1137
|
return html`
|
|
1071
|
-
<div class
|
|
1138
|
+
<div class=${rowClasses}>
|
|
1072
1139
|
<div class="action-buttons">
|
|
1073
1140
|
${input.showRateButtons ? html`
|
|
1074
1141
|
<devtools-button
|
|
@@ -1111,7 +1178,8 @@ function renderActions(input: ChatMessageViewInput, output: ViewOutput): Lit.Lit
|
|
|
1111
1178
|
}
|
|
1112
1179
|
@click=${input.onReportClick}
|
|
1113
1180
|
></devtools-button>
|
|
1114
|
-
|
|
1181
|
+
${aiAssistanceV2 ? Lit.nothing : html`
|
|
1182
|
+
<div class="vertical-separator"></div>
|
|
1115
1183
|
<devtools-button
|
|
1116
1184
|
.data=${{
|
|
1117
1185
|
variant: Buttons.Button.Variant.ICON,
|
|
@@ -1122,6 +1190,18 @@ function renderActions(input: ChatMessageViewInput, output: ViewOutput): Lit.Lit
|
|
|
1122
1190
|
} as Buttons.Button.ButtonData}
|
|
1123
1191
|
aria-label=${lockedString(UIStringsNotTranslate.copyResponse)}
|
|
1124
1192
|
@click=${input.onCopyResponseClick}></devtools-button>
|
|
1193
|
+
`}
|
|
1194
|
+
${input.onExportClick && aiAssistanceV2 && input.isLastMessage ? html`
|
|
1195
|
+
<div class="vertical-separator"></div>
|
|
1196
|
+
<devtools-button
|
|
1197
|
+
class="export-for-agents-button"
|
|
1198
|
+
.jslogContext=${'ai-export-for-agents'}
|
|
1199
|
+
.variant=${Buttons.Button.Variant.OUTLINED}
|
|
1200
|
+
.iconName=${'copy'}
|
|
1201
|
+
@click=${input.onExportClick}
|
|
1202
|
+
>${lockedString(UIStringsNotTranslate.exportForAgents)}</devtools-button>
|
|
1203
|
+
<div class="vertical-separator"></div>
|
|
1204
|
+
` : Lit.nothing}
|
|
1125
1205
|
</div>
|
|
1126
1206
|
${input.suggestions ? html`<div class="suggestions-container">
|
|
1127
1207
|
<div class="scroll-button-container left hidden" ${ref(element => { output.suggestionsLeftScrollButtonContainer = element; } )}>
|
|
@@ -1225,11 +1305,13 @@ export class ChatMessage extends UI.Widget.Widget {
|
|
|
1225
1305
|
isReadOnly = false;
|
|
1226
1306
|
canShowFeedbackForm = false;
|
|
1227
1307
|
isLastMessage = false;
|
|
1308
|
+
isFirstMessage = false;
|
|
1228
1309
|
markdownRenderer!: MarkdownLitRenderer;
|
|
1229
1310
|
onSuggestionClick: (suggestion: string) => void = () => {};
|
|
1230
1311
|
onFeedbackSubmit:
|
|
1231
1312
|
(rpcId: Host.AidaClient.RpcGlobalId, rate: Host.AidaClient.Rating, feedback?: string) => void = () => {};
|
|
1232
1313
|
onCopyResponseClick: (message: ModelChatMessage) => void = () => {};
|
|
1314
|
+
onExportClick: () => void = () => {};
|
|
1233
1315
|
walkthrough: MessageInput['walkthrough'] = {
|
|
1234
1316
|
onOpen: () => {},
|
|
1235
1317
|
onToggle: () => {},
|
|
@@ -1272,6 +1354,7 @@ export class ChatMessage extends UI.Widget.Widget {
|
|
|
1272
1354
|
canShowFeedbackForm: this.canShowFeedbackForm,
|
|
1273
1355
|
markdownRenderer: this.markdownRenderer,
|
|
1274
1356
|
isLastMessage: this.isLastMessage,
|
|
1357
|
+
isFirstMessage: this.isFirstMessage,
|
|
1275
1358
|
onSuggestionClick: this.onSuggestionClick,
|
|
1276
1359
|
onRatingClick: this.#handleRateClick.bind(this),
|
|
1277
1360
|
onReportClick: () => UIHelpers.openInNewTab(REPORT_URL),
|
|
@@ -1280,6 +1363,7 @@ export class ChatMessage extends UI.Widget.Widget {
|
|
|
1280
1363
|
this.onCopyResponseClick(this.message);
|
|
1281
1364
|
}
|
|
1282
1365
|
},
|
|
1366
|
+
onExportClick: this.onExportClick,
|
|
1283
1367
|
scrollSuggestionsScrollContainer: this.#scrollSuggestionsScrollContainer.bind(this),
|
|
1284
1368
|
onSuggestionsScrollOrResize: this.#handleSuggestionsScrollOrResize.bind(this),
|
|
1285
1369
|
onSubmit: this.#handleSubmit.bind(this),
|
|
@@ -1436,7 +1520,7 @@ async function makeTimelineRangeSummaryWidget(widgetData: TimelineRangeSummaryAi
|
|
|
1436
1520
|
widget(Timeline.ThirdPartyTreeView.ThirdPartyTreeViewWidget, {
|
|
1437
1521
|
maxRows: 10,
|
|
1438
1522
|
model: {
|
|
1439
|
-
selectedEvents: thirdPartyTree.selectedEvents
|
|
1523
|
+
selectedEvents: thirdPartyTree.selectedEvents ?? null,
|
|
1440
1524
|
parsedTrace,
|
|
1441
1525
|
entityMapper: thirdPartyTree.entityMapper(),
|
|
1442
1526
|
},
|