261 lines
7.6 KiB
TypeScript
261 lines
7.6 KiB
TypeScript
import { Injectable } from '@nestjs/common';
|
|
import OpenAI from 'openai';
|
|
import { shuffle } from 'lodash';
|
|
import { zodResponseFormat } from 'openai/helpers/zod';
|
|
import { z } from 'zod';
|
|
|
|
const openai = new OpenAI({
|
|
apiKey: process.env.OPENAI_API_KEY || 'sk-proj-',
|
|
});
|
|
|
|
const PicturePrompt = z.object({
|
|
prompt: z.string(),
|
|
});
|
|
|
|
const VoicePrompt = z.object({
|
|
voice: z.string(),
|
|
});
|
|
|
|
@Injectable()
|
|
export class OpenaiService {
|
|
async generateImage(prompt: string, isUrl: boolean, isVertical = false) {
|
|
const generate = (
|
|
await openai.images.generate({
|
|
prompt,
|
|
response_format: isUrl ? 'url' : 'b64_json',
|
|
model: 'dall-e-3',
|
|
...(isVertical ? { size: '1024x1792' } : {}),
|
|
})
|
|
).data[0];
|
|
|
|
return isUrl ? generate.url : generate.b64_json;
|
|
}
|
|
|
|
async generatePromptForPicture(prompt: string) {
|
|
return (
|
|
(
|
|
await openai.beta.chat.completions.parse({
|
|
model: 'gpt-4.1',
|
|
messages: [
|
|
{
|
|
role: 'system',
|
|
content: `You are an assistant that take a description and style and generate a prompt that will be used later to generate images, make it a very long and descriptive explanation, and write a lot of things for the renderer like, if it${"'"}s realistic describe the camera`,
|
|
},
|
|
{
|
|
role: 'user',
|
|
content: `prompt: ${prompt}`,
|
|
},
|
|
],
|
|
response_format: zodResponseFormat(PicturePrompt, 'picturePrompt'),
|
|
})
|
|
).choices[0].message.parsed?.prompt || ''
|
|
);
|
|
}
|
|
|
|
async generateVoiceFromText(prompt: string) {
|
|
return (
|
|
(
|
|
await openai.beta.chat.completions.parse({
|
|
model: 'gpt-4.1',
|
|
messages: [
|
|
{
|
|
role: 'system',
|
|
content: `You are an assistant that takes a social media post and convert it to a normal human voice, to be later added to a character, when a person talk they don\'t use "-", and sometimes they add pause with "..." to make it sounds more natural, make sure you use a lot of pauses and make it sound like a real person`,
|
|
},
|
|
{
|
|
role: 'user',
|
|
content: `prompt: ${prompt}`,
|
|
},
|
|
],
|
|
response_format: zodResponseFormat(VoicePrompt, 'voice'),
|
|
})
|
|
).choices[0].message.parsed?.voice || ''
|
|
);
|
|
}
|
|
|
|
async generatePosts(content: string) {
|
|
const posts = (
|
|
await Promise.all([
|
|
openai.chat.completions.create({
|
|
messages: [
|
|
{
|
|
role: 'assistant',
|
|
content:
|
|
'Generate a Twitter post from the content without emojis in the following JSON format: { "post": string } put it in an array with one element',
|
|
},
|
|
{
|
|
role: 'user',
|
|
content: content!,
|
|
},
|
|
],
|
|
n: 5,
|
|
temperature: 1,
|
|
model: 'gpt-4.1',
|
|
}),
|
|
openai.chat.completions.create({
|
|
messages: [
|
|
{
|
|
role: 'assistant',
|
|
content:
|
|
'Generate a thread for social media in the following JSON format: Array<{ "post": string }> without emojis',
|
|
},
|
|
{
|
|
role: 'user',
|
|
content: content!,
|
|
},
|
|
],
|
|
n: 5,
|
|
temperature: 1,
|
|
model: 'gpt-4.1',
|
|
}),
|
|
])
|
|
).flatMap((p) => p.choices);
|
|
|
|
return shuffle(
|
|
posts.map((choice) => {
|
|
const { content } = choice.message;
|
|
const start = content?.indexOf('[')!;
|
|
const end = content?.lastIndexOf(']')!;
|
|
try {
|
|
return JSON.parse(
|
|
'[' +
|
|
content
|
|
?.slice(start + 1, end)
|
|
.replace(/\n/g, ' ')
|
|
.replace(/ {2,}/g, ' ') +
|
|
']'
|
|
);
|
|
} catch (e) {
|
|
return [];
|
|
}
|
|
})
|
|
);
|
|
}
|
|
async extractWebsiteText(content: string) {
|
|
const websiteContent = await openai.chat.completions.create({
|
|
messages: [
|
|
{
|
|
role: 'assistant',
|
|
content:
|
|
'You take a full website text, and extract only the article content',
|
|
},
|
|
{
|
|
role: 'user',
|
|
content,
|
|
},
|
|
],
|
|
model: 'gpt-4.1',
|
|
});
|
|
|
|
const { content: articleContent } = websiteContent.choices[0].message;
|
|
|
|
return this.generatePosts(articleContent!);
|
|
}
|
|
|
|
async separatePosts(content: string, len: number) {
|
|
const SeparatePostsPrompt = z.object({
|
|
posts: z.array(z.string()),
|
|
});
|
|
|
|
const SeparatePostPrompt = z.object({
|
|
post: z.string().max(len),
|
|
});
|
|
|
|
const posts =
|
|
(
|
|
await openai.beta.chat.completions.parse({
|
|
model: 'gpt-4.1',
|
|
messages: [
|
|
{
|
|
role: 'system',
|
|
content: `You are an assistant that take a social media post and break it to a thread, each post must be minimum ${
|
|
len - 10
|
|
} and maximum ${len} characters, keeping the exact wording and break lines, however make sure you split posts based on context`,
|
|
},
|
|
{
|
|
role: 'user',
|
|
content: content,
|
|
},
|
|
],
|
|
response_format: zodResponseFormat(
|
|
SeparatePostsPrompt,
|
|
'separatePosts'
|
|
),
|
|
})
|
|
).choices[0].message.parsed?.posts || [];
|
|
|
|
return {
|
|
posts: await Promise.all(
|
|
posts.map(async (post) => {
|
|
if (post.length <= len) {
|
|
return post;
|
|
}
|
|
|
|
let retries = 4;
|
|
while (retries) {
|
|
try {
|
|
return (
|
|
(
|
|
await openai.beta.chat.completions.parse({
|
|
model: 'gpt-4.1',
|
|
messages: [
|
|
{
|
|
role: 'system',
|
|
content: `You are an assistant that take a social media post and shrink it to be maximum ${len} characters, keeping the exact wording and break lines`,
|
|
},
|
|
{
|
|
role: 'user',
|
|
content: post,
|
|
},
|
|
],
|
|
response_format: zodResponseFormat(
|
|
SeparatePostPrompt,
|
|
'separatePost'
|
|
),
|
|
})
|
|
).choices[0].message.parsed?.post || ''
|
|
);
|
|
} catch (e) {
|
|
retries--;
|
|
}
|
|
}
|
|
|
|
return post;
|
|
})
|
|
),
|
|
};
|
|
}
|
|
|
|
async generateSlidesFromText(text: string) {
|
|
const message = `You are an assistant that takes a text and break it into slides, each slide should have an image prompt and voice text to be later used to generate a video and voice, image prompt should capture the essence of the slide and also have a back dark gradient on top, image prompt should not contain text in the picture, generate between 3-5 slides maximum`;
|
|
return (
|
|
(
|
|
await openai.beta.chat.completions.parse({
|
|
model: 'gpt-4.1',
|
|
messages: [
|
|
{
|
|
role: 'system',
|
|
content: message,
|
|
},
|
|
{
|
|
role: 'user',
|
|
content: text,
|
|
},
|
|
],
|
|
response_format: zodResponseFormat(
|
|
z.object({
|
|
slides: z.array(
|
|
z.object({
|
|
imagePrompt: z.string(),
|
|
voiceText: z.string(),
|
|
})
|
|
),
|
|
}),
|
|
'slides'
|
|
),
|
|
})
|
|
).choices[0].message.parsed?.slides || []
|
|
);
|
|
}
|
|
}
|