aboutsummaryrefslogtreecommitdiff
path: root/src/client/views/nodes/DataVizBox/DocCreatorMenu/DocCreatorMenu.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'src/client/views/nodes/DataVizBox/DocCreatorMenu/DocCreatorMenu.tsx')
-rw-r--r--src/client/views/nodes/DataVizBox/DocCreatorMenu/DocCreatorMenu.tsx434
1 files changed, 34 insertions, 400 deletions
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