diff options
author | bobzel <zzzman@gmail.com> | 2025-05-07 11:21:53 -0400 |
---|---|---|
committer | bobzel <zzzman@gmail.com> | 2025-05-07 11:21:53 -0400 |
commit | d0cbb43ec3fa19f76a570f5e0038bfc72c9f37b9 (patch) | |
tree | 33e6351bfbbdcd8019579a90e8636725ed1bc1c5 /src | |
parent | 74836a59c4af0a3c240c5e3435359935282ce049 (diff) |
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
Diffstat (limited to 'src')
-rw-r--r-- | src/client/views/ViewBoxInterface.ts | 1 | ||||
-rw-r--r-- | src/client/views/collections/collectionGrid/CollectionGridView.tsx | 2 | ||||
-rw-r--r-- | src/client/views/nodes/CollectionFreeFormDocumentView.tsx | 14 | ||||
-rw-r--r-- | src/client/views/nodes/DocumentView.scss | 3 | ||||
-rw-r--r-- | src/client/views/nodes/DocumentView.tsx | 59 | ||||
-rw-r--r-- | src/client/views/nodes/IconTagBox.tsx | 24 | ||||
-rw-r--r-- | src/client/views/nodes/ImageBox.scss | 2 | ||||
-rw-r--r-- | src/client/views/nodes/ImageBox.tsx | 96 | ||||
-rw-r--r-- | src/client/views/nodes/imageEditor/ImageEditor.tsx | 5 | ||||
-rw-r--r-- | src/client/views/nodes/imageEditor/imageEditorUtils/ImageHandler.ts | 6 | ||||
-rw-r--r-- | src/client/views/pdf/GPTPopup/GPTPopup.scss | 7 | ||||
-rw-r--r-- | src/client/views/pdf/GPTPopup/GPTPopup.tsx | 149 | ||||
-rw-r--r-- | src/client/views/smartdraw/SmartDrawHandler.tsx | 2 |
13 files changed, 168 insertions, 202 deletions
diff --git a/src/client/views/ViewBoxInterface.ts b/src/client/views/ViewBoxInterface.ts index 0ddac8914..d8dab8e89 100644 --- a/src/client/views/ViewBoxInterface.ts +++ b/src/client/views/ViewBoxInterface.ts @@ -64,5 +64,4 @@ export abstract class ViewBoxInterface<P> extends ObservableReactComponent<React dontRegisterView?: () => boolean; // KeyValueBox's don't want to register their views isUnstyledView?: () => boolean; // SchemaView and KeyValue are unstyled -- not titles, no opacity, no animations componentAIView?: () => JSX.Element; - componentAIViewHistory?: () => JSX.Element; } diff --git a/src/client/views/collections/collectionGrid/CollectionGridView.tsx b/src/client/views/collections/collectionGrid/CollectionGridView.tsx index 3320eacb6..b837b3a86 100644 --- a/src/client/views/collections/collectionGrid/CollectionGridView.tsx +++ b/src/client/views/collections/collectionGrid/CollectionGridView.tsx @@ -206,7 +206,7 @@ export class CollectionGridView extends CollectionSubView() { setContentViewBox={emptyFunction} whenChildContentsActiveChanged={this._props.whenChildContentsActiveChanged} onClickScript={this.onChildClickHandler} - dontCenter={StrCast(this.layoutDoc.layout_dontCenter) as 'x' | 'y' | 'xy'} + dontCenter={StrCast(this.layoutDoc.layout_dontCenter, StrCast(childLayout.layout_dontCenter)) as 'x' | 'y' | 'xy'} showTags={BoolCast(this.layoutDoc.showChildTags) || BoolCast(this.Document._layout_showTags)} /> ); diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx index 940c4cb99..3805b0dca 100644 --- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx +++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx @@ -148,7 +148,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF public static getValues(doc: Doc, time: number, fillIn: boolean = true) { return CollectionFreeFormDocumentView.animFields.reduce( (p, val) => { - p[val.key] = Cast(doc[`${val.key}_indexed`], listSpec('number'), fillIn ? [NumCast(doc[val.key], val.val)] : []).reduce( + p[val.key] = Cast(doc[`${val.key}_indexed`], listSpec('number'), fillIn ? [NumCast(doc[val.key], val.val)] : [])!.reduce( (prev, v, i) => ((i <= Math.round(time) && v !== undefined) || prev === undefined ? v : prev), undefined as unknown as number ); @@ -161,7 +161,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF public static getStringValues(doc: Doc, time: number) { return CollectionFreeFormDocumentView.animStringFields.reduce( (p, val) => { - p[val] = Cast(doc[`${val}_indexed`], listSpec('string'), [StrCast(doc[val])]).reduce((prev, v, i) => ((i <= Math.round(time) && v !== undefined) || prev === undefined ? v : prev), undefined as unknown as string); + p[val] = Cast(doc[`${val}_indexed`], listSpec('string'), [StrCast(doc[val])])!.reduce((prev, v, i) => ((i <= Math.round(time) && v !== undefined) || prev === undefined ? v : prev), undefined as unknown as string); return p; }, {} as { [val: string]: Opt<string> } @@ -171,7 +171,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF public static setStringValues(time: number, d: Doc, vals: { [val: string]: Opt<string> }) { const timecode = Math.round(time); Object.keys(vals).forEach(val => { - const findexed = Cast(d[`${val}_indexed`], listSpec('string'), []).slice(); + const findexed = Cast(d[`${val}_indexed`], listSpec('string'), [])!.slice(); findexed[timecode] = vals[val] || ''; d[`${val}_indexed`] = new List<string>(findexed); }); @@ -180,7 +180,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF public static setValues(time: number, d: Doc, vals: { [val: string]: Opt<number> }) { const timecode = Math.round(time); Object.keys(vals).forEach(val => { - const findexed = Cast(d[`${val}_indexed`], listSpec('number'), []).slice(); + const findexed = Cast(d[`${val}_indexed`], listSpec('number'), [])!.slice(); findexed[timecode] = vals[val] as unknown as number; d[`${val}_indexed`] = new List<number>(findexed); }); @@ -204,15 +204,15 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF docs.forEach(doc => { this.animFields.forEach(val => { const findexed = Cast(doc[`${val.key}_indexed`], listSpec('number'), null); - findexed?.length <= timecode + 1 && findexed.push(undefined as unknown as number); + (findexed?.length ?? 0) <= timecode + 1 && findexed?.push(undefined as unknown as number); }); this.animStringFields.forEach(val => { const findexed = Cast(doc[`${val}_indexed`], listSpec('string'), null); - findexed?.length <= timecode + 1 && findexed.push(undefined as unknown as string); + (findexed?.length ?? 0) <= timecode + 1 && findexed?.push(undefined as unknown as string); }); this.animDataFields(doc).forEach(val => { const findexed = Cast(doc[`${val}_indexed`], listSpec(InkField), null); - findexed?.length <= timecode + 1 && findexed.push(undefined as unknown as InkField); + (findexed?.length ?? 0) <= timecode + 1 && findexed?.push(undefined as unknown as InkField); }); }); return newTimer; diff --git a/src/client/views/nodes/DocumentView.scss b/src/client/views/nodes/DocumentView.scss index 5ac66f2cd..c4351a200 100644 --- a/src/client/views/nodes/DocumentView.scss +++ b/src/client/views/nodes/DocumentView.scss @@ -119,6 +119,7 @@ display: flex; justify-content: center; align-items: center; + margin: auto; position: relative; // allows contents to be positioned relative/below title > .formattedTextBox { position: absolute; // position a child text box @@ -302,6 +303,6 @@ background: transparent; .documentView-editorView-resizer { - height: 5px; + height: 2px; } } diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index d756f3226..bdb97d7bb 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -648,11 +648,8 @@ export class DocumentViewInternal extends DocComponent<DocumentViewProps & Field rootSelected = () => this._rootSelected; panelHeight = () => this._props.PanelHeight() - this.headerMargin - 2 * NumCast(this.Document.borderWidth); - screenToLocalContent = () => - this._props - .ScreenToLocalTransform() - .translate(-NumCast(this.Document.borderWidth), -this.headerMargin - NumCast(this.Document.borderWidth)) - .scale(this.viewingAiEditor() ? (this._props.PanelHeight() || 1) / this.aiContentsHeight() : 1); + aiShift = () => (!this.viewingAiEditor() ? 0 : (this._props.PanelWidth() - this.aiContentsWidth()) / 2); + aiScale = () => (this.viewingAiEditor() ? (this._props.PanelHeight() || 1) / this.aiContentsHeight() : 1); onClickFunc = () => (this.disableClickScriptFunc ? undefined : this.onClickHdlr); setHeight = (height: number) => { !this._props.suppressSetHeight && (this.layoutDoc._height = Math.min(NumCast(this.layoutDoc._maxHeight, Number.MAX_SAFE_INTEGER), height + 2 * NumCast(this.Document.borderWidth))); } // prettier-ignore setContentView = action((view: ViewBoxInterface<FieldViewProps>) => (this._componentView = view)); @@ -678,7 +675,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewProps & Field return this._props.styleProvider?.(doc, props, property); }; - @observable _aiWinHeight = 88; + @observable _aiWinHeight = 32; TagsBtnHeight = 22; @computed get currentScale() { @@ -703,33 +700,21 @@ export class DocumentViewInternal extends DocComponent<DocumentViewProps & Field @computed get uiBtnScaling() { return Math.max(this.maxWidgetSize / this.TagsBtnHeight, 1) * Math.min(1, this.viewScaling); } // prettier-ignore aiContentsWidth = () => (this.aiContentsHeight() * (this._props.NativeWidth?.() || 1)) / (this._props.NativeHeight?.() || 1); - aiContentsHeight = () => Math.max(10, this._props.PanelHeight() - this._aiWinHeight * this.uiBtnScaling); + aiContentsHeight = () => Math.max(10, this._props.PanelHeight() - (this._aiWinHeight + (this.tagsOverlayFunc() ? 22 : 0)) * this.uiBtnScaling); @computed get aiEditor() { return ( - <> - <div - className="documentView-editorView-history" - ref={r => this.historyRef(this._oldAiWheel, (this._oldAiWheel = r))} - style={{ - transform: `scale(${this.uiBtnScaling})`, - height: this.aiContentsHeight() / this.uiBtnScaling, - width: ((this._props.PanelWidth() - this.aiContentsWidth()) * 0.95) / this.uiBtnScaling, - }}> - {this._componentView?.componentAIViewHistory?.() ?? null} - </div> - <div - className="documentView-editorView" - style={{ - background: SnappingManager.userVariantColor, - width: `${100 / this.uiBtnScaling}%`, // - transform: `scale(${this.uiBtnScaling})`, - }} - ref={r => this.historyRef(this._oldHistoryWheel, (this._oldHistoryWheel = r))}> - <div className="documentView-editorView-resizer" /> - {this._componentView?.componentAIView?.() ?? null} - {this._props.DocumentView?.() ? <TagsView background={this.backgroundBoxColor} Views={[this._props.DocumentView?.()]} /> : null} - </div> - </> + <div + className="documentView-editorView" + style={{ + background: SnappingManager.userVariantColor, + width: `${100 / this.uiBtnScaling}%`, // + transform: `scale(${this.uiBtnScaling})`, + }} + ref={r => this.historyRef(this._oldHistoryWheel, (this._oldHistoryWheel = r))}> + <div className="documentView-editorView-resizer" /> + {this._componentView?.componentAIView?.() ?? null} + {this._props.DocumentView?.() ? <TagsView background={this.backgroundBoxColor} Views={[this._props.DocumentView?.()]} /> : null} + </div> ); } @computed get tagsOverlay() { @@ -780,7 +765,6 @@ export class DocumentViewInternal extends DocComponent<DocumentViewProps & Field PanelHeight={this.viewingAiEditor() ? this.aiContentsHeight : this.panelHeight} setHeight={this.setHeight} isContentActive={this.isContentActive} - ScreenToLocalTransform={this.screenToLocalContent} rootSelected={this.rootSelected} onClickScript={this.onClickFunc} setTitleFocus={this.setTitleFocus} @@ -1291,7 +1275,7 @@ export class DocumentView extends DocComponent<DocumentViewProps>() { if (this.ComponentView?.screenBounds?.()) { return this.ComponentView.screenBounds(); } - const xf = this.screenToContentsTransform().scale(this.nativeScaling).inverse(); + const xf = this.screenToContentBoundsTransform().inverse(); const [[left, top], [right, bottom]] = [xf.transformPoint(0, 0), xf.transformPoint(this.panelWidth, this.panelHeight)]; // transition is returned so that the bounds will 'update' at the end of an animated transition. This is needed by xAnchor in LinkBox @@ -1498,17 +1482,22 @@ export class DocumentView extends DocComponent<DocumentViewProps>() { isHovering = () => this._isHovering; selfView = () => this; /** - * @returns Transform to the document view (in the coordinate system of whatever contains the DocumentView) + * @returns Transform to the document view's available panel space (in the coordinate system of whatever contains the DocumentView) */ screenToViewTransform = () => this._props.ScreenToLocalTransform(); /** + * @returns Transform to the document view after centering in available panel space(in the coordinate system of whatever contains the DocumentView) + */ + private screenToContentBoundsTransform = () => this.screenToViewTransform().translate(-this.centeringX, -this.centeringY); + /** * @returns Transform to the coordinate system of the contents of the document view (includes native dimension scaling and centering) */ screenToContentsTransform = () => this._props .ScreenToLocalTransform() .translate(-this.centeringX, -this.centeringY) - .scale(1 / this.nativeScaling); + .translate(-(this._docViewInternal?.aiShift() ?? 0), 0) + .scale((this._docViewInternal?.aiScale() ?? 1) / this.nativeScaling); htmlOverlay = () => { const effect = StrCast(this._htmlOverlayEffect?.presentation_effect, StrCast(this._htmlOverlayEffect?.followLinkAnimEffect)); diff --git a/src/client/views/nodes/IconTagBox.tsx b/src/client/views/nodes/IconTagBox.tsx index 0bbd6a0d3..d04ec3a10 100644 --- a/src/client/views/nodes/IconTagBox.tsx +++ b/src/client/views/nodes/IconTagBox.tsx @@ -58,16 +58,20 @@ export class IconTagBox extends ObservableReactComponent<IconTagProps> { const tag = StrCast(key.toolType); const color = dv._props.styleProvider?.(dv.layoutDoc, dv.ComponentView?._props, StyleProp.FontColor) as string; return ( - <Toggle - tooltip={`Click to add/remove the tag ${tag}`} - toggleStatus={TagItem.docHasTag(dv.Document, tag)} - toggleType={ToggleType.BUTTON} - icon={<FontAwesomeIcon className={`fontIconBox-icon-${ToggleType.BUTTON}`} icon={icon} color={color} />} - size={Size.XSMALL} - type={Type.PRIM} - onClick={() => this.setIconTag(tag, !TagItem.docHasTag(this.View.Document, tag))} - color={color} - /> + <div> + {' '} + {/* tooltips require the wrapped item to be an element ref */} + <Toggle + tooltip={`Click to add/remove the tag ${tag}`} + toggleStatus={TagItem.docHasTag(dv.Document, tag)} + toggleType={ToggleType.BUTTON} + icon={<FontAwesomeIcon className={`fontIconBox-icon-${ToggleType.BUTTON}`} icon={icon} color={color} />} + size={Size.XSMALL} + type={Type.PRIM} + onClick={() => this.setIconTag(tag, !TagItem.docHasTag(this.View.Document, tag))} + color={color} + /> + </div> ); }; diff --git a/src/client/views/nodes/ImageBox.scss b/src/client/views/nodes/ImageBox.scss index 9f7a5d03f..5a6292fab 100644 --- a/src/client/views/nodes/ImageBox.scss +++ b/src/client/views/nodes/ImageBox.scss @@ -236,7 +236,7 @@ .imageBox-aiView-input { overflow: hidden; text-overflow: ellipsis; - max-width: 65%; + max-width: 80%; width: 100%; color: black; } 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<FieldViewProps>() { @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<FieldViewProps>() { @observable _filterFunc: ((doc: Doc) => boolean) | undefined = undefined; @observable private _fireflyRefStrength = 0; - componentAIViewHistory = () => ( - <div className="imageBox-aiView-history"> - <Button text="Clear History" type={Type.SEC} size={Size.XSMALL} /> - {this._prevImgs.map(img => ( - <div key={img.pathname}> - <img - className="imageBox-aiView-img" - src={ClientUtils.prepend(img.pathname.replace(extname(img.pathname), '_s' + extname(img.pathname)))} - onClick={() => { - this.dataDoc[this.fieldKey] = new ImageField(img.pathname); - this.dataDoc.ai_firefly_prompt = img.prompt; - this.dataDoc.ai_firefly_seed = img.seed; - }} - /> - <span>{img.prompt}</span> - </div> - ))} - </div> - ); - componentAIView = () => { - const field = this.dataDoc[this.fieldKey] instanceof ImageField ? Cast(this.dataDoc[this.fieldKey], ImageField, null) : new ImageField(String(this.dataDoc[this.fieldKey])); return ( <div className="imageBox-aiView"> <div className="imageBox-aiView-regenerate"> - <span className="imageBox-aiView-firefly" style={{ color: SnappingManager.userColor }}> - Firefly: - </span> <input style={{ color: SnappingManager.userColor, background: SnappingManager.userBackgroundColor }} className="imageBox-aiView-input" @@ -886,57 +860,39 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { <Button text="Create" type={Type.TERT} + size={Size.XSMALL} color={SnappingManager.userColor} background={SnappingManager.userBackgroundColor} // style={{ alignSelf: 'flex-end' }} icon={this._regenerateLoading ? <ReactLoading type="spin" color={SettingsManager.userVariantColor} width={16} height={20} /> : <AiOutlineSend />} iconPlacement="right" - onClick={action(async () => { + onClick={action(() => { this._regenerateLoading = true; - if (this._fireflyRefStrength) { - DrawingFillHandler.drawingToImage(this.Document, this._fireflyRefStrength, this._regenInput || StrCast(this.Document.title))?.then(action(() => (this._regenerateLoading = false))); - } else { - SmartDrawHandler.Instance.regenerate([this.Document], undefined, undefined, this._regenInput || StrCast(this.Document.title), true).then( - action(newImgs => { - const firstImg = newImgs[0]; - if (isFireflyImageData(firstImg)) { - const url = firstImg.pathname; - 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._prevImgs.unshift({ prompt: firstImg.prompt, seed: firstImg.seed, pathname: url }); - this.dataDoc.ai_firefly_history = JSON.stringify(this._prevImgs); - this.dataDoc.ai_firefly_prompt = firstImg.prompt; - this.dataDoc[this.fieldKey] = imgField; - this._regenerateLoading = false; - this._regenInput = ''; - } - }) - ); - } + DrawingFillHandler.drawingToImage(this.Document, this._fireflyRefStrength, this._regenInput || StrCast(this.Document.title))?.then(action(() => (this._regenerateLoading = false))); })} /> </div> - </div> - <div className="imageBox-aiView-strength"> - <span className="imageBox-aiView-similarity" style={{ color: SnappingManager.userColor }}> - Similarity - </span> - <Slider - className="imageBox-aiView-slider" - sx={{ - '& .MuiSlider-track': { color: SettingsManager.userColor }, - '& .MuiSlider-rail': { color: SettingsManager.userBackgroundColor }, - '& .MuiSlider-thumb': { color: SettingsManager.userColor, '&.Mui-focusVisible, &:hover, &.Mui-active': { boxShadow: `0px 0px 0px 8px${SettingsManager.userColor.slice(0, 7)}10` } }, - }} - min={0} - max={100} - step={1} - size="small" - value={this._fireflyRefStrength} - onChange={action((e, val) => this._canInteract && (this._fireflyRefStrength = val as number))} - valueLabelDisplay="auto" - /> + <div> + <NumberDropdown + color={SnappingManager.userColor} + background={SnappingManager.userBackgroundColor} + numberDropdownType="slider" + showPlusMinus={false} + formLabel="similarity" + tooltip="structure similarity of created images to current image" + type={Type.PRIM} + width={75} + min={0} + max={100} + number={this._fireflyRefStrength} + size={Size.XXSMALL} + setNumber={undoable( + action(val => this._canInteract && (this._fireflyRefStrength = val as number)), + `${this.Document.title} button set from list` + )} + fillWidth + /> + </div> </div> </div> ); diff --git a/src/client/views/nodes/imageEditor/ImageEditor.tsx b/src/client/views/nodes/imageEditor/ImageEditor.tsx index 85bd95d15..198b8e713 100644 --- a/src/client/views/nodes/imageEditor/ImageEditor.tsx +++ b/src/client/views/nodes/imageEditor/ImageEditor.tsx @@ -281,11 +281,14 @@ const ImageEditor = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addDoc try { const canvasOriginalImg = ImageUtility.getCanvasImg(img); if (!canvasOriginalImg) return; - const canvasMask = ImageUtility.getCanvasMask(canvas, canvasOriginalImg); + const canvasMask = ImageUtility.getCanvasMask(canvas, canvas); if (!canvasMask) return; const maskBlob = await ImageUtility.canvasToBlob(canvasMask); const imgBlob = await ImageUtility.canvasToBlob(canvasOriginalImg); const res = await ImageUtility.getEdit(imgBlob, maskBlob, input || 'Fill in the image in the same style', 2); + if ((res as any).status == 'error') { + alert((res as any).message); + } // create first image if (!newCollectionRef.current) { diff --git a/src/client/views/nodes/imageEditor/imageEditorUtils/ImageHandler.ts b/src/client/views/nodes/imageEditor/imageEditorUtils/ImageHandler.ts index 1c6a38a24..d6093c6eb 100644 --- a/src/client/views/nodes/imageEditor/imageEditorUtils/ImageHandler.ts +++ b/src/client/views/nodes/imageEditor/imageEditorUtils/ImageHandler.ts @@ -75,7 +75,7 @@ export class ImageUtility { fd.append('mask', maskBlob, 'mask.png'); fd.append('prompt', prompt); fd.append('size', '1024x1024'); - fd.append('n', n ? JSON.stringify(n) : '1'); + fd.append('n', n ? n + '' : '1'); fd.append('response_format', 'b64_json'); try { @@ -268,14 +268,14 @@ export class ImageUtility { ctx.drawImage(img, xOffset, 0, width, height); // draw reflected image padding - this.drawHorizontalReflection(ctx, canvas, xOffset); + // this.drawHorizontalReflection(ctx, canvas, xOffset); } else { // vertical padding, y offset const yOffset = Math.floor((canvasSize - height) / 2); ctx.drawImage(img, 0, yOffset, width, height); // draw reflected image padding - this.drawVerticalReflection(ctx, canvas, yOffset); + // this.drawVerticalReflection(ctx, canvas, yOffset); } return canvas; }; diff --git a/src/client/views/pdf/GPTPopup/GPTPopup.scss b/src/client/views/pdf/GPTPopup/GPTPopup.scss index bb43291ee..f6fa45221 100644 --- a/src/client/views/pdf/GPTPopup/GPTPopup.scss +++ b/src/client/views/pdf/GPTPopup/GPTPopup.scss @@ -7,6 +7,11 @@ $highlightedText: #82e0ff; $inputHeight: 60px; $headingHeight: 32px; +.gptPopup-sortBox { + display: block; + max-height: calc(100% - 45px); // leave room for input +} + .gptPopup-summary-box { position: fixed; padding-left: 10px; @@ -87,6 +92,7 @@ $headingHeight: 32px; } .btns-wrapper-gpt { height: 100%; + width: 100%; display: flex; justify-content: center; align-items: center; @@ -97,7 +103,6 @@ $headingHeight: 32px; flex-direction: column; width: 100%; height: 100%; - overflow-y: auto; padding-right: 5px; } diff --git a/src/client/views/pdf/GPTPopup/GPTPopup.tsx b/src/client/views/pdf/GPTPopup/GPTPopup.tsx index c45d8e052..9fbae5c90 100644 --- a/src/client/views/pdf/GPTPopup/GPTPopup.tsx +++ b/src/client/views/pdf/GPTPopup/GPTPopup.tsx @@ -93,7 +93,7 @@ export class GPTPopup extends ObservableReactComponent<object> { this.onGptResponse = (sortResult: string, questionType: GPTDocCommand, args?: string) => this.processGptResponse(selDoc, this._textToDocMap, sortResult, questionType, args); this.onQuizRandom = () => this.randomlyChooseDoc(selDoc.Document, hasChildDocs()); this._documentDescriptions = Promise.all(hasChildDocs().map(doc => - Doc.getDescription(doc).then(text => this._textToDocMap.set(text.trim(), doc) && `${DescriptionSeperator}${text}${DescriptionSeperator}`) + Doc.getDescription(doc).then(text => this._textToDocMap.set(text.replace(/\n/g, ' ').trim(), doc) && `${DescriptionSeperator}${text}${DescriptionSeperator}`) )).then(docDescriptions => docDescriptions.join()); // prettier-ignore } }, @@ -406,73 +406,80 @@ export class GPTPopup extends ObservableReactComponent<object> { scrollToBottom = () => setTimeout(() => this._messagesEndRef.current?.scrollIntoView({ behavior: 'smooth', block: 'end' }), 50); gptMenu = () => ( - <div className="btns-wrapper-gpt"> - <Button - tooltip="Ask Firefly to create images" - text="Ask Firefly" - onClick={() => this.setMode(GPTPopupMode.FIREFLY)} - color={SettingsManager.userColor} - background={SettingsManager.userVariantColor} - type={Type.TERT} - style={{ - width: '100%', - height: '40%', - textAlign: 'center', - color: '#ffffff', - fontSize: '16px', - marginBottom: '10px', - }} - /> - <Button - tooltip="Ask GPT to sort, tag, define, or filter your Docs!" - text="Ask GPT" - onClick={() => this.setMode(GPTPopupMode.USER_PROMPT)} - color={SettingsManager.userColor} - background={SettingsManager.userVariantColor} - type={Type.TERT} - style={{ - width: '100%', - height: '40%', - textAlign: 'center', - color: '#ffffff', - fontSize: '16px', - marginBottom: '10px', - }} - /> - <Button - tooltip="Test your knowledge by verifying answers with ChatGPT" - text="Take Quiz" - onClick={() => { - this._conversationArray = ['Define the selected card!']; - this.setMode(GPTPopupMode.QUIZ_RESPONSE); - this.onQuizRandom?.(); - }} - color={SettingsManager.userColor} - background={SettingsManager.userVariantColor} - type={Type.TERT} - style={{ - width: '100%', - height: '40%', - textAlign: 'center', - color: '#ffffff', - fontSize: '16px', - }} - /> + <div style={{ display: 'flex', maxHeight: 'calc(100% - 32px)', overflow: 'auto' }}> + <div className="btns-wrapper-gpt"> + <Button + tooltip="Ask Firefly to create images" + text="Ask Firefly" + onClick={() => this.setMode(GPTPopupMode.FIREFLY)} + color={SettingsManager.userColor} + background={SettingsManager.userVariantColor} + type={Type.TERT} + style={{ + width: '100%', + height: '40%', + textAlign: 'center', + color: '#ffffff', + fontSize: '16px', + marginBottom: '10px', + }} + /> + <Button + tooltip="Ask GPT to sort, tag, define, or filter your Docs!" + text="Ask GPT" + onClick={() => this.setMode(GPTPopupMode.USER_PROMPT)} + color={SettingsManager.userColor} + background={SettingsManager.userVariantColor} + type={Type.TERT} + style={{ + width: '100%', + height: '40%', + textAlign: 'center', + color: '#ffffff', + fontSize: '16px', + marginBottom: '10px', + }} + /> + <Button + tooltip="Test your knowledge by verifying answers with ChatGPT" + text="Take Quiz" + onClick={() => { + this._conversationArray = ['Define the selected card!']; + this.setMode(GPTPopupMode.QUIZ_RESPONSE); + this.onQuizRandom?.(); + }} + color={SettingsManager.userColor} + background={SettingsManager.userVariantColor} + type={Type.TERT} + style={{ + width: '100%', + height: '40%', + textAlign: 'center', + color: '#ffffff', + fontSize: '16px', + }} + /> + </div> </div> ); callGpt = action((mode: GPTPopupMode) => { this.setGptProcessing(true); + const reset = action(() => { + this.setGptProcessing(false); + this._userPrompt = ''; + this._quizAnswer = ''; + }); switch (mode) { case GPTPopupMode.FIREFLY: this._fireflyArray.push(this._userPrompt); - return this.generateFireflyImage(this._userPrompt).then(action(() => (this._userPrompt = ''))); + return this.generateFireflyImage(this._userPrompt).then(reset); case GPTPopupMode.USER_PROMPT: this._conversationArray.push(this._userPrompt); - return this.generateUserPromptResponse(this._userPrompt).then(action(() => (this._userPrompt = ''))); + return this.generateUserPromptResponse(this._userPrompt).then(reset); case GPTPopupMode.QUIZ_RESPONSE: this._conversationArray.push(this._quizAnswer); - return this.generateQuizAnswerAnalysis(DocumentView.SelectedDocs().lastElement(), this._quizAnswer).then(action(() => (this._quizAnswer = ''))); + return this.generateQuizAnswerAnalysis(DocumentView.SelectedDocs().lastElement(), this._quizAnswer).then(reset); } }); @@ -490,18 +497,20 @@ export class GPTPopup extends ObservableReactComponent<object> { }; gptUserInput = () => ( - <div className="btns-wrapper-gpt"> - <div className="chat-wrapper"> - <div className="chat-bubbles"> - {(this._mode === GPTPopupMode.FIREFLY ? this._fireflyArray : this._conversationArray).map((message, index) => ( - <div key={index} className={`chat-bubble ${index % 2 === 1 ? 'user-message' : 'chat-message'}`}> - {message} - </div> - ))} - {this._gptProcessing && <div className="chat-bubble chat-message">...</div>} - </div> + <div style={{ display: 'flex', maxHeight: 'calc(100% - 32px)', overflow: 'auto' }}> + <div className="btns-wrapper-gpt"> + <div className="chat-wrapper"> + <div className="chat-bubbles"> + {(this._mode === GPTPopupMode.FIREFLY ? this._fireflyArray : this._conversationArray).map((message, index) => ( + <div key={index} className={`chat-bubble ${index % 2 === 1 ? 'user-message' : 'chat-message'}`}> + {message} + </div> + ))} + {this._gptProcessing && <div className="chat-bubble chat-message">...</div>} + </div> - <div ref={this._messagesEndRef} style={{ height: '100px' }} /> + <div ref={this._messagesEndRef} style={{ height: '40px' }} /> + </div> </div> </div> ); @@ -520,7 +529,7 @@ export class GPTPopup extends ObservableReactComponent<object> { onChange={e => onChange(e.target.value)} onKeyDown={e => this.handleKeyPress(e, this._mode)} type="text" - style={{ color: SnappingManager.userColor }} + style={{ color: 'black' }} placeholder={placeholder} /> <Button // @@ -744,7 +753,7 @@ export class GPTPopup extends ObservableReactComponent<object> { onClick={() => this._collectionContext && Doc.setDocFilter(this._collectionContext, 'tags', GPTPopup.ChatTag, 'remove')} /> {[GPTPopupMode.USER_PROMPT, GPTPopupMode.QUIZ_RESPONSE, GPTPopupMode.FIREFLY].includes(this._mode) && ( - <IconButton color={SettingsManager.userVariantColor} background={SettingsManager.userColor} tooltip="back" icon={<CgCornerUpLeft size="16px" />} onClick={() => (this._mode = GPTPopupMode.GPT_MENU)} /> + <IconButton color={SettingsManager.userVariantColor} background={SettingsManager.userColor} tooltip="back" icon={<CgCornerUpLeft size="16px" />} onClick={action(() => (this._mode = GPTPopupMode.GPT_MENU))} /> )} </> )} @@ -753,7 +762,7 @@ export class GPTPopup extends ObservableReactComponent<object> { render() { return ( - <div className="gptPopup-summary-box" style={{ background: SnappingManager.userColor, color: SnappingManager.userBackgroundColor, display: SnappingManager.ChatVisible ? 'flex' : 'none', overflow: 'auto' }}> + <div className="gptPopup-summary-box" style={{ background: SnappingManager.userColor, color: SnappingManager.userBackgroundColor, display: SnappingManager.ChatVisible ? 'flex' : 'none' }}> {(() => { //prettier-ignore switch (this._mode) { diff --git a/src/client/views/smartdraw/SmartDrawHandler.tsx b/src/client/views/smartdraw/SmartDrawHandler.tsx index 4f0cd3978..a6e221f3e 100644 --- a/src/client/views/smartdraw/SmartDrawHandler.tsx +++ b/src/client/views/smartdraw/SmartDrawHandler.tsx @@ -398,7 +398,7 @@ export class SmartDrawHandler extends ObservableReactComponent<object> { */ colorWithGPT = async (drawing: Doc) => { const img = await DocumentView.GetDocImage(drawing); - const { href } = ImageCast(img).url; + const { href } = ImageCast(img)?.url ?? { href: '' }; const hrefParts = href.split('.'); const hrefComplete = `${hrefParts[0]}_o.${hrefParts[1]}`; try { |