aboutsummaryrefslogtreecommitdiff
path: root/src/client/apis/gpt/customization.ts
blob: bc3f57210e7b621d32c0f18f38be29ac11d3b489 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
import { ChatCompletionMessageParam } from 'openai/resources';
import { openai } from './setup';
import { ClientOptions, OpenAI } from 'openai';

export enum CustomizationType {
    PRES_TRAIL_SLIDE = 'trails',
}

export interface GeneratedResponse {
    collectionBackgroundColor: string;
    documentsWithColors: DocumentWithColor[];
}

export interface DocumentWithColor {
    id: number;
    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[] }[];
}
const prompts: { [key: string]: PromptInfo } = {
    trails: {
        description:
            'We are customizing the properties and transition of a slide in a presentation. You are given the current properties of the slide in a json with the fields [title, presentation_transition, presentation_effect, config_zoom, presentation_effectDirection], as well as the prompt for how the user wants to change it. Return a json with the required fields: [title, presentation_transition, presentation_effect, config_zoom, presentation_effectDirection] by applying the changes in the prompt to the current state of the slide.',
        features: [],
    },
};

export const addCustomizationProperty = (type: CustomizationType, name: string, description: string, values?: string[]) => {
    values ? prompts[type].features.push({ name, description, values }) : prompts[type].features.push({ name, description });
};

export const gptSlideProperties = ['title', 'presentation_transition', 'presentation_effect', 'presentation_effectDirection', 'config_zoom'];

const setupPresSlideCustomization = () => {
    addCustomizationProperty(CustomizationType.PRES_TRAIL_SLIDE, 'title', 'is the title/name of the slide.');
    addCustomizationProperty(CustomizationType.PRES_TRAIL_SLIDE, 'presentation_transition', 'is a number in milliseconds for how long it should take to transition/move to a slide.');
    addCustomizationProperty(CustomizationType.PRES_TRAIL_SLIDE, 'presentation_effect', 'is an effect applied to the slide when we transition to it.', ['None', 'Fade in', 'Flip', 'Rotate', 'Bounce', 'Roll']);
    addCustomizationProperty(CustomizationType.PRES_TRAIL_SLIDE, 'presentation_effectDirection', 'is what direction the effect is applied.', ['Enter from left', 'Enter from right', 'Enter from bottom', 'Enter from Top', 'Enter from center']);
    addCustomizationProperty(CustomizationType.PRES_TRAIL_SLIDE, 'config_zoom', 'is a number from 0 to 1.0 indicating the percentage we should zoom into the slide.');
    addCustomizationProperty(
        CustomizationType.PRES_TRAIL_SLIDE,
        'presEffectTiming',
        "is a json object of the format: {type: string, stiffness: number, damping: number, mass: number}. Type is always “custom”. Controls the spring-based timing of the presentation effect animation. Stiffness, damping, and mass control the physics-based properties of spring animations. This is used to create a more natural looking timing, bouncy effects, etc. Use spring physics to adjust these parameters to match the user's description of how they want to animate the effect."
    );
};

setupPresSlideCustomization();

export const gptTrailSlideCustomization = async (inputText: string, properties: any) => {
    console.log('properties', properties);
    let prompt = prompts.trails.description;

    prompts.trails.features.forEach(feature => {
        prompt += feature.name + ' ' + feature.description;
        if (feature.values) {
            prompt += `Its only possible values are [${feature.values.join(', ')}].`;
        }
    });

    // prompt +=
    //     'title is the title/name of the slide. presentation_transition is a number in milliseconds for how long it should take to transition/move to a slide. presentation_effect is an effect applied to the slide when we transition to it. Its only possible values are: [None, Fade in, Flip, Rotate, Bounce, Roll]. presentation_effectDirection is what direction the effect is applied. Its only possible values are:  [Enter from left, Enter from right, Enter from bottom, Enter from Top, Enter from center]. config_zoom is a number from 0 to 1.0 indicating the percentage we should zoom into the slide.';

    prompt += 'Set unchanged values to null. Please only return the json with these keys and their values.';

    console.log('messages', [
        { role: 'system', content: prompt },
        { role: 'user', content: `Prompt: ${inputText}, Current properties: ${JSON.stringify(properties)}` },
    ]);

    try {
        const response = await openai.chat.completions.create({
            model: 'gpt-4',
            messages: [
                { role: 'system', content: prompt },
                { role: 'user', content: `Prompt: ${inputText}, Current properties: ${JSON.stringify(properties)}` },
            ],
            temperature: 0,
            max_tokens: 1000,
        });
        return response.choices[0].message?.content;
    } catch (err) {
        console.log(err);
        return 'Error connecting with API.';
    }
};

// layout
export const smartLayout = async (inputData: { width: number; height: number }[]) => {
    let prompt =
        'I want to layout documents in a 2d space in a nice, grid-like fashion with nice padding of about 50 units around each document, making sure they do not overlap. Given a json array of documents containing their width and heights in this form: {width: number, height: number}[], give me a json array in this form: {x: number, y: number}[] corresponding to the documents with a nice layout. Return just the json array.';

    let messages: ChatCompletionMessageParam[] = [
        { role: 'system', content: prompt },
        { role: 'user', content: JSON.stringify(inputData) },
    ];

    console.log('Prompt: ', prompt);
    console.log('Messages: ', messages);

    try {
        const response = await openai.chat.completions.create({
            model: 'gpt-4',
            messages: messages,
            temperature: 0.01,
            max_tokens: 2000,
        });
        const content = response.choices[0].message?.content;
        if (content) {
            return content;
        }
        return 'Malformed response.';
    } catch (err) {
        console.log(err);
        return 'Error connecting with API.';
    }
};

// layout
export const smartLayout2 = async (inputData: { width: number; height: number }[]) => {
    let prompt =
        'I want to layout documents in a freeform 2d space in a masonry layout with a padding of around 50 units around each document. Take inspiration from existing UI grid and masonry layout design patterns. Make sure documents do not overlap. Given a json array of documents containing their width and heights in this form: {width: number, height: number}[], give me a json array in this form: {x: number, y: number}[] corresponding to the documents in the same order with the improved layout. Return just the json array.';

    let messages: ChatCompletionMessageParam[] = [
        { role: 'system', content: prompt },
        { role: 'user', content: JSON.stringify(inputData) },
    ];

    console.log('Prompt: ', prompt);
    console.log('Messages: ', messages);

    try {
        const response = await openai.chat.completions.create({
            model: 'gpt-4',
            messages: messages,
            temperature: 0,
            max_tokens: 2000,
        });
        const content = response.choices[0].message?.content;
        if (content) {
            return content;
        }
        return 'Malformed response.';
    } catch (err) {
        console.log(err);
        return 'Error connecting with API.';
    }
};

// palette / styling
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 +=
        ' The user is going to give you a json object of this format:' +
        JSON.stringify({ collectionDescription: 'string', documents: 'Document[]', imageColors: 'string[]' }) +
        '. 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',
            textContent: 'string',
        }) +
        (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.' : '') +
        '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',
            documentsWithColors: 'DocumentWithColor[]',
        }) +
        '. collectionBackgroundColor, should be a string hex value for the background color of the collection. documentsWithColors has the same length and order of the input documents. DocumentWithColor has this format:' +
        JSON.stringify({
            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.";

    // enforce format
    prompt += 'Important: Respond with only the JSON array and no other text.';

    // iteration

    let messages: ChatCompletionMessageParam[] = [
        { role: 'system', content: prompt },
        { role: 'user', content: JSON.stringify(inputData) },
    ];

    // continuing conversation
    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);

    try {
        const response = await openai.chat.completions.create({
            model: 'gpt-4',
            messages: messages,
            temperature: 0.1,
            max_tokens: 2000,
        });
        const content = response.choices[0].message?.content;
        console.log(content);
        if (content) {
            return content;
        }
        return 'Malformed response.';
    } catch (err) {
        console.log(err);
        return 'Error connecting with API.';
    }
};