Skip to content

Commit be7691a

Browse files
authored
chore: add guided prompt pattern for <ui5-ai-button> (#9854)
* chore: add guided prompt sample for <ui5-ai-button> * chore: correct ai-button state after send * chore: improve sample with popover * chore: make word count more realistic * chore: apply core review feedback * chore: add exp and new tags
1 parent ba521a8 commit be7691a

File tree

14 files changed

+972
-2
lines changed

14 files changed

+972
-2
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import html from '!!raw-loader!./sample.html';
2+
import css from '!!raw-loader!./main.css';
3+
import js from '!!raw-loader!./main.js';
4+
5+
<Editor html={html} js={js} css={css} />
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
[ui5-card] {
2+
width: 600px;
3+
}
4+
5+
.guidedPromptSection {
6+
display: flex;
7+
height: 450px;
8+
flex-direction: column;
9+
justify-content: flex-end;
10+
padding: 1rem;
11+
border-start-start-radius: 0.625rem;
12+
border-start-end-radius: 0.625rem;
13+
gap: 0.5rem;
14+
}
15+
16+
.guidedPromptLblBtn {
17+
display: flex;
18+
flex-direction: row;
19+
justify-content: space-between;
20+
}
21+
22+
.guidedPromptForm,
23+
.guidedPromptFormGroup {
24+
display: flex;
25+
flex-direction: column;
26+
}
27+
28+
.guidedPromptFormGroup:not(:first-child) {
29+
margin-top: 1rem;
30+
}
31+
32+
.guidedPromptForm [ui5-select] {
33+
width: 100%;
34+
}
35+
36+
.guidedPromptForm [ui5-range-slider] {
37+
margin-top: 1.5rem;
38+
}
39+
40+
[ui5-dialog] {
41+
width: 100%;
42+
}
43+
44+
[ui5-dialog] .content {
45+
display: grid;
46+
grid-template-columns: 2fr 1fr;
47+
grid-gap: 1rem;
48+
}
49+
50+
[ui5-dialog] [ui5-textarea] {
51+
height: 100%;
52+
}
53+
54+
.guidedPromptDialogFooter,
55+
.guidedPromptCardFooter {
56+
display: flex;
57+
flex-direction: row;
58+
justify-content: flex-end;
59+
gap: 0.25rem;
60+
padding: 0 0.75rem 0.75rem 0.75rem;
61+
}
62+
63+
.guidedPromptDialogFooter {
64+
align-items: center;
65+
padding: 0.25rem 0;
66+
width: 100%;
67+
}
68+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
import "@ui5/webcomponents/dist/Card.js";
2+
import "@ui5/webcomponents/dist/CardHeader.js";
3+
import "@ui5/webcomponents/dist/Toast.js";
4+
import "@ui5/webcomponents/dist/Token.js";
5+
import "@ui5/webcomponents/dist/Title.js";
6+
import "@ui5/webcomponents/dist/TextArea.js";
7+
import "@ui5/webcomponents/dist/Link.js";
8+
import "@ui5/webcomponents/dist/Label.js";
9+
import "@ui5/webcomponents/dist/Select.js";
10+
import "@ui5/webcomponents/dist/Option.js";
11+
import "@ui5/webcomponents/dist/Popover.js";
12+
import "@ui5/webcomponents/dist/Dialog.js";
13+
import "@ui5/webcomponents/dist/RangeSlider.js";
14+
import "@ui5/webcomponents/dist/SegmentedButton.js";
15+
import "@ui5/webcomponents/dist/Bar.js";
16+
import "@ui5/webcomponents/dist/Toolbar.js";
17+
import "@ui5/webcomponents-fiori/dist/DynamicSideContent.js";
18+
import "@ui5/webcomponents-ai/dist/Button.js";
19+
import "@ui5/webcomponents-icons/dist/ai.js";
20+
import "@ui5/webcomponents-icons/dist/stop.js";
21+
22+
let response = null;
23+
let responseContent = null;
24+
25+
if (!response) {
26+
response = await fetch("../assets/data/predefinedTexts.json");
27+
responseContent = await response.json();
28+
}
29+
30+
const dialog = document.getElementById("dialog");
31+
const aiDialogButton = document.getElementById("aiDialogButton");
32+
const openDialogButton = document.getElementById("openDialogButton");
33+
const closeDialogButton = document.getElementById("closeDialogButton");
34+
const output = document.getElementById("output");
35+
const dialogOutput = document.getElementById("dialogOutput");
36+
const structrureSelect = document.getElementById("structureSelect");
37+
const languageSelect = document.getElementById("languageSelect");
38+
const toneOfVoiceSelect = document.getElementById("toneOfVoiceSelect");
39+
const sendButton = document.getElementById("sendButton");
40+
const toast = document.getElementById("guidedPromptToast");
41+
42+
const texts = {
43+
paragraph: responseContent.predefinedTexts,
44+
bulleted: responseContent.predefinedTextsBulleted,
45+
};
46+
let options = {
47+
structure: "paragraph",
48+
language: "en",
49+
toneOfVoice: 2,
50+
};
51+
let text;
52+
let dialogGenerationId;
53+
54+
function startGeneration() {
55+
console.warn("startGeneration");
56+
dialogOutput.value = "";
57+
text = texts[options.structure][options.language][options.toneOfVoice];
58+
let generatedWordIndex = 0;
59+
const generationId = setInterval(function () {
60+
const words = text.split(" ");
61+
const maxWordIndex = words.length - 1;
62+
if (generatedWordIndex > maxWordIndex) {
63+
stopGeneration(generationId);
64+
aiDialogButton.state = "revise";
65+
openDialogButton.innerText = "Revise";
66+
closeDialogButton.disabled = false;
67+
return;
68+
}
69+
70+
dialogOutput.value += words[generatedWordIndex] + " ";
71+
generatedWordIndex++;
72+
}, 75);
73+
return generationId;
74+
}
75+
76+
function stopGeneration(generationId) {
77+
console.warn("stopGeneration");
78+
closeDialogButton.disabled = false;
79+
clearTimeout(generationId);
80+
}
81+
82+
function openDialogButtonClickHandler(evt) {
83+
openDialog();
84+
}
85+
86+
function aiDialogButtonClickHandler(evt) {
87+
var button = evt.target;
88+
switch (button.state) {
89+
case "":
90+
case "generate":
91+
startGenerationFromDialog();
92+
break;
93+
case "revise":
94+
startGenerationFromDialog();
95+
break;
96+
case "generating":
97+
button.state = "generate";
98+
openDialogButton.innerText = "Generate";
99+
closeDialogButton.disabled = false;
100+
stopGeneration(dialogGenerationId, closeDialogButton);
101+
break;
102+
}
103+
}
104+
105+
function startGenerationFromDialog() {
106+
aiDialogButton.state = "generating";
107+
closeDialogButton.disabled = true;
108+
dialogGenerationId = startGeneration();
109+
}
110+
111+
function openDialog() {
112+
dialog.open = true;
113+
}
114+
115+
function closeDialog() {
116+
dialog.open = false;
117+
output.value = dialogOutput.value;
118+
}
119+
120+
function structureSelectHandler(evt) {
121+
options.structure = evt.detail.selectedOption.value;
122+
}
123+
124+
function languageSelectHandler(evt) {
125+
options.language = evt.detail.selectedOption.value;
126+
}
127+
128+
function toneOfVoiceSelectHandler(evt) {
129+
const value = evt.detail.selectedItems[0].innerText;
130+
switch (value) {
131+
case "Formal": {
132+
options.toneOfVoice = 1;
133+
break;
134+
}
135+
case "Neutral": {
136+
options.toneOfVoice = 2;
137+
break;
138+
}
139+
case "Casual": {
140+
options.toneOfVoice = 3;
141+
}
142+
}
143+
}
144+
145+
sendButton.addEventListener("click", function() {
146+
const output = document.getElementById("output");
147+
if (output.value) {
148+
toast.open = true;
149+
output.valueState = "None";
150+
output.value = "";
151+
aiDialogButton.state = "generate";
152+
}
153+
});
154+
155+
structrureSelect.addEventListener("change", structureSelectHandler);
156+
openDialogButton.addEventListener("click", openDialogButtonClickHandler);
157+
aiDialogButton.addEventListener("click", aiDialogButtonClickHandler);
158+
closeDialogButton.addEventListener("click", closeDialog);
159+
languageSelect.addEventListener("change", languageSelectHandler);
160+
toneOfVoiceSelect.addEventListener(
161+
"selection-change",
162+
toneOfVoiceSelectHandler
163+
);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
<!-- playground-fold -->
2+
<!DOCTYPE html>
3+
<html lang="en">
4+
5+
<head>
6+
<meta charset="UTF-8">
7+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
8+
<link rel="stylesheet" href="./main.css">
9+
<title>Sample</title>
10+
</head>
11+
12+
<body style="background-color: var(--sapBackgroundColor); height: fit-content;">
13+
<!-- playground-fold-end -->
14+
<ui5-dialog
15+
id="dialog"
16+
opener="openDialogButton"
17+
header-text="Improve Text">
18+
<div class="content">
19+
<ui5-textarea
20+
id="dialogOutput"
21+
placeholder="Compose text..."></ui5-textarea>
22+
<div class="guidedPromptForm">
23+
<div class="guidedPromptFormGroup">
24+
<ui5-label>Structure:</ui5-label>
25+
<ui5-select id="structureSelect">
26+
<ui5-option value="paragraph">Paragraph</ui5-option>
27+
<ui5-option value="bulleted"> Bullet list</ui5-option>
28+
</ui5-select>
29+
</div>
30+
<div class="guidedPromptFormGroup">
31+
<ui5-label>Length:</ui5-label>
32+
<ui5-range-slider
33+
min="50"
34+
max="150"
35+
start-value="75"
36+
end-value="125"
37+
show-tooltip></ui5-range-slider>
38+
</div>
39+
<div class="guidedPromptFormGroup">
40+
<ui5-label>Language:</ui5-label>
41+
<ui5-select id="languageSelect">
42+
<ui5-option value="en">English</ui5-option>
43+
<ui5-option value="de">German</ui5-option>
44+
</ui5-select>
45+
</div>
46+
<div class="guidedPromptFormGroup">
47+
<ui5-label>Tone Of Voice:</ui5-label>
48+
<ui5-segmented-button id="toneOfVoiceSelect">
49+
<ui5-segmented-button-item>Formal</ui5-segmented-button-item>
50+
<ui5-segmented-button-item selected>Neutral</ui5-segmented-button-item>
51+
<ui5-segmented-button-item>Casual</ui5-segmented-button-item>
52+
</ui5-segmented-button>
53+
</div>
54+
</div>
55+
</div>
56+
57+
<div slot="footer" class="guidedPromptDialogFooter">
58+
<ui5-ai-button id="aiDialogButton" state="generate" design="Emphasized">
59+
<ui5-ai-button-state
60+
name="generate"
61+
text="Apply"
62+
icon="ai"
63+
></ui5-ai-button-state>
64+
<ui5-ai-button-state
65+
name="generating"
66+
text="Stop Generation"
67+
icon="stop"
68+
></ui5-ai-button-state>
69+
<ui5-ai-button-state
70+
name="revise"
71+
text="Revise"
72+
icon="ai"
73+
></ui5-ai-button-state>
74+
</ui5-ai-button>
75+
<ui5-button id="closeDialogButton">Close</ui5-button>
76+
</div>
77+
</ui5-dialog>
78+
79+
<ui5-card>
80+
<ui5-card-header slot="header" title-text="Monique Legrand" subtitle-text="Senior Sales Executive">
81+
<img src="https://sap.github.io/ui5-webcomponents/images/avatars/woman_avatar_1.png" slot="avatar">
82+
</ui5-card-header>
83+
<section class="guidedPromptSection">
84+
<ui5-label required>To: </ui5-label>
85+
<div style="display: flex; gap: 0.125rem;">
86+
<ui5-token selected text="DL Marketing Sector SAP" style="width: fit-content; margin-block-end: 1rem;"></ui5-token>
87+
</div>
88+
<div class="guidedPromptLblBtn">
89+
<ui5-label style="align-self: flex-end;" required>Offer: </ui5-label>
90+
<ui5-button id="openDialogButton" icon="ai"> Generate</ui5-button>
91+
</div>
92+
<ui5-textarea id="output" style="height: 100%;" placeholder="Compose text..."></ui5-textarea>
93+
</section>
94+
<div class="guidedPromptCardFooter">
95+
<ui5-button id="sendButton" design="Emphasized">Send</ui5-button>
96+
<ui5-button design="Transparent">Cancel</ui5-button>
97+
</div>
98+
<ui5-toast placement="MiddleCenter" id="guidedPromptToast" duration="3000">Your message was sent successfully!</ui5-toast>
99+
</ui5-card>
100+
101+
102+
103+
<!-- playground-fold -->
104+
<script type="module" src="main.js"></script>
105+
</body>
106+
107+
</html>
108+
<!-- playground-fold-end -->
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import html from '!!raw-loader!./sample.html';
2+
import css from '!!raw-loader!./main.css';
3+
import js from '!!raw-loader!./main.js';
4+
5+
<Editor html={html} js={js} css={css} />

0 commit comments

Comments
 (0)