From 2366c1aba6beb8f5a92d1fd03cc7e4cf388c1889 Mon Sep 17 00:00:00 2001 From: Cats Juice Date: Tue, 17 Jun 2025 09:26:29 +0800 Subject: [PATCH] feat(core): adjust the layout, style, and structure of the AI chat input (#12828) ## Summary by CodeRabbit - **New Features** - Added support for image uploads in the chat panel, including upload limits and user feedback when limits are exceeded. - Introduced a unified chat input preference menu for selecting AI models, toggling extended thinking, and enabling web search. - Menu buttons and menus now support test identifiers for improved testing. - **Improvements** - Updated chat input UI with enhanced styling, consolidated controls, and simplified feature toggling. - Improved layout and spacing for chat chips and image preview grids. - Chat abort icon now adapts to the current color theme. - **Refactor** - Replaced the separate AI model selection component with the new chat input preference menu. - Streamlined imports and custom element registrations for chat input preferences. - **Tests** - Enhanced test utilities to support the new chat input preference menu interactions. --- .../components/src/context-menu/button.ts | 24 ++- .../components/src/context-menu/menu.ts | 4 + .../core/src/blocksuite/ai/_common/icons.ts | 2 +- .../components/ai-chat-chips/add-popover.ts | 27 +++ .../ai-chat-chips/chat-panel-chips.ts | 8 +- .../ai/components/ai-chat-chips/chip.ts | 1 - .../components/ai-chat-input/ai-chat-input.ts | 167 +++++++++-------- .../ai-chat-input/preference-popup.ts | 168 ++++++++++++++++++ .../ai-chat-models/ai-chat-models.ts | 112 ------------ .../ai/components/ai-chat-models/index.ts | 1 - .../ai/components/image-preview-grid.ts | 7 +- .../core/src/blocksuite/ai/effects.ts | 4 +- .../e2e/utils/chat-panel-utils.ts | 12 ++ 13 files changed, 326 insertions(+), 211 deletions(-) create mode 100644 packages/frontend/core/src/blocksuite/ai/components/ai-chat-input/preference-popup.ts delete mode 100644 packages/frontend/core/src/blocksuite/ai/components/ai-chat-models/ai-chat-models.ts delete mode 100644 packages/frontend/core/src/blocksuite/ai/components/ai-chat-models/index.ts diff --git a/blocksuite/affine/components/src/context-menu/button.ts b/blocksuite/affine/components/src/context-menu/button.ts index 472092fd9..9aef8269a 100644 --- a/blocksuite/affine/components/src/context-menu/button.ts +++ b/blocksuite/affine/components/src/context-menu/button.ts @@ -11,6 +11,7 @@ import { property } from 'lit/decorators.js'; import { classMap } from 'lit/directives/class-map.js'; import { keyed } from 'lit/directives/keyed.js'; import type { ClassInfo } from 'lit-html/directives/class-map.js'; +import { ifDefined } from 'lit-html/directives/if-defined.js'; import { MenuFocusable } from './focusable.js'; import type { Menu } from './menu.js'; @@ -21,6 +22,7 @@ export type MenuButtonData = { class: ClassInfo; select: (ele: HTMLElement) => void | false; onHover?: (hover: boolean) => void; + testId?: string; }; export class MenuButton extends MenuFocusable { @@ -97,7 +99,12 @@ export class MenuButton extends MenuFocusable { focused: this.isFocused$.value, ...this.data.class, }); - return html`
${this.data.content()}
`; + return html`
+ ${this.data.content()} +
`; } @property({ attribute: false }) @@ -157,7 +164,12 @@ export class MobileMenuButton extends MenuFocusable { focused: this.isFocused$.value, ...this.data.class, }); - return html`
${this.data.content()}
`; + return html`
+ ${this.data.content()} +
`; } @property({ attribute: false }) @@ -188,6 +200,7 @@ export const menuButtonItems = { onHover?: (hover: boolean) => void; class?: MenuClass; hide?: () => boolean; + testId?: string; }) => menu => { if (config.hide?.() || !menu.search(config.name)) { @@ -209,6 +222,7 @@ export const menuButtonItems = { 'selected-item': config.isSelected ?? false, ...config.class, }, + testId: config.testId, }; return renderButton(data, menu); }, @@ -220,6 +234,7 @@ export const menuButtonItems = { label?: () => TemplateResult; select: (checked: boolean) => boolean; class?: ClassInfo; + testId?: string; }) => menu => { if (!menu.search(config.name)) { @@ -240,6 +255,7 @@ export const menuButtonItems = { return false; }, class: config.class ?? {}, + testId: config.testId, }; return html`${keyed(config.name, renderButton(data, menu))}`; }, @@ -247,10 +263,12 @@ export const menuButtonItems = { (config: { name: string; on: boolean; + prefix?: TemplateResult; postfix?: TemplateResult; label?: () => TemplateResult; onChange: (on: boolean) => void; class?: ClassInfo; + testId?: string; }) => menu => { if (!menu.search(config.name)) { @@ -262,6 +280,7 @@ export const menuButtonItems = { const data: MenuButtonData = { content: () => html` + ${config.prefix}
${config.label?.() ?? config.name}
@@ -276,6 +295,7 @@ export const menuButtonItems = { return false; }, class: config.class ?? {}, + testId: config.testId, }; return html`${keyed(config.name, renderButton(data, menu))}`; }, diff --git a/blocksuite/affine/components/src/context-menu/menu.ts b/blocksuite/affine/components/src/context-menu/menu.ts index 0b665aa03..2a682443b 100644 --- a/blocksuite/affine/components/src/context-menu/menu.ts +++ b/blocksuite/affine/components/src/context-menu/menu.ts @@ -23,6 +23,7 @@ export type MenuOptions = { placeholder?: string; }; items: MenuConfig[]; + testId?: string; }; // Global menu open listener type @@ -72,6 +73,9 @@ export class Menu { ? document.createElement('mobile-menu') : document.createElement('affine-menu'); this.menuElement.menu = this; + if (this.options.testId) { + this.menuElement.dataset.testid = this.options.testId; + } // Call global menu open listeners menuOpenListeners.forEach(listener => { diff --git a/packages/frontend/core/src/blocksuite/ai/_common/icons.ts b/packages/frontend/core/src/blocksuite/ai/_common/icons.ts index 2cdec6f63..ad3035e78 100644 --- a/packages/frontend/core/src/blocksuite/ai/_common/icons.ts +++ b/packages/frontend/core/src/blocksuite/ai/_common/icons.ts @@ -155,7 +155,7 @@ export const ChatAbortIcon = html` 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 c692e631a..97ad1c431 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 @@ -10,6 +10,7 @@ import { ShadowlessElement } from '@blocksuite/affine/std'; import type { DocMeta } from '@blocksuite/affine/store'; import { CollectionsIcon, + ImageIcon, MoreHorizontalIcon, SearchIcon, TagsIcon, @@ -20,6 +21,7 @@ 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'; enum AddPopoverMode { @@ -182,9 +184,28 @@ export class ChatPanelAddPopover extends SignalWatcher( 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.addImages(images); + }; + private readonly uploadGroup: MenuGroup = { name: 'Upload', items: [ + { + key: 'images', + name: 'Upload images', + testId: 'ai-chat-with-images', + icon: ImageIcon(), + action: this._addImageChip, + }, { key: 'files', name: 'Upload files (pdf, txt, csv)', @@ -267,6 +288,12 @@ 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; + @query('.search-input') accessor searchInput!: HTMLInputElement; 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 fa3a8d18d..8df1f6c05 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 @@ -41,23 +41,27 @@ export class ChatPanelChips extends SignalWatcher( static override styles = css` .chips-wrapper { display: flex; + gap: 8px; + align-items: center; flex-wrap: wrap; - margin: 0 -4px 0 -4px; + 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; - margin: 4px; box-sizing: border-box; cursor: pointer; font-size: 12px; + color: ${unsafeCSSVarV2('icon/primary')}; } .add-button:hover, .collapse-button:hover, diff --git a/packages/frontend/core/src/blocksuite/ai/components/ai-chat-chips/chip.ts b/packages/frontend/core/src/blocksuite/ai/components/ai-chat-chips/chip.ts index bfed860c4..2e9e830e0 100644 --- a/packages/frontend/core/src/blocksuite/ai/components/ai-chat-chips/chip.ts +++ b/packages/frontend/core/src/blocksuite/ai/components/ai-chat-chips/chip.ts @@ -16,7 +16,6 @@ export class ChatPanelChip extends SignalWatcher( height: 24px; align-items: center; justify-content: center; - margin: 4px; padding: 0 4px; border-radius: 4px; border: 0.5px solid ${unsafeCSSVarV2('layer/insideBorder/border')}; diff --git a/packages/frontend/core/src/blocksuite/ai/components/ai-chat-input/ai-chat-input.ts b/packages/frontend/core/src/blocksuite/ai/components/ai-chat-input/ai-chat-input.ts index 2c5b9acfe..b6d3b4077 100644 --- a/packages/frontend/core/src/blocksuite/ai/components/ai-chat-input/ai-chat-input.ts +++ b/packages/frontend/core/src/blocksuite/ai/components/ai-chat-input/ai-chat-input.ts @@ -1,24 +1,18 @@ import { toast } from '@affine/component'; -import { stopPropagation } from '@affine/core/utils'; import type { CopilotSessionType } from '@affine/graphql'; import { SignalWatcher, WithDisposable } from '@blocksuite/affine/global/lit'; import { unsafeCSSVar, unsafeCSSVarV2 } from '@blocksuite/affine/shared/theme'; import { openFilesWith } from '@blocksuite/affine/shared/utils'; import type { EditorHost } from '@blocksuite/affine/std'; import { ShadowlessElement } from '@blocksuite/affine/std'; -import { - CloseIcon, - ImageIcon, - PublishIcon, - ThinkingIcon, -} from '@blocksuite/icons/lit'; +import { ArrowUpBigIcon, CloseIcon, ImageIcon } from '@blocksuite/icons/lit'; import { type Signal, signal } from '@preact/signals-core'; import { css, html, nothing } from 'lit'; import { property, query, state } from 'lit/decorators.js'; import { repeat } from 'lit/directives/repeat.js'; import { styleMap } from 'lit/directives/style-map.js'; -import { ChatAbortIcon, ChatSendIcon } from '../../_common/icons'; +import { ChatAbortIcon } from '../../_common/icons'; import { type AIError, AIProvider } from '../../provider'; import { reportResponse } from '../../utils/action-reporter'; import { readBlobAsURL } from '../../utils/image'; @@ -45,20 +39,42 @@ export class AIChatInput extends SignalWatcher( :host { width: 100%; } + + [data-theme='dark'] .chat-panel-input { + box-shadow: + var(--border-shadow), + 0px 0px 0px 0px rgba(28, 158, 228, 0), + 0px 0px 0px 2px transparent; + } + [data-theme='light'] .chat-panel-input { + box-shadow: + var(--border-shadow), + 0px 0px 0px 3px transparent, + 0px 2px 3px rgba(0, 0, 0, 0.05); + } + [data-theme='dark'] .chat-panel-input[data-if-focused='true'] { + box-shadow: + var(--border-shadow), + 0px 0px 0px 3px rgba(28, 158, 228, 0.3), + 0px 2px 3px rgba(0, 0, 0, 0.05); + } + .chat-panel-input { + --input-border-width: 0.5px; + --input-border-color: var(--affine-v2-layer-insideBorder-border); + --border-shadow: 0px 0px 0px var(--input-border-width) + var(--input-border-color); display: flex; flex-direction: column; justify-content: space-between; - gap: 12px; + gap: 4px; position: relative; - margin-top: 12px; - border-radius: 4px; - padding: 8px; + border-radius: 12px; + padding: 8px 6px 6px 8px; min-height: 94px; box-sizing: border-box; - border-width: 1px; - border-style: solid; - border-color: var(--affine-border-color); + transition: box-shadow 0.23s ease; + background-color: var(--affine-v2-input-background); .chat-selection-quote { padding: 4px 0px 8px 0px; @@ -207,7 +223,7 @@ export class AIChatInput extends SignalWatcher( font-size: 14px; font-weight: 400; font-family: var(--affine-font-family); - color: var(--affine-placeholder-color); + color: var(--affine-v2-text-placeholder); } textarea:focus { @@ -216,8 +232,8 @@ export class AIChatInput extends SignalWatcher( } .chat-panel-input[data-if-focused='true'] { - border-color: var(--affine-primary-color); - box-shadow: var(--affine-active-shadow); + --input-border-width: 1px; + --input-border-color: var(--affine-v2-layer-insideBorder-primaryBorder); user-select: none; } @@ -225,16 +241,32 @@ export class AIChatInput extends SignalWatcher( display: flex; justify-content: center; align-items: center; - } - - .chat-panel-send svg rect { - fill: var(--affine-primary-color); + width: 28px; + height: 28px; + flex-shrink: 0; + border-radius: 50%; + font-size: 20px; + background: var(--affine-v2-icon-activated); + color: var(--affine-v2-layer-pureWhite); } .chat-panel-send[aria-disabled='true'] { cursor: not-allowed; + background: var(--affine-v2-button-disable); } - .chat-panel-send[aria-disabled='true'] svg rect { - fill: var(--affine-text-disable-color); + .chat-panel-stop { + cursor: pointer; + width: 28px; + height: 28px; + flex-shrink: 0; + display: flex; + justify-content: center; + align-items: center; + border-radius: 50%; + font-size: 24px; + color: var(--affine-v2-icon-activated); + } + .chat-input-footer-spacer { + flex: 1; } `; @@ -342,7 +374,6 @@ export class AIChatInput extends SignalWatcher( const { images, status } = this.chatContextValue; const hasImages = images.length > 0; const maxHeight = hasImages ? 272 + 2 : 200 + 2; - const showLabel = this.panelWidth.value && this.panelWidth.value > 400; return html`
Upload
- ${this.modelSwitchConfig?.visible.value - ? html` - - ` - : nothing} - ${this.networkSearchConfig.visible.value - ? html` -
- ${PublishIcon()} - ${!showLabel - ? html`Search` - : nothing} - ${showLabel - ? html`Search` - : nothing} -
- ` - : nothing} -
- ${ThinkingIcon()} - ${!showLabel - ? html`Reason` - : nothing} - ${showLabel - ? html`Reason` - : nothing} -
+ + ${status === 'transmitting' || status === 'loading' - ? html`
+ ? html`
` - : html`
` + : html`
`} + ${ArrowUpBigIcon()} + `} `; } @@ -512,20 +515,12 @@ export class AIChatInput extends SignalWatcher( reportResponse('aborted:stop'); }; - private readonly _toggleNetworkSearch = (e: MouseEvent) => { - e.preventDefault(); - e.stopPropagation(); - - const enable = this.networkSearchConfig.enabled.value; - this.networkSearchConfig.setEnabled(!enable); + private readonly _toggleNetworkSearch = (isNetworkActive: boolean) => { + this.networkSearchConfig.setEnabled(isNetworkActive); }; - private readonly _toggleReasoning = (e: MouseEvent) => { - e.preventDefault(); - e.stopPropagation(); - - const enable = this.reasoningConfig.enabled.value; - this.reasoningConfig.setEnabled(!enable); + private readonly _toggleReasoning = (extendedThinking: boolean) => { + this.reasoningConfig.setEnabled(extendedThinking); }; private readonly _handleImageRemove = (index: number) => { diff --git a/packages/frontend/core/src/blocksuite/ai/components/ai-chat-input/preference-popup.ts b/packages/frontend/core/src/blocksuite/ai/components/ai-chat-input/preference-popup.ts new file mode 100644 index 000000000..6394e4d7b --- /dev/null +++ b/packages/frontend/core/src/blocksuite/ai/components/ai-chat-input/preference-popup.ts @@ -0,0 +1,168 @@ +import type { CopilotSessionType } from '@affine/graphql'; +import { + menu, + popMenu, + popupTargetFromElement, +} from '@blocksuite/affine/components/context-menu'; +import { SignalWatcher, WithDisposable } from '@blocksuite/affine/global/lit'; +import { + AiOutlineIcon, + ArrowDownSmallIcon, + ThinkingIcon, + WebIcon, +} from '@blocksuite/icons/lit'; +import { ShadowlessElement } from '@blocksuite/std'; +import { css, html } from 'lit'; +import { property } from 'lit/decorators.js'; + +import type { AIModelSwitchConfig } from './type'; + +export class ChatInputPreference extends SignalWatcher( + WithDisposable(ShadowlessElement) +) { + static override styles = css` + .chat-input-preference-trigger { + display: flex; + align-items: center; + padding: 0px 4px; + color: var(--affine-v2-icon-primary); + transition: all 0.23s ease; + border-radius: 4px; + } + .chat-input-preference-trigger:hover { + background-color: var(--affine-v2-layer-background-hoverOverlay); + } + .chat-input-preference-trigger-label { + font-size: 14px; + line-height: 22px; + font-weight: 500; + padding: 0px 4px; + } + .chat-input-preference-trigger-icon { + font-size: 20px; + line-height: 0; + } + .preference-action { + white-space: nowrap; + min-width: 220px; + } + `; + + @property({ attribute: false }) + accessor session!: CopilotSessionType | undefined; + + // --------- model props start --------- + @property({ attribute: false }) + accessor modelSwitchConfig: AIModelSwitchConfig | undefined = undefined; + + @property({ attribute: false }) + accessor onModelChange: ((modelId: string) => void) | undefined; + + @property({ attribute: false }) + accessor modelId: string | undefined = undefined; + // --------- model props end --------- + + // --------- extended thinking props start --------- + @property({ attribute: false }) + accessor extendedThinking: boolean = false; + + @property({ attribute: false }) + accessor onExtendedThinkingChange: + | ((extendedThinking: boolean) => void) + | undefined; + // --------- extended thinking props end --------- + + // --------- search props start --------- + @property({ attribute: false }) + accessor networkSearchVisible: boolean = false; + + @property({ attribute: false }) + accessor isNetworkActive: boolean = false; + + @property({ attribute: false }) + accessor onNetworkActiveChange: + | ((isNetworkActive: boolean) => void) + | undefined; + // --------- search props end --------- + + private readonly _onModelChange = (modelId: string) => { + this.onModelChange?.(modelId); + }; + + openPreference(e: Event) { + const element = e.currentTarget; + if (!(element instanceof HTMLElement)) return; + const modelItems = []; + const searchItems = []; + + // model switch + if (this.modelSwitchConfig?.visible.value) { + modelItems.push( + menu.subMenu({ + name: 'Model', + prefix: AiOutlineIcon(), + options: { + items: (this.session?.optionalModels ?? []).map(modelId => { + return menu.action({ + name: modelId, + select: () => this._onModelChange(modelId), + }); + }), + }, + }) + ); + } + + modelItems.push( + menu.toggleSwitch({ + name: 'Extended Thinking', + prefix: ThinkingIcon(), + on: this.extendedThinking, + onChange: (value: boolean) => this.onExtendedThinkingChange?.(value), + class: { 'preference-action': true }, + }) + ); + + if (this.networkSearchVisible) { + searchItems.push( + menu.toggleSwitch({ + name: 'Web Search', + prefix: WebIcon(), + on: this.isNetworkActive, + onChange: (value: boolean) => this.onNetworkActiveChange?.(value), + class: { 'preference-action': true }, + testId: 'chat-network-search', + }) + ); + } + + popMenu(popupTargetFromElement(element), { + options: { + items: [ + menu.group({ + items: [...modelItems], + }), + menu.group({ + items: [...searchItems], + }), + ], + testId: 'chat-input-preference', + }, + }); + } + + override render() { + return html``; + } +} diff --git a/packages/frontend/core/src/blocksuite/ai/components/ai-chat-models/ai-chat-models.ts b/packages/frontend/core/src/blocksuite/ai/components/ai-chat-models/ai-chat-models.ts deleted file mode 100644 index ce638500e..000000000 --- a/packages/frontend/core/src/blocksuite/ai/components/ai-chat-models/ai-chat-models.ts +++ /dev/null @@ -1,112 +0,0 @@ -import type { CopilotSessionType } from '@affine/graphql'; -import { createLitPortal } from '@blocksuite/affine/components/portal'; -import { WithDisposable } from '@blocksuite/affine/global/lit'; -import { unsafeCSSVar, unsafeCSSVarV2 } from '@blocksuite/affine/shared/theme'; -import { ShadowlessElement } from '@blocksuite/affine/std'; -import { flip, offset } from '@floating-ui/dom'; -import { css, html, nothing } from 'lit'; -import { property, query } from 'lit/decorators.js'; -import { repeat } from 'lit/directives/repeat.js'; - -export class AIChatModels extends WithDisposable(ShadowlessElement) { - @property() - accessor modelId: string | undefined = undefined; - - @property({ attribute: false }) - accessor onModelChange: ((modelId: string) => void) | undefined; - - @property({ attribute: false }) - accessor session!: CopilotSessionType | undefined; - - @query('.ai-chat-models') - accessor modelsButton!: HTMLDivElement; - - private _abortController: AbortController | null = null; - - static override styles = css` - ai-chat-models { - font-size: 13px; - cursor: pointer; - } - `; - - private readonly _onItemClick = (modelId: string) => { - this.onModelChange?.(modelId); - this._abortController?.abort(); - this._abortController = null; - }; - - private readonly _toggleSwitchModelMenu = () => { - if (this._abortController) { - this._abortController.abort(); - return; - } - - this._abortController = new AbortController(); - this._abortController.signal.addEventListener('abort', () => { - this._abortController = null; - }); - - createLitPortal({ - template: html` -
- ${repeat( - this.session?.optionalModels ?? [], - modelId => modelId, - modelId => { - return html`
this._onItemClick(modelId)} - > - ${modelId} -
`; - } - )} -
`, - portalStyles: { - zIndex: 'var(--affine-z-index-popover)', - }, - container: document.body, - computePosition: { - referenceElement: this.modelsButton, - placement: 'top-start', - middleware: [offset({ crossAxis: -30, mainAxis: 8 }), flip()], - autoUpdate: { animationFrame: true }, - }, - abortController: this._abortController, - closeOnClickAway: true, - }); - }; - - override render() { - if (!this.session) { - return nothing; - } - - return html` -
- ${this.modelId || this.session.model} -
- `; - } -} diff --git a/packages/frontend/core/src/blocksuite/ai/components/ai-chat-models/index.ts b/packages/frontend/core/src/blocksuite/ai/components/ai-chat-models/index.ts deleted file mode 100644 index d93f06b60..000000000 --- a/packages/frontend/core/src/blocksuite/ai/components/ai-chat-models/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './ai-chat-models'; diff --git a/packages/frontend/core/src/blocksuite/ai/components/image-preview-grid.ts b/packages/frontend/core/src/blocksuite/ai/components/image-preview-grid.ts index 48caabc28..be7e01d35 100644 --- a/packages/frontend/core/src/blocksuite/ai/components/image-preview-grid.ts +++ b/packages/frontend/core/src/blocksuite/ai/components/image-preview-grid.ts @@ -18,16 +18,15 @@ export class ImagePreviewGrid extends LitElement { .images-container { display: flex; flex-direction: row; - gap: 4px; + gap: 8px; flex-wrap: nowrap; position: relative; } .image-container { - width: 58px; - height: 58px; + width: 68px; + height: 68px; border-radius: 4px; - border: 1px solid var(--affine-border-color); cursor: pointer; overflow: hidden; position: relative; diff --git a/packages/frontend/core/src/blocksuite/ai/effects.ts b/packages/frontend/core/src/blocksuite/ai/effects.ts index 8832858c2..a7e35cb87 100644 --- a/packages/frontend/core/src/blocksuite/ai/effects.ts +++ b/packages/frontend/core/src/blocksuite/ai/effects.ts @@ -39,7 +39,7 @@ import { ChatPanelTagChip } from './components/ai-chat-chips/tag-chip'; import { AIChatComposer } from './components/ai-chat-composer'; import { AIChatInput } from './components/ai-chat-input'; import { AIChatEmbeddingStatusTooltip } from './components/ai-chat-input/embedding-status-tooltip'; -import { AIChatModels } from './components/ai-chat-models/ai-chat-models'; +import { ChatInputPreference } from './components/ai-chat-input/preference-popup'; import { AIHistoryClear } from './components/ai-history-clear'; import { effects as componentAiItemEffects } from './components/ai-item'; import { AIScrollableTextRenderer } from './components/ai-scrollable-text-renderer'; @@ -109,6 +109,7 @@ export function registerAIEffects() { customElements.define('chat-panel-chips', ChatPanelChips); customElements.define('ai-history-clear', AIHistoryClear); customElements.define('chat-panel-add-popover', ChatPanelAddPopover); + customElements.define('chat-input-preference', ChatInputPreference); customElements.define( 'chat-panel-candidates-popover', ChatPanelCandidatesPopover @@ -118,7 +119,6 @@ export function registerAIEffects() { customElements.define('chat-panel-tag-chip', ChatPanelTagChip); customElements.define('chat-panel-collection-chip', ChatPanelCollectionChip); customElements.define('chat-panel-chip', ChatPanelChip); - customElements.define('ai-chat-models', AIChatModels); customElements.define('ai-error-wrapper', AIErrorWrapper); customElements.define('ai-slides-renderer', AISlidesRenderer); customElements.define('ai-answer-wrapper', AIAnswerWrapper); diff --git a/tests/affine-cloud-copilot/e2e/utils/chat-panel-utils.ts b/tests/affine-cloud-copilot/e2e/utils/chat-panel-utils.ts index 9e453dea9..b54eaaeac 100644 --- a/tests/affine-cloud-copilot/e2e/utils/chat-panel-utils.ts +++ b/tests/affine-cloud-copilot/e2e/utils/chat-panel-utils.ts @@ -306,7 +306,16 @@ export class ChatPanelUtils { } } + public static async openChatInputPreference(page: Page) { + const trigger = page.getByTestId('chat-input-preference-trigger'); + await trigger.click(); + await page.getByTestId('chat-input-preference').waitFor({ + state: 'visible', + }); + } + public static async enableNetworkSearch(page: Page) { + await this.openChatInputPreference(page); const networkSearch = page.getByTestId('chat-network-search'); if ((await networkSearch.getAttribute('data-active')) === 'false') { await networkSearch.click(); @@ -314,6 +323,7 @@ export class ChatPanelUtils { } public static async disableNetworkSearch(page: Page) { + await this.openChatInputPreference(page); const networkSearch = page.getByTestId('chat-network-search'); if ((await networkSearch.getAttribute('data-active')) === 'true') { await networkSearch.click(); @@ -321,6 +331,7 @@ export class ChatPanelUtils { } public static async enableReasoning(page: Page) { + await this.openChatInputPreference(page); const reasoning = page.getByTestId('chat-reasoning'); if ((await reasoning.getAttribute('data-active')) === 'false') { await reasoning.click(); @@ -328,6 +339,7 @@ export class ChatPanelUtils { } public static async disableReasoning(page: Page) { + await this.openChatInputPreference(page); const reasoning = page.getByTestId('chat-reasoning'); if ((await reasoning.getAttribute('data-active')) === 'true') { await reasoning.click();