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]: Add UI for Tab Informer #27220

Open
wants to merge 2 commits into
base: aichat-tabsearch
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions browser/ui/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,8 @@ source_set("ui") {
# It doesn't make sense to view the webcompat webui on iOS & Android.
if (!is_android && !is_ios) {
sources += [
"ai_chat/tab_informer.cc",
"ai_chat/tab_informer.h",
"webui/webcompat_reporter/webcompat_reporter_ui.cc",
"webui/webcompat_reporter/webcompat_reporter_ui.h",
]
Expand Down
4 changes: 4 additions & 0 deletions components/ai_chat/core/browser/constants.cc
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,10 @@ base::span<const webui::LocalizedString> GetLocalizedStrings() {
{"searchQueries", IDS_CHAT_UI_SEARCH_QUERIES},
{"learnMore", IDS_CHAT_UI_LEARN_MORE},
{"closeNotice", IDS_CHAT_UI_CLOSE_NOTICE},
{"attachmentsTitle", IDS_CHAT_UI_ATTACHMENTS_TITLE},
{"attachmentsDescription", IDS_CHAT_UI_ATTACHMENTS_DESCRIPTION},
{"attachmentsBrowserTabsTitle",
IDS_CHAT_UI_ATTACHMENTS_BROWSER_TABS_TITLE},
{"noticeConversationHistoryBody",
IDS_CHAT_UI_NOTICE_CONVERSATION_HISTORY_BODY},
{"noticeConversationHistoryEmpty",
Expand Down
56 changes: 56 additions & 0 deletions components/ai_chat/resources/page/components/attachments/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// Copyright (c) 2025 The Brave Authors. All rights reserved.
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at https://mozilla.org/MPL/2.0/.
import * as React from 'react'
import styles from './style.module.scss'
import Button from '@brave/leo/react/button'
import Icon from '@brave/leo/react/icon'
import Input from '@brave/leo/react/input'
import RadioButton from '@brave/leo/react/radioButton'
import Flex from '$web-common/Flex'
import { useAIChat } from '../../state/ai_chat_context'
import { Tab } from 'components/ai_chat/resources/common/mojom'
import { useConversation } from '../../state/conversation_context'
import { getLocale } from '$web-common/locale'

function TabItem({ tab }: { tab: Tab }) {
const aiChat = useAIChat()
const { conversationUuid, associatedContentInfo } = useConversation()
return <RadioButton className={styles.tabItem} currentValue={associatedContentInfo?.contentId} value={tab.contentId} name='tab-item' onChange={() => {
aiChat.uiHandler?.associateTab(tab, conversationUuid!)
}}>
<span className={styles.title}>{tab.title}</span>
<img className={styles.icon} src={`chrome://favicon2/?size=20&pageUrl=${encodeURIComponent(tab.url.url)}`} />
</RadioButton>
}

export default function Attachments() {
const aiChat = useAIChat()
const conversation = useConversation()
const [search, setSearch] = React.useState('')

const tabs = aiChat.windows.flatMap(w => w.tabs).filter(t => t.title.toLowerCase().includes(search.toLowerCase()))
return <div className={styles.root}>
<div className={styles.header}>
<Flex direction='row' justify='space-between' align='center'>
<h4>{getLocale('attachmentsTitle')}</h4>
<Button fab kind='plain-faint' size='small' onClick={() => conversation.setShowAttachments(false)}>
<Icon name='close' />
</Button>
</Flex>
<span className={styles.description}>{getLocale('attachmentsDescription')}</span>
</div>
<div className={styles.tabSearchContainer}>
<Flex direction='row' justify='space-between' align='center'>
<h5>{getLocale('attachmentsBrowserTabsTitle')}</h5>
</Flex>
<Input placeholder='Search tabs' value={search} onInput={e => setSearch(e.value)}>
<Icon name='search' slot='icon-after' />
</Input>
<div className={styles.tabList}>
{tabs.map(t => <TabItem key={t.id} tab={t} />)}
</div>
</div>
</div>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
.root {
background: var(--leo-color-container-highlight);
padding: var(--leo-spacing-m);
margin-top: var(--leo-spacing-m);
border-radius: var(--leo-radius-l);
box-shadow: var(--leo-effect-elevation-01);

& leo-button[fab] {
flex: 0;
}

& h4 {
margin: 0;
color: var(--leo-color-text-secondary);
font: var(--leo-font-heading-h4);
}

& h5 {
margin: 0;
color: var(--leo-color-text-secondary);
font: var(--leo-font-default-semibold);
}
}

.header {
padding: var(--leo-spacing-l) var(--leo-spacing-xl);
}

.tabSearchContainer {
background: var(--leo-color-container-background);
padding: var(--leo-spacing-l) var(--leo-spacing-xl);
border-radius: var(--leo-radius-l);
display: flex;
flex-direction: column;
gap: var(--leo-spacing-m);
}

.tabList {
display: flex;
flex-direction: column;
gap: var(--leo-spacing-s);

border: 1px solid var(--leo-color-divider-subtle);
border-radius: var(--leo-radius-m);

padding: var(--leo-spacing-m);
}

.description {
font: var(--leo-font-small-regular);
color: var(--leo-color-text-tertiary);
}

.tabItem {
--leo-radiobutton-flex-direction: row-reverse;

padding: var(--leo-spacing-m);
display: flex;
gap: var(--leo-spacing-m);
align-items: center;
justify-content: space-between;

& .icon {
flex-shrink: 0;
}

& .title {
text-wrap: nowrap;
text-overflow: ellipsis;
overflow: hidden;
flex: 1;

color: var(--leo-color-text-primary);
font: var(--leo-font-default-regular);
}
}
13 changes: 12 additions & 1 deletion components/ai_chat/resources/page/components/header/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import getAPI from '../../api'
import FeatureButtonMenu, { Props as FeatureButtonMenuProps } from '../feature_button_menu'
import styles from './style.module.scss'
import { useAIChat, useIsSmall } from '../../state/ai_chat_context'
import { useConversation } from '../../state/conversation_context'
import { useConversation, useSupportsAttachments } from '../../state/conversation_context'
import { getLocale } from '$web-common/locale'
import { tabAssociatedChatId, useActiveChat } from '../../state/active_chat_context'

Expand Down Expand Up @@ -42,6 +42,8 @@ export const ConversationHeader = React.forwardRef(function (props: FeatureButto
const activeConversation = aiChatContext.visibleConversations.find(c => c.uuid === conversationContext.conversationUuid)
const showTitle = (!isTabAssociated || aiChatContext.isStandalone) && !isMobile
const canShowFullScreenButton = aiChatContext.isHistoryFeatureEnabled && !isMobile && !aiChatContext.isStandalone && conversationContext.conversationUuid
const supportsAttachments = useSupportsAttachments()

return (
<div className={styles.header} ref={ref}>
{showTitle ? (
Expand Down Expand Up @@ -97,6 +99,15 @@ export const ConversationHeader = React.forwardRef(function (props: FeatureButto
>
<Icon name='expand' />
</Button>}
{supportsAttachments && <Button
fab
kind={conversationContext.showAttachments ? 'plain' : 'plain-faint'}
aria-label={getLocale('attachmentsTitle')}
title={getLocale('attachmentsTitle')}
onClick={() => conversationContext.setShowAttachments(!conversationContext.showAttachments)}
>
<Icon name='attachment' />
</Button>}
<FeatureButtonMenu {...props} />
{!aiChatContext.isStandalone &&
<Button
Expand Down
Loading
Loading