@progress/kendo-ui
Version:
This package is part of the [Kendo UI for jQuery](http://www.telerik.com/kendo-ui) suite.
1,517 lines (1,512 loc) • 59.2 kB
JavaScript
//#region ../src/aiprompt/output-action-manager.js
(function($) {
const messageTypes = {
"ai": "assistant",
"system": "system",
"user": "user",
"tool": "tool"
};
class AIPromptOutputActionManager {
constructor(aiprompt, options = {}) {
this.aiprompt = aiprompt;
this.options = options;
this.buttonDefinitions = this._createButtonDefinitions();
}
static getBuiltInActionDefinitions() {
return {
copy: {
command: "copy",
icon: "copy",
fillMode: "flat",
themeColor: "primary",
text: null
},
retry: {
command: "retry",
icon: "arrow-rotate-cw",
fillMode: "flat",
text: null
},
ratePositive: {
command: "ratePositive",
icon: "thumb-up-outline",
fillMode: "flat",
iconButton: true,
text: null
},
rateNegative: {
command: "rateNegative",
icon: "thumb-down-outline",
fillMode: "flat",
iconButton: true,
text: null
},
stop: {
command: "stop",
icon: "stop",
fillMode: "flat",
text: null
}
};
}
static processOutputActions(actions) {
if (!actions) {
return null;
}
const builtInActions = AIPromptOutputActionManager.getBuiltInActionDefinitions();
return actions.map((action) => {
if (typeof action === "string") {
if (action === "rating") {
return [builtInActions.ratePositive, builtInActions.rateNegative];
} else if (action === "spacer") {
return { type: "spacer" };
}
return builtInActions[action] || {
command: action,
type: "button"
};
}
if (kendo.isPresent(action.command) && action.command == "rating") {
return [builtInActions.ratePositive, builtInActions.rateNegative];
}
return action;
}).flat();
}
_createButtonDefinitions() {
const that = this;
const baseDefinitions = AIPromptOutputActionManager.getBuiltInActionDefinitions();
return {
copy: {
...baseDefinitions.copy,
getMessage: () => that.view.options.messages.copyOutput,
handler: (e, promptOutput) => that._handleCopyAction(e, promptOutput)
},
retry: {
...baseDefinitions.retry,
getMessage: () => that.view.options.messages.retryGeneration,
handler: (e, promptOutput) => that._handleRetryAction(e, promptOutput)
},
ratePositive: {
...baseDefinitions.ratePositive,
getMessage: () => that.view.options.messages.ratePositive,
handler: (e, promptOutput) => that._handleRatePositiveAction(e, promptOutput)
},
rateNegative: {
...baseDefinitions.rateNegative,
getMessage: () => that.view.options.messages.rateNegative,
handler: (e, promptOutput) => that._handleRateNegativeAction(e, promptOutput)
},
stop: {
...baseDefinitions.stop,
getMessage: () => that.view.options.messages.stopGeneration,
handler: (e, promptOutput) => that._handleStopAction(e, promptOutput)
}
};
}
initializeButtons(container, actions = null) {
const that = this;
if (actions) {
actions.forEach((action) => {
if (action.type === "spacer") {
return;
}
const selector = `[data-action-command="${action.command}"]`;
that._initializeButton(container, selector, action);
});
}
}
_initializeButton(container, selector, action) {
const that = this;
const definition = that.buttonDefinitions[action.command];
container.find(selector).kendoButton({
icon: action.icon || definition?.icon,
fillMode: action.fillMode || definition?.fillMode || "flat",
themeColor: action.themeColor || definition?.themeColor,
rounded: action.rounded,
click: function(e) {
const promptOutput = that.aiprompt.outputManager.getOutputFromElement(e.target);
const eventArgs = {
command: action.command,
outputId: promptOutput.id,
output: promptOutput.output || promptOutput.text || promptOutput || "",
prompt: promptOutput.prompt || "",
button: e.sender.element
};
if (definition?.handler && !that.aiprompt.trigger("outputAction", eventArgs)) {
definition.handler(e, promptOutput);
} else if (!definition) {
that.aiprompt.trigger("outputAction", eventArgs);
}
}
});
}
_handleCopyAction(e, promptOutput) {
const hasCopyHandler = this.aiprompt._events && this.aiprompt._events.outputCopy && this.aiprompt._events.outputCopy.length > 0;
if (hasCopyHandler) {
kendo.logToConsole("The outputCopy event is deprecated. Use the outputAction event instead.", "warn");
}
const copyEventArgs = { output: promptOutput };
if (hasCopyHandler && this.aiprompt.trigger("outputCopy", copyEventArgs)) {
return;
}
if (navigator.clipboard && typeof navigator.clipboard.writeText === "function") {
navigator.clipboard.writeText(promptOutput.output || promptOutput.text || promptOutput || "");
}
}
_handleRetryAction(e, promptOutput) {
const { prompt, output } = this.aiprompt.outputManager.extractOutputData(promptOutput);
const that = this;
const history = {
role: messageTypes.ai,
contents: [{
$type: "text",
text: output
}]
};
const retryEventArgs = {
prompt,
output: promptOutput,
isRetry: true,
history: [history]
};
const service = that.aiprompt?._selectedView?.service;
if (service) {
retryEventArgs.service = service;
}
if (that.aiprompt.trigger("promptRequest", retryEventArgs)) {
return;
}
if (that.aiprompt?.options.service) {
that.aiprompt?.transport?.read({
prompt,
history: retryEventArgs.history,
isRetry: true,
service: retryEventArgs.service
});
}
}
_handleRatePositiveAction(e, promptOutput) {
const hasRatingChangeHandler = this.aiprompt._events && this.aiprompt._events.outputRating && this.aiprompt._events.outputRating.length > 0;
if (hasRatingChangeHandler) {
kendo.logToConsole("The outputRating event is deprecated. Use the outputAction event instead.", "warn");
}
this.aiprompt.trigger("outputRating", {
rateType: "positive",
output: promptOutput
});
kendo.ui.icon(e.sender.element.find(".k-icon"), "thumb-up");
const negativeButton = $(e.target).siblings("[data-action-command='rateNegative'], [ref-rate-negative]");
kendo.ui.icon(negativeButton.find(".k-icon"), "thumb-down-outline");
}
_handleRateNegativeAction(e, promptOutput) {
const hasRatingChangeHandler = this.aiprompt._events && this.aiprompt._events.outputRating && this.aiprompt._events.outputRating.length > 0;
if (hasRatingChangeHandler) {
kendo.logToConsole("The outputRating event is deprecated. Use the outputAction event instead.", "warn");
}
this.aiprompt.trigger("outputRating", {
rateType: "negative",
output: promptOutput
});
kendo.ui.icon(e.sender.element.find(".k-icon"), "thumb-down");
const positiveButton = $(e.target).siblings("[data-action-command='ratePositive'], [ref-rate-positive]");
kendo.ui.icon(positiveButton.find(".k-icon"), "thumb-up-outline");
}
_handleStopAction(e, promptOutput) {
this.aiprompt.trigger("promptRequestCancel", { output: promptOutput });
$(e.target).siblings(".k-hidden").removeClass("k-hidden");
$(e.target).addClass("k-hidden");
}
destroy() {
this.aiprompt = null;
this.options = null;
this.buttonDefinitions = null;
}
}
kendo.ui.AIPromptOutputActionManager = AIPromptOutputActionManager;
})(window.kendo.jQuery);
//#endregion
//#region ../src/aiprompt/template-builder.js
(function($) {
const CSS_CLASSES = {
PROMPT_VIEW: "k-prompt-view",
PROMPT_EXPANDER: "k-prompt-expander",
SUGGESTION_GROUP: "k-suggestion-group",
SUGGESTION: "k-suggestion",
CARD: "k-card",
CARD_LIST: "k-card-list",
CARD_HEADER: "k-card-header",
CARD_TITLE: "k-card-title",
CARD_SUBTITLE: "k-card-subtitle",
CARD_BODY: "k-card-body",
CARD_ACTIONS: "k-card-actions",
ACTIONS: "k-actions k-actions-start k-actions-horizontal",
SPACER: "k-spacer",
HIDDEN: "k-hidden"
};
const REFS = {
PROMPT_INPUT: "ref-prompt-input",
SUGGESTIONS_BUTTON: "ref-prompt-suggestions-button",
GENERATE_BUTTON: "ref-generate-output-button",
OUTPUT_BODY: "ref-output-body",
COPY_BUTTON: "ref-copy-button",
RETRY_BUTTON: "ref-retry-button",
RATE_POSITIVE: "ref-rate-positive",
RATE_NEGATIVE: "ref-rate-negative",
STOP_GENERATION: "ref-stop-generation"
};
class AIPromptTemplateBuilder {
static createPromptView({ suggestions, promptSuggestionItemTemplate, messages }) {
const suggestionsHtml = suggestions?.length ? AIPromptTemplateBuilder._createSuggestionsSection(suggestions, promptSuggestionItemTemplate, messages) : "";
return `<div class="${CSS_CLASSES.PROMPT_VIEW}">
<textarea ${REFS.PROMPT_INPUT}></textarea>
${suggestionsHtml}
</div>`;
}
static createPromptFooter({ messages }) {
return `<div class="${CSS_CLASSES.ACTIONS} k-prompt-actions">
<button ${REFS.GENERATE_BUTTON}>${messages.generateOutput}</button>
</div>`;
}
static createSuggestionItem({ suggestion }) {
return `<span role="listitem" class="${CSS_CLASSES.SUGGESTION}">${suggestion}</span>`;
}
static createOutputCard({ output, showOutputRating, messages, showOutputSubtitleTooltip, encodedPromptOutputs, isStreaming, outputActions, outputTemplate }) {
const contentHtml = AIPromptTemplateBuilder._generateContentHtml({
output,
outputTemplate,
encodedPromptOutputs
});
const actionsHtml = AIPromptTemplateBuilder._generateActionsHtml({
outputActions,
showOutputRating,
messages,
isStreaming
});
const dataIdAttr = output.id ? ` data-id="${output.id}"` : "";
return `<div role="listitem" tabindex="0" class="${CSS_CLASSES.CARD}"${dataIdAttr}>
${output.skipHeader ? "" : AIPromptTemplateBuilder._createCardHeader(output, messages, showOutputSubtitleTooltip)}
${output.skipBody ? "" : AIPromptTemplateBuilder._createCardBody(contentHtml, output.isLoading)}
${output.skipActions ? "" : actionsHtml}
</div>`;
}
static createOutputView({ promptOutputs, showOutputRating, messages, showOutputSubtitleTooltip, encodedPromptOutputs, outputActions, outputTemplate }) {
const cardsHtml = promptOutputs ? promptOutputs.map((output) => AIPromptTemplateBuilder.createOutputCard({
output,
showOutputRating,
messages,
showOutputSubtitleTooltip,
encodedPromptOutputs,
outputActions,
outputTemplate
})).join("") : "";
return `<div class="${CSS_CLASSES.PROMPT_VIEW}">
<div role="list" class="${CSS_CLASSES.CARD_LIST}">
${cardsHtml}
</div>
</div>`;
}
static _createSuggestionsSection(suggestions, promptSuggestionItemTemplate, messages) {
const suggestionItems = suggestions.map((suggestion) => promptSuggestionItemTemplate({ suggestion })).join("");
return `<div class="${CSS_CLASSES.PROMPT_EXPANDER}">
<button ${REFS.SUGGESTIONS_BUTTON} aria-expanded="true">${messages.promptSuggestions}</button>
<div class="k-prompt-expander-content" role="list">
<div class="${CSS_CLASSES.SUGGESTION_GROUP}">
${suggestionItems}
</div>
</div>
</div>`;
}
static _createCardHeader(output, messages, showOutputSubtitleTooltip) {
const tooltipAttr = showOutputSubtitleTooltip ? `title="${kendo.htmlEncode(output.prompt)}"` : "";
return `<div class="${CSS_CLASSES.CARD_HEADER}">
<div class="${CSS_CLASSES.CARD_TITLE}">${messages.outputTitle}</div>
<div class="${CSS_CLASSES.CARD_SUBTITLE}" ${tooltipAttr}>${kendo.htmlEncode(output.prompt)}</div>
</div>`;
}
static _createCardBody(contentHtml, isLoading) {
return `<div class="${CSS_CLASSES.CARD_BODY}" ${REFS.OUTPUT_BODY}>
${contentHtml}
</div>`;
}
static _generateContentHtml({ output, outputTemplate, encodedPromptOutputs }) {
if (outputTemplate && typeof outputTemplate === "function" && !output.isLoading && output.output) {
return outputTemplate({
output,
content: output.output
});
}
const content = output.output || "";
const loadingAttr = output.isLoading ? " data-loading=\"true\"" : " data-loading=\"false\"";
return `<p ref-output-content${loadingAttr}>${encodedPromptOutputs ? kendo.htmlEncode(content) : content}</p>`;
}
static _generateActionsHtml({ outputActions, showOutputRating, messages, isStreaming }) {
if (!outputActions) {
outputActions = showOutputRating ? [
"copy",
"retry",
"spacer",
"rating"
] : ["copy", "retry"];
}
return AIPromptTemplateBuilder._createCustomActions(outputActions, showOutputRating, messages, isStreaming);
}
static _createCustomActions(outputActions, showOutputRating, messages, isStreaming) {
const filteredActions = outputActions.filter((action) => action.command !== "stop");
const hasRatingButtons = filteredActions.some((action) => action.command === "ratePositive" || action.command === "rateNegative");
let actionsToRender = [...filteredActions];
if (showOutputRating && !hasRatingButtons) {
const hasExistingSpacer = actionsToRender.some((action) => action.type === "spacer");
if (!hasExistingSpacer) {
actionsToRender.push({ type: "spacer" });
}
actionsToRender.push({
command: "ratePositive",
text: messages.ratePositive,
type: "button"
}, {
command: "rateNegative",
text: messages.rateNegative,
type: "button"
});
}
const actionsHtml = actionsToRender.map((action) => AIPromptTemplateBuilder._createActionButton(action, messages, isStreaming)).join("");
return `<div class="${CSS_CLASSES.ACTIONS} ${CSS_CLASSES.CARD_ACTIONS}">
${actionsHtml}
</div>`;
}
static _createActionButton(action, messages, isStreaming) {
if (action.type === "spacer") {
return `<span class="${CSS_CLASSES.SPACER}"></span>`;
}
const text = action.text || AIPromptTemplateBuilder._getActionText(action.command, messages);
const title = action.title || text;
return `<button data-action-command="${action.command}" title="${title}">${action.iconButton ? "" : text}</button>`;
}
static _getActionText(command, messages) {
const textMap = {
"copy": messages.copyOutput,
"retry": messages.retryGeneration,
"ratePositive": messages.ratePositive,
"rateNegative": messages.rateNegative
};
return textMap[command] || command;
}
}
kendo.ui.AIPromptTemplateBuilder = AIPromptTemplateBuilder;
})(window.kendo.jQuery);
//#endregion
//#region ../src/aiprompt/speech-manager.js
(function($) {
class AIPromptSpeechManager {
constructor(view, options = {}) {
this.view = view;
this.aiprompt = view.aiprompt;
this.options = this._processSettings(options);
this._speechButton = null;
}
_processSettings(speechToTextConfig) {
const defaultOptions = {
integrationMode: "webSpeech",
lang: "en-US",
continuous: false,
interimResults: false,
maxAlternatives: 1
};
if (speechToTextConfig === false || speechToTextConfig === null) {
return {
enabled: false,
options: null
};
} else if (speechToTextConfig === true) {
return {
enabled: true,
options: defaultOptions
};
} else if (typeof speechToTextConfig === "object") {
return {
enabled: true,
options: $.extend({}, defaultOptions, speechToTextConfig)
};
}
return {
enabled: true,
options: defaultOptions
};
}
isEnabled() {
return this.options.enabled;
}
getTextAreaSuffixOptions() {
if (!this.isEnabled()) {
return {};
}
return { suffixOptions: {
template: function() {
return "<button ref-speech-to-text-button title=\"Speech to Text\"></button>";
},
separator: false
} };
}
initialize(textareaWidget) {
if (!this.isEnabled() || !textareaWidget) {
return false;
}
const speechButton = textareaWidget.wrapper.find("button[ref-speech-to-text-button]");
if (speechButton.length === 0) {
return false;
}
this._speechButton = speechButton.kendoSpeechToTextButton({
...this.options.options,
fillMode: "flat"
}).getKendoSpeechToTextButton();
this._speechButton.bind("result", (e) => this._handleResult(e, textareaWidget));
this.aiprompt.speechToTextButton = this._speechButton;
return true;
}
_handleResult(e, textareaWidget) {
if (e.isFinal || !this.options.options.interimResults) {
const transcript = e.alternatives[0]?.transcript || "";
const currentValue = textareaWidget.value();
let newValue = currentValue ? currentValue + " " + transcript : transcript;
const maxLength = textareaWidget.options.maxlength;
if (maxLength && newValue.length > maxLength) {
newValue = newValue.substring(0, maxLength);
}
textareaWidget.value(newValue);
}
}
startRecognition() {
if (this._speechButton) {
this._speechButton.startRecognition();
}
}
stopRecognition() {
if (this._speechButton) {
this._speechButton.stopRecognition();
}
}
abortRecognition() {
if (this._speechButton) {
this._speechButton.abortRecognition();
}
}
isListening() {
return this._speechButton ? this._speechButton.isListening() : false;
}
destroy() {
if (this._speechButton) {
this._speechButton.destroy();
this._speechButton = null;
}
}
}
kendo.ui.AIPromptSpeechManager = AIPromptSpeechManager;
})(window.kendo.jQuery);
//#endregion
//#region ../src/aiprompt/output-manager.js
(function($) {
class AIPromptOutputObject {
constructor(outputData, outputManager) {
this.id = outputData.id;
this.data = outputData;
this._element = null;
this._bodyElement = null;
this._aiprompt = outputManager.aiprompt;
this._isLoading = outputData.isLoading || false;
}
get isLoading() {
return this._isLoading;
}
set isLoading(value) {
const wasLoading = this._isLoading;
this._isLoading = value;
this.data.isLoading = value;
if (value === true) {
this.showSkeleton();
} else if (value === false) {
this.hideSkeleton();
if (wasLoading || this.data.output) {
this.applyFinalTemplate();
}
}
}
getElement() {
return this._element;
}
setElement(element) {
this._element = element;
return this;
}
updateContent(newContent) {
if (!this._element || this._element.length === 0) {
return this;
}
this.data.content = newContent;
this.data.output = newContent;
const bodyElement = this._element.find(".k-card-body");
const contentElement = bodyElement.find("[ref-output-content]");
if (newContent && newContent.trim() && contentElement.length > 0) {
bodyElement.find(".k-skeleton").remove();
contentElement.attr("data-loading", "false").show();
const encodedPromptOutputs = this._aiprompt.options.encodedPromptOutputs;
contentElement.html(encodedPromptOutputs ? kendo.htmlEncode(newContent) : newContent);
}
return this;
}
showSkeleton() {
this.showHeaderSkeleton();
this.showBodySkeleton();
this.showActionSkeleton();
return this;
}
hideSkeleton() {
this.hideHeaderSkeleton();
this.hideBodySkeleton();
this.hideActionSkeleton();
return this;
}
applyFinalTemplate() {
if (!this._element || this._element.length === 0) {
return this;
}
const bodyElement = this._element.find(".k-card-body");
const contentElement = bodyElement.find("[ref-output-content]");
let outputTemplate = this._aiprompt?.options?.outputTemplate;
if (!outputTemplate) {
return this;
} else if (typeof outputTemplate === "string") {
outputTemplate = kendo.template(outputTemplate);
}
if (outputTemplate && typeof outputTemplate === "function" && this.data.output) {
const customContent = outputTemplate({
output: this.data,
content: this.data.output
});
bodyElement.html(customContent);
} else if (contentElement.length > 0) {
contentElement.attr("data-loading", "false").show();
}
return this;
}
showHeaderSkeleton() {
if (!this._element || this._element.length === 0) {
return this;
}
const headerElement = this._element.find(".k-card-header");
if (headerElement.length === 0) {
return this;
}
headerElement.children().hide();
if (headerElement.find(".k-skeleton").length === 0) {
const skeleton = $(`<span class="k-skeleton k-skeleton-text k-skeleton-pulse"></span>`);
skeleton.css("width", "60%").css("height", "24px");
headerElement.prepend(skeleton);
}
return this;
}
hideHeaderSkeleton() {
if (!this._element || this._element.length === 0) {
return this;
}
const headerElement = this._element.find(".k-card-header");
if (headerElement.length === 0) {
return this;
}
headerElement.find(".k-skeleton").remove();
headerElement.children().removeClass("k-hidden").show();
return this;
}
showBodySkeleton() {
if (!this._element || this._element.length === 0) {
return this;
}
const bodyElement = this._element.find(".k-card-body");
const contentElement = bodyElement.find("[ref-output-content]");
contentElement.attr("data-loading", "true").hide();
if (bodyElement.find(".k-skeleton").length === 0) {
const skeleton = $(`<span class="k-skeleton k-skeleton-rect k-skeleton-pulse"></span>`);
skeleton.css("height", "80px");
bodyElement.prepend(skeleton);
}
return this;
}
hideBodySkeleton() {
if (!this._element || this._element.length === 0) {
return this;
}
const bodyElement = this._element.find(".k-card-body");
bodyElement.find(".k-skeleton").remove();
this.applyFinalTemplate();
return this;
}
showActionSkeleton() {
if (!this._element || this._element.length === 0) {
return this;
}
const actionsElement = this._element.find(".k-card-actions");
if (actionsElement.length > 0) {
actionsElement.children().hide();
if (actionsElement.find(".k-skeleton").length === 0) {
const skeleton = $(`<span class="k-skeleton k-skeleton-text k-skeleton-pulse"></span>`);
skeleton.css("width", "100%").css("height", "32px");
actionsElement.prepend(skeleton);
}
}
return this;
}
hideActionSkeleton() {
if (!this._element || this._element.length === 0) {
return this;
}
const actionsElement = this._element.find(".k-card-actions");
if (actionsElement.length > 0) {
actionsElement.find(".k-skeleton").remove();
actionsElement.children().removeClass("k-hidden").show();
}
return this;
}
toggleActionButtons(isStreaming, outputActions) {
if (!this._element || this._element.length === 0) {
return this;
}
if (outputActions) {
const allActions = this._element.find("[data-action-command]");
if (isStreaming) {
allActions.addClass("k-hidden");
} else {
allActions.removeClass("k-hidden");
}
} else {
const actionButtons = this._element.find("[ref-copy-button], [ref-retry-button]");
if (isStreaming) {
actionButtons.addClass("k-hidden");
} else {
actionButtons.removeClass("k-hidden");
}
}
return this;
}
destroy() {
this._element = null;
this._bodyElement = null;
this._aiprompt = null;
this.data = null;
this.id = null;
this._isLoading = false;
}
}
class AIPromptOutputManager {
constructor(aiprompt) {
this.aiprompt = aiprompt;
}
createOutputObject(outputData) {
return new AIPromptOutputObject(outputData, this);
}
getLastOutputObject() {
if (this.aiprompt.promptOutputs.length > 0) {
const lastOutput = this.aiprompt.promptOutputs[0];
return this.aiprompt.outputObjects.get(lastOutput.id);
}
return null;
}
updatePromptOutputContent(content, outputId) {
let outputObj;
if (outputId) {
outputObj = this.aiprompt.outputObjects.get(outputId);
} else {
outputObj = this.getLastOutputObject();
}
if (outputObj) {
outputObj.updateContent(content);
return outputObj;
} else {
return null;
}
}
stopLoading(objectId) {
let outputObj = this.aiprompt.outputObjects.get(objectId);
if (outputObj) {
outputObj.isLoading = false;
} else {
outputObj = this.getLastOutputObject();
if (outputObj) {
outputObj.isLoading = false;
}
}
}
stopAllLoading() {
this.aiprompt.outputObjects.forEach((outputObj) => {
outputObj.isLoading = false;
});
}
getOutputFromElement(element) {
let card = $(element).closest(".k-card");
let id = card.data("id");
let promptOutput = this.aiprompt.promptOutputs.find((output) => output.id == id);
if (!promptOutput && this.aiprompt.outputObjects) {
promptOutput = this.aiprompt.outputObjects.get(id);
}
return promptOutput;
}
extractOutputData(promptOutput) {
if (!promptOutput) {
return {
prompt: null,
output: null
};
}
if (promptOutput.data) {
return {
prompt: promptOutput.data.prompt,
output: promptOutput.data.output
};
}
return {
prompt: promptOutput.prompt,
output: promptOutput.output
};
}
destroy() {
if (this.aiprompt && this.aiprompt.outputObjects) {
this.aiprompt.outputObjects.forEach((outputObj) => {
if (outputObj) {
outputObj._element = null;
outputObj._bodyElement = null;
outputObj._aiprompt = null;
outputObj.data = null;
}
});
this.aiprompt.outputObjects.clear();
}
this.aiprompt = null;
}
}
kendo.ui.AIPromptOutputObject = AIPromptOutputObject;
kendo.ui.AIPromptOutputManager = AIPromptOutputManager;
})(window.kendo.jQuery);
//#endregion
//#region ../src/aiprompt/views.js
(function($) {
let Widget = kendo.ui.Widget;
const messageTypes = {
"ai": "assistant",
"system": "system",
"user": "user",
"tool": "tool"
};
const KDISABLED = "k-disabled";
const CSS_CLASSES = {
PROMPT_VIEW: "k-prompt-view",
PROMPT_EXPANDER: "k-prompt-expander",
SUGGESTION_GROUP: "k-suggestion-group",
SUGGESTION: "k-suggestion",
CARD: "k-card",
CARD_LIST: "k-card-list"
};
const REFERENCE_ATTRIBUTES = {
PROMPT_SUGGESTIONS_BUTTON: "ref-prompt-suggestions-button",
PROMPT_INPUT: "ref-prompt-input",
GENERATE_OUTPUT_BUTTON: "ref-generate-output-button",
OUTPUT_CONTENT: "ref-output-content",
STOP_GENERATION_BUTTON: "ref-stop-generation-button"
};
let AIPromptBaseView = kendo.ui.AIPromptBaseView = Widget.extend({
init: function(element, options) {
let that = this;
Widget.fn.init.call(that, element, options);
that.aiprompt = element.getKendoAIPrompt();
that.contentElement = that.options.contentElement;
that.footerElement = that.options.footerElement;
that.buttonText = that.options.buttonText;
that.buttonIcon = that.options.buttonIcon;
that.service = that.options.service;
},
options: {
name: "AIPromptBaseView",
buttonText: "",
buttonIcon: ""
},
render: function() {
let that = this;
that._renderContent();
that._renderFooter();
},
_renderContentElement: function() {
let that = this;
let content = $("<div></div>").addClass("k-prompt-content");
that.contentElement = content;
that.element.append(content);
return that.contentElement;
},
_renderFooterElement: function() {
let that = this;
let footer = $("<div></div>").addClass("k-prompt-footer");
that.footerElement = footer;
that.element.append(footer);
return that.footerElement;
},
_ajaxRequest: function(prompt, isRetry, history) {
let that = this;
let service = that.service;
let data = that._getAjaxData(prompt, isRetry, history);
const url = typeof service === "string" ? service : service.url;
const requestOptions = {
url,
type: "POST",
contentType: "application/json",
data: JSON.stringify(data),
success: (response) => that._ajaxSuccessHandler(response, isRetry, prompt)
};
if (service?.headers) {
requestOptions.headers = service.headers;
}
kendo.ui.progress(that.contentElement, true);
return $.ajax(requestOptions);
},
_ajaxSuccessHandler: function(response, isRetry, prompt) {
const that = this;
const outputGetter = that.service?.outputGetter || that._getResponseMessageText;
const output = {
id: kendo.guid(),
output: outputGetter(response),
prompt,
isRetry,
activeView: 1
};
that.aiprompt.trigger("promptResponse", {
output: output.output,
prompt: output.prompt,
outputId: output.id,
isRetry: output.isRetry,
response: output.response
});
that.aiprompt.addPromptOutput(output);
that.aiprompt.activeView(output.activeView);
if (!isRetry) {
const generateButton = that.footerElement?.find(`button[${REFERENCE_ATTRIBUTES.GENERATE_OUTPUT_BUTTON}]`);
generateButton?.removeClass(KDISABLED);
}
kendo.ui.progress(that.contentElement, false);
},
_getResponseMessageText: function(response) {
return response?.Message?.Text || "An error occurred while processing the request.";
},
_getAjaxData: function(prompt, isRetry, history) {
const that = this;
const service = that.service;
let defaultData = [{
role: { value: messageTypes.user },
text: prompt
}];
if (history?.length) {
defaultData = history.concat(defaultData);
}
if (typeof service === "string") {
return defaultData;
}
if (kendo.isPresent(service.data) && Object.keys(service.data).length) {
service.data.messages = defaultData;
return service.data;
}
if (kendo.isFunction(service?.data)) {
return service.data(prompt, isRetry, history);
}
if ($.isPlainObject(service) && kendo.isPresent(service.url)) {
return defaultData;
}
throw new Error("Invalid AIPrompt service configuration.");
},
destroy: function() {
let that = this;
Widget.fn.destroy.call(that);
if (that.contentElement) {
that.contentElement.off();
kendo.destroy(that.contentElement);
that.contentElement.remove();
}
if (that.footerElement) {
that.footerElement.off();
kendo.destroy(that.footerElement);
that.footerElement.remove();
}
that.aiprompt.speechToTextButton = null;
}
});
kendo.ui.AIPromptPromptView = AIPromptBaseView.extend({
init: function(element, options) {
let that = this;
AIPromptBaseView.fn.init.call(that, element, options);
that.promptSuggestions = that.options.promptSuggestions;
that.promptSuggestionItemTemplate = that.options.promptSuggestionItemTemplate ? kendo.template(that.options.promptSuggestionItemTemplate) : kendo.ui.AIPromptTemplateBuilder.createSuggestionItem;
that.speechManager = new kendo.ui.AIPromptSpeechManager(that, that.options.speechToText);
},
options: {
name: "AIPromptPromptView",
buttonIcon: "sparkles"
},
_renderContent: function() {
let that = this;
let suggestions = that.promptSuggestions;
let promptSuggestionItemTemplate = that.promptSuggestionItemTemplate;
let content;
if (that.options.viewTemplate) {
content = kendo.template(that.options.viewTemplate)({
suggestions,
promptSuggestionItemTemplate,
messages: that.options.messages
});
} else {
content = kendo.ui.AIPromptTemplateBuilder.createPromptView({
suggestions,
promptSuggestionItemTemplate,
messages: that.options.messages
});
}
that._renderContentElement();
that.contentElement.append(content);
},
_renderFooter: function() {
let that = this;
let footer;
if (that.options.footerTemplate) {
footer = kendo.template(that.options.footerTemplate)({ messages: that.options.messages });
} else {
footer = kendo.ui.AIPromptTemplateBuilder.createPromptFooter({ messages: that.options.messages });
}
that._renderFooterElement();
that.footerElement.append(footer);
},
setTextAreaValue: function(value) {
let that = this;
const textareaWidget = that.contentElement.find(`textarea[${REFERENCE_ATTRIBUTES.PROMPT_INPUT}]`).getKendoTextArea();
if (textareaWidget) {
textareaWidget.value(value);
} else {
that.contentElement.find(`textarea[${REFERENCE_ATTRIBUTES.PROMPT_INPUT}]`).val(value);
}
},
_focusSuggestion(element) {
let that = this;
if (!element || !element.length) {
return;
}
that.contentElement.find(`.${CSS_CLASSES.SUGGESTION_GROUP} .${CSS_CLASSES.SUGGESTION}[tabindex=0]`).attr("tabindex", "-1");
element.attr("tabindex", "0").trigger("focus");
},
startSpeechRecognition: function() {
let that = this;
that.speechManager.startRecognition();
},
stopSpeechRecognition: function() {
let that = this;
that.speechManager.stopRecognition();
},
abortSpeechRecognition: function() {
let that = this;
that.speechManager.abortRecognition();
},
isSpeechListening: function() {
let that = this;
return that.speechManager.isListening();
},
initializeComponents: function() {
let that = this;
let suggestions = that.promptSuggestions;
const generateButton = that.footerElement.find(`button[${REFERENCE_ATTRIBUTES.GENERATE_OUTPUT_BUTTON}]`);
let textAreaOptions = $.extend({
resize: "vertical",
placeholder: that.options.messages.promptPlaceholder
}, that.options.promptTextArea || {});
if (that.speechManager.isEnabled()) {
textAreaOptions = $.extend(true, textAreaOptions, that.speechManager.getTextAreaSuffixOptions());
}
const textareaWidget = that.contentElement.find(`textarea[${REFERENCE_ATTRIBUTES.PROMPT_INPUT}]`).kendoTextArea(textAreaOptions).getKendoTextArea();
if (that.speechManager.isEnabled()) {
that.speechManager.initialize(textareaWidget);
}
generateButton.kendoButton({
icon: "sparkles",
themeColor: "primary",
rounded: "full",
click: function(e) {
const textareaWidget = that.contentElement.find(`textarea[${REFERENCE_ATTRIBUTES.PROMPT_INPUT}]`).getKendoTextArea();
const prompt = textareaWidget ? textareaWidget.value() : that.contentElement.find(`textarea[${REFERENCE_ATTRIBUTES.PROMPT_INPUT}]`).val();
const eventArgs = {
prompt,
isRetry: false,
history: []
};
if (that.service) {
eventArgs.service = that.service;
}
if (that.aiprompt.trigger("promptRequest", eventArgs)) {
return;
}
if (that.service) {
that.aiprompt.transport.read({
prompt: eventArgs.prompt,
history: eventArgs.history,
isRetry: false,
service: that.service
});
}
}
});
if (suggestions?.length) {
that.contentElement.find(`.${CSS_CLASSES.SUGGESTION_GROUP} .${CSS_CLASSES.SUGGESTION}`).first().attr("tabindex", "0");
let nextExpanderContentId = kendo.guid();
let expanderButton = that.contentElement.find(`.${CSS_CLASSES.PROMPT_EXPANDER} button[${REFERENCE_ATTRIBUTES.PROMPT_SUGGESTIONS_BUTTON}]`);
that.contentElement.find(`.${CSS_CLASSES.PROMPT_EXPANDER} button[${REFERENCE_ATTRIBUTES.PROMPT_SUGGESTIONS_BUTTON}]`).attr("aria-controls", nextExpanderContentId);
expanderButton.next(`.${CSS_CLASSES.PROMPT_EXPANDER_CONTENT}`).attr("id", nextExpanderContentId);
that.contentElement.find(`.${CSS_CLASSES.PROMPT_EXPANDER} button[${REFERENCE_ATTRIBUTES.PROMPT_SUGGESTIONS_BUTTON}]`).kendoButton({
icon: "chevron-up",
fillMode: "flat",
click: function(e) {
let expander = $(e.target).closest(".k-prompt-expander");
let content = expander.find(".k-prompt-expander-content");
let iconEl = e.sender.element.find(".k-icon");
kendo.ui.icon(iconEl, content.is(":visible") ? "chevron-down" : "chevron-up");
content.toggle();
e.sender.element.attr("aria-expanded", content.is(":visible"));
}
});
that.contentElement.on("click", ".k-suggestion-group .k-suggestion", function(e) {
that.setTextAreaValue($(e.target).text());
});
that.contentElement.on("keydown", ".k-suggestion-group .k-suggestion", function(e) {
if (e.keyCode === 40 || e.keyCode === 38 || e.keyCode === 36 || e.keyCode === 35 || e.keyCode === 13 || e.keyCode === 32) {
e.preventDefault();
let target = $(e.target);
let siblings = target.siblings();
let next, prev;
if (e.keyCode === 40) {
next = target.next();
that._focusSuggestion(next);
}
if (e.keyCode === 38) {
prev = target.prev();
that._focusSuggestion(prev);
}
if (e.keyCode === 36) {
prev = siblings.first();
that._focusSuggestion(prev);
}
if (e.keyCode === 35) {
next = siblings.last();
that._focusSuggestion(next);
}
if (e.keyCode === 13 || e.keyCode === 32) {
that.setTextAreaValue($(e.target).text());
}
}
});
}
if (kendo.isFunction(that.options.initializeComponents)) {
that.options.initializeComponents({ view: that });
}
},
render: function() {
let that = this;
that._renderContent();
that._renderFooter();
that.initializeComponents();
},
destroy: function() {
let that = this;
if (that.speechManager) {
that.speechManager.destroy();
that.speechManager = null;
}
AIPromptBaseView.fn.destroy.call(that);
}
});
kendo.ui.AIPromptOutputView = AIPromptBaseView.extend({
init: function(element, options) {
let that = this;
AIPromptBaseView.fn.init.call(that, element, options);
that.promptOutputs = that.aiprompt && that.aiprompt.promptOutputs ? that.aiprompt.promptOutputs : [];
that.showOutputRating = that.options.showOutputRating;
that.isStreaming = that.options.isStreaming || false;
that.outputActions = that.options.outputActions;
that.outputTemplate = that.options.outputTemplate;
that.outputActionManager = that.options.outputActionManager;
},
options: {
name: "AIPromptOutputView",
buttonIcon: "comment",
isStreaming: false,
promptOutputs: []
},
startStreaming: function() {
let that = this;
that.isStreaming = true;
that._showStopButton();
},
stopStreaming: function() {
let that = this;
that.isStreaming = false;
that._hideStopButton();
},
_showStopButton: function() {
let that = this;
if (!that.stopGenerationButton) {
if (that._initStopGenerationButton()) {
that.stopGenerationButton.show();
}
} else {
that.stopGenerationButton.show();
}
},
_hideStopButton: function() {
let that = this;
if (that.stopGenerationButton) {
that.stopGenerationButton.hide();
}
},
renderPromptOutput: function(output) {
let that = this;
let showOutputRating = that.options.showOutputRating;
let encodedPromptOutputs = that.options.encodedPromptOutputs;
let messages = that.options.messages;
let isStreaming = that.isStreaming || false;
let outputActions = that.outputActions;
if (!that.cardListContainer || that.cardListContainer.length === 0) {
if (that.outputsContainer) {
that.cardListContainer = that.outputsContainer.find(".k-card-list");
}
if (!that.cardListContainer || that.cardListContainer.length === 0) {
return;
}
}
if (isStreaming && !that.stopGenerationButton) {
that._initStopGenerationButton();
}
const outputObj = that.aiprompt.outputObjects.get(output.id);
if (outputObj) {
const cardHtml = kendo.ui.AIPromptTemplateBuilder.createOutputCard({
output,
showOutputRating,
messages,
showOutputSubtitleTooltip: true,
encodedPromptOutputs,
isStreaming,
outputActions,
outputTemplate: that.outputTemplate
});
const card = $(cardHtml);
outputObj._element = card;
outputObj._bodyElement = card.find(`.k-card-body`);
that.cardListContainer.prepend(card);
if (output.isLoading) {
outputObj.showSkeleton();
} else if (output.output) {
outputObj.applyFinalTemplate();
}
that.initializeComponents(card);
} else {
let card = $(kendo.ui.AIPromptTemplateBuilder.createOutputCard({
output,
showOutputRating,
messages,
showOutputSubtitleTooltip: true,
encodedPromptOutputs,
isStreaming,
outputActions,
outputTemplate: that.outputTemplate
}));
that.cardListContainer.prepend(card);
that.initializeComponents(card);
}
},
updatePromptOutputContent: function(outputId, content) {
let that = this;
const outputObj = that.aiprompt.outputObjects.get(outputId);
if (outputObj) {
outputObj.updateContent(content);
}
},
_initStopGenerationButton: function() {
let that = this;
let contentElement = that.contentElement;
if (!contentElement || contentElement.length === 0) {
return false;
}
if (that.stopGenerationButton) {
return true;
}
let stopFab = $("<button class='k-prompt-stop-fab k-generating'></button>");
stopFab.attr({
"aria-label": that.options.messages.stopGeneration,
"title": that.options.messages.stopGeneration
});
contentElement.prepend(stopFab);
that.stopGenerationButton = stopFab.kendoFloatingActionButton({
_classNames: ["k-prompt-stop-fab", "k-generating"],
icon: "stop-sm",
positionMode: "absolute",
align: "bottom end",
rounded: "full",
click: function(e) {
that.stopStreaming();
that.aiprompt.trigger("promptRequestCancel", {});
}
}).getKendoFloatingActionButton();
that.stopGenerationButton.hide();
return true;
},
_renderContent: function() {
let that = this;
let promptOutputs = that.promptOutputs;
let showOutputRating = that.options.showOutputRating;
let showOutputSubtitleTooltip = that.options.showOutputSubtitleTooltip;
let messages = that.options.messages;
let encodedPromptOutputs = that.options.encodedPromptOutputs;
let outputActions = that.outputActions;
let outputsContainer;
if (that.viewTemplate) {
outputsContainer = kendo.template(that.viewTemplate)({
promptOutputs,
showOutputRating,
messages,
showOutputSubtitleTooltip,
encodedPromptOutputs,
outputActions,
outputTemplate: that.outputTemplate
});
} else {
outputsContainer = kendo.ui.AIPromptTemplateBuilder.createOutputView({
promptOutputs,
showOutputRating,
messages,
showOutputSubtitleTooltip,
encodedPromptOutputs,
outputActions,
outputTemplate: that.outputTemplate
});
}
that.outputsContainer = $(outputsContainer);
that.cardListContainer = that.outputsContainer.find(".k-card-list");
that._renderContentElement();
that.contentElement.append(that.outputsContainer);
that._initStopGenerationButton();
},
initializeComponents: function(parentElement) {
let that = this;
parentElement = parentElement || that.contentElement;
that.outputActionManager.initializeButtons(parentElement, that.outputActions);
parentElement.find("[data-loading=\"true\"]").hide();
parentElement.find("[data-loading=\"false\"]").show();
if (that.aiprompt && that.aiprompt.outputObjects) {
that.aiprompt.outputObjects.forEach((outputObj, outputId) => {
const cardElement = parentElement.find(`.k-card[data-id="${outputId}"]`);
if (cardElement.length > 0) {
outputObj._element = cardElement;
outputObj._bodyElement = cardElement.find(".k-card-body");
if (that.outputTemplate && typeof that.outputTemplate === "function" && outputObj.data && outputObj.data.output && !outputObj.data.isLoading) {
const customContent = that.outputTemplate({
output: outputObj.data,
content: outputObj.data.output
});
outputObj._bodyElement.html(customContent);
}
const buttons = cardElement.find(".k-button");
const hasInitializedButtons = buttons.length > 0 && buttons.first().data("kendoButton");
if (!hasInitializedButtons && buttons.length > 0) {
that.outputActionManager.initializeButtons(cardElement, that.outputActions);
}
}
});
}
},
_initializeCardButtons: function(cardElement) {
let that = this;
that.outputActionManager.initializeButtons(cardElement, that.outputActions);
},
render: function() {
let that = this;
that._renderContent();
that.initializeComponents();
that.contentElement.on("keydown", ".k-card", function(e) {
let target = $(e.target);
if (e.keyCode === 40 || e.keyCode === 38 || e.keyCode === 36 || e.keyCode === 35) {
e.preventDefault();
if (e.keyCode === 40) {
target.next(".k-card").trigger("focus");
}
if (e.keyCode === 38) {
target.prev(".k-card").trigger("focus");
}
if (e.keyCode === 36) {
that.contentElement.find(".k-card").first().trigger("focus");
}
if (e.keyCode === 35) {
that.contentElement.find(".k-card").last().trigger("focus");
}
}
});
},
destroy: function() {
let that = this;
AIPromptBaseView.fn.destroy.call(that);
}
});
kendo.ui.AIPromptCommandsView = AIPromptBaseView.extend({
options: {
name: "AIPromptCommandsView",
buttonText: "",
buttonIcon: "more-horizontal",
promptCommands: []
},
initializeComponents: function() {
let that = this;
let commandItems = that.options.promptCommands;
let panelBarEl = $("<div></div>").kendoPanelBar({
animation: false,
dataSource: commandItems,
selectable: false,
select: function(ev) {
let item = $(ev.item);
let dataItem = this.dataItem(item);
if (dataItem.hasChildren) {
return;
}
that.aiprompt.trigger("commandExecute", {
sender: that.aiprompt,
item: dataItem
});
}
});
const promptViewWrapper = $("<div class='k-prompt-view'>");
promptViewWrapper.append(panelBarEl);
that.contentElement.append(promptViewWrapper);
},
render: function() {
let that = this;
that._renderContentElement();
that.initializeComponents();
}
});
let EMPTY_TEMPLATE = () => "";
kendo.ui.AIPromptCustomView = AIPromptBaseView.extend({
options: {
name: "AIPromptCustomView",
buttonText: "",
buttonIcon: "",
viewTemplate: EMPTY_TEMPLATE,
footerTemplate: EMPTY_TEMPLATE
},
initializeComponents: function() {
let that = this;
if (typeof that.options.initializeComponents === "function") {
that.options.initializeComponents.call(that);
}
},
_renderContent: function() {
let that = this;
let content = kendo.template(that.options.viewTemplate)({ aiprompt: that });
that._renderContentElement();
that.contentElement.append(content);
},
_renderFooter: function() {
let that = this;
if (that.options.footerTemplate === EMPTY_TEMPLATE) {
return;
}
let footer = kendo.template(that.options.footerTemplate)({ messages: that.options.messages });
that._renderFooterElement();
that.footerElement.append(footer);
},
render: function() {
let that = this;
that._renderContent();
that._renderFooter();
that.initializeComponents();
}
});
})(window.kendo.jQuery);
var views_default = kendo;
//#endregion
//#region ../src/kendo.aiprompt.js
const __meta__ = {
id: "aiprompt",
name: "AIPrompt",
category: "web",
description: "The AIPrompt component simplifies the incorporation of external AI services into apps.",
depends: [
"core",
"icons",
"textarea",
"button",
"toolbar",
"panelbar",
"data",
"floatingactionbutton",
"skeletoncontainer",
"speechtotextbutton"
]
};
(function($) {
let kendo = window.kendo, Widget = kendo.ui.Widget, NS = ".kendoAIPrompt", ui = kendo.ui, extend = $.extend, COMMAND_EXECUTE = "commandExecute", PROMPT_REQUEST = "promptRequest", PROMPT_RESPONSE = "promptResponse", OUTPUT_RATING = "outputRating", OUTPUT_COPY = "outputCopy", OUTPUT_VIEW = "output", OUTPUT_ACTION = "outputAction", PROMPT_REQUEST_CANCEL = "promptRequestCancel", DEFAULT_OUTPUT_ACTIONS = [
"copy",
"retry",
"spacer",
"rating"
], DEFAULT_OUTPUT_ACTIONS_WITHOUT_RATING = ["copy", "retry"], FOCUS = "focus", KDISABLED = "k-disabled";
let cssClasses = {
menuButton: "k-menu-button",
aIPrompt: "k-prompt"
};
let defaultViews = {
prompt: {
type: "kendo.ui.AIPromptPromptView",
name: "prompt",
buttonIcon: "sparkles"
},
output: {
type: "kendo.ui.AIPromptOutputView",
name: "output",
buttonIcon: "comment"
},
commands: {
type: "kendo.ui.AIPromptCommandsView",
name: "commands",
buttonIcon: "more-horizontal"
},
custom: {
type: "kendo.ui.AIPromptCustomView",
name: "custom"
}
};
let AIPrompt = Widget.extend({
init: function(element, options) {
let that = this;
options = options || {};
Widget.fn.init.call(that, element, options);
if (that.options.views.length == 0) {
that.options.views = ["prompt", "output"];
if (this.options.promptCommands && this.options.promptCommands.length) {
this.options.views.push("commands");
}
}
that.options.outputActions = options.outputActions && options.outputActions.length > 0 ? options.outputActions : DEFAULT_OUTPUT_ACTIONS_WITHOUT_RATING;
that.promptOutputs = that.options.promptOutputs || [];
that.outputObjects = new Map();
that.outputManager = new kendo.ui.AIPromptOutputManager(that);
that.outputActions = kendo.ui.AIPromptOutputActionManager.processOutputActions(that.options.outputActions);
that.outputActionManager = new kendo.ui.AIPromptOutputActionManager(that, { outputActions: that.outputActions });
if (Array.isArray(that.promptOutputs) && that.promptOutputs.length > 0) {
that.promptOutputs.forEach((output) => {
if (!output.id) {
output.id = kendo.guid();
}
const outputObj = that.outputManager.createOutputObject(output);
that.outputObjects.set(output.id, outputObj);
});
}
that._initLayout();
that._initViews();
that._initToolbar();
that.activeView(that.options.activeView);
if (that.options.service) {
that.transport = new kendo.data.AiTransport({
service: that.opti