diff options
Diffstat (limited to 'src/client/views/smartdraw/SmartDrawHandler.tsx')
-rw-r--r-- | src/client/views/smartdraw/SmartDrawHandler.tsx | 569 |
1 files changed, 381 insertions, 188 deletions
diff --git a/src/client/views/smartdraw/SmartDrawHandler.tsx b/src/client/views/smartdraw/SmartDrawHandler.tsx index b4635673c..1cceabed3 100644 --- a/src/client/views/smartdraw/SmartDrawHandler.tsx +++ b/src/client/views/smartdraw/SmartDrawHandler.tsx @@ -1,19 +1,21 @@ +import { Button, IconButton } from '@dash/components'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { Slider, Switch } from '@mui/material'; -import { Button, IconButton } from 'browndash-components'; +import { Checkbox, FormControlLabel, Radio, RadioGroup, Slider, Switch } from '@mui/material'; import { action, makeObservable, observable, runInAction } from 'mobx'; import { observer } from 'mobx-react'; 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'; import { InkData, InkField, InkTool } from '../../../fields/InkField'; import { BoolCast, ImageCast, NumCast, StrCast } from '../../../fields/Types'; +import { Networking } from '../../Network'; import { GPTCallType, gptAPICall, gptDrawingColor } from '../../apis/gpt/GPT'; +import { DocumentType } from '../../documents/DocumentTypes'; import { Docs } from '../../documents/Documents'; import { SettingsManager } from '../../util/SettingsManager'; import { undoable } from '../../util/UndoManager'; @@ -21,18 +23,24 @@ import { SVGToBezier, SVGType } from '../../util/bezierFit'; import { InkingStroke } from '../InkingStroke'; import { ObservableReactComponent } from '../ObservableReactComponent'; import { MarqueeView } from '../collections/collectionFreeForm'; -import { ActiveArrowEnd, ActiveArrowStart, ActiveDash, ActiveFillColor, ActiveInkBezierApprox, ActiveInkColor, ActiveInkWidth, ActiveIsInkMask, DocumentView } from '../nodes/DocumentView'; +import { ActiveInkArrowEnd, ActiveInkArrowStart, ActiveInkBezierApprox, ActiveInkColor, ActiveInkDash, ActiveInkFillColor, ActiveInkWidth, ActiveIsInkMask, DocumentView } from '../nodes/DocumentView'; +import { FireflyDimensionsMap, FireflyImageData, FireflyImageDimensions } from './FireflyConstants'; import './SmartDrawHandler.scss'; +import { Upload } from '../../../server/SharedMediaTypes'; +import { PointData } from '../../../pen-gestures/GestureTypes'; +import { List } from '../../../fields/List'; export interface DrawingOptions { - text: string; - complexity: number; - size: number; - autoColor: boolean; - x: number; - y: number; + text?: string; + complexity?: number; + size?: number; + autoColor?: boolean; + x?: number; + y?: number; } +type svgparsedData = [PointData[], string, string]; + /** * The SmartDrawHandler allows users to generate drawings with GPT from text input. Users are able to enter * the item to draw, how complex they want the drawing to be, how large the drawing should be, and whether @@ -55,22 +63,27 @@ 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 _errorOccurredOnce = false; + private _selectedDocs: Doc[] = []; @observable private _display: boolean = false; @observable private _pageX: number = 0; @observable private _pageY: number = 0; + @observable private _scale: number = 0; @observable private _yRelativeToTop: boolean = true; @observable private _isLoading: boolean = false; + @observable private _userInput: string = ''; + @observable private _regenInput: string = ''; @observable private _showOptions: boolean = false; @observable private _showEditBox: boolean = false; @observable private _complexity: number = 5; @observable private _size: number = 200; @observable private _autoColor: boolean = true; - @observable private _regenInput: string = ''; + @observable private _imgDims: FireflyImageDimensions = FireflyImageDimensions.Square; + @observable private _canInteract: boolean = true; + @observable private _generateDrawing: boolean = true; + @observable private _generateImage: boolean = true; @observable public ShowRegenerate: boolean = false; @@ -82,10 +95,10 @@ export class SmartDrawHandler extends ObservableReactComponent<object> { /** * AddDrawing and RemoveDrawing are defined by the other classes that call the smart draw functions (i.e. - CollectionFreeForm, FormattedTextBox, AnnotationPalette) to define how a drawing document should be added - or removed in their respective locations (to the freeform canvs, to the annotation palette's preview, etc.) + CollectionFreeForm, FormattedTextBox, StickerPalette) to define how a drawing document should be added + or removed in their respective locations (to the freeform canvas, to the sticker palette's preview, etc.) */ - public AddDrawing: (doc: Doc, opts: DrawingOptions, gptRes: string) => void = unimplementedFunction; + public AddDrawing: (doc: Doc, opts: DrawingOptions, gptRes: string, x?: number, y?: number) => void = unimplementedFunction; public RemoveDrawing: (useLastContainer: boolean, doc?: Doc) => void = unimplementedFunction; /** * This creates the ink document that represents a drawing, so it goes through the strokes that make up the drawing, @@ -93,7 +106,7 @@ export class SmartDrawHandler extends ObservableReactComponent<object> { * classes to customize the way the drawing docs get created. For example, the freeform canvas has a different way of * defining document bounds, so CreateDrawingDoc is redefined when that class calls gpt draw functions. */ - public CreateDrawingDoc: (strokeList: [InkData, string, string][], opts: DrawingOptions, gptRes: string, containerDoc?: Doc) => Doc | undefined = (strokeList: [InkData, string, string][], opts: DrawingOptions) => { + public static CreateDrawingDoc: (strokeList: [InkData, string, string][], opts: DrawingOptions, gptRes: string, containerDoc?: Doc) => Doc | undefined = (strokeList: [InkData, string, string][], opts: DrawingOptions) => { const drawing: Doc[] = []; strokeList.forEach((stroke: [InkData, string, string]) => { const bounds = InkField.getBounds(stroke[0]); @@ -105,14 +118,14 @@ export class SmartDrawHandler extends ObservableReactComponent<object> { y: bounds.top - inkWidth / 2, _width: bounds.width + inkWidth, _height: bounds.height + inkWidth, - stroke_showLabel: BoolCast(Doc.UserDoc().activeInkHideTextLabels)}, // prettier-ignore + stroke_showLabel: false}, // prettier-ignore inkWidth, opts.autoColor ? stroke[1] : ActiveInkColor(), ActiveInkBezierApprox(), - stroke[2] === 'none' ? ActiveFillColor() : stroke[2], - ActiveArrowStart(), - ActiveArrowEnd(), - ActiveDash(), + stroke[2] === 'none' ? ActiveInkFillColor() : stroke[2], + ActiveInkArrowStart(), + ActiveInkArrowEnd(), + ActiveInkDash(), ActiveIsInkMask() ); drawing.push(inkDoc); @@ -122,9 +135,10 @@ export class SmartDrawHandler extends ObservableReactComponent<object> { }; @action - displaySmartDrawHandler = (x: number, y: number) => { + displaySmartDrawHandler = (x: number, y: number, scale: number) => { [this._pageX, this._pageY] = [x, y]; this._display = true; + this._scale = scale; }; /** @@ -134,14 +148,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 }; }; /** @@ -178,9 +192,9 @@ export class SmartDrawHandler extends ObservableReactComponent<object> { /** * This allows users to press the return/enter key to send input. */ - handleKeyPress = (event: React.KeyboardEvent) => { - if (event.key === 'Enter') { - this.handleSendClick(); + handleKeyPress = (e: React.KeyboardEvent) => { + if (e.key === 'Enter') { + this.handleSendClick(this._pageX, this._pageY); } }; @@ -190,34 +204,24 @@ export class SmartDrawHandler extends ObservableReactComponent<object> { * what the user sees. */ @action - handleSendClick = async () => { + handleSendClick = async (X: number, Y: number) => { + if ((!this.ShowRegenerate && this._userInput == '') || (!this._generateImage && !this._generateDrawing)) return; this._isLoading = true; this._canInteract = false; if (this.ShowRegenerate) { - await this.regenerate(); - runInAction(() => { - this._regenInput = ''; - this._showEditBox = false; - }); + await this.regenerate(this._selectedDocs, undefined, undefined, this._regenInput).then(action(() => (this._showEditBox = false))); } else { - runInAction(() => { - this._showOptions = false; - }); + this._showOptions = false; try { - await this.drawWithGPT({ X: this._pageX, Y: this._pageY }, this._userInput, this._complexity, this._size, this._autoColor); + if (this._generateImage) { + await this.createImageWithFirefly(this._userInput); + } + if (this._generateDrawing) { + await this.drawWithGPT({ X, Y }, this._userInput, this._complexity, this._size, this._autoColor); + } this.hideSmartDrawHandler(); - - runInAction(() => { - this.ShowRegenerate = true; - }); } catch (err) { - if (this._errorOccurredOnce) { - console.error('GPT call failed', err); - this._errorOccurredOnce = false; - } else { - this._errorOccurredOnce = true; - await this.drawWithGPT({ X: this._pageX, Y: this._pageY }, this._userInput, this._complexity, this._size, this._autoColor); - } + console.error('GPT call failed', err); } } runInAction(() => { @@ -229,75 +233,164 @@ export class SmartDrawHandler extends ObservableReactComponent<object> { /** * Calls GPT API to create a drawing based on user input. */ - @action - drawWithGPT = async (startPt: { X: number; Y: number }, input: string, complexity: number, size: number, autoColor: boolean) => { - if (input === '') return; - this._lastInput = { text: input, complexity: complexity, size: size, autoColor: autoColor, x: startPt.X, y: startPt.Y }; - const res = await gptAPICall(`"${input}", "${complexity}", "${size}"`, GPTCallType.DRAW, undefined, true); - if (!res) { - console.error('GPT call failed'); - return; + drawWithGPT = async (screenPt: { X: number; Y: number }, input: string, complexity: number, size: number, autoColor: boolean) => { + if (input) { + this._lastInput = { text: input, complexity: complexity, size: size, autoColor: autoColor, x: screenPt.X, y: screenPt.Y }; + const res = await gptAPICall(`"${input}", "${complexity}", "${size}"`, GPTCallType.DRAW, undefined, true); + if (res) { + const strokeData = await this.parseSvg(res, { X: 0, Y: 0 }, false, autoColor); + const drawingDoc = strokeData && SmartDrawHandler.CreateDrawingDoc(strokeData.data, strokeData.lastInput, strokeData.lastRes); + drawingDoc && this.AddDrawing(drawingDoc, this._lastInput, res, screenPt.X, screenPt.Y); + drawingDoc && this._selectedDocs.push(drawingDoc); + return strokeData; + } else { + console.error('GPT call failed'); + } } - 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); + return undefined; + }; - this._errorOccurredOnce = false; - return strokeData; + /** + * Calls Firefly API to create an image based on user input + */ + createImageWithFirefly = (input: string, seed?: number): Promise<FireflyImageData | Doc | undefined> => { + this._lastInput.text = input; + return SmartDrawHandler.CreateWithFirefly(input, this._imgDims, seed).then(doc => { + doc instanceof Doc && this.AddDrawing(doc, this._lastInput, input, this._pageX, this._pageY); + return doc; + }); + }; /** + * Calls Firefly API to create an image based on user input + */ + recreateImageWithFirefly = (input: string, seed?: number): Promise<FireflyImageData | Doc | undefined> => { + this._lastInput.text = input; + return SmartDrawHandler.ReCreateWithFirefly(input, this._imgDims, seed); }; + public static ReCreateWithFirefly(input: string, imgDims: FireflyImageDimensions, seed?: number): Promise<FireflyImageData | Doc | undefined> { + const dims = FireflyDimensionsMap[imgDims]; + return Networking.PostToServer('/queryFireflyImage', { prompt: input, width: dims.width, height: dims.height, seed }) + .then(res => { + const img = res as Upload.FileInformation; + const error = res as { error: string }; + if ('error' in error) { + alert('recreate image failed: ' + error.error); + return undefined; + } + return { prompt: input, seed, pathname: img.accessPaths.agnostic.client }; + }) + .catch(e => { + alert('recreate image failed: ' + e.toString()); + return undefined; + }); + } + public static CreateWithFirefly(input: string, imgDims: FireflyImageDimensions, seed?: number): Promise<FireflyImageData | Doc | undefined> { + const dims = FireflyDimensionsMap[imgDims]; + return Networking.PostToServer('/queryFireflyImage', { prompt: input, width: dims.width, height: dims.height, seed }) + .then(res => { + const img = res as Upload.FileInformation; + const error = res as { error: string }; + if ('error' in error) { + alert('create image failed: ' + error.error); + return undefined; + } + const newseed = img.accessPaths.agnostic.client.match(/\/(\d+)upload/)?.[1]; + return Docs.Create.ImageDocument(img.accessPaths.agnostic.client, { + title: input, + nativeWidth: dims.width, + nativeHeight: dims.height, + tags: new List<string>(['@ai']), + _width: Math.min(400, dims.width), + _height: (Math.min(400, dims.width) * dims.height) / dims.width, + ai: 'firefly', + ai_firefly_seed: +(newseed ?? 0), + ai_firefly_prompt: input, + }); + }) + .catch(e => { + alert('create image failed: ' + e.toString()); + return undefined; + }); + } /** * 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 = (drawingDocs: Doc[], lastInput?: DrawingOptions, lastResponse?: string, regenInput?: string, changeInPlace?: boolean) => { if (lastInput) this._lastInput = lastInput; if (lastResponse) this._lastResponse = lastResponse; if (regenInput) this._regenInput = regenInput; - - 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, this._selectedDoc); - const drawingDoc = strokeData && this.CreateDrawingDoc(strokeData.data, strokeData.lastInput, strokeData.lastRes); - drawingDoc && this.AddDrawing(drawingDoc, this._lastInput, res); - return strokeData; - } catch (err) { - console.error('Error regenerating drawing', err); - } + return Promise.all( + drawingDocs.map(async doc => { + switch (doc.type) { + case DocumentType.IMG: { + const func = changeInPlace ? this.recreateImageWithFirefly : this.createImageWithFirefly; + const newPrompt = doc.ai_firefly_prompt ? `${doc.ai_firefly_prompt} ~~~ ${this._regenInput}` : this._regenInput; + return this._regenInput ? func(newPrompt, NumCast(doc?.ai_firefly_seed)) : func(this._lastInput.text || StrCast(doc.ai_firefly_prompt)); + } + case DocumentType.COL: { + try { + const res = await (async () => { + if (this._regenInput) { + const prompt = `This is your previously generated svg code: ${this._lastResponse} for the user input "${this._lastInput.text}". Please regenerate it with the provided specifications.`; + this._lastInput.text = `${this._lastInput.text} ~~~ ${this._regenInput}`; + return gptAPICall(`"${this._regenInput}"`, GPTCallType.DRAW, prompt, true); + } + return gptAPICall(`"${this._lastInput.text}", "${this._lastInput.complexity}", "${this._lastInput.size}"`, GPTCallType.DRAW, undefined, true); + })(); + if (res) { + const strokeData = await this.parseSvg(res, { X: this._lastInput.x ?? 0, Y: this._lastInput.y ?? 0 }, true, lastInput?.autoColor || this._autoColor); + this.RemoveDrawing !== unimplementedFunction && this.RemoveDrawing(true, doc); + const drawingDoc = strokeData && SmartDrawHandler.CreateDrawingDoc(strokeData.data, strokeData.lastInput, strokeData.lastRes); + drawingDoc && this.AddDrawing(drawingDoc, this._lastInput, res); + } else { + console.error('GPT call failed'); + } + } catch (err) { + console.error('Error regenerating drawing', err); + } + break; + } + } + }) + ); }; /** * Parses the svg code that GPT returns into Bezier curves, with coordinates and colors. */ - @action parseSvg = async (res: string, startPoint: { X: number; Y: number }, regenerate: boolean, autoColor: boolean) => { const svg = res.match(/<svg[^>]*>([\s\S]*?)<\/svg>/g); + if (svg) { this._lastResponse = svg[0]; const svgObject = await parse(svg[0]); + console.log(res, svgObject); const svgStrokes: INode[] = svgObject.children; const strokeData: [InkData, string, string][] = []; + + const tl = { X: Number.MAX_SAFE_INTEGER, Y: Number.MAX_SAFE_INTEGER }; + let last: PointData = { X: 0, Y: 0 }; svgStrokes.forEach(child => { - const convertedBezier: InkData = SVGToBezier(child.name as SVGType, child.attributes); + const convertedBezier: InkData = SVGToBezier(child.name as SVGType, child.attributes, last); + last = convertedBezier.lastElement(); strokeData.push([ - convertedBezier.map(point => ({ X: point.X + startPoint.X - this._size / 1.5, Y: point.Y + startPoint.Y - this._size / 2 })), + convertedBezier.map(point => { + if (point.X < tl.X) tl.X = point.X; + if (point.Y < tl.Y) tl.Y = point.Y; + return { X: point.X, Y: point.Y }; + }), (regenerate ? this._lastInput.autoColor : autoColor) ? child.attributes.stroke : '', (regenerate ? this._lastInput.autoColor : autoColor) ? child.attributes.fill : '', ]); }); - return { data: strokeData, lastInput: this._lastInput, lastRes: svg[0] }; + const mapStroke = (pd: PointData): PointData => ({ X: startPoint.X + (pd.X - tl.X) * this._scale, Y: startPoint.Y + (pd.Y - tl.Y) * this._scale }); + return { + data: strokeData.map(sdata => [sdata[0].map(mapStroke), sdata[1], sdata[2]] as svgparsedData), + lastInput: this._lastInput, + lastRes: svg[0], + }; } }; @@ -342,11 +435,124 @@ export class SmartDrawHandler extends ObservableReactComponent<object> { }); }, 'color strokes'); - renderDisplay() { + renderGenerateOutputOptions = () => ( + <div className="smartdraw-output-options"> + <div className="drawing-checkbox"> + Generate Ink + <Checkbox + sx={{ + color: 'white', + '&.Mui-checked': { + color: SettingsManager.userVariantColor, + }, + }} + checked={this._generateDrawing} + onChange={() => this._canInteract && (this._generateDrawing = !this._generateDrawing)} + /> + </div> + <div className="image-checkbox"> + Generate Image + <Checkbox + sx={{ + color: 'white', + '&.Mui-checked': { + color: SettingsManager.userVariantColor, + }, + }} + checked={this._generateImage} + onChange={action(() => this._canInteract && (this._generateImage = !this._generateImage))} + /> + </div> + </div> + ); + + renderGenerateDrawing = () => ( + <div className="smartdraw-options-container"> + Drawing Options + <div className="smartdraw-options"> + <div className="smartdraw-auto-color"> + Auto color + <Switch + sx={{ + '& .MuiSwitch-switchBase.Mui-checked': { color: SettingsManager.userColor }, + '& .MuiSwitch-switchBase.Mui-checked + .MuiSwitch-track': { backgroundColor: SettingsManager.userVariantColor }, + }} + defaultChecked={true} + value={this._autoColor} + size="small" + onChange={action(() => this._canInteract && (this._autoColor = !this._autoColor))} + /> + </div> + <div className="smartdraw-complexity"> + Complexity + <Slider + className="smartdraw-slider" + sx={{ + '& .MuiSlider-track': { color: SettingsManager.userVariantColor }, + '& .MuiSlider-rail': { color: SettingsManager.userColor }, + '& .MuiSlider-thumb': { color: SettingsManager.userColor, '&.Mui-focusVisible, &:hover, &.Mui-active': { boxShadow: `0px 0px 0px 8px${SettingsManager.userColor.slice(0, 7)}10` } }, + }} + min={1} + max={10} + step={1} + size="small" + value={this._complexity} + onChange={action((e, val) => this._canInteract && (this._complexity = val as number))} + valueLabelDisplay="auto" + /> + </div> + <div className="smartdraw-size"> + Size (in pixels) + <Slider + className="smartdraw-slider" + sx={{ + '& .MuiSlider-track': { color: SettingsManager.userVariantColor }, + '& .MuiSlider-rail': { color: SettingsManager.userColor }, + '& .MuiSlider-thumb': { color: SettingsManager.userColor, '&.Mui-focusVisible, &:hover, &.Mui-active': { boxShadow: `0px 0px 0px 8px${SettingsManager.userColor.slice(0, 7)}20` } }, + }} + min={50} + max={700} + step={10} + size="small" + value={this._size} + onChange={action((e, val) => this._canInteract && (this._size = val as number))} + valueLabelDisplay="auto" + /> + </div> + </div> + </div> + ); + + renderGenerateImage = () => ( + <div className="smartdraw-options-container"> + Image Options + <div className="smartdraw-dimensions"> + <RadioGroup row defaultValue="square" sx={{ alignItems: 'center' }}> + {Object.values(FireflyImageDimensions).map(dim => ( + <FormControlLabel sx={{ width: '40%' }} key={dim} value={dim} control={<Radio />} onChange={() => this._canInteract && (this._imgDims = dim)} label={dim} /> + ))} + </RadioGroup> + </div> + </div> + ); + + renderDisplay = () => { return ( <div - id="label-handler" 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, @@ -354,7 +560,7 @@ export class SmartDrawHandler extends ObservableReactComponent<object> { background: SettingsManager.userBackgroundColor, color: SettingsManager.userColor, }}> - <div> + <div className="smart-draw-main"> <IconButton tooltip="Cancel" onClick={() => { @@ -365,6 +571,7 @@ export class SmartDrawHandler extends ObservableReactComponent<object> { color={SettingsManager.userColor} /> <input + style={{ color: SettingsManager.userColor, background: SettingsManager.userBackgroundColor }} aria-label="Smart Draw Input" className="smartdraw-input" type="text" @@ -381,111 +588,97 @@ export class SmartDrawHandler extends ObservableReactComponent<object> { icon={this._isLoading ? <ReactLoading type="spin" color={SettingsManager.userVariantColor} width={16} height={20} /> : <AiOutlineSend />} iconPlacement="right" color={SettingsManager.userColor} - onClick={this.handleSendClick} + onClick={() => this.handleSendClick(this._pageX, this._pageY)} /> </div> {this._showOptions && ( - <div className="smartdraw-options"> - <div className="auto-color"> - Auto color - <Switch - sx={{ - '& .MuiSwitch-switchBase.Mui-checked': { color: SettingsManager.userColor }, - '& .MuiSwitch-switchBase.Mui-checked + .MuiSwitch-track': { backgroundColor: SettingsManager.userVariantColor }, - }} - defaultChecked={true} - value={this._autoColor} - size="small" - onChange={action(() => this._canInteract && (this._autoColor = !this._autoColor))} - /> - </div> - <div className="complexity"> - Complexity - <Slider - sx={{ - '& .MuiSlider-track': { color: SettingsManager.userVariantColor }, - '& .MuiSlider-rail': { color: SettingsManager.userColor }, - '& .MuiSlider-thumb': { color: SettingsManager.userColor, '&.Mui-focusVisible, &:hover, &.Mui-active': { boxShadow: `0px 0px 0px 8px${SettingsManager.userColor.slice(0, 7)}10` } }, - }} - style={{ width: '80%' }} - min={1} - max={10} - step={1} - size="small" - value={this._complexity} - onChange={action((e, val) => this._canInteract && (this._complexity = val as number))} - valueLabelDisplay="auto" - /> - </div> - <div className="size"> - Size (in pixels) - <Slider - className="size-slider" - sx={{ - '& .MuiSlider-track': { color: SettingsManager.userVariantColor }, - '& .MuiSlider-rail': { color: SettingsManager.userColor }, - '& .MuiSlider-thumb': { color: SettingsManager.userColor, '&.Mui-focusVisible, &:hover, &.Mui-active': { boxShadow: `0px 0px 0px 8px${SettingsManager.userColor.slice(0, 7)}20` } }, - }} - min={50} - max={700} - step={10} - size="small" - value={this._size} - onChange={action((e, val) => this._canInteract && (this._size = val as number))} - valueLabelDisplay="auto" - /> - </div> + <div> + {this.renderGenerateOutputOptions()} + {this._generateDrawing ? this.renderGenerateDrawing() : null} + {this._generateImage ? this.renderGenerateImage() : null} </div> )} </div> ); - } + }; - renderRegenerate() { - return ( - <div - className="smart-draw-handler" - style={{ - left: this._pageX, - ...(this._yRelativeToTop ? { top: Math.max(0, this._pageY) } : { bottom: this._pageY }), - background: SettingsManager.userBackgroundColor, - color: SettingsManager.userColor, - }}> - <div className="regenerate-box"> - <IconButton - tooltip="Regenerate" - icon={this._isLoading && this._regenInput === '' ? <ReactLoading type="spin" color={SettingsManager.userVariantColor} width={16} height={20} /> : <FontAwesomeIcon icon={'rotate'} />} - color={SettingsManager.userColor} - onClick={this.handleSendClick} - /> - <IconButton tooltip="Edit with GPT" icon={<FontAwesomeIcon icon="pen-to-square" />} color={SettingsManager.userColor} onClick={action(() => (this._showEditBox = !this._showEditBox))} /> - {this._showEditBox && ( - <div className="edit-box"> - <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="Edit instructions" - /> - <Button - style={{ alignSelf: 'flex-end' }} - text="Send" - icon={this._isLoading && this._regenInput !== '' ? <ReactLoading type="spin" color={SettingsManager.userVariantColor} width={16} height={20} /> : <AiOutlineSend />} - iconPlacement="right" - color={SettingsManager.userColor} - onClick={this.handleSendClick} - /> - </div> - )} - </div> - </div> + renderRegenerateEditBox = () => ( + <div className="edit-box"> + <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="Edit instructions" + onPointerDown={e => e.stopPropagation()} + /> + <Button + style={{ alignSelf: 'flex-end' }} + text="Send" + icon={this._isLoading && this._regenInput !== '' ? <ReactLoading type="spin" color={SettingsManager.userVariantColor} width={16} height={20} /> : <AiOutlineSend />} + iconPlacement="right" + color={SettingsManager.userColor} + onClick={() => this.handleSendClick(this._pageX, this._pageY)} + /> + </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, + color: SettingsManager.userColor, + }}> + <div className="regenerate-box"> + <IconButton + tooltip="Regenerate" + icon={this._isLoading && this._regenInput === '' ? <ReactLoading type="spin" color={SettingsManager.userVariantColor} width={16} height={20} /> : <FontAwesomeIcon icon={'rotate'} />} + color={SettingsManager.userColor} + onClick={() => this.handleSendClick(this._pageX, this._pageY)} + /> + <IconButton tooltip="Edit with GPT" icon={<FontAwesomeIcon icon="pen-to-square" />} color={SettingsManager.userColor} onClick={action(() => (this._showEditBox = !this._showEditBox))} /> + {this._showEditBox ? this.renderRegenerateEditBox() : null} + </div> + </div> + ); render() { - return this._display ? this.renderDisplay() : this.ShowRegenerate ? this.renderRegenerate() : null; + return this._display + ? this.renderDisplay() // + : this.ShowRegenerate + ? this.renderRegenerate() + : null; } } |