// Copyright 2024 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/markdown_view/markdown_view.js';
import '../../../ui/kit/kit.js';
import * as Common from '../../../core/common/common.js';
import * as Host from '../../../core/host/host.js';
import * as i18n from '../../../core/i18n/i18n.js';
import * as Platform from '../../../core/platform/platform.js';
import * as Root from '../../../core/root/root.js';
import * as SDK from '../../../core/sdk/sdk.js';
import * as AiAssistanceModel from '../../../models/ai_assistance/ai_assistance.js';
import * as ComputedStyle from '../../../models/computed_style/computed_style.js';
import * as Formatter from '../../../models/formatter/formatter.js';
import * as TextUtils from '../../../models/text_utils/text_utils.js';
import * as Trace from '../../../models/trace/trace.js';
import * as Workspace from '../../../models/workspace/workspace.js';
import * as PanelsCommon from '../../../panels/common/common.js';
import * as TraceBounds from '../../../services/trace_bounds/trace_bounds.js';
import * as Marked from '../../../third_party/marked/marked.js';
import * as Buttons from '../../../ui/components/buttons/buttons.js';
import * as Input from '../../../ui/components/input/input.js';
import * as Snackbars from '../../../ui/components/snackbars/snackbars.js';
import * as UIHelpers from '../../../ui/helpers/helpers.js';
import * as UI from '../../../ui/legacy/legacy.js';
import * as Lit from '../../../ui/lit/lit.js';
import * as VisualLogging from '../../../ui/visual_logging/visual_logging.js';
import * as Elements from '../../elements/elements.js';
import * as Lighthouse from '../../lighthouse/lighthouse.js';
import * as NetworkForward from '../../network/forward/forward.js';
import * as Network from '../../network/network.js';
import * as TimelineComponents from '../../timeline/components/components.js';
import * as TimelineInsights from '../../timeline/components/insights/insights.js';
import * as Timeline from '../../timeline/timeline.js';
import * as TimelineUtils from '../../timeline/utils/utils.js';
import { PanelUtils } from '../../utils/utils.js';
import chatMessageStyles from './chatMessage.css.js';
import { getButtonLabel } from './WalkthroughUtils.js';
import { walkthroughCloseTitle, walkthroughTitle, WalkthroughView } from './WalkthroughView.js';
const { html, Directives: { ref, ifDefined } } = Lit;
const lockedString = i18n.i18n.lockedString;
const { widget } = UI.Widget;
const REPORT_URL = 'https://crbug.com/508304827';
const SCROLL_ROUNDING_OFFSET = 1;
const MAX_NUM_LINES_IN_CODEBLOCK = 11;
/*
* Strings that don't need to be translated at this time.
*/
const UIStringsNotTranslate = {
    /**
     * @description The title of the button that allows submitting positive
     * feedback about the response for AI assistance.
     */
    thumbsUp: 'Good response',
    /**
     * @description The title of the button that allows submitting negative
     * feedback about the response for AI assistance.
     */
    thumbsDown: 'Bad response',
    /**
     * @description The placeholder text for the feedback input.
     */
    provideFeedbackPlaceholder: 'Provide additional feedback',
    /**
     * @description The disclaimer text that tells the user what will be shared
     * and what will be stored.
     */
    disclaimer: 'Submitted feedback will also include your conversation',
    /**
     * @description The button text for the action of submitting feedback.
     */
    submit: 'Submit',
    /**
     * @description The header of the feedback form asking.
     */
    whyThisRating: 'Why did you choose this rating? (optional)',
    /**
     * @description The button text for the action that hides the feedback form.
     */
    close: 'Close',
    /**
     * @description The title of the button that opens a page to report a legal
     * issue with the AI assistance message.
     */
    report: 'Report legal issue',
    /**
     * @description The title of the button for scrolling to see next suggestions
     */
    scrollToNext: 'Scroll to next suggestions',
    /**
     * @description The title of the button for scrolling to see previous suggestions
     */
    scrollToPrevious: 'Scroll to previous suggestions',
    /**
     * @description The title of the button that copies the AI-generated response to the clipboard.
     */
    copyResponse: 'Copy response',
    /**
     * @description The error message when the request to the LLM failed for some reason.
     */
    systemError: '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.',
    /**
     * @description The error message when the LLM gets stuck in a loop (max steps reached).
     */
    maxStepsError: 'Seems like I am stuck with the investigation. It would be better if you start over.',
    /**
     * @description The error message when the LLM selects context from a different origin.
     */
    crossOriginError: 'I have selected the new context but you will have to start a new chat.',
    /**
     * @description Displayed when the user stop the response
     */
    stoppedResponse: 'You stopped this response',
    /**
     * @description Button text that confirm code execution that may affect the page.
     */
    confirmActionRequestApproval: 'Continue',
    /**
     * @description Button text that cancels code execution that may affect the page.
     */
    declineActionRequestApproval: 'Cancel',
    /**
     * @description The generic name of the AI agent (do not translate)
     */
    ai: 'AI',
    /**
     * @description Gemini (do not translate)
     */
    gemini: 'Gemini',
    /**
     * @description The fallback text when a step has no title yet
     */
    investigating: 'Investigating',
    /**
     * @description Prefix to the title of each thinking step of a user action is required to continue
     */
    paused: 'Paused',
    /**
     * @description Heading text for the code block that shows the executed code.
     */
    codeExecuted: 'Code executed',
    /**
     * @description Heading text for the code block that shows the code to be executed after side effect confirmation.
     */
    codeToExecute: 'Code to execute',
    /**
     * @description Heading text for the code block that shows the returned data.
     */
    dataReturned: 'Data returned',
    /**
     * @description Aria label for the check mark icon to be read by screen reader
     */
    completed: 'Completed',
    /**
     * @description Aria label for the spinner to be read by screen reader when a step is in progress.
     */
    inProgress: 'In progress',
    /**
     * @description Aria label for the aborted icon to be read by screen reader
     */
    aborted: 'Aborted',
    /**
     * @description Alt text for the image input (displayed in the chat messages) that has been sent to the model.
     */
    imageInputSentToTheModel: 'Image input sent to the model',
    /**
     * @description Title for the link which wraps the image input rendered in chat messages.
     */
    openImageInNewTab: 'Open image in a new tab',
    /**
     * @description Alt text for image when it is not available.
     */
    imageUnavailable: 'Image unavailable',
    /**
     * @description Title for the button that takes the user into other DevTools panels to reveal items the AI references.
     */
    reveal: 'Reveal',
    /**
     * @description Title used for revealing the performance trace.
     */
    revealTrace: 'Reveal trace',
    /**
     * @description Accessible label for the reveal button in the computed styles widget.
     */
    revealComputedStyles: 'Reveal computed styles',
    /**
     * @description Accessible label for the reveal button in the core web vitals widget.
     */
    revealCoreWebVitals: 'Reveal Core Web Vitals',
    /**
     * @description Accessible label for the reveal button in the style properties widget.
     */
    revealStyleProperties: 'Reveal style properties',
    /**
     * @description Accessible label for the reveal button in the LCP breakdown widget.
     */
    revealLcpBreakdown: 'Reveal LCP breakdown',
    /**
     * @description Accessible label for the reveal button in the LCP discovery widget.
     */
    revealLcpDiscovery: 'Reveal LCP discovery',
    /**
     * @description Accessible label for the reveal button in the layout shift culprits widget.
     */
    revealClsCulprits: 'Reveal layout shift culprits',
    /**
     * @description Accessible label for the reveal button in the render-blocking requests widget.
     */
    revealRenderBlockingBreakdown: 'Reveal render-blocking requests',
    /**
     * @description Accessible label for the reveal button in the performance summary widget.
     */
    revealPerformanceSummary: 'Reveal performance summary',
    /**
     * @description Accessible label for the reveal button in the network track widget.
     */
    revealNetworkActivity: 'Reveal network activity',
    /**
     * @description Accessible label for the reveal button in the bottom up thread activity widget.
     */
    revealBottomUpTree: 'Reveal bottom-up thread activity',
    /**
     * @description Accessible label for the reveal button in the network dependency tree widget.
     */
    revealNetworkDependencyTree: 'Reveal network dependency tree',
    /**
     * @description Accessible label for the reveal button in the 3rd parties widget.
     */
    revealThirdParties: 'Reveal 3rd parties',
    /**
     * @description Title for the core web vitals widget.
     */
    coreVitals: 'Core Web Vitals',
    /**
     * @description Title for the Lighthouse report widget.
     */
    lighthouseReport: 'Lighthouse report',
    /**
     * @description Accessible label for the reveal button in the Lighthouse report widget.
     */
    revealLighthouse: 'Reveal Lighthouse report',
    /**
     * @description Title for the Timeline event summary widget.
     */
    timelineEventSummary: 'Event summary',
    /**
     * @description Accessible label for the reveal button in the Timeline event summary widget.
     */
    revealTimelineEventSummary: 'Reveal event',
    /**
     * @description Title for the LCP breakdown widget.
     */
    lcpBreakdown: 'LCP breakdown',
    /**
     * @description Title for the LCP discovery widget.
     */
    lcpDiscovery: 'LCP discovery',
    /**
     * @description Title for the layout shift culprits widget.
     */
    clsCulprits: 'Layout shift culprits',
    /**
     * @description Title for the render-blocking requests widget.
     */
    renderBlockingBreakdown: 'Render-blocking requests',
    /**
     * @description Title for the network dependency tree widget.
     */
    networkDependencyTree: 'Network dependency tree',
    /**
     * @description Title for the 3rd parties widget.
     */
    thirdParties: '3rd parties',
    /**
     * @description Title for the performance summary widget.
     */
    performanceSummary: 'Performance summary',
    /**
     * @description Title for the network activity summary widget.
     */
    networkActivitySummary: 'Network activity',
    /**
     * @description The title of the button that allows exporting the conversation for agents.
     */
    exportForAgents: 'Copy to coding agent',
    /**
     * @description Title for the bottom up thread activity widget.
     */
    bottomUpTree: 'Bottom-up thread activity',
    /**
     * @description Accessible label for the reveal button in the forced reflow widget.
     */
    revealForcedReflow: 'Reveal forced reflow',
    /**
     * @description Title for the forced reflow widget.
     */
    forcedReflow: 'Forced reflow',
    /**
     * @description Accessible label for the reveal button in the cache widget.
     */
    revealCache: 'Reveal efficient cache lifetimes',
    /**
     * @description Title for the cache widget.
     */
    cache: 'Efficient cache lifetimes',
    /**
     * @description Accessible label for the reveal button in the INP breakdown widget.
     */
    revealInpBreakdown: 'Reveal INP breakdown',
    /**
     * @description Title for the INP breakdown widget.
     */
    inpBreakdown: 'INP breakdown',
    /**
     * @description Accessible label for the reveal button in the document latency widget.
     */
    revealDocumentLatency: 'Reveal document latency',
    /**
     * @description Title for the document latency widget.
     */
    documentLatency: 'Document latency',
    /**
     * @description Accessible label for the reveal button in the DOM size widget.
     */
    revealDomSize: 'Reveal DOM size',
    /**
     * @description Title for the DOM size widget.
     */
    domSize: 'DOM size',
    /**
     * @description Accessible label for the reveal button in the duplicated JavaScript widget.
     */
    revealDuplicateJavaScript: 'Reveal duplicated JavaScript',
    /**
     * @description Title for the duplicated JavaScript widget.
     */
    duplicateJavaScript: 'Duplicated JavaScript',
    /**
     * @description Accessible label for the reveal button in the image delivery widget.
     */
    revealImageDelivery: 'Reveal image delivery',
    /**
     * @description Title for the image delivery widget.
     */
    imageDelivery: 'Image delivery',
    /**
     * @description Accessible label for the reveal button in the font display widget.
     */
    revealFontDisplay: 'Reveal font display',
    /**
     * @description Title for the font display widget.
     */
    fontDisplay: 'Font display',
    /**
     * @description Accessible label for the reveal button in the slow CSS selectors widget.
     */
    revealSlowCssSelector: 'Reveal slow CSS selectors',
    /**
     * @description Title for the slow CSS selectors widget.
     */
    slowCssSelector: 'Slow CSS selectors',
    /**
     * @description Accessible label for the reveal button in the legacy JavaScript widget.
     */
    revealLegacyJavaScript: 'Reveal legacy JavaScript',
    /**
     * @description Title for the legacy JavaScript widget.
     */
    legacyJavaScript: 'Legacy JavaScript',
    /**
     * @description Accessible label for the reveal button in the viewport optimization widget.
     */
    revealViewport: 'Reveal viewport optimization',
    /**
     * @description Title for the viewport optimization widget.
     */
    viewport: 'Viewport optimization',
    /**
     * @description Accessible label for the reveal button in the network request general headers widget.
     */
    revealNetworkRequest: 'Reveal network request',
    /**
     * @description Title for the network request general headers widget.
     */
    networkRequest: 'Network request',
    /**
     * @description Accessible label for the reveal button in the modern HTTP usage widget.
     */
    revealModernHttp: 'Reveal modern HTTP usage',
    /**
     * @description Title for the modern HTTP usage widget.
     */
    modernHttp: 'Modern HTTP usage',
    /**
     * @description Accessible label for the reveal button in the character set declaration widget.
     */
    revealCharacterSet: 'Reveal character set declaration',
    /**
     * @description Title for the character set declaration widget.
     */
    characterSet: 'Character set declaration',
    /**
     * @description Title for the network requests list widget.
     */
    networkRequests: 'Network requests',
    /**
     * @description Accessible label for the reveal button in the network requests list widget.
     */
    revealFirstNetworkRequest: 'Reveal first network request in Network panel',
    /**
     * @description Title for the source files list widget.
     */
    inspectedFileNames: 'Inspected file names',
};
export const DEFAULT_VIEW = (input, output, target) => {
    const hasAiV2 = Boolean(Root.Runtime.hostConfig.devToolsAiAssistanceV2?.enabled);
    const message = input.message;
    if (message.entity === "user" /* ChatMessageEntity.USER */) {
        const imageInput = message.imageInput && 'inlineData' in message.imageInput ?
            renderImageChatMessage(message.imageInput.inlineData) :
            Lit.nothing;
        const messageClasses = Lit.Directives.classMap({
            'chat-message': true,
            query: true,
            'is-last-message': input.isLastMessage,
            'is-first-message': input.isFirstMessage,
            'ai-v2': hasAiV2,
        });
        const userQueryWrapperClasses = Lit.Directives.classMap({
            // Don't need to style at all unless we are on the V2 flag.
            // Once we ship this can be removed entirely.
            'user-query-wrapper': hasAiV2
        });
        // clang-format off
        Lit.render(html `
      <style>${Input.textInputStyles}</style>
      <style>${chatMessageStyles}</style>
      <div class=${userQueryWrapperClasses}>
        <section class=${messageClasses} jslog=${VisualLogging.section('question')}>
          ${imageInput}
          <div class="message-content">${renderTextAsMarkdown(message.text, input.markdownRenderer)}</div>
        </section>
      </div>
    `, target);
        // clang-format on
        return;
    }
    const steps = message.parts.filter(part => part.type === 'step').map(part => part.step);
    const icon = AiAssistanceModel.AiUtils.getIconName();
    const messageClasses = Lit.Directives.classMap({
        'chat-message': true,
        answer: true,
        'is-last-message': input.isLastMessage,
        'is-first-message': input.isFirstMessage,
        'ai-v2': hasAiV2,
    });
    // clang-format off
    Lit.render(html `
    <style>${Input.textInputStyles}</style>
    <style>${chatMessageStyles}</style>
    <section class=${messageClasses} jslog=${VisualLogging.section('answer')}>
      ${hasAiV2 ? Lit.nothing : html `
        <div class="message-info">
          <devtools-icon name=${icon}></devtools-icon>
          <div class="message-name">
            <h2>${AiAssistanceModel.AiUtils.isGeminiBranding() ? lockedString(UIStringsNotTranslate.gemini) : lockedString(UIStringsNotTranslate.ai)}</h2>
          </div>
        </div>`}
      ${hasAiV2 ? renderWalkthroughUI(input, steps) : Lit.nothing}
      <div class="answer-body-wrapper">
        ${Lit.Directives.repeat(message.parts, (_, index) => index, (part, index) => {
        const isLastPart = index === message.parts.length - 1;
        if (part.type === 'answer') {
            return html `<p>${renderTextAsMarkdown(part.text, input.markdownRenderer, { animate: !input.isReadOnly && input.isLoading && isLastPart && input.isLastMessage })}</p>`;
        }
        if (part.type === 'widget') {
            return html `${Lit.Directives.until(renderWidgets(part.widgets, { wrapperClass: 'main-widgets-wrapper' }))}`;
        }
        if (!hasAiV2 && part.type === 'step') {
            return renderStep({
                step: part.step,
                isLoading: input.isLoading,
                markdownRenderer: input.markdownRenderer,
                isLast: isLastPart,
            });
        }
        return Lit.nothing;
    })}
        ${renderError(message)}
        ${input.shouldShowCSSChangeSummary && hasAiV2 && input.changeSummary ? html `
          <devtools-code-block
            .code=${input.changeSummary}
            .codeLang=${'css'}
            .displayLimit=${MAX_NUM_LINES_IN_CODEBLOCK}
            .displayNotice=${true}
            class="ai-css-change"
          ></devtools-code-block>
        ` : Lit.nothing}
        ${input.showActions ? renderActions(input, output) : Lit.nothing}
      </div>
      ${hasAiV2 ? renderSideEffectStepsUI(input, steps) : Lit.nothing}
    </section>
  `, target);
    // clang-format on
};
function renderTextAsMarkdown(text, markdownRenderer, { animate, ref: refFn } = {}) {
    let tokens = [];
    try {
        tokens = Marked.Marked.lexer(text);
        for (const token of tokens) {
            // Try to render all the tokens to make sure that
            // they all have a template defined for them. If there
            // isn't any template defined for a token, we'll fallback
            // to rendering the text as plain text instead of markdown.
            markdownRenderer.renderToken(token);
        }
    }
    catch {
        // The tokens were not parsed correctly or
        // one of the tokens are not supported, so we
        // continue to render this as text.
        return html `${text}`;
    }
    // clang-format off
    return html `<devtools-markdown-view
    .data=${{ tokens, renderer: markdownRenderer, animationEnabled: animate }}
    ${refFn ? ref(refFn) : Lit.nothing}>
  </devtools-markdown-view>`;
    // clang-format on
}
export function titleForStep(step) {
    return step.title ?? `${lockedString(UIStringsNotTranslate.investigating)}…`;
}
function renderTitle(step) {
    const paused = step.requestApproval ?
        html `<span class="paused">${lockedString(UIStringsNotTranslate.paused)}: </span>` :
        Lit.nothing;
    return html `<h3 class="title" aria-label=${titleForStep(step)}>${paused}${titleForStep(step)}</h3>`;
}
function renderStepCode(step) {
    if (!step.code && !step.output) {
        return Lit.nothing;
    }
    // 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)
    // thus we show "Code to execute" text rather than "Code executed" text on the heading of the code block.
    const codeHeadingText = (step.output && !step.canceled) ? lockedString(UIStringsNotTranslate.codeExecuted) :
        lockedString(UIStringsNotTranslate.codeToExecute);
    // If there is output, we don't show notice on this code block and instead show
    // it in the data returned code block.
    // clang-format off
    const code = step.code ? html `<div class="action-result">
      <devtools-code-block
        .code=${step.code.trim()}
        .codeLang=${'js'}
        .displayNotice=${!Boolean(step.output)}
        .header=${codeHeadingText}
        .showCopyButton=${true}
      ></devtools-code-block>
  </div>` :
        Lit.nothing;
    const output = step.output ? html `<div class="js-code-output">
    <devtools-code-block
      .code=${step.output}
      .codeLang=${'js'}
      .displayNotice=${true}
      .header=${lockedString(UIStringsNotTranslate.dataReturned)}
      .showCopyButton=${false}
    ></devtools-code-block>
  </div>` :
        Lit.nothing;
    return html `<div class="step-code">${code}${output}</div>`;
    // clang-format on
}
function renderStepDetails({ step, markdownRenderer, isLast, }) {
    const sideEffects = isLast && step.requestApproval ? renderSideEffectConfirmationUi(step) : Lit.nothing;
    const thought = step.thought ? html `<p>${renderTextAsMarkdown(step.thought, markdownRenderer)}</p>` : Lit.nothing;
    // clang-format off
    const contextDetails = step.contextDetails ?
        html `${Lit.Directives.repeat(step.contextDetails, contextDetail => {
            return html `<div class="context-details">
      <devtools-code-block
        .code=${contextDetail.text}
        .codeLang=${contextDetail.codeLang || ''}
        .displayNotice=${false}
        .header=${contextDetail.title}
        .showCopyButton=${true}
      ></devtools-code-block>
    </div>`;
        })}` : Lit.nothing;
    return html `<div class="step-details">
    ${thought}
    ${renderStepCode(step)}
    ${sideEffects}
    ${contextDetails}
  </div>`;
    // clang-format on
}
function renderWalkthroughSidebarButton(input, steps) {
    const { message, walkthrough } = input;
    const lastStep = steps.at(-1);
    if (walkthrough.isInlined || !lastStep) {
        return Lit.nothing;
    }
    const hasOneStepWithWidget = steps.some(step => step.widgets?.length);
    const isExpanded = walkthrough.isExpanded && input.message.id === input.walkthrough.activeSidebarMessage?.id;
    const title = isExpanded ? walkthroughCloseTitle({ hasWidgets: hasOneStepWithWidget }) : walkthroughTitle({
        isLoading: input.isLoading,
        hasWidgets: hasOneStepWithWidget,
        lastStep,
    });
    // The button should be tonal when there are widgets, but we only
    // want to change it visually at the end once everything has stopped
    // loading.
    const variant = hasOneStepWithWidget && !input.isLoading ? "tonal" /* Buttons.Button.Variant.TONAL */ : "text" /* Buttons.Button.Variant.TEXT */;
    const icon = AiAssistanceModel.AiUtils.getIconName();
    const toggleContainerClasses = Lit.Directives.classMap({
        'walkthrough-toggle-container': true,
        // We only apply the widget styling when loading is complete
        'has-widgets': hasOneStepWithWidget && !input.isLoading,
    });
    const accessibleLabel = getButtonLabel({
        isExpanded,
        isLoading: input.isLoading,
        hasWidgets: hasOneStepWithWidget,
        prompt: input.prompt,
        stepTitle: titleForStep(lastStep),
    });
    // clang-format off
    return html `
    <div class=${toggleContainerClasses}>
      ${input.isLoading ?
        html `<devtools-spinner></devtools-spinner>` :
        html `<devtools-icon name=${icon}></devtools-icon>`}
      <devtools-button
        .variant=${variant}
        .size=${"SMALL" /* Buttons.Button.Size.SMALL */}
        .title=${lastStep.isLoading ? titleForStep(lastStep) : title}
        .accessibleLabel=${accessibleLabel}
        .jslogContext=${walkthrough.isExpanded ? 'ai-hide-walkthrough-sidebar' : 'ai-show-walkthrough-sidebar'}
        data-show-walkthrough
        @click=${() => {
        if (walkthrough.activeSidebarMessage?.id === input.message.id && walkthrough.isExpanded) {
            walkthrough.onToggle(false, message);
        }
        else {
            // Can't just toggle the visibility here; we need to ensure we
            // update the state with this message as the user could have had
            // the walkthrough open with an alternative message.
            walkthrough.onOpen(message);
        }
    }}>${title}<devtools-icon class="chevron" .name=${isExpanded ? 'cross' : 'chevron-right'}></devtools-icon>
      </devtools-button>
    </div>
  `;
    // clang-format on
}
/**
 * Responsible for rendering the AI Walkthrough UI. This can take different
 * shapes and involve different parts depending on if the walkthrough is
 * inlined, expanded, or if we have side-effect steps. In cases where the
 * walkthrough is closed, side-effect steps are rendered inline in the chat.
 */
function renderWalkthroughUI(input, steps) {
    const lastStep = steps.at(-1);
    if (!lastStep) {
        // No steps = no walkthrough UI in the chat view.
        return Lit.nothing;
    }
    // If the walkthrough is in the sidebar, we render a button into the
    // ChatView to open it.
    const openWalkThroughSidebarButton = !input.walkthrough.isInlined ? renderWalkthroughSidebarButton(input, steps) : Lit.nothing;
    // A message's walkthrough is considered expanded if the walkthrough is
    // open and it is specifically targeting this message. This is necessary
    // because the walkthrough state is shared across all messages in the chat.
    const isExpanded = input.walkthrough.isInlined ?
        input.walkthrough.inlineExpandedMessages.some(m => m.id === input.message.id) :
        (input.walkthrough.isExpanded && input.walkthrough.activeSidebarMessage?.id === input.message.id);
    // clang-format off
    const walkthroughInline = input.walkthrough.isInlined ? html `
    <div class="walkthrough-container">
      ${widget(WalkthroughView, {
        message: input.message,
        isLoading: input.isLoading && input.isLastMessage,
        markdownRenderer: input.markdownRenderer,
        isInlined: true,
        isExpanded,
        prompt: input.prompt,
        onToggle: input.walkthrough.onToggle,
        onOpen: input.walkthrough.onOpen,
    })}
    </div>
  ` : Lit.nothing;
    return html `
    ${openWalkThroughSidebarButton}
    ${walkthroughInline}
  `;
    // clang-format on
}
function renderSideEffectStepsUI(input, steps) {
    const sideEffectSteps = steps.filter(s => s.requestApproval);
    if (sideEffectSteps.length === 0) {
        return Lit.nothing;
    }
    // clang-format off
    return html `
    ${sideEffectSteps.map(step => html `
      <div class="side-effect-container">
        ${renderStep({
        step,
        isLoading: input.isLoading,
        markdownRenderer: input.markdownRenderer,
        isLast: true
    })}
      </div> `)}
  `;
    // clang-format on
}
function renderStepBadge({ step, isLoading, isLast }) {
    if (isLoading && isLast && !step.requestApproval) {
        return html `<devtools-spinner aria-label=${lockedString(UIStringsNotTranslate.inProgress)}></devtools-spinner>`;
    }
    let iconName = 'checkmark';
    let ariaLabel = lockedString(UIStringsNotTranslate.completed);
    let role = 'button';
    if (isLast && step.requestApproval) {
        role = undefined;
        ariaLabel = lockedString(UIStringsNotTranslate.paused);
        iconName = 'pause-circle';
    }
    else if (step.canceled) {
        ariaLabel = lockedString(UIStringsNotTranslate.aborted);
        iconName = 'cross';
    }
    return html `<devtools-icon
      class="indicator"
      role=${ifDefined(role)}
      aria-label=${ifDefined(ariaLabel)}
      .name=${iconName}
    ></devtools-icon>`;
}
export function renderStep({ step, isLoading, markdownRenderer, isLast }) {
    const stepClasses = Lit.Directives.classMap({
        step: true,
        empty: !step.thought && !step.code && !step.contextDetails && !step.requestApproval,
        paused: Boolean(step.requestApproval),
        canceled: Boolean(step.canceled),
    });
    // clang-format off
    return html `
    <details class=${stepClasses}
      jslog=${VisualLogging.expand('step').track({ click: true })}
      .open=${Boolean(step.requestApproval)}>
      <summary>
        <div class="summary">
          ${renderStepBadge({ step, isLoading, isLast })}
          ${renderTitle(step)}
          <devtools-icon
            class="arrow"
            name="chevron-down"
          ></devtools-icon>
        </div>
      </summary>
      ${renderStepDetails({ step, markdownRenderer, isLast })}
    </details>
    ${Lit.Directives.until(renderWidgets(step.widgets, { wrapperClass: 'step-widgets-wrapper' }))}
    `;
    // clang-format on
}
const nodeCache = new Map();
async function resolveNode(backendNodeId) {
    const cachedNode = nodeCache.get(backendNodeId);
    if (cachedNode) {
        return cachedNode;
    }
    const target = SDK.TargetManager.TargetManager.instance().primaryPageTarget();
    if (!target) {
        return null;
    }
    const node = new SDK.DOMModel.DeferredDOMNode(target, backendNodeId);
    const resolved = await node.resolvePromise();
    if (resolved) {
        nodeCache.set(backendNodeId, resolved);
    }
    return resolved;
}
async function makeComputedStyleWidget(widgetData) {
    const domNodeForId = await resolveNode(widgetData.data.backendNodeId);
    if (!domNodeForId) {
        return null;
    }
    const styles = new ComputedStyle.ComputedStyleModel.ComputedStyle(domNodeForId, widgetData.data.computedStyles);
    let filterText = null;
    try {
        filterText = new RegExp(widgetData.data.properties.join('|'), 'i');
    }
    catch {
        // If the AI provides an invalid regex (e.g. "*"), we don't want to crash.
        // We can just skip the widget in this case.
        return null;
    }
    // clang-format off
    const renderedWidget = html `<devtools-widget
      class="computed-styles-widget" ${widget(Elements.ComputedStyleWidget.ComputedStyleWidget, {
        nodeStyle: styles,
        matchedStyles: widgetData.data.matchedCascade,
        // This disables showing the nested traces and detailed information in the widget.
        propertyTraces: null,
        allowUserControl: false,
        filterText,
        enableNarrowViewResizing: false,
    })}></devtools-widget>`;
    // clang-format on
    return {
        renderedWidget,
        revealable: new Elements.ElementsPanel.NodeComputedStyles(domNodeForId),
        accessibleRevealLabel: lockedString(UIStringsNotTranslate.revealComputedStyles),
        // clang-format off
        title: html `
      <span class="computed-style-title-wrapper">
        <span class="computed-style-title-prefix">Computed styles</span>
        <span class="style-class-wrapper">
          (<devtools-widget
            ${widget(PanelsCommon.DOMLinkifier.DOMNodeLink, {
            node: domNodeForId,
        })}
          ></devtools-widget>)
        </span>
      </span>`,
        // clang-format on
        jslogContext: 'computed-styles',
    };
}
async function makeCoreWebVitalsWidget(widgetData) {
    // clang-format off
    const renderedWidget = html `<devtools-widget class="core-vitals-widget" ${widget(TimelineComponents.CWVMetrics.CWVMetrics, { data: widgetData.data, skipBottomBorder: true })}>
  </devtools-widget>`;
    // clang-format on
    return {
        renderedWidget,
        revealable: new TimelineUtils.Helpers.RevealableCoreVitals(widgetData.data.insightSetKey),
        accessibleRevealLabel: lockedString(UIStringsNotTranslate.revealCoreWebVitals),
        title: lockedString(UIStringsNotTranslate.coreVitals),
        jslogContext: 'core-web-vitals',
    };
}
async function makeStylePropertiesWidget(widgetData) {
    const domNodeForId = await resolveNode(widgetData.data.backendNodeId);
    if (!domNodeForId) {
        return null;
    }
    let filter = null;
    try {
        filter = widgetData.data.selector ? new RegExp(widgetData.data.selector) : null;
    }
    catch {
        // If the AI provides an invalid regex (e.g. "*"), we don't want to crash.
        // We can just skip the widget in this case.
        return null;
    }
    // clang-format off
    const renderedWidget = html `<devtools-widget
      class="styling-preview-widget"
      ${widget(Elements.StandaloneStylesContainer.StandaloneStylesContainer, {
        domNode: domNodeForId,
        filter,
    })}>
  </devtools-widget>`;
    // clang-format on
    return {
        renderedWidget,
        revealable: domNodeForId,
        accessibleRevealLabel: lockedString(UIStringsNotTranslate.revealStyleProperties),
        title: html `<devtools-widget
      ${widget(PanelsCommon.DOMLinkifier.DOMNodeLink, {
            node: domNodeForId,
        })}
    ></devtools-widget>`,
        jslogContext: 'standalone-styles',
    };
}
const INSIGHT_METADATA = {
    [Trace.Insights.Types.InsightKeys.LCP_BREAKDOWN]: {
        component: TimelineInsights.LCPBreakdown.LCPBreakdown,
        accessibleLabel: UIStringsNotTranslate.revealLcpBreakdown,
        title: UIStringsNotTranslate.lcpBreakdown,
        jslog: 'lcp-breakdown-widget',
    },
    [Trace.Insights.Types.InsightKeys.RENDER_BLOCKING]: {
        component: TimelineInsights.RenderBlocking.RenderBlocking,
        accessibleLabel: UIStringsNotTranslate.revealRenderBlockingBreakdown,
        title: UIStringsNotTranslate.renderBlockingBreakdown,
        jslog: 'render-blocking-widget',
    },
    [Trace.Insights.Types.InsightKeys.LCP_DISCOVERY]: {
        component: TimelineInsights.LCPDiscovery.LCPDiscovery,
        accessibleLabel: UIStringsNotTranslate.revealLcpDiscovery,
        title: UIStringsNotTranslate.lcpDiscovery,
        jslog: 'lcp-discovery-widget',
    },
    [Trace.Insights.Types.InsightKeys.CLS_CULPRITS]: {
        component: TimelineInsights.CLSCulprits.CLSCulprits,
        accessibleLabel: UIStringsNotTranslate.revealClsCulprits,
        title: UIStringsNotTranslate.clsCulprits,
        jslog: 'cls-culprits-widget',
    },
    [Trace.Insights.Types.InsightKeys.NETWORK_DEPENDENCY_TREE]: {
        component: TimelineInsights.NetworkDependencyTree.NetworkDependencyTree,
        accessibleLabel: UIStringsNotTranslate.revealNetworkDependencyTree,
        title: UIStringsNotTranslate.networkDependencyTree,
        jslog: 'network-dependency-tree-widget',
    },
    [Trace.Insights.Types.InsightKeys.THIRD_PARTIES]: {
        component: TimelineInsights.ThirdParties.ThirdParties,
        accessibleLabel: UIStringsNotTranslate.revealThirdParties,
        title: UIStringsNotTranslate.thirdParties,
        jslog: 'third-parties-widget',
    },
    [Trace.Insights.Types.InsightKeys.FORCED_REFLOW]: {
        component: TimelineInsights.ForcedReflow.ForcedReflow,
        accessibleLabel: UIStringsNotTranslate.revealForcedReflow,
        title: UIStringsNotTranslate.forcedReflow,
        jslog: 'forced-reflow-widget',
    },
    [Trace.Insights.Types.InsightKeys.CACHE]: {
        component: TimelineInsights.Cache.Cache,
        accessibleLabel: UIStringsNotTranslate.revealCache,
        title: UIStringsNotTranslate.cache,
        jslog: 'cache-widget',
    },
    [Trace.Insights.Types.InsightKeys.INP_BREAKDOWN]: {
        component: TimelineInsights.INPBreakdown.INPBreakdown,
        accessibleLabel: UIStringsNotTranslate.revealInpBreakdown,
        title: UIStringsNotTranslate.inpBreakdown,
        jslog: 'inp-breakdown-widget',
    },
    [Trace.Insights.Types.InsightKeys.DOCUMENT_LATENCY]: {
        component: TimelineInsights.DocumentLatency.DocumentLatency,
        accessibleLabel: UIStringsNotTranslate.revealDocumentLatency,
        title: UIStringsNotTranslate.documentLatency,
        jslog: 'document-latency-widget',
    },
    [Trace.Insights.Types.InsightKeys.DOM_SIZE]: {
        component: TimelineInsights.DOMSize.DOMSize,
        accessibleLabel: UIStringsNotTranslate.revealDomSize,
        title: UIStringsNotTranslate.domSize,
        jslog: 'dom-size-widget',
    },
    [Trace.Insights.Types.InsightKeys.DUPLICATE_JAVASCRIPT]: {
        component: TimelineInsights.DuplicatedJavaScript.DuplicatedJavaScript,
        accessibleLabel: UIStringsNotTranslate.revealDuplicateJavaScript,
        title: UIStringsNotTranslate.duplicateJavaScript,
        jslog: 'duplicate-javascript-widget',
    },
    [Trace.Insights.Types.InsightKeys.IMAGE_DELIVERY]: {
        component: TimelineInsights.ImageDelivery.ImageDelivery,
        accessibleLabel: UIStringsNotTranslate.revealImageDelivery,
        title: UIStringsNotTranslate.imageDelivery,
        jslog: 'image-delivery-widget',
    },
    [Trace.Insights.Types.InsightKeys.FONT_DISPLAY]: {
        component: TimelineInsights.FontDisplay.FontDisplay,
        accessibleLabel: UIStringsNotTranslate.revealFontDisplay,
        title: UIStringsNotTranslate.fontDisplay,
        jslog: 'font-display-widget',
    },
    [Trace.Insights.Types.InsightKeys.SLOW_CSS_SELECTOR]: {
        component: TimelineInsights.SlowCSSSelector.SlowCSSSelector,
        accessibleLabel: UIStringsNotTranslate.revealSlowCssSelector,
        title: UIStringsNotTranslate.slowCssSelector,
        jslog: 'slow-css-selector-widget',
    },
    [Trace.Insights.Types.InsightKeys.LEGACY_JAVASCRIPT]: {
        component: TimelineInsights.LegacyJavaScript.LegacyJavaScript,
        accessibleLabel: UIStringsNotTranslate.revealLegacyJavaScript,
        title: UIStringsNotTranslate.legacyJavaScript,
        jslog: 'legacy-javascript-widget',
    },
    [Trace.Insights.Types.InsightKeys.VIEWPORT]: {
        component: TimelineInsights.Viewport.Viewport,
        accessibleLabel: UIStringsNotTranslate.revealViewport,
        title: UIStringsNotTranslate.viewport,
        jslog: 'viewport-widget',
    },
    [Trace.Insights.Types.InsightKeys.MODERN_HTTP]: {
        component: TimelineInsights.ModernHTTP.ModernHTTP,
        accessibleLabel: UIStringsNotTranslate.revealModernHttp,
        title: UIStringsNotTranslate.modernHttp,
        jslog: 'modern-http-widget',
    },
    [Trace.Insights.Types.InsightKeys.CHARACTER_SET]: {
        component: TimelineInsights.CharacterSet.CharacterSet,
        accessibleLabel: UIStringsNotTranslate.revealCharacterSet,
        title: UIStringsNotTranslate.characterSet,
        jslog: 'character-set-widget',
    },
};
function renderInsightWidget(component, insight, jslog, accessibleLabel, title, bounds) {
    const renderedWidget = html `<devtools-widget
    class=${jslog}
    ${widget(component, {
        model: insight,
        minimal: true,
        bounds: bounds ?? null,
    })}></devtools-widget>`;
    return {
        renderedWidget,
        revealable: new TimelineUtils.Helpers.RevealableInsight(insight),
        accessibleRevealLabel: lockedString(accessibleLabel),
        title: lockedString(title),
        jslogContext: jslog,
    };
}
async function makePerfInsightWidget(widgetData) {
    const insightKey = widgetData.data.insight;
    const insight = widgetData.data.insightData;
    const meta = INSIGHT_METADATA[insightKey];
    if (!meta) {
        return null;
    }
    let bounds;
    if (insightKey === Trace.Insights.Types.InsightKeys.CLS_CULPRITS) {
        const traceBounds = TraceBounds.TraceBounds.BoundsManager.instance().state()?.micro.entireTraceBounds;
        if (!traceBounds) {
            return null;
        }
        bounds = traceBounds;
    }
    return renderInsightWidget(meta.component, insight, meta.jslog, meta.accessibleLabel, meta.title, bounds);
}
async function makeBottomUpTimelineTreeWidget(widgetData) {
    const bottomUpRootNode = AiAssistanceModel.AIQueries.AIQueries.mainThreadActivityBottomUp(widgetData.data.bounds, widgetData.data.parsedTrace);
    if (!bottomUpRootNode) {
        return null;
    }
    const events = bottomUpRootNode.events;
    const startTime = Trace.Helpers.Timing.microToMilli(widgetData.data.bounds.min);
    const endTime = Trace.Helpers.Timing.microToMilli(widgetData.data.bounds.max);
    const renderedWidget = html `<devtools-widget
      class="bottom-up-timeline-tree-widget"
      ${widget(Timeline.TimelineTreeView.BottomUpTimelineTreeView, {
        selectedEvents: events,
        parsedTrace: widgetData.data.parsedTrace,
        startTime,
        endTime,
        compactMode: true,
        maxLinkLength: 15,
        maxRows: 10,
    })}></devtools-widget>`;
    return {
        renderedWidget,
        revealable: new TimelineUtils.Helpers.RevealableBottomUpProfile(widgetData.data.bounds),
        accessibleRevealLabel: lockedString(UIStringsNotTranslate.revealBottomUpTree),
        title: lockedString(UIStringsNotTranslate.bottomUpTree),
        jslogContext: 'bottom-up',
    };
}
function renderWidgetResponse(response) {
    if (response === null) {
        return Lit.nothing;
    }
    function onReveal() {
        if (response === null) {
            return;
        }
        Common.Revealer.reveal(response?.revealable).catch((error) => {
            if (!error.message) {
                return;
            }
            Snackbars.Snackbar.Snackbar.show({ message: error.message });
        });
    }
    const classes = Lit.Directives.classMap({
        'widget-and-revealer-container': true,
        'revealer-only': response.renderedWidget === null,
    });
    const revealButton = html `
    <devtools-button class="widget-reveal-button"
      .variant=${"text" /* Buttons.Button.Variant.TEXT */}
      .accessibleLabel=${response.accessibleRevealLabel}
      .jslogContext=${'reveal'}
      @click=${onReveal}
    >
      ${response.customRevealTitle ?? lockedString(UIStringsNotTranslate.reveal)}
      <devtools-icon name='tab-move'></devtools-icon>
    </devtools-button>
  `;
    // clang-format off
    return html `
    <div class=${classes} jslog=${ifDefined(response.jslogContext ? VisualLogging.section(response.jslogContext) : undefined)}>
      ${response.title ? html `
        <div class="widget-header">
          <h4 class="widget-name">${response.title}</h4>
          <div class="widget-reveal-container">
            ${revealButton}
          </div>
        </div>
      ` : Lit.nothing}
      ${response.renderedWidget ? html `
        <div class="widget-content-container">
          ${response.renderedWidget}
        </div>` : Lit.nothing}
      ${!response.title ? html `
        <div class="widget-reveal-container">
          ${revealButton}
        </div>
      ` : Lit.nothing}
    </div>
    `;
    // clang-format on
}
async function makePerformanceTraceWidget(widgetData) {
    const customRevealTitle = lockedString(UIStringsNotTranslate.revealTrace);
    return {
        renderedWidget: null,
        title: null,
        revealable: new Timeline.TimelinePanel.ParsedTraceRevealable(widgetData.data.parsedTrace),
        customRevealTitle,
        accessibleRevealLabel: customRevealTitle,
        jslogContext: 'performance-trace',
    };
}
async function makeSourceFileWidget(widgetData) {
    const file = widgetData.data.uiSourceCode;
    const customRevealTitle = i18n.i18n.lockedString(`Show ${file.name()}`);
    return {
        renderedWidget: null,
        title: null,
        revealable: file,
        customRevealTitle,
        accessibleRevealLabel: customRevealTitle,
        jslogContext: 'source-file-widget',
    };
}
async function makeSourceCodeWidget(widgetData) {
    const url = widgetData.data.url;
    const filename = url.split('/').pop() || url;
    const line = widgetData.data.line;
    const column = widgetData.data.column;
    // If both line and column numbers are provided, we represent a specific function execution,
    // so the widget title is formatted as 'filename:line:column' (matching the Sources Panel style).
    // Otherwise, if line and column are not provided, we are viewing the entire file contents,
    // so the header simply displays 'filename'.
    const header = line !== undefined && column !== undefined ? `${filename}:${line}:${column}` : filename;
    const uiSourceCode = Workspace.Workspace.WorkspaceImpl.instance().uiSourceCodeForURL(url);
    const lastDotIndex = filename.lastIndexOf('.');
    const fileExtension = lastDotIndex !== -1 ? filename.substring(lastDotIndex + 1) : '';
    let code = widgetData.data.code;
    if (TextUtils.TextUtils.isMinified(code)) {
        const canonicalMimeType = uiSourceCode?.contentType().canonicalMimeType() || 'text/javascript';
        const formatted = await Formatter.ScriptFormatter.formatScriptContent(canonicalMimeType, code, '  ');
        code = formatted.formattedContent;
    }
    const renderedWidget = html `
    <devtools-code-block
      class="source-code-widget"
      .displayLimit=${20}
      .code=${code}
      .codeLang=${fileExtension}
      .displayToolbar=${false}
      .displayNotice=${false}
    ></devtools-code-block>
  `;
    return {
        renderedWidget,
        title: lockedString(header),
        revealable: uiSourceCode,
        accessibleRevealLabel: i18n.i18n.lockedString(`Show ${filename} in Sources`),
        jslogContext: 'source-code-widget',
    };
}
function renderFileRevealButton(file, collapsed) {
    const onReveal = () => {
        void Common.Revealer.reveal(file);
    };
    const accessibleLabel = i18n.i18n.lockedString(`Show ${file.fullDisplayName()}`);
    const className = `widget-reveal-button ${collapsed ? 'collapsed-file' : 'visible-file'}`;
    return html `
    <devtools-button class=${className}
      .variant=${"text" /* Buttons.Button.Variant.TEXT */}
      .accessibleLabel=${accessibleLabel}
      .jslogContext=${'reveal'}
      @click=${onReveal}>
      ${file.fullDisplayName()}
      <devtools-icon name='tab-move'></devtools-icon>
    </devtools-button>
  `;
}
async function makeSourceFilesListWidget(widgetData) {
    const files = widgetData.data.uiSourceCodes;
    if (files.length === 0) {
        return null;
    }
    // If there are more than 10 files, only show the first 10, and hide the rest unless "Show all" is clicked.
    // clang-format off
    const renderedWidget = html `
    <div class="source-files-widget">
      ${files.slice(0, 10).map(file => renderFileRevealButton(file, /* collapsed */ false))}
      ${files.length > 10 ? html `
        <details class="source-files-details">
          <summary class="show-more-summary">${i18n.i18n.lockedString(`Show all ${files.length} files`)}</summary>
          ${files.slice(10).map(file => renderFileRevealButton(file, /* collapsed */ true))}
        </details> ` : Lit.nothing}
    </div>`;
    // clang-format on
    const title = lockedString(UIStringsNotTranslate.inspectedFileNames);
    return {
        renderedWidget,
        title,
        revealable: files[0],
        accessibleRevealLabel: i18n.i18n.lockedString('Reveal first file in Sources panel'),
        jslogContext: 'source-files-list-widget',
    };
}
const expandedNetworkRequestsWidgets = new WeakSet();
// A widget with a table of the list of network requests sent to the agent.
// Only show 15 requests maximum in collapsed version. The rest of the requests
// will be hidden unless the user clicks "Show all".
async function makeNetworkRequestsListWidget(widgetData) {
    const requests = widgetData.data.requests;
    if (requests.length === 0) {
        return null;
    }
    const isExpanded = expandedNetworkRequestsWidgets.has(widgetData);
    // We only want just expanded widget to be expanded, if the user closed and reopened the walkthrought, the widget should be collapsed again.
    // Therefore, after rendering the widget, we remove the widget from the set of expanded widgets so that it is collapsed on next render.
    if (isExpanded) {
        expandedNetworkRequestsWidgets.delete(widgetData);
    }
    const displayedRequests = isExpanded ? requests : requests.slice(0, 15);
    // The table contains same fields as the ones sent to the agent.
    // clang-format off
    const renderedWidget = html `
    <div class="network-requests-widget">
      <devtools-data-grid striped inline>
        <table>
          <tr>
            <th id="name" weight="4">${i18n.i18n.lockedString('Name')}</th>
            <th id="status" weight="1">${i18n.i18n.lockedString('Status')}</th>
            <th id="size" weight="1">${i18n.i18n.lockedString('Size')}</th>
            <th id="time" weight="1">${i18n.i18n.lockedString('Time')}</th>
          </tr>
          ${displayedRequests.map(request => html `
            <tr>
              <td>${request.name()}</td>
              <td>${request.statusCode}</td>
              <td>${i18n.ByteUtilities.formatBytesToKb(request.transferSize)}</td>
              <td>${i18n.TimeUtilities.secondsToString(request.duration)}</td>
            </tr>
          `)}
        </table>
      </devtools-data-grid>
      ${!isExpanded && requests.length > 15 ? html `
        <div class="show-all-container">
          <button class="show-all-widget-requests-button text-button"
            jslog=${VisualLogging.action('show-all-widget-requests-button').track({ click: true })}
            @click=${(e) => {
        expandedNetworkRequestsWidgets.add(widgetData);
        const widgetEl = e.target.closest('.widget');
        if (widgetEl) {
            const widget = UI.Widget.Widget.get(widgetEl);
            if (widget && widget.performUpdate) {
                void widget.performUpdate();
            }
        }
    }}>
            ${i18n.i18n.lockedString(`Show all ${requests.length} network requests`)}
          </button>
        </div>
      ` : Lit.nothing}
    </div>
  `;
    // clang-format on
    return {
        renderedWidget,
        title: lockedString(UIStringsNotTranslate.networkRequests),
        revealable: requests[0],
        accessibleRevealLabel: lockedString(UIStringsNotTranslate.revealFirstNetworkRequest),
        jslogContext: 'network-requests-list-widget',
    };
}
function renderNetworkRequestPreview(networkRequest) {
    const filename = networkRequest.url.split('/').pop() || networkRequest.url;
    const size = i18n.ByteUtilities.bytesToString(networkRequest.size);
    const resourceType = Common.ResourceType.resourceTypes[networkRequest.resourceType];
    const { iconName, color } = PanelUtils.iconDataForResourceType(resourceType);
    // This can be `null` if the image is too large to have a preview generated.
    // And we can't fallback to the actual URL due to CSP restrictions.
    const imageUrl = networkRequest.imageContent?.asImagePreviewUrl();
    // clang-format off
    return html `
    <div class="network-request-preview">
      <div class="network-request-header">
        <div class="network-request-icon">
          ${resourceType.isImage() && imageUrl ? // only try to render the image if we have a preview URL, else fallback to a coloured square.
        html `<img src=${imageUrl} alt=${filename} />` :
        html `<devtools-icon name=${iconName} style=${Lit.Directives.styleMap({ color: color ?? '' })}></devtools-icon>`}
        </div>
        <div class="network-request-details">
          <div class="network-request-name" title=${networkRequest.url}>${filename}</div>
          <div class="network-request-size">${size}</div>
        </div>
      </div>
    </div>
  `;
    // clang-format on
}
async function makeDomTreeWidget(widgetData) {
    const root = widgetData.data.root;
    if (!(root instanceof SDK.DOMModel.DOMNodeSnapshot)) {
        return null;
    }
    const networkRequest = widgetData.data.networkRequest;
    // clang-format off
    const renderedWidget = html `
    ${networkRequest ? renderNetworkRequestPreview(networkRequest) : Lit.nothing}
    <devtools-widget class="dom-tree-widget" ${widget(Elements.ElementsTreeOutline.DOMTreeWidget, {
        maxTreeDepth: 2,
        enableContextMenu: false,
        showComments: false,
        showAIButton: false,
        disableEdits: true,
        expandRoot: true,
        rootDOMNode: root,
        visibleWidth: 400,
        wrap: true,
        maxRows: 10,
    })}></devtools-widget>
  `;
    // clang-format on
    return {
        renderedWidget,
        revealable: new SDK.DOMModel.DeferredDOMNode(root.domModel().target(), root.backendNodeId()),
        accessibleRevealLabel: widgetData.data.accessibleRevealLabel,
        title: widgetData.data.title,
        jslogContext: 'dom-snapshot',
    };
}
/**
 * Renders AI-defined UI widgets.
 * When a ModelChatMessage contains a WidgetPart, or a Step has widgets,
 * the ChatMessage component iterates through the \`widgets\` array.
 * For each widget, it determines the appropriate rendering logic based on
 * the \`widgetData.name\`.
 *
 * Currently, 'COMPUTED_STYLES', 'CORE_VITALS' and 'STYLE_PROPERTIES' widgets are supported.
 * For these, the corresponding \`make...Widget\` functions are called to construct the necessary
 * data and configuration for the UI components. The widget is then rendered using the
 * \`<devtools-widget>\` custom element, which dynamically instantiates and displays the
 * specified UI.Widget subclass with the provided configuration.
 *
 * This allows for a flexible and extensible system where new widget types
 * can be added to the AI responses and rendered in DevTools by adding
 * corresponding `make...Widget` functions and handling them here.
 */
/**
 * Generates a deterministic unique identifier for a given AiWidget based on
 * its name and identifying data. This signature is used for widget deduplication.
 */
export function getWidgetSignature(widget) {
    switch (widget.name) {
        case 'COMPUTED_STYLES':
            return `${widget.name}:${widget.data.backendNodeId}`;
        case 'CORE_VITALS':
            return `${widget.name}:${widget.data.insightSetKey}`;
        case 'STYLE_PROPERTIES':
            return `${widget.name}:${widget.data.backendNodeId}:${widget.data.selector ?? ''}`;
        case 'DOM_TREE':
            return `${widget.name}:${widget.data.root.backendNodeId()}`;
        case 'PERFORMANCE_TRACE':
            return `${widget.name}`;
        case 'PERF_INSIGHT':
            return `${widget.name}:${widget.data.insight}:${widget.data.insightData.insightKey}:${widget.data.insightData.navigation?.args?.data?.navigationId ?? 'no-nav-id'}`;
        case 'TIMELINE_RANGE_SUMMARY':
            return `${widget.name}:${widget.data.track}:${widget.data.bounds.min}-${widget.data.bounds.max}`;
        case 'BOTTOM_UP_TREE':
            return `${widget.name}:${widget.data.bounds.min}-${widget.data.bounds.max}`;
        case 'NETWORK_TRACK':
            return `${widget.name}:${widget.data.bounds.min}-${widget.data.bounds.max}`;
        case 'SOURCE_FILE':
            return `${widget.name}:${widget.data.uiSourceCode.url()}`;
        case 'SOURCE_FILES_LIST':
            return `${widget.name}:${widget.data.uiSourceCodes.map(f => f.url()).join(',')}`;
        case 'LIGHTHOUSE_REPORT':
            return `${widget.name}:${widget.data.report.fetchTime}`;
        case 'TIMELINE_EVENT_SUMMARY':
            return `${widget.name}:${widget.data.event.ts}:${widget.data.event.name}`;
        case 'NETWORK_REQUEST_GENERAL_HEADERS':
            return `${widget.name}:${widget.data.request.requestId()}`;
        case 'SOURCE_CODE':
            return `${widget.name}:${widget.data.url}:${widget.data.line ?? ''}:${widget.data.column ?? ''}`;
        case 'NETWORK_REQUESTS_LIST':
            return `${widget.name}:${widget.data.requests.map(r => r.requestId()).join(',')}`;
        default:
            Platform.assertNever(widget, 'Unknown AiWidget name');
    }
}
/**
 * Returns a new ModelChatMessage where widgets have been deduplicated
 * across all parts and steps of the message. The first occurrence of each
 * unique widget (determined by its signature) is preserved.
 */
export function getDeduplicatedWidgetsMessage(message) {
    const seenWidgets = new Set();
    const filterWidgets = (widgets) => {
        return widgets.filter(widget => {
            const signature = getWidgetSignature(widget);
            if (seenWidgets.has(signature)) {
                return false;
            }
            seenWidgets.add(signature);
            return true;
        });
    };
    const deduplicatedParts = message.parts.map(part => {
        if (part.type === 'widget') {
            return {
                ...part,
                widgets: filterWidgets(part.widgets),
            };
        }
        if (part.type === 'step' && part.step.widgets) {
            return {
                ...part,
                step: {
                    ...part.step,
                    widgets: filterWidgets(part.step.widgets),
                },
            };
        }
        return part;
    });
    return {
        ...message,
        parts: deduplicatedParts,
    };
}
async function renderWidgets(widgets, options = {}) {
    if (!Root.Runtime.hostConfig.devToolsAiAssistanceV2?.enabled || !widgets || widgets.length === 0) {
        return Lit.nothing;
    }
    const ui = await Promise.all(widgets.map(async (widgetData) => {
        let response = null;
        switch (widgetData.name) {
            case 'COMPUTED_STYLES':
                response = await makeComputedStyleWidget(widgetData);
                break;
            case 'CORE_VITALS':
                response = await makeCoreWebVitalsWidget(widgetData);
                break;
            case 'STYLE_PROPERTIES':
                response = await makeStylePropertiesWidget(widgetData);
                break;
            case 'DOM_TREE':
                response = await makeDomTreeWidget(widgetData);
                break;
            case 'PERFORMANCE_TRACE':
                response = await makePerformanceTraceWidget(widgetData);
                break;
            case 'PERF_INSIGHT':
                response = await makePerfInsightWidget(widgetData);
                break;
            case 'TIMELINE_RANGE_SUMMARY':
                response = await makeTimelineRangeSummaryWidget(widgetData);
                break;
            case 'BOTTOM_UP_TREE':
                response = await makeBottomUpTimelineTreeWidget(widgetData);
                break;
            case 'NETWORK_TRACK':
                response = await makeNetworkTrackWidget(widgetData);
                break;
            case 'SOURCE_FILE':
                response = await makeSourceFileWidget(widgetData);
                break;
            case 'SOURCE_FILES_LIST':
                response = await makeSourceFilesListWidget(widgetData);
                break;
            case 'NETWORK_REQUESTS_LIST':
                response = await makeNetworkRequestsListWidget(widgetData);
                break;
            case 'LIGHTHOUSE_REPORT':
                response = await makeLighthouseReportWidget(widgetData);
                break;
            case 'TIMELINE_EVENT_SUMMARY':
                response = await makeTimelineEventSummaryWidget(widgetData);
                break;
            case 'NETWORK_REQUEST_GENERAL_HEADERS':
                response = await makeNetworkRequestGeneralHeadersWidget(widgetData);
                break;
            case 'SOURCE_CODE':
                response = await makeSourceCodeWidget(widgetData);
                break;
            default:
                Platform.assertNever(widgetData, 'Unknown AiWidget name');
        }
        return renderWidgetResponse(response);
    }));
    if (options.wrapperClass) {
        return html `<div class=${options.wrapperClass}>${ui}</div>`;
    }
    return html `${ui}`;
}
function renderSideEffectConfirmationUi(step) {
    if (!step.requestApproval) {
        return Lit.nothing;
    }
    // clang-format off
    return html `<div
    class="side-effect-confirmation"
    jslog=${VisualLogging.section('side-effect-confirmation')}
  >
    ${step.requestApproval.description ? html `<p>${step.requestApproval.description}</p>` : Lit.nothing}
    <div class="side-effect-buttons-container">
      <devtools-button
        .data=${{
        variant: "outlined" /* Buttons.Button.Variant.OUTLINED */,
        jslogContext: 'decline-execute-code',
    }}
        @click=${() => step.requestApproval?.onAnswer(false)}
      >${lockedString(UIStringsNotTranslate.declineActionRequestApproval)}</devtools-button>
      <devtools-button
        .data=${{
        variant: "primary" /* Buttons.Button.Variant.PRIMARY */,
        jslogContext: 'accept-execute-code',
        iconName: 'play',
    }}
        @click=${() => step.requestApproval?.onAnswer(true)}
      >${lockedString(UIStringsNotTranslate.confirmActionRequestApproval)}</devtools-button>
    </div>
  </div>`;
    // clang-format on
}
function renderError(message) {
    if (message.error) {
        let errorMessage;
        switch (message.error) {
            case "unknown" /* AiAssistanceModel.AiAgent.ErrorType.UNKNOWN */:
            case "block" /* AiAssistanceModel.AiAgent.ErrorType.BLOCK */:
                errorMessage = UIStringsNotTranslate.systemError;
                break;
            case "max-steps" /* AiAssistanceModel.AiAgent.ErrorType.MAX_STEPS */:
                errorMessage = UIStringsNotTranslate.maxStepsError;
                break;
            case "cross-origin" /* AiAssistanceModel.AiAgent.ErrorType.CROSS_ORIGIN */:
                errorMessage = UIStringsNotTranslate.crossOriginError;
                break;
            case "abort" /* AiAssistanceModel.AiAgent.ErrorType.ABORT */:
                return html `<p class="aborted" jslog=${VisualLogging.section('aborted')}>${lockedString(UIStringsNotTranslate.stoppedResponse)}</p>`;
        }
        return html `<p class="error" jslog=${VisualLogging.section('error')}>${lockedString(errorMessage)}</p>`;
    }
    return Lit.nothing;
}
function renderImageChatMessage(inlineData) {
    if (inlineData.data === AiAssistanceModel.AiConversation.NOT_FOUND_IMAGE_DATA) {
        // clang-format off
        return html `<div class="unavailable-image" title=${UIStringsNotTranslate.imageUnavailable}>
      <devtools-icon name='file-image'></devtools-icon>
    </div>`;
        // clang-format on
    }
    const imageUrl = `data:${inlineData.mimeType};base64,${inlineData.data}`;
    // clang-format off
    return html `<devtools-link
      class="image-link" title=${UIStringsNotTranslate.openImageInNewTab}
      href=${imageUrl}
    >
      <img src=${imageUrl} alt=${UIStringsNotTranslate.imageInputSentToTheModel} />
    </devtools-link>`;
    // clang-format on
}
function renderActions(input, output) {
    const aiAssistanceV2 = Root.Runtime.hostConfig.devToolsAiAssistanceV2?.enabled;
    const rowClasses = Lit.Directives.classMap({
        'ai-assistance-feedback-row': true,
        'not-v2': !aiAssistanceV2,
    });
    // clang-format off
    return html `
    <div class=${rowClasses}>
      <div class="action-buttons">
        ${input.showRateButtons ? html `
          <devtools-button
            .data=${{
        variant: "icon" /* Buttons.Button.Variant.ICON */,
        size: "SMALL" /* Buttons.Button.Size.SMALL */,
        iconName: 'thumb-up',
        toggledIconName: 'thumb-up-filled',
        toggled: input.currentRating === "POSITIVE" /* Host.AidaClient.Rating.POSITIVE */,
        toggleType: "primary-toggle" /* Buttons.Button.ToggleType.PRIMARY */,
        title: lockedString(UIStringsNotTranslate.thumbsUp),
        jslogContext: 'thumbs-up',
    }}
            @click=${() => input.onRatingClick("POSITIVE" /* Host.AidaClient.Rating.POSITIVE */)}
          ></devtools-button>
          <devtools-button
            .data=${{
        variant: "icon" /* Buttons.Button.Variant.ICON */,
        size: "SMALL" /* Buttons.Button.Size.SMALL */,
        iconName: 'thumb-down',
        toggledIconName: 'thumb-down-filled',
        toggled: input.currentRating === "NEGATIVE" /* Host.AidaClient.Rating.NEGATIVE */,
        toggleType: "primary-toggle" /* Buttons.Button.ToggleType.PRIMARY */,
        title: lockedString(UIStringsNotTranslate.thumbsDown),
        jslogContext: 'thumbs-down',
    }}
            @click=${() => input.onRatingClick("NEGATIVE" /* Host.AidaClient.Rating.NEGATIVE */)}
          ></devtools-button>
          ${aiAssistanceV2 ? Lit.nothing : html `<div class="vertical-separator"></div>`}
        ` : Lit.nothing}
        <devtools-button
          .data=${{
        variant: "icon" /* Buttons.Button.Variant.ICON */,
        size: "SMALL" /* Buttons.Button.Size.SMALL */,
        title: lockedString(UIStringsNotTranslate.report),
        iconName: 'report',
        jslogContext: 'report',
    }}
          @click=${input.onReportClick}
        ></devtools-button>
        ${aiAssistanceV2 ? Lit.nothing : html `
          <div class="vertical-separator"></div>
          <devtools-button
            .data=${{
        variant: "icon" /* Buttons.Button.Variant.ICON */,
        size: "SMALL" /* Buttons.Button.Size.SMALL */,
        title: lockedString(UIStringsNotTranslate.copyResponse),
        iconName: 'copy',
        jslogContext: 'copy-ai-response',
    }}
            aria-label=${lockedString(UIStringsNotTranslate.copyResponse)}
            @click=${input.onCopyResponseClick}></devtools-button>
        `}
        ${input.onExportClick && aiAssistanceV2 && input.isLastMessage ? html `
          <devtools-button
            class="export-for-agents-button"
            .jslogContext=${'ai-export-for-agents'}
            .variant=${"outlined" /* Buttons.Button.Variant.OUTLINED */}
            .iconName=${'copy'}
            aria-label=${lockedString(UIStringsNotTranslate.exportForAgents)}
            @click=${input.onExportClick}
          >${lockedString(UIStringsNotTranslate.exportForAgents)}</devtools-button>
          ${input.suggestions ? html `<div class="vertical-separator"></div>` : Lit.nothing}
        ` : Lit.nothing}
      </div>
      ${input.suggestions ? html `<div class="suggestions-container">
        <div class="scroll-button-container left hidden" ${ref(element => { output.suggestionsLeftScrollButtonContainer = element; })}>
          <devtools-button
            class='scroll-button'
            .data=${{
        variant: "icon" /* Buttons.Button.Variant.ICON */,
        size: "SMALL" /* Buttons.Button.Size.SMALL */,
        iconName: 'chevron-left',
        title: lockedString(UIStringsNotTranslate.scrollToPrevious),
        jslogContext: 'chevron-left',
    }}
            @click=${() => input.scrollSuggestionsScrollContainer('left')}
          ></devtools-button>
        </div>
        <div class="suggestions-scroll-container" @scroll=${input.onSuggestionsScrollOrResize} ${ref(element => { output.suggestionsScrollContainer = element; })}>
          ${input.suggestions.map(suggestion => html `<devtools-button
            class='suggestion'
            .data=${{
        variant: "outlined" /* Buttons.Button.Variant.OUTLINED */,
        title: suggestion,
        jslogContext: 'suggestion',
    }}
            @click=${() => input.onSuggestionClick(suggestion)}
          >${suggestion}</devtools-button>`)}
        </div>
        <div class="scroll-button-container right hidden" ${ref(element => { output.suggestionsRightScrollButtonContainer = element; })}>
          <devtools-button
            class='scroll-button'
            .data=${{
        variant: "icon" /* Buttons.Button.Variant.ICON */,
        size: "SMALL" /* Buttons.Button.Size.SMALL */,
        iconName: 'chevron-right',
        title: lockedString(UIStringsNotTranslate.scrollToNext),
        jslogContext: 'chevron-right',
    }}
            @click=${() => input.scrollSuggestionsScrollContainer('right')}
          ></devtools-button>
        </div>
      </div>` : Lit.nothing}
    </div>
    ${input.isShowingFeedbackForm ? html `
      <form class="feedback-form" @submit=${input.onSubmit}>
        <div class="feedback-header">
          <h4 class="feedback-title">${lockedString(UIStringsNotTranslate.whyThisRating)}</h4>
          <devtools-button
            aria-label=${lockedString(UIStringsNotTranslate.close)}
            @click=${input.onClose}
            .data=${{
        variant: "icon" /* Buttons.Button.Variant.ICON */,
        iconName: 'cross',
        size: "SMALL" /* Buttons.Button.Size.SMALL */,
        title: lockedString(UIStringsNotTranslate.close),
        jslogContext: 'close',
    }}
          ></devtools-button>
        </div>
        <input
          type="text"
          class="devtools-text-input feedback-input"
          @input=${(event) => input.onInputChange(event.target.value)}
          placeholder=${lockedString(UIStringsNotTranslate.provideFeedbackPlaceholder)}
          jslog=${VisualLogging.textField('feedback').track({ keydown: 'Enter' })}
        >
        <span class="feedback-disclaimer">${lockedString(UIStringsNotTranslate.disclaimer)}</span>
        <div>
          <devtools-button
          aria-label=${lockedString(UIStringsNotTranslate.submit)}
          .data=${{
        type: 'submit',
        disabled: input.isSubmitButtonDisabled,
        variant: "outlined" /* Buttons.Button.Variant.OUTLINED */,
        size: "SMALL" /* Buttons.Button.Size.SMALL */,
        title: lockedString(UIStringsNotTranslate.submit),
        jslogContext: 'send',
    }}
          >${lockedString(UIStringsNotTranslate.submit)}</devtools-button>
        </div>
      </div>
    </form>
    ` : Lit.nothing}
  `;
    // clang-format on
}
export class ChatMessage extends UI.Widget.Widget {
    message = { entity: "user" /* ChatMessageEntity.USER */, text: '', id: '' };
    isLoading = false;
    isReadOnly = false;
    prompt = '';
    canShowFeedbackForm = false;
    isLastMessage = false;
    isFirstMessage = false;
    shouldShowCSSChangeSummary = false;
    markdownRenderer;
    onSuggestionClick = () => { };
    onFeedbackSubmit = () => { };
    onCopyResponseClick = () => { };
    onExportClick = () => { };
    changeSummary;
    walkthrough = {
        onOpen: () => { },
        onToggle: () => { },
        isInlined: false,
        isExpanded: false,
        activeSidebarMessage: null,
        inlineExpandedMessages: [],
    };
    #suggestionsResizeObserver = new ResizeObserver(() => this.#handleSuggestionsScrollOrResize());
    #suggestionsEvaluateLayoutThrottler = new Common.Throttler.Throttler(100);
    #feedbackValue = '';
    #currentRating;
    #isShowingFeedbackForm = false;
    #isSubmitButtonDisabled = true;
    #view;
    #viewOutput = {};
    #isObservingSuggestions = false;
    constructor(element, view) {
        super(element);
        this.#view = view ?? DEFAULT_VIEW;
    }
    wasShown() {
        super.wasShown();
        void this.performUpdate();
        this.#evaluateSuggestionsLayout();
    }
    performUpdate() {
        const message = this.message.entity === "model" /* ChatMessageEntity.MODEL */ ? getDeduplicatedWidgetsMessage(this.message) : this.message;
        this.#view({
            message,
            isLoading: this.isLoading,
            isReadOnly: this.isReadOnly,
            canShowFeedbackForm: this.canShowFeedbackForm,
            markdownRenderer: this.markdownRenderer,
            isLastMessage: this.isLastMessage,
            isFirstMessage: this.isFirstMessage,
            prompt: this.prompt,
            shouldShowCSSChangeSummary: this.shouldShowCSSChangeSummary,
            onSuggestionClick: this.onSuggestionClick,
            onRatingClick: this.#handleRateClick.bind(this),
            onReportClick: () => UIHelpers.openInNewTab(REPORT_URL),
            onCopyResponseClick: () => {
                if (this.message.entity === "model" /* ChatMessageEntity.MODEL */) {
                    this.onCopyResponseClick(this.message);
                }
            },
            onExportClick: this.onExportClick,
            scrollSuggestionsScrollContainer: this.#scrollSuggestionsScrollContainer.bind(this),
            onSuggestionsScrollOrResize: this.#handleSuggestionsScrollOrResize.bind(this),
            onSubmit: this.#handleSubmit.bind(this),
            onClose: this.#handleClose.bind(this),
            onInputChange: this.#handleInputChange.bind(this),
            isSubmitButtonDisabled: this.#isSubmitButtonDisabled,
            // Props for actions logic
            showActions: !(this.isLastMessage && this.isLoading),
            showRateButtons: this.message.entity === "model" /* ChatMessageEntity.MODEL */ && !!this.message.rpcId,
            suggestions: (this.isLastMessage && this.message.entity === "model" /* ChatMessageEntity.MODEL */ && !this.isReadOnly &&
                this.message.parts.at(-1)?.type === 'answer') ?
                this.message.parts.at(-1).suggestions :
                undefined,
            currentRating: this.#currentRating,
            isShowingFeedbackForm: this.#isShowingFeedbackForm,
            onFeedbackSubmit: this.onFeedbackSubmit,
            changeSummary: this.changeSummary,
            walkthrough: this.walkthrough,
        }, this.#viewOutput, this.contentElement);
        if (this.#viewOutput.suggestionsScrollContainer && !this.#isObservingSuggestions) {
            this.#suggestionsResizeObserver.observe(this.#viewOutput.suggestionsScrollContainer);
            this.#isObservingSuggestions = true;
        }
    }
    #handleInputChange(value) {
        this.#feedbackValue = value;
        const disableSubmit = !value;
        if (disableSubmit !== this.#isSubmitButtonDisabled) {
            this.#isSubmitButtonDisabled = disableSubmit;
            void this.performUpdate();
        }
    }
    #evaluateSuggestionsLayout = () => {
        const suggestionsScrollContainer = this.#viewOutput.suggestionsScrollContainer;
        const leftScrollButtonContainer = this.#viewOutput.suggestionsLeftScrollButtonContainer;
        const rightScrollButtonContainer = this.#viewOutput.suggestionsRightScrollButtonContainer;
        if (!suggestionsScrollContainer || !leftScrollButtonContainer || !rightScrollButtonContainer) {
            return;
        }
        const shouldShowLeftButton = suggestionsScrollContainer.scrollLeft > SCROLL_ROUNDING_OFFSET;
        const shouldShowRightButton = suggestionsScrollContainer.scrollLeft +
            suggestionsScrollContainer.offsetWidth + SCROLL_ROUNDING_OFFSET <
            suggestionsScrollContainer.scrollWidth;
        leftScrollButtonContainer.classList.toggle('hidden', !shouldShowLeftButton);
        rightScrollButtonContainer.classList.toggle('hidden', !shouldShowRightButton);
    };
    willHide() {
        super.willHide();
        this.#suggestionsResizeObserver.disconnect();
        this.#isObservingSuggestions = false;
    }
    #handleSuggestionsScrollOrResize() {
        void this.#suggestionsEvaluateLayoutThrottler.schedule(() => {
            this.#evaluateSuggestionsLayout();
            return Promise.resolve();
        });
    }
    #scrollSuggestionsScrollContainer(direction) {
        const suggestionsScrollContainer = this.#viewOutput.suggestionsScrollContainer;
        if (!suggestionsScrollContainer) {
            return;
        }
        suggestionsScrollContainer.scroll({
            top: 0,
            left: direction === 'left' ? suggestionsScrollContainer.scrollLeft - suggestionsScrollContainer.clientWidth :
                suggestionsScrollContainer.scrollLeft + suggestionsScrollContainer.clientWidth,
            behavior: 'smooth',
        });
    }
    #handleRateClick(rating) {
        if (this.#currentRating === rating) {
            this.#currentRating = undefined;
            this.#isShowingFeedbackForm = false;
            this.#isSubmitButtonDisabled = true;
            // This effectively reset the user rating
            if (this.message.entity === "model" /* ChatMessageEntity.MODEL */ && this.message.rpcId) {
                this.onFeedbackSubmit(this.message.rpcId, "SENTIMENT_UNSPECIFIED" /* Host.AidaClient.Rating.SENTIMENT_UNSPECIFIED */);
            }
            void this.performUpdate();
            return;
        }
        this.#currentRating = rating;
        this.#isShowingFeedbackForm = this.canShowFeedbackForm;
        if (this.message.entity === "model" /* ChatMessageEntity.MODEL */ && this.message.rpcId) {
            this.onFeedbackSubmit(this.message.rpcId, rating);
        }
        void this.performUpdate();
    }
    #handleClose() {
        this.#isShowingFeedbackForm = false;
        this.#isSubmitButtonDisabled = true;
        void this.performUpdate();
    }
    #handleSubmit(ev) {
        ev.preventDefault();
        const input = this.#feedbackValue;
        if (!this.#currentRating || !input) {
            return;
        }
        if (this.message.entity === "model" /* ChatMessageEntity.MODEL */ && this.message.rpcId) {
            this.onFeedbackSubmit(this.message.rpcId, this.#currentRating, input);
        }
        this.#isShowingFeedbackForm = false;
        this.#isSubmitButtonDisabled = true;
        void this.performUpdate();
    }
}
async function makeTimelineRangeSummaryWidget(widgetData) {
    const { bounds, parsedTrace, track } = widgetData.data;
    let events = [];
    // Note: right now "main" is the only track we support, but in the future we
    // can imagine supporting more.
    if (track === 'main') {
        // To find the main thread, we first find the navigationId that is "active"
        // for the given bounds. We do this because the "main" thread can change on
        // navigations, so this is the most accurate way to find the main thread at
        // the timespan we are interested in.
        let navigationId;
        for (const nav of parsedTrace.data.Meta.mainFrameNavigations) {
            if (nav.ts <= bounds.min) {
                navigationId = nav.args.data?.navigationId;
            }
            else {
                break;
            }
        }
        const mainThread = AiAssistanceModel.AIQueries.AIQueries.findMainThread(navigationId, parsedTrace);
        if (mainThread) {
            events = mainThread.entries;
            AiAssistanceModel.Debug.debugLog(`TimelineRangeSummaryAiWidget found main thread. PID:`, mainThread.pid, 'TID:', mainThread.tid, 'Number of entries:', mainThread.entries.length);
        }
    }
    if (!events) {
        AiAssistanceModel.Debug.debugLog(`Warning: could not find events for TimelineRangeSummaryAiWidget`, widgetData);
        return null;
    }
    const thirdPartyTree = new Timeline.ThirdPartyTreeView.ThirdPartyTreeViewWidget();
    const mapper = Trace.EntityMapper.EntityMapper.getOrCreate(parsedTrace);
    thirdPartyTree.model = { selectedEvents: events, parsedTrace, entityMapper: mapper };
    thirdPartyTree.activeSelection = Timeline.TimelineSelection.selectionFromRangeMicroSeconds(bounds.min, bounds.max);
    thirdPartyTree.refreshTree(true);
    // clang-format off
    const template = html `
    <devtools-widget
      ${widget(TimelineComponents.TimelineRangeSummaryView.TimelineRangeSummaryView, {
        data: {
            parsedTrace,
            events,
            isInAIWidget: true,
            startTime: Trace.Helpers.Timing.microToMilli(bounds.min),
            endTime: Trace.Helpers.Timing.microToMilli(bounds.max),
            thirdPartyTreeTemplate: html `${widget(Timeline.ThirdPartyTreeView.ThirdPartyTreeViewWidget, {
                maxRows: 10,
                isInAIWidget: true,
                model: {
                    selectedEvents: thirdPartyTree.selectedEvents ?? null,
                    parsedTrace,
                    entityMapper: thirdPartyTree.entityMapper(),
                },
                activeSelection: { bounds },
                onBottomUpButtonClicked: (node) => {
                    void Common.Revealer.reveal(new TimelineUtils.Helpers.RevealableBottomUpProfile(bounds, node ?? undefined));
                },
            })}`,
        },
    })}
    ></devtools-widget>`;
    // clang-format on
    return {
        renderedWidget: template,
        revealable: new TimelineUtils.Helpers.RevealableTimeRange(bounds),
        accessibleRevealLabel: lockedString(UIStringsNotTranslate.revealPerformanceSummary),
        title: lockedString(UIStringsNotTranslate.performanceSummary),
        jslogContext: 'timeline-range-summary',
    };
}
async function makeNetworkTrackWidget(widgetData) {
    const { parsedTrace, bounds } = widgetData.data;
    const dataProvider = new Timeline.TimelineFlameChartNetworkDataProvider.TimelineFlameChartNetworkDataProvider();
    // clang-format off
    const template = html `
    <devtools-performance-agent-network-track
      .data=${{
        parsedTrace,
        bounds,
        dataProvider,
    }}
    ></devtools-performance-agent-network-track>`;
    // clang-format on
    return {
        renderedWidget: template,
        revealable: new TimelineUtils.Helpers.RevealableTimeRange(bounds),
        accessibleRevealLabel: lockedString(UIStringsNotTranslate.revealNetworkActivity),
        title: lockedString(UIStringsNotTranslate.networkActivitySummary),
        jslogContext: 'network-track-widget',
    };
}
async function makeLighthouseReportWidget(widgetData) {
    const reportEl = Lighthouse.LighthouseReportRenderer.LighthouseReportRenderer.renderLighthouseScores(widgetData.data.report);
    if (!reportEl) {
        return null;
    }
    const snapshotReport = widgetData.data.snapshotReport;
    return {
        renderedWidget: html `<div class="lighthouse-report-widget">${reportEl}</div>`,
        revealable: new Lighthouse.LighthousePanel.ActiveLighthouseReport(widgetData.data.report),
        accessibleRevealLabel: lockedString(UIStringsNotTranslate.revealLighthouse),
        title: lockedString(UIStringsNotTranslate.lighthouseReport),
        jslogContext: snapshotReport ? 'lighthouse-snapshot-report-widget' : 'lighthouse-report-widget',
    };
}
async function makeTimelineEventSummaryWidget(widgetData) {
    const renderedWidget = html `<devtools-widget class="timeline-event-summary-widget" ${widget(() => {
        return Timeline.TimelineDetailsView.TimelineDetailsPane.makeEventWidget(widgetData.data.event, widgetData.data.parsedTrace);
    })}></devtools-widget>`;
    return {
        renderedWidget,
        revealable: new SDK.TraceObject.RevealableEvent(widgetData.data.event),
        accessibleRevealLabel: lockedString(UIStringsNotTranslate.revealTimelineEventSummary),
        title: lockedString(UIStringsNotTranslate.timelineEventSummary),
        jslogContext: 'timeline-event-summary-widget',
    };
}
async function makeNetworkRequestGeneralHeadersWidget(widgetData) {
    const renderedWidget = html `<devtools-widget class="network-request-general-headers-widget" ${widget(() => {
        return Network.RequestHeadersView.RequestHeadersView.createGeneralHeadersView(widgetData.data.request);
    })}></devtools-widget>`;
    return {
        renderedWidget,
        revealable: NetworkForward.UIRequestLocation.UIRequestLocation.tab(widgetData.data.request, "headers-component" /* NetworkForward.UIRequestLocation.UIRequestTabs.HEADERS_COMPONENT */),
        accessibleRevealLabel: lockedString(UIStringsNotTranslate.revealNetworkRequest),
        title: lockedString(UIStringsNotTranslate.networkRequest),
        jslogContext: 'network-request-general-headers-widget',
    };
}
//# sourceMappingURL=ChatMessage.js.map