@rtsee/ngx 0.0.108 → 0.0.110

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,5 @@
1
1
  import * as i0 from '@angular/core';
2
- import { Injectable, Input, Component, ViewChild, EventEmitter, Output, forwardRef, Directive, DOCUMENT, HostListener, Inject, ChangeDetectionStrategy, output, CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
2
+ import { Injectable, Input, Component, ViewChild, EventEmitter, Output, forwardRef, Directive, HostListener, reflectComponentType, DOCUMENT, Inject, ChangeDetectionStrategy, output, CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
3
3
  import * as i2$1 from '@angular/common';
4
4
  import { NgClass, NgIf, NgForOf, DatePipe, NgComponentOutlet, SlicePipe, JsonPipe, DOCUMENT as DOCUMENT$1, NgStyle, CommonModule, NgOptimizedImage } from '@angular/common';
5
5
  import * as i1 from '@angular/forms';
@@ -17,8 +17,8 @@ import * as i2 from '@angular/router';
17
17
  import { RouterLink, RouterLinkActive, NavigationEnd } from '@angular/router';
18
18
  import { Divider } from 'primeng/divider';
19
19
  import * as i1$1 from '@angular/common/http';
20
- import { RTSeeChatMemberRoles, RTSeeIllustratorEvents, RTSeeChatTypes, OzzyTaskStatus } from '@rtsee/common';
21
- import { sanitizeImageUrl, ManageAgentForm, RTSeeForm, RTSeeFormFieldRadioGroup, RTSeeFormField, RTSeeFormFieldImageUploader, RTSeeRater } from '@rtsee/utilities-ui';
20
+ import { RTSeeChatMemberRoles, RTSeeIllustratorEvents, RTSeeChatTypes, OzzyTaskStatus, getStoryTranslatorLanguage, STORY_TRANSLATOR_LANGUAGES } from '@rtsee/common';
21
+ import { sanitizeImageUrl, ManageAgentForm, RTSeeForm, RTSeeFormFieldRadioGroup, RTSeeFormField, RTSeeFormFieldImageUploader, fetchImageBlob, RTSeeRater } from '@rtsee/utilities-ui';
22
22
  import { InputText } from 'primeng/inputtext';
23
23
  import { InputGroup } from 'primeng/inputgroup';
24
24
  import { Textarea } from 'primeng/textarea';
@@ -28,6 +28,7 @@ import { InputNumber } from 'primeng/inputnumber';
28
28
  import * as i1$2 from 'primeng/autocomplete';
29
29
  import { AutoCompleteModule } from 'primeng/autocomplete';
30
30
  import { ImageCropperComponent } from 'ngx-image-cropper';
31
+ import { Tooltip } from 'primeng/tooltip';
31
32
  import { Fieldset } from 'primeng/fieldset';
32
33
  import { DatePicker } from 'primeng/datepicker';
33
34
  import { RTSeeManageChatInfo, RTSeeChatEvents, OzzyClientTaskEvents, OzzyWorkflowClientEvents, RTSeeMentionAutocomplete, RTSeeWorkflowAutocomplete } from '@rtsee/messenger';
@@ -54,7 +55,6 @@ import { Listbox } from 'primeng/listbox';
54
55
  import { RTSeeRoutesList } from '@rtsee/factory';
55
56
  import { Avatar } from 'primeng/avatar';
56
57
  import { Image } from 'primeng/image';
57
- import { Tooltip } from 'primeng/tooltip';
58
58
  import { PanelMenu } from 'primeng/panelmenu';
59
59
  import { Menu } from 'primeng/menu';
60
60
  import { Password } from 'primeng/password';
@@ -675,15 +675,40 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.4", ngImpor
675
675
  }] } });
676
676
 
677
677
  class RTSeeImageUploaderComponent {
678
- constructor(elementRef) {
678
+ constructor(elementRef, cdr) {
679
679
  this.elementRef = elementRef;
680
+ this.cdr = cdr;
681
+ this.onUploaderChanged = () => this.cdr.markForCheck();
680
682
  this.submittingFromPainting = false;
681
683
  }
682
684
  ngOnInit() {
683
685
  this.attachEventHandlers();
684
686
  }
687
+ ngOnDestroy() {
688
+ this.imageUploader?.off('changed', this.onUploaderChanged);
689
+ }
685
690
  attachEventHandlers() {
686
691
  this.imageUploader.on('completed', this.onImagesUploaded.bind(this));
692
+ this.imageUploader.on('changed', this.onUploaderChanged);
693
+ }
694
+ get optimizeTooltip() {
695
+ if (this.imageUploader.optimizing) {
696
+ return 'Optimizing…';
697
+ }
698
+ const base = `Optimize (curr size: ${this.imageUploader.currentImageSizeMb}mb)`;
699
+ return this.imageUploader.optimizationError
700
+ ? `${base} - last attempt failed: ${this.imageUploader.optimizationError}`
701
+ : base;
702
+ }
703
+ async onOptimizeClicked(event) {
704
+ // The preview container opens the uploader on click
705
+ event.stopPropagation();
706
+ event.preventDefault();
707
+ const optimizedUrl = await this.imageUploader.optimizeCurrentImage();
708
+ if (optimizedUrl) {
709
+ // The optimized url is already propagated to the form field (`completed`) - persist it
710
+ setTimeout(() => this.submitParentForm(), 50);
711
+ }
687
712
  }
688
713
  onNextButtonClicked() {
689
714
  this.imageUploader.onNextClicked();
@@ -712,6 +737,10 @@ class RTSeeImageUploaderComponent {
712
737
  // 1. If RTSeeIllustrator is in reviewingGeneratedImage mode, apply currently reviewed image to ImageUploader
713
738
  this.submittingFromPainting = true;
714
739
  this.imageUploader.applyImageUrl(sanitizeImageUrl(response.imageUrl));
740
+ // Reset illustrator state and UI
741
+ if (typeof window !== 'undefined') {
742
+ window.dispatchEvent(new CustomEvent(RTSeeIllustratorEvents.RESET_STATE));
743
+ }
715
744
  // and if Imageuploader is part of the form it should immediately submit the form
716
745
  setTimeout(() => {
717
746
  this.submitParentForm();
@@ -760,8 +789,11 @@ class RTSeeImageUploaderComponent {
760
789
  return;
761
790
  }
762
791
  }
763
- // 2. Programmatic RTSeeForm submit
764
- if (this.imageUploader.form && typeof this.imageUploader.form.submit === 'function') {
792
+ // 2. Custom submit hook, then programmatic RTSeeForm submit
793
+ if (this.imageUploader.onSubmitForm) {
794
+ this.imageUploader.onSubmitForm();
795
+ }
796
+ else if (this.imageUploader.form && typeof this.imageUploader.form.submit === 'function') {
765
797
  this.imageUploader.form.submit();
766
798
  }
767
799
  }
@@ -769,13 +801,13 @@ class RTSeeImageUploaderComponent {
769
801
  console.warn('[RTSeeImageUploaderComponent] submitParentForm error:', e);
770
802
  }
771
803
  }
772
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.4", ngImport: i0, type: RTSeeImageUploaderComponent, deps: [{ token: i0.ElementRef }], target: i0.ɵɵFactoryTarget.Component }); }
773
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.4", type: RTSeeImageUploaderComponent, isStandalone: true, selector: "rtsee-image-uploader", inputs: { imageUploader: "imageUploader" }, viewQueries: [{ propertyName: "cropperComponent", first: true, predicate: RTSeeCropperComponent, descendants: true }], ngImport: i0, template: "<div class=\"rtsee-image-uploader\">\n @if (!imageUploader.awaitsUploadOrCrop) {\n <div class=\"rtsee-image-uploader-image\"\n (click)=\"imageUploader.init()\"\n >\n @if (imageUploader.uploadedPhotoPreview) {\n <img [src]=\"imageUploader.uploadedPhotoPreview\"\n class=\"rtsee-image-uploader-preview-image\"\n alt=\"uploaded_photo\"\n >\n }\n @if (!imageUploader.uploadedPhotoPreview) {\n <div class=\"rtsee-preloader-image-placeholder\">\n <span class=\"material-icons-outlined rtsee-upload-icon\">cloud_upload</span>\n <span class=\"rtsee-upload-text\">Tap to upload image</span>\n </div>\n }\n <div class=\"rtsee-image-uploader-illustrator-btn\"\n (click)=\"onPaintingIconClicked($event)\"\n title=\"Apply illustration from NanoBanana Illustrator\"\n >\n <p-button icon=\"pi pi-palette\" severity=\"help\" [rounded]=\"true\"></p-button>\n </div>\n <div class=\"rtsee-image-uploader-image-change-btn\">\n <p-button icon=\"pi pi-pencil\" severity=\"info\"></p-button>\n </div>\n </div>\n }\n\n @if (imageUploader.awaitsUploadOrCrop) {\n <rtsee-uploader [uploader]=\"imageUploader.uploader\"\n [hidden]=\"imageUploader.cropper?.activeImage\"\n ></rtsee-uploader>\n\n @if (imageUploader.cropper?.activeImage) {\n <rtsee-cropper [cropper]=\"imageUploader.cropper\"\n (nextClicked)=\"onNextButtonClicked()\"\n ></rtsee-cropper>\n }\n }\n</div>\n", dependencies: [{ kind: "component", type: RTSeeUploaderComponent, selector: "rtsee-uploader", inputs: ["uploader"] }, { kind: "component", type: RTSeeCropperComponent, selector: "rtsee-cropper", inputs: ["cropper"], outputs: ["nextClicked"] }, { kind: "component", type: Button, selector: "p-button", inputs: ["type", "iconPos", "icon", "badge", "label", "disabled", "loading", "loadingIcon", "raised", "rounded", "text", "plain", "severity", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "buttonProps", "autofocus", "fluid"], outputs: ["onClick", "onFocus", "onBlur"] }] }); }
804
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.4", ngImport: i0, type: RTSeeImageUploaderComponent, deps: [{ token: i0.ElementRef }, { token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component }); }
805
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.4", type: RTSeeImageUploaderComponent, isStandalone: true, selector: "rtsee-image-uploader", inputs: { imageUploader: "imageUploader" }, viewQueries: [{ propertyName: "cropperComponent", first: true, predicate: RTSeeCropperComponent, descendants: true }], ngImport: i0, template: "<div class=\"rtsee-image-uploader\">\n @if (!imageUploader.awaitsUploadOrCrop) {\n <div class=\"rtsee-image-uploader-image\"\n (click)=\"imageUploader.init()\"\n >\n @if (imageUploader.uploadedPhotoPreview) {\n <img [src]=\"imageUploader.uploadedPhotoPreview\"\n class=\"rtsee-image-uploader-preview-image\"\n alt=\"uploaded_photo\"\n >\n }\n @if (!imageUploader.uploadedPhotoPreview) {\n <div class=\"rtsee-preloader-image-placeholder\">\n <span class=\"material-icons-outlined rtsee-upload-icon\">cloud_upload</span>\n <span class=\"rtsee-upload-text\">Tap to upload image</span>\n </div>\n }\n <div class=\"rtsee-image-uploader-illustrator-btn\"\n (click)=\"onPaintingIconClicked($event)\"\n title=\"Apply illustration from NanoBanana Illustrator\"\n >\n <p-button icon=\"pi pi-palette\" severity=\"help\" [rounded]=\"true\"></p-button>\n </div>\n <div class=\"rtsee-image-uploader-image-change-btn\"\n [class.rtsee-shifted]=\"imageUploader.needsOptimization\"\n >\n <p-button icon=\"pi pi-pencil\" severity=\"info\"></p-button>\n </div>\n @if (imageUploader.needsOptimization) {\n <div class=\"rtsee-image-uploader-optimize-btn\"\n id=\"rtsee-image-uploader-optimize-btn\"\n [class.rtsee-optimizing]=\"imageUploader.optimizing\"\n [pTooltip]=\"optimizeTooltip\"\n tooltipPosition=\"top\"\n (click)=\"onOptimizeClicked($event)\"\n >\n <p-button [icon]=\"imageUploader.optimizing ? 'pi pi-spin pi-spinner' : 'pi pi-exclamation-triangle'\"\n severity=\"warn\"\n [rounded]=\"true\"\n [disabled]=\"imageUploader.optimizing\"\n [attr.aria-label]=\"optimizeTooltip\"\n ></p-button>\n </div>\n }\n </div>\n }\n\n @if (imageUploader.awaitsUploadOrCrop) {\n <rtsee-uploader [uploader]=\"imageUploader.uploader\"\n [hidden]=\"imageUploader.cropper?.activeImage\"\n ></rtsee-uploader>\n\n @if (imageUploader.cropper?.activeImage) {\n <rtsee-cropper [cropper]=\"imageUploader.cropper\"\n (nextClicked)=\"onNextButtonClicked()\"\n ></rtsee-cropper>\n }\n }\n</div>\n", dependencies: [{ kind: "component", type: RTSeeUploaderComponent, selector: "rtsee-uploader", inputs: ["uploader"] }, { kind: "component", type: RTSeeCropperComponent, selector: "rtsee-cropper", inputs: ["cropper"], outputs: ["nextClicked"] }, { kind: "component", type: Button, selector: "p-button", inputs: ["type", "iconPos", "icon", "badge", "label", "disabled", "loading", "loadingIcon", "raised", "rounded", "text", "plain", "severity", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "buttonProps", "autofocus", "fluid"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "directive", type: Tooltip, selector: "[pTooltip]", inputs: ["tooltipPosition", "tooltipEvent", "positionStyle", "tooltipStyleClass", "tooltipZIndex", "escape", "showDelay", "hideDelay", "life", "positionTop", "positionLeft", "autoHide", "fitContent", "hideOnEscape", "pTooltip", "tooltipDisabled", "tooltipOptions", "appendTo"] }] }); }
774
806
  }
775
807
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.4", ngImport: i0, type: RTSeeImageUploaderComponent, decorators: [{
776
808
  type: Component,
777
- args: [{ selector: 'rtsee-image-uploader', imports: [RTSeeUploaderComponent, RTSeeCropperComponent, Button], template: "<div class=\"rtsee-image-uploader\">\n @if (!imageUploader.awaitsUploadOrCrop) {\n <div class=\"rtsee-image-uploader-image\"\n (click)=\"imageUploader.init()\"\n >\n @if (imageUploader.uploadedPhotoPreview) {\n <img [src]=\"imageUploader.uploadedPhotoPreview\"\n class=\"rtsee-image-uploader-preview-image\"\n alt=\"uploaded_photo\"\n >\n }\n @if (!imageUploader.uploadedPhotoPreview) {\n <div class=\"rtsee-preloader-image-placeholder\">\n <span class=\"material-icons-outlined rtsee-upload-icon\">cloud_upload</span>\n <span class=\"rtsee-upload-text\">Tap to upload image</span>\n </div>\n }\n <div class=\"rtsee-image-uploader-illustrator-btn\"\n (click)=\"onPaintingIconClicked($event)\"\n title=\"Apply illustration from NanoBanana Illustrator\"\n >\n <p-button icon=\"pi pi-palette\" severity=\"help\" [rounded]=\"true\"></p-button>\n </div>\n <div class=\"rtsee-image-uploader-image-change-btn\">\n <p-button icon=\"pi pi-pencil\" severity=\"info\"></p-button>\n </div>\n </div>\n }\n\n @if (imageUploader.awaitsUploadOrCrop) {\n <rtsee-uploader [uploader]=\"imageUploader.uploader\"\n [hidden]=\"imageUploader.cropper?.activeImage\"\n ></rtsee-uploader>\n\n @if (imageUploader.cropper?.activeImage) {\n <rtsee-cropper [cropper]=\"imageUploader.cropper\"\n (nextClicked)=\"onNextButtonClicked()\"\n ></rtsee-cropper>\n }\n }\n</div>\n" }]
778
- }], ctorParameters: () => [{ type: i0.ElementRef }], propDecorators: { cropperComponent: [{
809
+ args: [{ selector: 'rtsee-image-uploader', imports: [RTSeeUploaderComponent, RTSeeCropperComponent, Button, Tooltip], template: "<div class=\"rtsee-image-uploader\">\n @if (!imageUploader.awaitsUploadOrCrop) {\n <div class=\"rtsee-image-uploader-image\"\n (click)=\"imageUploader.init()\"\n >\n @if (imageUploader.uploadedPhotoPreview) {\n <img [src]=\"imageUploader.uploadedPhotoPreview\"\n class=\"rtsee-image-uploader-preview-image\"\n alt=\"uploaded_photo\"\n >\n }\n @if (!imageUploader.uploadedPhotoPreview) {\n <div class=\"rtsee-preloader-image-placeholder\">\n <span class=\"material-icons-outlined rtsee-upload-icon\">cloud_upload</span>\n <span class=\"rtsee-upload-text\">Tap to upload image</span>\n </div>\n }\n <div class=\"rtsee-image-uploader-illustrator-btn\"\n (click)=\"onPaintingIconClicked($event)\"\n title=\"Apply illustration from NanoBanana Illustrator\"\n >\n <p-button icon=\"pi pi-palette\" severity=\"help\" [rounded]=\"true\"></p-button>\n </div>\n <div class=\"rtsee-image-uploader-image-change-btn\"\n [class.rtsee-shifted]=\"imageUploader.needsOptimization\"\n >\n <p-button icon=\"pi pi-pencil\" severity=\"info\"></p-button>\n </div>\n @if (imageUploader.needsOptimization) {\n <div class=\"rtsee-image-uploader-optimize-btn\"\n id=\"rtsee-image-uploader-optimize-btn\"\n [class.rtsee-optimizing]=\"imageUploader.optimizing\"\n [pTooltip]=\"optimizeTooltip\"\n tooltipPosition=\"top\"\n (click)=\"onOptimizeClicked($event)\"\n >\n <p-button [icon]=\"imageUploader.optimizing ? 'pi pi-spin pi-spinner' : 'pi pi-exclamation-triangle'\"\n severity=\"warn\"\n [rounded]=\"true\"\n [disabled]=\"imageUploader.optimizing\"\n [attr.aria-label]=\"optimizeTooltip\"\n ></p-button>\n </div>\n }\n </div>\n }\n\n @if (imageUploader.awaitsUploadOrCrop) {\n <rtsee-uploader [uploader]=\"imageUploader.uploader\"\n [hidden]=\"imageUploader.cropper?.activeImage\"\n ></rtsee-uploader>\n\n @if (imageUploader.cropper?.activeImage) {\n <rtsee-cropper [cropper]=\"imageUploader.cropper\"\n (nextClicked)=\"onNextButtonClicked()\"\n ></rtsee-cropper>\n }\n }\n</div>\n" }]
810
+ }], ctorParameters: () => [{ type: i0.ElementRef }, { type: i0.ChangeDetectorRef }], propDecorators: { cropperComponent: [{
779
811
  type: ViewChild,
780
812
  args: [RTSeeCropperComponent]
781
813
  }], imageUploader: [{
@@ -1857,10 +1889,54 @@ class MediaWidgetComponent {
1857
1889
  this.visibleImages = [];
1858
1890
  this.hiddenCount = 0;
1859
1891
  this.showAll = false;
1892
+ this.downloadingUrl = null;
1860
1893
  this.MAX_GRID = 4;
1861
1894
  }
1862
1895
  ngOnInit() {
1863
1896
  this.updateVisibleImages();
1897
+ this.notifyRequestedGeneration();
1898
+ }
1899
+ ngOnChanges(changes) {
1900
+ // e.g. downsized versions got attached to the message images
1901
+ if (changes['message'] && !changes['message'].firstChange) {
1902
+ this.updateVisibleImages();
1903
+ }
1904
+ }
1905
+ /** Downsized version for rendering, the original otherwise */
1906
+ previewUrl(img) {
1907
+ return img.smallVersionUrl || img.url;
1908
+ }
1909
+ async downloadOriginal(event, img) {
1910
+ event.stopPropagation();
1911
+ event.preventDefault();
1912
+ if (typeof window === 'undefined' || !img?.url || this.downloadingUrl) {
1913
+ return;
1914
+ }
1915
+ this.downloadingUrl = img.url;
1916
+ try {
1917
+ const blob = await fetchImageBlob(img.url);
1918
+ const objectUrl = URL.createObjectURL(blob);
1919
+ const link = document.createElement('a');
1920
+ link.href = objectUrl;
1921
+ link.download = this.getDownloadFileName(img, blob);
1922
+ document.body.appendChild(link);
1923
+ link.click();
1924
+ link.remove();
1925
+ setTimeout(() => URL.revokeObjectURL(objectUrl), 1000);
1926
+ }
1927
+ catch (e) {
1928
+ // CORS / network failure - let the browser open the original instead
1929
+ console.warn('[MediaWidget] Blob download failed, opening original image:', e);
1930
+ window.open(img.url, '_blank', 'noopener');
1931
+ }
1932
+ finally {
1933
+ this.downloadingUrl = null;
1934
+ }
1935
+ }
1936
+ getDownloadFileName(img, blob) {
1937
+ const extension = blob.type?.split('/')[1]?.split('+')[0] || 'png';
1938
+ const base = (img.name || img.id || 'image').replace(/[^\w.-]+/g, '-');
1939
+ return base.includes('.') ? base : `${base}.${extension}`;
1864
1940
  }
1865
1941
  get hasVideo() {
1866
1942
  return !!this.message.video;
@@ -1884,16 +1960,33 @@ class MediaWidgetComponent {
1884
1960
  event.stopPropagation();
1885
1961
  event.preventDefault();
1886
1962
  if (typeof window !== 'undefined' && img?.url) {
1887
- const cleanUrl = sanitizeImageUrl(img.url);
1963
+ // Retain AWS presigned URL so Illustrator can render the image preview!
1888
1964
  window.dispatchEvent(new CustomEvent(RTSeeIllustratorEvents.SET_IMAGE_FOR_REVIEW, {
1889
1965
  detail: {
1890
- imageUrl: cleanUrl,
1966
+ imageUrl: img.url,
1891
1967
  prompt: img.name || (typeof this.message.text === 'string' ? this.message.text : '') || '',
1892
1968
  source: 'messenger',
1893
1969
  },
1894
1970
  }));
1895
1971
  }
1896
1972
  }
1973
+ addToContext(event, img) {
1974
+ event.stopPropagation();
1975
+ event.preventDefault();
1976
+ if (typeof window !== 'undefined' && img?.url) {
1977
+ window.dispatchEvent(new CustomEvent(RTSeeIllustratorEvents.ADD_IMAGE_TO_CONTEXT, {
1978
+ detail: {
1979
+ id: 'chat-img-' + Math.random().toString(36).substring(2, 9),
1980
+ imageUrl: img.url,
1981
+ title: img.name || 'Chat image',
1982
+ description: (typeof this.message.text === 'string' ? this.message.text : '') || '',
1983
+ visualDescription: '',
1984
+ sourceType: 'custom',
1985
+ active: true,
1986
+ },
1987
+ }));
1988
+ }
1989
+ }
1897
1990
  updateVisibleImages() {
1898
1991
  const all = this.message.images || [];
1899
1992
  if (this.showAll || all.length <= this.MAX_GRID) {
@@ -1905,12 +1998,25 @@ class MediaWidgetComponent {
1905
1998
  this.hiddenCount = all.length - this.MAX_GRID;
1906
1999
  }
1907
2000
  }
2001
+ notifyRequestedGeneration() {
2002
+ const reqId = this.message?.data?.requestId;
2003
+ if (reqId && this.visibleImages.length > 0 && typeof window !== 'undefined') {
2004
+ window.dispatchEvent(new CustomEvent(RTSeeIllustratorEvents.REQUESTED_GENERATION_SUCCEED, {
2005
+ detail: {
2006
+ requestId: reqId,
2007
+ imageUrl: this.visibleImages[0].url,
2008
+ messageId: this.message.id,
2009
+ chatId: this.chat?.id,
2010
+ },
2011
+ }));
2012
+ }
2013
+ }
1908
2014
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.4", ngImport: i0, type: MediaWidgetComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
1909
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.4", type: MediaWidgetComponent, isStandalone: true, selector: "rtsee-media-widget", inputs: { message: "message", messenger: "messenger", chat: "chat", showCost: "showCost" }, ngImport: i0, template: "<div class=\"rts-media-widget\">\n <!-- Video player -->\n @if (message.video) {\n <div class=\"rts-media-video\">\n <video\n controls\n playsinline\n preload=\"metadata\"\n [src]=\"message.video.url\"\n class=\"rts-video-player\"\n >\n Your browser does not support video playback.\n </video>\n </div>\n }\n\n <!-- Image grid -->\n @if (hasImages) {\n <div class=\"rts-media-images\" [ngClass]=\"gridClass\">\n @for (img of visibleImages; track img.url) {\n <div class=\"rts-media-image-wrapper\">\n <img [src]=\"img.url\" [alt]=\"img.name\" class=\"rts-media-image\" loading=\"lazy\" />\n <button\n type=\"button\"\n class=\"rts-media-btn-paint\"\n title=\"Send image to Illustrator\"\n (click)=\"sendToIllustrator($event, img)\"\n >\n <i class=\"pi pi-palette\"></i>\n <span>+ paint</span>\n </button>\n </div>\n }\n </div>\n }\n\n <!-- Show more / less toggle -->\n @if (hiddenCount > 0 || showAll) {\n <button class=\"rts-media-toggle\" (click)=\"toggleShowAll()\">\n {{ showAll ? 'Show less' : 'Show ' + hiddenCount + ' more' }}\n </button>\n }\n\n <!-- Cost display (optional, disabled when nested inside agentic widget) -->\n @if (showCost && message.cost) {\n <div class=\"rts-media-cost\">\n <rtsee-cost-display [cost]=\"message.cost\"></rtsee-cost-display>\n </div>\n }\n</div>\n", styles: [".rts-media-widget{max-width:100%}.rts-media-video{border-radius:8px;overflow:hidden;margin-bottom:6px}.rts-video-player{width:100%;max-height:360px;border-radius:8px;background:#000;display:block}.rts-media-images{display:grid;gap:4px;border-radius:8px;overflow:hidden;margin-bottom:4px}.rts-media-grid-1{grid-template-columns:1fr}.rts-media-grid-2{grid-template-columns:1fr 1fr}.rts-media-grid-4{grid-template-columns:1fr 1fr;grid-template-rows:auto auto}.rts-media-image-wrapper{position:relative;overflow:hidden;border-radius:6px;background:var(--rts-bg-secondary, #1e1e2e)}.rts-media-btn-paint{position:absolute;top:8px;right:8px;z-index:5;display:inline-flex;align-items:center;gap:4px;padding:4px 8px;background:#12121cd9;-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px);color:#e9d5ff;border:1px solid rgba(168,85,247,.45);border-radius:6px;font-size:11px;font-weight:600;cursor:pointer;transition:all .2s cubic-bezier(.34,1.56,.64,1);box-shadow:0 2px 8px #00000080}.rts-media-btn-paint i{font-size:11px;color:#c084fc}.rts-media-btn-paint span{line-height:1}.rts-media-btn-paint:hover{background:#9333eae6;color:#fff;border-color:#a855f7;transform:translateY(-1px) scale(1.04);box-shadow:0 4px 12px #a855f773}.rts-media-btn-paint:hover i{color:#fff}.rts-media-btn-paint:active{transform:translateY(0) scale(.98)}.rts-media-image{width:100%;height:auto;max-height:280px;object-fit:cover;display:block;transition:transform .2s ease;cursor:pointer}.rts-media-image:hover{transform:scale(1.02)}.rts-media-grid-1 .rts-media-image{max-height:400px;object-fit:contain}.rts-media-toggle{display:block;width:100%;padding:6px 0;border:none;background:none;color:var(--rts-agent-accent, #8b5cf6);font-size:12px;font-weight:500;cursor:pointer;text-align:center;transition:opacity .15s ease}.rts-media-toggle:hover{opacity:.8}.rts-media-cost{display:flex;justify-content:flex-end;padding-top:4px}\n"], dependencies: [{ kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "component", type: CostDisplayComponent, selector: "rtsee-cost-display", inputs: ["cost"] }] }); }
2015
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.4", type: MediaWidgetComponent, isStandalone: true, selector: "rtsee-media-widget", inputs: { message: "message", messenger: "messenger", chat: "chat", showCost: "showCost" }, usesOnChanges: true, ngImport: i0, template: "<div class=\"rts-media-widget\">\n <!-- Video player -->\n @if (message.video) {\n <div class=\"rts-media-video\">\n <video\n controls\n playsinline\n preload=\"metadata\"\n [src]=\"message.video.url\"\n class=\"rts-video-player\"\n >\n Your browser does not support video playback.\n </video>\n </div>\n }\n\n <!-- Image grid -->\n @if (hasImages) {\n <div class=\"rts-media-images\" [ngClass]=\"gridClass\">\n @for (img of visibleImages; track img.id || img.url) {\n <div class=\"rts-media-image-wrapper\">\n <img [src]=\"previewUrl(img)\" [alt]=\"img.name\" class=\"rts-media-image\" loading=\"lazy\" />\n <button\n type=\"button\"\n class=\"rts-media-btn-download\"\n [class.rts-downloading]=\"downloadingUrl === img.url\"\n title=\"Download original image\"\n aria-label=\"Download original image\"\n (click)=\"downloadOriginal($event, img)\"\n >\n <i class=\"pi\" [ngClass]=\"downloadingUrl === img.url ? 'pi-spin pi-spinner' : 'pi-download'\"></i>\n </button>\n <div class=\"rts-media-image-actions\">\n <button\n type=\"button\"\n class=\"rts-media-btn-paint\"\n title=\"Send image to Illustrator (Review & Iterate)\"\n (click)=\"sendToIllustrator($event, img)\"\n >\n <i class=\"pi pi-palette\"></i>\n <span>+ paint</span>\n </button>\n <button\n type=\"button\"\n class=\"rts-media-btn-context\"\n title=\"Add image to Illustrator context references\"\n (click)=\"addToContext($event, img)\"\n >\n <i class=\"pi pi-plus\"></i>\n <span>+ context</span>\n </button>\n </div>\n </div>\n }\n </div>\n }\n\n <!-- Show more / less toggle -->\n @if (hiddenCount > 0 || showAll) {\n <button class=\"rts-media-toggle\" (click)=\"toggleShowAll()\">\n {{ showAll ? 'Show less' : 'Show ' + hiddenCount + ' more' }}\n </button>\n }\n\n <!-- Cost display (optional, disabled when nested inside agentic widget) -->\n @if (showCost && message.cost) {\n <div class=\"rts-media-cost\">\n <rtsee-cost-display [cost]=\"message.cost\"></rtsee-cost-display>\n </div>\n }\n</div>\n", styles: [".rts-media-widget{max-width:100%}.rts-media-video{border-radius:8px;overflow:hidden;margin-bottom:6px}.rts-video-player{width:100%;max-height:360px;border-radius:8px;background:#000;display:block}.rts-media-images{display:grid;gap:4px;border-radius:8px;overflow:hidden;margin-bottom:4px}.rts-media-grid-1{grid-template-columns:1fr}.rts-media-grid-2{grid-template-columns:1fr 1fr}.rts-media-grid-4{grid-template-columns:1fr 1fr;grid-template-rows:auto auto}.rts-media-image-wrapper{position:relative;overflow:hidden;border-radius:6px;background:var(--rts-bg-secondary, #1e1e2e)}.rts-media-image-actions{position:absolute;top:8px;right:8px;z-index:5;display:inline-flex;align-items:center;gap:6px}.rts-media-btn-paint{display:inline-flex;align-items:center;gap:4px;padding:4px 8px;background:#12121cd9;-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px);color:#e9d5ff;border:1px solid rgba(168,85,247,.45);border-radius:6px;font-size:11px;font-weight:600;cursor:pointer;transition:all .2s cubic-bezier(.34,1.56,.64,1);box-shadow:0 2px 8px #00000080}.rts-media-btn-paint i{font-size:11px;color:#c084fc}.rts-media-btn-paint span{line-height:1}.rts-media-btn-paint:hover{background:#9333eae6;color:#fff;border-color:#a855f7;transform:translateY(-1px) scale(1.04);box-shadow:0 4px 12px #a855f773}.rts-media-btn-paint:hover i{color:#fff}.rts-media-btn-paint:active{transform:translateY(0) scale(.98)}.rts-media-btn-context{display:inline-flex;align-items:center;gap:4px;padding:4px 8px;background:#12121cd9;-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px);color:#bae6fd;border:1px solid rgba(56,189,248,.45);border-radius:6px;font-size:11px;font-weight:600;cursor:pointer;transition:all .2s cubic-bezier(.34,1.56,.64,1);box-shadow:0 2px 8px #00000080}.rts-media-btn-context i{font-size:11px;color:#38bdf8}.rts-media-btn-context span{line-height:1}.rts-media-btn-context:hover{background:#0ea5e9e6;color:#fff;border-color:#38bdf8;transform:translateY(-1px) scale(1.04);box-shadow:0 4px 12px #38bdf873}.rts-media-btn-context:hover i{color:#fff}.rts-media-btn-context:active{transform:translateY(0) scale(.98)}.rts-media-btn-download{position:absolute;left:8px;bottom:8px;z-index:5;width:30px;height:30px;display:inline-flex;align-items:center;justify-content:center;padding:0;background:#12121cd9;-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px);color:#d1fae5;border:1px solid rgba(52,211,153,.45);border-radius:50%;cursor:pointer;opacity:.85;transition:all .2s cubic-bezier(.34,1.56,.64,1);box-shadow:0 2px 8px #00000080}.rts-media-btn-download i{font-size:12px;color:#34d399}.rts-media-btn-download:hover{opacity:1;background:#10b981e6;border-color:#34d399;transform:translateY(-1px) scale(1.08);box-shadow:0 4px 12px #34d39973}.rts-media-btn-download:hover i{color:#fff}.rts-media-btn-download:active{transform:translateY(0) scale(.96)}.rts-media-btn-download.rts-downloading{cursor:progress;opacity:1}.rts-media-image{width:100%;height:auto;max-height:280px;object-fit:cover;display:block;transition:transform .2s ease;cursor:pointer}.rts-media-image:hover{transform:scale(1.02)}.rts-media-grid-1 .rts-media-image{max-height:400px;object-fit:contain}.rts-media-toggle{display:block;width:100%;padding:6px 0;border:none;background:none;color:var(--rts-agent-accent, #8b5cf6);font-size:12px;font-weight:500;cursor:pointer;text-align:center;transition:opacity .15s ease}.rts-media-toggle:hover{opacity:.8}.rts-media-cost{display:flex;justify-content:flex-end;padding-top:4px}\n"], dependencies: [{ kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "component", type: CostDisplayComponent, selector: "rtsee-cost-display", inputs: ["cost"] }] }); }
1910
2016
  }
1911
2017
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.4", ngImport: i0, type: MediaWidgetComponent, decorators: [{
1912
2018
  type: Component,
1913
- args: [{ selector: 'rtsee-media-widget', standalone: true, imports: [NgClass, CostDisplayComponent], template: "<div class=\"rts-media-widget\">\n <!-- Video player -->\n @if (message.video) {\n <div class=\"rts-media-video\">\n <video\n controls\n playsinline\n preload=\"metadata\"\n [src]=\"message.video.url\"\n class=\"rts-video-player\"\n >\n Your browser does not support video playback.\n </video>\n </div>\n }\n\n <!-- Image grid -->\n @if (hasImages) {\n <div class=\"rts-media-images\" [ngClass]=\"gridClass\">\n @for (img of visibleImages; track img.url) {\n <div class=\"rts-media-image-wrapper\">\n <img [src]=\"img.url\" [alt]=\"img.name\" class=\"rts-media-image\" loading=\"lazy\" />\n <button\n type=\"button\"\n class=\"rts-media-btn-paint\"\n title=\"Send image to Illustrator\"\n (click)=\"sendToIllustrator($event, img)\"\n >\n <i class=\"pi pi-palette\"></i>\n <span>+ paint</span>\n </button>\n </div>\n }\n </div>\n }\n\n <!-- Show more / less toggle -->\n @if (hiddenCount > 0 || showAll) {\n <button class=\"rts-media-toggle\" (click)=\"toggleShowAll()\">\n {{ showAll ? 'Show less' : 'Show ' + hiddenCount + ' more' }}\n </button>\n }\n\n <!-- Cost display (optional, disabled when nested inside agentic widget) -->\n @if (showCost && message.cost) {\n <div class=\"rts-media-cost\">\n <rtsee-cost-display [cost]=\"message.cost\"></rtsee-cost-display>\n </div>\n }\n</div>\n", styles: [".rts-media-widget{max-width:100%}.rts-media-video{border-radius:8px;overflow:hidden;margin-bottom:6px}.rts-video-player{width:100%;max-height:360px;border-radius:8px;background:#000;display:block}.rts-media-images{display:grid;gap:4px;border-radius:8px;overflow:hidden;margin-bottom:4px}.rts-media-grid-1{grid-template-columns:1fr}.rts-media-grid-2{grid-template-columns:1fr 1fr}.rts-media-grid-4{grid-template-columns:1fr 1fr;grid-template-rows:auto auto}.rts-media-image-wrapper{position:relative;overflow:hidden;border-radius:6px;background:var(--rts-bg-secondary, #1e1e2e)}.rts-media-btn-paint{position:absolute;top:8px;right:8px;z-index:5;display:inline-flex;align-items:center;gap:4px;padding:4px 8px;background:#12121cd9;-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px);color:#e9d5ff;border:1px solid rgba(168,85,247,.45);border-radius:6px;font-size:11px;font-weight:600;cursor:pointer;transition:all .2s cubic-bezier(.34,1.56,.64,1);box-shadow:0 2px 8px #00000080}.rts-media-btn-paint i{font-size:11px;color:#c084fc}.rts-media-btn-paint span{line-height:1}.rts-media-btn-paint:hover{background:#9333eae6;color:#fff;border-color:#a855f7;transform:translateY(-1px) scale(1.04);box-shadow:0 4px 12px #a855f773}.rts-media-btn-paint:hover i{color:#fff}.rts-media-btn-paint:active{transform:translateY(0) scale(.98)}.rts-media-image{width:100%;height:auto;max-height:280px;object-fit:cover;display:block;transition:transform .2s ease;cursor:pointer}.rts-media-image:hover{transform:scale(1.02)}.rts-media-grid-1 .rts-media-image{max-height:400px;object-fit:contain}.rts-media-toggle{display:block;width:100%;padding:6px 0;border:none;background:none;color:var(--rts-agent-accent, #8b5cf6);font-size:12px;font-weight:500;cursor:pointer;text-align:center;transition:opacity .15s ease}.rts-media-toggle:hover{opacity:.8}.rts-media-cost{display:flex;justify-content:flex-end;padding-top:4px}\n"] }]
2019
+ args: [{ selector: 'rtsee-media-widget', standalone: true, imports: [NgClass, CostDisplayComponent], template: "<div class=\"rts-media-widget\">\n <!-- Video player -->\n @if (message.video) {\n <div class=\"rts-media-video\">\n <video\n controls\n playsinline\n preload=\"metadata\"\n [src]=\"message.video.url\"\n class=\"rts-video-player\"\n >\n Your browser does not support video playback.\n </video>\n </div>\n }\n\n <!-- Image grid -->\n @if (hasImages) {\n <div class=\"rts-media-images\" [ngClass]=\"gridClass\">\n @for (img of visibleImages; track img.id || img.url) {\n <div class=\"rts-media-image-wrapper\">\n <img [src]=\"previewUrl(img)\" [alt]=\"img.name\" class=\"rts-media-image\" loading=\"lazy\" />\n <button\n type=\"button\"\n class=\"rts-media-btn-download\"\n [class.rts-downloading]=\"downloadingUrl === img.url\"\n title=\"Download original image\"\n aria-label=\"Download original image\"\n (click)=\"downloadOriginal($event, img)\"\n >\n <i class=\"pi\" [ngClass]=\"downloadingUrl === img.url ? 'pi-spin pi-spinner' : 'pi-download'\"></i>\n </button>\n <div class=\"rts-media-image-actions\">\n <button\n type=\"button\"\n class=\"rts-media-btn-paint\"\n title=\"Send image to Illustrator (Review & Iterate)\"\n (click)=\"sendToIllustrator($event, img)\"\n >\n <i class=\"pi pi-palette\"></i>\n <span>+ paint</span>\n </button>\n <button\n type=\"button\"\n class=\"rts-media-btn-context\"\n title=\"Add image to Illustrator context references\"\n (click)=\"addToContext($event, img)\"\n >\n <i class=\"pi pi-plus\"></i>\n <span>+ context</span>\n </button>\n </div>\n </div>\n }\n </div>\n }\n\n <!-- Show more / less toggle -->\n @if (hiddenCount > 0 || showAll) {\n <button class=\"rts-media-toggle\" (click)=\"toggleShowAll()\">\n {{ showAll ? 'Show less' : 'Show ' + hiddenCount + ' more' }}\n </button>\n }\n\n <!-- Cost display (optional, disabled when nested inside agentic widget) -->\n @if (showCost && message.cost) {\n <div class=\"rts-media-cost\">\n <rtsee-cost-display [cost]=\"message.cost\"></rtsee-cost-display>\n </div>\n }\n</div>\n", styles: [".rts-media-widget{max-width:100%}.rts-media-video{border-radius:8px;overflow:hidden;margin-bottom:6px}.rts-video-player{width:100%;max-height:360px;border-radius:8px;background:#000;display:block}.rts-media-images{display:grid;gap:4px;border-radius:8px;overflow:hidden;margin-bottom:4px}.rts-media-grid-1{grid-template-columns:1fr}.rts-media-grid-2{grid-template-columns:1fr 1fr}.rts-media-grid-4{grid-template-columns:1fr 1fr;grid-template-rows:auto auto}.rts-media-image-wrapper{position:relative;overflow:hidden;border-radius:6px;background:var(--rts-bg-secondary, #1e1e2e)}.rts-media-image-actions{position:absolute;top:8px;right:8px;z-index:5;display:inline-flex;align-items:center;gap:6px}.rts-media-btn-paint{display:inline-flex;align-items:center;gap:4px;padding:4px 8px;background:#12121cd9;-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px);color:#e9d5ff;border:1px solid rgba(168,85,247,.45);border-radius:6px;font-size:11px;font-weight:600;cursor:pointer;transition:all .2s cubic-bezier(.34,1.56,.64,1);box-shadow:0 2px 8px #00000080}.rts-media-btn-paint i{font-size:11px;color:#c084fc}.rts-media-btn-paint span{line-height:1}.rts-media-btn-paint:hover{background:#9333eae6;color:#fff;border-color:#a855f7;transform:translateY(-1px) scale(1.04);box-shadow:0 4px 12px #a855f773}.rts-media-btn-paint:hover i{color:#fff}.rts-media-btn-paint:active{transform:translateY(0) scale(.98)}.rts-media-btn-context{display:inline-flex;align-items:center;gap:4px;padding:4px 8px;background:#12121cd9;-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px);color:#bae6fd;border:1px solid rgba(56,189,248,.45);border-radius:6px;font-size:11px;font-weight:600;cursor:pointer;transition:all .2s cubic-bezier(.34,1.56,.64,1);box-shadow:0 2px 8px #00000080}.rts-media-btn-context i{font-size:11px;color:#38bdf8}.rts-media-btn-context span{line-height:1}.rts-media-btn-context:hover{background:#0ea5e9e6;color:#fff;border-color:#38bdf8;transform:translateY(-1px) scale(1.04);box-shadow:0 4px 12px #38bdf873}.rts-media-btn-context:hover i{color:#fff}.rts-media-btn-context:active{transform:translateY(0) scale(.98)}.rts-media-btn-download{position:absolute;left:8px;bottom:8px;z-index:5;width:30px;height:30px;display:inline-flex;align-items:center;justify-content:center;padding:0;background:#12121cd9;-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px);color:#d1fae5;border:1px solid rgba(52,211,153,.45);border-radius:50%;cursor:pointer;opacity:.85;transition:all .2s cubic-bezier(.34,1.56,.64,1);box-shadow:0 2px 8px #00000080}.rts-media-btn-download i{font-size:12px;color:#34d399}.rts-media-btn-download:hover{opacity:1;background:#10b981e6;border-color:#34d399;transform:translateY(-1px) scale(1.08);box-shadow:0 4px 12px #34d39973}.rts-media-btn-download:hover i{color:#fff}.rts-media-btn-download:active{transform:translateY(0) scale(.96)}.rts-media-btn-download.rts-downloading{cursor:progress;opacity:1}.rts-media-image{width:100%;height:auto;max-height:280px;object-fit:cover;display:block;transition:transform .2s ease;cursor:pointer}.rts-media-image:hover{transform:scale(1.02)}.rts-media-grid-1 .rts-media-image{max-height:400px;object-fit:contain}.rts-media-toggle{display:block;width:100%;padding:6px 0;border:none;background:none;color:var(--rts-agent-accent, #8b5cf6);font-size:12px;font-weight:500;cursor:pointer;text-align:center;transition:opacity .15s ease}.rts-media-toggle:hover{opacity:.8}.rts-media-cost{display:flex;justify-content:flex-end;padding-top:4px}\n"] }]
1914
2020
  }], propDecorators: { message: [{
1915
2021
  type: Input
1916
2022
  }], messenger: [{
@@ -2276,12 +2382,16 @@ class StorytellerStoryLinkStepComponent {
2276
2382
  get storyUrl() {
2277
2383
  return this.story.storyId ? `/story/${this.story.storyId}` : '';
2278
2384
  }
2385
+ get kicker() {
2386
+ const language = this.story.language ? getStoryTranslatorLanguage(this.story.language) : undefined;
2387
+ return language ? `Translated to ${language.nativeName}` : 'Your story is ready';
2388
+ }
2279
2389
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.4", ngImport: i0, type: StorytellerStoryLinkStepComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
2280
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.4", type: StorytellerStoryLinkStepComponent, isStandalone: true, selector: "rtsee-storyteller-story-link-step", inputs: { step: "step", workflow: "workflow", chat: "chat", messenger: "messenger" }, ngImport: i0, template: "@if (storyUrl) {\n <a class=\"rts-story-link-card\" [href]=\"storyUrl\" target=\"_blank\" rel=\"noopener\">\n <div class=\"rts-story-link-icon\">\uD83D\uDCD6</div>\n <div class=\"rts-story-link-body\">\n <span class=\"rts-story-link-kicker\">Your story is ready</span>\n <span class=\"rts-story-link-title\">{{ story.name || 'Open story' }}</span>\n @if (story.description) {\n <span class=\"rts-story-link-description\">{{ story.description }}</span>\n }\n @if (story.slidesCount) {\n <span class=\"rts-story-link-meta\">{{ story.slidesCount }} slides</span>\n }\n </div>\n <span class=\"rts-story-link-arrow pi pi-arrow-right\"></span>\n </a>\n} @else {\n <div class=\"rts-storyteller-hint\">The story link will appear here once the story is created.</div>\n}\n", styles: [":host{display:block;--rts-st-accent: #8b5cf6;--rts-st-accent-2: #6366f1}.rts-storyteller-form{display:flex;flex-direction:column;gap:12px}.rts-storyteller-field{display:flex;flex-direction:column;gap:5px}.rts-storyteller-label{display:flex;justify-content:space-between;align-items:baseline;font-size:10px;font-weight:700;letter-spacing:.08em;text-transform:uppercase;color:var(--rts-text-secondary, #8a8fa3)}.rts-storyteller-counter{font-weight:500;letter-spacing:0;text-transform:none}.rts-storyteller-input{width:100%;box-sizing:border-box;padding:9px 12px;border-radius:9px;border:1px solid var(--rts-border-dim, rgba(120, 120, 160, .25));background:var(--rts-bg-primary, rgba(127, 127, 160, .06));color:var(--rts-text-primary);font:inherit;font-size:13px;outline:none;transition:border-color .15s ease,box-shadow .15s ease,background .15s ease}.rts-storyteller-input:focus{border-color:var(--rts-st-accent);box-shadow:0 0 0 3px #8b5cf62e}.rts-storyteller-input:disabled{opacity:.75;cursor:default}.rts-storyteller-input.rts-storyteller-invalid{border-color:#f43f5e;box-shadow:0 0 0 3px #f43f5e1f}.rts-storyteller-textarea{min-height:80px;resize:vertical;line-height:1.5}.rts-storyteller-textarea-lg{min-height:160px;max-height:480px}.rts-storyteller-select-wrap{position:relative}.rts-storyteller-select{appearance:none;padding-right:32px;cursor:pointer}.rts-storyteller-select-caret{position:absolute;right:12px;top:50%;transform:translateY(-50%);font-size:10px;pointer-events:none;color:var(--rts-text-secondary, #8a8fa3)}.rts-storyteller-hint{font-size:12px;font-style:italic;color:var(--rts-text-secondary, #8a8fa3)}.rts-storyteller-error{padding:6px 10px;border-radius:6px;font-size:12px;color:#f43f5e;background:#f43f5e14;border:1px solid rgba(244,63,94,.25)}.rts-storyteller-actions{display:flex;justify-content:flex-end}.rts-storyteller-submit{display:inline-flex;align-items:center;gap:6px;padding:8px 18px;border:none;border-radius:9px;color:#fff;font-weight:600;font-size:13px;cursor:pointer;background:linear-gradient(135deg,var(--rts-st-accent-2),var(--rts-st-accent) 60%,#a78bfa);background-size:160% 160%;box-shadow:0 4px 14px -4px #6366f199;transition:transform .15s ease,box-shadow .15s ease,background-position .4s ease}.rts-storyteller-submit:hover:not(:disabled){transform:translateY(-1px);background-position:100% 0;box-shadow:0 6px 18px -4px #6366f1bf}.rts-storyteller-submit:disabled{opacity:.6;cursor:default;box-shadow:none}.rts-storyteller-spinner{width:11px;height:11px;border-radius:50%;border:2px solid rgba(255,255,255,.35);border-top-color:#fff;animation:rts-st-spin .8s linear infinite}.rts-story-link-card{display:flex;align-items:center;gap:12px;padding:14px;border-radius:12px;text-decoration:none;color:inherit;background:linear-gradient(135deg,#6366f11a,#8b5cf60f);border:1px solid rgba(139,92,246,.3);transition:transform .2s ease,box-shadow .2s ease,border-color .2s ease;animation:rts-st-pop .4s cubic-bezier(.2,.9,.3,1.3)}.rts-story-link-card:hover{transform:translateY(-2px);border-color:var(--rts-st-accent);box-shadow:0 10px 28px -12px #8b5cf699}.rts-story-link-card:hover .rts-story-link-arrow{transform:translate(3px)}.rts-story-link-icon{display:flex;align-items:center;justify-content:center;flex-shrink:0;width:42px;height:42px;border-radius:12px;font-size:20px;background:linear-gradient(135deg,var(--rts-st-accent-2),var(--rts-st-accent))}.rts-story-link-body{display:flex;flex-direction:column;gap:2px;min-width:0;flex:1}.rts-story-link-kicker{font-size:10px;font-weight:700;letter-spacing:.08em;text-transform:uppercase;color:var(--rts-st-accent)}.rts-story-link-title{font-size:15px;font-weight:600;color:var(--rts-text-primary)}.rts-story-link-description{font-size:12px;color:var(--rts-text-secondary, #8a8fa3);display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}.rts-story-link-meta{font-size:11px;color:var(--rts-text-secondary, #8a8fa3)}.rts-story-link-arrow{color:var(--rts-st-accent);transition:transform .2s ease}@keyframes rts-st-spin{to{transform:rotate(360deg)}}@keyframes rts-st-pop{0%{opacity:0;transform:scale(.96)}to{opacity:1;transform:none}}\n"] }); }
2390
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.4", type: StorytellerStoryLinkStepComponent, isStandalone: true, selector: "rtsee-storyteller-story-link-step", inputs: { step: "step", workflow: "workflow", chat: "chat", messenger: "messenger" }, ngImport: i0, template: "@if (storyUrl) {\n <a class=\"rts-story-link-card\" [href]=\"storyUrl\" target=\"_blank\" rel=\"noopener\">\n <div class=\"rts-story-link-icon\">\uD83D\uDCD6</div>\n <div class=\"rts-story-link-body\">\n <span class=\"rts-story-link-kicker\">{{ kicker }}</span>\n <span class=\"rts-story-link-title\">{{ story.name || 'Open story' }}</span>\n @if (story.description) {\n <span class=\"rts-story-link-description\">{{ story.description }}</span>\n }\n @if (story.slidesCount) {\n <span class=\"rts-story-link-meta\">{{ story.slidesCount }} slides</span>\n }\n </div>\n <span class=\"rts-story-link-arrow pi pi-arrow-right\"></span>\n </a>\n} @else {\n <div class=\"rts-storyteller-hint\">The story link will appear here once the story is created.</div>\n}\n", styles: [":host{display:block;--rts-st-accent: #8b5cf6;--rts-st-accent-2: #6366f1}.rts-storyteller-form{display:flex;flex-direction:column;gap:12px}.rts-storyteller-field{display:flex;flex-direction:column;gap:5px}.rts-storyteller-label{display:flex;justify-content:space-between;align-items:baseline;font-size:10px;font-weight:700;letter-spacing:.08em;text-transform:uppercase;color:var(--rts-text-secondary, #8a8fa3)}.rts-storyteller-counter{font-weight:500;letter-spacing:0;text-transform:none}.rts-storyteller-input{width:100%;box-sizing:border-box;padding:9px 12px;border-radius:9px;border:1px solid var(--rts-border-dim, rgba(120, 120, 160, .25));background:var(--rts-bg-primary, rgba(127, 127, 160, .06));color:var(--rts-text-primary);font:inherit;font-size:13px;outline:none;transition:border-color .15s ease,box-shadow .15s ease,background .15s ease}.rts-storyteller-input:focus{border-color:var(--rts-st-accent);box-shadow:0 0 0 3px #8b5cf62e}.rts-storyteller-input:disabled{opacity:.75;cursor:default}.rts-storyteller-input.rts-storyteller-invalid{border-color:#f43f5e;box-shadow:0 0 0 3px #f43f5e1f}.rts-storyteller-textarea{min-height:80px;resize:vertical;line-height:1.5}.rts-storyteller-textarea-lg{min-height:160px;max-height:480px}.rts-storyteller-select-wrap{position:relative}.rts-storyteller-select{appearance:none;padding-right:32px;cursor:pointer}.rts-storyteller-select-caret{position:absolute;right:12px;top:50%;transform:translateY(-50%);font-size:10px;pointer-events:none;color:var(--rts-text-secondary, #8a8fa3)}.rts-storyteller-hint{font-size:12px;font-style:italic;color:var(--rts-text-secondary, #8a8fa3)}.rts-storyteller-error{padding:6px 10px;border-radius:6px;font-size:12px;color:#f43f5e;background:#f43f5e14;border:1px solid rgba(244,63,94,.25)}.rts-storyteller-actions{display:flex;justify-content:flex-end}.rts-storyteller-submit{display:inline-flex;align-items:center;gap:6px;padding:8px 18px;border:none;border-radius:9px;color:#fff;font-weight:600;font-size:13px;cursor:pointer;background:linear-gradient(135deg,var(--rts-st-accent-2),var(--rts-st-accent) 60%,#a78bfa);background-size:160% 160%;box-shadow:0 4px 14px -4px #6366f199;transition:transform .15s ease,box-shadow .15s ease,background-position .4s ease}.rts-storyteller-submit:hover:not(:disabled){transform:translateY(-1px);background-position:100% 0;box-shadow:0 6px 18px -4px #6366f1bf}.rts-storyteller-submit:disabled{opacity:.6;cursor:default;box-shadow:none}.rts-storyteller-spinner{width:11px;height:11px;border-radius:50%;border:2px solid rgba(255,255,255,.35);border-top-color:#fff;animation:rts-st-spin .8s linear infinite}.rts-story-link-card{display:flex;align-items:center;gap:12px;padding:14px;border-radius:12px;text-decoration:none;color:inherit;background:linear-gradient(135deg,#6366f11a,#8b5cf60f);border:1px solid rgba(139,92,246,.3);transition:transform .2s ease,box-shadow .2s ease,border-color .2s ease;animation:rts-st-pop .4s cubic-bezier(.2,.9,.3,1.3)}.rts-story-link-card:hover{transform:translateY(-2px);border-color:var(--rts-st-accent);box-shadow:0 10px 28px -12px #8b5cf699}.rts-story-link-card:hover .rts-story-link-arrow{transform:translate(3px)}.rts-story-link-icon{display:flex;align-items:center;justify-content:center;flex-shrink:0;width:42px;height:42px;border-radius:12px;font-size:20px;background:linear-gradient(135deg,var(--rts-st-accent-2),var(--rts-st-accent))}.rts-story-link-body{display:flex;flex-direction:column;gap:2px;min-width:0;flex:1}.rts-story-link-kicker{font-size:10px;font-weight:700;letter-spacing:.08em;text-transform:uppercase;color:var(--rts-st-accent)}.rts-story-link-title{font-size:15px;font-weight:600;color:var(--rts-text-primary)}.rts-story-link-description{font-size:12px;color:var(--rts-text-secondary, #8a8fa3);display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}.rts-story-link-meta{font-size:11px;color:var(--rts-text-secondary, #8a8fa3)}.rts-story-link-arrow{color:var(--rts-st-accent);transition:transform .2s ease}@keyframes rts-st-spin{to{transform:rotate(360deg)}}@keyframes rts-st-pop{0%{opacity:0;transform:scale(.96)}to{opacity:1;transform:none}}\n"] }); }
2281
2391
  }
2282
2392
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.4", ngImport: i0, type: StorytellerStoryLinkStepComponent, decorators: [{
2283
2393
  type: Component,
2284
- args: [{ selector: 'rtsee-storyteller-story-link-step', standalone: true, template: "@if (storyUrl) {\n <a class=\"rts-story-link-card\" [href]=\"storyUrl\" target=\"_blank\" rel=\"noopener\">\n <div class=\"rts-story-link-icon\">\uD83D\uDCD6</div>\n <div class=\"rts-story-link-body\">\n <span class=\"rts-story-link-kicker\">Your story is ready</span>\n <span class=\"rts-story-link-title\">{{ story.name || 'Open story' }}</span>\n @if (story.description) {\n <span class=\"rts-story-link-description\">{{ story.description }}</span>\n }\n @if (story.slidesCount) {\n <span class=\"rts-story-link-meta\">{{ story.slidesCount }} slides</span>\n }\n </div>\n <span class=\"rts-story-link-arrow pi pi-arrow-right\"></span>\n </a>\n} @else {\n <div class=\"rts-storyteller-hint\">The story link will appear here once the story is created.</div>\n}\n", styles: [":host{display:block;--rts-st-accent: #8b5cf6;--rts-st-accent-2: #6366f1}.rts-storyteller-form{display:flex;flex-direction:column;gap:12px}.rts-storyteller-field{display:flex;flex-direction:column;gap:5px}.rts-storyteller-label{display:flex;justify-content:space-between;align-items:baseline;font-size:10px;font-weight:700;letter-spacing:.08em;text-transform:uppercase;color:var(--rts-text-secondary, #8a8fa3)}.rts-storyteller-counter{font-weight:500;letter-spacing:0;text-transform:none}.rts-storyteller-input{width:100%;box-sizing:border-box;padding:9px 12px;border-radius:9px;border:1px solid var(--rts-border-dim, rgba(120, 120, 160, .25));background:var(--rts-bg-primary, rgba(127, 127, 160, .06));color:var(--rts-text-primary);font:inherit;font-size:13px;outline:none;transition:border-color .15s ease,box-shadow .15s ease,background .15s ease}.rts-storyteller-input:focus{border-color:var(--rts-st-accent);box-shadow:0 0 0 3px #8b5cf62e}.rts-storyteller-input:disabled{opacity:.75;cursor:default}.rts-storyteller-input.rts-storyteller-invalid{border-color:#f43f5e;box-shadow:0 0 0 3px #f43f5e1f}.rts-storyteller-textarea{min-height:80px;resize:vertical;line-height:1.5}.rts-storyteller-textarea-lg{min-height:160px;max-height:480px}.rts-storyteller-select-wrap{position:relative}.rts-storyteller-select{appearance:none;padding-right:32px;cursor:pointer}.rts-storyteller-select-caret{position:absolute;right:12px;top:50%;transform:translateY(-50%);font-size:10px;pointer-events:none;color:var(--rts-text-secondary, #8a8fa3)}.rts-storyteller-hint{font-size:12px;font-style:italic;color:var(--rts-text-secondary, #8a8fa3)}.rts-storyteller-error{padding:6px 10px;border-radius:6px;font-size:12px;color:#f43f5e;background:#f43f5e14;border:1px solid rgba(244,63,94,.25)}.rts-storyteller-actions{display:flex;justify-content:flex-end}.rts-storyteller-submit{display:inline-flex;align-items:center;gap:6px;padding:8px 18px;border:none;border-radius:9px;color:#fff;font-weight:600;font-size:13px;cursor:pointer;background:linear-gradient(135deg,var(--rts-st-accent-2),var(--rts-st-accent) 60%,#a78bfa);background-size:160% 160%;box-shadow:0 4px 14px -4px #6366f199;transition:transform .15s ease,box-shadow .15s ease,background-position .4s ease}.rts-storyteller-submit:hover:not(:disabled){transform:translateY(-1px);background-position:100% 0;box-shadow:0 6px 18px -4px #6366f1bf}.rts-storyteller-submit:disabled{opacity:.6;cursor:default;box-shadow:none}.rts-storyteller-spinner{width:11px;height:11px;border-radius:50%;border:2px solid rgba(255,255,255,.35);border-top-color:#fff;animation:rts-st-spin .8s linear infinite}.rts-story-link-card{display:flex;align-items:center;gap:12px;padding:14px;border-radius:12px;text-decoration:none;color:inherit;background:linear-gradient(135deg,#6366f11a,#8b5cf60f);border:1px solid rgba(139,92,246,.3);transition:transform .2s ease,box-shadow .2s ease,border-color .2s ease;animation:rts-st-pop .4s cubic-bezier(.2,.9,.3,1.3)}.rts-story-link-card:hover{transform:translateY(-2px);border-color:var(--rts-st-accent);box-shadow:0 10px 28px -12px #8b5cf699}.rts-story-link-card:hover .rts-story-link-arrow{transform:translate(3px)}.rts-story-link-icon{display:flex;align-items:center;justify-content:center;flex-shrink:0;width:42px;height:42px;border-radius:12px;font-size:20px;background:linear-gradient(135deg,var(--rts-st-accent-2),var(--rts-st-accent))}.rts-story-link-body{display:flex;flex-direction:column;gap:2px;min-width:0;flex:1}.rts-story-link-kicker{font-size:10px;font-weight:700;letter-spacing:.08em;text-transform:uppercase;color:var(--rts-st-accent)}.rts-story-link-title{font-size:15px;font-weight:600;color:var(--rts-text-primary)}.rts-story-link-description{font-size:12px;color:var(--rts-text-secondary, #8a8fa3);display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}.rts-story-link-meta{font-size:11px;color:var(--rts-text-secondary, #8a8fa3)}.rts-story-link-arrow{color:var(--rts-st-accent);transition:transform .2s ease}@keyframes rts-st-spin{to{transform:rotate(360deg)}}@keyframes rts-st-pop{0%{opacity:0;transform:scale(.96)}to{opacity:1;transform:none}}\n"] }]
2394
+ args: [{ selector: 'rtsee-storyteller-story-link-step', standalone: true, template: "@if (storyUrl) {\n <a class=\"rts-story-link-card\" [href]=\"storyUrl\" target=\"_blank\" rel=\"noopener\">\n <div class=\"rts-story-link-icon\">\uD83D\uDCD6</div>\n <div class=\"rts-story-link-body\">\n <span class=\"rts-story-link-kicker\">{{ kicker }}</span>\n <span class=\"rts-story-link-title\">{{ story.name || 'Open story' }}</span>\n @if (story.description) {\n <span class=\"rts-story-link-description\">{{ story.description }}</span>\n }\n @if (story.slidesCount) {\n <span class=\"rts-story-link-meta\">{{ story.slidesCount }} slides</span>\n }\n </div>\n <span class=\"rts-story-link-arrow pi pi-arrow-right\"></span>\n </a>\n} @else {\n <div class=\"rts-storyteller-hint\">The story link will appear here once the story is created.</div>\n}\n", styles: [":host{display:block;--rts-st-accent: #8b5cf6;--rts-st-accent-2: #6366f1}.rts-storyteller-form{display:flex;flex-direction:column;gap:12px}.rts-storyteller-field{display:flex;flex-direction:column;gap:5px}.rts-storyteller-label{display:flex;justify-content:space-between;align-items:baseline;font-size:10px;font-weight:700;letter-spacing:.08em;text-transform:uppercase;color:var(--rts-text-secondary, #8a8fa3)}.rts-storyteller-counter{font-weight:500;letter-spacing:0;text-transform:none}.rts-storyteller-input{width:100%;box-sizing:border-box;padding:9px 12px;border-radius:9px;border:1px solid var(--rts-border-dim, rgba(120, 120, 160, .25));background:var(--rts-bg-primary, rgba(127, 127, 160, .06));color:var(--rts-text-primary);font:inherit;font-size:13px;outline:none;transition:border-color .15s ease,box-shadow .15s ease,background .15s ease}.rts-storyteller-input:focus{border-color:var(--rts-st-accent);box-shadow:0 0 0 3px #8b5cf62e}.rts-storyteller-input:disabled{opacity:.75;cursor:default}.rts-storyteller-input.rts-storyteller-invalid{border-color:#f43f5e;box-shadow:0 0 0 3px #f43f5e1f}.rts-storyteller-textarea{min-height:80px;resize:vertical;line-height:1.5}.rts-storyteller-textarea-lg{min-height:160px;max-height:480px}.rts-storyteller-select-wrap{position:relative}.rts-storyteller-select{appearance:none;padding-right:32px;cursor:pointer}.rts-storyteller-select-caret{position:absolute;right:12px;top:50%;transform:translateY(-50%);font-size:10px;pointer-events:none;color:var(--rts-text-secondary, #8a8fa3)}.rts-storyteller-hint{font-size:12px;font-style:italic;color:var(--rts-text-secondary, #8a8fa3)}.rts-storyteller-error{padding:6px 10px;border-radius:6px;font-size:12px;color:#f43f5e;background:#f43f5e14;border:1px solid rgba(244,63,94,.25)}.rts-storyteller-actions{display:flex;justify-content:flex-end}.rts-storyteller-submit{display:inline-flex;align-items:center;gap:6px;padding:8px 18px;border:none;border-radius:9px;color:#fff;font-weight:600;font-size:13px;cursor:pointer;background:linear-gradient(135deg,var(--rts-st-accent-2),var(--rts-st-accent) 60%,#a78bfa);background-size:160% 160%;box-shadow:0 4px 14px -4px #6366f199;transition:transform .15s ease,box-shadow .15s ease,background-position .4s ease}.rts-storyteller-submit:hover:not(:disabled){transform:translateY(-1px);background-position:100% 0;box-shadow:0 6px 18px -4px #6366f1bf}.rts-storyteller-submit:disabled{opacity:.6;cursor:default;box-shadow:none}.rts-storyteller-spinner{width:11px;height:11px;border-radius:50%;border:2px solid rgba(255,255,255,.35);border-top-color:#fff;animation:rts-st-spin .8s linear infinite}.rts-story-link-card{display:flex;align-items:center;gap:12px;padding:14px;border-radius:12px;text-decoration:none;color:inherit;background:linear-gradient(135deg,#6366f11a,#8b5cf60f);border:1px solid rgba(139,92,246,.3);transition:transform .2s ease,box-shadow .2s ease,border-color .2s ease;animation:rts-st-pop .4s cubic-bezier(.2,.9,.3,1.3)}.rts-story-link-card:hover{transform:translateY(-2px);border-color:var(--rts-st-accent);box-shadow:0 10px 28px -12px #8b5cf699}.rts-story-link-card:hover .rts-story-link-arrow{transform:translate(3px)}.rts-story-link-icon{display:flex;align-items:center;justify-content:center;flex-shrink:0;width:42px;height:42px;border-radius:12px;font-size:20px;background:linear-gradient(135deg,var(--rts-st-accent-2),var(--rts-st-accent))}.rts-story-link-body{display:flex;flex-direction:column;gap:2px;min-width:0;flex:1}.rts-story-link-kicker{font-size:10px;font-weight:700;letter-spacing:.08em;text-transform:uppercase;color:var(--rts-st-accent)}.rts-story-link-title{font-size:15px;font-weight:600;color:var(--rts-text-primary)}.rts-story-link-description{font-size:12px;color:var(--rts-text-secondary, #8a8fa3);display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}.rts-story-link-meta{font-size:11px;color:var(--rts-text-secondary, #8a8fa3)}.rts-story-link-arrow{color:var(--rts-st-accent);transition:transform .2s ease}@keyframes rts-st-spin{to{transform:rotate(360deg)}}@keyframes rts-st-pop{0%{opacity:0;transform:scale(.96)}to{opacity:1;transform:none}}\n"] }]
2285
2395
  }], propDecorators: { step: [{
2286
2396
  type: Input
2287
2397
  }], workflow: [{
@@ -2292,6 +2402,232 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.4", ngImpor
2292
2402
  type: Input
2293
2403
  }] } });
2294
2404
 
2405
+ const SEARCH_DEBOUNCE_MS = 250;
2406
+ /**
2407
+ * Story Translator step 1: pick a story (autocomplete by name) and the target language.
2408
+ */
2409
+ class StoryTranslatorFormStepComponent {
2410
+ constructor(cdr, host) {
2411
+ this.cdr = cdr;
2412
+ this.host = host;
2413
+ this.languages = STORY_TRANSLATOR_LANGUAGES;
2414
+ this.form = new FormGroup({
2415
+ /* eslint-disable */
2416
+ storyQuery: new FormControl(''),
2417
+ storyId: new FormControl('', Validators.required),
2418
+ language: new FormControl('', Validators.required),
2419
+ agentId: new FormControl('', Validators.required),
2420
+ /* eslint-enable */
2421
+ });
2422
+ this.selectedStory = null;
2423
+ this.suggestions = [];
2424
+ this.suggestionsOpened = false;
2425
+ this.highlightedIndex = 0;
2426
+ this.searchInProgress = false;
2427
+ this.searchError = null;
2428
+ this.searchTimer = null;
2429
+ this.searchRequestId = 0;
2430
+ this.onChangedBound = () => this.onWorkflowChanged();
2431
+ }
2432
+ get agents() {
2433
+ return (this.chat?.members || []).filter((m) => m.isAgent);
2434
+ }
2435
+ get readonly() {
2436
+ return !this.step?.isActive || !!this.workflow?.submitInProgress;
2437
+ }
2438
+ get canCancel() {
2439
+ return !!this.workflow?.isDraft && !this.workflow?.submitInProgress && !!this.messenger;
2440
+ }
2441
+ get selectedLanguage() {
2442
+ return this.languages.find((l) => l.code === this.form.get('language')?.value);
2443
+ }
2444
+ ngOnInit() {
2445
+ this.workflow?.on(OzzyWorkflowClientEvents.CHANGED, this.onChangedBound);
2446
+ const agents = this.agents;
2447
+ if (agents.length === 1) {
2448
+ this.form.get('agentId')?.setValue(agents[0].id);
2449
+ }
2450
+ this.syncWithStep();
2451
+ }
2452
+ ngOnDestroy() {
2453
+ this.workflow?.off(OzzyWorkflowClientEvents.CHANGED, this.onChangedBound);
2454
+ this.clearSearchTimer();
2455
+ }
2456
+ onStoryFocus() {
2457
+ if (this.readonly) {
2458
+ return;
2459
+ }
2460
+ this.suggestionsOpened = true;
2461
+ if (!this.suggestions.length) {
2462
+ this.scheduleSearch(0);
2463
+ }
2464
+ }
2465
+ onStoryInput() {
2466
+ // typing invalidates the previous selection
2467
+ if (this.selectedStory) {
2468
+ this.selectedStory = null;
2469
+ this.form.get('storyId')?.setValue('');
2470
+ }
2471
+ this.suggestionsOpened = true;
2472
+ this.scheduleSearch(SEARCH_DEBOUNCE_MS);
2473
+ }
2474
+ onStoryKeydown(event) {
2475
+ if (!this.suggestionsOpened || !this.suggestions.length) {
2476
+ if (event.key === 'ArrowDown') {
2477
+ this.suggestionsOpened = true;
2478
+ this.scheduleSearch(0);
2479
+ }
2480
+ return;
2481
+ }
2482
+ switch (event.key) {
2483
+ case 'ArrowDown':
2484
+ event.preventDefault();
2485
+ this.highlightedIndex = (this.highlightedIndex + 1) % this.suggestions.length;
2486
+ break;
2487
+ case 'ArrowUp':
2488
+ event.preventDefault();
2489
+ this.highlightedIndex = (this.highlightedIndex - 1 + this.suggestions.length) % this.suggestions.length;
2490
+ break;
2491
+ case 'Enter':
2492
+ event.preventDefault();
2493
+ this.selectStory(this.suggestions[this.highlightedIndex]);
2494
+ break;
2495
+ case 'Escape':
2496
+ event.preventDefault();
2497
+ this.suggestionsOpened = false;
2498
+ break;
2499
+ }
2500
+ }
2501
+ selectStory(story) {
2502
+ if (!story || this.readonly) {
2503
+ return;
2504
+ }
2505
+ this.selectedStory = story;
2506
+ this.form.patchValue({ storyId: story.id, storyQuery: story.name }, { emitEvent: false });
2507
+ this.form.get('storyId')?.markAsTouched();
2508
+ this.suggestionsOpened = false;
2509
+ }
2510
+ clearStory() {
2511
+ if (this.readonly) {
2512
+ return;
2513
+ }
2514
+ this.selectedStory = null;
2515
+ this.form.patchValue({ storyId: '', storyQuery: '' }, { emitEvent: false });
2516
+ this.suggestionsOpened = true;
2517
+ this.scheduleSearch(0);
2518
+ setTimeout(() => this.storyInput?.nativeElement.focus());
2519
+ }
2520
+ onDocumentMouseDown(event) {
2521
+ if (this.suggestionsOpened && !this.host.nativeElement.contains(event.target)) {
2522
+ this.suggestionsOpened = false;
2523
+ }
2524
+ }
2525
+ submit() {
2526
+ if (this.readonly) {
2527
+ return;
2528
+ }
2529
+ if (this.form.invalid || !this.selectedStory) {
2530
+ this.form.markAllAsTouched();
2531
+ return;
2532
+ }
2533
+ void this.workflow.submitStep({
2534
+ storyId: this.selectedStory.id,
2535
+ storyName: this.selectedStory.name,
2536
+ language: this.form.get('language')?.value,
2537
+ agentId: this.form.get('agentId')?.value,
2538
+ });
2539
+ }
2540
+ cancel() {
2541
+ if (!this.canCancel) {
2542
+ return;
2543
+ }
2544
+ this.messenger?.discardDraftWorkflow(this.workflow);
2545
+ }
2546
+ isInvalid(name) {
2547
+ const control = this.form.get(name);
2548
+ return !!control && control.invalid && control.touched;
2549
+ }
2550
+ scheduleSearch(delay) {
2551
+ this.clearSearchTimer();
2552
+ this.searchTimer = setTimeout(() => void this.search(), delay);
2553
+ }
2554
+ clearSearchTimer() {
2555
+ if (this.searchTimer) {
2556
+ clearTimeout(this.searchTimer);
2557
+ this.searchTimer = null;
2558
+ }
2559
+ }
2560
+ async search() {
2561
+ if (!this.messenger) {
2562
+ return;
2563
+ }
2564
+ const requestId = ++this.searchRequestId;
2565
+ const query = this.form.get('storyQuery')?.value || '';
2566
+ this.searchInProgress = true;
2567
+ this.searchError = null;
2568
+ this.cdr.markForCheck();
2569
+ try {
2570
+ const stories = await this.messenger.ozzy.searchStories(query);
2571
+ if (requestId !== this.searchRequestId) {
2572
+ return;
2573
+ }
2574
+ this.suggestions = stories;
2575
+ this.highlightedIndex = 0;
2576
+ }
2577
+ catch (e) {
2578
+ if (requestId !== this.searchRequestId) {
2579
+ return;
2580
+ }
2581
+ this.suggestions = [];
2582
+ this.searchError = e?.friendlyMessage || e?.message || 'Failed to load stories.';
2583
+ }
2584
+ finally {
2585
+ if (requestId === this.searchRequestId) {
2586
+ this.searchInProgress = false;
2587
+ this.cdr.detectChanges();
2588
+ }
2589
+ }
2590
+ }
2591
+ onWorkflowChanged() {
2592
+ this.syncWithStep();
2593
+ this.cdr.detectChanges();
2594
+ }
2595
+ syncWithStep() {
2596
+ const output = this.step?.output;
2597
+ if (output?.storyId) {
2598
+ this.selectedStory = { id: output.storyId, name: output.storyName || output.storyId };
2599
+ this.form.patchValue({ storyId: output.storyId, storyQuery: output.storyName || '', language: output.language, agentId: output.agentId }, { emitEvent: false });
2600
+ }
2601
+ if (this.readonly) {
2602
+ this.suggestionsOpened = false;
2603
+ this.form.disable({ emitEvent: false });
2604
+ }
2605
+ else {
2606
+ this.form.enable({ emitEvent: false });
2607
+ }
2608
+ }
2609
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.4", ngImport: i0, type: StoryTranslatorFormStepComponent, deps: [{ token: i0.ChangeDetectorRef }, { token: i0.ElementRef }], target: i0.ɵɵFactoryTarget.Component }); }
2610
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.4", type: StoryTranslatorFormStepComponent, isStandalone: true, selector: "rtsee-story-translator-form-step", inputs: { step: "step", workflow: "workflow", chat: "chat", messenger: "messenger" }, host: { listeners: { "document:mousedown": "onDocumentMouseDown($event)" } }, viewQueries: [{ propertyName: "storyInput", first: true, predicate: ["storyInput"], descendants: true }], ngImport: i0, template: "<form class=\"rts-storyteller-form\" [formGroup]=\"form\" (ngSubmit)=\"submit()\" autocomplete=\"off\">\n <div class=\"rts-storyteller-field\">\n <label class=\"rts-storyteller-label\" for=\"story-translator-story-{{ step.id }}\">Story</label>\n <div class=\"rts-st-autocomplete\">\n <span class=\"rts-st-autocomplete-icon pi\" [class.pi-search]=\"!searchInProgress\" [class.pi-spin]=\"searchInProgress\" [class.pi-spinner]=\"searchInProgress\"></span>\n <input #storyInput\n class=\"rts-storyteller-input rts-st-autocomplete-input\"\n [class.rts-storyteller-invalid]=\"isInvalid('storyId')\"\n [class.rts-st-autocomplete-selected]=\"!!selectedStory\"\n formControlName=\"storyQuery\"\n type=\"text\"\n role=\"combobox\"\n aria-autocomplete=\"list\"\n [attr.aria-expanded]=\"suggestionsOpened\"\n placeholder=\"Search stories by name\u2026\"\n id=\"story-translator-story-{{ step.id }}\"\n (focus)=\"onStoryFocus()\"\n (input)=\"onStoryInput()\"\n (keydown)=\"onStoryKeydown($event)\"\n />\n @if (selectedStory && !readonly) {\n <button type=\"button\"\n class=\"rts-st-autocomplete-clear pi pi-times\"\n aria-label=\"Clear story\"\n id=\"story-translator-clear-{{ step.id }}\"\n (click)=\"clearStory()\"\n ></button>\n }\n\n @if (suggestionsOpened && !readonly) {\n <ul class=\"rts-st-autocomplete-panel\" role=\"listbox\">\n @if (searchError) {\n <li class=\"rts-st-autocomplete-empty rts-st-autocomplete-error\">{{ searchError }}</li>\n } @else if (!suggestions.length && !searchInProgress) {\n <li class=\"rts-st-autocomplete-empty\">No stories found</li>\n }\n @for (story of suggestions; track story.id; let i = $index) {\n <li class=\"rts-st-autocomplete-option\"\n role=\"option\"\n [class.rts-st-autocomplete-option-active]=\"i === highlightedIndex\"\n [attr.aria-selected]=\"story.id === selectedStory?.id\"\n (mouseenter)=\"highlightedIndex = i\"\n (mousedown)=\"$event.preventDefault(); selectStory(story)\"\n >\n @if (story.imageUrl) {\n <img class=\"rts-st-autocomplete-thumb\" [src]=\"story.imageUrl\" [alt]=\"story.name\" loading=\"lazy\" />\n } @else {\n <span class=\"rts-st-autocomplete-thumb rts-st-autocomplete-thumb-empty\">\uD83D\uDCD6</span>\n }\n <span class=\"rts-st-autocomplete-option-body\">\n <span class=\"rts-st-autocomplete-option-name\">{{ story.name }}</span>\n @if (story.totalSlides) {\n <span class=\"rts-st-autocomplete-option-meta\">{{ story.totalSlides }} slides</span>\n }\n </span>\n </li>\n }\n </ul>\n }\n </div>\n </div>\n\n <label class=\"rts-storyteller-field\">\n <span class=\"rts-storyteller-label\">Translate to</span>\n <div class=\"rts-storyteller-select-wrap\">\n <select class=\"rts-storyteller-input rts-storyteller-select\"\n [class.rts-storyteller-invalid]=\"isInvalid('language')\"\n formControlName=\"language\"\n id=\"story-translator-language-{{ step.id }}\"\n >\n <option value=\"\" disabled>Select a language</option>\n @for (language of languages; track language.code) {\n <option [value]=\"language.code\">{{ language.nativeName }}</option>\n }\n </select>\n <span class=\"rts-storyteller-select-caret pi pi-chevron-down\"></span>\n </div>\n </label>\n\n <label class=\"rts-storyteller-field\">\n <span class=\"rts-storyteller-label\">Agent</span>\n @if (agents.length) {\n <div class=\"rts-storyteller-select-wrap\">\n <select class=\"rts-storyteller-input rts-storyteller-select\"\n [class.rts-storyteller-invalid]=\"isInvalid('agentId')\"\n formControlName=\"agentId\"\n id=\"story-translator-agent-{{ step.id }}\"\n >\n <option value=\"\" disabled>Select an agent</option>\n @for (agent of agents; track agent.id) {\n <option [value]=\"agent.id\">\n {{ agent.name }}{{ agent.agentConfiguration?.model?.model ? ' \u00B7 ' + agent.agentConfiguration?.model?.model : '' }}\n </option>\n }\n </select>\n <span class=\"rts-storyteller-select-caret pi pi-chevron-down\"></span>\n </div>\n } @else {\n <div class=\"rts-storyteller-hint\">There are no agentic members in this chat. Add an agent to continue.</div>\n }\n </label>\n\n @if (workflow.submitError) {\n <div class=\"rts-storyteller-error\">{{ workflow.submitError }}</div>\n }\n\n @if (step.isActive) {\n <div class=\"rts-storyteller-actions rts-st-translator-actions\">\n @if (canCancel) {\n <button class=\"rts-st-cancel\"\n type=\"button\"\n id=\"story-translator-cancel-{{ step.id }}\"\n (click)=\"cancel()\"\n >Cancel</button>\n }\n <button class=\"rts-storyteller-submit\"\n type=\"submit\"\n [disabled]=\"workflow.submitInProgress || !agents.length\"\n id=\"story-translator-submit-{{ step.id }}\"\n >\n @if (workflow.submitInProgress) {\n <span class=\"rts-storyteller-spinner\"></span> Translating\u2026\n } @else {\n \uD83C\uDF10 Translate{{ selectedLanguage ? ' to ' + selectedLanguage.nativeName : '' }}\n }\n </button>\n </div>\n }\n</form>\n", styles: [":host{display:block;--rts-st-accent: #8b5cf6;--rts-st-accent-2: #6366f1}.rts-storyteller-form{display:flex;flex-direction:column;gap:12px}.rts-storyteller-field{display:flex;flex-direction:column;gap:5px}.rts-storyteller-label{display:flex;justify-content:space-between;align-items:baseline;font-size:10px;font-weight:700;letter-spacing:.08em;text-transform:uppercase;color:var(--rts-text-secondary, #8a8fa3)}.rts-storyteller-counter{font-weight:500;letter-spacing:0;text-transform:none}.rts-storyteller-input{width:100%;box-sizing:border-box;padding:9px 12px;border-radius:9px;border:1px solid var(--rts-border-dim, rgba(120, 120, 160, .25));background:var(--rts-bg-primary, rgba(127, 127, 160, .06));color:var(--rts-text-primary);font:inherit;font-size:13px;outline:none;transition:border-color .15s ease,box-shadow .15s ease,background .15s ease}.rts-storyteller-input:focus{border-color:var(--rts-st-accent);box-shadow:0 0 0 3px #8b5cf62e}.rts-storyteller-input:disabled{opacity:.75;cursor:default}.rts-storyteller-input.rts-storyteller-invalid{border-color:#f43f5e;box-shadow:0 0 0 3px #f43f5e1f}.rts-storyteller-textarea{min-height:80px;resize:vertical;line-height:1.5}.rts-storyteller-textarea-lg{min-height:160px;max-height:480px}.rts-storyteller-select-wrap{position:relative}.rts-storyteller-select{appearance:none;padding-right:32px;cursor:pointer}.rts-storyteller-select-caret{position:absolute;right:12px;top:50%;transform:translateY(-50%);font-size:10px;pointer-events:none;color:var(--rts-text-secondary, #8a8fa3)}.rts-storyteller-hint{font-size:12px;font-style:italic;color:var(--rts-text-secondary, #8a8fa3)}.rts-storyteller-error{padding:6px 10px;border-radius:6px;font-size:12px;color:#f43f5e;background:#f43f5e14;border:1px solid rgba(244,63,94,.25)}.rts-storyteller-actions{display:flex;justify-content:flex-end}.rts-storyteller-submit{display:inline-flex;align-items:center;gap:6px;padding:8px 18px;border:none;border-radius:9px;color:#fff;font-weight:600;font-size:13px;cursor:pointer;background:linear-gradient(135deg,var(--rts-st-accent-2),var(--rts-st-accent) 60%,#a78bfa);background-size:160% 160%;box-shadow:0 4px 14px -4px #6366f199;transition:transform .15s ease,box-shadow .15s ease,background-position .4s ease}.rts-storyteller-submit:hover:not(:disabled){transform:translateY(-1px);background-position:100% 0;box-shadow:0 6px 18px -4px #6366f1bf}.rts-storyteller-submit:disabled{opacity:.6;cursor:default;box-shadow:none}.rts-storyteller-spinner{width:11px;height:11px;border-radius:50%;border:2px solid rgba(255,255,255,.35);border-top-color:#fff;animation:rts-st-spin .8s linear infinite}.rts-story-link-card{display:flex;align-items:center;gap:12px;padding:14px;border-radius:12px;text-decoration:none;color:inherit;background:linear-gradient(135deg,#6366f11a,#8b5cf60f);border:1px solid rgba(139,92,246,.3);transition:transform .2s ease,box-shadow .2s ease,border-color .2s ease;animation:rts-st-pop .4s cubic-bezier(.2,.9,.3,1.3)}.rts-story-link-card:hover{transform:translateY(-2px);border-color:var(--rts-st-accent);box-shadow:0 10px 28px -12px #8b5cf699}.rts-story-link-card:hover .rts-story-link-arrow{transform:translate(3px)}.rts-story-link-icon{display:flex;align-items:center;justify-content:center;flex-shrink:0;width:42px;height:42px;border-radius:12px;font-size:20px;background:linear-gradient(135deg,var(--rts-st-accent-2),var(--rts-st-accent))}.rts-story-link-body{display:flex;flex-direction:column;gap:2px;min-width:0;flex:1}.rts-story-link-kicker{font-size:10px;font-weight:700;letter-spacing:.08em;text-transform:uppercase;color:var(--rts-st-accent)}.rts-story-link-title{font-size:15px;font-weight:600;color:var(--rts-text-primary)}.rts-story-link-description{font-size:12px;color:var(--rts-text-secondary, #8a8fa3);display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}.rts-story-link-meta{font-size:11px;color:var(--rts-text-secondary, #8a8fa3)}.rts-story-link-arrow{color:var(--rts-st-accent);transition:transform .2s ease}@keyframes rts-st-spin{to{transform:rotate(360deg)}}@keyframes rts-st-pop{0%{opacity:0;transform:scale(.96)}to{opacity:1;transform:none}}\n", ".rts-st-autocomplete{position:relative}.rts-st-autocomplete-icon{position:absolute;left:12px;top:50%;transform:translateY(-50%);font-size:12px;pointer-events:none;color:var(--rts-text-secondary, #8a8fa3)}.rts-st-autocomplete-input{padding-left:32px;padding-right:32px}.rts-st-autocomplete-input.rts-st-autocomplete-selected{font-weight:600}.rts-st-autocomplete-clear{position:absolute;right:8px;top:50%;transform:translateY(-50%);width:22px;height:22px;padding:0;border:none;border-radius:50%;font-size:10px;cursor:pointer;color:var(--rts-text-secondary, #8a8fa3);background:transparent;transition:background .15s ease,color .15s ease}.rts-st-autocomplete-clear:hover{color:var(--rts-text-primary);background:#8b5cf626}.rts-st-autocomplete-panel{position:absolute;z-index:20;left:0;right:0;top:calc(100% + 4px);max-height:240px;margin:0;padding:4px;overflow-y:auto;list-style:none;border-radius:10px;border:1px solid var(--rts-border-dim, rgba(120, 120, 160, .25));background:var(--rts-bg-secondary, var(--rts-bg-primary, #1d1f2b));-webkit-backdrop-filter:blur(12px);backdrop-filter:blur(12px);box-shadow:0 14px 32px -12px #00000073;animation:rts-st-panel-in .14s ease-out}.rts-st-autocomplete-option{display:flex;align-items:center;gap:10px;padding:6px 8px;border-radius:7px;cursor:pointer;transition:background .12s ease}.rts-st-autocomplete-option.rts-st-autocomplete-option-active{background:linear-gradient(135deg,#6366f129,#8b5cf61a)}.rts-st-autocomplete-option[aria-selected=true] .rts-st-autocomplete-option-name{color:var(--rts-st-accent)}.rts-st-autocomplete-thumb{flex-shrink:0;width:30px;height:30px;border-radius:7px;object-fit:cover}.rts-st-autocomplete-thumb-empty{display:flex;align-items:center;justify-content:center;font-size:15px;background:#8b5cf624}.rts-st-autocomplete-option-body{display:flex;flex-direction:column;min-width:0}.rts-st-autocomplete-option-name{overflow:hidden;white-space:nowrap;text-overflow:ellipsis;font-size:13px;font-weight:500;color:var(--rts-text-primary)}.rts-st-autocomplete-option-meta{font-size:11px;color:var(--rts-text-secondary, #8a8fa3)}.rts-st-autocomplete-empty{padding:8px;font-size:12px;font-style:italic;color:var(--rts-text-secondary, #8a8fa3)}.rts-st-autocomplete-error{font-style:normal;color:#f43f5e}.rts-st-translator-actions{gap:8px}.rts-st-cancel{padding:8px 16px;border-radius:9px;border:1px solid var(--rts-border-dim, rgba(120, 120, 160, .25));background:transparent;color:var(--rts-text-secondary, #8a8fa3);font:inherit;font-size:13px;font-weight:600;cursor:pointer;transition:color .15s ease,border-color .15s ease,background .15s ease}.rts-st-cancel:hover{color:var(--rts-text-primary);border-color:#f43f5e73;background:#f43f5e0f}@keyframes rts-st-panel-in{0%{opacity:0;transform:translateY(-4px)}to{opacity:1;transform:none}}\n"], dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.SelectControlValueAccessor, selector: "select:not([multiple])[formControlName],select:not([multiple])[formControl],select:not([multiple])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }] }); }
2611
+ }
2612
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.4", ngImport: i0, type: StoryTranslatorFormStepComponent, decorators: [{
2613
+ type: Component,
2614
+ args: [{ selector: 'rtsee-story-translator-form-step', standalone: true, imports: [ReactiveFormsModule], template: "<form class=\"rts-storyteller-form\" [formGroup]=\"form\" (ngSubmit)=\"submit()\" autocomplete=\"off\">\n <div class=\"rts-storyteller-field\">\n <label class=\"rts-storyteller-label\" for=\"story-translator-story-{{ step.id }}\">Story</label>\n <div class=\"rts-st-autocomplete\">\n <span class=\"rts-st-autocomplete-icon pi\" [class.pi-search]=\"!searchInProgress\" [class.pi-spin]=\"searchInProgress\" [class.pi-spinner]=\"searchInProgress\"></span>\n <input #storyInput\n class=\"rts-storyteller-input rts-st-autocomplete-input\"\n [class.rts-storyteller-invalid]=\"isInvalid('storyId')\"\n [class.rts-st-autocomplete-selected]=\"!!selectedStory\"\n formControlName=\"storyQuery\"\n type=\"text\"\n role=\"combobox\"\n aria-autocomplete=\"list\"\n [attr.aria-expanded]=\"suggestionsOpened\"\n placeholder=\"Search stories by name\u2026\"\n id=\"story-translator-story-{{ step.id }}\"\n (focus)=\"onStoryFocus()\"\n (input)=\"onStoryInput()\"\n (keydown)=\"onStoryKeydown($event)\"\n />\n @if (selectedStory && !readonly) {\n <button type=\"button\"\n class=\"rts-st-autocomplete-clear pi pi-times\"\n aria-label=\"Clear story\"\n id=\"story-translator-clear-{{ step.id }}\"\n (click)=\"clearStory()\"\n ></button>\n }\n\n @if (suggestionsOpened && !readonly) {\n <ul class=\"rts-st-autocomplete-panel\" role=\"listbox\">\n @if (searchError) {\n <li class=\"rts-st-autocomplete-empty rts-st-autocomplete-error\">{{ searchError }}</li>\n } @else if (!suggestions.length && !searchInProgress) {\n <li class=\"rts-st-autocomplete-empty\">No stories found</li>\n }\n @for (story of suggestions; track story.id; let i = $index) {\n <li class=\"rts-st-autocomplete-option\"\n role=\"option\"\n [class.rts-st-autocomplete-option-active]=\"i === highlightedIndex\"\n [attr.aria-selected]=\"story.id === selectedStory?.id\"\n (mouseenter)=\"highlightedIndex = i\"\n (mousedown)=\"$event.preventDefault(); selectStory(story)\"\n >\n @if (story.imageUrl) {\n <img class=\"rts-st-autocomplete-thumb\" [src]=\"story.imageUrl\" [alt]=\"story.name\" loading=\"lazy\" />\n } @else {\n <span class=\"rts-st-autocomplete-thumb rts-st-autocomplete-thumb-empty\">\uD83D\uDCD6</span>\n }\n <span class=\"rts-st-autocomplete-option-body\">\n <span class=\"rts-st-autocomplete-option-name\">{{ story.name }}</span>\n @if (story.totalSlides) {\n <span class=\"rts-st-autocomplete-option-meta\">{{ story.totalSlides }} slides</span>\n }\n </span>\n </li>\n }\n </ul>\n }\n </div>\n </div>\n\n <label class=\"rts-storyteller-field\">\n <span class=\"rts-storyteller-label\">Translate to</span>\n <div class=\"rts-storyteller-select-wrap\">\n <select class=\"rts-storyteller-input rts-storyteller-select\"\n [class.rts-storyteller-invalid]=\"isInvalid('language')\"\n formControlName=\"language\"\n id=\"story-translator-language-{{ step.id }}\"\n >\n <option value=\"\" disabled>Select a language</option>\n @for (language of languages; track language.code) {\n <option [value]=\"language.code\">{{ language.nativeName }}</option>\n }\n </select>\n <span class=\"rts-storyteller-select-caret pi pi-chevron-down\"></span>\n </div>\n </label>\n\n <label class=\"rts-storyteller-field\">\n <span class=\"rts-storyteller-label\">Agent</span>\n @if (agents.length) {\n <div class=\"rts-storyteller-select-wrap\">\n <select class=\"rts-storyteller-input rts-storyteller-select\"\n [class.rts-storyteller-invalid]=\"isInvalid('agentId')\"\n formControlName=\"agentId\"\n id=\"story-translator-agent-{{ step.id }}\"\n >\n <option value=\"\" disabled>Select an agent</option>\n @for (agent of agents; track agent.id) {\n <option [value]=\"agent.id\">\n {{ agent.name }}{{ agent.agentConfiguration?.model?.model ? ' \u00B7 ' + agent.agentConfiguration?.model?.model : '' }}\n </option>\n }\n </select>\n <span class=\"rts-storyteller-select-caret pi pi-chevron-down\"></span>\n </div>\n } @else {\n <div class=\"rts-storyteller-hint\">There are no agentic members in this chat. Add an agent to continue.</div>\n }\n </label>\n\n @if (workflow.submitError) {\n <div class=\"rts-storyteller-error\">{{ workflow.submitError }}</div>\n }\n\n @if (step.isActive) {\n <div class=\"rts-storyteller-actions rts-st-translator-actions\">\n @if (canCancel) {\n <button class=\"rts-st-cancel\"\n type=\"button\"\n id=\"story-translator-cancel-{{ step.id }}\"\n (click)=\"cancel()\"\n >Cancel</button>\n }\n <button class=\"rts-storyteller-submit\"\n type=\"submit\"\n [disabled]=\"workflow.submitInProgress || !agents.length\"\n id=\"story-translator-submit-{{ step.id }}\"\n >\n @if (workflow.submitInProgress) {\n <span class=\"rts-storyteller-spinner\"></span> Translating\u2026\n } @else {\n \uD83C\uDF10 Translate{{ selectedLanguage ? ' to ' + selectedLanguage.nativeName : '' }}\n }\n </button>\n </div>\n }\n</form>\n", styles: [":host{display:block;--rts-st-accent: #8b5cf6;--rts-st-accent-2: #6366f1}.rts-storyteller-form{display:flex;flex-direction:column;gap:12px}.rts-storyteller-field{display:flex;flex-direction:column;gap:5px}.rts-storyteller-label{display:flex;justify-content:space-between;align-items:baseline;font-size:10px;font-weight:700;letter-spacing:.08em;text-transform:uppercase;color:var(--rts-text-secondary, #8a8fa3)}.rts-storyteller-counter{font-weight:500;letter-spacing:0;text-transform:none}.rts-storyteller-input{width:100%;box-sizing:border-box;padding:9px 12px;border-radius:9px;border:1px solid var(--rts-border-dim, rgba(120, 120, 160, .25));background:var(--rts-bg-primary, rgba(127, 127, 160, .06));color:var(--rts-text-primary);font:inherit;font-size:13px;outline:none;transition:border-color .15s ease,box-shadow .15s ease,background .15s ease}.rts-storyteller-input:focus{border-color:var(--rts-st-accent);box-shadow:0 0 0 3px #8b5cf62e}.rts-storyteller-input:disabled{opacity:.75;cursor:default}.rts-storyteller-input.rts-storyteller-invalid{border-color:#f43f5e;box-shadow:0 0 0 3px #f43f5e1f}.rts-storyteller-textarea{min-height:80px;resize:vertical;line-height:1.5}.rts-storyteller-textarea-lg{min-height:160px;max-height:480px}.rts-storyteller-select-wrap{position:relative}.rts-storyteller-select{appearance:none;padding-right:32px;cursor:pointer}.rts-storyteller-select-caret{position:absolute;right:12px;top:50%;transform:translateY(-50%);font-size:10px;pointer-events:none;color:var(--rts-text-secondary, #8a8fa3)}.rts-storyteller-hint{font-size:12px;font-style:italic;color:var(--rts-text-secondary, #8a8fa3)}.rts-storyteller-error{padding:6px 10px;border-radius:6px;font-size:12px;color:#f43f5e;background:#f43f5e14;border:1px solid rgba(244,63,94,.25)}.rts-storyteller-actions{display:flex;justify-content:flex-end}.rts-storyteller-submit{display:inline-flex;align-items:center;gap:6px;padding:8px 18px;border:none;border-radius:9px;color:#fff;font-weight:600;font-size:13px;cursor:pointer;background:linear-gradient(135deg,var(--rts-st-accent-2),var(--rts-st-accent) 60%,#a78bfa);background-size:160% 160%;box-shadow:0 4px 14px -4px #6366f199;transition:transform .15s ease,box-shadow .15s ease,background-position .4s ease}.rts-storyteller-submit:hover:not(:disabled){transform:translateY(-1px);background-position:100% 0;box-shadow:0 6px 18px -4px #6366f1bf}.rts-storyteller-submit:disabled{opacity:.6;cursor:default;box-shadow:none}.rts-storyteller-spinner{width:11px;height:11px;border-radius:50%;border:2px solid rgba(255,255,255,.35);border-top-color:#fff;animation:rts-st-spin .8s linear infinite}.rts-story-link-card{display:flex;align-items:center;gap:12px;padding:14px;border-radius:12px;text-decoration:none;color:inherit;background:linear-gradient(135deg,#6366f11a,#8b5cf60f);border:1px solid rgba(139,92,246,.3);transition:transform .2s ease,box-shadow .2s ease,border-color .2s ease;animation:rts-st-pop .4s cubic-bezier(.2,.9,.3,1.3)}.rts-story-link-card:hover{transform:translateY(-2px);border-color:var(--rts-st-accent);box-shadow:0 10px 28px -12px #8b5cf699}.rts-story-link-card:hover .rts-story-link-arrow{transform:translate(3px)}.rts-story-link-icon{display:flex;align-items:center;justify-content:center;flex-shrink:0;width:42px;height:42px;border-radius:12px;font-size:20px;background:linear-gradient(135deg,var(--rts-st-accent-2),var(--rts-st-accent))}.rts-story-link-body{display:flex;flex-direction:column;gap:2px;min-width:0;flex:1}.rts-story-link-kicker{font-size:10px;font-weight:700;letter-spacing:.08em;text-transform:uppercase;color:var(--rts-st-accent)}.rts-story-link-title{font-size:15px;font-weight:600;color:var(--rts-text-primary)}.rts-story-link-description{font-size:12px;color:var(--rts-text-secondary, #8a8fa3);display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}.rts-story-link-meta{font-size:11px;color:var(--rts-text-secondary, #8a8fa3)}.rts-story-link-arrow{color:var(--rts-st-accent);transition:transform .2s ease}@keyframes rts-st-spin{to{transform:rotate(360deg)}}@keyframes rts-st-pop{0%{opacity:0;transform:scale(.96)}to{opacity:1;transform:none}}\n", ".rts-st-autocomplete{position:relative}.rts-st-autocomplete-icon{position:absolute;left:12px;top:50%;transform:translateY(-50%);font-size:12px;pointer-events:none;color:var(--rts-text-secondary, #8a8fa3)}.rts-st-autocomplete-input{padding-left:32px;padding-right:32px}.rts-st-autocomplete-input.rts-st-autocomplete-selected{font-weight:600}.rts-st-autocomplete-clear{position:absolute;right:8px;top:50%;transform:translateY(-50%);width:22px;height:22px;padding:0;border:none;border-radius:50%;font-size:10px;cursor:pointer;color:var(--rts-text-secondary, #8a8fa3);background:transparent;transition:background .15s ease,color .15s ease}.rts-st-autocomplete-clear:hover{color:var(--rts-text-primary);background:#8b5cf626}.rts-st-autocomplete-panel{position:absolute;z-index:20;left:0;right:0;top:calc(100% + 4px);max-height:240px;margin:0;padding:4px;overflow-y:auto;list-style:none;border-radius:10px;border:1px solid var(--rts-border-dim, rgba(120, 120, 160, .25));background:var(--rts-bg-secondary, var(--rts-bg-primary, #1d1f2b));-webkit-backdrop-filter:blur(12px);backdrop-filter:blur(12px);box-shadow:0 14px 32px -12px #00000073;animation:rts-st-panel-in .14s ease-out}.rts-st-autocomplete-option{display:flex;align-items:center;gap:10px;padding:6px 8px;border-radius:7px;cursor:pointer;transition:background .12s ease}.rts-st-autocomplete-option.rts-st-autocomplete-option-active{background:linear-gradient(135deg,#6366f129,#8b5cf61a)}.rts-st-autocomplete-option[aria-selected=true] .rts-st-autocomplete-option-name{color:var(--rts-st-accent)}.rts-st-autocomplete-thumb{flex-shrink:0;width:30px;height:30px;border-radius:7px;object-fit:cover}.rts-st-autocomplete-thumb-empty{display:flex;align-items:center;justify-content:center;font-size:15px;background:#8b5cf624}.rts-st-autocomplete-option-body{display:flex;flex-direction:column;min-width:0}.rts-st-autocomplete-option-name{overflow:hidden;white-space:nowrap;text-overflow:ellipsis;font-size:13px;font-weight:500;color:var(--rts-text-primary)}.rts-st-autocomplete-option-meta{font-size:11px;color:var(--rts-text-secondary, #8a8fa3)}.rts-st-autocomplete-empty{padding:8px;font-size:12px;font-style:italic;color:var(--rts-text-secondary, #8a8fa3)}.rts-st-autocomplete-error{font-style:normal;color:#f43f5e}.rts-st-translator-actions{gap:8px}.rts-st-cancel{padding:8px 16px;border-radius:9px;border:1px solid var(--rts-border-dim, rgba(120, 120, 160, .25));background:transparent;color:var(--rts-text-secondary, #8a8fa3);font:inherit;font-size:13px;font-weight:600;cursor:pointer;transition:color .15s ease,border-color .15s ease,background .15s ease}.rts-st-cancel:hover{color:var(--rts-text-primary);border-color:#f43f5e73;background:#f43f5e0f}@keyframes rts-st-panel-in{0%{opacity:0;transform:translateY(-4px)}to{opacity:1;transform:none}}\n"] }]
2615
+ }], ctorParameters: () => [{ type: i0.ChangeDetectorRef }, { type: i0.ElementRef }], propDecorators: { step: [{
2616
+ type: Input
2617
+ }], workflow: [{
2618
+ type: Input
2619
+ }], chat: [{
2620
+ type: Input
2621
+ }], messenger: [{
2622
+ type: Input
2623
+ }], storyInput: [{
2624
+ type: ViewChild,
2625
+ args: ['storyInput']
2626
+ }], onDocumentMouseDown: [{
2627
+ type: HostListener,
2628
+ args: ['document:mousedown', ['$event']]
2629
+ }] } });
2630
+
2295
2631
  /**
2296
2632
  * Registry of the components which render workflow steps inside the rts-workflow widget.
2297
2633
  */
@@ -2301,6 +2637,7 @@ class WorkflowStepsService {
2301
2637
  { name: 'workflow-task-step', component: WorkflowTaskStepComponent },
2302
2638
  { name: 'storyteller-form-step', component: StorytellerFormStepComponent },
2303
2639
  { name: 'storyteller-story-link-step', component: StorytellerStoryLinkStepComponent },
2640
+ { name: 'story-translator-form-step', component: StoryTranslatorFormStepComponent },
2304
2641
  ];
2305
2642
  }
2306
2643
  register(widget) {
@@ -2476,8 +2813,22 @@ class MessageComponent {
2476
2813
  this.widgetsService = widgetsService;
2477
2814
  this.componentFactoryResolver = componentFactoryResolver;
2478
2815
  this.timeFormatService = timeFormatService;
2816
+ this.widgetRef = null;
2817
+ this.widgetHasMessageInput = false;
2479
2818
  }
2480
2819
  ngOnInit() { }
2820
+ ngOnChanges(changes) {
2821
+ // The message instance can be replaced (e.g. images got downsized versions) - forward it to the widget
2822
+ if (changes['message'] && !changes['message'].firstChange && this.widgetRef) {
2823
+ if (this.widgetHasMessageInput) {
2824
+ this.widgetRef.setInput('message', this.message);
2825
+ }
2826
+ else {
2827
+ this.widgetRef.instance.message = this.message;
2828
+ this.widgetRef.changeDetectorRef.markForCheck();
2829
+ }
2830
+ }
2831
+ }
2481
2832
  ngAfterViewInit() {
2482
2833
  /* eslint-disable */
2483
2834
  if (this.message.widget) {
@@ -2493,11 +2844,13 @@ class MessageComponent {
2493
2844
  componentRef.instance.messenger = this.messenger;
2494
2845
  componentRef.instance.chat = this.chat;
2495
2846
  componentRef.changeDetectorRef.detectChanges();
2847
+ this.widgetRef = componentRef;
2848
+ this.widgetHasMessageInput = !!reflectComponentType(widget)?.inputs.some((input) => input.propName === 'message');
2496
2849
  }
2497
2850
  /* eslint-enable */
2498
2851
  }
2499
2852
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.4", ngImport: i0, type: MessageComponent, deps: [{ token: MessageWidgetsService }, { token: i0.ComponentFactoryResolver }, { token: TimeFormatHelperService }], target: i0.ɵɵFactoryTarget.Component }); }
2500
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.4", type: MessageComponent, isStandalone: true, selector: "rtsee-message", inputs: { chat: "chat", message: "message", member: "member", messenger: "messenger" }, viewQueries: [{ propertyName: "widgetHost", first: true, predicate: WidgetDirective, descendants: true }], ngImport: i0, template: "<div class=\"rts-message-container\" #rtsMessageContainer\n [ngClass]=\"{'rts-message-from-me': message.from.id === messenger.clientId}\"\n>\n <div class=\"rts-messenger-message\">\n @if (message.widget) {\n <ng-template widgetHost></ng-template>\n }\n @if (!message.widget) {\n <p class=\"rts-messenger-message-text\"\n >{{ message.text }}</p>\n }\n <div class=\"rts-messenger-message-info\" #rtsMessageInfo>\n <rtsee-message-time-and-status [message]=\"message\"\n [inChat]=\"true\"\n [hideStatus]=\"message.from.id !== messenger.clientId\"\n ></rtsee-message-time-and-status>\n </div>\n </div>\n</div>\n", dependencies: [{ kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "component", type: MessageTimeAndStatusComponent, selector: "rtsee-message-time-and-status", inputs: ["message", "hideStatus", "inChat"] }, { kind: "directive", type: WidgetDirective, selector: "[widgetHost]" }] }); }
2853
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.4", type: MessageComponent, isStandalone: true, selector: "rtsee-message", inputs: { chat: "chat", message: "message", member: "member", messenger: "messenger" }, viewQueries: [{ propertyName: "widgetHost", first: true, predicate: WidgetDirective, descendants: true }], usesOnChanges: true, ngImport: i0, template: "<div class=\"rts-message-container\" #rtsMessageContainer\n [ngClass]=\"{'rts-message-from-me': message.from.id === messenger.clientId}\"\n>\n <div class=\"rts-messenger-message\">\n @if (message.widget) {\n <ng-template widgetHost></ng-template>\n }\n @if (!message.widget) {\n <p class=\"rts-messenger-message-text\"\n >{{ message.text }}</p>\n }\n <div class=\"rts-messenger-message-info\" #rtsMessageInfo>\n <rtsee-message-time-and-status [message]=\"message\"\n [inChat]=\"true\"\n [hideStatus]=\"message.from.id !== messenger.clientId\"\n ></rtsee-message-time-and-status>\n </div>\n </div>\n</div>\n", dependencies: [{ kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "component", type: MessageTimeAndStatusComponent, selector: "rtsee-message-time-and-status", inputs: ["message", "hideStatus", "inChat"] }, { kind: "directive", type: WidgetDirective, selector: "[widgetHost]" }] }); }
2501
2854
  }
2502
2855
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.4", ngImport: i0, type: MessageComponent, decorators: [{
2503
2856
  type: Component,
@@ -3859,11 +4212,11 @@ class StoryPlayerComponent {
3859
4212
  this.story.initSwiper(swiper);
3860
4213
  }
3861
4214
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.4", ngImport: i0, type: StoryPlayerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
3862
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.4", type: StoryPlayerComponent, isStandalone: true, selector: "rtsee-story-player", inputs: { story: "story", settings: "settings", presentation: "presentation" }, viewQueries: [{ propertyName: "swiperEl", first: true, predicate: ["swiperContainer"], descendants: true }], ngImport: i0, template: "<div class=\"rtsee-story-player\">\n <div class=\"rtsee-story-player-slider swiper\" #swiperContainer>\n <div class=\"rtsee-story-player-slider-inner swiper-wrapper\">\n @for (slide of story.slides; track slide.id) {\n <div class=\"story-player-slide-container swiper-slide\"\n [ngClass]=\"{'story-player-slide-active': slide.isActive}\"\n >\n <rtsee-slide [slide]=\"slide\"\n [story]=\"story\"\n [presentation]=\"presentation\"\n [settings]=\"settings\"\n ></rtsee-slide>\n </div>\n }\n </div>\n </div>\n</div>\n", dependencies: [{ kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "component", type: SlideComponent, selector: "rtsee-slide", inputs: ["presentation", "story", "slide", "settings"] }] }); }
4215
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.4", type: StoryPlayerComponent, isStandalone: true, selector: "rtsee-story-player", inputs: { story: "story", settings: "settings", presentation: "presentation" }, viewQueries: [{ propertyName: "swiperEl", first: true, predicate: ["swiperContainer"], descendants: true }], ngImport: i0, template: "<div class=\"rtsee-story-player\">\n <div class=\"rtsee-story-player-slider swiper\" #swiperContainer>\n <div class=\"rtsee-story-player-slider-inner swiper-wrapper\">\n @for (slide of story.slides; track (slide.id || slide.slideNumber)) {\n <div class=\"story-player-slide-container swiper-slide\"\n [ngClass]=\"{'story-player-slide-active': slide.isActive}\"\n >\n <rtsee-slide [slide]=\"slide\"\n [story]=\"story\"\n [presentation]=\"presentation\"\n [settings]=\"settings\"\n ></rtsee-slide>\n </div>\n }\n </div>\n </div>\n</div>\n", dependencies: [{ kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "component", type: SlideComponent, selector: "rtsee-slide", inputs: ["presentation", "story", "slide", "settings"] }] }); }
3863
4216
  }
3864
4217
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.4", ngImport: i0, type: StoryPlayerComponent, decorators: [{
3865
4218
  type: Component,
3866
- args: [{ selector: 'rtsee-story-player', imports: [NgClass, SlideComponent], standalone: true, template: "<div class=\"rtsee-story-player\">\n <div class=\"rtsee-story-player-slider swiper\" #swiperContainer>\n <div class=\"rtsee-story-player-slider-inner swiper-wrapper\">\n @for (slide of story.slides; track slide.id) {\n <div class=\"story-player-slide-container swiper-slide\"\n [ngClass]=\"{'story-player-slide-active': slide.isActive}\"\n >\n <rtsee-slide [slide]=\"slide\"\n [story]=\"story\"\n [presentation]=\"presentation\"\n [settings]=\"settings\"\n ></rtsee-slide>\n </div>\n }\n </div>\n </div>\n</div>\n" }]
4219
+ args: [{ selector: 'rtsee-story-player', imports: [NgClass, SlideComponent], standalone: true, template: "<div class=\"rtsee-story-player\">\n <div class=\"rtsee-story-player-slider swiper\" #swiperContainer>\n <div class=\"rtsee-story-player-slider-inner swiper-wrapper\">\n @for (slide of story.slides; track (slide.id || slide.slideNumber)) {\n <div class=\"story-player-slide-container swiper-slide\"\n [ngClass]=\"{'story-player-slide-active': slide.isActive}\"\n >\n <rtsee-slide [slide]=\"slide\"\n [story]=\"story\"\n [presentation]=\"presentation\"\n [settings]=\"settings\"\n ></rtsee-slide>\n </div>\n }\n </div>\n </div>\n</div>\n" }]
3867
4220
  }], ctorParameters: () => [], propDecorators: { story: [{
3868
4221
  type: Input
3869
4222
  }], settings: [{
@@ -3961,6 +4314,11 @@ class StoryElementsSectionComponent {
3961
4314
  this.collapsed = false;
3962
4315
  this.canDetach = true;
3963
4316
  this.toggled = new EventEmitter();
4317
+ this.batchMode = false;
4318
+ this.selectedItemIds = new Set();
4319
+ this.batchAdditionalPrompt = '';
4320
+ this.generatingItemIds = new Set();
4321
+ this.activeGenerationListeners = new Map();
3964
4322
  this.onIllustratorChanged = () => {
3965
4323
  this.cdr.markForCheck();
3966
4324
  };
@@ -3969,6 +4327,124 @@ class StoryElementsSectionComponent {
3969
4327
  if (this._illustrator) {
3970
4328
  this._illustrator.off('changed', this.onIllustratorChanged);
3971
4329
  }
4330
+ for (const [requestId, listener] of this.activeGenerationListeners.entries()) {
4331
+ window.removeEventListener(RTSeeIllustrator.REQUESTED_GENERATION_SUCCEED, listener);
4332
+ }
4333
+ this.activeGenerationListeners.clear();
4334
+ }
4335
+ startBatchSelection(event) {
4336
+ event.stopPropagation();
4337
+ event.preventDefault();
4338
+ if (this.collapsed) {
4339
+ this.toggled.emit();
4340
+ }
4341
+ this.batchMode = true;
4342
+ this.selectedItemIds.clear();
4343
+ for (const item of this.editor.items) {
4344
+ const url = (item.imageUrl || '').toLowerCase();
4345
+ if (!url || url.includes('default')) {
4346
+ this.selectedItemIds.add(item.id);
4347
+ }
4348
+ }
4349
+ this.cdr.markForCheck();
4350
+ }
4351
+ cancelBatchSelection() {
4352
+ this.batchMode = false;
4353
+ this.selectedItemIds.clear();
4354
+ this.batchAdditionalPrompt = '';
4355
+ this.cdr.markForCheck();
4356
+ }
4357
+ toggleItemSelection(id) {
4358
+ if (this.selectedItemIds.has(id)) {
4359
+ this.selectedItemIds.delete(id);
4360
+ }
4361
+ else {
4362
+ this.selectedItemIds.add(id);
4363
+ }
4364
+ this.cdr.markForCheck();
4365
+ }
4366
+ isItemSelected(id) {
4367
+ return this.selectedItemIds.has(id);
4368
+ }
4369
+ isItemGenerating(id) {
4370
+ return this.generatingItemIds.has(id);
4371
+ }
4372
+ async submitBatchGeneration() {
4373
+ if (this.selectedItemIds.size === 0)
4374
+ return;
4375
+ const selectedItems = this.editor.items.filter((item) => this.selectedItemIds.has(item.id));
4376
+ const additionalPrompt = this.batchAdditionalPrompt.trim();
4377
+ this.batchMode = false;
4378
+ this.selectedItemIds.clear();
4379
+ this.batchAdditionalPrompt = '';
4380
+ this.cdr.markForCheck();
4381
+ for (const item of selectedItems) {
4382
+ const requestId = RTSeeCore.uuidv4();
4383
+ this.generatingItemIds.add(item.id);
4384
+ const listener = async (e) => {
4385
+ const customEvent = e;
4386
+ if (customEvent.detail?.requestId === requestId && customEvent.detail?.imageUrl) {
4387
+ window.removeEventListener(RTSeeIllustrator.REQUESTED_GENERATION_SUCCEED, listener);
4388
+ this.activeGenerationListeners.delete(requestId);
4389
+ this.generatingItemIds.delete(item.id);
4390
+ const cleanUrl = sanitizeImageUrl(customEvent.detail.imageUrl);
4391
+ try {
4392
+ item.imageUrl = cleanUrl;
4393
+ await item.save({ imageUrl: cleanUrl });
4394
+ if (this.editor.expanded && this.editor.expanded.id === item.id && this.editor.form) {
4395
+ const imageField = this.editor.form.fields.find((f) => f.name === 'imageUrl' || f.type === 'image-uploader');
4396
+ if (imageField) {
4397
+ imageField.value = cleanUrl;
4398
+ if (imageField.imageUploader) {
4399
+ imageField.imageUploader.uploadedPhotoPreview = cleanUrl;
4400
+ imageField.imageUploader.value = cleanUrl;
4401
+ }
4402
+ }
4403
+ }
4404
+ }
4405
+ catch (err) {
4406
+ console.error(`Failed to apply generated image to ${this.editor.label}:`, err);
4407
+ }
4408
+ finally {
4409
+ this.cdr.markForCheck();
4410
+ }
4411
+ }
4412
+ };
4413
+ window.addEventListener(RTSeeIllustrator.REQUESTED_GENERATION_SUCCEED, listener);
4414
+ this.activeGenerationListeners.set(requestId, listener);
4415
+ const promptParts = [];
4416
+ promptParts.push(`${this.editor.label}: ${item.name || ''}`);
4417
+ if (item.visualDescription) {
4418
+ promptParts.push(`Visual appearance: ${item.visualDescription}`);
4419
+ }
4420
+ if (item.description && item.description !== item.visualDescription) {
4421
+ promptParts.push(`Description/Role: ${item.description}`);
4422
+ }
4423
+ if (additionalPrompt) {
4424
+ promptParts.push(`Additional prompt: ${additionalPrompt}`);
4425
+ }
4426
+ const entityPrompt = promptParts.join('\n');
4427
+ const config = this.illustrator?.config || {
4428
+ aspectRatio: '1:1',
4429
+ style: 'Classic Fairy Tale pixel-art Storybook',
4430
+ mood: 'Dreamy & Magical',
4431
+ lighting: 'Soft Diffused Storybook Glow',
4432
+ negativePrompt: 'modern elements, cars, text, watermark, signature, blurry, low quality',
4433
+ numberOfImages: 1,
4434
+ };
4435
+ const payload = {
4436
+ chatId: this.illustrator?.activeChatId || undefined,
4437
+ agentId: this.illustrator?.activeAgentId || '',
4438
+ prompt: entityPrompt,
4439
+ config: { ...config },
4440
+ contextImages: [],
4441
+ requestId,
4442
+ };
4443
+ window.dispatchEvent(new CustomEvent(RTSeeIllustratorEvents.SUBMIT_PROMPT, {
4444
+ detail: payload,
4445
+ }));
4446
+ }
4447
+ this.cdr.markForCheck();
3972
4448
  }
3973
4449
  async applyReviewedImageToItem(event, item) {
3974
4450
  event.stopPropagation();
@@ -3988,6 +4464,11 @@ class StoryElementsSectionComponent {
3988
4464
  }
3989
4465
  }
3990
4466
  }
4467
+ // Reset illustrator state and UI
4468
+ this._illustrator?.resetState();
4469
+ if (typeof window !== 'undefined') {
4470
+ window.dispatchEvent(new CustomEvent(RTSeeIllustratorEvents.RESET_STATE));
4471
+ }
3991
4472
  }
3992
4473
  catch (e) {
3993
4474
  console.error(`Failed to apply image to ${this.editor.label}:`, e);
@@ -4019,11 +4500,11 @@ class StoryElementsSectionComponent {
4019
4500
  return item.id || `new-${index}`;
4020
4501
  }
4021
4502
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.4", ngImport: i0, type: StoryElementsSectionComponent, deps: [{ token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component }); }
4022
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.4", type: StoryElementsSectionComponent, isStandalone: true, selector: "rtsee-story-elements-section", inputs: { editor: "editor", title: "title", icon: "icon", collapsed: "collapsed", canDetach: "canDetach", illustrator: "illustrator" }, outputs: { toggled: "toggled" }, ngImport: i0, template: "<section class=\"rtsee-ise-section\" [ngClass]=\"{ 'rtsee-ise-section--collapsed': collapsed }\">\n <header class=\"rtsee-ise-section-header\">\n <button type=\"button\"\n class=\"rtsee-ise-section-toggle\"\n [attr.aria-expanded]=\"!collapsed\"\n [id]=\"'rtsee-ise-toggle-' + editor.section\"\n (click)=\"toggled.emit()\">\n <i class=\"rtsee-ise-section-icon\" [ngClass]=\"icon\"></i>\n <h3 class=\"rtsee-ise-section-title\">{{ title }}</h3>\n <span class=\"rtsee-ise-section-count\">{{ editor.items.length }}</span>\n <i class=\"pi pi-chevron-down rtsee-ise-chevron\"></i>\n </button>\n </header>\n\n @if (!collapsed) {\n <div class=\"rtsee-ise-section-body\">\n <div class=\"rtsee-ise-thumbs-grid\">\n @for (item of editor.items; track trackById($index, item)) {\n <div class=\"rtsee-ise-thumb-card-container\" style=\"position: relative; display: flex; flex-direction: column;\">\n <button type=\"button\"\n class=\"rtsee-ise-thumb\"\n [ngClass]=\"{ 'rtsee-ise-thumb--active': editor.isExpanded(item) }\"\n [id]=\"'rtsee-ise-' + editor.section + '-' + item.id\"\n (click)=\"editor.expand(item)\">\n <div class=\"rtsee-ise-thumb-image\">\n @if (item.imageUrl) {\n <img [src]=\"item.imageUrl\" [alt]=\"item.name\" loading=\"lazy\" />\n } @else {\n <i [ngClass]=\"icon\"></i>\n }\n </div>\n <div class=\"rtsee-ise-thumb-body\">\n <span class=\"rtsee-ise-thumb-name\">{{ item.name }}</span>\n <span class=\"rtsee-ise-thumb-description\">{{ item.getTruncatedDescription(90) }}</span>\n </div>\n </button>\n <button type=\"button\"\n class=\"rtsee-ise-btn-add-illustrator\"\n style=\"position: absolute; top: 8px; right: 8px;\"\n title=\"Add {{ item.name }} to Illustrator context\"\n (click)=\"addToIllustrator($event, item)\">\n <i class=\"pi pi-palette\"></i>\n <span>+\uD83C\uDFA8</span>\n </button>\n @if (illustrator?.reviewingGeneratedImage && illustrator?.currentGeneratedImageUrl) {\n <button type=\"button\"\n class=\"rtsee-ise-btn-quick-apply\"\n style=\"position: absolute; bottom: 8px; right: 8px;\"\n [disabled]=\"item.saveInProgress\"\n [title]=\"'Apply reviewed image to ' + item.name\"\n (click)=\"applyReviewedImageToItem($event, item)\">\n @if (item.saveInProgress) {\n <i class=\"pi pi-spin pi-spinner\"></i>\n } @else {\n <i class=\"pi pi-plus\"></i>\n }\n </button>\n }\n </div>\n }\n\n <button type=\"button\"\n class=\"rtsee-ise-thumb rtsee-ise-thumb--add\"\n [ngClass]=\"{ 'rtsee-ise-thumb--active': editor.isCreating }\"\n [id]=\"'rtsee-ise-add-' + editor.section\"\n (click)=\"editor.expandNew()\">\n <i class=\"pi pi-plus\"></i>\n <span>New {{ editor.label }}</span>\n </button>\n </div>\n\n @if (editor.expanded && editor.form) {\n <div class=\"rtsee-ise-expanded rtsee-form-container\">\n <div class=\"rtsee-ise-expanded-header\">\n <h4>{{ editor.expanded.isNew ? 'New ' + editor.label : editor.expanded.name }}</h4>\n <div style=\"display: flex; align-items: center; gap: 8px;\">\n @if (!editor.expanded.isNew) {\n <button type=\"button\"\n class=\"rtsee-ise-btn-add-illustrator\"\n title=\"Add to Illustrator context\"\n (click)=\"addToIllustrator($event, editor.expanded)\">\n <i class=\"pi pi-palette\"></i>\n <span>+\uD83C\uDFA8 Illustrator</span>\n </button>\n }\n <button type=\"button\" class=\"rtsee-ise-icon-button\" (click)=\"editor.collapse()\" aria-label=\"Close\">\n <i class=\"pi pi-times\"></i>\n </button>\n </div>\n </div>\n\n @if (editor.error) {\n <div class=\"rtsee-ise-error\">{{ editor.error }}</div>\n }\n\n <rtsee-form [form]=\"editor.form\" (formSubmit)=\"submit()\">\n <div class=\"rtsee-ise-actions\">\n <p-button type=\"submit\"\n [label]=\"editor.expanded.isNew ? 'Create' : 'Save'\"\n [loading]=\"editor.saveInProgress\"\n [disabled]=\"editor.busy\" />\n <p-button label=\"Cancel\"\n severity=\"secondary\"\n (click)=\"editor.collapse()\"\n [disabled]=\"editor.busy\" />\n @if (!editor.expanded.isNew) {\n <span class=\"rtsee-ise-actions-spacer\"></span>\n @if (canDetach) {\n <p-button label=\"Remove from story\"\n icon=\"pi pi-minus-circle\"\n severity=\"secondary\"\n [text]=\"true\"\n (click)=\"editor.detach(editor.expanded)\"\n [disabled]=\"editor.busy\" />\n }\n <p-button label=\"Delete\"\n icon=\"pi pi-trash\"\n severity=\"danger\"\n [text]=\"true\"\n [loading]=\"editor.removeInProgress\"\n (click)=\"editor.delete(editor.expanded)\"\n [disabled]=\"editor.busy\" />\n }\n </div>\n </rtsee-form>\n </div>\n }\n </div>\n }\n</section>\n", dependencies: [{ kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "component", type: Button, selector: "p-button", inputs: ["type", "iconPos", "icon", "badge", "label", "disabled", "loading", "loadingIcon", "raised", "rounded", "text", "plain", "severity", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "buttonProps", "autofocus", "fluid"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "component", type: FormComponent, selector: "rtsee-form", inputs: ["form"], outputs: ["formSubmit"] }] }); }
4503
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.4", type: StoryElementsSectionComponent, isStandalone: true, selector: "rtsee-story-elements-section", inputs: { editor: "editor", title: "title", icon: "icon", collapsed: "collapsed", canDetach: "canDetach", illustrator: "illustrator" }, outputs: { toggled: "toggled" }, ngImport: i0, template: "<section class=\"rtsee-ise-section\" [ngClass]=\"{ 'rtsee-ise-section--collapsed': collapsed }\">\n <header class=\"rtsee-ise-section-header\">\n <button type=\"button\"\n class=\"rtsee-ise-section-toggle\"\n [attr.aria-expanded]=\"!collapsed\"\n [id]=\"'rtsee-ise-toggle-' + editor.section\"\n (click)=\"toggled.emit()\">\n <i class=\"rtsee-ise-section-icon\" [ngClass]=\"icon\"></i>\n <h3 class=\"rtsee-ise-section-title\">{{ title }}</h3>\n <span class=\"rtsee-ise-section-count\">{{ editor.items.length }}</span>\n <i class=\"pi pi-chevron-down rtsee-ise-chevron\"></i>\n </button>\n @if (editor.items.length > 0 && !batchMode) {\n <button type=\"button\"\n class=\"rtsee-ise-btn-batch-generate\"\n (click)=\"startBatchSelection($event)\"\n [title]=\"'Batch generate images for ' + title\">\n <i class=\"pi pi-sparkles\"></i>\n <span>Generate Images</span>\n </button>\n }\n </header>\n\n @if (batchMode) {\n <div class=\"rtsee-ise-batch-panel\">\n <div class=\"rtsee-ise-batch-header\">\n <span class=\"rtsee-ise-batch-title\">\n <i class=\"pi pi-sparkles\"></i>\n Batch Generate for {{ title }} ({{ selectedItemIds.size }} of {{ editor.items.length }} selected)\n </span>\n <span class=\"rtsee-ise-batch-hint\">\n Click items to select/deselect. Entities with default images are auto-selected.\n </span>\n </div>\n\n <textarea\n class=\"rtsee-ise-batch-prompt-input\"\n rows=\"2\"\n [(ngModel)]=\"batchAdditionalPrompt\"\n placeholder=\"Additional prompt instructions for all selected {{ title }} (optional)...\"\n ></textarea>\n\n <div class=\"rtsee-ise-batch-actions\">\n <button\n type=\"button\"\n class=\"rtsee-ise-btn-batch-cancel\"\n (click)=\"cancelBatchSelection()\"\n >\n Cancel\n </button>\n <button\n type=\"button\"\n class=\"rtsee-ise-btn-batch-submit\"\n [disabled]=\"selectedItemIds.size === 0\"\n (click)=\"submitBatchGeneration()\"\n >\n <i class=\"pi pi-sparkles\"></i>\n <span>Generate {{ selectedItemIds.size }} image{{ selectedItemIds.size === 1 ? '' : 's' }}</span>\n </button>\n </div>\n </div>\n }\n\n @if (!collapsed) {\n <div class=\"rtsee-ise-section-body\">\n <div class=\"rtsee-ise-thumbs-grid\">\n @for (item of editor.items; track trackById($index, item)) {\n <div class=\"rtsee-ise-thumb-card-container\" style=\"position: relative; display: flex; flex-direction: column;\">\n <button type=\"button\"\n class=\"rtsee-ise-thumb\"\n [ngClass]=\"{\n 'rtsee-ise-thumb--active': editor.isExpanded(item),\n 'rtsee-ise-thumb-card--selected': batchMode && isItemSelected(item.id)\n }\"\n [id]=\"'rtsee-ise-' + editor.section + '-' + item.id\"\n (click)=\"batchMode ? toggleItemSelection(item.id) : editor.expand(item)\">\n <div class=\"rtsee-ise-thumb-image\">\n @if (item.imageUrl) {\n <img [src]=\"item.imageUrl\" [alt]=\"item.name\" loading=\"lazy\" />\n } @else {\n <i [ngClass]=\"icon\"></i>\n }\n @if (isItemGenerating(item.id)) {\n <div class=\"rtsee-ise-thumb-generating-overlay\">\n <i class=\"pi pi-spin pi-spinner\"></i>\n <span>Generating\u2026</span>\n </div>\n }\n </div>\n <div class=\"rtsee-ise-thumb-body\">\n <span class=\"rtsee-ise-thumb-name\">{{ item.name }}</span>\n <span class=\"rtsee-ise-thumb-description\">{{ item.getTruncatedDescription(90) }}</span>\n </div>\n </button>\n @if (batchMode) {\n <div class=\"rtsee-ise-thumb-batch-checkbox\" (click)=\"toggleItemSelection(item.id); $event.stopPropagation()\">\n <input type=\"checkbox\" [checked]=\"isItemSelected(item.id)\" />\n </div>\n }\n <button type=\"button\"\n class=\"rtsee-ise-btn-add-illustrator\"\n style=\"position: absolute; top: 8px; right: 8px;\"\n title=\"Add {{ item.name }} to Illustrator context\"\n (click)=\"addToIllustrator($event, item)\">\n <i class=\"pi pi-palette\"></i>\n <span>+\uD83C\uDFA8</span>\n </button>\n @if (illustrator?.reviewingGeneratedImage && illustrator?.currentGeneratedImageUrl) {\n <button type=\"button\"\n class=\"rtsee-ise-btn-quick-apply\"\n style=\"position: absolute; bottom: 8px; right: 8px;\"\n [disabled]=\"item.saveInProgress\"\n [title]=\"'Apply reviewed image to ' + item.name\"\n (click)=\"applyReviewedImageToItem($event, item)\">\n @if (item.saveInProgress) {\n <i class=\"pi pi-spin pi-spinner\"></i>\n } @else {\n <i class=\"pi pi-plus\"></i>\n }\n </button>\n }\n </div>\n }\n\n <button type=\"button\"\n class=\"rtsee-ise-thumb rtsee-ise-thumb--add\"\n [ngClass]=\"{ 'rtsee-ise-thumb--active': editor.isCreating }\"\n [id]=\"'rtsee-ise-add-' + editor.section\"\n (click)=\"editor.expandNew()\">\n <i class=\"pi pi-plus\"></i>\n <span>New {{ editor.label }}</span>\n </button>\n </div>\n\n @if (editor.expanded && editor.form) {\n <div class=\"rtsee-ise-expanded rtsee-form-container\">\n <div class=\"rtsee-ise-expanded-header\">\n <h4>{{ editor.expanded.isNew ? 'New ' + editor.label : editor.expanded.name }}</h4>\n <div style=\"display: flex; align-items: center; gap: 8px;\">\n @if (!editor.expanded.isNew) {\n <button type=\"button\"\n class=\"rtsee-ise-btn-add-illustrator\"\n title=\"Add to Illustrator context\"\n (click)=\"addToIllustrator($event, editor.expanded)\">\n <i class=\"pi pi-palette\"></i>\n <span>+\uD83C\uDFA8 Illustrator</span>\n </button>\n }\n <button type=\"button\" class=\"rtsee-ise-icon-button\" (click)=\"editor.collapse()\" aria-label=\"Close\">\n <i class=\"pi pi-times\"></i>\n </button>\n </div>\n </div>\n\n @if (editor.error) {\n <div class=\"rtsee-ise-error\">{{ editor.error }}</div>\n }\n\n <rtsee-form [form]=\"editor.form\" (formSubmit)=\"submit()\">\n <div class=\"rtsee-ise-actions\">\n <p-button type=\"submit\"\n [label]=\"editor.expanded.isNew ? 'Create' : 'Save'\"\n [loading]=\"editor.saveInProgress\"\n [disabled]=\"editor.busy\" />\n <p-button label=\"Cancel\"\n severity=\"secondary\"\n (click)=\"editor.collapse()\"\n [disabled]=\"editor.busy\" />\n @if (!editor.expanded.isNew) {\n <span class=\"rtsee-ise-actions-spacer\"></span>\n @if (canDetach) {\n <p-button label=\"Remove from story\"\n icon=\"pi pi-minus-circle\"\n severity=\"secondary\"\n [text]=\"true\"\n (click)=\"editor.detach(editor.expanded)\"\n [disabled]=\"editor.busy\" />\n }\n <p-button label=\"Delete\"\n icon=\"pi pi-trash\"\n severity=\"danger\"\n [text]=\"true\"\n [loading]=\"editor.removeInProgress\"\n (click)=\"editor.delete(editor.expanded)\"\n [disabled]=\"editor.busy\" />\n }\n </div>\n </rtsee-form>\n </div>\n }\n </div>\n }\n</section>\n", dependencies: [{ kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: Button, selector: "p-button", inputs: ["type", "iconPos", "icon", "badge", "label", "disabled", "loading", "loadingIcon", "raised", "rounded", "text", "plain", "severity", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "buttonProps", "autofocus", "fluid"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "component", type: FormComponent, selector: "rtsee-form", inputs: ["form"], outputs: ["formSubmit"] }] }); }
4023
4504
  }
4024
4505
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.4", ngImport: i0, type: StoryElementsSectionComponent, decorators: [{
4025
4506
  type: Component,
4026
- args: [{ selector: "rtsee-story-elements-section", standalone: true, imports: [NgClass, Button, FormComponent], template: "<section class=\"rtsee-ise-section\" [ngClass]=\"{ 'rtsee-ise-section--collapsed': collapsed }\">\n <header class=\"rtsee-ise-section-header\">\n <button type=\"button\"\n class=\"rtsee-ise-section-toggle\"\n [attr.aria-expanded]=\"!collapsed\"\n [id]=\"'rtsee-ise-toggle-' + editor.section\"\n (click)=\"toggled.emit()\">\n <i class=\"rtsee-ise-section-icon\" [ngClass]=\"icon\"></i>\n <h3 class=\"rtsee-ise-section-title\">{{ title }}</h3>\n <span class=\"rtsee-ise-section-count\">{{ editor.items.length }}</span>\n <i class=\"pi pi-chevron-down rtsee-ise-chevron\"></i>\n </button>\n </header>\n\n @if (!collapsed) {\n <div class=\"rtsee-ise-section-body\">\n <div class=\"rtsee-ise-thumbs-grid\">\n @for (item of editor.items; track trackById($index, item)) {\n <div class=\"rtsee-ise-thumb-card-container\" style=\"position: relative; display: flex; flex-direction: column;\">\n <button type=\"button\"\n class=\"rtsee-ise-thumb\"\n [ngClass]=\"{ 'rtsee-ise-thumb--active': editor.isExpanded(item) }\"\n [id]=\"'rtsee-ise-' + editor.section + '-' + item.id\"\n (click)=\"editor.expand(item)\">\n <div class=\"rtsee-ise-thumb-image\">\n @if (item.imageUrl) {\n <img [src]=\"item.imageUrl\" [alt]=\"item.name\" loading=\"lazy\" />\n } @else {\n <i [ngClass]=\"icon\"></i>\n }\n </div>\n <div class=\"rtsee-ise-thumb-body\">\n <span class=\"rtsee-ise-thumb-name\">{{ item.name }}</span>\n <span class=\"rtsee-ise-thumb-description\">{{ item.getTruncatedDescription(90) }}</span>\n </div>\n </button>\n <button type=\"button\"\n class=\"rtsee-ise-btn-add-illustrator\"\n style=\"position: absolute; top: 8px; right: 8px;\"\n title=\"Add {{ item.name }} to Illustrator context\"\n (click)=\"addToIllustrator($event, item)\">\n <i class=\"pi pi-palette\"></i>\n <span>+\uD83C\uDFA8</span>\n </button>\n @if (illustrator?.reviewingGeneratedImage && illustrator?.currentGeneratedImageUrl) {\n <button type=\"button\"\n class=\"rtsee-ise-btn-quick-apply\"\n style=\"position: absolute; bottom: 8px; right: 8px;\"\n [disabled]=\"item.saveInProgress\"\n [title]=\"'Apply reviewed image to ' + item.name\"\n (click)=\"applyReviewedImageToItem($event, item)\">\n @if (item.saveInProgress) {\n <i class=\"pi pi-spin pi-spinner\"></i>\n } @else {\n <i class=\"pi pi-plus\"></i>\n }\n </button>\n }\n </div>\n }\n\n <button type=\"button\"\n class=\"rtsee-ise-thumb rtsee-ise-thumb--add\"\n [ngClass]=\"{ 'rtsee-ise-thumb--active': editor.isCreating }\"\n [id]=\"'rtsee-ise-add-' + editor.section\"\n (click)=\"editor.expandNew()\">\n <i class=\"pi pi-plus\"></i>\n <span>New {{ editor.label }}</span>\n </button>\n </div>\n\n @if (editor.expanded && editor.form) {\n <div class=\"rtsee-ise-expanded rtsee-form-container\">\n <div class=\"rtsee-ise-expanded-header\">\n <h4>{{ editor.expanded.isNew ? 'New ' + editor.label : editor.expanded.name }}</h4>\n <div style=\"display: flex; align-items: center; gap: 8px;\">\n @if (!editor.expanded.isNew) {\n <button type=\"button\"\n class=\"rtsee-ise-btn-add-illustrator\"\n title=\"Add to Illustrator context\"\n (click)=\"addToIllustrator($event, editor.expanded)\">\n <i class=\"pi pi-palette\"></i>\n <span>+\uD83C\uDFA8 Illustrator</span>\n </button>\n }\n <button type=\"button\" class=\"rtsee-ise-icon-button\" (click)=\"editor.collapse()\" aria-label=\"Close\">\n <i class=\"pi pi-times\"></i>\n </button>\n </div>\n </div>\n\n @if (editor.error) {\n <div class=\"rtsee-ise-error\">{{ editor.error }}</div>\n }\n\n <rtsee-form [form]=\"editor.form\" (formSubmit)=\"submit()\">\n <div class=\"rtsee-ise-actions\">\n <p-button type=\"submit\"\n [label]=\"editor.expanded.isNew ? 'Create' : 'Save'\"\n [loading]=\"editor.saveInProgress\"\n [disabled]=\"editor.busy\" />\n <p-button label=\"Cancel\"\n severity=\"secondary\"\n (click)=\"editor.collapse()\"\n [disabled]=\"editor.busy\" />\n @if (!editor.expanded.isNew) {\n <span class=\"rtsee-ise-actions-spacer\"></span>\n @if (canDetach) {\n <p-button label=\"Remove from story\"\n icon=\"pi pi-minus-circle\"\n severity=\"secondary\"\n [text]=\"true\"\n (click)=\"editor.detach(editor.expanded)\"\n [disabled]=\"editor.busy\" />\n }\n <p-button label=\"Delete\"\n icon=\"pi pi-trash\"\n severity=\"danger\"\n [text]=\"true\"\n [loading]=\"editor.removeInProgress\"\n (click)=\"editor.delete(editor.expanded)\"\n [disabled]=\"editor.busy\" />\n }\n </div>\n </rtsee-form>\n </div>\n }\n </div>\n }\n</section>\n" }]
4507
+ args: [{ selector: "rtsee-story-elements-section", standalone: true, imports: [NgClass, FormsModule, Button, FormComponent], template: "<section class=\"rtsee-ise-section\" [ngClass]=\"{ 'rtsee-ise-section--collapsed': collapsed }\">\n <header class=\"rtsee-ise-section-header\">\n <button type=\"button\"\n class=\"rtsee-ise-section-toggle\"\n [attr.aria-expanded]=\"!collapsed\"\n [id]=\"'rtsee-ise-toggle-' + editor.section\"\n (click)=\"toggled.emit()\">\n <i class=\"rtsee-ise-section-icon\" [ngClass]=\"icon\"></i>\n <h3 class=\"rtsee-ise-section-title\">{{ title }}</h3>\n <span class=\"rtsee-ise-section-count\">{{ editor.items.length }}</span>\n <i class=\"pi pi-chevron-down rtsee-ise-chevron\"></i>\n </button>\n @if (editor.items.length > 0 && !batchMode) {\n <button type=\"button\"\n class=\"rtsee-ise-btn-batch-generate\"\n (click)=\"startBatchSelection($event)\"\n [title]=\"'Batch generate images for ' + title\">\n <i class=\"pi pi-sparkles\"></i>\n <span>Generate Images</span>\n </button>\n }\n </header>\n\n @if (batchMode) {\n <div class=\"rtsee-ise-batch-panel\">\n <div class=\"rtsee-ise-batch-header\">\n <span class=\"rtsee-ise-batch-title\">\n <i class=\"pi pi-sparkles\"></i>\n Batch Generate for {{ title }} ({{ selectedItemIds.size }} of {{ editor.items.length }} selected)\n </span>\n <span class=\"rtsee-ise-batch-hint\">\n Click items to select/deselect. Entities with default images are auto-selected.\n </span>\n </div>\n\n <textarea\n class=\"rtsee-ise-batch-prompt-input\"\n rows=\"2\"\n [(ngModel)]=\"batchAdditionalPrompt\"\n placeholder=\"Additional prompt instructions for all selected {{ title }} (optional)...\"\n ></textarea>\n\n <div class=\"rtsee-ise-batch-actions\">\n <button\n type=\"button\"\n class=\"rtsee-ise-btn-batch-cancel\"\n (click)=\"cancelBatchSelection()\"\n >\n Cancel\n </button>\n <button\n type=\"button\"\n class=\"rtsee-ise-btn-batch-submit\"\n [disabled]=\"selectedItemIds.size === 0\"\n (click)=\"submitBatchGeneration()\"\n >\n <i class=\"pi pi-sparkles\"></i>\n <span>Generate {{ selectedItemIds.size }} image{{ selectedItemIds.size === 1 ? '' : 's' }}</span>\n </button>\n </div>\n </div>\n }\n\n @if (!collapsed) {\n <div class=\"rtsee-ise-section-body\">\n <div class=\"rtsee-ise-thumbs-grid\">\n @for (item of editor.items; track trackById($index, item)) {\n <div class=\"rtsee-ise-thumb-card-container\" style=\"position: relative; display: flex; flex-direction: column;\">\n <button type=\"button\"\n class=\"rtsee-ise-thumb\"\n [ngClass]=\"{\n 'rtsee-ise-thumb--active': editor.isExpanded(item),\n 'rtsee-ise-thumb-card--selected': batchMode && isItemSelected(item.id)\n }\"\n [id]=\"'rtsee-ise-' + editor.section + '-' + item.id\"\n (click)=\"batchMode ? toggleItemSelection(item.id) : editor.expand(item)\">\n <div class=\"rtsee-ise-thumb-image\">\n @if (item.imageUrl) {\n <img [src]=\"item.imageUrl\" [alt]=\"item.name\" loading=\"lazy\" />\n } @else {\n <i [ngClass]=\"icon\"></i>\n }\n @if (isItemGenerating(item.id)) {\n <div class=\"rtsee-ise-thumb-generating-overlay\">\n <i class=\"pi pi-spin pi-spinner\"></i>\n <span>Generating\u2026</span>\n </div>\n }\n </div>\n <div class=\"rtsee-ise-thumb-body\">\n <span class=\"rtsee-ise-thumb-name\">{{ item.name }}</span>\n <span class=\"rtsee-ise-thumb-description\">{{ item.getTruncatedDescription(90) }}</span>\n </div>\n </button>\n @if (batchMode) {\n <div class=\"rtsee-ise-thumb-batch-checkbox\" (click)=\"toggleItemSelection(item.id); $event.stopPropagation()\">\n <input type=\"checkbox\" [checked]=\"isItemSelected(item.id)\" />\n </div>\n }\n <button type=\"button\"\n class=\"rtsee-ise-btn-add-illustrator\"\n style=\"position: absolute; top: 8px; right: 8px;\"\n title=\"Add {{ item.name }} to Illustrator context\"\n (click)=\"addToIllustrator($event, item)\">\n <i class=\"pi pi-palette\"></i>\n <span>+\uD83C\uDFA8</span>\n </button>\n @if (illustrator?.reviewingGeneratedImage && illustrator?.currentGeneratedImageUrl) {\n <button type=\"button\"\n class=\"rtsee-ise-btn-quick-apply\"\n style=\"position: absolute; bottom: 8px; right: 8px;\"\n [disabled]=\"item.saveInProgress\"\n [title]=\"'Apply reviewed image to ' + item.name\"\n (click)=\"applyReviewedImageToItem($event, item)\">\n @if (item.saveInProgress) {\n <i class=\"pi pi-spin pi-spinner\"></i>\n } @else {\n <i class=\"pi pi-plus\"></i>\n }\n </button>\n }\n </div>\n }\n\n <button type=\"button\"\n class=\"rtsee-ise-thumb rtsee-ise-thumb--add\"\n [ngClass]=\"{ 'rtsee-ise-thumb--active': editor.isCreating }\"\n [id]=\"'rtsee-ise-add-' + editor.section\"\n (click)=\"editor.expandNew()\">\n <i class=\"pi pi-plus\"></i>\n <span>New {{ editor.label }}</span>\n </button>\n </div>\n\n @if (editor.expanded && editor.form) {\n <div class=\"rtsee-ise-expanded rtsee-form-container\">\n <div class=\"rtsee-ise-expanded-header\">\n <h4>{{ editor.expanded.isNew ? 'New ' + editor.label : editor.expanded.name }}</h4>\n <div style=\"display: flex; align-items: center; gap: 8px;\">\n @if (!editor.expanded.isNew) {\n <button type=\"button\"\n class=\"rtsee-ise-btn-add-illustrator\"\n title=\"Add to Illustrator context\"\n (click)=\"addToIllustrator($event, editor.expanded)\">\n <i class=\"pi pi-palette\"></i>\n <span>+\uD83C\uDFA8 Illustrator</span>\n </button>\n }\n <button type=\"button\" class=\"rtsee-ise-icon-button\" (click)=\"editor.collapse()\" aria-label=\"Close\">\n <i class=\"pi pi-times\"></i>\n </button>\n </div>\n </div>\n\n @if (editor.error) {\n <div class=\"rtsee-ise-error\">{{ editor.error }}</div>\n }\n\n <rtsee-form [form]=\"editor.form\" (formSubmit)=\"submit()\">\n <div class=\"rtsee-ise-actions\">\n <p-button type=\"submit\"\n [label]=\"editor.expanded.isNew ? 'Create' : 'Save'\"\n [loading]=\"editor.saveInProgress\"\n [disabled]=\"editor.busy\" />\n <p-button label=\"Cancel\"\n severity=\"secondary\"\n (click)=\"editor.collapse()\"\n [disabled]=\"editor.busy\" />\n @if (!editor.expanded.isNew) {\n <span class=\"rtsee-ise-actions-spacer\"></span>\n @if (canDetach) {\n <p-button label=\"Remove from story\"\n icon=\"pi pi-minus-circle\"\n severity=\"secondary\"\n [text]=\"true\"\n (click)=\"editor.detach(editor.expanded)\"\n [disabled]=\"editor.busy\" />\n }\n <p-button label=\"Delete\"\n icon=\"pi pi-trash\"\n severity=\"danger\"\n [text]=\"true\"\n [loading]=\"editor.removeInProgress\"\n (click)=\"editor.delete(editor.expanded)\"\n [disabled]=\"editor.busy\" />\n }\n </div>\n </rtsee-form>\n </div>\n }\n </div>\n }\n</section>\n" }]
4027
4508
  }], ctorParameters: () => [{ type: i0.ChangeDetectorRef }], propDecorators: { editor: [{
4028
4509
  type: Input,
4029
4510
  args: [{ required: true }]
@@ -4190,6 +4671,11 @@ class InteractiveStoryEditorComponent {
4190
4671
  this.cdr.markForCheck();
4191
4672
  try {
4192
4673
  await this.vm.updateSlideImage(slide, imageUrl);
4674
+ // Reset illustrator state and UI
4675
+ this.illustrator.resetState();
4676
+ if (typeof window !== 'undefined') {
4677
+ window.dispatchEvent(new CustomEvent(RTSeeIllustratorEvents.RESET_STATE));
4678
+ }
4193
4679
  }
4194
4680
  catch (e) {
4195
4681
  console.error('Failed to apply image to slide:', e);