-
-
-
-
-
+ ))}
+ >
);
};
diff --git a/libraries/nestjs-libraries/src/database/prisma/database.module.ts b/libraries/nestjs-libraries/src/database/prisma/database.module.ts
index c015ed8e..736c49b1 100644
--- a/libraries/nestjs-libraries/src/database/prisma/database.module.ts
+++ b/libraries/nestjs-libraries/src/database/prisma/database.module.ts
@@ -25,6 +25,8 @@ import { ItemUserService } from '@gitroom/nestjs-libraries/database/prisma/marke
import { MessagesService } from '@gitroom/nestjs-libraries/database/prisma/marketplace/messages.service';
import { MessagesRepository } from '@gitroom/nestjs-libraries/database/prisma/marketplace/messages.repository';
import { StripeService } from '@gitroom/nestjs-libraries/services/stripe.service';
+import { ExtractContentService } from '@gitroom/nestjs-libraries/openai/extract.content.service';
+import { OpenaiService } from '@gitroom/nestjs-libraries/openai/openai.service';
@Global()
@Module({
@@ -57,6 +59,8 @@ import { StripeService } from '@gitroom/nestjs-libraries/services/stripe.service
MessagesService,
CommentsService,
IntegrationManager,
+ ExtractContentService,
+ OpenaiService,
EmailService,
],
get exports() {
diff --git a/libraries/nestjs-libraries/src/database/prisma/posts/posts.repository.ts b/libraries/nestjs-libraries/src/database/prisma/posts/posts.repository.ts
index 46efc3ad..dc979553 100644
--- a/libraries/nestjs-libraries/src/database/prisma/posts/posts.repository.ts
+++ b/libraries/nestjs-libraries/src/database/prisma/posts/posts.repository.ts
@@ -38,6 +38,7 @@ export class PostsRepository {
name: true,
providerIdentifier: true,
picture: true,
+ type: true,
},
},
},
@@ -62,9 +63,10 @@ export class PostsRepository {
getPosts(orgId: string, query: GetPostsDto) {
const date = dayjs().year(query.year).isoWeek(query.week);
- const startDate = date.startOf('isoWeek').toDate();
- const endDate = date.endOf('isoWeek').toDate();
+ const startDate = date.startOf('week').toDate();
+ const endDate = date.endOf('week').toDate();
+ console.log(startDate, endDate);
return this._post.model.post.findMany({
where: {
OR: [
diff --git a/libraries/nestjs-libraries/src/database/prisma/posts/posts.service.ts b/libraries/nestjs-libraries/src/database/prisma/posts/posts.service.ts
index cf19893b..c7b0dcbb 100644
--- a/libraries/nestjs-libraries/src/database/prisma/posts/posts.service.ts
+++ b/libraries/nestjs-libraries/src/database/prisma/posts/posts.service.ts
@@ -7,9 +7,15 @@ import { IntegrationManager } from '@gitroom/nestjs-libraries/integrations/integ
import { Integration, Post, Media, From } from '@prisma/client';
import { GetPostsDto } from '@gitroom/nestjs-libraries/dtos/posts/get.posts.dto';
import { NotificationService } from '@gitroom/nestjs-libraries/database/prisma/notifications/notification.service';
-import { capitalize } from 'lodash';
+import { capitalize, chunk, shuffle } from 'lodash';
import { MessagesService } from '@gitroom/nestjs-libraries/database/prisma/marketplace/messages.service';
import { StripeService } from '@gitroom/nestjs-libraries/services/stripe.service';
+import { GeneratorDto } from '@gitroom/nestjs-libraries/dtos/generator/generator.dto';
+import { ExtractContentService } from '@gitroom/nestjs-libraries/openai/extract.content.service';
+import { OpenaiService } from '@gitroom/nestjs-libraries/openai/openai.service';
+import { CreateGeneratedPostsDto } from '@gitroom/nestjs-libraries/dtos/generator/create.generated.posts.dto';
+import { IntegrationService } from '@gitroom/nestjs-libraries/database/prisma/integrations/integration.service';
+import { makeId } from '@gitroom/nestjs-libraries/services/make.is';
type PostWithConditionals = Post & {
integration?: Integration;
@@ -24,7 +30,10 @@ export class PostsService {
private _integrationManager: IntegrationManager,
private _notificationService: NotificationService,
private _messagesService: MessagesService,
- private _stripeService: StripeService
+ private _stripeService: StripeService,
+ private _extractContentService: ExtractContentService,
+ private _openAiService: OpenaiService,
+ private _integrationService: IntegrationService
) {}
async getPostsRecursively(
@@ -257,7 +266,11 @@ export class PostsService {
throw new Error('You can not add a post to this publication');
}
const getOrgByOrder = await this._messagesService.getOrgByOrder(order);
- const submit = await this._postRepository.submit(id, order, getOrgByOrder?.messageGroup?.buyerOrganizationId!);
+ const submit = await this._postRepository.submit(
+ id,
+ order,
+ getOrgByOrder?.messageGroup?.buyerOrganizationId!
+ );
const messageModel = await this._messagesService.createNewMessage(
submit?.submittedForOrder?.messageGroupId || '',
From.SELLER,
@@ -432,4 +445,101 @@ export class PostsService {
);
}
}
+
+ async loadPostContent(postId: string) {
+ const post = await this._postRepository.getPostById(postId);
+ if (!post) {
+ return '';
+ }
+
+ return post.content;
+ }
+
+ async generatePosts(orgId: string, body: GeneratorDto) {
+ const content = body.url
+ ? await this._extractContentService.extractContent(body.url)
+ : await this.loadPostContent(body.post);
+
+ const value = body.url
+ ? await this._openAiService.extractWebsiteText(content!)
+ : await this._openAiService.generatePosts(content!);
+ return { list: value };
+ }
+
+ async generatePostsDraft(orgId: string, body: CreateGeneratedPostsDto) {
+ const getAllIntegrations = (
+ await this._integrationService.getIntegrationsList(orgId)
+ ).filter((f) => !f.disabled && f.providerIdentifier !== 'reddit');
+
+ // const posts = chunk(body.posts, getAllIntegrations.length);
+ const allDates = dayjs()
+ .isoWeek(body.week)
+ .year(body.year)
+ .startOf('isoWeek');
+
+ const dates = [...new Array(7)].map((_, i) => {
+ return allDates.add(i, 'day').format('YYYY-MM-DD');
+ });
+
+ const findTime = (): string => {
+ const totalMinutes = Math.floor(Math.random() * 144) * 10;
+
+ // Convert total minutes to hours and minutes
+ const hours = Math.floor(totalMinutes / 60);
+ const minutes = totalMinutes % 60;
+
+ // Format hours and minutes to always be two digits
+ const formattedHours = hours.toString().padStart(2, '0');
+ const formattedMinutes = minutes.toString().padStart(2, '0');
+ const randomDate =
+ shuffle(dates)[0] + 'T' + `${formattedHours}:${formattedMinutes}:00`;
+
+ if (dayjs(randomDate).isBefore(dayjs())) {
+ return findTime();
+ }
+
+ return randomDate;
+ };
+
+ for (const integration of getAllIntegrations) {
+ for (const toPost of body.posts) {
+ const group = makeId(10);
+ const randomDate = findTime();
+
+ await this.createPost(orgId, {
+ type: 'draft',
+ date: randomDate,
+ order: '',
+ posts: [
+ {
+ group,
+ integration: {
+ id: integration.id,
+ },
+ settings: {
+ subtitle: '',
+ title: '',
+ tags: [],
+ subreddit: [],
+ },
+ value: [
+ ...toPost.list.map((l) => ({
+ id: '',
+ content: l.post,
+ image: [],
+ })),
+ {
+ id: '',
+ content: `Check out the full story here:\n${
+ body.postId || body.url
+ }`,
+ image: [],
+ },
+ ],
+ },
+ ],
+ });
+ }
+ }
+ }
}
diff --git a/libraries/nestjs-libraries/src/dtos/generator/create.generated.posts.dto.ts b/libraries/nestjs-libraries/src/dtos/generator/create.generated.posts.dto.ts
new file mode 100644
index 00000000..da639677
--- /dev/null
+++ b/libraries/nestjs-libraries/src/dtos/generator/create.generated.posts.dto.ts
@@ -0,0 +1,52 @@
+import {
+ ArrayMinSize,
+ IsArray,
+ IsDefined,
+ IsNumber,
+ IsString,
+ ValidateIf,
+ ValidateNested,
+} from 'class-validator';
+import { Type } from 'class-transformer';
+
+class InnerPost {
+ @IsString()
+ @IsDefined()
+ post: string;
+}
+
+class PostGroup {
+ @IsArray()
+ @ArrayMinSize(1)
+ @ValidateNested({ each: true })
+ @Type(() => InnerPost)
+ @IsDefined()
+ list: InnerPost[];
+}
+
+export class CreateGeneratedPostsDto {
+ @IsArray()
+ @ArrayMinSize(1)
+ @ValidateNested({ each: true })
+ @Type(() => PostGroup)
+ @IsDefined()
+ posts: PostGroup[];
+
+ @IsNumber()
+ @IsDefined()
+ week: number;
+
+ @IsNumber()
+ @IsDefined()
+ year: number;
+
+ @IsString()
+ @IsDefined()
+ @ValidateIf((o) => !o.url)
+ url: string;
+
+ @IsString()
+ @IsDefined()
+ @ValidateIf((o) => !o.url)
+ postId: string;
+}
diff --git a/libraries/nestjs-libraries/src/dtos/generator/generator.dto.ts b/libraries/nestjs-libraries/src/dtos/generator/generator.dto.ts
new file mode 100644
index 00000000..2921ac87
--- /dev/null
+++ b/libraries/nestjs-libraries/src/dtos/generator/generator.dto.ts
@@ -0,0 +1,24 @@
+import {
+ IsDefined,
+ IsInt,
+ IsString,
+ IsUrl,
+ ValidateIf,
+ ValidateNested,
+} from 'class-validator';
+
+export class GeneratorDto {
+ @IsString()
+ @ValidateIf((o) => !o.post)
+ @IsUrl(
+ {},
+ {
+ message: 'Invalid URL',
+ }
+ )
+ url: string;
+
+ @ValidateIf((o) => !o.url)
+ @IsString()
+ post: string;
+}
diff --git a/libraries/nestjs-libraries/src/openai/extract.content.service.ts b/libraries/nestjs-libraries/src/openai/extract.content.service.ts
new file mode 100644
index 00000000..bf763cc2
--- /dev/null
+++ b/libraries/nestjs-libraries/src/openai/extract.content.service.ts
@@ -0,0 +1,90 @@
+import { Injectable } from '@nestjs/common';
+import { JSDOM } from 'jsdom';
+
+function findDepth(element: Element) {
+ let depth = 0;
+ let elementer = element;
+ while (elementer.parentNode) {
+ depth++;
+ // @ts-ignore
+ elementer = elementer.parentNode;
+ }
+ return depth;
+}
+
+@Injectable()
+export class ExtractContentService {
+ async extractContent(url: string) {
+ const load = await (await fetch(url)).text();
+ const dom = new JSDOM(load);
+
+ // only element that has a title
+ const allTitles = Array.from(dom.window.document.querySelectorAll('*'))
+ .filter((f) => {
+ return (
+ f.querySelector('h1') ||
+ f.querySelector('h2') ||
+ f.querySelector('h3') ||
+ f.querySelector('h4') ||
+ f.querySelector('h5') ||
+ f.querySelector('h6')
+ );
+ })
+ .reverse();
+
+ const findTheOneWithMostTitles = allTitles.reduce(
+ (all, current) => {
+ const depth = findDepth(current);
+ const calculate = ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'].reduce(
+ (total, tag) => {
+ if (current.querySelector(tag)) {
+ return total + 1;
+ }
+ return total;
+ },
+ 0
+ );
+
+ if (calculate > all.total) {
+ return { total: calculate, depth, element: current };
+ }
+
+ if (depth > all.depth) {
+ return { total: calculate, depth, element: current };
+ }
+
+ return all;
+ },
+ { total: 0, depth: 0, element: null as Element | null }
+ );
+
+ return findTheOneWithMostTitles?.element?.textContent?.replace(/\n/g, ' ').replace(/ {2,}/g, ' ');
+ //
+ // const allElements = Array.from(
+ // dom.window.document.querySelectorAll('*')
+ // ).filter((f) => f.tagName !== 'SCRIPT');
+ // const findIndex = allElements.findIndex((element) => {
+ // return (
+ // ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'].indexOf(
+ // element.tagName.toLowerCase()
+ // ) > -1
+ // );
+ // });
+ //
+ // if (!findIndex) {
+ // return false;
+ // }
+ //
+ // return allElements
+ // .slice(findIndex)
+ // .map((element) => element.textContent)
+ // .filter((f) => {
+ // const trim = f?.trim();
+ // return (trim?.length || 0) > 0 && trim !== '\n';
+ // })
+ // .map((f) => f?.trim())
+ // .join('')
+ // .replace(/\n/g, ' ')
+ // .replace(/ {2,}/g, ' ');
+ }
+}
diff --git a/libraries/nestjs-libraries/src/openai/openai.service.ts b/libraries/nestjs-libraries/src/openai/openai.service.ts
new file mode 100644
index 00000000..718e8185
--- /dev/null
+++ b/libraries/nestjs-libraries/src/openai/openai.service.ts
@@ -0,0 +1,90 @@
+import { Injectable } from '@nestjs/common';
+import OpenAI from 'openai';
+import { shuffle } from 'lodash';
+
+const openai = new OpenAI({
+ apiKey: process.env.OPENAI_API_KEY,
+});
+
+@Injectable()
+export class OpenaiService {
+ 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-4o',
+ }),
+ 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-4o',
+ }),
+ ])
+ ).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) {
+ console.log(content);
+ return [];
+ }
+ })
+ );
+ }
+ async extractWebsiteText(content: string) {
+ const websiteContent = await openai.chat.completions.create({
+ messages: [
+ {
+ role: 'assistant',
+ content:
+ 'Your take a full website text, and extract only the article content',
+ },
+ {
+ role: 'user',
+ content,
+ },
+ ],
+ model: 'gpt-4o',
+ });
+
+ const { content: articleContent } = websiteContent.choices[0].message;
+
+ return this.generatePosts(articleContent!);
+ }
+}
diff --git a/libraries/react-shared-libraries/src/form/select.tsx b/libraries/react-shared-libraries/src/form/select.tsx
index 61db635a..546fe9cf 100644
--- a/libraries/react-shared-libraries/src/form/select.tsx
+++ b/libraries/react-shared-libraries/src/form/select.tsx
@@ -1,24 +1,43 @@
-"use client";
+'use client';
-import {DetailedHTMLProps, FC, SelectHTMLAttributes, useMemo} from "react";
-import {clsx} from "clsx";
-import {useFormContext} from "react-hook-form";
+import { DetailedHTMLProps, FC, SelectHTMLAttributes, useMemo } from 'react';
+import { clsx } from 'clsx';
+import { useFormContext } from 'react-hook-form';
import interClass from '../helpers/inter.font';
+import { RegisterOptions } from 'react-hook-form/dist/types/validator';
-export const Select: FC
, HTMLSelectElement> & {error?: any, disableForm?: boolean, label: string, name: string}> = (props) => {
- const {label, className, disableForm, error, ...rest} = props;
- const form = useFormContext();
- const err = useMemo(() => {
- if (error) return error;
- if (!form || !form.formState.errors[props?.name!]) return;
- return form?.formState?.errors?.[props?.name!]?.message! as string;
- }, [form?.formState?.errors?.[props?.name!]?.message, error]);
+export const Select: FC<
+ DetailedHTMLProps<
+ SelectHTMLAttributes,
+ HTMLSelectElement
+ > & {
+ error?: any;
+ extraForm?: RegisterOptions;
+ disableForm?: boolean;
+ label: string;
+ name: string;
+ }
+> = (props) => {
+ const { label, className, disableForm, error, extraForm, ...rest } = props;
+ const form = useFormContext();
+ const err = useMemo(() => {
+ if (error) return error;
+ if (!form || !form.formState.errors[props?.name!]) return;
+ return form?.formState?.errors?.[props?.name!]?.message! as string;
+ }, [form?.formState?.errors?.[props?.name!]?.message, error]);
- return (
-
-
{label}
-
-
{err || <> >}
-
- )
-}
\ No newline at end of file
+ return (
+
+
{label}
+
+
{err || <> >}
+
+ );
+};
diff --git a/package-lock.json b/package-lock.json
index 256fce1a..875a1adb 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -66,6 +66,7 @@
"multer": "^1.4.5-lts.1",
"nestjs-command": "^3.1.4",
"next": "14.0.4",
+ "openai": "^4.47.1",
"prisma-paginate": "^5.2.1",
"react": "18.2.0",
"react-dnd": "^16.0.1",
@@ -13253,6 +13254,15 @@
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.16.9.tgz",
"integrity": "sha512-IeB32oIV4oGArLrd7znD2rkHQ6EDCM+2Sr76dJnrHwv9OHBTTM6nuDLK9bmikXzPa0ZlWMWtRGo/Uw4mrzQedA=="
},
+ "node_modules/@types/node-fetch": {
+ "version": "2.6.11",
+ "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.11.tgz",
+ "integrity": "sha512-24xFj9R5+rfQJLRyM56qh+wnVSYhyXC2tkoBndtY0U+vubqNsYXGjufB2nn8Q6gt0LrARwL6UBtMCSVCwl4B1g==",
+ "dependencies": {
+ "@types/node": "*",
+ "form-data": "^4.0.0"
+ }
+ },
"node_modules/@types/node-forge": {
"version": "1.3.11",
"resolved": "https://registry.npmjs.org/@types/node-forge/-/node-forge-1.3.11.tgz",
@@ -14359,6 +14369,17 @@
"resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz",
"integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q=="
},
+ "node_modules/abort-controller": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz",
+ "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==",
+ "dependencies": {
+ "event-target-shim": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=6.5"
+ }
+ },
"node_modules/accepts": {
"version": "1.3.8",
"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
@@ -14440,6 +14461,17 @@
"node": ">= 6.0.0"
}
},
+ "node_modules/agentkeepalive": {
+ "version": "4.5.0",
+ "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.5.0.tgz",
+ "integrity": "sha512-5GG/5IbQQpC9FpkRGsSvZI5QYeSCzlJHdpBQntCsuTOxhKD8lqKhrleg2Yi7yvMIf82Ycmmqln9U8V9qwEiJew==",
+ "dependencies": {
+ "humanize-ms": "^1.2.1"
+ },
+ "engines": {
+ "node": ">= 8.0.0"
+ }
+ },
"node_modules/ajv": {
"version": "8.12.0",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz",
@@ -19593,6 +19625,14 @@
"node": ">= 0.6"
}
},
+ "node_modules/event-target-shim": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz",
+ "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==",
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/eventemitter3": {
"version": "4.0.7",
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz",
@@ -20546,6 +20586,11 @@
"node": ">= 6"
}
},
+ "node_modules/form-data-encoder": {
+ "version": "1.7.2",
+ "resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-1.7.2.tgz",
+ "integrity": "sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A=="
+ },
"node_modules/format": {
"version": "0.2.2",
"resolved": "https://registry.npmjs.org/format/-/format-0.2.2.tgz",
@@ -20555,6 +20600,26 @@
"node": ">=0.4.x"
}
},
+ "node_modules/formdata-node": {
+ "version": "4.4.1",
+ "resolved": "https://registry.npmjs.org/formdata-node/-/formdata-node-4.4.1.tgz",
+ "integrity": "sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ==",
+ "dependencies": {
+ "node-domexception": "1.0.0",
+ "web-streams-polyfill": "4.0.0-beta.3"
+ },
+ "engines": {
+ "node": ">= 12.20"
+ }
+ },
+ "node_modules/formdata-node/node_modules/web-streams-polyfill": {
+ "version": "4.0.0-beta.3",
+ "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-4.0.0-beta.3.tgz",
+ "integrity": "sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug==",
+ "engines": {
+ "node": ">= 14"
+ }
+ },
"node_modules/forwarded": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
@@ -22518,6 +22583,14 @@
"node": ">=10.17.0"
}
},
+ "node_modules/humanize-ms": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz",
+ "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==",
+ "dependencies": {
+ "ms": "^2.0.0"
+ }
+ },
"node_modules/iconv-lite": {
"version": "0.4.24",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
@@ -30625,6 +30698,24 @@
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-5.1.0.tgz",
"integrity": "sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA=="
},
+ "node_modules/node-domexception": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz",
+ "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/jimmywarting"
+ },
+ {
+ "type": "github",
+ "url": "https://paypal.me/jimmywarting"
+ }
+ ],
+ "engines": {
+ "node": ">=10.5.0"
+ }
+ },
"node_modules/node-fetch": {
"version": "2.7.0",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz",
@@ -31285,6 +31376,24 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/openai": {
+ "version": "4.47.1",
+ "resolved": "https://registry.npmjs.org/openai/-/openai-4.47.1.tgz",
+ "integrity": "sha512-WWSxhC/69ZhYWxH/OBsLEirIjUcfpQ5+ihkXKp06hmeYXgBBIUCa9IptMzYx6NdkiOCsSGYCnTIsxaic3AjRCQ==",
+ "dependencies": {
+ "@types/node": "^18.11.18",
+ "@types/node-fetch": "^2.6.4",
+ "abort-controller": "^3.0.0",
+ "agentkeepalive": "^4.2.1",
+ "form-data-encoder": "1.7.2",
+ "formdata-node": "^4.3.2",
+ "node-fetch": "^2.6.7",
+ "web-streams-polyfill": "^3.2.1"
+ },
+ "bin": {
+ "openai": "bin/cli"
+ }
+ },
"node_modules/openapi-types": {
"version": "12.1.3",
"resolved": "https://registry.npmjs.org/openapi-types/-/openapi-types-12.1.3.tgz",
@@ -39644,6 +39753,14 @@
"url": "https://github.com/sponsors/wooorm"
}
},
+ "node_modules/web-streams-polyfill": {
+ "version": "3.3.3",
+ "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz",
+ "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==",
+ "engines": {
+ "node": ">= 8"
+ }
+ },
"node_modules/webidl-conversions": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz",
diff --git a/package.json b/package.json
index 4cad3037..d21007ef 100644
--- a/package.json
+++ b/package.json
@@ -70,6 +70,7 @@
"multer": "^1.4.5-lts.1",
"nestjs-command": "^3.1.4",
"next": "14.0.4",
+ "openai": "^4.47.1",
"prisma-paginate": "^5.2.1",
"react": "18.2.0",
"react-dnd": "^16.0.1",