aboutsummaryrefslogtreecommitdiff
path: root/src/client/views/nodes/scrapbook
diff options
context:
space:
mode:
authorsharkiecodes <lanyi_stroud@brown.edu>2025-06-01 20:24:04 -0400
committersharkiecodes <lanyi_stroud@brown.edu>2025-06-01 20:24:04 -0400
commit7626527799c0606fa9c4fd4d26a19189dc7e7a0e (patch)
tree858dca125a097e0b43b1685c0d96e8a2ddf1cb1b /src/client/views/nodes/scrapbook
parentc1f4a60b0016242a9097357074730f0cc9c151ba (diff)
reactive backgrounds, tagging of pdfs, group-select and suggested templates, text box content influences backgrounds
Diffstat (limited to 'src/client/views/nodes/scrapbook')
-rw-r--r--src/client/views/nodes/scrapbook/AIPresetGenerator.ts31
-rw-r--r--src/client/views/nodes/scrapbook/ScrapbookBox.tsx264
-rw-r--r--src/client/views/nodes/scrapbook/ScrapbookPreset.tsx8
-rw-r--r--src/client/views/nodes/scrapbook/ScrapbookPresetRegistry.ts3
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.
*/