diff options
| author | sharkiecodes <lanyi_stroud@brown.edu> | 2025-06-01 20:24:04 -0400 |
|---|---|---|
| committer | sharkiecodes <lanyi_stroud@brown.edu> | 2025-06-01 20:24:04 -0400 |
| commit | 7626527799c0606fa9c4fd4d26a19189dc7e7a0e (patch) | |
| tree | 858dca125a097e0b43b1685c0d96e8a2ddf1cb1b /src/client/views/nodes/scrapbook | |
| parent | c1f4a60b0016242a9097357074730f0cc9c151ba (diff) | |
reactive backgrounds, tagging of pdfs, group-select and suggested templates, text box content influences backgrounds
Diffstat (limited to 'src/client/views/nodes/scrapbook')
4 files changed, 117 insertions, 189 deletions
diff --git a/src/client/views/nodes/scrapbook/AIPresetGenerator.ts b/src/client/views/nodes/scrapbook/AIPresetGenerator.ts new file mode 100644 index 000000000..1f159222b --- /dev/null +++ b/src/client/views/nodes/scrapbook/AIPresetGenerator.ts @@ -0,0 +1,31 @@ +import { ScrapbookItemConfig } from './ScrapbookPreset'; +import { GPTCallType, gptAPICall } from '../../../apis/gpt/GPT'; + +// Represents the descriptor for each document +export interface DocumentDescriptor { + type: string; + tags: string[]; +} + +// Main function to request AI-generated presets +export async function requestAiGeneratedPreset(descriptors: DocumentDescriptor[]): Promise<ScrapbookItemConfig[]> { + const prompt = createPrompt(descriptors); + let aiResponse = await gptAPICall(prompt, GPTCallType.GENERATESCRAPBOOK); + // Strip out ```json and ``` if the model wrapped its answer in fences + aiResponse = aiResponse + .trim() + .replace(/^```(?:json)?\s*/, "") // remove leading ``` or ```json + .replace(/\s*```$/, ""); // remove trailing ``` + const parsedPreset = JSON.parse(aiResponse) as ScrapbookItemConfig[]; + return parsedPreset; +} + +// Helper to generate prompt text for AI +function createPrompt(descriptors: DocumentDescriptor[]): string { + let prompt = ""; + descriptors.forEach((desc, index) => { + prompt += `${index + 1}. Type: ${desc.type}, Tags: ${desc.tags.join(', ')}\n`; + }); + + return prompt; +} diff --git a/src/client/views/nodes/scrapbook/ScrapbookBox.tsx b/src/client/views/nodes/scrapbook/ScrapbookBox.tsx index 391dcb83d..5dd02295c 100644 --- a/src/client/views/nodes/scrapbook/ScrapbookBox.tsx +++ b/src/client/views/nodes/scrapbook/ScrapbookBox.tsx @@ -24,20 +24,72 @@ import { ImageCast } from '../../../../fields/Types'; import { SnappingManager } from '../../../util/SnappingManager'; import { IReactionDisposer } from 'mobx'; import { observer } from 'mobx-react'; -import { ImageField } from '../../../../fields/URLField'; import { runInAction } from 'mobx'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { faRedoAlt } from '@fortawesome/free-solid-svg-icons'; import { getPresetNames, createPreset } from './ScrapbookPresetRegistry'; -enum ScrapbookPresetType { - Classic = 'Classic', - Default = 'Default', - Collage = 'Collage', - Spotlight = 'Spotlight', -} +export function buildPlaceholdersFromConfigs(configs: ScrapbookItemConfig[]): Doc[] { + const placeholders: Doc[] = []; + + for (const cfg of configs) { + if (cfg.children && cfg.children.length) { + // --- nested container --- + const childDocs = cfg.children.map(child => { + const doc = Docs.Create.TextDocument("[placeholder] " + child.tag); + doc.accepts_docType = child.type; + + const ph = new Doc(); + ph.proto = doc; + ph.original = doc; + ph.x = child.x; + ph.y = child.y; + if (child.width != null) ph._width = child.width; + if (child.height != null) ph._height = child.height; + return ph; + }); + + // wrap those children in a stacking container + const protoW = cfg.containerWidth ?? cfg.width; + const protoH = cfg.containerHeight ?? cfg.height; + const containerProto = Docs.Create.StackingDocument( + childDocs, + { + ...(protoW != null ? { _width: protoW } : {}), + ...(protoH != null ? { _height: protoH } : {}), + title: cfg.tag + } + ); + + const ph = new Doc(); + ph.proto = containerProto; + ph.original = containerProto; + ph.x = cfg.x; + ph.y = cfg.y; + if (cfg.width != null) ph._width = cfg.width; + if (cfg.height != null) ph._height = cfg.height; + placeholders.push(ph); + + } else { + // --- flat placeholder --- + const doc = Docs.Create.TextDocument("[placeholder] " + cfg.tag); + doc.accepts_docType = cfg.type; + doc.accepts_tagType = cfg.acceptTag ?? cfg.tag; + + const ph = new Doc(); + ph.proto = doc; + ph.original = doc; + ph.x = cfg.x; + ph.y = cfg.y; + if (cfg.width != null) ph._width = cfg.width; + if (cfg.height != null) ph._height = cfg.height; + placeholders.push(ph); + } + } + return placeholders; +} // Scrapbook view: a container that lays out its child items in a grid/template @observer export class ScrapbookBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { @@ -55,14 +107,22 @@ export class ScrapbookBox extends ViewBoxAnnotatableComponent<FieldViewProps>() constructor(props: FieldViewProps) { super(props); makeObservable(this); - // whenever the preset changes, rebuild the layout - reaction( + const existingItems = DocListCast(this.dataDoc[this.fieldKey] as List<Doc>); + if (!existingItems || existingItems.length === 0) { + // Only wire up reaction/setTitle if it's truly a brand-new, empty Scrapbook + reaction( () => this.selectedPreset, presetName => this.initScrapbook(presetName), { fireImmediately: true } - ); - this.createdDate = this.getFormattedDate(); - + ); + + this.createdDate = this.getFormattedDate(); + this.setTitle(); + } else { + // If items are already present, just preserve whatever was injected. + // We still want `createdDate` set so that the UI title bar can show it if needed. + this.createdDate = this.getFormattedDate(); + } //this.configs = //ScrapbookPreset.createPreset(presetType); @@ -70,9 +130,7 @@ export class ScrapbookBox extends ViewBoxAnnotatableComponent<FieldViewProps>() if (!this.dataDoc[this.fieldKey]) { this.dataDoc[this.fieldKey] = new List<Doc>(); } - this.createdDate = this.getFormattedDate(); //this.initScrapbook(ScrapbookPresetType.Default); - this.setTitle(); //this.setLayout(ScrapbookPreset.Spotlight); } @@ -99,175 +157,11 @@ export class ScrapbookBox extends ViewBoxAnnotatableComponent<FieldViewProps>() } // 2) build placeholders from the preset + const placeholders = buildPlaceholdersFromConfigs(configs); - const placeholders: Doc[] = []; - - for (const cfg of configs) { - if (cfg.children) { - // --- nested container --- - const childDocs = cfg.children.map(child => { - const doc = Docs.Create.TextDocument(child.tag); - doc.accepts_docType = child.type; - doc.accepts_tagType = child.acceptTag ?? child.tag; - - const ph = new Doc(); - ph.proto = doc; - ph.original = doc; - ph.x = child.x; - ph.y = child.y; - if (child.width != null) ph._width = child.width; - if (child.height != null) ph._height = child.height; - return ph; - }); - - const protoW = cfg.containerWidth ?? cfg.width; - const protoH = cfg.containerHeight ?? cfg.height; - const containerProto = Docs.Create.StackingDocument( - childDocs, - { - ...(protoW != null ? { _width: protoW } : {}), - ...(protoH != null ? { _height: protoH } : {}), - title: cfg.tag - } - ); - - const ph = new Doc(); - ph.proto = containerProto; - ph.original = containerProto; - ph.x = cfg.x; - ph.y = cfg.y; - if (cfg.width != null) ph._width = cfg.width; - if (cfg.height != null) ph._height = cfg.height; - placeholders.push(ph); - - } else { - // --- flat placeholder --- - const doc = Docs.Create.TextDocument(cfg.tag); - doc.accepts_docType = cfg.type; - doc.accepts_tagType = cfg.acceptTag ?? cfg.tag; - - const ph = new Doc(); - ph.proto = doc; - ph.original = doc; - ph.x = cfg.x; - ph.y = cfg.y; - if (cfg.width != null) ph._width = cfg.width; - if (cfg.height != null) ph._height = cfg.height; - placeholders.push(ph); - } - } - // 3) commit them into the field this.dataDoc[this.fieldKey] = new List<Doc>(placeholders); } - @action - //INACTIVE VER ignore!! not in use rn, implementation ver 1 - setLayout(preset: ScrapbookPreset) { - // helper to wrap a TextDocument proto in a Doc with positioning - function makePlaceholder( - proto: Doc, x: number, y: number, - width: number, height: number - ): Doc { - const d = new Doc(); - d.proto = proto; - d.original = proto; - d.x = x; - d.y = y; - d._width = width; - d._height = height; - return d; - } - - let placeholders: Doc[]; - - switch (preset) { - case ScrapbookPresetType.Classic: - // One large landscape image on top, caption below, sidebar at right - const imgClassic = Docs.Create.TextDocument('image'); - imgClassic.accepts_docType = DocumentType.IMG; - imgClassic.accepts_tagType = 'LANDSCAPE'; - const phImageClassic = makePlaceholder(imgClassic, 0, -120, 300, 180); - - const captionClassic = Docs.Create.TextDocument('caption'); - captionClassic.accepts_docType = DocumentType.RTF; - captionClassic.accepts_tagType = 'caption'; - const phCaptionClassic = makePlaceholder(captionClassic, 0, 80, 300, 60); - - const sidebarClassic = Docs.Create.TextDocument('sidebar'); - sidebarClassic.accepts_docType = DocumentType.RTF; - sidebarClassic.accepts_tagType = 'lengthy description'; - const phSidebarClassic = makePlaceholder(sidebarClassic, 320, -50, 80, 200); - - placeholders = [phImageClassic, phCaptionClassic, phSidebarClassic]; - break; - - case ScrapbookPresetType.Collage: - // Grid of four person images, small captions under each - const personDocs: Doc[] = []; - for (let i = 0; i < 4; i++) { - const img = Docs.Create.TextDocument(`person ${i+1}`); - img.accepts_docType = DocumentType.IMG; - img.accepts_tagType = 'PERSON'; - // position in 2x2 grid - const x = (i % 2) * 160 - 80; - const y = Math.floor(i / 2) * 160 - 80; - personDocs.push(makePlaceholder(img, x, y, 150, 120)); - - const cap = Docs.Create.TextDocument(`caption ${i+1}`); - cap.accepts_docType = DocumentType.RTF; - cap.accepts_tagType = 'caption'; - personDocs.push(makePlaceholder(cap, x, y + 70, 150, 30)); - } - placeholders = personDocs; - break; - - case ScrapbookPresetType.Spotlight: - // Full-width title, then a stacking of an internal person image + landscape, then description - const titleSpot = Docs.Create.TextDocument('title'); - titleSpot.accepts_docType = DocumentType.RTF; - titleSpot.accepts_tagType = 'title'; - const phTitleSpot = makePlaceholder(titleSpot, 0, -180, 400, 60); - - const internalImg = Docs.Create.TextDocument('<person>'); - internalImg.accepts_docType = DocumentType.IMG; - internalImg.accepts_tagType = 'PERSON'; - const phInternal = makePlaceholder(internalImg, -100, -120, 120, 160); - - const landscapeImg = Docs.Create.TextDocument('<landscape>'); - landscapeImg.accepts_docType = DocumentType.IMG; - landscapeImg.accepts_tagType = 'LANDSCAPE'; - const phLandscape = makePlaceholder(landscapeImg, 50, 0, 200, 160); - - const stack = Docs.Create.StackingDocument( - [phInternal, phLandscape], - { _width: 360, _height: 180, title: 'spotlight stack' } - ); - const phStack = (() => { - const d = new Doc(); - d.proto = stack; - d.original = stack; - d.x = 8; - d.y = -84; - d._width = 360; - d._height = 180; - return d; - })(); - - const descSpot = Docs.Create.TextDocument('description'); - descSpot.accepts_docType = DocumentType.RTF; - descSpot.accepts_tagType = 'lengthy description'; - const phDescSpot = makePlaceholder(descSpot, 0, 140, 400, 100); - - placeholders = [phTitleSpot, phStack, phDescSpot]; - break; - - default: - placeholders = []; - } - - // finally assign into the dataDoc - this.dataDoc[this.fieldKey] = new List<Doc>(placeholders); - } @@ -276,8 +170,8 @@ export class ScrapbookBox extends ViewBoxAnnotatableComponent<FieldViewProps>() const title = `Scrapbook - ${this.createdDate}`; if (this.dataDoc.title !== title) { this.dataDoc.title = title; - - const image = Docs.Create.TextDocument('person image'); + if (!this.dataDoc[this.fieldKey]){ + const image = Docs.Create.TextDocument('[placeholder] person image'); image.accepts_docType = DocumentType.IMG; image.accepts_tagType = 'PERSON' //should i be writing fields on this doc? clarify diff between this and proto, original const placeholder = new Doc(); @@ -289,7 +183,7 @@ export class ScrapbookBox extends ViewBoxAnnotatableComponent<FieldViewProps>() placeholder.y = -100; //placeholder.overrideFields = new List<string>(['x', 'y']); // shouldn't need to do this for layout fields since the placeholder already overrides its protos - const summary = Docs.Create.TextDocument('long summary'); + const summary = Docs.Create.TextDocument('[placeholder] long summary'); summary.accepts_docType = DocumentType.RTF; summary.accepts_tagType = 'lengthy description'; //summary.$tags_chat = new List<string>(['lengthy description']); //we need to go back and set this @@ -301,7 +195,7 @@ export class ScrapbookBox extends ViewBoxAnnotatableComponent<FieldViewProps>() placeholder2._width = 250; //placeholder2.overrideFields = new List<string>(['x', 'y', '_width']); // shouldn't need to do this for layout fields since the placeholder already overrides its protos - const sidebar = Docs.Create.TextDocument('brief sidebar'); + const sidebar = Docs.Create.TextDocument('[placeholder] brief sidebar'); sidebar.accepts_docType = DocumentType.RTF; sidebar.accepts_tagType = 'title'; //accepts_textType = 'lengthy description' const placeholder3 = new Doc(); @@ -314,7 +208,7 @@ export class ScrapbookBox extends ViewBoxAnnotatableComponent<FieldViewProps>() - const internalImg = Docs.Create.TextDocument('landscape internal'); + const internalImg = Docs.Create.TextDocument('[placeholder] landscape internal'); internalImg.accepts_docType = DocumentType.IMG; internalImg.accepts_tagType = 'LANDSCAPE' //should i be writing fields on this doc? clarify diff between this and proto, original const placeholder5 = new Doc(); @@ -350,7 +244,7 @@ export class ScrapbookBox extends ViewBoxAnnotatableComponent<FieldViewProps>() difference between passing a new List<Doc> versus just the raw array? */ this.dataDoc[this.fieldKey] = new List<Doc>([placeholder, placeholder2, placeholder3, placeholder4]); - + } //this.dataDoc[this.fieldKey] = this.dataDoc[this.fieldKey] ?? new List<Doc>([placeholder, placeholder2, placeholder3, placeholder4]); diff --git a/src/client/views/nodes/scrapbook/ScrapbookPreset.tsx b/src/client/views/nodes/scrapbook/ScrapbookPreset.tsx index fc69552c0..87821c7bf 100644 --- a/src/client/views/nodes/scrapbook/ScrapbookPreset.tsx +++ b/src/client/views/nodes/scrapbook/ScrapbookPreset.tsx @@ -47,22 +47,22 @@ export class ScrapbookPreset { private static createClassicPreset(): ScrapbookItemConfig[] { return [ { type: DocumentType.IMG, - tag: 'LANDSCAPE', + tag: '[placeholder] LANDSCAPE', acceptTag: 'LANDSCAPE', x: 0, y: -100, width: 250, height: 200 }, { type: DocumentType.RTF, - tag: 'caption', + tag: '[placeholder] caption', acceptTag: 'caption', x: 0, y: 200, width: 250, height: 50 }, { type: DocumentType.RTF, - tag: 'lengthy description', + tag: '[placeholder] lengthy description', acceptTag: 'lengthy description', x: 280, y: -50, width: 50, height: 200 }, { type: DocumentType.IMG, - tag: 'PERSON', + tag: '[placeholder] PERSON', acceptTag: 'PERSON', x: -200, y: -100, width: 100, height: 200 }, diff --git a/src/client/views/nodes/scrapbook/ScrapbookPresetRegistry.ts b/src/client/views/nodes/scrapbook/ScrapbookPresetRegistry.ts index f7ddd70ab..d6fd3620c 100644 --- a/src/client/views/nodes/scrapbook/ScrapbookPresetRegistry.ts +++ b/src/client/views/nodes/scrapbook/ScrapbookPresetRegistry.ts @@ -6,6 +6,9 @@ type PresetGenerator = () => ScrapbookItemConfig[]; // Internal map of preset name to generator const presetRegistry = new Map<string, PresetGenerator>(); + + + /** * Register a new scrapbook preset under the given name. */ |
