mirror of
https://github.com/deiucanta/chatpad.git
synced 2026-03-11 09:04:31 +00:00
merged add config options
This commit is contained in:
parent
61f15eb82e
commit
e60272eefa
15 changed files with 5804 additions and 327 deletions
4
.parcelrc
Normal file
4
.parcelrc
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
{
|
||||||
|
"extends": ["@parcel/config-default"],
|
||||||
|
"reporters": ["...", "parcel-reporter-static-files-copy"]
|
||||||
|
}
|
||||||
|
|
@ -24,7 +24,13 @@ Crafted with love and care to provide the best experience possible.
|
||||||
## Self-host using Docker
|
## Self-host using Docker
|
||||||
|
|
||||||
```
|
```
|
||||||
docker run --name chatpad -d -p 1234:80 ghcr.io/deiucanta/chatpad:latest
|
docker run --name chatpad -d -p 8080:80 ghcr.io/deiucanta/chatpad:latest
|
||||||
|
```
|
||||||
|
|
||||||
|
## Self-host using Docker with custom config
|
||||||
|
|
||||||
|
```
|
||||||
|
docker run --name chatpad -d -v `pwd`/config.json:/usr/share/nginx/html/config.json -p 8080:80 ghcr.io/deiucanta/chatpad:latest
|
||||||
```
|
```
|
||||||
|
|
||||||
## One click Deployments
|
## One click Deployments
|
||||||
|
|
|
||||||
23
package-lock.json
generated
23
package-lock.json
generated
|
|
@ -44,7 +44,7 @@
|
||||||
"@types/react-dom": "^18.0.11",
|
"@types/react-dom": "^18.0.11",
|
||||||
"buffer": "^5.7.1",
|
"buffer": "^5.7.1",
|
||||||
"parcel": "^2.8.3",
|
"parcel": "^2.8.3",
|
||||||
"path-browserify": "^1.0.1",
|
"parcel-reporter-static-files-copy": "^1.5.0",
|
||||||
"process": "^0.11.10"
|
"process": "^0.11.10"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -6776,6 +6776,18 @@
|
||||||
"url": "https://opencollective.com/parcel"
|
"url": "https://opencollective.com/parcel"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/parcel-reporter-static-files-copy": {
|
||||||
|
"version": "1.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/parcel-reporter-static-files-copy/-/parcel-reporter-static-files-copy-1.5.0.tgz",
|
||||||
|
"integrity": "sha512-dsY3MQkbYSgEqS0/22vtD2mZtel8UC0ItH0ok8LmgFeCMTsdhyOtJgvt945ODIzu9lYc/sCIzksM8C77uSE3Fg==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@parcel/plugin": "^2.0.0-beta.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"parcel": "^2.0.0-beta.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/parent-module": {
|
"node_modules/parent-module": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
|
||||||
|
|
@ -12924,6 +12936,15 @@
|
||||||
"v8-compile-cache": "^2.0.0"
|
"v8-compile-cache": "^2.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"parcel-reporter-static-files-copy": {
|
||||||
|
"version": "1.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/parcel-reporter-static-files-copy/-/parcel-reporter-static-files-copy-1.5.0.tgz",
|
||||||
|
"integrity": "sha512-dsY3MQkbYSgEqS0/22vtD2mZtel8UC0ItH0ok8LmgFeCMTsdhyOtJgvt945ODIzu9lYc/sCIzksM8C77uSE3Fg==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@parcel/plugin": "^2.0.0-beta.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"parent-module": {
|
"parent-module": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,9 @@
|
||||||
"start": "parcel",
|
"start": "parcel",
|
||||||
"build": "parcel build"
|
"build": "parcel build"
|
||||||
},
|
},
|
||||||
|
"staticFiles": {
|
||||||
|
"staticPath": "src/static"
|
||||||
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@parcel/transformer-sass": "^2.8.3",
|
"@parcel/transformer-sass": "^2.8.3",
|
||||||
"@types/downloadjs": "^1.4.3",
|
"@types/downloadjs": "^1.4.3",
|
||||||
|
|
@ -16,6 +19,7 @@
|
||||||
"buffer": "^5.7.1",
|
"buffer": "^5.7.1",
|
||||||
"parcel": "^2.8.3",
|
"parcel": "^2.8.3",
|
||||||
"path-browserify": "^1.0.1",
|
"path-browserify": "^1.0.1",
|
||||||
|
"parcel-reporter-static-files-copy": "^1.5.0",
|
||||||
"process": "^0.11.10"
|
"process": "^0.11.10"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|
|
||||||
|
|
@ -40,6 +40,7 @@ import { DatabaseModal } from "./DatabaseModal";
|
||||||
import { LogoText } from "./Logo";
|
import { LogoText } from "./Logo";
|
||||||
import { Prompts } from "./Prompts";
|
import { Prompts } from "./Prompts";
|
||||||
import { SettingsModal } from "./SettingsModal";
|
import { SettingsModal } from "./SettingsModal";
|
||||||
|
import { config } from "../utils/config";
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface Window {
|
interface Window {
|
||||||
|
|
@ -186,35 +187,41 @@ export function Layout() {
|
||||||
</Navbar.Section>
|
</Navbar.Section>
|
||||||
<Navbar.Section sx={{ borderTop: border }} p="xs">
|
<Navbar.Section sx={{ borderTop: border }} p="xs">
|
||||||
<Center>
|
<Center>
|
||||||
<Tooltip
|
{config.allowDarkModeToggle && (
|
||||||
label={colorScheme === "dark" ? "Light Mode" : "Dark Mode"}
|
<Tooltip
|
||||||
>
|
label={colorScheme === "dark" ? "Light Mode" : "Dark Mode"}
|
||||||
<ActionIcon
|
|
||||||
sx={{ flex: 1 }}
|
|
||||||
size="xl"
|
|
||||||
onClick={() => toggleColorScheme()}
|
|
||||||
>
|
>
|
||||||
{colorScheme === "dark" ? (
|
<ActionIcon
|
||||||
<IconSunHigh size={20} />
|
sx={{ flex: 1 }}
|
||||||
) : (
|
size="xl"
|
||||||
<IconMoonStars size={20} />
|
onClick={() => toggleColorScheme()}
|
||||||
)}
|
>
|
||||||
</ActionIcon>
|
{colorScheme === "dark" ? (
|
||||||
</Tooltip>
|
<IconSunHigh size={20} />
|
||||||
<SettingsModal>
|
) : (
|
||||||
<Tooltip label="Settings">
|
<IconMoonStars size={20} />
|
||||||
<ActionIcon sx={{ flex: 1 }} size="xl">
|
)}
|
||||||
<IconSettings size={20} />
|
|
||||||
</ActionIcon>
|
</ActionIcon>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</SettingsModal>
|
)}
|
||||||
<DatabaseModal>
|
{config.allowSettingsModal && (
|
||||||
<Tooltip label="Database">
|
<SettingsModal>
|
||||||
<ActionIcon sx={{ flex: 1 }} size="xl">
|
<Tooltip label="Settings">
|
||||||
<IconDatabase size={20} />
|
<ActionIcon sx={{ flex: 1 }} size="xl">
|
||||||
</ActionIcon>
|
<IconSettings size={20} />
|
||||||
</Tooltip>
|
</ActionIcon>
|
||||||
</DatabaseModal>
|
</Tooltip>
|
||||||
|
</SettingsModal>
|
||||||
|
)}
|
||||||
|
{config.allowDatabaseModal && (
|
||||||
|
<DatabaseModal>
|
||||||
|
<Tooltip label="Database">
|
||||||
|
<ActionIcon sx={{ flex: 1 }} size="xl">
|
||||||
|
<IconDatabase size={20} />
|
||||||
|
</ActionIcon>
|
||||||
|
</Tooltip>
|
||||||
|
</DatabaseModal>
|
||||||
|
)}
|
||||||
<Tooltip label="Source Code">
|
<Tooltip label="Source Code">
|
||||||
<ActionIcon
|
<ActionIcon
|
||||||
component="a"
|
component="a"
|
||||||
|
|
@ -226,36 +233,40 @@ export function Layout() {
|
||||||
<IconBrandGithub size={20} />
|
<IconBrandGithub size={20} />
|
||||||
</ActionIcon>
|
</ActionIcon>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<Tooltip label="Follow on Twitter">
|
{config.showTwitterLink && (
|
||||||
<ActionIcon
|
<Tooltip label="Follow on Twitter">
|
||||||
component="a"
|
<ActionIcon
|
||||||
href="https://twitter.com/deiucanta"
|
component="a"
|
||||||
target="_blank"
|
href="https://twitter.com/deiucanta"
|
||||||
sx={{ flex: 1 }}
|
target="_blank"
|
||||||
size="xl"
|
sx={{ flex: 1 }}
|
||||||
>
|
size="xl"
|
||||||
<IconBrandTwitter size={20} />
|
>
|
||||||
</ActionIcon>
|
<IconBrandTwitter size={20} />
|
||||||
</Tooltip>
|
</ActionIcon>
|
||||||
<Tooltip label="Give Feedback">
|
</Tooltip>
|
||||||
<ActionIcon
|
)}
|
||||||
component="a"
|
{config.showFeedbackLink && (
|
||||||
href="https://feedback.chatpad.ai"
|
<Tooltip label="Give Feedback">
|
||||||
onClick={(event) => {
|
<ActionIcon
|
||||||
if (window.todesktop) {
|
component="a"
|
||||||
event.preventDefault();
|
href="https://feedback.chatpad.ai"
|
||||||
window.todesktop.contents.openUrlInBrowser(
|
onClick={(event) => {
|
||||||
"https://feedback.chatpad.ai"
|
if (window.todesktop) {
|
||||||
);
|
event.preventDefault();
|
||||||
}
|
window.todesktop.contents.openUrlInBrowser(
|
||||||
}}
|
"https://feedback.chatpad.ai"
|
||||||
target="_blank"
|
);
|
||||||
sx={{ flex: 1 }}
|
}
|
||||||
size="xl"
|
}}
|
||||||
>
|
target="_blank"
|
||||||
<IconMessage size={20} />
|
sx={{ flex: 1 }}
|
||||||
</ActionIcon>
|
size="xl"
|
||||||
</Tooltip>
|
>
|
||||||
|
<IconMessage size={20} />
|
||||||
|
</ActionIcon>
|
||||||
|
</Tooltip>
|
||||||
|
)}
|
||||||
</Center>
|
</Center>
|
||||||
</Navbar.Section>
|
</Navbar.Section>
|
||||||
</Navbar>
|
</Navbar>
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ import {
|
||||||
List,
|
List,
|
||||||
Modal,
|
Modal,
|
||||||
PasswordInput,
|
PasswordInput,
|
||||||
|
TextInput,
|
||||||
Select,
|
Select,
|
||||||
Stack,
|
Stack,
|
||||||
Text,
|
Text,
|
||||||
|
|
@ -15,7 +16,7 @@ import { notifications } from "@mantine/notifications";
|
||||||
import { useLiveQuery } from "dexie-react-hooks";
|
import { useLiveQuery } from "dexie-react-hooks";
|
||||||
import { cloneElement, ReactElement, useEffect, useState } from "react";
|
import { cloneElement, ReactElement, useEffect, useState } from "react";
|
||||||
import { db } from "../db";
|
import { db } from "../db";
|
||||||
import { availableModels, defaultModel } from "../utils/constants";
|
import { config } from "../utils/config";
|
||||||
import { checkOpenAIKey } from "../utils/openai";
|
import { checkOpenAIKey } from "../utils/openai";
|
||||||
|
|
||||||
export function SettingsModal({ children }: { children: ReactElement }) {
|
export function SettingsModal({ children }: { children: ReactElement }) {
|
||||||
|
|
@ -23,7 +24,11 @@ export function SettingsModal({ children }: { children: ReactElement }) {
|
||||||
const [submitting, setSubmitting] = useState(false);
|
const [submitting, setSubmitting] = useState(false);
|
||||||
|
|
||||||
const [value, setValue] = useState("");
|
const [value, setValue] = useState("");
|
||||||
const [model, setModel] = useState(defaultModel);
|
const [model, setModel] = useState(config.defaultModel);
|
||||||
|
const [type, setType] = useState(config.defaultType);
|
||||||
|
const [auth, setAuth] = useState(config.defaultAuth);
|
||||||
|
const [base, setBase] = useState("");
|
||||||
|
const [version, setVersion] = useState("");
|
||||||
|
|
||||||
const settings = useLiveQuery(async () => {
|
const settings = useLiveQuery(async () => {
|
||||||
return db.settings.where({ id: "general" }).first();
|
return db.settings.where({ id: "general" }).first();
|
||||||
|
|
@ -36,6 +41,18 @@ export function SettingsModal({ children }: { children: ReactElement }) {
|
||||||
if (settings?.openAiModel) {
|
if (settings?.openAiModel) {
|
||||||
setModel(settings.openAiModel);
|
setModel(settings.openAiModel);
|
||||||
}
|
}
|
||||||
|
if (settings?.openAiApiType) {
|
||||||
|
setType(settings.openAiApiType);
|
||||||
|
}
|
||||||
|
if (settings?.openAiApiAuth) {
|
||||||
|
setAuth(settings.openAiApiAuth);
|
||||||
|
}
|
||||||
|
if (settings?.openAiApiBase) {
|
||||||
|
setBase(settings.openAiApiBase);
|
||||||
|
}
|
||||||
|
if (settings?.openAiApiVersion) {
|
||||||
|
setVersion(settings.openAiApiVersion);
|
||||||
|
}
|
||||||
}, [settings]);
|
}, [settings]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
@ -111,20 +128,213 @@ export function SettingsModal({ children }: { children: ReactElement }) {
|
||||||
</List.Item>
|
</List.Item>
|
||||||
</List>
|
</List>
|
||||||
<Select
|
<Select
|
||||||
label="OpenAI Model"
|
label="OpenAI Type"
|
||||||
value={model}
|
value={type}
|
||||||
onChange={(value) => {
|
onChange={async (value) => {
|
||||||
db.settings.update("general", {
|
setSubmitting(true);
|
||||||
openAiModel: value ?? undefined,
|
try {
|
||||||
});
|
await db.settings.update("general", {
|
||||||
|
openAiApiType: value ?? 'openai',
|
||||||
|
});
|
||||||
|
notifications.show({
|
||||||
|
title: "Saved",
|
||||||
|
message: "Your OpenAI Type has been saved.",
|
||||||
|
});
|
||||||
|
} catch (error: any) {
|
||||||
|
if (error.toJSON().message === "Network Error") {
|
||||||
|
notifications.show({
|
||||||
|
title: "Error",
|
||||||
|
color: "red",
|
||||||
|
message: "No internet connection.",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
const message = error.response?.data?.error?.message;
|
||||||
|
if (message) {
|
||||||
|
notifications.show({
|
||||||
|
title: "Error",
|
||||||
|
color: "red",
|
||||||
|
message,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
setSubmitting(false);
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
withinPortal
|
withinPortal
|
||||||
data={availableModels}
|
data={[{ "value": "openai", "label": "OpenAI"}, { "value": "custom", "label": "Custom (e.g. Azure OpenAI)"}]}
|
||||||
|
/>
|
||||||
|
<Select
|
||||||
|
label="OpenAI Model (OpenAI Only)"
|
||||||
|
value={model}
|
||||||
|
onChange={async (value) => {
|
||||||
|
setSubmitting(true);
|
||||||
|
try {
|
||||||
|
await db.settings.update("general", {
|
||||||
|
openAiModel: value ?? undefined,
|
||||||
|
});
|
||||||
|
notifications.show({
|
||||||
|
title: "Saved",
|
||||||
|
message: "Your OpenAI Model has been saved.",
|
||||||
|
});
|
||||||
|
} catch (error: any) {
|
||||||
|
if (error.toJSON().message === "Network Error") {
|
||||||
|
notifications.show({
|
||||||
|
title: "Error",
|
||||||
|
color: "red",
|
||||||
|
message: "No internet connection.",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
const message = error.response?.data?.error?.message;
|
||||||
|
if (message) {
|
||||||
|
notifications.show({
|
||||||
|
title: "Error",
|
||||||
|
color: "red",
|
||||||
|
message,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
setSubmitting(false);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
withinPortal
|
||||||
|
data={config.availableModels}
|
||||||
/>
|
/>
|
||||||
<Alert color="orange" title="Warning">
|
<Alert color="orange" title="Warning">
|
||||||
The displayed cost was not updated yet to reflect the costs for each
|
The displayed cost was not updated yet to reflect the costs for each
|
||||||
model. Right now it will always show the cost for GPT-3.5.
|
model. Right now it will always show the cost for GPT-3.5 on OpenAI.
|
||||||
</Alert>
|
</Alert>
|
||||||
|
<Select
|
||||||
|
label="OpenAI Auth (Custom Only)"
|
||||||
|
value={auth}
|
||||||
|
onChange={async (value) => {
|
||||||
|
setSubmitting(true);
|
||||||
|
try {
|
||||||
|
await db.settings.update("general", {
|
||||||
|
openAiApiAuth: value ?? 'none',
|
||||||
|
});
|
||||||
|
notifications.show({
|
||||||
|
title: "Saved",
|
||||||
|
message: "Your OpenAI Auth has been saved.",
|
||||||
|
});
|
||||||
|
} catch (error: any) {
|
||||||
|
if (error.toJSON().message === "Network Error") {
|
||||||
|
notifications.show({
|
||||||
|
title: "Error",
|
||||||
|
color: "red",
|
||||||
|
message: "No internet connection.",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
const message = error.response?.data?.error?.message;
|
||||||
|
if (message) {
|
||||||
|
notifications.show({
|
||||||
|
title: "Error",
|
||||||
|
color: "red",
|
||||||
|
message,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
setSubmitting(false);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
withinPortal
|
||||||
|
data={[{ "value": "none", "label": "None"}, { "value": "bearer-token", "label": "Bearer Token"}, { "value": "api-key", "label": "API Key"}]}
|
||||||
|
/>
|
||||||
|
<form
|
||||||
|
onSubmit={async (event) => {
|
||||||
|
try {
|
||||||
|
setSubmitting(true);
|
||||||
|
event.preventDefault();
|
||||||
|
await db.settings.where({ id: "general" }).modify((row) => {
|
||||||
|
row.openAiApiBase = base;
|
||||||
|
console.log(row);
|
||||||
|
});
|
||||||
|
notifications.show({
|
||||||
|
title: "Saved",
|
||||||
|
message: "Your OpenAI Base has been saved.",
|
||||||
|
});
|
||||||
|
} catch (error: any) {
|
||||||
|
if (error.toJSON().message === "Network Error") {
|
||||||
|
notifications.show({
|
||||||
|
title: "Error",
|
||||||
|
color: "red",
|
||||||
|
message: "No internet connection.",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
const message = error.response?.data?.error?.message;
|
||||||
|
if (message) {
|
||||||
|
notifications.show({
|
||||||
|
title: "Error",
|
||||||
|
color: "red",
|
||||||
|
message,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
setSubmitting(false);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Flex gap="xs" align="end">
|
||||||
|
<TextInput
|
||||||
|
label="OpenAI API Base (Custom Only)"
|
||||||
|
placeholder="https://<resource-name>.openai.azure.com/openai/deployments/<deployment>"
|
||||||
|
sx={{ flex: 1 }}
|
||||||
|
value={base}
|
||||||
|
onChange={(event) => setBase(event.currentTarget.value)}
|
||||||
|
formNoValidate
|
||||||
|
/>
|
||||||
|
<Button type="submit" loading={submitting}>
|
||||||
|
Save
|
||||||
|
</Button>
|
||||||
|
</Flex>
|
||||||
|
</form>
|
||||||
|
<form
|
||||||
|
onSubmit={async (event) => {
|
||||||
|
try {
|
||||||
|
setSubmitting(true);
|
||||||
|
event.preventDefault();
|
||||||
|
await db.settings.where({ id: "general" }).modify((row) => {
|
||||||
|
row.openAiApiVersion = version;
|
||||||
|
console.log(row);
|
||||||
|
});
|
||||||
|
notifications.show({
|
||||||
|
title: "Saved",
|
||||||
|
message: "Your OpenAI Version has been saved.",
|
||||||
|
});
|
||||||
|
} catch (error: any) {
|
||||||
|
if (error.toJSON().message === "Network Error") {
|
||||||
|
notifications.show({
|
||||||
|
title: "Error",
|
||||||
|
color: "red",
|
||||||
|
message: "No internet connection.",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
const message = error.response?.data?.error?.message;
|
||||||
|
if (message) {
|
||||||
|
notifications.show({
|
||||||
|
title: "Error",
|
||||||
|
color: "red",
|
||||||
|
message,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
setSubmitting(false);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Flex gap="xs" align="end">
|
||||||
|
<TextInput
|
||||||
|
label="OpenAI API Version (Custom Only)"
|
||||||
|
placeholder="2023-03-15-preview"
|
||||||
|
sx={{ flex: 1 }}
|
||||||
|
value={version}
|
||||||
|
onChange={(event) => setVersion(event.currentTarget.value)}
|
||||||
|
formNoValidate
|
||||||
|
/>
|
||||||
|
<Button type="submit" loading={submitting}>
|
||||||
|
Save
|
||||||
|
</Button>
|
||||||
|
</Flex>
|
||||||
|
</form>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Modal>
|
</Modal>
|
||||||
</>
|
</>
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import Dexie, { Table } from "dexie";
|
import Dexie, { Table } from "dexie";
|
||||||
import "dexie-export-import";
|
import "dexie-export-import";
|
||||||
|
import { config } from "../utils/config";
|
||||||
|
|
||||||
export interface Chat {
|
export interface Chat {
|
||||||
id: string;
|
id: string;
|
||||||
|
|
@ -27,6 +28,10 @@ export interface Settings {
|
||||||
id: "general";
|
id: "general";
|
||||||
openAiApiKey?: string;
|
openAiApiKey?: string;
|
||||||
openAiModel?: string;
|
openAiModel?: string;
|
||||||
|
openAiApiType?: 'openai' | 'custom';
|
||||||
|
openAiApiAuth?: 'none' | 'bearer-token' | 'api-key';
|
||||||
|
openAiApiBase?: string;
|
||||||
|
openAiApiVersion?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Database extends Dexie {
|
export class Database extends Dexie {
|
||||||
|
|
@ -47,6 +52,12 @@ export class Database extends Dexie {
|
||||||
this.on("populate", async () => {
|
this.on("populate", async () => {
|
||||||
db.settings.add({
|
db.settings.add({
|
||||||
id: "general",
|
id: "general",
|
||||||
|
openAiModel: config.defaultModel,
|
||||||
|
openAiApiType: config.defaultType,
|
||||||
|
openAiApiAuth: config.defaultAuth,
|
||||||
|
...(config.defaultKey != '' && { openAiApiKey: config.defaultKey }),
|
||||||
|
...(config.defaultBase != '' && { openAiApiBase: config.defaultBase }),
|
||||||
|
...(config.defaultVersion != '' && { openAiApiVersion: config.defaultVersion }),
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,10 @@
|
||||||
import { createRoot } from "react-dom/client";
|
import React from "react";
|
||||||
|
import ReactDOM from "react-dom";
|
||||||
import { App } from "./components/App";
|
import { App } from "./components/App";
|
||||||
|
import { loadConfig } from "./utils/config";
|
||||||
|
|
||||||
const container = document.getElementById("app");
|
loadConfig().then(() => {
|
||||||
const root = createRoot(container!);
|
const container = document.getElementById("app");
|
||||||
root.render(<App />);
|
const root = ReactDOM.createRoot(container!);
|
||||||
|
root.render(<App />);
|
||||||
|
});
|
||||||
|
|
|
||||||
|
|
@ -19,13 +19,11 @@ import { AiOutlineSend } from "react-icons/ai";
|
||||||
import { MessageItem } from "../components/MessageItem";
|
import { MessageItem } from "../components/MessageItem";
|
||||||
import { db } from "../db";
|
import { db } from "../db";
|
||||||
import { useChatId } from "../hooks/useChatId";
|
import { useChatId } from "../hooks/useChatId";
|
||||||
|
import { config } from "../utils/config";
|
||||||
import {
|
import {
|
||||||
writingCharacters,
|
createChatCompletion,
|
||||||
writingFormats,
|
createStreamChatCompletion,
|
||||||
writingStyles,
|
} from "../utils/openai";
|
||||||
writingTones,
|
|
||||||
} from "../utils/constants";
|
|
||||||
import { createChatCompletion, createStreamChatCompletion } from "../utils/openai";
|
|
||||||
|
|
||||||
export function ChatRoute() {
|
export function ChatRoute() {
|
||||||
const chatId = useChatId();
|
const chatId = useChatId();
|
||||||
|
|
@ -101,7 +99,7 @@ export function ChatRoute() {
|
||||||
});
|
});
|
||||||
setContent("");
|
setContent("");
|
||||||
|
|
||||||
const messageId = nanoid()
|
const messageId = nanoid();
|
||||||
await db.messages.add({
|
await db.messages.add({
|
||||||
id: messageId,
|
id: messageId,
|
||||||
chatId,
|
chatId,
|
||||||
|
|
@ -110,17 +108,22 @@ export function ChatRoute() {
|
||||||
createdAt: new Date(),
|
createdAt: new Date(),
|
||||||
});
|
});
|
||||||
|
|
||||||
await createStreamChatCompletion(apiKey, [
|
await createStreamChatCompletion(
|
||||||
{
|
apiKey,
|
||||||
role: "system",
|
[
|
||||||
content: getSystemMessage(),
|
{
|
||||||
},
|
role: "system",
|
||||||
...(messages ?? []).map((message) => ({
|
content: getSystemMessage(),
|
||||||
role: message.role,
|
},
|
||||||
content: message.content,
|
...(messages ?? []).map((message) => ({
|
||||||
})),
|
role: message.role,
|
||||||
{ role: "user", content },
|
content: message.content,
|
||||||
], chatId, messageId);
|
})),
|
||||||
|
{ role: "user", content },
|
||||||
|
],
|
||||||
|
chatId,
|
||||||
|
messageId
|
||||||
|
);
|
||||||
|
|
||||||
setSubmitting(false);
|
setSubmitting(false);
|
||||||
|
|
||||||
|
|
@ -260,7 +263,7 @@ export function ChatRoute() {
|
||||||
<Select
|
<Select
|
||||||
value={writingCharacter}
|
value={writingCharacter}
|
||||||
onChange={setWritingCharacter}
|
onChange={setWritingCharacter}
|
||||||
data={writingCharacters}
|
data={config.writingCharacters}
|
||||||
placeholder="Character"
|
placeholder="Character"
|
||||||
variant="filled"
|
variant="filled"
|
||||||
searchable
|
searchable
|
||||||
|
|
@ -270,7 +273,7 @@ export function ChatRoute() {
|
||||||
<Select
|
<Select
|
||||||
value={writingTone}
|
value={writingTone}
|
||||||
onChange={setWritingTone}
|
onChange={setWritingTone}
|
||||||
data={writingTones}
|
data={config.writingTones}
|
||||||
placeholder="Tone"
|
placeholder="Tone"
|
||||||
variant="filled"
|
variant="filled"
|
||||||
searchable
|
searchable
|
||||||
|
|
@ -280,7 +283,7 @@ export function ChatRoute() {
|
||||||
<Select
|
<Select
|
||||||
value={writingStyle}
|
value={writingStyle}
|
||||||
onChange={setWritingStyle}
|
onChange={setWritingStyle}
|
||||||
data={writingStyles}
|
data={config.writingStyles}
|
||||||
placeholder="Style"
|
placeholder="Style"
|
||||||
variant="filled"
|
variant="filled"
|
||||||
searchable
|
searchable
|
||||||
|
|
@ -290,7 +293,7 @@ export function ChatRoute() {
|
||||||
<Select
|
<Select
|
||||||
value={writingFormat}
|
value={writingFormat}
|
||||||
onChange={setWritingFormat}
|
onChange={setWritingFormat}
|
||||||
data={writingFormats}
|
data={config.writingFormats}
|
||||||
placeholder="Format"
|
placeholder="Format"
|
||||||
variant="filled"
|
variant="filled"
|
||||||
searchable
|
searchable
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ import { useLiveQuery } from "dexie-react-hooks";
|
||||||
import { Logo } from "../components/Logo";
|
import { Logo } from "../components/Logo";
|
||||||
import { SettingsModal } from "../components/SettingsModal";
|
import { SettingsModal } from "../components/SettingsModal";
|
||||||
import { db } from "../db";
|
import { db } from "../db";
|
||||||
|
import { config } from "../utils/config";
|
||||||
|
|
||||||
export function IndexRoute() {
|
export function IndexRoute() {
|
||||||
const settings = useLiveQuery(() => db.settings.get("general"));
|
const settings = useLiveQuery(() => db.settings.get("general"));
|
||||||
|
|
@ -56,16 +57,18 @@ export function IndexRoute() {
|
||||||
))}
|
))}
|
||||||
</SimpleGrid>
|
</SimpleGrid>
|
||||||
<Group mt={50}>
|
<Group mt={50}>
|
||||||
<SettingsModal>
|
{config.allowSettingsModal && (
|
||||||
<Button
|
<SettingsModal>
|
||||||
size="md"
|
<Button
|
||||||
variant={openAiApiKey ? "light" : "filled"}
|
size="md"
|
||||||
leftIcon={<IconKey size={20} />}
|
variant={openAiApiKey ? "light" : "filled"}
|
||||||
>
|
leftIcon={<IconKey size={20} />}
|
||||||
{openAiApiKey ? "Change OpenAI Key" : "Enter OpenAI Key"}
|
>
|
||||||
</Button>
|
{openAiApiKey ? "Change OpenAI Key" : "Enter OpenAI Key"}
|
||||||
</SettingsModal>
|
</Button>
|
||||||
{!window.todesktop && (
|
</SettingsModal>
|
||||||
|
)}
|
||||||
|
{config.showDownloadLink && !window.todesktop && (
|
||||||
<Button
|
<Button
|
||||||
component="a"
|
component="a"
|
||||||
href="https://dl.todesktop.com/230313oyppkw40a"
|
href="https://dl.todesktop.com/230313oyppkw40a"
|
||||||
|
|
|
||||||
207
src/static/config.json
Normal file
207
src/static/config.json
Normal file
|
|
@ -0,0 +1,207 @@
|
||||||
|
{
|
||||||
|
"defaultModel": "gpt-3.5-turbo",
|
||||||
|
"defaultType": "openai",
|
||||||
|
"defaultAuth": "api-key",
|
||||||
|
"defaultBase": "",
|
||||||
|
"defaultVersion": "",
|
||||||
|
"defaultKey": "",
|
||||||
|
"availableModels": [
|
||||||
|
{
|
||||||
|
"value": "gpt-3.5-turbo",
|
||||||
|
"label": "GPT-3.5-TURBO (Default ChatGPT)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"value": "gpt-3.5-turbo-0301",
|
||||||
|
"label": "GPT-3.5-TURBO-0301"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"value": "gpt-4",
|
||||||
|
"label": "GPT-4 (Limited Beta)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"value": "gpt-4-0314",
|
||||||
|
"label": "GPT-4-0314 (Limited Beta)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"value": "gpt-4-32k",
|
||||||
|
"label": "GPT-4-32K (Limited Beta)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"value": "gpt-4-32k-0314",
|
||||||
|
"label": "GPT-4-32K-0314 (Limited Beta)"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"writingCharacters": [
|
||||||
|
{
|
||||||
|
"label": "Standup Comedian",
|
||||||
|
"value": "A performer who entertains audiences by telling jokes and humorous stories."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Life Coach",
|
||||||
|
"value": "A professional who helps individuals identify and achieve their personal and professional goals."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Career Counselor",
|
||||||
|
"value": "A professional who helps individuals explore and choose careers, develop job search strategies, and improve job performance."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Nutritionist",
|
||||||
|
"value": "A health professional who specializes in the study of nutrition and its effects on the body."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Product Manager",
|
||||||
|
"value": "A professional who oversees the development and marketing of a company's products."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Personal Trainer",
|
||||||
|
"value": "A fitness professional who works with individuals to develop personalized exercise programs and improve their overall health and fitness."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Life Hacker",
|
||||||
|
"value": "A person who uses unconventional methods to solve problems and increase productivity in everyday life."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Travel Advisor",
|
||||||
|
"value": "A professional who helps individuals plan and book travel arrangements."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Mindfulness Coach",
|
||||||
|
"value": "A professional who helps individuals develop mindfulness practices to reduce stress and improve well-being."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Financial Advisor",
|
||||||
|
"value": "A professional who provides guidance and advice on financial planning, investment strategies, and retirement planning."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Language Tutor",
|
||||||
|
"value": "A teacher who helps individuals learn and improve their language skills."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Travel Guide",
|
||||||
|
"value": "A professional who leads tours and provides information about local attractions and culture."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Marketing Expert",
|
||||||
|
"value": "A professional who develops and implements marketing strategies to promote products and services."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Software Developer",
|
||||||
|
"value": "A professional who designs, develops, and maintains software applications and systems."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Dating Coach",
|
||||||
|
"value": "A professional who helps individuals improve their dating and relationship skills."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "DIY Expert",
|
||||||
|
"value": "A person who is skilled at completing a wide range of do-it-yourself projects around the home."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Journalist",
|
||||||
|
"value": "A professional who investigates and reports on current events and news stories."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Tech Writer",
|
||||||
|
"value": "A professional who writes about technology and related topics for a variety of audiences."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Professional Chef",
|
||||||
|
"value": "A skilled culinary professional who prepares meals and manages kitchen operations."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Professional Salesperson",
|
||||||
|
"value": "A professional who sells products and services to businesses and consumers."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Startup Tech Lawyer",
|
||||||
|
"value": "A legal professional who specializes in providing legal advice and services to startup technology companies."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Graphic Designer",
|
||||||
|
"value": "A professional who designs visual materials such as logos, brochures, and websites."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Academic Researcher",
|
||||||
|
"value": "A professional who conducts research and produces scholarly work in a particular academic field."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Customer Support Agent",
|
||||||
|
"value": "A professional who provides assistance and support to customers who have questions or problems with a company's products or services."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "HR Consultant",
|
||||||
|
"value": "A professional who provides guidance and advice to organizations on human resource management and strategy"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"writingTones": [
|
||||||
|
"Assertive",
|
||||||
|
"Authoritative",
|
||||||
|
"Casual",
|
||||||
|
"Confident",
|
||||||
|
"Condescending",
|
||||||
|
"Conversational",
|
||||||
|
"Diplomatic",
|
||||||
|
"Direct",
|
||||||
|
"Eloquent",
|
||||||
|
"Formal",
|
||||||
|
"Friendly",
|
||||||
|
"Humorous",
|
||||||
|
"Informative",
|
||||||
|
"Inspiring",
|
||||||
|
"Intense",
|
||||||
|
"Irritable",
|
||||||
|
"Joking",
|
||||||
|
"Polite",
|
||||||
|
"Sarcastic",
|
||||||
|
"Sincere",
|
||||||
|
"Soothing",
|
||||||
|
"Stern",
|
||||||
|
"Sympathetic",
|
||||||
|
"Tactful",
|
||||||
|
"Witty"
|
||||||
|
],
|
||||||
|
"writingStyles": [
|
||||||
|
"Academic",
|
||||||
|
"Analytical",
|
||||||
|
"Argumentative",
|
||||||
|
"Conversational",
|
||||||
|
"Creative",
|
||||||
|
"Critical",
|
||||||
|
"Descriptive",
|
||||||
|
"Explanatory",
|
||||||
|
"Informative",
|
||||||
|
"Instructive",
|
||||||
|
"Investigative",
|
||||||
|
"Journalistic",
|
||||||
|
"Metaphorical",
|
||||||
|
"Narrative",
|
||||||
|
"Persuasive",
|
||||||
|
"Poetic",
|
||||||
|
"Satirical",
|
||||||
|
"Technical"
|
||||||
|
],
|
||||||
|
"writingFormats": [
|
||||||
|
{
|
||||||
|
"value": "Answer as concise as possible",
|
||||||
|
"label": "Concise"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"value": "Think step-by-step",
|
||||||
|
"label": "Step-by-step"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"value": "Answer in painstakingly detail",
|
||||||
|
"label": "Extreme Detail"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"value": "Explain like I'm five",
|
||||||
|
"label": "Explain Like I'm Five"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"showDownloadLink": true,
|
||||||
|
"allowDarkModeToggle": true,
|
||||||
|
"allowSettingsModal": true,
|
||||||
|
"allowDatabaseModal": true,
|
||||||
|
"showTwitterLink": true,
|
||||||
|
"showFeedbackLink": true
|
||||||
|
}
|
||||||
42
src/utils/config.ts
Normal file
42
src/utils/config.ts
Normal file
|
|
@ -0,0 +1,42 @@
|
||||||
|
interface Config {
|
||||||
|
defaultModel: AvailableModel["value"];
|
||||||
|
defaultType: 'openai' | 'custom';
|
||||||
|
defaultAuth: 'none' | 'bearer-token' | 'api-key';
|
||||||
|
defaultBase: string;
|
||||||
|
defaultVersion: string;
|
||||||
|
defaultKey: string;
|
||||||
|
availableModels: AvailableModel[];
|
||||||
|
writingCharacters: WritingCharacter[];
|
||||||
|
writingTones: string[];
|
||||||
|
writingStyles: string[];
|
||||||
|
writingFormats: WritingFormat[];
|
||||||
|
showDownloadLink: boolean;
|
||||||
|
allowDarkModeToggle: boolean;
|
||||||
|
allowSettingsModal: boolean;
|
||||||
|
allowDatabaseModal: boolean;
|
||||||
|
showTwitterLink: boolean;
|
||||||
|
showFeedbackLink: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface AvailableModel {
|
||||||
|
value: string;
|
||||||
|
label: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface WritingCharacter {
|
||||||
|
label: string;
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface WritingFormat {
|
||||||
|
value: string;
|
||||||
|
label: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export let config: Config;
|
||||||
|
|
||||||
|
export const loadConfig = async () => {
|
||||||
|
const response = await fetch("config.json");
|
||||||
|
config = await response.json();
|
||||||
|
return config;
|
||||||
|
};
|
||||||
|
|
@ -1,200 +0,0 @@
|
||||||
export const defaultModel = "gpt-3.5-turbo";
|
|
||||||
|
|
||||||
export const availableModels = [
|
|
||||||
{
|
|
||||||
value: "gpt-3.5-turbo",
|
|
||||||
label: "GPT-3.5-TURBO (Default ChatGPT)",
|
|
||||||
},
|
|
||||||
{ value: "gpt-3.5-turbo-0301", label: "GPT-3.5-TURBO-0301" },
|
|
||||||
{ value: "gpt-4", label: "GPT-4 (Limited Beta)" },
|
|
||||||
{ value: "gpt-4-0314", label: "GPT-4-0314 (Limited Beta)" },
|
|
||||||
{ value: "gpt-4-32k", label: "GPT-4-32K (Limited Beta)" },
|
|
||||||
{
|
|
||||||
value: "gpt-4-32k-0314",
|
|
||||||
label: "GPT-4-32K-0314 (Limited Beta)",
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
export const writingCharacters = [
|
|
||||||
{
|
|
||||||
label: "Standup Comedian",
|
|
||||||
value:
|
|
||||||
"A performer who entertains audiences by telling jokes and humorous stories.",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Life Coach",
|
|
||||||
value:
|
|
||||||
"A professional who helps individuals identify and achieve their personal and professional goals.",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Career Counselor",
|
|
||||||
value:
|
|
||||||
"A professional who helps individuals explore and choose careers, develop job search strategies, and improve job performance.",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Nutritionist",
|
|
||||||
value:
|
|
||||||
"A health professional who specializes in the study of nutrition and its effects on the body.",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Product Manager",
|
|
||||||
value:
|
|
||||||
"A professional who oversees the development and marketing of a company's products.",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Personal Trainer",
|
|
||||||
value:
|
|
||||||
"A fitness professional who works with individuals to develop personalized exercise programs and improve their overall health and fitness.",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Life Hacker",
|
|
||||||
value:
|
|
||||||
"A person who uses unconventional methods to solve problems and increase productivity in everyday life.",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Travel Advisor",
|
|
||||||
value:
|
|
||||||
"A professional who helps individuals plan and book travel arrangements.",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Mindfulness Coach",
|
|
||||||
value:
|
|
||||||
"A professional who helps individuals develop mindfulness practices to reduce stress and improve well-being.",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Financial Advisor",
|
|
||||||
value:
|
|
||||||
"A professional who provides guidance and advice on financial planning, investment strategies, and retirement planning.",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Language Tutor",
|
|
||||||
value:
|
|
||||||
"A teacher who helps individuals learn and improve their language skills.",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Travel Guide",
|
|
||||||
value:
|
|
||||||
"A professional who leads tours and provides information about local attractions and culture.",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Marketing Expert",
|
|
||||||
value:
|
|
||||||
"A professional who develops and implements marketing strategies to promote products and services.",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Software Developer",
|
|
||||||
value:
|
|
||||||
"A professional who designs, develops, and maintains software applications and systems.",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Dating Coach",
|
|
||||||
value:
|
|
||||||
"A professional who helps individuals improve their dating and relationship skills.",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "DIY Expert",
|
|
||||||
value:
|
|
||||||
"A person who is skilled at completing a wide range of do-it-yourself projects around the home.",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Journalist",
|
|
||||||
value:
|
|
||||||
"A professional who investigates and reports on current events and news stories.",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Tech Writer",
|
|
||||||
value:
|
|
||||||
"A professional who writes about technology and related topics for a variety of audiences.",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Professional Chef",
|
|
||||||
value:
|
|
||||||
"A skilled culinary professional who prepares meals and manages kitchen operations.",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Professional Salesperson",
|
|
||||||
value:
|
|
||||||
"A professional who sells products and services to businesses and consumers.",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Startup Tech Lawyer",
|
|
||||||
value:
|
|
||||||
"A legal professional who specializes in providing legal advice and services to startup technology companies.",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Graphic Designer",
|
|
||||||
value:
|
|
||||||
"A professional who designs visual materials such as logos, brochures, and websites.",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Academic Researcher",
|
|
||||||
value:
|
|
||||||
"A professional who conducts research and produces scholarly work in a particular academic field.",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Customer Support Agent",
|
|
||||||
value:
|
|
||||||
"A professional who provides assistance and support to customers who have questions or problems with a company's products or services.",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "HR Consultant",
|
|
||||||
value:
|
|
||||||
"A professional who provides guidance and advice to organizations on human resource management and strategy",
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
export const writingTones = [
|
|
||||||
"Assertive",
|
|
||||||
"Authoritative",
|
|
||||||
"Casual",
|
|
||||||
"Confident",
|
|
||||||
"Condescending",
|
|
||||||
"Conversational",
|
|
||||||
"Diplomatic",
|
|
||||||
"Direct",
|
|
||||||
"Eloquent",
|
|
||||||
"Formal",
|
|
||||||
"Friendly",
|
|
||||||
"Humorous",
|
|
||||||
"Informative",
|
|
||||||
"Inspiring",
|
|
||||||
"Intense",
|
|
||||||
"Irritable",
|
|
||||||
"Joking",
|
|
||||||
"Polite",
|
|
||||||
"Sarcastic",
|
|
||||||
"Sincere",
|
|
||||||
"Soothing",
|
|
||||||
"Stern",
|
|
||||||
"Sympathetic",
|
|
||||||
"Tactful",
|
|
||||||
"Witty",
|
|
||||||
];
|
|
||||||
|
|
||||||
export const writingStyles = [
|
|
||||||
"Academic",
|
|
||||||
"Analytical",
|
|
||||||
"Argumentative",
|
|
||||||
"Conversational",
|
|
||||||
"Creative",
|
|
||||||
"Critical",
|
|
||||||
"Descriptive",
|
|
||||||
"Explanatory",
|
|
||||||
"Informative",
|
|
||||||
"Instructive",
|
|
||||||
"Investigative",
|
|
||||||
"Journalistic",
|
|
||||||
"Metaphorical",
|
|
||||||
"Narrative",
|
|
||||||
"Persuasive",
|
|
||||||
"Poetic",
|
|
||||||
"Satirical",
|
|
||||||
"Technical",
|
|
||||||
];
|
|
||||||
|
|
||||||
export const writingFormats = [
|
|
||||||
{ value: "Answer as concise as possible", label: "Concise" },
|
|
||||||
{ value: "Think step-by-step", label: "Step-by-step" },
|
|
||||||
{ value: "Answer in painstakingly detail", label: "Extreme Detail" },
|
|
||||||
{ value: "Explain like I'm five", label: "Explain Like I'm Five" },
|
|
||||||
];
|
|
||||||
|
|
@ -1,12 +1,21 @@
|
||||||
|
import { encode } from "gpt-token-utils";
|
||||||
import { ChatCompletionRequestMessage, Configuration, OpenAIApi } from "openai";
|
import { ChatCompletionRequestMessage, Configuration, OpenAIApi } from "openai";
|
||||||
import { db } from "../db";
|
|
||||||
import { defaultModel } from "./constants";
|
|
||||||
import { OpenAIExt } from "openai-ext";
|
import { OpenAIExt } from "openai-ext";
|
||||||
import { encode } from 'gpt-token-utils'
|
import { db } from "../db";
|
||||||
|
import { config } from "./config";
|
||||||
|
|
||||||
function getClient(apiKey: string) {
|
function getClient(
|
||||||
|
apiKey: string,
|
||||||
|
apiType: string,
|
||||||
|
apiAuth: string,
|
||||||
|
basePath: string
|
||||||
|
) {
|
||||||
const configuration = new Configuration({
|
const configuration = new Configuration({
|
||||||
apiKey,
|
...((apiType === "openai" ||
|
||||||
|
(apiType === "custom" && apiAuth === "bearer-token")) && {
|
||||||
|
apiKey: apiKey,
|
||||||
|
}),
|
||||||
|
...(apiType === "custom" && { basePath: basePath }),
|
||||||
});
|
});
|
||||||
return new OpenAIApi(configuration);
|
return new OpenAIApi(configuration);
|
||||||
}
|
}
|
||||||
|
|
@ -15,27 +24,26 @@ export async function createStreamChatCompletion(
|
||||||
apiKey: string,
|
apiKey: string,
|
||||||
messages: ChatCompletionRequestMessage[],
|
messages: ChatCompletionRequestMessage[],
|
||||||
chatId: string,
|
chatId: string,
|
||||||
messageId: string,
|
messageId: string
|
||||||
) {
|
) {
|
||||||
const settings = await db.settings.get("general");
|
const settings = await db.settings.get("general");
|
||||||
const model = settings?.openAiModel ?? defaultModel;
|
const model = settings?.openAiModel ?? config.defaultModel;
|
||||||
|
|
||||||
return OpenAIExt.streamClientChatCompletion(
|
return OpenAIExt.streamClientChatCompletion(
|
||||||
{
|
{
|
||||||
model,
|
model,
|
||||||
messages
|
messages,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
apiKey: apiKey,
|
apiKey: apiKey,
|
||||||
handler: {
|
handler: {
|
||||||
onContent(content, isFinal, stream) {
|
onContent(content, isFinal, stream) {
|
||||||
setStreamContent(messageId, content, isFinal);
|
setStreamContent(messageId, content, isFinal);
|
||||||
if(isFinal){
|
if (isFinal) {
|
||||||
setTotalTokens(chatId, content);
|
setTotalTokens(chatId, content);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onDone(stream) {
|
onDone(stream) {},
|
||||||
},
|
|
||||||
onError(error, stream) {
|
onError(error, stream) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
},
|
},
|
||||||
|
|
@ -44,12 +52,16 @@ export async function createStreamChatCompletion(
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function setStreamContent(messageId:string, content:string, isFinal:boolean){
|
function setStreamContent(
|
||||||
content = (isFinal ? content : content + "█")
|
messageId: string,
|
||||||
db.messages.update(messageId, {content: content})
|
content: string,
|
||||||
|
isFinal: boolean
|
||||||
|
) {
|
||||||
|
content = isFinal ? content : content + "█";
|
||||||
|
db.messages.update(messageId, { content: content });
|
||||||
}
|
}
|
||||||
|
|
||||||
function setTotalTokens(chatId:string, content:string){
|
function setTotalTokens(chatId: string, content: string) {
|
||||||
let total_tokens = encode(content).length;
|
let total_tokens = encode(content).length;
|
||||||
db.chats.where({ id: chatId }).modify((chat) => {
|
db.chats.where({ id: chatId }).modify((chat) => {
|
||||||
if (chat.totalTokens) {
|
if (chat.totalTokens) {
|
||||||
|
|
@ -65,14 +77,29 @@ export async function createChatCompletion(
|
||||||
messages: ChatCompletionRequestMessage[]
|
messages: ChatCompletionRequestMessage[]
|
||||||
) {
|
) {
|
||||||
const settings = await db.settings.get("general");
|
const settings = await db.settings.get("general");
|
||||||
const model = settings?.openAiModel ?? defaultModel;
|
const model = settings?.openAiModel ?? config.defaultModel;
|
||||||
|
const type = settings?.openAiApiType ?? config.defaultType;
|
||||||
|
const auth = settings?.openAiApiAuth ?? config.defaultAuth;
|
||||||
|
const base = settings?.openAiApiBase ?? config.defaultBase;
|
||||||
|
const version = settings?.openAiApiVersion ?? config.defaultVersion;
|
||||||
|
|
||||||
const client = getClient(apiKey);
|
const client = getClient(apiKey, type, auth, base);
|
||||||
return client.createChatCompletion({
|
return client.createChatCompletion(
|
||||||
model,
|
{
|
||||||
stream: false,
|
model,
|
||||||
messages,
|
stream: false,
|
||||||
});
|
messages,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
...(type === "custom" && auth === "api-key" && { "api-key": apiKey }),
|
||||||
|
},
|
||||||
|
params: {
|
||||||
|
...(type === "custom" && { "api-version": version }),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function checkOpenAIKey(apiKey: string) {
|
export async function checkOpenAIKey(apiKey: string) {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue