From 3f54517e96ccff233b1560627995024e137dbdfd Mon Sep 17 00:00:00 2001 From: sharkiecodes Date: Tue, 11 Mar 2025 16:27:30 -0400 Subject: Doing outpainting implementation --- src/server/ApiManagers/FireflyManager.ts | 319 +++++++++++++++++++++++++++++++ 1 file changed, 319 insertions(+) (limited to 'src/server/ApiManagers') diff --git a/src/server/ApiManagers/FireflyManager.ts b/src/server/ApiManagers/FireflyManager.ts index e75ede9df..1e0fdb595 100644 --- a/src/server/ApiManagers/FireflyManager.ts +++ b/src/server/ApiManagers/FireflyManager.ts @@ -8,6 +8,7 @@ import { DashUploadUtils } from '../DashUploadUtils'; import { _error, _invalid, _success, Method } from '../RouteManager'; import { Directory, filesDirectory } from '../SocketData'; import ApiManager, { Registration } from './ApiManager'; +import { Upload } from '../SharedMediaTypes'; export default class FireflyManager extends ApiManager { getBearerToken = () => @@ -328,6 +329,324 @@ export default class FireflyManager extends ApiManager { }); }), }); + + register({ + method: Method.POST, + subscription: '/outpaintImageFour', + secureHandler: ({ req, res }) => + + this.uploadImageToDropbox(req.body.imageUrl, req.user as DashUserModel) + .then(uploadUrl => { + if (uploadUrl instanceof Error) { + _invalid(res, uploadUrl.message); + return; + } + return this.getBearerToken() + .then(tokenResponse => tokenResponse?.json()) + .then((tokenData: { access_token: string }) => + fetch('https://firefly-api.adobe.io/v3/images/expand', { + method: 'POST', + headers: { + //'Content-Type': 'application/json', + 'Accept': 'application/json', + 'x-api-key': process.env._CLIENT_FIREFLY_CLIENT_ID ?? '', + 'Authorization': `Bearer ${tokenData.access_token}`, + }, + body: JSON.stringify({ + image: { + source: { url: uploadUrl }, + }, + size: { + width: req.body.newDimensions.width, + height: req.body.newDimensions.height, + }, + prompt: req.body.prompt ?? '', + numVariations: 1, + placement: { + inset: { + left: 0, + top: 0, + right: req.body.newDimensions.width - req.body.originalDimensions.width, + bottom: req.body.newDimensions.height - req.body.originalDimensions.height, + }, + alignment: { + horizontal: 'center', + vertical: 'center', + }, + }, + }), + }) + ) + .then(expandResp => expandResp?.json()) + .then(expandData => { + if (expandData.error_code || !expandData.outputs?.[0]?.image?.url) { + console.error('Firefly validation error:', expandData); + _error(res, expandData.message ?? 'Failed to generate image'); + } else { + DashUploadUtils.UploadImage(expandData.outputs[0].image.url) + .then((info: Upload.ImageInformation | Error) => { + if (info instanceof Error) { + _invalid(res, info.message); + } else { + _success(res, { url: info.accessPaths.agnostic.client }); + } + }) + .catch(uploadErr => { + console.error('DashUpload Error:', uploadErr); + _error(res, 'Failed to upload generated image.'); + }); + } + }) + .catch(err => { + console.error('Firefly request error:', err); + _error(res, 'Failed to expand image'); + }); + }), + }); + register({ + method: Method.POST, + subscription: '/outpaintImageThree', + secureHandler: ({ req, res }) => + new Promise(resolver => { + this.uploadImageToDropbox(req.body.imageUrl, req.user as DashUserModel) + .then(dropboxImgUrl => { + if (dropboxImgUrl instanceof Error) { + _invalid(res, dropboxImgUrl.message); + throw new Error('Error uploading image to dropbox'); + } + return dropboxImgUrl; + }) + .then(dropboxImgUrl => + this.getBearerToken().then(tokenResponse => + tokenResponse?.json().then((tokenData: { access_token: string }) => + fetch('https://firefly-api.adobe.io/v3/images/expand', { + method: 'POST', + headers: [ + ['Content-Type', 'application/json'], + ['Accept', 'application/json'], + ['x-api-key', process.env._CLIENT_FIREFLY_CLIENT_ID ?? ''], + ['Authorization', `Bearer ${tokenData.access_token}`], + ], + body: JSON.stringify({ + image: { + source: { + url: dropboxImgUrl, + }, + }, + numVariations: 1, + prompt: req.body.prompt, + size: { + width: req.body.newDimensions.width, + height: req.body.newDimensions.height, + }, + placement: { + inset: { + left: 0, + top: 0, + right: req.body.newDimensions.width - req.body.originalDimensions.width, + bottom: req.body.newDimensions.height - req.body.originalDimensions.height, + }, + alignment: { + horizontal: 'center', + vertical: 'center', + }, + }, + }), + }) + .then(resp => resp.json()) + .then(expandData => { + if (expandData.error_code || !expandData.outputs?.[0]?.image?.url) { + _invalid(res, expandData.message ?? 'Failed to expand image'); + resolver(); + } else { + DashUploadUtils.UploadImage(expandData.outputs[0].image.url) + .then(info => { + if (info instanceof Error) { + _invalid(res, info.message); + } else { + _success(res, { url: info.accessPaths.agnostic.client }); + } + }) + .then(resolver) + .catch(uploadErr => { + console.error('DashUpload Error:', uploadErr); + _invalid(res, 'Failed to upload generated image.'); + resolver(); + }); + } + }) + .catch(err => { + console.error('Firefly API Error:', err); + _error(res, 'Failed to expand image'); + resolver(); + }) + ) + ) + ) + .catch(err => { + console.error('Dropbox Error:', err); + _error(res, 'Failed to upload image to Dropbox'); + resolver(); + }); + }), + }); + + + register({ + method: Method.POST, + subscription: '/outpaintImageTwo', + secureHandler: async ({ req, res }) => { + try { + const uploadUrl = await this.uploadImageToDropbox(req.body.imageUrl, req.user as DashUserModel); + + if (uploadUrl instanceof Error) { + _invalid(res, uploadUrl.message); + return; + } + + const tokenResponse = await this.getBearerToken(); + const tokenData = await tokenResponse?.json(); + + const body = JSON.stringify({ + image: { source: { url: uploadUrl } }, + prompt: req.body.prompt, + numVariations: 1, + size: { + width: req.body.newDimensions.width, + height: req.body.newDimensions.height, + }, + placement: { + inset: { + left: 0, + top: 0, + right: req.body.newDimensions.width - req.body.originalDimensions.width, + bottom: req.body.newDimensions.height - req.body.originalDimensions.height, + }, + alignment: { + horizontal: 'center', + vertical: 'center', + }, + }, + }); + + console.log('Sending outpainting request:', body); + + const expandResp = await fetch('https://firefly-api.adobe.io/v3/images/expand', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + Accept: 'application/json', + 'x-api-key': process.env._CLIENT_FIREFLY_CLIENT_ID ?? '', + Authorization: `Bearer ${tokenData.access_token}`, + }, + body, + }); + + const expandData = await expandResp.json(); + console.log('Received expandData:', expandData); + + if (expandData.error_code || !expandData.outputs?.[0]?.image?.url) { + console.error('Expand API Error:', expandData); + _error(res, expandData.message ?? 'Failed to generate image'); + return; + } + + const uploadedInfo = await DashUploadUtils.UploadImage(expandData.outputs[0].image.url); + + if (uploadedInfo instanceof Error) { + console.error('Upload Error:', uploadedInfo.message); + _invalid(res, uploadedInfo.message); + return; + } + + console.log('Successfully uploaded image URL:', uploadedInfo.accessPaths.agnostic.client); + _success(res, { url: uploadedInfo.accessPaths.agnostic.client }); + } catch (err) { + console.error('Unexpected error during outpainting:', err); + _error(res, 'Unexpected error during outpainting'); + } + }, + }); + + register({ + + + method: Method.POST, + subscription: '/outpaintImage', + secureHandler: ({ req, res }) => + this.uploadImageToDropbox(req.body.imageUrl, req.user as DashUserModel).then(uploadUrl => + uploadUrl instanceof Error + ? _invalid(res, uploadUrl.message) + : this.getBearerToken() + .then(tokenResponse => tokenResponse?.json()) + .then((tokenData: { access_token: string }) => + fetch('https://firefly-api.adobe.io/v3/images/expand', { + method: 'POST', + headers: [ + ['Content-Type', 'application/json'], + ['Accept', 'application/json'], + ['x-api-key', process.env._CLIENT_FIREFLY_CLIENT_ID ?? ''], + ['Authorization', `Bearer ${tokenData.access_token}`], + ], + body: JSON.stringify({ + image: { source: { url: uploadUrl } }, + numVariations: 1, + size: { + width: req.body.newDimensions.width, + height: req.body.newDimensions.height, + }, + prompt: req.body.prompt, + placement: { + inset: { + left: 0, + top: 0, + right: 0, //req.body.newDimensions.width - req.body.originalDimensions.width, + bottom: 0 //req.body.newDimensions.height - req.body.originalDimensions.height, + }, + alignment: { + horizontal: 'center', + vertical: 'center', + }, + }, + }), + }) + ) + .then(expandResp => expandResp.json()) + .then(expandData => { + if (expandData.error_code || !expandData.outputs?.[0]?.image?.url) { + _error(res, expandData.message ?? "Failed to generate image"); + } else { + DashUploadUtils.UploadImage(expandData.outputs[0].image.url) + .then((info: Upload.ImageInformation | Error) => { + if (info instanceof Error) { + _invalid(res, info.message); + } else { + // THIS IS CRUCIAL: Respond exactly how your front end expects + console.log("Successfully uploaded image. URL:", info.accessPaths.agnostic.client); + _success(res, { url: info.accessPaths.agnostic.client }); + } + }) + .catch(uploadErr => { + + console.error('DashUpload Error:', uploadErr); + _error(res, 'Failed to upload generated image.'); + }); + } + }) + .catch(err => { + console.error(err); + _error(res, 'Failed to expand image'); + }) + ), + }); + + /* register({ + method: Method.POST + subscription: '/queryFireflyOutpaint', + secureHandler: ({req, res}) => + this.outpaintImage() + })*/ + register({ method: Method.POST, subscription: '/queryFireflyImage', -- cgit v1.2.3-70-g09d2 From c5012b13e9df40540368f89f8f97e4035c4ced46 Mon Sep 17 00:00:00 2001 From: sharkiecodes Date: Tue, 11 Mar 2025 17:43:14 -0400 Subject: Added mask --- src/server/ApiManagers/FireflyManager.ts | 3 +++ 1 file changed, 3 insertions(+) (limited to 'src/server/ApiManagers') diff --git a/src/server/ApiManagers/FireflyManager.ts b/src/server/ApiManagers/FireflyManager.ts index 1e0fdb595..61bb65cc4 100644 --- a/src/server/ApiManagers/FireflyManager.ts +++ b/src/server/ApiManagers/FireflyManager.ts @@ -362,6 +362,9 @@ export default class FireflyManager extends ApiManager { }, prompt: req.body.prompt ?? '', numVariations: 1, + mask: { + source: {url: uploadUrl}, + }, placement: { inset: { left: 0, -- cgit v1.2.3-70-g09d2 From 79f44d95eb789d7b3cca33eedc38072f2bb43d2a Mon Sep 17 00:00:00 2001 From: bobzel Date: Tue, 11 Mar 2025 18:15:34 -0400 Subject: fixed calling firefly api for expand image --- src/client/views/nodes/ImageBox.tsx | 111 ++++--- src/client/views/smartdraw/DrawingFillHandler.tsx | 1 + src/server/ApiManagers/FireflyManager.ts | 363 ++++------------------ 3 files changed, 118 insertions(+), 357 deletions(-) (limited to 'src/server/ApiManagers') diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx index 114d5226b..a7dd5de15 100644 --- a/src/client/views/nodes/ImageBox.tsx +++ b/src/client/views/nodes/ImageBox.tsx @@ -1,7 +1,6 @@ import { Button, Colors, Size, Type } from '@dash/components'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { Slider, Tooltip } from '@mui/material'; -import { DimensionField } from '../../../fields/DimensionField'; import axios from 'axios'; import { action, computed, IReactionDisposer, makeObservable, observable, ObservableMap, reaction } from 'mobx'; import { observer } from 'mobx-react'; @@ -359,32 +358,31 @@ export class ImageBox extends ViewBoxAnnotatableComponent() { processOutpainting = async () => { const field = Cast(this.dataDoc[this.fieldKey], ImageField); if (!field) return; - + const origWidth = NumCast(this.Document._outpaintingOriginalWidth); const origHeight = NumCast(this.Document._outpaintingOriginalHeight); - + if (!origWidth || !origHeight) { - console.error("Original dimensions (_outpaintingOriginalWidth/_outpaintingOriginalHeight) not set. Ensure resizeViewForOutpainting was called first."); + console.error('Original dimensions (_outpaintingOriginalWidth/_outpaintingOriginalHeight) not set. Ensure resizeViewForOutpainting was called first.'); return; } - + //alert(`Original dimensions: ${origWidth} x ${origHeight}`); - - + // Set flag that outpainting is in progress this._outpaintingInProgress = true; - + try { // Get the current path to the image const currentPath = this.choosePath(field.url); - + // Get original and new dimensions for calculating mask const newWidth = NumCast(this.Document._width); const newHeight = NumCast(this.Document._height); - + // Optional: Ask user for a prompt to guide the outpainting - let prompt = "Extend this image naturally with matching content"; - const customPrompt = await new Promise((resolve) => { + let prompt = 'Extend this image naturally with matching content'; + const customPrompt = await new Promise(resolve => { const dialog = document.createElement('div'); Object.assign(dialog.style, { position: 'fixed', @@ -395,16 +393,16 @@ export class ImageBox extends ViewBoxAnnotatableComponent() { padding: '20px', borderRadius: '8px', boxShadow: '0 4px 12px rgba(0,0,0,0.2)', - zIndex: '10000' + zIndex: '10000', }); - + const title = document.createElement('h3'); title.style.marginTop = '0'; title.textContent = 'Outpaint Image'; - + const description = document.createElement('p'); description.textContent = 'Enter a prompt for extending the image:'; - + const input = document.createElement('input'); input.id = 'outpaint-prompt'; input.type = 'text'; @@ -412,50 +410,50 @@ export class ImageBox extends ViewBoxAnnotatableComponent() { Object.assign(input.style, { width: '300px', padding: '8px', - marginBottom: '10px' + marginBottom: '10px', }); - + const buttonContainer = document.createElement('div'); Object.assign(buttonContainer.style, { display: 'flex', justifyContent: 'flex-end', - gap: '10px' + gap: '10px', }); - + const cancelButton = document.createElement('button'); cancelButton.textContent = 'Cancel'; - + const confirmButton = document.createElement('button'); confirmButton.textContent = 'Generate'; Object.assign(confirmButton.style, { background: '#0078d4', color: 'white', border: 'none', - padding: '8px 16px' + padding: '8px 16px', }); - + buttonContainer.appendChild(cancelButton); buttonContainer.appendChild(confirmButton); - + dialog.appendChild(title); dialog.appendChild(description); dialog.appendChild(input); dialog.appendChild(buttonContainer); - + document.body.appendChild(dialog); - + cancelButton.onclick = () => { document.body.removeChild(dialog); - resolve(""); + resolve(''); }; - + confirmButton.onclick = () => { const promptValue = input.value; document.body.removeChild(dialog); resolve(promptValue); }; }); - + // If user cancelled, reset dimensions to original if (!customPrompt) { this.Document._width = origWidth; @@ -463,7 +461,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent() { this._outpaintingInProgress = false; return; } - + // Show loading indicator const loadingOverlay = document.createElement('div'); loadingOverlay.style.position = 'absolute'; @@ -477,52 +475,50 @@ export class ImageBox extends ViewBoxAnnotatableComponent() { loadingOverlay.style.alignItems = 'center'; loadingOverlay.innerHTML = '
Generating outpainted image...
'; this._mainCont?.appendChild(loadingOverlay); - + // Call the outpaint API - const response = await Networking.PostToServer('/outpaintImageFour', { + const response = await Networking.PostToServer('/outpaintImage', { imageUrl: currentPath, prompt: customPrompt, originalDimensions: { width: origWidth, - height: origHeight + height: origHeight, }, newDimensions: { width: newWidth, - height: newHeight - } + height: newHeight, + }, }); if (response && typeof response === 'object' && 'url' in response && typeof response.url === 'string') { - console.log("Response is valid and contains URL:", response.url); - + console.log('Response is valid and contains URL:', response.url); } else { - console.error("Unexpected API response:", response); - alert("Failed to receive a valid image URL from server."); + console.error('Unexpected API response:', response); + alert('Failed to receive a valid image URL from server.'); } - - + if (response && 'url' in response && typeof response.url === 'string') { // Save the original image as an alternate if (!this.dataDoc[this.fieldKey + '_alternates']) { this.dataDoc[this.fieldKey + '_alternates'] = new List(); } - + // Create a copy of the current image as an alternate const originalDoc = Docs.Create.ImageDocument(field.url.href, { title: `Original: ${this.Document.title}`, _nativeWidth: Doc.NativeWidth(this.dataDoc), - _nativeHeight: Doc.NativeHeight(this.dataDoc) + _nativeHeight: Doc.NativeHeight(this.dataDoc), }); - + // Add to alternates Doc.AddDocToList(this.dataDoc, this.fieldKey + '_alternates', originalDoc); - + // Update the image with the outpainted version this.dataDoc[this.fieldKey] = new ImageField(response.url); - + // Update native dimensions Doc.SetNativeWidth(this.dataDoc, newWidth); Doc.SetNativeHeight(this.dataDoc, newHeight); - + // Add AI metadata this.Document.ai = true; this.Document.ai_outpainted = true; @@ -531,18 +527,17 @@ export class ImageBox extends ViewBoxAnnotatableComponent() { // If failed, revert to original dimensions this.Document._width = origWidth; this.Document._height = origHeight; - alert("Failed to outpaint image. Please try again."); + alert('Failed to outpaint image. Please try again.'); } - + // Remove loading overlay this._mainCont?.removeChild(loadingOverlay); - } catch (error) { - console.error("Error during outpainting:", error); + console.error('Error during outpainting:', error); // Revert to original dimensions on error - this.Document._width = origWidth - this.Document._height = origHeight - alert("An error occurred while outpainting. Please try again."); + this.Document._width = origWidth; + this.Document._height = origHeight; + alert('An error occurred while outpainting. Please try again.'); } finally { // Clear the outpainting flags this._outpaintingInProgress = false; @@ -608,12 +603,12 @@ export class ImageBox extends ViewBoxAnnotatableComponent() { icon: this.Document.savedAsSticker ? 'clipboard-check' : 'file-arrow-down', }); // Add new outpainting option - funcs.push({ - description: 'Outpaint Image', + funcs.push({ + description: 'Outpaint Image', event: () => { this.processOutpainting(); - }, - icon: 'brush' + }, + icon: 'brush', }); // Add outpainting history option if the image was outpainted this.Document.ai_outpainted && diff --git a/src/client/views/smartdraw/DrawingFillHandler.tsx b/src/client/views/smartdraw/DrawingFillHandler.tsx index c672bc718..37799a98d 100644 --- a/src/client/views/smartdraw/DrawingFillHandler.tsx +++ b/src/client/views/smartdraw/DrawingFillHandler.tsx @@ -48,6 +48,7 @@ export class DrawingFillHandler { .then(res => { const genratedDocs = DocCast(drawing.ai_firefly_generatedDocs) ?? Docs.Create.MasonryDocument([], { _width: 400, _height: 400 }); drawing[DocData].ai_firefly_generatedDocs = genratedDocs; + if ('error' in res) throw new Error(res.error as string); (res as Upload.ImageInformation[]).map(info => Doc.AddDocToList( genratedDocs, diff --git a/src/server/ApiManagers/FireflyManager.ts b/src/server/ApiManagers/FireflyManager.ts index 1e0fdb595..8b0fa6ec7 100644 --- a/src/server/ApiManagers/FireflyManager.ts +++ b/src/server/ApiManagers/FireflyManager.ts @@ -8,7 +8,7 @@ import { DashUploadUtils } from '../DashUploadUtils'; import { _error, _invalid, _success, Method } from '../RouteManager'; import { Directory, filesDirectory } from '../SocketData'; import ApiManager, { Registration } from './ApiManager'; -import { Upload } from '../SharedMediaTypes'; +import { Upload } from '../SharedMediaTypes'; export default class FireflyManager extends ApiManager { getBearerToken = () => @@ -332,315 +332,80 @@ export default class FireflyManager extends ApiManager { register({ method: Method.POST, - subscription: '/outpaintImageFour', + subscription: '/outpaintImage', secureHandler: ({ req, res }) => - - this.uploadImageToDropbox(req.body.imageUrl, req.user as DashUserModel) - .then(uploadUrl => { - if (uploadUrl instanceof Error) { - _invalid(res, uploadUrl.message); - return; - } - return this.getBearerToken() - .then(tokenResponse => tokenResponse?.json()) - .then((tokenData: { access_token: string }) => - fetch('https://firefly-api.adobe.io/v3/images/expand', { - method: 'POST', - headers: { - //'Content-Type': 'application/json', - 'Accept': 'application/json', - 'x-api-key': process.env._CLIENT_FIREFLY_CLIENT_ID ?? '', - 'Authorization': `Bearer ${tokenData.access_token}`, + this.uploadImageToDropbox(req.body.imageUrl, req.user as DashUserModel).then(uploadUrl => { + if (uploadUrl instanceof Error) { + _invalid(res, uploadUrl.message); + return; + } + return this.getBearerToken() + .then(tokenResponse => tokenResponse?.json()) + .then((tokenData: { access_token: string }) => + fetch('https://firefly-api.adobe.io/v3/images/expand', { + method: 'POST', + headers: [ + ['Content-Type', 'application/json'], + ['Accept', 'application/json'], + ['x-api-key', process.env._CLIENT_FIREFLY_CLIENT_ID ?? ''], + ['Authorization', `Bearer ${tokenData.access_token}`], + ], + body: JSON.stringify({ + image: { + source: { url: uploadUrl }, }, - body: JSON.stringify({ - image: { - source: { url: uploadUrl }, - }, - size: { - width: req.body.newDimensions.width, - height: req.body.newDimensions.height, + // mask: { + // source: { url: uploadUrl }, + // }, + size: { + width: Math.round(req.body.newDimensions.width), + height: Math.round(req.body.newDimensions.height), + }, + prompt: req.body.prompt ?? '', + numVariations: 1, + placement: { + inset: { + left: 0, + top: 0, + right: Math.round(req.body.newDimensions.width - req.body.originalDimensions.width), + bottom: Math.round(req.body.newDimensions.height - req.body.originalDimensions.height), }, - prompt: req.body.prompt ?? '', - numVariations: 1, - placement: { - inset: { - left: 0, - top: 0, - right: req.body.newDimensions.width - req.body.originalDimensions.width, - bottom: req.body.newDimensions.height - req.body.originalDimensions.height, - }, - alignment: { - horizontal: 'center', - vertical: 'center', - }, + alignment: { + horizontal: 'center', + vertical: 'center', }, - }), - }) - ) - .then(expandResp => expandResp?.json()) - .then(expandData => { - if (expandData.error_code || !expandData.outputs?.[0]?.image?.url) { - console.error('Firefly validation error:', expandData); - _error(res, expandData.message ?? 'Failed to generate image'); - } else { - DashUploadUtils.UploadImage(expandData.outputs[0].image.url) - .then((info: Upload.ImageInformation | Error) => { - if (info instanceof Error) { - _invalid(res, info.message); - } else { - _success(res, { url: info.accessPaths.agnostic.client }); - } - }) - .catch(uploadErr => { - console.error('DashUpload Error:', uploadErr); - _error(res, 'Failed to upload generated image.'); - }); - } + }, + }), }) - .catch(err => { - console.error('Firefly request error:', err); - _error(res, 'Failed to expand image'); - }); - }), - }); - register({ - method: Method.POST, - subscription: '/outpaintImageThree', - secureHandler: ({ req, res }) => - new Promise(resolver => { - this.uploadImageToDropbox(req.body.imageUrl, req.user as DashUserModel) - .then(dropboxImgUrl => { - if (dropboxImgUrl instanceof Error) { - _invalid(res, dropboxImgUrl.message); - throw new Error('Error uploading image to dropbox'); + ) + .then(expandResp => expandResp?.json()) + .then(expandData => { + if (expandData.error_code || !expandData.outputs?.[0]?.image?.url) { + console.error('Firefly validation error:', expandData); + _error(res, expandData.message ?? 'Failed to generate image'); + } else { + return DashUploadUtils.UploadImage(expandData.outputs[0].image.url) + .then((info: Upload.ImageInformation | Error) => { + if (info instanceof Error) { + _invalid(res, info.message); + } else { + _success(res, { url: info.accessPaths.agnostic.client }); + } + }) + .catch(uploadErr => { + console.error('DashUpload Error:', uploadErr); + _error(res, 'Failed to upload generated image.'); + }); } - return dropboxImgUrl; }) - .then(dropboxImgUrl => - this.getBearerToken().then(tokenResponse => - tokenResponse?.json().then((tokenData: { access_token: string }) => - fetch('https://firefly-api.adobe.io/v3/images/expand', { - method: 'POST', - headers: [ - ['Content-Type', 'application/json'], - ['Accept', 'application/json'], - ['x-api-key', process.env._CLIENT_FIREFLY_CLIENT_ID ?? ''], - ['Authorization', `Bearer ${tokenData.access_token}`], - ], - body: JSON.stringify({ - image: { - source: { - url: dropboxImgUrl, - }, - }, - numVariations: 1, - prompt: req.body.prompt, - size: { - width: req.body.newDimensions.width, - height: req.body.newDimensions.height, - }, - placement: { - inset: { - left: 0, - top: 0, - right: req.body.newDimensions.width - req.body.originalDimensions.width, - bottom: req.body.newDimensions.height - req.body.originalDimensions.height, - }, - alignment: { - horizontal: 'center', - vertical: 'center', - }, - }, - }), - }) - .then(resp => resp.json()) - .then(expandData => { - if (expandData.error_code || !expandData.outputs?.[0]?.image?.url) { - _invalid(res, expandData.message ?? 'Failed to expand image'); - resolver(); - } else { - DashUploadUtils.UploadImage(expandData.outputs[0].image.url) - .then(info => { - if (info instanceof Error) { - _invalid(res, info.message); - } else { - _success(res, { url: info.accessPaths.agnostic.client }); - } - }) - .then(resolver) - .catch(uploadErr => { - console.error('DashUpload Error:', uploadErr); - _invalid(res, 'Failed to upload generated image.'); - resolver(); - }); - } - }) - .catch(err => { - console.error('Firefly API Error:', err); - _error(res, 'Failed to expand image'); - resolver(); - }) - ) - ) - ) .catch(err => { - console.error('Dropbox Error:', err); - _error(res, 'Failed to upload image to Dropbox'); - resolver(); + console.error('Firefly request error:', err); + _error(res, 'Failed to expand image'); }); }), }); - - - register({ - method: Method.POST, - subscription: '/outpaintImageTwo', - secureHandler: async ({ req, res }) => { - try { - const uploadUrl = await this.uploadImageToDropbox(req.body.imageUrl, req.user as DashUserModel); - - if (uploadUrl instanceof Error) { - _invalid(res, uploadUrl.message); - return; - } - - const tokenResponse = await this.getBearerToken(); - const tokenData = await tokenResponse?.json(); - - const body = JSON.stringify({ - image: { source: { url: uploadUrl } }, - prompt: req.body.prompt, - numVariations: 1, - size: { - width: req.body.newDimensions.width, - height: req.body.newDimensions.height, - }, - placement: { - inset: { - left: 0, - top: 0, - right: req.body.newDimensions.width - req.body.originalDimensions.width, - bottom: req.body.newDimensions.height - req.body.originalDimensions.height, - }, - alignment: { - horizontal: 'center', - vertical: 'center', - }, - }, - }); - - console.log('Sending outpainting request:', body); - - const expandResp = await fetch('https://firefly-api.adobe.io/v3/images/expand', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - Accept: 'application/json', - 'x-api-key': process.env._CLIENT_FIREFLY_CLIENT_ID ?? '', - Authorization: `Bearer ${tokenData.access_token}`, - }, - body, - }); - - const expandData = await expandResp.json(); - console.log('Received expandData:', expandData); - - if (expandData.error_code || !expandData.outputs?.[0]?.image?.url) { - console.error('Expand API Error:', expandData); - _error(res, expandData.message ?? 'Failed to generate image'); - return; - } - - const uploadedInfo = await DashUploadUtils.UploadImage(expandData.outputs[0].image.url); - - if (uploadedInfo instanceof Error) { - console.error('Upload Error:', uploadedInfo.message); - _invalid(res, uploadedInfo.message); - return; - } - - console.log('Successfully uploaded image URL:', uploadedInfo.accessPaths.agnostic.client); - _success(res, { url: uploadedInfo.accessPaths.agnostic.client }); - } catch (err) { - console.error('Unexpected error during outpainting:', err); - _error(res, 'Unexpected error during outpainting'); - } - }, - }); - - register({ - - - method: Method.POST, - subscription: '/outpaintImage', - secureHandler: ({ req, res }) => - this.uploadImageToDropbox(req.body.imageUrl, req.user as DashUserModel).then(uploadUrl => - uploadUrl instanceof Error - ? _invalid(res, uploadUrl.message) - : this.getBearerToken() - .then(tokenResponse => tokenResponse?.json()) - .then((tokenData: { access_token: string }) => - fetch('https://firefly-api.adobe.io/v3/images/expand', { - method: 'POST', - headers: [ - ['Content-Type', 'application/json'], - ['Accept', 'application/json'], - ['x-api-key', process.env._CLIENT_FIREFLY_CLIENT_ID ?? ''], - ['Authorization', `Bearer ${tokenData.access_token}`], - ], - body: JSON.stringify({ - image: { source: { url: uploadUrl } }, - numVariations: 1, - size: { - width: req.body.newDimensions.width, - height: req.body.newDimensions.height, - }, - prompt: req.body.prompt, - placement: { - inset: { - left: 0, - top: 0, - right: 0, //req.body.newDimensions.width - req.body.originalDimensions.width, - bottom: 0 //req.body.newDimensions.height - req.body.originalDimensions.height, - }, - alignment: { - horizontal: 'center', - vertical: 'center', - }, - }, - }), - }) - ) - .then(expandResp => expandResp.json()) - .then(expandData => { - if (expandData.error_code || !expandData.outputs?.[0]?.image?.url) { - _error(res, expandData.message ?? "Failed to generate image"); - } else { - DashUploadUtils.UploadImage(expandData.outputs[0].image.url) - .then((info: Upload.ImageInformation | Error) => { - if (info instanceof Error) { - _invalid(res, info.message); - } else { - // THIS IS CRUCIAL: Respond exactly how your front end expects - console.log("Successfully uploaded image. URL:", info.accessPaths.agnostic.client); - _success(res, { url: info.accessPaths.agnostic.client }); - } - }) - .catch(uploadErr => { - - console.error('DashUpload Error:', uploadErr); - _error(res, 'Failed to upload generated image.'); - }); - } - }) - .catch(err => { - console.error(err); - _error(res, 'Failed to expand image'); - }) - ), - }); - /* register({ + /* register({ method: Method.POST subscription: '/queryFireflyOutpaint', secureHandler: ({req, res}) => @@ -683,7 +448,7 @@ export default class FireflyManager extends ApiManager { : this.expandImage(uploadUrl, req.body.prompt).then(text => { if (text.error_code) _error(res, text.message); else - DashUploadUtils.UploadImage(text.outputs[0].image.url).then(info => { + return DashUploadUtils.UploadImage(text.outputs[0].image.url).then(info => { if (info instanceof Error) _invalid(res, info.message); else _success(res, info); }); -- cgit v1.2.3-70-g09d2 From d0b117d1f31f911d46dffe69e2f5c40b2bc5de3e Mon Sep 17 00:00:00 2001 From: bobzel Date: Tue, 11 Mar 2025 23:08:16 -0400 Subject: getting rid of old expand image code --- src/client/views/nodes/ImageBox.tsx | 24 ++---------------------- src/server/ApiManagers/FireflyManager.ts | 17 ----------------- 2 files changed, 2 insertions(+), 39 deletions(-) (limited to 'src/server/ApiManagers') diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx index a7dd5de15..14adfaf1e 100644 --- a/src/client/views/nodes/ImageBox.tsx +++ b/src/client/views/nodes/ImageBox.tsx @@ -552,9 +552,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent() { funcs.push({ description: 'Rotate Clockwise 90', event: this.rotate, icon: 'redo-alt' }); funcs.push({ description: `Show ${this.layoutDoc._showFullRes ? 'Dynamic Res' : 'Full Res'}`, event: this.resolution, icon: 'expand' }); funcs.push({ description: 'Set Native Pixel Size', event: this.setNativeSize, icon: 'expand-arrows-alt' }); - funcs.push({ - description: 'GetImageText', - event: () => { + funcs.push({ description: 'GetImageText', event: () => { Networking.PostToServer('/queryFireflyImageText', { file: (file => { const ext = extname(file); @@ -563,25 +561,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent() { }).then(text => alert(text)); }, icon: 'expand-arrows-alt', - }); - funcs.push({ - description: 'Expand Image', - event: () => { - Networking.PostToServer('/expandImage', { - prompt: 'sunny skies', - file: (file => { - const ext = extname(file); - return file.replace(ext, (this._error ? '_o' : this._curSuffix) + ext); - })(ImageCast(this.Document[Doc.LayoutFieldKey(this.Document)])?.url.href), - }).then(res => { - const info = res as Upload.ImageInformation; - const img = Docs.Create.ImageDocument(info.accessPaths.agnostic.client, { title: 'expand:' + this.Document.title }); - DocUtils.assignImageInfo(info, img); - this._props.addDocTab(img, OpenWhere.addRight); - }); - }, - icon: 'expand-arrows-alt', - }); + }); // prettier-ignore funcs.push({ description: 'Copy path', event: () => ClientUtils.CopyText(this.choosePath(field.url)), icon: 'copy' }); funcs.push({ description: 'Open Image Editor', event: this.docEditorView, icon: 'pencil-alt' }); this.layoutDoc.ai && diff --git a/src/server/ApiManagers/FireflyManager.ts b/src/server/ApiManagers/FireflyManager.ts index 77655bb23..887c4aa72 100644 --- a/src/server/ApiManagers/FireflyManager.ts +++ b/src/server/ApiManagers/FireflyManager.ts @@ -429,23 +429,6 @@ export default class FireflyManager extends ApiManager { ) ), }); - register({ - method: Method.POST, - subscription: '/expandImage', - secureHandler: ({ req, res }) => - this.uploadImageToDropbox(req.body.file, req.user as DashUserModel).then(uploadUrl => - uploadUrl instanceof Error - ? _invalid(res, uploadUrl.message) - : this.expandImage(uploadUrl, req.body.prompt).then(text => { - if (text.error_code) _error(res, text.message); - else - return DashUploadUtils.UploadImage(text.outputs[0].image.url).then(info => { - if (info instanceof Error) _invalid(res, info.message); - else _success(res, info); - }); - }) - ), - }); // construct this url and send user to it. It will allow them to authorize their dropbox account and will send the resulting token to our endpoint /refreshDropbox // https://www.dropbox.com/oauth2/authorize?client_id=DROPBOX_CLIENT_ID&response_type=code&token_access_type=offline&redirect_uri=http://localhost:1050/refreshDropbox -- cgit v1.2.3-70-g09d2 From ede7aaa19d903a7e55fc60d9bc213988ea602c97 Mon Sep 17 00:00:00 2001 From: bobzel Date: Thu, 3 Apr 2025 14:15:15 -0400 Subject: fixed image box button sizing. made drawing ai regen a little faster by not creating icons when there are no annotqations. --- src/client/apis/gpt/GPT.ts | 5 +- src/client/views/nodes/DocumentView.tsx | 2 +- src/client/views/nodes/ImageBox.scss | 7 +-- src/client/views/nodes/ImageBox.tsx | 23 ++++--- src/client/views/smartdraw/DrawingFillHandler.tsx | 75 +++++++++++------------ src/server/ApiManagers/FireflyManager.ts | 1 - src/server/ApiManagers/UploadManager.ts | 7 +-- src/server/DashUploadUtils.ts | 14 ++--- 8 files changed, 62 insertions(+), 72 deletions(-) (limited to 'src/server/ApiManagers') diff --git a/src/client/apis/gpt/GPT.ts b/src/client/apis/gpt/GPT.ts index 29b6ab989..140aebfe0 100644 --- a/src/client/apis/gpt/GPT.ts +++ b/src/client/apis/gpt/GPT.ts @@ -1,5 +1,6 @@ import { ChatCompletionMessageParam, Image } from 'openai/resources'; import { openai } from './setup'; +import { imageUrlToBase64 } from '../../../ClientUtils'; export enum GPTDocCommand { AssignTags = 1, @@ -310,7 +311,9 @@ const gptHandwriting = async (src: string): Promise => { } }; -const gptDescribeImage = async (image: string): Promise => { +const gptDescribeImage = async (userPrompt: string, url: string): Promise => { + if (userPrompt) return userPrompt; + const image = imageUrlToBase64(url); try { const response = await openai.chat.completions.create({ model: 'gpt-4o', diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 070a13103..37f888ddd 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -702,7 +702,7 @@ export class DocumentViewInternal extends DocComponent() { this._regenerateLoading = true; const drag = de.complete.docDragData.draggedDocuments.lastElement(); const dragField = drag[Doc.LayoutFieldKey(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 newPrompt = (text: string) => (oldPrompt ? `${oldPrompt} ~~~ ${text}` : text); - DrawingFillHandler.drawingToImage(this.Document, 90, newPrompt(dragField instanceof RichTextField ? dragField.Text : ''), drag)?.then(action(() => (this._regenerateLoading = false))); + DrawingFillHandler.drawingToImage(this.Document, 90, newPrompt(descText), drag)?.then(action(() => (this._regenerateLoading = false))); added = false; } else if (de.altKey || !this.dataDoc[this.fieldKey]) { const layoutDoc = de.complete.docDragData?.draggedDocuments[0]; @@ -401,13 +401,12 @@ export class ImageBox extends ViewBoxAnnotatableComponent() { }; // updateIcon = () => new Promise(res => res()); - updateIcon = (/* usePanelDimensions?: boolean */) => { - const contentDiv = this._mainCont; - return !contentDiv + updateIcon = (/* usePanelDimensions?: boolean */) => + !this._mainCont || !DocListCast(this.dataDoc[this.annotationKey]).length ? new Promise(res => res()) : UpdateIcon( this.layoutDoc[Id] + '_icon_' + new Date().getTime(), - contentDiv, + this._mainCont, this._props.PanelWidth(), // usePanelDimensions ? this._props.PanelWidth() : NumCast(this.layoutDoc._width), this._props.PanelHeight(), // usePanelDimensions ? this._props.PanelHeight() : NumCast(this.layoutDoc._height), this._props.PanelWidth(), @@ -422,7 +421,6 @@ export class ImageBox extends ViewBoxAnnotatableComponent() { this.dataDoc.icon_nativeHeight = nativeHeight; } ); - }; choosePath = (url: URL) => { if (!url?.href) return ''; @@ -453,15 +451,15 @@ export class ImageBox extends ViewBoxAnnotatableComponent() { /** * How much the content of the view is being scaled based on its nesting and its fit-to-width settings */ - @computed get viewScaling() { return this.ScreenToLocalBoxXf().Scale * ( this._props.NativeDimScaling?.() || 1); } // prettier-ignore + @computed get viewScaling() { return this.ScreenToLocalBoxXf().Scale * (this._props.NativeDimScaling?.()??1); } // prettier-ignore /** * The maximum size a UI widget can be scaled so that it won't be bigger in screen pixels than its normal 35 pixel size. */ - @computed get maxWidgetSize() { return Math.min(this._sideBtnWidth, 0.5 * Math.min(NumCast(this.Document.width)))* this.viewScaling; } // prettier-ignore + @computed get maxWidgetSize() { return Math.min(this._sideBtnWidth, 0.2 * this._props.PanelWidth())*this.viewScaling; } // prettier-ignore /** * How much to reactively scale a UI element so that it is as big as it can be (up to its normal 35pixel size) without being too big for the Doc content */ - @computed get uiBtnScaling() { return Math.min(this.maxWidgetSize / this._sideBtnWidth, 1); } // prettier-ignore + @computed get uiBtnScaling() { return Math.min(1/(this._props.NativeDimScaling?.()??1), this.maxWidgetSize / this._sideBtnWidth); } // prettier-ignore @computed get overlayImageIcon() { const usePath = this.layoutDoc[`_${this.fieldKey}_usePath`]; @@ -518,10 +516,11 @@ export class ImageBox extends ViewBoxAnnotatableComponent() { transform: `scale(${this.uiBtnScaling})`, width: this._sideBtnWidth, height: this._sideBtnWidth, - background: 'transparent', + background: 'black', + color: 'white', // color: SettingsManager.userBackgroundColor, }}> - {this._regenerateLoading ? : } + {this._regenerateLoading ? : } ); diff --git a/src/client/views/smartdraw/DrawingFillHandler.tsx b/src/client/views/smartdraw/DrawingFillHandler.tsx index d8ef8308d..a91ec23b8 100644 --- a/src/client/views/smartdraw/DrawingFillHandler.tsx +++ b/src/client/views/smartdraw/DrawingFillHandler.tsx @@ -1,4 +1,3 @@ -import { imageUrlToBase64 } from '../../../ClientUtils'; import { Doc, StrListCast } from '../../../fields/Doc'; import { List } from '../../../fields/List'; import { DocCast, ImageCast, StrCast } from '../../../fields/Types'; @@ -42,44 +41,42 @@ export class DrawingFillHandler { const { href } = ImageCast(imageField).url; const hrefParts = href.split('.'); const structureUrl = `${hrefParts.slice(0, -1).join('.')}_o.${hrefParts.lastElement()}`; - return imageUrlToBase64(structureUrl) - .then(gptDescribeImage) - .then((prompt, newPrompt = user_prompt || prompt) => - Networking.PostToServer('/queryFireflyImageFromStructure', { prompt: `${newPrompt}`, width: dims.width, height: dims.height, structureUrl, strength, presets: styles, styleUrl }) - .then(res => { - const error = ('error' in res && (res.error as string)) || ''; - if (error.includes('Dropbox') && confirm('Create image failed. Try authorizing DropBox?\r\n' + error.replace(/^[^"]*/, ''))) { - window.open(`https://www.dropbox.com/oauth2/authorize?client_id=${DashDropboxId}&response_type=code&token_access_type=offline&redirect_uri=http://localhost:1050/refreshDropbox`, '_blank')?.focus(); - return; - } - 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; - (res as Upload.ImageInformation[]).map(info => - Doc.AddDocToList( - genratedDocs, - undefined, - Docs.Create.ImageDocument(info.accessPaths.agnostic.client, { - ai: 'firefly', - 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, - }), - undefined, - undefined, - true - ) - ); - if (!DocumentView.getFirstDocumentView(genratedDocs)) DocumentViewInternal.addDocTabFunc(genratedDocs, OpenWhere.addRight); - }) - .catch(e => { - alert(e.toString()); - }) - ); // prettier-ignore:q + return gptDescribeImage(user_prompt, structureUrl).then(newPrompt => + Networking.PostToServer('/queryFireflyImageFromStructure', { prompt: `${newPrompt}`, width: dims.width, height: dims.height, structureUrl, strength, presets: styles, styleUrl }) + .then(res => { + const error = ('error' in res && (res.error as string)) || ''; + if (error.includes('Dropbox') && confirm('Create image failed. Try authorizing DropBox?\r\n' + error.replace(/^[^"]*/, ''))) { + window.open(`https://www.dropbox.com/oauth2/authorize?client_id=${DashDropboxId}&response_type=code&token_access_type=offline&redirect_uri=http://localhost:1050/refreshDropbox`, '_blank')?.focus(); + return; + } + 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; + (res as Upload.ImageInformation[]).map(info => + Doc.AddDocToList( + genratedDocs, + undefined, + Docs.Create.ImageDocument(info.accessPaths.agnostic.client, { + ai: 'firefly', + 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, + }), + undefined, + undefined, + true + ) + ); + if (!DocumentView.getFirstDocumentView(genratedDocs)) DocumentViewInternal.addDocTabFunc(genratedDocs, OpenWhere.addRight); + }) + .catch(e => { + alert(e.toString()); + }) + ); // prettier-ignore:q } }); }; diff --git a/src/server/ApiManagers/FireflyManager.ts b/src/server/ApiManagers/FireflyManager.ts index e75ede9df..07428798c 100644 --- a/src/server/ApiManagers/FireflyManager.ts +++ b/src/server/ApiManagers/FireflyManager.ts @@ -117,7 +117,6 @@ export default class FireflyManager extends ApiManager { generateImage = (prompt: string = 'a realistic illustration of a cat coding', width: number = 2048, height: number = 2048, seed?: number) => { let body = `{ "prompt": "${prompt}", "size": { "width": ${width}, "height": ${height}} }`; if (seed) { - console.log('RECEIVED SEED', seed); body = `{ "prompt": "${prompt}", "size": { "width": ${width}, "height": ${height}}, "seeds": [${seed}]}`; } const fetched = this.getBearerToken().then(response => diff --git a/src/server/ApiManagers/UploadManager.ts b/src/server/ApiManagers/UploadManager.ts index c9d5df547..7c55e4a42 100644 --- a/src/server/ApiManagers/UploadManager.ts +++ b/src/server/ApiManagers/UploadManager.ts @@ -126,11 +126,8 @@ export default class UploadManager extends ApiManager { secureHandler: async ({ req, res }) => { const { sources } = req.body; if (Array.isArray(sources)) { - const results = await Promise.all(sources.map(source => DashUploadUtils.UploadImage(source))); - res.send(results); - return; - } - res.send(); + res.send(await Promise.all(sources.map(source => DashUploadUtils.UploadImage(source)))); + } else res.send(); }, }); diff --git a/src/server/DashUploadUtils.ts b/src/server/DashUploadUtils.ts index a2747257a..ed109d8f7 100644 --- a/src/server/DashUploadUtils.ts +++ b/src/server/DashUploadUtils.ts @@ -450,14 +450,12 @@ export namespace DashUploadUtils { * 3) the size of the image, in bytes (4432130) * 4) the content type of the image, i.e. image/(jpeg | png | ...) */ - export const UploadImage = async (source: string, filename?: string, prefix: string = ''): Promise => { - const result = await InspectImage(source); - if (result instanceof Error) { - return { name: result.name, message: result.message }; - } - const outputFile = filename || result.filename || ''; - return UploadInspectedImage(result, outputFile, prefix, isLocal().exec(source) || source.startsWith('data:') ? true : false); - }; + export const UploadImage = (source: string, filename?: string, prefix: string = ''): Promise => + InspectImage(source).then(async result => + result instanceof Error + ? ({ name: result.name, message: result.message } as Error) // + : UploadInspectedImage(result, filename || result.filename || '', prefix, isLocal().exec(source) || source.startsWith('data:') ? true : false) + ); type md5 = 'md5'; type falsetype = false; -- cgit v1.2.3-70-g09d2 From 49c571cb3f8db0732ac9b461890af2c09722c71b Mon Sep 17 00:00:00 2001 From: bobzel Date: Wed, 16 Apr 2025 19:53:26 -0400 Subject: fixed authorizing dropbox. --- src/client/Network.ts | 4 +- src/client/util/request-image-size.ts | 38 +++-- src/client/views/smartdraw/DrawingFillHandler.tsx | 8 +- src/server/ApiManagers/FireflyManager.ts | 164 ++++++++++------------ src/server/DashUploadUtils.ts | 7 +- 5 files changed, 101 insertions(+), 120 deletions(-) (limited to 'src/server/ApiManagers') diff --git a/src/client/Network.ts b/src/client/Network.ts index a2ecf1bea..b11dcb379 100644 --- a/src/client/Network.ts +++ b/src/client/Network.ts @@ -21,10 +21,10 @@ export namespace Networking { 'Content-Type': 'application/json', }, body: body ? JSON.stringify(body) : undefined, - }).then(async response => { + }).then(response => { if (response.ok) return response.json() as object; - return await response.text().then(text => ({ error: '' + response.status + ':' + response.statusText + '-' + text })); + return response.text().then(text => ({ error: '' + response.status + ':' + response.statusText + '-' + text })); }); } diff --git a/src/client/util/request-image-size.ts b/src/client/util/request-image-size.ts index 32ab23618..798390c7d 100644 --- a/src/client/util/request-image-size.ts +++ b/src/client/util/request-image-size.ts @@ -33,27 +33,25 @@ export function requestImageSize(url: string): Promise { res.on('data', chunk => { buffer = Buffer.concat([buffer, chunk]); - }); - - res.on('error', reject); - - res.on('end', () => { - try { - size = imageSize(buffer); - if (size) { - resolve(size); - req.abort(); + }) + .on('error', reject) + .on('end', () => { + try { + size = imageSize(buffer); + if (size) { + resolve(size); + req.abort(); + } + } catch (err) { + /* empty */ + console.log('Error: ', err); + } + if (!size) { + reject(new Error('Image has no size')); + return; } - } catch (err) { - /* empty */ - console.log('Error: ', err); - } - if (!size) { - reject(new Error('Image has no size')); - return; - } - resolve(size); - }); + resolve(size); + }); }); req.on('error', reject); diff --git a/src/client/views/smartdraw/DrawingFillHandler.tsx b/src/client/views/smartdraw/DrawingFillHandler.tsx index a91ec23b8..b0945fd83 100644 --- a/src/client/views/smartdraw/DrawingFillHandler.tsx +++ b/src/client/views/smartdraw/DrawingFillHandler.tsx @@ -26,11 +26,12 @@ export class DrawingFillHandler { const styleUrl = tags.length ? undefined : await DocumentView.GetDocImage(styleDocs.filter(doc => doc?.data instanceof ImageField).lastElement())?.then(styleImg => { - const hrefParts = ImageCast(styleImg).url.href.split('.'); - return `${hrefParts.slice(0, -1).join('.')}_o.${hrefParts.lastElement()}`; + const hrefParts = ImageCast(styleImg)?.url.href.split('.'); + return !hrefParts ? undefined : `${hrefParts.slice(0, -1).join('.')}_o.${hrefParts.lastElement()}`; }); return DocumentView.GetDocImage(drawing)?.then(imageField => { - if (imageField) { + const href = ImageCast(imageField)?.url.href; + if (href) { const aspectRatio = (drawing.width as number) / (drawing.height as number); const dims = (() => { if (aspectRatio > AspectRatioLimits[FireflyImageDimensions.Widescreen]) return FireflyDimensionsMap[FireflyImageDimensions.Widescreen]; @@ -38,7 +39,6 @@ export class DrawingFillHandler { if (aspectRatio < AspectRatioLimits[FireflyImageDimensions.Portrait]) return FireflyDimensionsMap[FireflyImageDimensions.Portrait]; return FireflyDimensionsMap[FireflyImageDimensions.Square]; })(); - const { href } = ImageCast(imageField).url; const hrefParts = href.split('.'); const structureUrl = `${hrefParts.slice(0, -1).join('.')}_o.${hrefParts.lastElement()}`; return gptDescribeImage(user_prompt, structureUrl).then(newPrompt => diff --git a/src/server/ApiManagers/FireflyManager.ts b/src/server/ApiManagers/FireflyManager.ts index 07428798c..d22142d7d 100644 --- a/src/server/ApiManagers/FireflyManager.ts +++ b/src/server/ApiManagers/FireflyManager.ts @@ -70,49 +70,42 @@ export default class FireflyManager extends ApiManager { ); uploadImageToDropbox = (fileUrl: string, user: DashUserModel | undefined, dbx = new Dropbox({ accessToken: user?.dropboxToken || '' })) => - new Promise((res, rej) => + new Promise((resolve, reject) => { fs.readFile(path.join(filesDirectory, `${Directory.images}/${path.basename(fileUrl)}`), undefined, (err, contents) => { if (err) { - console.log('Error: ', err); - rej(); - } else { - dbx.filesUpload({ path: `/Apps/browndash/${path.basename(fileUrl)}`, contents }) - .then(response => { - dbx.filesGetTemporaryLink({ path: response.result.path_display ?? '' }) - .then(link => res(link.result.link)) - .catch(e => res(new Error(e.toString()))); - }) - .catch(e => { - if (user?.dropboxRefresh) { - console.log('*********** try refresh dropbox for: ' + user.email + ' ***********'); - this.refreshDropboxToken(user).then(token => { - if (!token) { - console.log('Dropbox error: cannot refresh token'); - res(new Error(e.toString())); - } else { - const dbxNew = new Dropbox({ accessToken: user.dropboxToken || '' }); - dbxNew - .filesUpload({ path: `/Apps/browndash/${path.basename(fileUrl)}`, contents }) - .then(response => { - dbxNew - .filesGetTemporaryLink({ path: response.result.path_display ?? '' }) - .then(link => res(link.result.link)) - .catch(linkErr => res(new Error(linkErr.toString()))); - }) - .catch(uploadErr => { - console.log('Dropbox error:', uploadErr); - res(new Error(uploadErr.toString())); - }); - } - }); - } else { - console.log('Dropbox error:', e); - res(new Error(e.toString())); - } - }); + return reject(new Error('Error reading file:' + err.message)); } - }) - ); + + const uploadToDropbox = (dropboxClient: Dropbox) => + dropboxClient + .filesUpload({ path: `/Apps/browndash/${path.basename(fileUrl)}`, contents }) + .then(response => + dropboxClient + .filesGetTemporaryLink({ path: response.result.path_display ?? '' }) + .then(link => resolve(link.result.link)) + .catch(linkErr => reject(new Error('Failed to get temporary link: ' + linkErr.message))) + ) + .catch(uploadErr => reject(new Error('Failed to upload file to Dropbox: ' + uploadErr.message))); + + uploadToDropbox(dbx).catch(e => { + if (user?.dropboxRefresh) { + console.log('Attempting to refresh Dropbox token for user:', user.email); + this.refreshDropboxToken(user) + .then(token => { + if (!token) { + return reject(new Error('Failed to refresh Dropbox token.' + user.email)); + } + + const dbxNew = new Dropbox({ accessToken: token }); + uploadToDropbox(dbxNew).catch(finalErr => reject(new Error('Failed to refresh Dropbox token:' + finalErr.message))); + }) + .catch(refreshErr => reject(new Error('Failed to refresh Dropbox token: ' + refreshErr.message))); + } else { + reject(new Error('Dropbox error: ' + e.message)); + } + }); + }); + }); generateImage = (prompt: string = 'a realistic illustration of a cat coding', width: number = 2048, height: number = 2048, seed?: number) => { let body = `{ "prompt": "${prompt}", "size": { "width": ${width}, "height": ${height}} }`; @@ -289,43 +282,36 @@ export default class FireflyManager extends ApiManager { register({ method: Method.POST, subscription: '/queryFireflyImageFromStructure', - secureHandler: ({ req, res }) => - new Promise(resolver => { - (req.body.styleUrl ? this.uploadImageToDropbox(req.body.styleUrl, req.user as DashUserModel) : Promise.resolve(undefined)) - .then(styleUrl => { - if (styleUrl instanceof Error) { - _invalid(res, styleUrl.message); - throw new Error('Error uploading images to dropbox'); - } - this.uploadImageToDropbox(req.body.structureUrl, req.user as DashUserModel) - .then(dropboxStructureUrl => { - if (dropboxStructureUrl instanceof Error) { - _invalid(res, dropboxStructureUrl.message); - throw new Error('Error uploading images to dropbox'); - } - return { styleUrl, structureUrl: dropboxStructureUrl }; - }) - .then(uploads => - this.generateImageFromStructure(req.body.prompt, req.body.width, req.body.height, uploads.structureUrl, req.body.strength, req.body.presets, uploads.styleUrl) - .then(images => { - Promise.all((images ?? [new Error('no images were generated')]).map(fire => (fire instanceof Error ? fire : DashUploadUtils.UploadImage(fire.url)))) - .then(dashImages => { - if (dashImages.every(img => img instanceof Error)) _invalid(res, dashImages[0]!.message); - else _success(res, JSON.stringify(dashImages.filter(img => !(img instanceof Error)))); - }) - .then(resolver); - }) - .catch(e => { - _invalid(res, e.message); - resolver(); - }) - ); - }) - .catch(() => { - /* do nothing */ - resolver(); - }); - }), + secureHandler: ({ req, res }) => + new Promise(resolver => + (req.body.styleUrl + ? this.uploadImageToDropbox(req.body.styleUrl, req.user as DashUserModel) + : Promise.resolve(undefined) + ) + .then(styleUrl => + this.uploadImageToDropbox(req.body.structureUrl, req.user as DashUserModel) + .then(dropboxStructureUrl => + ({ styleUrl, structureUrl: dropboxStructureUrl }) + ) + + ) + .then(uploads => + this.generateImageFromStructure( + req.body.prompt, req.body.width, req.body.height, uploads.structureUrl, req.body.strength, req.body.presets, uploads.styleUrl + ).then(images => + Promise.all((images ?? [new Error('no images were generated')]).map(fire => (fire instanceof Error ? fire : DashUploadUtils.UploadImage(fire.url)))) + .then(dashImages => + (dashImages.every(img => img instanceof Error)) + ? _invalid(res, dashImages[0]!.message) + : _success(res, JSON.stringify(dashImages.filter(img => !(img instanceof Error)))) + ) + ) + ) + .catch(e => { + _invalid(res, e.message); + resolver(); + }) + ), // prettier-ignore }); register({ method: Method.POST, @@ -357,18 +343,18 @@ export default class FireflyManager extends ApiManager { method: Method.POST, subscription: '/expandImage', secureHandler: ({ req, res }) => - this.uploadImageToDropbox(req.body.file, req.user as DashUserModel).then(uploadUrl => - uploadUrl instanceof Error - ? _invalid(res, uploadUrl.message) - : this.expandImage(uploadUrl, req.body.prompt).then(text => { - if (text.error_code) _error(res, text.message); - else - DashUploadUtils.UploadImage(text.outputs[0].image.url).then(info => { - if (info instanceof Error) _invalid(res, info.message); - else _success(res, info); - }); - }) - ), + this.uploadImageToDropbox(req.body.file, req.user as DashUserModel) + .then(uploadUrl => + this.expandImage(uploadUrl, req.body.prompt).then(text => { + if (text.error_code) _error(res, text.message); + else + DashUploadUtils.UploadImage(text.outputs[0].image.url).then(info => { + if (info instanceof Error) _invalid(res, info.message); + else _success(res, info); + }); + }) + ) + .catch(e => _invalid(res, e.message)), }); // construct this url and send user to it. It will allow them to authorize their dropbox account and will send the resulting token to our endpoint /refreshDropbox diff --git a/src/server/DashUploadUtils.ts b/src/server/DashUploadUtils.ts index ed109d8f7..f76371b0d 100644 --- a/src/server/DashUploadUtils.ts +++ b/src/server/DashUploadUtils.ts @@ -221,9 +221,7 @@ export namespace DashUploadUtils { const parseExifData = async (source: string) => { const image = await request.get(source, { encoding: null }); const { /* data, */ error } = await new Promise<{ data: ExifData; error: string | undefined }>(resolve => { - new ExifImage({ image }, (exifError, data) => { - resolve({ data, error: exifError?.message }); - }); + new ExifImage({ image }, (exifError, data) => resolve({ data, error: exifError?.message })); }); return error ? { data: undefined, error } : { data: await exifr.parse(image), error }; }; @@ -295,7 +293,7 @@ export namespace DashUploadUtils { try { // Compute the native width and height ofthe image with an npm module - const { width: nativeWidth, height: nativeHeight } = await requestImageSize(resolvedUrl); + const { width: nativeWidth, height: nativeHeight } = await requestImageSize(resolvedUrl).catch(() => ({ width: 0, height: 0 })); // Bundle up the information into an object return { source, @@ -307,7 +305,6 @@ export namespace DashUploadUtils { ...results, }; } catch (e: unknown) { - console.log(e); return new Error(e ? e.toString?.() : 'unkown error'); } }; -- cgit v1.2.3-70-g09d2 From 9a46e81d662e59413a076b2e0041d1455bc15294 Mon Sep 17 00:00:00 2001 From: bobzel Date: Wed, 23 Apr 2025 20:38:48 -0400 Subject: from last --- .../views/nodes/scrapbook/ScrapbookVersionTwo.tsx | 125 --------------------- src/server/ApiManagers/FireflyManager.ts | 2 +- 2 files changed, 1 insertion(+), 126 deletions(-) delete mode 100644 src/client/views/nodes/scrapbook/ScrapbookVersionTwo.tsx (limited to 'src/server/ApiManagers') diff --git a/src/client/views/nodes/scrapbook/ScrapbookVersionTwo.tsx b/src/client/views/nodes/scrapbook/ScrapbookVersionTwo.tsx deleted file mode 100644 index d15d2fe56..000000000 --- a/src/client/views/nodes/scrapbook/ScrapbookVersionTwo.tsx +++ /dev/null @@ -1,125 +0,0 @@ -//IGNORE FOR NOW, CURRENTLY NOT USED IN SCRAPBOOK IMPLEMENTATION -import { action, makeObservable, observable } from 'mobx'; -import * as React from 'react'; -import { RichTextField } from '../../../../fields/RichTextField'; -import { Docs } from '../../../documents/Documents'; -import { DocumentType } from '../../../documents/DocumentTypes'; -import { ViewBoxAnnotatableComponent } from '../../DocComponent'; -import { FieldView, FieldViewProps } from '../FieldView'; -import { FormattedTextBox, FormattedTextBoxProps } from '../formattedText/FormattedTextBox'; - -export class ScrapbookVersionTwo extends ViewBoxAnnotatableComponent() { - @observable scrapbookDate: string; - - public static LayoutString(fieldStr: string) { - return FieldView.LayoutString(ScrapbookVersionTwo, fieldStr); - } - - constructor(props: FormattedTextBoxProps) { - super(props); - makeObservable(this); - this.scrapbookDate = this.getFormattedDate(); - - console.log('Constructor: Setting initial title and text...'); - this.setDailyTitle(); - this.setDailyText(); - } - - getFormattedDate(): string { - const date = new Date().toLocaleDateString(undefined, { - weekday: 'long', - year: 'numeric', - month: 'long', - day: 'numeric', - }); - console.log('getFormattedDate():', date); - return date; - } - - @action - setDailyTitle() { - console.log('setDailyTitle() called...'); - console.log('Current title before update:', this.dataDoc.title); - - if (!this.dataDoc.title || this.dataDoc.title !== this.scrapbookDate) { - console.log('Updating title to:', this.scrapbookDate); - this.dataDoc.title = this.scrapbookDate; - } - - console.log('New title after update:', this.dataDoc.title); - } - - @action - setDailyText() { - console.log('setDailyText() called...'); - const placeholderText = 'Start writing here...'; - const initialText = `Scrapbook - $\n${placeholderText}`; - - console.log('Checking if dataDoc has text field...'); - - const styles = { - bold: true, // Make the journal date bold - color: 'red', // Set the journal date color to blue - fontSize: 12, // Set the font size to 18px for the whole text - display: 'grid', - gridTemplateColumns: 'repeat(auto-fill, minmax(100px, 1fr))', - gap: '8px', - padding: '10px', - background: '#fafafa', - width: '100%', - height: '100%', - }; - - console.log('Setting new text field with:', initialText); - this.dataDoc[this.fieldKey] = RichTextField.textToRtf( - initialText, - undefined, // No image DocId - styles, // Pass the styles object here - placeholderText.length // The position for text selection - ); - - console.log('Current text field:', this.dataDoc[this.fieldKey]); - } - - componentDidMount(): void { - console.log('componentDidMount() triggered...'); - // bcz: This should be moved into Docs.Create.DailyJournalDocument() - // otherwise, it will override all the text whenever the note is reloaded - this.setDailyTitle(); - this.setDailyText(); - } - - render() { - return ( -
- -
- ); - } -} - -Docs.Prototypes.TemplateMap.set(DocumentType.SCRAPBOOK, { - layout: { view: ScrapbookVersionTwo, dataField: 'text' }, - options: { - acl: '', - _height: 35, - _xMargin: 10, - _yMargin: 10, - _layout_autoHeight: true, - _layout_nativeDimEditable: true, - _layout_reflowVertical: true, - _layout_reflowHorizontal: true, - defaultDoubleClick: 'ignore', - systemIcon: 'BsFileEarmarkTextFill', - }, -}); \ No newline at end of file diff --git a/src/server/ApiManagers/FireflyManager.ts b/src/server/ApiManagers/FireflyManager.ts index fd61f6c9e..0b19f66e0 100644 --- a/src/server/ApiManagers/FireflyManager.ts +++ b/src/server/ApiManagers/FireflyManager.ts @@ -6,9 +6,9 @@ import * as path from 'path'; import { DashUserModel } from '../authentication/DashUserModel'; import { DashUploadUtils } from '../DashUploadUtils'; import { _error, _invalid, _success, Method } from '../RouteManager'; +import { Upload } from '../SharedMediaTypes'; import { Directory, filesDirectory } from '../SocketData'; import ApiManager, { Registration } from './ApiManager'; -import { Upload } from '../SharedMediaTypes'; export default class FireflyManager extends ApiManager { getBearerToken = () => -- cgit v1.2.3-70-g09d2 From 10bd1bd27df3347ec7d50d8c49c1a65730db96a6 Mon Sep 17 00:00:00 2001 From: bobzel Date: Thu, 24 Apr 2025 11:20:03 -0400 Subject: fixed reauthorizing dropboxtoken. --- src/server/ApiManagers/FireflyManager.ts | 32 +++++++++++++++----------------- 1 file changed, 15 insertions(+), 17 deletions(-) (limited to 'src/server/ApiManagers') diff --git a/src/server/ApiManagers/FireflyManager.ts b/src/server/ApiManagers/FireflyManager.ts index 0b19f66e0..5311ca643 100644 --- a/src/server/ApiManagers/FireflyManager.ts +++ b/src/server/ApiManagers/FireflyManager.ts @@ -86,25 +86,23 @@ export default class FireflyManager extends ApiManager { .then(link => resolve(link.result.link)) .catch(linkErr => reject(new Error('Failed to get temporary link: ' + linkErr.message))) ) - .catch(uploadErr => reject(new Error('Failed to upload file to Dropbox: ' + uploadErr.message))); + .catch(uploadErr => { + if (user?.dropboxRefresh) { + console.log('Attempting to refresh Dropbox token for user:', user.email); + this.refreshDropboxToken(user) + .then(token => { + if (!token) return reject(new Error('Failed to refresh Dropbox token.' + user.email)); - uploadToDropbox(dbx).catch(e => { - if (user?.dropboxRefresh) { - console.log('Attempting to refresh Dropbox token for user:', user.email); - this.refreshDropboxToken(user) - .then(token => { - if (!token) { - return reject(new Error('Failed to refresh Dropbox token.' + user.email)); - } + const dbxNew = new Dropbox({ accessToken: token }); + uploadToDropbox(dbxNew).catch(finalErr => reject(new Error('Failed to refresh Dropbox token:' + finalErr.message))); + }) + .catch(refreshErr => reject(new Error('Failed to refresh Dropbox token: ' + refreshErr.message))); + } else { + reject(new Error('Dropbox error: ' + uploadErr.message)); + } + }); - const dbxNew = new Dropbox({ accessToken: token }); - uploadToDropbox(dbxNew).catch(finalErr => reject(new Error('Failed to refresh Dropbox token:' + finalErr.message))); - }) - .catch(refreshErr => reject(new Error('Failed to refresh Dropbox token: ' + refreshErr.message))); - } else { - reject(new Error('Dropbox error: ' + e.message)); - } - }); + uploadToDropbox(dbx); }); }); -- cgit v1.2.3-70-g09d2 From a25dc02334de3f5b58aff1911bdae30d49a1d26b Mon Sep 17 00:00:00 2001 From: bobzel Date: Mon, 28 Apr 2025 12:52:29 -0400 Subject: cleaned up outpainting and Doc resize code. fixes problems with doc resizing of text boxes with ctrl-key. --- src/client/documents/Documents.ts | 2 - src/client/views/DocumentDecorations.tsx | 106 +++++++-------------- src/client/views/nodes/ImageBox.tsx | 10 +- .../views/nodes/formattedText/FormattedTextBox.tsx | 2 +- src/server/ApiManagers/FireflyManager.ts | 8 +- 5 files changed, 39 insertions(+), 89 deletions(-) (limited to 'src/server/ApiManagers') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index e694419a4..a4a668085 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -535,8 +535,6 @@ export class DocumentOptions { * The list of embedded Doc instances in each Scrapbook slot */ scrapbookContents?: List; - - _outpaintingMetadata?: STRt = new StrInfo('serialized JSON metadata needed for image outpainting', false); } export const DocOptions = new DocumentOptions(); diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index 3f11a4713..2d39b827d 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -445,6 +445,12 @@ export class DocumentDecorations extends ObservableReactComponent { SnappingManager.SetIsResizing(DocumentView.Selected().lastElement()?.Document[Id]); // turns off pointer events on things like youtube videos and web pages so that dragging doesn't get "stuck" when cursor moves over them + DocumentView.Selected() + .filter(dv => e.shiftKey && dv.ComponentView instanceof ImageBox) + .forEach(dv => { + dv.Document._outpaintingOriginalWidth = NumCast(dv.Document._width); + dv.Document._outpaintingOriginalHeight = NumCast(dv.Document._height); + }); setupMoveUpEvents(this, e, this.onPointerMove, this.onPointerUp, emptyFunction); e.stopPropagation(); const id = (this._resizeHdlId = e.currentTarget.className); @@ -484,50 +490,31 @@ export class DocumentDecorations extends ObservableReactComponent { + runInAction(() => { // resize selected docs if we're not in the middle of a resize (ie, throttle input events to frame rate) this._interactionLock = true; this._snapPt = thisPt; - // Special handling for shift+click (outpainting mode) - if (e.shiftKey && DocumentView.Selected().some(dv => dv.ComponentView instanceof ImageBox)) { - DocumentView.Selected().forEach(docView => { - if (docView.ComponentView instanceof ImageBox) { - // Set flag for outpainting mode - docView.Document._outpaintingResize = true; + const notOutpainted = DocumentView.Selected().filter(dv => !e.shiftKey || !(dv.ComponentView instanceof ImageBox)); + // Special handling for shift-drag resize (outpainting of Images) + DocumentView.Selected() + .filter(dv => !notOutpainted.includes(dv)) + .forEach(dv => this.resizeViewForOutpainting(dv, refPt, scale, { dragHdl, shiftKey: e.shiftKey })); // Adjust only the document dimensions without scaling internal content - // Adjust only the document dimensions without scaling internal content - this.resizeViewForOutpainting(docView, refPt, scale, { dragHdl, shiftKey: e.shiftKey }); - } else { - // Handle regular resize for non-image components - e.ctrlKey && !Doc.NativeHeight(docView.Document) && docView.toggleNativeDimensions(); - const hasFixedAspect = this.hasFixedAspect(docView.Document); - const scaleAspect = { x: scale.x === 1 && hasFixedAspect ? scale.y : scale.x, y: scale.x !== 1 && hasFixedAspect ? scale.x : scale.y }; - this.resizeView(docView, refPt, scaleAspect, { dragHdl, ctrlKey: e.ctrlKey }); - } - }); - } else { - // Regular resize behavior (existing code) - e.ctrlKey && DocumentView.Selected().forEach(docView => !Doc.NativeHeight(docView.Document) && docView.toggleNativeDimensions()); - const hasFixedAspect = DocumentView.Selected() - .map(dv => dv.Document) - .some(this.hasFixedAspect); - const scaleAspect = { x: scale.x === 1 && hasFixedAspect ? scale.y : scale.x, y: scale.x !== 1 && hasFixedAspect ? scale.x : scale.y }; - DocumentView.Selected().forEach(docView => this.resizeView(docView, refPt, scaleAspect, { dragHdl, ctrlKey: e.ctrlKey })); - } + // Regular resize behavior for docs not being outpainted + e.ctrlKey && notOutpainted.forEach(docView => !Doc.NativeHeight(docView.Document) && docView.toggleNativeDimensions()); + const hasFixedAspect = notOutpainted.map(dv => dv.Document).some(this.hasFixedAspect); + const scaleAspect = { x: scale.x === 1 && hasFixedAspect ? scale.y : scale.x, y: scale.x !== 1 && hasFixedAspect ? scale.x : scale.y }; + notOutpainted.forEach(docView => this.resizeView(docView, refPt, scaleAspect, { dragHdl, freezeNativeDims: e.ctrlKey })); - await new Promise(res => { - setTimeout(() => { - res((this._interactionLock = undefined)); - }); - }); + new Promise(res => setTimeout(() => res((this._interactionLock = undefined)))); }); return false; @@ -545,11 +532,6 @@ export class DocumentDecorations extends ObservableReactComponent { + onPointerUp = (e: PointerEvent): void => { SnappingManager.SetIsResizing(undefined); SnappingManager.clearSnapLines(); // Check if any outpainting needs to be processed - DocumentView.Selected().forEach(view => { - if (view.Document._needsOutpainting && view.ComponentView instanceof ImageBox) { - // Trigger outpainting process in the ImageBox component - (view.ComponentView as ImageBox).processOutpainting(); - - // Clear flags - view.Document._needsOutpainting = false; - view.Document._outpaintingResize = false; - } - }); + DocumentView.Selected() + .filter(dv => e.shiftKey && dv.ComponentView instanceof ImageBox) + .forEach(view => (view.ComponentView as ImageBox).processOutpainting()); this._resizeHdlId = ''; this._resizeUndo?.end(); @@ -647,7 +608,7 @@ export class DocumentDecorations extends ObservableReactComponent { + resizeView = (docView: DocumentView, refPt: number[], scale: { x: number; y: number }, opts: { dragHdl: string; freezeNativeDims: boolean }) => { const doc = docView.Document; if (doc.isGroup) { DocListCast(doc.data) @@ -660,25 +621,24 @@ export class DocumentDecorations extends ObservableReactComponent() { }, { fireImmediately: true } ); - this._disposers.outpainting = reaction( - () => this.Document?._needsOutpainting, - needsOutpainting => { - if (needsOutpainting && this.Document?._outpaintingResize) { - this.processOutpainting(); - } - } - ); } componentWillUnmount() { @@ -421,7 +413,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent() { const response = await Networking.PostToServer('/outpaintImage', { imageUrl: currentPath, prompt: customPrompt, - originalDimensions: { width: origWidth, height: origHeight }, + originalDimensions: { width: Math.min(newWidth, origWidth), height: Math.min(newHeight, origHeight) }, newDimensions: { width: newWidth, height: newHeight }, }); diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index 164c64107..98e461a52 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -1149,7 +1149,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent { this.layoutDoc['_' + this.fieldKey + '_height'] = scrollHeight; - if (!this.layoutDoc.isTemplateForField) this.layoutDoc._nativeHeight = scrollHeight; + if (!this.layoutDoc.isTemplateForField && NumCast(this.layoutDoc._nativeHeight)) this.layoutDoc._nativeHeight = scrollHeight; }); addPlugin = (plugin: Plugin) => { diff --git a/src/server/ApiManagers/FireflyManager.ts b/src/server/ApiManagers/FireflyManager.ts index 5311ca643..63581d3b3 100644 --- a/src/server/ApiManagers/FireflyManager.ts +++ b/src/server/ApiManagers/FireflyManager.ts @@ -343,10 +343,10 @@ export default class FireflyManager extends ApiManager { numVariations: 1, placement: { inset: { - left: 0, - top: 0, - right: Math.round(req.body.newDimensions.width - req.body.originalDimensions.width), - bottom: Math.round(req.body.newDimensions.height - req.body.originalDimensions.height), + left: Math.round(req.body.newDimensions.width - req.body.originalDimensions.width) / 2, + top: Math.round(req.body.newDimensions.height - req.body.originalDimensions.height) / 2, + right: Math.round(req.body.newDimensions.width - req.body.originalDimensions.width) / 2, + bottom: Math.round(req.body.newDimensions.height - req.body.originalDimensions.height) / 2, }, alignment: { horizontal: 'center', -- cgit v1.2.3-70-g09d2 From efa1501b38aa71f306ae0717afc86f0803c71be2 Mon Sep 17 00:00:00 2001 From: bobzel Date: Mon, 28 Apr 2025 15:43:21 -0400 Subject: fix for outpainting alignment --- src/client/views/nodes/ImageBox.tsx | 2 ++ src/server/ApiManagers/FireflyManager.ts | 8 ++++---- 2 files changed, 6 insertions(+), 4 deletions(-) (limited to 'src/server/ApiManagers') diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx index 197ef0998..004c2da44 100644 --- a/src/client/views/nodes/ImageBox.tsx +++ b/src/client/views/nodes/ImageBox.tsx @@ -444,6 +444,8 @@ export class ImageBox extends ViewBoxAnnotatableComponent() { this.Document.$ai = true; this.Document.$ai_outpainted = true; this.Document.$ai_outpaint_prompt = customPrompt; + this.Document._outpaintingOriginalWidth = undefined; + this.Document._outpaintingOriginalHeight = undefined; } else { this.Document._width = origWidth; this.Document._height = origHeight; diff --git a/src/server/ApiManagers/FireflyManager.ts b/src/server/ApiManagers/FireflyManager.ts index 63581d3b3..1b8a85a5c 100644 --- a/src/server/ApiManagers/FireflyManager.ts +++ b/src/server/ApiManagers/FireflyManager.ts @@ -343,10 +343,10 @@ export default class FireflyManager extends ApiManager { numVariations: 1, placement: { inset: { - left: Math.round(req.body.newDimensions.width - req.body.originalDimensions.width) / 2, - top: Math.round(req.body.newDimensions.height - req.body.originalDimensions.height) / 2, - right: Math.round(req.body.newDimensions.width - req.body.originalDimensions.width) / 2, - bottom: Math.round(req.body.newDimensions.height - req.body.originalDimensions.height) / 2, + left: Math.round((req.body.newDimensions.width - req.body.originalDimensions.width) / 2), + top: Math.round((req.body.newDimensions.height - req.body.originalDimensions.height) / 2), + right: Math.round((req.body.newDimensions.width - req.body.originalDimensions.width) / 2), + bottom: Math.round((req.body.newDimensions.height - req.body.originalDimensions.height) / 2), }, alignment: { horizontal: 'center', -- cgit v1.2.3-70-g09d2 From d4659e2bd3ddb947683948083232c26fb1227f39 Mon Sep 17 00:00:00 2001 From: bobzel Date: Fri, 2 May 2025 13:09:24 -0400 Subject: fixed up image outpaint dialog and added options for placementt. --- src/client/views/nodes/ImageBox.scss | 7 +- src/client/views/nodes/ImageBox.tsx | 156 +++++++++++++++++++++++++------ src/server/ApiManagers/FireflyManager.ts | 12 +-- 3 files changed, 135 insertions(+), 40 deletions(-) (limited to 'src/server/ApiManagers') diff --git a/src/client/views/nodes/ImageBox.scss b/src/client/views/nodes/ImageBox.scss index 3adc16879..9f7a5d03f 100644 --- a/src/client/views/nodes/ImageBox.scss +++ b/src/client/views/nodes/ImageBox.scss @@ -242,12 +242,9 @@ } } .imageBox-regenerate-dialog { - position: fixed; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); + position: absolute; background: white; - padding: 20px; + padding: 10px; border-radius: 8px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2); z-index: 10000; diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx index 31a135fa7..d16baada6 100644 --- a/src/client/views/nodes/ImageBox.tsx +++ b/src/client/views/nodes/ImageBox.tsx @@ -1,4 +1,4 @@ -import { Button, Colors, EditableText, Size, Type } from '@dash/components'; +import { Button, Colors, EditableText, IconButton, Size, Toggle, ToggleType, Type } from '@dash/components'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { Slider, Tooltip } from '@mui/material'; import axios from 'axios'; @@ -353,6 +353,8 @@ export class ImageBox extends ViewBoxAnnotatableComponent() { @action openOutpaintPrompt = () => { + this._outpaintVAlign = ''; + this._outpaintAlign = ''; this._showOutpaintPrompt = true; }; @@ -361,6 +363,16 @@ export class ImageBox extends ViewBoxAnnotatableComponent() { this._showOutpaintPrompt = false; }; + @action + cancelOutpaintPrompt = () => { + const origWidth = NumCast(this.Document[this.fieldKey + '_outpaintOriginalWidth']); + const origHeight = NumCast(this.Document[this.fieldKey + '_outpaintOriginalHeight']); + this.Document._width = origWidth; + this.Document._height = origHeight; + this._outpaintingInProgress = false; + this.closeOutpaintPrompt(); + }; + @action handlePromptChange = (val: string | number) => { this._outpaintPromptInput = '' + val; @@ -377,17 +389,12 @@ export class ImageBox extends ViewBoxAnnotatableComponent() { const field = Cast(this.dataDoc[this.fieldKey], ImageField); if (!field) return; - const origWidth = NumCast(this.Document[this.fieldKey + '_outpaintOriginalWidth']); - const origHeight = NumCast(this.Document[this.fieldKey + '_outpaintOriginalHeight']); - // Set flag that outpainting is in progress this._outpaintingInProgress = true; // Revert dimensions if prompt is blank (acts like Cancel) if (!customPrompt) { - this.Document._width = origWidth; - this.Document._height = origHeight; - this._outpaintingInProgress = false; + this.cancelOutpaintPrompt(); return; } @@ -410,11 +417,15 @@ export class ImageBox extends ViewBoxAnnotatableComponent() { loadingOverlay.innerHTML = '
Generating outpainted image...
'; this._mainCont?.appendChild(loadingOverlay); + const origWidth = NumCast(this.Document[this.fieldKey + '_outpaintOriginalWidth']); + const origHeight = NumCast(this.Document[this.fieldKey + '_outpaintOriginalHeight']); const response = await Networking.PostToServer('/outpaintImage', { imageUrl: currentPath, prompt: customPrompt, originalDimensions: { width: Math.min(newWidth, origWidth), height: Math.min(newHeight, origHeight) }, newDimensions: { width: newWidth, height: newHeight }, + halignment: this._outpaintAlign, + valignment: this._outpaintVAlign, }); const error = ('error' in response && (response.error as string)) || ''; @@ -447,8 +458,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent() { this.Document[this.fieldKey + '_outpaintOriginalWidth'] = undefined; this.Document[this.fieldKey + '_outpaintOriginalHeight'] = undefined; } else { - this.Document._width = origWidth; - this.Document._height = origHeight; + this.cancelOutpaintPrompt(); alert('Failed to receive a valid image URL from server.'); } batch.end(); @@ -456,30 +466,106 @@ export class ImageBox extends ViewBoxAnnotatableComponent() { this._mainCont?.removeChild(loadingOverlay); } catch (error) { - console.error('Error during outpainting:', error); - this.Document._width = origWidth; - this.Document._height = origHeight; - alert('An error occurred while outpainting. Please try again.'); + this.cancelOutpaintPrompt(); + alert('An error occurred while outpainting.' + error); } finally { runInAction(() => (this._outpaintingInProgress = false)); } }; - componentUI = () => + @observable _outpaintAlign = ''; + @observable _outpaintVAlign = ''; + @computed get outpaintVertical() { + return this._props.PanelWidth() / this._props.PanelHeight() < this.nativeSize.nativeWidth / this.nativeSize.nativeHeight; + } + + componentUI = (/* boundsLeft: number, boundsTop: number*/) => !this._showOutpaintPrompt ? null : ( -
-

Outpaint Image

- this.handlePromptChange(val)} - val={this._outpaintPromptInput} - type={Type.TERT} - color={SettingsManager.userColor} - background={SettingsManager.userBackgroundColor} - /> -
-