refactor(core): simplify chat panel user message rendering (#10910)

### TL;DR
Refactor style of  user chat message

> CLOSE AF-2323 AF-2324 AF-2325

### What Changed
* Refactor style of user message.
* Refactor style of image message.
This commit is contained in:
yoyoyohamapi
2025-03-20 04:59:10 +00:00
parent e83f7eba00
commit 5aa36efab0
3 changed files with 117 additions and 86 deletions

View File

@@ -114,10 +114,6 @@ export class ChatPanelMessages extends WithDisposable(ShadowlessElement) {
white-space: nowrap; white-space: nowrap;
} }
.message {
display: contents;
}
.down-indicator { .down-indicator {
position: absolute; position: absolute;
left: 50%; left: 50%;
@@ -249,25 +245,20 @@ export class ChatPanelMessages extends WithDisposable(ShadowlessElement) {
(_, index) => index, (_, index) => index,
(item, index) => { (item, index) => {
const isLast = index === filteredItems.length - 1; const isLast = index === filteredItems.length - 1;
return html`<div class="message"> return isChatMessage(item) && item.role === 'user'
${isChatMessage(item) && item.role === 'user' ? html`<chat-panel-user-message
? html`<chat-panel-user-message .item=${item}
.host=${this.host} ></chat-panel-user-message>`
.item=${item} : html`<chat-panel-assistant-message
.avatarUrl=${this.avatarUrl} .host=${this.host}
.previewSpecBuilder=${this.previewSpecBuilder} .item=${item}
></chat-panel-user-message>` .isLast=${isLast}
: html`<chat-panel-assistant-message .status=${status}
.host=${this.host} .error=${error}
.item=${item} .previewSpecBuilder=${this.previewSpecBuilder}
.isLast=${isLast} .getSessionId=${this.getSessionId}
.status=${status} .retry=${() => this.retry()}
.error=${error} ></chat-panel-assistant-message>`;
.previewSpecBuilder=${this.previewSpecBuilder}
.getSessionId=${this.getSessionId}
.retry=${() => this.retry()}
></chat-panel-assistant-message>`}
</div>`;
} }
)} )}
</div> </div>

View File

@@ -1,77 +1,100 @@
import type { EditorHost } from '@blocksuite/affine/block-std';
import { ShadowlessElement } from '@blocksuite/affine/block-std'; import { ShadowlessElement } from '@blocksuite/affine/block-std';
import { WithDisposable } from '@blocksuite/affine/global/lit'; import { WithDisposable } from '@blocksuite/affine/global/lit';
import { css, html, nothing } from 'lit'; import { css, html, nothing } from 'lit';
import { property } from 'lit/decorators.js'; import { property } from 'lit/decorators.js';
import { repeat } from 'lit/directives/repeat.js';
import { type ChatItem, isChatMessage } from './chat-context'; import { type ChatMessage } from './chat-context';
export class ChatPanelUserMessage extends WithDisposable(ShadowlessElement) { export class ChatPanelUserMessage extends WithDisposable(ShadowlessElement) {
static override styles = css` static override styles = css`
.avatar-container { .chat-user-message {
width: 24px; display: flex;
height: 24px; flex-direction: column;
} align-items: flex-end;
.avatar { .images {
width: 100%; display: flex;
height: 100%; flex-direction: row;
border-radius: 50%; flex-wrap: nowrap;
background-color: var(--affine-primary-color); gap: 8px;
} margin-bottom: 8px;
max-width: 100%;
overflow-x: auto;
padding: 4px;
scrollbar-width: auto;
}
.avatar-container img { .images::-webkit-scrollbar {
width: 100%; height: 4px;
height: 100%; }
border-radius: 50%;
object-fit: cover; .images::-webkit-scrollbar-thumb {
background-color: var(--affine-border-color);
border-radius: 4px;
}
.images::-webkit-scrollbar-track {
background: transparent;
}
img {
max-width: 180px;
max-height: 264px;
object-fit: cover;
border-radius: 8px;
flex-shrink: 0;
}
.text-content {
display: inline-block;
text-align: left;
max-width: 800px;
max-height: 500px;
overflow-y: auto;
background: var(--affine-v2-aI-userTextBackground);
border-radius: 8px;
padding: 12px;
white-space: pre-wrap;
word-wrap: break-word;
}
} }
`; `;
@property({ attribute: false }) @property({ attribute: false })
accessor host!: EditorHost; accessor item!: ChatMessage;
@property({ attribute: false }) renderImages(images: string[]) {
accessor item!: ChatItem; return images.length > 0
? html`<div class="images">
${repeat(
images,
image => image,
image => {
return html`<img src="${image}" />`;
}
)}
</div>`
: nothing;
}
@property({ attribute: false }) renderText(text: string) {
accessor avatarUrl: string = ''; return text.length > 0
? html`<div class="text-content">${text}</div>`
@property({ attribute: false }) : nothing;
accessor previewSpecBuilder: any;
renderAvatar() {
return html`<div class="user-info">
<div class="avatar-container">
${this.avatarUrl
? html`<img .src=${this.avatarUrl} />`
: html`<div class="avatar"></div>`}
</div>
You
</div>`;
} }
renderContent() { renderContent() {
const { host, item } = this; const { item } = this;
if (isChatMessage(item)) { const imagesRendered = item.attachments
return html`<chat-text ? this.renderImages(item.attachments)
.host=${host} : nothing;
.attachments=${item.attachments} return html` ${imagesRendered} ${this.renderText(item.content)} `;
.text=${item.content}
.state=${'finished'}
.previewSpecBuilder=${this.previewSpecBuilder}
></chat-text>`;
}
return nothing;
} }
protected override render() { protected override render() {
return html` return html` <div class="chat-user-message">${this.renderContent()}</div> `;
${this.renderAvatar()}
<div class="item-wrapper">${this.renderContent()}</div>
`;
} }
} }

View File

@@ -106,13 +106,25 @@ const clearChat = async (page: Page) => {
const collectHistory = async (page: Page) => { const collectHistory = async (page: Page) => {
const chatPanel = await page.waitForSelector('.chat-panel-messages'); const chatPanel = await page.waitForSelector('.chat-panel-messages');
return Promise.all( return Promise.all(
Array.from(await chatPanel.$$('.message')).map(async m => ({ Array.from(
name: await m.$('.user-info').then(i => i?.innerText()), await chatPanel.$$('chat-panel-user-message,chat-panel-assistant-message')
content: await m ).map(async m => {
.$('chat-text') const isAssistant = await m.evaluate(
.then(t => t?.$('editor-host')) el => el.tagName === 'CHAT-PANEL-ASSISTANT-MESSAGE'
.then(e => e?.innerText()), );
})) return isAssistant
? {
name: await m.$('.user-info').then(i => i?.innerText()),
content: await m
.$('chat-text')
.then(t => t?.$('editor-host'))
.then(e => e?.innerText()),
}
: {
name: 'You',
content: await m.$('.text-content').then(i => i?.innerText()),
};
})
); );
}; };
@@ -123,12 +135,17 @@ const collectChat = async (page: Page) => {
return []; return [];
} }
// wait ai response // wait ai response
await page.waitForSelector('.chat-panel-messages .message chat-copy-more', { await page.waitForSelector(
timeout: ONE_MINUTE, '.chat-panel-messages chat-panel-assistant-message chat-copy-more',
}); {
timeout: ONE_MINUTE,
}
);
await page.waitForTimeout(200); await page.waitForTimeout(200);
const lastMessage = await chatPanel.$$('.message').then(m => m[m.length - 1]); await page
await lastMessage.waitForSelector('chat-copy-more'); .locator('.chat-panel-messages > chat-panel-assistant-message')
.last()
.locator('chat-copy-more');
await page.waitForTimeout(200); await page.waitForTimeout(200);
return collectHistory(page); return collectHistory(page);
}; };