├── .DS_Store
├── LICENSE.md
├── README.md
├── _locales
├── en
│ └── messages.json
└── it
│ └── messages.json
├── manifest.json
└── src
├── api
└── openAI.js
├── assets
└── icons
│ ├── 128.png
│ ├── 48.png
│ └── settings.png
├── background.js
├── main.js
├── popup.html
└── styles
└── main.css
/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/omar-diop/ads-gpt/cd393b715a08cd14d692a65d115140b2c01d094f/.DS_Store
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | Copyright 2024 Omar Diop
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4 |
5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6 |
7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
8 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Ads GPT
2 |
3 | Ads GPT is a free and open source Google Chrome extension that helps marketing professionals and entrepreneurs generate headlines and body text for Facebook ads with ease. With Ads GPT, you can quickly create captivating and targeted ads that will improve your presence on Facebook.
4 |
5 | ## Features
6 |
7 | - Generate headlines and body text for Facebook ads starting from a simple product or service description provided by the user.
8 | - User-friendly interface to help you create ads with ease.
9 | - Ability to generate captivating and targeted ads that will improve your presence on Facebook.
10 |
11 | ## Installation
12 |
13 | There are two ways to install Ads GPT:
14 |
15 | ### From the Chrome Web Store
16 |
17 | To install Ads GPT from the Chrome Web Store, follow these steps:
18 |
19 | Go to the Chrome Web Store.
20 |
21 | 1. Search for "Ads GPT".
22 | 2. Click on the "Add to Chrome" button.
23 | 3. When prompted, click on "Add Extension" to install Ads GPT.
24 | 4. Once installed, you can access Ads GPT from the Chrome menu or by clicking on the Ads GPT icon in the Chrome toolbar.
25 |
26 | ### As an Unpacked Extension
27 |
28 | To install Ads GPT as an unpacked extension, follow these steps:
29 |
30 | 1. Clone or download the source code from the GitHub repository.
31 | 2. Go to the "chrome://extensions" URL in Google Chrome.
32 | 3. Turn on the "Developer mode" toggle switch.
33 | 4. Click on the "Load unpacked" button.
34 | 5. Select the directory where you have cloned or extracted the source code.
35 | 6. Once installed, you can access Ads GPT from the Chrome menu or by clicking on the Ads GPT icon in the Chrome toolbar.
36 |
37 | ## Contribute
38 |
39 | Ads GPT is an open source project and we welcome contributions from the community. If you encounter any issues or have ideas for improvements, please report them on [GitHub](https://github.com/omar-diop/ads-copy-genius/issues).
40 |
41 | ## License
42 |
43 | Ads GPT is licensed under the [**MIT** License](/LICENSE.md) by Omar Diop.
44 |
--------------------------------------------------------------------------------
/_locales/en/messages.json:
--------------------------------------------------------------------------------
1 | {
2 | "appName": {
3 | "message": "Ads GPT"
4 | },
5 | "appDesc": {
6 | "message": "Elevate your advertising game."
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/_locales/it/messages.json:
--------------------------------------------------------------------------------
1 | {
2 | "appName": {
3 | "message": "Ads GPT"
4 | },
5 | "appDesc": {
6 | "message": "Migliora le tue Ads."
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "__MSG_appName__",
3 | "author": "Omar Diop",
4 | "description": "__MSG_appDesc__",
5 | "default_locale": "en",
6 | "version": "0.0.5",
7 | "manifest_version": 3,
8 | "background.service_worker": {
9 | "scripts": ["background.js"],
10 | "persistent": false
11 | },
12 | "permissions": ["storage"],
13 | "action": {
14 | "default_popup": "/src/popup.html",
15 | "default_icon": {
16 | "48": "/src/assets/icons/48.png",
17 | "128": "/src/assets/icons/128.png"
18 | }
19 | },
20 | "icons": {
21 | "48": "/src/assets/icons/48.png",
22 | "128": "/src/assets/icons/128.png"
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/api/openAI.js:
--------------------------------------------------------------------------------
1 | const API_URL = "https://api.openai.com/v1/chat/completions"
2 |
3 | function generateHeadlines(description, language) {
4 | const localizedPrompt = {
5 | it: `Trova cinque headlines con angle diversi per il seguente prodotto da sponsorizzare tramite Facebook Ads:\n\nProdotto:${description}. Formatta il risultato in un elenco puntato.`,
6 | en: `Find five headlines with different marketing angles for the following product to sponsor through Facebook Ads:\n\nProduct:${description}. Format the result in a bullet-point list.`,
7 | }
8 | return postData(API_URL, {
9 | model: "gpt-4",
10 | messages: [{ role: "user", content: localizedPrompt[language] }],
11 | temperature: 0.7,
12 | })
13 | }
14 |
15 | function generateBodies(description, language) {
16 | const localizedPrompt = {
17 | it: `Trova tre body text con angle diversi per il seguente prodotto da sponsorizzare tramite Facebook Ads:\n\nProdotto:${description}. Formatta il risultato in un elenco puntato.`,
18 | en: `Find three body texts with different angles for the following product to sponsor through Facebook Ads:\n\nProduct:${description}. Format the result in a bullet-point list.`,
19 | }
20 | return postData(API_URL, {
21 | model: "gpt-4",
22 | messages: [{ role: "user", content: localizedPrompt[language] }],
23 | temperature: 0.7,
24 | })
25 | }
26 |
27 | async function postData(url = "", data = {}) {
28 | const { apiKey } = await chrome.storage.local.get("apiKey")
29 | const response = await fetch(url, {
30 | method: "POST",
31 | mode: "cors",
32 | cache: "no-cache",
33 | headers: {
34 | "Content-Type": "application/json",
35 | Authorization: `Bearer ${apiKey}`,
36 | },
37 | body: JSON.stringify(data),
38 | })
39 | return response.json()
40 | }
41 |
--------------------------------------------------------------------------------
/src/assets/icons/128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/omar-diop/ads-gpt/cd393b715a08cd14d692a65d115140b2c01d094f/src/assets/icons/128.png
--------------------------------------------------------------------------------
/src/assets/icons/48.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/omar-diop/ads-gpt/cd393b715a08cd14d692a65d115140b2c01d094f/src/assets/icons/48.png
--------------------------------------------------------------------------------
/src/assets/icons/settings.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/omar-diop/ads-gpt/cd393b715a08cd14d692a65d115140b2c01d094f/src/assets/icons/settings.png
--------------------------------------------------------------------------------
/src/background.js:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/omar-diop/ads-gpt/cd393b715a08cd14d692a65d115140b2c01d094f/src/background.js
--------------------------------------------------------------------------------
/src/main.js:
--------------------------------------------------------------------------------
1 | document.addEventListener("DOMContentLoaded", () => {
2 | const mainView = document.getElementById("main")
3 | const authView = document.getElementById("auth")
4 | const settingsButton = document.getElementById("settings-button")
5 | const logoButton = document.getElementById("logo-container")
6 | const submitButton = document.getElementById("submit-button")
7 | const resetButton = document.getElementById("reset-button")
8 | const submitApiKeyButton = document.getElementById("submit-key-button")
9 | const headlinesResult = document.querySelector(".headlines-result")
10 | const bodiesResult = document.querySelector(".bodies-result")
11 | const headlinesLoader = document.querySelector(".headlines-loader")
12 | const bodiesLoader = document.querySelector(".bodies-loader")
13 | const bodiesError = document.querySelector(".bodies-error")
14 | const headlinesError = document.querySelector(".headlines-error")
15 |
16 | chrome.storage.local.get("apiKey").then(({ apiKey }) => {
17 | if (apiKey) {
18 | mainView.style.display = "block"
19 |
20 | chrome.storage.session.get("savedStade").then(({ savedStade }) => {
21 | if (savedStade) {
22 | const { productDescription, headlines, bodies, language } = savedStade
23 |
24 | setDescription(productDescription)
25 | setLanguage(language)
26 | setHeadlines(headlines)
27 | setBodies(bodies)
28 | }
29 | })
30 | } else {
31 | authView.style.display = "block"
32 | console.log("no auth")
33 | }
34 | })
35 |
36 | //Buttons Events
37 |
38 | logoButton.addEventListener("click", () => {
39 | chrome.storage.local.get("apiKey").then(({ apiKey }) => {
40 | if (apiKey) {
41 | authView.style.display = "none"
42 | mainView.style.display = "block"
43 | }
44 | })
45 | })
46 |
47 | settingsButton.addEventListener("click", () => {
48 | const apiKeyInput = document.getElementById("apiKey")
49 | chrome.storage.local.get("apiKey").then(({ apiKey }) => {
50 | if (apiKey) {
51 | apiKeyInput.value = apiKey
52 | }
53 | })
54 |
55 | authView.style.display = "block"
56 | mainView.style.display = "none"
57 | })
58 |
59 | submitButton.addEventListener("click", () => {
60 | const description = document.getElementById("textarea").value
61 | const language = document.getElementById("language").value
62 |
63 | headlinesLoader.style.display = "block"
64 | bodiesLoader.style.display = "block"
65 | headlinesResult.style.display = "none"
66 | bodiesResult.style.display = "none"
67 | bodiesError.style.display = "none"
68 | headlinesError.style.display = "none"
69 |
70 | let session = {
71 | productDescription: description,
72 | headlines: null,
73 | bodies: null,
74 | language: language,
75 | }
76 |
77 | generateHeadlines(description, language)
78 | .then((data) => {
79 | const output = cleanOutput(data.choices[0].message.content)
80 | session = { ...session, headlines: output }
81 | chrome.storage.session.set({
82 | savedStade: session,
83 | })
84 | setHeadlines(output)
85 | })
86 | .catch((error) => {
87 | headlinesLoader.style.display = "none"
88 | headlinesError.style.display = "block"
89 | console.log(error)
90 | })
91 |
92 | generateBodies(description, language)
93 | .then((data) => {
94 | const output = cleanOutput(data.choices[0].message.content)
95 | session = { ...session, bodies: output }
96 | chrome.storage.session.set({
97 | savedStade: session,
98 | })
99 | setBodies(output)
100 | })
101 | .catch((error) => {
102 | bodiesLoader.style.display = "none"
103 | bodiesError.style.display = "block"
104 | console.log(error)
105 | })
106 | })
107 |
108 | resetButton.addEventListener("click", () => {
109 | headlinesLoader.style.display = "none"
110 | bodiesLoader.style.display = "none"
111 | headlinesResult.style.display = "none"
112 | bodiesResult.style.display = "none"
113 | document.getElementById("textarea").value = ""
114 | chrome.storage.session.set({
115 | savedStade: {
116 | productDescription: null,
117 | headlines: null,
118 | bodies: null,
119 | language: "en",
120 | },
121 | })
122 | })
123 |
124 | submitApiKeyButton.addEventListener("click", () => {
125 | const apiKey = document.getElementById("apiKey").value
126 |
127 | chrome.storage.local
128 | .set({
129 | apiKey,
130 | })
131 | .then(() => {
132 | authView.style.display = "none"
133 | mainView.style.display = "block"
134 | console.log("Saved API Key")
135 | })
136 | })
137 | })
138 |
139 | function setLanguage(language) {
140 | if (!language) return
141 | document.getElementById("language").value = language
142 | }
143 |
144 | function setDescription(description) {
145 | if (!description) return
146 | document.getElementById("textarea").value = description
147 | }
148 |
149 | function setHeadlines(headlines) {
150 | if (!headlines) return
151 | const headlinesResult = document.querySelector(".headlines-result")
152 | const headlinesLoader = document.querySelector(".headlines-loader")
153 | const headlinesDiv = document.querySelector("#headlines")
154 | headlinesDiv.innerText = headlines
155 | headlinesLoader.style.display = "none"
156 | headlinesResult.style.display = "block"
157 | }
158 |
159 | function setBodies(bodies) {
160 | if (!bodies) return
161 | const bodiesResult = document.querySelector(".bodies-result")
162 | const bodiesLoader = document.querySelector(".bodies-loader")
163 | const bodiesDiv = document.querySelector("#body-texts")
164 | bodiesDiv.innerText = bodies
165 | bodiesLoader.style.display = "none"
166 | bodiesResult.style.display = "block"
167 | }
168 |
169 | function cleanOutput(text) {
170 | return text.trim()
171 | }
172 |
--------------------------------------------------------------------------------
/src/popup.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
15 |
16 |
17 |
34 |
35 |
38 |
39 |
40 |
41 |
42 |
43 |
52 |
53 |
54 |
55 |
56 |
60 |
61 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
Generating headlines...
75 |
76 |
77 |
78 |
79 |
80 |
84 |
85 |
Headlines
86 |
An error occurred, please try again.
87 |
88 |
89 |
Generating body texts...
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
Body Texts
99 |
100 |
101 |
102 |
Body Texts
103 |
An error occurred, please try again.
104 |
105 |
106 |
107 |
108 |
114 |
115 |
116 |
117 |
118 |
--------------------------------------------------------------------------------
/src/styles/main.css:
--------------------------------------------------------------------------------
1 | body {
2 | width: 350px;
3 | margin: 0;
4 | }
5 |
6 | .inner {
7 | padding: 1rem;
8 | }
9 |
10 | #main,
11 | #auth {
12 | display: none;
13 | }
14 |
15 | h1 {
16 | font-size: 1.2rem;
17 | margin: 0;
18 | margin-bottom: 10px;
19 | color: #262626;
20 | }
21 |
22 | .link {
23 | font-weight: bold;
24 | color: rgb(18, 163, 130);
25 | text-decoration: none;
26 | }
27 |
28 | .link:hover {
29 | text-decoration: underline;
30 | }
31 |
32 | .form {
33 | display: flex;
34 | flex-direction: column;
35 | }
36 |
37 | .authDesc {
38 | font-size: 0.9rem;
39 | }
40 |
41 | .button {
42 | font-family: Roboto, system-ui, sans-serif;
43 | align-items: center;
44 | border: 1px solid rgb(218, 220, 224);
45 | border-radius: 4px;
46 | box-sizing: border-box;
47 | color: rgb(18, 163, 130);
48 | cursor: pointer;
49 | display: inline-flex;
50 | flex-shrink: 0;
51 | font-weight: 500;
52 | height: 32px;
53 | justify-content: center;
54 | min-width: 5.14em;
55 | outline-width: 0;
56 | padding: 8px 16px;
57 | position: relative;
58 | user-select: none;
59 | background: white;
60 | margin: 10px 0 5px 10px;
61 | }
62 |
63 | .buttons {
64 | display: flex;
65 | align-items: center;
66 | justify-content: flex-end;
67 | }
68 |
69 | .button:hover {
70 | background: rgba(66, 133, 244, 0.04);
71 | border-color: 210, 227, 252;
72 | }
73 |
74 | .button.primary {
75 | background: rgb(18, 163, 130);
76 | color: #fff;
77 | border-color: rgb(18, 163, 130);
78 | }
79 |
80 | .button.primary:hover {
81 | background: rgba(18, 163, 130, 0.9);
82 | box-shadow: 0 1px 2px 0 rgba(66, 133, 244, 0.3),
83 | 0 1px 3px 1px rgba(66, 133, 244, 0.15);
84 | }
85 |
86 | .header {
87 | display: flex;
88 | flex-direction: row;
89 | align-items: center;
90 | justify-content: space-between;
91 | background-color: rgba(18, 163, 130, 0.2);
92 | padding: 0.8rem;
93 | }
94 |
95 | .logoContainer img {
96 | width: 40px;
97 | height: 40px;
98 | }
99 |
100 | .header h1 {
101 | margin: 0;
102 | margin-left: 0.5rem;
103 | }
104 |
105 | textarea,
106 | select {
107 | border: 2px solid rgba(18, 163, 130, 0.4);
108 | padding: 8px;
109 | border-radius: 4px;
110 | display: block;
111 | margin-top: 5px;
112 | margin-bottom: 10px;
113 | resize: vertical;
114 | }
115 |
116 | textarea:focus,
117 | select:focus {
118 | outline: none;
119 | border: 2px solid rgba(18, 163, 130, 0.8);
120 | }
121 |
122 | input {
123 | border: 2px solid rgba(18, 163, 130, 0.4);
124 | padding: 8px;
125 | border-radius: 4px;
126 | margin-bottom: 1rem;
127 | }
128 |
129 | .buttons p {
130 | margin: 10px 0 5px 10px;
131 | }
132 |
133 | .skeleton {
134 | animation: skeleton-loading 1s linear infinite alternate;
135 | }
136 |
137 | @keyframes skeleton-loading {
138 | 0% {
139 | background-color: hsl(200, 20%, 80%);
140 | }
141 | 100% {
142 | background-color: hsl(200, 20%, 95%);
143 | }
144 | }
145 |
146 | .skeleton-text {
147 | width: 100%;
148 | height: 0.7rem;
149 | margin-bottom: 0.5rem;
150 | border-radius: 0.25rem;
151 | }
152 |
153 | .headlines-loader,
154 | .bodies-loader,
155 | .bodies-result,
156 | .headlines-result,
157 | .headlines-error,
158 | .bodies-error {
159 | display: none;
160 | }
161 |
162 | .headlines-error,
163 | .bodies-error {
164 | color: rgb(163, 18, 30);
165 | }
166 |
167 | .resultText {
168 | font-size: 0.8rem;
169 | line-height: 1.1rem;
170 | color: #252525;
171 | }
172 |
173 | .logoContainer {
174 | display: flex;
175 | flex-direction: row;
176 | align-items: center;
177 | }
178 |
179 | .logoContainer:hover {
180 | cursor: pointer;
181 | }
182 |
183 | .content {
184 | margin-bottom: 2rem;
185 | }
186 |
187 | .label {
188 | font-weight: 700;
189 | font-size: 0.8rem;
190 | }
191 |
192 | .settingsImage {
193 | width: 25px;
194 | height: 25px;
195 | }
196 |
197 | .settingsButton {
198 | background-color: transparent;
199 | outline: none;
200 | border: none;
201 | cursor: pointer;
202 | }
203 |
204 | .footer {
205 | text-align: center;
206 | background-color: #d3d3d3;
207 | padding: 1rem;
208 | color: #707070;
209 | }
210 |
--------------------------------------------------------------------------------