From 18774b42e3c8e1e899978fe9f16a4d123adee803 Mon Sep 17 00:00:00 2001
From: Geireann Lindfield Roberts <geireann_lindfield_roberts@brown.edu>
Date: Wed, 1 Jan 2025 22:32:19 -0800
Subject: monorepo setup

---
 src/client/views/DocumentButtonBar.tsx | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

(limited to 'src/client/views/DocumentButtonBar.tsx')

diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx
index 32bf67df1..9e010afbb 100644
--- a/src/client/views/DocumentButtonBar.tsx
+++ b/src/client/views/DocumentButtonBar.tsx
@@ -2,7 +2,7 @@ import { IconLookup, IconProp } from '@fortawesome/fontawesome-svg-core';
 import { faCalendarDays } from '@fortawesome/free-solid-svg-icons';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { Tooltip } from '@mui/material';
-import { Popup } from 'browndash-components';
+import { Popup } from '@dash/components';
 import { action, computed, makeObservable, observable } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
-- 
cgit v1.2.3-70-g09d2


From 6e7cb570f9bad527cd4772bb5c715dd588fb77df Mon Sep 17 00:00:00 2001
From: eleanor-park <eleanor_park@brown.edu>
Date: Thu, 2 Jan 2025 22:39:25 -0500
Subject: tags can now be used as style presets

---
 src/client/apis/gpt/GPT.ts                        |  2 +-
 src/client/views/DocumentButtonBar.tsx            | 18 ++++++++++++++++++
 src/client/views/nodes/DocumentView.tsx           |  2 ++
 src/client/views/smartdraw/DrawingFillHandler.tsx | 10 +++++++---
 src/client/views/smartdraw/FireflyConstants.ts    | 22 ++++++++++++++++++++++
 src/server/ApiManagers/FireflyManager.ts          |  7 +++++--
 6 files changed, 55 insertions(+), 6 deletions(-)

(limited to 'src/client/views/DocumentButtonBar.tsx')

diff --git a/src/client/apis/gpt/GPT.ts b/src/client/apis/gpt/GPT.ts
index 9241eb120..4b0d58cc1 100644
--- a/src/client/apis/gpt/GPT.ts
+++ b/src/client/apis/gpt/GPT.ts
@@ -260,7 +260,7 @@ const gptDescribeImage = async (image: string): Promise<string> => {
                     content: [
                         {
                             type: 'text',
-                            text: `Identify what this drawing is, naming as many elements and their location in the drawing as possible`,
+                            text: `Briefly identify what this drawing is, naming all the drawing elements and their location within the image. Do not include anything about the drawing style.`,
                         },
                         {
                             type: 'image_url',
diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx
index 32bf67df1..b7033af3f 100644
--- a/src/client/views/DocumentButtonBar.tsx
+++ b/src/client/views/DocumentButtonBar.tsx
@@ -314,6 +314,23 @@ export class DocumentButtonBar extends ObservableReactComponent<{ views: () => (
         );
     }
 
+    @computed
+    get aiEditorButton() {
+        const targetDoc = this.view0?.Document;
+        return !targetDoc ? null : (
+            <Tooltip title={<div className="dash-ai-editor-button">Edit with AI</div>}>
+                <div
+                    className="documentButtonBar-icon"
+                    style={{ color: 'white' }}
+                    onClick={() => {
+                        CalendarManager.Instance.open(this.view0, targetDoc);
+                    }}>
+                    <FontAwesomeIcon className="documentdecorations-icon" icon="robot" />
+                </div>
+            </Tooltip>
+        );
+    }
+
     @observable _isRecording = false;
     _stopFunc: () => void = emptyFunction;
     @computed
@@ -484,6 +501,7 @@ export class DocumentButtonBar extends ObservableReactComponent<{ views: () => (
                 <div className="documentButtonBar-button">{this.pinButton}</div>
                 <div className="documentButtonBar-button">{this.recordButton}</div>
                 <div className="documentButtonBar-button">{this.calendarButton}</div>
+                <div className="documentButtonBar-button">{this.aiEditorButton}</div>
                 <div className="documentButtonBar-button">{this.keywordButton}</div>
                 {!Doc.UserDoc().documentLinksButton_fullMenu ? null : <div className="documentButtonBar-button">{this.shareButton}</div>}
                 <div className="documentButtonBar-button">{this.menuButton}</div>
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 8519cda3c..048b92c71 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -1400,6 +1400,8 @@ export class DocumentView extends DocComponent<DocumentViewProps>() {
     NativeHeight = () => this.effectiveNativeHeight;
     PanelWidth = () => this.panelWidth;
     PanelHeight = () => this.panelHeight;
+    ReducedPanelWidth = () => this.panelWidth / 2;
+    ReducedPanelHeight = () => this.panelWidth / 2;
     NativeDimScaling = () => this.nativeScaling;
     hideLinkCount = () => !!this.hideLinkButton;
     isHovering = () => this._isHovering;
diff --git a/src/client/views/smartdraw/DrawingFillHandler.tsx b/src/client/views/smartdraw/DrawingFillHandler.tsx
index 7a95e27c2..52652d377 100644
--- a/src/client/views/smartdraw/DrawingFillHandler.tsx
+++ b/src/client/views/smartdraw/DrawingFillHandler.tsx
@@ -8,10 +8,13 @@ import { Docs } from '../../documents/Documents';
 import { Networking } from '../../Network';
 import { DocumentView, DocumentViewInternal } from '../nodes/DocumentView';
 import { OpenWhere } from '../nodes/OpenWhere';
-import { AspectRatioLimits, FireflyDimensionsMap, FireflyImageDimensions } from './FireflyConstants';
+import { AspectRatioLimits, FireflyDimensionsMap, FireflyImageDimensions, FireflyStylePresets } from './FireflyConstants';
 
 export class DrawingFillHandler {
-    static drawingToImage = (drawing: Doc, strength: number, prompt: string) =>
+    static drawingToImage = (drawing: Doc, strength: number, prompt: string) => {
+        const docData = drawing[DocData];
+        const tags: string[] = ((docData?.tags as unknown as string[]) ?? []).map(tag => tag.slice(1)) ?? [];
+        const styles = tags.filter(tag => FireflyStylePresets.has(tag));
         DocumentView.GetDocImage(drawing)?.then(imageField => {
             if (imageField) {
                 const aspectRatio = (drawing.width as number) / (drawing.height as number);
@@ -31,11 +34,12 @@ export class DrawingFillHandler {
                 imageUrlToBase64(structureUrl)
                     .then((hrefBase64: string) => gptDescribeImage(hrefBase64))
                     .then((prompt: string) => {
-                        Networking.PostToServer('/queryFireflyImageFromStructure', { prompt: prompt, width: dims.width, height: dims.height, structureUrl, strength }).then((info: Upload.ImageInformation) =>
+                        Networking.PostToServer('/queryFireflyImageFromStructure', { prompt: prompt, width: dims.width, height: dims.height, structureUrl: structureUrl, strength: strength, styles: styles }).then((info: Upload.ImageInformation) =>
                             DocumentViewInternal.addDocTabFunc(Docs.Create.ImageDocument(info.accessPaths.agnostic.client, { ai_generated: true, nativeWidth: dims.width, nativeHeight: dims.height }), OpenWhere.addRight)
                         ); // prettier-ignore
                     });
             }
             return false;
         });
+    };
 }
diff --git a/src/client/views/smartdraw/FireflyConstants.ts b/src/client/views/smartdraw/FireflyConstants.ts
index f51305fba..3574039e4 100644
--- a/src/client/views/smartdraw/FireflyConstants.ts
+++ b/src/client/views/smartdraw/FireflyConstants.ts
@@ -18,3 +18,25 @@ export const AspectRatioLimits = {
     portrait: 0.875,
     widescreen: 1.472,
 };
+
+// prettier-ignore
+export const FireflyStylePresets = 
+    new Set<string>(['graphic', 'wireframe',
+        'vector_look','bw','cool_colors','golden','monochromatic','muted_color','toned_image','vibrant_colors','warm_tone','closeup',
+        'knolling','landscape_photography','macrophotography','photographed_through_window','shallow_depth_of_field','shot_from_above',
+        'shot_from_below','surface_detail','wide_angle','beautiful','bohemian','chaotic','dais','divine','eclectic','futuristic','kitschy',
+        'nostalgic','simple','antique_photo','bioluminescent','bokeh','color_explosion','dark','faded_image','fisheye','gomori_photography',
+        'grainy_film','iridescent','isometric','misty','neon','otherworldly_depiction','ultraviolet','underwater', 'backlighting', 
+        'dramatic_light', 'golden_hour', 'harsh_light','long','low_lighting','multiexposure','studio_light','surreal_lighting',
+        '3d_patterns','charcoal','claymation','fabric','fur','guilloche_patterns','layered_paper','marble_sculpture','made_of_metal',
+        'origami','paper_mache','polka','strange_patterns','wood_carving','yarn','art_deco','art_nouveau','baroque','bauhaus',
+        'constructivism','cubism','cyberpunk','fantasy','fauvism', 'film_noir','glitch_art','impressionism','industrialism','maximalism',
+        'minimalism','modern_art','modernism','neo','pointillism','psychedelic','science_fiction','steampunk','surrealism','synthetism',
+        'synthwave','vaporwave','acrylic_paint','bold_lines','chiaroscuro','color_shift_art','daguerreotype','digital_fractal',
+        'doodle_drawing','double_exposure_portrait','fresco','geometric_pen','halftone','ink','light_painting','line_drawing','linocut',
+        'oil_paint','paint_spattering','painting','palette_knife','photo_manipulation','scribble_texture','sketch','splattering',
+        'stippling_drawing','watercolor','3d','anime','cartoon','cinematic','comic_book','concept_art','cyber_matrix','digital_art',
+        'flat_design','geometric','glassmorphism','glitch_graphic','graffiti','hyper_realistic','interior_design','line_gradient',
+        'low_poly','newspaper_collage','optical_illusion','pattern_pixel','pixel_art','pop_art','product_photo','psychedelic_background',
+        'psychedelic_wonderland','scandinavian','splash_images','stamp','trompe_loeil'
+    ]);
diff --git a/src/server/ApiManagers/FireflyManager.ts b/src/server/ApiManagers/FireflyManager.ts
index 4c4aac5e0..cb5d7c1da 100644
--- a/src/server/ApiManagers/FireflyManager.ts
+++ b/src/server/ApiManagers/FireflyManager.ts
@@ -20,9 +20,10 @@ export default class FireflyManager extends ApiManager {
             return undefined;
         });
 
-    generateImageFromStructure = (prompt: string = 'a realistic illustration of a cat coding', width: number = 2048, height: number = 2048, structureUrl: string, strength: number = 50) =>
+    generateImageFromStructure = (prompt: string = 'a realistic illustration of a cat coding', width: number = 2048, height: number = 2048, structureUrl: string, strength: number = 50, styles: string[]) =>
         this.getBearerToken().then(response =>
             response?.json().then((data: { access_token: string }) =>
+                //prettier-ignore
                 fetch('https://firefly-api.adobe.io/v3/images/generate', {
                     method: 'POST',
                     headers: [
@@ -42,6 +43,8 @@ export default class FireflyManager extends ApiManager {
                                       source: { url: structureUrl },
                                   },
                               },
+                        //prettier-ignore
+                        style: { presets: styles }
                     }),
                 })
                     .then(response2 => response2.json().then(json => JSON.stringify((json.outputs?.[0] as { image: { url: string } })?.image)))
@@ -227,7 +230,7 @@ export default class FireflyManager extends ApiManager {
             subscription: '/queryFireflyImageFromStructure',
             secureHandler: async ({ req, res }) =>
                 this.uploadImageToDropbox(req.body.structureUrl).then(uploadUrl =>
-                    this.generateImageFromStructure(req.body.prompt, req.body.width, req.body.height, uploadUrl, req.body.strength).then(fire =>
+                    this.generateImageFromStructure(req.body.prompt, req.body.width, req.body.height, uploadUrl, req.body.strength, req.body.styles).then(fire =>
                         DashUploadUtils.UploadImage(JSON.parse(fire ?? '').url).then(info => {
                             if (info instanceof Error) _invalid(res, info.message);
                             else _success(res, info);
-- 
cgit v1.2.3-70-g09d2


From 1d62d867621b293c41ff8488ca5a3bd6010723d5 Mon Sep 17 00:00:00 2001
From: eleanor-park <eleanor_park@brown.edu>
Date: Sun, 5 Jan 2025 23:47:18 -0500
Subject: added AI image editor

---
 src/client/documents/Documents.ts                  |   6 +-
 src/client/util/CurrentUserUtils.ts                |   2 +-
 src/client/views/DocumentButtonBar.tsx             |   7 +-
 src/client/views/ViewBoxInterface.ts               |   1 +
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  12 +--
 src/client/views/nodes/DocumentView.tsx            |  22 +++-
 src/client/views/nodes/ImageBox.scss               |  28 +++++
 src/client/views/nodes/ImageBox.tsx                | 113 ++++++++++++++++++++-
 src/client/views/pdf/AnchorMenu.tsx                |  12 +--
 src/client/views/smartdraw/DrawingFillHandler.tsx  |   2 +-
 src/client/views/smartdraw/SmartDrawHandler.tsx    |  92 +++++++++--------
 src/client/views/smartdraw/StickerPalette.tsx      |  17 ++--
 src/server/ApiManagers/FireflyManager.ts           |   1 -
 13 files changed, 235 insertions(+), 80 deletions(-)

(limited to 'src/client/views/DocumentButtonBar.tsx')

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 785af3409..7f1387ff8 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -517,9 +517,9 @@ export class DocumentOptions {
     card_sort?: STRt = new StrInfo('way cards are sorted in deck view');
     card_sort_isDesc?: BOOLt = new BoolInfo('whether the cards are sorted ascending or descending');
 
-    ai_generated?: boolean; // to mark items as ai generated
-    firefly_seed?: number;
-    firefly_prompt?: string;
+    ai?: string; // to mark items as ai generated
+    ai_firefly_seed?: number;
+    ai_firefly_prompt?: string;
 }
 
 export const DocOptions = new DocumentOptions();
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index 9af79a02e..b41fd09dc 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -779,7 +779,7 @@ pie title Minerals in my tap water
             { title: " Size",      toolTip: "Size of area pencil eraser",   btnType: ButtonType.NumberSliderButton,                toolType: InkProperty.EraserWidth,ignoreClick: true, scripts: {script: '{ return setInkProperty(this.toolType, value, _readOnly_);}'}, funcs: {hidden:"NotRadiusEraser()"}, numBtnMin: 1, linearBtnWidth:40},
             { title: "Mask",       toolTip: "Make Stroke a Stencil Mask",   btnType: ButtonType.ToggleButton, icon: "user-circle", toolType: InkProperty.Mask,                          scripts: {onClick:'{ return setInkProperty(this.toolType, value, _readOnly_);}'}, funcs: {hidden:"IsNoviceMode()" } },
             { title: "Labels",     toolTip: "Show Labels Inside Shapes",    btnType: ButtonType.ToggleButton, icon: "text-width",  toolType: InkProperty.Labels,                        scripts: {onClick:'{ return setInkProperty(this.toolType, value, _readOnly_);}'}},
-            { title: "Smart Draw", toolTip: "Draw with GPT",                btnType: ButtonType.ToggleButton, icon: "user-pen",    toolType: InkTool.SmartDraw,                         scripts: {onClick:'{ return setActiveTool(this.toolType, false, _readOnly_);}'},  funcs: {hidden: "IsNoviceMode()"}},
+            { title: "Smart Draw", toolTip: "Draw with AI",                btnType: ButtonType.ToggleButton, icon: "user-pen",    toolType: InkTool.SmartDraw,                         scripts: {onClick:'{ return setActiveTool(this.toolType, false, _readOnly_);}'},  funcs: {hidden: "IsNoviceMode()"}},
         ];
     }
 
diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx
index b7033af3f..d722b28b5 100644
--- a/src/client/views/DocumentButtonBar.tsx
+++ b/src/client/views/DocumentButtonBar.tsx
@@ -319,12 +319,7 @@ export class DocumentButtonBar extends ObservableReactComponent<{ views: () => (
         const targetDoc = this.view0?.Document;
         return !targetDoc ? null : (
             <Tooltip title={<div className="dash-ai-editor-button">Edit with AI</div>}>
-                <div
-                    className="documentButtonBar-icon"
-                    style={{ color: 'white' }}
-                    onClick={() => {
-                        CalendarManager.Instance.open(this.view0, targetDoc);
-                    }}>
+                <div className="documentButtonBar-icon" style={{ color: 'white' }} onClick={undoable(() => this.view0?.toggleAIEditor(), 'toggle AI editor')}>
                     <FontAwesomeIcon className="documentdecorations-icon" icon="robot" />
                 </div>
             </Tooltip>
diff --git a/src/client/views/ViewBoxInterface.ts b/src/client/views/ViewBoxInterface.ts
index b7980d74e..df08f2564 100644
--- a/src/client/views/ViewBoxInterface.ts
+++ b/src/client/views/ViewBoxInterface.ts
@@ -60,4 +60,5 @@ export abstract class ViewBoxInterface<P> extends ObservableReactComponent<React
     search?: (str: string, bwd?: boolean, clear?: boolean) => boolean;
     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?: (top: number) => JSX.Element;
 }
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index ef0b80720..9af698ec7 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1290,12 +1290,12 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         const docData = doc[DocData];
         docData.title = opts.text.match(/^(.*?)~~~.*$/)?.[1] || opts.text;
         docData._width = opts.size;
-        docData.drawingInput = opts.text;
-        docData.drawingComplexity = opts.complexity;
-        docData.drawingColored = opts.autoColor;
-        docData.drawingSize = opts.size;
-        docData.drawingData = gptRes;
-        docData.ai_generated = true;
+        docData.ai_drawing_input = opts.text;
+        docData.ai_drawing_complexity = opts.complexity;
+        docData.ai_drawing_colored = opts.autoColor;
+        docData.ai_drawing_size = opts.size;
+        docData.ai_drawing_data = gptRes;
+        docData.ai = 'gpt';
         this._drawingContainer = doc;
         this.addDocument(doc);
         this._batch?.end();
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 048b92c71..ab58023e3 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -123,6 +123,12 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
     @observable _componentView: Opt<ViewBoxInterface<FieldViewProps>> = undefined; // needs to be accessed from DocumentView wrapper class
     @observable _animateScaleTime: Opt<number> = undefined; // milliseconds for animating between views.  defaults to 300 if not uset
     @observable _animateScalingTo = 0;
+    @observable public _showAIEditor: boolean = false;
+
+    @action
+    showAIEditor() {
+        this._showAIEditor = !this._showAIEditor;
+    }
 
     get _contentDiv()  { return this._mainCont.current; } // prettier-ignore
     get _docView()     { return this._props.DocumentView?.(); } // prettier-ignore
@@ -552,7 +558,6 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
                 appearanceItems.splice(0, 0, { description: 'Open in Lightbox', event: () => DocumentView.SetLightboxDoc(this.Document), icon: 'external-link-alt' });
             }
             appearanceItems.push({ description: 'Pin', event: () => this._props.pinToPres(this.Document, {}), icon: 'map-pin' });
-            appearanceItems.push({ description: 'Make Image', event: () => DrawingFillHandler.drawingToImage(this.Document, StrCast(this.Document.title)), icon: 'map-pin' });
 
             !Doc.noviceMode && templateDoc && appearanceItems.push({ description: 'Open Template   ', event: () => this._props.addDocTab(templateDoc, OpenWhere.addRight), icon: 'eye' });
             !appearance && appearanceItems.length && cm.addItem({ description: 'Appearance...', subitems: appearanceItems, icon: 'compass' });
@@ -711,6 +716,8 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
         return this._props.styleProvider?.(doc, props, property);
     };
 
+    rpw = () => this._props.PanelWidth() * 0.6;
+    rph = () => this.panelHeight() * 0.6;
     @computed get viewBoxContents() {
         TraceMobx();
         const isInk = this.layoutDoc._layout_isSvg && !this._props.LayoutTemplateString;
@@ -720,7 +727,9 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
                 className="documentView-contentsView"
                 style={{
                     pointerEvents: (isInk || noBackground ? 'none' : this.contentPointerEvents()) ?? (this._mounted ? 'all' : 'none'),
-                    height: this.headerMargin ? `calc(100% - ${this.headerMargin}px)` : undefined,
+                    width: this._showAIEditor ? this.rpw() : undefined,
+                    height: this._showAIEditor ? this.rph() : this.headerMargin ? `calc(100% - ${this.headerMargin}px)` : undefined,
+                    justifySelf: 'center',
                 }}>
                 <DocumentContentsView
                     {...this._props}
@@ -728,7 +737,8 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
                     pointerEvents={this.contentPointerEvents}
                     setContentViewBox={this.setContentView}
                     childFilters={this.childFilters}
-                    PanelHeight={this.panelHeight}
+                    PanelHeight={this._showAIEditor ? this.rpw : this.panelHeight}
+                    PanelWidth={this._showAIEditor ? this.rph : this._props.PanelWidth}
                     setHeight={this.setHeight}
                     isContentActive={this.isContentActive}
                     ScreenToLocalTransform={this.screenToLocalContent}
@@ -737,6 +747,7 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
                     setTitleFocus={this.setTitleFocus}
                     hideClickBehaviors={BoolCast(this.Document.hideClickBehaviors)}
                 />
+                {this._showAIEditor && (this._componentView?.componentAIView?.(this.rph()) ?? null)}
             </div>
         );
     }
@@ -1286,6 +1297,11 @@ export class DocumentView extends DocComponent<DocumentViewProps>() {
         }
     };
 
+    @action
+    public toggleAIEditor = () => {
+        this._docViewInternal && this._docViewInternal.showAIEditor();
+    };
+
     public setTextHtmlOverlay = action((text: string | undefined, effect?: Doc) => {
         this._htmlOverlayText = text;
         this._htmlOverlayEffect = effect;
diff --git a/src/client/views/nodes/ImageBox.scss b/src/client/views/nodes/ImageBox.scss
index 3ffda5a35..03314e90f 100644
--- a/src/client/views/nodes/ImageBox.scss
+++ b/src/client/views/nodes/ImageBox.scss
@@ -139,3 +139,31 @@
 .imageBox-fadeBlocker-hover {
     opacity: 0;
 }
+
+.imageBox-aiView {
+    padding: 5px;
+    position: absolute;
+    overflow: scroll;
+    text-align: center;
+    font-weight: bold;
+    margin-top: 5px;
+
+    .imageBox-aiView-subtitle {
+        align-self: start;
+    }
+
+    .imageBox-aiView-regenerate-container,
+    .imageBox-aiView-options-container {
+        font-weight: normal;
+        text-align: start;
+    }
+
+    .imageBox-aiView-regenerate,
+    .imageBox-aiView-options {
+        display: flex;
+        flex-direction: row;
+        justify-content: center;
+        flex-direction: row;
+        gap: 5px;
+    }
+}
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index 7ce429f0f..f00580d77 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -1,7 +1,7 @@
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { Tooltip } from '@mui/material';
 import axios from 'axios';
-import { Colors } from 'browndash-components';
+import { Colors, Type } from 'browndash-components';
 import { action, computed, IReactionDisposer, makeObservable, observable, ObservableMap, reaction } from 'mobx';
 import { observer } from 'mobx-react';
 import { extname } from 'path';
@@ -41,6 +41,9 @@ import './ImageBox.scss';
 import { OpenWhere } from './OpenWhere';
 import { Upload } from '../../../server/SharedMediaTypes';
 import { SmartDrawHandler } from '../smartdraw/SmartDrawHandler';
+import { Button } from 'browndash-components';
+import { SettingsManager } from '../../util/SettingsManager';
+import { AiOutlineSend } from 'react-icons/ai';
 
 export class ImageEditorData {
     // eslint-disable-next-line no-use-before-define
@@ -352,7 +355,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
                 }),
                 icon: 'pencil-alt',
             });
-            this.layoutDoc.ai_generated &&
+            this.layoutDoc.ai &&
                 funcs.push({
                     description: 'Regenerate AI Image',
                     event: action(e => {
@@ -526,6 +529,112 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
         );
     }
 
+    @observable private _regenInput = '';
+    @observable private _canInteract = true;
+    @observable private _regenerateLoading = false;
+
+    componentAIView = (top: number) => {
+        const field = Cast(this.dataDoc[this.fieldKey], ImageField);
+        const showRegenerate = this.Document[DocData].ai;
+        return (
+            <div className="imageBox-aiView" style={{ top: top, width: NumCast(this.Document.width), height: NumCast(this.Document.width) - top }}>
+                Edit Image with AI
+                {showRegenerate && (
+                    <div className="imageBox-aiView-regenerate-container">
+                        <text className="imageBox-aiView-subtitle">Regenerate AI Image</text>
+                        <div className="imageBox-aiView-regenerate">
+                            <input
+                                aria-label="Edit instructions input"
+                                // className="smartdraw-input"
+                                type="text"
+                                value={this._regenInput}
+                                onChange={action(e => this._canInteract && (this._regenInput = e.target.value))}
+                                // onKeyDown={this.handleKeyPress}
+                                placeholder="Prompt (Optional)"
+                            />
+                            <Button
+                                text="Regenerate"
+                                type={Type.SEC}
+                                // style={{ alignSelf: 'flex-end' }}
+                                icon={this._regenerateLoading && this._regenInput !== '' ? <ReactLoading type="spin" color={SettingsManager.userVariantColor} width={16} height={20} /> : <AiOutlineSend />}
+                                iconPlacement="right"
+                                onClick={action(async () => {
+                                    this._regenerateLoading = true;
+                                    await SmartDrawHandler.Instance.regenerate([this.Document], undefined, undefined, this._regenInput);
+                                    this._regenerateLoading = false;
+                                    this._regenInput = '';
+                                })}
+                            />
+                            <Button
+                                // style={{ alignSelf: 'flex-end' }}
+                                text="Get Variations"
+                                type={Type.SEC}
+                                // icon={this._isLoading && this._regenInput !== '' ? <ReactLoading type="spin" color={SettingsManager.userVariantColor} width={16} height={20} /> : <AiOutlineSend />}
+                                iconPlacement="right"
+                                // onClick={this.handleSendClick}
+                            />
+                        </div>
+                    </div>
+                )}
+                <div className="imageBox-aiView-options-container">
+                    {showRegenerate && <text className="imageBox-aiView-subtitle"> More Image Options </text>}
+                    <div className="imageBox-aiView-options">
+                        <Button
+                            type={Type.TERT}
+                            text="Get Text"
+                            icon={<FontAwesomeIcon icon="font" />}
+                            color={SettingsManager.userBackgroundColor}
+                            iconPlacement="right"
+                            onClick={() => {
+                                Networking.PostToServer('/queryFireflyImageText', {
+                                    file: (file => {
+                                        const ext = extname(file);
+                                        return file.replace(ext, (this._error ? '_o' : this._curSuffix) + ext);
+                                    })(ImageCast(this.Document[Doc.LayoutFieldKey(this.Document)])?.url.href),
+                                }).then(text => alert(text));
+                            }}
+                        />
+                        <Button
+                            type={Type.TERT}
+                            text="Generative Fill"
+                            icon={<FontAwesomeIcon icon="fill" />}
+                            color={SettingsManager.userBackgroundColor}
+                            // icon={this._isLoading && this._regenInput !== '' ? <ReactLoading type="spin" color={SettingsManager.userVariantColor} width={16} height={20} /> : <AiOutlineSend />}
+                            iconPlacement="right"
+                            onClick={action(() => {
+                                ImageEditorData.Open = true;
+                                ImageEditorData.Source = (field && this.choosePath(field.url)) || '';
+                                ImageEditorData.AddDoc = this._props.addDocument;
+                                ImageEditorData.RootDoc = this.Document;
+                            })}
+                        />
+                        <Button
+                            type={Type.TERT}
+                            text="Expand"
+                            icon={<FontAwesomeIcon icon="expand" />}
+                            color={SettingsManager.userBackgroundColor}
+                            // icon={this._isLoading && this._regenInput !== '' ? <ReactLoading type="spin" color={SettingsManager.userVariantColor} width={16} height={20} /> : <AiOutlineSend />}
+                            iconPlacement="right"
+                            onClick={() => {
+                                Networking.PostToServer('/expandImage', {
+                                    prompt: 'sunny skies',
+                                    file: (file => {
+                                        const ext = extname(file);
+                                        return file.replace(ext, (this._error ? '_o' : this._curSuffix) + ext);
+                                    })(ImageCast(this.Document[Doc.LayoutFieldKey(this.Document)])?.url.href),
+                                }).then((info: Upload.ImageInformation) => {
+                                    const img = Docs.Create.ImageDocument(info.accessPaths.agnostic.client, { title: 'expand:' + this.Document.title });
+                                    DocUtils.assignImageInfo(info, img);
+                                    this._props.addDocTab(img, OpenWhere.addRight);
+                                });
+                            }}
+                        />
+                    </div>
+                </div>
+            </div>
+        );
+    };
+
     @computed get annotationLayer() {
         TraceMobx();
         return <div className="imageBox-annotationLayer" style={{ height: this._props.PanelHeight() }} ref={this._annotationLayer} />;
diff --git a/src/client/views/pdf/AnchorMenu.tsx b/src/client/views/pdf/AnchorMenu.tsx
index bb8082061..2e704aa8d 100644
--- a/src/client/views/pdf/AnchorMenu.tsx
+++ b/src/client/views/pdf/AnchorMenu.tsx
@@ -153,12 +153,12 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
         this.AddDrawingAnnotation(drawing);
         const docData = drawing[DocData];
         docData.title = opts.text.match(/^(.*?)~~~.*$/)?.[1] || opts.text;
-        docData.drawingInput = opts.text;
-        docData.drawingComplexity = opts.complexity;
-        docData.drawingColored = opts.autoColor;
-        docData.drawingSize = opts.size;
-        docData.drawingData = gptRes;
-        docData.ai_generated = true;
+        docData.ai_drawing_input = opts.text;
+        docData.ai_drawing_complexity = opts.complexity;
+        docData.ai_drawing_colored = opts.autoColor;
+        docData.ai_drawing_size = opts.size;
+        docData.ai_drawing_data = gptRes;
+        docData.ai = 'gpt';
     });
 
     pointerDown = (e: React.PointerEvent) => {
diff --git a/src/client/views/smartdraw/DrawingFillHandler.tsx b/src/client/views/smartdraw/DrawingFillHandler.tsx
index 52652d377..8e41ee105 100644
--- a/src/client/views/smartdraw/DrawingFillHandler.tsx
+++ b/src/client/views/smartdraw/DrawingFillHandler.tsx
@@ -35,7 +35,7 @@ export class DrawingFillHandler {
                     .then((hrefBase64: string) => gptDescribeImage(hrefBase64))
                     .then((prompt: string) => {
                         Networking.PostToServer('/queryFireflyImageFromStructure', { prompt: prompt, width: dims.width, height: dims.height, structureUrl: structureUrl, strength: strength, styles: styles }).then((info: Upload.ImageInformation) =>
-                            DocumentViewInternal.addDocTabFunc(Docs.Create.ImageDocument(info.accessPaths.agnostic.client, { ai_generated: true, nativeWidth: dims.width, nativeHeight: dims.height }), OpenWhere.addRight)
+                            DocumentViewInternal.addDocTabFunc(Docs.Create.ImageDocument(info.accessPaths.agnostic.client, { ai: 'firefly', ai_firefly_prompt: prompt, nativeWidth: dims.width, nativeHeight: dims.height }), OpenWhere.addRight)
                         ); // prettier-ignore
                     });
             }
diff --git a/src/client/views/smartdraw/SmartDrawHandler.tsx b/src/client/views/smartdraw/SmartDrawHandler.tsx
index 6c9470480..0c67c7a13 100644
--- a/src/client/views/smartdraw/SmartDrawHandler.tsx
+++ b/src/client/views/smartdraw/SmartDrawHandler.tsx
@@ -58,7 +58,7 @@ export class SmartDrawHandler extends ObservableReactComponent<object> {
 
     private _lastInput: DrawingOptions = { text: '', complexity: 5, size: 350, autoColor: true, x: 0, y: 0 };
     private _lastResponse: string = '';
-    private _selectedDoc: Doc | undefined = undefined;
+    private _selectedDocs: Doc[] = [];
     private _errorOccurredOnce = false;
 
     @observable private _display: boolean = false;
@@ -144,14 +144,14 @@ export class SmartDrawHandler extends ObservableReactComponent<object> {
      */
     @action
     displayRegenerate = (x: number, y: number) => {
-        this._selectedDoc = DocumentView.SelectedDocs()?.lastElement();
+        this._selectedDocs = [DocumentView.SelectedDocs()?.lastElement()];
         [this._pageX, this._pageY] = [x, y];
         this._display = false;
         this.ShowRegenerate = true;
         this._showEditBox = false;
-        const docData = this._selectedDoc[DocData];
+        const docData = this._selectedDocs[0][DocData];
         this._lastResponse = StrCast(docData.drawingData);
-        this._lastInput = { text: StrCast(docData.drawingInput), complexity: NumCast(docData.drawingComplexity), size: NumCast(docData.drawingSize), autoColor: BoolCast(docData.drawingColored), x: this._pageX, y: this._pageY };
+        this._lastInput = { text: StrCast(docData.ai_drawing_input), complexity: NumCast(docData.ai_drawing_complexity), size: NumCast(docData.ai_drawing_size), autoColor: BoolCast(docData.ai_drawing_colored), x: this._pageX, y: this._pageY };
     };
 
     /**
@@ -205,8 +205,9 @@ export class SmartDrawHandler extends ObservableReactComponent<object> {
         this._isLoading = true;
         this._canInteract = false;
         if (this.ShowRegenerate) {
-            await this.regenerate();
+            await this.regenerate(this._selectedDocs);
             runInAction(() => {
+                this._selectedDocs = [];
                 this._regenInput = '';
                 this._showEditBox = false;
             });
@@ -253,7 +254,7 @@ export class SmartDrawHandler extends ObservableReactComponent<object> {
                 const strokeData = await this.parseSvg(res, startPt, false, autoColor);
                 const drawingDoc = strokeData && this.CreateDrawingDoc(strokeData.data, strokeData.lastInput, strokeData.lastRes);
                 drawingDoc && this.AddDrawing(drawingDoc, this._lastInput, res);
-                this._selectedDoc = drawingDoc;
+                drawingDoc && this._selectedDocs.push(drawingDoc);
                 this._errorOccurredOnce = false;
                 return strokeData;
             } else {
@@ -274,57 +275,62 @@ export class SmartDrawHandler extends ObservableReactComponent<object> {
                 title: input.match(/^(.*?)~~~.*$/)?.[1] || input,
                 nativeWidth: dims.width,
                 nativeHeight: dims.height,
-                ai_generated: true,
-                firefly_seed: img.accessPaths.agnostic.client.match(/\/(\d+)upload/)[1],
-                firefly_prompt: input,
+                ai: 'firefly',
+                ai_firefly_seed: img.accessPaths.agnostic.client.match(/\/(\d+)upload/)[1],
+                ai_firefly_prompt: input,
             });
             DocumentViewInternal.addDocTabFunc(imgDoc, OpenWhere.addRight);
-            this._selectedDoc = imgDoc;
+            this._selectedDocs.push(imgDoc);
         });
     };
 
     /**
      * Regenerates drawings with the option to add a specific regenerate prompt/request.
+     * @param doc the drawing Docs to regenerate
      */
     @action
-    regenerate = async (lastInput?: DrawingOptions, lastResponse?: string, regenInput?: string) => {
+    regenerate = async (drawingDocs: Doc[], lastInput?: DrawingOptions, lastResponse?: string, regenInput?: string) => {
         if (lastInput) this._lastInput = lastInput;
         if (lastResponse) this._lastResponse = lastResponse;
         if (regenInput) this._regenInput = regenInput;
-        if (this._generateImage) {
-            if (this._regenInput !== '') {
-                if (this._selectedDoc) {
-                    const docData = this._selectedDoc[DocData];
-                    const newPrompt = `${docData.firefly_prompt}, ${this._regenInput}`;
-                    const seed: number = docData?.firefly_seed as number;
-                    await this.createImageWithFirefly(newPrompt, seed);
+        await Promise.all(
+            drawingDocs.map(async doc => {
+                const docData = doc[DocData];
+                if (docData.type == 'image') {
+                    const seed: number = docData?.ai_firefly_seed as number;
+                    if (this._regenInput !== '') {
+                        // if (this._selectedDoc) {
+                        const newPrompt = `${docData.ai_firefly_prompt}, ${this._regenInput}`;
+                        await this.createImageWithFirefly(newPrompt, seed);
+                        // }
+                    } else {
+                        await this.createImageWithFirefly(this._lastInput.text || StrCast(docData.ai_firefly_prompt));
+                    }
                 }
-            } else {
-                await this.createImageWithFirefly(this._lastInput.text);
-            }
-        }
-        if (this._generateDrawing) {
-            try {
-                let res;
-                if (this._regenInput !== '') {
-                    const prompt: string = `This is your previously generated svg code: ${this._lastResponse} for the user input "${this._lastInput.text}". Please regenerate it with the provided specifications.`;
-                    res = await gptAPICall(`"${this._regenInput}"`, GPTCallType.DRAW, prompt, true);
-                    this._lastInput.text = `${this._lastInput.text} ~~~ ${this._regenInput}`;
-                } else {
-                    res = await gptAPICall(`"${this._lastInput.text}", "${this._lastInput.complexity}", "${this._lastInput.size}"`, GPTCallType.DRAW, undefined, true);
-                }
-                if (!res) {
-                    console.error('GPT call failed');
-                    return;
+                if (docData.type == 'collection') {
+                    try {
+                        let res;
+                        if (this._regenInput !== '') {
+                            const prompt: string = `This is your previously generated svg code: ${this._lastResponse} for the user input "${this._lastInput.text}". Please regenerate it with the provided specifications.`;
+                            res = await gptAPICall(`"${this._regenInput}"`, GPTCallType.DRAW, prompt, true);
+                            this._lastInput.text = `${this._lastInput.text} ~~~ ${this._regenInput}`;
+                        } else {
+                            res = await gptAPICall(`"${this._lastInput.text}", "${this._lastInput.complexity}", "${this._lastInput.size}"`, GPTCallType.DRAW, undefined, true);
+                        }
+                        if (!res) {
+                            console.error('GPT call failed');
+                            return;
+                        }
+                        const strokeData = await this.parseSvg(res, { X: this._lastInput.x, Y: this._lastInput.y }, true, lastInput?.autoColor || this._autoColor);
+                        this.RemoveDrawing !== unimplementedFunction && this.RemoveDrawing(true, doc);
+                        const drawingDoc = strokeData && this.CreateDrawingDoc(strokeData.data, strokeData.lastInput, strokeData.lastRes);
+                        drawingDoc && this.AddDrawing(drawingDoc, this._lastInput, res);
+                    } catch (err) {
+                        console.error('Error regenerating drawing', err);
+                    }
                 }
-                const strokeData = await this.parseSvg(res, { X: this._lastInput.x, Y: this._lastInput.y }, true, lastInput?.autoColor || this._autoColor);
-                this.RemoveDrawing !== unimplementedFunction && this.RemoveDrawing(true, this._selectedDoc);
-                const drawingDoc = strokeData && this.CreateDrawingDoc(strokeData.data, strokeData.lastInput, strokeData.lastRes);
-                drawingDoc && this.AddDrawing(drawingDoc, this._lastInput, res);
-            } catch (err) {
-                console.error('Error regenerating drawing', err);
-            }
-        }
+            })
+        );
     };
 
     /**
diff --git a/src/client/views/smartdraw/StickerPalette.tsx b/src/client/views/smartdraw/StickerPalette.tsx
index 352a02e32..d23763eb9 100644
--- a/src/client/views/smartdraw/StickerPalette.tsx
+++ b/src/client/views/smartdraw/StickerPalette.tsx
@@ -142,19 +142,20 @@ export class StickerPalette extends ObservableReactComponent<StickerPaletteProps
     }
 
     /**
-     * Calls the draw with GPT functions in SmartDrawHandler to allow users to generate drawings straight from
+     * Calls the draw with AI functions in SmartDrawHandler to allow users to generate drawings straight from
      * the sticker palette.
      */
     @undoBatch
     generateDrawings = action(async () => {
         this._isLoading = true;
+        const prevDrawings = DocListCast(this._props.Document[DocData].data);
         this._props.Document[DocData].data = undefined;
         SmartDrawHandler.Instance.AddDrawing = this.addDrawing;
         this._canInteract = false;
         await Promise.all(
             Array.from({ length: 3 }).map((_, i) => {
                 return this._showRegenerate
-                    ? SmartDrawHandler.Instance.regenerate(this._opts, this._gptRes[i], this._userInput)
+                    ? SmartDrawHandler.Instance.regenerate(prevDrawings, this._opts, this._gptRes[i], this._userInput)
                     : SmartDrawHandler.Instance.drawWithGPT({ X: 0, Y: 0 }, this._userInput, this._opts.complexity, this._opts.size, this._opts.autoColor);
             })
         );
@@ -181,12 +182,12 @@ export class StickerPalette extends ObservableReactComponent<StickerPaletteProps
         const focusedDrawing = DocListCast(this._props.Document.data)[cIndex];
         const docData = focusedDrawing[DocData];
         docData.title = this._opts.text.match(/^(.*?)~~~.*$/)?.[1] || this._opts.text;
-        docData.drawingInput = this._opts.text;
-        docData.drawingComplexity = this._opts.complexity;
-        docData.drawingColored = this._opts.autoColor;
-        docData.drawingSize = this._opts.size;
-        docData.drawingData = this._gptRes[cIndex];
-        docData.ai_generated = true;
+        docData.ai_drawing_input = this._opts.text;
+        docData.ai_drawing_complexity = this._opts.complexity;
+        docData.ai_drawing_colored = this._opts.autoColor;
+        docData.ai_drawing_size = this._opts.size;
+        docData.ai_drawing_data = this._gptRes[cIndex];
+        docData.ai = 'gpt';
         focusedDrawing.width = this._opts.size;
         docData.x = this._opts.x;
         docData.y = this._opts.y;
diff --git a/src/server/ApiManagers/FireflyManager.ts b/src/server/ApiManagers/FireflyManager.ts
index cb5d7c1da..6daa5840e 100644
--- a/src/server/ApiManagers/FireflyManager.ts
+++ b/src/server/ApiManagers/FireflyManager.ts
@@ -70,7 +70,6 @@ export default class FireflyManager extends ApiManager {
         );
 
     generateImage = (prompt: string = 'a realistic illustration of a cat coding', width: number = 2048, height: number = 2048, seed?: number) => {
-        console.log('DIMENSIONS', width, height);
         let body = `{  "prompt": "${prompt}", "size": { "width": ${width}, "height": ${height}} }`;
         if (seed) {
             console.log('RECEIVED SEED', seed);
-- 
cgit v1.2.3-70-g09d2


From a162057114fd3d961e447d6c3051c1c8c0c6b46d Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Wed, 26 Feb 2025 23:41:07 -0500
Subject: made ai. button draggable to regenerate images.

---
 src/ClientUtils.ts                                 |  2 +-
 src/client/views/DocumentButtonBar.tsx             | 18 +++++++-
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  2 +-
 src/client/views/nodes/ImageBox.tsx                |  6 +--
 src/client/views/smartdraw/DrawingFillHandler.tsx  |  6 +--
 src/client/views/smartdraw/SmartDrawHandler.tsx    | 51 +++++++++++++++++++---
 src/server/ApiManagers/FireflyManager.ts           | 10 ++---
 7 files changed, 72 insertions(+), 23 deletions(-)

(limited to 'src/client/views/DocumentButtonBar.tsx')

diff --git a/src/ClientUtils.ts b/src/ClientUtils.ts
index c5b61477b..e1f490c1a 100644
--- a/src/ClientUtils.ts
+++ b/src/ClientUtils.ts
@@ -599,7 +599,7 @@ export function StopEvent(e: React.PointerEvent | React.MouseEvent | React.Keybo
 
 export function setupMoveUpEvents(
     target: object,
-    e: React.PointerEvent,
+    e: React.PointerEvent | PointerEvent,
     moveEvent: (e: PointerEvent, down: number[], delta: number[]) => boolean,
     upEvent: (e: PointerEvent, movement: number[], isClick: boolean) => void,
     clickEvent: (e: PointerEvent, doubleTap?: boolean) => unknown,
diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx
index a9f03a658..b17dbc93d 100644
--- a/src/client/views/DocumentButtonBar.tsx
+++ b/src/client/views/DocumentButtonBar.tsx
@@ -28,6 +28,7 @@ import { DocumentLinksButton } from './nodes/DocumentLinksButton';
 import { DocumentView } from './nodes/DocumentView';
 import { OpenWhere } from './nodes/OpenWhere';
 import { DashFieldView } from './nodes/formattedText/DashFieldView';
+import { SmartDrawHandler } from './smartdraw/SmartDrawHandler';
 
 @observer
 export class DocumentButtonBar extends ObservableReactComponent<{ views: () => (DocumentView | undefined)[]; stack?: unknown }> {
@@ -319,7 +320,22 @@ export class DocumentButtonBar extends ObservableReactComponent<{ views: () => (
         const targetDoc = this.view0?.Document;
         return !targetDoc ? null : (
             <Tooltip title={<div className="dash-ai-editor-button">Edit with AI</div>}>
-                <div className="documentButtonBar-icon" style={{ color: 'white' }} onClick={undoable(() => this.view0?.toggleAIEditor(), 'toggle AI editor')}>
+                <div
+                    className="documentButtonBar-icon"
+                    style={{ color: 'white' }}
+                    onPointerDown={e =>
+                        setupMoveUpEvents(
+                            this,
+                            e,
+                            me => {
+                                this.view0?.docViewPath().slice(-2)[0]?.ComponentView?.showSmartDraw?.(me.x, me.y, true);
+                                SmartDrawHandler.Instance.startDragging(me);
+                                return true;
+                            },
+                            emptyFunction,
+                            undoable(() => this.view0?.toggleAIEditor(), 'toggle AI editor')
+                        )
+                    }>
                     <FontAwesomeIcon className="documentdecorations-icon" icon="robot" />
                 </div>
             </Tooltip>
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 752e8b217..aa9b9b0fa 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1294,7 +1294,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
      */
     addDrawing = (doc: Doc, opts: DrawingOptions, gptRes: string, x?: number, y?: number) => {
         const docData = doc[DocData];
-        docData.title = opts.text.match(/^(.*?)~~~.*$/)?.[1] || opts.text;
+        docData.title = opts.text;
         docData._width = opts.size;
         docData.ai_drawing_input = opts.text;
         docData.ai_drawing_complexity = opts.complexity;
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index f3aea76a5..55474cb7e 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -625,11 +625,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
                             onClick={action(async () => {
                                 this._regenerateLoading = true;
                                 if (this._fireflyRefStrength) {
-                                    DrawingFillHandler.drawingToImage(this.props.Document, this._fireflyRefStrength, this._regenInput || StrCast(this.Document.title), this.Document)?.then(
-                                        action(() => {
-                                            this._regenerateLoading = false;
-                                        })
-                                    );
+                                    DrawingFillHandler.drawingToImage(this.props.Document, this._fireflyRefStrength, this._regenInput || StrCast(this.Document.title), this.Document)?.then(action(() => (this._regenerateLoading = false)));
                                 } else {
                                     SmartDrawHandler.Instance.regenerate([this.Document], undefined, undefined, this._regenInput || StrCast(this.Document.title), true).then(
                                         action(newImgs => {
diff --git a/src/client/views/smartdraw/DrawingFillHandler.tsx b/src/client/views/smartdraw/DrawingFillHandler.tsx
index d0a840883..7447f8afb 100644
--- a/src/client/views/smartdraw/DrawingFillHandler.tsx
+++ b/src/client/views/smartdraw/DrawingFillHandler.tsx
@@ -47,11 +47,11 @@ export class DrawingFillHandler {
                 return imageUrlToBase64(structureUrl)
                     .then(gptDescribeImage)
                     .then((prompt, newPrompt = user_prompt || prompt) =>
-                        Networking.PostToServer('/queryFireflyImageFromStructure', { prompt: `${newPrompt}`, width: dims.width, height: dims.height, structure: structureUrl, strength, presets: styles, styleUrl })
-                            .then((infos: Upload.ImageInformation[]) => {
+                        Networking.PostToServer('/queryFireflyImageFromStructure', { prompt: `${newPrompt}`, width: dims.width, height: dims.height, structureUrl, strength, presets: styles, styleUrl })
+                            .then(res => {
                                 const genratedDocs = DocCast(drawing.ai_firefly_generatedDocs) ?? Docs.Create.MasonryDocument([], { _width: 400, _height: 400 });
                                 drawing[DocData].ai_firefly_generatedDocs = genratedDocs;
-                                infos.map(info =>
+                                (res as Upload.ImageInformation[]).map(info =>
                                     Doc.AddDocToList(
                                         genratedDocs,
                                         undefined,
diff --git a/src/client/views/smartdraw/SmartDrawHandler.tsx b/src/client/views/smartdraw/SmartDrawHandler.tsx
index 811bc6e25..fbf471900 100644
--- a/src/client/views/smartdraw/SmartDrawHandler.tsx
+++ b/src/client/views/smartdraw/SmartDrawHandler.tsx
@@ -7,7 +7,7 @@ import React from 'react';
 import { AiOutlineSend } from 'react-icons/ai';
 import ReactLoading from 'react-loading';
 import { INode, parse } from 'svgson';
-import { imageUrlToBase64 } from '../../../ClientUtils';
+import { imageUrlToBase64, setupMoveUpEvents } from '../../../ClientUtils';
 import { unimplementedFunction } from '../../../Utils';
 import { Doc, DocListCast } from '../../../fields/Doc';
 import { DocData } from '../../../fields/DocSymbols';
@@ -205,11 +205,7 @@ export class SmartDrawHandler extends ObservableReactComponent<object> {
         this._isLoading = true;
         this._canInteract = false;
         if (this.ShowRegenerate) {
-            await this.regenerate(this._selectedDocs).then(
-                action(() => {
-                    this._showEditBox = false;
-                })
-            );
+            await this.regenerate(this._selectedDocs, undefined, undefined, this._regenInput).then(action(() => (this._showEditBox = false)));
         } else {
             this._showOptions = false;
             try {
@@ -295,7 +291,7 @@ export class SmartDrawHandler extends ObservableReactComponent<object> {
                 }
                 const newseed = img.accessPaths.agnostic.client.match(/\/(\d+)upload/)?.[1];
                 const imgDoc: Doc = Docs.Create.ImageDocument(img.accessPaths.agnostic.client, {
-                    title: input.match(/^(.*?)~~~.*$/)?.[1] || input,
+                    title: input,
                     nativeWidth: dims.width,
                     nativeHeight: dims.height,
                     ai: 'firefly',
@@ -523,6 +519,19 @@ export class SmartDrawHandler extends ObservableReactComponent<object> {
         return (
             <div
                 className="smart-draw-handler"
+                onPointerDown={e =>
+                    setupMoveUpEvents(
+                        this,
+                        e,
+                        action(me => {
+                            this._pageX = this._pageX + me.movementX;
+                            this._pageY = this._pageY + me.movementY;
+                            return false;
+                        }),
+                        () => {},
+                        () => {}
+                    )
+                }
                 style={{
                     display: this._display ? '' : 'none',
                     left: this._pageX,
@@ -581,6 +590,7 @@ export class SmartDrawHandler extends ObservableReactComponent<object> {
                 onChange={action(e => this._canInteract && (this._regenInput = e.target.value))}
                 onKeyDown={this.handleKeyPress}
                 placeholder="Edit instructions"
+                onPointerDown={e => e.stopPropagation()}
             />
             <Button
                 style={{ alignSelf: 'flex-end' }}
@@ -593,10 +603,37 @@ export class SmartDrawHandler extends ObservableReactComponent<object> {
         </div>
     );
 
+    startDragging = (e: PointerEvent) => {
+        setupMoveUpEvents(
+            this,
+            e,
+            action(me => {
+                this._pageX = this._pageX + me.movementX;
+                this._pageY = this._pageY + me.movementY;
+                return false;
+            }),
+            () => {},
+            () => {}
+        );
+    };
     renderRegenerate = () => (
         <div
             className="smart-draw-handler"
+            onPointerDown={e =>
+                setupMoveUpEvents(
+                    this,
+                    e,
+                    action(me => {
+                        this._pageX = this._pageX + me.movementX;
+                        this._pageY = this._pageY + me.movementY;
+                        return false;
+                    }),
+                    () => {},
+                    () => {}
+                )
+            }
             style={{
+                padding: 10,
                 left: this._pageX,
                 ...(this._yRelativeToTop ? { top: Math.max(0, this._pageY) } : { bottom: this._pageY }),
                 background: SettingsManager.userBackgroundColor,
diff --git a/src/server/ApiManagers/FireflyManager.ts b/src/server/ApiManagers/FireflyManager.ts
index 8a310aed8..e75ede9df 100644
--- a/src/server/ApiManagers/FireflyManager.ts
+++ b/src/server/ApiManagers/FireflyManager.ts
@@ -298,13 +298,13 @@ export default class FireflyManager extends ApiManager {
                                 _invalid(res, styleUrl.message);
                                 throw new Error('Error uploading images to dropbox');
                             }
-                            this.uploadImageToDropbox(req.body.structure, req.user as DashUserModel)
-                                .then(structureUrl => {
-                                    if (structureUrl instanceof Error) {
-                                        _invalid(res, structureUrl.message);
+                            this.uploadImageToDropbox(req.body.structureUrl, req.user as DashUserModel)
+                                .then(dropboxStructureUrl => {
+                                    if (dropboxStructureUrl instanceof Error) {
+                                        _invalid(res, dropboxStructureUrl.message);
                                         throw new Error('Error uploading images to dropbox');
                                     }
-                                    return { styleUrl, structureUrl };
+                                    return { styleUrl, structureUrl: dropboxStructureUrl };
                                 })
                                 .then(uploads =>
                                     this.generateImageFromStructure(req.body.prompt, req.body.width, req.body.height, uploads.structureUrl, req.body.strength, req.body.presets, uploads.styleUrl)
-- 
cgit v1.2.3-70-g09d2

