@rtsee/ngx 0.0.107 → 0.0.109

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.
@@ -18,7 +18,7 @@ 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
20
  import { RTSeeChatMemberRoles, RTSeeIllustratorEvents, RTSeeChatTypes, OzzyTaskStatus } from '@rtsee/common';
21
- import { ManageAgentForm, RTSeeForm, RTSeeFormFieldRadioGroup, RTSeeFormField, RTSeeFormFieldImageUploader, RTSeeRater } from '@rtsee/utilities-ui';
21
+ import { sanitizeImageUrl, ManageAgentForm, RTSeeForm, RTSeeFormFieldRadioGroup, RTSeeFormField, RTSeeFormFieldImageUploader, 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';
@@ -711,7 +711,11 @@ class RTSeeImageUploaderComponent {
711
711
  if (response.reviewingGeneratedImage && response.imageUrl) {
712
712
  // 1. If RTSeeIllustrator is in reviewingGeneratedImage mode, apply currently reviewed image to ImageUploader
713
713
  this.submittingFromPainting = true;
714
- this.imageUploader.applyImageUrl(response.imageUrl);
714
+ this.imageUploader.applyImageUrl(sanitizeImageUrl(response.imageUrl));
715
+ // Reset illustrator state and UI
716
+ if (typeof window !== 'undefined') {
717
+ window.dispatchEvent(new CustomEvent(RTSeeIllustratorEvents.RESET_STATE));
718
+ }
715
719
  // and if Imageuploader is part of the form it should immediately submit the form
716
720
  setTimeout(() => {
717
721
  this.submitParentForm();
@@ -1884,6 +1888,7 @@ class MediaWidgetComponent {
1884
1888
  event.stopPropagation();
1885
1889
  event.preventDefault();
1886
1890
  if (typeof window !== 'undefined' && img?.url) {
1891
+ // Retain AWS presigned URL so Illustrator can render the image preview!
1887
1892
  window.dispatchEvent(new CustomEvent(RTSeeIllustratorEvents.SET_IMAGE_FOR_REVIEW, {
1888
1893
  detail: {
1889
1894
  imageUrl: img.url,
@@ -1893,6 +1898,23 @@ class MediaWidgetComponent {
1893
1898
  }));
1894
1899
  }
1895
1900
  }
1901
+ addToContext(event, img) {
1902
+ event.stopPropagation();
1903
+ event.preventDefault();
1904
+ if (typeof window !== 'undefined' && img?.url) {
1905
+ window.dispatchEvent(new CustomEvent(RTSeeIllustratorEvents.ADD_IMAGE_TO_CONTEXT, {
1906
+ detail: {
1907
+ id: 'chat-img-' + Math.random().toString(36).substring(2, 9),
1908
+ imageUrl: img.url,
1909
+ title: img.name || 'Chat image',
1910
+ description: (typeof this.message.text === 'string' ? this.message.text : '') || '',
1911
+ visualDescription: '',
1912
+ sourceType: 'custom',
1913
+ active: true,
1914
+ },
1915
+ }));
1916
+ }
1917
+ }
1896
1918
  updateVisibleImages() {
1897
1919
  const all = this.message.images || [];
1898
1920
  if (this.showAll || all.length <= this.MAX_GRID) {
@@ -1903,13 +1925,24 @@ class MediaWidgetComponent {
1903
1925
  this.visibleImages = all.slice(0, this.MAX_GRID);
1904
1926
  this.hiddenCount = all.length - this.MAX_GRID;
1905
1927
  }
1928
+ const reqId = this.message?.data?.requestId;
1929
+ if (reqId && this.visibleImages.length > 0 && typeof window !== 'undefined') {
1930
+ window.dispatchEvent(new CustomEvent(RTSeeIllustratorEvents.REQUESTED_GENERATION_SUCCEED, {
1931
+ detail: {
1932
+ requestId: reqId,
1933
+ imageUrl: this.visibleImages[0].url,
1934
+ messageId: this.message.id,
1935
+ chatId: this.chat?.id,
1936
+ },
1937
+ }));
1938
+ }
1906
1939
  }
1907
1940
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.4", ngImport: i0, type: MediaWidgetComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
1908
- 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"] }] }); }
1941
+ 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 <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-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"] }] }); }
1909
1942
  }
1910
1943
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.4", ngImport: i0, type: MediaWidgetComponent, decorators: [{
1911
1944
  type: Component,
1912
- 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"] }]
1945
+ 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 <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-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"] }]
1913
1946
  }], propDecorators: { message: [{
1914
1947
  type: Input
1915
1948
  }], messenger: [{
@@ -3858,11 +3891,11 @@ class StoryPlayerComponent {
3858
3891
  this.story.initSwiper(swiper);
3859
3892
  }
3860
3893
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.4", ngImport: i0, type: StoryPlayerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
3861
- 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"] }] }); }
3894
+ 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"] }] }); }
3862
3895
  }
3863
3896
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.4", ngImport: i0, type: StoryPlayerComponent, decorators: [{
3864
3897
  type: Component,
3865
- 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" }]
3898
+ 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" }]
3866
3899
  }], ctorParameters: () => [], propDecorators: { story: [{
3867
3900
  type: Input
3868
3901
  }], settings: [{
@@ -3960,6 +3993,11 @@ class StoryElementsSectionComponent {
3960
3993
  this.collapsed = false;
3961
3994
  this.canDetach = true;
3962
3995
  this.toggled = new EventEmitter();
3996
+ this.batchMode = false;
3997
+ this.selectedItemIds = new Set();
3998
+ this.batchAdditionalPrompt = '';
3999
+ this.generatingItemIds = new Set();
4000
+ this.activeGenerationListeners = new Map();
3963
4001
  this.onIllustratorChanged = () => {
3964
4002
  this.cdr.markForCheck();
3965
4003
  };
@@ -3968,11 +4006,129 @@ class StoryElementsSectionComponent {
3968
4006
  if (this._illustrator) {
3969
4007
  this._illustrator.off('changed', this.onIllustratorChanged);
3970
4008
  }
4009
+ for (const [requestId, listener] of this.activeGenerationListeners.entries()) {
4010
+ window.removeEventListener(RTSeeIllustrator.REQUESTED_GENERATION_SUCCEED, listener);
4011
+ }
4012
+ this.activeGenerationListeners.clear();
4013
+ }
4014
+ startBatchSelection(event) {
4015
+ event.stopPropagation();
4016
+ event.preventDefault();
4017
+ if (this.collapsed) {
4018
+ this.toggled.emit();
4019
+ }
4020
+ this.batchMode = true;
4021
+ this.selectedItemIds.clear();
4022
+ for (const item of this.editor.items) {
4023
+ const url = (item.imageUrl || '').toLowerCase();
4024
+ if (!url || url.includes('default')) {
4025
+ this.selectedItemIds.add(item.id);
4026
+ }
4027
+ }
4028
+ this.cdr.markForCheck();
4029
+ }
4030
+ cancelBatchSelection() {
4031
+ this.batchMode = false;
4032
+ this.selectedItemIds.clear();
4033
+ this.batchAdditionalPrompt = '';
4034
+ this.cdr.markForCheck();
4035
+ }
4036
+ toggleItemSelection(id) {
4037
+ if (this.selectedItemIds.has(id)) {
4038
+ this.selectedItemIds.delete(id);
4039
+ }
4040
+ else {
4041
+ this.selectedItemIds.add(id);
4042
+ }
4043
+ this.cdr.markForCheck();
4044
+ }
4045
+ isItemSelected(id) {
4046
+ return this.selectedItemIds.has(id);
4047
+ }
4048
+ isItemGenerating(id) {
4049
+ return this.generatingItemIds.has(id);
4050
+ }
4051
+ async submitBatchGeneration() {
4052
+ if (this.selectedItemIds.size === 0)
4053
+ return;
4054
+ const selectedItems = this.editor.items.filter((item) => this.selectedItemIds.has(item.id));
4055
+ const additionalPrompt = this.batchAdditionalPrompt.trim();
4056
+ this.batchMode = false;
4057
+ this.selectedItemIds.clear();
4058
+ this.batchAdditionalPrompt = '';
4059
+ this.cdr.markForCheck();
4060
+ for (const item of selectedItems) {
4061
+ const requestId = RTSeeCore.uuidv4();
4062
+ this.generatingItemIds.add(item.id);
4063
+ const listener = async (e) => {
4064
+ const customEvent = e;
4065
+ if (customEvent.detail?.requestId === requestId && customEvent.detail?.imageUrl) {
4066
+ window.removeEventListener(RTSeeIllustrator.REQUESTED_GENERATION_SUCCEED, listener);
4067
+ this.activeGenerationListeners.delete(requestId);
4068
+ this.generatingItemIds.delete(item.id);
4069
+ const cleanUrl = sanitizeImageUrl(customEvent.detail.imageUrl);
4070
+ try {
4071
+ item.imageUrl = cleanUrl;
4072
+ await item.save({ imageUrl: cleanUrl });
4073
+ if (this.editor.expanded && this.editor.expanded.id === item.id && this.editor.form) {
4074
+ const imageField = this.editor.form.fields.find((f) => f.name === 'imageUrl' || f.type === 'image-uploader');
4075
+ if (imageField) {
4076
+ imageField.value = cleanUrl;
4077
+ if (imageField.imageUploader) {
4078
+ imageField.imageUploader.uploadedPhotoPreview = cleanUrl;
4079
+ imageField.imageUploader.value = cleanUrl;
4080
+ }
4081
+ }
4082
+ }
4083
+ }
4084
+ catch (err) {
4085
+ console.error(`Failed to apply generated image to ${this.editor.label}:`, err);
4086
+ }
4087
+ finally {
4088
+ this.cdr.markForCheck();
4089
+ }
4090
+ }
4091
+ };
4092
+ window.addEventListener(RTSeeIllustrator.REQUESTED_GENERATION_SUCCEED, listener);
4093
+ this.activeGenerationListeners.set(requestId, listener);
4094
+ const promptParts = [];
4095
+ promptParts.push(`${this.editor.label}: ${item.name || ''}`);
4096
+ if (item.visualDescription) {
4097
+ promptParts.push(`Visual appearance: ${item.visualDescription}`);
4098
+ }
4099
+ if (item.description && item.description !== item.visualDescription) {
4100
+ promptParts.push(`Description/Role: ${item.description}`);
4101
+ }
4102
+ if (additionalPrompt) {
4103
+ promptParts.push(`Additional prompt: ${additionalPrompt}`);
4104
+ }
4105
+ const entityPrompt = promptParts.join('\n');
4106
+ const config = this.illustrator?.config || {
4107
+ aspectRatio: '1:1',
4108
+ style: 'Classic Fairy Tale pixel-art Storybook',
4109
+ mood: 'Dreamy & Magical',
4110
+ lighting: 'Soft Diffused Storybook Glow',
4111
+ negativePrompt: 'modern elements, cars, text, watermark, signature, blurry, low quality',
4112
+ numberOfImages: 1,
4113
+ };
4114
+ const payload = {
4115
+ chatId: this.illustrator?.activeChatId || undefined,
4116
+ agentId: this.illustrator?.activeAgentId || '',
4117
+ prompt: entityPrompt,
4118
+ config: { ...config },
4119
+ contextImages: [],
4120
+ requestId,
4121
+ };
4122
+ window.dispatchEvent(new CustomEvent(RTSeeIllustratorEvents.SUBMIT_PROMPT, {
4123
+ detail: payload,
4124
+ }));
4125
+ }
4126
+ this.cdr.markForCheck();
3971
4127
  }
3972
4128
  async applyReviewedImageToItem(event, item) {
3973
4129
  event.stopPropagation();
3974
4130
  event.preventDefault();
3975
- const imageUrl = this._illustrator?.currentGeneratedImageUrl;
4131
+ const imageUrl = sanitizeImageUrl(this._illustrator?.currentGeneratedImageUrl);
3976
4132
  if (!imageUrl)
3977
4133
  return;
3978
4134
  this.cdr.markForCheck();
@@ -3987,6 +4143,11 @@ class StoryElementsSectionComponent {
3987
4143
  }
3988
4144
  }
3989
4145
  }
4146
+ // Reset illustrator state and UI
4147
+ this._illustrator?.resetState();
4148
+ if (typeof window !== 'undefined') {
4149
+ window.dispatchEvent(new CustomEvent(RTSeeIllustratorEvents.RESET_STATE));
4150
+ }
3990
4151
  }
3991
4152
  catch (e) {
3992
4153
  console.error(`Failed to apply image to ${this.editor.label}:`, e);
@@ -4018,11 +4179,11 @@ class StoryElementsSectionComponent {
4018
4179
  return item.id || `new-${index}`;
4019
4180
  }
4020
4181
  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 }); }
4021
- 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"] }] }); }
4182
+ 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"] }] }); }
4022
4183
  }
4023
4184
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.4", ngImport: i0, type: StoryElementsSectionComponent, decorators: [{
4024
4185
  type: Component,
4025
- 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" }]
4186
+ 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" }]
4026
4187
  }], ctorParameters: () => [{ type: i0.ChangeDetectorRef }], propDecorators: { editor: [{
4027
4188
  type: Input,
4028
4189
  args: [{ required: true }]
@@ -4182,13 +4343,18 @@ class InteractiveStoryEditorComponent {
4182
4343
  async applyReviewedImageToSlide(event, slide) {
4183
4344
  event.stopPropagation();
4184
4345
  event.preventDefault();
4185
- const imageUrl = this.illustrator.currentGeneratedImageUrl;
4346
+ const imageUrl = sanitizeImageUrl(this.illustrator.currentGeneratedImageUrl);
4186
4347
  if (!imageUrl || !slide.id)
4187
4348
  return;
4188
4349
  this.slideUpdatingIds.add(slide.id);
4189
4350
  this.cdr.markForCheck();
4190
4351
  try {
4191
4352
  await this.vm.updateSlideImage(slide, imageUrl);
4353
+ // Reset illustrator state and UI
4354
+ this.illustrator.resetState();
4355
+ if (typeof window !== 'undefined') {
4356
+ window.dispatchEvent(new CustomEvent(RTSeeIllustratorEvents.RESET_STATE));
4357
+ }
4192
4358
  }
4193
4359
  catch (e) {
4194
4360
  console.error('Failed to apply image to slide:', e);