import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { action, makeObservable, observable } from 'mobx'; import { observer } from 'mobx-react'; import React from 'react'; import { SettingsManager } from '../../util/SettingsManager'; import { ObservableReactComponent } from '../ObservableReactComponent'; import { Button, IconButton } from 'browndash-components'; import ReactLoading from 'react-loading'; import { AiOutlineSend } from 'react-icons/ai'; import { gptAPICall, GPTCallType } from '../../apis/gpt/GPT'; import { InkData, InkTool } from '../../../fields/InkField'; import { SVGToBezier } from '../../util/bezierFit'; const { parse } = require('svgson'); import { Slider, Switch } from '@mui/material'; import { Doc } from '../../../fields/Doc'; import { DocData } from '../../../fields/DocSymbols'; import { DocumentView } from '../nodes/DocumentView'; import { BoolCast, NumCast, StrCast } from '../../../fields/Types'; import './SmartDrawHandler.scss'; import { unimplementedFunction } from '../../../Utils'; export interface DrawingOptions { text: string; complexity: number; size: number; autoColor: boolean; x: number; y: number; } @observer export class SmartDrawHandler extends ObservableReactComponent<{}> { static Instance: SmartDrawHandler; @observable private _display: boolean = false; @observable private _pageX: number = 0; @observable private _pageY: number = 0; @observable private _yRelativeToTop: boolean = true; @observable private _isLoading: boolean = false; @observable private _userInput: string = ''; @observable private _showOptions: boolean = false; @observable private _showEditBox: boolean = false; @observable public _showRegenerate: boolean = false; @observable private _complexity: number = 5; @observable private _size: number = 200; @observable private _autoColor: boolean = true; @observable private _regenInput: string = ''; @observable private _canInteract: boolean = true; public _addFunc: (strokeList: [InkData, string, string][], opts: DrawingOptions, gptRes: string, containerDoc?: Doc) => void = () => {}; public _deleteFunc: (doc?: Doc) => void = () => {}; private _lastInput: DrawingOptions = { text: '', complexity: 5, size: 350, autoColor: true, x: 0, y: 0 }; private _lastResponse: string = ''; private _selectedDoc: Doc | undefined = undefined; constructor(props: any) { super(props); makeObservable(this); SmartDrawHandler.Instance = this; } @action setUserInput = (input: string) => { if (this._canInteract) this._userInput = input; }; @action setRegenInput = (input: string) => { if (this._canInteract) this._regenInput = input; }; @action setShowOptions = () => { this._showOptions = !this._showOptions; }; @action setComplexity = (val: number) => { if (this._canInteract) this._complexity = val; }; @action setSize = (val: number) => { if (this._canInteract) this._size = val; }; @action setAutoColor = () => { if (this._canInteract) this._autoColor = !this._autoColor; }; @action displaySmartDrawHandler = (x: number, y: number, addFunc: (strokeData: [InkData, string, string][], opts: DrawingOptions, gptRes: string, containerDoc?: Doc) => void, deleteFunc: (doc?: Doc) => void) => { this._pageX = x; this._pageY = y; this._display = true; this._addFunc = addFunc; this._deleteFunc = deleteFunc; }; @action displayRegenerate = (x: number, y: number, addFunc: (strokeData: [InkData, string, string][], opts: DrawingOptions, gptRes: string, containerDoc?: Doc) => void, deleteFunc: (doc?: Doc) => void) => { this._selectedDoc = DocumentView.SelectedDocs()?.lastElement(); const docData = this._selectedDoc[DocData]; this._addFunc = addFunc; this._deleteFunc = deleteFunc; this._pageX = x; this._pageY = y; this._display = false; this._showRegenerate = true; this._showEditBox = false; 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 }; }; @action hideSmartDrawHandler = () => { this._showRegenerate = false; this._display = false; this._isLoading = false; this._showOptions = false; this._userInput = ''; this._complexity = 5; this._size = 350; this._autoColor = true; Doc.ActiveTool = InkTool.None; }; @action hideRegenerate = () => { if (!this._isLoading) { this._showRegenerate = false; this._isLoading = false; this._regenInput = ''; this._lastInput = { text: '', complexity: 5, size: 350, autoColor: true, x: 0, y: 0 }; } }; @action handleKeyPress = async (event: React.KeyboardEvent) => { if (event.key === 'Enter') { await this.handleSendClick(); } }; @action handleSendClick = async () => { this._isLoading = true; this._canInteract = false; if (this._showRegenerate) { await this.regenerate(); this._regenInput = ''; this._showEditBox = false; } else { this._showOptions = false; await this.drawWithGPT({ X: this._pageX, Y: this._pageY }, this._userInput, this._complexity, this._size, this._autoColor); } this._isLoading = false; this._canInteract = true; }; _errorOccurredOnce = false; @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 }; try { const res = await gptAPICall(`"${input}", "${complexity}", "${size}"`, GPTCallType.DRAW, undefined, true); if (!res) { console.error('GPT call failed'); return; } console.log(res); const strokeData = await this.parseResponse(res, startPt, false, autoColor); this._errorOccurredOnce = false; this.hideSmartDrawHandler(); this._showRegenerate = true; return strokeData; } catch (err) { if (this._errorOccurredOnce) { console.error('GPT call failed', err); this._errorOccurredOnce = false; } else { this._errorOccurredOnce = true; this.drawWithGPT(startPt, input, complexity, size, autoColor); } } }; @action edit = () => { this._showEditBox = !this._showEditBox; }; @action regenerate = async (lastInput?: DrawingOptions, lastResponse?: string, regenInput?: string) => { 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; } console.log(res); this.parseResponse(res, { X: this._lastInput.x, Y: this._lastInput.y }, true, lastInput?.autoColor || this._autoColor); } catch (err) { console.error('GPT call failed', err); } }; @action parseResponse = async (res: string, startPoint: { X: number; Y: number }, regenerate: boolean, autoColor: boolean) => { const svg = res.match(/]*>([\s\S]*?)<\/svg>/g); if (svg) { this._lastResponse = svg[0]; const svgObject = await parse(svg[0]); const svgStrokes: any = svgObject.children; const strokeData: [InkData, string, string][] = []; svgStrokes.forEach((child: any) => { const convertedBezier: InkData = SVGToBezier(child.name, child.attributes); strokeData.push([ convertedBezier.map(point => { return { X: point.X + startPoint.X - this._size / 1.5, Y: point.Y + startPoint.Y - this._size / 2 }; }), (regenerate ? this._lastInput.autoColor : autoColor) ? child.attributes.stroke : undefined, (regenerate ? this._lastInput.autoColor : autoColor) ? child.attributes.fill : undefined, ]); }); if (regenerate) { if (this._deleteFunc !== unimplementedFunction) this._deleteFunc(this._selectedDoc); this._addFunc(strokeData, this._lastInput, svg[0]); } else { this._addFunc(strokeData, this._lastInput, svg[0]); } return { data: strokeData, lastInput: this._lastInput, lastRes: svg[0] }; } }; render() { if (this._display) { return (
{ this.hideSmartDrawHandler(); this.hideRegenerate(); }} icon={} color={SettingsManager.userColor} style={{ width: '19px' }} /> { this.setUserInput(e.target.value); }} placeholder="Enter item to draw" onKeyDown={this.handleKeyPress} /> } color={SettingsManager.userColor} style={{ width: '14px' }} onClick={this.setShowOptions} />
{this._showOptions && ( <>
Auto color
Complexity { this.setComplexity(val as number); }} valueLabelDisplay="auto" />
Size (in pixels) { this.setSize(val as number); }} valueLabelDisplay="auto" />
)}
); } else if (this._showRegenerate) { return (
: } color={SettingsManager.userColor} onClick={this.handleSendClick} /> } color={SettingsManager.userColor} onClick={this.edit} /> {this._showEditBox && (
{ this.setRegenInput(e.target.value); }} onKeyDown={this.handleKeyPress} placeholder="Edit instructions" />
)}
); } else { return <>; } } }