diff --git a/packages/frontend/core/src/blocksuite/presets/ai/chat-panel/chat-context.ts b/packages/frontend/core/src/blocksuite/presets/ai/chat-panel/chat-context.ts
index c93d03efe1..8a2a955c67 100644
--- a/packages/frontend/core/src/blocksuite/presets/ai/chat-panel/chat-context.ts
+++ b/packages/frontend/core/src/blocksuite/presets/ai/chat-panel/chat-context.ts
@@ -18,6 +18,14 @@ export type ChatAction = {
export type ChatItem = ChatMessage | ChatAction;
+export function isChatAction(item: ChatItem): item is ChatAction {
+ return 'action' in item;
+}
+
+export function isChatMessage(item: ChatItem): item is ChatMessage {
+ return 'role' in item;
+}
+
export type ChatStatus =
| 'loading'
| 'success'
diff --git a/packages/frontend/core/src/blocksuite/presets/ai/chat-panel/chat-panel-chips.ts b/packages/frontend/core/src/blocksuite/presets/ai/chat-panel/chat-panel-chips.ts
index 9789ffe6fc..e051505ad5 100644
--- a/packages/frontend/core/src/blocksuite/presets/ai/chat-panel/chat-panel-chips.ts
+++ b/packages/frontend/core/src/blocksuite/presets/ai/chat-panel/chat-panel-chips.ts
@@ -6,8 +6,8 @@ import { createLitPortal } from '@blocksuite/affine/blocks';
import { WithDisposable } from '@blocksuite/affine/global/utils';
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 { css, html, nothing, type PropertyValues } from 'lit';
+import { property, query, state } from 'lit/decorators.js';
import { repeat } from 'lit/directives/repeat.js';
import { AIProvider } from '../provider';
@@ -21,7 +21,8 @@ export class ChatPanelChips extends WithDisposable(ShadowlessElement) {
display: flex;
flex-wrap: wrap;
}
- .add-button {
+ .add-button,
+ .collapse-button {
display: flex;
align-items: center;
justify-content: center;
@@ -32,8 +33,10 @@ export class ChatPanelChips extends WithDisposable(ShadowlessElement) {
margin: 4px 0;
box-sizing: border-box;
cursor: pointer;
+ font-size: 12px;
}
- .add-button:hover {
+ .add-button:hover,
+ .collapse-button:hover {
background-color: var(--affine-hover-color);
}
`;
@@ -61,13 +64,25 @@ export class ChatPanelChips extends WithDisposable(ShadowlessElement) {
@query('.add-button')
accessor addButton!: HTMLDivElement;
+ @state()
+ accessor isCollapsed = false;
+
override render() {
+ const isCollapsed =
+ this.isCollapsed &&
+ this.chatContextValue.chips.filter(c => c.state !== 'candidate').length >
+ 1;
+
+ const chips = isCollapsed
+ ? this.chatContextValue.chips.slice(0, 1)
+ : this.chatContextValue.chips;
+
return html`
${PlusIcon()}
${repeat(
- this.chatContextValue.chips,
+ chips,
chip => getChipKey(chip),
chip => {
if (isDocChip(chip)) {
@@ -88,9 +103,28 @@ export class ChatPanelChips extends WithDisposable(ShadowlessElement) {
return null;
}
)}
+ ${isCollapsed
+ ? html`
+ +${this.chatContextValue.chips.length - 1}
+
`
+ : nothing}
`;
}
+ protected override updated(_changedProperties: PropertyValues): void {
+ if (
+ _changedProperties.has('chatContextValue') &&
+ _changedProperties.get('chatContextValue')?.status === 'loading' &&
+ this.isCollapsed === false
+ ) {
+ this.isCollapsed = true;
+ }
+ }
+
+ private readonly _toggleCollapse = () => {
+ this.isCollapsed = !this.isCollapsed;
+ };
+
private readonly _toggleAddDocMenu = () => {
if (this._abortController) {
this._abortController.abort();
diff --git a/packages/frontend/core/src/blocksuite/presets/ai/chat-panel/chat-panel-messages.ts b/packages/frontend/core/src/blocksuite/presets/ai/chat-panel/chat-panel-messages.ts
index 67e95fb72f..a18c9d2acb 100644
--- a/packages/frontend/core/src/blocksuite/presets/ai/chat-panel/chat-panel-messages.ts
+++ b/packages/frontend/core/src/blocksuite/presets/ai/chat-panel/chat-panel-messages.ts
@@ -24,7 +24,12 @@ import {
import { AffineAvatarIcon, AffineIcon, DownArrowIcon } from '../_common/icons';
import { AIChatErrorRenderer } from '../messages/error';
import { AIProvider } from '../provider';
-import type { ChatContextValue, ChatItem, ChatMessage } from './chat-context';
+import {
+ type ChatContextValue,
+ type ChatItem,
+ type ChatMessage,
+ isChatMessage,
+} from './chat-context';
import { HISTORY_IMAGE_ACTIONS } from './const';
import { AIPreloadConfig } from './preload-config';
@@ -207,7 +212,7 @@ export class ChatPanelMessages extends WithDisposable(ShadowlessElement) {
const { isLoading } = this;
const filteredItems = items.filter(item => {
return (
- 'role' in item ||
+ isChatMessage(item) ||
item.messages?.length === 3 ||
(HISTORY_IMAGE_ACTIONS.includes(item.action) &&
item.messages?.length === 2)
@@ -244,7 +249,7 @@ export class ChatPanelMessages extends WithDisposable(ShadowlessElement) {
`
: repeat(
filteredItems,
- item => ('role' in item ? item.id : item.sessionId),
+ item => (isChatMessage(item) ? item.id : item.sessionId),
(item, index) => {
const isLast = index === filteredItems.length - 1;
return html`
@@ -317,7 +322,7 @@ export class ChatPanelMessages extends WithDisposable(ShadowlessElement) {
return AIChatErrorRenderer(host, error);
}
- if ('role' in item) {
+ if (isChatMessage(item)) {
const state = isLast
? status !== 'loading' && status !== 'transmitting'
? 'finished'
@@ -375,8 +380,8 @@ export class ChatPanelMessages extends WithDisposable(ShadowlessElement) {
}
renderAvatar(item: ChatItem) {
- const isUser = 'role' in item && item.role === 'user';
- const isAssistant = 'role' in item && item.role === 'assistant';
+ const isUser = isChatMessage(item) && item.role === 'user';
+ const isAssistant = isChatMessage(item) && item.role === 'assistant';
const isWithDocs =
isAssistant &&
item.content &&