chrome-devtools-frontend 1.0.1605219 → 1.0.1606789

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 (78) hide show
  1. package/front_end/Images/src/dots-circle.svg +10 -0
  2. package/front_end/core/host/UserMetrics.ts +0 -1
  3. package/front_end/core/platform/api/HostRuntime.ts +9 -6
  4. package/front_end/core/platform/browser/HostRuntime.ts +2 -2
  5. package/front_end/core/platform/node/HostRuntime.ts +7 -7
  6. package/front_end/core/protocol_client/InspectorBackend.ts +4 -0
  7. package/front_end/core/root/ExperimentNames.ts +0 -1
  8. package/front_end/core/sdk/CrashReportContextModel.ts +28 -0
  9. package/front_end/core/sdk/sdk.ts +2 -2
  10. package/front_end/entrypoints/heap_snapshot_worker/HeapSnapshotWorkerDispatcher.ts +3 -3
  11. package/front_end/entrypoints/heap_snapshot_worker/heap_snapshot_worker-entrypoint.ts +5 -4
  12. package/front_end/entrypoints/main/MainImpl.ts +0 -101
  13. package/front_end/models/ai_assistance/ChangeManager.ts +6 -6
  14. package/front_end/models/ai_assistance/agents/AccessibilityAgent.ts +112 -5
  15. package/front_end/models/ai_assistance/agents/AiAgent.ts +9 -25
  16. package/front_end/models/ai_assistance/agents/ContextSelectionAgent.snapshot.txt +2 -2
  17. package/front_end/models/ai_assistance/agents/ContextSelectionAgent.ts +6 -3
  18. package/front_end/models/ai_assistance/agents/ConversationSummaryAgent.ts +10 -9
  19. package/front_end/models/ai_assistance/agents/ExecuteJavascript.ts +313 -0
  20. package/front_end/models/ai_assistance/agents/FileAgent.ts +15 -1
  21. package/front_end/models/ai_assistance/agents/NetworkAgent.ts +15 -1
  22. package/front_end/models/ai_assistance/agents/PerformanceAgent.ts +19 -8
  23. package/front_end/models/ai_assistance/agents/StylingAgent.ts +35 -278
  24. package/front_end/models/ai_assistance/ai_assistance.ts +0 -2
  25. package/front_end/models/javascript_metadata/NativeFunctions.js +1 -5
  26. package/front_end/models/web_mcp/WebMCPModel.ts +187 -0
  27. package/front_end/models/web_mcp/web_mcp.ts +9 -0
  28. package/front_end/panels/ai_assistance/AiAssistancePanel.ts +8 -1
  29. package/front_end/panels/ai_assistance/ai_assistance.ts +1 -0
  30. package/front_end/panels/ai_assistance/components/AccessibilityAgentMarkdownRenderer.ts +88 -0
  31. package/front_end/panels/ai_assistance/components/ChatMessage.ts +149 -49
  32. package/front_end/panels/ai_assistance/components/ChatView.ts +11 -15
  33. package/front_end/panels/ai_assistance/components/ExportForAgentsDialog.ts +13 -4
  34. package/front_end/panels/ai_assistance/components/chatMessage.css +53 -1
  35. package/front_end/panels/ai_assistance/components/chatView.css +0 -10
  36. package/front_end/panels/ai_assistance/components/exportForAgentsDialog.css +13 -0
  37. package/front_end/panels/application/WebMCPView.ts +471 -25
  38. package/front_end/panels/application/webMCPView.css +53 -2
  39. package/front_end/panels/elements/ElementsTreeElement.ts +1 -1
  40. package/front_end/panels/elements/ElementsTreeOutline.ts +87 -0
  41. package/front_end/panels/elements/StylePropertiesSection.ts +0 -4
  42. package/front_end/panels/elements/elements.ts +3 -0
  43. package/front_end/panels/elements/elementsTreeOutline.css +21 -0
  44. package/front_end/panels/elements/stylePropertiesTreeOutline.css +7 -0
  45. package/front_end/panels/lighthouse/LighthousePanel.ts +7 -1
  46. package/front_end/panels/lighthouse/LighthouseProtocolService.ts +25 -2
  47. package/front_end/panels/profiler/HeapDetachedElementsView.ts +0 -4
  48. package/front_end/panels/profiler/HeapProfileView.ts +0 -4
  49. package/front_end/panels/profiler/HeapProfilerPanel.ts +4 -13
  50. package/front_end/panels/profiler/HeapSnapshotProxy.ts +2 -6
  51. package/front_end/panels/profiler/HeapSnapshotView.ts +19 -32
  52. package/front_end/panels/profiler/ProfileHeader.ts +1 -15
  53. package/front_end/panels/profiler/ProfileTypeRegistry.ts +4 -12
  54. package/front_end/panels/profiler/ProfileView.ts +1 -6
  55. package/front_end/panels/profiler/ProfilesPanel.ts +60 -7
  56. package/front_end/panels/protocol_monitor/ProtocolMonitor.ts +27 -22
  57. package/front_end/panels/timeline/EventsTimelineTreeView.ts +1 -1
  58. package/front_end/panels/timeline/ThirdPartyTreeView.ts +2 -2
  59. package/front_end/panels/timeline/TimelinePanel.ts +11 -153
  60. package/front_end/panels/timeline/TimelineTreeView.ts +147 -48
  61. package/front_end/panels/timeline/TimelineUIUtils.ts +2 -1
  62. package/front_end/panels/timeline/components/CWVMetrics.ts +18 -2
  63. package/front_end/panels/timeline/timeline-meta.ts +11 -0
  64. package/front_end/panels/timeline/utils/Helpers.ts +5 -0
  65. package/front_end/third_party/chromium/README.chromium +1 -1
  66. package/front_end/ui/components/markdown_view/CodeBlock.ts +47 -1
  67. package/front_end/ui/components/markdown_view/codeBlock.css +8 -0
  68. package/front_end/ui/legacy/FilterBar.ts +2 -0
  69. package/front_end/ui/legacy/SplitWidget.ts +33 -27
  70. package/front_end/ui/legacy/TabbedPane.ts +123 -3
  71. package/front_end/ui/legacy/Widget.ts +95 -48
  72. package/front_end/ui/legacy/components/source_frame/JSONView.ts +1 -1
  73. package/front_end/ui/legacy/components/utils/jsUtils.css +2 -0
  74. package/front_end/ui/legacy/infobar.css +1 -0
  75. package/front_end/ui/visual_logging/KnownContextValues.ts +6 -1
  76. package/package.json +1 -1
  77. package/front_end/core/sdk/WebMCPModel.ts +0 -104
  78. package/front_end/models/ai_assistance/ConversationHandler.ts +0 -325
@@ -62,8 +62,6 @@
62
62
  }
63
63
  }
64
64
 
65
-
66
-
67
65
  .link {
68
66
  color: var(--text-link);
69
67
  text-decoration: underline;
@@ -411,11 +409,3 @@ main {
411
409
  padding-bottom: var(--sys-size-1);
412
410
  }
413
411
  }
414
-
415
- .export-for-agents-button {
416
- /* Bottom margin to ensure it's not right up against the chat box when
417
- * the conversation requires a scrollbar */
418
- margin: 0 auto var(--sys-size-8) auto;
419
- display: block;
420
-
421
- }
@@ -63,6 +63,19 @@
63
63
  border: none;
64
64
  }
65
65
 
66
+ main {
67
+ position: relative;
68
+ }
69
+
70
+ .prompt-loading {
71
+ position: absolute;
72
+ padding: var(--sys-size-5);
73
+ display: flex;
74
+ align-items: center;
75
+ justify-content: flex-start;
76
+ gap: var(--sys-size-5);
77
+ }
78
+
66
79
  .export-for-agents-dialog .disclaimer {
67
80
  margin-top: var(--sys-size-5);
68
81
  font: var(--sys-typescale-body4-regular);
@@ -4,16 +4,24 @@
4
4
 
5
5
  import '../../ui/components/icon_button/icon_button.js';
6
6
  import '../../ui/components/lists/lists.js';
7
+ import '../../ui/legacy/components/data_grid/data_grid.js';
7
8
  import '../../ui/legacy/legacy.js';
8
9
 
9
10
  import * as i18n from '../../core/i18n/i18n.js';
11
+ import * as Platform from '../../core/platform/platform.js';
10
12
  import * as SDK from '../../core/sdk/sdk.js';
11
- import type * as Protocol from '../../generated/protocol.js';
13
+ import * as Protocol from '../../generated/protocol.js';
14
+ import * as WebMCP from '../../models/web_mcp/web_mcp.js';
15
+ import * as Adorners from '../../ui/components/adorners/adorners.js';
16
+ import * as Buttons from '../../ui/components/buttons/buttons.js';
17
+ import type * as IconButton from '../../ui/components/icon_button/icon_button.js';
12
18
  import * as UI from '../../ui/legacy/legacy.js';
13
19
  import {
20
+ Directives,
14
21
  html,
15
22
  render,
16
23
  } from '../../ui/lit/lit.js';
24
+ import * as VisualLogging from '../../ui/visual_logging/visual_logging.js';
17
25
 
18
26
  import webMCPViewStyles from './webMCPView.css.js';
19
27
 
@@ -38,25 +46,338 @@ const UIStrings = {
38
46
  /**
39
47
  * @description Text to display when no calls have been made
40
48
  */
41
- noCallsPlaceholder: 'Start interacting with your `WebMCP` agent to see real-time tool calls and executions here.'
49
+ noCallsPlaceholder: 'Start interacting with your `WebMCP` agent to see real-time tool calls and executions here.',
50
+ /**
51
+ * @description Text for the name of a tool call
52
+ */
53
+ name: 'Name',
54
+ /**
55
+ * @description Text for the status of a tool call
56
+ */
57
+ status: 'Status',
58
+ /**
59
+ * @description Text for the input of a tool call
60
+ */
61
+ input: 'Input',
62
+ /**
63
+ * @description Text for the output of a tool call
64
+ */
65
+ output: 'Output',
66
+ /**
67
+ * @description Text for the status of a tool call that is in progress
68
+ */
69
+ inProgress: 'In Progress',
70
+ /**
71
+ * @description Tooltip for the clear log button
72
+ */
73
+ clearLog: 'Clear log',
74
+ /**
75
+ * @description Placeholder for the filter input
76
+ */
77
+ filter: 'Filter',
78
+ /**
79
+ * @description Tooltip for the tool types dropdown
80
+ */
81
+ toolTypes: 'Tool types',
82
+ /**
83
+ * @description Tooltip for the status types dropdown
84
+ */
85
+ statusTypes: 'Status types',
86
+ /**
87
+ * @description Tooltip for the clear filters button
88
+ */
89
+ clearFilters: 'Clear filters',
90
+ /**
91
+ * @description Filter option for imperative tools
92
+ */
93
+ imperative: 'Imperative',
94
+ /**
95
+ * @description Filter option for declarative tools
96
+ */
97
+ declarative: 'Declarative',
98
+ /**
99
+ * @description Text for the status of a tool call that has failed
100
+ */
101
+ error: 'Error',
102
+ /**
103
+ * @description Text for the status of a tool call that was canceled
104
+ */
105
+ canceled: 'Canceled',
106
+ /**
107
+ * @description Text for the status of a tool call that succeeded
108
+ */
109
+ success: 'Success',
110
+ /**
111
+ * @description Text for the status of a tool call that has failed
112
+ */
113
+ pending: 'In Progress',
114
+ /**
115
+ * @description Text for the total number of tool calls
116
+ * @example {2} PH1
117
+ */
118
+ totalCalls: '{PH1} Total calls',
119
+ /**
120
+ * @description Text for the number of failed tool calls
121
+ * @example {1} PH1
122
+ */
123
+ failed: '{PH1} Failed',
124
+ /**
125
+ * @description Text for the number of canceled tool calls
126
+ * @example {1} PH1
127
+ */
128
+ canceledCount: '{PH1} Canceled',
129
+ /**
130
+ * @description Text for the number of in progress tool calls
131
+ * @example {1} PH1
132
+ */
133
+ inProgressCount: '{PH1} In Progress',
42
134
  } as const;
43
135
  const str_ = i18n.i18n.registerUIStrings('panels/application/WebMCPView.ts', UIStrings);
44
136
  const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_);
45
137
 
138
+ export interface FilterState {
139
+ text: string;
140
+ toolTypes?: {
141
+ imperative?: boolean,
142
+ declarative?: boolean,
143
+ };
144
+ statusTypes?: {
145
+ success?: boolean,
146
+ error?: boolean,
147
+ pending?: boolean,
148
+ };
149
+ }
150
+
151
+ export interface FilterMenuButton {
152
+ button: UI.Toolbar.ToolbarMenuButton;
153
+ setCount: (count: number) => void;
154
+ }
155
+
156
+ export interface FilterMenuButtons {
157
+ toolTypes: FilterMenuButton;
158
+ statusTypes: FilterMenuButton;
159
+ }
46
160
  export interface ViewInput {
47
- tools: Protocol.WebMCP.Tool[];
161
+ tools: WebMCP.WebMCPModel.Tool[];
162
+ filters: FilterState;
163
+ filterButtons: FilterMenuButtons;
164
+ onClearLogClick: () => void;
165
+ onFilterChange: (filters: FilterState) => void;
166
+ toolCalls: WebMCP.WebMCPModel.Call[];
48
167
  }
49
168
 
169
+ export function filterToolCalls(
170
+ toolCalls: WebMCP.WebMCPModel.Call[], filterState: FilterState): WebMCP.WebMCPModel.Call[] {
171
+ let filtered = [...toolCalls];
172
+
173
+ const statusTypes = filterState.statusTypes;
174
+ if (statusTypes) {
175
+ filtered = filtered.filter(call => {
176
+ const {success, error, pending} = statusTypes;
177
+ if (success && call.result?.status === Protocol.WebMCP.InvocationStatus.Success) {
178
+ return true;
179
+ }
180
+ if (error && call.result?.status === Protocol.WebMCP.InvocationStatus.Error) {
181
+ return true;
182
+ }
183
+ if (pending && call.result === undefined) {
184
+ return true;
185
+ }
186
+ return false;
187
+ });
188
+ }
189
+
190
+ const toolTypes = filterState.toolTypes;
191
+ if (toolTypes) {
192
+ filtered = filtered.filter(call => {
193
+ const {imperative, declarative} = toolTypes;
194
+ if (imperative && !call.tool.isDeclarative) {
195
+ return true;
196
+ }
197
+ if (declarative && call.tool.isDeclarative) {
198
+ return true;
199
+ }
200
+ return false;
201
+ });
202
+ }
203
+
204
+ if (filterState.text) {
205
+ const regex = Platform.StringUtilities.createPlainTextSearchRegex(filterState.text, 'i');
206
+ filtered = filtered.filter(call => {
207
+ return regex.test(call.tool.name) || regex.test(call.input) ||
208
+ (call.result?.output && regex.test(JSON.stringify(call.result.output))) ||
209
+ (call.result?.errorText && regex.test(call.result.errorText));
210
+ });
211
+ }
212
+
213
+ return filtered;
214
+ }
50
215
  export type View = (input: ViewInput, output: object, target: HTMLElement) => void;
216
+ function calculateToolStats(calls: WebMCP.WebMCPModel.Call[]):
217
+ {total: number, success: number, failed: number, canceled: number, inProgress: number} {
218
+ let total = 0, success = 0, failed = 0, canceled = 0, inProgress = 0;
219
+ for (const call of calls) {
220
+ total++;
221
+ if (call.result?.status === Protocol.WebMCP.InvocationStatus.Error) {
222
+ failed++;
223
+ } else if (call.result?.status === Protocol.WebMCP.InvocationStatus.Canceled) {
224
+ canceled++;
225
+ } else if (call.result?.status === Protocol.WebMCP.InvocationStatus.Success) {
226
+ success++;
227
+ } else if (call.result === undefined) {
228
+ inProgress++;
229
+ }
230
+ }
231
+ return {total, success, failed, canceled, inProgress};
232
+ }
233
+
234
+ function getIconGroupsFromStats(toolStats: ReturnType<typeof calculateToolStats>):
235
+ IconButton.IconButton.IconWithTextData[] {
236
+ const groups = [];
237
+ if (toolStats.success > 0) {
238
+ groups.push({
239
+ iconName: 'check-circle',
240
+ iconColor: 'var(--sys-color-green)',
241
+ iconWidth: '16px',
242
+ iconHeight: '16px',
243
+ text: String(toolStats.success),
244
+ });
245
+ }
246
+ if (toolStats.failed > 0) {
247
+ groups.push({
248
+ iconName: 'cross-circle-filled',
249
+ iconColor: 'var(--sys-color-error)',
250
+ iconWidth: '16px',
251
+ iconHeight: '16px',
252
+ text: String(toolStats.failed),
253
+ });
254
+ }
255
+ if (toolStats.canceled > 0) {
256
+ groups.push({
257
+ iconName: 'record-stop',
258
+ iconColor: 'var(--sys-color-on-surface-light)',
259
+ iconWidth: '16px',
260
+ iconHeight: '16px',
261
+ text: String(toolStats.canceled),
262
+ });
263
+ }
264
+ if (toolStats.inProgress > 0) {
265
+ groups.push({
266
+ iconName: 'dots-circle',
267
+ iconWidth: '16px',
268
+ iconHeight: '16px',
269
+ text: String(toolStats.inProgress),
270
+ });
271
+ }
272
+ return groups;
273
+ }
51
274
  export const DEFAULT_VIEW: View = (input, output, target) => {
52
275
  const tools = input.tools;
276
+ const stats = calculateToolStats(input.toolCalls);
277
+ const isFilterActive =
278
+ Boolean(input.filters.text) || Boolean(input.filters.toolTypes) || Boolean(input.filters.statusTypes);
279
+ const iconName = (call: WebMCP.WebMCPModel.Call): string => {
280
+ switch (call.result?.status) {
281
+ case Protocol.WebMCP.InvocationStatus.Error:
282
+ return 'cross-circle-filled';
283
+ case Protocol.WebMCP.InvocationStatus.Canceled:
284
+ return 'record-stop';
285
+ case undefined:
286
+ return 'dots-circle';
287
+ default:
288
+ return '';
289
+ }
290
+ };
291
+ const statusString = (call: WebMCP.WebMCPModel.Call): string => {
292
+ switch (call.result?.status) {
293
+ case Protocol.WebMCP.InvocationStatus.Error:
294
+ return i18nString(UIStrings.error);
295
+ case Protocol.WebMCP.InvocationStatus.Canceled:
296
+ return i18nString(UIStrings.canceled);
297
+ case Protocol.WebMCP.InvocationStatus.Success:
298
+ return i18nString(UIStrings.success);
299
+ default:
300
+ return i18nString(UIStrings.inProgress);
301
+ }
302
+ };
53
303
  // clang-format off
54
304
  render(html`
55
305
  <style>${webMCPViewStyles}</style>
306
+ <style>${UI.FilterBar.filterStyles}</style>
56
307
  <devtools-split-view class="webmcp-view" direction="row" sidebar-position="second" name="webmcp-split-view">
57
308
  <div slot="main" class="call-log">
309
+ <div class="webmcp-toolbar-container" role="toolbar" jslog=${VisualLogging.toolbar()}>
310
+ <devtools-toolbar class="webmcp-toolbar" role="presentation" wrappable>
311
+ <devtools-button title=${i18nString(UIStrings.clearLog)}
312
+ .iconName=${'clear'}
313
+ .variant=${Buttons.Button.Variant.TOOLBAR}
314
+ @click=${input.onClearLogClick}></devtools-button>
315
+ <div class="toolbar-divider"></div>
316
+ <devtools-toolbar-input type="filter"
317
+ placeholder=${i18nString(UIStrings.filter)}
318
+ .value=${input.filters.text}
319
+ @change=${(e: CustomEvent<string>) =>
320
+ input.onFilterChange({...input.filters, text: e.detail})}>
321
+ </devtools-toolbar-input>
322
+ <div class="toolbar-divider"></div>
323
+ ${input.filterButtons.toolTypes.button.element}
324
+ <div class="toolbar-divider"></div>
325
+ ${input.filterButtons.statusTypes.button.element}
326
+ <div class="toolbar-spacer"></div>
327
+ <devtools-button title=${i18nString(UIStrings.clearFilters)}
328
+ .iconName=${'filter-clear'}
329
+ .variant=${Buttons.Button.Variant.TOOLBAR}
330
+ @click=${() => input.onFilterChange({text: ''})}
331
+ ?hidden=${!isFilterActive}></devtools-button>
332
+ </devtools-toolbar>
333
+ </div>
334
+ ${input.toolCalls.length > 0 ? html`
335
+ <devtools-data-grid striped>
336
+ <table>
337
+ <tr>
338
+ <th id="name" weight="20">
339
+ ${i18nString(UIStrings.name)}
340
+ </th>
341
+ <th id="status" weight="20">${i18nString(UIStrings.status)}</th>
342
+ <th id="input" weight="30">${i18nString(UIStrings.input)}</th>
343
+ <th id="output" weight="30">${i18nString(UIStrings.output)}</th>
344
+ </tr>
345
+ ${Directives.repeat(input.toolCalls, call => call.invocationId + '-' + (call.result?.status ?? ''), call => html`
346
+ <tr class=${
347
+ call.result?.status === Protocol.WebMCP.InvocationStatus.Error ? 'status-error' :
348
+ call.result?.status === Protocol.WebMCP.InvocationStatus.Canceled ? 'status-cancelled' : ''
349
+ }>
350
+ <style>${webMCPViewStyles}</style>
351
+ <td>${call.tool.name}</td>
352
+ <td>
353
+ <div class="status-cell">
354
+ ${iconName(call) ? html`<devtools-icon class="small" name=${iconName(call)}></devtools-icon>`
355
+ : ''}
356
+ <span>${statusString(call)}</span>
357
+ </div>
358
+ </td>
359
+ <td>${call.input}</td>
360
+ <td>${call.result?.output ? JSON.stringify(call.result.output) : call.result?.errorText ?? ''}</td>
361
+ </tr>
362
+ `)}
363
+ </table>
364
+ </devtools-data-grid>
365
+ <div class="webmcp-toolbar-container" role="toolbar">
366
+ <devtools-toolbar class="webmcp-toolbar" role="presentation" wrappable>
367
+ <span class="toolbar-text">${i18nString(UIStrings.totalCalls, {PH1: stats.total})}</span>
368
+ <div class="toolbar-divider"></div>
369
+ <span class="toolbar-text status-error-text">${i18nString(UIStrings.failed, {PH1: stats.failed})}</span>
370
+ <div class="toolbar-divider"></div>
371
+ <span class="toolbar-text status-cancelled-text">${
372
+ i18nString(UIStrings.canceledCount, {PH1: stats.canceled})}</span>
373
+ <div class="toolbar-divider"></div>
374
+ <span class="toolbar-text">${i18nString(UIStrings.inProgressCount, {PH1: stats.inProgress})}</span>
375
+ </devtools-toolbar>
376
+ </div>
377
+ ` : html`
58
378
  ${UI.Widget.widget(UI.EmptyWidget.EmptyWidget, {header: i18nString(UIStrings.noCallsPlaceholderTitle),
59
- text: i18nString(UIStrings.noCallsPlaceholder)})}
379
+ text: i18nString(UIStrings.noCallsPlaceholder)})}
380
+ `}
60
381
  </div>
61
382
  <div slot="sidebar" class="tool-list">
62
383
  <div class="section-title">${i18nString(UIStrings.toolRegistry)}</div>
@@ -65,14 +386,20 @@ export const DEFAULT_VIEW: View = (input, output, target) => {
65
386
  text: i18nString(UIStrings.noToolsPlaceholder)})}
66
387
  ` : html`
67
388
  <devtools-list>
68
- ${tools.map(tool => html`
389
+ ${tools.map(tool => {
390
+ const toolStats = calculateToolStats(input.toolCalls.filter(c => c.tool === tool));
391
+ const groups = getIconGroupsFromStats(toolStats);
392
+ return html`
69
393
  <div class="tool-item">
70
394
  <div class="tool-name-container">
71
395
  <div class="tool-name source-code">${tool.name}</div>
396
+ ${groups.length > 0 ? html`<icon-button .data=${
397
+ {groups, compact: false} as IconButton.IconButton.IconButtonData}></icon-button>` : ''}
72
398
  </div>
73
399
  <div class="tool-description">${tool.description}</div>
74
400
  </div>
75
- `)}
401
+ `;
402
+ })}
76
403
  </devtools-list>
77
404
  `}
78
405
  </div>
@@ -84,35 +411,154 @@ export const DEFAULT_VIEW: View = (input, output, target) => {
84
411
  export class WebMCPView extends UI.Widget.VBox {
85
412
  readonly #view: View;
86
413
 
414
+ #filterState: FilterState = {
415
+ text: '',
416
+ };
417
+
418
+ #filterButtons: FilterMenuButtons;
419
+
420
+ static createFilterButtons(
421
+ onToolTypesClick: (contextMenu: UI.ContextMenu.ContextMenu) => void,
422
+ onStatusTypesClick: (contextMenu: UI.ContextMenu.ContextMenu) => void): FilterMenuButtons {
423
+ const createButton =
424
+ (label: string, onContextMenu: (contextMenu: UI.ContextMenu.ContextMenu) => void, jsLogContext: string):
425
+ FilterMenuButton => {
426
+ const button = new UI.Toolbar.ToolbarMenuButton(
427
+ onContextMenu,
428
+ /* isIconDropdown=*/ false, /* useSoftMenu=*/ true, jsLogContext,
429
+ /* iconName=*/ undefined,
430
+ /* keepOpen=*/ true);
431
+ button.setText(label);
432
+
433
+ /* eslint-disable-next-line @devtools/no-imperative-dom-api */
434
+ const adorner = new Adorners.Adorner.Adorner();
435
+ adorner.name = 'countWrapper';
436
+ const countElement = document.createElement('span');
437
+ adorner.append(countElement);
438
+ adorner.classList.add('active-filters-count');
439
+ adorner.classList.add('hidden');
440
+ button.setAdorner(adorner);
441
+
442
+ const setCount = (count: number): void => {
443
+ countElement.textContent = `${count}`;
444
+ count === 0 ? adorner.hide() : adorner.show();
445
+ };
446
+
447
+ return {button, setCount};
448
+ };
449
+
450
+ return {
451
+ toolTypes: createButton(i18nString(UIStrings.toolTypes), onToolTypesClick, 'webmcp.tool-types'),
452
+ statusTypes: createButton(i18nString(UIStrings.statusTypes), onStatusTypesClick, 'webmcp.status-types'),
453
+ };
454
+ }
87
455
  constructor(target?: HTMLElement, view: View = DEFAULT_VIEW) {
88
- super();
456
+ super(target);
89
457
  this.#view = view;
90
- SDK.TargetManager.TargetManager.instance().observeModels(SDK.WebMCPModel.WebMCPModel, {
91
- modelAdded: (model: SDK.WebMCPModel.WebMCPModel) => this.#webMCPModelAdded(model),
92
- modelRemoved: (model: SDK.WebMCPModel.WebMCPModel) => this.#webMCPModelRemoved(model),
458
+ this.#filterButtons = WebMCPView.createFilterButtons(
459
+ this.#showToolTypesContextMenu.bind(this),
460
+ this.#showStatusTypesContextMenu.bind(this),
461
+ );
462
+ SDK.TargetManager.TargetManager.instance().observeModels(WebMCP.WebMCPModel.WebMCPModel, {
463
+ modelAdded: (model: WebMCP.WebMCPModel.WebMCPModel) => this.#webMCPModelAdded(model),
464
+ modelRemoved: (model: WebMCP.WebMCPModel.WebMCPModel) => this.#webMCPModelRemoved(model),
93
465
  });
94
466
  this.requestUpdate();
95
467
  }
96
468
 
97
- #webMCPModelAdded(model: SDK.WebMCPModel.WebMCPModel): void {
98
- model.addEventListener(SDK.WebMCPModel.Events.TOOLS_ADDED, this.requestUpdate, this);
99
- model.addEventListener(SDK.WebMCPModel.Events.TOOLS_REMOVED, this.requestUpdate, this);
469
+ #showToolTypesContextMenu(contextMenu: UI.ContextMenu.ContextMenu): void {
470
+ const toggle = (key: 'imperative'|'declarative'): void => {
471
+ const current = this.#filterState.toolTypes ?? {};
472
+ const next = {...current, [key]: !current[key]};
473
+ let toolTypesToPass: FilterState['toolTypes'] = next;
474
+ if (!next.imperative && !next.declarative) {
475
+ toolTypesToPass = undefined;
476
+ }
477
+ this.#handleFilterChange({...this.#filterState, toolTypes: toolTypesToPass});
478
+ };
479
+
480
+ contextMenu.defaultSection().appendCheckboxItem(
481
+ i18nString(UIStrings.imperative), () => toggle('imperative'),
482
+ {checked: this.#filterState.toolTypes?.imperative ?? false, jslogContext: 'webmcp.imperative'});
483
+ contextMenu.defaultSection().appendCheckboxItem(
484
+ i18nString(UIStrings.declarative), () => toggle('declarative'),
485
+ {checked: this.#filterState.toolTypes?.declarative ?? false, jslogContext: 'webmcp.declarative'});
100
486
  }
101
487
 
102
- #webMCPModelRemoved(model: SDK.WebMCPModel.WebMCPModel): void {
103
- model.removeEventListener(SDK.WebMCPModel.Events.TOOLS_ADDED, this.requestUpdate, this);
104
- model.removeEventListener(SDK.WebMCPModel.Events.TOOLS_REMOVED, this.requestUpdate, this);
488
+ #showStatusTypesContextMenu(contextMenu: UI.ContextMenu.ContextMenu): void {
489
+ const toggle = (key: 'success'|'error'|'pending'): void => {
490
+ const current = this.#filterState.statusTypes ?? {};
491
+ const next = {...current, [key]: !current[key]};
492
+ let statusTypesToPass: FilterState['statusTypes'] = next;
493
+ if (!next.success && !next.error && !next.pending) {
494
+ statusTypesToPass = undefined;
495
+ }
496
+ this.#handleFilterChange({...this.#filterState, statusTypes: statusTypesToPass});
497
+ };
498
+
499
+ contextMenu.defaultSection().appendCheckboxItem(
500
+ i18nString(UIStrings.success), () => toggle('success'),
501
+ {checked: this.#filterState.statusTypes?.['success'] ?? false, jslogContext: 'webmcp.success'});
502
+ contextMenu.defaultSection().appendCheckboxItem(
503
+ i18nString(UIStrings.error), () => toggle('error'),
504
+ {checked: this.#filterState.statusTypes?.['error'] ?? false, jslogContext: 'webmcp.error'});
505
+ contextMenu.defaultSection().appendCheckboxItem(
506
+ i18nString(UIStrings.pending), () => toggle('pending'),
507
+ {checked: this.#filterState.statusTypes?.['pending'] ?? false, jslogContext: 'webmcp.pending'});
105
508
  }
509
+ #webMCPModelAdded(model: WebMCP.WebMCPModel.WebMCPModel): void {
510
+ model.addEventListener(WebMCP.WebMCPModel.Events.TOOLS_ADDED, this.requestUpdate, this);
511
+ model.addEventListener(WebMCP.WebMCPModel.Events.TOOLS_REMOVED, this.requestUpdate, this);
512
+ model.addEventListener(WebMCP.WebMCPModel.Events.TOOL_INVOKED, this.requestUpdate, this);
513
+ model.addEventListener(WebMCP.WebMCPModel.Events.TOOL_RESPONDED, this.requestUpdate, this);
514
+ }
515
+
516
+ #webMCPModelRemoved(model: WebMCP.WebMCPModel.WebMCPModel): void {
517
+ model.removeEventListener(WebMCP.WebMCPModel.Events.TOOLS_ADDED, this.requestUpdate, this);
518
+ model.removeEventListener(WebMCP.WebMCPModel.Events.TOOLS_REMOVED, this.requestUpdate, this);
519
+ model.removeEventListener(WebMCP.WebMCPModel.Events.TOOL_INVOKED, this.requestUpdate, this);
520
+ model.removeEventListener(WebMCP.WebMCPModel.Events.TOOL_RESPONDED, this.requestUpdate, this);
521
+ }
522
+
523
+ #handleClearLogClick = (): void => {
524
+ const models = SDK.TargetManager.TargetManager.instance().models(WebMCP.WebMCPModel.WebMCPModel);
525
+ for (const model of models) {
526
+ model.clearCalls();
527
+ }
528
+ this.requestUpdate();
529
+ };
530
+
531
+ #handleFilterChange = (filters: FilterState): void => {
532
+ this.#filterState = filters;
106
533
 
534
+ const toolTypesCount =
535
+ this.#filterState.toolTypes ? Object.values(this.#filterState.toolTypes).filter(Boolean).length : 0;
536
+ this.#filterButtons.toolTypes.setCount(toolTypesCount);
537
+
538
+ const statusTypesCount =
539
+ this.#filterState.statusTypes ? Object.values(this.#filterState.statusTypes).filter(Boolean).length : 0;
540
+ this.#filterButtons.statusTypes.setCount(statusTypesCount);
541
+
542
+ this.requestUpdate();
543
+ };
544
+
545
+ #getTools(): WebMCP.WebMCPModel.Tool[] {
546
+ const models = SDK.TargetManager.TargetManager.instance().models(WebMCP.WebMCPModel.WebMCPModel);
547
+ const tools = models.flatMap(model => model.tools.toArray());
548
+ return tools.sort((a, b) => a.name.localeCompare(b.name));
549
+ }
107
550
  override performUpdate(): void {
108
- const tools = SDK.TargetManager.TargetManager.instance()
109
- .models(SDK.WebMCPModel.WebMCPModel)
110
- .flatMap(m => m.tools.toArray())
111
- .sort((a, b) => a.name.localeCompare(b.name));
112
- this.#view(
113
- {
114
- tools,
115
- },
116
- {}, this.contentElement);
551
+ const models = SDK.TargetManager.TargetManager.instance().models(WebMCP.WebMCPModel.WebMCPModel);
552
+ const toolCalls = models.flatMap(model => model.toolCalls);
553
+ const filteredCalls = filterToolCalls(toolCalls, this.#filterState);
554
+ const input: ViewInput = {
555
+ tools: this.#getTools(),
556
+ toolCalls: filteredCalls,
557
+ filters: this.#filterState,
558
+ filterButtons: this.#filterButtons,
559
+ onClearLogClick: this.#handleClearLogClick,
560
+ onFilterChange: this.#handleFilterChange,
561
+ };
562
+ this.#view(input, {}, this.contentElement);
117
563
  }
118
564
  }