UNPKG

@progress/kendo-angular-conversational-ui

Version:

Kendo UI for Angular Conversational UI components

539 lines (536 loc) 24.3 kB
/**----------------------------------------------------------------------------------------- * Copyright © 2025 Progress Software Corporation. All rights reserved. * Licensed under commercial license. See LICENSE.md in the project root for more information *-------------------------------------------------------------------------------------------*/ import { Component, ContentChild, ContentChildren, ElementRef, EventEmitter, HostBinding, Input, NgZone, Output, QueryList, Renderer2, ViewChild } from "@angular/core"; import { NgFor, NgIf, NgTemplateOutlet } from "@angular/common"; import { Subscription } from "rxjs"; import { sparklesIcon, commentIcon, stopSmIcon } from "@progress/kendo-svg-icons"; import { L10N_PREFIX, LocalizationService } from '@progress/kendo-angular-l10n'; import { validatePackage } from "@progress/kendo-licensing"; import { ButtonComponent, FloatingActionButtonComponent } from "@progress/kendo-angular-buttons"; import { BaseView } from "./views/base-view"; import { packageMetadata } from "../package-metadata"; import { AIPromptService } from "./common/aiprompt.service"; import { AIPromptToolbarActionsDirective } from "./templates/toolbar-actions.template"; import { ToolbarNavigationService } from "./common/toolbar-navigation.service"; import { AIPromptToolbarFocusableDirective } from "./common/toolbar-focusable.directive"; import { LocalizedMessagesDirective } from "./localization/localized-messages.directive"; import { AIPromptOutputTemplateDirective } from "./templates/aiprompt-output-template.directive"; import { AIPromptOutputBodyTemplateDirective } from "./templates/aiprompt-output-body-template.directive"; import { take } from "rxjs/operators"; import * as i0 from "@angular/core"; import * as i1 from "@progress/kendo-angular-l10n"; import * as i2 from "./common/aiprompt.service"; import * as i3 from "./common/toolbar-navigation.service"; /** * Represents the [Kendo UI AIPrompt component for Angular](slug:overview_aiprompt). * * @example * ```html * <kendo-aiprompt * [promptCommands]="commands" * [promptSuggestions]="suggestions" * [promptOutputs]="outputs" * [showOutputRating]="true" * (promptRequest)="onPromptRequest($event)"> * </kendo-aiprompt> * ``` * * @remarks * Supported children components are: {@link AIPromptCustomMessagesComponent}, {@link CustomViewComponent}, {@link CommandViewComponent}, {@link PromptViewComponent}, {@link OutputViewComponent}. */ export class AIPromptComponent { localization; service; navigationService; ngZone; element; renderer; hostClasses = true; get dirAttr() { return this.direction; } constructor(localization, service, navigationService, ngZone, element, renderer) { this.localization = localization; this.service = service; this.navigationService = navigationService; this.ngZone = ngZone; this.element = element; this.renderer = renderer; validatePackage(packageMetadata); this.direction = localization.rtl ? 'rtl' : 'ltr'; this.subs.add(localization.changes.subscribe(({ rtl }) => { this.direction = rtl ? 'rtl' : 'ltr'; })); } /** * @hidden */ fabButton; /** * @hidden */ views; /** * @hidden */ toolbarActionsTemplate; /** * @hidden */ outputTemplate; /** * @hidden */ outputBodyTemplate; /** * The active view index of the AIPrompt component. */ set activeView(idx) { if (this._activeView !== idx) { this._activeView = idx; this.service.promptValue && (this.service.promptValue = ''); } } get activeView() { return this._activeView; } /** * Sets the collection of commands to render in the Command view. */ set promptCommands(value) { this.service.promptCommands = value; } /** * Sets the collection of suggestions to render in the Prompt view. */ set promptSuggestions(value) { this.service.promptSuggestions = value; } /** * Sets the collection of generated prompt outputs to render in the Output view. */ set promptOutputs(value) { this.service.promptOutputs = value; } /** * Specifies whether the rating buttons appear in each Output view card. * The rating buttons do not appear by default. * @default false */ set showOutputRating(value) { this.service.showOutputRating = value; } /** * Specifies whether the Stop generation button appears in the Output view. * The Stop generation button does not appear by default. * @default false */ streaming = false; /** * Sets the settings for the Speech to Text button in the Prompt view * ([see example](slug:configuration_aiprompt#enabling-speech-to-text)). */ set speechToTextButton(settings) { if (settings) { this.service.speechToTextButton = settings; } else { this.service.speechToTextButton = false; } } /** * Sets the settings for the TextArea in the Prompt view * ([see example](slug:configuration_aiprompt#configuring-the-prompt-text-area)). */ set textAreaSettings(settings) { this.service.textAreaSettings = settings; } /** * Sets the SVG icon for the Generate button in the Prompt view. */ generateButtonSVGIcon = sparklesIcon; /** * Sets the icon for the Generate button in the Prompt view. * @default 'sparkles' */ generateButtonIcon = 'sparkles'; /** * Sets the disabled state for the Generate button in the Prompt view. * @default false */ disabledGenerateButton = false; /** * Fires when the `activeView` property is updated. * Use this event for two-way binding of the `activeView` property. */ activeViewChange = new EventEmitter(); /** * Fires when you click the Generate button in the Prompt view or the Retry button in the Output view. * Use the event's `isRetry` field to determine the source element. */ promptRequest = new EventEmitter(); /** * Fires when you click a Command view command. * The event data contains the selected command. */ commandExecute = new EventEmitter(); /** * Fires when you click a Copy button in any Output view card. */ outputCopy = new EventEmitter(); /** * Fires when you click a rating button in any Output view card. */ outputRatingChange = new EventEmitter(); /** * Fires when you click the Stop Generation button in the Output view. */ promptRequestCancel = new EventEmitter(); ngOnInit() { const aiPromptElement = this.element.nativeElement; this.subs.add(this.renderer.listen(aiPromptElement, 'keydown', (event) => { if (event.key === 'Escape') { this.promptRequestCancel.emit(); } })); } ngOnChanges(changes) { if (changes['streaming'] && changes['streaming'].currentValue === true) { this.ngZone.onStable.pipe(take(1)).subscribe(() => { this.fabButton?.focus(); }); } } ngAfterContentChecked() { if (this.outputTemplate !== this.service.outputTemplate) { this.service.outputTemplate = this.outputTemplate; } if (this.outputBodyTemplate !== this.service.outputBodyTemplate) { this.service.outputBodyTemplate = this.outputBodyTemplate; } } ngAfterViewInit() { this.ngZone.runOutsideAngular(() => { this.service.aiPrompt = this; this.subs.add(this.service.requestEvent.subscribe(ev => this.promptRequest.emit(ev))); this.subs.add(this.service.executeEvent.subscribe(ev => this.commandExecute.emit(ev))); this.subs.add(this.service.outputCopyEvent.subscribe(ev => this.outputCopy.emit(ev))); this.subs.add(this.service.outputRatingChangeEvent.subscribe(ev => this.outputRatingChange.emit(ev))); }); } ngOnDestroy() { this.subs.unsubscribe(); } /** * Focuses the first focusable element in the AIPrompt component. */ focus() { this.navigationService.focusFirst(); } subs = new Subscription(); /** * @hidden */ get selectedView() { return this.viewsArray[this.activeView]; } /** * @hidden */ sparklesIcon = sparklesIcon; /** * @hidden */ outputIcon = commentIcon; /** * @hidden */ fabStopGenerationSVGIcon = stopSmIcon; /** * @hidden */ get viewsArray() { return this.views?.toArray(); } /** * @hidden */ fabPositionMode = 'absolute'; /** * @hidden */ fabAlignment = { vertical: 'bottom', horizontal: 'end' }; direction; _activeView = 0; /** * @hidden */ viewChange(idx) { if (idx !== this.activeView) { this.activeView = idx; this.activeViewChange.emit(idx); } } /** * @hidden */ messageFor(text) { return this.localization.get(text); } /** * @hidden */ get viewTemplate() { return this.selectedView?.viewType === 'custom' ? this.selectedView?.viewTemplate : this.selectedView?.template; } /** * @hidden */ handleGenerateOutput() { const value = this.service.promptValue; const eventArgs = { prompt: value, sender: this, isRetry: false }; this.promptRequest.emit(eventArgs); } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: AIPromptComponent, deps: [{ token: i1.LocalizationService }, { token: i2.AIPromptService }, { token: i3.ToolbarNavigationService }, { token: i0.NgZone }, { token: i0.ElementRef }, { token: i0.Renderer2 }], target: i0.ɵɵFactoryTarget.Component }); static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "16.2.12", type: AIPromptComponent, isStandalone: true, selector: "kendo-aiprompt", inputs: { activeView: "activeView", promptCommands: "promptCommands", promptSuggestions: "promptSuggestions", promptOutputs: "promptOutputs", showOutputRating: "showOutputRating", streaming: "streaming", speechToTextButton: "speechToTextButton", textAreaSettings: "textAreaSettings", generateButtonSVGIcon: "generateButtonSVGIcon", generateButtonIcon: "generateButtonIcon", disabledGenerateButton: "disabledGenerateButton" }, outputs: { activeViewChange: "activeViewChange", promptRequest: "promptRequest", commandExecute: "commandExecute", outputCopy: "outputCopy", outputRatingChange: "outputRatingChange", promptRequestCancel: "promptRequestCancel" }, host: { properties: { "class.k-prompt": "this.hostClasses", "attr.dir": "this.dirAttr" } }, providers: [ LocalizationService, AIPromptService, ToolbarNavigationService, { provide: L10N_PREFIX, useValue: 'kendo.aiprompt' } ], queries: [{ propertyName: "toolbarActionsTemplate", first: true, predicate: AIPromptToolbarActionsDirective, descendants: true }, { propertyName: "outputTemplate", first: true, predicate: AIPromptOutputTemplateDirective, descendants: true }, { propertyName: "outputBodyTemplate", first: true, predicate: AIPromptOutputBodyTemplateDirective, descendants: true }, { propertyName: "views", predicate: BaseView }], viewQueries: [{ propertyName: "fabButton", first: true, predicate: ["fabButton"], descendants: true }], exportAs: ["kendoAIPrompt"], usesOnChanges: true, ngImport: i0, template: ` <ng-container kendoAIPromptLocalizedMessages i18n-promptView="kendo.aiprompt.promptView|The Toolbar button text for the Prompt view." promptView="Ask AI" i18n-outputView="kendo.aiprompt.outputView|The Toolbar button text for the Output view." outputView="Output" i18n-generateOutput="kendo.aiprompt.generateOutput|The text for the Generate button in the Prompt view." generateOutput="Generate" i18n-promptPlaceholder="kendo.aiprompt.promptPlaceholder|The placeholder text for the Prompt View text area." promptPlaceholder="Ask or generate content with AI" i18n-copyOutput="kendo.aiprompt.copyOutput|The Copy button text in each Output view card." copyOutput="Copy" i18n-retryGeneration="kendo.aiprompt.retryGeneration|The Retry button text in each Output view card." retryGeneration="Retry" i18n-outputTitle="kendo.aiprompt.outputTitle|The title of each Output view card." outputTitle="Generated with AI" i18n-outputRetryTitle="kendo.aiprompt.outputRetryTitle|The title of each Output view retry card." outputRetryTitle="Generated with AI" i18n-promptSuggestions="kendo.aiprompt.promptSuggestions|The title of the Prompt suggestions button." promptSuggestions="Prompt suggestions" i18n-speechToTextButton="kendo.aiprompt.speechToTextButton|The aria-label for the Speech to Text button." speechToTextButton="Voice input"> </ng-container> <div class="k-prompt-header"> <div class="k-toolbar-flat k-toolbar k-toolbar-md" role="toolbar"> <button *ngFor="let view of viewsArray; let idx = index" kendoButton kendoAIPromptToolbarFocusable type="button" fillMode="flat" rounded="full" themeColor="primary" [svgIcon]="view.svgIcon" [icon]="view.icon" [selected]="idx === activeView" (click)="viewChange(idx)"> {{view.buttonText}} </button> <ng-template *ngIf="toolbarActionsTemplate" [ngTemplateOutlet]="toolbarActionsTemplate?.templateRef"> </ng-template> </div> </div> <div class="k-prompt-content"> <kendo-floatingactionbutton #fabButton *ngIf="streaming && selectedView?.viewType === 'output'" class="k-prompt-stop-fab" buttonClass="k-generating k-active" [positionMode]="fabPositionMode" [align]="fabAlignment" [svgIcon]="fabStopGenerationSVGIcon" icon="stop" (click)="promptRequestCancel.emit()" > </kendo-floatingactionbutton> <div class="k-prompt-view"> <ng-container *ngTemplateOutlet="viewTemplate"> </ng-container> </div> </div> <div class="k-prompt-footer" *ngIf="selectedView?.viewType === 'prompt'"> <div class="k-actions k-actions-start k-actions-horizontal k-prompt-actions"> <button kendoButton type="button" themeColor="primary" rounded="full" [attr.title]="messageFor('generateOutput')" [attr.aria-label]="messageFor('generateOutput')" [attr.aria-disabled]="disabledGenerateButton" [svgIcon]="generateButtonSVGIcon" [icon]="generateButtonIcon" [disabled]="disabledGenerateButton" (click)="handleGenerateOutput()">{{messageFor('generateOutput')}}</button> </div> </div> `, isInline: true, dependencies: [{ kind: "directive", type: LocalizedMessagesDirective, selector: "[kendoAIPromptLocalizedMessages]" }, { kind: "directive", type: NgFor, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "component", type: ButtonComponent, selector: "button[kendoButton]", inputs: ["arrowIcon", "toggleable", "togglable", "selected", "tabIndex", "imageUrl", "iconClass", "icon", "disabled", "size", "rounded", "fillMode", "themeColor", "svgIcon", "primary", "look"], outputs: ["selectedChange", "click"], exportAs: ["kendoButton"] }, { kind: "directive", type: AIPromptToolbarFocusableDirective, selector: "[kendoAIPromptToolbarFocusable]" }, { kind: "directive", type: NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: FloatingActionButtonComponent, selector: "kendo-floatingactionbutton", inputs: ["themeColor", "size", "rounded", "disabled", "align", "offset", "positionMode", "icon", "svgIcon", "iconClass", "buttonClass", "dialClass", "text", "dialItemAnimation", "tabIndex", "dialItems"], outputs: ["blur", "focus", "dialItemClick", "open", "close"] }] }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: AIPromptComponent, decorators: [{ type: Component, args: [{ exportAs: 'kendoAIPrompt', selector: 'kendo-aiprompt', providers: [ LocalizationService, AIPromptService, ToolbarNavigationService, { provide: L10N_PREFIX, useValue: 'kendo.aiprompt' } ], template: ` <ng-container kendoAIPromptLocalizedMessages i18n-promptView="kendo.aiprompt.promptView|The Toolbar button text for the Prompt view." promptView="Ask AI" i18n-outputView="kendo.aiprompt.outputView|The Toolbar button text for the Output view." outputView="Output" i18n-generateOutput="kendo.aiprompt.generateOutput|The text for the Generate button in the Prompt view." generateOutput="Generate" i18n-promptPlaceholder="kendo.aiprompt.promptPlaceholder|The placeholder text for the Prompt View text area." promptPlaceholder="Ask or generate content with AI" i18n-copyOutput="kendo.aiprompt.copyOutput|The Copy button text in each Output view card." copyOutput="Copy" i18n-retryGeneration="kendo.aiprompt.retryGeneration|The Retry button text in each Output view card." retryGeneration="Retry" i18n-outputTitle="kendo.aiprompt.outputTitle|The title of each Output view card." outputTitle="Generated with AI" i18n-outputRetryTitle="kendo.aiprompt.outputRetryTitle|The title of each Output view retry card." outputRetryTitle="Generated with AI" i18n-promptSuggestions="kendo.aiprompt.promptSuggestions|The title of the Prompt suggestions button." promptSuggestions="Prompt suggestions" i18n-speechToTextButton="kendo.aiprompt.speechToTextButton|The aria-label for the Speech to Text button." speechToTextButton="Voice input"> </ng-container> <div class="k-prompt-header"> <div class="k-toolbar-flat k-toolbar k-toolbar-md" role="toolbar"> <button *ngFor="let view of viewsArray; let idx = index" kendoButton kendoAIPromptToolbarFocusable type="button" fillMode="flat" rounded="full" themeColor="primary" [svgIcon]="view.svgIcon" [icon]="view.icon" [selected]="idx === activeView" (click)="viewChange(idx)"> {{view.buttonText}} </button> <ng-template *ngIf="toolbarActionsTemplate" [ngTemplateOutlet]="toolbarActionsTemplate?.templateRef"> </ng-template> </div> </div> <div class="k-prompt-content"> <kendo-floatingactionbutton #fabButton *ngIf="streaming && selectedView?.viewType === 'output'" class="k-prompt-stop-fab" buttonClass="k-generating k-active" [positionMode]="fabPositionMode" [align]="fabAlignment" [svgIcon]="fabStopGenerationSVGIcon" icon="stop" (click)="promptRequestCancel.emit()" > </kendo-floatingactionbutton> <div class="k-prompt-view"> <ng-container *ngTemplateOutlet="viewTemplate"> </ng-container> </div> </div> <div class="k-prompt-footer" *ngIf="selectedView?.viewType === 'prompt'"> <div class="k-actions k-actions-start k-actions-horizontal k-prompt-actions"> <button kendoButton type="button" themeColor="primary" rounded="full" [attr.title]="messageFor('generateOutput')" [attr.aria-label]="messageFor('generateOutput')" [attr.aria-disabled]="disabledGenerateButton" [svgIcon]="generateButtonSVGIcon" [icon]="generateButtonIcon" [disabled]="disabledGenerateButton" (click)="handleGenerateOutput()">{{messageFor('generateOutput')}}</button> </div> </div> `, standalone: true, imports: [LocalizedMessagesDirective, NgFor, ButtonComponent, AIPromptToolbarFocusableDirective, NgIf, NgTemplateOutlet, FloatingActionButtonComponent] }] }], ctorParameters: function () { return [{ type: i1.LocalizationService }, { type: i2.AIPromptService }, { type: i3.ToolbarNavigationService }, { type: i0.NgZone }, { type: i0.ElementRef }, { type: i0.Renderer2 }]; }, propDecorators: { hostClasses: [{ type: HostBinding, args: ['class.k-prompt'] }], dirAttr: [{ type: HostBinding, args: ['attr.dir'] }], fabButton: [{ type: ViewChild, args: ['fabButton'] }], views: [{ type: ContentChildren, args: [BaseView] }], toolbarActionsTemplate: [{ type: ContentChild, args: [AIPromptToolbarActionsDirective] }], outputTemplate: [{ type: ContentChild, args: [AIPromptOutputTemplateDirective] }], outputBodyTemplate: [{ type: ContentChild, args: [AIPromptOutputBodyTemplateDirective] }], activeView: [{ type: Input }], promptCommands: [{ type: Input }], promptSuggestions: [{ type: Input }], promptOutputs: [{ type: Input }], showOutputRating: [{ type: Input }], streaming: [{ type: Input }], speechToTextButton: [{ type: Input }], textAreaSettings: [{ type: Input }], generateButtonSVGIcon: [{ type: Input }], generateButtonIcon: [{ type: Input }], disabledGenerateButton: [{ type: Input }], activeViewChange: [{ type: Output }], promptRequest: [{ type: Output }], commandExecute: [{ type: Output }], outputCopy: [{ type: Output }], outputRatingChange: [{ type: Output }], promptRequestCancel: [{ type: Output }] } });