Skip to content

Commit f3e1233

Browse files
committed
Merge branch 'dmdimitrov/chat-ai-component' of https://github.com/IgniteUI/igniteui-webcomponents into dmdimitrov/chat-ai-component
2 parents c44b35b + d5353c3 commit f3e1233

6 files changed

Lines changed: 155 additions & 77 deletions

File tree

src/components/chat/chat-message-list.ts

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { repeat } from 'lit/directives/repeat.js';
44
import { registerComponent } from '../common/definitions/register.js';
55
import IgcChatMessageComponent from './chat-message.js';
66
import { styles } from './themes/message-list.base.css.js';
7-
import type { IgcMessage } from './types.js';
7+
import type { AttachmentTemplate, IgcMessage } from './types.js';
88

99
/**
1010
*
@@ -31,6 +31,18 @@ export default class IgcChatMessageListComponent extends LitElement {
3131
@property({ type: Boolean, attribute: 'disable-auto-scroll' })
3232
public disableAutoScroll = false;
3333

34+
@property({ type: Function })
35+
public attachmentTemplate?: AttachmentTemplate;
36+
37+
@property({ type: Function })
38+
public attachmentHeaderTemplate?: AttachmentTemplate;
39+
40+
@property({ type: Function })
41+
public attachmentActionsTemplate?: AttachmentTemplate;
42+
43+
@property({ type: Function })
44+
public attachmentContentTemplate?: AttachmentTemplate;
45+
3446
private formatDate(date: Date): string {
3547
const today = new Date();
3648
const yesterday = new Date(today);
@@ -109,6 +121,10 @@ export default class IgcChatMessageListComponent extends LitElement {
109121
<igc-chat-message
110122
.message=${message}
111123
.isResponse=${message.isResponse}
124+
.attachmentTemplate=${this.attachmentTemplate}
125+
.attachmentHeaderTemplate=${this.attachmentHeaderTemplate}
126+
.attachmentActionsTemplate=${this.attachmentActionsTemplate}
127+
.attachmentContentTemplate=${this.attachmentContentTemplate}
112128
></igc-chat-message>
113129
`
114130
)}

src/components/chat/chat-message.ts

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { registerComponent } from '../common/definitions/register.js';
55
import { renderMarkdown } from './markdown-util.js';
66
import { IgcMessageAttachmentsComponent } from './message-attachments.js';
77
import { styles } from './themes/message.base.css.js';
8-
import type { IgcMessage } from './types.js';
8+
import type { AttachmentTemplate, IgcMessage } from './types.js';
99

1010
/**
1111
*
@@ -33,19 +33,33 @@ export default class IgcChatMessageComponent extends LitElement {
3333
@property({ reflect: true, attribute: false })
3434
public isResponse = false;
3535

36+
@property({ type: Function })
37+
public attachmentTemplate?: AttachmentTemplate;
38+
39+
@property({ type: Function })
40+
public attachmentHeaderTemplate?: AttachmentTemplate;
41+
42+
@property({ type: Function })
43+
public attachmentActionsTemplate?: AttachmentTemplate;
44+
45+
@property({ type: Function })
46+
public attachmentContentTemplate?: AttachmentTemplate;
47+
3648
protected override render() {
37-
const containerClass = `message-container ${!this.isResponse ? 'sent' : ''}`;
49+
const containerClass = `message-container ${!this.isResponse ? 'sent' : ''} bubble`;
3850

3951
return html`
4052
<div class=${containerClass}>
4153
${this.message?.text.trim()
42-
? html` <div class="bubble">
43-
${renderMarkdown(this.message?.text)}
44-
</div>`
54+
? html` <div>${renderMarkdown(this.message?.text)}</div>`
4555
: ''}
4656
${this.message?.attachments && this.message?.attachments.length > 0
4757
? html`<igc-message-attachments
4858
.attachments=${this.message?.attachments}
59+
.attachmentTemplate=${this.attachmentTemplate}
60+
.attachmentHeaderTemplate=${this.attachmentHeaderTemplate}
61+
.attachmentActionsTemplate=${this.attachmentActionsTemplate}
62+
.attachmentContentTemplate=${this.attachmentContentTemplate}
4963
>
5064
</igc-message-attachments>`
5165
: ''}

src/components/chat/chat.ts

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,11 @@ import { EventEmitterMixin } from '../common/mixins/event-emitter.js';
77
import IgcChatInputComponent from './chat-input.js';
88
import IgcChatMessageListComponent from './chat-message-list.js';
99
import { styles } from './themes/chat.base.css.js';
10-
import type { IgcMessage, IgcMessageAttachment } from './types.js';
10+
import type {
11+
AttachmentTemplate,
12+
IgcMessage,
13+
IgcMessageAttachment,
14+
} from './types.js';
1115

1216
export interface IgcChatComponentEventMap {
1317
igcMessageSend: CustomEvent<IgcMessage>;
@@ -59,6 +63,18 @@ export default class IgcChatComponent extends EventEmitterMixin<
5963
@property({ type: String, attribute: 'header-text', reflect: true })
6064
public headerText = '';
6165

66+
@property({ type: Function })
67+
public attachmentTemplate?: AttachmentTemplate;
68+
69+
@property({ type: Function })
70+
public attachmentHeaderTemplate?: AttachmentTemplate;
71+
72+
@property({ type: Function })
73+
public attachmentActionsTemplate?: AttachmentTemplate;
74+
75+
@property({ type: Function })
76+
public attachmentContentTemplate?: AttachmentTemplate;
77+
6278
public override connectedCallback() {
6379
super.connectedCallback();
6480
this.addEventListener(
@@ -155,6 +171,10 @@ export default class IgcChatComponent extends EventEmitterMixin<
155171
.messages=${this.messages}
156172
.disableAutoScroll=${this.disableAutoScroll}
157173
.isAiResponding=${this.isAiResponding}
174+
.attachmentTemplate=${this.attachmentTemplate}
175+
.attachmentHeaderTemplate=${this.attachmentHeaderTemplate}
176+
.attachmentActionsTemplate=${this.attachmentActionsTemplate}
177+
.attachmentContentTemplate=${this.attachmentContentTemplate}
158178
>
159179
</igc-chat-message-list>
160180
<igc-chat-input

src/components/chat/message-attachments.ts

Lines changed: 89 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import IgcIconComponent from '../icon/icon.js';
77
import { registerIconFromText } from '../icon/icon.registry.js';
88
import { styles } from './themes/message-attachments.base.css';
99
import {
10+
type AttachmentTemplate,
1011
type IgcMessageAttachment,
1112
closeIcon,
1213
fileIcon,
@@ -41,6 +42,18 @@ export class IgcMessageAttachmentsComponent extends LitElement {
4142
@property({ type: String })
4243
previewImage = '';
4344

45+
@property({ type: Function })
46+
attachmentTemplate: AttachmentTemplate | undefined;
47+
48+
@property({ type: Function })
49+
attachmentHeaderTemplate: AttachmentTemplate | undefined;
50+
51+
@property({ type: Function })
52+
attachmentActionsTemplate: AttachmentTemplate | undefined;
53+
54+
@property({ type: Function })
55+
attachmentContentTemplate: AttachmentTemplate | undefined;
56+
4457
constructor() {
4558
super();
4659
registerIconFromText('close', closeIcon, 'material');
@@ -50,12 +63,6 @@ export class IgcMessageAttachmentsComponent extends LitElement {
5063
registerIconFromText('more', moreIcon, 'material');
5164
}
5265

53-
private formatFileSize(bytes = 0): string {
54-
if (bytes < 1024) return `${bytes} B`;
55-
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
56-
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
57-
}
58-
5966
private openImagePreview(url: string) {
6067
this.previewImage = url;
6168
}
@@ -82,60 +89,82 @@ export class IgcMessageAttachmentsComponent extends LitElement {
8289
protected override render() {
8390
return html`
8491
<div class="attachments-container">
85-
${this.attachments.map(
86-
(attachment) => html`
87-
<igc-expansion-panel
88-
indicator-position="none"
89-
.open=${attachment.type === 'image'}
90-
@igcClosing=${(ev: CustomEvent) =>
91-
this.handleToggle(ev, attachment)}
92-
@igcOpening=${(ev: CustomEvent) =>
93-
this.handleToggle(ev, attachment)}
94-
>
95-
<div slot="title" class="attachment">
96-
<div class="details">
97-
${attachment.type === 'image'
98-
? html`<igc-icon
99-
name="image"
100-
collection="material"
101-
class="medium"
102-
></igc-icon>`
103-
: html`<igc-icon
104-
name="file"
105-
collection="material"
106-
class="medium"
107-
></igc-icon>`}
108-
<span class="file-name">${attachment.name}</span>
109-
</div>
110-
<div class="actions">
111-
${attachment.type === 'image'
112-
? html` <igc-icon-button
113-
name="preview"
114-
collection="material"
115-
variant="flat"
116-
class="small"
117-
@click=${() => this.openImagePreview(attachment.url)}
118-
></igc-icon-button>`
119-
: ''}
120-
<igc-icon-button
121-
name="more"
122-
collection="material"
123-
variant="flat"
124-
class="small"
125-
></igc-icon-button>
126-
</div>
127-
</div>
128-
129-
${attachment.type === 'image'
130-
? html` <img
131-
class="image-attachment"
132-
src=${attachment.url}
133-
alt=${attachment.name}
134-
/>`
135-
: ''}
136-
</igc-expansion-panel>
137-
`
138-
)}
92+
${this.attachmentTemplate
93+
? this.attachmentTemplate(this.attachments)
94+
: html` ${this.attachments.map(
95+
(attachment) =>
96+
html` <igc-expansion-panel
97+
indicator-position="none"
98+
.open=${attachment.type === 'image'}
99+
@igcClosing=${(ev: CustomEvent) =>
100+
this.handleToggle(ev, attachment)}
101+
@igcOpening=${(ev: CustomEvent) =>
102+
this.handleToggle(ev, attachment)}
103+
>
104+
<div slot="title" class="attachment">
105+
<div class="details">
106+
${this.attachmentHeaderTemplate
107+
? this.attachmentHeaderTemplate(this.attachments)
108+
: html`
109+
<slot name="attachment-icon">
110+
${attachment.type === 'image'
111+
? html`<igc-icon
112+
name="image"
113+
collection="material"
114+
class="medium"
115+
></igc-icon>`
116+
: html`<igc-icon
117+
name="file"
118+
collection="material"
119+
class="medium"
120+
></igc-icon>`}
121+
</slot>
122+
<slot name="attachment-name">
123+
<span class="file-name">${attachment.name}</span>
124+
</slot>
125+
`}
126+
</div>
127+
<div class="actions">
128+
${this.attachmentActionsTemplate
129+
? this.attachmentActionsTemplate(this.attachments)
130+
: html`
131+
<slot name="attachment-actions">
132+
${attachment.type === 'image'
133+
? html` <igc-icon-button
134+
name="preview"
135+
collection="material"
136+
variant="flat"
137+
class="small"
138+
@click=${() =>
139+
this.openImagePreview(attachment.url)}
140+
></igc-icon-button>`
141+
: ''}
142+
<igc-icon-button
143+
name="more"
144+
collection="material"
145+
variant="flat"
146+
class="small"
147+
></igc-icon-button>
148+
</slot>
149+
`}
150+
</div>
151+
</div>
152+
153+
${this.attachmentContentTemplate
154+
? this.attachmentContentTemplate(this.attachments)
155+
: html`
156+
<slot name="attachment-content">
157+
${attachment.type === 'image'
158+
? html` <img
159+
class="image-attachment"
160+
src=${attachment.url}
161+
alt=${attachment.name}
162+
/>`
163+
: ''}
164+
</slot>
165+
`}
166+
</igc-expansion-panel>`
167+
)}`}
139168
</div>
140169
141170
${this.previewImage

src/components/chat/themes/message.base.scss

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,18 +8,14 @@
88
}
99

1010
.message-container {
11-
display: flex;
11+
display: block;
1212
justify-content: flex-start;
1313
align-items: flex-end;
1414
gap: 8px;
1515
margin-bottom: 4px;
1616
animation: fadeIn 0.2s ease-out;
1717
}
1818

19-
.message-container.sent {
20-
flex-direction: row-reverse;
21-
}
22-
2319
.avatar {
2420
width: 32px;
2521
height: 32px;
@@ -51,19 +47,18 @@
5147
.bubble {
5248
padding: 12px 16px;
5349
border-radius: 18px;
54-
background-color: #E5E5EA;
5550
color: black;
5651
word-break: break-all;
5752
font-weight: 400;
5853
line-height: 1.4;
5954
position: relative;
6055
transition: all 0.2s ease;
56+
width: fit-content;
6157
}
6258

63-
.sent .bubble {
59+
.sent {
6460
border-radius: 18px 18px 4px;
65-
background-color: #0A84FF;
66-
color: white;
61+
background-color: #E5E5EA;
6762
}
6863

6964
.received .bubble {

src/components/chat/types.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import type { TemplateResult } from 'lit';
2+
13
export type IgcMessageAttachmentType = 'image' | 'file';
24

35
export interface IgcMessage {
@@ -16,7 +18,9 @@ export interface IgcMessageAttachment {
1618
size?: number;
1719
thumbnail?: string;
1820
}
19-
21+
export type AttachmentTemplate = (
22+
attachments: IgcMessageAttachment[]
23+
) => TemplateResult;
2024
export const attachmentIcon =
2125
'<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#e3e3e3"><path d="M720-330q0 104-73 177T470-80q-104 0-177-73t-73-177v-370q0-75 52.5-127.5T400-880q75 0 127.5 52.5T580-700v350q0 46-32 78t-78 32q-46 0-78-32t-32-78v-370h80v370q0 13 8.5 21.5T470-320q13 0 21.5-8.5T500-350v-350q-1-42-29.5-71T400-800q-42 0-71 29t-29 71v370q-1 71 49 120.5T470-160q70 0 119-49.5T640-330v-390h80v390Z"/></svg>';
2226
export const sendButtonIcon =

0 commit comments

Comments
 (0)