Skip to content

Commit

Permalink
feat(ui5-ai-prompt-input): initial (experimental) implementation (#9078)
Browse files Browse the repository at this point in the history
  • Loading branch information
MapTo0 committed Jun 12, 2024
1 parent 2c36c45 commit 9dcdfdb
Show file tree
Hide file tree
Showing 9 changed files with 405 additions and 20 deletions.
31 changes: 31 additions & 0 deletions packages/ai/src/PromptInput.hbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<div class="ai-prompt-input-wrapper">
{{#if label}}
<ui5-label for="input">{{label}}</ui5-label>
{{/if}}

<div class="ai-prompt-input-form-wrapper">
<div class="ai-prompt-inner-input-wrapper">
<ui5-input
id="input"
.value="{{value}}"
placeholder="{{placeholder}}"
value-state="{{valueState}}"
?show-clear-icon={{showClearIcon}}
?disabled="{{disabled}}"
?readonly="{{readonly}}"
@keydown="{{_onkeydown}}"
@ui5-input="{{_onInnerInput}}"
@ui5-change="{{_onInnerChange}}"
>
{{#if valueStateMessage.length}}
<slot name="valueStateMessage" slot="valueStateMessage"></slot>
{{/if}}
</ui5-input>

{{#if showExceededText}}
<ui5-label class="ai-prompt-input-counter">{{_exceededText}}</ui5-label>
{{/if}}
</div>
<ui5-button ?disabled={{_submitButtonDisabled}} class="ai-prompt-input-button" design="Emphasized" icon="paper-plane" @click="{{_onButtonClick}}"></ui5-button>
</div>
</div>
230 changes: 220 additions & 10 deletions packages/ai/src/PromptInput.ts
Original file line number Diff line number Diff line change
@@ -1,44 +1,254 @@
import UI5Element from "@ui5/webcomponents-base/dist/UI5Element.js";
import customElement from "@ui5/webcomponents-base/dist/decorators/customElement.js";
import property from "@ui5/webcomponents-base/dist/decorators/property.js";
import event from "@ui5/webcomponents-base/dist/decorators/event.js";
import slot from "@ui5/webcomponents-base/dist/decorators/slot.js";
import litRender from "@ui5/webcomponents-base/dist/renderer/LitRenderer.js";

import Label from "@ui5/webcomponents/dist/Label.js";
import { getI18nBundle } from "@ui5/webcomponents-base/dist/i18nBundle.js";
import type I18nBundle from "@ui5/webcomponents-base/dist/i18nBundle.js";
import Integer from "@ui5/webcomponents-base/dist/types/Integer.js";
import ValueState from "@ui5/webcomponents-base/dist/types/ValueState.js";
import "@ui5/webcomponents-icons/dist/paper-plane.js";
import type { InputEventDetail } from "@ui5/webcomponents/dist/Input.js";
import Input from "@ui5/webcomponents/dist/Input.js";
import Label from "@ui5/webcomponents/dist/Label.js";
import Button from "@ui5/webcomponents/dist/Button.js";
import property from "@ui5/webcomponents-base/dist/decorators/property.js";
import {
isEnter,
} from "@ui5/webcomponents-base/dist/Keys.js";
import {
PROMPT_INPUT_CHARACTERS_LEFT,
PROMPT_INPUT_CHARACTERS_EXCEEDED,
} from "./generated/i18n/i18n-defaults.js";

import PromptInputTemplate from "./generated/templates/PromptInputTemplate.lit.js";

// Styles
import PromptInputCss from "./generated/themes/PromptInput.css.js";

/**
* @class
*
* ### Overview
*
* The PromptInput is an AI component.
* The `ui5-ai-prompt-input` component allows the user to write custom instructions in natural language, so that AI is guided to generate content tailored to user needs.
*
* **Note:** The web component is in an experimental state
*
* ### ES6 Module Import
*
* `import "@ui5/webcomponents-ai/dist/PromptInput.js";`
* `import "@ui5/webcomponents-ai/dist/PromptInput.js
* @class
* @constructor
* @extends UI5Element
* @since 2.0
* @public
* @extends UI5Element
*/
@customElement({
tag: "ui5-ai-prompt-input",
renderer: litRender,
styles: PromptInputCss,
template: PromptInputTemplate,
dependencies: [
Input,
Label,
Button,
],
})

/**
* Fired when the input operation has finished by pressing Enter
* or AI button is clicked.
*
* @since 2.0.0
* @public
*/
@event("submit")

/**
* Fired when the value of the component changes at each keystroke,
* and when a suggestion item has been selected.
*
* @since 2.0.0
* @public
*/
@event("input")

/**
* Fired when the input operation has finished by pressing Enter
* or on focusout.
*
* @since 2.0.0
* @public
*/
@event("change")
class PromptInput extends UI5Element {
/**
* Defines the value of the PromptInput.
* @public
* Defines the value of the component.
*
* @default ""
* @since 2.0.0
* @public
*/
@property()
value!: string;

/**
* Defines a short hint intended to aid the user with data entry when the
* component has no value.
* @default ""
* @since 2.0.0
* @public
*/
@property()
placeholder!: string;

/**
* Defines the label of the input field.
*
* @default ""
* @since 2.0.0
* @public
*/
@property()
label!: string;

/**
* Defines whether the clear icon of the input will be shown.
* @default false
* @public
* @since 2.0.0
*/
@property({ type: Boolean })
showClearIcon!: boolean;

/**
* Determines whether the characters exceeding the maximum allowed character count are visible
* in the component.
*
* If set to `false`, the user is not allowed to enter more characters than what is set in the
* `maxlength` property.
* If set to `true` the characters exceeding the `maxlength` value are selected on
* paste and the counter below the component displays their number.
* @default false
* @public
* @since 2.0.0
*/
@property({ type: Boolean })
showExceededText!: boolean;

/**
* Defines whether the component is in disabled state.
*
* **Note:** A disabled component is completely noninteractive.
* @default false
* @public
* @since 2.0.0
*/
@property({ type: Boolean })
disabled!: boolean;

/**
* Defines whether the component is read-only.
*
* **Note:** A read-only component is not editable,
* but still provides visual feedback upon user interaction.
* @default false
* @public
* @since 2.0.0
*/
@property({ type: Boolean })
readonly!: boolean;

/**
* Sets the maximum number of characters available in the input field.
*
* @default undefined
* @since 2.0.0
* @public
*/
@property({ validator: Integer })
maxlength?: number;

/**
* Defines the value state of the component.
* @default "None"
* @since 2.0.0
* @public
*/
@property({ type: ValueState, defaultValue: ValueState.None })
valueState!: `${ValueState}`;

/**
* Defines the value state message that will be displayed as pop up under the component.
* The value state message slot should contain only one root element.
*
* **Note:** If not specified, a default text (in the respective language) will be displayed.
*
* **Note:** The `valueStateMessage` would be displayed,
* when the component is in `Information`, `Critical` or `Negative` value state.
*
* @since 2.0.0
* @public
*/
@slot({
type: HTMLElement,
invalidateOnChildChange: true,
})
valueStateMessage!: Array<HTMLElement>;

static i18nBundle: I18nBundle;

static async onDefine() {
PromptInput.i18nBundle = await getI18nBundle("@ui5/webcomponents-ai");
}

constructor() {
super();
}

_onkeydown(e: KeyboardEvent) {
if (isEnter(e)) {
this.fireEvent("submit");
}
}

_onInnerInput(e: CustomEvent<InputEventDetail>) {
this.value = (e.target as Input).value;

this.fireEvent("input");
}

_onInnerChange() {
this.fireEvent("change");
}

_onButtonClick() {
this.fireEvent("submit");
}

get _exceededText() {
if (this.showExceededText) {
let leftCharactersCount;
const maxLength = this.maxlength;

if (maxLength !== undefined) {
leftCharactersCount = maxLength - this.value.length;

if (leftCharactersCount >= 0) {
return PromptInput.i18nBundle.getText(PROMPT_INPUT_CHARACTERS_LEFT, leftCharactersCount);
}

return PromptInput.i18nBundle.getText(PROMPT_INPUT_CHARACTERS_EXCEEDED, Math.abs(leftCharactersCount));
}
}
}

get _maxLenght() {
return this.maxlength || undefined;
}

get _submitButtonDisabled() {
return (this.value.length <= 0) || this.disabled || this.readonly;
}
}

PromptInput.define();
Expand Down
6 changes: 6 additions & 0 deletions packages/ai/src/i18n/messagebundle.properties
Original file line number Diff line number Diff line change
@@ -1,2 +1,8 @@
#This is the resource bundle for the UI5 Web Components
#__ldi.translation.uuid=96bea51a-d5e3-46f0-b1d1-514d97be02ec

#XTXT: Text for characters left
PROMPT_INPUT_CHARACTERS_LEFT={0} characters remaining

#XTXT: Text for characters over
PROMPT_INPUT_CHARACTERS_EXCEEDED={0} characters over limit
30 changes: 30 additions & 0 deletions packages/ai/src/themes/PromptInput.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
.ai-prompt-input-button {
margin-left: .5rem;
margin-top: 3px;
}

.ai-prompt-input-wrapper {
display: flex;
flex-direction: column;
width: 270px;
}

.ai-prompt-input-form-wrapper {
display: flex;
flex: 1;
}

.ai-prompt-input-counter {
font-size: .75rem;
align-self: flex-end;
}

#input {
width: 100%;
}

.ai-prompt-inner-input-wrapper {
display: flex;
flex-direction: column;
flex: 1;
}
Loading

0 comments on commit 9dcdfdb

Please sign in to comment.