From d0cbb43ec3fa19f76a570f5e0038bfc72c9f37b9 Mon Sep 17 00:00:00 2001 From: bobzel Date: Wed, 7 May 2025 11:21:53 -0400 Subject: fixed dont_center on Docs in gridview. fixed ai view screentolocal in docView, and made ai view more streamlined. got rid of ai history sidebar in images. fixed imageeditor to use a mask that doesn't show the original conctents. fixed gptpopup scrolling, turning off of spinner, and improved ability to filter Docs --- src/client/views/nodes/ImageBox.tsx | 96 ++++++++++--------------------------- 1 file changed, 26 insertions(+), 70 deletions(-) (limited to 'src/client/views/nodes/ImageBox.tsx') diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx index d16baada6..bf6915570 100644 --- a/src/client/views/nodes/ImageBox.tsx +++ b/src/client/views/nodes/ImageBox.tsx @@ -1,6 +1,6 @@ -import { Button, Colors, EditableText, IconButton, Size, Toggle, ToggleType, Type } from '@dash/components'; +import { Button, Colors, EditableText, IconButton, NumberDropdown, Size, Toggle, ToggleType, Type } from '@dash/components'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { Slider, Tooltip } from '@mui/material'; +import { Tooltip } from '@mui/material'; import axios from 'axios'; import { action, computed, IReactionDisposer, makeObservable, observable, ObservableMap, reaction, runInAction } from 'mobx'; import { observer } from 'mobx-react'; @@ -37,7 +37,6 @@ import { OverlayView } from '../OverlayView'; import { AnchorMenu } from '../pdf/AnchorMenu'; import { PinDocView, PinProps } from '../PinFuncs'; import { DrawingFillHandler } from '../smartdraw/DrawingFillHandler'; -import { FireflyImageData, isFireflyImageData } from '../smartdraw/FireflyConstants'; import { SmartDrawHandler } from '../smartdraw/SmartDrawHandler'; import { StickerPalette } from '../smartdraw/StickerPalette'; import { StyleProp } from '../StyleProp'; @@ -102,7 +101,6 @@ export class ImageBox extends ViewBoxAnnotatableComponent() { @observable private _regenInput = ''; @observable private _canInteract = true; @observable private _regenerateLoading = false; - @observable private _prevImgs: FireflyImageData[] = StrCast(this.Document.ai_firefly_history) ? JSON.parse(StrCast(this.Document.ai_firefly_history)) : []; // Add these observable properties to the ImageBox class @observable private _outpaintingInProgress = false; @@ -845,34 +843,10 @@ export class ImageBox extends ViewBoxAnnotatableComponent() { @observable _filterFunc: ((doc: Doc) => boolean) | undefined = undefined; @observable private _fireflyRefStrength = 0; - componentAIViewHistory = () => ( -
-
- ); - componentAIView = () => { - const field = this.dataDoc[this.fieldKey] instanceof ImageField ? Cast(this.dataDoc[this.fieldKey], ImageField, null) : new ImageField(String(this.dataDoc[this.fieldKey])); return (
- - Firefly: - () {
-
-
- - Similarity - - this._canInteract && (this._fireflyRefStrength = val as number))} - valueLabelDisplay="auto" - /> +
+ this._canInteract && (this._fireflyRefStrength = val as number)), + `${this.Document.title} button set from list` + )} + fillWidth + /> +
); -- cgit v1.2.3-70-g09d2 From 4b8c4c9277cca69ea3341ca6ddf4c7f5befc78a0 Mon Sep 17 00:00:00 2001 From: bobzel Date: Wed, 7 May 2025 15:46:06 -0400 Subject: cleaned up ai metadata on generated drawings. --- src/client/documents/Documents.ts | 5 +- src/client/util/DocumentManager.ts | 2 +- .../collectionFreeForm/CollectionFreeFormView.tsx | 54 ++----------------- src/client/views/nodes/ImageBox.tsx | 6 +-- src/client/views/pdf/GPTPopup/GPTPopup.tsx | 4 +- src/client/views/smartdraw/DrawingFillHandler.tsx | 6 +-- src/client/views/smartdraw/SmartDrawHandler.scss | 1 + src/client/views/smartdraw/SmartDrawHandler.tsx | 63 +++++++++++----------- src/client/views/smartdraw/StickerPalette.tsx | 12 ++--- 9 files changed, 57 insertions(+), 96 deletions(-) (limited to 'src/client/views/nodes/ImageBox.tsx') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index bbe872a91..9b6acef7b 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -519,8 +519,9 @@ export class DocumentOptions { card_sort_isDesc?: BOOLt = new BoolInfo('whether the cards are sorted ascending or descending'); ai?: string; // to mark items as ai generated - ai_firefly_seed?: number; - ai_firefly_prompt?: string; + ai_prompt_seed?: NUMt = new NumInfo('seed to GAI engine to make results deterministic'); + ai_prompt?: STRt = new StrInfo('input prompt to GAI engine'); + ai_generatedDocs?: List; // list of documents generated by GAI engine /** * JSON‐stringified slot configuration for ScrapbookBox diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts index 3bae2881e..fc8f22cf3 100644 --- a/src/client/util/DocumentManager.ts +++ b/src/client/util/DocumentManager.ts @@ -349,7 +349,7 @@ export class DocumentManager { // bcz: should this delay be an options parameter? setTimeout(() => { Doc.linkFollowHighlight(viewSpec ? [docView.Document, viewSpec] : docView.Document, undefined, options.effect); - const zoomableText = StrCast(targetDoc.text_html, StrCast(targetDoc.ai_firefly_prompt)); + const zoomableText = StrCast(targetDoc.text_html, StrCast(targetDoc.ai_prompt)); if (options.zoomTextSelections && Doc.IsUnhighlightTimerSet() && contextView && zoomableText) { // if the docView is a text anchor, the contextView is the PDF/Web/Text doc contextView.setTextHtmlOverlay(zoomableText, options.effect); diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 05958e6b9..bb3c59eae 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -1259,15 +1259,8 @@ export class CollectionFreeFormView extends CollectionSubView { - doc.$title = opts.text; - doc.$width = opts.size; - doc.$ai_drawing_input = opts.text; - doc.$ai_drawing_complexity = opts.complexity; - doc.$ai_drawing_colored = opts.autoColor; - doc.$ai_drawing_size = opts.size; - doc.$ai_drawing_data = gptRes; - doc.$ai = 'gpt'; + addDrawing = (doc: Doc, opts: DrawingOptions, x?: number, y?: number) => { + doc.$ai_prompt = opts.text; this._drawingContainer = doc; if (x !== undefined && y !== undefined) { [doc.x, doc.y] = this.screenToFreeformContentsXf.transformPoint(x, y); @@ -1977,7 +1970,7 @@ export class CollectionFreeFormView extends CollectionSubView { SmartDrawHandler.Instance.AddDrawing = this.addDrawing; SmartDrawHandler.Instance.RemoveDrawing = this.removeDrawing; - !SmartDrawHandler.Instance.ShowRegenerate ? SmartDrawHandler.Instance.displayRegenerate(this._downX, this._downY - 10) : SmartDrawHandler.Instance.hideRegenerate(); + !SmartDrawHandler.Instance.ShowRegenerate ? SmartDrawHandler.Instance.displayRegenerate(this._downX, this._downY - 10, NumCast(this.layoutDoc[this.scaleFieldKey])) : SmartDrawHandler.Instance.hideRegenerate(); }), icon: 'pen-to-square', }); @@ -2182,9 +2175,7 @@ export class CollectionFreeFormView extends CollectionSubView { - this._drawingFillInput = e.target.value; - })} + onChange={action(e => (this._drawingFillInput = e.target.value))} />
Similarity @@ -2213,11 +2204,7 @@ export class CollectionFreeFormView extends CollectionSubView { this._drawingFillLoading = true; - DrawingFillHandler.drawingToImage(this.props.Document, this._fireflyRefStrength, this._drawingFillInput || StrCast(this.Document.title))?.then( - action(() => { - this._drawingFillLoading = false; - }) - ); + DrawingFillHandler.drawingToImage(this.props.Document, this._fireflyRefStrength, this._drawingFillInput || StrCast(this.Document.title))?.then(action(() => (this._drawingFillLoading = false))); }), 'create image' )} @@ -2225,37 +2212,6 @@ export class CollectionFreeFormView extends CollectionSubView
-
- Regenerate -
- { - this._regenInput = e.target.value; - })} - placeholder="..under development.." - /> -
-
-
-
); }; diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx index bf6915570..b648f2adb 100644 --- a/src/client/views/nodes/ImageBox.tsx +++ b/src/client/views/nodes/ImageBox.tsx @@ -225,7 +225,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent() { const drag = de.complete.docDragData.draggedDocuments.lastElement(); const dragField = drag[Doc.LayoutDataKey(drag)]; const descText = RTFCast(dragField)?.Text || StrCast(dragField) || RTFCast(drag.text)?.Text || StrCast(drag.text) || StrCast(this.Document.title); - const oldPrompt = StrCast(this.Document.ai_firefly_prompt, StrCast(this.Document.title)); + const oldPrompt = StrCast(this.Document.ai_prompt, StrCast(this.Document.title)); const newPrompt = (text: string) => (oldPrompt ? `${oldPrompt} ~~~ ${text}` : text); DrawingFillHandler.drawingToImage(this.Document, 90, newPrompt(descText), drag)?.then(action(() => (this._regenerateLoading = false))); added = false; @@ -735,9 +735,9 @@ export class ImageBox extends ViewBoxAnnotatableComponent() {
DocCast(this.Document.ai_firefly_generatedDocs) && DocumentView.showDocument(DocCast(this.Document.ai_firefly_generatedDocs)!, { openLocation: OpenWhere.addRight })} + onClick={() => DocCast(this.Document.ai_generatedDocs) && DocumentView.showDocument(DocCast(this.Document.ai_generatedDocs)!, { openLocation: OpenWhere.addRight })} style={{ - display: (this._props.isContentActive() && (SnappingManager.CanEmbed || this.Document.ai_firefly_generatedDocs)) || this._regenerateLoading ? 'block' : 'none', + display: (this._props.isContentActive() && (SnappingManager.CanEmbed || this.Document.ai_generatedDocs)) || this._regenerateLoading ? 'block' : 'none', transform: `scale(${this.uiBtnScaling})`, width: this._sideBtnWidth, height: this._sideBtnWidth, diff --git a/src/client/views/pdf/GPTPopup/GPTPopup.tsx b/src/client/views/pdf/GPTPopup/GPTPopup.tsx index 9fbae5c90..568e48edf 100644 --- a/src/client/views/pdf/GPTPopup/GPTPopup.tsx +++ b/src/client/views/pdf/GPTPopup/GPTPopup.tsx @@ -211,7 +211,7 @@ export class GPTPopup extends ObservableReactComponent { const selView = DocumentView.Selected().lastElement(); const selDoc = selView?.Document; if (selDoc && (selView._props.renderDepth > 1 || selDoc[Doc.LayoutDataKey(selDoc)] instanceof ImageField)) { - const oldPrompt = StrCast(selDoc.ai_firefly_prompt, StrCast(selDoc.title)); + const oldPrompt = StrCast(selDoc.ai_prompt, StrCast(selDoc.title)); const newPrompt = oldPrompt ? `${oldPrompt} ~~~ ${imgDesc}` : imgDesc; return DrawingFillHandler.drawingToImage(selDoc, 100, newPrompt, selDoc) .then(action(() => (this._userPrompt = ''))) @@ -767,7 +767,7 @@ export class GPTPopup extends ObservableReactComponent { //prettier-ignore switch (this._mode) { case GPTPopupMode.USER_PROMPT: return this.promptBox("ASK", this._userPrompt, this.setUserPrompt, 'Ask GPT to sort, tag, define, or filter your documents for you!'); - case GPTPopupMode.FIREFLY: return this.promptBox("CREATE", this._userPrompt, this.setUserPrompt, StrCast(DocumentView.Selected().lastElement()?.Document.ai_firefly_prompt, 'Ask Firefly to generate images')); + case GPTPopupMode.FIREFLY: return this.promptBox("CREATE", this._userPrompt, this.setUserPrompt, StrCast(DocumentView.Selected().lastElement()?.Document.ai_prompt, 'Ask Firefly to generate images')); case GPTPopupMode.QUIZ_RESPONSE: return this.promptBox("QUIZ", this._quizAnswer, this.setQuizAnswer, 'Describe/answer the selected document!'); case GPTPopupMode.GPT_MENU: return this.menuBox(); case GPTPopupMode.SUMMARY: return this.summaryBox(); diff --git a/src/client/views/smartdraw/DrawingFillHandler.tsx b/src/client/views/smartdraw/DrawingFillHandler.tsx index 2c69284db..f773957e7 100644 --- a/src/client/views/smartdraw/DrawingFillHandler.tsx +++ b/src/client/views/smartdraw/DrawingFillHandler.tsx @@ -51,19 +51,19 @@ export class DrawingFillHandler { if (error.includes('Dropbox') && confirm('Create image failed. Try authorizing DropBox?\r\n' + error.replace(/^[^"]*/, ''))) { return DrawingFillHandler.authorizeDropbox(); } - const genratedDocs = DocCast(drawing.ai_firefly_generatedDocs) ?? Docs.Create.MasonryDocument([], { title: StrCast(drawing.title) + ' AI Images', _width: 400, _height: 400 }); - drawing.$ai_firefly_generatedDocs = genratedDocs; + const genratedDocs = DocCast(drawing.ai_generatedDocs) ?? Docs.Create.MasonryDocument([], { title: StrCast(drawing.title) + ' AI Images', _width: 400, _height: 400 }); + drawing.$ai_generatedDocs = genratedDocs; (res as Upload.ImageInformation[]).map(info => Doc.AddDocToList( genratedDocs, undefined, Docs.Create.ImageDocument(info.accessPaths.agnostic.client, { ai: 'firefly', + ai_prompt: newPrompt, tags: new List(['@ai']), title: newPrompt, _data_usePath: 'alternate:hover', data_alternates: new List([drawing]), - ai_firefly_prompt: newPrompt, _width: 500, data_nativeWidth: info.nativeWidth, data_nativeHeight: info.nativeHeight, diff --git a/src/client/views/smartdraw/SmartDrawHandler.scss b/src/client/views/smartdraw/SmartDrawHandler.scss index cca7d77c7..e80f1122b 100644 --- a/src/client/views/smartdraw/SmartDrawHandler.scss +++ b/src/client/views/smartdraw/SmartDrawHandler.scss @@ -73,5 +73,6 @@ .edit-box { display: flex; flex-direction: row; + color: black; } } diff --git a/src/client/views/smartdraw/SmartDrawHandler.tsx b/src/client/views/smartdraw/SmartDrawHandler.tsx index a6e221f3e..3976ec39e 100644 --- a/src/client/views/smartdraw/SmartDrawHandler.tsx +++ b/src/client/views/smartdraw/SmartDrawHandler.tsx @@ -61,7 +61,6 @@ export class SmartDrawHandler extends ObservableReactComponent { static Instance: SmartDrawHandler; private _lastInput: DrawingOptions = { text: '', complexity: 5, size: 350, autoColor: true, x: 0, y: 0 }; - private _lastResponse: string = ''; private _selectedDocs: Doc[] = []; @observable private _display: boolean = false; @@ -97,7 +96,7 @@ export class SmartDrawHandler extends ObservableReactComponent { 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, x?: number, y?: number) => void = unimplementedFunction; + public AddDrawing: (doc: Doc, opts: DrawingOptions, 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, @@ -105,7 +104,7 @@ export class SmartDrawHandler extends ObservableReactComponent { * 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 static 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, gptRes: string) => { const drawing: Doc[] = []; strokeList.forEach((stroke: [InkData, string, string]) => { const bounds = InkField.getBounds(stroke[0]); @@ -130,7 +129,14 @@ export class SmartDrawHandler extends ObservableReactComponent { drawing.push(inkDoc); }); - return MarqueeView.getCollection(drawing, undefined, true, { left: 1, top: 1, width: 1, height: 1 }); + const drawn = MarqueeView.getCollection(drawing, undefined, true, { left: 1, top: 1, width: 1, height: 1 }); + + drawn.$ai_drawing = true; + drawn.$ai_drawing_complexity = opts.complexity; + drawn.$ai_drawing_colored = opts.autoColor; + drawn.$ai_drawing_size = opts.size; + drawn.$ai_drawing_data = gptRes; + return drawn; }; @action @@ -146,15 +152,16 @@ export class SmartDrawHandler extends ObservableReactComponent { * the regenerate popup show by user command. */ @action - displayRegenerate = (x: number, y: number) => { + displayRegenerate = (x: number, y: number, scale: number) => { this._selectedDocs = [DocumentView.SelectedDocs()?.lastElement()]; [this._pageX, this._pageY] = [x, y]; + this._scale = scale; this._display = false; this.ShowRegenerate = true; this._showEditBox = false; const docData = this._selectedDocs[0]; - this._lastResponse = StrCast(docData.$drawingData); - 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 }; + this._regenInput = StrCast(docData.$ai_prompt, StrCast(docData.title)); + this._lastInput = { text: StrCast(docData.$ai_prompt), complexity: NumCast(docData.$ai_drawing_complexity), size: NumCast(docData.$ai_drawing_size), autoColor: BoolCast(docData.$ai_drawing_colored), x: this._pageX, y: this._pageY }; }; /** @@ -168,9 +175,6 @@ export class SmartDrawHandler extends ObservableReactComponent { this._isLoading = false; this._showOptions = false; this._userInput = ''; - this._complexity = 5; - this._size = 350; - this._autoColor = true; Doc.ActiveTool = InkTool.None; } }; @@ -184,7 +188,6 @@ export class SmartDrawHandler extends ObservableReactComponent { this.ShowRegenerate = false; this._isLoading = false; this._regenInput = ''; - this._lastInput = { text: '', complexity: 5, size: 350, autoColor: true, x: 0, y: 0 }; } }; @@ -208,7 +211,9 @@ export class SmartDrawHandler extends ObservableReactComponent { this._isLoading = true; this._canInteract = false; if (this.ShowRegenerate) { - await this.regenerate(this._selectedDocs, undefined, undefined, this._regenInput).then(action(() => (this._showEditBox = false))); + this._lastInput.x = X; + this._lastInput.y = Y; + await this.regenerate(this._selectedDocs).then(action(() => (this._showEditBox = false))); } else { this._showOptions = false; try { @@ -234,13 +239,15 @@ export class SmartDrawHandler extends ObservableReactComponent { */ 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 }; + this._lastInput = { text: input, complexity, size, 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); + if (drawingDoc) { + this.AddDrawing(drawingDoc, this._lastInput, screenPt.X, screenPt.Y); + this._selectedDocs.push(drawingDoc); + } return strokeData; } else { console.error('GPT call failed'); @@ -255,7 +262,7 @@ export class SmartDrawHandler extends ObservableReactComponent { createImageWithFirefly = (input: string, seed?: number): Promise => { 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); + doc instanceof Doc && this.AddDrawing(doc, this._lastInput, this._pageX, this._pageY); return doc; }); }; /** @@ -301,8 +308,8 @@ export class SmartDrawHandler extends ObservableReactComponent { _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, + ai_prompt_seed: +(newseed ?? 0), + ai_prompt: input, }); }) .catch(e => { @@ -316,33 +323,30 @@ export class SmartDrawHandler extends ObservableReactComponent { * @param doc the drawing Docs to regenerate */ @action - regenerate = (drawingDocs: Doc[], lastInput?: DrawingOptions, lastResponse?: string, regenInput?: string, changeInPlace?: boolean) => { - if (lastInput) this._lastInput = lastInput; - if (lastResponse) this._lastResponse = lastResponse; + regenerate = (drawingDocs: Doc[], regenInput?: string, changeInPlace?: boolean) => { if (regenInput) this._regenInput = regenInput; 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)); + const newPrompt = doc.ai_prompt && doc.ai_prompt !== this._regenInput ? `${doc.ai_prompt} ~~~ ${this._regenInput}` : this._regenInput; + return this._regenInput ? func(newPrompt, NumCast(doc?.ai_prompt_seed)) : func(this._lastInput.text || StrCast(doc.ai_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.`; + const prompt = `This is your previously generated svg code: ${doc.$ai_drawing_data} for the user input "${doc.ai_prompt}". 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); + return gptAPICall(`"${doc.$ai_prompt}", "${doc.$ai_drawing_complexity}", "${doc.$ai_drawing_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 strokeData = await this.parseSvg(res, { X: 0, Y: 0 }, true, this._autoColor); const drawingDoc = strokeData && SmartDrawHandler.CreateDrawingDoc(strokeData.data, strokeData.lastInput, strokeData.lastRes); - drawingDoc && this.AddDrawing(drawingDoc, this._lastInput, res); + drawingDoc && this.AddDrawing(drawingDoc, this._lastInput, this._lastInput.x, this._lastInput.y); } else { console.error('GPT call failed'); } @@ -363,7 +367,6 @@ export class SmartDrawHandler extends ObservableReactComponent { const svg = res.match(/]*>([\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; @@ -664,7 +667,7 @@ export class SmartDrawHandler extends ObservableReactComponent {
: } + icon={this._isLoading ? : } color={SettingsManager.userColor} onClick={() => this.handleSendClick(this._pageX, this._pageY)} /> diff --git a/src/client/views/smartdraw/StickerPalette.tsx b/src/client/views/smartdraw/StickerPalette.tsx index 0e234e966..6ef3d26ad 100644 --- a/src/client/views/smartdraw/StickerPalette.tsx +++ b/src/client/views/smartdraw/StickerPalette.tsx @@ -9,7 +9,7 @@ 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 } from '../../../fields/Types'; +import { ImageCast, NumCast, StrCast } from '../../../fields/Types'; import { ImageField } from '../../../fields/URLField'; import { DocumentType } from '../../documents/DocumentTypes'; import { Docs } from '../../documents/Documents'; @@ -147,9 +147,9 @@ export class StickerPalette extends ObservableReactComponent { + numberRange(3).map(() => { return this._showRegenerate - ? SmartDrawHandler.Instance.regenerate(prevDrawings, this._opts, this._gptRes[i], this._userInput) + ? 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(() => { @@ -161,8 +161,8 @@ export class StickerPalette extends ObservableReactComponent { - this._gptRes.push(gptRes); + addDrawing = (drawing: Doc) => { + this._gptRes.push(StrCast(drawing.$ai_drawing_data)); drawing.$freeform_fitContentsToBox = true; Doc.AddDocToList(this._props.Doc, 'data', drawing); }; @@ -176,7 +176,7 @@ export class StickerPalette extends ObservableReactComponent Date: Fri, 9 May 2025 23:01:36 -0400 Subject: added user templates to template_user global field. fixed schemas to not erase field values when setting a new column name. fixed templates to render properly when switching by fixing contentsRef to be observable. fixed templates with fields in stacks to autoHeight update correctly by . fixed images in templates to set their nativeDim after rendering. fixed text views of descriptions to auto-updated by setting the modification date of the description field. --- src/client/documents/DocUtils.ts | 20 +++++++++---------- src/client/util/DropConverter.ts | 4 +++- .../CollectionStackingViewFieldColumn.tsx | 6 ++++-- src/client/views/collections/CollectionSubView.tsx | 13 ++++++++++-- .../collectionSchema/CollectionSchemaView.tsx | 2 +- src/client/views/nodes/DocumentView.tsx | 6 +++--- src/client/views/nodes/ImageBox.tsx | 5 +++-- .../views/nodes/formattedText/FormattedTextBox.tsx | 3 ++- src/fields/Doc.ts | 23 +++++++++++++++++++--- src/server/SharedMediaTypes.ts | 3 +++ 10 files changed, 60 insertions(+), 25 deletions(-) (limited to 'src/client/views/nodes/ImageBox.tsx') diff --git a/src/client/documents/DocUtils.ts b/src/client/documents/DocUtils.ts index 9135899d7..36e03daed 100644 --- a/src/client/documents/DocUtils.ts +++ b/src/client/documents/DocUtils.ts @@ -642,8 +642,15 @@ export namespace DocUtils { return dd; } - export function assignImageInfo(result: Upload.FileInformation, protoIn: Doc) { + export function assignUploadInfo(result: Upload.FileInformation, protoIn: Doc) { const proto = protoIn; + + if (Upload.isTextInformation(result)) { + proto.text = result.rawText; + } + if (Upload.isVideoInformation(result)) { + proto.data_duration = result.duration; + } if (Upload.isImageInformation(result)) { const maxNativeDim = Math.max(result.nativeHeight, result.nativeWidth); const exifRotation = StrCast(result.exifData?.data?.Orientation).toLowerCase(); @@ -677,15 +684,8 @@ export namespace DocUtils { const pathname = result.accessPaths.agnostic.client; const doc = await DocUtils.DocumentFromType(type, pathname, full, overwriteDoc); if (doc) { - const proto = Doc.GetProto(doc); - proto.text = result.rawText; - !(result instanceof Error) && DocUtils.assignImageInfo(result, proto); - if (Upload.isVideoInformation(result)) { - proto.data_duration = result.duration; - } - if (overwriteDoc) { - Doc.removeCurrentlyLoading(overwriteDoc); - } + DocUtils.assignUploadInfo(result, Doc.GetProto(doc)); + overwriteDoc && Doc.removeCurrentlyLoading(overwriteDoc); generatedDocuments.push(doc); } return doc; diff --git a/src/client/util/DropConverter.ts b/src/client/util/DropConverter.ts index b6b111930..1d4779d8a 100644 --- a/src/client/util/DropConverter.ts +++ b/src/client/util/DropConverter.ts @@ -41,7 +41,7 @@ function makeTemplate(doc: Doc, first: boolean = true): boolean { if (first && !docs.length) { // bcz: feels hacky : if the root level document has items, it's not a field template isTemplate = Doc.MakeMetadataFieldTemplate(doc, layoutDoc[DocData], true) || isTemplate; - } else if (docData instanceof RichTextField || docData instanceof ImageField) { + } else if (docData instanceof RichTextField || docData instanceof ImageField || (docData === undefined && doc.type === DocumentType.IMG)) { if (!StrCast(layoutDoc.title).startsWith('-')) { isTemplate = Doc.MakeMetadataFieldTemplate(layoutDoc, layoutDoc[DocData], true); } @@ -86,6 +86,8 @@ export function makeUserTemplateButtonOrImage(doc: Doc, image?: string) { dbox.dragFactory = layoutDoc; dbox.dropPropertiesToRemove = doc.dropPropertiesToRemove instanceof ObjectField ? ObjectField.MakeCopy(doc.dropPropertiesToRemove) : undefined; dbox.onDragStart = ScriptField.MakeFunction('getCopy(this.dragFactory)'); + const userTemplatesDoc = DocCast(Doc.UserDoc().template_user); + userTemplatesDoc && Doc.AddDocToList(userTemplatesDoc, 'data', layoutDoc); return dbox; } diff --git a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx index 994669734..345f60e75 100644 --- a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx +++ b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx @@ -71,6 +71,7 @@ export class CollectionStackingViewFieldColumn extends ObservableReactComponent< } _ele: HTMLElement | null = null; + _eleMasonrySingle = React.createRef(); protected onInternalPreDrop = (e: Event, de: DragManager.DropEvent, targetDropAction: dropActionType) => { const dragData = de.complete.docDragData; @@ -92,13 +93,13 @@ export class CollectionStackingViewFieldColumn extends ObservableReactComponent< createColumnDropRef = (ele: HTMLDivElement | null) => { this.dropDisposer?.(); if (ele) this.dropDisposer = DragManager.MakeDropTarget(ele, this.columnDrop.bind(this), this._props.Doc, this.onInternalPreDrop.bind(this)); - else if (this._ele) this.props.refList.splice(this.props.refList.indexOf(this._ele), 1); + else if (this._eleMasonrySingle.current) this.props.refList.splice(this.props.refList.indexOf(this._eleMasonrySingle.current), 1); this._ele = ele; }; @action componentDidMount() { - this._ele && this.props.refList.push(this._ele); + this._eleMasonrySingle.current && this.props.refList.push(this._eleMasonrySingle.current); this._disposers.collapser = reaction( () => this._props.headingObject?.collapsed, collapsed => { this.collapsed = collapsed !== undefined ? BoolCast(collapsed) : false; }, // prettier-ignore @@ -363,6 +364,7 @@ export class CollectionStackingViewFieldColumn extends ObservableReactComponent< }}>
() { } const loading = Docs.Create.LoadingDocument(file, options); Doc.addCurrentlyLoading(loading); - DocUtils.uploadFileToDoc(file, {}, loading); + DocUtils.uploadFileToDoc(file, {}, loading).then(d => { + if (d && d?.type === DocumentType.IMG) { + const imgTemplate = DocListCast(DocCast(Doc.UserDoc().template_user)?.data).find(d => d.title === 'shower'); + if (imgTemplate) { + d.layout_fieldKey = 'layout_shower'; + d.layout_shower = imgTemplate; + } + } + }); + return loading; }) )) diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx index c06391f35..6442385c0 100644 --- a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx +++ b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx @@ -359,7 +359,7 @@ export class CollectionSchemaView extends CollectionSubView() { @action addNewKey = (key: string, defaultVal: FieldType | undefined) => { this.childDocs.forEach(doc => { - doc[DocData][key] = defaultVal; + if (doc[DocData][key] === undefined) doc[DocData][key] = defaultVal; }); }; diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index bdb97d7bb..f9c21451e 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -740,7 +740,7 @@ export class DocumentViewInternal extends DocComponent (this.widgetDecorations ? this.widgetOverlay : null); viewingAiEditor = () => (this._props.showAIEditor && this._componentView?.componentAIView?.() !== undefined ? this.aiEditor : null); - _contentsRef = React.createRef(); + @observable _contentsRef: DocumentContentsView | undefined = undefined; @computed get viewBoxContents() { TraceMobx(); const isInk = this.layoutDoc._layout_isSvg && !this._props.LayoutTemplateString; @@ -756,7 +756,7 @@ export class DocumentViewInternal extends DocComponent (this._contentsRef = r))} layoutFieldKey={StrCast(this.Document.layout_fieldKey, 'layout')} pointerEvents={this.contentPointerEvents} setContentViewBox={this.setContentView} @@ -1168,7 +1168,7 @@ export class DocumentView extends DocComponent() { * @returns boolean whether sub-component Doc is in synch with the layoutDoc that this view thinks its rendering */ IsInvalid = (renderDoc?: Doc): boolean => { - const docContents = this._docViewInternal?._contentsRef.current; + const docContents = this._docViewInternal?._contentsRef; return !( (!renderDoc || (docContents?.layoutDoc === renderDoc && // diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx index b648f2adb..f7ad5c7e2 100644 --- a/src/client/views/nodes/ImageBox.tsx +++ b/src/client/views/nodes/ImageBox.tsx @@ -152,7 +152,8 @@ export class ImageBox extends ViewBoxAnnotatableComponent() { this._disposers.path = reaction( () => ({ nativeSize: this.nativeSize, width: NumCast(this.layoutDoc._width), height: this.layoutDoc._height }), ({ nativeSize, width, height }) => { - if (!this.layoutDoc._layout_nativeDimEditable || !height) { + if (!this.layoutDoc._layout_nativeDimEditable || !height || this.layoutDoc.layout_resetNativeDim) { + this.layoutDoc.layout_resetNativeDim = undefined; // template images need to reset their dimensions when they are rendered with content. afterwards, remove this flag. this.layoutDoc._height = (width * nativeSize.nativeHeight) / nativeSize.nativeWidth; } }, @@ -1048,7 +1049,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent() { alert('Error uploading files - possibly due to unsupported file types'); } else { this.dataDoc[this.fieldKey] = new ImageField(result.accessPaths.agnostic.client); - !(result instanceof Error) && DocUtils.assignImageInfo(result, this.dataDoc); + !(result instanceof Error) && DocUtils.assignUploadInfo(result, this.dataDoc); } disposer(); } else { diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index 824ac97da..57720baae 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -1224,13 +1224,14 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent { if (this.EditorView && this.ApplyingChange !== this.fieldKey) { if (incomingValue?.data) { - const updatedState = JSON.parse(incomingValue.data.Data); + const updatedState = JSON.parse(incomingValue.data.Data.replace(/\n/g, '')); if (JSON.stringify(this.EditorView.state.toJSON()) !== JSON.stringify(updatedState)) { this.EditorView.updateState(EditorState.fromJSON(this.config, updatedState)); this.tryUpdateScrollHeight(); } } else if (this.EditorView.state.doc.textContent !== (incomingValue?.str ?? '')) { selectAll(this.EditorView.state, tx => this.EditorView?.dispatch(tx.insertText(incomingValue?.str ?? ''))); + this.tryUpdateScrollHeight(); } } }, diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts index ba94f0504..990b6606f 100644 --- a/src/fields/Doc.ts +++ b/src/fields/Doc.ts @@ -25,6 +25,7 @@ import { ComputedField, ScriptField } from './ScriptField'; import { BoolCast, Cast, DocCast, FieldValue, ImageCastWithSuffix, NumCast, RTFCast, StrCast, ToConstructor, toList } from './Types'; import { containedFieldChangedHandler, deleteProperty, GetEffectiveAcl, getField, getter, makeEditable, makeReadOnly, setter, SharingPermissions } from './util'; import { gptImageLabel } from '../client/apis/gpt/GPT'; +import { DateField } from './DateField'; export let ObjGetRefField: (id: string, force?: boolean) => Promise; export let ObjGetRefFields: (ids: string[]) => Promise>; @@ -1109,6 +1110,10 @@ export namespace Doc { Cast(templateFieldValue, listSpec(Doc), [])?.map(d => d instanceof Doc && MakeMetadataFieldTemplate(d, templateDoc)); Doc.GetProto(templateField)[metadataFieldKey] = ObjectField.MakeCopy(templateFieldValue); } + if (templateField.type === DocumentType.IMG) { + // bcz: should be a better way .. but, if the image is a template, then we can't expect to know the aspect ratio. When the image is replaced by data and rendered, we want to recomputed the native dimensions. + templateField[DocData].layout_resetNativeDim = true; + } // get the layout string that the template uses to specify its layout const templateFieldLayoutString = StrCast(Doc.LayoutField(templateField[DocLayout])); @@ -1184,13 +1189,15 @@ export namespace Doc { return Doc.NativeWidth(doc, dataDoc, useDim) / (Doc.NativeHeight(doc, dataDoc, useDim) || 1); } export function NativeWidth(doc?: Doc, dataDoc?: Doc, useWidth?: boolean) { - return !doc ? 0 : NumCast(doc._nativeWidth, NumCast((dataDoc || doc)[Doc.LayoutDataKey(doc) + '_nativeWidth'], useWidth ? NumCast(doc._width) : 0)); + // if this is a field template, then don't use the doc's nativeWidth/height + return !doc ? 0 : NumCast(doc.isTemplateForField ? undefined : doc._nativeWidth, NumCast((dataDoc || doc)[Doc.LayoutDataKey(doc) + '_nativeWidth'], !doc.isTemplateForField && useWidth ? NumCast(doc._width) : 0)); } export function NativeHeight(doc?: Doc, dataDoc?: Doc, useHeight?: boolean) { if (!doc) return 0; const nheight = (Doc.NativeWidth(doc, dataDoc, useHeight) / NumCast(doc._width)) * NumCast(doc._height); // divide before multiply to avoid floating point errrorin case nativewidth = width const dheight = NumCast((dataDoc || doc)[Doc.LayoutDataKey(doc) + '_nativeHeight'], useHeight ? NumCast(doc._height) : 0); - return NumCast(doc._nativeHeight, nheight || dheight); + // if this is a field template, then don't use the doc's nativeWidth/height + return NumCast(doc.isTemplateForField ? undefined : doc._nativeHeight, nheight || dheight); } export function OutpaintingWidth(doc?: Doc, dataDoc?: Doc, useWidth?: boolean) { @@ -1504,7 +1511,13 @@ export namespace Doc { case DocumentType.RTF: return RTFCast(tdoc[Doc.LayoutDataKey(tdoc)])?.Text ?? StrCast(tdoc[Doc.LayoutDataKey(tdoc)]); default: return StrCast(tdoc.title).startsWith("Untitled") ? "" : StrCast(tdoc.title); }}); // prettier-ignore - return docText(doc).then(text => (doc['$' + Doc.LayoutDataKey(doc) + '_description'] = text)); + return docText(doc).then( + action(text => { + // set the time when the date changes. This also allows a live textbox view to react to the update, otherwise, it wouldn't take effect until the next time the view is rerendered. + doc['$' + Doc.LayoutDataKey(doc) + '_description_modificationDate'] = new DateField(); + return (doc['$' + Doc.LayoutDataKey(doc) + '_description'] = text); + }) + ); } // prettier-ignore @@ -1820,3 +1833,7 @@ ScriptingGlobals.add(function setDocRangeFilter(container: Doc, key: string, ran ScriptingGlobals.add(function toJavascriptString(str: string) { return Field.toJavascriptString(str as FieldType); }); +// eslint-disable-next-line prefer-arrow-callback +ScriptingGlobals.add(function getDescription(doc: Doc) { + return Doc.getDescription(doc); +}); diff --git a/src/server/SharedMediaTypes.ts b/src/server/SharedMediaTypes.ts index 9aa4b120f..43a9ce963 100644 --- a/src/server/SharedMediaTypes.ts +++ b/src/server/SharedMediaTypes.ts @@ -19,6 +19,9 @@ export enum AudioAnnoState { } export namespace Upload { + export function isTextInformation(uploadResponse: Upload.FileInformation): uploadResponse is Upload.ImageInformation { + return 'rawText' in uploadResponse; + } export function isImageInformation(uploadResponse: Upload.FileInformation): uploadResponse is Upload.ImageInformation { return 'nativeWidth' in uploadResponse; } -- cgit v1.2.3-70-g09d2