feat(core): show ai-island and navigate to chat page if not available in sidebar (#13085)

close AI-318, AI-317

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Updated the AI chat button label to "AFFiNE Intelligence" and changed
its icon for improved clarity.
* Enhanced the AI chat button's placement in the sidebar for better
accessibility.
* Improved the AI chat button’s visibility and interaction logic based
on current view and sidebar state.
* **Style**
* Adjusted button styles to disable interaction when hidden, enhancing
user experience.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
Cats Juice
2025-07-08 21:17:28 +08:00
committed by GitHub
parent 0bd1f10498
commit e04d407b2f
3 changed files with 36 additions and 12 deletions

View File

@@ -18,7 +18,7 @@ import { useI18n } from '@affine/i18n';
import { track } from '@affine/track';
import type { Store } from '@blocksuite/affine/store';
import {
AiIcon,
AiOutlineIcon,
AllDocsIcon,
ImportIcon,
JournalIcon,
@@ -97,8 +97,8 @@ const AIChatButton = () => {
);
return (
<MenuLinkItem icon={<AiIcon />} active={aiChatActive} to={'/chat'}>
<span data-testid="ai-chat">Intelligent</span>
<MenuLinkItem icon={<AiOutlineIcon />} active={aiChatActive} to={'/chat'}>
<span data-testid="ai-chat">AFFiNE Intelligence</span>
</MenuLinkItem>
);
};
@@ -198,10 +198,10 @@ export const RootAppSidebar = memo((): ReactElement => {
/>
<AddPageButton />
</div>
<AIChatButton />
<AllDocsButton />
<AppSidebarJournalButton />
{sessionStatus === 'authenticated' && <NotificationButton />}
<AIChatButton />
<MenuItem
data-testid="slider-bar-workspace-setting-button"
icon={<SettingsIcon />}

View File

@@ -1,12 +1,17 @@
import { WorkbenchService } from '@affine/core/modules/workbench';
import { useLiveData, useService } from '@toeverything/infra';
import clsx from 'clsx';
import { useEffect, useState } from 'react';
import { useCallback, useEffect, useState } from 'react';
import { IslandContainer } from './container';
import { AIIcon } from './icons';
import { aiIslandBtn, aiIslandWrapper, toolStyle } from './styles.css';
const hideChat: Array<string | ((path: string) => boolean)> = [
'/chat',
path => path.includes('attachments'),
];
export const AIIsland = () => {
// to make sure ai island is hidden first and animate in
const [hide, setHide] = useState(true);
@@ -16,12 +21,33 @@ export const AIIsland = () => {
const haveChatTab = useLiveData(
activeView.sidebarTabs$.map(tabs => tabs.some(t => t.id === 'chat'))
);
const activeLocation = useLiveData(activeView.location$);
const activeTab = useLiveData(activeView.activeSidebarTab$);
const sidebarOpen = useLiveData(workbench.sidebarOpen$);
useEffect(() => {
setHide((sidebarOpen && activeTab?.id === 'chat') || !haveChatTab);
}, [activeTab, haveChatTab, sidebarOpen]);
let hide = true;
if (haveChatTab) {
hide = !!sidebarOpen && activeTab?.id === 'chat';
} else {
const path = activeLocation.pathname;
hide = hideChat.some(item =>
typeof item === 'string' ? path === item : item(path)
);
}
setHide(hide);
}, [activeLocation.pathname, activeTab, haveChatTab, sidebarOpen]);
const onOpenChat = useCallback(() => {
if (hide) return;
if (haveChatTab) {
workbench.openSidebar();
activeView.activeSidebarTab('chat');
} else {
workbench.open('/chat');
workbench.closeSidebar();
}
}, [activeView, haveChatTab, hide, workbench]);
return (
<IslandContainer className={clsx(toolStyle, { hide })}>
@@ -29,11 +55,7 @@ export const AIIsland = () => {
<button
className={aiIslandBtn}
data-testid="ai-island"
onClick={() => {
if (hide) return;
workbench.openSidebar();
activeView.activeSidebarTab('chat');
}}
onClick={onOpenChat}
>
<AIIcon />
</button>

View File

@@ -57,6 +57,8 @@ export const rightSidebarButton = style({
opacity: 0,
maxWidth: 0,
marginLeft: 0,
// prevent click event from being triggered
pointerEvents: 'none',
},
},
});