UNPKG

chrome-devtools-frontend

Version:
237 lines (213 loc) • 9.04 kB
// Copyright 2025 The Chromium Authors // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import '../../ui/components/spinners/spinners.js'; import '../../ui/components/tooltips/tooltips.js'; import * as Host from '../../core/host/host.js'; import * as i18n from '../../core/i18n/i18n.js'; import * as Root from '../../core/root/root.js'; import * as UI from '../../ui/legacy/legacy.js'; import {Directives, html, nothing, render} from '../../ui/lit/lit.js'; import * as VisualLogging from '../../ui/visual_logging/visual_logging.js'; import styles from './aiCodeCompletionDisclaimer.css.js'; const UIStringsNotTranslate = { /** * @description Disclaimer text for AI code completion */ relevantData: 'Relevant data', /** * @description Disclaimer text for AI code completion */ isSentToGoogle: 'is sent to Google', /** * @description Text for tooltip shown on hovering over "Relevant Data" in the disclaimer text for AI code completion. */ tooltipDisclaimerTextForAiCodeCompletion: 'To generate code suggestions, your console input and the history of your current console session are shared with Google. This data may be seen by human reviewers to improve this feature.', /** * @description Text for tooltip shown on hovering over "Relevant Data" in the disclaimer text for AI code completion. */ tooltipDisclaimerTextForAiCodeCompletionNoLogging: 'To generate code suggestions, your console input and the history of your current console session are shared with Google. This data will not be used to improve Google’s AI models.', /** * Text for tooltip shown on hovering over spinner. */ tooltipTextForSpinner: 'Shows when data is being sent to Google to generate code suggestions', /** * @description Text for tooltip button which redirects to AI settings */ manageInSettings: 'Manage in settings', /** *@description Text announced when request is sent to AIDA and the spinner is loading */ dataIsBeingSentToGoogle: 'Data is being sent to Google', } as const; const lockedString = i18n.i18n.lockedString; export interface ViewInput { disclaimerTooltipId?: string; spinnerTooltipId?: string; noLogging: boolean; aidaAvailability?: Host.AidaClient.AidaAccessPreconditions; onManageInSettingsTooltipClick: () => void; } export interface ViewOutput { hideTooltip?: () => void; setLoading?: (isLoading: boolean) => void; } export type View = (input: ViewInput, output: ViewOutput, target: HTMLElement) => void; export const DEFAULT_SUMMARY_TOOLBAR_VIEW: View = (input, output, target) => { if (input.aidaAvailability !== Host.AidaClient.AidaAccessPreconditions.AVAILABLE || !input.disclaimerTooltipId || !input.spinnerTooltipId) { render(nothing, target); return; } // clang-format off render( html` <style>${styles}</style> <div class="ai-code-completion-disclaimer"><devtools-spinner .active=${false} ${Directives.ref(el => { if (el instanceof HTMLElement) { output.setLoading = (isLoading: boolean) => { el.toggleAttribute('active', isLoading); }; } })} aria-details=${input.spinnerTooltipId} aria-describedby=${input.spinnerTooltipId}></devtools-spinner> <devtools-tooltip id=${input.spinnerTooltipId} variant=${'rich'} jslogContext=${'ai-code-completion-spinner-tooltip'}> <div class="disclaimer-tooltip-container"><div class="tooltip-text"> ${lockedString(UIStringsNotTranslate.tooltipTextForSpinner)} </div></div></devtools-tooltip> <span tabIndex="0" class="link" role="link" jslog=${VisualLogging.link('open-ai-settings').track({ click: true, })} aria-details=${input.disclaimerTooltipId} aria-describedby=${input.disclaimerTooltipId} @click=${() => { void UI.ViewManager.ViewManager.instance().showView('chrome-ai'); }} >${lockedString(UIStringsNotTranslate.relevantData)}</span>${lockedString(UIStringsNotTranslate.isSentToGoogle)} <devtools-tooltip id=${input.disclaimerTooltipId} variant=${'rich'} jslogContext=${'ai-code-completion-disclaimer'} ${Directives.ref(el => { if (el instanceof HTMLElement) { output.hideTooltip = () => { el.hidePopover(); }; } })}> <div class="disclaimer-tooltip-container"><div class="tooltip-text"> ${input.noLogging ? lockedString(UIStringsNotTranslate.tooltipDisclaimerTextForAiCodeCompletionNoLogging) : lockedString(UIStringsNotTranslate.tooltipDisclaimerTextForAiCodeCompletion)} </div> <span tabIndex="0" class="link" role="link" jslog=${VisualLogging.link('open-ai-settings').track({ click: true, })} @click=${input.onManageInSettingsTooltipClick} >${lockedString(UIStringsNotTranslate.manageInSettings)}</span></div></devtools-tooltip> </div> `, target); // clang-format on }; const MINIMUM_LOADING_STATE_TIMEOUT = 1000; export class AiCodeCompletionDisclaimer extends UI.Widget.Widget { readonly #view: View; #viewOutput: ViewOutput = {}; #spinnerTooltipId?: string; #disclaimerTooltipId?: string; #noLogging: boolean; // Whether the enterprise setting is `ALLOW_WITHOUT_LOGGING` or not. #loading = false; #loadingStartTime = 0; #spinnerLoadingTimeout: number|undefined; #aidaAvailability?: Host.AidaClient.AidaAccessPreconditions; #boundOnAidaAvailabilityChange: () => Promise<void>; constructor(element?: HTMLElement, view: View = DEFAULT_SUMMARY_TOOLBAR_VIEW) { super(element); this.markAsExternallyManaged(); this.#noLogging = Root.Runtime.hostConfig.aidaAvailability?.enterprisePolicyValue === Root.Runtime.GenAiEnterprisePolicyValue.ALLOW_WITHOUT_LOGGING; this.#boundOnAidaAvailabilityChange = this.#onAidaAvailabilityChange.bind(this); this.#view = view; } set disclaimerTooltipId(disclaimerTooltipId: string) { this.#disclaimerTooltipId = disclaimerTooltipId; this.requestUpdate(); } set spinnerTooltipId(spinnerTooltipId: string) { this.#spinnerTooltipId = spinnerTooltipId; this.requestUpdate(); } set loading(loading: boolean) { if (!loading && !this.#loading) { return; } if (loading) { if (!this.#loading) { this.#viewOutput.setLoading?.(true); UI.ARIAUtils.LiveAnnouncer.status(lockedString(UIStringsNotTranslate.dataIsBeingSentToGoogle)); } if (this.#spinnerLoadingTimeout) { clearTimeout(this.#spinnerLoadingTimeout); this.#spinnerLoadingTimeout = undefined; } this.#loadingStartTime = performance.now(); this.#loading = true; } else { this.#loading = false; const duration = performance.now() - this.#loadingStartTime; const remainingTime = Math.max(MINIMUM_LOADING_STATE_TIMEOUT - duration, 0); this.#spinnerLoadingTimeout = window.setTimeout(() => { this.#viewOutput.setLoading?.(false); this.#spinnerLoadingTimeout = undefined; }, remainingTime); } } async #onAidaAvailabilityChange(): Promise<void> { const currentAidaAvailability = await Host.AidaClient.AidaClient.checkAccessPreconditions(); if (currentAidaAvailability !== this.#aidaAvailability) { this.#aidaAvailability = currentAidaAvailability; this.requestUpdate(); } } #onManageInSettingsTooltipClick(): void { this.#viewOutput.hideTooltip?.(); void UI.ViewManager.ViewManager.instance().showView('chrome-ai'); } override performUpdate(): void { this.#view( { disclaimerTooltipId: this.#disclaimerTooltipId, spinnerTooltipId: this.#spinnerTooltipId, noLogging: this.#noLogging, aidaAvailability: this.#aidaAvailability, onManageInSettingsTooltipClick: this.#onManageInSettingsTooltipClick.bind(this), }, this.#viewOutput, this.contentElement); } override wasShown(): void { super.wasShown(); Host.AidaClient.HostConfigTracker.instance().addEventListener( Host.AidaClient.Events.AIDA_AVAILABILITY_CHANGED, this.#boundOnAidaAvailabilityChange); void this.#onAidaAvailabilityChange(); } override willHide(): void { super.willHide(); Host.AidaClient.HostConfigTracker.instance().removeEventListener( Host.AidaClient.Events.AIDA_AVAILABILITY_CHANGED, this.#boundOnAidaAvailabilityChange); } }