diff --git a/packages/frontend/core/src/blocksuite/ai/chat-panel/index.ts b/packages/frontend/core/src/blocksuite/ai/chat-panel/index.ts index 2f50d0aebb..710aecfe92 100644 --- a/packages/frontend/core/src/blocksuite/ai/chat-panel/index.ts +++ b/packages/frontend/core/src/blocksuite/ai/chat-panel/index.ts @@ -20,10 +20,8 @@ import { property, state } from 'lit/decorators.js'; import { keyed } from 'lit/directives/keyed.js'; import { AffineIcon } from '../_common/icons'; -import type { - DocDisplayConfig, - SearchMenuConfig, -} from '../components/ai-chat-chips'; +import type { SearchMenuConfig } from '../components/ai-chat-add-context'; +import type { DocDisplayConfig } from '../components/ai-chat-chips'; import type { ChatContextValue } from '../components/ai-chat-content'; import type { AINetworkSearchConfig, @@ -385,6 +383,7 @@ export class ChatPanel extends SignalWatcher( .affineFeatureFlagService=${this.affineFeatureFlagService} .affineThemeService=${this.affineThemeService} .notificationService=${this.notificationService} + .affineWorkspaceDialogService=${this.affineWorkspaceDialogService} > `; diff --git a/packages/frontend/core/src/blocksuite/ai/components/ai-chat-add-context/ai-chat-add-context.ts b/packages/frontend/core/src/blocksuite/ai/components/ai-chat-add-context/ai-chat-add-context.ts new file mode 100644 index 0000000000..b44c2ff79e --- /dev/null +++ b/packages/frontend/core/src/blocksuite/ai/components/ai-chat-add-context/ai-chat-add-context.ts @@ -0,0 +1,93 @@ +import { createLitPortal } from '@blocksuite/affine/components/portal'; +import { SignalWatcher, WithDisposable } from '@blocksuite/affine/global/lit'; +import { ShadowlessElement } from '@blocksuite/affine/std'; +import { PlusIcon } from '@blocksuite/icons/lit'; +import { flip, offset } from '@floating-ui/dom'; +import { css, html } from 'lit'; +import { property, query } from 'lit/decorators.js'; + +import type { ChatChip, DocDisplayConfig } from '../ai-chat-chips'; +import type { SearchMenuConfig } from './type'; + +export class AIChatAddContext extends SignalWatcher( + WithDisposable(ShadowlessElement) +) { + static override styles = css` + .ai-chat-add-context { + display: flex; + flex-shrink: 0; + flex-grow: 0; + align-items: center; + justify-content: center; + cursor: pointer; + } + `; + + @property({ attribute: false }) + accessor addChip!: (chip: ChatChip) => Promise; + + @property({ attribute: false }) + accessor addImages!: (images: File[]) => void; + + @property({ attribute: false }) + accessor docDisplayConfig!: DocDisplayConfig; + + @property({ attribute: false }) + accessor searchMenuConfig!: SearchMenuConfig; + + @property({ attribute: false }) + accessor portalContainer: HTMLElement | null = null; + + @query('.ai-chat-add-context') + accessor addButton!: HTMLDivElement; + + private abortController: AbortController | null = null; + + override render() { + return html` +
+ ${PlusIcon()} +
+ `; + } + + private readonly toggleAddDocMenu = () => { + if (this.abortController) { + this.abortController.abort(); + return; + } + + this.abortController = new AbortController(); + this.abortController.signal.addEventListener('abort', () => { + this.abortController = null; + }); + + createLitPortal({ + template: html` + + `, + portalStyles: { + zIndex: 'var(--affine-z-index-popover)', + }, + container: this.portalContainer ?? document.body, + computePosition: { + referenceElement: this.addButton, + placement: 'top-start', + middleware: [offset({ crossAxis: -30, mainAxis: 8 }), flip()], + autoUpdate: { animationFrame: true }, + }, + abortController: this.abortController, + closeOnClickAway: true, + }); + }; +} diff --git a/packages/frontend/core/src/blocksuite/ai/components/ai-chat-add-context/index.ts b/packages/frontend/core/src/blocksuite/ai/components/ai-chat-add-context/index.ts new file mode 100644 index 0000000000..55e91d7398 --- /dev/null +++ b/packages/frontend/core/src/blocksuite/ai/components/ai-chat-add-context/index.ts @@ -0,0 +1,2 @@ +export * from './ai-chat-add-context'; +export * from './type'; diff --git a/packages/frontend/core/src/blocksuite/ai/components/ai-chat-add-context/type.ts b/packages/frontend/core/src/blocksuite/ai/components/ai-chat-add-context/type.ts new file mode 100644 index 0000000000..378468fbc0 --- /dev/null +++ b/packages/frontend/core/src/blocksuite/ai/components/ai-chat-add-context/type.ts @@ -0,0 +1,24 @@ +import type { + SearchCollectionMenuAction, + SearchDocMenuAction, + SearchTagMenuAction, +} from '@affine/core/modules/search-menu/services'; +import type { LinkedMenuGroup } from '@blocksuite/affine/widgets/linked-doc'; + +export interface SearchMenuConfig { + getDocMenuGroup: ( + query: string, + action: SearchDocMenuAction, + abortSignal: AbortSignal + ) => LinkedMenuGroup; + getTagMenuGroup: ( + query: string, + action: SearchTagMenuAction, + abortSignal: AbortSignal + ) => LinkedMenuGroup; + getCollectionMenuGroup: ( + query: string, + action: SearchCollectionMenuAction, + abortSignal: AbortSignal + ) => LinkedMenuGroup; +} diff --git a/packages/frontend/core/src/blocksuite/ai/components/ai-chat-chips/add-popover.ts b/packages/frontend/core/src/blocksuite/ai/components/ai-chat-chips/add-popover.ts index 89ecdd5868..375429289e 100644 --- a/packages/frontend/core/src/blocksuite/ai/components/ai-chat-chips/add-popover.ts +++ b/packages/frontend/core/src/blocksuite/ai/components/ai-chat-chips/add-popover.ts @@ -21,8 +21,8 @@ import { css, html, type TemplateResult } from 'lit'; import { property, query, state } from 'lit/decorators.js'; import { repeat } from 'lit/directives/repeat.js'; -import { MAX_IMAGE_COUNT } from '../ai-chat-input'; -import type { ChatChip, DocDisplayConfig, SearchMenuConfig } from './type'; +import type { SearchMenuConfig } from '../ai-chat-add-context'; +import type { ChatChip, DocDisplayConfig } from './type'; enum AddPopoverMode { Default = 'default', @@ -165,35 +165,31 @@ export class ChatPanelAddPopover extends SignalWatcher( const files = await openFilesWith(); if (!files || files.length === 0) return; + this.abortController.abort(); const images = files.filter(file => file.type.startsWith('image/')); if (images.length > 0) { this.addImages(images); } const others = files.filter(file => !file.type.startsWith('image/')); - for (const file of others) { + const addChipPromises = others.map(async file => { if (file.size > 50 * 1024 * 1024) { toast(`${file.name} is too large, please upload a file less than 50MB`); - } else { - await this.addChip({ - file, - state: 'processing', - }); + return; } - } + await this.addChip({ + file, + state: 'processing', + }); + }); + await Promise.all(addChipPromises); this._track('file'); - this.abortController.abort(); }; private readonly _addImageChip = async () => { - if (this.isImageUploadDisabled) return; - const images = await openFilesWith('Images'); if (!images) return; - if (this.uploadImageCount + images.length > MAX_IMAGE_COUNT) { - toast(`You can only upload up to ${MAX_IMAGE_COUNT} images`); - return; - } + this.abortController.abort(); this.addImages(images); }; @@ -289,9 +285,6 @@ export class ChatPanelAddPopover extends SignalWatcher( @property({ attribute: 'data-testid', reflect: true }) accessor testId: string = 'ai-search-input'; - @property({ attribute: false }) - accessor isImageUploadDisabled!: boolean; - @property({ attribute: false }) accessor uploadImageCount!: number; @@ -498,31 +491,31 @@ export class ChatPanelAddPopover extends SignalWatcher( } private readonly _addDocChip = async (meta: DocMeta) => { + this.abortController.abort(); await this.addChip({ docId: meta.id, state: 'processing', }); const mode = this.docDisplayConfig.getDocPrimaryMode(meta.id); this._track('doc', mode); - this.abortController.abort(); }; private readonly _addTagChip = async (tag: TagMeta) => { + this.abortController.abort(); await this.addChip({ tagId: tag.id, state: 'processing', }); this._track('tags'); - this.abortController.abort(); }; private readonly _addCollectionChip = async (collection: CollectionMeta) => { + this.abortController.abort(); await this.addChip({ collectionId: collection.id, state: 'processing', }); this._track('collections'); - this.abortController.abort(); }; private readonly _handleKeyDown = (event: KeyboardEvent) => { diff --git a/packages/frontend/core/src/blocksuite/ai/components/ai-chat-chips/chat-panel-chips.ts b/packages/frontend/core/src/blocksuite/ai/components/ai-chat-chips/chat-panel-chips.ts index 2a3275f6e0..df1170007a 100644 --- a/packages/frontend/core/src/blocksuite/ai/components/ai-chat-chips/chat-panel-chips.ts +++ b/packages/frontend/core/src/blocksuite/ai/components/ai-chat-chips/chat-panel-chips.ts @@ -3,7 +3,7 @@ import { createLitPortal } from '@blocksuite/affine/components/portal'; import { SignalWatcher, WithDisposable } from '@blocksuite/affine/global/lit'; import { unsafeCSSVarV2 } from '@blocksuite/affine/shared/theme'; import { ShadowlessElement } from '@blocksuite/affine/std'; -import { MoreVerticalIcon, PlusIcon } from '@blocksuite/icons/lit'; +import { MoreVerticalIcon } from '@blocksuite/icons/lit'; import { flip, offset } from '@floating-ui/dom'; import { computed, type Signal, signal } from '@preact/signals-core'; import { css, html, nothing, type PropertyValues } from 'lit'; @@ -11,16 +11,7 @@ import { property, query, state } from 'lit/decorators.js'; import { repeat } from 'lit/directives/repeat.js'; import { isEqual } from 'lodash-es'; -import { AIProvider } from '../../provider'; -import type { - ChatChip, - CollectionChip, - DocChip, - DocDisplayConfig, - FileChip, - SearchMenuConfig, - TagChip, -} from './type'; +import type { ChatChip, DocChip, DocDisplayConfig, FileChip } from './type'; import { estimateTokenCount, getChipKey, @@ -39,44 +30,46 @@ export class ChatPanelChips extends SignalWatcher( WithDisposable(ShadowlessElement) ) { static override styles = css` - .chips-wrapper { + .ai-chat-panel-chips { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; padding: 4px 12px; - } - .add-button, - .collapse-button, - .more-candidate-button { - display: flex; - flex-shrink: 0; - flex-grow: 0; - align-items: center; - justify-content: center; - width: 24px; - height: 24px; - border: 0.5px solid ${unsafeCSSVarV2('layer/insideBorder/border')}; - border-radius: 4px; - box-sizing: border-box; - cursor: pointer; - font-size: 12px; - color: ${unsafeCSSVarV2('icon/primary')}; - } - .add-button:hover, - .collapse-button:hover, - .more-candidate-button:hover { - background-color: ${unsafeCSSVarV2('layer/background/hoverOverlay')}; - } - .more-candidate-button { - border-width: 1px; - border-style: dashed; - border-color: ${unsafeCSSVarV2('icon/tertiary')}; - background: ${unsafeCSSVarV2('layer/background/secondary')}; - color: ${unsafeCSSVarV2('icon/secondary')}; - } - .more-candidate-button svg { - color: ${unsafeCSSVarV2('icon/secondary')}; + + .collapse-button, + .more-candidate-button { + display: flex; + flex-shrink: 0; + flex-grow: 0; + align-items: center; + justify-content: center; + width: 24px; + height: 24px; + border: 0.5px solid ${unsafeCSSVarV2('layer/insideBorder/border')}; + border-radius: 4px; + box-sizing: border-box; + cursor: pointer; + font-size: 12px; + color: ${unsafeCSSVarV2('icon/primary')}; + } + + .collapse-button:hover, + .more-candidate-button:hover { + background-color: ${unsafeCSSVarV2('layer/background/hoverOverlay')}; + } + + .more-candidate-button { + border-width: 1px; + border-style: dashed; + border-color: ${unsafeCSSVarV2('icon/tertiary')}; + background: ${unsafeCSSVarV2('layer/background/secondary')}; + color: ${unsafeCSSVarV2('icon/secondary')}; + } + + .more-candidate-button svg { + color: ${unsafeCSSVarV2('icon/secondary')}; + } } `; @@ -86,38 +79,35 @@ export class ChatPanelChips extends SignalWatcher( accessor chips!: ChatChip[]; @property({ attribute: false }) - accessor createContextId!: () => Promise; + accessor isCollapsed!: boolean; @property({ attribute: false }) - accessor updateChips!: (chips: ChatChip[]) => void; + accessor addChip!: (chip: ChatChip) => Promise; @property({ attribute: false }) - accessor addImages!: (images: File[]) => void; + accessor updateChip!: ( + chip: ChatChip, + options: Partial + ) => void; @property({ attribute: false }) - accessor pollContextDocsAndFiles!: () => void; + accessor removeChip!: (chip: ChatChip) => Promise; + + @property({ attribute: false }) + accessor toggleCollapse!: () => void; @property({ attribute: false }) accessor docDisplayConfig!: DocDisplayConfig; - @property({ attribute: false }) - accessor searchMenuConfig!: SearchMenuConfig; - @property({ attribute: false }) accessor portalContainer: HTMLElement | null = null; @property({ attribute: 'data-testid', reflect: true }) accessor testId = 'chat-panel-chips'; - @query('.add-button') - accessor addButton!: HTMLDivElement; - @query('.more-candidate-button') accessor moreCandidateButton!: HTMLDivElement; - @state() - accessor isCollapsed = false; - @state() accessor referenceDocs: Signal< Array<{ @@ -144,14 +134,7 @@ export class ChatPanelChips extends SignalWatcher( const isCollapsed = this.isCollapsed && allChips.length > 1; const chips = isCollapsed ? allChips.slice(0, 1) : allChips; - return html`
-
- ${PlusIcon()} -
+ return html`
${repeat( chips, chip => getChipKey(chip), @@ -159,9 +142,9 @@ export class ChatPanelChips extends SignalWatcher( if (isDocChip(chip)) { return html``; @@ -169,7 +152,7 @@ export class ChatPanelChips extends SignalWatcher( if (isFileChip(chip)) { return html``; } if (isTagChip(chip)) { @@ -180,7 +163,7 @@ export class ChatPanelChips extends SignalWatcher( return html``; } if (isCollectionChip(chip)) { @@ -193,7 +176,7 @@ export class ChatPanelChips extends SignalWatcher( return html``; } return null; @@ -208,7 +191,7 @@ export class ChatPanelChips extends SignalWatcher(
` : nothing} ${isCollapsed - ? html`
+ ? html`
+${allChips.length - 1}
` : nothing} @@ -227,14 +210,6 @@ export class ChatPanelChips extends SignalWatcher( } protected override updated(_changedProperties: PropertyValues): void { - if ( - _changedProperties.has('chatContextValue') && - _changedProperties.get('chatContextValue')?.status === 'loading' && - this.isCollapsed === false - ) { - this.isCollapsed = true; - } - if (_changedProperties.has('chips')) { this._updateReferenceDocs(); } @@ -245,46 +220,6 @@ export class ChatPanelChips extends SignalWatcher( this._cleanup?.(); } - private readonly _toggleCollapse = () => { - this.isCollapsed = !this.isCollapsed; - }; - - private readonly _toggleAddDocMenu = () => { - if (this._abortController) { - this._abortController.abort(); - return; - } - - this._abortController = new AbortController(); - this._abortController.signal.addEventListener('abort', () => { - this._abortController = null; - }); - - createLitPortal({ - template: html` - - `, - portalStyles: { - zIndex: 'var(--affine-z-index-popover)', - }, - container: this.portalContainer ?? document.body, - computePosition: { - referenceElement: this.addButton, - placement: 'top-start', - middleware: [offset({ crossAxis: -30, mainAxis: 8 }), flip()], - autoUpdate: { animationFrame: true }, - }, - abortController: this._abortController, - closeOnClickAway: true, - }); - }; - private readonly _toggleMoreCandidatesMenu = () => { if (this._abortController) { this._abortController.abort(); @@ -303,7 +238,7 @@ export class ChatPanelChips extends SignalWatcher( createLitPortal({ template: html` { - this.isCollapsed = false; - // remove the chip if it already exists - const chips = this._omitChip(this.chips, chip); - this.updateChips([...chips, chip]); - if (chips.length < this.chips.length) { - await this._removeFromContext(chip); - } - await this._addToContext(chip); - this.pollContextDocsAndFiles(); - }; - - private readonly _updateChip = ( - chip: ChatChip, - options: Partial - ) => { - const index = this._findChipIndex(this.chips, chip); - if (index === -1) { - return; - } - const nextChip: ChatChip = { - ...chip, - ...options, - }; - this.updateChips([ - ...this.chips.slice(0, index), - nextChip, - ...this.chips.slice(index + 1), - ]); - }; - - private readonly _removeChip = async (chip: ChatChip) => { - const chips = this._omitChip(this.chips, chip); - this.updateChips(chips); - if (chips.length < this.chips.length) { - await this._removeFromContext(chip); - } - }; - - private readonly _addToContext = async (chip: ChatChip) => { - if (isDocChip(chip)) { - return await this._addDocToContext(chip); - } - if (isFileChip(chip)) { - return await this._addFileToContext(chip); - } - if (isTagChip(chip)) { - return await this._addTagToContext(chip); - } - if (isCollectionChip(chip)) { - return await this._addCollectionToContext(chip); - } - return null; - }; - - private readonly _addDocToContext = async (chip: DocChip) => { - try { - const contextId = await this.createContextId(); - if (!contextId || !AIProvider.context) { - throw new Error('Context not found'); - } - await AIProvider.context.addContextDoc({ - contextId, - docId: chip.docId, - }); - } catch (e) { - this._updateChip(chip, { - state: 'failed', - tooltip: e instanceof Error ? e.message : 'Add context doc error', - }); - } - }; - - private readonly _addFileToContext = async (chip: FileChip) => { - try { - const contextId = await this.createContextId(); - if (!contextId || !AIProvider.context) { - throw new Error('Context not found'); - } - const contextFile = await AIProvider.context.addContextFile(chip.file, { - contextId, - }); - this._updateChip(chip, { - state: contextFile.status, - blobId: contextFile.blobId, - fileId: contextFile.id, - }); - } catch (e) { - this._updateChip(chip, { - state: 'failed', - tooltip: e instanceof Error ? e.message : 'Add context file error', - }); - } - }; - - private readonly _addTagToContext = async (chip: TagChip) => { - try { - const contextId = await this.createContextId(); - if (!contextId || !AIProvider.context) { - throw new Error('Context not found'); - } - // TODO: server side docIds calculation - const docIds = this.docDisplayConfig.getTagPageIds(chip.tagId); - await AIProvider.context.addContextTag({ - contextId, - tagId: chip.tagId, - docIds, - }); - this._updateChip(chip, { - state: 'finished', - }); - } catch (e) { - this._updateChip(chip, { - state: 'failed', - tooltip: e instanceof Error ? e.message : 'Add context tag error', - }); - } - }; - - private readonly _addCollectionToContext = async (chip: CollectionChip) => { - try { - const contextId = await this.createContextId(); - if (!contextId || !AIProvider.context) { - throw new Error('Context not found'); - } - // TODO: server side docIds calculation - const docIds = this.docDisplayConfig.getCollectionPageIds( - chip.collectionId - ); - await AIProvider.context.addContextCollection({ - contextId, - collectionId: chip.collectionId, - docIds, - }); - this._updateChip(chip, { - state: 'finished', - }); - } catch (e) { - this._updateChip(chip, { - state: 'failed', - tooltip: - e instanceof Error ? e.message : 'Add context collection error', - }); - } - }; - - private readonly _removeFromContext = async ( - chip: ChatChip - ): Promise => { - try { - const contextId = await this.createContextId(); - if (!contextId || !AIProvider.context) { - return true; - } - if (isDocChip(chip)) { - return await AIProvider.context.removeContextDoc({ - contextId, - docId: chip.docId, - }); - } - if (isFileChip(chip) && chip.fileId) { - return await AIProvider.context.removeContextFile({ - contextId, - fileId: chip.fileId, - }); - } - if (isTagChip(chip)) { - return await AIProvider.context.removeContextTag({ - contextId, - tagId: chip.tagId, - }); - } - if (isCollectionChip(chip)) { - return await AIProvider.context.removeContextCollection({ - contextId, - collectionId: chip.collectionId, - }); - } - return true; - } catch { - return true; - } - }; - private readonly _checkTokenLimit = ( newChip: DocChip, newTokenCount: number @@ -544,44 +295,4 @@ export class ChatPanelChips extends SignalWatcher( this.referenceDocs = signal; this._cleanup = cleanup; }; - - private readonly _omitChip = (chips: ChatChip[], chip: ChatChip) => { - return chips.filter(item => { - if (isDocChip(chip)) { - return !isDocChip(item) || item.docId !== chip.docId; - } - if (isFileChip(chip)) { - return !isFileChip(item) || item.file !== chip.file; - } - if (isTagChip(chip)) { - return !isTagChip(item) || item.tagId !== chip.tagId; - } - if (isCollectionChip(chip)) { - return ( - !isCollectionChip(item) || item.collectionId !== chip.collectionId - ); - } - return true; - }); - }; - - private readonly _findChipIndex = (chips: ChatChip[], chip: ChatChip) => { - return chips.findIndex(item => { - if (isDocChip(chip)) { - return isDocChip(item) && item.docId === chip.docId; - } - if (isFileChip(chip)) { - return isFileChip(item) && item.file === chip.file; - } - if (isTagChip(chip)) { - return isTagChip(item) && item.tagId === chip.tagId; - } - if (isCollectionChip(chip)) { - return ( - isCollectionChip(item) && item.collectionId === chip.collectionId - ); - } - return -1; - }); - }; } diff --git a/packages/frontend/core/src/blocksuite/ai/components/ai-chat-chips/type.ts b/packages/frontend/core/src/blocksuite/ai/components/ai-chat-chips/type.ts index 567b240bcb..7757eb8d97 100644 --- a/packages/frontend/core/src/blocksuite/ai/components/ai-chat-chips/type.ts +++ b/packages/frontend/core/src/blocksuite/ai/components/ai-chat-chips/type.ts @@ -1,11 +1,5 @@ import type { TagMeta } from '@affine/core/components/page-list'; -import type { - SearchCollectionMenuAction, - SearchDocMenuAction, - SearchTagMenuAction, -} from '@affine/core/modules/search-menu/services'; import type { DocMeta, Store } from '@blocksuite/affine/store'; -import type { LinkedMenuGroup } from '@blocksuite/affine/widgets/linked-doc'; import type { Signal } from '@preact/signals-core'; export type ChipState = 'candidate' | 'processing' | 'finished' | 'failed'; @@ -75,21 +69,3 @@ export interface DocDisplayConfig { }; getCollectionPageIds: (collectionId: string) => string[]; } - -export interface SearchMenuConfig { - getDocMenuGroup: ( - query: string, - action: SearchDocMenuAction, - abortSignal: AbortSignal - ) => LinkedMenuGroup; - getTagMenuGroup: ( - query: string, - action: SearchTagMenuAction, - abortSignal: AbortSignal - ) => LinkedMenuGroup; - getCollectionMenuGroup: ( - query: string, - action: SearchCollectionMenuAction, - abortSignal: AbortSignal - ) => LinkedMenuGroup; -} diff --git a/packages/frontend/core/src/blocksuite/ai/components/ai-chat-chips/utils.ts b/packages/frontend/core/src/blocksuite/ai/components/ai-chat-chips/utils.ts index 20ed59c6f0..c62079875d 100644 --- a/packages/frontend/core/src/blocksuite/ai/components/ai-chat-chips/utils.ts +++ b/packages/frontend/core/src/blocksuite/ai/components/ai-chat-chips/utils.ts @@ -78,6 +78,42 @@ export function getChipKey(chip: ChatChip) { return null; } +export function omitChip(chips: ChatChip[], chip: ChatChip) { + return chips.filter(item => { + if (isDocChip(chip)) { + return !isDocChip(item) || item.docId !== chip.docId; + } + if (isFileChip(chip)) { + return !isFileChip(item) || item.file !== chip.file; + } + if (isTagChip(chip)) { + return !isTagChip(item) || item.tagId !== chip.tagId; + } + if (isCollectionChip(chip)) { + return !isCollectionChip(item) || item.collectionId !== chip.collectionId; + } + return true; + }); +} + +export function findChipIndex(chips: ChatChip[], chip: ChatChip) { + return chips.findIndex(item => { + if (isDocChip(chip)) { + return isDocChip(item) && item.docId === chip.docId; + } + if (isFileChip(chip)) { + return isFileChip(item) && item.file === chip.file; + } + if (isTagChip(chip)) { + return isTagChip(item) && item.tagId === chip.tagId; + } + if (isCollectionChip(chip)) { + return isCollectionChip(item) && item.collectionId === chip.collectionId; + } + return -1; + }); +} + export function estimateTokenCount(text: string): number { const chinese = text.match(/[\u4e00-\u9fa5]/g)?.length || 0; const english = text.replace(/[\u4e00-\u9fa5]/g, ''); diff --git a/packages/frontend/core/src/blocksuite/ai/components/ai-chat-composer/ai-chat-composer.ts b/packages/frontend/core/src/blocksuite/ai/components/ai-chat-composer/ai-chat-composer.ts index 772ad76172..f9c999589d 100644 --- a/packages/frontend/core/src/blocksuite/ai/components/ai-chat-composer/ai-chat-composer.ts +++ b/packages/frontend/core/src/blocksuite/ai/components/ai-chat-composer/ai-chat-composer.ts @@ -12,20 +12,28 @@ import type { import { SignalWatcher, WithDisposable } from '@blocksuite/affine/global/lit'; import type { EditorHost } from '@blocksuite/affine/std'; import { ShadowlessElement } from '@blocksuite/affine/std'; -import { css, html } from 'lit'; +import type { NotificationService } from '@blocksuite/affine-shared/services'; +import { css, html, type PropertyValues } from 'lit'; import { property, state } from 'lit/decorators.js'; import { AIProvider } from '../../provider'; +import type { SearchMenuConfig } from '../ai-chat-add-context'; import type { ChatChip, CollectionChip, DocChip, DocDisplayConfig, FileChip, - SearchMenuConfig, TagChip, } from '../ai-chat-chips'; -import { isCollectionChip, isDocChip, isTagChip } from '../ai-chat-chips'; +import { + findChipIndex, + isCollectionChip, + isDocChip, + isFileChip, + isTagChip, + omitChip, +} from '../ai-chat-chips'; import type { AIChatInputContext, AINetworkSearchConfig, @@ -105,9 +113,15 @@ export class AIChatComposer extends SignalWatcher( @property({ attribute: false }) accessor affineWorkspaceDialogService!: WorkspaceDialogService; + @property({ attribute: false }) + accessor notificationService!: NotificationService; + @state() accessor chips: ChatChip[] = []; + @state() + accessor isChipsCollapsed = false; + @state() accessor embeddingCompleted = false; @@ -121,11 +135,12 @@ export class AIChatComposer extends SignalWatcher( return html` @@ -136,15 +151,18 @@ export class AIChatComposer extends SignalWatcher( .docId=${this.docId} .session=${this.session} .chips=${this.chips} + .addChip=${this.addChip} + .addImages=${this.addImages} .createSession=${this.createSession} .chatContextValue=${this.chatContextValue} .updateContext=${this.updateContext} .networkSearchConfig=${this.networkSearchConfig} .reasoningConfig=${this.reasoningConfig} .docDisplayConfig=${this.docDisplayConfig} + .searchMenuConfig=${this.searchMenuConfig} + .portalContainer=${this.portalContainer} .onChatSuccess=${this.onChatSuccess} .trackOptions=${this.trackOptions} - .addImages=${this.addImages} >