From 609ec6d37b5abf94f3ab84784544ebe47804cdf5 Mon Sep 17 00:00:00 2001 From: sharkiecodes Date: Tue, 29 Apr 2025 23:01:11 -0400 Subject: Autotag --- .../views/nodes/formattedText/FormattedTextBox.tsx | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) (limited to 'src/client/views/nodes/formattedText') diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index 98e461a52..f4cbbcc9e 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -64,6 +64,7 @@ import { removeMarkWithAttrs } from './prosemirrorPatches'; import { RichTextMenu, RichTextMenuPlugin } from './RichTextMenu'; import { RichTextRules } from './RichTextRules'; import { schema } from './schema_rts'; +import { tickStep } from 'd3'; // import * as applyDevTools from 'prosemirror-dev-tools'; export interface FormattedTextBoxProps extends FieldViewProps { @@ -304,6 +305,14 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent { + this.Document.$tags_chat = new List(); + gptAPICall(RTFCast(this.Document[Doc.LayoutDataKey(this.Document)])?.Text ?? StrCast(this.Document[Doc.LayoutDataKey(this.Document)]), + GPTCallType.CLASSIFYTEXT).then(desc => (this.Document.$tags_chat as List).push(desc)); + this.Document._layout_showTags = true; + //or... then(desc => this.Document.$tags_chat = desc); + } + leafText = (node: Node) => { if (node.type === this.EditorView?.state.schema.nodes.dashField) { const refDoc = !node.attrs.docId ? this.rootDoc : (DocServer.GetCachedRefField(node.attrs.docId as string) as Doc); @@ -1236,6 +1245,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent Doc.IsSearchMatch(this.Document), @@ -1269,6 +1279,14 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent ({ title: this.Document.title, sel: this.props.isSelected() }), + action(() => { + this.autoTag(); + }), + { fireImmediately: true } + ); + if (!this._props.dontRegisterView) { this._disposers.record = reaction( () => this.recordingDictation, -- cgit v1.2.3-70-g09d2 From a2195058135bfac873aba2a5e8677baf8d90b201 Mon Sep 17 00:00:00 2001 From: bobzel Date: Fri, 9 May 2025 23:09:35 -0400 Subject: fixed reaction exception from accessing props and not _props --- src/client/views/nodes/formattedText/FormattedTextBox.tsx | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) (limited to 'src/client/views/nodes/formattedText') diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index 97049d0eb..10becc00b 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -307,11 +307,10 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent { this.Document.$tags_chat = new List(); - gptAPICall(RTFCast(this.Document[Doc.LayoutDataKey(this.Document)])?.Text ?? StrCast(this.Document[Doc.LayoutDataKey(this.Document)]), - GPTCallType.CLASSIFYTEXT).then(desc => (this.Document.$tags_chat as List).push(desc)); + gptAPICall(RTFCast(this.Document[Doc.LayoutDataKey(this.Document)])?.Text ?? StrCast(this.Document[Doc.LayoutDataKey(this.Document)]), GPTCallType.CLASSIFYTEXT).then(desc => (this.Document.$tags_chat as List).push(desc)); this.Document._layout_showTags = true; - //or... then(desc => this.Document.$tags_chat = desc); - } + //or... then(desc => this.Document.$tags_chat = desc); + }; leafText = (node: Node) => { if (node.type === this.EditorView?.state.schema.nodes.dashField) { @@ -1246,7 +1245,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent Doc.IsSearchMatch(this.Document), @@ -1281,13 +1279,13 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent ({ title: this.Document.title, sel: this.props.isSelected() }), + () => ({ title: this.Document.title, sel: this._props.isSelected() }), action(() => { this.autoTag(); }), { fireImmediately: true } ); - + if (!this._props.dontRegisterView) { this._disposers.record = reaction( () => this.recordingDictation, -- cgit v1.2.3-70-g09d2 From 7626527799c0606fa9c4fd4d26a19189dc7e7a0e Mon Sep 17 00:00:00 2001 From: sharkiecodes Date: Sun, 1 Jun 2025 20:24:04 -0400 Subject: reactive backgrounds, tagging of pdfs, group-select and suggested templates, text box content influences backgrounds --- src/client/apis/gpt/GPT.ts | 41 +++- .../collections/collectionFreeForm/MarqueeView.tsx | 70 ++++-- src/client/views/nodes/ImageBox.tsx | 2 +- src/client/views/nodes/PDFBox.tsx | 44 ++++ src/client/views/nodes/VideoBox.tsx | 51 ++++ .../views/nodes/formattedText/FormattedTextBox.tsx | 52 +++- .../views/nodes/scrapbook/AIPresetGenerator.ts | 31 +++ src/client/views/nodes/scrapbook/ScrapbookBox.tsx | 264 ++++++--------------- .../views/nodes/scrapbook/ScrapbookPreset.tsx | 8 +- .../nodes/scrapbook/ScrapbookPresetRegistry.ts | 3 + 10 files changed, 347 insertions(+), 219 deletions(-) create mode 100644 src/client/views/nodes/scrapbook/AIPresetGenerator.ts (limited to 'src/client/views/nodes/formattedText') diff --git a/src/client/apis/gpt/GPT.ts b/src/client/apis/gpt/GPT.ts index 4dd30f8b3..03fce21f7 100644 --- a/src/client/apis/gpt/GPT.ts +++ b/src/client/apis/gpt/GPT.ts @@ -30,7 +30,6 @@ enum GPTCallType { DRAW = 'draw', COLOR = 'color', TEMPLATE = 'template', - SCRAPBOOK = 'scrapbook', VIZSUM = 'vizsum', VIZSUM2 = 'vizsum2', FILL = 'fill', @@ -41,7 +40,9 @@ enum GPTCallType { SUBSETDOCS = 'subset_docs', // select a subset of documents based on their descriptions DOCINFO = 'doc_info', // provide information about a document SORTDOCS = 'sort_docs', - CLASSIFYTEXT = 'classify_text', // classify text into one of the three categories: title, caption, lengthy description + CLASSIFYTEXTMINIMAL = 'classify_text_minimal', // classify text into one of the three categories: title, caption, lengthy description + CLASSIFYTEXTFULL = 'classify_text_full', //tags pdf content + GENERATESCRAPBOOK = 'generate_scrapbook' } type GPTCallOpts = { @@ -93,7 +94,7 @@ const callTypeMap: { [type in GPTCallType]: GPTCallOpts } = { prompt: "You are a helpful resarch assistant. Analyze the user's data to find meaningful patterns and/or correlation. Please only return a JSON with a correlation column 1 propert, a correlation column 2 property, and an analysis property. ", }, //new - classify_text: { + classify_text_minimal: { model: 'gpt-4o', maxTokens: 2048, temp: 0.25, @@ -101,6 +102,13 @@ const callTypeMap: { [type in GPTCallType]: GPTCallOpts } = { most appropriate category: '${TextClassifications.Title}', '${TextClassifications.Caption}', or '${TextClassifications.LengthyDescription}'. Output exclusively the classification in your response. ` }, + classify_text_full: { + model: 'gpt-4o', + maxTokens: 2048, + temp: 0.25, + prompt: `Based on the content of the text, provide six descriptive tags (single words) separated by spaces. + Finally, include a seventh more detailed summary phrase using underscores.` + }, describe: { model: 'gpt-4-vision-preview', maxTokens: 2048, temp: 0, prompt: 'Describe these images in 3-5 words' }, flashcard: { model: 'gpt-4-turbo', @@ -160,12 +168,29 @@ const callTypeMap: { [type in GPTCallType]: GPTCallOpts } = { prompt: 'You will be coloring drawings. You will be given what the drawing is, then a list of descriptions for parts of the drawing. Based on each description, respond with the stroke and fill color that it should be. Follow the rules: 1. Avoid using black for stroke color 2. Make the stroke color 1-3 shades darker than the fill color 3. Use the same colors when possible. Format as {#abcdef #abcdef}, making sure theres a color for each description, and do not include any additional text.', }, - scrapbook: { - model: 'gpt-4-turbo', - maxTokens: 512, + generate_scrapbook: { + model: 'gpt-4o', + maxTokens: 2048, temp: 0.5, - prompt: 'You will be given a list of field descriptions for one or more templates in the format {field #0: “description”}{field #1: “description”}{...}, and a list of column descriptions in the format {“title”: “description”}{...}. Your job is to match columns with fields based on their descriptions. Your output should be in the following JSON format: {“template_title”:{“#”: “title”, “#”: “title”, “#”: “title” …}, “template_title”:{“#”: “title”, “#”: “title”, “#”: “title” …}} where “template_title” is the templates title as specified in the description provided, # represents the field # and “title” the title of the column assigned to it. A filled out example might look like {“fivefield2”:{“0”:”Name”, “1”:”Image”, “2”:”Caption”, “3”:”Position”, “4”:”Stats”}, “fivefield3”:{0:”Image”, 1:”Name”, 2:”Caption”, 3:”Stats”, 4:”Position”}. Include one object for each template. IT IS VERY IMPORTANT THAT YOU ONLY INCLUDE TEXT IN THE FORMAT ABOVE, WITH NO ADDITIONS WHATSOEVER. Do not include extraneous ‘#’ characters, ‘column’ for columns, or ‘template’ for templates: ONLY THE TITLES AND NUMBERS. There should never be one column assigned to more than one field (ie. if the “name” column is assigned to field 1, it can’t be assigned to any other fields) . Do this for each template whose fields are described. The descriptions are as follows:', - }, + prompt: `Generate an aesthetically pleasing scrapbook layout preset based on these items. + Return your response as JSON in the format: + [{ + "type": DocumentType.RTF or DocumentType.IMG or DocumentType.PDF + "tag": a singular tag summarizing the document + "x": number, + "y": number, + "width": number, + "height": number + }, ...] + If there are mutliple documents, you may include + "children": [ + { type: + tag: + x: , y: , width: , height: + } + ] ` + + }, command_type: { model: 'gpt-4-turbo', diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index f5e699d3e..0b91d628b 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -28,6 +28,8 @@ 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 './MarqueeView.scss'; interface MarqueeViewProps { @@ -519,35 +521,71 @@ export class MarqueeView extends ObservableReactComponent { + + const selectedDocs = this.marqueeSelect(false); + if (!selectedDocs.length) return; - @undoBatch - generateScrapbook = action(() => { - let docs = new Array(); + const descriptors: DocumentDescriptor[] = selectedDocs.map(doc => ({ + type: typeof doc.$type === 'string' ? doc.$type : 'UNKNOWN', + tags: (() => { + const internalTagsSet = new Set(); + StrListCast(doc.$tags_chat ?? new List()).forEach(tag => { + internalTagsSet.add(tag); + }); + return Array.from(internalTagsSet); + })() + })); + + + const aiPreset = await requestAiGeneratedPreset(descriptors); + if (!aiPreset.length) { + alert("Failed to generate preset"); + return; + } + + 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; + + 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' + }); const selected = this.marqueeSelect(false).map(d => { this._props.removeDocument?.(d); d.x = NumCast(d.x) - this.Bounds.left; d.y = NumCast(d.y) - this.Bounds.top; - docs.push(d); return d; }); - const scrapbook = Docs.Create.ScrapbookDocument(docs, { - backgroundColor: '#e2ad32', - x: this.Bounds.left, - y: this.Bounds.top, - followLinkToggle: true, - _width: 200, - _height: 200, - _layout_showSidebar: true, - title: 'overview', - }); + + this._props.addDocument?.(scrapbook); + selectedDocs.forEach(doc => this._props.removeDocument?.(doc)); 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); - //this._props.addLiveTextDocument(summary); - this._props.addDocument?.(scrapbook); MarqueeOptionsMenu.Instance.fadeOut(true); + this.hideMarquee(); }); diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx index bd612d04f..9067f7e0c 100644 --- a/src/client/views/nodes/ImageBox.tsx +++ b/src/client/views/nodes/ImageBox.tsx @@ -132,7 +132,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent() { const base64 = await imageUrlToBase64(url); if (!base64) throw new Error('Failed to load image data'); - // 3) ask GPT for exactly one label: PERSON or LANDSCAPE + // 3) ask GPT for labels one label: PERSON or LANDSCAPE const raw = await gptImageLabel( base64, `Classify this image as PERSON or LANDSCAPE. You may only respond with one of these two options. Then diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx index 55e6d5596..282b06215 100644 --- a/src/client/views/nodes/PDFBox.tsx +++ b/src/client/views/nodes/PDFBox.tsx @@ -34,6 +34,9 @@ import { ImageBox } from './ImageBox'; import { OpenWhere } from './OpenWhere'; import './PDFBox.scss'; import { CreateImage } from './WebBoxRenderer'; +import { gptAPICall } from '../../apis/gpt/GPT'; +import { List } from '../../../fields/List'; +import { GPTCallType } from '../../apis/gpt/GPT'; @observer export class PDFBox extends ViewBoxAnnotatableComponent() { @@ -76,6 +79,47 @@ export class PDFBox extends ViewBoxAnnotatableComponent() { } } + autoTag = async () => { + try { + if (!this._pdf) { + throw new Error('PDF not loaded'); + } + + // 1) Extract text from the first few pages (e.g., first 2 pages) + const maxPages = Math.min(2, this._pdf.numPages); + let textContent = ''; + for (let pageNum = 1; pageNum <= maxPages; pageNum++) { + const page = await this._pdf.getPage(pageNum); + const text = await page.getTextContent(); + const pageText = text.items.map(item => ('str' in item ? item.str : '')).join(' '); + textContent += ` ${pageText}`; + } + + if (!textContent.trim()) { + throw new Error('No text found in PDF'); + } + + // 2) Ask GPT to classify and provide descriptive tags + const raw = await gptAPICall( + `"${textContent.trim().slice(0, 2000)}"`, + GPTCallType.CLASSIFYTEXTFULL + ); + + // 3) Normalize and store the labels + const label = raw.trim().toUpperCase(); + + const tokens = label.split(/\s+/); + this.Document.$tags_chat = new List(); + tokens.forEach(tok => (this.Document.$tags_chat as List).push(tok)); + + // 4) Show tags in layout + this.Document._layout_showTags = true; + + } catch (err) { + console.error('PDF autoTag failed:', err); + } +}; + replaceCanvases = (oldDiv: HTMLElement, newDiv: HTMLElement) => { if (oldDiv.childNodes) { for (let i = 0; i < oldDiv.childNodes.length; i++) { diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx index fa099178c..0e7afbab1 100644 --- a/src/client/views/nodes/VideoBox.tsx +++ b/src/client/views/nodes/VideoBox.tsx @@ -30,6 +30,7 @@ import { StyleProp } from '../StyleProp'; import { DocumentView } from './DocumentView'; import { FieldView, FieldViewProps } from './FieldView'; import { FocusViewOptions } from './FocusViewOptions'; +import { gptImageLabel } from '../../apis/gpt/GPT'; import './VideoBox.scss'; /** @@ -109,6 +110,56 @@ export class VideoBox extends ViewBoxAnnotatableComponent() { return this._videoRef; } + + autoTag = async () => { + try { + if (!this.player) throw new Error('Video element not available.'); + + // 1) Extract a frame at the video's midpoint + const videoDuration = this.player.duration; + const snapshotTime = videoDuration / 2; + + // Seek the video element to the midpoint + await new Promise((resolve, reject) => { + const onSeeked = () => { + this.player!.removeEventListener('seeked', onSeeked); + resolve(); + }; + this.player!.addEventListener('seeked', onSeeked); + this.player!.currentTime = snapshotTime; + }); + + // 2) Draw the frame onto a canvas and get a base64 representation + const canvas = document.createElement('canvas'); + canvas.width = this.player.videoWidth; + canvas.height = this.player.videoHeight; + const ctx = canvas.getContext('2d'); + if (!ctx) throw new Error('Failed to create canvas context.'); + ctx.drawImage(this.player, 0, 0, canvas.width, canvas.height); + const base64Image = canvas.toDataURL('image/png'); + + // 3) Send the image data to GPT for classification and descriptive tags + const raw = await gptImageLabel( + base64Image, + `Classify this video frame as either a PERSON or LANDSCAPE. + Then provide five additional descriptive tags (single words) separated by spaces. + Finally, add one detailed summary phrase using underscores.` + ); + + // 4) Normalize and store labels in the Document's tags + const label = raw.trim().toUpperCase(); + const tokens = label.split(/\s+/); + this.Document.$tags_chat = new List(); + tokens.forEach(tok => (this.Document.$tags_chat as List).push(tok)); + + // 5) Turn on tag display in layout + this.Document._layout_showTags = true; + + } catch (err) { + console.error('Video autoTag failed:', err); + } +}; + componentDidMount() { this.unmounting = false; this._props.setContentViewBox?.(this); // this tells the DocumentView that this VideoBox is the "content" of the document. this allows the DocumentView to indirectly call getAnchor() on the VideoBox when making a link. diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index 10becc00b..04a14a15f 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -8,6 +8,7 @@ import { baseKeymap, selectAll, splitBlock } from 'prosemirror-commands'; import { history } from 'prosemirror-history'; import { inputRules } from 'prosemirror-inputrules'; import { keymap } from 'prosemirror-keymap'; +import { runInAction } from 'mobx'; import { Fragment, Mark, Node, Slice } from 'prosemirror-model'; import { EditorState, NodeSelection, Plugin, Selection, TextSelection, Transaction } from 'prosemirror-state'; import { EditorView, NodeViewConstructor } from 'prosemirror-view'; @@ -305,12 +306,53 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent { + autoTag = async () => { + + const layoutKey = Doc.LayoutDataKey(this.Document); + const rawText = RTFCast(this.Document[layoutKey])?.Text ?? StrCast(this.Document[layoutKey]); + + const callType = rawText.includes("[placeholder]") + ? GPTCallType.CLASSIFYTEXTMINIMAL + : GPTCallType.CLASSIFYTEXTFULL; + + gptAPICall(rawText, callType).then(desc => { + runInAction(() => { + // Clear existing tags + this.Document.$tags_chat = new List(); + + // Split GPT response into tokens and push individually + const tokens = desc.trim().split(/\s+/); + tokens.forEach(tok => { + (this.Document.$tags_chat as List).push(tok); + }); + + this.Document._layout_showTags = true; + }); + }); + /*this.Document.$tags_chat = new List(); + gptAPICall(RTFCast(this.Document[Doc.LayoutDataKey(this.Document)])?.Text ?? StrCast(this.Document[Doc.LayoutDataKey(this.Document)]), GPTCallType.CLASSIFYTEXTMINIMAL).then(desc => (this.Document.$tags_chat as List).push(desc)); + this.Document._layout_showTags = true;*/ + + + // 2) grab whatever’s actually in the field (either RTF or plain string) +/* + const rawText = RTFCast(this.Document[Doc.LayoutDataKey(this.Document)])?.Text ?? StrCast(this.Document[Doc.LayoutDataKey(this.Document)]) + + // 3) pick minimal vs. full classification based on "[placeholder]" substring + if (rawText.includes("[placeholder]")) { this.Document.$tags_chat = new List(); - gptAPICall(RTFCast(this.Document[Doc.LayoutDataKey(this.Document)])?.Text ?? StrCast(this.Document[Doc.LayoutDataKey(this.Document)]), GPTCallType.CLASSIFYTEXT).then(desc => (this.Document.$tags_chat as List).push(desc)); - this.Document._layout_showTags = true; - //or... then(desc => this.Document.$tags_chat = desc); - }; + gptAPICall(RTFCast(this.Document[Doc.LayoutDataKey(this.Document)])?.Text ?? StrCast(this.Document[Doc.LayoutDataKey(this.Document)]), GPTCallType.CLASSIFYTEXTMINIMAL).then(desc => { + (this.Document.$tags_chat as List).push(desc); + }); + } else { + this.Document.$tags_chat = new List(); + gptAPICall(RTFCast(this.Document[Doc.LayoutDataKey(this.Document)])?.Text ?? StrCast(this.Document[Doc.LayoutDataKey(this.Document)]), GPTCallType.CLASSIFYTEXTFULL).then(desc => { + (this.Document.$tags_chat as List).push(desc); + })}; + // 4) make sure the UI will show tags + this.Document._layout_showTags = true;*/ + +}; leafText = (node: Node) => { if (node.type === this.EditorView?.state.schema.nodes.dashField) { 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 { + 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() { @@ -55,14 +107,22 @@ export class ScrapbookBox extends ViewBoxAnnotatableComponent() constructor(props: FieldViewProps) { super(props); makeObservable(this); - // whenever the preset changes, rebuild the layout - reaction( + const existingItems = DocListCast(this.dataDoc[this.fieldKey] as List); + 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() if (!this.dataDoc[this.fieldKey]) { this.dataDoc[this.fieldKey] = new List(); } - this.createdDate = this.getFormattedDate(); //this.initScrapbook(ScrapbookPresetType.Default); - this.setTitle(); //this.setLayout(ScrapbookPreset.Spotlight); } @@ -99,175 +157,11 @@ export class ScrapbookBox extends ViewBoxAnnotatableComponent() } // 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(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(''); - internalImg.accepts_docType = DocumentType.IMG; - internalImg.accepts_tagType = 'PERSON'; - const phInternal = makePlaceholder(internalImg, -100, -120, 120, 160); - - const landscapeImg = Docs.Create.TextDocument(''); - 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(placeholders); - } @@ -276,8 +170,8 @@ export class ScrapbookBox extends ViewBoxAnnotatableComponent() 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() placeholder.y = -100; //placeholder.overrideFields = new List(['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(['lengthy description']); //we need to go back and set this @@ -301,7 +195,7 @@ export class ScrapbookBox extends ViewBoxAnnotatableComponent() placeholder2._width = 250; //placeholder2.overrideFields = new List(['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() - 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() difference between passing a new List versus just the raw array? */ this.dataDoc[this.fieldKey] = new List([placeholder, placeholder2, placeholder3, placeholder4]); - + } //this.dataDoc[this.fieldKey] = this.dataDoc[this.fieldKey] ?? new List([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(); + + + /** * Register a new scrapbook preset under the given name. */ -- cgit v1.2.3-70-g09d2 From 08b40f21d9295f36277827ac33085f82c9ee1646 Mon Sep 17 00:00:00 2001 From: sharkiecodes Date: Fri, 6 Jun 2025 11:07:59 -0400 Subject: clean-up --- .../collections/collectionFreeForm/MarqueeView.tsx | 4 +- src/client/views/nodes/ImageBox.tsx | 8 +- .../views/nodes/formattedText/FormattedTextBox.tsx | 23 --- src/client/views/nodes/scrapbook/ScrapbookBox.scss | 63 ++++++ src/client/views/nodes/scrapbook/ScrapbookBox.tsx | 216 +++++---------------- .../nodes/scrapbook/ScrapbookSettingsPanel.tsx | 60 ------ .../views/nodes/scrapbook/ScrapbookSlot.scss | 85 -------- src/client/views/nodes/scrapbook/ScrapbookSlot.tsx | 28 --- .../views/nodes/scrapbook/ScrapbookSlotTypes.ts | 25 --- .../views/nodes/scrapbook/scrapbookleftover.ts | 6 +- 10 files changed, 114 insertions(+), 404 deletions(-) create mode 100644 src/client/views/nodes/scrapbook/ScrapbookBox.scss delete mode 100644 src/client/views/nodes/scrapbook/ScrapbookSettingsPanel.tsx delete mode 100644 src/client/views/nodes/scrapbook/ScrapbookSlot.scss delete mode 100644 src/client/views/nodes/scrapbook/ScrapbookSlot.tsx delete mode 100644 src/client/views/nodes/scrapbook/ScrapbookSlotTypes.ts (limited to 'src/client/views/nodes/formattedText') diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index b8d203cf6..9f96163cd 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -16,7 +16,7 @@ import { DocumentType } from '../../../documents/DocumentTypes'; import { Docs, DocumentOptions } from '../../../documents/Documents'; import { SnappingManager, freeformScrollMode } from '../../../util/SnappingManager'; import { Transform } from '../../../util/Transform'; -import { UndoManager, undoBatch } from '../../../util/UndoManager'; +import { UndoManager, undoBatch, undoable } from '../../../util/UndoManager'; import { ContextMenu } from '../../ContextMenu'; import { ObservableReactComponent } from '../../ObservableReactComponent'; import { MarqueeViewBounds } from '../../PinFuncs'; @@ -548,7 +548,7 @@ export class MarqueeView extends ObservableReactComponent { const selectedDocs = this.marqueeSelect(false); diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx index 2473f1c0a..79a218e45 100644 --- a/src/client/views/nodes/ImageBox.tsx +++ b/src/client/views/nodes/ImageBox.tsx @@ -46,7 +46,6 @@ import { FocusViewOptions } from './FocusViewOptions'; import './ImageBox.scss'; import { OpenWhere } from './OpenWhere'; import { gptImageLabel } from '../../apis/gpt/GPT'; -import { ImageLabelBox } from '../collections/collectionFreeForm/ImageLabelBox'; const DefaultPath = '/assets/unknown-file-icon-hi.png'; export class ImageEditorData { @@ -157,13 +156,10 @@ export class ImageBox extends ViewBoxAnnotatableComponent() { this.Document.$tags_chat = new List(); tokens.forEach(tok => { (this.Document.$tags_chat as List).push(tok)}); - (this.Document.$tags_chat as List).push(`ASPECT_${aspectRatio}`); - //!!! changed may 11 (this.Document.$tags_chat as List).push(label); + (this.Document.$tags_chat as List).push(`ASPECT_${aspectRatio}`); // 6) flip on “show tags” in the layout // (same flag that ImageLabelBox.toggleDisplayInformation uses) - //note to self: What if i used my own field (ex: Document.$auto_description or something - //Would i still have to toggle it on for it to show in the metadata? this.Document._layout_showTags = true; } catch (err) { @@ -172,8 +168,6 @@ export class ImageBox extends ViewBoxAnnotatableComponent() { } }; - //Doc.getDescription(this.Document).then(desc => this.desc = desc) - diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index 04a14a15f..ad75d320a 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -329,29 +329,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent(); - gptAPICall(RTFCast(this.Document[Doc.LayoutDataKey(this.Document)])?.Text ?? StrCast(this.Document[Doc.LayoutDataKey(this.Document)]), GPTCallType.CLASSIFYTEXTMINIMAL).then(desc => (this.Document.$tags_chat as List).push(desc)); - this.Document._layout_showTags = true;*/ - - - // 2) grab whatever’s actually in the field (either RTF or plain string) -/* - const rawText = RTFCast(this.Document[Doc.LayoutDataKey(this.Document)])?.Text ?? StrCast(this.Document[Doc.LayoutDataKey(this.Document)]) - - // 3) pick minimal vs. full classification based on "[placeholder]" substring - if (rawText.includes("[placeholder]")) { - this.Document.$tags_chat = new List(); - gptAPICall(RTFCast(this.Document[Doc.LayoutDataKey(this.Document)])?.Text ?? StrCast(this.Document[Doc.LayoutDataKey(this.Document)]), GPTCallType.CLASSIFYTEXTMINIMAL).then(desc => { - (this.Document.$tags_chat as List).push(desc); - }); - } else { - this.Document.$tags_chat = new List(); - gptAPICall(RTFCast(this.Document[Doc.LayoutDataKey(this.Document)])?.Text ?? StrCast(this.Document[Doc.LayoutDataKey(this.Document)]), GPTCallType.CLASSIFYTEXTFULL).then(desc => { - (this.Document.$tags_chat as List).push(desc); - })}; - // 4) make sure the UI will show tags - this.Document._layout_showTags = true;*/ - }; leafText = (node: Node) => { diff --git a/src/client/views/nodes/scrapbook/ScrapbookBox.scss b/src/client/views/nodes/scrapbook/ScrapbookBox.scss new file mode 100644 index 000000000..8dc93df60 --- /dev/null +++ b/src/client/views/nodes/scrapbook/ScrapbookBox.scss @@ -0,0 +1,63 @@ + +.scrapbook-box { + /* Make sure the container fills its parent, and set a base background */ + position: relative; /* so that absolute children (loading overlay, etc.) are positioned relative to this */ + width: 100%; + height: 100%; + background: beige; + overflow: hidden; /* prevent scrollbars if children overflow */ +} + +/* Loading overlay that covers the entire scrapbook while AI-generation is in progress */ +.scrapbook-box-loading-overlay { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + display: flex; + justify-content: center; + align-items: center; + background: rgba(255, 255, 255, 0.8); + z-index: 10; /* sits above the ImageBox and other content */ +} + +/* The (this.selectedPreset = e.currentTarget.value)} - style={{ position: 'absolute', top: 8, left: 8, zIndex: 20 }} + + {this.src && this.imgDoc && ( + + )} + + + {getPresetNames().map(name => ( + + ))} + + {this._props.isContentActive() && ( -
e.stopPropagation()} - > +
-
- )} - - - {/*
Drop an image here
*/} -
- ); - } -} - - - -//function extractScrapbookConfigs(docs: Doc[]): ScrapbookItemConfig[] { - //return docs.map(doc => extractConfig(doc)); -//} - -// function extractConfig(doc: Doc): ScrapbookItemConfig { -// const layoutKey = Doc.LayoutDataKey(doc); -// const childDocs = doc[layoutKey] ? DocListCast(doc[layoutKey]) : []; - -// const isContainer = childDocs.length > 0; - -// const cfg: ScrapbookItemConfig = { -// type: isContainer ? DocumentType.COL : doc.$type, -// tag: -// acceptTag: doc.accepts_tagType, -// x: doc.x || 0, -// y: doc.y || 0, -// width: doc._width, -// height: doc._height, -// }; - -// if (isContainer) { -// cfg.containerWidth = doc.proto._width; -// cfg.containerHeight = doc.proto._height; -// cfg.children = childDocs.map(child => extractConfig(child)); -// } + + )} -// return cfg; -// } + + + ); + } +} -// Register scrapbook Docs.Prototypes.TemplateMap.set(DocumentType.SCRAPBOOK, { diff --git a/src/client/views/nodes/scrapbook/ScrapbookSettingsPanel.tsx b/src/client/views/nodes/scrapbook/ScrapbookSettingsPanel.tsx deleted file mode 100644 index 5808ab4d1..000000000 --- a/src/client/views/nodes/scrapbook/ScrapbookSettingsPanel.tsx +++ /dev/null @@ -1,60 +0,0 @@ -import React from 'react'; -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { faRedoAlt } from '@fortawesome/free-solid-svg-icons'; -import { action } from 'mobx'; - -export default class ScrapbookSettingsPanel extends React.Component { - - constructor(props) { - super(props); - this.state = { regenerating: false }; - } - - regenerateScrapbook = async () => { - this.setState({ regenerating: true }); - try { - // Example API call or method invoking ChatGPT for JSON - const newLayout = await fetch('/api/generate-scrapbook-layout', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ currentLayout: this.props.currentLayout }) - }).then(res => res.json()); - - action(() => { - // Apply new layout - this.props.applyNewLayout(newLayout); - })(); - } catch (err) { - console.error('Failed to regenerate layout:', err); - } finally { - this.setState({ regenerating: false }); - } - }; - - render() { - const { regenerating } = this.state; - - return ( -
- -
- ); - } -} \ No newline at end of file diff --git a/src/client/views/nodes/scrapbook/ScrapbookSlot.scss b/src/client/views/nodes/scrapbook/ScrapbookSlot.scss deleted file mode 100644 index ae647ad36..000000000 --- a/src/client/views/nodes/scrapbook/ScrapbookSlot.scss +++ /dev/null @@ -1,85 +0,0 @@ -//IGNORE FOR NOW, CURRENTLY NOT USED IN SCRAPBOOK IMPLEMENTATION -.scrapbook-slot { - position: absolute; - background-color: rgba(245, 245, 245, 0.7); - border: 2px dashed #ccc; - border-radius: 5px; - box-sizing: border-box; - transition: all 0.2s ease; - overflow: hidden; - - &.scrapbook-slot-over { - border-color: #4a90e2; - background-color: rgba(74, 144, 226, 0.1); - } - - &.scrapbook-slot-filled { - border-style: solid; - border-color: rgba(0, 0, 0, 0.1); - background-color: transparent; - - &.scrapbook-slot-over { - border-color: #4a90e2; - background-color: rgba(74, 144, 226, 0.1); - } - } - - .scrapbook-slot-empty { - display: flex; - align-items: center; - justify-content: center; - width: 100%; - height: 100%; - } - - .scrapbook-slot-placeholder { - text-align: center; - color: #888; - } - - .scrapbook-slot-title { - font-weight: bold; - margin-bottom: 5px; - } - - .scrapbook-slot-instruction { - font-size: 0.9em; - font-style: italic; - } - - .scrapbook-slot-content { - width: 100%; - height: 100%; - position: relative; - } - - .scrapbook-slot-controls { - position: absolute; - top: 5px; - right: 5px; - z-index: 10; - opacity: 0; - transition: opacity 0.2s ease; - - .scrapbook-slot-remove-btn { - background-color: rgba(255, 255, 255, 0.8); - border: 1px solid #ccc; - border-radius: 50%; - width: 20px; - height: 20px; - display: flex; - align-items: center; - justify-content: center; - cursor: pointer; - font-size: 10px; - - &:hover { - background-color: rgba(255, 0, 0, 0.1); - } - } - } - - &:hover .scrapbook-slot-controls { - opacity: 1; - } -} \ No newline at end of file diff --git a/src/client/views/nodes/scrapbook/ScrapbookSlot.tsx b/src/client/views/nodes/scrapbook/ScrapbookSlot.tsx deleted file mode 100644 index 2c8f93778..000000000 --- a/src/client/views/nodes/scrapbook/ScrapbookSlot.tsx +++ /dev/null @@ -1,28 +0,0 @@ - -//IGNORE FOR NOW, CURRENTLY NOT USED IN SCRAPBOOK IMPLEMENTATION -export interface SlotDefinition { - id: string; - x: number; y: number; - defaultWidth: number; - defaultHeight: number; - } - - export interface SlotContentMap { - slotId: string; - docId?: string; - } - - export interface ScrapbookConfig { - slots: SlotDefinition[]; - contents?: SlotContentMap[]; - } - - export const DEFAULT_SCRAPBOOK_CONFIG: ScrapbookConfig = { - slots: [ - { id: "slot1", x: 10, y: 10, defaultWidth: 180, defaultHeight: 120 }, - { id: "slot2", x: 200, y: 10, defaultWidth: 180, defaultHeight: 120 }, - // …etc - ], - contents: [] - }; - \ No newline at end of file diff --git a/src/client/views/nodes/scrapbook/ScrapbookSlotTypes.ts b/src/client/views/nodes/scrapbook/ScrapbookSlotTypes.ts deleted file mode 100644 index 686917d9a..000000000 --- a/src/client/views/nodes/scrapbook/ScrapbookSlotTypes.ts +++ /dev/null @@ -1,25 +0,0 @@ -// ScrapbookSlotTypes.ts -export interface SlotDefinition { - id: string; - title: string; - x: number; - y: number; - defaultWidth: number; - defaultHeight: number; - } - - export interface ScrapbookConfig { - slots: SlotDefinition[]; - contents?: { slotId: string; docId: string }[]; - } - - // give it three slots by default: - export const DEFAULT_SCRAPBOOK_CONFIG: ScrapbookConfig = { - slots: [ - { id: "main", title: "Main Content", x: 20, y: 20, defaultWidth: 360, defaultHeight: 200 }, - { id: "notes", title: "Notes", x: 20, y: 240, defaultWidth: 360, defaultHeight: 160 }, - { id: "resources", title: "Resources", x: 400, y: 20, defaultWidth: 320, defaultHeight: 380 }, - ], - contents: [], - }; - \ No newline at end of file diff --git a/src/client/views/nodes/scrapbook/scrapbookleftover.ts b/src/client/views/nodes/scrapbook/scrapbookleftover.ts index 2f381ab95..ee7a858e4 100644 --- a/src/client/views/nodes/scrapbook/scrapbookleftover.ts +++ b/src/client/views/nodes/scrapbook/scrapbookleftover.ts @@ -1,8 +1,4 @@ - - - - - //DocListCast(this.Document.items).map(doc => DocListCast(doc[Doc.LayoutDataKey(doc)]) + //DocListCast(this.Document.items).map(doc => DocListCast(doc[Doc.LayoutDataKey(doc)]) if (placeholder) { /**Look at the autotags and see what matches*RTFCast(d[Doc.LayoutDataKey(d)])?.Text*/ -- cgit v1.2.3-70-g09d2 From 403dcfb5e8b659f62ed51212ede3f5807caa58c6 Mon Sep 17 00:00:00 2001 From: bobzel Date: Tue, 10 Jun 2025 19:06:31 -0400 Subject: cleanup and streamlining of scrapbook code. --- src/client/documents/Documents.ts | 3 + .../collections/collectionFreeForm/MarqueeView.tsx | 112 ++-- .../views/nodes/formattedText/FormattedTextBox.tsx | 44 +- src/client/views/nodes/scrapbook/ScrapbookBox.scss | 87 +-- src/client/views/nodes/scrapbook/ScrapbookBox.tsx | 645 +++++++-------------- .../views/nodes/scrapbook/ScrapbookPreset.tsx | 169 ++---- .../nodes/scrapbook/ScrapbookPresetRegistry.ts | 20 +- 7 files changed, 353 insertions(+), 727 deletions(-) (limited to 'src/client/views/nodes/formattedText') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 4ad9c9bd8..aea636040 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -331,6 +331,9 @@ export class DocumentOptions { toolType?: string; // type of pen tool expertMode?: BOOLt = new BoolInfo('something available only in expert (not novice) mode'); + placeholder_docType?: STRt = new StrInfo('type of document that this document accepts as a child', false, false, Array.from(Object.keys(DocumentType))); + placeholder_acceptTags?: List; + contextMenuFilters?: List; contextMenuScripts?: List; contextMenuLabels?: List; diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index b2b904509..12515a72c 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -81,14 +81,11 @@ export class MarqueeView extends ObservableReactComponent