feat(core): adjust the layout, style, and structure of the AI chat input (#12828)

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

## 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.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
Cats Juice
2025-06-17 09:26:29 +08:00
committed by GitHub
parent cdaaa52845
commit 2366c1aba6
13 changed files with 326 additions and 211 deletions

View File

@@ -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` <div class="${classString}">${this.data.content()}</div>`;
return html` <div
class="${classString}"
data-testid=${ifDefined(this.data.testId)}
>
${this.data.content()}
</div>`;
}
@property({ attribute: false })
@@ -157,7 +164,12 @@ export class MobileMenuButton extends MenuFocusable {
focused: this.isFocused$.value,
...this.data.class,
});
return html` <div class="${classString}">${this.data.content()}</div>`;
return html` <div
class="${classString}"
data-testid=${ifDefined(this.data.testId)}
>
${this.data.content()}
</div>`;
}
@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}
<div class="affine-menu-action-text">
${config.label?.() ?? config.name}
</div>
@@ -276,6 +295,7 @@ export const menuButtonItems = {
return false;
},
class: config.class ?? {},
testId: config.testId,
};
return html`${keyed(config.name, renderButton(data, menu))}`;
},

View File

@@ -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 => {