├── apps ├── web │ ├── README.md │ ├── .eslintrc.cjs │ ├── src │ │ ├── lib │ │ │ ├── enums.ts │ │ │ ├── format-functions.ts │ │ │ ├── utils.ts │ │ │ ├── is-function-call.ts │ │ │ └── parse-function-call.ts │ │ ├── app │ │ │ ├── page.tsx │ │ │ ├── api │ │ │ │ ├── inngest │ │ │ │ │ └── route.ts │ │ │ │ └── chat │ │ │ │ │ └── route.ts │ │ │ ├── layout.tsx │ │ │ └── globals.css │ │ ├── inngest │ │ │ ├── inngest.server.client.ts │ │ │ ├── ai-flow.ts │ │ │ ├── message-writer.ts │ │ │ └── function-invoker.ts │ │ ├── components │ │ │ ├── ui │ │ │ │ ├── textarea.tsx │ │ │ │ └── button.tsx │ │ │ ├── icons.tsx │ │ │ └── chat-box.tsx │ │ ├── types.ts │ │ └── hooks │ │ │ └── use-backend-chat.ts │ ├── next.config.js │ ├── postcss.config.js │ ├── .env.production │ ├── .env.template │ ├── .env.development │ ├── next-env.d.ts │ ├── .gitignore │ ├── tsconfig.json │ ├── package.json │ └── tailwind.config.js └── openai-party │ ├── partykit.json │ ├── public │ ├── logo.png │ ├── favicon.ico │ ├── favicon-16x16.png │ ├── favicon-32x32.png │ ├── apple-touch-icon.png │ ├── android-chrome-192x192.png │ ├── android-chrome-512x512.png │ ├── site.webmanifest │ ├── index.html │ └── normalize.css │ ├── package.json │ ├── src │ ├── styles.css │ └── server.ts │ ├── README.md │ ├── .gitignore │ └── tsconfig.json ├── .npmrc ├── pnpm-workspace.yaml ├── tsconfig.json ├── packages ├── eslint-config-custom │ ├── README.md │ ├── package.json │ ├── library.js │ ├── react-internal.js │ └── next.js └── tsconfig │ ├── package.json │ ├── react-library.json │ ├── base.json │ └── nextjs.json ├── turbo.json ├── package.json ├── .gitignore ├── LICENSE └── README.md /apps/web/README.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | auto-install-peers = true 2 | -------------------------------------------------------------------------------- /pnpm-workspace.yaml: -------------------------------------------------------------------------------- 1 | packages: 2 | - "packages/*" 3 | - "apps/*" -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "tsconfig/base.json", 3 | } 4 | -------------------------------------------------------------------------------- /apps/web/.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | 3 | extends: ["custom/next"], 4 | }; 5 | -------------------------------------------------------------------------------- /apps/web/src/lib/enums.ts: -------------------------------------------------------------------------------- 1 | export const DONE = "\\ok"; 2 | export const CONFIRM = "\\confirm"; -------------------------------------------------------------------------------- /apps/openai-party/partykit.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "openai-party", 3 | "main": "src/server.ts" 4 | } 5 | -------------------------------------------------------------------------------- /apps/web/next.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | reactStrictMode: true, 3 | transpilePackages: [], 4 | }; 5 | -------------------------------------------------------------------------------- /packages/eslint-config-custom/README.md: -------------------------------------------------------------------------------- 1 | # `@turbo/eslint-config` 2 | 3 | Collection of internal eslint configurations. 4 | -------------------------------------------------------------------------------- /apps/web/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /apps/web/.env.production: -------------------------------------------------------------------------------- 1 | NEXT_PUBLIC_PARTY_KIT_URL=https://my-party.joelhooks.partykit.dev 2 | OPENAI_MODEL_NAME=gpt-3.5-turbo-16k-0613 -------------------------------------------------------------------------------- /apps/openai-party/public/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joelhooks/inngest-partykit-nextjs-openai/HEAD/apps/openai-party/public/logo.png -------------------------------------------------------------------------------- /apps/openai-party/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joelhooks/inngest-partykit-nextjs-openai/HEAD/apps/openai-party/public/favicon.ico -------------------------------------------------------------------------------- /apps/web/.env.template: -------------------------------------------------------------------------------- 1 | NEXT_PUBLIC_PARTY_KIT_URL=http://127.0.0.1:1999 2 | OPENAI_API_KEY= 3 | LINEAR_API_KEY= 4 | INNGEST_EVENT_KEY= 5 | INNGEST_SIGNING_KEY= -------------------------------------------------------------------------------- /apps/openai-party/public/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joelhooks/inngest-partykit-nextjs-openai/HEAD/apps/openai-party/public/favicon-16x16.png -------------------------------------------------------------------------------- /apps/openai-party/public/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joelhooks/inngest-partykit-nextjs-openai/HEAD/apps/openai-party/public/favicon-32x32.png -------------------------------------------------------------------------------- /apps/openai-party/public/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joelhooks/inngest-partykit-nextjs-openai/HEAD/apps/openai-party/public/apple-touch-icon.png -------------------------------------------------------------------------------- /apps/web/src/app/page.tsx: -------------------------------------------------------------------------------- 1 | import Chat from "@/components/chat-box"; 2 | 3 | export default function Page(): JSX.Element { 4 | return ( 5 | 6 | ); 7 | } 8 | -------------------------------------------------------------------------------- /apps/openai-party/public/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joelhooks/inngest-partykit-nextjs-openai/HEAD/apps/openai-party/public/android-chrome-192x192.png -------------------------------------------------------------------------------- /apps/openai-party/public/android-chrome-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joelhooks/inngest-partykit-nextjs-openai/HEAD/apps/openai-party/public/android-chrome-512x512.png -------------------------------------------------------------------------------- /apps/web/src/lib/format-functions.ts: -------------------------------------------------------------------------------- 1 | import type { APIDocs, Functions } from "../types"; 2 | 3 | export const formatFunctions = (f: Functions): APIDocs[] => { 4 | return Object.values(f).map((g) => g.docs); 5 | }; -------------------------------------------------------------------------------- /apps/web/src/lib/utils.ts: -------------------------------------------------------------------------------- 1 | import { type ClassValue, clsx } from "clsx"; 2 | import { twMerge } from "tailwind-merge"; 3 | 4 | export function cn(...inputs: ClassValue[]) { 5 | return twMerge(clsx(inputs)); 6 | } 7 | -------------------------------------------------------------------------------- /apps/web/.env.development: -------------------------------------------------------------------------------- 1 | NEXT_PUBLIC_PARTY_KIT_URL=http://127.0.0.1:1999 2 | NEXT_PUBLIC_PARTYKIT_ROOM_NAME=linear-issues-manager 3 | INNGEST_EVENT_KEY=local_1 4 | INNGEST_SIGNING_KEY=inn_1_sk 5 | OPENAI_MODEL_NAME=gpt-3.5-turbo-16k-0613 6 | -------------------------------------------------------------------------------- /apps/web/next-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | 4 | // NOTE: This file should not be edited 5 | // see https://nextjs.org/docs/basic-features/typescript for more information. 6 | -------------------------------------------------------------------------------- /apps/web/src/lib/is-function-call.ts: -------------------------------------------------------------------------------- 1 | import type { AIMessage } from "../types"; 2 | 3 | export const isFunctionCall = (o: AIMessage[]): boolean => { 4 | const last = o[o.length - 1]; 5 | return !!(last as AIMessage)?.function_call?.name; 6 | }; -------------------------------------------------------------------------------- /apps/web/src/app/api/inngest/route.ts: -------------------------------------------------------------------------------- 1 | import { serve } from "inngest/next"; 2 | import { inngest } from "@/inngest/inngest.server.client"; 3 | import { aibot } from "@/inngest/ai-flow"; 4 | 5 | export const { GET, POST, PUT } = serve(inngest, [aibot]); 6 | -------------------------------------------------------------------------------- /packages/tsconfig/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tsconfig", 3 | "version": "0.0.0", 4 | "private": true, 5 | "license": "MIT", 6 | "files": [ 7 | "base.json", 8 | "nextjs.json" 9 | ], 10 | "publishConfig": { 11 | "access": "public" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /packages/tsconfig/react-library.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/tsconfig", 3 | "display": "React Library", 4 | "extends": "./base.json", 5 | "compilerOptions": { 6 | "jsx": "react-jsx", 7 | "lib": ["ES2015", "DOM"], 8 | "module": "ESNext", 9 | "target": "esnext" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /packages/eslint-config-custom/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "eslint-config-custom", 3 | "license": "MIT", 4 | "version": "0.0.0", 5 | "private": true, 6 | "devDependencies": { 7 | "@next/eslint-plugin-next": "^13.4.19", 8 | "@vercel/style-guide": "^4.0.2", 9 | "eslint-config-turbo": "^1.10.12" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /apps/web/src/app/layout.tsx: -------------------------------------------------------------------------------- 1 | import "./globals.css"; 2 | import { Inter } from "next/font/google"; 3 | 4 | const inter = Inter({ subsets: ["latin"] }); 5 | 6 | export default function RootLayout({ 7 | children, 8 | }: { 9 | children: React.ReactNode; 10 | }): JSX.Element { 11 | return ( 12 | 13 | {children} 14 | 15 | ); 16 | } 17 | -------------------------------------------------------------------------------- /apps/openai-party/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "openai-party", 3 | "version": "0.0.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "partykit dev", 7 | "deploy": "partykit deploy" 8 | }, 9 | "dependencies": { 10 | 11 | "inngest": "^2.5.2", 12 | "partysocket": "0.0.0-3228764" 13 | 14 | }, 15 | "devDependencies": { 16 | "partykit": "0.0.0-3228764", 17 | "typescript": "^5.1.6" 18 | } 19 | } -------------------------------------------------------------------------------- /turbo.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://turbo.build/schema.json", 3 | "globalDependencies": ["**/.env.*local"], 4 | "pipeline": { 5 | "build": { 6 | "dependsOn": ["^build"], 7 | "env": ["OPENAI_API_KEY", "NEXT_PUBLIC_PARTY_KIT_URL", "OPENAI_MODEL_NAME"], 8 | "outputs": [".next/**", "!.next/cache/**"] 9 | }, 10 | "lint": {}, 11 | "dev": { 12 | "cache": false, 13 | "persistent": true 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /apps/openai-party/public/site.webmanifest: -------------------------------------------------------------------------------- 1 | { 2 | "name": "PartyKit", 3 | "short_name": "PartyKit", 4 | "icons": [ 5 | { 6 | "src": "/android-chrome-192x192.png", 7 | "sizes": "192x192", 8 | "type": "image/png" 9 | }, 10 | { 11 | "src": "/android-chrome-512x512.png", 12 | "sizes": "512x512", 13 | "type": "image/png" 14 | } 15 | ], 16 | "theme_color": "#ffffff", 17 | "background_color": "#ffffff", 18 | "display": "standalone" 19 | } 20 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "scripts": { 4 | "build": "turbo run build", 5 | "dev": "turbo run dev", 6 | "dev-turbo": "turbo run dev", 7 | "lint": "turbo run lint", 8 | "format": "prettier --write \"**/*.{ts,tsx,md}\"" 9 | }, 10 | "devDependencies": { 11 | "eslint": "^8.47.0", 12 | "prettier": "^3.0.2", 13 | "tsconfig": "workspace:*", 14 | "turbo": "latest", 15 | "typescript": "^5.1.6" 16 | }, 17 | "name": "inngest-partykit-nextjs-openai" 18 | } 19 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | node_modules 5 | .pnp 6 | .pnp.js 7 | 8 | # testing 9 | coverage 10 | 11 | # next.js 12 | .next/ 13 | out/ 14 | build 15 | 16 | # misc 17 | .DS_Store 18 | *.pem 19 | 20 | # debug 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | 25 | # local env files 26 | .env 27 | .env.local 28 | .env.development.local 29 | .env.test.local 30 | .env.production.local 31 | 32 | # turbo 33 | .turbo 34 | 35 | # vercel 36 | .vercel 37 | -------------------------------------------------------------------------------- /apps/web/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # next.js 12 | /.next/ 13 | /out/ 14 | 15 | # production 16 | /build 17 | 18 | # misc 19 | .DS_Store 20 | *.pem 21 | 22 | # debug 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | 27 | # local env files 28 | .env.local 29 | .env.development.local 30 | .env.test.local 31 | .env.production.local 32 | 33 | # vercel 34 | .vercel 35 | -------------------------------------------------------------------------------- /apps/openai-party/src/styles.css: -------------------------------------------------------------------------------- 1 | /* 2 | We've already included normalize.css. 3 | 4 | But we'd like a modern looking boilerplate. 5 | Clean type, sans-serif, and a nice color palette. 6 | 7 | */ 8 | 9 | body { 10 | font-family: sans-serif; 11 | font-size: 16px; 12 | line-height: 1.5; 13 | color: #333; 14 | } 15 | 16 | h1, 17 | h2, 18 | h3, 19 | h4, 20 | h5, 21 | h6 { 22 | font-family: sans-serif; 23 | font-weight: 600; 24 | line-height: 1.25; 25 | margin-top: 0; 26 | margin-bottom: 0.5rem; 27 | } 28 | 29 | #app { 30 | padding: 1rem; 31 | } 32 | -------------------------------------------------------------------------------- /apps/web/src/lib/parse-function-call.ts: -------------------------------------------------------------------------------- 1 | import type {ChatCompletionRequestMessage} from "openai-edge"; 2 | import type { AIMessage, FunctionCall } from "../types"; 3 | 4 | export const parseFunctionCall = (o: AIMessage): FunctionCall => { 5 | if (!!(o as ChatCompletionRequestMessage).function_call) { 6 | const fn = o as ChatCompletionRequestMessage; 7 | return { 8 | name: fn.function_call?.name || "", 9 | arguments: JSON.parse(fn.function_call?.arguments || "{}"), 10 | }; 11 | } 12 | throw new Error("no function call available"); 13 | }; 14 | -------------------------------------------------------------------------------- /packages/tsconfig/base.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/tsconfig", 3 | "display": "Default", 4 | "compilerOptions": { 5 | "composite": false, 6 | "declaration": true, 7 | "declarationMap": true, 8 | "esModuleInterop": true, 9 | "forceConsistentCasingInFileNames": true, 10 | "inlineSources": false, 11 | "isolatedModules": true, 12 | "moduleResolution": "node", 13 | "noUnusedLocals": false, 14 | "noUnusedParameters": false, 15 | "preserveWatchOutput": true, 16 | "skipLibCheck": true, 17 | "strict": true, 18 | "strictNullChecks": true 19 | }, 20 | "exclude": ["node_modules"] 21 | } 22 | -------------------------------------------------------------------------------- /apps/web/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "strict": true, 8 | "forceConsistentCasingInFileNames": true, 9 | "noEmit": true, 10 | "esModuleInterop": true, 11 | "module": "esnext", 12 | "moduleResolution": "node", 13 | "resolveJsonModule": true, 14 | "isolatedModules": true, 15 | "jsx": "preserve", 16 | "incremental": true, 17 | "plugins": [ 18 | { 19 | "name": "next" 20 | } 21 | ], 22 | "paths": { 23 | "@/*": ["./src/*"] 24 | } 25 | }, 26 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], 27 | "exclude": ["node_modules"] 28 | } 29 | -------------------------------------------------------------------------------- /apps/web/src/inngest/inngest.server.client.ts: -------------------------------------------------------------------------------- 1 | import { EventSchemas, Inngest } from "inngest"; 2 | import type { Message } from "ai"; 3 | 4 | export const inngest = new Inngest({ 5 | name: "Inngest + PartyKit: OpenAI Function invocation app", 6 | schemas: new EventSchemas().fromRecord(), 7 | }); 8 | 9 | type ChatStarted = { 10 | data: { 11 | requestId: string; 12 | messages: Message[]; 13 | }; 14 | }; 15 | 16 | type ChatCancelled = { 17 | data: { 18 | requestId: string; 19 | }; 20 | }; 21 | 22 | type ChatConfirmed = { 23 | data: { 24 | requestId: string; 25 | confirm: boolean; 26 | }; 27 | }; 28 | 29 | type Events = { 30 | "api/chat.started": ChatStarted; 31 | "api/chat.cancelled": ChatCancelled; 32 | "api/chat.confirmed": ChatConfirmed; 33 | }; 34 | -------------------------------------------------------------------------------- /packages/eslint-config-custom/library.js: -------------------------------------------------------------------------------- 1 | const { resolve } = require("node:path"); 2 | 3 | const project = resolve(process.cwd(), "tsconfig.json"); 4 | 5 | /* 6 | * This is a custom ESLint configuration for use with 7 | * typescript packages. 8 | * 9 | * This config extends the Vercel Engineering Style Guide. 10 | * For more information, see https://github.com/vercel/style-guide 11 | * 12 | */ 13 | 14 | module.exports = { 15 | extends: [ 16 | "@vercel/style-guide/eslint/node", 17 | "@vercel/style-guide/eslint/typescript", 18 | ].map(require.resolve), 19 | parserOptions: { 20 | project, 21 | }, 22 | globals: { 23 | React: true, 24 | JSX: true, 25 | }, 26 | settings: { 27 | "import/resolver": { 28 | typescript: { 29 | project, 30 | }, 31 | }, 32 | }, 33 | ignorePatterns: ["node_modules/", "dist/"], 34 | }; 35 | -------------------------------------------------------------------------------- /apps/openai-party/README.md: -------------------------------------------------------------------------------- 1 | ## 🎈 openai-party 2 | 3 | Welcome to the party, pal! 4 | 5 | This is a [Partykit](https://partykit.io) project, which lets you create real-time collaborative applications with minimal coding effort. 6 | 7 | [`server.ts`](./src/server.ts) is the server-side code, which is responsible for handling WebSocket events and HTTP requests. [`client.tsx`](./src/client.tsx) is the client-side code, which connects to the server and listens for events. 8 | 9 | You can start developing by running `npm run dev` and opening [http://localhost:1999](http://localhost:1999) in your browser. When you're ready, you can deploy your application on to the PartyKit cloud with `npm run deploy`. 10 | 11 | Refer to our docs for more information: https://github.com/partykit/partykit/blob/main/README.md. For more help, reach out to us on [Discord](https://discord.gg/g5uqHQJc3z), [GitHub](https://github.com/partykit/partykit), or [Twitter](https://twitter.com/partykit_io). 12 | -------------------------------------------------------------------------------- /packages/tsconfig/nextjs.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/tsconfig", 3 | "display": "Next.js", 4 | "compilerOptions": { 5 | "composite": false, 6 | "esModuleInterop": true, 7 | "forceConsistentCasingInFileNames": true, 8 | "inlineSources": false, 9 | "isolatedModules": true, 10 | "moduleResolution": "node", 11 | "noUnusedLocals": false, 12 | "noUnusedParameters": false, 13 | "preserveWatchOutput": true, 14 | "skipLibCheck": true, 15 | "strict": true, 16 | "strictNullChecks": true, 17 | "plugins": [{ "name": "next" }], 18 | "allowJs": true, 19 | "declaration": false, 20 | "declarationMap": false, 21 | "incremental": true, 22 | "jsx": "preserve", 23 | "lib": ["dom", "dom.iterable", "esnext"], 24 | "module": "esnext", 25 | "noEmit": true, 26 | "resolveJsonModule": true, 27 | "target": "esnext" 28 | }, 29 | "include": ["src", "next-env.d.ts"], 30 | "exclude": ["node_modules"] 31 | } 32 | -------------------------------------------------------------------------------- /apps/web/src/components/ui/textarea.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { cn } from "@/lib/utils"; 3 | 4 | 5 | export interface TextareaProps 6 | extends React.TextareaHTMLAttributes {} 7 | 8 | const Textarea = React.forwardRef( 9 | ({ className, ...props }, ref) => { 10 | return ( 11 |