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 --- .../DocCreatorMenu/Backend/TemplateManager.ts | 99 ++++++++++++++++++++++ .../Backend/TemplateMenuFireflyManager.ts | 0 .../Backend/TemplateMenuGPTManager.ts | 0 3 files changed, 99 insertions(+) create mode 100644 src/client/views/nodes/DataVizBox/DocCreatorMenu/Backend/TemplateManager.ts create mode 100644 src/client/views/nodes/DataVizBox/DocCreatorMenu/Backend/TemplateMenuFireflyManager.ts create mode 100644 src/client/views/nodes/DataVizBox/DocCreatorMenu/Backend/TemplateMenuGPTManager.ts (limited to 'src/client/views/nodes/DataVizBox/DocCreatorMenu/Backend') diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu/Backend/TemplateManager.ts b/src/client/views/nodes/DataVizBox/DocCreatorMenu/Backend/TemplateManager.ts new file mode 100644 index 000000000..6d63078a8 --- /dev/null +++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu/Backend/TemplateManager.ts @@ -0,0 +1,99 @@ +import { action, makeAutoObservable } from 'mobx'; +import { Col } from '../DocCreatorMenu'; +import { FieldSettings } from '../TemplateFieldTypes/TemplateField'; +import { Template } from '../Template'; +import { NumListCast } from '../../../../../../fields/Doc'; +import { DataVizBox } from '../../DataVizBox'; +import { TemplateFieldType } from '../TemplateBackend'; +import { TemplateMenuGPTManager } from './TemplateMenuGPTManager'; + +export type Conditional = { + field: string; + operator: '=' | '>' | '<'; + condition: string; + target: 'self' | 'template'; + attribute: string; + value: string; +} + +export class TemplateManager { + + templates: Template[] = []; + + fieldConditions: Record = {}; + + constructor(templateSettings: FieldSettings[]) { + makeAutoObservable(this); + this.templates = this.initializeTemplates(templateSettings); + } + + initializeTemplates = (templateSettings: FieldSettings[]) => templateSettings.map(settings => { + return new Template(settings, this.fieldConditions)}); + + getValidTemplates = (cols: Col[]) => this.templates.filter(template => template.isValidTemplate(cols)); + + addTemplate = (newTemplate: Template) => this.templates.push(newTemplate); + + removeTemplate = (template: Template) => { + this.templates.splice(this.templates.indexOf(template), 1); + template.cleanup(); + }; + + addFieldCondition = (fieldTitle: string, condition: Conditional) => { + if (this.fieldConditions[fieldTitle] === undefined) { + this.fieldConditions[fieldTitle] = [condition]; + } else { + this.fieldConditions[fieldTitle].push(condition); + } + } + + removeFieldCondition = (fieldTitle: string, condition: Conditional) => { + if (this.fieldConditions[fieldTitle]) { + this.fieldConditions[fieldTitle] = this.fieldConditions[fieldTitle].filter(cond => cond !== condition); + } + } + + createDocsFromTemplate = action((dv: DataVizBox, template: Template, csvColumns: Col[], GPTManager: TemplateMenuGPTManager, debug: boolean = false) => { + 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); + field && field.setContent(content[title], field.viewType); + }); + + const gptFunc = (type: TemplateFieldType) => (type === TemplateFieldType.VISUAL ? GPTManager.renderGPTImageCall : GPTManager.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); + } + }); + + return Promise.all(gptPromises) + }; + + const rowContents = debug + ? [{}, {}, {}, {}] + : 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 => { + return renderedDocs; + }) + ); + }); +} diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu/Backend/TemplateMenuFireflyManager.ts b/src/client/views/nodes/DataVizBox/DocCreatorMenu/Backend/TemplateMenuFireflyManager.ts new file mode 100644 index 000000000..e69de29bb diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu/Backend/TemplateMenuGPTManager.ts b/src/client/views/nodes/DataVizBox/DocCreatorMenu/Backend/TemplateMenuGPTManager.ts new file mode 100644 index 000000000..e69de29bb -- cgit v1.2.3-70-g09d2 From f60a8539a4b1fd0c9b3249b94bff011fd7f742e5 Mon Sep 17 00:00:00 2001 From: Nathan-SR <144961007+Nathan-SR@users.noreply.github.com> Date: Fri, 9 May 2025 19:15:29 -0400 Subject: bunch of changes, everything still broken --- .../DocCreatorMenu/Backend/TemplateManager.ts | 12 +- .../DocCreatorMenu/Backend/TemplateMenuAIUtils.ts | 129 ++++++++++ .../Backend/TemplateMenuFireflyManager.ts | 0 .../Backend/TemplateMenuGPTManager.ts | 0 .../DataVizBox/DocCreatorMenu/DocCreatorMenu.tsx | 261 ++++++++------------- .../Menu/SuggestedTemplatesWindow.tsx | 81 ------- .../DocCreatorMenu/Menu/TemplateEditingWindow.tsx | 134 +++++++++++ .../DocCreatorMenu/Menu/TemplatePreviewBox.tsx | 31 ++- .../Menu/TemplatesSidescrollDisplay.tsx | 60 +++++ .../nodes/DataVizBox/DocCreatorMenu/Template.ts | 15 +- .../TemplateFieldTypes/TemplateField.ts | 21 +- 11 files changed, 477 insertions(+), 267 deletions(-) create mode 100644 src/client/views/nodes/DataVizBox/DocCreatorMenu/Backend/TemplateMenuAIUtils.ts delete mode 100644 src/client/views/nodes/DataVizBox/DocCreatorMenu/Backend/TemplateMenuFireflyManager.ts delete mode 100644 src/client/views/nodes/DataVizBox/DocCreatorMenu/Backend/TemplateMenuGPTManager.ts delete mode 100644 src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/SuggestedTemplatesWindow.tsx create mode 100644 src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/TemplateEditingWindow.tsx create mode 100644 src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/TemplatesSidescrollDisplay.tsx (limited to 'src/client/views/nodes/DataVizBox/DocCreatorMenu/Backend') diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu/Backend/TemplateManager.ts b/src/client/views/nodes/DataVizBox/DocCreatorMenu/Backend/TemplateManager.ts index 6d63078a8..ef7dbc7ab 100644 --- a/src/client/views/nodes/DataVizBox/DocCreatorMenu/Backend/TemplateManager.ts +++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu/Backend/TemplateManager.ts @@ -2,10 +2,10 @@ import { action, makeAutoObservable } from 'mobx'; import { Col } from '../DocCreatorMenu'; import { FieldSettings } from '../TemplateFieldTypes/TemplateField'; import { Template } from '../Template'; -import { NumListCast } from '../../../../../../fields/Doc'; +import { Doc, NumListCast } from '../../../../../../fields/Doc'; import { DataVizBox } from '../../DataVizBox'; import { TemplateFieldType } from '../TemplateBackend'; -import { TemplateMenuGPTManager } from './TemplateMenuGPTManager'; +import { TemplateMenuAIUtils } from './TemplateMenuAIUtils'; export type Conditional = { field: string; @@ -28,7 +28,7 @@ export class TemplateManager { } initializeTemplates = (templateSettings: FieldSettings[]) => templateSettings.map(settings => { - return new Template(settings, this.fieldConditions)}); + return new Template(settings)}); getValidTemplates = (cols: Col[]) => this.templates.filter(template => template.isValidTemplate(cols)); @@ -53,7 +53,7 @@ export class TemplateManager { } } - createDocsFromTemplate = action((dv: DataVizBox, template: Template, csvColumns: Col[], GPTManager: TemplateMenuGPTManager, debug: boolean = false) => { + createDocsFromTemplate = action((dv: DataVizBox, template: Template, csvColumns: Col[], debug: boolean = false) => { const fields = Array.from(Object.keys(dv.records[0])); const processContent = (content: { [title: string]: string }) => { @@ -66,7 +66,7 @@ export class TemplateManager { field && field.setContent(content[title], field.viewType); }); - const gptFunc = (type: TemplateFieldType) => (type === TemplateFieldType.VISUAL ? GPTManager.renderGPTImageCall : GPTManager.renderGPTTextCall); + const gptFunc = (type: TemplateFieldType) => (type === TemplateFieldType.VISUAL ? TemplateMenuAIUtils.renderGPTImageCall : TemplateMenuAIUtils.renderGPTTextCall); const gptPromises = csvColumns .filter(field => field.type !== TemplateFieldType.UNSET && field.AIGenerated) .map(field => { @@ -76,7 +76,7 @@ export class TemplateManager { } }); - return Promise.all(gptPromises) + return templateCopy.getRenderedDoc(); }; const rowContents = debug diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu/Backend/TemplateMenuAIUtils.ts b/src/client/views/nodes/DataVizBox/DocCreatorMenu/Backend/TemplateMenuAIUtils.ts new file mode 100644 index 000000000..446fe3442 --- /dev/null +++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu/Backend/TemplateMenuAIUtils.ts @@ -0,0 +1,129 @@ +import { action } from "mobx"; +import { Upload } from "openai/resources"; +import { ClientUtils } from "../../../../../../ClientUtils"; +import { Networking } from "../../../../../Network"; +import { gptImageCall, gptAPICall, GPTCallType } from "../../../../../apis/gpt/GPT"; +import { Col } from "../DocCreatorMenu"; +import { TemplateFieldSize, TemplateFieldType } from "../TemplateBackend"; +import { TemplateField, ViewType } from "../TemplateFieldTypes/TemplateField"; +import { Template } from "../Template"; +import { Doc } from "../../../../../../fields/Doc"; +import { DrawingFillHandler } from "../../../../smartdraw/DrawingFillHandler"; +import { CollectionFreeFormView } from "../../../../collections/collectionFreeForm"; + +export class TemplateMenuAIUtils { + + public static generateGPTImage = async (prompt: string): Promise => { + 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); + } + }; + + public static renderGPTImageCall = async (template: Template, col: Col, fieldNumber: number | undefined): Promise => { + 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; + }; + + public static renderGPTTextCall = async (template: Template, col: Col, fieldNum: number | undefined): Promise => { + 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(`${Math.random() * 100000}: ${prompt}`, GPTCallType.FILL); + + 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)); + + field.setContent(info.content ?? '', ViewType.TEXT); + field.setTitle(col.title); + }); + } + } catch (err) { + console.log(err); + } + + return true; + }; + + /** + * 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 + */ + public static applyGPTContentToTemplate = async (template: Template, assignments: { [field: string]: Col }): Promise