Initial commit

This commit is contained in:
Achiya Elyasaf
2023-07-31 15:00:05 +03:00
commit 06d7c3af5c
16 changed files with 776 additions and 0 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

BIN
popup/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

BIN
popup/icon_128.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

BIN
popup/icon_16.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

BIN
popup/icon_48.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

175
popup/popup.css Normal file
View File

@@ -0,0 +1,175 @@
body {
font-family: Arial, sans-serif;
background-color: #f5f5f5;
margin: 0;
padding: 20px;
}
.extension-title {
font-size: 24px;
color: #4a4a4a;
margin-bottom: 20px;
}
.form-container {
background-color: #fff;
border-radius: 5px;
margin: 10px 0;
padding: 20px;
box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.1);
}
.api-token-text {
font-size: 16px;
color: #4a4a4a;
margin-bottom: 10px;
}
.api-token-status {
font-weight: bold;
}
.api-token-input {
width: 100%;
padding: 8px;
font-size: 14px;
border: 1px solid #ccc;
border-radius: 4px;
margin-bottom: 10px;
}
.btn {
font-size: 14px;
padding: 8px 16px;
background-color: #4a4a4a;
color: #fff;
border: none;
border-radius: 4px;
cursor: pointer;
margin: 5px 0;
transition: background-color 0.3s;
width: 300px;
}
.btn:hover {
background-color: #333;
}
.message-box {
font-size: 14px;
padding: 20px;
color: #4a4a4a;
}
.message-box .error {
color: #ff0000;
}
.message-box .message {
color: #4a4a4a;
}
#controls {
margin-top: 30px;
display: flex;
flex-direction: column;
}
.control {
display: flex;
justify-content: space-between;
margin-bottom: 10px;
}
.input {
display: flex;
align-items: center;
font-weight: bold;
color: #4a4a4a;
}
.input span:first-child {
margin-right: 5px;
}
.effect {
font-size: 14px;
color: #4a4a4a;
}
.settings-form {
margin-top: 30px;
}
.settings-text {
font-size: 18px;
color: #4a4a4a;
margin-bottom: 20px;
}
.settings-container {
display: flex;
flex-direction: column;
}
.settings-item {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 10px;
}
.settings-item-text {
font-size: 16px;
color: #4a4a4a;
}
.switch {
position: relative;
display: inline-block;
width: 60px;
height: 34px;
}
.switch input {
opacity: 0;
width: 0;
height: 0;
}
.slider {
position: absolute;
cursor: pointer;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: #ccc;
transition: 0.4s;
}
.slider:before {
position: absolute;
content: "";
height: 26px;
width: 26px;
left: 4px;
bottom: 4px;
background-color: white;
transition: 0.4s;
}
input:checked + .slider {
background-color: #4a4a4a;
}
input:checked + .slider:before {
transform: translateX(26px);
}
.slider.round {
border-radius: 34px;
}
.slider.round:before {
border-radius: 50%;
}

58
popup/popup.html Normal file
View File

@@ -0,0 +1,58 @@
<html>
<head>
<script src="/scripts/jquery.js"></script>
<script src="/popup/popup.js"></script>
<link rel="stylesheet" href="/popup/popup.css" />
</head>
<body>
<h1 class="extension-title">LeafLLM: an AI-powered Overleaf</h1>
<div id="controls" class="form-container">
<div class="control">
<div class="input"><span>ALT</span><span>I</span></div>
<div class="effect">Improve selected text.</div>
</div>
<div class="control">
<div class="input"><span>ALT</span><span>C</span></div>
<div class="effect">Complete selected text.</div>
</div>
<div class="control">
<div class="input"><span>ALT</span><span>A</span></div>
<div class="effect">Ask GPT.</div>
</div>
</div>
<div id="settings-form" class="form-container">
<div class="settings-text">Settings</div>
<div class="settings-container">
<div class="settings-item">
<div class="settings-item-text">Text Improvement</div>
<label class="switch">
<input name="text-improvement" class="settings-item-checkbox" type="checkbox" />
<span class="slider round"></span>
</label>
</div>
<div class="settings-item">
<div class="settings-item-text">Text Completion</div>
<label class="switch">
<input name="text-completion" class="settings-item-checkbox" type="checkbox" />
<span class="slider round"></span>
</label>
</div>
<div class="settings-item">
<div class="settings-item-text">Ask</div>
<label class="switch">
<input name="text-ask" class="settings-item-checkbox" type="checkbox" />
<span class="slider round"></span>
</label>
</div>
</div>
</div>
<div id="api-token-form" class="form-container">
<div class="api-token-text"><span>API Token is </span><span class="api-token-status">not set</span></div>
<input class="api-token-input" name="api-token" placeholder="OpenAI API Token" type="text" />
<div class="message">To get an API key, go to <a href="https://platform.openai.com/account/api-keys">https://platform.openai.com/account/api-keys</a> </div>
<button class="submit btn">Set API Token</button>
<button class="clear btn">Reset API Token</button>
</div>
<div id="message-box" class="message-box"></div>
</body>
</html>

105
popup/popup.js Normal file
View File

@@ -0,0 +1,105 @@
const apiKeyRegex = /sk-[a-zA-Z0-9]{48}/;
const settings = [
{ key: "textCompletion", name: "text-completion" },
{ key: "textImprovement", name: "text-improvement" },
{ key: "textAsk", name: "text-ask" },
];
function addMessage(message) {
$("#message-box").append(`<div class="message">${message}</div>`);
}
function addErrorMessage(message) {
$("#message-box").append(`<div class="error">${message}</div>`);
}
function clearMessages() {
$("#message-box").empty();
}
async function refreshStorage() {
chrome.storage.local.get("openAIAPIKey").then(({ openAIAPIKey }) => {
$("#api-token-form .api-token-status").text(chrome.runtime.lastError || !openAIAPIKey ? "not set" : "set");
});
chrome.storage.local.get(settings.map(({ key }) => key)).then((storage) => {
settings.forEach(({ key, name }) => {
$(`#settings-form input[name='${name}']:checkbox`).prop("checked", storage[key]);
});
});
}
async function handleAPITokenSet(event) {
event.preventDefault();
event.stopPropagation();
clearMessages();
const input = $("#api-token-form").find("input[name='api-token']");
const openAIAPIKey = input.val();
input.val("");
if (!openAIAPIKey || !apiKeyRegex.test(openAIAPIKey)) {
addErrorMessage("Invalid API Token.");
return;
}
try {
await chrome.storage.local.set({ openAIAPIKey });
} catch (error) {
console.log(error);
addErrorMessage("Failed to set API Token.");
return;
}
await refreshStorage();
}
async function handleAPITokenClear(event) {
event.preventDefault();
event.stopPropagation();
clearMessages();
try {
await chrome.storage.local.remove("openAIAPIKey");
} catch (error) {
console.log(error);
addErrorMessage("Failed to remove API Token.");
return;
}
await refreshStorage();
}
function makeHandleSettingChange(key) {
return async (event) => {
event.preventDefault();
event.stopPropagation();
clearMessages();
const value = event.target.checked;
try {
await chrome.storage.local.set({ [key]: value });
} catch (error) {
console.log(error);
addErrorMessage(`Failed to set ${key} setting.`);
return;
}
await refreshStorage();
};
}
$(document).ready(async function () {
$("#api-token-form .submit").on("click", handleAPITokenSet);
$("#api-token-form .clear").on("click", handleAPITokenClear);
settings.forEach(({ key, name }) =>
$(`#settings-form input[name='${name}']:checkbox`).on("change", makeHandleSettingChange(key))
);
await refreshStorage();
});