aboutsummaryrefslogtreecommitdiff
path: root/src/client/views/nodes/ImageBox.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'src/client/views/nodes/ImageBox.tsx')
-rw-r--r--src/client/views/nodes/ImageBox.tsx61
1 files changed, 48 insertions, 13 deletions
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index 8ed59c6e1..5b738ee19 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -8,7 +8,7 @@ import { extname } from 'path';
import * as React from 'react';
import { AiOutlineSend } from 'react-icons/ai';
import ReactLoading from 'react-loading';
-import { ClientUtils, DashColor, returnEmptyString, returnFalse, returnOne, returnZero, setupMoveUpEvents, UpdateIcon } from '../../../ClientUtils';
+import { ClientUtils, imageUrlToBase64, DashColor, returnEmptyString, returnFalse, returnOne, returnZero, setupMoveUpEvents, UpdateIcon, returnTrue } from '../../../ClientUtils';
import { Doc, DocListCast, Opt } from '../../../fields/Doc';
import { DocData } from '../../../fields/DocSymbols';
import { Id } from '../../../fields/FieldSymbols';
@@ -16,7 +16,7 @@ import { InkTool } from '../../../fields/InkField';
import { List } from '../../../fields/List';
import { ObjectField } from '../../../fields/ObjectField';
import { ComputedField } from '../../../fields/ScriptField';
-import { Cast, DocCast, ImageCast, NumCast, RTFCast, StrCast } from '../../../fields/Types';
+import { Cast, DocCast, ImageCast, NumCast, RTFCast, StrCast, ImageCastWithSuffix } from '../../../fields/Types';
import { ImageField } from '../../../fields/URLField';
import { TraceMobx } from '../../../fields/util';
import { emptyFunction } from '../../../Utils';
@@ -45,6 +45,7 @@ import { FieldView, FieldViewProps } from './FieldView';
import { FocusViewOptions } from './FocusViewOptions';
import './ImageBox.scss';
import { OpenWhere } from './OpenWhere';
+import { gptImageLabel } from '../../apis/gpt/GPT';
const DefaultPath = '/assets/unknown-file-icon-hi.png';
export class ImageEditorData {
@@ -112,7 +113,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
this._props.setContentViewBox?.(this);
}
- @computed get oupaintOriginalSize(): { width: number; height: number } {
+ @computed get outpaintOriginalSize(): { width: number; height: number } {
return {
width: NumCast(this.Document[this.fieldKey + '_outpaintOriginalWidth']),
height: NumCast(this.Document[this.fieldKey + '_outpaintOriginalHeight']),
@@ -139,6 +140,42 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
this._dropDisposer?.();
ele && (this._dropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this), this.Document));
};
+
+ autoTag = async () => {
+ if (this.Document.$tags_chat) return;
+ try {
+ // 1) grab the full-size URL
+ const layoutKey = Doc.LayoutDataKey(this.Document);
+ const url = ImageCastWithSuffix(this.Document[layoutKey], '_o');
+ if (!url) throw new Error('No image URL found');
+
+ // 2) convert to base64
+ const base64 = await imageUrlToBase64(url);
+ if (!base64) throw new Error('Failed to load image data');
+
+ // 3) ask GPT for labels one label: PERSON or LANDSCAPE
+ const label = await gptImageLabel(
+ base64,
+ `Classify this image as PERSON or LANDSCAPE. You may only respond with one of these two options.
+ Then provide five additional descriptive tags to describe the image for a total of 6 words outputted, delimited by spaces.
+ For example: "LANDSCAPE BUNNY NATURE FOREST PEACEFUL OUTDOORS".
+ Then add one final lengthier summary tag (separated by underscores) that describes the image.`
+ ).then(raw => raw.trim().toUpperCase());
+
+ const { nativeWidth, nativeHeight } = this.nativeSize;
+ const aspectRatio = ((nativeWidth || 1) / (nativeHeight || 1)).toFixed(2);
+
+ // 5) stash it on the Doc
+ // overwrite any old tags so re-runs still work
+ this.Document.$tags_chat = new List<string>([...label.split(/\s+/), `ASPECT_${aspectRatio}`]);
+
+ // 6) flip on “show tags” in the layout
+ this.Document._layout_showTags = true;
+ } catch (err) {
+ console.error('autoTag failed:', err);
+ }
+ };
+
getAnchor = (addAsAnnotation: boolean, pinProps?: PinProps) => {
const visibleAnchor = this._getAnchor?.(this._savedAnnotations, true); // use marquee anchor, otherwise, save zoom/pan as anchor
const anchor =
@@ -225,9 +262,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
}
};
- handleSelection = async (selection: string) => {
- this._searchInput = selection;
- };
+ handleSelection = (selection: string) => (this._searchInput = selection);
drop = undoable(
action((e: Event, de: DragManager.DropEvent) => {
@@ -385,16 +420,14 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
@action
cancelOutpaintPrompt = () => {
- [this.Document._width, this.Document._height] = [this.oupaintOriginalSize.width, this.oupaintOriginalSize.height];
+ [this.Document._width, this.Document._height] = [this.outpaintOriginalSize.width, this.outpaintOriginalSize.height];
this._outpaintingInProgress = false;
this.outpaintOriginalSize = undefined;
this.closeOutpaintPrompt();
};
@action
- handlePromptChange = (val: string | number) => {
- this._outpaintPromptInput = '' + val;
- };
+ handlePromptChange = (val: string | number) => (this._outpaintPromptInput = '' + val);
@action
submitOutpaintPrompt = () => {
@@ -435,7 +468,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
loadingOverlay.innerHTML = '<div style="color: white; font-size: 16px;">Generating outpainted image...</div>';
this._mainCont?.appendChild(loadingOverlay);
- const { width: origWidth, height: origHeight } = this.oupaintOriginalSize;
+ const { width: origWidth, height: origHeight } = this.outpaintOriginalSize;
const response = await Networking.PostToServer('/outpaintImage', {
imageUrl: currentPath,
prompt: customPrompt,
@@ -495,6 +528,8 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
return this._props.PanelWidth() / this._props.PanelHeight() < this.nativeSize.nativeWidth / this.nativeSize.nativeHeight;
}
+ isOutpaintable = () => true;
+
componentUI = (/* boundsLeft: number, boundsTop: number*/) =>
!this._showOutpaintPrompt ? null : (
<div
@@ -959,11 +994,11 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
return { width, height };
};
savedAnnotations = () => this._savedAnnotations;
+ showBorderRounding = returnTrue;
rejectDrop = (de: DragManager.DropEvent, subView?: DocumentView | undefined) => (this.dataDoc[this.fieldKey] === undefined ? true : (this._props.rejectDrop?.(de, subView) ?? false));
render() {
TraceMobx();
- const borderRad = this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.BorderRounding) as string;
- const borderRadius = borderRad?.includes('px') ? `${Number(borderRad.split('px')[0]) / (this._props.NativeDimScaling?.() || 1)}px` : borderRad;
+ const borderRadius = this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.BorderRounding) as string;
return (
<>
<div