Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

AI Chat becomes a trusted WebUI with an UntrustedWebUI frame for LLM-generated responses #26855

Merged
merged 32 commits into from
Dec 12, 2024

Conversation

petemill
Copy link
Member

@petemill petemill commented Dec 4, 2024

Issues

Description

  • changes Leo Url to chrome://leo-ai
  • untrusted frame content Url is chrome-untrusted://leo-ai-conversation-entries/[conversation-uuid]

This was requested by the security team in order to make sure the API footprint exposed to a renderer which is renderering LLM-generated content is as small as possible.

The two frames use the same JS build to optimize bundle size, but do not share the same allowed mojom interfaces. The untrusted frame is limited to UntrustedConversationHandler to send calls to the browser and UntrustedUI to receive calls from the browser.

The frames need to communicate with each other in order to solve the main hassle of using an iframe: layout. An iframe is always a fixed width and height, decided by its host and will not expand to the child body size.

Since the iframed content is just part of the scroll area of a Conversation, we must tell the parent whenever the body size changes. Further complicating the situation is continuously knowing when the latest part of a conversation response is rendered, so that we can scroll the container to show it.

The inter-frame communication is done via mojom directly, initially facilitated by the C++ WebUI handlers.

TODO:

  • ratings
  • storybook fix to not use iframe
  • nice fade to avoid content jumping in height as iframe works out how big it needs to be
  • scroll to bottom still not working
  • organize mojom file, split some interfaces to a new file
    • Will do more of this in a follow-up as it would end up making review more difficult

Resolves

Submitter Checklist:

  • I confirm that no security/privacy review is needed and no other type of reviews are needed, or that I have requested them
  • There is a ticket for my issue
  • Used Github auto-closing keywords in the PR description above
  • Wrote a good PR/commit description
  • Squashed any review feedback or "fixup" commits before merge, so that history is a record of what happened in the repo, not your PR
  • Added appropriate labels (QA/Yes or QA/No; release-notes/include or release-notes/exclude; OS/...) to the associated issue
  • Checked the PR locally:
    • npm run test -- brave_browser_tests, npm run test -- brave_unit_tests wiki
    • npm run presubmit wiki, npm run gn_check, npm run tslint
  • Ran git rebase master (if needed)

Reviewer Checklist:

  • A security review is not needed, or a link to one is included in the PR description
  • New files have MPL-2.0 license header
  • Adequate test coverage exists to prevent regressions
  • Major classes, functions and non-trivial code blocks are well-commented
  • Changes in component dependencies are properly reflected in gn
  • Code follows the style guide
  • Test plan is specified in PR before merging

After-merge Checklist:

Test Plan:

@petemill petemill self-assigned this Dec 4, 2024
@petemill petemill force-pushed the ai-chat-untrusted-frame branch 3 times, most recently from 890fbf6 to 2fa7d02 Compare December 4, 2024 08:20
}

public addStateChangeListener(callback: (event: Event) => void) {
this.eventTarget.addEventListener('uistatechange', callback)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit:

Suggested change
this.eventTarget.addEventListener('uistatechange', callback)
this.eventTarget.addEventListener('statechange', callback)

import * as React from 'react'
import API from './api'

export default function useAPIState<T, Y>(api: API<Y>, defaultState: T) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 this looks great, I am 100% on board

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe worth

Suggested change
export default function useAPIState<T, Y>(api: API<Y>, defaultState: T) {
export default function useAPIState<T extends Y, Y>(api: API<Y>, defaultState: T) {

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, can't this just be:

export default function useAPIState<T>(api: API<T>, defaultState: T) {

@petemill petemill force-pushed the ai-chat-untrusted-frame branch from 8cf1be5 to efb3d9b Compare December 6, 2024 10:05
@github-actions github-actions bot added the CI/storybook-url Deploy storybook and provide a unique URL for each build label Dec 6, 2024
@petemill
Copy link
Member Author

petemill commented Dec 6, 2024

Issue and Security review incoming...

@petemill petemill marked this pull request as ready for review December 6, 2024 10:05
@petemill petemill requested review from a team and bridiver as code owners December 6, 2024 10:05
Comment on lines +481 to 508
// Browser-side handler for a Conversation's UI responsible for displaying
// untrusted content (e.g. content generated by the AI engine).
interface UntrustedConversationHandler {
BindUntrustedConversationUI(
pending_remote<UntrustedConversationUI> untrusted_ui)
=> (ConversationEntriesState conversation_entries_state);

// Get all visible history entries, including in-progress responses
GetConversationHistory() => (array<ConversationTurn> conversation_history);

ModifyConversation(uint32 turn_index, string new_text);
};

// Untrusted-UI-side handler for a Conversation, responsible for displaying
// content generated by the AI engine.
interface UntrustedConversationUI {
// TODO(petemill): Provide single entry that's been updated so that we don't
// need to fetch (and clone) all conversation entries each time text is added
// to the most recent entry.
OnConversationHistoryUpdate();
OnEntriesUIStateChanged(ConversationEntriesState state);
OnFaviconImageDataChanged();
};
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the API exposed to the untrusted frame from the Conversation

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not sure if it's a good idea to mix them in the same mojom file? Are there other examples of doing this?

Comment on lines 12 to 31
interface ParentUIFrame {
// <iframe> cannot be sized to the natural height of its content, so the child
// must let the parent know whenever the body height changes so that the parent
// can manually set the same height on the <iframe> to avoid scrolling.
ChildHeightChanged(uint32 height);

// This is sent to the UI rather than straight to the browser to allow
// for further rating feedback to be attached. The parent frame deals
// with it instead of the child frame, due to UI space as well as sensitive
// user information being captured.
RateMessage(string turn_uuid, bool is_liked);
};

// Browser-side handler for untrusted frame that handles rendering of
// conversation entries.
interface UntrustedUIHandler {
OpenSearchURL(string query);
OpenLearnMoreAboutBraveSearchWithLeo();
BindParentPage(pending_receiver<ParentUIFrame> parent_frame);
};
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These are the API calls that the Untrusted frame can make to the C++ UI class and the Javascript parent frame

Comment on lines +412 to +432
// State required to show the conversations entries UI block
struct ConversationEntriesState {
// Whether an answer generation is in progress
bool is_generating;
// Whether the current model is a built-in Leo model
bool is_leo_model;
// How much of the content has been used by the AI engine, percentage,or null
// if no content is associated.
uint32? content_used_percentage;
// Whether the content has been refined
bool is_content_refined;
// Whether the UI should represent that the user cannot submit new messages
// or edits to the conversation.
bool can_submit_user_entries;
};

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the data, in addition to the conversation history itself, that the untrusted frame receives

Comment on lines 147 to 169
source->OverrideContentSecurityPolicy(
network::mojom::CSPDirectiveName::ScriptSrc,
"script-src 'self' chrome-untrusted://resources;");
source->OverrideContentSecurityPolicy(
network::mojom::CSPDirectiveName::StyleSrc,
"style-src 'self' 'unsafe-inline' chrome-untrusted://resources;");
source->OverrideContentSecurityPolicy(
network::mojom::CSPDirectiveName::ImgSrc,
"img-src 'self' blob: chrome-untrusted://resources;");
source->OverrideContentSecurityPolicy(
network::mojom::CSPDirectiveName::FontSrc,
"font-src 'self' data: chrome-untrusted://resources;");
source->OverrideContentSecurityPolicy(
network::mojom::CSPDirectiveName::FrameAncestors,
base::StringPrintf("frame-ancestors %s;", kAIChatUIURL));
source->OverrideContentSecurityPolicy(
network::mojom::CSPDirectiveName::TrustedTypes, "trusted-types default;");
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CSP for the untrusted frame

Comment on lines 93 to 102
source->OverrideContentSecurityPolicy(
network::mojom::CSPDirectiveName::ScriptSrc,
"script-src 'self' chrome-untrusted://resources;");
untrusted_source->OverrideContentSecurityPolicy(
"script-src 'self' chrome://resources;");
source->OverrideContentSecurityPolicy(
network::mojom::CSPDirectiveName::StyleSrc,
"style-src 'self' 'unsafe-inline' chrome-untrusted://resources;");
untrusted_source->OverrideContentSecurityPolicy(
"style-src 'self' 'unsafe-inline' chrome://resources;");
source->OverrideContentSecurityPolicy(
network::mojom::CSPDirectiveName::ImgSrc,
"img-src 'self' blob: chrome-untrusted://resources;");
untrusted_source->OverrideContentSecurityPolicy(
"img-src 'self' blob: chrome://resources chrome://favicon2;");
source->OverrideContentSecurityPolicy(
network::mojom::CSPDirectiveName::FontSrc,
"font-src 'self' data: chrome-untrusted://resources;");
"font-src 'self' data: chrome://resources;");
source->OverrideContentSecurityPolicy(
network::mojom::CSPDirectiveName::ChildSrc,
base::StringPrintf("child-src %s;", kAIChatUntrustedConversationUIURL));
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CSP changes for the trusted parent frame

Comment on lines +82 to +87
inline constexpr char kAIChatUIURL[] = "chrome://leo-ai/";
inline constexpr char kAIChatUIHost[] = "leo-ai";
inline constexpr char kAIChatUntrustedConversationUIURL[] =
"chrome-untrusted://leo-ai-conversation-entries/";
inline constexpr char kAIChatUntrustedConversationUIHost[] =
"leo-ai-conversation-entries";
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

new URLs

@@ -5,9 +5,9 @@

module ai_chat.mojom;

import "brave/components/ai_chat/core/common/mojom/untrusted_frame.mojom";
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This file can be split up further. I started doing it but stashed the changes for now because I didn't want to further bloat this PR.

Comment on lines 469 to +479
mojo::ReceiverSet<mojom::ConversationHandler> receivers_;
// TODO(petemill): Rename to ConversationUIHandler
mojo::ReceiverSet<mojom::UntrustedConversationHandler> untrusted_receivers_;
mojo::RemoteSet<mojom::ConversationUI> conversation_ui_handlers_;
mojo::RemoteSet<mojom::UntrustedConversationUI>
untrusted_conversation_ui_handlers_;
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ConversationHandler has to manage receiver and remote for each parent trusted webui and child untrusted webui

@petemill petemill force-pushed the ai-chat-untrusted-frame branch from ce3b00c to 84b6868 Compare December 12, 2024 03:52
Copy link
Contributor

[puLL-Merge] - brave/brave-core@26855

Description

This PR introduces significant changes to the AI Chat feature in the Brave browser. It restructures the UI components, adds new functionality, and improves the overall architecture of the AI Chat system. The changes include splitting the UI into trusted and untrusted parts, implementing a new conversation entries frame, and updating the API and state management for both the main UI and the conversation entries.

Changes

Changes

  1. browser/ai_chat/:

    • Added new files ai_chat_urls.cc and ai_chat_urls.h to handle URL-related functions for AI Chat.
    • Updated ai_chat_throttle_unittest.cc to use new URL constants.
  2. browser/ui/webui/ai_chat/:

    • Added ai_chat_untrusted_conversation_ui.cc and ai_chat_untrusted_conversation_ui.h to implement the untrusted conversation UI.
    • Updated ai_chat_ui.cc and ai_chat_ui.h to accommodate new changes and integrate with the untrusted conversation UI.
  3. components/ai_chat/:

    • Significant updates to conversation_handler.cc and conversation_handler.h to support new functionality and integrate with the untrusted conversation UI.
    • Updates to associated_content_driver.cc and associated_content_driver.h to handle title changes.
    • Changes to features.cc and features.h to add new feature flags.
  4. components/ai_chat/resources/:

    • Restructured the resources directory, moving common components and adding new files for the untrusted conversation frame.
    • Added new React components and updated existing ones to support the new UI structure.
  5. ios/:

    • Updated iOS-specific files to align with the new API changes, particularly in handling conversation ratings.
sequenceDiagram
    participant User
    participant TrustedUI
    participant UntrustedUI
    participant ConversationHandler
    participant AIService

    User->>TrustedUI: Interact with AI Chat
    TrustedUI->>UntrustedUI: Load conversation entries
    UntrustedUI->>ConversationHandler: Request conversation history
    ConversationHandler->>AIService: Get conversation data
    AIService-->>ConversationHandler: Return conversation data
    ConversationHandler-->>UntrustedUI: Send conversation history
    UntrustedUI-->>TrustedUI: Update UI with conversation entries
    User->>UntrustedUI: Rate a message
    UntrustedUI->>TrustedUI: Notify of rating
    TrustedUI->>ConversationHandler: Submit rating
    ConversationHandler->>AIService: Process rating
    AIService-->>ConversationHandler: Confirm rating
    ConversationHandler-->>TrustedUI: Update UI with rating result
Loading

Possible Issues

  1. The separation of trusted and untrusted UI components may introduce complexity in state management and communication between different parts of the application.
  2. The new URL structure and handling might require updates in other parts of the codebase that are not covered in this PR.

Security Hotspots

  1. The introduction of an untrusted conversation frame (ai_chat_untrusted_conversation_ui) requires careful review to ensure proper isolation and security measures are in place.
  2. The new message rating system in conversation_handler.cc should be reviewed to ensure it doesn't introduce any vulnerabilities in handling user input.

petemill added a commit that referenced this pull request Dec 12, 2024
- [AIChat conversation data storage by petemill · Pull Request \#25876 · brave/brave-core](#25876)
  - [AI Chat conversations should persist browser restart · Issue \#42800 · brave/brave-browser](brave/brave-browser#42800) `QA/Yes`
- [\[AIChat\]: Add URL based routing for different chats by fallaciousreasoning · Pull Request \#26050 · brave/brave-core](#26050)
  - [\[AI Chat\]: URLs for all conversations · Issue \#42055 · brave/brave-browser](brave/brave-browser#42055) `QA/Yes`
    - test plan: url and header variations specified
- [AI Chat fullpage UI notices and polish by petemill · Pull Request \#26678 · brave/brave-core](#26678)
  - [AI Chat conversation list should prompt to enable history storage if it's disabled · Issue \#42576 · brave/brave-browser](brave/brave-browser#42576) `QA/Yes`
    - test plan: specified
  - [AI Chat notice for conversation storage · Issue \#42360 · brave/brave-browser](brave/brave-browser#42360) `QA/Yes`
    - test plan: specified
  - [AI Chat FullPage shouldn't show in SidePanel mode when restored at startup · Issue \#42413 · brave/brave-browser](brave/brave-browser#42413) `QA/Yes`
    - test plan: specified
- [AI Chat becomes a trusted WebUI with an UntrustedWebUI frame for LLM-generated responses by petemill · Pull Request \#26855 · brave/brave-core](#26855)
  - [Change AI Chat url to chrome://leo-ai · Issue \#42817 · brave/brave-browser](brave/brave-browser#42817) `QA/Yes`
  - [AI Chat conversation entries should be isolated in an untrusted frame · Issue \#42818 · brave/brave-browser](brave/brave-browser#42818) `QA/No`
- [\[AI Chat\]: Update copy button label by fallaciousreasoning · Pull Request \#26422 · brave/brave-core](#26422)
  - [Change copy button name to text if text in code block · Issue \#42117 · brave/brave-browser](brave/brave-browser#42117) `QA/Yes`
- [\[AI Chat\]: Conversation starter pack, for unassociated content by fallaciousreasoning · Pull Request \#26379 · brave/brave-core](#26379)
  - [\[AI Chat\]: Add support for static conversation starters · Issue \#42106 · brave/brave-browser](brave/brave-browser#42106) `QA/Yes`
    - test plan: needs improvement
- [Css tweaks and icon change for sidebar by aguscruiz · Pull Request \#26450 · brave/brave-core](#26450)
  - [\[Leo full page\] - Tweak to expand/collapse sidebar icons · Issue \#42068 · brave/brave-browser](brave/brave-browser#42068) `QA/Yes`
    - test plan: specified (check icons are correct)
- [\[AI Chat\]: Update styling on suggestions by fallaciousreasoning · Pull Request \#26565 · brave/brave-core](#26565)
  - [\[AI Chat\]: Update suggestions style to match new design · Issue \#42107 · brave/brave-browser](brave/brave-browser#42107) `QA/Yes`
    - test plan: specified (check suggestions have new style)
- [\[AI Chat\]: Don't show starter suggestions on non-empty chats by fallaciousreasoning · Pull Request \#26677 · brave/brave-core](#26677)
  - [AI Chat static conversation starters shouldn't show if conversation has chat history · Issue \#42412 · brave/brave-browser](brave/brave-browser#42412) `QA/Yes`
    - test plan: specified (verify conversation starters only show when applicable)
- [fix hit area for leo conversations list by aguscruiz · Pull Request \#26793 · brave/brave-core](#26793)
  - [Leo full page - Conversation list - Make the whole item clickable instead of excluding the padding · Issue \#42552 · brave/brave-browser](brave/brave-browser#42552) `QA/No`
- [\[Nala / @brave/leo\] update dependency by petemill · Pull Request \#26767 · brave/brave-core](#26767)
  - brave/brave-browser#42545 `QA/No`
petemill added a commit that referenced this pull request Dec 12, 2024
- [AIChat conversation data storage by petemill · Pull Request \#25876 · brave/brave-core](#25876)
  - [AI Chat conversations should persist browser restart · Issue \#42800 · brave/brave-browser](brave/brave-browser#42800) `QA/Yes`
- [\[AIChat\]: Add URL based routing for different chats by fallaciousreasoning · Pull Request \#26050 · brave/brave-core](#26050)
  - [\[AI Chat\]: URLs for all conversations · Issue \#42055 · brave/brave-browser](brave/brave-browser#42055) `QA/Yes`
    - test plan: url and header variations specified
- [AI Chat fullpage UI notices and polish by petemill · Pull Request \#26678 · brave/brave-core](#26678)
  - [AI Chat conversation list should prompt to enable history storage if it's disabled · Issue \#42576 · brave/brave-browser](brave/brave-browser#42576) `QA/Yes`
    - test plan: specified
  - [AI Chat notice for conversation storage · Issue \#42360 · brave/brave-browser](brave/brave-browser#42360) `QA/Yes`
    - test plan: specified
  - [AI Chat FullPage shouldn't show in SidePanel mode when restored at startup · Issue \#42413 · brave/brave-browser](brave/brave-browser#42413) `QA/Yes`
    - test plan: specified
- [AI Chat becomes a trusted WebUI with an UntrustedWebUI frame for LLM-generated responses by petemill · Pull Request \#26855 · brave/brave-core](#26855)
  - [Change AI Chat url to chrome://leo-ai · Issue \#42817 · brave/brave-browser](brave/brave-browser#42817) `QA/Yes`
  - [AI Chat conversation entries should be isolated in an untrusted frame · Issue \#42818 · brave/brave-browser](brave/brave-browser#42818) `QA/No`
- [\[AI Chat\]: Update copy button label by fallaciousreasoning · Pull Request \#26422 · brave/brave-core](#26422)
  - [Change copy button name to text if text in code block · Issue \#42117 · brave/brave-browser](brave/brave-browser#42117) `QA/Yes`
- [\[AI Chat\]: Conversation starter pack, for unassociated content by fallaciousreasoning · Pull Request \#26379 · brave/brave-core](#26379)
  - [\[AI Chat\]: Add support for static conversation starters · Issue \#42106 · brave/brave-browser](brave/brave-browser#42106) `QA/Yes`
    - test plan: needs improvement
- [Css tweaks and icon change for sidebar by aguscruiz · Pull Request \#26450 · brave/brave-core](#26450)
  - [\[Leo full page\] - Tweak to expand/collapse sidebar icons · Issue \#42068 · brave/brave-browser](brave/brave-browser#42068) `QA/Yes`
    - test plan: specified (check icons are correct)
- [\[AI Chat\]: Update styling on suggestions by fallaciousreasoning · Pull Request \#26565 · brave/brave-core](#26565)
  - [\[AI Chat\]: Update suggestions style to match new design · Issue \#42107 · brave/brave-browser](brave/brave-browser#42107) `QA/Yes`
    - test plan: specified (check suggestions have new style)
- [\[AI Chat\]: Don't show starter suggestions on non-empty chats by fallaciousreasoning · Pull Request \#26677 · brave/brave-core](#26677)
  - [AI Chat static conversation starters shouldn't show if conversation has chat history · Issue \#42412 · brave/brave-browser](brave/brave-browser#42412) `QA/Yes`
    - test plan: specified (verify conversation starters only show when applicable)
- [fix hit area for leo conversations list by aguscruiz · Pull Request \#26793 · brave/brave-core](#26793)
  - [Leo full page - Conversation list - Make the whole item clickable instead of excluding the padding · Issue \#42552 · brave/brave-browser](brave/brave-browser#42552) `QA/No`
- [\[Nala / @brave/leo\] update dependency by petemill · Pull Request \#26767 · brave/brave-core](#26767)
  - brave/brave-browser#42545 `QA/No`
@petemill petemill enabled auto-merge (squash) December 12, 2024 06:08
@petemill petemill merged commit 2fc11a2 into master Dec 12, 2024
18 checks passed
@petemill petemill deleted the ai-chat-untrusted-frame branch December 12, 2024 06:42
@github-actions github-actions bot added this to the 1.75.x - Nightly milestone Dec 12, 2024
@brave-builds
Copy link
Collaborator

Released in v1.75.93

petemill added a commit that referenced this pull request Dec 12, 2024
- [AIChat conversation data storage by petemill · Pull Request \#25876 · brave/brave-core](#25876)
  - [AI Chat conversations should persist browser restart · Issue \#42800 · brave/brave-browser](brave/brave-browser#42800) `QA/Yes`
- [\[AIChat\]: Add URL based routing for different chats by fallaciousreasoning · Pull Request \#26050 · brave/brave-core](#26050)
  - [\[AI Chat\]: URLs for all conversations · Issue \#42055 · brave/brave-browser](brave/brave-browser#42055) `QA/Yes`
    - test plan: url and header variations specified
- [AI Chat fullpage UI notices and polish by petemill · Pull Request \#26678 · brave/brave-core](#26678)
  - [AI Chat conversation list should prompt to enable history storage if it's disabled · Issue \#42576 · brave/brave-browser](brave/brave-browser#42576) `QA/Yes`
    - test plan: specified
  - [AI Chat notice for conversation storage · Issue \#42360 · brave/brave-browser](brave/brave-browser#42360) `QA/Yes`
    - test plan: specified
  - [AI Chat FullPage shouldn't show in SidePanel mode when restored at startup · Issue \#42413 · brave/brave-browser](brave/brave-browser#42413) `QA/Yes`
    - test plan: specified
- [AI Chat becomes a trusted WebUI with an UntrustedWebUI frame for LLM-generated responses by petemill · Pull Request \#26855 · brave/brave-core](#26855)
  - [Change AI Chat url to chrome://leo-ai · Issue \#42817 · brave/brave-browser](brave/brave-browser#42817) `QA/Yes`
  - [AI Chat conversation entries should be isolated in an untrusted frame · Issue \#42818 · brave/brave-browser](brave/brave-browser#42818) `QA/No`
- [\[AI Chat\]: Update copy button label by fallaciousreasoning · Pull Request \#26422 · brave/brave-core](#26422)
  - [Change copy button name to text if text in code block · Issue \#42117 · brave/brave-browser](brave/brave-browser#42117) `QA/Yes`
- [\[AI Chat\]: Conversation starter pack, for unassociated content by fallaciousreasoning · Pull Request \#26379 · brave/brave-core](#26379)
  - [\[AI Chat\]: Add support for static conversation starters · Issue \#42106 · brave/brave-browser](brave/brave-browser#42106) `QA/Yes`
    - test plan: needs improvement
- [Css tweaks and icon change for sidebar by aguscruiz · Pull Request \#26450 · brave/brave-core](#26450)
  - [\[Leo full page\] - Tweak to expand/collapse sidebar icons · Issue \#42068 · brave/brave-browser](brave/brave-browser#42068) `QA/Yes`
    - test plan: specified (check icons are correct)
- [\[AI Chat\]: Update styling on suggestions by fallaciousreasoning · Pull Request \#26565 · brave/brave-core](#26565)
  - [\[AI Chat\]: Update suggestions style to match new design · Issue \#42107 · brave/brave-browser](brave/brave-browser#42107) `QA/Yes`
    - test plan: specified (check suggestions have new style)
- [\[AI Chat\]: Don't show starter suggestions on non-empty chats by fallaciousreasoning · Pull Request \#26677 · brave/brave-core](#26677)
  - [AI Chat static conversation starters shouldn't show if conversation has chat history · Issue \#42412 · brave/brave-browser](brave/brave-browser#42412) `QA/Yes`
    - test plan: specified (verify conversation starters only show when applicable)
- [fix hit area for leo conversations list by aguscruiz · Pull Request \#26793 · brave/brave-core](#26793)
  - [Leo full page - Conversation list - Make the whole item clickable instead of excluding the padding · Issue \#42552 · brave/brave-browser](brave/brave-browser#42552) `QA/No`
- [\[Nala / @brave/leo\] update dependency by petemill · Pull Request \#26767 · brave/brave-core](#26767)
  - brave/brave-browser#42545 `QA/No`
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
CI/storybook-url Deploy storybook and provide a unique URL for each build puLL-Merge
Projects
None yet