Skip to content

Commit 39beb22

Browse files
committed
latest build
1 parent 98dde22 commit 39beb22

6 files changed

+238
-4
lines changed

index.css

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
body {
2+
min-width: 250px;
3+
}
4+
5+
#key_entered {
6+
display: none;
7+
}

index.html

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<html>
2+
<head>
3+
<link rel="stylesheet" href="index.css">
4+
</head>
5+
<body>
6+
<div id="key_needed">
7+
<p>To get started, add your OpenAI API Key!</p>
8+
<input id="key_input" />
9+
<button id="save_key_button">Add key</button>
10+
</div>
11+
<div id="key_entered">
12+
<p>You entered your OpenAI API Key.</p>
13+
<button id="change_key_button">Change key</button>
14+
</div>
15+
</body>
16+
<script src="index.js"></script>
17+
</html>

index.js

+45
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
const checkForKey = () => {
2+
return new Promise((resolve, reject) => {
3+
chrome.storage.local.get(['openai-key'], (result) => {
4+
resolve(result['openai-key']);
5+
});
6+
});
7+
};
8+
9+
const encode = (input) => {
10+
return btoa(input);
11+
};
12+
13+
const saveKey = () => {
14+
const input = document.getElementById('key_input');
15+
16+
if (input) {
17+
const { value } = input;
18+
19+
// Encode String
20+
const encodedValue = encode(value);
21+
22+
// Save to google storage
23+
chrome.storage.local.set({ 'openai-key': encodedValue }, () => {
24+
document.getElementById('key_needed').style.display = 'none';
25+
document.getElementById('key_entered').style.display = 'block';
26+
});
27+
}
28+
};
29+
30+
const changeKey = () => {
31+
document.getElementById('key_needed').style.display = 'block';
32+
document.getElementById('key_entered').style.display = 'none';
33+
};
34+
35+
document.getElementById('save_key_button').addEventListener('click', saveKey);
36+
document
37+
.getElementById('change_key_button')
38+
.addEventListener('click', changeKey);
39+
40+
checkForKey().then((response) => {
41+
if (response) {
42+
document.getElementById('key_needed').style.display = 'none';
43+
document.getElementById('key_entered').style.display = 'block';
44+
}
45+
});

manifest.json

+16-4
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
2-
"name": "GPT-3 Writer",
3-
"description": "Write a blog post in the style of your favorite thinker anywhere!",
2+
"name": "GPT-3 magic story generator",
3+
"description": "Write a story on the science topic of your interest anywhere!",
44
"version": "1.0",
55
"manifest_version": 3,
66
"icons": {
@@ -11,6 +11,18 @@
1111
},
1212
"action": {
1313
"default_popup": "index.html",
14-
"default_title": "Generate blog post"
15-
}
14+
"default_title": "Generate magic story"
15+
},
16+
"background": {
17+
"service_worker": "scripts/contextMenuServiceWorker.js"
18+
},
19+
20+
"permissions": ["contextMenus", "tabs", "storage"],
21+
22+
"content_scripts": [
23+
{
24+
"matches": ["http://*/*", "https://*/*"],
25+
"js": ["scripts/content.js"]
26+
}
27+
]
1628
}

scripts/content.js

+54
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
const insert = (content) => {
2+
// Find Calmly editor input section
3+
const elements = document.getElementsByClassName('droid');
4+
5+
if (elements.length === 0) {
6+
return;
7+
}
8+
9+
const element = elements[0];
10+
11+
// Grab the first p tag so we can replace it with our injection
12+
const pToRemove = element.childNodes[0];
13+
pToRemove.remove();
14+
15+
// Split content by \n
16+
const splitContent = content.split('\n');
17+
18+
// Wrap in p tags
19+
splitContent.forEach((content) => {
20+
const p = document.createElement('p');
21+
22+
if (content === '') {
23+
const br = document.createElement('br');
24+
p.appendChild(br);
25+
} else {
26+
p.textContent = content;
27+
}
28+
29+
// Insert into HTML one at a time
30+
element.appendChild(p);
31+
});
32+
33+
// On success return true
34+
return true;
35+
};
36+
37+
chrome.runtime.onMessage.addListener(
38+
// This is the message listener
39+
(request, sender, sendResponse) => {
40+
if (request.message === 'inject') {
41+
const { content } = request;
42+
43+
// Call this insert function
44+
const result = insert(content);
45+
46+
// If something went wrong, send a failed status
47+
if (!result) {
48+
sendResponse({ status: 'failed' });
49+
}
50+
51+
sendResponse({ status: 'success' });
52+
}
53+
}
54+
);

scripts/contextMenuServiceWorker.js

+99
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
const getKey = () => {
2+
return new Promise((resolve, reject) => {
3+
chrome.storage.local.get(['openai-key'], (result) => {
4+
if (result['openai-key']) {
5+
const decodedKey = atob(result['openai-key']);
6+
resolve(decodedKey);
7+
}
8+
});
9+
});
10+
};
11+
12+
const sendMessage = (content) => {
13+
chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => {
14+
const activeTab = tabs[0].id;
15+
16+
chrome.tabs.sendMessage(
17+
activeTab,
18+
{ message: 'inject', content },
19+
(response) => {
20+
if (response.status === 'failed') {
21+
console.log('injection failed.');
22+
}
23+
}
24+
);
25+
});
26+
};
27+
28+
const generate = async (prompt) => {
29+
// Get your API key from storage
30+
const key = await getKey();
31+
const url = 'https://api.openai.com/v1/completions';
32+
33+
// Call completions endpoint
34+
const completionResponse = await fetch(url, {
35+
method: 'POST',
36+
headers: {
37+
'Content-Type': 'application/json',
38+
Authorization: `Bearer ${key}`,
39+
},
40+
body: JSON.stringify({
41+
model: 'text-davinci-003',
42+
prompt: prompt,
43+
max_tokens: 1250,
44+
temperature: 0.7,
45+
}),
46+
});
47+
48+
// Select the top choice and send back
49+
const completion = await completionResponse.json();
50+
return completion.choices.pop();
51+
}
52+
53+
const generateCompletionAction = async (info) => {
54+
try {
55+
sendMessage('generating...');
56+
57+
const { selectionText } = info;
58+
const basePromptPrefix = `
59+
Give me examples of names of a story by a professional writer that explains the following concepts:
60+
61+
Topic:
62+
`;
63+
64+
const baseCompletion = await generate(
65+
`${basePromptPrefix}${selectionText}`
66+
);
67+
68+
// Add your second prompt here
69+
const secondPrompt = `
70+
Take the table of contents and title of the blog post below and generate a blog post written in thwe style of Paul Graham. Make it feel like a story. Don't just list the points. Go deep into each one. Explain why.
71+
72+
Topic: ${selectionText}
73+
74+
Sample Names of Stories: ${baseCompletion.text}
75+
76+
Final Story:
77+
`;
78+
79+
// Call your second prompt
80+
const secondPromptCompletion = await generate(secondPrompt);
81+
82+
sendMessage(secondPromptCompletion.text);
83+
} catch (error) {
84+
console.log(error);
85+
86+
sendMessage(error.toString());
87+
}
88+
};
89+
90+
chrome.runtime.onInstalled.addListener(() => {
91+
chrome.contextMenus.create({
92+
id: 'context-run',
93+
title: 'Generate magic story',
94+
contexts: ['selection'],
95+
});
96+
});
97+
98+
// Add listener
99+
chrome.contextMenus.onClicked.addListener(generateCompletionAction);

0 commit comments

Comments
 (0)