chrome-devtools-frontend 1.0.1589336 → 1.0.1590494

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.
Files changed (44) hide show
  1. package/front_end/core/common/Settings.ts +30 -1
  2. package/front_end/generated/Deprecation.ts +7 -0
  3. package/front_end/generated/InspectorBackendCommands.ts +9 -3
  4. package/front_end/generated/protocol-mapping.d.ts +9 -0
  5. package/front_end/generated/protocol-proxy-api.d.ts +7 -0
  6. package/front_end/generated/protocol.ts +98 -0
  7. package/front_end/models/javascript_metadata/NativeFunctions.js +48 -0
  8. package/front_end/panels/ai_assistance/AiAssistancePanel.ts +140 -32
  9. package/front_end/panels/ai_assistance/README.md +71 -0
  10. package/front_end/panels/ai_assistance/aiAssistancePanel.css +5 -0
  11. package/front_end/panels/ai_assistance/ai_assistance.ts +1 -0
  12. package/front_end/panels/ai_assistance/components/ChatMessage.ts +110 -7
  13. package/front_end/panels/ai_assistance/components/ChatView.ts +10 -0
  14. package/front_end/panels/ai_assistance/components/WalkthroughView.ts +208 -0
  15. package/front_end/panels/ai_assistance/components/chatMessage.css +2 -2
  16. package/front_end/panels/ai_assistance/components/walkthroughView.css +121 -0
  17. package/front_end/panels/application/DeviceBoundSessionsView.ts +64 -2
  18. package/front_end/panels/console/ConsoleView.ts +8 -8
  19. package/front_end/panels/console/console-meta.ts +10 -6
  20. package/front_end/panels/elements/ColorSwatchPopoverIcon.ts +14 -14
  21. package/front_end/panels/elements/ComputedStyleWidget.ts +27 -22
  22. package/front_end/panels/elements/ElementsTreeElement.ts +56 -2
  23. package/front_end/panels/elements/ElementsTreeOutline.ts +2 -0
  24. package/front_end/panels/elements/StyleEditorWidget.ts +12 -12
  25. package/front_end/panels/elements/StylePropertiesSection.ts +65 -64
  26. package/front_end/panels/elements/StylePropertyTreeElement.ts +138 -135
  27. package/front_end/panels/elements/StylesContainer.ts +57 -0
  28. package/front_end/panels/elements/StylesSidebarPane.ts +2 -1
  29. package/front_end/panels/layer_viewer/Layers3DView.ts +2 -2
  30. package/front_end/panels/network/RequestInitiatorView.ts +3 -2
  31. package/front_end/panels/timeline/timeline-meta.ts +4 -4
  32. package/front_end/panels/whats_new/ReleaseNoteText.ts +10 -16
  33. package/front_end/panels/whats_new/resources/WNDT.md +6 -6
  34. package/front_end/third_party/chromium/README.chromium +1 -1
  35. package/front_end/ui/components/markdown_view/MarkdownView.ts +5 -6
  36. package/front_end/ui/components/markdown_view/markdownView.css +6 -0
  37. package/front_end/ui/components/markdown_view/markdown_view.ts +0 -2
  38. package/front_end/ui/legacy/Treeoutline.ts +58 -10
  39. package/front_end/ui/legacy/Widget.ts +11 -1
  40. package/front_end/ui/legacy/inspectorCommon.css +2 -0
  41. package/front_end/ui/visual_logging/KnownContextValues.ts +11 -1
  42. package/package.json +1 -1
  43. package/front_end/ui/components/markdown_view/MarkdownLink.ts +0 -53
  44. package/front_end/ui/components/markdown_view/markdownLink.css +0 -11
@@ -42,12 +42,18 @@ import {DisabledWidget} from './components/DisabledWidget.js';
42
42
  import {ExploreWidget} from './components/ExploreWidget.js';
43
43
  import {MarkdownRendererWithCodeBlock} from './components/MarkdownRendererWithCodeBlock.js';
44
44
  import {PerformanceAgentMarkdownRenderer} from './components/PerformanceAgentMarkdownRenderer.js';
45
+ import {
46
+ WalkthroughView,
47
+ } from './components/WalkthroughView.js';
45
48
  import {isAiAssistancePatchingEnabled} from './PatchWidget.js';
46
49
 
47
50
  const {html} = Lit;
48
51
 
49
52
  const AI_ASSISTANCE_SEND_FEEDBACK = 'https://crbug.com/364805393' as Platform.DevToolsPath.UrlString;
50
- const AI_ASSISTANCE_HELP = 'https://developer.chrome.com/docs/devtools/ai-assistance';
53
+ const AI_ASSISTANCE_HELP =
54
+ 'https://developer.chrome.com/docs/devtools/ai-assistance' as Platform.DevToolsPath.UrlString;
55
+ const WALKTHROUGH_SIDEBAR_BREAKPOINT = 700;
56
+ const WALKTHROUGH_SIDEBAR_INITIAL_WIDTH = 400;
51
57
 
52
58
  const UIStrings = {
53
59
  /**
@@ -311,9 +317,16 @@ interface ToolbarViewInput {
311
317
  onExportConversationClick: () => void;
312
318
  onHelpClick: () => void;
313
319
  onSettingsClick: () => void;
314
- isLoading: boolean;
320
+
315
321
  showChatActions: boolean;
316
322
  showActiveConversationActions: boolean;
323
+ isLoading: boolean;
324
+
325
+ walkthrough: {
326
+ isExpanded: boolean,
327
+ isInlined: boolean,
328
+ onToggle: (isOpen: boolean) => void,
329
+ };
317
330
  }
318
331
 
319
332
  export const enum ViewState {
@@ -414,8 +427,7 @@ function defaultView(input: ViewInput, output: PanelViewOutput, target: HTMLElem
414
427
  function renderState(): Lit.TemplateResult {
415
428
  switch (input.state) {
416
429
  case ViewState.CHAT_VIEW: {
417
- return html`
418
- <devtools-ai-chat-view
430
+ return html`<devtools-ai-chat-view
419
431
  .props=${input.props}
420
432
  ${Lit.Directives.ref(el => {
421
433
  if (!el || !(el instanceof ChatView)) {
@@ -440,13 +452,40 @@ function defaultView(input: ViewInput, output: PanelViewOutput, target: HTMLElem
440
452
  }
441
453
  }
442
454
 
443
- Lit.render(
444
- html`
455
+ const shouldShowWalkthrough = input.state === ViewState.CHAT_VIEW && input.walkthrough.isExpanded;
456
+
457
+ if (Root.Runtime.hostConfig.devToolsAiAssistanceV2?.enabled) {
458
+ Lit.render(html`
459
+ ${toolbarView(input)}
460
+ <div class="ai-assistance-view-container">
461
+ <devtools-split-view
462
+ name="ai-assistance-split-view-state"
463
+ direction="column"
464
+ sidebar-position="second"
465
+ sidebar-visibility=${shouldShowWalkthrough && !input.walkthrough.isInlined ? 'visible' : 'hidden'}
466
+ sidebar-initial-size=${WALKTHROUGH_SIDEBAR_INITIAL_WIDTH}
467
+ >
468
+ <div slot="main" class="main-view">
469
+ ${renderState()}
470
+ </div>
471
+ <div slot="sidebar" class="sidebar-view">
472
+ ${shouldShowWalkthrough ? html`
473
+ <devtools-widget .widgetConfig=${UI.Widget.widgetConfig(WalkthroughView, {
474
+ message: input.props.walkthrough.activeMessage,
475
+ isLoading: input.props.isLoading,
476
+ markdownRenderer: input.props.markdownRenderer,
477
+ onToggle: input.props.walkthrough.onToggle,
478
+ })}></devtools-widget>` : Lit.nothing}
479
+ </div>
480
+ </devtools-split-view>
481
+ </div>
482
+ `, target);
483
+ } else {
484
+ Lit.render(html`
445
485
  ${toolbarView(input)}
446
486
  <div class="ai-assistance-view-container">${renderState()}</div>
447
- `,
448
- target
449
- );
487
+ `, target);
488
+ }
450
489
  // clang-format on
451
490
  }
452
491
 
@@ -482,6 +521,30 @@ function createPerformanceTraceContext(focus: AiAssistanceModel.AIContext.AgentF
482
521
  return new AiAssistanceModel.PerformanceAgent.PerformanceTraceContext(focus);
483
522
  }
484
523
 
524
+ /**
525
+ * State relating to the visibility of the Walkthrough.
526
+ * Note that we have to track the active message and the visibility as distinct
527
+ * state, because you can toggle the walkthrough via the sidebar, in which case
528
+ * we need to make it visible/hidden but keep track of the active message for
529
+ * when the user expands it again.
530
+ */
531
+ interface WalkthroughState {
532
+ /**
533
+ * Whether to show the walkthrough inline (done at narrow widths) or in a side panel.
534
+ */
535
+ isInlined: boolean;
536
+ /**
537
+ * If the walkthrough UI is currently visible (either in the sidebar, or inlined)
538
+ */
539
+ isExpanded: boolean;
540
+ /**
541
+ * The message that the walkthrough is showing all the steps for. A
542
+ * conversation can have many Model messages (1 per each user prompt) so we
543
+ * need to track which one we are showing for the walkthrough.
544
+ */
545
+ activeMessage: ModelChatMessage|null;
546
+ }
547
+
485
548
  let panelInstance: AiAssistancePanel;
486
549
  export class AiAssistancePanel extends UI.Panel.Panel {
487
550
  static panelName = 'freestyler';
@@ -516,6 +579,11 @@ export class AiAssistancePanel extends UI.Panel.Panel {
516
579
  };
517
580
  #timelinePanelInstance: TimelinePanel.TimelinePanel.TimelinePanel|null = null;
518
581
  #runAbortController = new AbortController();
582
+ #walkthrough: WalkthroughState = {
583
+ isInlined: false,
584
+ isExpanded: false,
585
+ activeMessage: null,
586
+ };
519
587
 
520
588
  constructor(private view: View = defaultView, {aidaClient, aidaAvailability, syncInfo}: {
521
589
  aidaClient: Host.AidaClient.AidaClient,
@@ -542,6 +610,29 @@ export class AiAssistancePanel extends UI.Panel.Panel {
542
610
  AiAssistanceModel.AiHistoryStorage.Events.HISTORY_DELETED, this.#onHistoryDeleted, this);
543
611
  }
544
612
 
613
+ #getToolbarInput(): ToolbarViewInput {
614
+ return {
615
+ isLoading: this.#isLoading,
616
+ showChatActions: this.#shouldShowChatActions(),
617
+ showActiveConversationActions: Boolean(this.#conversation && !this.#conversation.isEmpty),
618
+ onNewChatClick: this.#handleNewChatRequest.bind(this),
619
+ populateHistoryMenu: this.#populateHistoryMenu.bind(this),
620
+ onDeleteClick: this.#onDeleteClicked.bind(this),
621
+ onExportConversationClick: this.#onExportConversationClick.bind(this),
622
+ onHelpClick: () => {
623
+ UIHelpers.openInNewTab(AI_ASSISTANCE_HELP);
624
+ },
625
+ onSettingsClick: () => {
626
+ void UI.ViewManager.ViewManager.instance().showView('chrome-ai');
627
+ },
628
+ walkthrough: {
629
+ isExpanded: this.#walkthrough.isExpanded,
630
+ isInlined: this.#walkthrough.isInlined,
631
+ onToggle: this.#toggleWalkthrough.bind(this),
632
+ }
633
+ };
634
+ }
635
+
545
636
  async #getPanelViewInput(): Promise<PanelViewInput> {
546
637
  const blockedByAge = Root.Runtime.hostConfig.aidaAvailability?.blockedByAge === true;
547
638
 
@@ -602,6 +693,13 @@ export class AiAssistancePanel extends UI.Panel.Panel {
602
693
  onCopyResponseClick: this.#onCopyResponseClick.bind(this),
603
694
  onContextRemoved: isAiAssistanceContextSelectionAgentEnabled() ? this.#handleContextRemoved.bind(this) : null,
604
695
  onContextAdd,
696
+ walkthrough: {
697
+ onToggle: this.#toggleWalkthrough.bind(this),
698
+ onOpen: this.#openWalkthrough.bind(this),
699
+ isExpanded: this.#walkthrough.isExpanded,
700
+ isInlined: this.#walkthrough.isInlined,
701
+ activeMessage: this.#walkthrough.activeMessage,
702
+ },
605
703
  }
606
704
  };
607
705
  }
@@ -611,6 +709,34 @@ export class AiAssistancePanel extends UI.Panel.Panel {
611
709
  };
612
710
  }
613
711
 
712
+ // Responsive logic for Walkthrough
713
+ override onResize(): void {
714
+ super.onResize();
715
+ if (Root.Runtime.hostConfig.devToolsAiAssistanceV2?.enabled) {
716
+ this.#updateWalkthroughResponsiveness();
717
+ }
718
+ }
719
+
720
+ #updateWalkthroughResponsiveness(): void {
721
+ const isNarrow = this.contentElement.offsetWidth < WALKTHROUGH_SIDEBAR_BREAKPOINT;
722
+ if (isNarrow === this.#walkthrough.isInlined) {
723
+ return;
724
+ }
725
+ this.#walkthrough.isInlined = isNarrow;
726
+ this.requestUpdate();
727
+ }
728
+
729
+ #openWalkthrough(message: ModelChatMessage): void {
730
+ this.#walkthrough.activeMessage = message;
731
+ this.#walkthrough.isExpanded = true;
732
+ this.requestUpdate();
733
+ }
734
+
735
+ #toggleWalkthrough(isOpen: boolean): void {
736
+ this.#walkthrough.isExpanded = isOpen;
737
+ this.requestUpdate();
738
+ }
739
+
614
740
  #getAiAssistanceEnabledSetting(): Common.Settings.Setting<boolean>|undefined {
615
741
  try {
616
742
  return Common.Settings.moduleSetting('ai-assistance-enabled') as Common.Settings.Setting<boolean>;
@@ -932,31 +1058,13 @@ export class AiAssistancePanel extends UI.Panel.Panel {
932
1058
  return this.#changeManager.formatChangesForPatching(this.#conversation.id, /* includeSourceLocation= */ true);
933
1059
  }
934
1060
 
935
- #getToolbarInput(): ToolbarViewInput {
936
- return {
937
- isLoading: this.#isLoading,
938
- showChatActions: this.#shouldShowChatActions(),
939
- showActiveConversationActions: Boolean(this.#conversation && !this.#conversation.isEmpty),
940
- onNewChatClick: this.#handleNewChatRequest.bind(this),
941
- populateHistoryMenu: this.#populateHistoryMenu.bind(this),
942
- onDeleteClick: this.#onDeleteClicked.bind(this),
943
- onExportConversationClick: this.#onExportConversationClick.bind(this),
944
- onHelpClick: () => {
945
- UIHelpers.openInNewTab(AI_ASSISTANCE_HELP);
946
- },
947
- onSettingsClick: () => {
948
- void UI.ViewManager.ViewManager.instance().showView('chrome-ai');
949
- },
1061
+ override async performUpdate(): Promise<void> {
1062
+ const viewInput: ViewInput = {
1063
+ ...this.#getToolbarInput(),
1064
+ ...await this.#getPanelViewInput(),
950
1065
  };
951
- }
952
1066
 
953
- override async performUpdate(): Promise<void> {
954
- this.view(
955
- {
956
- ...this.#getToolbarInput(),
957
- ...await this.#getPanelViewInput(),
958
- },
959
- this.#viewOutput, this.contentElement);
1067
+ this.view(viewInput, this.#viewOutput, this.contentElement);
960
1068
  }
961
1069
 
962
1070
  #onCopyResponseClick(message: ModelChatMessage): void {
@@ -0,0 +1,71 @@
1
+ # AI Walkthrough: Data and UI Structure
2
+
3
+ This document outlines the structure of the data and UI for the AI Walkthrough feature in DevTools.
4
+
5
+ ## UI Layers
6
+
7
+ The UI is composed of several nested components, each with a specific responsibility.
8
+
9
+ ### 1. `AiAssistancePanel`
10
+
11
+ This is the top-level UI component for the entire AI Assistance feature. It acts as the main container and controller.
12
+
13
+ - **Manages State:** It holds the state of the conversation, including the history of messages.
14
+ - **Renders Child Views:** It determines which of the main views to render: the `ChatView` for an active conversation, an `ExploreWidget` for discovering features, or a `DisabledWidget` if the feature is not enabled.
15
+ - **Orchestrates Walkthrough:** It manages the state of the `WalkthroughView`, including whether it is visible, whether it is rendered inline or in a sidebar, and which model message's steps are being displayed.
16
+
17
+ ### 2. `ChatView`
18
+
19
+ This component is responsible for displaying the entire chat interface.
20
+
21
+ - **Renders Messages:** It takes the list of messages from the `AiAssistancePanel` and renders a `ChatMessage` component for each one.
22
+ - **Hosts Input:** It contains the `ChatInput` component where the user types their queries.
23
+
24
+ ### 3. `ChatMessage`
25
+
26
+ This component displays a single message in the conversation, which can be either from the user or from the AI model.
27
+
28
+ - **User Message:** Renders the user's text query.
29
+ - **Model Message:** Renders the multi-part response from the model, which includes the thinking process (steps) and the final answer. It also contains the "Show thinking" button that triggers the `WalkthroughView`.
30
+
31
+ ### 4. `WalkthroughView`
32
+
33
+ This component is dedicated to displaying the "Investigation steps" or the "thinking process" of the AI model for a given response.
34
+
35
+ - **Displays Steps:** It receives the list of `Step` objects from a `ModelChatMessage` and renders them in a readable format.
36
+ - **Flexible Rendering:** It can be displayed either as a separate sidebar view (managed by `AiAssistancePanel`) or inline within a `ChatMessage` component, depending on the available screen width.
37
+
38
+ ## Data Structure
39
+
40
+ The conversation is built upon a clear data model that distinguishes between user input and the model's complex output.
41
+
42
+ ### The Message Model
43
+
44
+ A conversation is an array of `Message` objects. There are two types of messages:
45
+
46
+ - **`UserChatMessage`**: Represents a query from the user. It has a simple structure:
47
+ - `entity`: Always `ChatMessageEntity.USER`.
48
+ - `text`: The user's query as a string.
49
+
50
+ - **`ModelChatMessage`**: Represents a response from the AI model. It has a more complex structure to accommodate the walkthrough feature:
51
+ - `entity`: Always `ChatMessageEntity.MODEL`.
52
+ - `parts`: An array of `ModelMessagePart` objects.
53
+
54
+ ### User and Model Message Relationship
55
+
56
+ In a typical conversation flow, there is a one-to-one relationship between a `UserChatMessage` and the subsequent `ModelChatMessage`. The user asks a question, and the model provides a response that contains its reasoning and final answer.
57
+
58
+ ### `ModelChatMessage` Structure: Steps and Answer
59
+
60
+ The key to the walkthrough feature lies in the `parts` array of a `ModelChatMessage`. A `ModelMessagePart` can be one of two types:
61
+
62
+ 1. **`StepPart`**: Represents a single step in the model's reasoning process. It contains a `step` object with details like:
63
+ - `title`: The title of the step (e.g., "Analyzing CSS").
64
+ - `thought`: A textual description of what the model is thinking.
65
+ - `code`: Any code that the model is executing.
66
+ - `output`: The result of the executed code.
67
+
68
+ 2. **`AnswerPart`**: Represents the final, conclusive answer from the model.
69
+ - `text`: The markdown-formatted text of the answer.
70
+
71
+ A single `ModelChatMessage` will typically have **zero or more `StepPart`s** followed by **one `AnswerPart`**. This structure allows the UI to first show the final answer and provide the option to progressively disclose the "thinking" steps that led to it via the `WalkthroughView`.
@@ -29,6 +29,11 @@
29
29
  align-items: center;
30
30
  justify-content: center;
31
31
  }
32
+
33
+ devtools-split-view {
34
+ width: 100%;
35
+ height: 100%;
36
+ }
32
37
  }
33
38
 
34
39
  .toolbar-feedback-link {
@@ -11,3 +11,4 @@ export * as ChatMessage from './components/ChatMessage.js';
11
11
  export * as ExploreWidget from './components/ExploreWidget.js';
12
12
  export * as PatchWidget from './PatchWidget.js';
13
13
  export * as DisabledWidget from './components/DisabledWidget.js';
14
+ export * as WalkthroughView from './components/WalkthroughView.js';
@@ -9,6 +9,7 @@ import * as Common from '../../../core/common/common.js';
9
9
  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
+ import * as Root from '../../../core/root/root.js';
12
13
  import * as AiAssistanceModel from '../../../models/ai_assistance/ai_assistance.js';
13
14
  import * as Marked from '../../../third_party/marked/marked.js';
14
15
  import * as Buttons from '../../../ui/components/buttons/buttons.js';
@@ -21,6 +22,7 @@ import * as Lit from '../../../ui/lit/lit.js';
21
22
  import * as VisualLogging from '../../../ui/visual_logging/visual_logging.js';
22
23
 
23
24
  import chatMessageStyles from './chatMessage.css.js';
25
+ import {WalkthroughView} from './WalkthroughView.js';
24
26
 
25
27
  const {html, Directives: {ref, ifDefined}} = Lit;
26
28
  const lockedString = i18n.i18n.lockedString;
@@ -162,6 +164,10 @@ const UIStringsNotTranslate = {
162
164
  * @description Alt text for image when it is not available.
163
165
  */
164
166
  imageUnavailable: 'Image unavailable',
167
+ /**
168
+ * @description Title for the button that shows the thinking process (walkthrough).
169
+ */
170
+ showThinking: 'Show thinking',
165
171
  } as const;
166
172
 
167
173
  export interface Step {
@@ -260,6 +266,12 @@ export interface MessageInput {
260
266
  onSuggestionClick: (suggestion: string) => void;
261
267
  onFeedbackSubmit: (rpcId: Host.AidaClient.RpcGlobalId, rate: Host.AidaClient.Rating, feedback?: string) => void;
262
268
  onCopyResponseClick: (message: ModelChatMessage) => void;
269
+ walkthrough: {
270
+ onOpen: (message: ModelChatMessage) => void,
271
+ isExpanded: boolean,
272
+ onToggle: (isOpen: boolean) => void,
273
+ isInlined: boolean,
274
+ };
263
275
  }
264
276
 
265
277
  export const DEFAULT_VIEW = (input: ChatMessageViewInput, output: ViewOutput, target: HTMLElement): void => {
@@ -298,7 +310,9 @@ export const DEFAULT_VIEW = (input: ChatMessageViewInput, output: ViewOutput, ta
298
310
  return;
299
311
  }
300
312
 
313
+ const steps = message.parts.filter(part => part.type === 'step').map(part => part.step);
301
314
  const icon = AiAssistanceModel.AiUtils.getIconName();
315
+ const aiAssistanceV2 = Root.Runtime.hostConfig.devToolsAiAssistanceV2?.enabled;
302
316
 
303
317
  // clang-format off
304
318
  Lit.render(html`
@@ -314,6 +328,7 @@ export const DEFAULT_VIEW = (input: ChatMessageViewInput, output: ViewOutput, ta
314
328
  <h2>${AiAssistanceModel.AiUtils.isGeminiBranding() ? lockedString(UIStringsNotTranslate.gemini) : lockedString(UIStringsNotTranslate.ai)}</h2>
315
329
  </div>
316
330
  </div>
331
+ ${aiAssistanceV2 ? renderWalkthroughUI(input, steps) : Lit.nothing}
317
332
  ${Lit.Directives.repeat(
318
333
  message.parts,
319
334
  (_, index) => index,
@@ -322,12 +337,15 @@ export const DEFAULT_VIEW = (input: ChatMessageViewInput, output: ViewOutput, ta
322
337
  if (part.type === 'answer') {
323
338
  return html`<p>${renderTextAsMarkdown(part.text, input.markdownRenderer, { animate: !input.isReadOnly && input.isLoading && isLastPart && input.isLastMessage })}</p>`;
324
339
  }
325
- return renderStep({
326
- step: part.step,
327
- isLoading: input.isLoading,
328
- markdownRenderer: input.markdownRenderer,
329
- isLast: isLastPart,
330
- });
340
+ if (!aiAssistanceV2 && part.type === 'step') {
341
+ return renderStep({
342
+ step: part.step,
343
+ isLoading: input.isLoading,
344
+ markdownRenderer: input.markdownRenderer,
345
+ isLast: isLastPart,
346
+ });
347
+ }
348
+ return Lit.nothing;
331
349
  },
332
350
  )}
333
351
  ${renderError(message)}
@@ -452,6 +470,84 @@ function renderStepDetails({
452
470
  // clang-format on
453
471
  }
454
472
 
473
+ /**
474
+ * Responsible for rendering the AI Walkthrough UI. This can take different
475
+ * shapes and involve different parts depending on if the walkthrough is
476
+ * inlined, expanded, or if we have side-effect steps that need the user to
477
+ * view them.
478
+ */
479
+ function renderWalkthroughUI(input: ChatMessageViewInput, steps: Step[]): Lit.LitTemplate {
480
+ if (steps.length === 0) {
481
+ return Lit.nothing;
482
+ }
483
+
484
+ const sideEffectSteps = steps.filter(s => s.sideEffect);
485
+
486
+ // If the walkthrough is in the sidebar, we render a button into the
487
+ // ChatView to open it.
488
+ // clang-format off
489
+ const openWalkThroughSidebarButton = !input.walkthrough.isInlined ? html`
490
+ <div class="walkthrough-toggle-container">
491
+ <devtools-button
492
+ .variant=${Buttons.Button.Variant.OUTLINED}
493
+ .size=${Buttons.Button.Size.SMALL}
494
+ .title=${lockedString(UIStringsNotTranslate.showThinking)}
495
+ .jslogContext=${'ai-show-walkthrough'}
496
+ @click=${() => {
497
+ if(input.walkthrough.isExpanded) {
498
+ input.walkthrough.onToggle(false);
499
+ } else {
500
+ // Can't just toggle the visibility here; we need to ensure we
501
+ // update the state with this message as the user could have had
502
+ // the walkthrough open with an alternative message.
503
+ input.walkthrough.onOpen(input.message as ModelChatMessage);
504
+ }
505
+ }}
506
+ >${lockedString(UIStringsNotTranslate.showThinking)}</devtools-button>
507
+ </div>
508
+ ` : Lit.nothing;
509
+ // clang-format on
510
+
511
+ // If there are side-effect steps, and the walkthrough is not open, we render
512
+ // those inline so that the user can see them and approve them.
513
+ // Note: this is a temporary approach and needs more UX discussion; b/487921578
514
+ // clang-format off
515
+ const sideEffectStepsUI = !input.walkthrough.isInlined && !input.walkthrough.isExpanded && sideEffectSteps.length > 0 ? sideEffectSteps.map(step => html`
516
+ <div class="side-effect-container">
517
+ ${renderStep({
518
+ step,
519
+ isLoading: input.isLoading,
520
+ markdownRenderer: input.markdownRenderer,
521
+ isLast: true
522
+ })}
523
+ </div> `) : Lit.nothing;
524
+ // clang-format on
525
+
526
+ // If the walkthrough is inlined (narrow width screens), render it here.
527
+ // Note that we force it open if there is a side-effect.
528
+ // clang-format off
529
+ const walkthroughInline = input.walkthrough.isInlined ? html`
530
+ <div class="walkthrough-container">
531
+ <devtools-widget .widgetConfig=${UI.Widget.widgetConfig(WalkthroughView, {
532
+ message: input.message as ModelChatMessage,
533
+ isLoading: input.isLoading && input.isLastMessage,
534
+ markdownRenderer: input.markdownRenderer,
535
+ isInlined: true,
536
+ isExpanded: input.isLastMessage &&
537
+ (input.walkthrough.isExpanded || steps.some(step => Boolean(step.sideEffect))),
538
+ onToggle: input.walkthrough.onToggle,
539
+ })}></devtools-widget>
540
+ </div>
541
+ ` : Lit.nothing;
542
+
543
+ return html`
544
+ ${openWalkThroughSidebarButton}
545
+ ${sideEffectStepsUI}
546
+ ${walkthroughInline}
547
+ `;
548
+ // clang-format on
549
+ }
550
+
455
551
  function renderStepBadge({step, isLoading, isLast}: {
456
552
  step: Step,
457
553
  isLoading: boolean,
@@ -481,7 +577,7 @@ function renderStepBadge({step, isLoading, isLast}: {
481
577
  ></devtools-icon>`;
482
578
  }
483
579
 
484
- function renderStep({step, isLoading, markdownRenderer, isLast}: {
580
+ export function renderStep({step, isLoading, markdownRenderer, isLast}: {
485
581
  step: Step,
486
582
  isLoading: boolean,
487
583
  markdownRenderer: MarkdownLitRenderer,
@@ -761,6 +857,12 @@ export class ChatMessage extends UI.Widget.Widget {
761
857
  onFeedbackSubmit:
762
858
  (rpcId: Host.AidaClient.RpcGlobalId, rate: Host.AidaClient.Rating, feedback?: string) => void = () => {};
763
859
  onCopyResponseClick: (message: ModelChatMessage) => void = () => {};
860
+ walkthrough: MessageInput['walkthrough'] = {
861
+ onOpen: () => {},
862
+ onToggle: () => {},
863
+ isInlined: false,
864
+ isExpanded: false,
865
+ };
764
866
 
765
867
  #suggestionsResizeObserver = new ResizeObserver(() => this.#handleSuggestionsScrollOrResize());
766
868
  #suggestionsEvaluateLayoutThrottler = new Common.Throttler.Throttler(50);
@@ -822,6 +924,7 @@ export class ChatMessage extends UI.Widget.Widget {
822
924
  currentRating: this.#currentRating,
823
925
  isShowingFeedbackForm: this.#isShowingFeedbackForm,
824
926
  onFeedbackSubmit: this.onFeedbackSubmit,
927
+ walkthrough: this.walkthrough,
825
928
  },
826
929
  this.#viewOutput, this.contentElement);
827
930
  }
@@ -76,6 +76,13 @@ export interface Props {
76
76
  disclaimerText: Platform.UIString.LocalizedString;
77
77
  uploadImageInputEnabled?: boolean;
78
78
  markdownRenderer: MarkdownLitRenderer;
79
+ walkthrough: {
80
+ onOpen: (message: ModelChatMessage) => void,
81
+ onToggle: (isOpen: boolean) => void,
82
+ isExpanded: boolean,
83
+ isInlined: boolean,
84
+ activeMessage: ModelChatMessage|null,
85
+ };
79
86
  }
80
87
 
81
88
  interface ChatWidgetInput extends Props {
@@ -115,6 +122,9 @@ const DEFAULT_VIEW: View = (input, output, target) => {
115
122
  onSuggestionClick: input.handleSuggestionClick,
116
123
  onFeedbackSubmit: input.onFeedbackSubmit,
117
124
  onCopyResponseClick: input.onCopyResponseClick,
125
+ walkthrough: {
126
+ ...input.walkthrough,
127
+ }
118
128
  })}></devtools-widget>`
119
129
  )}
120
130
  ${input.isLoading ? nothing : html`<devtools-widget