aboutsummaryrefslogtreecommitdiff
path: root/src/client/views/collections
diff options
context:
space:
mode:
authorsharkiecodes <lanyi_stroud@brown.edu>2025-06-05 22:48:33 -0400
committersharkiecodes <lanyi_stroud@brown.edu>2025-06-05 22:48:33 -0400
commit42b35b687f081e579cbec524426105df3ac695ef (patch)
tree1fdfd3e14699f5e34c1cc6aedb96316ece746a36 /src/client/views/collections
parent7626527799c0606fa9c4fd4d26a19189dc7e7a0e (diff)
attempting marqueeview gen of multiple scrapbooks
Diffstat (limited to 'src/client/views/collections')
-rw-r--r--src/client/views/collections/collectionFreeForm/MarqueeView.scss2
-rw-r--r--src/client/views/collections/collectionFreeForm/MarqueeView.tsx191
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>
+)}
+ </>
+
);
}
}