UNPKG

@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
//#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