From a83dfdf011d7f5c7d8874c1f6cd776b4909d0a79 Mon Sep 17 00:00:00 2001
From: Nathan-SR <144961007+Nathan-SR@users.noreply.github.com>
Date: Fri, 25 Apr 2025 12:03:05 -0400
Subject: c

---
 .../DataVizBox/DocCreatorMenu/DocCreatorMenu.tsx   | 434 ++-------------------
 1 file changed, 34 insertions(+), 400 deletions(-)

(limited to 'src/client/views/nodes/DataVizBox/DocCreatorMenu/DocCreatorMenu.tsx')

diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu/DocCreatorMenu.tsx b/src/client/views/nodes/DataVizBox/DocCreatorMenu/DocCreatorMenu.tsx
index de345a335..ed2e20843 100644
--- a/src/client/views/nodes/DataVizBox/DocCreatorMenu/DocCreatorMenu.tsx
+++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu/DocCreatorMenu.tsx
@@ -30,10 +30,12 @@ import './DocCreatorMenu.scss';
 import { TemplateField, ViewType } from './TemplateFieldTypes/TemplateField';
 import { Template } from './Template';
 import { TemplateFieldSize, TemplateFieldType, TemplateLayouts } from './TemplateBackend';
-import { TemplateManager } from './TemplateManager';
+import { TemplateManager } from './Backend/TemplateManager';
 import { DrawingFillHandler } from '../../../smartdraw/DrawingFillHandler';
 import { CgPathIntersect } from 'react-icons/cg';
 import { StaticContentField } from './TemplateFieldTypes/StaticContentField';
+import { SuggestedTemplatesWindow } from './Menu/SuggestedTemplatesWindow';
+import { TemplateMenuGPTManager } from './Backend/TemplateMenuGPTManager';
 
 export enum LayoutType {
     FREEFORM = 'Freeform',
@@ -64,6 +66,7 @@ export type Col = {
     title: string;
     type: TemplateFieldType;
     defaultContent?: string;
+    AIGenerated?: boolean;
 };
 
 export type Conditional = {
@@ -84,10 +87,11 @@ export class DocCreatorMenu extends ObservableReactComponent<DocCreateMenuProps>
     // eslint-disable-next-line no-use-before-define
     static Instance: DocCreatorMenu;
 
-    private DEBUG_MODE: boolean = false;
+    private DEBUG_MODE: boolean = true;
     private _disposers: { [name: string]: IDisposer } = {};
     private _ref: HTMLDivElement | null = null;
     private templateManager: TemplateManager;
+    private GPTManager: TemplateMenuGPTManager;
 
     @observable _fullyRenderedDocs: Doc[] = []; // collection of templates filled in with content
     @observable _renderedDocCollection: Doc | undefined = undefined; // fullyRenderedDocs in a parent collection
@@ -148,6 +152,7 @@ export class DocCreatorMenu extends ObservableReactComponent<DocCreateMenuProps>
         makeObservable(this);
         DocCreatorMenu.Instance = this;
         this.templateManager = new TemplateManager(TemplateLayouts.allTemplates);
+        this.GPTManager = new TemplateMenuGPTManager();
     }
 
     setContainerRef: React.LegacyRef<HTMLDivElement> = (node) => {
@@ -162,12 +167,6 @@ export class DocCreatorMenu extends ObservableReactComponent<DocCreateMenuProps>
         this._suggestedTemplates = [];
         this._userCreatedFields = [];
     };
-    @action addUserTemplate = (template: Template) => {
-        this._userTemplates.push(template);
-    };
-    @action removeUserTemplate = (template: Template) => {
-        this._userTemplates.splice(this._userTemplates.indexOf(template), 1);
-    };
     @action setSuggestedTemplates = (templates: Template[]) => {
         this._suggestedTemplates = templates; //prettier-ignore
     };
@@ -238,7 +237,7 @@ export class DocCreatorMenu extends ObservableReactComponent<DocCreateMenuProps>
         return bounds;
     }
 
-    setUpButtonClick = (e: React.PointerEvent, func: () => void) => {
+    setUpButtonClick = (e: React.PointerEvent, func: (...args: any) => void) => {
         setupMoveUpEvents(
             this,
             e,
@@ -518,6 +517,31 @@ export class DocCreatorMenu extends ObservableReactComponent<DocCreateMenuProps>
         this.forceUpdate();
     };
 
+    generatePresetTemplates = async (debug: boolean) => {
+        const templates: Template[] = [];
+
+        if (debug) {
+            templates.push(...this.templateManager.templates);
+        } else {
+            this._dataViz?.updateColDefaults();
+
+            templates.push(...this.templateManager.getValidTemplates(this.fieldsInfos));
+
+            const assignments = await this.GPTManager.assignColsToFields(templates, this.fieldsInfos);
+
+            const renderedTemplatePromises = assignments.map(([template, assgns]) => this.GPTManager.applyGPTContentToTemplate(template, assgns));
+
+            await Promise.all(renderedTemplatePromises);
+        }
+
+        setTimeout(
+            action(() => {
+                this.setSuggestedTemplates(templates);
+                this._GPTLoading = false;
+            })
+        );
+    };
+
     @action setVariationTab = (open: boolean) => {
         this._variationsTab = open;
         if (this._previewWindow && open) {
@@ -554,292 +578,6 @@ export class DocCreatorMenu extends ObservableReactComponent<DocCreateMenuProps>
         doc.y = 10000;
     }
 
-    generateGPTImage = async (prompt: string): Promise<string | undefined> => {
-        try {
-            const res = await gptImageCall(prompt);
-
-            if (res) {
-                const result = (await Networking.PostToServer('/uploadRemoteImage', { sources: res })) as Upload.FileInformation[];
-                const source = ClientUtils.prepend(result[0].accessPaths.agnostic.client);
-                return source;
-            }
-        } catch (e) {
-            console.log(e);
-        }
-    };
-
-    /**
-     * Populates a preset template framework with content from a datavizbox or any AI-generated content.
-     * @param template the preloaded template framework being filled in
-     * @param assignments a list of template field numbers (from top to bottom) and their assigned columns from the linked dataviz
-     * @returns a doc containing the fully rendered template
-     */
-    applyGPTContentToTemplate = async (template: Template, assignments: { [field: string]: Col }): Promise<Template | undefined> => {
-        const GPTTextCalls = Object.entries(assignments).filter(([, col]) => col.type === TemplateFieldType.TEXT && this._userCreatedFields.includes(col));
-        const GPTIMGCalls = Object.entries(assignments).filter(([, col]) => col.type === TemplateFieldType.VISUAL && this._userCreatedFields.includes(col));
-
-        if (GPTTextCalls.length) {
-            const promises = GPTTextCalls.map(([str, col]) => {
-                return this.renderGPTTextCall(template, col, Number(str));
-            });
-
-            await Promise.all(promises);
-        }
-
-        if (GPTIMGCalls.length) {
-            const promises = GPTIMGCalls.map(async ([fieldNum, col]) => {
-                return this.renderGPTImageCall(template, col, Number(fieldNum));
-            });
-
-            await Promise.all(promises);
-        }
-
-        return template;
-    };
-
-    compileFieldDescriptions = (templates: Template[]): string => {
-        let descriptions: string = '';
-        templates.forEach(template => {
-            descriptions += `----------  NEW TEMPLATE TO INCLUDE: The title is: ${template.title}. Its fields are: `;
-            descriptions += template.descriptionSummary;
-        });
-
-        return descriptions;
-    };
-
-    compileColDescriptions = (cols: Col[]): string => {
-        let descriptions: string = ' ------------- COL DESCRIPTIONS START HERE:';
-        cols.forEach(col => (descriptions += `{title: ${col.title}, sizes: ${String(col.sizes)}, type: ${col.type}, descreiption: ${col.desc}} `));
-
-        return descriptions;
-    };
-
-    getColByTitle = (title: string) => {
-        return this.fieldsInfos.filter(col => col.title === title)[0];
-    };
-
-    @action
-    assignColsToFields = async (templates: Template[], cols: Col[]): Promise<[Template, { [field: number]: Col }][]> => {
-        const fieldDescriptions: string = this.compileFieldDescriptions(templates);
-        const colDescriptions: string = this.compileColDescriptions(cols);
-
-        const inputText = fieldDescriptions.concat(colDescriptions);
-
-        ++this._callCount;
-        const origCount = this._callCount;
-
-        const prompt: string = `(${origCount}) ${inputText}`;
-
-        this._GPTLoading = true;
-
-        try {
-            const res = await gptAPICall(prompt, GPTCallType.TEMPLATE);
-
-            if (res) {
-                const assignments: { [templateTitle: string]: { [fieldID: string]: string } } = JSON.parse(res);
-                const brokenDownAssignments: [Template, { [fieldID: number]: Col }][] = [];
-
-                Object.entries(assignments).forEach(([tempTitle, assignment]) => {
-                    const template = templates.filter(temp => temp.title === tempTitle)[0];
-                    if (!template) return;
-                    const toObj = Object.entries(assignment).reduce(
-                        (a, [fieldID, colTitle]) => {
-                            const col = this.getColByTitle(colTitle);
-                            if (!this._userCreatedFields.includes(col)) {
-                                // do the following for any fields not added by the user; will change in the future, for now only GPT content works with user-added field
-                                const field = template.getFieldByID(Number(fieldID));
-                                field.setContent(col.defaultContent ?? '', col.type === TemplateFieldType.VISUAL ? ViewType.IMG : ViewType.TEXT);
-                                field.setTitle(col.title);
-                                this._conditions.filter(c => c.field === field.getTitle()).forEach(conditional => {
-                                    if (field.getContent() === conditional.condition){
-                                        if (conditional.target === 'self'){
-                                            field.renderedDoc![conditional.attribute] = conditional.value;
-                                            (field.settings.opts as any)[conditional.attribute] = conditional.value;
-                                        } else if (conditional.target === 'template'){
-                                            template._mainField!.renderedDoc![conditional.attribute] = conditional.value;
-                                            (template._mainField!.settings.opts as any)[conditional.attribute] = conditional.value;
-                                        }
-                                    }
-                                })
-                            } else {
-                                a[Number(fieldID)] = this.getColByTitle(colTitle);
-                            }
-                            return a;
-                        },
-                        {} as { [field: number]: Col }
-                    );
-                    brokenDownAssignments.push([template, toObj]);
-                });
-
-                return brokenDownAssignments;
-            }
-        } catch (err) {
-            console.error(err);
-        }
-
-        return [];
-    };
-
-    generatePresetTemplates = async () => {
-        const templates: Template[] = [];
-
-        if (this.DEBUG_MODE) {
-            templates.push(...this.templateManager.templates);
-        } else {
-            this._dataViz?.updateColDefaults();
-
-            templates.push(...this.templateManager.getValidTemplates(this.fieldsInfos));
-
-            const assignments = await this.assignColsToFields(templates, this.fieldsInfos);
-
-            const renderedTemplatePromises = assignments.map(([template, assgns]) => this.applyGPTContentToTemplate(template, assgns));
-
-            await Promise.all(renderedTemplatePromises);
-        }
-
-        setTimeout(
-            action(() => {
-                this.setSuggestedTemplates(templates);
-                this._GPTLoading = false;
-            })
-        );
-    };
-
-    renderGPTImageCall = async (template: Template, col: Col, fieldNumber: number | undefined): Promise<boolean> => {
-        const generateAndLoadImage = async (fieldNum: string, column: Col, prompt: string) => {
-            const url = await this.generateGPTImage(prompt);
-            const field: TemplateField = template.getFieldByID(Number(fieldNum));
-
-            field.setContent(url ?? '', ViewType.IMG);
-            field.setTitle(col.title);
-        };
-
-        const fieldContent: string = template.compiledContent;
-
-        try {
-            const sysPrompt =
-                'Your job is to create a prompt for an AI image generator to help it generate an image based on existing content in a template and a user prompt. Your prompt should focus heavily on visual elements to help the image generator; avoid unecessary info that might distract it. ONLY INCLUDE THE PROMPT, NO OTHER TEXT OR EXPLANATION. The existing content is as follows: ' +
-                fieldContent +
-                ' **** The user prompt is: ' +
-                col.desc;
-
-            const prompt = await gptAPICall(sysPrompt, GPTCallType.COMPLETEPROMPT);
-
-            await generateAndLoadImage(String(fieldNumber), col, prompt);
-        } catch (e) {
-            console.log(e);
-        }
-        return true;
-    };
-
-    renderGPTTextCall = async (template: Template, col: Col, fieldNum: number | undefined): Promise<boolean> => {
-        const wordLimit = (size: TemplateFieldSize) => {
-            switch (size) {
-                case TemplateFieldSize.TINY:
-                    return 2;
-                case TemplateFieldSize.SMALL:
-                    return 5;
-                case TemplateFieldSize.MEDIUM:
-                    return 20;
-                case TemplateFieldSize.LARGE:
-                    return 50;
-                case TemplateFieldSize.HUGE:
-                    return 100;
-                default:
-                    return 10;
-            }
-        };
-
-        const textAssignment = `--- title: ${col.title}, prompt: ${col.desc}, word limit: ${wordLimit(col.sizes[0])} words, assigned field: ${fieldNum} ---`;
-
-        const fieldContent: string = template.compiledContent;
-
-        try {
-            const prompt = fieldContent + textAssignment;
-
-            const res = await gptAPICall(`${++this._callCount}: ${prompt}`, GPTCallType.FILL);
-
-            // console.log('prompt: ', prompt, ' response: ', res);
-
-            if (res) {
-                const assignments: { [title: string]: { number: string; content: string } } = JSON.parse(res);
-                Object.entries(assignments).forEach(([, /* title */ info]) => {
-                    const field: TemplateField = template.getFieldByID(Number(info.number));
-                    // const column = this.getColByTitle(title);
-
-                    field.setContent(info.content ?? '', ViewType.TEXT);
-                    field.setTitle(col.title);
-                });
-            }
-        } catch (err) {
-            console.log(err);
-        }
-
-        return true;
-    };
-
-    createDocsFromTemplate = action((dv: DataVizBox, template: Template) => {
-        this._docsRendering = true;
-        const fields = Array.from(Object.keys(dv.records[0]));
-
-        const processContent = (content: { [title: string]: string }) => {
-            const templateCopy = template.cloneBase();
-
-            fields
-                .filter(title => title)
-                .forEach(title => {
-                    const field = templateCopy.getFieldByTitle(title);
-                    if (field !== undefined) {
-                        field.setContent(content[title], field.viewType);
-                        console.log('content set')
-                        this._conditions.filter(c => c.field === title).forEach(conditional => {
-                            console.log('in conditional')
-                            if (content[title] === conditional.condition){
-                                if (conditional.target === 'self'){
-                                    field.renderedDoc![conditional.attribute] = conditional.value;
-                                    (field.settings.opts as any)[conditional.attribute] = conditional.value;
-                                } else if (conditional.target === 'template'){
-                                    console.log('setting', conditional.attribute,  'to: ', conditional.value)
-                                    templateCopy._mainField!.renderedDoc![conditional.attribute] = conditional.value;
-                                    (templateCopy._mainField!.settings.opts as any)[conditional.attribute] = conditional.value;
-                                }
-                            }
-                        })
-                    }
-                });
-
-            const gptFunc = (type: TemplateFieldType) => (type === TemplateFieldType.VISUAL ? this.renderGPTImageCall : this.renderGPTTextCall);
-            const gptPromises = this._userCreatedFields
-                .filter(field => field.type !== TemplateFieldType.UNSET)
-                .map(field => {
-                    const templateField = templateCopy.getFieldByTitle(field.title);
-                    if (templateField !== undefined) {
-                        return gptFunc(field.type)(templateCopy, field, templateField.getID);
-                    }
-                });
-
-            return Promise.all(gptPromises).then(() => (this._DOCCC = templateCopy._mainField?.renderedDoc));
-        };
-
-        const rowContents = this.DEBUG_MODE
-            ? [{}, {}, {}, {}]
-            : NumListCast(dv.layoutDoc.dataViz_selectedRows).map(row =>
-                  fields.reduce(
-                      (values, col) => {
-                          values[col] = dv.records[row][col];
-                          return values;
-                      },
-                      {} as { [title: string]: string }
-                  )
-              );
-        return Promise.all(rowContents.map(processContent)).then(
-            action(renderedDocs => {
-                this._docsRendering = false; // removes loading indicator
-                return renderedDocs;
-            })
-        );
-    });
-
     addRenderedCollectionToMainview = () => {
         const collection = this._renderedDocCollection;
         if (collection) {
@@ -978,110 +716,6 @@ export class DocCreatorMenu extends ObservableReactComponent<DocCreateMenuProps>
             />
         );
 
-    get templatesPreviewContents() {
-        const GPTOptions = <div></div>;
-
-        //<img className='docCreatorMenu-preview-image expanded' src={this._expandedPreview.icon!.url.href.replace(".png", "_o.png")} />
-
-        return (
-            <div className={`docCreatorMenu-templates-view`}>
-                {this._expandedPreview ? (
-                    this.editingWindow
-                ) : (
-                    <div className="docCreatorMenu-templates-displays">
-                         <div className="docCreatorMenu-section">
-                            <div className="docCreatorMenu-section-topbar">
-                                <div className="docCreatorMenu-section-title">Suggested Templates</div>
-                                <button className="docCreatorMenu-menu-button section-reveal-options" onPointerDown={e => this.setUpButtonClick(e, () => runInAction(() => (this._menuContent = 'dashboard')))}>
-                                    <FontAwesomeIcon icon="gear" />
-                                </button>
-                            </div>
-                            <div className="docCreatorMenu-templates-preview-window" style={{ justifyContent: this._GPTLoading || this._menuDimensions.width > 400 ? 'center' : '' }}>
-                                {this._GPTLoading ? (
-                                    <div className="loading-spinner">
-                                        <ReactLoading type="spin" color={StrCast(Doc.UserDoc().userVariantColor)} height={30} width={30} />
-                                    </div>
-                                ) : (
-                                    this._suggestedTemplates.map(template => (
-                                        <div
-                                            key={template.title}
-                                            className="docCreatorMenu-preview-window"
-                                            style={{
-                                                border: this._selectedTemplate === template ? `solid 3px ${Colors.MEDIUM_BLUE}` : '',
-                                                boxShadow: this._selectedTemplate === template ? `0 0 15px rgba(68, 118, 247, .8)` : '',
-                                            }}
-                                            onPointerDown={e => this.setUpButtonClick(e, () => runInAction(() => this.updateSelectedTemplate(template)))}>
-                                            <button
-                                                className="option-button left"
-                                                onPointerDown={e =>
-                                                    this.setUpButtonClick(e, () => {
-                                                        this.setExpandedView(template);
-                                                        this.forceUpdate();
-                                                    })
-                                                }>
-                                                <FontAwesomeIcon icon="magnifying-glass" color="white" />
-                                            </button>
-                                            <button className="option-button right" onPointerDown={e => this.setUpButtonClick(e, () => this.addUserTemplate(template))}>
-                                                <FontAwesomeIcon icon="plus" color="white" />
-                                            </button>
-                                            {this.docPreview(template.getRenderedDoc())}
-                                        </div>
-                                    ))
-                                )}
-                            </div>
-                            <div className="docCreatorMenu-GPT-options">
-                                <div className="docCreatorMenu-GPT-options-container">
-                                    <button className="docCreatorMenu-menu-button" onPointerDown={e => this.setUpButtonClick(e, () => this.generatePresetTemplates())}>
-                                        <FontAwesomeIcon icon="arrows-rotate" />
-                                    </button>
-                                </div>
-                                {this._GPTOpt ? GPTOptions : null}
-                            </div>
-                        </div>
-                        <hr className="docCreatorMenu-option-divider full no-margin" />
-                        <div className="docCreatorMenu-section">
-                            <div className="docCreatorMenu-section-topbar">
-                                <div className="docCreatorMenu-section-title">Your Templates</div>
-                                <button className="docCreatorMenu-menu-button section-reveal-options" onPointerDown={e => this.setUpButtonClick(e, () => (this._GPTOpt = !this._GPTOpt))}>
-                                    <FontAwesomeIcon icon="gear" />
-                                </button>
-                            </div>
-                            <div className="docCreatorMenu-templates-preview-window" style={{ justifyContent: this._menuDimensions.width > 400 ? 'center' : '' }}>
-                                <div className="docCreatorMenu-preview-window empty">
-                                    <FontAwesomeIcon icon="plus" color="rgb(160, 160, 160)" />
-                                </div>
-                                {this._userTemplates.map(template => (
-                                    <div
-                                        key={template.toString()}
-                                        className="docCreatorMenu-preview-window"
-                                        style={{
-                                            border: this._selectedTemplate === template ? `solid 3px ${Colors.MEDIUM_BLUE}` : '',
-                                            boxShadow: this._selectedTemplate === template ? `0 0 15px rgba(68, 118, 247, .8)` : '',
-                                        }}
-                                        onPointerDown={e => this.setUpButtonClick(e, () => runInAction(() => this.updateSelectedTemplate(template)))}>
-                                        <button
-                                            className="option-button left"
-                                            onPointerDown={e =>
-                                                this.setUpButtonClick(e, () => {
-                                                    this.setExpandedView(template);
-                                                })
-                                            }>
-                                            <FontAwesomeIcon icon="magnifying-glass" color="white" />
-                                        </button>
-                                        <button className="option-button right" onPointerDown={e => this.setUpButtonClick(e, () => this.removeUserTemplate(template))}>
-                                            <FontAwesomeIcon icon="minus" color="white" />
-                                        </button>
-                                        {this.docPreview(template.getRenderedDoc())}
-                                    </div>
-                                ))}
-                            </div>
-                        </div>
-                    </div>
-                )}
-            </div>
-        );
-    }
-
     @action updateXMargin = (input: string) => {
         this._layout.xMargin = Number(input);
         setTimeout(() => {
@@ -1503,7 +1137,7 @@ export class DocCreatorMenu extends ObservableReactComponent<DocCreateMenuProps>
 
     get renderSelectedViewType() {
         switch (this._menuContent) {
-            case 'templates': return this.templatesPreviewContents;
+            case 'templates': return <SuggestedTemplatesWindow menu={this} setupButtonClick={this.setUpButtonClick}/>;
             case 'options':   return this.optionsMenuContents;
             case 'dashboard': return this.dashboardContents;
         } // prettier-ignore
-- 
cgit v1.2.3-70-g09d2

