diff options
| author | sharkiecodes <lanyi_stroud@brown.edu> | 2025-06-05 22:48:33 -0400 |
|---|---|---|
| committer | sharkiecodes <lanyi_stroud@brown.edu> | 2025-06-05 22:48:33 -0400 |
| commit | 42b35b687f081e579cbec524426105df3ac695ef (patch) | |
| tree | 1fdfd3e14699f5e34c1cc6aedb96316ece746a36 /src/client/views/collections | |
| parent | 7626527799c0606fa9c4fd4d26a19189dc7e7a0e (diff) | |
attempting marqueeview gen of multiple scrapbooks
Diffstat (limited to 'src/client/views/collections')
| -rw-r--r-- | src/client/views/collections/collectionFreeForm/MarqueeView.scss | 2 | ||||
| -rw-r--r-- | src/client/views/collections/collectionFreeForm/MarqueeView.tsx | 191 |
2 files changed, 177 insertions, 16 deletions
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.scss b/src/client/views/collections/collectionFreeForm/MarqueeView.scss index 7c9d0f6e1..b514b0911 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 0b91d628b..05d4cd81d 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -22,6 +22,7 @@ import { ObservableReactComponent } from '../../ObservableReactComponent'; import { MarqueeViewBounds } from '../../PinFuncs'; import { PreviewCursor } from '../../PreviewCursor'; import { DocumentView } from '../../nodes/DocumentView'; +import { OverlayDisposer } from '../../OverlayView'; import { OpenWhere } from '../../nodes/OpenWhere'; import { pasteImageBitmap } from '../../nodes/WebBoxRenderer'; import { FormattedTextBox } from '../../nodes/formattedText/FormattedTextBox'; @@ -30,8 +31,15 @@ import { ImageLabelBoxData } from './ImageLabelBox'; import { MarqueeOptionsMenu } from './MarqueeOptionsMenu'; import { StrListCast } from '../../../../fields/Doc'; import { requestAiGeneratedPreset, DocumentDescriptor } from '../../nodes/scrapbook/AIPresetGenerator'; +import { ScrapbookItemConfig } from '../../nodes/scrapbook/ScrapbookPreset'; +import { OverlayView } from '../../OverlayView'; +import { runInAction } from 'mobx'; +import { ScrapbookPicker } from '../../nodes/scrapbook/ScrapbookPicker'; +import { buildPlaceholdersFromConfigs, slotRealDocIntoPlaceholders } from '../../nodes/scrapbook/ScrapbookBox'; import './MarqueeView.scss'; +import { build } from 'xregexp'; + interface MarqueeViewProps { Doc: Doc; getContainerTransform: () => Transform; @@ -78,6 +86,16 @@ 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 + // This will hold the “disposer” function returned by addWindow() + private _overlayDisposer: OverlayDisposer | null = null; + + + @computed get Transform() { return this._props.getTransform(); @@ -277,7 +295,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.generateScrapbook = this.generateAiScrapbooks; MarqueeOptionsMenu.Instance.showMarquee = this.showMarquee; MarqueeOptionsMenu.Instance.hideMarquee = this.hideMarquee; MarqueeOptionsMenu.Instance.jumpTo(e.clientX, e.clientY); @@ -521,6 +539,17 @@ export class MarqueeView extends ObservableReactComponent<SubCollectionViewProps }); + getAiPresetsDescriptors(): DocumentDescriptor[] { + const selected = this.marqueeSelect(false); + return selected.map((doc) => ({ + type: typeof doc.$type === 'string' ? doc.$type : 'UNKNOWN', + tags: (() => { + const s = new Set<string>(); + StrListCast(doc.$tags_chat ?? new List<string>()).forEach((t) => s.add(t)); + return Array.from(s); + })(), + })); + } generateScrapbook = action(async () => { @@ -528,28 +557,23 @@ export class MarqueeView extends ObservableReactComponent<SubCollectionViewProps const selectedDocs = this.marqueeSelect(false); if (!selectedDocs.length) return; - const descriptors: DocumentDescriptor[] = selectedDocs.map(doc => ({ - type: typeof doc.$type === 'string' ? doc.$type : 'UNKNOWN', - tags: (() => { - const internalTagsSet = new Set<string>(); - StrListCast(doc.$tags_chat ?? new List<string>()).forEach(tag => { - internalTagsSet.add(tag); - }); - return Array.from(internalTagsSet); - })() - })); - + 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.accepts_docType = cfg.type as DocumentType; - placeholderDoc.accepts_tagType = cfg.acceptTag ?? cfg.tag; + placeholderDoc.accepts_tagType = new List<string>(cfg.acceptTags ?? [cfg.tag]); const placeholder = new Doc(); placeholder.proto = placeholderDoc; @@ -560,7 +584,7 @@ export class MarqueeView extends ObservableReactComponent<SubCollectionViewProps if (cfg.height != null) placeholder._height = cfg.height; return placeholder; - }); + });*/ const scrapbook = Docs.Create.ScrapbookDocument(scrapbookPlaceholders, { backgroundColor: '#e2ad32', @@ -570,6 +594,25 @@ export class MarqueeView extends ObservableReactComponent<SubCollectionViewProps _height: 500, title: 'AI-generated Scrapbook' }); + + + + // 3) Now grab that new scrapbook’s flat placeholders + const flatPl = DocListCast(scrapbook[Doc.LayoutDataKey(scrapbook)]) as Doc[]; + const unwrap = (items: Doc[]): Doc[] => + items.flatMap(d => + d.$type === DocumentType.COL + ? unwrap(DocListCast(d[Doc.LayoutDataKey(d)])) + : [d] + ); + const allPlaceholders = unwrap(flatPl); + + // 4) Slot each selectedDocs[i] into the first matching placeholder + selectedDocs.forEach(realDoc => { + slotRealDocIntoPlaceholders(realDoc, allPlaceholders + ); + }); + const selected = this.marqueeSelect(false).map(d => { this._props.removeDocument?.(d); d.x = NumCast(d.x) - this.Bounds.left; @@ -589,6 +632,102 @@ export class MarqueeView extends ObservableReactComponent<SubCollectionViewProps }); + + + /** Called when the user clicks one of the N thumbnails */ + @action + onScrapbookChoice(idx: number) { + const chosenDoc = this.aiChoices[idx]; + if (!chosenDoc) return; + + // 1) Move chosenDoc from off‐screen to the marquee area + const bounds = this.Bounds; + chosenDoc.x = bounds.left; + chosenDoc.y = bounds.top; + chosenDoc._width = NumCast(bounds.width || chosenDoc._width); + chosenDoc._height = NumCast(bounds.height || chosenDoc._height); + chosenDoc.$title = 'AI‐chosen scrapbook'; + + // 2) Remove the other temp docs + this.aiChoices.forEach((doc, i) => { + if (i !== idx) { + this.props.removeDocument?.(doc); + } + }); + + // 3) Clear state and close the popup + this.aiChoices = []; + this.pickerVisible = false; + } + + /** Called when user clicks outside or the “×” in ScrapbookPicker */ + @action + cancelScrapbookChoice() { + // 1) Remove all temp scrapbooks + this.aiChoices.forEach((doc) => { + this.props.removeDocument?.(doc); + }); + + // 2) Clear array and hide popup + this.aiChoices = []; + this.pickerVisible = false; + } + @action + generateAiScrapbooks = async () => { + const n = 3; // Number of AI scrapbook presets + const descriptors = this.getAiPresetsDescriptors(); + if (descriptors.length === 0) { + alert('No documents selected to generate a scrapbook from!'); + return; + } + + // 1) Start N parallel requests for JSON configs + const calls: Promise<ScrapbookItemConfig[]>[] = []; + for (let i = 0; i < n; i++) { + calls.push(requestAiGeneratedPreset(descriptors)); + } + + // 2) Optionally show a “spinner” overlay in‐line here if you like… + // (But for brevity, let’s omit that.) + + try { + const allConfigsArrays = await Promise.all(calls); + + runInAction(() => { + // 3) For each returned config array, build placeholders and create _hidden_ Scrapbook doc + this.aiChoices = allConfigsArrays.map((cfgArr, i) => { + const placeholders = buildPlaceholdersFromConfigs(cfgArr); + // Off‐screen ScrapbookDocument: + const tempSb: Doc = Docs.Create.ScrapbookDocument(placeholders, { + title: `AI Preset (temp ${i + 1})`, + x: -10000, // far off screen + y: -10000, + _width: 600, + _height: 600, + backgroundColor: 'transparent', + _layout_showFlash: false, // keep it hidden until chosen + }); + this.props.addDocument?.(tempSb); + tempSb.$tags_chat = new List<string>(['@ai_preset']); + return tempSb; + }); + + // 4) Compute where to show the popup in screen coords + const bounds = this.Bounds; + const screenTopLeft = this._props + .getContainerTransform() + .transformPoint(bounds.left, bounds.top); + this.pickerX = screenTopLeft[0] + 20; + this.pickerY = screenTopLeft[1] + 20; + this.pickerVisible = true; + }); + } catch (err) { + console.error('Error generating AI scrapbooks:', err); + alert('Failed to generate presets. Please try again.'); + } + }; + + @action marqueeCommand = (e: KeyboardEvent) => { const ee = e as unknown as KeyboardEvent & { propagationIsStopped?: boolean }; @@ -755,6 +894,7 @@ export class MarqueeView extends ObservableReactComponent<SubCollectionViewProps }; render() { return ( + <> <div className="marqueeView" ref={r => { @@ -774,6 +914,27 @@ export class MarqueeView extends ObservableReactComponent<SubCollectionViewProps {this._visible ? this.marqueeDiv : null} {this.props.children} </div> + {this.pickerVisible && ( + <div + className="marqueeView-scrapbook-overlay" + style={{ + position: 'absolute', + top: this.pickerY, + left: this.pickerX, + zIndex: 2000, // just ensure it floats above + }} + > + <ScrapbookPicker + choices={this.aiChoices} + x={this.pickerX} + y={this.pickerY} + onSelect={(i) => this.onScrapbookChoice(i)} + onCancel={() => this.cancelScrapbookChoice()} + /> + </div> +)} + </> + ); } } |
