chrome-devtools-frontend 1.0.1559913 → 1.0.1561528

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 (130) hide show
  1. package/front_end/core/host/InspectorFrontendHostStub.ts +0 -3
  2. package/front_end/core/platform/ArrayUtilities.ts +13 -0
  3. package/front_end/core/root/Runtime.ts +0 -5
  4. package/front_end/core/sdk/DOMModel.ts +8 -0
  5. package/front_end/core/sdk/NetworkManager.ts +4 -0
  6. package/front_end/core/sdk/NetworkRequest.ts +9 -0
  7. package/front_end/core/sdk/OverlayModel.ts +20 -9
  8. package/front_end/entrypoints/main/MainImpl.ts +2 -1
  9. package/front_end/generated/InspectorBackendCommands.ts +4 -2
  10. package/front_end/generated/protocol-mapping.d.ts +7 -0
  11. package/front_end/generated/protocol-proxy-api.d.ts +5 -0
  12. package/front_end/generated/protocol.ts +24 -0
  13. package/front_end/models/ai_assistance/data_formatters/PerformanceTraceFormatter.snapshot.txt +23 -22
  14. package/front_end/models/badges/UserBadges.ts +48 -16
  15. package/front_end/models/greendev/Prototypes.ts +6 -1
  16. package/front_end/models/trace/extras/TraceTree.ts +1 -1
  17. package/front_end/models/trace/handlers/PageLoadMetricsHandler.ts +8 -3
  18. package/front_end/panels/ai_assistance/AiAssistancePanel.ts +11 -142
  19. package/front_end/panels/ai_assistance/PatchWidget.ts +90 -72
  20. package/front_end/panels/ai_assistance/ai_assistance.ts +1 -0
  21. package/front_end/panels/ai_assistance/components/ChatInput.ts +701 -0
  22. package/front_end/panels/ai_assistance/components/ChatView.ts +71 -1268
  23. package/front_end/panels/ai_assistance/components/UserActionRow.ts +514 -31
  24. package/front_end/panels/ai_assistance/components/chatInput.css +387 -0
  25. package/front_end/panels/ai_assistance/components/chatView.css +38 -599
  26. package/front_end/panels/ai_assistance/components/userActionRow.css +230 -0
  27. package/front_end/panels/autofill/AutofillView.ts +2 -2
  28. package/front_end/panels/changes/ChangesView.ts +15 -1
  29. package/front_end/panels/changes/changesView.css +6 -0
  30. package/front_end/panels/common/BadgeNotification.ts +44 -58
  31. package/front_end/panels/common/CopyChangesToPrompt.ts +233 -0
  32. package/front_end/panels/common/common.ts +1 -0
  33. package/front_end/panels/elements/ElementsTreeElement.ts +183 -359
  34. package/front_end/panels/elements/ElementsTreeOutline.ts +0 -6
  35. package/front_end/panels/elements/ShortcutTreeElement.ts +57 -50
  36. package/front_end/panels/elements/StylePropertiesSection.ts +1 -3
  37. package/front_end/panels/elements/components/AdornerManager.ts +5 -149
  38. package/front_end/panels/issues/HiddenIssuesRow.ts +1 -2
  39. package/front_end/panels/issues/IssueKindView.ts +2 -4
  40. package/front_end/panels/issues/IssueView.ts +2 -4
  41. package/front_end/panels/network/NetworkDataGridNode.ts +65 -1
  42. package/front_end/panels/network/NetworkLogView.ts +2 -4
  43. package/front_end/panels/network/NetworkLogViewColumns.ts +9 -0
  44. package/front_end/panels/screencast/ScreencastApp.ts +1 -0
  45. package/front_end/panels/settings/SettingsScreen.ts +3 -2
  46. package/front_end/panels/timeline/CompatibilityTracksAppender.ts +14 -1
  47. package/front_end/panels/timeline/ThirdPartyTreeView.ts +1 -4
  48. package/front_end/panels/timeline/TimelineController.ts +185 -3
  49. package/front_end/panels/timeline/TimelineFlameChartDataProvider.ts +52 -25
  50. package/front_end/panels/timeline/TimelineFlameChartView.ts +1 -0
  51. package/front_end/panels/timeline/TimelinePanel.ts +17 -104
  52. package/front_end/panels/timeline/TimelineTreeView.ts +1 -0
  53. package/front_end/panels/timeline/components/insights/BaseInsightComponent.ts +2 -2
  54. package/front_end/panels/timeline/components/insights/Table.ts +3 -3
  55. package/front_end/panels/whats_new/ReleaseNoteText.ts +15 -9
  56. package/front_end/panels/whats_new/resources/WNDT.md +6 -6
  57. package/front_end/third_party/chromium/README.chromium +1 -1
  58. package/front_end/third_party/codemirror.next/rebuild.sh +1 -1
  59. package/front_end/third_party/lit/rebuild.sh +1 -1
  60. package/front_end/third_party/puppeteer/README.chromium +2 -2
  61. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/api/Page.d.ts +2 -3
  62. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/api/Page.d.ts.map +1 -1
  63. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/api/Page.js.map +1 -1
  64. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/bidi/HTTPRequest.d.ts.map +1 -1
  65. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/bidi/HTTPRequest.js +9 -0
  66. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/bidi/HTTPRequest.js.map +1 -1
  67. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/bidi/HTTPResponse.d.ts +3 -0
  68. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/bidi/HTTPResponse.d.ts.map +1 -1
  69. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/bidi/HTTPResponse.js +9 -0
  70. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/bidi/HTTPResponse.js.map +1 -1
  71. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/bidi/core/Request.d.ts +3 -0
  72. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/bidi/core/Request.d.ts.map +1 -1
  73. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/bidi/core/Request.js +10 -0
  74. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/bidi/core/Request.js.map +1 -1
  75. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/cdp/Browser.d.ts.map +1 -1
  76. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/cdp/Browser.js +8 -4
  77. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/cdp/Browser.js.map +1 -1
  78. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/generated/injected.d.ts +1 -1
  79. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/generated/injected.d.ts.map +1 -1
  80. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/generated/injected.js +1 -1
  81. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/generated/injected.js.map +1 -1
  82. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/injected/injected.d.ts +1 -1
  83. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/revisions.d.ts +3 -3
  84. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/revisions.js +3 -3
  85. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/revisions.js.map +1 -1
  86. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/util/Mutex.d.ts +2 -2
  87. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/util/version.d.ts +1 -1
  88. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/util/version.js +1 -1
  89. package/front_end/third_party/puppeteer/package/lib/es5-iife/puppeteer-core-browser.d.ts +10 -1
  90. package/front_end/third_party/puppeteer/package/lib/es5-iife/puppeteer-core-browser.js +13 -7
  91. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/api/Page.d.ts +2 -3
  92. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/api/Page.d.ts.map +1 -1
  93. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/api/Page.js.map +1 -1
  94. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/bidi/HTTPRequest.d.ts.map +1 -1
  95. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/bidi/HTTPRequest.js +9 -0
  96. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/bidi/HTTPRequest.js.map +1 -1
  97. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/bidi/HTTPResponse.d.ts +3 -0
  98. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/bidi/HTTPResponse.d.ts.map +1 -1
  99. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/bidi/HTTPResponse.js +9 -0
  100. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/bidi/HTTPResponse.js.map +1 -1
  101. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/bidi/core/Request.d.ts +3 -0
  102. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/bidi/core/Request.d.ts.map +1 -1
  103. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/bidi/core/Request.js +10 -0
  104. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/bidi/core/Request.js.map +1 -1
  105. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/cdp/Browser.d.ts.map +1 -1
  106. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/cdp/Browser.js +8 -4
  107. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/cdp/Browser.js.map +1 -1
  108. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/generated/injected.d.ts +1 -1
  109. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/generated/injected.d.ts.map +1 -1
  110. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/generated/injected.js +1 -1
  111. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/generated/injected.js.map +1 -1
  112. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/revisions.d.ts +3 -3
  113. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/revisions.js +3 -3
  114. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/revisions.js.map +1 -1
  115. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/util/version.d.ts +1 -1
  116. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/util/version.js +1 -1
  117. package/front_end/third_party/puppeteer/package/lib/types.d.ts +10 -1
  118. package/front_end/third_party/puppeteer/package/package.json +3 -3
  119. package/front_end/third_party/puppeteer/package/src/api/Page.ts +2 -3
  120. package/front_end/third_party/puppeteer/package/src/bidi/HTTPRequest.ts +13 -0
  121. package/front_end/third_party/puppeteer/package/src/bidi/HTTPResponse.ts +10 -0
  122. package/front_end/third_party/puppeteer/package/src/bidi/core/Request.ts +15 -0
  123. package/front_end/third_party/puppeteer/package/src/cdp/Browser.ts +9 -4
  124. package/front_end/third_party/puppeteer/package/src/generated/injected.ts +1 -1
  125. package/front_end/third_party/puppeteer/package/src/revisions.ts +3 -3
  126. package/front_end/third_party/puppeteer/package/src/util/version.ts +1 -1
  127. package/front_end/ui/components/adorners/Adorner.ts +8 -68
  128. package/front_end/ui/legacy/TabbedPane.ts +1 -1
  129. package/front_end/ui/visual_logging/KnownContextValues.ts +3 -0
  130. package/package.json +2 -1
@@ -2,12 +2,18 @@
2
2
  // Use of this source code is governed by a BSD-style license that can be
3
3
  // found in the LICENSE file.
4
4
 
5
+ import '../../../ui/components/markdown_view/markdown_view.js';
6
+
5
7
  import * as Common from '../../../core/common/common.js';
6
8
  import * as Host from '../../../core/host/host.js';
7
9
  import * as i18n from '../../../core/i18n/i18n.js';
8
10
  import type * as Platform from '../../../core/platform/platform.js';
11
+ import * as AiAssistanceModel from '../../../models/ai_assistance/ai_assistance.js';
12
+ import * as Marked from '../../../third_party/marked/marked.js';
9
13
  import * as Buttons from '../../../ui/components/buttons/buttons.js';
10
14
  import * as Input from '../../../ui/components/input/input.js';
15
+ import type * as MarkdownView from '../../../ui/components/markdown_view/markdown_view.js';
16
+ import type {MarkdownLitRenderer} from '../../../ui/components/markdown_view/MarkdownView.js';
11
17
  import * as UIHelpers from '../../../ui/helpers/helpers.js';
12
18
  import * as UI from '../../../ui/legacy/legacy.js';
13
19
  import * as Lit from '../../../ui/lit/lit.js';
@@ -15,7 +21,11 @@ import * as VisualLogging from '../../../ui/visual_logging/visual_logging.js';
15
21
 
16
22
  import userActionRowStyles from './userActionRow.css.js';
17
23
 
18
- const {html, Directives: {ref}} = Lit;
24
+ const {html, Directives: {ref, ifDefined}} = Lit;
25
+ const lockedString = i18n.i18n.lockedString;
26
+
27
+ const REPORT_URL = 'https://crbug.com/364805393' as Platform.DevToolsPath.UrlString;
28
+ const SCROLL_ROUNDING_OFFSET = 1;
19
29
 
20
30
  /*
21
31
  * Strings that don't need to be translated at this time.
@@ -70,13 +80,132 @@ const UIStringsNotTranslate = {
70
80
  * @description The title of the button that copies the AI-generated response to the clipboard.
71
81
  */
72
82
  copyResponse: 'Copy response',
83
+ /**
84
+ * @description The error message when the request to the LLM failed for some reason.
85
+ */
86
+ systemError:
87
+ 'Something unforeseen happened and I can no longer continue. Try your request again and see if that resolves the issue. If this keeps happening, update Chrome to the latest version.',
88
+ /**
89
+ * @description The error message when the LLM gets stuck in a loop (max steps reached).
90
+ */
91
+ maxStepsError: 'Seems like I am stuck with the investigation. It would be better if you start over.',
92
+ /**
93
+ * @description Displayed when the user stop the response
94
+ */
95
+ stoppedResponse: 'You stopped this response',
96
+ /**
97
+ * @description Prompt for user to confirm code execution that may affect the page.
98
+ */
99
+ sideEffectConfirmationDescription: 'This code may modify page content. Continue?',
100
+ /**
101
+ * @description Button text that confirm code execution that may affect the page.
102
+ */
103
+ positiveSideEffectConfirmation: 'Continue',
104
+ /**
105
+ * @description Button text that cancels code execution that may affect the page.
106
+ */
107
+ negativeSideEffectConfirmation: 'Cancel',
108
+ /**
109
+ * @description The generic name of the AI agent (do not translate)
110
+ */
111
+ ai: 'AI',
112
+ /**
113
+ * @description The fallback text when we can't find the user full name
114
+ */
115
+ you: 'You',
116
+ /**
117
+ * @description The fallback text when a step has no title yet
118
+ */
119
+ investigating: 'Investigating',
120
+ /**
121
+ * @description Prefix to the title of each thinking step of a user action is required to continue
122
+ */
123
+ paused: 'Paused',
124
+ /**
125
+ * @description Heading text for the code block that shows the executed code.
126
+ */
127
+ codeExecuted: 'Code executed',
128
+ /**
129
+ * @description Heading text for the code block that shows the code to be executed after side effect confirmation.
130
+ */
131
+ codeToExecute: 'Code to execute',
132
+ /**
133
+ * @description Heading text for the code block that shows the returned data.
134
+ */
135
+ dataReturned: 'Data returned',
136
+ /**
137
+ * @description Aria label for the check mark icon to be read by screen reader
138
+ */
139
+ completed: 'Completed',
140
+ /**
141
+ * @description Aria label for the cancel icon to be read by screen reader
142
+ */
143
+ canceled: 'Canceled',
144
+ /**
145
+ * @description Alt text for the image input (displayed in the chat messages) that has been sent to the model.
146
+ */
147
+ imageInputSentToTheModel: 'Image input sent to the model',
148
+ /**
149
+ * @description Alt text for the account avatar.
150
+ */
151
+ accountAvatar: 'Account avatar',
152
+ /**
153
+ * @description Title for the x-link which wraps the image input rendered in chat messages.
154
+ */
155
+ openImageInNewTab: 'Open image in a new tab',
156
+ /**
157
+ * @description Alt text for image when it is not available.
158
+ */
159
+ imageUnavailable: 'Image unavailable',
73
160
  } as const;
74
161
 
75
- const lockedString = i18n.i18n.lockedString;
162
+ export interface Step {
163
+ isLoading: boolean;
164
+ thought?: string;
165
+ title?: string;
166
+ code?: string;
167
+ output?: string;
168
+ canceled?: boolean;
169
+ sideEffect?: ConfirmSideEffectDialog;
170
+ contextDetails?: [AiAssistanceModel.AiAgent.ContextDetail, ...AiAssistanceModel.AiAgent.ContextDetail[]];
171
+ }
76
172
 
77
- const REPORT_URL = 'https://support.google.com/legal/troubleshooter/1114905?hl=en#ts=1115658%2C13380504' as
78
- Platform.DevToolsPath.UrlString;
79
- const SCROLL_ROUNDING_OFFSET = 1;
173
+ export interface ConfirmSideEffectDialog {
174
+ onAnswer: (result: boolean) => void;
175
+ }
176
+
177
+ export const enum ChatMessageEntity {
178
+ MODEL = 'model',
179
+ USER = 'user',
180
+ }
181
+
182
+ export interface AnswerPart {
183
+ type: 'answer';
184
+ text: string;
185
+ suggestions?: [string, ...string[]];
186
+ }
187
+
188
+ export interface StepPart {
189
+ type: 'step';
190
+ step: Step;
191
+ }
192
+
193
+ export type ModelMessagePart = AnswerPart|StepPart;
194
+
195
+ export interface UserChatMessage {
196
+ entity: ChatMessageEntity.USER;
197
+ text: string;
198
+ imageInput?: Host.AidaClient.Part;
199
+ }
200
+
201
+ export interface ModelChatMessage {
202
+ entity: ChatMessageEntity.MODEL;
203
+ parts: ModelMessagePart[];
204
+ error?: AiAssistanceModel.AiAgent.ErrorType;
205
+ rpcId?: Host.AidaClient.RpcGlobalId;
206
+ }
207
+
208
+ export type ChatMessage = UserChatMessage|ModelChatMessage;
80
209
 
81
210
  export interface RatingViewInput {
82
211
  currentRating?: Host.AidaClient.Rating;
@@ -104,7 +233,8 @@ export interface FeedbackFormViewInput {
104
233
  isSubmitButtonDisabled: boolean;
105
234
  }
106
235
 
107
- export type UserActionRowViewInput = RatingViewInput&ActionViewInput&SuggestionViewInput&FeedbackFormViewInput;
236
+ export type UserActionRowViewInput =
237
+ MessageInput&RatingViewInput&ActionViewInput&SuggestionViewInput&FeedbackFormViewInput;
108
238
 
109
239
  export interface ViewOutput {
110
240
  suggestionsLeftScrollButtonContainer?: Element;
@@ -112,20 +242,352 @@ export interface ViewOutput {
112
242
  suggestionsRightScrollButtonContainer?: Element;
113
243
  }
114
244
 
115
- export interface UserActionRowWidgetParams {
116
- showRateButtons: boolean;
117
- onFeedbackSubmit: (rate: Host.AidaClient.Rating, feedback?: string) => void;
245
+ export interface MessageInput {
118
246
  suggestions?: [string, ...string[]];
119
- onCopyResponseClick: () => void;
120
- onSuggestionClick: (suggestion: string) => void;
247
+ message: ChatMessage;
248
+ isLoading: boolean;
249
+ isReadOnly: boolean;
250
+ isLastMessage: boolean;
121
251
  canShowFeedbackForm: boolean;
252
+ userInfo: Pick<Host.InspectorFrontendHostAPI.SyncInformation, 'accountImage'|'accountFullName'>;
253
+ markdownRenderer: MarkdownLitRenderer;
254
+ onSuggestionClick: (suggestion: string) => void;
255
+ onFeedbackSubmit: (rpcId: Host.AidaClient.RpcGlobalId, rate: Host.AidaClient.Rating, feedback?: string) => void;
256
+ onCopyResponseClick: (message: ModelChatMessage) => void;
122
257
  }
123
258
 
124
259
  export const DEFAULT_VIEW = (input: UserActionRowViewInput, output: ViewOutput, target: HTMLElement): void => {
260
+ const message = input.message;
261
+ if (message.entity === ChatMessageEntity.USER) {
262
+ const name = input.userInfo.accountFullName || lockedString(UIStringsNotTranslate.you);
263
+ const image = input.userInfo.accountImage ?
264
+ html`<img src="data:image/png;base64, ${input.userInfo.accountImage}" alt=${
265
+ UIStringsNotTranslate.accountAvatar} />` :
266
+ html`<devtools-icon
267
+ name="profile"
268
+ ></devtools-icon>`;
269
+ const imageInput = message.imageInput && 'inlineData' in message.imageInput ?
270
+ renderImageChatMessage(message.imageInput.inlineData) :
271
+ Lit.nothing;
272
+ // clang-format off
273
+ Lit.render(html`
274
+ <style>${Input.textInputStyles}</style>
275
+ <style>${userActionRowStyles}</style>
276
+ <section
277
+ class="chat-message query ${input.isLastMessage ? 'is-last-message' : ''}"
278
+ jslog=${VisualLogging.section('question')}
279
+ >
280
+ <div class="message-info">
281
+ ${image}
282
+ <div class="message-name">
283
+ <h2>${name}</h2>
284
+ </div>
285
+ </div>
286
+ ${imageInput}
287
+ <div class="message-content">${renderTextAsMarkdown(message.text, input.markdownRenderer)}</div>
288
+ </section>
289
+ `, target);
290
+ // clang-format on
291
+ return;
292
+ }
293
+
125
294
  // clang-format off
126
295
  Lit.render(html`
127
296
  <style>${Input.textInputStyles}</style>
128
297
  <style>${userActionRowStyles}</style>
298
+ <section
299
+ class="chat-message answer ${input.isLastMessage ? 'is-last-message' : ''}"
300
+ jslog=${VisualLogging.section('answer')}
301
+ >
302
+ <div class="message-info">
303
+ <devtools-icon name="smart-assistant"></devtools-icon>
304
+ <div class="message-name">
305
+ <h2>${lockedString(UIStringsNotTranslate.ai)}</h2>
306
+ </div>
307
+ </div>
308
+ ${Lit.Directives.repeat(
309
+ message.parts,
310
+ (_, index) => index,
311
+ (part, index) => {
312
+ const isLastPart = index === message.parts.length - 1;
313
+ if (part.type === 'answer') {
314
+ return html`<p>${renderTextAsMarkdown(part.text, input.markdownRenderer, { animate: !input.isReadOnly && input.isLoading && isLastPart && input.isLastMessage })}</p>`;
315
+ }
316
+ return renderStep({
317
+ step: part.step,
318
+ isLoading: input.isLoading,
319
+ markdownRenderer: input.markdownRenderer,
320
+ isLast: isLastPart,
321
+ });
322
+ },
323
+ )}
324
+ ${renderError(message)}
325
+ ${input.isLastMessage && !input.isLoading ? renderActions(input, output) : Lit.nothing}
326
+ </section>
327
+ `, target);
328
+ // clang-format on
329
+ };
330
+
331
+ export type View = typeof DEFAULT_VIEW;
332
+
333
+ function renderTextAsMarkdown(text: string, markdownRenderer: MarkdownLitRenderer, {animate, ref: refFn}: {
334
+ animate?: boolean,
335
+ ref?: (element?: Element) => void,
336
+ } = {}): Lit.TemplateResult {
337
+ let tokens = [];
338
+ try {
339
+ tokens = Marked.Marked.lexer(text);
340
+ for (const token of tokens) {
341
+ // Try to render all the tokens to make sure that
342
+ // they all have a template defined for them. If there
343
+ // isn't any template defined for a token, we'll fallback
344
+ // to rendering the text as plain text instead of markdown.
345
+ markdownRenderer.renderToken(token);
346
+ }
347
+ } catch {
348
+ // The tokens were not parsed correctly or
349
+ // one of the tokens are not supported, so we
350
+ // continue to render this as text.
351
+ return html`${text}`;
352
+ }
353
+
354
+ // clang-format off
355
+ return html`<devtools-markdown-view
356
+ .data=${{tokens, renderer: markdownRenderer, animationEnabled: animate} as MarkdownView.MarkdownView.MarkdownViewData}
357
+ ${refFn ? ref(refFn) : Lit.nothing}>
358
+ </devtools-markdown-view>`;
359
+ // clang-format on
360
+ }
361
+
362
+ function renderTitle(step: Step): Lit.LitTemplate {
363
+ const paused =
364
+ step.sideEffect ? html`<span class="paused">${lockedString(UIStringsNotTranslate.paused)}: </span>` : Lit.nothing;
365
+ const actionTitle = step.title ?? `${lockedString(UIStringsNotTranslate.investigating)}…`;
366
+
367
+ return html`<span class="title">${paused}${actionTitle}</span>`;
368
+ }
369
+
370
+ function renderStepCode(step: Step): Lit.LitTemplate {
371
+ if (!step.code && !step.output) {
372
+ return Lit.nothing;
373
+ }
374
+
375
+ // If there is no "output" yet, it means we didn't execute the code yet (e.g. maybe it is still waiting for confirmation from the user)
376
+ // thus we show "Code to execute" text rather than "Code executed" text on the heading of the code block.
377
+ const codeHeadingText = (step.output && !step.canceled) ? lockedString(UIStringsNotTranslate.codeExecuted) :
378
+ lockedString(UIStringsNotTranslate.codeToExecute);
379
+
380
+ // If there is output, we don't show notice on this code block and instead show
381
+ // it in the data returned code block.
382
+ // clang-format off
383
+ const code = step.code ? html`<div class="action-result">
384
+ <devtools-code-block
385
+ .code=${step.code.trim()}
386
+ .codeLang=${'js'}
387
+ .displayNotice=${!Boolean(step.output)}
388
+ .header=${codeHeadingText}
389
+ .showCopyButton=${true}
390
+ ></devtools-code-block>
391
+ </div>` :
392
+ Lit.nothing;
393
+ const output = step.output ? html`<div class="js-code-output">
394
+ <devtools-code-block
395
+ .code=${step.output}
396
+ .codeLang=${'js'}
397
+ .displayNotice=${true}
398
+ .header=${lockedString(UIStringsNotTranslate.dataReturned)}
399
+ .showCopyButton=${false}
400
+ ></devtools-code-block>
401
+ </div>` :
402
+ Lit.nothing;
403
+
404
+ return html`<div class="step-code">${code}${output}</div>`;
405
+ // clang-format on
406
+ }
407
+
408
+ function renderStepDetails({
409
+ step,
410
+ markdownRenderer,
411
+ isLast,
412
+ }: {
413
+ step: Step,
414
+ markdownRenderer: MarkdownLitRenderer,
415
+ isLast: boolean,
416
+ }): Lit.LitTemplate {
417
+ const sideEffects = isLast && step.sideEffect ? renderSideEffectConfirmationUi(step) : Lit.nothing;
418
+ const thought = step.thought ? html`<p>${renderTextAsMarkdown(step.thought, markdownRenderer)}</p>` : Lit.nothing;
419
+
420
+ // clang-format off
421
+ const contextDetails = step.contextDetails ?
422
+ html`${Lit.Directives.repeat(
423
+ step.contextDetails,
424
+ contextDetail => {
425
+ return html`<div class="context-details">
426
+ <devtools-code-block
427
+ .code=${contextDetail.text}
428
+ .codeLang=${contextDetail.codeLang || ''}
429
+ .displayNotice=${false}
430
+ .header=${contextDetail.title}
431
+ .showCopyButton=${true}
432
+ ></devtools-code-block>
433
+ </div>`;
434
+ },
435
+ )}` : Lit.nothing;
436
+
437
+ return html`<div class="step-details">
438
+ ${thought}
439
+ ${renderStepCode(step)}
440
+ ${sideEffects}
441
+ ${contextDetails}
442
+ </div>`;
443
+ // clang-format on
444
+ }
445
+
446
+ function renderStepBadge({step, isLoading, isLast}: {
447
+ step: Step,
448
+ isLoading: boolean,
449
+ isLast: boolean,
450
+ }): Lit.LitTemplate {
451
+ if (isLoading && isLast && !step.sideEffect) {
452
+ return html`<devtools-spinner></devtools-spinner>`;
453
+ }
454
+
455
+ let iconName = 'checkmark';
456
+ let ariaLabel: string|undefined = lockedString(UIStringsNotTranslate.completed);
457
+ let role: 'button'|undefined = 'button';
458
+ if (isLast && step.sideEffect) {
459
+ role = undefined;
460
+ ariaLabel = undefined;
461
+ iconName = 'pause-circle';
462
+ } else if (step.canceled) {
463
+ ariaLabel = lockedString(UIStringsNotTranslate.canceled);
464
+ iconName = 'cross';
465
+ }
466
+
467
+ return html`<devtools-icon
468
+ class="indicator"
469
+ role=${ifDefined(role)}
470
+ aria-label=${ifDefined(ariaLabel)}
471
+ .name=${iconName}
472
+ ></devtools-icon>`;
473
+ }
474
+
475
+ function renderStep({step, isLoading, markdownRenderer, isLast}: {
476
+ step: Step,
477
+ isLoading: boolean,
478
+ markdownRenderer: MarkdownLitRenderer,
479
+ isLast: boolean,
480
+ }): Lit.LitTemplate {
481
+ const stepClasses = Lit.Directives.classMap({
482
+ step: true,
483
+ empty: !step.thought && !step.code && !step.contextDetails && !step.sideEffect,
484
+ paused: Boolean(step.sideEffect),
485
+ canceled: Boolean(step.canceled),
486
+ });
487
+ // clang-format off
488
+ return html`
489
+ <details class=${stepClasses}
490
+ jslog=${VisualLogging.section('step')}
491
+ .open=${Boolean(step.sideEffect)}>
492
+ <summary>
493
+ <div class="summary">
494
+ ${renderStepBadge({ step, isLoading, isLast })}
495
+ ${renderTitle(step)}
496
+ <devtools-icon
497
+ class="arrow"
498
+ name="chevron-down"
499
+ ></devtools-icon>
500
+ </div>
501
+ </summary>
502
+ ${renderStepDetails({step, markdownRenderer, isLast})}
503
+ </details>`;
504
+ // clang-format on
505
+ }
506
+
507
+ function renderSideEffectConfirmationUi(step: Step): Lit.LitTemplate {
508
+ if (!step.sideEffect) {
509
+ return Lit.nothing;
510
+ }
511
+
512
+ // clang-format off
513
+ return html`<div
514
+ class="side-effect-confirmation"
515
+ jslog=${VisualLogging.section('side-effect-confirmation')}
516
+ >
517
+ <p>${lockedString(UIStringsNotTranslate.sideEffectConfirmationDescription)}</p>
518
+ <div class="side-effect-buttons-container">
519
+ <devtools-button
520
+ .data=${
521
+ {
522
+ variant: Buttons.Button.Variant.OUTLINED,
523
+ jslogContext: 'decline-execute-code',
524
+ } as Buttons.Button.ButtonData
525
+ }
526
+ @click=${() => step.sideEffect?.onAnswer(false)}
527
+ >${lockedString(
528
+ UIStringsNotTranslate.negativeSideEffectConfirmation,
529
+ )}</devtools-button>
530
+ <devtools-button
531
+ .data=${
532
+ {
533
+ variant: Buttons.Button.Variant.PRIMARY,
534
+ jslogContext: 'accept-execute-code',
535
+ iconName: 'play',
536
+ } as Buttons.Button.ButtonData
537
+ }
538
+ @click=${() => step.sideEffect?.onAnswer(true)}
539
+ >${
540
+ lockedString(UIStringsNotTranslate.positiveSideEffectConfirmation)
541
+ }</devtools-button>
542
+ </div>
543
+ </div>`;
544
+ // clang-format on
545
+ }
546
+
547
+ function renderError(message: ModelChatMessage): Lit.LitTemplate {
548
+ if (message.error) {
549
+ let errorMessage;
550
+ switch (message.error) {
551
+ case AiAssistanceModel.AiAgent.ErrorType.UNKNOWN:
552
+ case AiAssistanceModel.AiAgent.ErrorType.BLOCK:
553
+ errorMessage = UIStringsNotTranslate.systemError;
554
+ break;
555
+ case AiAssistanceModel.AiAgent.ErrorType.MAX_STEPS:
556
+ errorMessage = UIStringsNotTranslate.maxStepsError;
557
+ break;
558
+ case AiAssistanceModel.AiAgent.ErrorType.ABORT:
559
+ return html`<p class="aborted" jslog=${VisualLogging.section('aborted')}>${
560
+ lockedString(UIStringsNotTranslate.stoppedResponse)}</p>`;
561
+ }
562
+
563
+ return html`<p class="error" jslog=${VisualLogging.section('error')}>${lockedString(errorMessage)}</p>`;
564
+ }
565
+
566
+ return Lit.nothing;
567
+ }
568
+
569
+ function renderImageChatMessage(inlineData: Host.AidaClient.MediaBlob): Lit.LitTemplate {
570
+ if (inlineData.data === AiAssistanceModel.AiConversation.NOT_FOUND_IMAGE_DATA) {
571
+ // clang-format off
572
+ return html`<div class="unavailable-image" title=${UIStringsNotTranslate.imageUnavailable}>
573
+ <devtools-icon name='file-image'></devtools-icon>
574
+ </div>`;
575
+ // clang-format on
576
+ }
577
+ const imageUrl = `data:${inlineData.mimeType};base64,${inlineData.data}`;
578
+ // clang-format off
579
+ return html`<x-link
580
+ class="image-link" title=${UIStringsNotTranslate.openImageInNewTab}
581
+ href=${imageUrl}
582
+ >
583
+ <img src=${imageUrl} alt=${UIStringsNotTranslate.imageInputSentToTheModel} />
584
+ </x-link>`;
585
+ // clang-format on
586
+ }
587
+
588
+ function renderActions(input: UserActionRowViewInput, output: ViewOutput): Lit.LitTemplate {
589
+ // clang-format off
590
+ return html`
129
591
  <div class="ai-assistance-feedback-row">
130
592
  <div class="action-buttons">
131
593
  ${input.showRateButtons ? html`
@@ -273,23 +735,22 @@ export const DEFAULT_VIEW = (input: UserActionRowViewInput, output: ViewOutput,
273
735
  </div>
274
736
  </form>
275
737
  ` : Lit.nothing}
276
- `, target);
738
+ `;
277
739
  // clang-format on
278
- };
279
-
280
- export type View = typeof DEFAULT_VIEW;
740
+ }
281
741
 
282
- /**
283
- * This presenter has too many responsibilities (rating buttons, feedback
284
- * form, suggestions).
285
- */
286
- export class UserActionRow extends UI.Widget.Widget implements UserActionRowWidgetParams {
287
- showRateButtons = false;
288
- onFeedbackSubmit: (rate: Host.AidaClient.Rating, feedback?: string) => void = () => {};
289
- suggestions: [string, ...string[]]|undefined;
290
- onCopyResponseClick: () => void = () => {};
291
- onSuggestionClick: (suggestion: string) => void = () => {};
742
+ export class UserActionRow extends UI.Widget.Widget {
743
+ message: ChatMessage = {entity: ChatMessageEntity.USER, text: ''};
744
+ isLoading = false;
745
+ isReadOnly = false;
292
746
  canShowFeedbackForm = false;
747
+ isLastMessage = false;
748
+ userInfo: Pick<Host.InspectorFrontendHostAPI.SyncInformation, 'accountImage'|'accountFullName'> = {};
749
+ markdownRenderer!: MarkdownLitRenderer;
750
+ onSuggestionClick: (suggestion: string) => void = () => {};
751
+ onFeedbackSubmit:
752
+ (rpcId: Host.AidaClient.RpcGlobalId, rate: Host.AidaClient.Rating, feedback?: string) => void = () => {};
753
+ onCopyResponseClick: (message: ModelChatMessage) => void = () => {};
293
754
 
294
755
  #suggestionsResizeObserver = new ResizeObserver(() => this.#handleSuggestionsScrollOrResize());
295
756
  #suggestionsEvaluateLayoutThrottler = new Common.Throttler.Throttler(50);
@@ -320,20 +781,36 @@ export class UserActionRow extends UI.Widget.Widget implements UserActionRowWidg
320
781
  override performUpdate(): Promise<void>|void {
321
782
  this.#view(
322
783
  {
784
+ message: this.message,
785
+ isLoading: this.isLoading,
786
+ isReadOnly: this.isReadOnly,
787
+ canShowFeedbackForm: this.canShowFeedbackForm,
788
+ userInfo: this.userInfo,
789
+ markdownRenderer: this.markdownRenderer,
790
+ isLastMessage: this.isLastMessage,
323
791
  onSuggestionClick: this.onSuggestionClick,
324
792
  onRatingClick: this.#handleRateClick.bind(this),
325
793
  onReportClick: () => UIHelpers.openInNewTab(REPORT_URL),
326
- onCopyResponseClick: this.onCopyResponseClick,
794
+ onCopyResponseClick: () => {
795
+ if (this.message.entity === ChatMessageEntity.MODEL) {
796
+ this.onCopyResponseClick(this.message);
797
+ }
798
+ },
327
799
  scrollSuggestionsScrollContainer: this.#scrollSuggestionsScrollContainer.bind(this),
328
800
  onSuggestionsScrollOrResize: this.#handleSuggestionsScrollOrResize.bind(this),
329
801
  onSubmit: this.#handleSubmit.bind(this),
330
802
  onClose: this.#handleClose.bind(this),
331
803
  onInputChange: this.#handleInputChange.bind(this),
332
804
  isSubmitButtonDisabled: this.#isSubmitButtonDisabled,
333
- showRateButtons: this.showRateButtons,
334
- suggestions: this.suggestions,
805
+ // Props for actions logic
806
+ showRateButtons: this.message.entity === ChatMessageEntity.MODEL && !!this.message.rpcId,
807
+ suggestions: (this.message.entity === ChatMessageEntity.MODEL && !this.isReadOnly &&
808
+ this.message.parts.at(-1)?.type === 'answer') ?
809
+ (this.message.parts.at(-1) as AnswerPart).suggestions :
810
+ undefined,
335
811
  currentRating: this.#currentRating,
336
812
  isShowingFeedbackForm: this.#isShowingFeedbackForm,
813
+ onFeedbackSubmit: this.onFeedbackSubmit,
337
814
  },
338
815
  this.#viewOutput, this.contentElement);
339
816
  }
@@ -395,14 +872,18 @@ export class UserActionRow extends UI.Widget.Widget implements UserActionRowWidg
395
872
  this.#isShowingFeedbackForm = false;
396
873
  this.#isSubmitButtonDisabled = true;
397
874
  // This effectively reset the user rating
398
- this.onFeedbackSubmit(Host.AidaClient.Rating.SENTIMENT_UNSPECIFIED);
875
+ if (this.message.entity === ChatMessageEntity.MODEL && this.message.rpcId) {
876
+ this.onFeedbackSubmit(this.message.rpcId, Host.AidaClient.Rating.SENTIMENT_UNSPECIFIED);
877
+ }
399
878
  void this.performUpdate();
400
879
  return;
401
880
  }
402
881
 
403
882
  this.#currentRating = rating;
404
883
  this.#isShowingFeedbackForm = this.canShowFeedbackForm;
405
- this.onFeedbackSubmit(rating);
884
+ if (this.message.entity === ChatMessageEntity.MODEL && this.message.rpcId) {
885
+ this.onFeedbackSubmit(this.message.rpcId, rating);
886
+ }
406
887
  void this.performUpdate();
407
888
  }
408
889
 
@@ -418,7 +899,9 @@ export class UserActionRow extends UI.Widget.Widget implements UserActionRowWidg
418
899
  if (!this.#currentRating || !input) {
419
900
  return;
420
901
  }
421
- this.onFeedbackSubmit(this.#currentRating, input);
902
+ if (this.message.entity === ChatMessageEntity.MODEL && this.message.rpcId) {
903
+ this.onFeedbackSubmit(this.message.rpcId, this.#currentRating, input);
904
+ }
422
905
  this.#isShowingFeedbackForm = false;
423
906
  this.#isSubmitButtonDisabled = true;
424
907
  void this.performUpdate();