feat: openai completion support

This commit is contained in:
Artemy 2023-04-04 16:50:11 +03:00
parent 8fb91d7ecb
commit 2c07e71349
12 changed files with 155 additions and 17 deletions

59
package-lock.json generated
View file

@ -16,6 +16,7 @@
"dotenv": "^16.0.3",
"express": "^4.18.2",
"js-sha3": "^0.8.0",
"openai": "^3.2.1",
"react": "^18.2.0",
"react-contenteditable": "^3.3.7",
"react-dom": "^18.2.0",
@ -889,6 +890,14 @@
"postcss": "^8.1.0"
}
},
"node_modules/axios": {
"version": "0.26.1",
"resolved": "https://registry.npmjs.org/axios/-/axios-0.26.1.tgz",
"integrity": "sha512-fPwcX4EvnSHuInCMItEhAGnaSEXRBjtzh9fOtsE6E1G6p7vl7edEeZe11QHf18+6+9gR5PbKV/sGKNaD8YaMeA==",
"dependencies": {
"follow-redirects": "^1.14.8"
}
},
"node_modules/bail": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz",
@ -2202,6 +2211,25 @@
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
},
"node_modules/follow-redirects": {
"version": "1.15.2",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz",
"integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==",
"funding": [
{
"type": "individual",
"url": "https://github.com/sponsors/RubenVerborgh"
}
],
"engines": {
"node": ">=4.0"
},
"peerDependenciesMeta": {
"debug": {
"optional": true
}
}
},
"node_modules/form-data": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
@ -3913,6 +3941,15 @@
"node": ">= 0.8"
}
},
"node_modules/openai": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/openai/-/openai-3.2.1.tgz",
"integrity": "sha512-762C9BNlJPbjjlWZi4WYK9iM2tAVAv0uUp1UmI34vb0CN5T2mjB/qM6RYBmNKMh/dN9fC+bxqPwWJZUTWW052A==",
"dependencies": {
"axios": "^0.26.0",
"form-data": "^4.0.0"
}
},
"node_modules/optionator": {
"version": "0.8.3",
"resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz",
@ -6269,6 +6306,14 @@
"postcss-value-parser": "^4.2.0"
}
},
"axios": {
"version": "0.26.1",
"resolved": "https://registry.npmjs.org/axios/-/axios-0.26.1.tgz",
"integrity": "sha512-fPwcX4EvnSHuInCMItEhAGnaSEXRBjtzh9fOtsE6E1G6p7vl7edEeZe11QHf18+6+9gR5PbKV/sGKNaD8YaMeA==",
"requires": {
"follow-redirects": "^1.14.8"
}
},
"bail": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz",
@ -7138,6 +7183,11 @@
}
}
},
"follow-redirects": {
"version": "1.15.2",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz",
"integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA=="
},
"form-data": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
@ -8256,6 +8306,15 @@
"ee-first": "1.1.1"
}
},
"openai": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/openai/-/openai-3.2.1.tgz",
"integrity": "sha512-762C9BNlJPbjjlWZi4WYK9iM2tAVAv0uUp1UmI34vb0CN5T2mjB/qM6RYBmNKMh/dN9fC+bxqPwWJZUTWW052A==",
"requires": {
"axios": "^0.26.0",
"form-data": "^4.0.0"
}
},
"optionator": {
"version": "0.8.3",
"resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz",

View file

@ -19,6 +19,7 @@
"dotenv": "^16.0.3",
"express": "^4.18.2",
"js-sha3": "^0.8.0",
"openai": "^3.2.1",
"react": "^18.2.0",
"react-contenteditable": "^3.3.7",
"react-dom": "^18.2.0",

View file

@ -12,6 +12,7 @@ import PubNoteSafe from "./pages/pubNoteSafe";
import RenderMarkdown from "./components/markdown";
import socket from "./components/socket";
import Settings from "./pages/settings";
import Chat from "./pages/chat";
import Locales from "./localisation/main";
import { useState } from "react";
@ -52,6 +53,7 @@ function App() {
<Route path="/pubNotesSafe/:id" element={<PubNoteSafe />} />
<Route path="/pubError" element={<PubError />} />
<Route path="/settings" element={<Settings />} />
<Route path="/chat" element={<Chat />} />
<Route
path="/about"
element={

View file

@ -5,7 +5,11 @@ function Button(props) {
<Link to={props.href} className={props.className}>
<div
onClick={props.onClick}
className={`transition-transform w-48 ease-[cubic-bezier(.69,.58,.32,1.69)] hover:scale-105 p-2 pl-6 text-lg bg-zinc-100 hover:bg-zinc-300 dark:bg-zinc-600 dark:hover:bg-zinc-800 rounded-2xl ${props.className}`}
className={`transition-transform ${
props.w ? props.w : "w-48"
} ease-[cubic-bezier(.69,.58,.32,1.69)] hover:scale-105 p-2 pl-6 text-lg bg-zinc-100 hover:bg-zinc-300 dark:bg-zinc-600 dark:hover:bg-zinc-800 rounded-2xl ${
props.className
}`}
>
{props.children}
</div>
@ -23,9 +27,9 @@ function IconWithText(props) {
);
}
return (
<div className="grid grid-cols-4">
<div className="grid place-content-start grid-cols-4">
<div>{props.icon}</div>
<div className="grid-span-3">{props.children}</div>
<div className="justify-self-start col-span-3">{props.children}</div>
</div>
);
}
@ -36,6 +40,7 @@ function ButtonWithIcon(props) {
href={props.href}
className={props.className}
onClick={props.onClick}
w={props.w}
>
<IconWithText
reverse={props.reverse}

31
src/components/openai.js Normal file
View file

@ -0,0 +1,31 @@
import { Configuration, OpenAIApi } from "openai";
async function Complete(setText) {
document.body.style.cursor = "wait";
let initText = localStorage.getItem("NoteText");
const configuration = new Configuration({
apiKey: settings.openAiKey,
});
const openai = new OpenAIApi(configuration);
const response = await openai.createCompletion({
model: "text-davinci-003",
prompt: initText,
max_tokens: 1000,
temperature: 1,
top_p: 1,
n: 1,
stream: false,
logprobs: null,
});
let totalText = initText + response.data.choices[0].text;
localStorage.setItem("NoteText", totalText);
setText(totalText);
document.body.style.cursor = "default";
}
export { Complete };

View file

@ -78,4 +78,17 @@ function SettingsSelectInput({
);
}
export { SettingsCheckBox, SettingsTextInput, SettingsSelectInput };
function SettingsPlaceholder({ text }) {
return (
<h1 className="text-center lg:text-left leading-tight text-xl font-semibold">
{text}
</h1>
);
}
export {
SettingsCheckBox,
SettingsTextInput,
SettingsSelectInput,
SettingsPlaceholder,
};

View file

@ -1,4 +1,4 @@
let inputStyle =
"form-control block px-3 py-1.5 text-base font-normal text-gray-700 dark:text-white bg-white dark:bg-zinc-900 bg-clip-padding border border-solid border-gray-300 rounded-lg transition ease-in-out focus:border-blue-600 focus:outline-none";
"w-full form-control block px-3 py-1.5 text-base font-normal text-gray-700 dark:text-white bg-white dark:bg-zinc-900 bg-clip-padding border border-solid border-gray-300 rounded-lg transition ease-in-out focus:border-blue-600 focus:outline-none";
export { inputStyle };

View file

@ -41,6 +41,8 @@ let en = {
Delete: "Delete",
Open: "Open",
NoNotesYet: "No notes yet",
AIComplete: "Continue Note (AI)",
AdditionalFeatures: "Additional features",
};
export default en;

View file

@ -42,6 +42,8 @@ let ru = {
Delete: "Удалить",
Open: "Открыть",
NoNotesYet: "Заметок пока нет",
AIComplete: "Продолжить заметку (ИИ)",
AdditionalFeatures: "Дополнительные функции",
};
export default ru;

3
src/pages/chat.jsx Normal file
View file

@ -0,0 +1,3 @@
function Chat() {}
export default Chat;

View file

@ -1,5 +1,8 @@
import { ButtonWithIcon } from "../components/button";
import { ChevronDoubleRightIcon } from "@heroicons/react/24/outline";
import {
ChevronDoubleRightIcon,
DocumentTextIcon,
} from "@heroicons/react/24/outline";
import { CheckBox } from "../components/checkbox";
import { useState } from "react";
import RenderMarkdown from "../components/markdown";
@ -12,8 +15,12 @@ import rehypeParse from "rehype-parse";
import remarkStringify from "remark-stringify";
import remarkGfm from "remark-gfm";
import remarkMath from "remark-math";
import { SettingsCheckBox } from "../components/settingsInputs";
import {
SettingsCheckBox,
SettingsPlaceholder,
} from "../components/settingsInputs";
import { inputStyle } from "../components/styles";
import { Complete } from "../components/openai";
function CreateNote() {
const [preview, setPreview] = useState(false);
@ -126,6 +133,23 @@ function CreateNote() {
className="m-1"
/>
</div>
{settings.additionalFeatures && (
<div className="justify-self-start lg:justify-self-start">
<SettingsPlaceholder text={locals.AdditionalFeatures} />
{!!settings.openAiKey && (
<ButtonWithIcon
icon={DocumentTextIcon}
text={locals.AIComplete}
className="m-1"
w="w-full"
onClick={() => {
Complete(setText);
}}
/>
)}
</div>
)}
</div>
</div>
);

View file

@ -2,6 +2,7 @@ import {
SettingsCheckBox,
SettingsTextInput,
SettingsSelectInput,
SettingsPlaceholder,
} from "../components/settingsInputs";
import { reRenderPage } from "../components/utils";
import Locales from "../localisation/main";
@ -38,17 +39,20 @@ function Settings() {
<SettingsCheckBox
label={locals.EditPreview}
title={locals.EditPreviewWarn}
checked={settings.editPreview}
settingName="editPreview"
/>
<SettingsCheckBox
label={locals.PublicNote}
title={locals.PublicNoteTitle}
checked={settings.publicNote}
settingName="publicNote"
/>
<SettingsCheckBox
label={locals.AdditionalFeatures}
settingName="additionalFeatures"
/>
<SettingsPlaceholder text={locals.Interface} />
<SettingsSelectInput
@ -87,12 +91,4 @@ function Settings() {
);
}
function SettingsPlaceholder({ text }) {
return (
<h1 className="text-center lg:text-left leading-tight text-xl font-semibold">
{text}
</h1>
);
}
export default Settings;