chrome-devtools-frontend 1.0.1592362 → 1.0.1593518
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/tab-move.svg +1 -0
- package/front_end/application_tokens.css +4 -4
- package/front_end/core/protocol_client/InspectorBackend.ts +0 -4
- package/front_end/core/sdk/EmulationModel.ts +1 -41
- package/front_end/generated/InspectorBackendCommands.ts +2 -1
- package/front_end/generated/SupportedCSSProperties.js +2 -2
- package/front_end/generated/protocol-mapping.d.ts +7 -0
- package/front_end/generated/protocol-proxy-api.d.ts +5 -0
- package/front_end/generated/protocol.ts +59 -0
- package/front_end/models/ai_assistance/agents/AiAgent.ts +24 -1
- package/front_end/models/ai_assistance/agents/BreakpointDebuggerAgent.ts +26 -86
- package/front_end/models/ai_assistance/agents/ContextSelectionAgent.ts +14 -4
- package/front_end/models/ai_assistance/agents/StylingAgent.ts +16 -2
- package/front_end/models/emulation/DeviceModeModel.ts +0 -47
- package/front_end/models/javascript_metadata/NativeFunctions.js +6 -6
- package/front_end/panels/accessibility/AXBreadcrumbsPane.ts +5 -3
- package/front_end/panels/ai_assistance/AiAssistancePanel.ts +29 -6
- package/front_end/panels/ai_assistance/README.md +61 -0
- package/front_end/panels/ai_assistance/components/ChatMessage.ts +141 -27
- package/front_end/panels/ai_assistance/components/ChatView.ts +5 -1
- package/front_end/panels/ai_assistance/components/WalkthroughView.ts +19 -7
- package/front_end/panels/ai_assistance/components/chatMessage.css +30 -0
- package/front_end/panels/ai_assistance/components/walkthroughView.css +12 -7
- package/front_end/panels/animation/AnimationGroupPreviewUI.ts +1 -1
- package/front_end/panels/application/ServiceWorkerUpdateCycleView.ts +1 -0
- package/front_end/panels/application/components/BackForwardCacheView.ts +4 -2
- package/front_end/panels/autofill/AutofillView.ts +1 -1
- package/front_end/panels/browser_debugger/DOMBreakpointsSidebarPane.ts +161 -154
- package/front_end/panels/browser_debugger/XHRBreakpointsSidebarPane.ts +1 -0
- package/front_end/panels/browser_debugger/domBreakpointsSidebarPane.css +58 -36
- package/front_end/panels/console/ConsoleViewMessage.ts +1 -0
- package/front_end/panels/console/consoleView.css +7 -2
- package/front_end/panels/elements/ComputedStyleWidget.ts +5 -1
- package/front_end/panels/elements/ElementsTreeElement.ts +1 -0
- package/front_end/panels/elements/LayoutPane.ts +8 -7
- package/front_end/panels/elements/StandaloneStylesContainer.ts +254 -0
- package/front_end/panels/elements/components/ComputedStyleTrace.ts +4 -0
- package/front_end/panels/elements/components/ElementsBreadcrumbs.ts +1 -1
- package/front_end/panels/elements/components/StylePropertyEditor.ts +2 -1
- package/front_end/panels/elements/components/computedStyleProperty.css +1 -1
- package/front_end/panels/elements/elements.ts +2 -0
- package/front_end/panels/emulation/DeviceModeToolbar.ts +0 -25
- package/front_end/panels/lighthouse/LighthouseReportRenderer.ts +1 -1
- package/front_end/panels/linear_memory_inspector/components/LinearMemoryValueInterpreter.ts +1 -1
- package/front_end/panels/linear_memory_inspector/components/ValueInterpreterDisplay.ts +1 -1
- package/front_end/panels/media/PlayerListView.ts +1 -1
- package/front_end/panels/network/NetworkLogViewColumns.ts +2 -1
- package/front_end/panels/network/components/RequestHeaderSection.ts +1 -1
- package/front_end/panels/protocol_monitor/JSONEditor.ts +1 -1
- package/front_end/panels/recorder/components/RecordingListView.ts +1 -1
- package/front_end/panels/recorder/components/StepEditor.ts +3 -3
- package/front_end/panels/settings/KeybindsSettingsTab.ts +2 -1
- package/front_end/panels/sources/components/HeadersView.ts +2 -2
- package/front_end/panels/timeline/components/BreadcrumbsUI.ts +1 -1
- package/front_end/panels/timeline/components/SidebarAnnotationsTab.ts +1 -1
- package/front_end/third_party/chromium/README.chromium +1 -1
- package/front_end/third_party/puppeteer/README.chromium +2 -2
- package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/bidi/core/Navigation.d.ts.map +1 -1
- package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/bidi/core/Navigation.js +1 -0
- package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/bidi/core/Navigation.js.map +1 -1
- package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/cdp/BrowserContext.js +1 -1
- package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/cdp/BrowserContext.js.map +1 -1
- package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/cdp/Page.js +1 -1
- package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/cdp/Page.js.map +1 -1
- package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/injected/injected.d.ts +1 -1
- package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/node/ChromeLauncher.d.ts.map +1 -1
- package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/node/ChromeLauncher.js +1 -0
- package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/node/ChromeLauncher.js.map +1 -1
- package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/revisions.d.ts +3 -3
- package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/revisions.js +3 -3
- package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/revisions.js.map +1 -1
- package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/util/Mutex.d.ts +2 -2
- package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/util/version.d.ts +1 -1
- package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/util/version.js +1 -1
- package/front_end/third_party/puppeteer/package/lib/es5-iife/puppeteer-core-browser.js +6 -6
- package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/bidi/core/Navigation.d.ts.map +1 -1
- package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/bidi/core/Navigation.js +1 -0
- package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/bidi/core/Navigation.js.map +1 -1
- package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/cdp/BrowserContext.js +1 -1
- package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/cdp/BrowserContext.js.map +1 -1
- package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/cdp/Page.js +1 -1
- package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/cdp/Page.js.map +1 -1
- package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/node/ChromeLauncher.d.ts.map +1 -1
- package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/node/ChromeLauncher.js +1 -0
- package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/node/ChromeLauncher.js.map +1 -1
- package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/revisions.d.ts +3 -3
- package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/revisions.js +3 -3
- package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/revisions.js.map +1 -1
- package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/util/version.d.ts +1 -1
- package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/util/version.js +1 -1
- package/front_end/third_party/puppeteer/package/package.json +4 -4
- package/front_end/third_party/puppeteer/package/src/bidi/core/Navigation.ts +1 -0
- package/front_end/third_party/puppeteer/package/src/cdp/BrowserContext.ts +1 -1
- package/front_end/third_party/puppeteer/package/src/cdp/Page.ts +1 -1
- package/front_end/third_party/puppeteer/package/src/node/ChromeLauncher.ts +1 -0
- package/front_end/third_party/puppeteer/package/src/revisions.ts +3 -3
- package/front_end/third_party/puppeteer/package/src/util/version.ts +1 -1
- package/front_end/ui/components/suggestion_input/SuggestionInput.ts +7 -12
- package/front_end/ui/components/tree_outline/TreeOutline.ts +6 -2
- package/front_end/ui/components/tree_outline/treeOutline.css +5 -0
- package/front_end/ui/legacy/ListControl.ts +5 -3
- package/front_end/ui/legacy/ListWidget.ts +1 -1
- package/front_end/ui/legacy/SoftContextMenu.ts +2 -1
- package/front_end/ui/legacy/Treeoutline.ts +1 -0
- package/front_end/ui/legacy/components/data_grid/DataGrid.ts +4 -2
- package/front_end/ui/legacy/components/quick_open/FilteredListWidget.ts +1 -1
- package/front_end/ui/legacy/components/utils/JSPresentationUtils.ts +31 -0
- package/front_end/ui/visual_logging/KnownContextValues.ts +1 -0
- package/package.json +1 -1
|
@@ -113,7 +113,6 @@ export class DeviceModeModel extends Common.ObjectWrapper.ObjectWrapper<EventTyp
|
|
|
113
113
|
#emulationModel: SDK.EmulationModel.EmulationModel|null;
|
|
114
114
|
#onModelAvailable: (() => void)|null;
|
|
115
115
|
#outlineRect?: Rect;
|
|
116
|
-
#screenOrientationLocked: boolean;
|
|
117
116
|
|
|
118
117
|
private constructor() {
|
|
119
118
|
super();
|
|
@@ -173,7 +172,6 @@ export class DeviceModeModel extends Common.ObjectWrapper.ObjectWrapper<EventTyp
|
|
|
173
172
|
|
|
174
173
|
this.#emulationModel = null;
|
|
175
174
|
this.#onModelAvailable = null;
|
|
176
|
-
this.#screenOrientationLocked = false;
|
|
177
175
|
SDK.TargetManager.TargetManager.instance().observeModels(SDK.EmulationModel.EmulationModel, this);
|
|
178
176
|
}
|
|
179
177
|
|
|
@@ -444,9 +442,6 @@ export class DeviceModeModel extends Common.ObjectWrapper.ObjectWrapper<EventTyp
|
|
|
444
442
|
this.#onModelAvailable = null;
|
|
445
443
|
callback();
|
|
446
444
|
}
|
|
447
|
-
emulationModel.addEventListener(
|
|
448
|
-
SDK.EmulationModel.EmulationModelEvents.SCREEN_ORIENTATION_LOCK_CHANGED, this.onScreenOrientationLockChanged,
|
|
449
|
-
this);
|
|
450
445
|
const resourceTreeModel = emulationModel.target().model(SDK.ResourceTreeModel.ResourceTreeModel);
|
|
451
446
|
if (resourceTreeModel) {
|
|
452
447
|
resourceTreeModel.addEventListener(SDK.ResourceTreeModel.Events.FrameResized, this.onFrameChange, this);
|
|
@@ -459,12 +454,7 @@ export class DeviceModeModel extends Common.ObjectWrapper.ObjectWrapper<EventTyp
|
|
|
459
454
|
|
|
460
455
|
modelRemoved(emulationModel: SDK.EmulationModel.EmulationModel): void {
|
|
461
456
|
if (this.#emulationModel === emulationModel) {
|
|
462
|
-
emulationModel.removeEventListener(
|
|
463
|
-
SDK.EmulationModel.EmulationModelEvents.SCREEN_ORIENTATION_LOCK_CHANGED, this.onScreenOrientationLockChanged,
|
|
464
|
-
this);
|
|
465
457
|
this.#emulationModel = null;
|
|
466
|
-
this.#screenOrientationLocked = false;
|
|
467
|
-
this.dispatchEventToListeners(Events.UPDATED);
|
|
468
458
|
}
|
|
469
459
|
}
|
|
470
460
|
|
|
@@ -481,43 +471,6 @@ export class DeviceModeModel extends Common.ObjectWrapper.ObjectWrapper<EventTyp
|
|
|
481
471
|
this.showHingeIfApplicable(overlayModel);
|
|
482
472
|
}
|
|
483
473
|
|
|
484
|
-
private onScreenOrientationLockChanged(
|
|
485
|
-
event: Common.EventTarget.EventTargetEvent<SDK.EmulationModel.ScreenOrientationLockChangedEvent>): void {
|
|
486
|
-
this.#screenOrientationLocked = event.data.locked;
|
|
487
|
-
if (event.data.locked && event.data.orientation) {
|
|
488
|
-
this.applyOrientationLock(event.data.orientation);
|
|
489
|
-
}
|
|
490
|
-
this.dispatchEventToListeners(Events.UPDATED);
|
|
491
|
-
}
|
|
492
|
-
|
|
493
|
-
private applyOrientationLock(orientation: Protocol.Emulation.ScreenOrientation): void {
|
|
494
|
-
const wantsLandscape = orientation.type === Protocol.Emulation.ScreenOrientationType.LandscapePrimary ||
|
|
495
|
-
orientation.type === Protocol.Emulation.ScreenOrientationType.LandscapeSecondary;
|
|
496
|
-
|
|
497
|
-
if (this.#type === Type.Device && this.#device && this.#mode) {
|
|
498
|
-
// For device emulation, switch to the matching orientation mode.
|
|
499
|
-
const isCurrentlyLandscape =
|
|
500
|
-
this.#mode.orientation === Horizontal || this.#mode.orientation === HorizontalSpanned;
|
|
501
|
-
if (wantsLandscape !== isCurrentlyLandscape) {
|
|
502
|
-
const rotationPartner = this.#device.getRotationPartner(this.#mode);
|
|
503
|
-
if (rotationPartner) {
|
|
504
|
-
this.emulate(this.#type, this.#device, rotationPartner);
|
|
505
|
-
}
|
|
506
|
-
}
|
|
507
|
-
} else if (this.#type === Type.Responsive) {
|
|
508
|
-
// For responsive mode, swap width/height if orientation doesn't match.
|
|
509
|
-
const appliedSize = this.appliedDeviceSize();
|
|
510
|
-
const isCurrentlyLandscape = appliedSize.width > appliedSize.height;
|
|
511
|
-
if (wantsLandscape !== isCurrentlyLandscape) {
|
|
512
|
-
this.setSizeAndScaleToFit(appliedSize.height, appliedSize.width);
|
|
513
|
-
}
|
|
514
|
-
}
|
|
515
|
-
}
|
|
516
|
-
|
|
517
|
-
isScreenOrientationLocked(): boolean {
|
|
518
|
-
return this.#screenOrientationLocked;
|
|
519
|
-
}
|
|
520
|
-
|
|
521
474
|
private scaleSettingChanged(): void {
|
|
522
475
|
this.calculateAndEmulate(false);
|
|
523
476
|
}
|
|
@@ -7322,16 +7322,16 @@ export const NativeFunctions = [
|
|
|
7322
7322
|
signatures: [["type","eventInitDict"]]
|
|
7323
7323
|
},
|
|
7324
7324
|
{
|
|
7325
|
-
name: "
|
|
7326
|
-
signatures: [["
|
|
7325
|
+
name: "provideContext",
|
|
7326
|
+
signatures: [["?options"]]
|
|
7327
7327
|
},
|
|
7328
7328
|
{
|
|
7329
|
-
name: "
|
|
7330
|
-
signatures: [["
|
|
7329
|
+
name: "registerTool",
|
|
7330
|
+
signatures: [["tool"]]
|
|
7331
7331
|
},
|
|
7332
7332
|
{
|
|
7333
|
-
name: "
|
|
7334
|
-
signatures: [["
|
|
7333
|
+
name: "unregisterTool",
|
|
7334
|
+
signatures: [["name"]]
|
|
7335
7335
|
},
|
|
7336
7336
|
{
|
|
7337
7337
|
name: "SnapEvent",
|
|
@@ -452,9 +452,11 @@ export class AXBreadcrumb {
|
|
|
452
452
|
|
|
453
453
|
this.#element = document.createElement('div');
|
|
454
454
|
this.#element.classList.add('ax-breadcrumb');
|
|
455
|
-
this.#element.setAttribute(
|
|
456
|
-
|
|
457
|
-
|
|
455
|
+
this.#element.setAttribute('jslog', `${VisualLogging.treeItem().track({
|
|
456
|
+
click: true,
|
|
457
|
+
resize: true,
|
|
458
|
+
keydown: 'ArrowUp|ArrowDown|ArrowLeft|ArrowRight|Enter'
|
|
459
|
+
})}`);
|
|
458
460
|
elementsToAXBreadcrumb.set(this.#element, this);
|
|
459
461
|
|
|
460
462
|
this.#nodeElement = document.createElement('div');
|
|
@@ -460,10 +460,23 @@ function defaultView(input: ViewInput, output: PanelViewOutput, target: HTMLElem
|
|
|
460
460
|
}
|
|
461
461
|
}
|
|
462
462
|
|
|
463
|
-
const shouldShowWalkthrough = input.state === ViewState.CHAT_VIEW && input.walkthrough.isExpanded;
|
|
464
|
-
|
|
465
463
|
if (Root.Runtime.hostConfig.devToolsAiAssistanceV2?.enabled ||
|
|
466
464
|
Greendev.Prototypes.instance().isEnabled('breakpointDebuggerAgent')) {
|
|
465
|
+
|
|
466
|
+
const shouldShowWalkthrough = input.state === ViewState.CHAT_VIEW && input.walkthrough.isExpanded;
|
|
467
|
+
/**
|
|
468
|
+
* We want to mark the walkthrough as loading only if it's showing the last
|
|
469
|
+
* message. Otherwise, a previous walkthrough will show as loading if we
|
|
470
|
+
* rely only on the isLoading flag.
|
|
471
|
+
*/
|
|
472
|
+
let walkthroughIsForLastMessage = false;
|
|
473
|
+
if(input.state === ViewState.CHAT_VIEW) {
|
|
474
|
+
const lastMessage = input.props.messages.at(-1);
|
|
475
|
+
if(lastMessage && input.props.walkthrough.activeMessage === lastMessage) {
|
|
476
|
+
walkthroughIsForLastMessage = true;
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
|
|
467
480
|
Lit.render(html`
|
|
468
481
|
${toolbarView(input)}
|
|
469
482
|
<div class="ai-assistance-view-container">
|
|
@@ -481,7 +494,7 @@ function defaultView(input: ViewInput, output: PanelViewOutput, target: HTMLElem
|
|
|
481
494
|
${shouldShowWalkthrough ? html`
|
|
482
495
|
<devtools-widget .widgetConfig=${UI.Widget.widgetConfig(WalkthroughView, {
|
|
483
496
|
message: input.props.walkthrough.activeMessage,
|
|
484
|
-
isLoading: input.props.isLoading,
|
|
497
|
+
isLoading: input.props.isLoading && walkthroughIsForLastMessage,
|
|
485
498
|
markdownRenderer: input.props.markdownRenderer,
|
|
486
499
|
onToggle: input.props.walkthrough.onToggle,
|
|
487
500
|
})}></devtools-widget>` : Lit.nothing}
|
|
@@ -1439,11 +1452,19 @@ export class AiAssistancePanel extends UI.Panel.Panel {
|
|
|
1439
1452
|
this.#updateConversationState();
|
|
1440
1453
|
}
|
|
1441
1454
|
|
|
1455
|
+
#clearWalkthrough(): void {
|
|
1456
|
+
this.#walkthrough.isExpanded = false;
|
|
1457
|
+
this.#walkthrough.activeMessage = null;
|
|
1458
|
+
}
|
|
1459
|
+
|
|
1442
1460
|
#onDeleteClicked(): void {
|
|
1443
1461
|
if (!this.#conversation) {
|
|
1444
1462
|
return;
|
|
1445
1463
|
}
|
|
1446
1464
|
|
|
1465
|
+
// Ensure we clear the walkthrough so it doesn't hold onto a chat that is about to be deleted.
|
|
1466
|
+
this.#clearWalkthrough();
|
|
1467
|
+
|
|
1447
1468
|
void AiAssistanceModel.AiHistoryStorage.AiHistoryStorage.instance().deleteHistoryEntry(this.#conversation.id);
|
|
1448
1469
|
this.#updateConversationState();
|
|
1449
1470
|
UI.ARIAUtils.LiveAnnouncer.alert(i18nString(UIStrings.chatDeleted));
|
|
@@ -1650,7 +1671,7 @@ export class AiAssistancePanel extends UI.Panel.Panel {
|
|
|
1650
1671
|
let announcedAnswerLoading = false;
|
|
1651
1672
|
let announcedAnswerReady = false;
|
|
1652
1673
|
for await (const data of items) {
|
|
1653
|
-
step.
|
|
1674
|
+
step.requestApproval = undefined;
|
|
1654
1675
|
switch (data.type) {
|
|
1655
1676
|
case AiAssistanceModel.AiAgent.ResponseType.USER_QUERY: {
|
|
1656
1677
|
this.#messages.push({
|
|
@@ -1711,10 +1732,11 @@ export class AiAssistancePanel extends UI.Panel.Panel {
|
|
|
1711
1732
|
case AiAssistanceModel.AiAgent.ResponseType.SIDE_EFFECT: {
|
|
1712
1733
|
step.isLoading = false;
|
|
1713
1734
|
step.code ??= data.code;
|
|
1714
|
-
step.
|
|
1735
|
+
step.requestApproval = {
|
|
1736
|
+
description: data.description,
|
|
1715
1737
|
onAnswer: (result: boolean) => {
|
|
1716
1738
|
data.confirm(result);
|
|
1717
|
-
step.
|
|
1739
|
+
step.requestApproval = undefined;
|
|
1718
1740
|
this.requestUpdate();
|
|
1719
1741
|
},
|
|
1720
1742
|
};
|
|
@@ -1726,6 +1748,7 @@ export class AiAssistancePanel extends UI.Panel.Panel {
|
|
|
1726
1748
|
step.code ??= data.code;
|
|
1727
1749
|
step.output ??= data.output;
|
|
1728
1750
|
step.canceled = data.canceled;
|
|
1751
|
+
step.widgets ??= data.widgets;
|
|
1729
1752
|
commitStep();
|
|
1730
1753
|
break;
|
|
1731
1754
|
}
|
|
@@ -83,3 +83,64 @@ The spinner displayed next to a step in the UI is intentionally tied to the **co
|
|
|
83
83
|
* **Providing Continuous Feedback**: Using the conversation's `isLoading` ensures a persistent visual indicator that the AI is actively working on the query, even if individual steps are quickly parsed. This prevents the UI from appearing unresponsive.
|
|
84
84
|
* **Avoiding a "Stuck" Feeling**: If the spinner were tied to `step.isLoading`, it would flicker on and off rapidly, potentially making the user feel that the AI has stopped processing or is stuck.
|
|
85
85
|
* **Clear Progress Visualization**: The spinner is dynamically moved to the *last* active step in the list as long as the overall conversation is loading. Once a step is completed, it receives a checkmark, and the spinner moves to the next active step.
|
|
86
|
+
|
|
87
|
+
## AI-Defined UI Widgets
|
|
88
|
+
|
|
89
|
+
To provide a richer user experience, AI functions can now return data that represents UI widgets. These widgets are then rendered directly within the AI assistance panel, allowing for more interactive and contextual responses.
|
|
90
|
+
|
|
91
|
+
### Data Structure for Widgets
|
|
92
|
+
|
|
93
|
+
This is achieved through a new type of `ModelMessagePart`: the `WidgetPart`.
|
|
94
|
+
|
|
95
|
+
- **`WidgetPart`**: Represents a UI widget to be rendered. It contains a `widget` object with the following properties:
|
|
96
|
+
- `name`: A string identifier for the widget to be rendered (e.g., `'freestyler'`). This name is used by the frontend to select the correct widget component.
|
|
97
|
+
- `data`: An object containing the data required by the widget. The structure of this data is specific to each widget.
|
|
98
|
+
|
|
99
|
+
A `ModelChatMessage` can contain one or more `WidgetPart`s, usually as part of the final answer.
|
|
100
|
+
|
|
101
|
+
**Example `ModelChatMessage` with a `WidgetPart`:**
|
|
102
|
+
|
|
103
|
+
```json
|
|
104
|
+
{
|
|
105
|
+
"entity": "MODEL",
|
|
106
|
+
"parts": [
|
|
107
|
+
{
|
|
108
|
+
"type": "answer",
|
|
109
|
+
"text": "Here is a widget to help you with CSS."
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
"type": "widget",
|
|
113
|
+
"widget": {
|
|
114
|
+
"name": "freestyler",
|
|
115
|
+
"data": {
|
|
116
|
+
"css": "color: red;"
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
]
|
|
121
|
+
}
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### From AI Function to UI
|
|
125
|
+
|
|
126
|
+
The process of rendering an AI-defined UI widget begins within the AI function handler itself. AI functions, typically defined in `front_end/models/ai_assistance/agents/AiAgent.js`, can return an `AiWidget` type as part of their response.
|
|
127
|
+
|
|
128
|
+
1. **AI Function Output**: An AI function constructs an `AiWidget` object, specifying its `name` (e.g., `'freestyler'` or `'COMPUTED_STYLES'`) and a `data` payload that contains all the necessary information for the widget to render.
|
|
129
|
+
|
|
130
|
+
2. **`ModelChatMessage` Integration**: This `AiWidget` is then encapsulated within a `WidgetPart`, which is added to the `parts` array of a `ModelChatMessage`. This `ModelChatMessage` is what the `ChatMessage` UI component receives.
|
|
131
|
+
|
|
132
|
+
3. **`ChatMessage` Processing**: In `front_end/panels/ai_assistance/components/ChatMessage.ts`, the `ChatMessage` component is responsible for iterating through the `parts` of a `ModelChatMessage`. When it encounters a `WidgetPart`, it delegates the rendering to the `renderStepWidgets` function.
|
|
133
|
+
|
|
134
|
+
4. **`renderStepWidgets` Mapping**: The `renderStepWidgets` function acts as a registry and renderer for different widget types. It reads the `widget.name` from the `WidgetPart` and, based on this name, calls a specific `make...Widget` function (e.g., `makeComputedStyleWidget` for `'COMPUTED_STYLES'` widgets).
|
|
135
|
+
|
|
136
|
+
5. **Widget Instantiation and Rendering**: Each `make...Widget` function is responsible for taking the `widget.data` and converting it into a `UI.Widget.widgetConfig` object. This configuration is then used with the `<devtools-widget>` Lit component, which dynamically instantiates the corresponding `UI.Widget` subclass and renders it into the DOM.
|
|
137
|
+
|
|
138
|
+
This modular approach ensures that new UI widgets can be introduced and managed by AI functions without requiring significant changes to the core messaging or rendering infrastructure.
|
|
139
|
+
|
|
140
|
+
The `ChatMessage` component is responsible for handling `WidgetPart`s. When it encounters a `WidgetPart`, it will:
|
|
141
|
+
|
|
142
|
+
1. Look up the widget name (`widget.name`) in a registry of available widget components.
|
|
143
|
+
2. Instantiate the corresponding widget component.
|
|
144
|
+
3. Pass the `widget.data` to the component as properties.
|
|
145
|
+
|
|
146
|
+
This allows for a flexible system where new widgets can be added to the frontend and then invoked by the AI without requiring changes to the core message handling logic.
|
|
@@ -10,7 +10,10 @@ import * as Host from '../../../core/host/host.js';
|
|
|
10
10
|
import * as i18n from '../../../core/i18n/i18n.js';
|
|
11
11
|
import type * as Platform from '../../../core/platform/platform.js';
|
|
12
12
|
import * as Root from '../../../core/root/root.js';
|
|
13
|
+
import * as SDK from '../../../core/sdk/sdk.js';
|
|
14
|
+
import type {AiWidget, ComputedStyleAiWidget} from '../../../models/ai_assistance/agents/AiAgent.js';
|
|
13
15
|
import * as AiAssistanceModel from '../../../models/ai_assistance/ai_assistance.js';
|
|
16
|
+
import * as ComputedStyle from '../../../models/computed_style/computed_style.js';
|
|
14
17
|
import * as Marked from '../../../third_party/marked/marked.js';
|
|
15
18
|
import * as Buttons from '../../../ui/components/buttons/buttons.js';
|
|
16
19
|
import * as Input from '../../../ui/components/input/input.js';
|
|
@@ -20,9 +23,10 @@ import * as UIHelpers from '../../../ui/helpers/helpers.js';
|
|
|
20
23
|
import * as UI from '../../../ui/legacy/legacy.js';
|
|
21
24
|
import * as Lit from '../../../ui/lit/lit.js';
|
|
22
25
|
import * as VisualLogging from '../../../ui/visual_logging/visual_logging.js';
|
|
26
|
+
import * as Elements from '../../elements/elements.js';
|
|
23
27
|
|
|
24
28
|
import chatMessageStyles from './chatMessage.css.js';
|
|
25
|
-
import {WalkthroughView} from './WalkthroughView.js';
|
|
29
|
+
import {walkthroughTitle, WalkthroughView} from './WalkthroughView.js';
|
|
26
30
|
|
|
27
31
|
const {html, Directives: {ref, ifDefined}} = Lit;
|
|
28
32
|
const lockedString = i18n.i18n.lockedString;
|
|
@@ -96,18 +100,14 @@ const UIStringsNotTranslate = {
|
|
|
96
100
|
* @description Displayed when the user stop the response
|
|
97
101
|
*/
|
|
98
102
|
stoppedResponse: 'You stopped this response',
|
|
99
|
-
/**
|
|
100
|
-
* @description Prompt for user to confirm code execution that may affect the page.
|
|
101
|
-
*/
|
|
102
|
-
sideEffectConfirmationDescription: 'This code may modify page content. Continue?',
|
|
103
103
|
/**
|
|
104
104
|
* @description Button text that confirm code execution that may affect the page.
|
|
105
105
|
*/
|
|
106
|
-
|
|
106
|
+
confirmActionRequestApproval: 'Continue',
|
|
107
107
|
/**
|
|
108
108
|
* @description Button text that cancels code execution that may affect the page.
|
|
109
109
|
*/
|
|
110
|
-
|
|
110
|
+
declineActionRequestApproval: 'Cancel',
|
|
111
111
|
/**
|
|
112
112
|
* @description The generic name of the AI agent (do not translate)
|
|
113
113
|
*/
|
|
@@ -168,6 +168,11 @@ const UIStringsNotTranslate = {
|
|
|
168
168
|
* @description Title for the button that shows the thinking process (walkthrough).
|
|
169
169
|
*/
|
|
170
170
|
showThinking: 'Show thinking',
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* @description Title for the button that takes the user into other DevTools panels to reveal items the AI references.
|
|
174
|
+
*/
|
|
175
|
+
reveal: 'Reveal'
|
|
171
176
|
} as const;
|
|
172
177
|
|
|
173
178
|
export interface Step {
|
|
@@ -176,12 +181,14 @@ export interface Step {
|
|
|
176
181
|
title?: string;
|
|
177
182
|
code?: string;
|
|
178
183
|
output?: string;
|
|
184
|
+
widgets?: AiWidget[];
|
|
179
185
|
canceled?: boolean;
|
|
180
|
-
|
|
186
|
+
requestApproval?: ConfirmSideEffectDialog;
|
|
181
187
|
contextDetails?: [AiAssistanceModel.AiAgent.ContextDetail, ...AiAssistanceModel.AiAgent.ContextDetail[]];
|
|
182
188
|
}
|
|
183
189
|
|
|
184
190
|
export interface ConfirmSideEffectDialog {
|
|
191
|
+
description: string|null;
|
|
185
192
|
onAnswer: (result: boolean) => void;
|
|
186
193
|
}
|
|
187
194
|
|
|
@@ -386,13 +393,14 @@ function renderTextAsMarkdown(text: string, markdownRenderer: MarkdownLitRendere
|
|
|
386
393
|
// clang-format on
|
|
387
394
|
}
|
|
388
395
|
|
|
389
|
-
function titleForStep(step: Step): string {
|
|
396
|
+
export function titleForStep(step: Step): string {
|
|
390
397
|
return step.title ?? `${lockedString(UIStringsNotTranslate.investigating)}…`;
|
|
391
398
|
}
|
|
392
399
|
|
|
393
400
|
function renderTitle(step: Step): Lit.LitTemplate {
|
|
394
|
-
const paused =
|
|
395
|
-
|
|
401
|
+
const paused = step.requestApproval ?
|
|
402
|
+
html`<span class="paused">${lockedString(UIStringsNotTranslate.paused)}: </span>` :
|
|
403
|
+
Lit.nothing;
|
|
396
404
|
|
|
397
405
|
return html`<span class="title">${paused}${titleForStep(step)}</span>`;
|
|
398
406
|
}
|
|
@@ -444,7 +452,7 @@ function renderStepDetails({
|
|
|
444
452
|
markdownRenderer: MarkdownLitRenderer,
|
|
445
453
|
isLast: boolean,
|
|
446
454
|
}): Lit.LitTemplate {
|
|
447
|
-
const sideEffects = isLast && step.
|
|
455
|
+
const sideEffects = isLast && step.requestApproval ? renderSideEffectConfirmationUi(step) : Lit.nothing;
|
|
448
456
|
const thought = step.thought ? html`<p>${renderTextAsMarkdown(step.thought, markdownRenderer)}</p>` : Lit.nothing;
|
|
449
457
|
|
|
450
458
|
// clang-format off
|
|
@@ -481,7 +489,10 @@ function renderWalkthroughSidebarButton(
|
|
|
481
489
|
if (walkthrough.isInlined) {
|
|
482
490
|
return Lit.nothing;
|
|
483
491
|
}
|
|
484
|
-
const title =
|
|
492
|
+
const title = walkthroughTitle({
|
|
493
|
+
isLoading: input.isLoading,
|
|
494
|
+
lastStep,
|
|
495
|
+
});
|
|
485
496
|
|
|
486
497
|
// clang-format off
|
|
487
498
|
return html`
|
|
@@ -520,7 +531,7 @@ function renderWalkthroughUI(input: ChatMessageViewInput, steps: Step[]): Lit.Li
|
|
|
520
531
|
// No steps = no walkthrough UI in the chat view.
|
|
521
532
|
return Lit.nothing;
|
|
522
533
|
}
|
|
523
|
-
const sideEffectSteps = steps.filter(s => s.
|
|
534
|
+
const sideEffectSteps = steps.filter(s => s.requestApproval);
|
|
524
535
|
// If the walkthrough is in the sidebar, we render a button into the
|
|
525
536
|
// ChatView to open it.
|
|
526
537
|
const openWalkThroughSidebarButton =
|
|
@@ -552,7 +563,7 @@ function renderWalkthroughUI(input: ChatMessageViewInput, steps: Step[]): Lit.Li
|
|
|
552
563
|
markdownRenderer: input.markdownRenderer,
|
|
553
564
|
isInlined: true,
|
|
554
565
|
isExpanded: input.isLastMessage &&
|
|
555
|
-
(input.walkthrough.isExpanded || steps.some(step => Boolean(step.
|
|
566
|
+
(input.walkthrough.isExpanded || steps.some(step => Boolean(step.requestApproval))),
|
|
556
567
|
onToggle: input.walkthrough.onToggle,
|
|
557
568
|
})}></devtools-widget>
|
|
558
569
|
</div>
|
|
@@ -571,14 +582,14 @@ function renderStepBadge({step, isLoading, isLast}: {
|
|
|
571
582
|
isLoading: boolean,
|
|
572
583
|
isLast: boolean,
|
|
573
584
|
}): Lit.LitTemplate {
|
|
574
|
-
if (isLoading && isLast && !step.
|
|
585
|
+
if (isLoading && isLast && !step.requestApproval) {
|
|
575
586
|
return html`<devtools-spinner></devtools-spinner>`;
|
|
576
587
|
}
|
|
577
588
|
|
|
578
589
|
let iconName = 'checkmark';
|
|
579
590
|
let ariaLabel: string|undefined = lockedString(UIStringsNotTranslate.completed);
|
|
580
591
|
let role: 'button'|undefined = 'button';
|
|
581
|
-
if (isLast && step.
|
|
592
|
+
if (isLast && step.requestApproval) {
|
|
582
593
|
role = undefined;
|
|
583
594
|
ariaLabel = undefined;
|
|
584
595
|
iconName = 'pause-circle';
|
|
@@ -601,17 +612,18 @@ export function renderStep({step, isLoading, markdownRenderer, isLast}: {
|
|
|
601
612
|
markdownRenderer: MarkdownLitRenderer,
|
|
602
613
|
isLast: boolean,
|
|
603
614
|
}): Lit.LitTemplate {
|
|
615
|
+
const shouldRenderWidgets = Boolean(step.widgets?.length && Root.Runtime.hostConfig.devToolsAiAssistanceV2?.enabled);
|
|
604
616
|
const stepClasses = Lit.Directives.classMap({
|
|
605
617
|
step: true,
|
|
606
|
-
empty: !step.thought && !step.code && !step.contextDetails && !step.
|
|
607
|
-
paused: Boolean(step.
|
|
618
|
+
empty: !step.thought && !step.code && !step.contextDetails && !step.requestApproval,
|
|
619
|
+
paused: Boolean(step.requestApproval),
|
|
608
620
|
canceled: Boolean(step.canceled),
|
|
609
621
|
});
|
|
610
622
|
// clang-format off
|
|
611
623
|
return html`
|
|
612
624
|
<details class=${stepClasses}
|
|
613
625
|
jslog=${VisualLogging.section('step')}
|
|
614
|
-
.open=${Boolean(step.
|
|
626
|
+
.open=${Boolean(step.requestApproval)}>
|
|
615
627
|
<summary>
|
|
616
628
|
<div class="summary">
|
|
617
629
|
${renderStepBadge({ step, isLoading, isLast })}
|
|
@@ -623,12 +635,114 @@ export function renderStep({step, isLoading, markdownRenderer, isLast}: {
|
|
|
623
635
|
</div>
|
|
624
636
|
</summary>
|
|
625
637
|
${renderStepDetails({step, markdownRenderer, isLast})}
|
|
626
|
-
</details
|
|
638
|
+
</details>
|
|
639
|
+
${shouldRenderWidgets ? html`
|
|
640
|
+
<div class="step-widgets-wrapper">
|
|
641
|
+
${Lit.Directives.until(renderStepWidgets(step))}
|
|
642
|
+
</div>` : Lit.nothing
|
|
643
|
+
}`;
|
|
644
|
+
// clang-format on
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
interface WidgetMakerResponse {
|
|
648
|
+
renderedWidget: Lit.LitTemplate;
|
|
649
|
+
revealable: unknown;
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
async function makeComputedStyleWidget(widgetData: ComputedStyleAiWidget): Promise<WidgetMakerResponse|null> {
|
|
653
|
+
const target = SDK.TargetManager.TargetManager.instance().primaryPageTarget();
|
|
654
|
+
if (!target) {
|
|
655
|
+
return null;
|
|
656
|
+
}
|
|
657
|
+
const node = new SDK.DOMModel.DeferredDOMNode(
|
|
658
|
+
target,
|
|
659
|
+
widgetData.data.backendNodeId,
|
|
660
|
+
);
|
|
661
|
+
const resolved = await node.resolvePromise();
|
|
662
|
+
if (!resolved) {
|
|
663
|
+
return null;
|
|
664
|
+
}
|
|
665
|
+
const model = new ComputedStyle.ComputedStyleModel.ComputedStyleModel(resolved);
|
|
666
|
+
const styles = new ComputedStyle.ComputedStyleModel.ComputedStyle(resolved, widgetData.data.computedStyles);
|
|
667
|
+
|
|
668
|
+
const widgetConfig = UI.Widget.widgetConfig(Elements.ComputedStyleWidget.ComputedStyleWidget, {
|
|
669
|
+
nodeStyle: styles,
|
|
670
|
+
matchedStyles: widgetData.data.matchedCascade,
|
|
671
|
+
// This disables showing the nested traces and detailed information in the widget.
|
|
672
|
+
propertyTraces: null,
|
|
673
|
+
computedStyleModel: model,
|
|
674
|
+
allowUserControl: false,
|
|
675
|
+
filterText: new RegExp(widgetData.data.properties.join('|'), 'i')
|
|
676
|
+
});
|
|
677
|
+
|
|
678
|
+
// clang-format off
|
|
679
|
+
const widget = html`<devtools-widget class="computed-styles-widget" .widgetConfig=${widgetConfig}></devtools-widget>`;
|
|
627
680
|
// clang-format on
|
|
681
|
+
|
|
682
|
+
return {renderedWidget: widget, revealable: new Elements.ElementsPanel.NodeComputedStyles(resolved)};
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
function renderWidgetResponse(response: WidgetMakerResponse|null): Lit.LitTemplate {
|
|
686
|
+
if (response === null) {
|
|
687
|
+
return Lit.nothing;
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
function onReveal(): void {
|
|
691
|
+
if (response === null) {
|
|
692
|
+
return;
|
|
693
|
+
}
|
|
694
|
+
void Common.Revealer.reveal(response?.revealable);
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
// clang-format off
|
|
698
|
+
return html`
|
|
699
|
+
<div class="widget-content-container">
|
|
700
|
+
${response.renderedWidget}
|
|
701
|
+
</div>
|
|
702
|
+
<div class="widget-reveal-container">
|
|
703
|
+
<devtools-button class="widget-reveal"
|
|
704
|
+
.iconName=${'tab-move'}
|
|
705
|
+
.variant=${Buttons.Button.Variant.TEXT}
|
|
706
|
+
@click=${onReveal}
|
|
707
|
+
>${lockedString(UIStringsNotTranslate.reveal)}</devtools-button>
|
|
708
|
+
</div>
|
|
709
|
+
`;
|
|
710
|
+
// clang-format on
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
/**
|
|
714
|
+
* Renders AI-defined UI widgets within a step.
|
|
715
|
+
* When a ModelChatMessage contains a WidgetPart, the ChatMessage component
|
|
716
|
+
* iterates through the `step.widgets` array. For each widget, it determines
|
|
717
|
+
* the appropriate rendering logic based on the `widgetData.name`.
|
|
718
|
+
*
|
|
719
|
+
* Currently, only 'COMPUTED_STYLES' widgets are supported. For these, the
|
|
720
|
+
* `makeComputedStyleWidget` function is called to construct the necessary
|
|
721
|
+
* data and configuration for the `Elements.ComputedStyleWidget.ComputedStyleWidget`
|
|
722
|
+
* component. The widget is then rendered using the `<devtools-widget>`
|
|
723
|
+
* custom element, which dynamically instantiates and displays the specified
|
|
724
|
+
* UI.Widget subclass with the provided configuration.
|
|
725
|
+
*
|
|
726
|
+
* This allows for a flexible and extensible system where new widget types
|
|
727
|
+
* can be added to the AI responses and rendered in DevTools by adding
|
|
728
|
+
* corresponding `make...Widget` functions and handling them here.
|
|
729
|
+
*/
|
|
730
|
+
async function renderStepWidgets(step: Step): Promise<Lit.LitTemplate> {
|
|
731
|
+
if (!step.widgets || step.widgets.length === 0) {
|
|
732
|
+
return Lit.nothing;
|
|
733
|
+
}
|
|
734
|
+
const ui = await Promise.all(step.widgets.map(async widgetData => {
|
|
735
|
+
if (widgetData.name === 'COMPUTED_STYLES') {
|
|
736
|
+
const response = await makeComputedStyleWidget(widgetData);
|
|
737
|
+
return renderWidgetResponse(response);
|
|
738
|
+
}
|
|
739
|
+
return Lit.nothing;
|
|
740
|
+
}));
|
|
741
|
+
return html`${ui}`;
|
|
628
742
|
}
|
|
629
743
|
|
|
630
744
|
function renderSideEffectConfirmationUi(step: Step): Lit.LitTemplate {
|
|
631
|
-
if (!step.
|
|
745
|
+
if (!step.requestApproval) {
|
|
632
746
|
return Lit.nothing;
|
|
633
747
|
}
|
|
634
748
|
|
|
@@ -637,7 +751,7 @@ function renderSideEffectConfirmationUi(step: Step): Lit.LitTemplate {
|
|
|
637
751
|
class="side-effect-confirmation"
|
|
638
752
|
jslog=${VisualLogging.section('side-effect-confirmation')}
|
|
639
753
|
>
|
|
640
|
-
|
|
754
|
+
${step.requestApproval.description ? html`<p>${step.requestApproval.description}</p>` : Lit.nothing}
|
|
641
755
|
<div class="side-effect-buttons-container">
|
|
642
756
|
<devtools-button
|
|
643
757
|
.data=${
|
|
@@ -646,9 +760,9 @@ function renderSideEffectConfirmationUi(step: Step): Lit.LitTemplate {
|
|
|
646
760
|
jslogContext: 'decline-execute-code',
|
|
647
761
|
} as Buttons.Button.ButtonData
|
|
648
762
|
}
|
|
649
|
-
@click=${() => step.
|
|
763
|
+
@click=${() => step.requestApproval?.onAnswer(false)}
|
|
650
764
|
>${lockedString(
|
|
651
|
-
UIStringsNotTranslate.
|
|
765
|
+
UIStringsNotTranslate.declineActionRequestApproval,
|
|
652
766
|
)}</devtools-button>
|
|
653
767
|
<devtools-button
|
|
654
768
|
.data=${
|
|
@@ -658,9 +772,9 @@ function renderSideEffectConfirmationUi(step: Step): Lit.LitTemplate {
|
|
|
658
772
|
iconName: 'play',
|
|
659
773
|
} as Buttons.Button.ButtonData
|
|
660
774
|
}
|
|
661
|
-
@click=${() => step.
|
|
775
|
+
@click=${() => step.requestApproval?.onAnswer(true)}
|
|
662
776
|
>${
|
|
663
|
-
lockedString(UIStringsNotTranslate.
|
|
777
|
+
lockedString(UIStringsNotTranslate.confirmActionRequestApproval)
|
|
664
778
|
}</devtools-button>
|
|
665
779
|
</div>
|
|
666
780
|
</div>`;
|
|
@@ -20,7 +20,11 @@ import chatViewStyles from './chatView.css.js';
|
|
|
20
20
|
|
|
21
21
|
export {ChatInput, type ImageInputData} from './ChatInput.js';
|
|
22
22
|
|
|
23
|
-
const {
|
|
23
|
+
const {
|
|
24
|
+
ref,
|
|
25
|
+
repeat,
|
|
26
|
+
classMap,
|
|
27
|
+
} = Directives;
|
|
24
28
|
|
|
25
29
|
/*
|
|
26
30
|
* Strings that don't need to be translated at this time.
|
|
@@ -10,9 +10,11 @@ import * as UI from '../../../ui/legacy/legacy.js';
|
|
|
10
10
|
import * as Lit from '../../../ui/lit/lit.js';
|
|
11
11
|
|
|
12
12
|
import chatMessageStyles from './chatMessage.css.js';
|
|
13
|
-
import {type ModelChatMessage, renderStep} from './ChatMessage.js';
|
|
13
|
+
import {type ModelChatMessage, renderStep, type Step, titleForStep} from './ChatMessage.js';
|
|
14
14
|
import walkthroughViewStyles from './walkthroughView.css.js';
|
|
15
15
|
|
|
16
|
+
const lockedString = i18n.i18n.lockedString;
|
|
17
|
+
|
|
16
18
|
const {html, render} = Lit;
|
|
17
19
|
|
|
18
20
|
const UIStrings = {
|
|
@@ -25,9 +27,9 @@ const UIStrings = {
|
|
|
25
27
|
*/
|
|
26
28
|
title: 'Investigation steps',
|
|
27
29
|
/**
|
|
28
|
-
* @description Title for the
|
|
30
|
+
* @description Title for the button that shows the thinking process (walkthrough).
|
|
29
31
|
*/
|
|
30
|
-
|
|
32
|
+
showThinking: 'Show thinking',
|
|
31
33
|
} as const;
|
|
32
34
|
const str_ = i18n.i18n.registerUIStrings('panels/ai_assistance/components/WalkthroughView.ts', UIStrings);
|
|
33
35
|
const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_);
|
|
@@ -41,8 +43,17 @@ export interface ViewInput {
|
|
|
41
43
|
onToggle: (isOpen: boolean) => void;
|
|
42
44
|
}
|
|
43
45
|
|
|
44
|
-
function
|
|
45
|
-
|
|
46
|
+
export function walkthroughTitle(input: {
|
|
47
|
+
isLoading: boolean,
|
|
48
|
+
lastStep: Step,
|
|
49
|
+
}): string {
|
|
50
|
+
const title = input.isLoading ? titleForStep(input.lastStep) : lockedString(UIStrings.showThinking);
|
|
51
|
+
return title;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function renderInlineWalkthrough(input: ViewInput, stepsOutput: Lit.LitTemplate, steps: Step[]): Lit.LitTemplate {
|
|
55
|
+
const lastStep = steps.at(-1);
|
|
56
|
+
if (!input.isInlined || !lastStep) {
|
|
46
57
|
return Lit.nothing;
|
|
47
58
|
}
|
|
48
59
|
|
|
@@ -54,7 +65,8 @@ function renderInlineWalkthrough(input: ViewInput, stepsOutput: Lit.LitTemplate)
|
|
|
54
65
|
return html`
|
|
55
66
|
<details class="walkthrough-inline" ?open=${input.isExpanded} @toggle=${onToggle}>
|
|
56
67
|
<summary>
|
|
57
|
-
${
|
|
68
|
+
${input.isLoading ? html`<devtools-spinner></devtools-spinner>` : Lit.nothing}
|
|
69
|
+
${walkthroughTitle({isLoading: input.isLoading, lastStep,})}
|
|
58
70
|
<devtools-icon name="chevron-down"></devtools-icon>
|
|
59
71
|
</summary>
|
|
60
72
|
${stepsOutput}
|
|
@@ -123,7 +135,7 @@ export const DEFAULT_VIEW = (
|
|
|
123
135
|
${chatMessageStyles}
|
|
124
136
|
${walkthroughViewStyles}
|
|
125
137
|
</style>
|
|
126
|
-
${input.isInlined ? renderInlineWalkthrough(input, stepsOutput)
|
|
138
|
+
${input.isInlined ? renderInlineWalkthrough(input, stepsOutput, steps)
|
|
127
139
|
: renderSidebarWalkthrough(input, stepsOutput, steps.length)
|
|
128
140
|
}`, target);
|
|
129
141
|
// clang-format on
|