Skip to content

Commit

Permalink
feat: add chatGPT stream feature
Browse files Browse the repository at this point in the history
  • Loading branch information
hadnet committed Apr 9, 2023
1 parent f8bd3a4 commit e93b4d8
Show file tree
Hide file tree
Showing 6 changed files with 114 additions and 65 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"release": "HUSKY_SKIP_HOOKS=1 dotenv release-it"
},
"dependencies": {
"eventsource-parser": "^1.0.0",
"hast-util-to-html": "^8.0.4",
"highlight.js": "^11.7.0",
"lowlight": "^2.8.1",
Expand Down
106 changes: 70 additions & 36 deletions src/pages/Background/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { nonNullable } from '../../utils';
import { Actions, type Action, type OpenaiResponse } from '../../types';
import { Actions, type Action } from '../../types';

let isDark: boolean;

Expand Down Expand Up @@ -44,41 +44,75 @@ function setCSSTheme(isDark: boolean, tabId: number | undefined) {
}
}

chrome.runtime.onMessage.addListener(
(message: Action, sender, sendResponse) => {
switch (message.action) {
case Actions.fetchOpenAi:
//INFO: cant use async/await here because onMessage doesnt work properly with `await`
fetch('https://openai-api.hadnet.workers.dev/?action=explain-code', {
method: 'POST',
body: JSON.stringify({ code: message.payload?.code }),
headers: {
'Content-Type': 'application/json; charset=utf-8',
},
chrome.runtime.onMessage.addListener((message: Action, sender) => {
switch (message.action) {
case Actions.fetchOpenAi: {
//INFO: cant use async/await here because onMessage doesnt work properly with `await`
fetch('https://openai-api.hadnet.workers.dev/?action=explain-code', {
method: 'POST',
body: JSON.stringify({ code: message.payload?.code }),
headers: {
'Content-Type': 'application/json; charset=utf-8',
},
})
.then((response) => {
let content = '';

if (!response.ok) {
throw new Error(response.statusText);
}

// This data is a ReadableStream
const data = response.body;
if (!data) {
return;
}

(async () => {
const reader = data.getReader();
const _decoder = new TextDecoder();
let done = false;

while (!done) {
const { value, done: doneReading } = await reader.read();
done = doneReading;
const chunkValue = _decoder.decode(value);
content += chunkValue;
chrome.tabs.query(
{ active: true, currentWindow: true },
function (tabs) {
if (!tabs[0].id) return;
chrome.tabs.sendMessage(tabs[0].id, {
action: Actions.stream,
payload: content,
});
}
);
}
})();
})
.then((response) => response.json())
.then((data: OpenaiResponse) => {
sendResponse(data);
})
.catch((e) => console.log('Error', e));
break;
case Actions.copyToClipboard:
chrome.notifications.create(
{
type: 'basic',
title: 'Copied!',
message: 'The code snippet was copied to your clipboard',
iconUrl: 'icon-128.png',
},
() => console.log('notification created')
);
break;
default:
if (sender.url?.includes('https://twitter.com')) {
isDark = message.payload?.isDark;
setCSSTheme(isDark, sender.tab?.id);
}
.catch((e) => {
if (e instanceof Error)
throw new Error(`Error in fetching API: ${e.message}`);
});
break;
}
return true;
case Actions.copyToClipboard:
chrome.notifications.create(
{
type: 'basic',
title: 'Copied!',
message: 'The code snippet was copied to your clipboard',
iconUrl: 'icon-128.png',
},
() => console.log('A notification was created')
);
break;
default:
if (sender.url?.includes('https://twitter.com')) {
isDark = message.payload?.isDark;
setCSSTheme(isDark, sender.tab?.id);
}
}
);
return true;
});
54 changes: 28 additions & 26 deletions src/pages/Content/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@ import hljs from 'highlight.js';
import { marked } from 'marked';
import { nonNullable } from '../../utils';
import { IconNames, getIcon, type Color } from './modules/svg-icons';
import { Actions, type Action, type OpenaiResponse } from '../../types';
// import { toHtml } from 'hast-util-to-html';
import { Actions, type Action } from '../../types';

// console.log('Content script works!');
let shimmer: HTMLDivElement;
let loading = false;

function convertToMarkdownAndHighlight(t: Element, color: Color) {
if (t.textContent) {
Expand Down Expand Up @@ -79,7 +79,6 @@ function convertToMarkdownAndHighlight(t: Element, color: Color) {
cardDOM.appendChild(langIconBtn);

if (language.match(/tsx?$/)) {
let loading = false;
const helpIconBtn = document.createElement('a');
helpIconBtn.style.position = 'absolute';
helpIconBtn.style.cursor = 'pointer';
Expand All @@ -89,7 +88,7 @@ function convertToMarkdownAndHighlight(t: Element, color: Color) {
helpIconBtn.innerHTML = getIcon('openai', isDark ? '#d3d3d3' : '#545063');

helpIconBtn.addEventListener('click', () => {
const shimmer = document.createElement('div');
shimmer = document.createElement('div');
shimmer.style.display = 'flex';
shimmer.style.flexDirection = 'column';
shimmer.style.background = isDark ? '#1c1a23' : '#f1eae7';
Expand All @@ -102,27 +101,10 @@ function convertToMarkdownAndHighlight(t: Element, color: Color) {
cardDOM.parentElement?.appendChild(shimmer);
if (!loading) {
loading = true;
chrome.runtime.sendMessage<Action, OpenaiResponse>(
{
action: Actions.fetchOpenAi,
payload: { code: codeString },
},
(data) => {
const setContent = (content: string, icon: IconNames = 'info') =>
`<div style="display: flex; flex-direction: row;"><div style="margin-right: 16px;">${getIcon(
icon,
isDark ? '#a8edff' : '#232323'
)}</div><div>${content}</div></div>`.trim();
if ('error' in data) {
shimmer.innerHTML = setContent(data.error, 'warning');
} else {
shimmer.style.padding = '20px';
shimmer.style.lineHeight = '150%';
shimmer.innerHTML = setContent(data.choices[0].text);
}
loading = false;
}
);
chrome.runtime.sendMessage<Action, Response>({
action: Actions.fetchOpenAi,
payload: { code: codeString },
});
}
});
cardDOM.appendChild(helpIconBtn);
Expand Down Expand Up @@ -159,6 +141,26 @@ chrome.runtime.sendMessage<Action>({
payload: { isDark },
});

chrome.runtime.onMessage.addListener((message: Action<string>) => {
switch (message.action) {
case Actions.stream: {
const data = message.payload;
loading = false;

const setContent = (content: string, icon: IconNames = 'info') =>
`<div style="display: flex; flex-direction: row;"><div style="margin-right: 16px;">${getIcon(
icon,
isDark ? '#a8edff' : '#232323'
)}</div><div>${content}</div></div>`.trim();

shimmer.style.padding = '20px';
shimmer.style.lineHeight = '150%';
shimmer.innerHTML = setContent(data ?? 'No response');
break;
}
}
});

theme.addEventListener('change', (event) => {
event.preventDefault();
const theme = window.matchMedia('(prefers-color-scheme: dark)');
Expand Down
3 changes: 2 additions & 1 deletion src/static/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@
{
"matches": [
"https://twitter.com/*",
"https://openai-api.hadnet.workers.dev/*"
"https://openai-api.hadnet.workers.dev/*",
"http://127.0.0.1:8787/*"
],
"js": ["contentScript.js"]
}
Expand Down
5 changes: 3 additions & 2 deletions src/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,10 @@ export const enum Actions {
copyToClipboard = 'copyToClipboard',
fetchOpenAi = 'fetchOpenAi',
changeTheme = 'changeTheme',
stream = 'streamText',
}

export type Action = {
export type Action<T = any> = {
action: Actions;
payload?: any;
payload?: T;
};
10 changes: 10 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1337,6 +1337,11 @@
resolved "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.4.tgz"
integrity sha512-Hcv+nVC0kZnQ3tD9GVu5xSMR4VVYOteQIr/hwFPVEvPdlXqgGEuRjiheChHgdM+JyqdgNcmzZOX/tnl0JOiI7A==

"@microsoft/fetch-event-source@^2.0.1":
version "2.0.1"
resolved "https://registry.yarnpkg.com/@microsoft/fetch-event-source/-/fetch-event-source-2.0.1.tgz#9ceecc94b49fbaa15666e38ae8587f64acce007d"
integrity sha512-W6CLUJ2eBMw3Rec70qrsEW0jOm/3twwJv21mrmj2yORiaVmVYGS4sSS5yUwvQc1ZlDLYGPnClVWmUUMagKNsfA==

"@nicolo-ribaudo/eslint-scope-5-internals@5.1.1-v1":
version "5.1.1-v1"
resolved "https://registry.npmjs.org/@nicolo-ribaudo/eslint-scope-5-internals/-/eslint-scope-5-internals-5.1.1-v1.tgz"
Expand Down Expand Up @@ -4190,6 +4195,11 @@ events@^3.2.0:
resolved "https://registry.npmjs.org/events/-/events-3.3.0.tgz"
integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==

eventsource-parser@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/eventsource-parser/-/eventsource-parser-1.0.0.tgz#6332e37fd5512e3c8d9df05773b2bf9e152ccc04"
integrity sha512-9jgfSCa3dmEme2ES3mPByGXfgZ87VbP97tng1G2nWwWx6bV2nYxm2AWCrbQjXToSe+yYlqaZNtxffR9IeQr95g==

execa@7.1.1, execa@^7.0.0:
version "7.1.1"
resolved "https://registry.npmjs.org/execa/-/execa-7.1.1.tgz"
Expand Down

0 comments on commit e93b4d8

Please sign in to comment.