diff --git a/Spacefile b/Spacefile index 06fc891..05c8a4b 100644 --- a/Spacefile +++ b/Spacefile @@ -19,6 +19,13 @@ micros: path: /api engine: nodejs16 dev: npm run dev + commands: + - npm run build + run: node lib/index.js + include: + - lib + - node_modules + - package.json public_routes: - "/public/*" provide_actions: true diff --git a/backend/package-lock.json b/backend/package-lock.json index aab44cb..f3aefb0 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -14,7 +14,8 @@ "deta-space-client": "^2.2.2", "dotenv": "^16.3.1", "express": "^4.18.2", - "nanoid": "^3.3.6" + "nanoid": "^3.3.6", + "openai": "^3.2.1" }, "devDependencies": { "@types/express": "^4.17.17", @@ -560,6 +561,19 @@ "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, + "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/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -783,6 +797,17 @@ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/commander": { "version": "2.20.3", "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", @@ -858,6 +883,14 @@ "ms": "2.0.0" } }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", @@ -1177,6 +1210,38 @@ "node": ">= 0.8" } }, + "node_modules/follow-redirects": { + "version": "1.15.3", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.3.tgz", + "integrity": "sha512-1VzOtuEM8pC9SFU1E+8KfTjZyMztRsgEfwQl44z8A25uy13jSzTj6dyK2Df52iV0vgHCfBwLhDWevLn95w5v6Q==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/formdata-polyfill": { "version": "4.0.10", "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", @@ -1751,6 +1816,15 @@ "node": ">= 0.8" } }, + "node_modules/openai": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/openai/-/openai-3.3.0.tgz", + "integrity": "sha512-uqxI/Au+aPRnsaQRe8CojU0eCR7I0mBiKjD3sNMzY6DaC1ZVrc85u98mtJW6voDug8fgGN+DIZmTDxTthxb7dQ==", + "dependencies": { + "axios": "^0.26.0", + "form-data": "^4.0.0" + } + }, "node_modules/parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", @@ -2999,6 +3073,19 @@ "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, + "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" + } + }, "balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -3146,6 +3233,14 @@ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" }, + "combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "requires": { + "delayed-stream": "~1.0.0" + } + }, "commander": { "version": "2.20.3", "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", @@ -3209,6 +3304,11 @@ "ms": "2.0.0" } }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==" + }, "depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", @@ -3449,6 +3549,21 @@ "unpipe": "~1.0.0" } }, + "follow-redirects": { + "version": "1.15.3", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.3.tgz", + "integrity": "sha512-1VzOtuEM8pC9SFU1E+8KfTjZyMztRsgEfwQl44z8A25uy13jSzTj6dyK2Df52iV0vgHCfBwLhDWevLn95w5v6Q==" + }, + "form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + } + }, "formdata-polyfill": { "version": "4.0.10", "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", @@ -3851,6 +3966,15 @@ "ee-first": "1.1.1" } }, + "openai": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/openai/-/openai-3.3.0.tgz", + "integrity": "sha512-uqxI/Au+aPRnsaQRe8CojU0eCR7I0mBiKjD3sNMzY6DaC1ZVrc85u98mtJW6voDug8fgGN+DIZmTDxTthxb7dQ==", + "requires": { + "axios": "^0.26.0", + "form-data": "^4.0.0" + } + }, "parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", diff --git a/backend/package.json b/backend/package.json index f7f69e2..a3d0829 100644 --- a/backend/package.json +++ b/backend/package.json @@ -7,7 +7,7 @@ "scripts": { "build": "tsc", "dev": "nodemon --esm src/index.ts", - "start": "ts-node --esm src/index.ts" + "start": "node lib/index.js" }, "keywords": [], "author": "", @@ -18,7 +18,8 @@ "deta-space-client": "^2.2.2", "dotenv": "^16.3.1", "express": "^4.18.2", - "nanoid": "^3.3.6" + "nanoid": "^3.3.6", + "openai": "^3.2.1" }, "devDependencies": { "@types/express": "^4.17.17", diff --git a/backend/src/config.ts b/backend/src/config.ts new file mode 100644 index 0000000..a7a7a42 --- /dev/null +++ b/backend/src/config.ts @@ -0,0 +1,242 @@ +export default { + "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-0613", + "label": "GPT-3.5-TURBO-0613" + }, + { + "value": "gpt-3.5-turbo-0301", + "label": "GPT-3.5-TURBO-0301 (Legacy)" + }, + { + "value": "gpt-3.5-turbo-16k", + "label": "GPT-3.5-TURBO-16K" + }, + { + "value": "gpt-3.5-turbo-16k-0613", + "label": "GPT-3.5-TURBO-16K-0613" + }, + { + "value": "gpt-4", + "label": "GPT-4 (Limited Beta)" + }, + { + "value": "gpt-4-0613", + "label": "GPT-4-0613 (Limited Beta)" + }, + { + "value": "gpt-4-0314", + "label": "GPT-4-0314 (Limited Beta, Legacy)" + }, + { + "value": "gpt-4-32k", + "label": "GPT-4-32K (Limited Beta)" + }, + { + "value": "gpt-4-32k-0613", + "label": "GPT-4-32K-0613 (Limited Beta)" + }, + { + "value": "gpt-4-32k-0314", + "label": "GPT-4-32K-0314 (Limited Beta, Legacy)" + } + ], + "simplifiedModels": [ + { + "value": "gpt-3.5-turbo", + "label": "GPT-3.5" + }, + { + "value": "gpt-4", + "label": "GPT-4" + } + ], + "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 with a One-Liner", + "label": "One-Liner" + }, + { + "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": false, + "allowDarkModeToggle": true, + "allowSettingsModal": true, + "allowDatabaseModal": false, + "showTwitterLink": false, + "showFeedbackLink": false, + "githubUrl": "https://github.com/deta/dialogue" + } \ No newline at end of file diff --git a/backend/src/openai.ts b/backend/src/openai.ts index a498714..fe27d7a 100644 --- a/backend/src/openai.ts +++ b/backend/src/openai.ts @@ -1,7 +1,68 @@ -import { chats, messages, prompts, generateKey } from './db.js' +import { ChatCompletionRequestMessage, Configuration, OpenAIApi } from "openai"; +import { chats, messages, generateKey } from './db.js' import type { Prompt, Message, Chat, Settings } from './types.js' -import { getSystemMessage, createChatCompletion } from '../../src/utils/openai.js' +import config from "./config.js"; + +function getClient( + apiKey: string, + apiType: string, + apiAuth: string, + basePath: string +) { + const configuration = new Configuration({ + ...((apiType === "openai" || + (apiType === "custom" && apiAuth === "bearer-token")) && { + apiKey: apiKey, + }), + ...(apiType === "custom" && { basePath: basePath }), + }); + return new OpenAIApi(configuration); +} + +export async function createChatCompletion( + settings: Settings, + messages: ChatCompletionRequestMessage[] +) { + 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 apiKey = settings.openAiApiKey!; + + 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 function getSystemMessage(prompt: { content?: string, character?: string, tone?: string, style?: string, format?: string }) { + const message: string[] = []; + if (prompt.character) message.push(`You are ${prompt.character}`); + if (prompt.tone) message.push(`Respond in ${prompt.tone.toLowerCase()} tone.`); + if (prompt.style) message.push(`Respond in ${prompt.style.toLowerCase()} style.`); + if (prompt.format) message.push(`${prompt.format.toLowerCase()}.`); + if (message.length === 0) + message.push( + "You are ChatGPT, a large language model trained by OpenAI." + ); + if (prompt.content) message.push(prompt.content); + return message.join(" "); +}; export const createChatWithMessage = async (chat: Chat, settings: Settings, content: string, prompt?: Prompt, previousMessages?: Message[]) => { let systemMessageValue = "" diff --git a/backend/src/types.ts b/backend/src/types.ts index 9d97991..a72ec32 100644 --- a/backend/src/types.ts +++ b/backend/src/types.ts @@ -1 +1,83 @@ -export * from '../../src/db/types.js' \ No newline at end of file +export interface Chat { + key: string; + description: string; + totalTokens: number; + prompt?: string | null; + writingInstructions?: string | null; + writingCharacter?: string | null; + writingTone?: string | null; + writingStyle?: string | null; + writingFormat?: string | null; + model?: string | null; + private?: boolean; + shared?: boolean; + createdAt: string; + } + + export interface Message { + key: string; + chatId: string; + role: "system" | "assistant" | "user"; + content: string; + createdAt: string; + } + + export interface Prompt { + key: string; + title: string; + content: string; + writingCharacter?: string | null; + writingTone?: string | null; + writingStyle?: string | null; + writingFormat?: string | null; + createdAt: string; + } + + export interface Settings { + key: "general"; + openAiApiKey?: string; + openAiModel?: string; + openAiApiType?: 'openai' | 'custom'; + openAiApiAuth?: 'none' | 'bearer-token' | 'api-key'; + openAiApiBase?: string; + openAiApiVersion?: string; + } + + export interface Integration { + key: string; + instance: string + apiKey: string + } + + export enum ViewType { + DETAIL = '@deta/detail', + RAW = '@deta/raw', + FILE = '@deta/file', + LIST = '@deta/list', + TABLE = '@deta/table', + } + + export type ActionInput = { + name: string + title?: string + type?: string + optional: boolean + } + +export type AppAction = { + title: string + name: string + description: string + local?: boolean + output: ViewType + app_name: string + instance_id: string + instance_alias: string + channel: string + version: string + input?: ActionInput[] + icon_url?: string + placeholder_icon_config?: { + css_background: string + } + } \ No newline at end of file diff --git a/backend/tsconfig.json b/backend/tsconfig.json index bfacea7..02779ea 100644 --- a/backend/tsconfig.json +++ b/backend/tsconfig.json @@ -12,6 +12,6 @@ }, "$schema": "https://json.schemastore.org/tsconfig", "display": "Recommended", - "include": ["src/**/*", "../../src/db", "../../src/utils"], + "include": ["src/**/*"], "exclude": ["node_modules", "**/*.spec.ts"] } \ No newline at end of file