diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/client/views/nodes/ImageBox.scss | 32 | ||||
-rw-r--r-- | src/client/views/nodes/ImageBox.tsx | 494 |
2 files changed, 351 insertions, 175 deletions
diff --git a/src/client/views/nodes/ImageBox.scss b/src/client/views/nodes/ImageBox.scss index 3d6942e6f..d7a14a9df 100644 --- a/src/client/views/nodes/ImageBox.scss +++ b/src/client/views/nodes/ImageBox.scss @@ -245,3 +245,35 @@ color: black; } } +.imageBox-regenerate-dialog { + position: fixed; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + background: white; + padding: 20px; + border-radius: 8px; + box-shadow: 0 4px 12px rgba(0,0,0,0.2); + z-index: 10000; + + h3 { margin-top: 0; } + + input { + width: 300px; + padding: 8px; + margin-bottom: 10px; + } + + .buttons { + display: flex; + justify-content: flex-end; + gap: 10px; + + .generate-btn { + background: #0078d4; + color: white; + border: none; + padding: 8px 16px; + } + } + }
\ No newline at end of file diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx index 14adfaf1e..033b0b5a2 100644 --- a/src/client/views/nodes/ImageBox.tsx +++ b/src/client/views/nodes/ImageBox.tsx @@ -353,197 +353,340 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { } }); - // Add this method to process outpainting when resize is complete - @action - processOutpainting = async () => { - const field = Cast(this.dataDoc[this.fieldKey], ImageField); - if (!field) return; + @observable _showOutpaintPrompt: boolean = false; +@observable _outpaintPromptInput: string = 'Extend this image naturally with matching content'; + +@action +openOutpaintPrompt = () => { + this._showOutpaintPrompt = true; +}; + +@action +closeOutpaintPrompt = () => { + this._showOutpaintPrompt = false; +}; + +@action +handlePromptChange = (e: React.ChangeEvent<HTMLInputElement>) => { + this._outpaintPromptInput = e.target.value; +}; + +@action +submitOutpaintPrompt = () => { + this.closeOutpaintPrompt(); + this.processOutpaintingWithPrompt(this._outpaintPromptInput); +}; + +@action +processOutpaintingWithPrompt = async (customPrompt: string) => { + 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.'); + return; + } - const origWidth = NumCast(this.Document._outpaintingOriginalWidth); - const origHeight = NumCast(this.Document._outpaintingOriginalHeight); + // Set flag that outpainting is in progress + this._outpaintingInProgress = true; - if (!origWidth || !origHeight) { - console.error('Original dimensions (_outpaintingOriginalWidth/_outpaintingOriginalHeight) not set. Ensure resizeViewForOutpainting was called first.'); + try { + const currentPath = this.choosePath(field.url); + const newWidth = NumCast(this.Document._width); + const newHeight = NumCast(this.Document._height); + + // Revert dimensions if prompt is blank (acts like Cancel) + if (!customPrompt) { + this.Document._width = origWidth; + this.Document._height = origHeight; + this._outpaintingInProgress = false; 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<string>(resolve => { - const dialog = document.createElement('div'); - Object.assign(dialog.style, { - position: 'fixed', - top: '50%', - left: '50%', - transform: 'translate(-50%, -50%)', - background: 'white', - padding: '20px', - borderRadius: '8px', - boxShadow: '0 4px 12px rgba(0,0,0,0.2)', - 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'; - input.value = 'Extend this image naturally with matching content'; - Object.assign(input.style, { - width: '300px', - padding: '8px', - marginBottom: '10px', - }); - - const buttonContainer = document.createElement('div'); - Object.assign(buttonContainer.style, { - display: 'flex', - justifyContent: 'flex-end', - 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', - }); - - 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(''); - }; + // Optional: add loading indicator + const loadingOverlay = document.createElement('div'); + loadingOverlay.style.position = 'absolute'; + loadingOverlay.style.top = '0'; + loadingOverlay.style.left = '0'; + loadingOverlay.style.width = '100%'; + loadingOverlay.style.height = '100%'; + loadingOverlay.style.background = 'rgba(0,0,0,0.5)'; + loadingOverlay.style.display = 'flex'; + loadingOverlay.style.justifyContent = 'center'; + loadingOverlay.style.alignItems = 'center'; + loadingOverlay.innerHTML = '<div style="color: white; font-size: 16px;">Generating outpainted image...</div>'; + this._mainCont?.appendChild(loadingOverlay); + + const response = await Networking.PostToServer('/outpaintImage', { + imageUrl: currentPath, + prompt: customPrompt, + originalDimensions: { width: origWidth, height: origHeight }, + newDimensions: { width: newWidth, height: newHeight }, + }); - confirmButton.onclick = () => { - const promptValue = input.value; - document.body.removeChild(dialog); - resolve(promptValue); - }; - }); + if (response && typeof response === 'object' && 'url' in response && typeof response.url === 'string') { + console.log('Received outpainted image:', response.url); - // If user cancelled, reset dimensions to original - if (!customPrompt) { - this.Document._width = origWidth; - this.Document._height = origHeight; - this._outpaintingInProgress = false; - return; + if (!this.dataDoc[this.fieldKey + '_alternates']) { + this.dataDoc[this.fieldKey + '_alternates'] = new List<Doc>(); } - // Show loading indicator - const loadingOverlay = document.createElement('div'); - loadingOverlay.style.position = 'absolute'; - loadingOverlay.style.top = '0'; - loadingOverlay.style.left = '0'; - loadingOverlay.style.width = '100%'; - loadingOverlay.style.height = '100%'; - loadingOverlay.style.background = 'rgba(0,0,0,0.5)'; - loadingOverlay.style.display = 'flex'; - loadingOverlay.style.justifyContent = 'center'; - loadingOverlay.style.alignItems = 'center'; - loadingOverlay.innerHTML = '<div style="color: white; font-size: 16px;">Generating outpainted image...</div>'; - this._mainCont?.appendChild(loadingOverlay); - - // Call the outpaint API - const response = await Networking.PostToServer('/outpaintImage', { - imageUrl: currentPath, - prompt: customPrompt, - originalDimensions: { - width: origWidth, - height: origHeight, - }, - newDimensions: { - width: newWidth, - height: newHeight, - }, + const originalDoc = Docs.Create.ImageDocument(field.url.href, { + title: `Original: ${this.Document.title}`, + _nativeWidth: Doc.NativeWidth(this.dataDoc), + _nativeHeight: Doc.NativeHeight(this.dataDoc), }); - if (response && typeof response === 'object' && 'url' in response && typeof response.url === 'string') { - 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.'); - } - - 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<Doc>(); - } - - // 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), - }); - - // 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); + Doc.AddDocToList(this.dataDoc, this.fieldKey + '_alternates', originalDoc); - // Update native dimensions - Doc.SetNativeWidth(this.dataDoc, newWidth); - Doc.SetNativeHeight(this.dataDoc, newHeight); + // Replace with new outpainted image + this.dataDoc[this.fieldKey] = new ImageField(response.url); - // Add AI metadata - this.Document.ai = true; - this.Document.ai_outpainted = true; - this.Document.ai_outpaint_prompt = customPrompt; - } else { - // If failed, revert to original dimensions - this.Document._width = origWidth; - this.Document._height = origHeight; - alert('Failed to outpaint image. Please try again.'); - } + Doc.SetNativeWidth(this.dataDoc, newWidth); + Doc.SetNativeHeight(this.dataDoc, newHeight); - // Remove loading overlay - this._mainCont?.removeChild(loadingOverlay); - } catch (error) { - console.error('Error during outpainting:', error); - // Revert to original dimensions on error + this.Document.ai = true; + this.Document.ai_outpainted = true; + this.Document.ai_outpaint_prompt = customPrompt; + } else { + console.error('Unexpected API response:', response); 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; - delete this.Document._originalDims; + alert('Failed to receive a valid image URL from server.'); } - }; + + 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.'); + } finally { + this._outpaintingInProgress = false; + delete this.Document._originalDims; + } +}; + +processOutpainting = () => { + this.openOutpaintPrompt(); +}; + +componentUI = () => ( + <> + {this._showOutpaintPrompt && ( + <div className="imageBox-regenerate-dialog"> + <h3>Outpaint Image</h3> + <p>Enter a prompt for extending the image:</p> + <input + type="text" + value={this._outpaintPromptInput} + onChange={this.handlePromptChange} + /> + <div className="buttons"> + <button onClick={this.closeOutpaintPrompt}>Cancel</button> + <button className="generate-btn" onClick={this.submitOutpaintPrompt}> + Generate + </button> + </div> + </div> + )} + </> +); + + // // Add this method to process outpainting when resize is complete + // @action + // 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.'); + // 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<string>(resolve => { + // const dialog = document.createElement('div'); + // Object.assign(dialog.style, { + // position: 'fixed', + // top: '50%', + // left: '50%', + // transform: 'translate(-50%, -50%)', + // background: 'white', + // padding: '20px', + // borderRadius: '8px', + // boxShadow: '0 4px 12px rgba(0,0,0,0.2)', + // 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'; + // input.value = 'Extend this image naturally with matching content'; + // Object.assign(input.style, { + // width: '300px', + // padding: '8px', + // marginBottom: '10px', + // }); + + // const buttonContainer = document.createElement('div'); + // Object.assign(buttonContainer.style, { + // display: 'flex', + // justifyContent: 'flex-end', + // 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', + // }); + + // 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(''); + // }; + + // 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; + // this.Document._height = origHeight; + // this._outpaintingInProgress = false; + // return; + // } + + // // Show loading indicator + // const loadingOverlay = document.createElement('div'); + // loadingOverlay.style.position = 'absolute'; + // loadingOverlay.style.top = '0'; + // loadingOverlay.style.left = '0'; + // loadingOverlay.style.width = '100%'; + // loadingOverlay.style.height = '100%'; + // loadingOverlay.style.background = 'rgba(0,0,0,0.5)'; + // loadingOverlay.style.display = 'flex'; + // loadingOverlay.style.justifyContent = 'center'; + // loadingOverlay.style.alignItems = 'center'; + // loadingOverlay.innerHTML = '<div style="color: white; font-size: 16px;">Generating outpainted image...</div>'; + // this._mainCont?.appendChild(loadingOverlay); + + // // Call the outpaint API + // const response = await Networking.PostToServer('/outpaintImage', { + // imageUrl: currentPath, + // prompt: customPrompt, + // originalDimensions: { + // width: origWidth, + // height: origHeight, + // }, + // newDimensions: { + // width: newWidth, + // height: newHeight, + // }, + // }); + // if (response && typeof response === 'object' && 'url' in response && typeof response.url === 'string') { + // 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.'); + // } + + // 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<Doc>(); + // } + + // // 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), + // }); + + // // 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; + // this.Document.ai_outpaint_prompt = customPrompt; + // } else { + // // If failed, revert to original dimensions + // this.Document._width = origWidth; + // this.Document._height = origHeight; + // alert('Failed to outpaint image. Please try again.'); + // } + + // // Remove loading overlay + // this._mainCont?.removeChild(loadingOverlay); + // } catch (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.'); + // } finally { + // // Clear the outpainting flags + // this._outpaintingInProgress = false; + // delete this.Document._originalDims; + // } + // }; specificContextMenu = (): void => { const field = Cast(this.dataDoc[this.fieldKey], ImageField); @@ -586,10 +729,11 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { funcs.push({ description: 'Outpaint Image', event: () => { - this.processOutpainting(); + this.openOutpaintPrompt(); }, icon: 'brush', }); + // Add outpainting history option if the image was outpainted this.Document.ai_outpainted && funcs.push({ |