- ${answer ? renderImages(answer) : nothing}
+
+ ${answer
+ ? html``
+ : nothing}
`;
}
diff --git a/packages/frontend/core/src/blocksuite/ai/chat-panel/actions/image.ts b/packages/frontend/core/src/blocksuite/ai/chat-panel/actions/image.ts
index 40e73db4d9..de3cd86202 100644
--- a/packages/frontend/core/src/blocksuite/ai/chat-panel/actions/image.ts
+++ b/packages/frontend/core/src/blocksuite/ai/chat-panel/actions/image.ts
@@ -1,4 +1,5 @@
import './action-wrapper';
+import '../content/images';
import type { EditorHost } from '@blocksuite/affine/block-std';
import { ShadowlessElement } from '@blocksuite/affine/block-std';
@@ -8,7 +9,6 @@ import { property } from 'lit/decorators.js';
import { styleMap } from 'lit/directives/style-map.js';
import type { ChatAction } from '../chat-context';
-import { renderImages } from '../components/images';
export class ActionImage extends WithDisposable(ShadowlessElement) {
@property({ attribute: false })
@@ -18,11 +18,13 @@ export class ActionImage extends WithDisposable(ShadowlessElement) {
accessor host!: EditorHost;
protected override render() {
- const answer = this.item.messages[0].attachments;
+ const images = this.item.messages[0].attachments;
return html`
- ${answer ? renderImages(answer) : nothing}
+ ${images
+ ? html``
+ : nothing}
`;
}
diff --git a/packages/frontend/core/src/blocksuite/ai/chat-panel/chat-panel-messages.ts b/packages/frontend/core/src/blocksuite/ai/chat-panel/chat-panel-messages.ts
index 1e642c0bd0..9a09d5383e 100644
--- a/packages/frontend/core/src/blocksuite/ai/chat-panel/chat-panel-messages.ts
+++ b/packages/frontend/core/src/blocksuite/ai/chat-panel/chat-panel-messages.ts
@@ -19,6 +19,7 @@ import { AIProvider } from '../provider';
import {
type ChatContextValue,
type ChatMessage,
+ isChatAction,
isChatMessage,
} from './chat-context';
import { HISTORY_IMAGE_ACTIONS } from './const';
@@ -245,20 +246,28 @@ export class ChatPanelMessages extends WithDisposable(ShadowlessElement) {
(_, index) => index,
(item, index) => {
const isLast = index === filteredItems.length - 1;
- return isChatMessage(item) && item.role === 'user'
- ? html`
`
- : html`
this.retry()}
- >`;
+ if (isChatMessage(item) && item.role === 'user') {
+ return html`
`;
+ } else if (isChatMessage(item) && item.role === 'assistant') {
+ return html`
this.retry()}
+ >`;
+ } else if (isChatAction(item)) {
+ return html`
`;
+ }
+ return nothing;
}
)}
diff --git a/packages/frontend/core/src/blocksuite/ai/chat-panel/chat-panel-user-message.ts b/packages/frontend/core/src/blocksuite/ai/chat-panel/chat-panel-user-message.ts
deleted file mode 100644
index 8715bd9041..0000000000
--- a/packages/frontend/core/src/blocksuite/ai/chat-panel/chat-panel-user-message.ts
+++ /dev/null
@@ -1,105 +0,0 @@
-import { ShadowlessElement } from '@blocksuite/affine/block-std';
-import { WithDisposable } from '@blocksuite/affine/global/lit';
-import { css, html, nothing } from 'lit';
-import { property } from 'lit/decorators.js';
-import { repeat } from 'lit/directives/repeat.js';
-
-import { type ChatMessage } from './chat-context';
-
-export class ChatPanelUserMessage extends WithDisposable(ShadowlessElement) {
- static override styles = css`
- .chat-user-message {
- display: flex;
- flex-direction: column;
- align-items: flex-end;
-
- .images {
- display: flex;
- flex-direction: row;
- flex-wrap: nowrap;
- gap: 8px;
- margin-bottom: 8px;
- max-width: 100%;
- overflow-x: auto;
- padding: 4px;
- scrollbar-width: auto;
- }
-
- .images::-webkit-scrollbar {
- height: 4px;
- }
-
- .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 })
- accessor item!: ChatMessage;
-
- renderImages(images: string[]) {
- return images.length > 0
- ? html`
+ ${repeat(
+ this.images,
+ image => image,
+ image => html`

`
+ )}
+
`;
+ } else {
+ return html`
+ ${repeat(
+ this.images,
+ image => image,
+ image =>
+ html`
+

+
`
+ )}
+
`;
+ }
+ }
+}
+
+declare global {
+ interface HTMLElementTagNameMap {
+ 'chat-content-images': ChatContentImages;
+ }
+}
diff --git a/packages/frontend/core/src/blocksuite/ai/chat-panel/content/pure-text.ts b/packages/frontend/core/src/blocksuite/ai/chat-panel/content/pure-text.ts
new file mode 100644
index 0000000000..756dc39b75
--- /dev/null
+++ b/packages/frontend/core/src/blocksuite/ai/chat-panel/content/pure-text.ts
@@ -0,0 +1,35 @@
+import { ShadowlessElement } from '@blocksuite/affine/block-std';
+import { css, html, nothing } from 'lit';
+import { property } from 'lit/decorators.js';
+
+export class ChatContentPureText extends ShadowlessElement {
+ static override styles = css`
+ .chat-content-pure-text {
+ 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 })
+ accessor text: string = '';
+
+ protected override render() {
+ return this.text.length > 0
+ ? html`
- ${AffineAvatarIcon} AFFiNE AI
+
${isWithDocs
? html`
with your docs`
: nothing}
@@ -82,61 +71,28 @@ export class ChatPanelAssistantMessage extends WithDisposable(
return html`
`;
}
- if (isChatMessage(item)) {
- const state = isLast
- ? status !== 'loading' && status !== 'transmitting'
- ? 'finished'
- : 'generating'
- : 'finished';
- const shouldRenderError = isLast && status === 'error' && !!error;
- return html`
- ${shouldRenderError ? AIChatErrorRenderer(host, error) : nothing}
- ${this.renderEditorActions()}`;
- } else {
- switch (item.action) {
- case 'Create a presentation':
- return html`
`;
- case 'Make it real':
- return html`
`;
- case 'Brainstorm mindmap':
- return html`
`;
- case 'Explain this image':
- case 'Generate a caption':
- return html`
`;
- default:
- if (HISTORY_IMAGE_ACTIONS.includes(item.action)) {
- return html`
`;
- }
+ const state = isLast
+ ? status !== 'loading' && status !== 'transmitting'
+ ? 'finished'
+ : 'generating'
+ : 'finished';
+ const shouldRenderError = isLast && status === 'error' && !!error;
- return html`
`;
- }
- }
+ return html`
+ ${item.attachments
+ ? html`
`
+ : nothing}
+
+ ${shouldRenderError ? AIChatErrorRenderer(host, error) : nothing}
+ ${this.renderEditorActions()}
+ `;
}
renderEditorActions() {
@@ -185,7 +141,7 @@ export class ChatPanelAssistantMessage extends WithDisposable(
protected override render() {
return html`
- ${this.renderAvatar()}
+ ${this.renderHeader()}
${this.renderContent()}
`;
}
@@ -193,6 +149,6 @@ export class ChatPanelAssistantMessage extends WithDisposable(
declare global {
interface HTMLElementTagNameMap {
- 'chat-panel-assistant-message': ChatPanelAssistantMessage;
+ 'chat-message-assistant': ChatMessageAssistant;
}
}
diff --git a/packages/frontend/core/src/blocksuite/ai/chat-panel/message/user.ts b/packages/frontend/core/src/blocksuite/ai/chat-panel/message/user.ts
new file mode 100644
index 0000000000..9c58e4f336
--- /dev/null
+++ b/packages/frontend/core/src/blocksuite/ai/chat-panel/message/user.ts
@@ -0,0 +1,45 @@
+import '../content/images';
+import '../content/pure-text';
+
+import { ShadowlessElement } from '@blocksuite/affine/block-std';
+import { WithDisposable } from '@blocksuite/affine/global/lit';
+import { css, html, nothing } from 'lit';
+import { property } from 'lit/decorators.js';
+
+import { type ChatMessage } from '../chat-context';
+
+export class ChatMessageUser extends WithDisposable(ShadowlessElement) {
+ static override styles = css`
+ .chat-message-user {
+ display: flex;
+ flex-direction: column;
+ align-items: flex-end;
+ }
+ `;
+
+ @property({ attribute: false })
+ accessor item!: ChatMessage;
+
+ renderContent() {
+ const { item } = this;
+
+ return html`
+ ${item.attachments
+ ? html`
`
+ : nothing}
+
+ `;
+ }
+
+ protected override render() {
+ return html`
${this.renderContent()}
`;
+ }
+}
+
+declare global {
+ interface HTMLElementTagNameMap {
+ 'chat-message-user': ChatMessageUser;
+ }
+}
diff --git a/packages/frontend/core/src/blocksuite/ai/effects.ts b/packages/frontend/core/src/blocksuite/ai/effects.ts
index f154a7ffda..d5c2cc5f41 100644
--- a/packages/frontend/core/src/blocksuite/ai/effects.ts
+++ b/packages/frontend/core/src/blocksuite/ai/effects.ts
@@ -15,7 +15,6 @@ import { UserInfo } from './blocks/ai-chat-block/components/user-info';
import { AIChatBlockSchemaExtension } from './blocks/ai-chat-block/model';
import { ChatPanel } from './chat-panel';
import { ActionWrapper } from './chat-panel/actions/action-wrapper';
-import { ChatText } from './chat-panel/actions/chat-text';
import { ActionImage } from './chat-panel/actions/image';
import { ActionImageToText } from './chat-panel/actions/image-to-text';
import { ActionMakeReal } from './chat-panel/actions/make-real';
@@ -23,17 +22,22 @@ import { ActionMindmap } from './chat-panel/actions/mindmap';
import { ActionSlides } from './chat-panel/actions/slides';
import { ActionText } from './chat-panel/actions/text';
import { AILoading } from './chat-panel/ai-loading';
-import { ChatPanelAssistantMessage } from './chat-panel/chat-panel-assistant-message';
import { ChatPanelChips } from './chat-panel/chat-panel-chips';
import { ChatPanelInput } from './chat-panel/chat-panel-input';
import { ChatPanelMessages } from './chat-panel/chat-panel-messages';
-import { ChatPanelUserMessage } from './chat-panel/chat-panel-user-message';
import { ChatPanelAddPopover } from './chat-panel/components/add-popover';
import { ChatPanelChip } from './chat-panel/components/chip';
import { ChatPanelCollectionChip } from './chat-panel/components/collection-chip';
import { ChatPanelDocChip } from './chat-panel/components/doc-chip';
import { ChatPanelFileChip } from './chat-panel/components/file-chip';
import { ChatPanelTagChip } from './chat-panel/components/tag-chip';
+import { AssistantAvatar } from './chat-panel/content/assistant-avatar';
+import { ChatContentImages } from './chat-panel/content/images';
+import { ChatContentPureText } from './chat-panel/content/pure-text';
+import { ChatContentRichText } from './chat-panel/content/rich-text';
+import { ChatMessageAction } from './chat-panel/message/action';
+import { ChatMessageAssistant } from './chat-panel/message/assistant';
+import { ChatMessageUser } from './chat-panel/message/user';
import { effects as componentAiItemEffects } from './components/ai-item';
import { AIScrollableTextRenderer } from './components/ai-scrollable-text-renderer';
import { AskAIButton } from './components/ask-ai-button';
@@ -83,7 +87,6 @@ export function registerAIEffects() {
customElements.define('chat-copy-more', ChatCopyMore);
customElements.define('image-preview-grid', ImagePreviewGrid);
customElements.define('action-wrapper', ActionWrapper);
- customElements.define('chat-text', ChatText);
customElements.define('action-image-to-text', ActionImageToText);
customElements.define('action-image', ActionImage);
customElements.define('action-make-real', ActionMakeReal);
@@ -91,11 +94,6 @@ export function registerAIEffects() {
customElements.define('action-slides', ActionSlides);
customElements.define('action-text', ActionText);
customElements.define('ai-loading', AILoading);
- customElements.define(
- 'chat-panel-assistant-message',
- ChatPanelAssistantMessage
- );
- customElements.define('chat-panel-user-message', ChatPanelUserMessage);
customElements.define('chat-panel-input', ChatPanelInput);
customElements.define('chat-panel-messages', ChatPanelMessages);
customElements.define('chat-panel', ChatPanel);
@@ -136,6 +134,13 @@ export function registerAIEffects() {
customElements.define('ai-panel-input', AIPanelInput);
customElements.define('ai-panel-generating', AIPanelGenerating);
customElements.define('ai-panel-error', AIPanelError);
+ customElements.define('chat-assistant-avatar', AssistantAvatar);
+ customElements.define('chat-content-images', ChatContentImages);
+ customElements.define('chat-content-pure-text', ChatContentPureText);
+ customElements.define('chat-content-rich-text', ChatContentRichText);
+ customElements.define('chat-message-action', ChatMessageAction);
+ customElements.define('chat-message-assistant', ChatMessageAssistant);
+ customElements.define('chat-message-user', ChatMessageUser);
customElements.define(AFFINE_AI_PANEL_WIDGET, AffineAIPanelWidget);
customElements.define(AFFINE_EDGELESS_COPILOT_WIDGET, EdgelessCopilotWidget);
diff --git a/tests/affine-cloud-copilot/e2e/copilot.spec.ts b/tests/affine-cloud-copilot/e2e/copilot.spec.ts
index f7b24b6fb2..79b2cd47c8 100644
--- a/tests/affine-cloud-copilot/e2e/copilot.spec.ts
+++ b/tests/affine-cloud-copilot/e2e/copilot.spec.ts
@@ -107,22 +107,30 @@ const collectHistory = async (page: Page) => {
const chatPanel = await page.waitForSelector('.chat-panel-messages');
return Promise.all(
Array.from(
- await chatPanel.$$('chat-panel-user-message,chat-panel-assistant-message')
+ await chatPanel.$$(
+ 'chat-message-user,chat-message-assistant,chat-message-action'
+ )
).map(async m => {
const isAssistant = await m.evaluate(
- el => el.tagName === 'CHAT-PANEL-ASSISTANT-MESSAGE'
+ el => el.tagName.toLocaleLowerCase() === 'chat-message-assistant'
);
- return isAssistant
+ const isChatAction = await m.evaluate(
+ el => el.tagName.toLocaleLowerCase() === 'chat-message-action'
+ );
+
+ return isAssistant || isChatAction
? {
name: await m.$('.user-info').then(i => i?.innerText()),
content: await m
- .$('chat-text')
+ .$('chat-content-rich-text')
.then(t => t?.$('editor-host'))
.then(e => e?.innerText()),
}
: {
name: 'You',
- content: await m.$('.text-content').then(i => i?.innerText()),
+ content: await m
+ .$('chat-content-pure-text')
+ .then(i => i?.innerText()),
};
})
);
@@ -136,14 +144,14 @@ const collectChat = async (page: Page) => {
}
// wait ai response
await page.waitForSelector(
- '.chat-panel-messages chat-panel-assistant-message chat-copy-more',
+ '.chat-panel-messages chat-message-assistant chat-copy-more',
{
timeout: ONE_MINUTE,
}
);
await page.waitForTimeout(200);
await page
- .locator('.chat-panel-messages > chat-panel-assistant-message')
+ .locator('.chat-panel-messages > chat-message-assistant')
.last()
.locator('chat-copy-more');
await page.waitForTimeout(200);