mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-08-08 20:57:08 +08:00
feat(core): auto collapse ai chips (#10209)
Support issue [BS-2545](https://linear.app/affine-design/issue/BS-2545). Automatically collapse the AI chips when starting a new chat. 
This commit is contained in:
@@ -18,6 +18,14 @@ export type ChatAction = {
|
|||||||
|
|
||||||
export type ChatItem = ChatMessage | 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 =
|
export type ChatStatus =
|
||||||
| 'loading'
|
| 'loading'
|
||||||
| 'success'
|
| 'success'
|
||||||
|
|||||||
@@ -6,8 +6,8 @@ import { createLitPortal } from '@blocksuite/affine/blocks';
|
|||||||
import { WithDisposable } from '@blocksuite/affine/global/utils';
|
import { WithDisposable } from '@blocksuite/affine/global/utils';
|
||||||
import { PlusIcon } from '@blocksuite/icons/lit';
|
import { PlusIcon } from '@blocksuite/icons/lit';
|
||||||
import { flip, offset } from '@floating-ui/dom';
|
import { flip, offset } from '@floating-ui/dom';
|
||||||
import { css, html } from 'lit';
|
import { css, html, nothing, type PropertyValues } from 'lit';
|
||||||
import { property, query } from 'lit/decorators.js';
|
import { property, query, state } from 'lit/decorators.js';
|
||||||
import { repeat } from 'lit/directives/repeat.js';
|
import { repeat } from 'lit/directives/repeat.js';
|
||||||
|
|
||||||
import { AIProvider } from '../provider';
|
import { AIProvider } from '../provider';
|
||||||
@@ -21,7 +21,8 @@ export class ChatPanelChips extends WithDisposable(ShadowlessElement) {
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
}
|
}
|
||||||
.add-button {
|
.add-button,
|
||||||
|
.collapse-button {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
@@ -32,8 +33,10 @@ export class ChatPanelChips extends WithDisposable(ShadowlessElement) {
|
|||||||
margin: 4px 0;
|
margin: 4px 0;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
font-size: 12px;
|
||||||
}
|
}
|
||||||
.add-button:hover {
|
.add-button:hover,
|
||||||
|
.collapse-button:hover {
|
||||||
background-color: var(--affine-hover-color);
|
background-color: var(--affine-hover-color);
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
@@ -61,13 +64,25 @@ export class ChatPanelChips extends WithDisposable(ShadowlessElement) {
|
|||||||
@query('.add-button')
|
@query('.add-button')
|
||||||
accessor addButton!: HTMLDivElement;
|
accessor addButton!: HTMLDivElement;
|
||||||
|
|
||||||
|
@state()
|
||||||
|
accessor isCollapsed = false;
|
||||||
|
|
||||||
override render() {
|
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` <div class="chips-wrapper">
|
return html` <div class="chips-wrapper">
|
||||||
<div class="add-button" @click=${this._toggleAddDocMenu}>
|
<div class="add-button" @click=${this._toggleAddDocMenu}>
|
||||||
${PlusIcon()}
|
${PlusIcon()}
|
||||||
</div>
|
</div>
|
||||||
${repeat(
|
${repeat(
|
||||||
this.chatContextValue.chips,
|
chips,
|
||||||
chip => getChipKey(chip),
|
chip => getChipKey(chip),
|
||||||
chip => {
|
chip => {
|
||||||
if (isDocChip(chip)) {
|
if (isDocChip(chip)) {
|
||||||
@@ -88,9 +103,28 @@ export class ChatPanelChips extends WithDisposable(ShadowlessElement) {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
)}
|
)}
|
||||||
|
${isCollapsed
|
||||||
|
? html`<div class="collapse-button" @click=${this._toggleCollapse}>
|
||||||
|
+${this.chatContextValue.chips.length - 1}
|
||||||
|
</div>`
|
||||||
|
: nothing}
|
||||||
</div>`;
|
</div>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 = () => {
|
private readonly _toggleAddDocMenu = () => {
|
||||||
if (this._abortController) {
|
if (this._abortController) {
|
||||||
this._abortController.abort();
|
this._abortController.abort();
|
||||||
|
|||||||
@@ -24,7 +24,12 @@ import {
|
|||||||
import { AffineAvatarIcon, AffineIcon, DownArrowIcon } from '../_common/icons';
|
import { AffineAvatarIcon, AffineIcon, DownArrowIcon } from '../_common/icons';
|
||||||
import { AIChatErrorRenderer } from '../messages/error';
|
import { AIChatErrorRenderer } from '../messages/error';
|
||||||
import { AIProvider } from '../provider';
|
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 { HISTORY_IMAGE_ACTIONS } from './const';
|
||||||
import { AIPreloadConfig } from './preload-config';
|
import { AIPreloadConfig } from './preload-config';
|
||||||
|
|
||||||
@@ -207,7 +212,7 @@ export class ChatPanelMessages extends WithDisposable(ShadowlessElement) {
|
|||||||
const { isLoading } = this;
|
const { isLoading } = this;
|
||||||
const filteredItems = items.filter(item => {
|
const filteredItems = items.filter(item => {
|
||||||
return (
|
return (
|
||||||
'role' in item ||
|
isChatMessage(item) ||
|
||||||
item.messages?.length === 3 ||
|
item.messages?.length === 3 ||
|
||||||
(HISTORY_IMAGE_ACTIONS.includes(item.action) &&
|
(HISTORY_IMAGE_ACTIONS.includes(item.action) &&
|
||||||
item.messages?.length === 2)
|
item.messages?.length === 2)
|
||||||
@@ -244,7 +249,7 @@ export class ChatPanelMessages extends WithDisposable(ShadowlessElement) {
|
|||||||
</div> `
|
</div> `
|
||||||
: repeat(
|
: repeat(
|
||||||
filteredItems,
|
filteredItems,
|
||||||
item => ('role' in item ? item.id : item.sessionId),
|
item => (isChatMessage(item) ? item.id : item.sessionId),
|
||||||
(item, index) => {
|
(item, index) => {
|
||||||
const isLast = index === filteredItems.length - 1;
|
const isLast = index === filteredItems.length - 1;
|
||||||
return html`<div class="message">
|
return html`<div class="message">
|
||||||
@@ -317,7 +322,7 @@ export class ChatPanelMessages extends WithDisposable(ShadowlessElement) {
|
|||||||
return AIChatErrorRenderer(host, error);
|
return AIChatErrorRenderer(host, error);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ('role' in item) {
|
if (isChatMessage(item)) {
|
||||||
const state = isLast
|
const state = isLast
|
||||||
? status !== 'loading' && status !== 'transmitting'
|
? status !== 'loading' && status !== 'transmitting'
|
||||||
? 'finished'
|
? 'finished'
|
||||||
@@ -375,8 +380,8 @@ export class ChatPanelMessages extends WithDisposable(ShadowlessElement) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
renderAvatar(item: ChatItem) {
|
renderAvatar(item: ChatItem) {
|
||||||
const isUser = 'role' in item && item.role === 'user';
|
const isUser = isChatMessage(item) && item.role === 'user';
|
||||||
const isAssistant = 'role' in item && item.role === 'assistant';
|
const isAssistant = isChatMessage(item) && item.role === 'assistant';
|
||||||
const isWithDocs =
|
const isWithDocs =
|
||||||
isAssistant &&
|
isAssistant &&
|
||||||
item.content &&
|
item.content &&
|
||||||
|
|||||||
Reference in New Issue
Block a user