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.
- package/front_end/core/common/Settings.ts +30 -1
- package/front_end/generated/Deprecation.ts +7 -0
- package/front_end/generated/InspectorBackendCommands.ts +9 -3
- package/front_end/generated/protocol-mapping.d.ts +9 -0
- package/front_end/generated/protocol-proxy-api.d.ts +7 -0
- package/front_end/generated/protocol.ts +98 -0
- package/front_end/models/javascript_metadata/NativeFunctions.js +48 -0
- package/front_end/panels/ai_assistance/AiAssistancePanel.ts +140 -32
- package/front_end/panels/ai_assistance/README.md +71 -0
- package/front_end/panels/ai_assistance/aiAssistancePanel.css +5 -0
- package/front_end/panels/ai_assistance/ai_assistance.ts +1 -0
- package/front_end/panels/ai_assistance/components/ChatMessage.ts +110 -7
- package/front_end/panels/ai_assistance/components/ChatView.ts +10 -0
- package/front_end/panels/ai_assistance/components/WalkthroughView.ts +208 -0
- package/front_end/panels/ai_assistance/components/chatMessage.css +2 -2
- package/front_end/panels/ai_assistance/components/walkthroughView.css +121 -0
- package/front_end/panels/application/DeviceBoundSessionsView.ts +64 -2
- package/front_end/panels/console/ConsoleView.ts +8 -8
- package/front_end/panels/console/console-meta.ts +10 -6
- package/front_end/panels/elements/ColorSwatchPopoverIcon.ts +14 -14
- package/front_end/panels/elements/ComputedStyleWidget.ts +27 -22
- package/front_end/panels/elements/ElementsTreeElement.ts +56 -2
- package/front_end/panels/elements/ElementsTreeOutline.ts +2 -0
- package/front_end/panels/elements/StyleEditorWidget.ts +12 -12
- package/front_end/panels/elements/StylePropertiesSection.ts +65 -64
- package/front_end/panels/elements/StylePropertyTreeElement.ts +138 -135
- package/front_end/panels/elements/StylesContainer.ts +57 -0
- package/front_end/panels/elements/StylesSidebarPane.ts +2 -1
- package/front_end/panels/layer_viewer/Layers3DView.ts +2 -2
- package/front_end/panels/network/RequestInitiatorView.ts +3 -2
- package/front_end/panels/timeline/timeline-meta.ts +4 -4
- package/front_end/panels/whats_new/ReleaseNoteText.ts +10 -16
- package/front_end/panels/whats_new/resources/WNDT.md +6 -6
- package/front_end/third_party/chromium/README.chromium +1 -1
- package/front_end/ui/components/markdown_view/MarkdownView.ts +5 -6
- package/front_end/ui/components/markdown_view/markdownView.css +6 -0
- package/front_end/ui/components/markdown_view/markdown_view.ts +0 -2
- package/front_end/ui/legacy/Treeoutline.ts +58 -10
- package/front_end/ui/legacy/Widget.ts +11 -1
- package/front_end/ui/legacy/inspectorCommon.css +2 -0
- package/front_end/ui/visual_logging/KnownContextValues.ts +11 -1
- package/package.json +1 -1
- package/front_end/ui/components/markdown_view/MarkdownLink.ts +0 -53
- 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 =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
444
|
-
|
|
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
|
-
|
|
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
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
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
|
-
|
|
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`.
|
|
@@ -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
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
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
|