diff options
-rw-r--r-- | src/client/apis/gpt/GPT.ts | 2 | ||||
-rw-r--r-- | src/client/documents/Documents.ts | 1 | ||||
-rw-r--r-- | src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss | 29 | ||||
-rw-r--r-- | src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx | 98 | ||||
-rw-r--r-- | src/client/views/nodes/DocumentView.scss | 2 | ||||
-rw-r--r-- | src/client/views/nodes/DocumentView.tsx | 4 | ||||
-rw-r--r-- | src/client/views/nodes/ImageBox.scss | 2 | ||||
-rw-r--r-- | src/client/views/nodes/ImageBox.tsx | 73 | ||||
-rw-r--r-- | src/client/views/smartdraw/DrawingFillHandler.tsx | 2 | ||||
-rw-r--r-- | src/client/views/smartdraw/SmartDrawHandler.tsx | 2 |
10 files changed, 151 insertions, 64 deletions
diff --git a/src/client/apis/gpt/GPT.ts b/src/client/apis/gpt/GPT.ts index 4b0d58cc1..cf3a28a8e 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: `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.`, + text: `Very briefly identify what this drawing is and list 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/documents/Documents.ts b/src/client/documents/Documents.ts index 7f1387ff8..0bff74ac1 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -19,6 +19,7 @@ import { DocServer } from '../DocServer'; import { dropActionType } from '../util/DropActionTypes'; import { CollectionViewType, DocumentType } from './DocumentTypes'; import { Id } from '../../fields/FieldSymbols'; +import { FireflyImageData } from '../views/smartdraw/FireflyConstants'; class EmptyBox { public static LayoutString() { diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss index 2c94446fb..dff2cb282 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss @@ -304,3 +304,32 @@ display: none; } } + +.collectionFreeformView-aiView { + text-align: center; + font-weight: bold; + width: 100%; + + .collectionfreeformview-aiView-prompt { + height: 25px; + } + + .collectionFreeFormView-aiView-strength { + text-align: center; + } + + .collectionFreeformView-aiView-options-container, + .collectionFreeFormView-aiView-regenerate-container { + text-align: start; + font-weight: normal; + padding: 5px; + } + .collectionFreeformView-aiView-options, + .collectionFreeFormView-aiView-regenerate { + display: flex; + flex-direction: row; + gap: 10px; + justify-content: center; + align-items: center; + } +} diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 7834eb352..112bfd178 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -1,9 +1,5 @@ import { Bezier } from 'bezier-js'; -<<<<<<< HEAD -import { Button, Colors, Type } from 'browndash-components'; -======= -import { Colors } from '@dash/components'; ->>>>>>> 2f7d1f0073943e1eb9e0f34c4459bc0176377697 +import { Button, Colors, Type } from '@dash/components'; import { Property } from 'csstype'; import { action, computed, IReactionDisposer, makeObservable, observable, reaction, runInAction } from 'mobx'; import { observer } from 'mobx-react'; @@ -73,6 +69,9 @@ import './CollectionFreeFormView.scss'; import { MarqueeView } from './MarqueeView'; import ReactLoading from 'react-loading'; import { SettingsManager } from '../../../util/SettingsManager'; +import { Slider } from '@mui/material'; +import { AiOutlineSend } from 'react-icons/ai'; +import { DrawingFillHandler } from '../../smartdraw/DrawingFillHandler'; @observer class CollectionFreeFormOverlayView extends React.Component<{ elements: () => ViewDefResult[] }> { @@ -2194,8 +2193,21 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection } @observable private _regenInput = ''; + @observable private _drawingFillInput = ''; @observable private _canInteract = true; - @observable private _regenerateLoading = false; + @observable private _regenLoading = false; + @observable private _drawingFillLoading = false; + @observable private _fireflyRefStrength = 50; + // _oldDrag: HTMLElement | null = null; + + // onPassiveDrag = (e: PointerEvent) => e.stopPropagation(); + // protected createDashEventsTarget = (ele: HTMLDivElement | null) => { + // // this._dropDisposer?.(); + // this._oldDrag?.removeEventListener('pointerdown', this.onPassiveDrag); + // this._oldDrag = ele; + // // prevent wheel events from passively propagating up through containers and prevents containers from preventDefault which would block scrolling + // ele?.addEventListener('pointerdown', this.onPassiveDrag, { passive: false }); + // }; componentAIViewHistory = () => { return ( @@ -2218,49 +2230,79 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection componentAIView = () => { const showRegenerate = this.Document[DocData].ai; return ( - <div className="collectionfreeformview-aiView"> - Edit Image with AI + <div className="collectionfreeformview-aiView" onPointerDown={e => e.stopPropagation()}> + Edit Collection with AI {showRegenerate && ( <div className="collectionfreeformview-aiView-regenerate-container"> - {/* <text className="collectionfreeformview-aiView-subtitle">Regenerate AI Image</text> + <text className="collectionfreeformview-aiView-subtitle">Regenerate AI Image</text> <div className="collectionfreeformview-aiView-regenerate"> <input className="collectionfreeformview-aiView-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} - icon={this._regenerateLoading ? <ReactLoading type="spin" color={SettingsManager.userVariantColor} width={16} height={20} /> : <AiOutlineSend />} + icon={this._regenLoading ? <ReactLoading type="spin" color={SettingsManager.userVariantColor} width={16} height={20} /> : <AiOutlineSend />} iconPlacement="right" onClick={action(async () => { - this._regenerateLoading = true; - SmartDrawHandler.Instance.regenerate([this.Document], undefined, undefined, this._regenInput, true).then(newDrawings => { - if (newDrawings[0]) { - } - }); + this._regenLoading = true; + SmartDrawHandler.Instance.CreateDrawingDoc = this.createDrawingDoc; + SmartDrawHandler.Instance.AddDrawing = this.addDrawing; + SmartDrawHandler.Instance.RemoveDrawing = this.removeDrawing; + await SmartDrawHandler.Instance.regenerate([this.Document], undefined, undefined, this._regenInput, true); + this._regenLoading = false; })} /> - <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> )} <div className="collectionfreeformview-aiView-options-container"> - {showRegenerate && <text className="collectionfreeformview-aiView-subtitle"> Turn Drawing to Image </text>} - <div className="collectionfreeformview-aiView-options"></div> + <text className="collectionfreeformview-aiView-subtitle"> Create Image with Firefly </text> + <div className="collectionfreeformview-aiView-options"> + <input className="collectionfreeformview-aiView-prompt" placeholder="Prompt (Optional)" type="text" value={this._drawingFillInput} onChange={action(e => this._canInteract && (this._drawingFillInput = e.target.value))} /> + <div className="collectionfreeformview-aiView-strength"> + Reference Strength + <Slider + className="collectionfreeformview-aiView-slider" + sx={{ + '& .MuiSlider-track': { color: SettingsManager.userVariantColor }, + '& .MuiSlider-rail': { color: SettingsManager.userBackgroundColor }, + '& .MuiSlider-thumb': { color: SettingsManager.userVariantColor, '&.Mui-focusVisible, &:hover, &.Mui-active': { boxShadow: `0px 0px 0px 8px${SettingsManager.userColor.slice(0, 7)}10` } }, + }} + min={1} + max={100} + step={1} + size="small" + value={this._fireflyRefStrength} + onChange={action((e, val) => this._canInteract && (this._fireflyRefStrength = val as number))} + valueLabelDisplay="auto" + /> + </div> + <Button + text="Send" + type={Type.SEC} + icon={this._drawingFillLoading && this._drawingFillInput !== '' ? <ReactLoading type="spin" color={SettingsManager.userVariantColor} width={16} height={20} /> : <AiOutlineSend />} + iconPlacement="right" + onClick={undoable( + action(() => { + this._drawingFillLoading = true; + DrawingFillHandler.drawingToImage( + this.props.Document, + this._fireflyRefStrength, + this._drawingFillInput !== '' ? this._drawingFillInput : StrCast(this.props.Document.title) !== 'grouping' ? StrCast(this.props.Document.title) : '' + ); + this._drawingFillInput = ''; + this._drawingFillLoading = false; + }), + 'create image' + )} + /> + </div> </div> </div> ); diff --git a/src/client/views/nodes/DocumentView.scss b/src/client/views/nodes/DocumentView.scss index 82195b9c1..a3d47290a 100644 --- a/src/client/views/nodes/DocumentView.scss +++ b/src/client/views/nodes/DocumentView.scss @@ -281,6 +281,8 @@ .documentView-editorView { width: 100%; overflow-y: scroll; + justify-items: center; + background-color: rgb(223, 223, 223); .documentView-editorView-resizer { height: 5px; diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 622eccc4f..d656bcf50 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -716,8 +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; + rpw = () => this._props.PanelWidth() / 2; + rph = () => this.panelHeight() / 2; @computed get viewBoxContents() { TraceMobx(); const isInk = this.layoutDoc._layout_isSvg && !this._props.LayoutTemplateString; diff --git a/src/client/views/nodes/ImageBox.scss b/src/client/views/nodes/ImageBox.scss index e083f52b4..9532f4ad3 100644 --- a/src/client/views/nodes/ImageBox.scss +++ b/src/client/views/nodes/ImageBox.scss @@ -159,8 +159,6 @@ .imageBox-aiView { text-align: center; font-weight: bold; - align-content: center; - height: 100%; .imageBox-aiView-subtitle { position: relative; diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx index fe069eacc..db8bb2c6e 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, Button, Type } from '@dash/components'; +import { Colors, Button, Type, Size } from '@dash/components'; import { action, computed, IReactionDisposer, makeObservable, observable, ObservableMap, reaction } from 'mobx'; import { observer } from 'mobx-react'; import { extname } from 'path'; @@ -76,6 +76,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { return FieldView.LayoutString(ImageBox, fieldKey); } _ffref = React.createRef<CollectionFreeFormView>(); + _oldWheel: HTMLElement | null = null; private _ignoreScroll = false; private _forcedScroll = false; private _dropDisposer?: DragManager.DragDropDisposer; @@ -94,6 +95,12 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { @observable private _error = ''; @observable private _isHovering = false; // flag to switch between primary and alternate images on hover + // variables for AI Image Editor + @observable private _regenInput = ''; + @observable private _canInteract = true; + @observable private _regenerateLoading = false; + @observable private _prevImgs: FireflyImageData[] = []; + constructor(props: FieldViewProps) { super(props); makeObservable(this); @@ -529,25 +536,34 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { ); } - @observable private _regenInput = ''; - @observable private _canInteract = true; - @observable private _regenerateLoading = false; - @observable private _prevImgs: FireflyImageData[] = []; + onPassiveWheel = (e: WheelEvent) => e.stopPropagation(); + + protected createDashEventsTarget = (ele: HTMLDivElement | null) => { + // this._dropDisposer?.(); + this._oldWheel?.removeEventListener('wheel', this.onPassiveWheel); + this._oldWheel = ele; + // prevent wheel events from passively propagating up through containers and prevents containers from preventDefault which would block scrolling + ele?.addEventListener('wheel', this.onPassiveWheel, { passive: false }); + }; componentAIViewHistory = () => { return ( - <div className="imageBox-aiView-history"> + <div className="imageBox-aiView-history" ref={this.createDashEventsTarget}> + <Button text="Clear History" type={Type.SEC} size={Size.XSMALL} /> {this._prevImgs.map(img => ( - <img - key={img.pathname} - className="imageBox-aiView-img" - src={img.href} - onClick={() => { - this.dataDoc[this.fieldKey] = new ImageField(img.pathname); - this.dataDoc.ai_firefly_prompt = img.prompt; - this.dataDoc.ai_firefly_seed = img.seed; - }} - /> + <div> + <img + key={img.pathname} + className="imageBox-aiView-img" + src={img.href} + onClick={() => { + this.dataDoc[this.fieldKey] = new ImageField(img.pathname); + this.dataDoc.ai_firefly_prompt = img.prompt; + this.dataDoc.ai_firefly_seed = img.seed; + }} + /> + <text>{img.prompt}</text> + </div> ))} </div> ); @@ -557,7 +573,14 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { const field = this.dataDoc[this.fieldKey] instanceof ImageField ? Cast(this.dataDoc[this.fieldKey], ImageField, null) : new ImageField(String(this.dataDoc[this.fieldKey])); const showRegenerate = this.Document[DocData].ai; return ( - <div className="imageBox-aiView"> + <div + className="imageBox-aiView" + ref={(ele: HTMLDivElement | null) => { + this._oldWheel?.removeEventListener('wheel', this.onPassiveWheel); + this._oldWheel = ele; + // prevent wheel events from passively propagating up through containers and prevents containers from preventDefault which would block scrolling + ele?.addEventListener('wheel', this.onPassiveWheel, { passive: false }); + }}> Edit Image with AI {showRegenerate && ( <div className="imageBox-aiView-regenerate-container"> @@ -566,11 +589,9 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { <input className="imageBox-aiView-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 @@ -587,22 +608,16 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { 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.dataDoc[this.fieldKey] = imgField; this._prevImgs.unshift({ prompt: newImgs[0].prompt, seed: newImgs[0].seed, href: this.paths.lastElement(), pathname: url }); + this.dataDoc.ai_firefly_history = `${this._prevImgs}`; + this.dataDoc[this.fieldKey] = imgField; 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} - /> + <Button text="Get Variations" type={Type.SEC} iconPlacement="right" /> </div> </div> )} @@ -704,8 +719,8 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { const { nativeWidth: width, nativeHeight: height } = await Networking.PostToServer('/inspectImage', { source: this.paths[0] }); return { width, height }; }; - savedAnnotations = () => this._savedAnnotations; + render() { TraceMobx(); const borderRad = this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.BorderRounding) as string; diff --git a/src/client/views/smartdraw/DrawingFillHandler.tsx b/src/client/views/smartdraw/DrawingFillHandler.tsx index fea4acb67..43d16df89 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 => { Networking.PostToServer('/queryFireflyImageFromStructure', - { prompt: user_prompt || prompt, width: dims.width, height: dims.height, structureUrl, strength, styles }) + { prompt: `${user_prompt}, ${prompt}`, width: dims.width, height: dims.height, structureUrl, strength, styles }) .then((info: Upload.ImageInformation) => DocumentViewInternal.addDocTabFunc(Docs.Create.ImageDocument(info.accessPaths.agnostic.client, { ai: 'firefly', ai_firefly_prompt: user_prompt || prompt, nativeWidth: dims.width, nativeHeight: dims.height }), OpenWhere.addRight) diff --git a/src/client/views/smartdraw/SmartDrawHandler.tsx b/src/client/views/smartdraw/SmartDrawHandler.tsx index 9248cbee3..4052ea852 100644 --- a/src/client/views/smartdraw/SmartDrawHandler.tsx +++ b/src/client/views/smartdraw/SmartDrawHandler.tsx @@ -279,7 +279,7 @@ export class SmartDrawHandler extends ObservableReactComponent<object> { nativeWidth: dims.width, nativeHeight: dims.height, ai: 'firefly', - ai_firefly_seed: aiseed, + ai_firefly_seed: seed, ai_firefly_prompt: input, }); DocumentViewInternal.addDocTabFunc(imgDoc, OpenWhere.addRight); |