diff --git a/packages/frontend/core/src/blocksuite/ai/chat-panel/components/add-popover.ts b/packages/frontend/core/src/blocksuite/ai/chat-panel/components/add-popover.ts index 07ca99a7a7..dffe8fc4b9 100644 --- a/packages/frontend/core/src/blocksuite/ai/chat-panel/components/add-popover.ts +++ b/packages/frontend/core/src/blocksuite/ai/chat-panel/components/add-popover.ts @@ -1,24 +1,50 @@ import { toast } from '@affine/component'; import { ShadowlessElement } from '@blocksuite/affine/block-std'; -import { type LinkedMenuGroup } from '@blocksuite/affine/blocks/root'; import { SignalWatcher, WithDisposable } from '@blocksuite/affine/global/lit'; import { scrollbarStyle } from '@blocksuite/affine/shared/styles'; -import { openFileOrFiles } from '@blocksuite/affine/shared/utils'; -import { SearchIcon, UploadIcon } from '@blocksuite/icons/lit'; +import { openFileOrFiles, type Signal } from '@blocksuite/affine/shared/utils'; +import { + CollectionsIcon, + SearchIcon, + TagsIcon, + UploadIcon, +} from '@blocksuite/icons/lit'; import type { DocMeta } from '@blocksuite/store'; -import { css, html } from 'lit'; +import { css, html, type TemplateResult } from 'lit'; import { property, query, state } from 'lit/decorators.js'; import type { DocSearchMenuConfig } from '../chat-config'; import type { ChatChip } from '../chat-context'; +enum AddPopoverMode { + Default = 'default', + Tags = 'tags', + Collections = 'collections', +} + +export type MenuGroup = { + name: string; + items: MenuItem[] | Signal; + maxDisplay?: number; +}; + +export type MenuItem = { + key: string; + name: string | TemplateResult<1>; + icon: TemplateResult<1>; + action: MenuAction; + suffix?: string | TemplateResult<1>; +}; + +export type MenuAction = () => Promise | void; + export class ChatPanelAddPopover extends SignalWatcher( WithDisposable(ShadowlessElement) ) { static override styles = css` .add-popover { width: 280px; - max-height: 240px; + max-height: 450px; overflow-y: auto; border: 0.5px solid var(--affine-border-color); border-radius: 4px; @@ -76,14 +102,65 @@ export class ChatPanelAddPopover extends SignalWatcher( private accessor _query = ''; @state() - private accessor _docGroup: LinkedMenuGroup = { + private accessor _docGroup: MenuGroup = { name: 'No Result', items: [], }; + private readonly tcGroup: MenuGroup = { + name: 'Tag & Collection', + items: [ + { + key: 'tags', + name: 'Tags', + icon: TagsIcon(), + action: () => { + this._mode = AddPopoverMode.Tags; + }, + }, + { + key: 'collections', + name: 'Collections', + icon: CollectionsIcon(), + action: () => { + this._mode = AddPopoverMode.Collections; + }, + }, + ], + }; + + private readonly _addFileChip = async () => { + const file = await openFileOrFiles(); + if (!file) return; + if (file.size > 50 * 1024 * 1024) { + toast('You can only upload files less than 50MB'); + return; + } + this.addChip({ + file, + state: 'processing', + }); + this.abortController.abort(); + }; + + private readonly uploadGroup: MenuGroup = { + name: 'Upload', + items: [ + { + key: 'files', + name: 'Upload files (pdf, txt, csv)', + icon: UploadIcon(), + action: this._addFileChip, + }, + ], + }; + @state() private accessor _activatedItemIndex = 0; + @state() + private accessor _mode: AddPopoverMode = AddPopoverMode.Default; + @property({ attribute: false }) accessor docSearchMenuConfig!: DocSearchMenuConfig; @@ -108,68 +185,90 @@ export class ChatPanelAddPopover extends SignalWatcher( } override render() { - const items = Array.isArray(this._docGroup.items) - ? this._docGroup.items - : this._docGroup.items.value; + const groups = this._getMenuGroup(); return html`
-
- ${SearchIcon()} - -
-
-
- ${items.length > 0 - ? items.map(({ key, name, icon, action }, curIdx) => { - return html` action()?.catch(console.error)} - @mousemove=${() => (this._activatedItemIndex = curIdx)} - > - ${icon} - `; - }) - : html`
No Result
`} -
-
-
- (this._activatedItemIndex = items.length + 1)} - > - ${UploadIcon()} - -
+ ${this._renderSearchInput()} ${this._renderDivider()} + ${this._renderMenuGroup(groups)}
`; } - private readonly _addFileChip = async () => { - const file = await openFileOrFiles(); - if (!file) return; - if (file.size > 50 * 1024 * 1024) { - toast('You can only upload files less than 50MB'); - return; + private _renderSearchInput() { + return html`
+ ${SearchIcon()} + +
`; + } + + private _getPlaceholder() { + switch (this._mode) { + case AddPopoverMode.Tags: + return 'Search Tag'; + case AddPopoverMode.Collections: + return 'Search Collection'; + default: + return 'Search docs, tags, collections'; } - this.addChip({ - file, - state: 'processing', + } + + private _renderDivider() { + return html`
`; + } + + private _renderMenuGroup(groups: MenuGroup[]) { + let startIndex = 0; + return groups.map((group, idx) => { + const items = Array.isArray(group.items) + ? group.items + : group.items.value; + const menuGroup = html``; + startIndex += items.length; + return menuGroup; }); - this.abortController.abort(); - }; + } + + private _renderMenuItems(items: MenuItem[], startIndex: number) { + return html`
+ ${items.length > 0 + ? items.map(({ key, name, icon, action }, idx) => { + const curIdx = startIndex + idx; + return html` action()?.catch(console.error)} + @mousemove=${() => (this._activatedItemIndex = curIdx)} + > + ${icon} + `; + }) + : html`
No Result
`} +
`; + } + + private _getMenuGroup() { + switch (this._mode) { + case AddPopoverMode.Tags: + return []; + case AddPopoverMode.Collections: + return []; + default: + if (this._query) { + return [this._docGroup, this.uploadGroup]; + } + return [this._docGroup, this.tcGroup, this.uploadGroup]; + } + } private _onInput(event: Event) { this._query = (event.target as HTMLInputElement).value; diff --git a/packages/frontend/core/src/blocksuite/ai/chat-panel/components/collection-chip.ts b/packages/frontend/core/src/blocksuite/ai/chat-panel/components/collection-chip.ts index 27a2254aab..31379e7990 100644 --- a/packages/frontend/core/src/blocksuite/ai/chat-panel/components/collection-chip.ts +++ b/packages/frontend/core/src/blocksuite/ai/chat-panel/components/collection-chip.ts @@ -1,6 +1,6 @@ import { ShadowlessElement } from '@blocksuite/affine/block-std'; import { SignalWatcher, WithDisposable } from '@blocksuite/affine/global/lit'; -import { AddCollectionIcon } from '@blocksuite/icons/lit'; +import { CollectionsIcon } from '@blocksuite/icons/lit'; import { html } from 'lit'; import { property } from 'lit/decorators.js'; @@ -17,7 +17,7 @@ export class ChatPanelCollectionChip extends SignalWatcher( const { state, collectionName } = this.chip; const isLoading = state === 'processing'; const tooltip = getChipTooltip(state, collectionName, this.chip.tooltip); - const collectionIcon = AddCollectionIcon(); + const collectionIcon = CollectionsIcon(); const icon = getChipIcon(state, collectionIcon); return html`