UNPKG

devexpress-reporting

Version:

DevExpress Reporting provides the capability to develop a reporting application to create and customize reports.

433 lines (432 loc) 20.3 kB
/** * DevExpress HTML/JS Reporting (viewer\ai\_aiPanelModel.js) * Version: 24.2.7 * Build date: Apr 29, 2025 * Copyright (c) 2012 - 2025 Developer Express Inc. ALL RIGHTS RESERVED * License: https://www.devexpress.com/Support/EULAs/universal.xml */ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; }; import themes from 'devextreme/ui/themes'; import { BaseRenderingModel, mutable } from '@devexpress/analytics-core/analytics-serializer-native'; import { $dx, DxDeferred, formatUnicorn } from '@devexpress/analytics-core/analytics-internal-native'; import { getLocalization, TabInfo } from '@devexpress/analytics-core/analytics-utils-native'; import { getParentContainer } from '@devexpress/analytics-core/analytics-internal-native'; import { AIServicesEnabled, AILanguages } from '../settings'; import { createViewModel, TextSourceTarget } from './_aiPanel.viewmodel'; export class AIPanelViewModel extends BaseRenderingModel { constructor(reportPreview) { super(); this.reportPreview = reportPreview; this.aiProcessingRequestDeferred = new DxDeferred(); this.ignore = false; this.languages = AILanguages(); this.TextSourceTargetText = { Document: getLocalization('Entire Document', 'ASPxReportsStringId.AIPanel_TargetTypeDocument'), Page: getLocalization('Page', 'ASPxReportsStringId.AIPanel_TargetType_Page'), Selection: getLocalization('Selection', 'ASPxReportsStringId.AIPanel_TargetTypeSelection') }; this.InvalidLanguageErrorMsg = getLocalization('Select a language', 'ASPxReportsStringId.AIPanel_InvalidLanguageValueErrorMsg'); this.SummarizeActionText = getLocalization('Summarize', 'ASPxReportsStringId.AIPanel_SummarizeAction'); this.TranslateActionText = getLocalization('Translate', 'ASPxReportsStringId.AIPanel_TranslateAction'); this.warningMsg = 'Text is too long to process within a single request. Text will be split into sections and processed in {0} steps. Proceed?'; this.targets = [ { key: TextSourceTarget.Document, text: this.TextSourceTargetText.Document }, { key: TextSourceTarget.Page, text: this.TextSourceTargetText.Page }, { key: TextSourceTarget.Selection, text: this.TextSourceTargetText.Selection } ]; const getDisabled = () => reportPreview.documentBuilding || !reportPreview.documentId || reportPreview.pageIndex === -1; const tabInfoVisible = () => !getDisabled() && AIServicesEnabled(); this.tabInfo = new TabInfo({ text: getLocalization('AI Operations', 'ASPxReportsStringId.AIPanel_PanelHeaderLabel'), template: 'dxrd-preview-ai-panel', model: this, localizationId: 'ASPxReportsStringId.AIPanel_PanelHeaderLabel', imageClassName: 'aitab', imageTemplateName: 'dxrd-svg-tabs-aitab', visible: tabInfoVisible() }); const summarizeMenuItem = { text: this.SummarizeActionText, key: 'dx_summarize_text' }; const that = this; this.smartTagAIMenu = { dataSource: [{ text: '', icon: 'dxrd-svg-menu-aimenu', items: [ summarizeMenuItem, { text: this.TranslateActionText, items: AILanguages() } ], }], hideSubmenuOnMouseLeave: false, onItemClick(e) { e.event?.stopPropagation(); if (!e.itemData || !e.itemData['key'] || that.loading) return; that.selectedTarget = TextSourceTarget.Selection; that.expandAITabPanel(); if (e.itemData['key'] === 'dx_summarize_text') { that.onSummarizeClick(); } else if (e.itemData['key']) { that.selectedLanguage = e.itemData['key']; that.onTranslateClick(); } }, onSubmenuShowing: (e) => { const popupContainer = getParentContainer(e['element']); e['submenu']._overlay.option('container', popupContainer); e['submenu']._overlay.option('focusStateEnabled', false); e['submenu']._overlay.option('position', { collision: 'none', boundary: popupContainer }); e['submenu']._attachHoverEvents(); }, visible: AIServicesEnabled() }; window.addEventListener('resize', that.resizeResultArea); this.addDisposable(() => window.removeEventListener('resize', that.resizeResultArea), reportPreview.events.on('documentIdChanged', (args) => { this.tabInfo.visible = tabInfoVisible(); this.dispose(); }), reportPreview.events.on('documentBuildingChanged', (args) => { this.tabInfo.visible = tabInfoVisible(); this.pagesListDataSource = []; if (!args.newValue && args.oldValue && this.reportPreview.pageIndex !== -1) { this.reportPreview.pages.forEach(page => { this.pagesListDataSource.push({ key: page.pageIndex, text: (page.pageIndex + 1).toString() }); }); } this.dispose(); }), reportPreview.events.on('reportIdChanged', (args) => { this.dispose(); }), reportPreview.events.on('currentPageChanged', (args) => { if (args.newValue) { const currentPage = args.newValue; currentPage.smartTagAIMenu = this.smartTagAIMenu; this.currentPageSelectionSubcriber && this.currentPageSelectionSubcriber(); this.currentPageSelectionSubcriber = currentPage.events.on('activeBricksChanged', (args) => { this.handleSelectedBrickChangedEvent(args.newValue); }); } })); this.addDisposable(AIServicesEnabled.subscribe(() => { this.tabInfo.visible = tabInfoVisible(); })); } expandAITabPanel() { this.reportPreview.tabPanel?.toggleTabVisibility({ model: this.tabInfo }, true); } handleSelectedBrickChangedEvent(selectedBricks) { if (selectedBricks.length === 0) return; let topBrick = selectedBricks[0]; selectedBricks.forEach(brick => { if (brick.top < topBrick.top) topBrick = brick; else if (brick.top == topBrick.top && brick.left < topBrick.left) topBrick = brick; }); const topP = topBrick.topP; this.reportPreview.currentPage.smartTagAIMenuPosition = { heightP: topBrick.heightP, topP: topP, widthP: topBrick.widthP, leftP: this.reportPreview.rtlViewer ? '0%' : '92.5%', rightP: this.reportPreview.rtlViewer ? '92.5%' : '0%' }; } updateViewModel(args) { const viewModel = this.getViewModel(); if (args.propertyName === 'aiProcessingResultText') { viewModel.aiProcessingResultText = this.aiProcessingResultText; if (this.aiProcessingResultText && this.aiProcessingResultText.length > 0) viewModel.resultVisible = true; else viewModel.resultVisible = false; this.resizeResultArea(); } else if (args.propertyName === 'warningTextLimitPopupVisible') { viewModel.warningTextLimitPopupVisible = this.warningTextLimitPopupVisible; } else if (args.propertyName === 'pageSelectBoxVisible' || args.propertyName === 'pagesListDataSource') { viewModel.pageSelectBoxOptions.dataSource = this.pagesListDataSource; viewModel.pageSelectBoxOptions.visible = this.pageSelectBoxVisible; viewModel.pageSelectBoxVisible = this.pageSelectBoxVisible; } else if (args.propertyName === 'proceedForAllChunks') { viewModel.proceedForAllCheckBoxOptions.value = this.proceedForAllChunks; } else if (args.propertyName === 'proceedNextButtonClick') { viewModel.proceedNextButtonClick = this.proceedNextButtonClick; } else if (args.propertyName === 'loading') { if (!this.loading) { viewModel.buttons.summarizeTextButton.text = this.SummarizeActionText; viewModel.buttons.translateTextButton.text = this.TranslateActionText; viewModel.buttons.translateTextButton.disabled = false; viewModel.buttons.summarizeTextButton.disabled = false; } else { viewModel.buttons.translateTextButton.disabled = true; viewModel.buttons.summarizeTextButton.disabled = true; } } else if (args.propertyName === 'selectedPage') { viewModel.pageSelectBoxOptions.value = this.selectedPage; } else if (args.propertyName === 'selectedTarget') { viewModel.targetSelectBoxOptions.value = this.selectedTarget; } else if (args.propertyName === 'selectedLanguage') { viewModel.languageSelectBoxOptions.value = this.selectedLanguage; } else if (args.propertyName === 'copyButtonTemplate') { viewModel.buttons.copyResultButton.template = this.copyButtonTemplate; } else if (args.propertyName === 'limitExceededWarningMsg') { viewModel.limitExceededWarningMsg = this.limitExceededWarningMsg; } } createViewModel() { return createViewModel.call(this, super.createViewModel()); } onPropertyChanged(args) { if (args.propertyName === 'aiProcessingResultText') { this.resizeResultArea(); } else if (args.propertyName === 'loading') { if (!this.loading) { this.summarizeButtonLoader?.option('visible', false); this.translateButtonLoader?.option('visible', false); } } } get translateWholeDocument() { return this.selectedTarget === TextSourceTarget.Document; } _preparetextProcessingButtonTemplate(data, container) { const buttonIndicatorElement = document.createElement('div'); const buttonContainer = document.createElement('div'); const buttonTextElement = document.createElement('span'); buttonIndicatorElement.style.height = '20px'; buttonIndicatorElement.style.width = '20px'; buttonContainer.className = 'dxrd-ai-button-container'; buttonTextElement.className = 'dxrd-ai-button-text'; buttonTextElement.textContent = data.text; buttonContainer.appendChild(buttonTextElement); $dx(container).append(buttonIndicatorElement); $dx(container).append(buttonContainer); return buttonIndicatorElement; } _getTextContentToProcess() { let page; const useSelectedContent = this.selectedTarget === TextSourceTarget.Selection; if (useSelectedContent) page = this.reportPreview.currentPage; else if (!this.translateWholeDocument) page = this.reportPreview.pages[this.selectedPage]; return this.translateWholeDocument ? '' : (page?.getBricksTextContent(useSelectedContent) ?? ''); } copyResultClick(e) { const animateCopy = () => { this.copyButtonTemplate = 'dxrd-svg-toolbox-checkbox'; setTimeout(() => { this.copyButtonTemplate = 'dxrd-svg-toolbar-copy'; }, 3000); }; if (navigator.clipboard && navigator.clipboard.writeText) { navigator.clipboard.writeText(this.aiProcessingResultText).then(() => { animateCopy(); }); } else { const tempTextArea = document.createElement('textarea'); tempTextArea.value = this.aiProcessingResultText; tempTextArea.style.position = 'absolute'; tempTextArea.style.left = '-9999px'; document.body.appendChild(tempTextArea); tempTextArea.select(); document.execCommand('copy'); document.body.removeChild(tempTextArea); animateCopy(); } } onSummarizeClick() { if (this.loading) return; this.clearProcessing(); this.loading = true; const viewModel = this.getViewModel(); if (this.summarizeButtonLoader) { viewModel.buttons.summarizeTextButton.text = ''; this.summarizeButtonLoader.option('visible', true); } const text = this._getTextContentToProcess(); this.summarizeText(text, this.selectedLanguage); } onTranslateClick() { if (this.loading) return; if (!this.selectedLanguage) { this.languageSelectBoxComponent?.option({ validationStatus: 'invalid', validationErrors: [{ message: this.InvalidLanguageErrorMsg }] }); return; } this.clearProcessing(); this.loading = true; const viewModel = this.getViewModel(); if (this.translateButtonLoader) { viewModel.buttons.translateTextButton.text = ''; this.translateButtonLoader.option('visible', true); } const text = this._getTextContentToProcess(); this.translateText(text); } translateText(text, prevResponse, currentChunkPosition) { const ignore = true; this.aiProcessingRequestDeferred = new DxDeferred(); this.reportPreview.requestWrapper.translateTextRequest(text, this.selectedLanguage ?? 'en', this.translateWholeDocument, prevResponse, currentChunkPosition, ignore) .then(result => this.aiProcessingRequestDeferred.resolve(result)) .catch(fail => this.failRequestHandler(fail, ignore)); this.aiProcessingRequestDeferred.done((result) => { this._handleTextProcessingResult(result, (prevResultText, nextChunkPosition) => this.translateText(text, prevResultText, nextChunkPosition)); }); } summarizeText(text, language, prevResponse, currentChunkPosition) { this.aiProcessingRequestDeferred = new DxDeferred(); this.reportPreview.requestWrapper.summarizeTextRequest(text, this.translateWholeDocument, prevResponse, currentChunkPosition, language, this.ignore) .then(result => this.aiProcessingRequestDeferred.resolve(result)) .catch(fail => this.failRequestHandler(fail, this.ignore)); this.aiProcessingRequestDeferred.done((result) => { result.language = language; this._handleTextProcessingResult(result, (prevResultText, nextChunkPosition) => this.summarizeText(text, language, prevResultText, nextChunkPosition), true); }); } _handleTextProcessingResult(result, nextChunkRequestFunc, isSummary = false) { if (result.success) { if (!isSummary) this.aiProcessingResultText += result.text; if (result.requestAgain) { let nextChunkPosition = result.chunkIndex; if (nextChunkPosition === -1 || (result.text && result.text.length)) nextChunkPosition += 1; if (this.proceedForAllChunks) { nextChunkRequestFunc(result.text, nextChunkPosition); } else { const remainingChunkRequests = parseInt(result.totalChunks) - nextChunkPosition; this.limitExceededWarningMsg = formatUnicorn(getLocalization(this.warningMsg, 'ASPxReportsStringId.AIPanel_LimitExceededWarning'), remainingChunkRequests); this.warningTextLimitPopupVisible = true; this.proceedNextButtonClick = (e) => { this.warningTextLimitPopupVisible = false; nextChunkRequestFunc(result.text, nextChunkPosition); }; } } else { if (isSummary) { if (result.language) { this.reportPreview.requestWrapper.translateTextRequest(result.text, result.language, false, '', 0, this.ignore) .then(translateResult => { if (translateResult.success) { this.aiProcessingResultText = translateResult.text; this.clearProcessing(false); } else this.failRequestHandler(result.ErrorMessage, false); }) .catch(fail => { this.aiProcessingResultText = result.text; this.failRequestHandler(fail, this.ignore); }); } else { this.aiProcessingResultText = result.text; this.clearProcessing(false); } } else { this.clearProcessing(false); } } } else { this.failRequestHandler(result.ErrorMessage, false); } } resizeResultArea() { const aiPanelElement = document.getElementById('dxrd-preview-ai-panel-id'); const resultTextElement = document.getElementById('dxrd-ai-panel-text-area-id'); if (aiPanelElement && resultTextElement) { const offset = (themes.current()?.startsWith('material') ?? false) ? 245 : 225; resultTextElement.style.height = (aiPanelElement.clientHeight - offset) + 'px'; } else { resultTextElement && (resultTextElement.style.height = '100%'); } } failRequestHandler(errorMessage, ignore) { this.clearProcessing(false); } clearProcessing(clearContent = true) { this.loading = false; this.prevRespose = ''; if (clearContent) this.aiProcessingResultText = ''; this.aiProcessingRequestDeferred?.reject(); this.warningTextLimitPopupVisible = false; this.proceedForAllChunks = false; this.warningTextLimitPopupVisible = false; } disabled() { return false; } dispose() { this.clearProcessing(); } } __decorate([ mutable('') ], AIPanelViewModel.prototype, "aiProcessingResultText", void 0); __decorate([ mutable('en') ], AIPanelViewModel.prototype, "selectedLanguage", void 0); __decorate([ mutable('document') ], AIPanelViewModel.prototype, "selectedTarget", void 0); __decorate([ mutable(0) ], AIPanelViewModel.prototype, "selectedPage", void 0); __decorate([ mutable(false) ], AIPanelViewModel.prototype, "proceedForAllChunks", void 0); __decorate([ mutable(false) ], AIPanelViewModel.prototype, "warningTextLimitPopupVisible", void 0); __decorate([ mutable(false) ], AIPanelViewModel.prototype, "pageSelectBoxVisible", void 0); __decorate([ mutable() ], AIPanelViewModel.prototype, "proceedNextButtonClick", void 0); __decorate([ mutable('dxrd-svg-toolbar-copy') ], AIPanelViewModel.prototype, "copyButtonTemplate", void 0); __decorate([ mutable('') ], AIPanelViewModel.prototype, "limitExceededWarningMsg", void 0); __decorate([ mutable(false) ], AIPanelViewModel.prototype, "loading", void 0); __decorate([ mutable([]) ], AIPanelViewModel.prototype, "pagesListDataSource", void 0);