From d0fccd1050f5d6ccc24c1e4d2b7d1c0ed94fb2a7 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Sun, 23 Mar 2025 10:27:28 -0400
Subject: updated more [DocData] to .$ things

---
 src/client/views/nodes/imageEditor/ImageEditor.tsx | 9 ++++-----
 1 file changed, 4 insertions(+), 5 deletions(-)

(limited to 'src/client/views/nodes/imageEditor/ImageEditor.tsx')

diff --git a/src/client/views/nodes/imageEditor/ImageEditor.tsx b/src/client/views/nodes/imageEditor/ImageEditor.tsx
index 657e689bb..85bd95d15 100644
--- a/src/client/views/nodes/imageEditor/ImageEditor.tsx
+++ b/src/client/views/nodes/imageEditor/ImageEditor.tsx
@@ -24,8 +24,8 @@ import { PointerHandler } from './imageEditorUtils/PointerHandler';
 import { activeColor, bgColor, brushWidthOffset, canvasSize, eraserColor, freeformRenderSize, newCollectionSize, offsetDistanceY, offsetX } from './imageEditorUtils/imageEditorConstants';
 import { CutMode, CursorData, ImageDimensions, ImageEditTool, ImageToolType, Point } from './imageEditorUtils/imageEditorInterfaces';
 import { DocumentView } from '../DocumentView';
-import { DocData } from '../../../../fields/DocSymbols';
 import { SettingsManager } from '../../../util/SettingsManager';
+import { Upload } from '../../../../server/SharedMediaTypes';
 
 interface GenerativeFillProps {
     imageEditorOpen: boolean;
@@ -397,9 +397,8 @@ const ImageEditor = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addDoc
             const newImgDoc = await createNewImgDoc(finalImg, firstDoc);
             if (newImgDoc) {
                 // set the image to transparent to remove the background / brushstrokes
-                const docData = newImgDoc[DocData];
-                docData.backgroundColor = 'transparent';
-                docData.disableMixBlend = true;
+                newImgDoc.$backgroundColor = 'transparent';
+                newImgDoc.$disableMixBlend = true;
                 if (firstDoc) setIsFirstDoc(false);
                 setEdits([...prevEdits, { url: finalImgURL, saveRes: undefined }]);
             }
@@ -476,7 +475,7 @@ const ImageEditor = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addDoc
     const createNewImgDoc = async (img: HTMLImageElement, firstDoc: boolean /*, parent?: Doc */): Promise<Doc | undefined> => {
         if (!imageRootDoc) return undefined;
         const { src } = img;
-        const [result] = await Networking.PostToServer('/uploadRemoteImage', { sources: [src] });
+        const [result] = (await Networking.PostToServer('/uploadRemoteImage', { sources: [src] })) as Upload.ImageInformation[];
         const source = ClientUtils.prepend(result.accessPaths.agnostic.client);
 
         if (firstDoc) {
-- 
cgit v1.2.3-70-g09d2


From d0cbb43ec3fa19f76a570f5e0038bfc72c9f37b9 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Wed, 7 May 2025 11:21:53 -0400
Subject: fixed dont_center on Docs in gridview.  fixed ai view screentolocal
 in docView, and made ai view more streamlined.  got rid of ai history sidebar
 in images.   fixed imageeditor to use a mask that doesn't show the original
 conctents.  fixed gptpopup scrolling, turning off of spinner, and improved
 ability to filter Docs

---
 src/client/views/ViewBoxInterface.ts               |   1 -
 .../collectionGrid/CollectionGridView.tsx          |   2 +-
 .../views/nodes/CollectionFreeFormDocumentView.tsx |  14 +-
 src/client/views/nodes/DocumentView.scss           |   3 +-
 src/client/views/nodes/DocumentView.tsx            |  59 ++++----
 src/client/views/nodes/IconTagBox.tsx              |  24 ++--
 src/client/views/nodes/ImageBox.scss               |   2 +-
 src/client/views/nodes/ImageBox.tsx                |  96 ++++---------
 src/client/views/nodes/imageEditor/ImageEditor.tsx |   5 +-
 .../imageEditor/imageEditorUtils/ImageHandler.ts   |   6 +-
 src/client/views/pdf/GPTPopup/GPTPopup.scss        |   7 +-
 src/client/views/pdf/GPTPopup/GPTPopup.tsx         | 149 +++++++++++----------
 src/client/views/smartdraw/SmartDrawHandler.tsx    |   2 +-
 13 files changed, 168 insertions(+), 202 deletions(-)

(limited to 'src/client/views/nodes/imageEditor/ImageEditor.tsx')

diff --git a/src/client/views/ViewBoxInterface.ts b/src/client/views/ViewBoxInterface.ts
index 0ddac8914..d8dab8e89 100644
--- a/src/client/views/ViewBoxInterface.ts
+++ b/src/client/views/ViewBoxInterface.ts
@@ -64,5 +64,4 @@ export abstract class ViewBoxInterface<P> extends ObservableReactComponent<React
     dontRegisterView?: () => boolean; // KeyValueBox's don't want to register their views
     isUnstyledView?: () => boolean; // SchemaView and KeyValue are unstyled -- not titles, no opacity, no animations
     componentAIView?: () => JSX.Element;
-    componentAIViewHistory?: () => JSX.Element;
 }
diff --git a/src/client/views/collections/collectionGrid/CollectionGridView.tsx b/src/client/views/collections/collectionGrid/CollectionGridView.tsx
index 3320eacb6..b837b3a86 100644
--- a/src/client/views/collections/collectionGrid/CollectionGridView.tsx
+++ b/src/client/views/collections/collectionGrid/CollectionGridView.tsx
@@ -206,7 +206,7 @@ export class CollectionGridView extends CollectionSubView() {
             setContentViewBox={emptyFunction}
             whenChildContentsActiveChanged={this._props.whenChildContentsActiveChanged}
             onClickScript={this.onChildClickHandler}
-            dontCenter={StrCast(this.layoutDoc.layout_dontCenter) as 'x' | 'y' | 'xy'}
+            dontCenter={StrCast(this.layoutDoc.layout_dontCenter, StrCast(childLayout.layout_dontCenter)) as 'x' | 'y' | 'xy'}
             showTags={BoolCast(this.layoutDoc.showChildTags) || BoolCast(this.Document._layout_showTags)}
         />
     );
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index 940c4cb99..3805b0dca 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -148,7 +148,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
     public static getValues(doc: Doc, time: number, fillIn: boolean = true) {
         return CollectionFreeFormDocumentView.animFields.reduce(
             (p, val) => {
-                p[val.key] = Cast(doc[`${val.key}_indexed`], listSpec('number'), fillIn ? [NumCast(doc[val.key], val.val)] : []).reduce(
+                p[val.key] = Cast(doc[`${val.key}_indexed`], listSpec('number'), fillIn ? [NumCast(doc[val.key], val.val)] : [])!.reduce(
                     (prev, v, i) => ((i <= Math.round(time) && v !== undefined) || prev === undefined ? v : prev),
                     undefined as unknown as number
                 );
@@ -161,7 +161,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
     public static getStringValues(doc: Doc, time: number) {
         return CollectionFreeFormDocumentView.animStringFields.reduce(
             (p, val) => {
-                p[val] = Cast(doc[`${val}_indexed`], listSpec('string'), [StrCast(doc[val])]).reduce((prev, v, i) => ((i <= Math.round(time) && v !== undefined) || prev === undefined ? v : prev), undefined as unknown as string);
+                p[val] = Cast(doc[`${val}_indexed`], listSpec('string'), [StrCast(doc[val])])!.reduce((prev, v, i) => ((i <= Math.round(time) && v !== undefined) || prev === undefined ? v : prev), undefined as unknown as string);
                 return p;
             },
             {} as { [val: string]: Opt<string> }
@@ -171,7 +171,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
     public static setStringValues(time: number, d: Doc, vals: { [val: string]: Opt<string> }) {
         const timecode = Math.round(time);
         Object.keys(vals).forEach(val => {
-            const findexed = Cast(d[`${val}_indexed`], listSpec('string'), []).slice();
+            const findexed = Cast(d[`${val}_indexed`], listSpec('string'), [])!.slice();
             findexed[timecode] = vals[val] || '';
             d[`${val}_indexed`] = new List<string>(findexed);
         });
@@ -180,7 +180,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
     public static setValues(time: number, d: Doc, vals: { [val: string]: Opt<number> }) {
         const timecode = Math.round(time);
         Object.keys(vals).forEach(val => {
-            const findexed = Cast(d[`${val}_indexed`], listSpec('number'), []).slice();
+            const findexed = Cast(d[`${val}_indexed`], listSpec('number'), [])!.slice();
             findexed[timecode] = vals[val] as unknown as number;
             d[`${val}_indexed`] = new List<number>(findexed);
         });
@@ -204,15 +204,15 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
         docs.forEach(doc => {
             this.animFields.forEach(val => {
                 const findexed = Cast(doc[`${val.key}_indexed`], listSpec('number'), null);
-                findexed?.length <= timecode + 1 && findexed.push(undefined as unknown as number);
+                (findexed?.length ?? 0) <= timecode + 1 && findexed?.push(undefined as unknown as number);
             });
             this.animStringFields.forEach(val => {
                 const findexed = Cast(doc[`${val}_indexed`], listSpec('string'), null);
-                findexed?.length <= timecode + 1 && findexed.push(undefined as unknown as string);
+                (findexed?.length ?? 0) <= timecode + 1 && findexed?.push(undefined as unknown as string);
             });
             this.animDataFields(doc).forEach(val => {
                 const findexed = Cast(doc[`${val}_indexed`], listSpec(InkField), null);
-                findexed?.length <= timecode + 1 && findexed.push(undefined as unknown as InkField);
+                (findexed?.length ?? 0) <= timecode + 1 && findexed?.push(undefined as unknown as InkField);
             });
         });
         return newTimer;
diff --git a/src/client/views/nodes/DocumentView.scss b/src/client/views/nodes/DocumentView.scss
index 5ac66f2cd..c4351a200 100644
--- a/src/client/views/nodes/DocumentView.scss
+++ b/src/client/views/nodes/DocumentView.scss
@@ -119,6 +119,7 @@
         display: flex;
         justify-content: center;
         align-items: center;
+        margin: auto;
         position: relative; // allows contents to be positioned relative/below title
         > .formattedTextBox {
             position: absolute; // position a child text box
@@ -302,6 +303,6 @@
     background: transparent;
 
     .documentView-editorView-resizer {
-        height: 5px;
+        height: 2px;
     }
 }
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index d756f3226..bdb97d7bb 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -648,11 +648,8 @@ export class DocumentViewInternal extends DocComponent<DocumentViewProps & Field
 
     rootSelected = () => this._rootSelected;
     panelHeight = () => this._props.PanelHeight() - this.headerMargin - 2 * NumCast(this.Document.borderWidth);
-    screenToLocalContent = () =>
-        this._props
-            .ScreenToLocalTransform()
-            .translate(-NumCast(this.Document.borderWidth), -this.headerMargin - NumCast(this.Document.borderWidth))
-            .scale(this.viewingAiEditor() ? (this._props.PanelHeight() || 1) / this.aiContentsHeight() : 1);
+    aiShift = () => (!this.viewingAiEditor() ? 0 : (this._props.PanelWidth() - this.aiContentsWidth()) / 2);
+    aiScale = () => (this.viewingAiEditor() ? (this._props.PanelHeight() || 1) / this.aiContentsHeight() : 1);
     onClickFunc = () => (this.disableClickScriptFunc ? undefined : this.onClickHdlr);
     setHeight = (height: number) => { !this._props.suppressSetHeight && (this.layoutDoc._height = Math.min(NumCast(this.layoutDoc._maxHeight, Number.MAX_SAFE_INTEGER), height + 2 * NumCast(this.Document.borderWidth))); } // prettier-ignore
     setContentView = action((view: ViewBoxInterface<FieldViewProps>) => (this._componentView = view));
@@ -678,7 +675,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewProps & Field
         return this._props.styleProvider?.(doc, props, property);
     };
 
-    @observable _aiWinHeight = 88;
+    @observable _aiWinHeight = 32;
 
     TagsBtnHeight = 22;
     @computed get currentScale() {
@@ -703,33 +700,21 @@ export class DocumentViewInternal extends DocComponent<DocumentViewProps & Field
     @computed get uiBtnScaling() {  return Math.max(this.maxWidgetSize / this.TagsBtnHeight, 1) * Math.min(1, this.viewScaling); } // prettier-ignore
 
     aiContentsWidth = () => (this.aiContentsHeight() * (this._props.NativeWidth?.() || 1)) / (this._props.NativeHeight?.() || 1);
-    aiContentsHeight = () => Math.max(10, this._props.PanelHeight() - this._aiWinHeight * this.uiBtnScaling);
+    aiContentsHeight = () => Math.max(10, this._props.PanelHeight() - (this._aiWinHeight + (this.tagsOverlayFunc() ? 22 : 0)) * this.uiBtnScaling);
     @computed get aiEditor() {
         return (
-            <>
-                <div
-                    className="documentView-editorView-history"
-                    ref={r => this.historyRef(this._oldAiWheel, (this._oldAiWheel = r))}
-                    style={{
-                        transform: `scale(${this.uiBtnScaling})`,
-                        height: this.aiContentsHeight() / this.uiBtnScaling,
-                        width: ((this._props.PanelWidth() - this.aiContentsWidth()) * 0.95) / this.uiBtnScaling,
-                    }}>
-                    {this._componentView?.componentAIViewHistory?.() ?? null}
-                </div>
-                <div
-                    className="documentView-editorView"
-                    style={{
-                        background: SnappingManager.userVariantColor,
-                        width: `${100 / this.uiBtnScaling}%`, //
-                        transform: `scale(${this.uiBtnScaling})`,
-                    }}
-                    ref={r => this.historyRef(this._oldHistoryWheel, (this._oldHistoryWheel = r))}>
-                    <div className="documentView-editorView-resizer" />
-                    {this._componentView?.componentAIView?.() ?? null}
-                    {this._props.DocumentView?.() ? <TagsView background={this.backgroundBoxColor} Views={[this._props.DocumentView?.()]} /> : null}
-                </div>
-            </>
+            <div
+                className="documentView-editorView"
+                style={{
+                    background: SnappingManager.userVariantColor,
+                    width: `${100 / this.uiBtnScaling}%`, //
+                    transform: `scale(${this.uiBtnScaling})`,
+                }}
+                ref={r => this.historyRef(this._oldHistoryWheel, (this._oldHistoryWheel = r))}>
+                <div className="documentView-editorView-resizer" />
+                {this._componentView?.componentAIView?.() ?? null}
+                {this._props.DocumentView?.() ? <TagsView background={this.backgroundBoxColor} Views={[this._props.DocumentView?.()]} /> : null}
+            </div>
         );
     }
     @computed get tagsOverlay() {
@@ -780,7 +765,6 @@ export class DocumentViewInternal extends DocComponent<DocumentViewProps & Field
                         PanelHeight={this.viewingAiEditor() ? this.aiContentsHeight : this.panelHeight}
                         setHeight={this.setHeight}
                         isContentActive={this.isContentActive}
-                        ScreenToLocalTransform={this.screenToLocalContent}
                         rootSelected={this.rootSelected}
                         onClickScript={this.onClickFunc}
                         setTitleFocus={this.setTitleFocus}
@@ -1291,7 +1275,7 @@ export class DocumentView extends DocComponent<DocumentViewProps>() {
         if (this.ComponentView?.screenBounds?.()) {
             return this.ComponentView.screenBounds();
         }
-        const xf = this.screenToContentsTransform().scale(this.nativeScaling).inverse();
+        const xf = this.screenToContentBoundsTransform().inverse();
         const [[left, top], [right, bottom]] = [xf.transformPoint(0, 0), xf.transformPoint(this.panelWidth, this.panelHeight)];
 
         // transition is returned so that the bounds will 'update' at the end of an animated transition.  This is needed by xAnchor in LinkBox
@@ -1498,9 +1482,13 @@ export class DocumentView extends DocComponent<DocumentViewProps>() {
     isHovering = () => this._isHovering;
     selfView = () => this;
     /**
-     * @returns Transform to the document view (in the coordinate system of whatever contains the DocumentView)
+     * @returns Transform to the document view's available panel space (in the coordinate system of whatever contains the DocumentView)
      */
     screenToViewTransform = () => this._props.ScreenToLocalTransform();
+    /**
+     * @returns Transform to the document view after centering in available panel space(in the coordinate system of whatever contains the DocumentView)
+     */
+    private screenToContentBoundsTransform = () => this.screenToViewTransform().translate(-this.centeringX, -this.centeringY);
     /**
      * @returns Transform to the coordinate system of the contents of the document view (includes native dimension scaling and centering)
      */
@@ -1508,7 +1496,8 @@ export class DocumentView extends DocComponent<DocumentViewProps>() {
         this._props
             .ScreenToLocalTransform()
             .translate(-this.centeringX, -this.centeringY)
-            .scale(1 / this.nativeScaling);
+            .translate(-(this._docViewInternal?.aiShift() ?? 0), 0)
+            .scale((this._docViewInternal?.aiScale() ?? 1) / this.nativeScaling);
 
     htmlOverlay = () => {
         const effect = StrCast(this._htmlOverlayEffect?.presentation_effect, StrCast(this._htmlOverlayEffect?.followLinkAnimEffect));
diff --git a/src/client/views/nodes/IconTagBox.tsx b/src/client/views/nodes/IconTagBox.tsx
index 0bbd6a0d3..d04ec3a10 100644
--- a/src/client/views/nodes/IconTagBox.tsx
+++ b/src/client/views/nodes/IconTagBox.tsx
@@ -58,16 +58,20 @@ export class IconTagBox extends ObservableReactComponent<IconTagProps> {
         const tag = StrCast(key.toolType);
         const color = dv._props.styleProvider?.(dv.layoutDoc, dv.ComponentView?._props, StyleProp.FontColor) as string;
         return (
-            <Toggle
-                tooltip={`Click to add/remove the tag ${tag}`}
-                toggleStatus={TagItem.docHasTag(dv.Document, tag)}
-                toggleType={ToggleType.BUTTON}
-                icon={<FontAwesomeIcon className={`fontIconBox-icon-${ToggleType.BUTTON}`} icon={icon} color={color} />}
-                size={Size.XSMALL}
-                type={Type.PRIM}
-                onClick={() => this.setIconTag(tag, !TagItem.docHasTag(this.View.Document, tag))}
-                color={color}
-            />
+            <div>
+                {' '}
+                {/* tooltips require the wrapped item to be an element ref */}
+                <Toggle
+                    tooltip={`Click to add/remove the tag ${tag}`}
+                    toggleStatus={TagItem.docHasTag(dv.Document, tag)}
+                    toggleType={ToggleType.BUTTON}
+                    icon={<FontAwesomeIcon className={`fontIconBox-icon-${ToggleType.BUTTON}`} icon={icon} color={color} />}
+                    size={Size.XSMALL}
+                    type={Type.PRIM}
+                    onClick={() => this.setIconTag(tag, !TagItem.docHasTag(this.View.Document, tag))}
+                    color={color}
+                />
+            </div>
         );
     };
 
diff --git a/src/client/views/nodes/ImageBox.scss b/src/client/views/nodes/ImageBox.scss
index 9f7a5d03f..5a6292fab 100644
--- a/src/client/views/nodes/ImageBox.scss
+++ b/src/client/views/nodes/ImageBox.scss
@@ -236,7 +236,7 @@
     .imageBox-aiView-input {
         overflow: hidden;
         text-overflow: ellipsis;
-        max-width: 65%;
+        max-width: 80%;
         width: 100%;
         color: black;
     }
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index d16baada6..bf6915570 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -1,6 +1,6 @@
-import { Button, Colors, EditableText, IconButton, Size, Toggle, ToggleType, Type } from '@dash/components';
+import { Button, Colors, EditableText, IconButton, NumberDropdown, Size, Toggle, ToggleType, Type } from '@dash/components';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { Slider, Tooltip } from '@mui/material';
+import { Tooltip } from '@mui/material';
 import axios from 'axios';
 import { action, computed, IReactionDisposer, makeObservable, observable, ObservableMap, reaction, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
@@ -37,7 +37,6 @@ import { OverlayView } from '../OverlayView';
 import { AnchorMenu } from '../pdf/AnchorMenu';
 import { PinDocView, PinProps } from '../PinFuncs';
 import { DrawingFillHandler } from '../smartdraw/DrawingFillHandler';
-import { FireflyImageData, isFireflyImageData } from '../smartdraw/FireflyConstants';
 import { SmartDrawHandler } from '../smartdraw/SmartDrawHandler';
 import { StickerPalette } from '../smartdraw/StickerPalette';
 import { StyleProp } from '../StyleProp';
@@ -102,7 +101,6 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
     @observable private _regenInput = '';
     @observable private _canInteract = true;
     @observable private _regenerateLoading = false;
-    @observable private _prevImgs: FireflyImageData[] = StrCast(this.Document.ai_firefly_history) ? JSON.parse(StrCast(this.Document.ai_firefly_history)) : [];
 
     // Add these observable properties to the ImageBox class
     @observable private _outpaintingInProgress = false;
@@ -845,34 +843,10 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
     @observable _filterFunc: ((doc: Doc) => boolean) | undefined = undefined;
     @observable private _fireflyRefStrength = 0;
 
-    componentAIViewHistory = () => (
-        <div className="imageBox-aiView-history">
-            <Button text="Clear History" type={Type.SEC} size={Size.XSMALL} />
-            {this._prevImgs.map(img => (
-                <div key={img.pathname}>
-                    <img
-                        className="imageBox-aiView-img"
-                        src={ClientUtils.prepend(img.pathname.replace(extname(img.pathname), '_s' + extname(img.pathname)))}
-                        onClick={() => {
-                            this.dataDoc[this.fieldKey] = new ImageField(img.pathname);
-                            this.dataDoc.ai_firefly_prompt = img.prompt;
-                            this.dataDoc.ai_firefly_seed = img.seed;
-                        }}
-                    />
-                    <span>{img.prompt}</span>
-                </div>
-            ))}
-        </div>
-    );
-
     componentAIView = () => {
-        const field = this.dataDoc[this.fieldKey] instanceof ImageField ? Cast(this.dataDoc[this.fieldKey], ImageField, null) : new ImageField(String(this.dataDoc[this.fieldKey]));
         return (
             <div className="imageBox-aiView">
                 <div className="imageBox-aiView-regenerate">
-                    <span className="imageBox-aiView-firefly" style={{ color: SnappingManager.userColor }}>
-                        Firefly:
-                    </span>
                     <input
                         style={{ color: SnappingManager.userColor, background: SnappingManager.userBackgroundColor }}
                         className="imageBox-aiView-input"
@@ -886,57 +860,39 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
                         <Button
                             text="Create"
                             type={Type.TERT}
+                            size={Size.XSMALL}
                             color={SnappingManager.userColor}
                             background={SnappingManager.userBackgroundColor}
                             // style={{ alignSelf: 'flex-end' }}
                             icon={this._regenerateLoading ? <ReactLoading type="spin" color={SettingsManager.userVariantColor} width={16} height={20} /> : <AiOutlineSend />}
                             iconPlacement="right"
-                            onClick={action(async () => {
+                            onClick={action(() => {
                                 this._regenerateLoading = true;
-                                if (this._fireflyRefStrength) {
-                                    DrawingFillHandler.drawingToImage(this.Document, this._fireflyRefStrength, this._regenInput || StrCast(this.Document.title))?.then(action(() => (this._regenerateLoading = false)));
-                                } else {
-                                    SmartDrawHandler.Instance.regenerate([this.Document], undefined, undefined, this._regenInput || StrCast(this.Document.title), true).then(
-                                        action(newImgs => {
-                                            const firstImg = newImgs[0];
-                                            if (isFireflyImageData(firstImg)) {
-                                                const url = firstImg.pathname;
-                                                const imgField = new ImageField(url);
-                                                this._prevImgs.length === 0 &&
-                                                    this._prevImgs.push({ prompt: StrCast(this.dataDoc.ai_firefly_prompt), seed: this.dataDoc.ai_firefly_seed as number, href: this.paths.lastElement(), pathname: field?.url.pathname ?? '' });
-                                                this._prevImgs.unshift({ prompt: firstImg.prompt, seed: firstImg.seed, pathname: url });
-                                                this.dataDoc.ai_firefly_history = JSON.stringify(this._prevImgs);
-                                                this.dataDoc.ai_firefly_prompt = firstImg.prompt;
-                                                this.dataDoc[this.fieldKey] = imgField;
-                                                this._regenerateLoading = false;
-                                                this._regenInput = '';
-                                            }
-                                        })
-                                    );
-                                }
+                                DrawingFillHandler.drawingToImage(this.Document, this._fireflyRefStrength, this._regenInput || StrCast(this.Document.title))?.then(action(() => (this._regenerateLoading = false)));
                             })}
                         />
                     </div>
-                </div>
-                <div className="imageBox-aiView-strength">
-                    <span className="imageBox-aiView-similarity" style={{ color: SnappingManager.userColor }}>
-                        Similarity
-                    </span>
-                    <Slider
-                        className="imageBox-aiView-slider"
-                        sx={{
-                            '& .MuiSlider-track': { color: SettingsManager.userColor },
-                            '& .MuiSlider-rail': { color: SettingsManager.userBackgroundColor },
-                            '& .MuiSlider-thumb': { color: SettingsManager.userColor, '&.Mui-focusVisible, &:hover, &.Mui-active': { boxShadow: `0px 0px 0px 8px${SettingsManager.userColor.slice(0, 7)}10` } },
-                        }}
-                        min={0}
-                        max={100}
-                        step={1}
-                        size="small"
-                        value={this._fireflyRefStrength}
-                        onChange={action((e, val) => this._canInteract && (this._fireflyRefStrength = val as number))}
-                        valueLabelDisplay="auto"
-                    />
+                    <div>
+                        <NumberDropdown
+                            color={SnappingManager.userColor}
+                            background={SnappingManager.userBackgroundColor}
+                            numberDropdownType="slider"
+                            showPlusMinus={false}
+                            formLabel="similarity"
+                            tooltip="structure similarity of created images to current image"
+                            type={Type.PRIM}
+                            width={75}
+                            min={0}
+                            max={100}
+                            number={this._fireflyRefStrength}
+                            size={Size.XXSMALL}
+                            setNumber={undoable(
+                                action(val => this._canInteract && (this._fireflyRefStrength = val as number)),
+                                `${this.Document.title} button set from list`
+                            )}
+                            fillWidth
+                        />
+                    </div>
                 </div>
             </div>
         );
diff --git a/src/client/views/nodes/imageEditor/ImageEditor.tsx b/src/client/views/nodes/imageEditor/ImageEditor.tsx
index 85bd95d15..198b8e713 100644
--- a/src/client/views/nodes/imageEditor/ImageEditor.tsx
+++ b/src/client/views/nodes/imageEditor/ImageEditor.tsx
@@ -281,11 +281,14 @@ const ImageEditor = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addDoc
         try {
             const canvasOriginalImg = ImageUtility.getCanvasImg(img);
             if (!canvasOriginalImg) return;
-            const canvasMask = ImageUtility.getCanvasMask(canvas, canvasOriginalImg);
+            const canvasMask = ImageUtility.getCanvasMask(canvas, canvas);
             if (!canvasMask) return;
             const maskBlob = await ImageUtility.canvasToBlob(canvasMask);
             const imgBlob = await ImageUtility.canvasToBlob(canvasOriginalImg);
             const res = await ImageUtility.getEdit(imgBlob, maskBlob, input || 'Fill in the image in the same style', 2);
+            if ((res as any).status == 'error') {
+                alert((res as any).message);
+            }
 
             // create first image
             if (!newCollectionRef.current) {
diff --git a/src/client/views/nodes/imageEditor/imageEditorUtils/ImageHandler.ts b/src/client/views/nodes/imageEditor/imageEditorUtils/ImageHandler.ts
index 1c6a38a24..d6093c6eb 100644
--- a/src/client/views/nodes/imageEditor/imageEditorUtils/ImageHandler.ts
+++ b/src/client/views/nodes/imageEditor/imageEditorUtils/ImageHandler.ts
@@ -75,7 +75,7 @@ export class ImageUtility {
         fd.append('mask', maskBlob, 'mask.png');
         fd.append('prompt', prompt);
         fd.append('size', '1024x1024');
-        fd.append('n', n ? JSON.stringify(n) : '1');
+        fd.append('n', n ? n + '' : '1');
         fd.append('response_format', 'b64_json');
 
         try {
@@ -268,14 +268,14 @@ export class ImageUtility {
             ctx.drawImage(img, xOffset, 0, width, height);
 
             // draw reflected image padding
-            this.drawHorizontalReflection(ctx, canvas, xOffset);
+            // this.drawHorizontalReflection(ctx, canvas, xOffset);
         } else {
             // vertical padding, y offset
             const yOffset = Math.floor((canvasSize - height) / 2);
             ctx.drawImage(img, 0, yOffset, width, height);
 
             // draw reflected image padding
-            this.drawVerticalReflection(ctx, canvas, yOffset);
+            //  this.drawVerticalReflection(ctx, canvas, yOffset);
         }
         return canvas;
     };
diff --git a/src/client/views/pdf/GPTPopup/GPTPopup.scss b/src/client/views/pdf/GPTPopup/GPTPopup.scss
index bb43291ee..f6fa45221 100644
--- a/src/client/views/pdf/GPTPopup/GPTPopup.scss
+++ b/src/client/views/pdf/GPTPopup/GPTPopup.scss
@@ -7,6 +7,11 @@ $highlightedText: #82e0ff;
 $inputHeight: 60px;
 $headingHeight: 32px;
 
+.gptPopup-sortBox {
+    display: block;
+    max-height: calc(100% - 45px); // leave room for input
+}
+
 .gptPopup-summary-box {
     position: fixed;
     padding-left: 10px;
@@ -87,6 +92,7 @@ $headingHeight: 32px;
     }
     .btns-wrapper-gpt {
         height: 100%;
+        width: 100%;
         display: flex;
         justify-content: center;
         align-items: center;
@@ -97,7 +103,6 @@ $headingHeight: 32px;
             flex-direction: column;
             width: 100%;
             height: 100%;
-            overflow-y: auto;
             padding-right: 5px;
         }
 
diff --git a/src/client/views/pdf/GPTPopup/GPTPopup.tsx b/src/client/views/pdf/GPTPopup/GPTPopup.tsx
index c45d8e052..9fbae5c90 100644
--- a/src/client/views/pdf/GPTPopup/GPTPopup.tsx
+++ b/src/client/views/pdf/GPTPopup/GPTPopup.tsx
@@ -93,7 +93,7 @@ export class GPTPopup extends ObservableReactComponent<object> {
                     this.onGptResponse = (sortResult: string, questionType: GPTDocCommand, args?: string) => this.processGptResponse(selDoc, this._textToDocMap, sortResult, questionType, args);
                     this.onQuizRandom = () => this.randomlyChooseDoc(selDoc.Document, hasChildDocs());
                     this._documentDescriptions = Promise.all(hasChildDocs().map(doc =>
-                            Doc.getDescription(doc).then(text => this._textToDocMap.set(text.trim(), doc) && `${DescriptionSeperator}${text}${DescriptionSeperator}`)
+                            Doc.getDescription(doc).then(text => this._textToDocMap.set(text.replace(/\n/g, ' ').trim(), doc) && `${DescriptionSeperator}${text}${DescriptionSeperator}`)
                         )).then(docDescriptions => docDescriptions.join()); // prettier-ignore
                 }
             },
@@ -406,73 +406,80 @@ export class GPTPopup extends ObservableReactComponent<object> {
     scrollToBottom = () => setTimeout(() => this._messagesEndRef.current?.scrollIntoView({ behavior: 'smooth', block: 'end' }), 50);
 
     gptMenu = () => (
-        <div className="btns-wrapper-gpt">
-            <Button
-                tooltip="Ask Firefly to create images"
-                text="Ask Firefly"
-                onClick={() => this.setMode(GPTPopupMode.FIREFLY)}
-                color={SettingsManager.userColor}
-                background={SettingsManager.userVariantColor}
-                type={Type.TERT}
-                style={{
-                    width: '100%',
-                    height: '40%',
-                    textAlign: 'center',
-                    color: '#ffffff',
-                    fontSize: '16px',
-                    marginBottom: '10px',
-                }}
-            />
-            <Button
-                tooltip="Ask GPT to sort, tag, define, or filter your Docs!"
-                text="Ask GPT"
-                onClick={() => this.setMode(GPTPopupMode.USER_PROMPT)}
-                color={SettingsManager.userColor}
-                background={SettingsManager.userVariantColor}
-                type={Type.TERT}
-                style={{
-                    width: '100%',
-                    height: '40%',
-                    textAlign: 'center',
-                    color: '#ffffff',
-                    fontSize: '16px',
-                    marginBottom: '10px',
-                }}
-            />
-            <Button
-                tooltip="Test your knowledge by verifying answers with ChatGPT"
-                text="Take Quiz"
-                onClick={() => {
-                    this._conversationArray = ['Define the selected card!'];
-                    this.setMode(GPTPopupMode.QUIZ_RESPONSE);
-                    this.onQuizRandom?.();
-                }}
-                color={SettingsManager.userColor}
-                background={SettingsManager.userVariantColor}
-                type={Type.TERT}
-                style={{
-                    width: '100%',
-                    height: '40%',
-                    textAlign: 'center',
-                    color: '#ffffff',
-                    fontSize: '16px',
-                }}
-            />
+        <div style={{ display: 'flex', maxHeight: 'calc(100% - 32px)', overflow: 'auto' }}>
+            <div className="btns-wrapper-gpt">
+                <Button
+                    tooltip="Ask Firefly to create images"
+                    text="Ask Firefly"
+                    onClick={() => this.setMode(GPTPopupMode.FIREFLY)}
+                    color={SettingsManager.userColor}
+                    background={SettingsManager.userVariantColor}
+                    type={Type.TERT}
+                    style={{
+                        width: '100%',
+                        height: '40%',
+                        textAlign: 'center',
+                        color: '#ffffff',
+                        fontSize: '16px',
+                        marginBottom: '10px',
+                    }}
+                />
+                <Button
+                    tooltip="Ask GPT to sort, tag, define, or filter your Docs!"
+                    text="Ask GPT"
+                    onClick={() => this.setMode(GPTPopupMode.USER_PROMPT)}
+                    color={SettingsManager.userColor}
+                    background={SettingsManager.userVariantColor}
+                    type={Type.TERT}
+                    style={{
+                        width: '100%',
+                        height: '40%',
+                        textAlign: 'center',
+                        color: '#ffffff',
+                        fontSize: '16px',
+                        marginBottom: '10px',
+                    }}
+                />
+                <Button
+                    tooltip="Test your knowledge by verifying answers with ChatGPT"
+                    text="Take Quiz"
+                    onClick={() => {
+                        this._conversationArray = ['Define the selected card!'];
+                        this.setMode(GPTPopupMode.QUIZ_RESPONSE);
+                        this.onQuizRandom?.();
+                    }}
+                    color={SettingsManager.userColor}
+                    background={SettingsManager.userVariantColor}
+                    type={Type.TERT}
+                    style={{
+                        width: '100%',
+                        height: '40%',
+                        textAlign: 'center',
+                        color: '#ffffff',
+                        fontSize: '16px',
+                    }}
+                />
+            </div>
         </div>
     );
 
     callGpt = action((mode: GPTPopupMode) => {
         this.setGptProcessing(true);
+        const reset = action(() => {
+            this.setGptProcessing(false);
+            this._userPrompt = '';
+            this._quizAnswer = '';
+        });
         switch (mode) {
             case GPTPopupMode.FIREFLY:
                 this._fireflyArray.push(this._userPrompt);
-                return this.generateFireflyImage(this._userPrompt).then(action(() => (this._userPrompt = '')));
+                return this.generateFireflyImage(this._userPrompt).then(reset);
             case GPTPopupMode.USER_PROMPT:
                 this._conversationArray.push(this._userPrompt);
-                return this.generateUserPromptResponse(this._userPrompt).then(action(() => (this._userPrompt = '')));
+                return this.generateUserPromptResponse(this._userPrompt).then(reset);
             case GPTPopupMode.QUIZ_RESPONSE:
                 this._conversationArray.push(this._quizAnswer);
-                return this.generateQuizAnswerAnalysis(DocumentView.SelectedDocs().lastElement(), this._quizAnswer).then(action(() => (this._quizAnswer = '')));
+                return this.generateQuizAnswerAnalysis(DocumentView.SelectedDocs().lastElement(), this._quizAnswer).then(reset);
         }
     });
 
@@ -490,18 +497,20 @@ export class GPTPopup extends ObservableReactComponent<object> {
     };
 
     gptUserInput = () => (
-        <div className="btns-wrapper-gpt">
-            <div className="chat-wrapper">
-                <div className="chat-bubbles">
-                    {(this._mode === GPTPopupMode.FIREFLY ? this._fireflyArray : this._conversationArray).map((message, index) => (
-                        <div key={index} className={`chat-bubble ${index % 2 === 1 ? 'user-message' : 'chat-message'}`}>
-                            {message}
-                        </div>
-                    ))}
-                    {this._gptProcessing && <div className="chat-bubble chat-message">...</div>}
-                </div>
+        <div style={{ display: 'flex', maxHeight: 'calc(100% - 32px)', overflow: 'auto' }}>
+            <div className="btns-wrapper-gpt">
+                <div className="chat-wrapper">
+                    <div className="chat-bubbles">
+                        {(this._mode === GPTPopupMode.FIREFLY ? this._fireflyArray : this._conversationArray).map((message, index) => (
+                            <div key={index} className={`chat-bubble ${index % 2 === 1 ? 'user-message' : 'chat-message'}`}>
+                                {message}
+                            </div>
+                        ))}
+                        {this._gptProcessing && <div className="chat-bubble chat-message">...</div>}
+                    </div>
 
-                <div ref={this._messagesEndRef} style={{ height: '100px' }} />
+                    <div ref={this._messagesEndRef} style={{ height: '40px' }} />
+                </div>
             </div>
         </div>
     );
@@ -520,7 +529,7 @@ export class GPTPopup extends ObservableReactComponent<object> {
                     onChange={e => onChange(e.target.value)}
                     onKeyDown={e => this.handleKeyPress(e, this._mode)}
                     type="text"
-                    style={{ color: SnappingManager.userColor }}
+                    style={{ color: 'black' }}
                     placeholder={placeholder}
                 />
                 <Button //
@@ -744,7 +753,7 @@ export class GPTPopup extends ObservableReactComponent<object> {
                         onClick={() => this._collectionContext && Doc.setDocFilter(this._collectionContext, 'tags', GPTPopup.ChatTag, 'remove')}
                     />
                     {[GPTPopupMode.USER_PROMPT, GPTPopupMode.QUIZ_RESPONSE, GPTPopupMode.FIREFLY].includes(this._mode) && (
-                        <IconButton color={SettingsManager.userVariantColor} background={SettingsManager.userColor} tooltip="back" icon={<CgCornerUpLeft size="16px" />} onClick={() => (this._mode = GPTPopupMode.GPT_MENU)} />
+                        <IconButton color={SettingsManager.userVariantColor} background={SettingsManager.userColor} tooltip="back" icon={<CgCornerUpLeft size="16px" />} onClick={action(() => (this._mode = GPTPopupMode.GPT_MENU))} />
                     )}
                 </>
             )}
@@ -753,7 +762,7 @@ export class GPTPopup extends ObservableReactComponent<object> {
 
     render() {
         return (
-            <div className="gptPopup-summary-box" style={{ background: SnappingManager.userColor, color: SnappingManager.userBackgroundColor, display: SnappingManager.ChatVisible ? 'flex' : 'none', overflow: 'auto' }}>
+            <div className="gptPopup-summary-box" style={{ background: SnappingManager.userColor, color: SnappingManager.userBackgroundColor, display: SnappingManager.ChatVisible ? 'flex' : 'none' }}>
                 {(() => {
                     //prettier-ignore
                     switch (this._mode) {
diff --git a/src/client/views/smartdraw/SmartDrawHandler.tsx b/src/client/views/smartdraw/SmartDrawHandler.tsx
index 4f0cd3978..a6e221f3e 100644
--- a/src/client/views/smartdraw/SmartDrawHandler.tsx
+++ b/src/client/views/smartdraw/SmartDrawHandler.tsx
@@ -398,7 +398,7 @@ export class SmartDrawHandler extends ObservableReactComponent<object> {
      */
     colorWithGPT = async (drawing: Doc) => {
         const img = await DocumentView.GetDocImage(drawing);
-        const { href } = ImageCast(img).url;
+        const { href } = ImageCast(img)?.url ?? { href: '' };
         const hrefParts = href.split('.');
         const hrefComplete = `${hrefParts[0]}_o.${hrefParts[1]}`;
         try {
-- 
cgit v1.2.3-70-g09d2

