aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorNathan-SR <144961007+Nathan-SR@users.noreply.github.com>2024-09-05 02:27:13 -0400
committerNathan-SR <144961007+Nathan-SR@users.noreply.github.com>2024-09-05 02:27:13 -0400
commit5bbd63ae6c581a945c9b7000d330de87398f8f36 (patch)
treefeba69062ce9b48839d016f4d940d5d8b3fc1cc0 /src
parentb4e4cca578747c987efde7fa8c14299790f3ee02 (diff)
fingers crossed
Diffstat (limited to 'src')
-rw-r--r--src/client/apis/gpt/GPT.ts6
-rw-r--r--src/client/views/nodes/DataVizBox/DocCreatorMenu.tsx190
2 files changed, 150 insertions, 46 deletions
diff --git a/src/client/apis/gpt/GPT.ts b/src/client/apis/gpt/GPT.ts
index 6befad310..7351fd0f5 100644
--- a/src/client/apis/gpt/GPT.ts
+++ b/src/client/apis/gpt/GPT.ts
@@ -14,7 +14,8 @@ enum GPTCallType {
DATA = 'data',
TEMPLATE = "template",
VIZSUM = 'vizsum',
- VIZSUM2 = 'vizsum2'
+ VIZSUM2 = 'vizsum2',
+ FILL = 'fill'
}
type GPTCallOpts = {
@@ -58,7 +59,8 @@ const callTypeMap: { [type: string]: GPTCallOpts } = {
},
template: { model: 'gpt-4-turbo', maxTokens: 512, temp: 0.5, prompt: 'You will be given a list of field descriptions for multiple templates in the format {field #0: “description”}{field #1: “description”}{...}, and a list of column descriptions in the format {“title”: “description”}{...}. Your job is to match columns with fields based on their descriptions. Your output should be in the following JSON format: {“Template title”:{“#”: “title”, “#”: “title”, “#”: “title” …}, “Template title”:{“#”: “title”, “#”: “title”, “#”: “title” …}} where “Template title” represents the template, # represents the field # and “title” the title of the column assigned to it. A filled out example might look like {“fivefield2”:{“0”:”Name”, “1”:”Image”, “2”:”Caption”, “3”:”Position”, “4”:”Stats”}, “fivefield3”:{0:”Image”, 1:”Name”, 2:”Caption”, 3:”Stats”, 4:”Position”}. Include one object for each template. IT IS VERY IMPORTANT THAT YOU ONLY INCLUDE TEXT IN THE FORMAT ABOVE, WITH NO ADDITIONS WHATSOEVER. Do not include extraneous ‘#’ characters, ‘column’ for columns, or ‘template’ for templates: ONLY THE TITLES AND NUMBERS. There should never be one column assigned to more than one field (ie. if the “name” column is assigned to field 1, it can’t be assigned to any other fields) . Do this for each template whose fields are described. The descriptions are as follows:' },
vizsum: { model: 'gpt-4-turbo', maxTokens: 512, temp: 0.5, prompt: 'Your job is to provide brief descriptions for columns in a dataset based on example rows. Your descriptions should be geared towards how each column’s data might fit together into a visual template. Would they make good titles, main focuses, captions, descriptions, etc. Pay special attention to connections between columns, i.e. is there one column that specifically seems to describe/be related to another more than the rest? You should provide your analysis in JSON format like so: {“col1”:”description”, “col2”:”description”, …}. DO NOT INCLUDE ANY OTHER TEXT, ONLY THE JSON.'},
- vizsum2: { model: 'gpt-4-turbo', maxTokens: 512, temp: 0.5, prompt: 'Your job is to provide structured information on columns in a dataset based on example rows. You will categorize each column in two ways: by type and size. The size categories are as follows: tiny (one or two words), small (a sentence/multiple words), medium (a few sentences), large (a longer paragraph), and huge (a very long or multiple paragraphs). The type categories are as follows: visual (links/file paths to images, pdfs, maps, or any other visual media type), and text (plain text that isn’t a link/file path). Visual media should be assumed to have size “medium” “large” or “huge”. You will give your responses in JSON format, like so: {“title (of column)”:{“type”:”text”, “size”:”small”}, “title (of column)”:{“type”:”visual”, “size”:”medium”}, …}. DO NOT INCLUDE ANY OTHER TEXT, ONLY THE JSON.'}
+ vizsum2: { model: 'gpt-4-turbo', maxTokens: 512, temp: 0.5, prompt: 'Your job is to provide structured information on columns in a dataset based on example rows. You will categorize each column in two ways: by type and size. The size categories are as follows: tiny (one or two words), small (a sentence/multiple words), medium (a few sentences), large (a longer paragraph), and huge (a very long or multiple paragraphs). The type categories are as follows: visual (links/file paths to images, pdfs, maps, or any other visual media type), and text (plain text that isn’t a link/file path). Visual media should be assumed to have size “medium” “large” or “huge”. You will give your responses in JSON format, like so: {“title (of column)”:{“type”:”text”, “size”:”small”}, “title (of column)”:{“type”:”visual”, “size”:”medium”}, …}. DO NOT INCLUDE ANY OTHER TEXT, ONLY THE JSON.'},
+ fill: { model: 'gpt-4-turbo', maxTokens: 512, temp: 0.5, prompt: 'Your job is to generate content for fields based on a user prompt and background context given to you. You will be given the content of the other fields present in the format: ---- Field # (field title): content ---- Field # (field title): content ----- (etc.) You will be given info on the columns to generate for in the format ---- title: , prompt: , word limit: , assigned field: ----. For each column, based on the prompt, word limit, and the context of existing fields, you should generate a short response in the following JSON format: {“title” (should be the title from the column description with no additions): {“number”:”#” (where # is the assigned field of the column), “content”:”response” (where response is your response to the prompt in the column info)}}. ONLY INCLUDE THE JSON TEXT WITH NO OTHER ADDED TEXT. The word limit for each column applies only to that column’s response. You should include one object per column you are provided info on.' }
};
let lastCall = '';
let lastResp = '';
diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu.tsx b/src/client/views/nodes/DataVizBox/DocCreatorMenu.tsx
index 191b387c4..33d9a8827 100644
--- a/src/client/views/nodes/DataVizBox/DocCreatorMenu.tsx
+++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu.tsx
@@ -31,6 +31,7 @@ import { dropActionType } from '../../../util/DropActionTypes';
import { ImageBox } from '../ImageBox';
import { a } from '@react-spring/web';
import { RichTextMenu } from '../formattedText/RichTextMenu';
+import e from 'cors';
export enum LayoutType {
Stacked = 'stacked',
@@ -137,8 +138,22 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps> {
@computed get fieldsInfos(): Col[] {
const colInfo = this._dataViz?.colsInfo;
- return this.selectedFields.map(field => {return {title: field, type: colInfo?.get(field)?.type ?? TemplateFieldType.UNSET, desc: colInfo?.get(field)?.desc ?? '', size: colInfo?.get(field)?.size ?? TemplateFieldSize.MEDIUM, defaultContent: colInfo?.get(field)?.defaultContent ?? ''}});
- //.concat(this._columns)
+ return this.selectedFields.map(field => {
+ const fieldInfo = colInfo?.get(field);
+
+ const col: Col = {
+ title: field,
+ type: fieldInfo?.type ?? TemplateFieldType.UNSET,
+ desc: fieldInfo?.desc ?? '',
+ size: fieldInfo?.size ?? TemplateFieldSize.MEDIUM
+ };
+
+ if (fieldInfo?.defaultContent !== undefined) {
+ col.defaultContent = fieldInfo.defaultContent;
+ }
+
+ return col;
+ }).concat(this._columns);
}
@computed get canMakeDocs(){
@@ -463,7 +478,7 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps> {
}
};
- setFieldType = (column: Col, type: TemplateFieldType) => {
+ setColType = (column: Col, type: TemplateFieldType) => {
if (this.selectedFields.includes(column.title)) {
this._dataViz?.setColumnType(column.title, type);
} else {
@@ -472,7 +487,7 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps> {
this.forceUpdate();
};
- setFieldSize = (column: Col, size: TemplateFieldSize) => {
+ setColSize = (column: Col, size: TemplateFieldSize) => {
if (this.selectedFields.includes(column.title)) {
this._dataViz?.setColumnSize(column.title, size);
} else {
@@ -481,11 +496,12 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps> {
this.forceUpdate();
};
- setFieldDesc = (column: Col, desc: string) => {
+ setColDesc = (column: Col, desc: string) => {
if (this.selectedFields.includes(column.title)) {
this._dataViz?.setColumnDesc(column.title, desc);
} else {
column.desc = desc;
+ console.log(column, column.desc)
}
this.forceUpdate();
};
@@ -578,11 +594,45 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps> {
// return new Doc;
// }
- createEmptyTemplate = (template: TemplateDocInfos, assignments: {[field: string]: Col}): Doc => {
+ fillPresetTemplate = async(template: TemplateDocInfos, assignments: {[field: string]: Col}): Promise<Doc> => {
+ 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 fields: Doc[] = [];
- Object.entries(assignments).forEach(([f, col]) => {
+ const GPTAssignments = Object.entries(assignments).filter(([f, col]) => this._columns.includes(col));
+ const nonGPTAssignments: [string, Col][] = Object.entries(assignments).filter(a => !GPTAssignments.includes(a));
+
+ const stringifyGPTInfo = (): string => {
+ let string: string = '*** COLUMN INFO:';
+ GPTAssignments.forEach(([fieldNum, col]) => {
+ `--- title: ${col.title}, prompt: ${col.desc}, word limit: ${wordLimit(col.size)} words, assigned field: ${fieldNum} ---`
+ });
+ return string += ' ***';
+ };
+
+ const GPTAssignmentString = stringifyGPTInfo();
+ console.log('assignment string', GPTAssignmentString);
+
+ let fieldContent: string = '';
+
+ Object.entries(nonGPTAssignments).forEach(([f, strCol]) => {
const field: Field = template.fields[Number(f)];
+ const col = strCol[1];
const doc = (col.type === TemplateFieldType.VISUAL ? FieldFuncs.ImageField : FieldFuncs.TextField)({
tl: field.tl,
@@ -594,6 +644,8 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps> {
field.opts
);
+ fieldContent += `--- Field #${f} (title: ${col.title}): ${col.defaultContent ?? ''} ---`
+
fields.push(doc);
});
@@ -611,21 +663,67 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps> {
fields.push(doc);
});
- const renderedTemplate = Docs.Create.FreeformDocument(fields, {
- _height: template.height,
- _width: template.width,
- title: template.title,
- backgroundColor: template.opts.backgroundColor,
- _layout_borderRounding: `${template.opts.cornerRounding}px` ?? '0px',
- borderWidth: template.opts.borderWidth,
- borderColor: template.opts.borderColor,
- x: 400,
- y: 400,
- });
+ const createMainDoc = (): Doc => {
+ const main = Docs.Create.FreeformDocument(fields, {
+ _height: template.height,
+ _width: template.width,
+ title: template.title,
+ backgroundColor: template.opts.backgroundColor,
+ _layout_borderRounding: `${template.opts.cornerRounding}px` ?? '0px',
+ borderWidth: template.opts.borderWidth,
+ borderColor: template.opts.borderColor,
+ x: 400,
+ y: 400,
+ });
- const mainCollection = this._dataViz?.DocumentView?.().containerViewPath?.().lastElement()?.ComponentView as CollectionFreeFormView;
- mainCollection.addDocument(renderedTemplate);
- return renderedTemplate;
+ const mainCollection = this._dataViz?.DocumentView?.().containerViewPath?.().lastElement()?.ComponentView as CollectionFreeFormView;
+ mainCollection.addDocument(main);
+
+ return main;
+ }
+
+ console.log('field content', fieldContent)
+
+ if (GPTAssignments.length) {
+
+ try {
+ const prompt = fieldContent + GPTAssignmentString;
+
+ const res = await gptAPICall(prompt, GPTCallType.FILL);
+
+ if (res){
+ console.log('response', res);
+
+ const assignments: {[title: string]: {number: string, content: string}} = JSON.parse(res);
+ console.log('assignments', assignments);
+ Object.entries(assignments).forEach(([title, info]) => {
+ const field: Field = template.fields[Number(info.number)];
+ const col = this.getColByTitle(title);
+
+ const doc = (col.type === TemplateFieldType.VISUAL ? FieldFuncs.ImageField : FieldFuncs.TextField)({
+ tl: field.tl,
+ br: field.br },
+ template.height,
+ template.width,
+ col.title,
+ info.content ?? '',
+ field.opts
+ );
+
+ fields.push(doc);
+ });
+
+ return createMainDoc();
+ }
+ } catch(err) {
+ console.log(err);
+ }
+
+ } else {
+ return createMainDoc();
+ }
+
+ return new Doc;
};
compileFieldDescriptions = (templates: TemplateDocInfos[]): string => {
@@ -693,17 +791,21 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps> {
return [];
};
- generatePresetTemplates = async() => {
+ generatePresetTemplates = async () => {
const cols = this.fieldsInfos;
const templates = this.findValidTemplates(cols, TemplateLayouts.allTemplates);
- const assignments: [TemplateDocInfos, {[field: number]: Col}][] = await this.assignColsToFields(templates, cols);
- const renderedTemplates: Doc[] = [];
- assignments.forEach(([template, assignments]) => {
- renderedTemplates.push(this.createEmptyTemplate(template, assignments));
- });
-
- setTimeout(() => {this.setGSuggestedTemplates(renderedTemplates)});
- }
+
+ const assignments: [TemplateDocInfos, { [field: number]: Col }][] = await this.assignColsToFields(templates, cols);
+
+ const renderedTemplatePromises: Promise<Doc>[] = assignments.map(([template, assignments]) =>
+ this.fillPresetTemplate(template, assignments)
+ );
+
+ const renderedTemplates: Doc[] = await Promise.all(renderedTemplatePromises);
+
+ setTimeout(() => { this.setGSuggestedTemplates(renderedTemplates) });
+ };
+
get templatesPreviewContents(){
const renderedTemplates: Doc[] = [];
@@ -1067,15 +1169,15 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps> {
{field.type === TemplateFieldType.UNSET ? <span className='placeholder'>Select media type</span> : null}
<span className='type-display'>{field.type === TemplateFieldType.TEXT ? 'Text Field' : field.type === TemplateFieldType.VISUAL ? 'File Field' : ''}</span>
<div className='bubbles'>
- <input className='bubble' type="radio" name="type" onClick={() => {this.setFieldType(field, TemplateFieldType.TEXT)}}/>
+ <input className='bubble' type="radio" name="type" onClick={() => {this.setColType(field, TemplateFieldType.TEXT)}}/>
<div className='text'>Text</div>
- <input className='bubble' type="radio" name="type" onClick={() => {this.setFieldType(field, TemplateFieldType.VISUAL)}}/>
+ <input className='bubble' type="radio" name="type" onClick={() => {this.setColType(field, TemplateFieldType.VISUAL)}}/>
<div className='text'>File</div>
</div>
</div>
<input className='field-property-container' id={`${Math.random() * 100}`} placeholder={this._dataViz?.GPTSummary?.get(field.title)?.size} style={{width: field.title === '' ? '30%' : ''}}/>
</div>
- <textarea className='field-description-container' onChange={(e) => this._dataViz?.setColumnDesc(field.title, e.target.value)} defaultValue={field.desc === this._dataViz?.GPTSummary?.get(field.title)?.desc ? '' : field.desc } placeholder={this._dataViz?.GPTSummary?.get(field.title)?.desc ?? 'Add a description to help with template generation.'} />
+ <textarea className='field-description-container' onChange={(e) => this.setColDesc(field, e.target.value)} defaultValue={field.desc === this._dataViz?.GPTSummary?.get(field.title)?.desc ? '' : field.desc } placeholder={this._dataViz?.GPTSummary?.get(field.title)?.desc ?? 'Add a description to help with template generation.'} />
<div>
<button className='docCreatorMenu-menu-button section-reveal-options top-right' onPointerDown={e => this.setUpButtonClick(e, () => this.removeField(field))}>
<FontAwesomeIcon icon='trash'/>
@@ -1821,37 +1923,37 @@ export class TemplateLayouts {
backgroundColor: '#9E9C95'
},
fields: [{
- tl: [-.95, .8],
- br: [-.1, .95],
+ tl: [-.875, -.9],
+ br: [.875, .7],
types: [TemplateFieldType.VISUAL],
sizes: [TemplateFieldSize.MEDIUM, TemplateFieldSize.LARGE, TemplateFieldSize.HUGE],
- description: '',
+ description: 'A medium to large visual field for the main content of the template',
opts: {
- backgroundColor: 'transparent',
- color: 'white',
- contentXCentering: 'h-right',
+ borderWidth: '15',
+ borderColor: '#E0E0DA',
}
}, {
- tl: [.1, .8],
- br: [.95, .95],
+ tl: [.1, .775],
+ br: [.95, .975],
types: [TemplateFieldType.TEXT],
sizes: [TemplateFieldSize.TINY, TemplateFieldSize.SMALL],
description: 'A very small text field for one to a few words. The content should represent a general categorization of the image.',
opts: {
backgroundColor: 'transparent',
- color: 'red',
+ color: '#AF0D0D',
fontTransform: 'uppercase',
fontBold: true,
contentXCentering: 'h-left'
}
}, {
- tl: [0, -.9],
- br: [.85, -.66],
+ tl: [-.95, .775],
+ br: [-.1, .975],
types: [TemplateFieldType.TEXT],
sizes: [TemplateFieldSize.TINY, TemplateFieldSize.SMALL],
description: 'A very small text field for one to a few words. The content should contextualize field 2.',
opts: {
backgroundColor: 'transparent',
+ color: 'black',
contentXCentering: 'h-right'
}
}],