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}
@@ -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`
-
- `
- : 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();