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.service.ts b/libraries/nestjs-libraries/src/database/prisma/posts/posts.service.ts
index cf19893b..d8d26b54 100644
--- a/libraries/nestjs-libraries/src/database/prisma/posts/posts.service.ts
+++ b/libraries/nestjs-libraries/src/database/prisma/posts/posts.service.ts
@@ -10,6 +10,9 @@ import { NotificationService } from '@gitroom/nestjs-libraries/database/prisma/n
import { capitalize } 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';
type PostWithConditionals = Post & {
integration?: Integration;
@@ -24,7 +27,9 @@ export class PostsService {
private _integrationManager: IntegrationManager,
private _notificationService: NotificationService,
private _messagesService: MessagesService,
- private _stripeService: StripeService
+ private _stripeService: StripeService,
+ private _extractContentService: ExtractContentService,
+ private _openAiService: OpenaiService
) {}
async getPostsRecursively(
@@ -432,4 +437,14 @@ export class PostsService {
);
}
}
+
+ async generatePosts(orgId: string, body: GeneratorDto) {
+ const content = await this._extractContentService.extractContent(body.url);
+ if (content) {
+ const value = await this._openAiService.extractWebsiteText(content);
+ return {list: value};
+ }
+
+ return [];
+ }
}
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..1af46b6f
--- /dev/null
+++ b/libraries/nestjs-libraries/src/dtos/generator/generator.dto.ts
@@ -0,0 +1,35 @@
+import {
+ IsDefined,
+ IsInt,
+ IsString,
+ IsUrl,
+ ValidateIf,
+ ValidateNested,
+} from 'class-validator';
+
+class Date {
+ @IsInt()
+ week: number;
+
+ @IsInt()
+ year: number;
+}
+export class GeneratorDto {
+ @IsDefined()
+ @ValidateNested()
+ date: Date;
+
+ @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..e6938334
--- /dev/null
+++ b/libraries/nestjs-libraries/src/openai/extract.content.service.ts
@@ -0,0 +1,36 @@
+import { Injectable } from '@nestjs/common';
+import { JSDOM } from 'jsdom';
+
+@Injectable()
+export class ExtractContentService {
+ async extractContent(url: string) {
+ const load = await (await fetch(url)).text();
+ const dom = new JSDOM(load);
+ 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..0c9ca217
--- /dev/null
+++ b/libraries/nestjs-libraries/src/openai/openai.service.ts
@@ -0,0 +1,80 @@
+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 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;
+
+ 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: articleContent!,
+ },
+ ],
+ n: 5,
+ temperature: 0.7,
+ 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: articleContent!,
+ },
+ ],
+ n: 5,
+ temperature: 0.7,
+ 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) + ']');
+ } catch (e) {
+ console.log(content);
+ return [];
+ }
+ })
+ );
+ }
+}
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",