diff options
Diffstat (limited to 'src/client/views/nodes/DataVizBox/DocCreatorMenu/DocCreatorMenu.tsx')
-rw-r--r-- | src/client/views/nodes/DataVizBox/DocCreatorMenu/DocCreatorMenu.tsx | 434 |
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 |