merged add config options

This commit is contained in:
serban-alexandru 2023-05-08 16:45:50 +03:00
parent 61f15eb82e
commit e60272eefa
15 changed files with 5804 additions and 327 deletions

4
.parcelrc Normal file
View file

@ -0,0 +1,4 @@
{
"extends": ["@parcel/config-default"],
"reporters": ["...", "parcel-reporter-static-files-copy"]
}

View file

@ -24,7 +24,13 @@ Crafted with love and care to provide the best experience possible.
## 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

23
package-lock.json generated
View file

@ -44,7 +44,7 @@
"@types/react-dom": "^18.0.11",
"buffer": "^5.7.1",
"parcel": "^2.8.3",
"path-browserify": "^1.0.1",
"parcel-reporter-static-files-copy": "^1.5.0",
"process": "^0.11.10"
}
},
@ -6776,6 +6776,18 @@
"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": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
@ -12924,6 +12936,15 @@
"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": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",

View file

@ -7,6 +7,9 @@
"start": "parcel",
"build": "parcel build"
},
"staticFiles": {
"staticPath": "src/static"
},
"devDependencies": {
"@parcel/transformer-sass": "^2.8.3",
"@types/downloadjs": "^1.4.3",
@ -16,6 +19,7 @@
"buffer": "^5.7.1",
"parcel": "^2.8.3",
"path-browserify": "^1.0.1",
"parcel-reporter-static-files-copy": "^1.5.0",
"process": "^0.11.10"
},
"dependencies": {

View file

@ -40,6 +40,7 @@ import { DatabaseModal } from "./DatabaseModal";
import { LogoText } from "./Logo";
import { Prompts } from "./Prompts";
import { SettingsModal } from "./SettingsModal";
import { config } from "../utils/config";
declare global {
interface Window {
@ -186,35 +187,41 @@ export function Layout() {
</Navbar.Section>
<Navbar.Section sx={{ borderTop: border }} p="xs">
<Center>
<Tooltip
label={colorScheme === "dark" ? "Light Mode" : "Dark Mode"}
>
<ActionIcon
sx={{ flex: 1 }}
size="xl"
onClick={() => toggleColorScheme()}
{config.allowDarkModeToggle && (
<Tooltip
label={colorScheme === "dark" ? "Light Mode" : "Dark Mode"}
>
{colorScheme === "dark" ? (
<IconSunHigh size={20} />
) : (
<IconMoonStars size={20} />
)}
</ActionIcon>
</Tooltip>
<SettingsModal>
<Tooltip label="Settings">
<ActionIcon sx={{ flex: 1 }} size="xl">
<IconSettings size={20} />
<ActionIcon
sx={{ flex: 1 }}
size="xl"
onClick={() => toggleColorScheme()}
>
{colorScheme === "dark" ? (
<IconSunHigh size={20} />
) : (
<IconMoonStars size={20} />
)}
</ActionIcon>
</Tooltip>
</SettingsModal>
<DatabaseModal>
<Tooltip label="Database">
<ActionIcon sx={{ flex: 1 }} size="xl">
<IconDatabase size={20} />
</ActionIcon>
</Tooltip>
</DatabaseModal>
)}
{config.allowSettingsModal && (
<SettingsModal>
<Tooltip label="Settings">
<ActionIcon sx={{ flex: 1 }} size="xl">
<IconSettings size={20} />
</ActionIcon>
</Tooltip>
</SettingsModal>
)}
{config.allowDatabaseModal && (
<DatabaseModal>
<Tooltip label="Database">
<ActionIcon sx={{ flex: 1 }} size="xl">
<IconDatabase size={20} />
</ActionIcon>
</Tooltip>
</DatabaseModal>
)}
<Tooltip label="Source Code">
<ActionIcon
component="a"
@ -226,36 +233,40 @@ export function Layout() {
<IconBrandGithub size={20} />
</ActionIcon>
</Tooltip>
<Tooltip label="Follow on Twitter">
<ActionIcon
component="a"
href="https://twitter.com/deiucanta"
target="_blank"
sx={{ flex: 1 }}
size="xl"
>
<IconBrandTwitter size={20} />
</ActionIcon>
</Tooltip>
<Tooltip label="Give Feedback">
<ActionIcon
component="a"
href="https://feedback.chatpad.ai"
onClick={(event) => {
if (window.todesktop) {
event.preventDefault();
window.todesktop.contents.openUrlInBrowser(
"https://feedback.chatpad.ai"
);
}
}}
target="_blank"
sx={{ flex: 1 }}
size="xl"
>
<IconMessage size={20} />
</ActionIcon>
</Tooltip>
{config.showTwitterLink && (
<Tooltip label="Follow on Twitter">
<ActionIcon
component="a"
href="https://twitter.com/deiucanta"
target="_blank"
sx={{ flex: 1 }}
size="xl"
>
<IconBrandTwitter size={20} />
</ActionIcon>
</Tooltip>
)}
{config.showFeedbackLink && (
<Tooltip label="Give Feedback">
<ActionIcon
component="a"
href="https://feedback.chatpad.ai"
onClick={(event) => {
if (window.todesktop) {
event.preventDefault();
window.todesktop.contents.openUrlInBrowser(
"https://feedback.chatpad.ai"
);
}
}}
target="_blank"
sx={{ flex: 1 }}
size="xl"
>
<IconMessage size={20} />
</ActionIcon>
</Tooltip>
)}
</Center>
</Navbar.Section>
</Navbar>

View file

@ -6,6 +6,7 @@ import {
List,
Modal,
PasswordInput,
TextInput,
Select,
Stack,
Text,
@ -15,7 +16,7 @@ import { notifications } from "@mantine/notifications";
import { useLiveQuery } from "dexie-react-hooks";
import { cloneElement, ReactElement, useEffect, useState } from "react";
import { db } from "../db";
import { availableModels, defaultModel } from "../utils/constants";
import { config } from "../utils/config";
import { checkOpenAIKey } from "../utils/openai";
export function SettingsModal({ children }: { children: ReactElement }) {
@ -23,7 +24,11 @@ export function SettingsModal({ children }: { children: ReactElement }) {
const [submitting, setSubmitting] = useState(false);
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 () => {
return db.settings.where({ id: "general" }).first();
@ -36,6 +41,18 @@ export function SettingsModal({ children }: { children: ReactElement }) {
if (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]);
return (
@ -111,20 +128,213 @@ export function SettingsModal({ children }: { children: ReactElement }) {
</List.Item>
</List>
<Select
label="OpenAI Model"
value={model}
onChange={(value) => {
db.settings.update("general", {
openAiModel: value ?? undefined,
});
label="OpenAI Type"
value={type}
onChange={async (value) => {
setSubmitting(true);
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
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">
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>
<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>
</Modal>
</>

View file

@ -1,5 +1,6 @@
import Dexie, { Table } from "dexie";
import "dexie-export-import";
import { config } from "../utils/config";
export interface Chat {
id: string;
@ -27,6 +28,10 @@ export interface Settings {
id: "general";
openAiApiKey?: string;
openAiModel?: string;
openAiApiType?: 'openai' | 'custom';
openAiApiAuth?: 'none' | 'bearer-token' | 'api-key';
openAiApiBase?: string;
openAiApiVersion?: string;
}
export class Database extends Dexie {
@ -47,6 +52,12 @@ export class Database extends Dexie {
this.on("populate", async () => {
db.settings.add({
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 }),
});
});
}

View file

@ -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 { loadConfig } from "./utils/config";
const container = document.getElementById("app");
const root = createRoot(container!);
root.render(<App />);
loadConfig().then(() => {
const container = document.getElementById("app");
const root = ReactDOM.createRoot(container!);
root.render(<App />);
});

View file

@ -19,13 +19,11 @@ import { AiOutlineSend } from "react-icons/ai";
import { MessageItem } from "../components/MessageItem";
import { db } from "../db";
import { useChatId } from "../hooks/useChatId";
import { config } from "../utils/config";
import {
writingCharacters,
writingFormats,
writingStyles,
writingTones,
} from "../utils/constants";
import { createChatCompletion, createStreamChatCompletion } from "../utils/openai";
createChatCompletion,
createStreamChatCompletion,
} from "../utils/openai";
export function ChatRoute() {
const chatId = useChatId();
@ -101,7 +99,7 @@ export function ChatRoute() {
});
setContent("");
const messageId = nanoid()
const messageId = nanoid();
await db.messages.add({
id: messageId,
chatId,
@ -110,17 +108,22 @@ export function ChatRoute() {
createdAt: new Date(),
});
await createStreamChatCompletion(apiKey, [
{
role: "system",
content: getSystemMessage(),
},
...(messages ?? []).map((message) => ({
role: message.role,
content: message.content,
})),
{ role: "user", content },
], chatId, messageId);
await createStreamChatCompletion(
apiKey,
[
{
role: "system",
content: getSystemMessage(),
},
...(messages ?? []).map((message) => ({
role: message.role,
content: message.content,
})),
{ role: "user", content },
],
chatId,
messageId
);
setSubmitting(false);
@ -260,7 +263,7 @@ export function ChatRoute() {
<Select
value={writingCharacter}
onChange={setWritingCharacter}
data={writingCharacters}
data={config.writingCharacters}
placeholder="Character"
variant="filled"
searchable
@ -270,7 +273,7 @@ export function ChatRoute() {
<Select
value={writingTone}
onChange={setWritingTone}
data={writingTones}
data={config.writingTones}
placeholder="Tone"
variant="filled"
searchable
@ -280,7 +283,7 @@ export function ChatRoute() {
<Select
value={writingStyle}
onChange={setWritingStyle}
data={writingStyles}
data={config.writingStyles}
placeholder="Style"
variant="filled"
searchable
@ -290,7 +293,7 @@ export function ChatRoute() {
<Select
value={writingFormat}
onChange={setWritingFormat}
data={writingFormats}
data={config.writingFormats}
placeholder="Format"
variant="filled"
searchable

View file

@ -19,6 +19,7 @@ import { useLiveQuery } from "dexie-react-hooks";
import { Logo } from "../components/Logo";
import { SettingsModal } from "../components/SettingsModal";
import { db } from "../db";
import { config } from "../utils/config";
export function IndexRoute() {
const settings = useLiveQuery(() => db.settings.get("general"));
@ -56,16 +57,18 @@ export function IndexRoute() {
))}
</SimpleGrid>
<Group mt={50}>
<SettingsModal>
<Button
size="md"
variant={openAiApiKey ? "light" : "filled"}
leftIcon={<IconKey size={20} />}
>
{openAiApiKey ? "Change OpenAI Key" : "Enter OpenAI Key"}
</Button>
</SettingsModal>
{!window.todesktop && (
{config.allowSettingsModal && (
<SettingsModal>
<Button
size="md"
variant={openAiApiKey ? "light" : "filled"}
leftIcon={<IconKey size={20} />}
>
{openAiApiKey ? "Change OpenAI Key" : "Enter OpenAI Key"}
</Button>
</SettingsModal>
)}
{config.showDownloadLink && !window.todesktop && (
<Button
component="a"
href="https://dl.todesktop.com/230313oyppkw40a"

207
src/static/config.json Normal file
View 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
View 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;
};

View file

@ -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" },
];

View file

@ -1,12 +1,21 @@
import { encode } from "gpt-token-utils";
import { ChatCompletionRequestMessage, Configuration, OpenAIApi } from "openai";
import { db } from "../db";
import { defaultModel } from "./constants";
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({
apiKey,
...((apiType === "openai" ||
(apiType === "custom" && apiAuth === "bearer-token")) && {
apiKey: apiKey,
}),
...(apiType === "custom" && { basePath: basePath }),
});
return new OpenAIApi(configuration);
}
@ -15,27 +24,26 @@ export async function createStreamChatCompletion(
apiKey: string,
messages: ChatCompletionRequestMessage[],
chatId: string,
messageId: string,
messageId: string
) {
const settings = await db.settings.get("general");
const model = settings?.openAiModel ?? defaultModel;
const model = settings?.openAiModel ?? config.defaultModel;
return OpenAIExt.streamClientChatCompletion(
{
model,
messages
messages,
},
{
apiKey: apiKey,
handler: {
handler: {
onContent(content, isFinal, stream) {
setStreamContent(messageId, content, isFinal);
if(isFinal){
if (isFinal) {
setTotalTokens(chatId, content);
}
},
onDone(stream) {
},
onDone(stream) {},
onError(error, stream) {
console.error(error);
},
@ -44,12 +52,16 @@ export async function createStreamChatCompletion(
);
}
function setStreamContent(messageId:string, content:string, isFinal:boolean){
content = (isFinal ? content : content + "█")
db.messages.update(messageId, {content: content})
function setStreamContent(
messageId: string,
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;
db.chats.where({ id: chatId }).modify((chat) => {
if (chat.totalTokens) {
@ -65,14 +77,29 @@ export async function createChatCompletion(
messages: ChatCompletionRequestMessage[]
) {
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);
return client.createChatCompletion({
model,
stream: false,
messages,
});
const client = getClient(apiKey, type, auth, base);
return client.createChatCompletion(
{
model,
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) {

5124
yarn.lock Normal file

File diff suppressed because it is too large Load diff