├── .gitignore
├── LICENSE.txt
├── README.md
├── api
├── dotEnv
├── index.js
├── lib
│ ├── error.js
│ ├── httpServer.js
│ ├── promise-any.js
│ └── routes.js
├── package.json
└── services
│ └── cohere-service.js
├── app
├── .gitignore
├── .prettierrc.cjs
├── .vscode
│ ├── extensions.json
│ └── launch.json
├── README.md
├── astro.config.mjs
├── package.json
├── postcss.config.js
├── public
│ └── favicon.svg
├── src
│ ├── components
│ │ ├── Ai.jsx
│ │ ├── CohereSVG.astro
│ │ ├── Footer.astro
│ │ ├── GithubSVG.astro
│ │ ├── LinkedInSVG.astro
│ │ ├── Nav.astro
│ │ ├── Response.jsx
│ │ └── TwitchSVG.astro
│ ├── env.d.ts
│ ├── hooks
│ │ └── api.js
│ ├── layouts
│ │ └── Layout.astro
│ └── pages
│ │ ├── api.astro
│ │ ├── clickbait.astro
│ │ ├── cta.astro
│ │ └── index.astro
├── tailwind.config.cjs
└── tsconfig.json
├── package.json
├── pnpm-lock.yaml
└── pnpm-workspace.yaml
/.gitignore:
--------------------------------------------------------------------------------
1 | /node_modules
2 |
3 | dist/
4 | .output/
5 | # generated types
6 | .astro/
7 |
8 | # dependencies
9 | node_modules/
10 |
11 | # logs
12 | npm-debug.log*
13 | yarn-debug.log*
14 | yarn-error.log*
15 | pnpm-debug.log*
16 |
17 |
18 | # environment variables
19 | .env
20 | .env.production
21 |
22 | # macOS-specific files
23 | .DS_Store
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 Christian Villegas
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](https://persuaderai.christianvillegas.com/)
2 |
3 | # Persuader.ai
4 |
5 | ## What is this?
6 |
7 | The Persuader.ai is a tool designed to assist content creators in the creation of persuasive clickbait titles and Call-to-Action (CTA) phrases. It aims to inspire content creators and encourage the consumption of their content by potential viewers, readers, listeners, and customers.
8 |
9 | The tool can be utilized as is or integrated through its API for automated content publishing. For those seeking greater independence, the source code can be obtained from the [github repository](https://github.com/chrisvill2312/persuader-ai) for deployment on personal systems.
10 |
11 | (This tool was created as part of the applications created for the [Midudev's AI Hackaton constest](https://github.com/topics/midudev-cohere-2023))
12 |
13 | ## Persuader.ai's API
14 |
15 | The endpoints of this API are:
16 | (Using https://persuaderai.christianvillegas.com/ as a base URL)
17 |
18 | A POST to "/api/clickbait" generate clickbait titles.
19 | A POST to "/api/cta" generate Call-to-Action (CTA) phrases.
20 |
21 | You need to give the prompt and quantity in a JSON on the body of the HTTP request on both of the endpoint, someting like this:
22 |
23 | {
24 | "input":"the result of being a bad person",
25 | "quantity": 2
26 | }
27 |
28 |
29 | As a result the server gives you JSON with an array of strigs like this:
30 |
31 | [
32 | "This Is What Happens When You Are A Bad Person",
33 | "This Is What Happens If You Are A Bad Person"
34 | ]
35 |
36 | ## If you want to fork or download this project
37 | Don't forget to create a .env file on the API folder. It should contained:
38 |
39 |
40 |
41 | API_KEY=ofCohere'sAPI
42 | PORT=10002 // the port of the http server
43 |
--------------------------------------------------------------------------------
/api/dotEnv:
--------------------------------------------------------------------------------
1 | API_KEY=ofCohere'sAPI
2 | PORT=10002
--------------------------------------------------------------------------------
/api/index.js:
--------------------------------------------------------------------------------
1 | import httpServer from './lib/httpServer.js'
2 | import * as dotenv from 'dotenv'
3 | dotenv.config()
4 | const { PORT } = process.env
5 |
6 | httpServer.listen(PORT, () => { console.log(`Listening on port ${PORT}`) })
7 |
--------------------------------------------------------------------------------
/api/lib/error.js:
--------------------------------------------------------------------------------
1 | export default function createCustomError (type = 'error') {
2 | class CustomError extends Error {
3 | constructor (error) {
4 | super()
5 | this.name = type
6 | this.message = error
7 | }
8 | }
9 | return CustomError
10 | }
11 |
--------------------------------------------------------------------------------
/api/lib/httpServer.js:
--------------------------------------------------------------------------------
1 | import express from 'express'
2 | import morgan from 'morgan'
3 | import cors from 'cors'
4 | import router from './routes.js'
5 |
6 | const httpServer = express()
7 |
8 | httpServer.use(morgan('dev'))
9 | httpServer.use((req, res, next) => {
10 | res.set('Cache-Control', 'no-store')
11 | next()
12 | })
13 | httpServer.use(express.json())
14 | httpServer.use(express.urlencoded({ extended: true }))
15 | httpServer.use(cors())
16 | httpServer.use('/', express.static('../app/dist'))
17 | httpServer.use('/', router)
18 |
19 | export default httpServer
20 |
21 | /*
22 |
23 | * I've did the for sentence because cohere could delay to much sometimes and a parallel call generally
24 | get a most quick response. Yes! i Know, is bad coding but i don't want to wait for the free version
25 | of the api, no matter if it means use more calls
26 |
27 | */
28 |
--------------------------------------------------------------------------------
/api/lib/promise-any.js:
--------------------------------------------------------------------------------
1 | // A ES6 implementation of the m0ppers's promise.any library for the missing
2 | // method any on the class Promise https://github.com/m0ppers/promise-any
3 |
4 | function reverse (promise) {
5 | return new Promise((resolve, reject) => Promise.resolve(promise).then(reject, resolve))
6 | }
7 |
8 | export default function promiseAny (iterable) {
9 | return reverse(Promise.all([...iterable].map(reverse)))
10 | };
11 |
--------------------------------------------------------------------------------
/api/lib/routes.js:
--------------------------------------------------------------------------------
1 | import { Router } from 'express'
2 | import { generateClickbait, generateCTA } from '../services/cohere-service.js'
3 |
4 | const router = Router()
5 |
6 | function noNeededInfo (input, quantity) {
7 | if (!input || !quantity) return true
8 | if (input.length === 0) return true
9 | if (quantity < 1 || quantity > 5) return true
10 | return false
11 | }
12 | router.post('/api/clickbait', (req, res) => {
13 | if (noNeededInfo(req.body.input, req.body.quantity)) res.status(400).send({ error: 'missing or wrong data.' })
14 | else {
15 | generateClickbait(req.body.input, req.body.quantity)
16 | .then((result) => res.status(200).json(result))
17 | .catch(() => res.status(503).json(['AI Service error']))
18 | }
19 | })
20 | router.post('/api/cta', (req, res) => {
21 | if (noNeededInfo(req.body.input, req.body.quantity)) res.status(400).send({ error: 'missing or wrong data.' })
22 | else {
23 | generateCTA(req.body.input, req.body.quantity)
24 | .then((result) => res.status(200).json(result))
25 | .catch(() => res.status(503).json(['AI Service error']))
26 | }
27 | })
28 |
29 | export default router
30 |
--------------------------------------------------------------------------------
/api/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "api",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "type": "module",
7 | "scripts": {
8 | "start": "node index.js",
9 | "dev": "node --watch index.js",
10 | "test": "echo \"Error: no test specified\" && exit 1",
11 | "lint": "eslint"
12 | },
13 | "keywords": [],
14 | "author": "",
15 | "license": "ISC",
16 | "dependencies": {
17 | "cohere-ai": "^5.0.2",
18 | "cors": "^2.8.5",
19 | "dotenv": "^16.0.3",
20 | "express": "^4.18.2",
21 | "morgan": "^1.10.0"
22 | },
23 | "devDependencies": {
24 | "eslint": "^8.29.0",
25 | "standard": "^17.0.0"
26 | },
27 | "eslintConfig": {
28 | "extends": "standard"
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/api/services/cohere-service.js:
--------------------------------------------------------------------------------
1 | import * as dotenv from 'dotenv'
2 | import cohere from 'cohere-ai'
3 | // import createCustomError from '../lib/error.js'
4 | dotenv.config()
5 |
6 | const CTAPrompt = `
7 | this is a call to action phrase generator from given action.
8 | --
9 | action: click the button
10 | phrase: Click this now!
11 | --
12 | action: watch a new video
13 | phrase: Enjoy the new awesome video!
14 | --
15 | action: enter in the stream
16 | phrase: Get in the Stream and let's have some fun!
17 | --
18 | action: hear the podcast
19 | phrase: Let's listen to this podcast!
20 | --
21 | action: don't wait
22 | phrase: what are you waiting for?
23 | --
24 | action: hurry
25 | phrase: don't loose the time!
26 | --
27 | action: {action}
28 | phrase: `
29 |
30 | const CTAGen = {
31 | max_tokens: 10,
32 | temperature: 0.5,
33 | k: 0,
34 | p: 1,
35 | frequency_penalty: 1,
36 | presence_penalty: 0,
37 | stop_sequences: ['--'],
38 | return_likelihoods: 'NONE'
39 | }
40 |
41 | const CBPrompt = `
42 | this is a clickbait title generator from given topic.
43 | --
44 | topic: we show an interview with the famous actor Will Smith
45 | title: Will Smith tells us everything!
46 | --
47 | topic: We show how make a dinner
48 | title: You’ll Never Believe this Simple Method to make a dinner!
49 | --
50 | topic: We show 9 methods to get a job
51 | title: 9 methods to get a job That You Need to Know!
52 | --
53 | topic: we show a best way to invest money
54 | title: This Weird Trick Increased your money outrageously!
55 | --
56 | topic: we show the effects of don't take care of your health
57 | title: This Is What Happens if You Stop Worrying Too Much about your health
58 | --
59 | topic: we show 7 tools to Boost a Blog Engagement
60 | title: The 7 Best Visual Content Tools to Boost your Blog’s Engagement!
61 | --
62 | topic: we show {topic}
63 | title: `
64 |
65 | const CBGen = {
66 | max_tokens: 20,
67 | temperature: 0.3,
68 | k: 0,
69 | p: 1,
70 | frequency_penalty: 1,
71 | presence_penalty: 0,
72 | stop_sequences: ['--'],
73 | return_likelihoods: 'NONE'
74 | }
75 |
76 | cohere.init(process.env.API_KEY, '2022-12-06')
77 |
78 | export async function generateClickbait (topic, quantity) {
79 | let input = { ...CBGen, prompt: CBPrompt.replace('{topic}', topic) }
80 | quantity && (input = { ...input, num_generations: quantity })
81 | try {
82 | const generated = await cohere.generate(input)
83 | console.log(generated.body.generations)
84 | const response = generated.body.generations.map((result) => result.text.split('\n')[0])
85 | console.log(response)
86 | return response
87 | } catch (error) {
88 | // const ClickbaitError = createCustomError('ClickbaitGenerationError')
89 | // return new ClickbaitError(error.message)
90 | return 'error'
91 | }
92 | }
93 |
94 | export async function generateCTA (action, quantity) {
95 | let input = { ...CTAGen, prompt: CTAPrompt.replace('{action}', action) }
96 | quantity && (input = { ...input, num_generations: quantity })
97 | try {
98 | const generated = await cohere.generate(input)
99 | console.log(generated.body.generations)
100 | const response = generated.body.generations.map((result) => result.text.split('\n')[0])
101 | console.log(response)
102 | return response
103 | } catch (error) {
104 | // const CTAError = createCustomError('CTAGenerationError')
105 | // return new CTAError(error.message)
106 | return 'error'
107 | }
108 | }
109 |
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | # build output
2 | dist/
3 | .output/
4 | # generated types
5 | .astro/
6 |
7 | # dependencies
8 | node_modules/
9 |
10 | # logs
11 | npm-debug.log*
12 | yarn-debug.log*
13 | yarn-error.log*
14 | pnpm-debug.log*
15 |
16 |
17 | # environment variables
18 | .env
19 | .env.production
20 |
21 | # macOS-specific files
22 | .DS_Store
23 |
--------------------------------------------------------------------------------
/app/.prettierrc.cjs:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: [require.resolve('prettier-plugin-astro')],
3 | overrides: [
4 | {
5 | files: '*.astro',
6 | options: {
7 | parser: 'astro',
8 | },
9 | },
10 | ],
11 | };
--------------------------------------------------------------------------------
/app/.vscode/extensions.json:
--------------------------------------------------------------------------------
1 | {
2 | "recommendations": ["astro-build.astro-vscode"],
3 | "unwantedRecommendations": []
4 | }
5 |
--------------------------------------------------------------------------------
/app/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "0.2.0",
3 | "configurations": [
4 | {
5 | "command": "./node_modules/.bin/astro dev",
6 | "name": "Development server",
7 | "request": "launch",
8 | "type": "node-terminal"
9 | }
10 | ]
11 | }
12 |
--------------------------------------------------------------------------------
/app/README.md:
--------------------------------------------------------------------------------
1 | # Welcome to [Astro](https://astro.build)
2 |
3 | [](https://stackblitz.com/github/withastro/astro/tree/latest/examples/basics)
4 | [](https://codesandbox.io/s/github/withastro/astro/tree/latest/examples/basics)
5 |
6 | > 🧑🚀 **Seasoned astronaut?** Delete this file. Have fun!
7 |
8 | 
9 |
10 |
11 | ## 🚀 Project Structure
12 |
13 | Inside of your Astro project, you'll see the following folders and files:
14 |
15 | ```
16 | /
17 | ├── public/
18 | │ └── favicon.svg
19 | ├── src/
20 | │ ├── components/
21 | │ │ └── Card.astro
22 | │ ├── layouts/
23 | │ │ └── Layout.astro
24 | │ └── pages/
25 | │ └── index.astro
26 | └── package.json
27 | ```
28 |
29 | Astro looks for `.astro` or `.md` files in the `src/pages/` directory. Each page is exposed as a route based on its file name.
30 |
31 | There's nothing special about `src/components/`, but that's where we like to put any Astro/React/Vue/Svelte/Preact components.
32 |
33 | Any static assets, like images, can be placed in the `public/` directory.
34 |
35 | ## 🧞 Commands
36 |
37 | All commands are run from the root of the project, from a terminal:
38 |
39 | | Command | Action |
40 | | :--------------------- | :------------------------------------------------- |
41 | | `npm install` | Installs dependencies |
42 | | `npm run dev` | Starts local dev server at `localhost:3000` |
43 | | `npm run build` | Build your production site to `./dist/` |
44 | | `npm run preview` | Preview your build locally, before deploying |
45 | | `npm run astro ...` | Run CLI commands like `astro add`, `astro preview` |
46 | | `npm run astro --help` | Get help using the Astro CLI |
47 |
48 | ## 👀 Want to learn more?
49 |
50 | Feel free to check [our documentation](https://docs.astro.build) or jump into our [Discord server](https://astro.build/chat).
51 |
--------------------------------------------------------------------------------
/app/astro.config.mjs:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'astro/config';
2 |
3 | // https://astro.build/config
4 | import react from "@astrojs/react";
5 |
6 | // https://astro.build/config
7 | import tailwind from "@astrojs/tailwind";
8 |
9 | // https://astro.build/config
10 | export default defineConfig({
11 | integrations: [react(), tailwind()]
12 | });
--------------------------------------------------------------------------------
/app/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@example/basics",
3 | "type": "module",
4 | "version": "0.0.1",
5 | "private": true,
6 | "scripts": {
7 | "dev": "astro dev",
8 | "start": "astro dev",
9 | "build": "astro build",
10 | "preview": "astro preview",
11 | "astro": "astro"
12 | },
13 | "dependencies": {
14 | "@astrojs/react": "^2.0.0",
15 | "@astrojs/tailwind": "^3.0.0",
16 | "@types/react": "^18.0.21",
17 | "@types/react-dom": "^18.0.6",
18 | "@uiball/loaders": "^1.2.6",
19 | "astro": "^2.0.1",
20 | "axios": "^1.2.6",
21 | "react": "^18.0.0",
22 | "react-dom": "^18.0.0",
23 | "tailwindcss": "^3.0.24"
24 | },
25 | "proxy": "http://localhost:10002",
26 | "devDependencies": {
27 | "prettier": "^2.8.3",
28 | "prettier-plugin-astro": "^0.8.0"
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/app/postcss.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: [
3 | require('autoprefixer'),
4 | require('cssnano'),
5 | ],
6 | };
--------------------------------------------------------------------------------
/app/public/favicon.svg:
--------------------------------------------------------------------------------
1 |
14 |
--------------------------------------------------------------------------------
/app/src/components/Ai.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from "react";
2 | import useAPI, { CB, CTA } from "../hooks/api.js";
3 | import Response from "./Response.jsx";
4 | import { LeapFrog } from "@uiball/loaders";
5 |
6 | function Ai(props) {
7 | const { mode } = props;
8 | const [input, quantity] =
9 | mode === CB
10 | ? ["how to learn a programing language", 1]
11 | : ["Like this video", 1];
12 | const [userInput, AIResponse, setInput] = useAPI(input, quantity, mode);
13 | const [toSubmit, setSubmit] = useState({ clickbaitInput: "", quantity: 1 });
14 | const inputRef = React.createRef();
15 | const eventHandler = (e) => {
16 | let modification = {};
17 | if (e.target.name === "quantity") {
18 | if (parseInt(e.target.value) < 1) modification = { [e.target.name]: 1 };
19 | if (parseInt(e.target.value) > 5) modification = { [e.target.name]: 5 };
20 | if (!modification.quantity)
21 | modification = { [e.target.name]: parseInt(e.target.value) };
22 | } else modification = { [e.target.name]: e.target.value };
23 |
24 | /*
25 | e.target.name === "quantity"
26 | ? parseInt(e.target.value) < 1
27 | ? { quantity: 1 }
28 | : { quantity: parseInt(e.target.value) }
29 | : { [e.target.name]: e.target.value };
30 | PirxJ me salvo en esta linea de una quemada de cabeza (cosas de stream) */ // Para el recuerdo del stream de Twitch
31 |
32 | setSubmit((state) => ({ ...state, ...modification }));
33 | };
34 | const runSubmit = (e) => {
35 | e.preventDefault();
36 | setInput(toSubmit.clickbaitInput, toSubmit.quantity);
37 | };
38 |
39 | useEffect(() => console.log(AIResponse), [AIResponse]);
40 |
41 | return (
42 | <>
43 |
101 | {
102 | `Creating the best ${mode === CTA ? "CTA" : "Clickbait"}${
103 | toSubmit.quantity > 1 ? "s" : ""
104 | } for you` /* UX inspirited from GermanL3t */
105 | }
106 | {`${
48 | mode === CTA ? "CTA" : "Clickbait"
49 | } generator`}
50 |
89 |
115 | Sorry but at this moment the provider of the service isn't responding 😢, try again in a moment 116 |
117 | )} 118 | 119 | > 120 | ); 121 | } 122 | 123 | export default Ai; 124 | -------------------------------------------------------------------------------- /app/src/components/CohereSVG.astro: -------------------------------------------------------------------------------- 1 | --- 2 | const { size, className = "" } = Astro.props; 3 | --- 4 | 5 | 15 | -------------------------------------------------------------------------------- /app/src/components/Footer.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import TwitchSVG from "./TwitchSVG.astro"; 3 | import LinkedInSVG from "./LinkedInSVG.astro"; 4 | import GithubSVG from "./GithubSVG.astro"; 5 | import CohereSVG from "./CohereSVG.astro"; 6 | const size = "1.15rem"; 7 | const color = ""; 8 | --- 9 | 10 | 82 | -------------------------------------------------------------------------------- /app/src/components/GithubSVG.astro: -------------------------------------------------------------------------------- 1 | --- 2 | const { size, className = "" } = Astro.props; 3 | --- 4 | 5 | 16 | -------------------------------------------------------------------------------- /app/src/components/LinkedInSVG.astro: -------------------------------------------------------------------------------- 1 | --- 2 | const { size, className = "" } = Astro.props; 3 | --- 4 | 5 | 19 | -------------------------------------------------------------------------------- /app/src/components/Nav.astro: -------------------------------------------------------------------------------- 1 | --- 2 | const btnStyle = 3 | "flex py-2 px-4 my-1 mx-2 border rounded border-amber-600 hover:border-amber-400 hover:scale-105 hover:bg-indigo-700 active:scale-95 active:border-amber-200 active:bg-indigo-500 sm:text-xl transition-all"; 4 | // 5 | --- 6 | 7 | 8 |35 | {!copyStatus.copied 36 | ? response 37 | : copyStatus.achievedCopy 38 | ? "Copied to the clipboard!" 39 | : "Error copying to the clipboard 😭"} 40 |
41 | ); 42 | } 43 | 44 | export default Response; 45 | -------------------------------------------------------------------------------- /app/src/components/TwitchSVG.astro: -------------------------------------------------------------------------------- 1 | --- 2 | const { size, className = "" } = Astro.props; 3 | --- 4 | 5 | 25 | -------------------------------------------------------------------------------- /app/src/env.d.ts: -------------------------------------------------------------------------------- 1 | ///
10 | The endpoints of this API are:
11 |
12 | (Using https://persuaderai.christianvillegas.com/ as a base URL)
15 |
17 | A POST to "/api/clickbait" generate clickbait titles.
18 |
19 | A POST to "/api/cta" generate Call-to-Action (CTA) phrases.
20 |
22 | You need to give the prompt and quantity in a JSON on the body of the HTTP 23 | request on both of the endpoints, someting like this: 24 |
25 |28 | {`{`} 29 | {" "}"input":"the result of being a bad person", 30 | {" "}"quantity": 2 31 | {`}` /*pre tag founded by felixicaza*/} 32 |33 |
35 | As a result the server gives you a JSON with an array of strings like this: 36 |
37 |40 | {`[`} 41 | {" "}"This Is What Happens When You Are A Bad Person", 42 | {" "}"This Is What Happens If You Are A Bad Person" 43 | {`]` /*pre tag founded by felixicaza*/} 44 |45 |
11 | The Persuader.ai is a tool designed to assist content creators in the
12 | creation of persuasive clickbait titles and Call-to-Action (CTA) phrases.
13 | It aims to inspire content creators and encourage the consumption of their
14 | content by potential viewers, readers, listeners, and customers.
15 |
16 |
17 | The tool can be utilized as is or integrated through its API for automated
18 | content publishing. For those seeking greater independence, the source code
19 | can be obtained from the
20 | github repository for deployment on personal systems.
26 |
28 | (This tool was created as part of the applications created for the 29 | Midudev's AI Hackaton constest) 35 |
36 |