diff --git a/apps/web/package.json b/apps/web/package.json index 0c7bbb0..d343a76 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -41,7 +41,8 @@ "react-icons": "^5.2.0", "recharts": "^2.12.7", "superjson": "2.2.1", - "zod": "^3.23.0" + "zod": "^3.23.0", + "zod-validation-error": "^3.3.0" }, "devDependencies": { "@svgr/webpack": "^8.1.0", diff --git a/apps/web/src/app/api/chat/route.ts b/apps/web/src/app/api/chat/route.ts new file mode 100644 index 0000000..6329ec8 --- /dev/null +++ b/apps/web/src/app/api/chat/route.ts @@ -0,0 +1,55 @@ +import { env } from "@/env"; +import { z } from "zod"; +import { fromZodError } from "zod-validation-error"; + +export async function POST(req: Request) { + const body = z + .object({ + messages: z + .object({ + role: z.enum(["user", "assistant"] as const), + content: z.string(), + }) + .array(), + }) + .safeParse(await req.json()); + + if (body.error) { + const error = fromZodError(body.error).toString(); + return new Response(error, { status: 400 }); + } + + const res = await fetch(env.BACKEND_URL + "/questions", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + question: body.data.messages.pop()?.content, + isBahasa: true, + }), + }); + + if (!res.body) { + return new Response("No response body", { status: 500 }); + } + const reader = res.body.getReader(); + const stream = new ReadableStream({ + async start(controller) { + while (true) { + const { done, value } = await reader.read(); + if (done) { + controller.close(); + break; + } + // Convert Uint8Array to string and remove "data: " prefix + const text = new TextDecoder().decode(value).replace(/^data:\s*/gm, ""); + + // Enqueue the modified chunk back into the stream + controller.enqueue(new TextEncoder().encode(text)); + } + }, + }); + + return new Response(stream); +} diff --git a/apps/web/src/env.ts b/apps/web/src/env.ts index 2612592..eff6de2 100644 --- a/apps/web/src/env.ts +++ b/apps/web/src/env.ts @@ -20,6 +20,7 @@ export const env = createEnv({ POSTGRES_URL: z.string().min(1), PORT: z.string().default("3000"), VERCEL_TOKEN: z.string().min(1), + BACKEND_URL: z.string().url().min(1), }, /** diff --git a/bun.lockb b/bun.lockb index 789762f..6fc48ab 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/packages/ui/src/chat.tsx b/packages/ui/src/chat.tsx index 929a9da..51fd90d 100644 --- a/packages/ui/src/chat.tsx +++ b/packages/ui/src/chat.tsx @@ -29,13 +29,26 @@ function useChatScroll(dep: T) { return ref; } +function getChatApi() { + if (process.env.CHAT_API) { + return process.env.CHAT_API; + } + + if (process.env.NODE_ENV !== "development") { + return "https://tanya-in.vercel.app/api/chat"; + } + + // default to '/api/chat' + return undefined; +} + export function Chat() { const { messages, isLoading, append: mutate, } = useChat({ - api: "http://localhost:8000/generate_stream", + api: getChatApi(), streamMode: "text", onError: (error) => toast.error(error.message), }); diff --git a/yarn.lock b/yarn.lock index 0e38340..b0b5cb3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1,21 +1,21 @@ # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. # yarn lockfile v1 -# bun ./bun.lockb --hash: 198042D389D2B9C3-e0d552a6a876c6f9-247ACD9CB417E852-c12409ea040b0ad6 +# bun ./bun.lockb --hash: 94C751655075BDC3-8b5d55dd641c93d5-439A8AD76AE90791-92fbf465f864692f -"@ai-sdk/provider@0.0.5": - version "0.0.5" - resolved "https://registry.npmjs.org/@ai-sdk/provider/-/provider-0.0.5.tgz" - integrity sha512-TZDldBZ5clAsNwJ2PSeo/b1uILj9a2lvi0rnOj2RCNZDgaVqFRVIAnKyeHusCRv2lzhPIw03B3fiGI6VoLzOAA== +"@ai-sdk/provider@0.0.6": + version "0.0.6" + resolved "https://registry.npmjs.org/@ai-sdk/provider/-/provider-0.0.6.tgz" + integrity sha512-kiPqIsSnUimckaUn87WepxfjPNdy8SXlPP7P6yWuG3e1NmyFHcyuH6EBBZxXLmu0oZtkb+QEeP3UDWGSc+wwKQ== dependencies: json-schema "0.4.0" -"@ai-sdk/provider-utils@0.0.8": - version "0.0.8" - resolved "https://registry.npmjs.org/@ai-sdk/provider-utils/-/provider-utils-0.0.8.tgz" - integrity sha512-J/ZNvFhORd3gCeK3jFvNrxp1Dnvy6PvPq21RJ+OsIEjsoHeKQaHALCWG0aJunXDuzd+Mck/lCg7LqA0qmIbHIg== +"@ai-sdk/provider-utils@0.0.9": + version "0.0.9" + resolved "https://registry.npmjs.org/@ai-sdk/provider-utils/-/provider-utils-0.0.9.tgz" + integrity sha512-bZFTVp/GIZ3Yz4VejYs0B93bmnwfk+RoSHAQxAe9WamkQFrZLuNgd2ngAFjF6b4naLynWe1ZCk+dLWEtozCciw== dependencies: - "@ai-sdk/provider" "0.0.5" + "@ai-sdk/provider" "0.0.6" eventsource-parser "1.1.2" nanoid "3.3.6" secure-json-parse "2.7.0" @@ -1048,9 +1048,9 @@ regenerator-runtime "^0.14.0" "@babel/runtime-corejs3@^7.9.2": - version "7.24.5" - resolved "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.24.5.tgz" - integrity sha512-GWO0mgzNMLWaSYM4z4NVIuY0Cd1fl8cPnuetuddu5w/qGuvt5Y7oUi/kvvQGK9xgOkFJDQX2heIvTRn/OQ1XTg== + version "7.24.6" + resolved "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.24.6.tgz" + integrity sha512-tbC3o8uHK9xMgMsvUm9qGqxVpbv6yborMBLbDteHIc7JDNHsTV0vDMQ5j1O1NXvO+BDELtL9KgoWYaUVIVGt8w== dependencies: core-js-pure "^3.30.2" regenerator-runtime "^0.14.0" @@ -3867,6 +3867,7 @@ recharts "^2.12.7" superjson "2.2.1" zod "^3.23.0" + zod-validation-error "^3.3.0" "@tootallnate/quickjs-emscripten@^0.23.0": version "0.23.0" @@ -4349,12 +4350,12 @@ aggregate-error@^3.0.0: indent-string "^4.0.0" ai@^3.1.12: - version "3.1.12" - resolved "https://registry.npmjs.org/ai/-/ai-3.1.12.tgz" - integrity sha512-XlurBw1sdgQCFmCTPYjKjpm+fPS6iY+tLb/PYNUEjZn3bhqosAkcqUkGJTsFP49OMAWO1Lm2oPthCakKcn6Lzw== + version "3.1.14" + resolved "https://registry.npmjs.org/ai/-/ai-3.1.14.tgz" + integrity sha512-yW9ipraLm2PzzWc74eWUUVUIVZbO/Ggv//83iBfDkOTndrBmFqyIk6DtVVzf+RwKNUYuZzj8v5MKcgnHQhceXw== dependencies: - "@ai-sdk/provider" "0.0.5" - "@ai-sdk/provider-utils" "0.0.8" + "@ai-sdk/provider" "0.0.6" + "@ai-sdk/provider-utils" "0.0.9" secure-json-parse "2.7.0" eventsource-parser "1.1.2" jsondiffpatch "0.6.0" @@ -4805,7 +4806,7 @@ chalk@^4.0.0, chalk@^4.1.0, chalk@^4.1.1, chalk@^4.1.2: ansi-styles "^4.1.0" supports-color "^7.1.0" -chalk@5.3.0, chalk@^5.3.0: +chalk@^5.3.0, chalk@~5.3.0: version "5.3.0" resolved "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz" integrity sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w== @@ -5024,7 +5025,7 @@ commander@^10.0.0: resolved "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz" integrity sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug== -commander@12.1.0: +commander@~12.1.0: version "12.1.0" resolved "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz" integrity sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA== @@ -5333,7 +5334,7 @@ debug@^3.2.7: dependencies: ms "^2.1.1" -debug@4, debug@4.3.4, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4: +debug@4, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4, debug@~4.3.4: version "4.3.4" resolved "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz" integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== @@ -6134,7 +6135,7 @@ execa@5.1.1, execa@^5.0.0: signal-exit "^3.0.3" strip-final-newline "^2.0.0" -execa@8.0.1: +execa@~8.0.1: version "8.0.1" resolved "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz" integrity sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg== @@ -7275,7 +7276,7 @@ lilconfig@^3.0.0: resolved "https://registry.npmjs.org/lilconfig/-/lilconfig-3.0.0.tgz" integrity sha512-K2U4W2Ff5ibV7j7ydLr+zLAkIg5JJ4lPn1Ltsdt+Tz/IjQ8buJ55pZAxoP34lqIiwtF9iAvtLv3JGv7CAyAg+g== -lilconfig@3.1.1: +lilconfig@~3.1.1: version "3.1.1" resolved "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.1.tgz" integrity sha512-O18pf7nyvHTckunPWCV1XUNXU1piu01y2b7ATJ0ppkUkk8ocqVWBrYjJBCwHDjD/ZWcfyrA0P4gKhzWGi5EINQ== @@ -7286,22 +7287,22 @@ lines-and-columns@^1.1.6: integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg== lint-staged@^15.2.2: - version "15.2.4" - resolved "https://registry.npmjs.org/lint-staged/-/lint-staged-15.2.4.tgz" - integrity sha512-3F9KRQIS2fVDGtCkBp4Bx0jswjX7zUcKx6OF0ZeY1prksUyKPRIIUqZhIUYAstJfvj6i48VFs4dwVIbCYwvTYQ== - dependencies: - chalk "5.3.0" - commander "12.1.0" - debug "4.3.4" - execa "8.0.1" - lilconfig "3.1.1" - listr2 "8.2.1" - micromatch "4.0.6" - pidtree "0.6.0" - string-argv "0.3.2" - yaml "2.4.2" - -listr2@8.2.1: + version "15.2.5" + resolved "https://registry.npmjs.org/lint-staged/-/lint-staged-15.2.5.tgz" + integrity sha512-j+DfX7W9YUvdzEZl3Rk47FhDF6xwDBV5wwsCPw6BwWZVPYJemusQmvb9bRsW23Sqsaa+vRloAWogbK4BUuU2zA== + dependencies: + chalk "~5.3.0" + commander "~12.1.0" + debug "~4.3.4" + execa "~8.0.1" + lilconfig "~3.1.1" + listr2 "~8.2.1" + micromatch "~4.0.7" + pidtree "~0.6.0" + string-argv "~0.3.2" + yaml "~2.4.2" + +listr2@~8.2.1: version "8.2.1" resolved "https://registry.npmjs.org/listr2/-/listr2-8.2.1.tgz" integrity sha512-irTfvpib/rNiD637xeevjO2l3Z5loZmuaRi0L0YE5LfijwVY96oyVn0DFD3o/teAok7nfobMG1THvvcHh/BP6g== @@ -7541,13 +7542,13 @@ micromatch@^4.0.4, micromatch@^4.0.5: braces "^3.0.2" picomatch "^2.3.1" -micromatch@4.0.6: - version "4.0.6" - resolved "https://registry.npmjs.org/micromatch/-/micromatch-4.0.6.tgz" - integrity sha512-Y4Ypn3oujJYxJcMacVgcs92wofTHxp9FzfDpQON4msDefoC0lb3ETvQLOdLcbhSwU1bz8HrL/1sygfBIHudrkQ== +micromatch@~4.0.7: + version "4.0.7" + resolved "https://registry.npmjs.org/micromatch/-/micromatch-4.0.7.tgz" + integrity sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q== dependencies: braces "^3.0.3" - picomatch "^4.0.2" + picomatch "^2.3.1" mimic-fn@^2.1.0: version "2.1.0" @@ -8128,12 +8129,7 @@ picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.3.1: resolved "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz" integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== -picomatch@^4.0.2: - version "4.0.2" - resolved "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz" - integrity sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg== - -pidtree@0.6.0: +pidtree@~0.6.0: version "0.6.0" resolved "https://registry.npmjs.org/pidtree/-/pidtree-0.6.0.tgz" integrity sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g== @@ -8900,7 +8896,7 @@ socks-proxy-agent@^8.0.2: debug "^4.3.4" socks "^2.7.1" -solid-js@^1.2, solid-js@^1.7.7: +solid-js@^1.2: version "1.8.17" resolved "https://registry.npmjs.org/solid-js/-/solid-js-1.8.17.tgz" integrity sha512-E0FkUgv9sG/gEBWkHr/2XkBluHb1fkrHywUgA6o6XolPDCJ4g1HaLmQufcBBhiF36ee40q+HpG/vCZu7fLpI3Q== @@ -8959,9 +8955,9 @@ spdx-expression-parse@^3.0.0: spdx-license-ids "^3.0.0" spdx-license-ids@^3.0.0: - version "3.0.17" - resolved "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.17.tgz" - integrity sha512-sh8PWc/ftMqAAdFiBu6Fy6JUOYjqDJBJvIhpfDMyHrr0Rbp5liZqd4TjtQ/RgfLjKFZb+LMx5hpml5qOWy0qvg== + version "3.0.18" + resolved "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.18.tgz" + integrity sha512-xxRs31BqRYHwiMzudOrpSiHtZ8i/GeionCBDSilhYRj+9gIcI8wCZTlXZKu9vZIVqViP3dcp9qE5G6AlIaD+TQ== split2@^3.0.0: version "3.2.2" @@ -8987,7 +8983,7 @@ streamsearch@^1.1.0: resolved "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz" integrity sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg== -string-argv@0.3.2: +string-argv@~0.3.2: version "0.3.2" resolved "https://registry.npmjs.org/string-argv/-/string-argv-0.3.2.tgz" integrity sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q== @@ -9164,7 +9160,7 @@ supports-preserve-symlinks-flag@^1.0.0: resolved "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz" integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== -"svelte@^3.0.0 || ^4.0.0", svelte@^4.0.0: +svelte@^4.0.0: version "4.2.17" resolved "https://registry.npmjs.org/svelte/-/svelte-4.2.17.tgz" integrity sha512-N7m1YnoXtRf5wya5Gyx3TWuTddI4nAyayyIWFojiWV5IayDYNV5i2mRp/7qNGol4DtxEYxljmrbgp1HM6hUbmQ== @@ -9751,7 +9747,7 @@ victory-vendor@^36.6.8: d3-time "^3.0.0" d3-timer "^3.0.1" -vue@3.4.27, "vue@>=3.2.26 < 4", vue@^3.3.4: +vue@3.4.27, "vue@>=3.2.26 < 4": version "3.4.27" resolved "https://registry.npmjs.org/vue/-/vue-3.4.27.tgz" integrity sha512-8s/56uK6r01r1icG/aEOHqyMVxd1bkYcSe9j8HcKtr/xTOFWvnzIVTehNW+5Yt89f+DLBe4A569pnZLS5HzAMA== @@ -9912,7 +9908,7 @@ yaml@^2.3.4: resolved "https://registry.npmjs.org/yaml/-/yaml-2.3.4.tgz" integrity sha512-8aAvwVUSHpfEqTQ4w/KMlf3HcRdt50E5ODIQJBw1fQ5RL34xabzxtUlzTXVqc4rkZsPbvrXKWnABCD7kWSmocA== -yaml@2.4.2: +yaml@~2.4.2: version "2.4.2" resolved "https://registry.npmjs.org/yaml/-/yaml-2.4.2.tgz" integrity sha512-B3VqDZ+JAg1nZpaEmWtTXUlBneoGx6CPM9b0TENK6aoSu5t73dItudwdgmi6tHlIZZId4dZ9skcAQ2UbcyAeVA== @@ -9950,7 +9946,7 @@ yocto-queue@^0.1.0: resolved "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz" integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== -zod@*, zod@^3.0.0, zod@^3.20.2, zod@^3.22.4, zod@^3.23.0: +zod@*, zod@^3.0.0, zod@^3.18.0, zod@^3.20.2, zod@^3.22.4, zod@^3.23.0: version "3.23.8" resolved "https://registry.npmjs.org/zod/-/zod-3.23.8.tgz" integrity sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g== @@ -9959,3 +9955,8 @@ zod-to-json-schema@3.22.5: version "3.22.5" resolved "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.22.5.tgz" integrity sha512-+akaPo6a0zpVCCseDed504KBJUQpEW5QZw7RMneNmKw+fGaML1Z9tUNLnHHAC8x6dzVRO1eB2oEMyZRnuBZg7Q== + +zod-validation-error@^3.3.0: + version "3.3.0" + resolved "https://registry.npmjs.org/zod-validation-error/-/zod-validation-error-3.3.0.tgz" + integrity sha512-Syib9oumw1NTqEv4LT0e6U83Td9aVRk9iTXPUQr1otyV1PuXQKOvOwhMNqZIq5hluzHP2pMgnOmHEo7kPdI2mw==