aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSophie Zhang <sophie_zhang@brown.edu>2023-12-06 22:06:16 -0500
committerSophie Zhang <sophie_zhang@brown.edu>2023-12-06 22:06:16 -0500
commitf59e343553878029d846e5eae2963fc9c3481b13 (patch)
treec8fc80a059841664aeb79f09f26217737494aa4c
parent7bde1b066a68fca6202b3f42c1cb54aa85c13890 (diff)
types
-rw-r--r--src/client/apis/gpt/customization.ts51
-rw-r--r--src/client/views/PropertiesView.scss6
-rw-r--r--src/client/views/PropertiesView.tsx85
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx21
-rw-r--r--src/client/views/nodes/trails/PresBox.tsx4
5 files changed, 112 insertions, 55 deletions
diff --git a/src/client/apis/gpt/customization.ts b/src/client/apis/gpt/customization.ts
index 38458f22c..135b83353 100644
--- a/src/client/apis/gpt/customization.ts
+++ b/src/client/apis/gpt/customization.ts
@@ -1,3 +1,4 @@
+import { ChatCompletionRequestMessage } from 'openai';
import { openai } from './setup';
export enum CustomizationType {
@@ -14,6 +15,18 @@ export interface DocumentWithColor {
color: string;
}
+export interface StyleInputDocument {
+ id: number;
+ textContent: string;
+ textSize: number;
+}
+
+export interface StyleInput {
+ collectionDescription: string;
+ documents: StyleInputDocument[];
+ imageColors: string[];
+}
+
interface PromptInfo {
description: string;
features: { name: string; description: string; values?: string[] }[];
@@ -71,12 +84,12 @@ export const gptTrailSlideCustomization = async (inputText: string) => {
};
// palette / styling
-export const generatePalette = async (inputData: any, useImageData: boolean) => {
- let prompt = 'Dash is a hypermedia web application that allows users to organize documents of different media types into collections. I want you to come up with cohesive color palettes for a collection.';
+export const generatePalette = async (inputData: StyleInput, useImageData: boolean, inputText: string, lastResponse?: GeneratedResponse[]) => {
+ let prompt = 'Dash is a hypermedia web application that allows users to organize documents of different media types into collections. The user wants you to come up with cohesive color palettes for a collection.';
prompt +=
- 'I am going to give you a json object of this format:' +
+ ' The user is going to give you a json object of this format:' +
JSON.stringify({ collectionDescription: 'string', documents: 'Document[]', imageColors: 'string[]' }) +
- '. collectionDescription is the title of the collection, which you should create color palettes based on. This is the document format:' +
+ '. The user may follow by giving more specific instructions on what kind of palettes they want. collectionDescription is the title of the collection, which you should create color palettes based on. This is the document format:' +
JSON.stringify({
id: 'number',
textSize: 'number',
@@ -84,7 +97,7 @@ export const generatePalette = async (inputData: any, useImageData: boolean) =>
}) +
(useImageData ? '. Finally, imageColors are the main hex colors of the images in the collection.' : '. Ignore imageColors.') +
'You are going to generate three distinct variants of color palettes for the user to choose from based mostly on collectionDescription, and loosely on the text content and text size of the documents.' +
- (useImageData && 'You should slightly take imageColors into account, but primarly focus on crafting a palette that matches the text content.') +
+ (useImageData ? 'You should slightly take imageColors into account, but primarly focus on crafting a palette that matches the text content.' : '') +
'The variants should start with a light palette and grow increasingly more intense and vibrant. Return a json array of three objects in this format:' +
JSON.stringify({
collectionBackgroundColor: 'string',
@@ -95,16 +108,32 @@ export const generatePalette = async (inputData: any, useImageData: boolean) =>
id: 'number',
color: 'string',
}) +
- ", and each element’s color is based on the theme of the overall color palette and also by its document’s textContent. Please pay attention to aesthetics of how each document's color complement the background and each other and choose a variety of colors when appropriate. Important: Respond with only the JSON array and no other text.";
+ ", and each element’s color is based on the theme of the overall color palette and also by its document’s textContent. Please pay attention to aesthetics of how each document's color complement the background and each other and choose a variety of colors when appropriate.";
+
+ // enforce format
+ prompt += 'Important: Respond with only the JSON array and no other text.';
+
+ // iteration
+
+ let messages: ChatCompletionRequestMessage[] = [
+ { role: 'system', content: prompt },
+ { role: 'user', content: JSON.stringify(inputData) },
+ ];
+
+ if (lastResponse && inputText !== '') {
+ messages.push({ role: 'assistant', content: JSON.stringify(lastResponse) });
+ messages.push({ role: 'user', content: 'Please modify the previously generated palettes with the following: ' + inputText });
+ } else if (inputText !== '') {
+ messages.push({ role: 'user', content: inputText });
+ }
+
+ console.log('Prompt: ', prompt);
+ console.log('Messages: ', messages);
- console.log('Prompt', prompt);
try {
const response = await openai.createChatCompletion({
model: 'gpt-4',
- messages: [
- { role: 'system', content: prompt },
- { role: 'user', content: JSON.stringify(inputData) },
- ],
+ messages: messages,
temperature: 0.1,
max_tokens: 2000,
});
diff --git a/src/client/views/PropertiesView.scss b/src/client/views/PropertiesView.scss
index ffcad0e7e..874e2a366 100644
--- a/src/client/views/PropertiesView.scss
+++ b/src/client/views/PropertiesView.scss
@@ -16,6 +16,12 @@
background-color: #3b3c3e;
}
}
+.styling-chatbox {
+ color: #000000;
+ width: 100%;
+ outline: none;
+ border: none;
+}
.propertiesView {
height: 100%;
width: 250;
diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx
index 6c193f5a7..e48574857 100644
--- a/src/client/views/PropertiesView.tsx
+++ b/src/client/views/PropertiesView.tsx
@@ -39,9 +39,10 @@ import { PropertiesDocContextSelector } from './PropertiesDocContextSelector';
import { PropertiesSection } from './PropertiesSection';
import './PropertiesView.scss';
import { DefaultStyleProvider } from './StyleProvider';
-import { FaFillDrip } from 'react-icons/fa';
-import { GeneratedResponse, generatePalette } from '../apis/gpt/customization';
+import { FaFillDrip, FaRegHeart } from 'react-icons/fa';
+import { GeneratedResponse, StyleInput, generatePalette } from '../apis/gpt/customization';
import { ExtractColors } from './ExtractColors';
+import ReactTextareaAutosize from 'react-textarea-autosize';
const _global = (window /* browser */ || global) /* node */ as any;
interface PropertiesViewProps {
@@ -95,26 +96,50 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
@observable openStyling: boolean = true;
// GPT styling
- public styleInput: any;
+ public styleInput: StyleInput | undefined;
+ @observable loadingStyles: boolean = false;
@observable generatedStyles: GeneratedResponse[] = [];
@observable inputDocs: Doc[] = [];
@observable selectedStyle: number = -1;
@observable useImageData = false;
+ @observable chatInput: string = '';
+
+ @action
+ setChatInput = (input: string) => {
+ this.chatInput = input;
+ };
+
+ @action
+ setLoading = (loading: boolean) => {
+ this.loadingStyles = loading;
+ };
+
@action
gptStyling = async () => {
- this.generatedStyles = [];
+ // this.generatedStyles = [];
this.selectedStyle = -1;
+ this.setLoading(true);
+ console.log('Style input: ', this.styleInput);
+
+ if (!this.styleInput) return;
+
try {
- const res = await generatePalette(this.styleInput, this.useImageData);
+ let res: any;
+ if (this.generatedStyles.length === 0) {
+ res = await generatePalette(this.styleInput, this.useImageData, this.chatInput);
+ } else {
+ res = await generatePalette(this.styleInput, this.useImageData, this.chatInput, this.generatedStyles);
+ }
if (typeof res === 'string') {
- console.log(res);
+ console.log('Generated palettes: ', res);
const resObj = JSON.parse(res) as GeneratedResponse[];
this.setGeneratedStyles(resObj);
}
} catch (err) {
console.error(err);
}
+ this.setLoading(false);
};
@action
@@ -1206,24 +1231,38 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
return (
<PropertiesSection title="Styling" isOpen={this.openStyling} setIsOpen={bool => (this.openStyling = bool)} onDoubleClick={() => this.CloseAll()}>
<div className="propertiesView-content" style={{ position: 'relative', height: 'auto', display: 'flex', flexDirection: 'column', alignItems: 'center', gap: '4px' }}>
- {this.generatedStyles.length > 0
- ? this.generatedStyles.map((style, i) => (
- <div
- key={i}
- className="propertiesView-palette"
- style={{ display: 'flex', gap: '4px', backgroundColor: this.selectedStyle === i ? StrCast(Doc.UserDoc().userVariantColor) : '#00000000' }}
- onClick={() => this.styleCollection(i)}>
- <div style={{ width: '24px', height: '24px', backgroundColor: style.collectionBackgroundColor, borderRadius: '2px' }}></div>
- {ExtractColors.sortColors(style.documentsWithColors.map(doc => ExtractColors.hexToFinalColor(doc.color))).map(c => (
- <div key={c.hex} style={{ width: '24px', height: '24px', backgroundColor: c.hex, borderRadius: '2px' }}></div>
- ))}
- </div>
- ))
- : 'Generating styles...'}
- <div style={{ display: 'flex', justifyContent: 'flex-end', gap: '8px' }}>
+ {this.generatedStyles.length > 0 &&
+ this.generatedStyles.map((style, i) => (
+ <div
+ key={i}
+ className="propertiesView-palette"
+ style={{ display: 'flex', gap: '4px', backgroundColor: this.selectedStyle === i ? StrCast(Doc.UserDoc().userVariantColor) : '#00000000' }}
+ onClick={() => this.styleCollection(i)}>
+ <div style={{ width: '24px', height: '24px', backgroundColor: style.collectionBackgroundColor, borderRadius: '2px' }}></div>
+ {ExtractColors.sortColors(style.documentsWithColors.map(doc => ExtractColors.hexToFinalColor(doc.color))).map(c => (
+ <div key={c.hex} style={{ width: '24px', height: '24px', backgroundColor: c.hex, borderRadius: '2px' }}></div>
+ ))}
+ {/* <FaRegHeart onClick={() => {}}/> */}
+ </div>
+ ))}
+ {this.loadingStyles && 'Generating styles...'}
+ <ReactTextareaAutosize
+ minRows={3}
+ placeholder="Customize..."
+ className="styling-chatbox"
+ autoFocus={true}
+ value={this.chatInput}
+ onChange={e => {
+ this.setChatInput(e.target.value);
+ }}
+ onKeyDown={e => {
+ e.stopPropagation();
+ }}
+ />
+ <div style={{ display: 'flex', justifyContent: 'flex-end', gap: '16px' }}>
<div style={{ display: 'flex', gap: '4px', alignItems: 'center' }}>
- <label>Use Images </label>
- <input type="checkbox" checked={this.useImageData} onChange={action(e => (this.useImageData = e.target.checked))} />
+ <label style={{ margin: '0px' }}>Use Images </label>
+ <input style={{ margin: '0px' }} type="checkbox" checked={this.useImageData} onChange={action(e => (this.useImageData = e.target.checked))} />
</div>
<Button text={'Regenerate'} type={Type.TERT} color={StrCast(Doc.UserDoc().userVariantColor)} onClick={this.gptStyling} />
</div>
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index f5ba0de96..2ac8f6291 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -52,7 +52,7 @@ import { CollectionFreeFormRemoteCursors } from './CollectionFreeFormRemoteCurso
import './CollectionFreeFormView.scss';
import { MarqueeView } from './MarqueeView';
import React = require('react');
-import { DocumentWithColor, GeneratedResponse, generatePalette } from '../../../apis/gpt/customization';
+import { DocumentWithColor, GeneratedResponse, generatePalette, StyleInput, StyleInputDocument } from '../../../apis/gpt/customization';
import { PropertiesView } from '../../PropertiesView';
import { MainView } from '../../MainView';
import { ExtractColors } from '../../ExtractColors';
@@ -1824,6 +1824,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
this.openProperties();
PropertiesView.Instance.setGeneratedStyles([]);
PropertiesView.Instance.selectedStyle = -1;
+ PropertiesView.Instance.useImageData = false;
+
console.log('Title', this.rootDoc.title);
console.log('bgcolor', this.layoutDoc._backgroundColor);
// doc.backgroundColor
@@ -1842,7 +1844,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
PropertiesView.Instance?.setInputDocs(inputDocs);
// also pass it colors
- const gptInput = inputDocs.map((doc, i) => ({
+ const gptInput: StyleInputDocument[] = inputDocs.map((doc, i) => ({
id: i,
textContent: (doc.text as RichTextField)?.Text,
textSize: 16,
@@ -1858,21 +1860,6 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
PropertiesView.Instance.styleInput = styleInput;
PropertiesView.Instance.gptStyling();
-
- // try {
- // const res = await generatePalette(styleInput);
- // if (typeof res === 'string') {
- // console.log(res);
- // const resObj = JSON.parse(res) as GeneratedResponse[];
- // PropertiesView.Instance.setGeneratedStyles(resObj);
- // // const resObj = JSON.parse(res) as GeneratedResponse;
- // // console.log('Result ', resObj);
- // // this.rootDoc.backgroundColor = resObj.collectionBackgroundColor;
- // // (resObj.documentsWithColors).forEach((elem, i) => (inputDocs[i].backgroundColor = elem.color));
- // }
- // } catch (err) {
- // console.error(err);
- // }
};
onContextMenu = (e: React.MouseEvent) => {
diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx
index 19f271246..f1b501506 100644
--- a/src/client/views/nodes/trails/PresBox.tsx
+++ b/src/client/views/nodes/trails/PresBox.tsx
@@ -2812,10 +2812,6 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
this.stopDictation(true);
e.stopPropagation();
}}
- onKeyPress={e => e.stopPropagation()}
- onPointerDown={e => e.stopPropagation()}
- onClick={e => e.stopPropagation()}
- onPointerUp={e => e.stopPropagation()}
/>
{/* <input className="chatbox" placeholder="Customize..." value={this.chatInput} onChange={e => this.setChatInput(e.target.value)} /> */}
<div style={{ alignSelf: 'flex-end', display: 'flex', gap: '8px' }}>