diff --git a/package-lock.json b/package-lock.json index a76cd200..313481a8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -30,7 +30,9 @@ "@tiptap/suggestion": "^2.0.3", "@types/node": "20.3.1", "@types/react-dom": "18.2.6", + "@upstash/ratelimit": "^0.4.4", "@vercel/blob": "^0.9.2", + "@vercel/kv": "^0.2.3", "ai": "^2.2.10", "autoprefixer": "10.4.14", "chart.js": "^4.4.0", @@ -3181,6 +3183,33 @@ "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==" }, + "node_modules/@upstash/core-analytics": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/@upstash/core-analytics/-/core-analytics-0.0.6.tgz", + "integrity": "sha512-cpPSR0XJAJs4Ddz9nq3tINlPS5aLfWVCqhhtHnXt4p7qr5+/Znlt1Es736poB/9rnl1hAHrOsOvVj46NEXcVqA==", + "dependencies": { + "@upstash/redis": "^1.19.3" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@upstash/ratelimit": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/@upstash/ratelimit/-/ratelimit-0.4.4.tgz", + "integrity": "sha512-y3q6cNDdcRQ2MRPRf5UNWBN36IwnZ4kAEkGoH3i6OqdWwz4qlBxNsw4/Rpqn9h93+Nx1cqg5IOq7O2e2zMJY1w==", + "dependencies": { + "@upstash/core-analytics": "^0.0.6" + } + }, + "node_modules/@upstash/redis": { + "version": "1.22.0", + "resolved": "https://registry.npmjs.org/@upstash/redis/-/redis-1.22.0.tgz", + "integrity": "sha512-sXoJDoEqqik0HbrNE7yRWckOySEFsoBxfRdCgOqkc0w6py19ZZG50SpGkDDEUXSnBqP8VgGYXhWAiBpqxrt5oA==", + "dependencies": { + "isomorphic-fetch": "^3.0.0" + } + }, "node_modules/@vercel/blob": { "version": "0.9.2", "resolved": "https://registry.npmjs.org/@vercel/blob/-/blob-0.9.2.tgz", @@ -3219,6 +3248,17 @@ } } }, + "node_modules/@vercel/kv": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@vercel/kv/-/kv-0.2.3.tgz", + "integrity": "sha512-Wq1+EsRBQmvLlcqCZeYVg1MAARWrnETgLe3Sy3UCqG+zg7LThpkt0YHZe1NN3Aj4IRmCKQamotWrLDdEx+ZB3w==", + "dependencies": { + "@upstash/redis": "1.22.0" + }, + "engines": { + "node": ">=14.6" + } + }, "node_modules/@vue/compiler-core": { "version": "3.3.4", "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.3.4.tgz", @@ -6744,6 +6784,15 @@ "node": ">=0.10.0" } }, + "node_modules/isomorphic-fetch": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/isomorphic-fetch/-/isomorphic-fetch-3.0.0.tgz", + "integrity": "sha512-qvUtwJ3j6qwsF3jLxkZ72qCgjMysPzDfeV240JHiGZsANBYd+EEuu35v7dfrJ9Up0Ak07D7GGSkGhCHTqg/5wA==", + "dependencies": { + "node-fetch": "^2.6.1", + "whatwg-fetch": "^3.4.1" + } + }, "node_modules/istanbul-lib-coverage": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz", @@ -12417,6 +12466,11 @@ "node": ">=12" } }, + "node_modules/whatwg-fetch": { + "version": "3.6.19", + "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.6.19.tgz", + "integrity": "sha512-d67JP4dHSbm2TrpFj8AbO8DnL1JXL5J9u0Kq2xW6d0TFDbCA3Muhdt8orXC22utleTVj7Prqt82baN6RBvnEgw==" + }, "node_modules/whatwg-mimetype": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz", diff --git a/package.json b/package.json index fe2d238a..b7818b06 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,9 @@ "@tiptap/suggestion": "^2.0.3", "@types/node": "20.3.1", "@types/react-dom": "18.2.6", + "@upstash/ratelimit": "^0.4.4", "@vercel/blob": "^0.9.2", + "@vercel/kv": "^0.2.3", "ai": "^2.2.10", "autoprefixer": "10.4.14", "chart.js": "^4.4.0", diff --git a/src/app/api/generate/route.ts b/src/app/api/generate/route.ts index fea82630..9cbd5f3f 100644 --- a/src/app/api/generate/route.ts +++ b/src/app/api/generate/route.ts @@ -1,5 +1,7 @@ import { Configuration, OpenAIApi } from 'openai-edge'; import { OpenAIStream, StreamingTextResponse } from 'ai'; +import { kv } from '@vercel/kv'; +import { Ratelimit } from '@upstash/ratelimit'; const config = new Configuration({ apiKey: process.env.NEXT_PUBLIC_OPENAI_API_KEY, @@ -9,33 +11,32 @@ const openai = new OpenAIApi(config); export const runtime = 'edge'; export async function POST(req: Request): Promise { - // if ( - // // process.env.NODE_ENV != 'development' && - // // process.env.KV_REST_API_URL && - // // process.env.KV_REST_API_TOKEN - // false - // ) { - // const ip = req.headers.get('x-forwarded-for'); - // const ratelimit = new Ratelimit({ - // redis: kv, - // limiter: Ratelimit.slidingWindow(50, '1 d'), - // }); + if ( + process.env.KV_REST_API_URL && + process.env.KV_REST_API_TOKEN + ) { + const ip = req.headers.get('x-forwarded-for'); + console.log(ip); + const ratelimit = new Ratelimit({ + redis: kv, + limiter: Ratelimit.slidingWindow(50, '1 d'), + }); - // const { success, limit, reset, remaining } = await ratelimit.limit( - // `novel_ratelimit_${ip}` - // ); + const { success, limit, reset, remaining } = await ratelimit.limit( + `novel_ratelimit_${ip}` + ); - // if (!success) { - // return new Response('You have reached your request limit for the day.', { - // status: 429, - // headers: { - // 'X-RateLimit-Limit': limit.toString(), - // 'X-RateLimit-Remaining': remaining.toString(), - // 'X-RateLimit-Reset': reset.toString(), - // }, - // }); - // } - // } + if (!success) { + return new Response('일일 자동완성 최대 사용량 50회를 초과하였습니다', { + status: 429, + headers: { + 'X-RateLimit-Limit': limit.toString(), + 'X-RateLimit-Remaining': remaining.toString(), + 'X-RateLimit-Reset': reset.toString(), + }, + }); + } + } let { prompt } = await req.json(); const response = await openai.createChatCompletion({ model: 'gpt-3.5-turbo', diff --git a/src/components/create/memo/EditorForm.tsx b/src/components/create/memo/EditorForm.tsx index e659ddb2..688aef8b 100644 --- a/src/components/create/memo/EditorForm.tsx +++ b/src/components/create/memo/EditorForm.tsx @@ -51,7 +51,7 @@ export default function EditorForm() { }); }, onError: (err) => { - console.log(err); + notifyToast(err.message, "error"); }, }); diff --git a/src/components/editor/components/FakeEditor.tsx b/src/components/editor/components/FakeEditor.tsx index ec3a8fff..0403c088 100644 --- a/src/components/editor/components/FakeEditor.tsx +++ b/src/components/editor/components/FakeEditor.tsx @@ -21,8 +21,8 @@ export default function FakeEditor({ editor }: { editor: Editor | null }) { ref={edirotRef} className={`fixed flex flex-col top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 w-3/4 h-3/4 bg-white z-10 top-1/12 p-6 rounded-xl shadow-lg overflow-y-auto`} > -
- chatGpt로 생성된 답변입니다. 답변이 정확하지 않을 수 있습니다. +
+ chatGpt로 생성된 답변입니다. 답변이 정확하지 않을 수 있으며, 일일 최대 사용량은 50회로 제한됩니다.