mirror of
https://github.com/artegoser/AnoPaper.git
synced 2024-11-06 04:53:56 +03:00
feat: openai completion support
This commit is contained in:
parent
8fb91d7ecb
commit
2c07e71349
12 changed files with 155 additions and 17 deletions
59
package-lock.json
generated
59
package-lock.json
generated
|
@ -16,6 +16,7 @@
|
||||||
"dotenv": "^16.0.3",
|
"dotenv": "^16.0.3",
|
||||||
"express": "^4.18.2",
|
"express": "^4.18.2",
|
||||||
"js-sha3": "^0.8.0",
|
"js-sha3": "^0.8.0",
|
||||||
|
"openai": "^3.2.1",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-contenteditable": "^3.3.7",
|
"react-contenteditable": "^3.3.7",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
|
@ -889,6 +890,14 @@
|
||||||
"postcss": "^8.1.0"
|
"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": {
|
"node_modules/bail": {
|
||||||
"version": "2.0.2",
|
"version": "2.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz",
|
"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",
|
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
|
||||||
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
|
"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": {
|
"node_modules/form-data": {
|
||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
|
||||||
|
@ -3913,6 +3941,15 @@
|
||||||
"node": ">= 0.8"
|
"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": {
|
"node_modules/optionator": {
|
||||||
"version": "0.8.3",
|
"version": "0.8.3",
|
||||||
"resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz",
|
"resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz",
|
||||||
|
@ -6269,6 +6306,14 @@
|
||||||
"postcss-value-parser": "^4.2.0"
|
"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": {
|
"bail": {
|
||||||
"version": "2.0.2",
|
"version": "2.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz",
|
"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": {
|
"form-data": {
|
||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
|
||||||
|
@ -8256,6 +8306,15 @@
|
||||||
"ee-first": "1.1.1"
|
"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": {
|
"optionator": {
|
||||||
"version": "0.8.3",
|
"version": "0.8.3",
|
||||||
"resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz",
|
"resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz",
|
||||||
|
|
|
@ -19,6 +19,7 @@
|
||||||
"dotenv": "^16.0.3",
|
"dotenv": "^16.0.3",
|
||||||
"express": "^4.18.2",
|
"express": "^4.18.2",
|
||||||
"js-sha3": "^0.8.0",
|
"js-sha3": "^0.8.0",
|
||||||
|
"openai": "^3.2.1",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-contenteditable": "^3.3.7",
|
"react-contenteditable": "^3.3.7",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
|
|
|
@ -12,6 +12,7 @@ import PubNoteSafe from "./pages/pubNoteSafe";
|
||||||
import RenderMarkdown from "./components/markdown";
|
import RenderMarkdown from "./components/markdown";
|
||||||
import socket from "./components/socket";
|
import socket from "./components/socket";
|
||||||
import Settings from "./pages/settings";
|
import Settings from "./pages/settings";
|
||||||
|
import Chat from "./pages/chat";
|
||||||
import Locales from "./localisation/main";
|
import Locales from "./localisation/main";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
|
|
||||||
|
@ -52,6 +53,7 @@ function App() {
|
||||||
<Route path="/pubNotesSafe/:id" element={<PubNoteSafe />} />
|
<Route path="/pubNotesSafe/:id" element={<PubNoteSafe />} />
|
||||||
<Route path="/pubError" element={<PubError />} />
|
<Route path="/pubError" element={<PubError />} />
|
||||||
<Route path="/settings" element={<Settings />} />
|
<Route path="/settings" element={<Settings />} />
|
||||||
|
<Route path="/chat" element={<Chat />} />
|
||||||
<Route
|
<Route
|
||||||
path="/about"
|
path="/about"
|
||||||
element={
|
element={
|
||||||
|
|
|
@ -5,7 +5,11 @@ function Button(props) {
|
||||||
<Link to={props.href} className={props.className}>
|
<Link to={props.href} className={props.className}>
|
||||||
<div
|
<div
|
||||||
onClick={props.onClick}
|
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}
|
{props.children}
|
||||||
</div>
|
</div>
|
||||||
|
@ -23,9 +27,9 @@ function IconWithText(props) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<div className="grid grid-cols-4">
|
<div className="grid place-content-start grid-cols-4">
|
||||||
<div>{props.icon}</div>
|
<div>{props.icon}</div>
|
||||||
<div className="grid-span-3">{props.children}</div>
|
<div className="justify-self-start col-span-3">{props.children}</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -36,6 +40,7 @@ function ButtonWithIcon(props) {
|
||||||
href={props.href}
|
href={props.href}
|
||||||
className={props.className}
|
className={props.className}
|
||||||
onClick={props.onClick}
|
onClick={props.onClick}
|
||||||
|
w={props.w}
|
||||||
>
|
>
|
||||||
<IconWithText
|
<IconWithText
|
||||||
reverse={props.reverse}
|
reverse={props.reverse}
|
||||||
|
|
31
src/components/openai.js
Normal file
31
src/components/openai.js
Normal 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 };
|
|
@ -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,
|
||||||
|
};
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
let inputStyle =
|
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 };
|
export { inputStyle };
|
||||||
|
|
|
@ -41,6 +41,8 @@ let en = {
|
||||||
Delete: "Delete",
|
Delete: "Delete",
|
||||||
Open: "Open",
|
Open: "Open",
|
||||||
NoNotesYet: "No notes yet",
|
NoNotesYet: "No notes yet",
|
||||||
|
AIComplete: "Continue Note (AI)",
|
||||||
|
AdditionalFeatures: "Additional features",
|
||||||
};
|
};
|
||||||
|
|
||||||
export default en;
|
export default en;
|
||||||
|
|
|
@ -42,6 +42,8 @@ let ru = {
|
||||||
Delete: "Удалить",
|
Delete: "Удалить",
|
||||||
Open: "Открыть",
|
Open: "Открыть",
|
||||||
NoNotesYet: "Заметок пока нет",
|
NoNotesYet: "Заметок пока нет",
|
||||||
|
AIComplete: "Продолжить заметку (ИИ)",
|
||||||
|
AdditionalFeatures: "Дополнительные функции",
|
||||||
};
|
};
|
||||||
|
|
||||||
export default ru;
|
export default ru;
|
||||||
|
|
3
src/pages/chat.jsx
Normal file
3
src/pages/chat.jsx
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
function Chat() {}
|
||||||
|
|
||||||
|
export default Chat;
|
|
@ -1,5 +1,8 @@
|
||||||
import { ButtonWithIcon } from "../components/button";
|
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 { CheckBox } from "../components/checkbox";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import RenderMarkdown from "../components/markdown";
|
import RenderMarkdown from "../components/markdown";
|
||||||
|
@ -12,8 +15,12 @@ import rehypeParse from "rehype-parse";
|
||||||
import remarkStringify from "remark-stringify";
|
import remarkStringify from "remark-stringify";
|
||||||
import remarkGfm from "remark-gfm";
|
import remarkGfm from "remark-gfm";
|
||||||
import remarkMath from "remark-math";
|
import remarkMath from "remark-math";
|
||||||
import { SettingsCheckBox } from "../components/settingsInputs";
|
import {
|
||||||
|
SettingsCheckBox,
|
||||||
|
SettingsPlaceholder,
|
||||||
|
} from "../components/settingsInputs";
|
||||||
import { inputStyle } from "../components/styles";
|
import { inputStyle } from "../components/styles";
|
||||||
|
import { Complete } from "../components/openai";
|
||||||
|
|
||||||
function CreateNote() {
|
function CreateNote() {
|
||||||
const [preview, setPreview] = useState(false);
|
const [preview, setPreview] = useState(false);
|
||||||
|
@ -126,6 +133,23 @@ function CreateNote() {
|
||||||
className="m-1"
|
className="m-1"
|
||||||
/>
|
/>
|
||||||
</div>
|
</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>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -2,6 +2,7 @@ import {
|
||||||
SettingsCheckBox,
|
SettingsCheckBox,
|
||||||
SettingsTextInput,
|
SettingsTextInput,
|
||||||
SettingsSelectInput,
|
SettingsSelectInput,
|
||||||
|
SettingsPlaceholder,
|
||||||
} from "../components/settingsInputs";
|
} from "../components/settingsInputs";
|
||||||
import { reRenderPage } from "../components/utils";
|
import { reRenderPage } from "../components/utils";
|
||||||
import Locales from "../localisation/main";
|
import Locales from "../localisation/main";
|
||||||
|
@ -38,17 +39,20 @@ function Settings() {
|
||||||
<SettingsCheckBox
|
<SettingsCheckBox
|
||||||
label={locals.EditPreview}
|
label={locals.EditPreview}
|
||||||
title={locals.EditPreviewWarn}
|
title={locals.EditPreviewWarn}
|
||||||
checked={settings.editPreview}
|
|
||||||
settingName="editPreview"
|
settingName="editPreview"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<SettingsCheckBox
|
<SettingsCheckBox
|
||||||
label={locals.PublicNote}
|
label={locals.PublicNote}
|
||||||
title={locals.PublicNoteTitle}
|
title={locals.PublicNoteTitle}
|
||||||
checked={settings.publicNote}
|
|
||||||
settingName="publicNote"
|
settingName="publicNote"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<SettingsCheckBox
|
||||||
|
label={locals.AdditionalFeatures}
|
||||||
|
settingName="additionalFeatures"
|
||||||
|
/>
|
||||||
|
|
||||||
<SettingsPlaceholder text={locals.Interface} />
|
<SettingsPlaceholder text={locals.Interface} />
|
||||||
|
|
||||||
<SettingsSelectInput
|
<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;
|
export default Settings;
|
||||||
|
|
Loading…
Reference in a new issue