mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-12 04:18:54 +00:00
refactor(core): reorganize chat-panel components (#10935)
### TL;DR Split complex chat message component into smaller, reusable units with finer granularity > CLOSE AF-2323 AF-2326 ### What Changed * Split messages into components: * `<chat-message-assistant />` * `<chat-message-user />` * `<chat-message-action />` * Split message content into types: * `<chat-content-images />` * `<chat-content-rich-text />` * `<chat-content-pure-text />`
This commit is contained in:
@@ -24,7 +24,6 @@ import { property, state } from 'lit/decorators.js';
|
||||
|
||||
import { createTextRenderer } from '../../components/text-renderer';
|
||||
import type { ChatAction } from '../chat-context';
|
||||
import { renderImages } from '../components/images';
|
||||
import { HISTORY_IMAGE_ACTIONS } from '../const';
|
||||
|
||||
const icons: Record<string, TemplateResult<1>> = {
|
||||
@@ -141,7 +140,10 @@ export class ActionWrapper extends WithDisposable(LitElement) {
|
||||
<div class="answer-prompt">
|
||||
<div class="subtitle">Answer</div>
|
||||
${HISTORY_IMAGE_ACTIONS.includes(item.action)
|
||||
? images && renderImages(images)
|
||||
? images &&
|
||||
html`<chat-content-images
|
||||
.images=${images}
|
||||
></chat-content-images>`
|
||||
: nothing}
|
||||
${answer
|
||||
? createTextRenderer(this.host, { customHeading: true })(answer)
|
||||
|
||||
@@ -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 ActionImageToText extends WithDisposable(ShadowlessElement) {
|
||||
@property({ attribute: false })
|
||||
@@ -21,8 +21,14 @@ export class ActionImageToText extends WithDisposable(ShadowlessElement) {
|
||||
const answer = this.item.messages[1].attachments;
|
||||
|
||||
return html`<action-wrapper .host=${this.host} .item=${this.item}>
|
||||
<div style=${styleMap({ marginBottom: '12px' })}>
|
||||
${answer ? renderImages(answer) : nothing}
|
||||
<div
|
||||
style=${styleMap({
|
||||
marginBottom: '12px',
|
||||
})}
|
||||
>
|
||||
${answer
|
||||
? html`<chat-content-images .images=${answer}></chat-content-images>`
|
||||
: nothing}
|
||||
</div>
|
||||
</action-wrapper>`;
|
||||
}
|
||||
|
||||
@@ -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`<action-wrapper .host=${this.host} .item=${this.item}>
|
||||
<div style=${styleMap({ marginBottom: '12px' })}>
|
||||
${answer ? renderImages(answer) : nothing}
|
||||
${images
|
||||
? html`<chat-content-images .images=${images}></chat-content-images>`
|
||||
: nothing}
|
||||
</div>
|
||||
</action-wrapper>`;
|
||||
}
|
||||
|
||||
@@ -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`<chat-panel-user-message
|
||||
.item=${item}
|
||||
></chat-panel-user-message>`
|
||||
: html`<chat-panel-assistant-message
|
||||
.host=${this.host}
|
||||
.item=${item}
|
||||
.isLast=${isLast}
|
||||
.status=${status}
|
||||
.error=${error}
|
||||
.previewSpecBuilder=${this.previewSpecBuilder}
|
||||
.getSessionId=${this.getSessionId}
|
||||
.retry=${() => this.retry()}
|
||||
></chat-panel-assistant-message>`;
|
||||
if (isChatMessage(item) && item.role === 'user') {
|
||||
return html`<chat-message-user
|
||||
.item=${item}
|
||||
></chat-message-user>`;
|
||||
} else if (isChatMessage(item) && item.role === 'assistant') {
|
||||
return html`<chat-message-assistant
|
||||
.host=${this.host}
|
||||
.item=${item}
|
||||
.isLast=${isLast}
|
||||
.status=${isLast ? status : 'idle'}
|
||||
.error=${isLast ? error : null}
|
||||
.previewSpecBuilder=${this.previewSpecBuilder}
|
||||
.getSessionId=${this.getSessionId}
|
||||
.retry=${() => this.retry()}
|
||||
></chat-message-assistant>`;
|
||||
} else if (isChatAction(item)) {
|
||||
return html`<chat-message-action
|
||||
.host=${this.host}
|
||||
.item=${item}
|
||||
></chat-message-action>`;
|
||||
}
|
||||
return nothing;
|
||||
}
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -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`<div class="images">
|
||||
${repeat(
|
||||
images,
|
||||
image => image,
|
||||
image => {
|
||||
return html`<img src="${image}" />`;
|
||||
}
|
||||
)}
|
||||
</div>`
|
||||
: nothing;
|
||||
}
|
||||
|
||||
renderText(text: string) {
|
||||
return text.length > 0
|
||||
? html`<div class="text-content">${text}</div>`
|
||||
: nothing;
|
||||
}
|
||||
|
||||
renderContent() {
|
||||
const { item } = this;
|
||||
|
||||
const imagesRendered = item.attachments
|
||||
? this.renderImages(item.attachments)
|
||||
: nothing;
|
||||
return html` ${imagesRendered} ${this.renderText(item.content)} `;
|
||||
}
|
||||
|
||||
protected override render() {
|
||||
return html` <div class="chat-user-message">${this.renderContent()}</div> `;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'chat-panel-user-message': ChatPanelUserMessage;
|
||||
}
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
import { html } from 'lit';
|
||||
import { repeat } from 'lit/directives/repeat.js';
|
||||
|
||||
export const renderImages = (images: string[]) => {
|
||||
return html`<style>
|
||||
.images-container {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
flex-direction: column;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.image-container {
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: 70%;
|
||||
max-width: 320px;
|
||||
|
||||
img {
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
width: auto;
|
||||
height: auto;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<div class="images-container">
|
||||
${repeat(
|
||||
images,
|
||||
image => image,
|
||||
image => {
|
||||
return html`<div class="image-container">
|
||||
<img src="${image}" />
|
||||
</div>`;
|
||||
}
|
||||
)}
|
||||
</div>`;
|
||||
};
|
||||
@@ -0,0 +1,30 @@
|
||||
import { ShadowlessElement } from '@blocksuite/affine/block-std';
|
||||
import { AiIcon } from '@blocksuite/icons/lit';
|
||||
import { css, html } from 'lit';
|
||||
|
||||
const AffineAvatarIcon = AiIcon({
|
||||
width: '20px',
|
||||
height: '20px',
|
||||
style: 'color: var(--affine-primary-color)',
|
||||
});
|
||||
|
||||
export class AssistantAvatar extends ShadowlessElement {
|
||||
static override styles = css`
|
||||
.assistant-avatar {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
`;
|
||||
protected override render() {
|
||||
return html`<span class="assistant-avatar"
|
||||
>${AffineAvatarIcon} AFFiNE AI</span
|
||||
>`;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'chat-assistant-avatar': AssistantAvatar;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
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';
|
||||
|
||||
export class ChatContentImages extends WithDisposable(ShadowlessElement) {
|
||||
static override styles = css`
|
||||
.chat-content-images-row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: nowrap;
|
||||
gap: 8px;
|
||||
margin-bottom: 8px;
|
||||
max-width: 100%;
|
||||
overflow-x: auto;
|
||||
padding: 4px;
|
||||
scrollbar-width: auto;
|
||||
}
|
||||
|
||||
.chat-content-images-row::-webkit-scrollbar {
|
||||
height: 4px;
|
||||
}
|
||||
|
||||
.chat-content-images-row::-webkit-scrollbar-thumb {
|
||||
background-color: var(--affine-border-color);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.chat-content-images-row::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.chat-content-images-row img {
|
||||
max-width: 180px;
|
||||
max-height: 264px;
|
||||
object-fit: cover;
|
||||
border-radius: 8px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.chat-content-images-column {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
flex-direction: column;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.chat-content-images-column .image-container {
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: 70%;
|
||||
max-width: 320px;
|
||||
}
|
||||
|
||||
.chat-content-images-column .image-container img {
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
width: auto;
|
||||
height: auto;
|
||||
}
|
||||
`;
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor images: string[] = [];
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor layout: 'row' | 'column' = 'row';
|
||||
|
||||
protected override render() {
|
||||
if (this.images.length === 0) {
|
||||
return nothing;
|
||||
}
|
||||
|
||||
if (this.layout === 'row') {
|
||||
return html`<div class="chat-content-images-row">
|
||||
${repeat(
|
||||
this.images,
|
||||
image => image,
|
||||
image => html`<img src="${image}" />`
|
||||
)}
|
||||
</div>`;
|
||||
} else {
|
||||
return html`<div class="chat-content-images-column">
|
||||
${repeat(
|
||||
this.images,
|
||||
image => image,
|
||||
image =>
|
||||
html`<div class="image-container">
|
||||
<img src="${image}" />
|
||||
</div>`
|
||||
)}
|
||||
</div>`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'chat-content-images': ChatContentImages;
|
||||
}
|
||||
}
|
||||
@@ -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`<div class="chat-content-pure-text">${this.text}</div>`
|
||||
: nothing;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'chat-content-pure-text': ChatContentPureText;
|
||||
}
|
||||
}
|
||||
@@ -1,22 +1,16 @@
|
||||
import './action-wrapper';
|
||||
|
||||
import type { EditorHost } from '@blocksuite/affine/block-std';
|
||||
import { ShadowlessElement } from '@blocksuite/affine/block-std';
|
||||
import { WithDisposable } from '@blocksuite/affine/global/lit';
|
||||
import type { SpecBuilder } from '@blocksuite/affine/shared/utils';
|
||||
import { html, nothing } from 'lit';
|
||||
import { html } from 'lit';
|
||||
import { property } from 'lit/decorators.js';
|
||||
|
||||
import { createTextRenderer } from '../../components/text-renderer';
|
||||
import { renderImages } from '../components/images';
|
||||
|
||||
export class ChatText extends WithDisposable(ShadowlessElement) {
|
||||
export class ChatContentRichText extends WithDisposable(ShadowlessElement) {
|
||||
@property({ attribute: false })
|
||||
accessor host!: EditorHost;
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor attachments: string[] | undefined = undefined;
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor text!: string;
|
||||
|
||||
@@ -27,18 +21,16 @@ export class ChatText extends WithDisposable(ShadowlessElement) {
|
||||
accessor previewSpecBuilder!: SpecBuilder;
|
||||
|
||||
protected override render() {
|
||||
const { attachments, text, host } = this;
|
||||
return html`${attachments && attachments.length > 0
|
||||
? renderImages(attachments)
|
||||
: nothing}${createTextRenderer(host, {
|
||||
const { text, host } = this;
|
||||
return html`${createTextRenderer(host, {
|
||||
customHeading: true,
|
||||
extensions: this.previewSpecBuilder.value,
|
||||
})(text, this.state)} `;
|
||||
})(text, this.state)}`;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'chat-text': ChatText;
|
||||
'chat-content-rich-text': ChatContentRichText;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
import '../content/assistant-avatar';
|
||||
|
||||
import type { EditorHost } from '@blocksuite/affine/block-std';
|
||||
import { ShadowlessElement } from '@blocksuite/affine/block-std';
|
||||
import { WithDisposable } from '@blocksuite/affine/global/lit';
|
||||
import { html } from 'lit';
|
||||
import { property } from 'lit/decorators.js';
|
||||
|
||||
import { type ChatAction } from '../chat-context';
|
||||
import { HISTORY_IMAGE_ACTIONS } from '../const';
|
||||
|
||||
export class ChatMessageAction extends WithDisposable(ShadowlessElement) {
|
||||
@property({ attribute: false })
|
||||
accessor host!: EditorHost;
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor item!: ChatAction;
|
||||
|
||||
renderHeader() {
|
||||
return html`
|
||||
<div class="user-info">
|
||||
<chat-assistant-avatar></chat-assistant-avatar>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
renderContent() {
|
||||
const { host, item } = this;
|
||||
|
||||
switch (item.action) {
|
||||
case 'Create a presentation':
|
||||
return html`<action-slides
|
||||
.host=${host}
|
||||
.item=${item}
|
||||
></action-slides>`;
|
||||
case 'Make it real':
|
||||
return html`<action-make-real
|
||||
.host=${host}
|
||||
.item=${item}
|
||||
></action-make-real>`;
|
||||
case 'Brainstorm mindmap':
|
||||
return html`<action-mindmap
|
||||
.host=${host}
|
||||
.item=${item}
|
||||
></action-mindmap>`;
|
||||
case 'Explain this image':
|
||||
case 'Generate a caption':
|
||||
return html`<action-image-to-text
|
||||
.host=${host}
|
||||
.item=${item}
|
||||
></action-image-to-text>`;
|
||||
default:
|
||||
if (HISTORY_IMAGE_ACTIONS.includes(item.action)) {
|
||||
return html`<action-image
|
||||
.host=${host}
|
||||
.item=${item}
|
||||
></action-image>`;
|
||||
}
|
||||
|
||||
return html`<action-text
|
||||
.item=${item}
|
||||
.host=${host}
|
||||
.isCode=${item.action === 'Explain this code' ||
|
||||
item.action === 'Check code error'}
|
||||
></action-text>`;
|
||||
}
|
||||
}
|
||||
|
||||
protected override render() {
|
||||
return html` ${this.renderHeader()} ${this.renderContent()} `;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'chat-message-action': ChatMessageAction;
|
||||
}
|
||||
}
|
||||
@@ -1,30 +1,22 @@
|
||||
import '../content/assistant-avatar';
|
||||
import '../content/rich-text';
|
||||
|
||||
import type { EditorHost } from '@blocksuite/affine/block-std';
|
||||
import { ShadowlessElement } from '@blocksuite/affine/block-std';
|
||||
import { WithDisposable } from '@blocksuite/affine/global/lit';
|
||||
import { isInsidePageEditor } from '@blocksuite/affine/shared/utils';
|
||||
import { AiIcon } from '@blocksuite/icons/lit';
|
||||
import { css, html, nothing } from 'lit';
|
||||
import { property } from 'lit/decorators.js';
|
||||
|
||||
import {
|
||||
EdgelessEditorActions,
|
||||
PageEditorActions,
|
||||
} from '../_common/chat-actions-handle';
|
||||
import { type AIError } from '../components/ai-item/types';
|
||||
import { AIChatErrorRenderer } from '../messages/error';
|
||||
import { isChatMessage } from './chat-context';
|
||||
import { type ChatItem } from './chat-context';
|
||||
import { HISTORY_IMAGE_ACTIONS } from './const';
|
||||
} from '../../_common/chat-actions-handle';
|
||||
import { type AIError } from '../../components/ai-item/types';
|
||||
import { AIChatErrorRenderer } from '../../messages/error';
|
||||
import { type ChatMessage, isChatMessage } from '../chat-context';
|
||||
|
||||
const AffineAvatarIcon = AiIcon({
|
||||
width: '20px',
|
||||
height: '20px',
|
||||
style: 'color: var(--affine-primary-color)',
|
||||
});
|
||||
|
||||
export class ChatPanelAssistantMessage extends WithDisposable(
|
||||
ShadowlessElement
|
||||
) {
|
||||
export class ChatMessageAssistant extends WithDisposable(ShadowlessElement) {
|
||||
static override styles = css`
|
||||
.message-info {
|
||||
color: var(--affine-placeholder-color);
|
||||
@@ -37,7 +29,7 @@ export class ChatPanelAssistantMessage extends WithDisposable(
|
||||
accessor host!: EditorHost;
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor item!: ChatItem;
|
||||
accessor item!: ChatMessage;
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor isLast: boolean = false;
|
||||
@@ -57,18 +49,15 @@ export class ChatPanelAssistantMessage extends WithDisposable(
|
||||
@property({ attribute: false })
|
||||
accessor retry!: () => void;
|
||||
|
||||
renderAvatar() {
|
||||
const isAssistant =
|
||||
isChatMessage(this.item) && this.item.role === 'assistant';
|
||||
renderHeader() {
|
||||
const isWithDocs =
|
||||
isAssistant &&
|
||||
'content' in this.item &&
|
||||
this.item.content &&
|
||||
this.item.content.includes('[^') &&
|
||||
/\[\^\d+\]:{"type":"doc","docId":"[^"]+"}/.test(this.item.content);
|
||||
|
||||
return html`<div class="user-info">
|
||||
${AffineAvatarIcon} AFFiNE AI
|
||||
<chat-assistant-avatar></chat-assistant-avatar>
|
||||
${isWithDocs
|
||||
? html`<span class="message-info">with your docs</span>`
|
||||
: nothing}
|
||||
@@ -82,61 +71,28 @@ export class ChatPanelAssistantMessage extends WithDisposable(
|
||||
return html`<ai-loading></ai-loading>`;
|
||||
}
|
||||
|
||||
if (isChatMessage(item)) {
|
||||
const state = isLast
|
||||
? status !== 'loading' && status !== 'transmitting'
|
||||
? 'finished'
|
||||
: 'generating'
|
||||
: 'finished';
|
||||
const shouldRenderError = isLast && status === 'error' && !!error;
|
||||
return html`<chat-text
|
||||
.host=${host}
|
||||
.attachments=${item.attachments}
|
||||
.text=${item.content}
|
||||
.state=${state}
|
||||
.previewSpecBuilder=${this.previewSpecBuilder}
|
||||
></chat-text>
|
||||
${shouldRenderError ? AIChatErrorRenderer(host, error) : nothing}
|
||||
${this.renderEditorActions()}`;
|
||||
} else {
|
||||
switch (item.action) {
|
||||
case 'Create a presentation':
|
||||
return html`<action-slides
|
||||
.host=${host}
|
||||
.item=${item}
|
||||
></action-slides>`;
|
||||
case 'Make it real':
|
||||
return html`<action-make-real
|
||||
.host=${host}
|
||||
.item=${item}
|
||||
></action-make-real>`;
|
||||
case 'Brainstorm mindmap':
|
||||
return html`<action-mindmap
|
||||
.host=${host}
|
||||
.item=${item}
|
||||
></action-mindmap>`;
|
||||
case 'Explain this image':
|
||||
case 'Generate a caption':
|
||||
return html`<action-image-to-text
|
||||
.host=${host}
|
||||
.item=${item}
|
||||
></action-image-to-text>`;
|
||||
default:
|
||||
if (HISTORY_IMAGE_ACTIONS.includes(item.action)) {
|
||||
return html`<action-image
|
||||
.host=${host}
|
||||
.item=${item}
|
||||
></action-image>`;
|
||||
}
|
||||
const state = isLast
|
||||
? status !== 'loading' && status !== 'transmitting'
|
||||
? 'finished'
|
||||
: 'generating'
|
||||
: 'finished';
|
||||
const shouldRenderError = isLast && status === 'error' && !!error;
|
||||
|
||||
return html`<action-text
|
||||
.item=${item}
|
||||
.host=${host}
|
||||
.isCode=${item.action === 'Explain this code' ||
|
||||
item.action === 'Check code error'}
|
||||
></action-text>`;
|
||||
}
|
||||
}
|
||||
return html`
|
||||
${item.attachments
|
||||
? html`<chat-content-images
|
||||
.images=${item.attachments}
|
||||
></chat-content-images>`
|
||||
: nothing}
|
||||
<chat-content-rich-text
|
||||
.host=${host}
|
||||
.text=${item.content}
|
||||
.state=${state}
|
||||
.previewSpecBuilder=${this.previewSpecBuilder}
|
||||
></chat-content-rich-text>
|
||||
${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()}
|
||||
<div class="item-wrapper">${this.renderContent()}</div>
|
||||
`;
|
||||
}
|
||||
@@ -193,6 +149,6 @@ export class ChatPanelAssistantMessage extends WithDisposable(
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'chat-panel-assistant-message': ChatPanelAssistantMessage;
|
||||
'chat-message-assistant': ChatMessageAssistant;
|
||||
}
|
||||
}
|
||||
@@ -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`<chat-content-images
|
||||
.images=${item.attachments}
|
||||
></chat-content-images>`
|
||||
: nothing}
|
||||
<chat-content-pure-text .text=${item.content}></chat-content-pure-text>
|
||||
`;
|
||||
}
|
||||
|
||||
protected override render() {
|
||||
return html` <div class="chat-message-user">${this.renderContent()}</div> `;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'chat-message-user': ChatMessageUser;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user