Skip to content

Commit 4ece286

Browse files
author
Ali Raheem
committed
Chat mode
1 parent 07ffdab commit 4ece286

File tree

12 files changed

+295
-93
lines changed

12 files changed

+295
-93
lines changed

aify.xpi

2.11 KB
Binary file not shown.

plugin/html/API.js

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ export const fetchModels = async (apiKey) => {
1414
return await response.json();
1515
};
1616

17-
export const fetchResponse = async (apiKey, model, original, prompt, maxTokens) => {
17+
export const fetchResponse = async (apiKey, model, messages, maxTokens) => {
1818
const response = await fetch("https://api.openai.com/v1/chat/completions", {
1919
method: "POST",
2020
headers: {
@@ -23,10 +23,7 @@ export const fetchResponse = async (apiKey, model, original, prompt, maxTokens)
2323
},
2424
body: JSON.stringify({
2525
model,
26-
messages: [
27-
{ role: "system", content: prompt },
28-
{ role: "user", content: original }
29-
],
26+
messages: messages,
3027
...(maxTokens > 0 ? { 'max_tokens': parseInt(maxTokens) } : {})
3128
}),
3229
});

plugin/html/actions.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
<body>
88
<div id="actions-container"></div>
99
<hr>
10+
<a href="/html/chat.html" class="flat" id="chat-link">Chat</a><br/><br/>
1011
<a href="/html/settings.html" class="flat" id="settings-link">Settings </a>
1112
<script type="module" src="actions.js"></script>
1213
</body>

plugin/html/actions.js

Lines changed: 45 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,50 +1,45 @@
1-
import { promptVersion } from './globals.js';
2-
3-
const addAction = (name, prompt, actionsContainer) => {
4-
const actionDiv = document.createElement("div");
5-
const nameInput = document.createElement("p");
6-
nameInput.classList.add("flat");
7-
nameInput.innerText = name;
8-
nameInput.classList.add("action-name");
9-
10-
const getHighlightedText = () => {
11-
return browser.tabs.query({ active: true, currentWindow: true }).then((tabs) => {
12-
return browser.tabs.sendMessage(tabs[0].id, { command: "getSelectedText" });
13-
});
14-
};
15-
16-
nameInput.onclick = () => {
17-
getHighlightedText().then((highlightedText) => {
18-
if (highlightedText) {
19-
rewrite(highlightedText, prompt, name);
20-
}
21-
});
22-
};
23-
24-
actionDiv.appendChild(nameInput);
25-
actionsContainer.appendChild(actionDiv);
26-
};
27-
28-
const rewrite = async (original, action, draftTitle) => {
29-
await browser.storage.local.set({ original, action, draftTitle });
30-
browser.windows.create({ url: "/html/draft.html", type: "popup", width: 512, height: 600 });
31-
};
32-
33-
document.addEventListener("DOMContentLoaded", () => {
34-
const actionsContainer = document.getElementById("actions-container");
35-
browser.storage.local.get(["actions", "promptUpdated"], (data) => {
36-
const { actions, promptUpdated = 0 } = data;
37-
38-
if (promptVersion > promptUpdated) {
39-
const warningIcon = document.createElement('img');
40-
warningIcon.src = "/images/warning.png";
41-
warningIcon.classList.add('small-icon');
42-
const settingsLink = document.getElementById('settings-link');
43-
settingsLink.appendChild(warningIcon);
44-
};
45-
46-
actions.forEach((action) => {
47-
addAction(action.name, action.prompt, actionsContainer);
48-
});
49-
});
50-
});
1+
import { promptVersion } from './globals.js';
2+
3+
const addAction = (name, prompt, actionsContainer) => {
4+
const actionDiv = document.createElement("div");
5+
const nameInput = document.createElement("p");
6+
nameInput.classList.add("flat");
7+
nameInput.innerText = name;
8+
nameInput.classList.add("action-name");
9+
10+
const getHighlightedText = () => {
11+
return browser.tabs.query({ active: true, currentWindow: true }).then((tabs) => {
12+
return browser.tabs.sendMessage(tabs[0].id, { command: "getSelectedText" });
13+
});
14+
};
15+
16+
nameInput.onclick = () => {
17+
getHighlightedText().then((highlightedText) => {
18+
if (highlightedText) {
19+
const messages = [{role: "system", content: prompt},
20+
{role: "user", content: highlightedText}];
21+
browser.storage.local.set({ messages, draftTitle: name }).then( () => {
22+
browser.windows.create({ url: "/html/draft.html", type: "popup",
23+
width: 600, height: 512 });
24+
});
25+
}
26+
});
27+
};
28+
actionDiv.appendChild(nameInput);
29+
actionsContainer.appendChild(actionDiv);
30+
};
31+
32+
document.addEventListener("DOMContentLoaded", () => {
33+
const actionsContainer = document.getElementById("actions-container");
34+
browser.storage.local.get(["actions", "promptUpdated"], (data) => {
35+
const { actions, promptUpdated = 0 } = data;
36+
if (promptVersion > promptUpdated) {
37+
const warningIcon = document.createElement('img');
38+
warningIcon.src = "/images/warning.png";
39+
warningIcon.classList.add('small-icon');
40+
const settingsLink = document.getElementById('settings-link');
41+
settingsLink.appendChild(warningIcon);
42+
};
43+
actions.forEach((action) => {addAction(action.name, action.prompt, actionsContainer)});
44+
});
45+
});

plugin/html/chat.css

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
body {
2+
margin: 0;
3+
padding: 0;
4+
font-family: Arial, sans-serif;
5+
}
6+
7+
.chat-container {
8+
height: 100vh;
9+
display: flex;
10+
flex-direction: column;
11+
}
12+
13+
.messages {
14+
flex-grow: 1;
15+
overflow-y: auto;
16+
padding: 10px;
17+
border-bottom: 1px solid #ccc;
18+
}
19+
20+
.chat-input-area {
21+
display: flex;
22+
justify-content: space-between;
23+
align-items: center;
24+
padding: 10px;
25+
height: 15%;
26+
}
27+
28+
#message-input {
29+
flex-grow: 1;
30+
margin-right: 10px;
31+
height: 80%;
32+
}
33+
34+
.system-message {
35+
background-color:hotpink;
36+
}
37+
38+
.user-message {
39+
background-color: blanchedalmond;
40+
}
41+
42+
.assistant-message {
43+
background-color: cadetblue;
44+
}
45+
46+
.message {
47+
padding: 10px;
48+
margin: 5px;
49+
border-radius: 4px;
50+
}

plugin/html/chat.html

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<!DOCTYPE html>
2+
<html>
3+
<head>
4+
<title>Aify Chat</title>
5+
<link rel="stylesheet" type="text/css" media="screen" href="chat.css">
6+
<link rel="stylesheet" type="text/css" media="screen" href="styles.css">
7+
</head>
8+
<body>
9+
<div class="chat-container">
10+
<div id="messages" class="messages">
11+
<!-- Chat messages will be added here dynamically -->
12+
</div>
13+
<div class="chat-input-area">
14+
<textarea id="message-input" placeholder="Type your message here..."></textarea>
15+
<button id="send-button" class="button good">Send</button>
16+
<!-- <button id="clear-button" class="button bad">Clear</button> -->
17+
</div>
18+
</div>
19+
<script type="module" src="chat.js"></script>
20+
</body>
21+
</html>

plugin/html/chat.js

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
import { fetchResponse } from './API.js';
2+
import { chatPrompt } from './globals.js';
3+
4+
document.addEventListener('DOMContentLoaded', async function () {
5+
var messageInput = document.getElementById('message-input');
6+
var sendButton = document.getElementById('send-button');
7+
// const clearButton = document.getElementById('clear-button');
8+
var messagesContainer = document.getElementById('messages');
9+
var loadingIcon = document.createElement('img');
10+
11+
loadingIcon.src = '/images/loading.png';
12+
loadingIcon.classList.add('rotate');
13+
14+
var storage = await browser.storage.local.get(["messages"]);
15+
var messages = storage.messages || [{role: "system", content: chatPrompt}];
16+
17+
messages.forEach(function(message) {
18+
displayMessage(message.role, message.content);
19+
});
20+
21+
//clearButton.addEventListener('click', async () => {messages = []; await browser.storage.local.set({messages}); window.close()})
22+
sendButton.addEventListener('click', async function () {
23+
var messageText = messageInput.value.trim();
24+
25+
if (messageText) {
26+
messages.push({role: 'user', content: messageText});
27+
displayMessage('user', messageText);
28+
29+
sendButton.disabled = true;
30+
messagesContainer.appendChild(loadingIcon);
31+
32+
try {
33+
const { apiKey, model, maxTokens } = await browser.storage.local.get(
34+
["apiKey","model", "maxTokens"]);
35+
const response = await fetchResponse(apiKey, model, messages, maxTokens);
36+
// messagesContainer.innerText = response;
37+
// Remove loading icon and enable button when API call finishes
38+
loadingIcon.parentElement.removeChild(loadingIcon);
39+
sendButton.disabled = false;
40+
41+
if (response) {
42+
// Display the response from the model
43+
displayMessage('assistant', response);
44+
messages.push({role: 'assistant', content: response});
45+
46+
// Save messages to local storage
47+
browser.storage.local.set({messages});
48+
}
49+
} catch(error) {
50+
console.error(error);
51+
messagesContainer.textContent = "Error: Unable to retrieve data";
52+
}
53+
54+
messageInput.value = '';
55+
}
56+
});
57+
58+
function displayMessage(role, text) {
59+
var messageDiv = document.createElement('div');
60+
messageDiv.classList.add(role + '-message');
61+
messageDiv.classList.add('message');
62+
messageDiv.innerText = text;
63+
messagesContainer.appendChild(messageDiv);
64+
65+
// Scroll to bottom
66+
messagesContainer.scrollTop = messagesContainer.scrollHeight;
67+
}
68+
});
69+
70+
window.addEventListener('unload', async function (event) {
71+
await browser.storage.local.set({ messages: "[]", draftTitle: "" });
72+
});
73+

plugin/html/draft.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010

1111
</main>
1212
<div class="btns">
13+
<button id="chat-button" class="button neutral">Convert to Chat</button><br/>
1314
<button id="regenerate" value="Regenerate" class="button bad">Regenerate</button><br/>
1415
</div>
1516
</body>

plugin/html/draft.js

Lines changed: 39 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,39 @@
1-
import { fetchModels, fetchResponse } from './API.js';
2-
3-
async function regenerate(draftContainer, original, action, draftTitle) {
4-
const loadingIcon = document.createElement("img");
5-
loadingIcon.src = "/images/loading.png";
6-
loadingIcon.classList.add("rotate");
7-
8-
draftContainer.textContent = "";
9-
draftContainer.appendChild(loadingIcon);
10-
11-
try {
12-
const draft = await callBackendAPI(original, action);
13-
draftContainer.innerText = draft;
14-
document.title = draftTitle;
15-
} catch (error) {
16-
console.error(error);
17-
draftContainer.textContent = "Error: Unable to retrieve data";
18-
}
19-
}
20-
21-
async function callBackendAPI(original, action) {
22-
const { model, apiKey, maxTokens } = await browser.storage.local.get(["model", "apiKey", "maxTokens"]);
23-
const response = await fetchResponse(apiKey, model, original, action, maxTokens);
24-
return response;
25-
}
26-
27-
document.addEventListener("DOMContentLoaded", async () => {
28-
const regenButton = document.getElementById('regenerate');
29-
const draftContainer = document.getElementById("draft-container");
30-
31-
const { original, action, draftTitle } = await browser.storage.local.get(["original", "action", "draftTitle"]);
32-
33-
await browser.storage.local.set({ original: "", action: "", draftTitle: "" });
34-
35-
regenButton.addEventListener("click", () => regenerate(draftContainer, original, action, draftTitle));
36-
regenerate(draftContainer, original, action, draftTitle);
37-
});
1+
import { fetchResponse } from './API.js';
2+
3+
async function generate(draftContainer, messages, draftTitle) {
4+
const loadingIcon = document.createElement("img");
5+
loadingIcon.src = "/images/loading.png";
6+
loadingIcon.classList.add("rotate");
7+
8+
draftContainer.textContent = "";
9+
draftContainer.appendChild(loadingIcon);
10+
11+
try {
12+
const { apiKey, model, maxTokens } = await browser.storage.local.get(
13+
["apiKey","model", "maxTokens"]);
14+
const response = await fetchResponse(apiKey, model, messages, maxTokens);
15+
draftContainer.innerText = response;
16+
messages.push({role: "assistant", content: response});
17+
document.title = draftTitle;
18+
await browser.storage.local.set({messages});
19+
} catch (error) {
20+
console.error(error);
21+
draftContainer.textContent = "Error: Unable to retrieve data";
22+
}
23+
}
24+
25+
document.addEventListener("DOMContentLoaded", async () => {
26+
const regenButton = document.getElementById('regenerate');
27+
const chatButton = document.getElementById('chat-button');
28+
const draftContainer = document.getElementById("draft-container");
29+
const { messages, draftTitle } = await browser.storage.local.get(["messages", "draftTitle"]);
30+
regenButton.addEventListener("click", () => generate(draftContainer, messages, draftTitle));
31+
chatButton.addEventListener("click", async () => {
32+
await browser.storage.local.set({messages});
33+
await browser.windows.create({ url: "/html/chat.html", type: "popup",
34+
width: 600, height: 600 });
35+
window.close();
36+
})
37+
generate(draftContainer, messages, draftTitle);
38+
await browser.storage.local.set({ messages: "[]", draftTitle: "" });
39+
});

plugin/html/globals.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,5 @@ export const defaultActions = [
88
{ name: "Translate this", prompt: "Translate the following email in English." },
99
{ name: "Prompt provided", prompt: "You are a helpful chatbot that will do their best to complete the following tasks with a single response." },
1010
];
11+
export const chatPrompt = "You are a helpful chatbot, answer any questions the user has";
1112
export const defaultModel = "gpt-3.5-turbo";

0 commit comments

Comments
 (0)