// Copyright 2026 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import * as i18n from '../../../core/i18n/i18n.js';
import * as Buttons from '../../../ui/components/buttons/buttons.js';
import * as Input from '../../../ui/components/input/input.js';
import type {MarkdownLitRenderer} from '../../../ui/components/markdown_view/MarkdownView.js';
import * as UI from '../../../ui/legacy/legacy.js';
import * as Lit from '../../../ui/lit/lit.js';
import chatMessageStyles from './chatMessage.css.js';
import {type ModelChatMessage, renderStep, type Step, titleForStep} from './ChatMessage.js';
import walkthroughViewStyles from './walkthroughView.css.js';
const lockedString = i18n.i18n.lockedString;
const {html, render} = Lit;
const UIStrings = {
/**
* @description Title for the close button in the walkthrough view.
*/
close: 'Close',
/**
* @description Title for the walkthrough view.
*/
title: 'Investigation steps',
/**
* @description Title for the button that shows the thinking process (walkthrough).
*/
showThinking: 'Show thinking',
} as const;
const str_ = i18n.i18n.registerUIStrings('panels/ai_assistance/components/WalkthroughView.ts', UIStrings);
const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_);
export interface ViewInput {
message: ModelChatMessage|null;
isLoading: boolean;
markdownRenderer: MarkdownLitRenderer;
isInlined: boolean;
isExpanded: boolean;
onToggle: (isOpen: boolean) => void;
}
export function walkthroughTitle(input: {
isLoading: boolean,
lastStep: Step,
}): string {
const title = input.isLoading ? titleForStep(input.lastStep) : lockedString(UIStrings.showThinking);
return title;
}
function renderInlineWalkthrough(input: ViewInput, stepsOutput: Lit.LitTemplate, steps: Step[]): Lit.LitTemplate {
const lastStep = steps.at(-1);
if (!input.isInlined || !lastStep) {
return Lit.nothing;
}
function onToggle(event: Event): void {
input.onToggle((event.target as HTMLDetailsElement).open);
}
// clang-format off
return html`
${input.isLoading ? html`` : Lit.nothing}
${walkthroughTitle({isLoading: input.isLoading, lastStep,})}
${stepsOutput}
`;
// clang-format on
}
function renderSidebarWalkthrough(input: ViewInput, stepsOutput: Lit.LitTemplate, stepsCount: number): Lit.LitTemplate {
if (input.isInlined) {
return Lit.nothing;
}
// clang-format off
return html`
${stepsOutput}
${stepsCount === 0 ? html`
No walkthrough steps available yet.
` : Lit.nothing}
`;
// clang-format on
}
export const DEFAULT_VIEW = (
input: ViewInput,
_output: null,
target: HTMLElement|DocumentFragment,
): void => {
const steps = input.message?.parts.filter(t => t.type === 'step')?.map(p => p.step) ?? [];
// clang-format off
const stepsOutput = steps.length > 0 ? html`
${steps.map((step, index) => html`
${renderStep({
step,
isLoading: input.isLoading,
markdownRenderer: input.markdownRenderer,
isLast: index === steps.length - 1
})}
`)}
` : Lit.nothing;
render(html`
${input.isInlined ? renderInlineWalkthrough(input, stepsOutput, steps)
: renderSidebarWalkthrough(input, stepsOutput, steps.length)
}`, target);
// clang-format on
};
export type View = typeof DEFAULT_VIEW;
export class WalkthroughView extends UI.Widget.Widget {
#view: View;
#message: ModelChatMessage|null = null;
// TODO(b/487921187): fix loading state - also unsure if we need this vs
// looking at the loading state in the message's steps.
#isLoading = false;
#markdownRenderer: MarkdownLitRenderer|null = null;
#onToggle: (isOpen: boolean) => void = () => {};
#isInlined = false;
#isExpanded = false;
constructor(element?: HTMLElement, view: View = DEFAULT_VIEW) {
super(element);
this.#view = view;
}
set isLoading(isLoading: boolean) {
this.#isLoading = isLoading;
this.requestUpdate();
}
get isLoading(): boolean {
return this.#isLoading;
}
get markdownRenderer(): MarkdownLitRenderer|null {
return this.#markdownRenderer;
}
set markdownRenderer(markdownRenderer: MarkdownLitRenderer|null) {
this.#markdownRenderer = markdownRenderer;
this.requestUpdate();
}
get message(): ModelChatMessage|null {
return this.#message;
}
set message(message: ModelChatMessage|null) {
this.#message = message;
this.requestUpdate();
}
set onToggle(onToggle: (isOpen: boolean) => void) {
this.#onToggle = onToggle;
this.requestUpdate();
}
set isInlined(isInlined: boolean) {
this.#isInlined = isInlined;
this.requestUpdate();
}
set isExpanded(isExpanded: boolean) {
this.#isExpanded = isExpanded;
this.requestUpdate();
}
override performUpdate(): void {
if (!this.#markdownRenderer) {
return;
}
this.#view(
{
isLoading: this.#isLoading,
markdownRenderer: this.#markdownRenderer,
onToggle: this.#onToggle,
isInlined: this.#isInlined,
isExpanded: this.#isExpanded,
message: this.#message,
},
null, this.contentElement);
}
}