diff --git a/.env.example b/.env.example index 8a30ea8c16..1acf6cfde5 100644 --- a/.env.example +++ b/.env.example @@ -2,6 +2,10 @@ ## Then get your OpenAI API Key here: https://platform.openai.com/account/api-keys OPENAI_API_KEY=XXXXXXXX +## Set the preview to get the user to use their own API key - stored in browser local storage +## If this is set to preview customer supplied key will be used if provided with a fallback to the above OPENAI_API_KEY +NEXT_PUBLIC_VERCEL_ENV=preview + # Update these with your Supabase details from your project settings > API # https://app.supabase.com/project/_/settings/api # In local dev you can get these by running `supabase status`. diff --git a/app/api/chat/route.ts b/app/api/chat/route.ts index 4d58613891..8f561953ad 100644 --- a/app/api/chat/route.ts +++ b/app/api/chat/route.ts @@ -10,19 +10,21 @@ import { nanoid } from '@/lib/utils' export const runtime = 'edge' -const configuration = new Configuration({ - apiKey: process.env.OPENAI_API_KEY -}) +export async function POST(req: Request) { + const json = await req.json() + const { messages, previewToken } = json + + // set configuration for OpenAI - api key set to previewToken from req otherwise use env variable + const configuration = new Configuration({ + apiKey: previewToken || process.env.OPENAI_API_KEY + }) -const openai = new OpenAIApi(configuration) + const openai = new OpenAIApi(configuration) -export async function POST(req: Request) { const cookieStore = cookies() const supabase = createRouteHandlerClient({ cookies: () => cookieStore }) - const json = await req.json() - const { messages, previewToken } = json const userId = (await auth({ cookieStore }))?.user.id if (!userId) { diff --git a/components/chat.tsx b/components/chat.tsx index a9ecafe059..eb428279d9 100644 --- a/components/chat.tsx +++ b/components/chat.tsx @@ -16,12 +16,13 @@ import { DialogHeader, DialogTitle } from '@/components/ui/dialog' -import { useState } from 'react' +import { useEffect, useState } from 'react' import { Button } from './ui/button' import { Input } from './ui/input' import { toast } from 'react-hot-toast' -const IS_PREVIEW = process.env.VERCEL_ENV === 'preview' +// set IS_PREVIEW to true if the environment is a preview environment +const IS_PREVIEW = process.env.NEXT_PUBLIC_VERCEL_ENV === 'preview' export interface ChatProps extends React.ComponentProps<'div'> { initialMessages?: Message[] id?: string @@ -32,8 +33,24 @@ export function Chat({ id, initialMessages, className }: ChatProps) { 'ai-token', null ) - const [previewTokenDialog, setPreviewTokenDialog] = useState(IS_PREVIEW) + // initialize previewTokenDialog to false, To be set based on the preview flag later in useEffect to avoid hydration errors + + const [previewTokenDialog, setPreviewTokenDialog] = useState(false) const [previewTokenInput, setPreviewTokenInput] = useState(previewToken ?? '') + + useEffect(() => { + // if the environment is a preview environment and the preview token is not set, open the preview token dialog + setPreviewTokenDialog(IS_PREVIEW); + + // reset the preview token and dialog when the component unmounts + return () => { + setPreviewTokenDialog(false) + setPreviewToken(null) + setPreviewTokenInput('') + } + + }, []); + const { messages, append, reload, stop, isLoading, input, setInput } = useChat({ initialMessages, diff --git a/package.json b/package.json index 9236055dec..57d948fa67 100644 --- a/package.json +++ b/package.json @@ -30,6 +30,7 @@ "ai": "^2.1.6", "class-variance-authority": "^0.6.1", "clsx": "^1.2.1", + "encoding": "^0.1.13", "focus-trap-react": "^10.1.1", "nanoid": "^4.0.2", "next": "13.4.10", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 71420f003a..0fbd567cb6 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -37,7 +37,7 @@ dependencies: version: 0.7.2(@supabase/supabase-js@2.26.0) '@supabase/supabase-js': specifier: ^2.26.0 - version: 2.26.0 + version: 2.26.0(encoding@0.1.13) '@vercel/analytics': specifier: ^1.0.1 version: 1.0.1 @@ -53,6 +53,9 @@ dependencies: clsx: specifier: ^1.2.1 version: 1.2.1 + encoding: + specifier: ^0.1.13 + version: 0.1.13 focus-trap-react: specifier: ^10.1.1 version: 10.1.1(prop-types@15.8.1)(react-dom@18.2.0)(react@18.2.0) @@ -1164,7 +1167,7 @@ packages: '@supabase/supabase-js': ^2.19.0 dependencies: '@supabase/auth-helpers-shared': 0.4.1(@supabase/supabase-js@2.26.0) - '@supabase/supabase-js': 2.26.0 + '@supabase/supabase-js': 2.26.0(encoding@0.1.13) set-cookie-parser: 2.6.0 dev: false @@ -1173,30 +1176,30 @@ packages: peerDependencies: '@supabase/supabase-js': ^2.19.0 dependencies: - '@supabase/supabase-js': 2.26.0 + '@supabase/supabase-js': 2.26.0(encoding@0.1.13) jose: 4.14.4 dev: false - /@supabase/functions-js@2.1.2: + /@supabase/functions-js@2.1.2(encoding@0.1.13): resolution: {integrity: sha512-QCR6pwJs9exCl37bmpMisUd6mf+0SUBJ6mUpiAjEkSJ/+xW8TCuO14bvkWHADd5hElJK9MxNlMQXxSA4DRz9nQ==} dependencies: - cross-fetch: 3.1.8 + cross-fetch: 3.1.8(encoding@0.1.13) transitivePeerDependencies: - encoding dev: false - /@supabase/gotrue-js@2.39.0: + /@supabase/gotrue-js@2.39.0(encoding@0.1.13): resolution: {integrity: sha512-Fhro0zcglqKAJRLeGGompySUwx74RM/CE1ICd/7R2qhpE7qWDiTM0f2s6+qK5YQnu7akHnIIvSn9NAN9MuTOqw==} dependencies: - cross-fetch: 3.1.8 + cross-fetch: 3.1.8(encoding@0.1.13) transitivePeerDependencies: - encoding dev: false - /@supabase/postgrest-js@1.7.2: + /@supabase/postgrest-js@1.7.2(encoding@0.1.13): resolution: {integrity: sha512-GK80JpRq8l6Qll85erICypAfQCied8tdlXfsDN14W844HqXCSOisk8AaE01DAwGJanieaoN5fuqhzA2yKxDvEQ==} dependencies: - cross-fetch: 3.1.8 + cross-fetch: 3.1.8(encoding@0.1.13) transitivePeerDependencies: - encoding dev: false @@ -1211,23 +1214,23 @@ packages: - supports-color dev: false - /@supabase/storage-js@2.5.1: + /@supabase/storage-js@2.5.1(encoding@0.1.13): resolution: {integrity: sha512-nkR0fQA9ScAtIKA3vNoPEqbZv1k5B5HVRYEvRWdlP6mUpFphM9TwPL2jZ/ztNGMTG5xT6SrHr+H7Ykz8qzbhjw==} dependencies: - cross-fetch: 3.1.8 + cross-fetch: 3.1.8(encoding@0.1.13) transitivePeerDependencies: - encoding dev: false - /@supabase/supabase-js@2.26.0: + /@supabase/supabase-js@2.26.0(encoding@0.1.13): resolution: {integrity: sha512-RXmTPTobaYAwkSobadHZmEVLmzX3SGrtRZIGfLWnLv92VzBRrjuXn0a+bJqKl50GUzsyqPA+j5pod7EwMkcH5A==} dependencies: - '@supabase/functions-js': 2.1.2 - '@supabase/gotrue-js': 2.39.0 - '@supabase/postgrest-js': 1.7.2 + '@supabase/functions-js': 2.1.2(encoding@0.1.13) + '@supabase/gotrue-js': 2.39.0(encoding@0.1.13) + '@supabase/postgrest-js': 1.7.2(encoding@0.1.13) '@supabase/realtime-js': 2.7.3 - '@supabase/storage-js': 2.5.1 - cross-fetch: 3.1.8 + '@supabase/storage-js': 2.5.1(encoding@0.1.13) + cross-fetch: 3.1.8(encoding@0.1.13) transitivePeerDependencies: - encoding - supports-color @@ -1853,10 +1856,10 @@ packages: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} dev: true - /cross-fetch@3.1.8: + /cross-fetch@3.1.8(encoding@0.1.13): resolution: {integrity: sha512-cvA+JwZoU0Xq+h6WkMvAUqPEYy92Obet6UdKLfW60qn99ftItKjB5T+BkyWOFWe2pUyfQ+IJHmpOTznqk1M6Kg==} dependencies: - node-fetch: 2.6.12 + node-fetch: 2.6.12(encoding@0.1.13) transitivePeerDependencies: - encoding dev: false @@ -2063,6 +2066,12 @@ packages: resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} dev: true + /encoding@0.1.13: + resolution: {integrity: sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==} + dependencies: + iconv-lite: 0.6.3 + dev: false + /enhanced-resolve@5.14.0: resolution: {integrity: sha512-+DCows0XNwLDcUhbFJPdlQEVnT2zXlCv7hPxemTz86/O+B/hCQ+mb7ydkPKiflpVraqLPCAfu7lDy+hBXueojw==} engines: {node: '>=10.13.0'} @@ -2890,6 +2899,13 @@ packages: engines: {node: '>=14.18.0'} dev: true + /iconv-lite@0.6.3: + resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} + engines: {node: '>=0.10.0'} + dependencies: + safer-buffer: 2.1.2 + dev: false + /ignore@5.2.4: resolution: {integrity: sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==} engines: {node: '>= 4'} @@ -3849,7 +3865,7 @@ packages: - babel-plugin-macros dev: false - /node-fetch@2.6.12: + /node-fetch@2.6.12(encoding@0.1.13): resolution: {integrity: sha512-C/fGU2E8ToujUivIO0H+tpQ6HWo4eEmchoPIoXtxCrVghxdKq+QOHqEZW7tuP3KlV3bC8FRMO5nMCC7Zm1VP6g==} engines: {node: 4.x || >=6.0.0} peerDependencies: @@ -3858,6 +3874,7 @@ packages: encoding: optional: true dependencies: + encoding: 0.1.13 whatwg-url: 5.0.0 dev: false @@ -4584,6 +4601,10 @@ packages: is-regex: 1.1.4 dev: true + /safer-buffer@2.1.2: + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + dev: false + /satori@0.10.1: resolution: {integrity: sha512-F4bTCkDp931tLb7+UCNPBuSQwXhikrUkI4fBQo6fA8lF0Evqqgg3nDyUpRktQpR5Ry1DIiIVqLyEwkAms87ykg==} engines: {node: '>=16'}