import { Button } from '@dash/components'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { Slider, Switch } from '@mui/material'; import { action, makeObservable, observable } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; import { AiOutlineSend } from 'react-icons/ai'; import ReactLoading from 'react-loading'; import { returnEmptyFilter, returnFalse, returnTrue } from '../../../ClientUtils'; import { emptyFunction, numberRange } from '../../../Utils'; import { Doc, DocListCast, returnEmptyDoclist } from '../../../fields/Doc'; import { ImageCast, NumCast, StrCast } from '../../../fields/Types'; import { ImageField } from '../../../fields/URLField'; import { DocumentType } from '../../documents/DocumentTypes'; import { Docs } from '../../documents/Documents'; import { makeUserTemplateButtonOrImage } from '../../util/DropConverter'; import { SettingsManager } from '../../util/SettingsManager'; import { Transform } from '../../util/Transform'; import { undoBatch } from '../../util/UndoManager'; import { ObservableReactComponent } from '../ObservableReactComponent'; import { DefaultStyleProvider, returnEmptyDocViewList } from '../StyleProvider'; import { DocumentView, DocumentViewInternal } from '../nodes/DocumentView'; import { FieldView } from '../nodes/FieldView'; import { DrawingOptions, SmartDrawHandler } from './SmartDrawHandler'; import './StickerPalette.scss'; interface StickerPaletteProps { Doc: Doc; } enum StickerPaletteMode { create, view, } /** * The StickerPalette can be toggled in the lightbox view of a document. The goal of the palette * is to offer an easy way for users to create stickers and drag and drop them onto a document. * These stickers can technically be of any document type and operate similarly to user templates. * However, the palette is designed to be geared toward ink stickers and image stickers. * * On the "add" side of the palette, there is a way to create a drawing sticker with GPT. Users can * enter the item to draw, toggle different settings, then GPT will generate three versions of the drawing * to choose from. These drawings can then be saved to the palette as stickers. */ @observer export class StickerPalette extends ObservableReactComponent { public static LayoutString(fieldKey: string) { return FieldView.LayoutString(StickerPalette, fieldKey); } /** * Adds a doc to the sticker palette. Gets a snapshot of the document to use as a preview in the palette. When this * preview is dragged onto a parent document, a copy of that document is added as a sticker. */ public static addToPalette = async (doc: Doc) => { if (!doc.savedAsSticker) { const docView = DocumentView.getDocumentView(doc); await docView?.ComponentView?.updateIcon?.(true); const { clone } = Doc.MakeClone(doc); clone.title = doc.title; const image = ImageCast(doc.icon, ImageCast(clone[Doc.LayoutDataKey(clone)]))?.url?.href; Doc.MyStickers && Doc.AddDocToList(Doc.MyStickers, 'data', makeUserTemplateButtonOrImage(clone, image)); doc.savedAsSticker = true; } }; public static getIcon(group: Doc) { const docView = DocumentView.getDocumentView(group); docView?.ComponentView?.updateIcon?.(true); return !docView ? undefined : new Promise(res => setTimeout(() => res(ImageCast(docView.Document.icon)), 1000)); } private _gptRes: string[] = []; @observable private _paletteMode = StickerPaletteMode.view; @observable private _userInput: string = ''; @observable private _isLoading: boolean = false; @observable private _canInteract: boolean = true; @observable private _showRegenerate: boolean = false; @observable private _docView: DocumentView | null = null; @observable private _docCarouselView: DocumentView | null = null; @observable private _opts: DrawingOptions = { text: '', complexity: 5, size: 200, autoColor: true, x: 0, y: 0 }; constructor(props: StickerPaletteProps) { super(props); makeObservable(this); } componentWillUnmount() { this.resetPalette(true); } Contains = (view: DocumentView) => (this._docView && (view.containerViewPath?.() ?? []).concat(view).includes(this._docView)) || // (this._docCarouselView && (view.containerViewPath?.() ?? []).concat(view).includes(this._docCarouselView)); return170 = () => 170; handleKeyPress = (event: React.KeyboardEvent) => { if (event.key === 'Enter') { this.generateDrawings(); } }; setPaletteMode = action((mode: StickerPaletteMode) => { this._paletteMode = mode; }); setUserInput = action((input: string) => { if (!this._isLoading) this._userInput = input; }); setDetail = action((detail: number) => { if (this._canInteract) this._opts.complexity = detail; }); setColor = action((autoColor: boolean) => { if (this._canInteract) this._opts.autoColor = autoColor; }); setSize = action((size: number) => { if (this._canInteract) this._opts.size = size; }); resetPalette = action((changePaletteMode: boolean) => { if (changePaletteMode) this.setPaletteMode(StickerPaletteMode.view); this.setUserInput(''); this.setDetail(5); this.setColor(true); this.setSize(200); this._showRegenerate = false; this._canInteract = true; this._opts = { text: '', complexity: 5, size: 200, autoColor: true, x: 0, y: 0 }; this._gptRes = []; this._props.Doc.$data = undefined; }); /** * Calls the draw with AI functions in SmartDrawHandler to allow users to generate drawings straight from * the sticker palette. */ @undoBatch generateDrawings = action(() => { this._isLoading = true; const prevDrawings = DocListCast(this._props.Doc.$data); this._props.Doc.$data = undefined; SmartDrawHandler.Instance.AddDrawing = this.addDrawing; this._canInteract = false; Promise.all( numberRange(3).map(() => { return this._showRegenerate ? SmartDrawHandler.Instance.regenerate(prevDrawings, this._userInput) : SmartDrawHandler.Instance.drawWithGPT({ X: 0, Y: 0 }, this._userInput, this._opts.complexity || 0, this._opts.size || 0, !!this._opts.autoColor); }) ).then(() => { this._opts.text !== '' ? (this._opts.text = `${this._opts.text} ~~~ ${this._userInput}`) : (this._opts.text = this._userInput); this._userInput = ''; this._isLoading = false; this._showRegenerate = true; }); }); @action addDrawing = (drawing: Doc) => { this._gptRes.push(StrCast(drawing.$ai_drawing_data)); drawing.$freeform_fitContentsToBox = true; Doc.AddDocToList(this._props.Doc, 'data', drawing); }; /** * Saves the currently showing, newly generated drawing to the sticker palette and sets the metadata. * AddToPalette() is generically used to add any document to the palette, while this defines the behavior for when a user * presses the "save drawing" button. */ saveDrawing = () => { const cIndex = NumCast(this._props.Doc.carousel_index); const focusedDrawing = DocListCast(this._props.Doc.data)[cIndex]; focusedDrawing.$title = this._opts.text?.match(/^(.*?)~~~.*$/)?.[1] || this._opts.text; focusedDrawing.$ai_prompt = this._opts.text; focusedDrawing.$ai_drawing_complexity = this._opts.complexity; focusedDrawing.$ai_drawing_colored = this._opts.autoColor; focusedDrawing.$ai_drawing_size = this._opts.size; focusedDrawing.$ai_drawing_data = this._gptRes[cIndex]; focusedDrawing.$ai = 'gpt'; focusedDrawing.width = this._opts.size; focusedDrawing.x = this._opts.x; focusedDrawing.y = this._opts.y; StickerPalette.addToPalette(focusedDrawing).then(() => this.resetPalette(true)); }; renderCreateInput = () => (
this.setUserInput(e.target.value)} placeholder={this._showRegenerate ? '(Optional) Enter edits' : 'Enter item to draw'} onKeyDown={this.handleKeyPress} />
); renderCreateOptions = () => (
Color this.setColor(!this._opts.autoColor)} />
Detail typeof val === 'number' && this.setDetail(val)} valueLabelDisplay="auto" />
Size typeof val === 'number' && this.setSize(val)} valueLabelDisplay="auto" />
); renderDoc = (doc: Doc, refFunc: (r: DocumentView) => void) => { return ( ); }; renderPaletteCreate = () => ( <> {this.renderCreateInput()} {this.renderCreateOptions()} {this.renderDoc(this._props.Doc, (r: DocumentView) => { this._docCarouselView = r; })}
); renderPaletteView = () => ( <> {Doc.MyStickers && this.renderDoc(Doc.MyStickers, (r: DocumentView) => { this._docView = r; })}