diff options
Diffstat (limited to 'src/client/views/collections')
4 files changed, 108 insertions, 22 deletions
diff --git a/src/client/views/collections/collectionFreeForm/ImageLabelBox.tsx b/src/client/views/collections/collectionFreeForm/ImageLabelBox.tsx index ff9fb14e7..e3a3f9b05 100644 --- a/src/client/views/collections/collectionFreeForm/ImageLabelBox.tsx +++ b/src/client/views/collections/collectionFreeForm/ImageLabelBox.tsx @@ -160,15 +160,16 @@ export class ImageLabelBox extends ViewBoxBaseComponent<FieldViewProps>() { classifyImagesInBox = async () => { this.startLoading(); + const selectedImages = this._selectedImages; // Converts the images into a Base64 format, afterwhich the information is sent to GPT to label them. - const imageInfos = this._selectedImages.map(async doc => { + const imageInfos = selectedImages.map(async doc => { if (!doc.$tags_chat) { const url = ImageCastWithSuffix(doc[Doc.LayoutDataKey(doc)], '_o') ?? ''; return imageUrlToBase64(url).then(hrefBase64 => !hrefBase64 ? undefined : - gptImageLabel(hrefBase64,'Give three labels to describe this image.').then(labels => + gptImageLabel(hrefBase64, 'Give three labels to describe this image.').then(labels => ({ doc, labels }))) ; // prettier-ignore } }); diff --git a/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx b/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx index abd828945..2ec59e5d5 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx @@ -16,6 +16,7 @@ export class MarqueeOptionsMenu extends AntimodeMenu<AntimodeMenuProps> { public createCollection: (e: KeyboardEvent | React.PointerEvent | undefined, group?: boolean, selection?: Doc[]) => Doc | void = unimplementedFunction; public delete: (e: KeyboardEvent | React.PointerEvent | undefined) => void = unimplementedFunction; public summarize: (e: KeyboardEvent | React.PointerEvent | undefined) => void = unimplementedFunction; + public generateScrapbook: (e: KeyboardEvent | React.PointerEvent | undefined) => void = unimplementedFunction; public showMarquee: () => void = unimplementedFunction; public hideMarquee: () => void = unimplementedFunction; public pinWithView: (e: KeyboardEvent | React.PointerEvent | undefined) => void = unimplementedFunction; @@ -38,6 +39,7 @@ export class MarqueeOptionsMenu extends AntimodeMenu<AntimodeMenuProps> { <IconButton tooltip="Create a Collection" onPointerDown={this.createCollection} icon={<FontAwesomeIcon icon="object-group" />} color={this.userColor} /> <IconButton tooltip="Create a Grouping" onPointerDown={e => this.createCollection(e, true)} icon={<FontAwesomeIcon icon="layer-group" />} color={this.userColor} /> <IconButton tooltip="Summarize Documents" onPointerDown={this.summarize} icon={<FontAwesomeIcon icon="compress-arrows-alt" />} color={this.userColor} /> + <IconButton tooltip="Generate Scrapbook" onPointerDown={this.generateScrapbook} icon={<FontAwesomeIcon icon="palette" />} color={this.userColor} /> <IconButton tooltip="Delete Documents" onPointerDown={this.delete} icon={<FontAwesomeIcon icon="trash-alt" />} color={this.userColor} /> <IconButton tooltip="Pin selected region" onPointerDown={this.pinWithView} icon={<FontAwesomeIcon icon="map-pin" />} color={this.userColor} /> <IconButton tooltip="Classify and Sort Images" onPointerDown={this.classifyImages} icon={<FontAwesomeIcon icon="object-group" />} color={this.userColor} /> diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.scss b/src/client/views/collections/collectionFreeForm/MarqueeView.scss index af787ed00..135f4deac 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.scss +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.scss @@ -28,4 +28,4 @@ .marquee-legend::after { content: 'Press <space> for lasso'; } -} +}
\ No newline at end of file diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index c120cddf0..128606675 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -28,6 +28,9 @@ import { FormattedTextBox } from '../../nodes/formattedText/FormattedTextBox'; import { SubCollectionViewProps } from '../CollectionSubView'; import { ImageLabelBoxData } from './ImageLabelBox'; import { MarqueeOptionsMenu } from './MarqueeOptionsMenu'; +import { StrListCast } from '../../../../fields/Doc'; +import { requestAiGeneratedPreset, DocumentDescriptor } from '../../nodes/scrapbook/AIPresetGenerator'; +import { buildPlaceholdersFromConfigs, slotRealDocIntoPlaceholders } from '../../nodes/scrapbook/ScrapbookBox'; import './MarqueeView.scss'; interface MarqueeViewProps { @@ -76,6 +79,11 @@ export class MarqueeView extends ObservableReactComponent<SubCollectionViewProps @observable _labelsVisibile: boolean = false; @observable _lassoPts: [number, number][] = []; @observable _lassoFreehand: boolean = false; + // ─── New Observables for “Pick 1 of N AI Scrapbook” ─── + @observable aiChoices: Doc[] = []; // temporary hidden Scrapbook docs + @observable pickerX = 0; // popup x coordinate + @observable pickerY = 0; // popup y coordinate + @observable pickerVisible = false; // show/hide ScrapbookPicker @computed get Transform() { return this._props.getTransform(); @@ -276,6 +284,7 @@ export class MarqueeView extends ObservableReactComponent<SubCollectionViewProps MarqueeOptionsMenu.Instance.createCollection = this.collection; MarqueeOptionsMenu.Instance.delete = this.delete; MarqueeOptionsMenu.Instance.summarize = this.summary; + MarqueeOptionsMenu.Instance.generateScrapbook = this.generateScrapbook; MarqueeOptionsMenu.Instance.showMarquee = this.showMarquee; MarqueeOptionsMenu.Instance.hideMarquee = this.hideMarquee; MarqueeOptionsMenu.Instance.jumpTo(e.clientX, e.clientY); @@ -518,6 +527,77 @@ export class MarqueeView extends ObservableReactComponent<SubCollectionViewProps MarqueeOptionsMenu.Instance.fadeOut(true); }); + getAiPresetsDescriptors = (): DocumentDescriptor[] => + this.marqueeSelect(false).map(doc => ({ + type: typeof doc.$type === 'string' ? doc.$type : 'UNKNOWN', + tags: Array.from(new Set(StrListCast(doc.$tags_chat))), + })); + + generateScrapbook = action(async () => { + const selectedDocs = this.marqueeSelect(false); + if (!selectedDocs.length) return; + + const descriptors = this.getAiPresetsDescriptors(); + if (descriptors.length === 0) { + alert('No documents selected to generate a scrapbook from!'); + return; + } + + const aiPreset = await requestAiGeneratedPreset(descriptors); + if (!aiPreset.length) { + alert('Failed to generate preset'); + return; + } + const scrapbookPlaceholders: Doc[] = buildPlaceholdersFromConfigs(aiPreset); + /* + const scrapbookPlaceholders: Doc[] = aiPreset.map(cfg => { + const placeholderDoc = Docs.Create.TextDocument(cfg.tag); + placeholderDoc.placeholder_docType = cfg.type as DocumentType; + placeholderDoc.placeholder_acceptTags = new List<string>(cfg.acceptTags ?? [cfg.tag]); + + const placeholder = new Doc(); + placeholder.proto = placeholderDoc; + placeholder.original = placeholderDoc; + placeholder.x = cfg.x; + placeholder.y = cfg.y; + if (cfg.width != null) placeholder._width = cfg.width; + if (cfg.height != null) placeholder._height = cfg.height; + + return placeholder; + });*/ + + const scrapbook = Docs.Create.ScrapbookDocument(scrapbookPlaceholders, { + backgroundColor: '#e2ad32', + x: this.Bounds.left, + y: this.Bounds.top, + _width: 500, + _height: 500, + title: 'AI-generated Scrapbook', + }); + + // 3) Now grab that new scrapbook’s flat placeholders + const allPlaceholders = DocUtils.unwrapPlaceholders(scrapbookPlaceholders); + + // 4) Slot each selectedDocs[i] into the first matching placeholder + selectedDocs.forEach(realDoc => slotRealDocIntoPlaceholders(realDoc, allPlaceholders)); + + const selected = selectedDocs.map(d => { + this._props.removeDocument?.(d); + d.x = NumCast(d.x) - this.Bounds.left; + d.y = NumCast(d.y) - this.Bounds.top; + return d; + }); + + this._props.addDocument?.(scrapbook); + const portal = Docs.Create.FreeformDocument(selected, { title: 'summarized documents', x: this.Bounds.left + 200, y: this.Bounds.top, isGroup: true, backgroundColor: 'transparent' }); + DocUtils.MakeLink(scrapbook, portal, { link_relationship: 'summary of:summarized by' }); + + portal.hidden = true; + this._props.addDocument?.(portal); + MarqueeOptionsMenu.Instance.fadeOut(true); + this.hideMarquee(); + }); + @action marqueeCommand = (e: KeyboardEvent) => { const ee = e as unknown as KeyboardEvent & { propagationIsStopped?: boolean }; @@ -539,6 +619,7 @@ export class MarqueeView extends ObservableReactComponent<SubCollectionViewProps if (e.key === 'g') this.collection(e, true); if (e.key === 'c' || e.key === 't') this.collection(e); if (e.key === 's' || e.key === 'S') this.summary(); + if (e.key === 'g' || e.key === 'G') this.generateScrapbook(); // ← scrapbook shortcut if (e.key === 'p') this.pileup(); this.cleanupInteractions(false); } @@ -683,25 +764,27 @@ export class MarqueeView extends ObservableReactComponent<SubCollectionViewProps }; render() { return ( - <div - className="marqueeView" - ref={r => { - r?.addEventListener('dashDragMovePause', this.onDragMovePause as EventListenerOrEventListenerObject); - this.MarqueeRef = r; - }} - style={{ - overflow: StrCast(this._props.Document._overflow), - cursor: Doc.ActiveTool === InkTool.Ink || this._visible ? 'crosshair' : 'pointer', - }} - onDragOver={e => e.preventDefault()} - onScroll={e => { - e.currentTarget.scrollTop = e.currentTarget.scrollLeft = 0; - }} - onClick={this.onClick} - onPointerDown={this.onPointerDown}> - {this._visible ? this.marqueeDiv : null} - {this.props.children} - </div> + <> + <div + className="marqueeView" + ref={r => { + r?.addEventListener('dashDragMovePause', this.onDragMovePause as EventListenerOrEventListenerObject); + this.MarqueeRef = r; + }} + style={{ + overflow: StrCast(this._props.Document._overflow), + cursor: Doc.ActiveTool === InkTool.Ink || this._visible ? 'crosshair' : 'pointer', + }} + onDragOver={e => e.preventDefault()} + onScroll={e => { + e.currentTarget.scrollTop = e.currentTarget.scrollLeft = 0; + }} + onClick={this.onClick} + onPointerDown={this.onPointerDown}> + {this._visible ? this.marqueeDiv : null} + {this.props.children} + </div> + </> ); } } |