diff options
11 files changed, 82 insertions, 48 deletions
diff --git a/src/client/apis/gpt/GPT.ts b/src/client/apis/gpt/GPT.ts index 57a229569..d7b378958 100644 --- a/src/client/apis/gpt/GPT.ts +++ b/src/client/apis/gpt/GPT.ts @@ -221,6 +221,7 @@ const gptAPICall = async (inputTextIn: string, callType: GPTCallType, prompt?: s const gptImageCall = async (prompt: string, n?: number) => { try { const response = await openai.images.generate({ + model: 'dall-e-3', prompt: prompt, n: n ?? 1, size: '1024x1024', diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu/Backend/TemplateManager.ts b/src/client/views/nodes/DataVizBox/DocCreatorMenu/Backend/TemplateManager.ts index ef7dbc7ab..d11f05766 100644 --- a/src/client/views/nodes/DataVizBox/DocCreatorMenu/Backend/TemplateManager.ts +++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu/Backend/TemplateManager.ts @@ -53,11 +53,11 @@ export class TemplateManager { } } - createDocsFromTemplate = action((dv: DataVizBox, template: Template, csvColumns: Col[], debug: boolean = false) => { + createDocsFromTemplate = action((dv: DataVizBox, template: Template, cols: Col[], debug: boolean = false) => { const fields = Array.from(Object.keys(dv.records[0])); - const processContent = (content: { [title: string]: string }) => { - const templateCopy = template.cloneBase(); + const processContent = async (content: { [title: string]: string }) => { + const templateCopy = template.clone(); fields .filter(title => title) @@ -67,14 +67,22 @@ export class TemplateManager { }); const gptFunc = (type: TemplateFieldType) => (type === TemplateFieldType.VISUAL ? TemplateMenuAIUtils.renderGPTImageCall : TemplateMenuAIUtils.renderGPTTextCall); - const gptPromises = csvColumns - .filter(field => field.type !== TemplateFieldType.UNSET && field.AIGenerated) - .map(field => { - const templateField = templateCopy.getFieldByTitle(field.title); - if (templateField !== undefined) { - return gptFunc(field.type)(templateCopy, field, templateField.getID); - } - }); + const applyGPTContent = async () => { + const promises = cols + .filter(field => field.AIGenerated) + .map(field => { + const templateField = templateCopy.getFieldByTitle(field.title); + if (templateField !== undefined) { + return gptFunc(field.type)(templateCopy, field, templateField.getID); + } + return null; + }) + .filter(p => p !== null); + + await Promise.all(promises); + }; + + await applyGPTContent(); return templateCopy.getRenderedDoc(); }; @@ -90,6 +98,7 @@ export class TemplateManager { {} as { [title: string]: string } ) ); + return Promise.all(rowContents.map(processContent)).then( action(renderedDocs => { return renderedDocs; diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu/Backend/TemplateMenuAIUtils.ts b/src/client/views/nodes/DataVizBox/DocCreatorMenu/Backend/TemplateMenuAIUtils.ts index 446fe3442..9bc2bfce2 100644 --- a/src/client/views/nodes/DataVizBox/DocCreatorMenu/Backend/TemplateMenuAIUtils.ts +++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu/Backend/TemplateMenuAIUtils.ts @@ -27,12 +27,13 @@ export class TemplateMenuAIUtils { } }; - public static renderGPTImageCall = async (template: Template, col: Col, fieldNumber: number | undefined): Promise<boolean> => { - const generateAndLoadImage = async (fieldNum: string, column: Col, prompt: string) => { + public static renderGPTImageCall = async (template: Template, col: Col, fieldNumber: number): Promise<boolean> => { + const generateAndLoadImage = async (id: number, prompt: string) => { const url = await this.generateGPTImage(prompt); - const field: TemplateField = template.getFieldByID(Number(fieldNum)); + var field: TemplateField = template.getFieldByID(id); field.setContent(url ?? '', ViewType.IMG); + field = template.getFieldByID(id); field.setTitle(col.title); }; @@ -40,14 +41,14 @@ export class TemplateMenuAIUtils { 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: ' + + `#${Math.random() * 100}: 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); + await generateAndLoadImage(fieldNumber, prompt); } catch (e) { console.log(e); } @@ -104,20 +105,20 @@ export class TemplateMenuAIUtils { * @returns a doc containing the fully rendered template */ public static applyGPTContentToTemplate = async (template: Template, assignments: { [field: string]: Col }): Promise<Template | undefined> => { - const GPTTextCalls = Object.entries(assignments).filter(([, col]) => col.type === TemplateFieldType.TEXT && !col.AIGenerated); - const GPTIMGCalls = Object.entries(assignments).filter(([, col]) => col.type === TemplateFieldType.VISUAL && !col.AIGenerated); + const GPTTextCalls = Object.entries(assignments).filter(([, col]) => col.type === TemplateFieldType.TEXT && col.AIGenerated); + const GPTIMGCalls = Object.entries(assignments).filter(([, col]) => col.type === TemplateFieldType.VISUAL && col.AIGenerated); if (GPTTextCalls.length) { - const promises = GPTTextCalls.map(([str, col]) => { - return TemplateMenuAIUtils.renderGPTTextCall(template, col, Number(str)); + const promises = GPTTextCalls.map(([id, col]) => { + return TemplateMenuAIUtils.renderGPTTextCall(template, col, Number(id)); }); await Promise.all(promises); } if (GPTIMGCalls.length) { - const promises = GPTIMGCalls.map(async ([fieldNum, col]) => { - return TemplateMenuAIUtils.renderGPTImageCall(template, col, Number(fieldNum)); + const promises = GPTIMGCalls.map(async ([id, col]) => { + return TemplateMenuAIUtils.renderGPTImageCall(template, col, Number(id)); }); await Promise.all(promises); diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu/DocCreatorMenu.tsx b/src/client/views/nodes/DataVizBox/DocCreatorMenu/DocCreatorMenu.tsx index 7beb93636..8a98399b6 100644 --- a/src/client/views/nodes/DataVizBox/DocCreatorMenu/DocCreatorMenu.tsx +++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu/DocCreatorMenu.tsx @@ -374,6 +374,7 @@ export class DocCreatorMenu extends ObservableReactComponent<DocCreateMenuProps> } @action updateRenderedPreviewCollection = async (template: Template) => { + this._docsRendering = true; this._fullyRenderedDocs = this._dataViz ? ((await this.templateManager.createDocsFromTemplate(this._dataViz, template, this.fieldsInfos, this.DEBUG_MODE)).filter(doc => doc).map(doc => doc!) ?? []) as unknown as Doc[] : []; this.updateRenderedDocCollection(); }; @@ -384,7 +385,6 @@ export class DocCreatorMenu extends ObservableReactComponent<DocCreateMenuProps> return; } else { this._selectedTemplate = template; - //template.renderUpdates(); this.updateRenderedPreviewCollection(template); } }; @@ -434,7 +434,7 @@ export class DocCreatorMenu extends ObservableReactComponent<DocCreateMenuProps> }; @action addField = () => { - const newFields: Col[] = this._userCreatedFields.concat([{ title: '', type: TemplateFieldType.UNSET, desc: '', sizes: [] }]); + const newFields: Col[] = this._userCreatedFields.concat([{ title: '', type: TemplateFieldType.UNSET, desc: '', sizes: [], AIGenerated: true }]); this._userCreatedFields = newFields; }; @@ -544,10 +544,10 @@ export class DocCreatorMenu extends ObservableReactComponent<DocCreateMenuProps> 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)); + if (!col.AIGenerated) { + var field = template.getFieldByID(Number(fieldID)); field.setContent(col.defaultContent ?? '', col.type === TemplateFieldType.VISUAL ? ViewType.IMG : ViewType.TEXT); + field = template.getFieldByID(Number(fieldID)); field.setTitle(col.title); } else { a[Number(fieldID)] = this.getColByTitle(colTitle); @@ -602,7 +602,7 @@ export class DocCreatorMenu extends ObservableReactComponent<DocCreateMenuProps> clone.x = 10000; clone.y = 10000; - await DrawingFillHandler.drawingToImage(clone, 100, prompt, undefined, this) + await DrawingFillHandler.drawingToImage(clone, 100, prompt, clone, this) return this.variations; } @@ -762,10 +762,9 @@ export class DocCreatorMenu extends ObservableReactComponent<DocCreateMenuProps> this._renderedDocCollection = collection; - // const mainCollection = this._dataViz?.DocumentView?.().containerViewPath?.().lastElement()?.ComponentView as CollectionFreeFormView; - // mainCollection.addDocument(collection); + this._docsRendering = false; - console.log('changed to: ', collection); + this.forceUpdate(); }; layoutPreviewContents = action(() => { diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/TemplateEditingScreen.tsx b/src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/TemplateEditingScreen.tsx deleted file mode 100644 index e69de29bb..000000000 --- a/src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/TemplateEditingScreen.tsx +++ /dev/null diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/TemplateEditingWindow.tsx b/src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/TemplateEditingWindow.tsx index 8b58ac1cf..df6b791c7 100644 --- a/src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/TemplateEditingWindow.tsx +++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/TemplateEditingWindow.tsx @@ -86,9 +86,9 @@ export class TemplateEditingWindow extends ObservableReactComponent<TemplateEdit onPointerDown={e => this._props.setupButtonClick(e, async () => { this._props.menu._variations = []; this._loading = true; - this._variationURLs = await this._props.menu.generateVariations(this._props.template.getRenderedDoc()!, this.fireflyPrompt); + this._variationURLs = await this._props.menu.generateVariations(this._props.template.clone(false).getRenderedDoc()!, this.fireflyPrompt); this._variationURLs.forEach(url => { - const newTemplate: Template = this._props.template.cloneBase(); + const newTemplate: Template = this._props.template.clone(true); this._props.menu._variations.push(newTemplate); }); this._loading = false; diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu/Template.ts b/src/client/views/nodes/DataVizBox/DocCreatorMenu/Template.ts index 1dc6692a2..10724c9f7 100644 --- a/src/client/views/nodes/DataVizBox/DocCreatorMenu/Template.ts +++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu/Template.ts @@ -57,7 +57,7 @@ export class Template { //dispose each subfields disposers, etc. }; - cloneBase = () => new Template(this._mainField?.makeClone(undefined) ?? TemplateLayouts.BasicSettings); + clone = (withContent: boolean = false) => new Template(this._mainField?.makeClone(undefined, withContent) ?? TemplateLayouts.BasicSettings); getRenderedDoc = () => this.doc; @@ -130,9 +130,7 @@ export class Template { }); } - this._mainField.addField(field); - - this._mainField.refreshRenderedDoc(); + this._mainField.makeBackgroundField(field) } getMatches = (cols: Col[]): number[][] => { diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu/TemplateFieldTypes/DynamicField.ts b/src/client/views/nodes/DataVizBox/DocCreatorMenu/TemplateFieldTypes/DynamicField.ts index 5fba68c14..fc520ba47 100644 --- a/src/client/views/nodes/DataVizBox/DocCreatorMenu/TemplateFieldTypes/DynamicField.ts +++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu/TemplateFieldTypes/DynamicField.ts @@ -11,6 +11,7 @@ import { FieldSettings, TemplateField, ViewType } from './TemplateField'; export class DynamicField extends TemplateField { protected _disposers: { [name: string]: IDisposer } = {}; protected _subfields: TemplateField[] = []; + protected backgroundField: TemplateField | undefined; get getSubfields() { return this._subfields; @@ -72,11 +73,31 @@ export class DynamicField extends TemplateField { addChildToDocument = (doc: Doc) => this._renderDoc && Doc.SetContainer(doc, this._renderDoc); + makeBackgroundField = (field: TemplateField) => { + if (this.backgroundField && this.backgroundField !== field) { + this.removeField(this.backgroundField); + this.backgroundField = undefined; + } + if (field && field !== this.backgroundField) { + this.addField(field); + this.backgroundField = field; + } + this.refreshRenderedDoc(); + } + matches = (): Array<number> => []; - makeClone(parent?: DynamicField) { + makeClone(parent?: DynamicField, withContent: boolean = false) { const dynClone = super.makeClone(parent) as DynamicField; - dynClone._subfields = this.getSubfields.map(cloneField => cloneField.makeClone(dynClone)); + dynClone._subfields = this.getSubfields.map(field => { + if (field === this.backgroundField) { + const backgroundField: TemplateField = field.makeClone(dynClone, true); + dynClone.makeBackgroundField(backgroundField); + return backgroundField; + } else { + return field.makeClone(dynClone, withContent) + } + }); if (dynClone._renderDoc) { dynClone._renderDoc[DocData].data = new List<Doc>(dynClone.getSubfields.filter(sub => sub.renderedDoc).map(sub => sub.renderedDoc!)); } diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu/TemplateFieldTypes/StaticContentField.ts b/src/client/views/nodes/DataVizBox/DocCreatorMenu/TemplateFieldTypes/StaticContentField.ts index 420d03076..112f37cc6 100644 --- a/src/client/views/nodes/DataVizBox/DocCreatorMenu/TemplateFieldTypes/StaticContentField.ts +++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu/TemplateFieldTypes/StaticContentField.ts @@ -37,6 +37,11 @@ export class ImageTemplateField extends StaticContentField { this._renderDoc = Docs.Create.ImageDocument(this._content, settings.opts); return this; } + + updateDocSetting(setting: string, newVal: string) { + if (setting === 'backgroundColor') return; + super.updateDocSetting(setting, newVal); + } } export class TextTemplateField extends StaticContentField { diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu/TemplateFieldTypes/TemplateField.ts b/src/client/views/nodes/DataVizBox/DocCreatorMenu/TemplateFieldTypes/TemplateField.ts index e2779968d..bc6b13c76 100644 --- a/src/client/views/nodes/DataVizBox/DocCreatorMenu/TemplateFieldTypes/TemplateField.ts +++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu/TemplateFieldTypes/TemplateField.ts @@ -67,22 +67,23 @@ export abstract class TemplateField { setTitle = (title: string) => { this._title = title; - this._renderDoc && (this._renderDoc.title = title); + this.settings.title = title; + if (this._renderDoc) this._renderDoc.title = title }; getTitle = () => this._title; - updateDocSetting = (setting: string, newVal: string) => { + updateDocSetting(setting: string, newVal: string) { if (this._renderDoc) this._renderDoc[setting] = newVal; const settings: {[s: string]: string } = {[setting]: newVal} Object.assign(this.settings.opts, settings); } - makeClone(parent?: TemplateField) { + makeClone(parent?: TemplateField, withContent: boolean = false) { const settings: FieldSettings = structuredClone(this._settings); const cloned = TemplateField.CreateField(settings, this._id, parent, true); // create a value for this.Document/subfields that we want to ignore - this._renderDoc && Doc.MakeClone(this._renderDoc).then(({ clone }) => (cloned._renderDoc = clone)); - cloned._title = this._title; + cloned.setTitle(this._title); cloned._dimensions = this._dimensions; + withContent && cloned.setContent(this.getContent()); return cloned; } @@ -91,9 +92,8 @@ export abstract class TemplateField { } // eslint-disable-next-line @typescript-eslint/no-unused-vars changeFieldType = (newType: ViewType): TemplateField => { - const newSettings = this._settings; - newSettings.viewType = newType; - const newField = TemplateField.CreateField(newSettings, this._id, this._parent, true); + this._settings.viewType = newType; + const newField = TemplateField.CreateField(this._settings, this._id, this._parent, true); this._parent?.exchangeFields(newField, this); return newField; }; diff --git a/src/client/views/nodes/ImageBox.scss b/src/client/views/nodes/ImageBox.scss index 3d6942e6f..5625f2f56 100644 --- a/src/client/views/nodes/ImageBox.scss +++ b/src/client/views/nodes/ImageBox.scss @@ -105,7 +105,7 @@ display: flex; height: 100%; img { - object-fit: contain; + // object-fit: contain; height: 100%; } |