@genexus/mercury 0.28.6 → 0.30.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/assets/MERCURY_ASSETS.d.ts +1741 -1613
- package/dist/assets/MERCURY_ASSETS.js +1 -1
- package/dist/assets/MERCURY_ASSETS.ts +10217 -10121
- package/dist/assets/icons/internal/dark/assistant-fill.svg +1 -0
- package/dist/assets/icons/internal/light/assistant-fill.svg +1 -0
- package/dist/assets/icons/objects/dark/assistant.svg +1 -0
- package/dist/assets/icons/objects/light/assistant.svg +1 -0
- package/dist/bundles/css/all.css +1 -1
- package/dist/bundles/css/base/icons.css +1 -1
- package/dist/bundles/css/components/accordion.css +1 -1
- package/dist/bundles/css/components/button.css +1 -1
- package/dist/bundles/css/components/chat.css +1 -1
- package/dist/bundles/css/components/checkbox.css +1 -1
- package/dist/bundles/css/components/combo-box.css +1 -1
- package/dist/bundles/css/components/dialog.css +1 -1
- package/dist/bundles/css/components/dropdown.css +1 -1
- package/dist/bundles/css/components/edit.css +1 -1
- package/dist/bundles/css/components/flexible-layout.css +1 -1
- package/dist/bundles/css/components/icon.css +1 -1
- package/dist/bundles/css/components/layout-splitter.css +1 -1
- package/dist/bundles/css/components/list-box.css +1 -1
- package/dist/bundles/css/components/markdown-viewer.css +1 -1
- package/dist/bundles/css/components/navigation-list.css +1 -1
- package/dist/bundles/css/components/paginator.css +1 -1
- package/dist/bundles/css/components/pills.css +1 -1
- package/dist/bundles/css/components/segmented-control.css +1 -1
- package/dist/bundles/css/components/tab.css +1 -1
- package/dist/bundles/css/components/tree-view.css +1 -1
- package/dist/bundles/css/utils/form--full.css +1 -1
- package/dist/bundles/css/utils/form.css +1 -1
- package/dist/bundles/css/utils/layout.css +1 -1
- package/dist/components/chat/actions.lit.d.ts +2 -0
- package/dist/components/chat/actions.lit.js +25 -0
- package/dist/components/chat/code-block.lit.d.ts +2 -0
- package/dist/components/chat/code-block.lit.js +37 -0
- package/dist/components/chat/file.lit.d.ts +9 -0
- package/dist/components/chat/file.lit.js +124 -0
- package/dist/components/chat/mer-animated-dots.d.ts +10 -0
- package/dist/components/chat/mer-animated-dots.js +60 -0
- package/dist/components/chat/mer-spinner.lit.d.ts +10 -0
- package/dist/components/chat/mer-spinner.lit.js +32 -0
- package/dist/components/chat/render.lit.d.ts +4 -0
- package/dist/components/chat/render.lit.js +138 -0
- package/dist/components/chat/types.d.ts +4 -0
- package/dist/components/chat/types.js +1 -0
- package/dist/components/chat/utils.d.ts +23 -0
- package/dist/components/chat/utils.js +54 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -1
- package/package.json +15 -3
|
@@ -1 +1 @@
|
|
|
1
|
-
.layout{--spacing-body-block-start: var(--mer-spacing--md);--spacing-body-block-end: var(--mer-spacing--md);--spacing-body-inline-start: var(--mer-spacing--lg);--spacing-body-inline-end: var(--mer-spacing--lg);display:grid;gap:var(--mer-spacing--md)}.layout__panel{display:grid}.layout--cols-1{grid-template-rows:1fr;grid-template-columns:1fr}.layout--cols-2{grid-template-rows:1fr;grid-template-columns:repeat(2, 1fr)}.layout--cols-3{grid-template-rows:1fr;grid-template-columns:repeat(3, 1fr)}.layout--cols-4{grid-template-rows:1fr;grid-template-columns:repeat(4, 1fr)}.layout--cols-1-2{grid-template-rows:1fr;grid-template-columns:1fr 2fr}.layout--cols-2-1{grid-template-rows:1fr;grid-template-columns:2fr 1fr}
|
|
1
|
+
@keyframes spin{0%{transform:rotate(0deg)}100%{transform:rotate(360deg)}}.layout{--spacing-body-block-start: var(--mer-spacing--md);--spacing-body-block-end: var(--mer-spacing--md);--spacing-body-inline-start: var(--mer-spacing--lg);--spacing-body-inline-end: var(--mer-spacing--lg);display:grid;gap:var(--mer-spacing--md)}.layout__panel{display:grid}.layout--cols-1{grid-template-rows:1fr;grid-template-columns:1fr}.layout--cols-2{grid-template-rows:1fr;grid-template-columns:repeat(2, 1fr)}.layout--cols-3{grid-template-rows:1fr;grid-template-columns:repeat(3, 1fr)}.layout--cols-4{grid-template-rows:1fr;grid-template-columns:repeat(4, 1fr)}.layout--cols-1-2{grid-template-rows:1fr;grid-template-columns:1fr 2fr}.layout--cols-2-1{grid-template-rows:1fr;grid-template-columns:2fr 1fr}.control-footer,.control-footer-with-border{--control-footer-justify-content: end;padding-block-start:var(--mer-spacing--xs);display:flex;justify-content:var(--control-footer-justify-content);align-items:center}.control-footer-start{--control-footer-justify-content: start}.control-footer-center{--control-footer-justify-content: center}.control-footer-space-between{--control-footer-justify-content: space-between}.control-footer-with-border{border-block-start:var(--mer-border__width--sm) solid var(--mer-border-color__on-elevation--01)}.control-header,.control-header-with-border{padding-block-end:var(--mer-spacing--md)}.control-header-with-border{border-block-end:var(--mer-border__width--sm) solid var(--mer-border-color__on-elevation--01)}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { html } from "lit";
|
|
2
|
+
import { when } from "lit/directives/when.js";
|
|
3
|
+
import { copyButtonClickHandler, getMessageSerializedContentAll, tokenMap } from "./utils";
|
|
4
|
+
const copyClickHandler = (event, message) => {
|
|
5
|
+
// We use the whole message to support copying the files and/or sources
|
|
6
|
+
const messageSerializedContentAll = getMessageSerializedContentAll(message);
|
|
7
|
+
copyButtonClickHandler(event, messageSerializedContentAll);
|
|
8
|
+
};
|
|
9
|
+
export const customActionsRender = (message, chatRef) => {
|
|
10
|
+
const { accessibleName } = chatRef.translations;
|
|
11
|
+
const conditionToRender1 = message.role === "assistant" &&
|
|
12
|
+
(message.status === "complete" || !message.status);
|
|
13
|
+
const conditionToRender2 = message.role === "error";
|
|
14
|
+
return when(conditionToRender1 || conditionToRender2, () => html `<div part="actions-container">
|
|
15
|
+
<button
|
|
16
|
+
aria-label=${accessibleName.copyMessageContent}
|
|
17
|
+
part=${tokenMap({
|
|
18
|
+
[`action-button action-copy-message ${message.id}`]: true,
|
|
19
|
+
...(message.parts ? { [message.parts]: true } : {})
|
|
20
|
+
})}
|
|
21
|
+
type="button"
|
|
22
|
+
@click=${(e) => copyClickHandler(e, message)}
|
|
23
|
+
></button>
|
|
24
|
+
</div>`);
|
|
25
|
+
};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { html } from "lit";
|
|
2
|
+
import { when } from "lit/directives/when.js";
|
|
3
|
+
import { copyButtonClickHandler } from "./utils";
|
|
4
|
+
const downloadCodeBlockCallback = (plainText, language, downloadCodeBlock) => () => downloadCodeBlock(plainText, language);
|
|
5
|
+
export const customCodeBlockRender = (chatRef) => (options) => {
|
|
6
|
+
const { accessibleName, text } = chatRef.translations;
|
|
7
|
+
const downloadCb = chatRef.callbacks?.downloadCodeBlock;
|
|
8
|
+
return html `<div part="code-block">
|
|
9
|
+
<div part="code-block__header">
|
|
10
|
+
<span part="code-block__header-caption">${options.language}</span>
|
|
11
|
+
|
|
12
|
+
<div part="code-block__header-actions">
|
|
13
|
+
<button
|
|
14
|
+
class="button-copy-code"
|
|
15
|
+
aria-label=${text.copyCodeButton}
|
|
16
|
+
part="code-block__button--icon-only code-block__copy-code-button"
|
|
17
|
+
type="button"
|
|
18
|
+
@click=${(e) => copyButtonClickHandler(e, options.plainText)}
|
|
19
|
+
></button>
|
|
20
|
+
|
|
21
|
+
${when(downloadCb, () => html `<button
|
|
22
|
+
aria-label=${accessibleName.downloadCodeButton}
|
|
23
|
+
part="code-block__button--icon-only code-block__download-code-button"
|
|
24
|
+
@click=${downloadCodeBlockCallback(options.plainText, options.language, downloadCb)}
|
|
25
|
+
></button>`)}
|
|
26
|
+
</div>
|
|
27
|
+
</div>
|
|
28
|
+
|
|
29
|
+
<ch-code
|
|
30
|
+
.language=${options.language}
|
|
31
|
+
.lastNestedChildClass=${options.lastNestedChildClass}
|
|
32
|
+
part="code-block__content"
|
|
33
|
+
.showIndicator=${options.showIndicator}
|
|
34
|
+
.value=${options.plainText}
|
|
35
|
+
></ch-code>
|
|
36
|
+
</div>`;
|
|
37
|
+
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { ChatMessageFile } from "@genexus/chameleon-controls-library/dist/types/components/chat/types";
|
|
2
|
+
import { type TemplateResult } from "lit";
|
|
3
|
+
import "./mer-spinner.lit.js";
|
|
4
|
+
export declare const customFileRenders: {
|
|
5
|
+
readonly audio: (file: ChatMessageFile) => TemplateResult;
|
|
6
|
+
readonly video: (file: ChatMessageFile) => TemplateResult;
|
|
7
|
+
readonly image: (file: ChatMessageFile) => TemplateResult;
|
|
8
|
+
readonly file: (file: ChatMessageFile) => TemplateResult;
|
|
9
|
+
};
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { html, nothing } from "lit";
|
|
2
|
+
import { ifDefined } from "lit/directives/if-defined.js";
|
|
3
|
+
import "./mer-spinner.lit.js";
|
|
4
|
+
import { tokenMap } from "./utils";
|
|
5
|
+
const DEFAULT_FILE_UPLOAD_STATE = "uploaded";
|
|
6
|
+
/**
|
|
7
|
+
* Returns a skeleton element shown while a file is being uploaded.
|
|
8
|
+
*
|
|
9
|
+
* In Mercury, the loading state is visually represented in the footer,
|
|
10
|
+
* so this skeleton is currently unused. However, it is recommended to keep
|
|
11
|
+
* this function in the file in case the design changes and a visual loader
|
|
12
|
+
* inside the file container becomes necessary again.
|
|
13
|
+
*
|
|
14
|
+
* @param file - The file object containing metadata and upload state.
|
|
15
|
+
* @param fileFormat - The format type of the file (e.g., text, image, etc.).
|
|
16
|
+
* @returns A TemplateResult with the skeleton element or `nothing` if not loading.
|
|
17
|
+
*/
|
|
18
|
+
const fileSkeleton = (file, fileFormat) => {
|
|
19
|
+
return file.uploadState === "in-progress"
|
|
20
|
+
? html `<div
|
|
21
|
+
part=${tokenMap({
|
|
22
|
+
[`file-skeleton format-${fileFormat} ${file.mimeType} ${file.uploadState ?? DEFAULT_FILE_UPLOAD_STATE}`]: true,
|
|
23
|
+
[file.extension]: !!file.extension,
|
|
24
|
+
[file.parts]: !!file.parts
|
|
25
|
+
})}
|
|
26
|
+
></div>`
|
|
27
|
+
: nothing;
|
|
28
|
+
};
|
|
29
|
+
const fileFooter = (file, fileFormat) => {
|
|
30
|
+
return html `<div
|
|
31
|
+
class="elevation-1"
|
|
32
|
+
part=${tokenMap({
|
|
33
|
+
[`file-footer format-${fileFormat} ${file.mimeType} ${file.uploadState ?? DEFAULT_FILE_UPLOAD_STATE}`]: true,
|
|
34
|
+
[file.extension]: !!file.extension,
|
|
35
|
+
[file.parts]: !!file.parts
|
|
36
|
+
})}
|
|
37
|
+
>
|
|
38
|
+
<span class="body-regular-xs" part="file-caption">
|
|
39
|
+
${file.caption}${file.extension && "." + file.extension.toLowerCase()}
|
|
40
|
+
</span>
|
|
41
|
+
|
|
42
|
+
${file.uploadState === "in-progress"
|
|
43
|
+
? html `<mer-spinner></mer-spinner>`
|
|
44
|
+
: nothing}
|
|
45
|
+
</div>`;
|
|
46
|
+
};
|
|
47
|
+
const getFileContainerParts = (file, fileFormat) => {
|
|
48
|
+
return tokenMap({
|
|
49
|
+
[`file-container format-${fileFormat} ${file.mimeType} ${file.uploadState ?? DEFAULT_FILE_UPLOAD_STATE}`]: true,
|
|
50
|
+
[file.extension]: !!file.extension,
|
|
51
|
+
[file.parts]: !!file.parts
|
|
52
|
+
});
|
|
53
|
+
};
|
|
54
|
+
const getFileParts = (file, fileFormat) => tokenMap({
|
|
55
|
+
[`file format-${fileFormat} ${file.mimeType} ${file.uploadState ?? DEFAULT_FILE_UPLOAD_STATE}`]: true,
|
|
56
|
+
[file.extension]: !!file.extension,
|
|
57
|
+
[file.parts]: !!file.parts
|
|
58
|
+
});
|
|
59
|
+
// TODO: Improve accessibility by exposing progress or spin states while
|
|
60
|
+
// uploading
|
|
61
|
+
export const customFileRenders = {
|
|
62
|
+
audio: (file) => {
|
|
63
|
+
return html `
|
|
64
|
+
<li class="file-container" part=${getFileContainerParts(file, "audio")}>
|
|
65
|
+
<audio
|
|
66
|
+
aria-label=${ifDefined(file.accessibleName)}
|
|
67
|
+
part=${getFileParts(file, "audio")}
|
|
68
|
+
src=${ifDefined(file.uploadState === "in-progress" ? undefined : file.url)}
|
|
69
|
+
controls
|
|
70
|
+
></audio>
|
|
71
|
+
|
|
72
|
+
<!-- ${fileSkeleton(file, "audio")} -->
|
|
73
|
+
${fileFooter(file, "audio")}
|
|
74
|
+
</li>
|
|
75
|
+
`;
|
|
76
|
+
},
|
|
77
|
+
video: (file) => {
|
|
78
|
+
return html `
|
|
79
|
+
<li class="file-container" part=${getFileContainerParts(file, "video")}>
|
|
80
|
+
<video
|
|
81
|
+
aria-label=${ifDefined(file.accessibleName)}
|
|
82
|
+
part=${getFileParts(file, "video")}
|
|
83
|
+
src=${ifDefined(file.uploadState === "in-progress" ? undefined : file.url)}
|
|
84
|
+
controls
|
|
85
|
+
></video>
|
|
86
|
+
<!-- ${fileSkeleton(file, "video")} -->
|
|
87
|
+
${fileFooter(file, "video")}
|
|
88
|
+
</li>
|
|
89
|
+
`;
|
|
90
|
+
},
|
|
91
|
+
image: (file) => {
|
|
92
|
+
return html `
|
|
93
|
+
<li class="file-container" part=${getFileContainerParts(file, "image")}>
|
|
94
|
+
<img
|
|
95
|
+
aria-label=${ifDefined(file.accessibleName)}
|
|
96
|
+
part=${getFileParts(file, "image")}
|
|
97
|
+
src=${file.url}
|
|
98
|
+
alt=${file.alternativeText ?? file.accessibleName ?? ""}
|
|
99
|
+
loading="lazy"
|
|
100
|
+
/>
|
|
101
|
+
<!-- ${fileSkeleton(file, "image")} -->
|
|
102
|
+
${fileFooter(file, "image")}
|
|
103
|
+
</li>
|
|
104
|
+
`;
|
|
105
|
+
},
|
|
106
|
+
file: (file) => {
|
|
107
|
+
const disabledWhileUploading = file.uploadState === "in-progress";
|
|
108
|
+
return html `
|
|
109
|
+
<li class="file-container" part=${getFileContainerParts(file, "file")}>
|
|
110
|
+
<a
|
|
111
|
+
aria-label=${ifDefined(file.accessibleName)}
|
|
112
|
+
role=${disabledWhileUploading ? "link" : nothing}
|
|
113
|
+
aria-disabled=${disabledWhileUploading ? "true" : nothing}
|
|
114
|
+
part=${getFileParts(file, "file")}
|
|
115
|
+
href=${disabledWhileUploading ? nothing : file.url}
|
|
116
|
+
target="_blank"
|
|
117
|
+
>
|
|
118
|
+
</a>
|
|
119
|
+
<!-- ${fileSkeleton(file, "file")} -->
|
|
120
|
+
${fileFooter(file, "file")}
|
|
121
|
+
</li>
|
|
122
|
+
`;
|
|
123
|
+
}
|
|
124
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { LitElement } from "lit";
|
|
2
|
+
export declare class MerAnimatedDots extends LitElement {
|
|
3
|
+
static styles: import("lit").CSSResult;
|
|
4
|
+
render(): import("lit-html").TemplateResult<1>;
|
|
5
|
+
}
|
|
6
|
+
declare global {
|
|
7
|
+
interface HTMLElementTagNameMap {
|
|
8
|
+
"mer-animated-dots": MerAnimatedDots;
|
|
9
|
+
}
|
|
10
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { LitElement, css, html } from "lit";
|
|
2
|
+
const DOT_SELECTOR = "dot";
|
|
3
|
+
export class MerAnimatedDots extends LitElement {
|
|
4
|
+
static styles = css `
|
|
5
|
+
:host {
|
|
6
|
+
--dot-size: 5px;
|
|
7
|
+
--dot-initial-opacity: 0;
|
|
8
|
+
--dot-initial-scale: 0.5;
|
|
9
|
+
--animation-duration: 0.4s;
|
|
10
|
+
display: inline-flex;
|
|
11
|
+
gap: calc(var(--dot-size) / 1.15);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
.dot {
|
|
15
|
+
display: inline-block;
|
|
16
|
+
inline-size: var(--dot-size);
|
|
17
|
+
block-size: var(--dot-size);
|
|
18
|
+
border-radius: calc(var(--dot-size) / 2);
|
|
19
|
+
opacity: var(--dot-initial-opacity);
|
|
20
|
+
transform: scale(var(--dot-initial-scale));
|
|
21
|
+
animation: animateDot var(--animation-duration) ease-in-out infinite alternate;
|
|
22
|
+
}
|
|
23
|
+
.dot:first-child {
|
|
24
|
+
background-color: var(
|
|
25
|
+
--chat-avatar-waiting-dot-1-color,
|
|
26
|
+
var(--mer-color__chat-awaiting-dot-1)
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
.dot:nth-child(2) {
|
|
30
|
+
animation-delay: calc(var(--animation-duration) / 3);
|
|
31
|
+
background-color: var(
|
|
32
|
+
--chat-avatar-waiting-dot-2-color,
|
|
33
|
+
var(--mer-color__chat-awaiting-dot-2)
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
.dot:nth-child(3) {
|
|
37
|
+
animation-delay: calc(var(--animation-duration) / 1.5);
|
|
38
|
+
background-color: var(
|
|
39
|
+
--chat-avatar-waiting-dot-3-color,
|
|
40
|
+
var(--mer-color__chat-awaiting-dot-3)
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
@keyframes animateDot {
|
|
45
|
+
to {
|
|
46
|
+
opacity: 1;
|
|
47
|
+
transform: scale(1);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
`;
|
|
51
|
+
// Render the UI as a function of component state
|
|
52
|
+
render() {
|
|
53
|
+
return html `<div class=${DOT_SELECTOR} part="dot dot-1"></div>
|
|
54
|
+
<div class=${DOT_SELECTOR} part="dot dot-2"></div>
|
|
55
|
+
<div class=${DOT_SELECTOR} part="dot dot-3"></div>`;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
if (!customElements.get("mer-animated-dots")) {
|
|
59
|
+
customElements.define("mer-animated-dots", MerAnimatedDots);
|
|
60
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { LitElement } from "lit";
|
|
2
|
+
export declare class MerSpinner extends LitElement {
|
|
3
|
+
static styles: import("lit").CSSResult;
|
|
4
|
+
render(): import("lit-html").TemplateResult<1>;
|
|
5
|
+
}
|
|
6
|
+
declare global {
|
|
7
|
+
interface HTMLElementTagNameMap {
|
|
8
|
+
"mer-spinner": MerSpinner;
|
|
9
|
+
}
|
|
10
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { LitElement, css, html } from "lit";
|
|
2
|
+
export class MerSpinner extends LitElement {
|
|
3
|
+
static styles = css `
|
|
4
|
+
:host {
|
|
5
|
+
--spinner-size: var(--mer-spacing--md);
|
|
6
|
+
--spinner-color: var(--mer-color__primary--300);
|
|
7
|
+
--track-color: var(--mer-color__tinted-primary--50);
|
|
8
|
+
--border-thinness: 7;
|
|
9
|
+
|
|
10
|
+
inline-size: var(--spinner-size);
|
|
11
|
+
block-size: var(--spinner-size);
|
|
12
|
+
border: calc(var(--spinner-size) / var(--border-thinness)) solid
|
|
13
|
+
var(--track-color);
|
|
14
|
+
border-block-start: calc(var(--spinner-size) / var(--border-thinness))
|
|
15
|
+
solid var(--spinner-color);
|
|
16
|
+
border-radius: 50%;
|
|
17
|
+
animation: spin var(--mer-timing--regular, 1s) linear infinite;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
@keyframes spin {
|
|
21
|
+
100% {
|
|
22
|
+
transform: rotate(360deg);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
`;
|
|
26
|
+
render() {
|
|
27
|
+
return html ``;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
if (!customElements.get("mer-spinner")) {
|
|
31
|
+
customElements.define("mer-spinner", MerSpinner);
|
|
32
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { ChatMessageRenderBySections, ChatSendContainerLayout } from "@genexus/chameleon-controls-library/dist/types/components/chat/types";
|
|
2
|
+
import "./mer-animated-dots.js";
|
|
3
|
+
export declare const renderItem: ChatMessageRenderBySections;
|
|
4
|
+
export declare const sendContainerLayout: ChatSendContainerLayout;
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
/*
|
|
2
|
+
README:
|
|
3
|
+
This file contains two utilities:
|
|
4
|
+
|
|
5
|
+
1) [Sections for customitizing the ch-chat renderItem]
|
|
6
|
+
The following sections are being customized at the time of writting:
|
|
7
|
+
- actions: customActionsRender,
|
|
8
|
+
- codeBlock: customCodeBlockRender,
|
|
9
|
+
- content: customContentRender
|
|
10
|
+
|
|
11
|
+
What for?
|
|
12
|
+
|
|
13
|
+
- actions: In order to be able to customize the copy button, by adding a part
|
|
14
|
+
"copying" when the user copies the message. This additional part allows changing
|
|
15
|
+
the current button icon of "copy" to a "checked" icon.
|
|
16
|
+
- codeBlock: Same reason as "actions" section.
|
|
17
|
+
- content: Mercury includes a "header" section for the assistant message. This
|
|
18
|
+
header includes an avatar, the assistant name, and optionally the message time.
|
|
19
|
+
This header requires custom markup, and for this reason this section has to be
|
|
20
|
+
customized.
|
|
21
|
+
|
|
22
|
+
2) [Configuration for the ch-chat sendContainerLayout property]
|
|
23
|
+
The chat styles are designed for a specific layout, so users must use the sendContainerLayout provided by Mercury. Customizing it is not supported.
|
|
24
|
+
*/
|
|
25
|
+
import { html, nothing } from "lit";
|
|
26
|
+
// Side effect to define the animation for "waiting" messages.
|
|
27
|
+
import { customActionsRender } from "./actions.lit.js";
|
|
28
|
+
import { customCodeBlockRender } from "./code-block.lit.js";
|
|
29
|
+
import { customFileRenders } from "./file.lit.js";
|
|
30
|
+
import { DEFAULT_ASSISTANT_STATUS, getMessageContent, tokenMap } from "./utils.js";
|
|
31
|
+
import "./mer-animated-dots.js";
|
|
32
|
+
// 1) [Sections for customitizing the ch-chat renderItem.]
|
|
33
|
+
/**
|
|
34
|
+
* Mercury section that includes the assistant the assistant name, and an optional message time.
|
|
35
|
+
*
|
|
36
|
+
* @param messageMetaData - Metadata containing assistantName and time
|
|
37
|
+
* @returns TemplateResult rendering the assistant message header
|
|
38
|
+
*/
|
|
39
|
+
const messageHeader = (messageMetaData, waiting = false) => {
|
|
40
|
+
const assistantName = messageMetaData.assistantName;
|
|
41
|
+
const assistantId = messageMetaData.assistantName
|
|
42
|
+
.toLowerCase()
|
|
43
|
+
.replace(/ /g, "-");
|
|
44
|
+
const messageTime = messageMetaData.time;
|
|
45
|
+
return html ` <div part="header assistant header-${assistantId}">
|
|
46
|
+
<div part="header__start assistant">
|
|
47
|
+
<div part="header__avatar-container assistant">
|
|
48
|
+
<div part="header__avatar-border assistant"></div>
|
|
49
|
+
<div part="header__avatar-image assistant"></div>
|
|
50
|
+
</div>
|
|
51
|
+
<div part="header__role assistant">${assistantName}</div>
|
|
52
|
+
${waiting ? html `<mer-animated-dots></mer-animated-dots>` : nothing}
|
|
53
|
+
</div>
|
|
54
|
+
|
|
55
|
+
${messageTime
|
|
56
|
+
? html `<time datetime="${messageTime}" part="header__time assistant"
|
|
57
|
+
>${messageTime}</time
|
|
58
|
+
>`
|
|
59
|
+
: nothing}
|
|
60
|
+
</div>`;
|
|
61
|
+
};
|
|
62
|
+
// customzied in order to add the custom "messageHeader"
|
|
63
|
+
const customAssistantContentRender = (message, chatRef, codeBlockRender) => {
|
|
64
|
+
const messageContent = getMessageContent(message);
|
|
65
|
+
return message.status === "waiting"
|
|
66
|
+
? html `${messageHeader(message.metadata, true)}
|
|
67
|
+
<div
|
|
68
|
+
class="assistant-loading"
|
|
69
|
+
part=${tokenMap({
|
|
70
|
+
[`assistant content waiting ${message.id}`]: true,
|
|
71
|
+
...(message.parts ? { [message.parts]: true } : {})
|
|
72
|
+
})}
|
|
73
|
+
>
|
|
74
|
+
${messageContent}
|
|
75
|
+
</div>`
|
|
76
|
+
: html `${messageHeader(message.metadata)}
|
|
77
|
+
<ch-markdown-viewer
|
|
78
|
+
part=${tokenMap({
|
|
79
|
+
[`assistant content ${message.id} ${message.status ?? DEFAULT_ASSISTANT_STATUS}`]: true,
|
|
80
|
+
...(message.parts ? { [message.parts]: true } : {})
|
|
81
|
+
})}
|
|
82
|
+
.renderCode=${
|
|
83
|
+
// WA: Define codeBlockRender as "any" to avoid types incompatibility:
|
|
84
|
+
// codeBlockRender returns TemplateResult and renderCode expects TemplateResult | undefined
|
|
85
|
+
// In Chameleon this type mismatch does not happens. TODO: Evaluate a solution in Chameleon.
|
|
86
|
+
codeBlockRender(chatRef)}
|
|
87
|
+
.showIndicator=${message.status === "streaming"}
|
|
88
|
+
.theme=${chatRef.markdownTheme ?? undefined}
|
|
89
|
+
.value=${messageContent}
|
|
90
|
+
></ch-markdown-viewer>`;
|
|
91
|
+
};
|
|
92
|
+
// customzied in order to add the custom "messageHeader"
|
|
93
|
+
const customErrorContentRender = (message, chatRef, codeBlockRender) => {
|
|
94
|
+
const errorContent = getMessageContent(message);
|
|
95
|
+
return html `${messageHeader(message.metadata)}
|
|
96
|
+
<ch-markdown-viewer
|
|
97
|
+
part=${tokenMap({
|
|
98
|
+
[`error content ${message.id}`]: true,
|
|
99
|
+
...(message.parts ? { [message.parts]: true } : {})
|
|
100
|
+
})}
|
|
101
|
+
.renderCode=${
|
|
102
|
+
// WA: Define codeBlockRender as "any" to avoid types incompatibility:
|
|
103
|
+
// codeBlockRender returns TemplateResult and renderCode expects TemplateResult | undefined
|
|
104
|
+
// In Chameleon this type mismatch does not happens. TODO: Evaluate a solution in Chameleon.
|
|
105
|
+
codeBlockRender(chatRef)}
|
|
106
|
+
.theme=${chatRef.markdownTheme ?? undefined}
|
|
107
|
+
.value=${errorContent}
|
|
108
|
+
></ch-markdown-viewer>`;
|
|
109
|
+
};
|
|
110
|
+
// No modification. It is required to be defined.
|
|
111
|
+
const defaultSystemContentRender = () => "string | TemplateResult";
|
|
112
|
+
// No modification. It is required to be defined.
|
|
113
|
+
const defaultUserContentRender = (messageModel) => getMessageContent(messageModel) ?? "";
|
|
114
|
+
const contentRenderByRole = {
|
|
115
|
+
// TODO: Check if each contentRender can be optional, since it may be not me required to implement
|
|
116
|
+
// a custom render for each type of content. ie.: Mercury does not requres to customize "system" and "user"
|
|
117
|
+
assistant: customAssistantContentRender,
|
|
118
|
+
error: customErrorContentRender,
|
|
119
|
+
system: defaultSystemContentRender, // No modification
|
|
120
|
+
user: defaultUserContentRender // No modification
|
|
121
|
+
};
|
|
122
|
+
const customContentRender = (message, chatRef, codeBlockRender) => contentRenderByRole[message.role](message, chatRef, codeBlockRender);
|
|
123
|
+
export const renderItem = {
|
|
124
|
+
actions: customActionsRender,
|
|
125
|
+
codeBlock: customCodeBlockRender,
|
|
126
|
+
content: customContentRender,
|
|
127
|
+
file: customFileRenders
|
|
128
|
+
};
|
|
129
|
+
// 2) [Configuration for the ch-chat sendContainerLayout property]
|
|
130
|
+
export const sendContainerLayout = {
|
|
131
|
+
sendInputAfter: [
|
|
132
|
+
"attach-files-button",
|
|
133
|
+
"live-audio-button",
|
|
134
|
+
"send-button",
|
|
135
|
+
"chat-attached-files-viewer"
|
|
136
|
+
],
|
|
137
|
+
sendContainerAfter: ["live-audio-container"]
|
|
138
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { ChatMessageNoId } from "@genexus/chameleon-controls-library/dist/types/components/chat/types";
|
|
2
|
+
export declare const getMessageSerializedContentAll: (message: ChatMessageNoId) => string;
|
|
3
|
+
export declare const getMessageContent: (message: ChatMessageNoId) => string | undefined;
|
|
4
|
+
/**
|
|
5
|
+
* Converts an object mapping token keys to booleans into a space-separated
|
|
6
|
+
* string containing the token keys that map to truthy values.
|
|
7
|
+
* @example
|
|
8
|
+
* part={tokenMap({
|
|
9
|
+
* header: true,
|
|
10
|
+
* disabled: this.disabled,
|
|
11
|
+
* selected: this.selected,
|
|
12
|
+
* [levelPart]: canShowLines,
|
|
13
|
+
* "expand-button":
|
|
14
|
+
* canShowLines && !this.leaf && this.expandableButton !== "no"
|
|
15
|
+
* })}
|
|
16
|
+
*/
|
|
17
|
+
export declare const tokenMap: (tokens: {
|
|
18
|
+
[key: string]: boolean;
|
|
19
|
+
}) => string;
|
|
20
|
+
export declare const copyToTheClipboard: (text: string) => Promise<void>;
|
|
21
|
+
export declare const copy: (text: string) => () => void;
|
|
22
|
+
export declare const copyButtonClickHandler: (event: Event, text: string) => void;
|
|
23
|
+
export declare const DEFAULT_ASSISTANT_STATUS = "complete";
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
// TODO: The functions on this file are a WA until Chameleon 7 is released and we can reuse
|
|
2
|
+
// the existing functions from Chameleon. At the moment, we have to copy paste the functions
|
|
3
|
+
// because in Chameleon 6 we can import them.
|
|
4
|
+
// The following utils were copied from Chamelmeon (src/components/chat/utils.ts or src/common/utils.ts)
|
|
5
|
+
export const getMessageSerializedContentAll = (message) => typeof message.content === "string"
|
|
6
|
+
? message.content
|
|
7
|
+
: JSON.stringify(message.content, undefined, 2);
|
|
8
|
+
export const getMessageContent = (message) => typeof message.content === "string"
|
|
9
|
+
? message.content
|
|
10
|
+
: message.content.message;
|
|
11
|
+
/**
|
|
12
|
+
* Converts an object mapping token keys to booleans into a space-separated
|
|
13
|
+
* string containing the token keys that map to truthy values.
|
|
14
|
+
* @example
|
|
15
|
+
* part={tokenMap({
|
|
16
|
+
* header: true,
|
|
17
|
+
* disabled: this.disabled,
|
|
18
|
+
* selected: this.selected,
|
|
19
|
+
* [levelPart]: canShowLines,
|
|
20
|
+
* "expand-button":
|
|
21
|
+
* canShowLines && !this.leaf && this.expandableButton !== "no"
|
|
22
|
+
* })}
|
|
23
|
+
*/
|
|
24
|
+
export const tokenMap = (tokens) => {
|
|
25
|
+
const keys = Object.keys(tokens);
|
|
26
|
+
let result = "";
|
|
27
|
+
for (let index = 0; index < keys.length; index++) {
|
|
28
|
+
const tokenKey = keys[index];
|
|
29
|
+
const tokenValue = tokens[tokenKey];
|
|
30
|
+
if (tokenValue) {
|
|
31
|
+
result += result === "" ? tokenKey : ` ${tokenKey}`;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return result;
|
|
35
|
+
};
|
|
36
|
+
export const copyToTheClipboard = (text) => navigator.clipboard.writeText(text);
|
|
37
|
+
export const copy = (text) => () => {
|
|
38
|
+
copyToTheClipboard(text);
|
|
39
|
+
};
|
|
40
|
+
export const copyButtonClickHandler = (event, text) => {
|
|
41
|
+
copyToTheClipboard(text);
|
|
42
|
+
const button = event.currentTarget;
|
|
43
|
+
if (button) {
|
|
44
|
+
const buttonOriginalParts = button.getAttribute("part");
|
|
45
|
+
const buttonWithCopyingParts = `${buttonOriginalParts} copying`;
|
|
46
|
+
button.setAttribute("disabled", "disabled");
|
|
47
|
+
button.setAttribute("part", buttonWithCopyingParts);
|
|
48
|
+
setTimeout(() => {
|
|
49
|
+
button.removeAttribute("disabled");
|
|
50
|
+
button.setAttribute("part", buttonOriginalParts || "");
|
|
51
|
+
}, 1500);
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
export const DEFAULT_ASSISTANT_STATUS = "complete";
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";export*from"./assets-manager.js";export*from"./bundles.js";export*from"./register-mercury.js";export*from"./types.js";
|
|
1
|
+
"use strict";export*from"./assets-manager.js";export*from"./bundles.js";export*from"./register-mercury.js";export*from"./types.js";export*from"./components/chat/render.lit.js";export*from"./components/chat/types";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@genexus/mercury",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.30.0",
|
|
4
4
|
"description": "Mercury Design System is a robust and scalable solution designed to improve product development.",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"module": "./dist/index.js",
|
|
@@ -43,6 +43,16 @@
|
|
|
43
43
|
"default": "./dist/register-mercury.js"
|
|
44
44
|
}
|
|
45
45
|
},
|
|
46
|
+
"./components/chat/render.js": {
|
|
47
|
+
"import": {
|
|
48
|
+
"types": "./dist/components/chat/render.d.ts",
|
|
49
|
+
"default": "./dist/components/chat/render.js"
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
"./components/chat/message-metadata": {
|
|
53
|
+
"types": "./dist/components/chat/types.d.ts",
|
|
54
|
+
"import": "./dist/components/chat/types.js"
|
|
55
|
+
},
|
|
46
56
|
"./package.json": "./package.json"
|
|
47
57
|
},
|
|
48
58
|
"scripts": {
|
|
@@ -69,9 +79,11 @@
|
|
|
69
79
|
"test.watch": "yarn playwright install && vitest watch"
|
|
70
80
|
},
|
|
71
81
|
"license": "Apache-2.0",
|
|
82
|
+
"peerDependencies": {
|
|
83
|
+
"@genexus/chameleon-controls-library": ">= 6.20.0"
|
|
84
|
+
},
|
|
72
85
|
"devDependencies": {
|
|
73
|
-
"@
|
|
74
|
-
"@genexus/chameleon-controls-library": "~6.7.0",
|
|
86
|
+
"@genexus/chameleon-controls-library": "6.20.0",
|
|
75
87
|
"@genexus/svg-sass-generator": "1.1.24",
|
|
76
88
|
"@jackolope/ts-lit-plugin": "^3.1.4",
|
|
77
89
|
"@types/node": "~22.10.5",
|