From 220f89372e24df0da535b51efb2b9f610efaad13 Mon Sep 17 00:00:00 2001 From: Hector Lozano Date: Tue, 14 Oct 2025 00:37:20 +0200 Subject: [PATCH 1/4] feat: consumiendo AI de openrouter desde el backend sin modo stream --- client/package-lock.json | 149 +++++------------------- client/package.json | 2 - client/src/hooks/useAI.ts | 66 +++++------ client/src/lib/openrouter.ts | 5 - client/src/pages/boards/service.ts | 13 +++ client/src/utils/endpoints.ts | 5 + server/package-lock.json | 154 +++++++++++++------------ server/package.json | 8 +- server/src/app.ts | 3 + server/src/controllers/aiController.ts | 54 +++++++++ server/src/routes/ai.routes.ts | 9 ++ 11 files changed, 229 insertions(+), 239 deletions(-) delete mode 100644 client/src/lib/openrouter.ts create mode 100644 server/src/controllers/aiController.ts create mode 100644 server/src/routes/ai.routes.ts diff --git a/client/package-lock.json b/client/package-lock.json index 88f25cb..30a7af1 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -10,11 +10,9 @@ "dependencies": { "@hello-pangea/dnd": "18.0.1", "@iconify/react": "6.0.2", - "@openrouter/ai-sdk-provider": "1.2.0", "@redux-devtools/extension": "3.3.0", "@tailwindcss/vite": "4.1.11", "@tinymce/tinymce-react": "6.3.0", - "ai": "5.0.54", "animejs": "4.2.1", "axios": "1.12.1", "clsx": "2.1.1", @@ -70,51 +68,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@ai-sdk/gateway": { - "version": "1.0.30", - "resolved": "https://registry.npmjs.org/@ai-sdk/gateway/-/gateway-1.0.30.tgz", - "integrity": "sha512-QdrSUryr/CLcsCISokLHOImcHj1adGXk1yy4B3qipqLhcNc33Kj/O/3crI790Qp85oDx7sc4vm7R4raf9RA/kg==", - "license": "Apache-2.0", - "dependencies": { - "@ai-sdk/provider": "2.0.0", - "@ai-sdk/provider-utils": "3.0.10" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "zod": "^3.25.76 || ^4.1.8" - } - }, - "node_modules/@ai-sdk/provider": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@ai-sdk/provider/-/provider-2.0.0.tgz", - "integrity": "sha512-6o7Y2SeO9vFKB8lArHXehNuusnpddKPk7xqL7T2/b+OvXMRIXUO1rR4wcv1hAFUAT9avGZshty3Wlua/XA7TvA==", - "license": "Apache-2.0", - "dependencies": { - "json-schema": "^0.4.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@ai-sdk/provider-utils": { - "version": "3.0.10", - "resolved": "https://registry.npmjs.org/@ai-sdk/provider-utils/-/provider-utils-3.0.10.tgz", - "integrity": "sha512-T1gZ76gEIwffep6MWI0QNy9jgoybUHE7TRaHB5k54K8mF91ciGFlbtCGxDYhMH3nCRergKwYFIDeFF0hJSIQHQ==", - "license": "Apache-2.0", - "dependencies": { - "@ai-sdk/provider": "2.0.0", - "@standard-schema/spec": "^1.0.0", - "eventsource-parser": "^3.0.5" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "zod": "^3.25.76 || ^4.1.8" - } - }, "node_modules/@ampproject/remapping": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", @@ -180,6 +133,7 @@ "integrity": "sha512-2BCOP7TN8M+gVDj7/ht3hsaO/B/n5oDbiAyyvnRlNOs+u1o+JWNYTQrmpuNp1/Wq2gcFrI01JAW+paEKDMx/CA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.3", @@ -538,6 +492,7 @@ } ], "license": "MIT", + "peer": true, "engines": { "node": ">=18" }, @@ -561,6 +516,7 @@ } ], "license": "MIT", + "peer": true, "engines": { "node": ">=18" } @@ -1375,28 +1331,6 @@ "node": ">= 8" } }, - "node_modules/@openrouter/ai-sdk-provider": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@openrouter/ai-sdk-provider/-/ai-sdk-provider-1.2.0.tgz", - "integrity": "sha512-stuIwq7Yb7DNmk3GuCtz+oS3nZOY4TXEV3V5KsknDGQN7Fpu3KRMQVWRc1J073xKdf0FC9EHOctSyzsACmp5Ag==", - "license": "Apache-2.0", - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "ai": "^5.0.0", - "zod": "^3.24.1 || ^v4" - } - }, - "node_modules/@opentelemetry/api": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz", - "integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==", - "license": "Apache-2.0", - "engines": { - "node": ">=8.0.0" - } - }, "node_modules/@pkgjs/parseargs": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", @@ -1720,12 +1654,6 @@ "integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==", "license": "MIT" }, - "node_modules/@standard-schema/spec": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.0.0.tgz", - "integrity": "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==", - "license": "MIT" - }, "node_modules/@tailwindcss/node": { "version": "4.1.11", "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.11.tgz", @@ -2103,8 +2031,7 @@ "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@types/babel__core": { "version": "7.20.5", @@ -2187,6 +2114,7 @@ "integrity": "sha512-ut5FthK5moxFKH2T1CUOC6ctR67rQRvvHdFLCD2Ql6KXmMuCrjsSsRI9UsLCm9M18BMwClv4pn327UvB7eeO1w==", "devOptional": true, "license": "MIT", + "peer": true, "dependencies": { "undici-types": "~7.8.0" } @@ -2197,6 +2125,7 @@ "integrity": "sha512-AwAfQ2Wa5bCx9WP8nZL2uMZWod7J7/JSplxbTmBQ5ms6QpqNYm672H0Vu9ZVKVngQ+ii4R/byguVEUZQyeg44g==", "devOptional": true, "license": "MIT", + "peer": true, "dependencies": { "csstype": "^3.0.2" } @@ -2207,6 +2136,7 @@ "integrity": "sha512-4hOiT/dwO8Ko0gV1m/TJZYk3y0KBnY9vzDh7W+DH17b2HFSOGgdj33dhihPeuy3l0q23+4e+hoXHV6hCC4dCXw==", "dev": true, "license": "MIT", + "peer": true, "peerDependencies": { "@types/react": "^19.0.0" } @@ -2280,6 +2210,7 @@ "integrity": "sha512-3MyiDfrfLeK06bi/g9DqJxP5pV74LNv4rFTyvGDmT3x2p1yp1lOd+qYZfiRPIOf/oON+WRZR5wxxuF85qOar+w==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.35.1", "@typescript-eslint/types": "8.35.1", @@ -2680,6 +2611,7 @@ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -2799,24 +2731,6 @@ "node": ">= 14" } }, - "node_modules/ai": { - "version": "5.0.54", - "resolved": "https://registry.npmjs.org/ai/-/ai-5.0.54.tgz", - "integrity": "sha512-eM3EH4VVCWRMfs17r8HF8RtCN/+vBdpWOQoHSVooIfB0BZerOHyrktrVoDP6G6xatUzGLTvJT3rMKLkbPTLPBg==", - "license": "Apache-2.0", - "dependencies": { - "@ai-sdk/gateway": "1.0.30", - "@ai-sdk/provider": "2.0.0", - "@ai-sdk/provider-utils": "3.0.10", - "@opentelemetry/api": "1.9.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "zod": "^3.25.76 || ^4.1.8" - } - }, "node_modules/ajv": { "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", @@ -3063,6 +2977,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "baseline-browser-mapping": "^2.8.9", "caniuse-lite": "^1.0.30001746", @@ -3361,7 +3276,8 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/data-urls": { "version": "5.0.0", @@ -3462,8 +3378,7 @@ "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz", "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/dunder-proto": { "version": "1.0.1", @@ -3722,6 +3637,7 @@ "integrity": "sha512-zmxXPNMOXmwm9E0yQLi5uqXHs7uq2UIiqEKo3Gq+3fwo1XrJ+hijAZImyF7hclW3E6oHz43Yk3RP8at6OTKflQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.12.1", @@ -3945,15 +3861,6 @@ "bare-events": "^2.7.0" } }, - "node_modules/eventsource-parser": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.6.tgz", - "integrity": "sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg==", - "license": "MIT", - "engines": { - "node": ">=18.0.0" - } - }, "node_modules/expect-type": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.2.2.tgz", @@ -4581,6 +4488,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "@babel/runtime": "^7.27.6" }, @@ -5025,12 +4933,6 @@ "dev": true, "license": "MIT" }, - "node_modules/json-schema": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", - "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==", - "license": "(AFL-2.1 OR BSD-3-Clause)" - }, "node_modules/json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", @@ -5395,7 +5297,6 @@ "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==", "dev": true, "license": "MIT", - "peer": true, "bin": { "lz-string": "bin/bin.js" } @@ -5836,6 +5737,7 @@ "integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==", "dev": true, "license": "MIT", + "peer": true, "bin": { "prettier": "bin/prettier.cjs" }, @@ -5939,7 +5841,6 @@ "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "ansi-regex": "^5.0.1", "ansi-styles": "^5.0.0", @@ -5955,7 +5856,6 @@ "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=10" }, @@ -6035,6 +5935,7 @@ "resolved": "https://registry.npmjs.org/react/-/react-19.1.0.tgz", "integrity": "sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==", "license": "MIT", + "peer": true, "engines": { "node": ">=0.10.0" } @@ -6044,6 +5945,7 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.0.tgz", "integrity": "sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==", "license": "MIT", + "peer": true, "dependencies": { "scheduler": "^0.26.0" }, @@ -6129,8 +6031,7 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/react-redux": { "version": "9.2.0", @@ -6246,7 +6147,8 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz", "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/redux-thunk": { "version": "3.1.0", @@ -6965,6 +6867,7 @@ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -6976,7 +6879,8 @@ "version": "8.1.2", "resolved": "https://registry.npmjs.org/tinymce/-/tinymce-8.1.2.tgz", "integrity": "sha512-KITxHEEHRlxC5xOnxA123eAJ67NgsWxNphtItWt9TRu07DiTZrWIqJeIKRX9euE51/l3kJO4WQiqoBXKTJJGsA==", - "license": "GPL-2.0-or-later" + "license": "GPL-2.0-or-later", + "peer": true }, "node_modules/tinypool": { "version": "1.1.1", @@ -7119,6 +7023,7 @@ "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", "devOptional": true, "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -7303,6 +7208,7 @@ "resolved": "https://registry.npmjs.org/vite/-/vite-7.1.5.tgz", "integrity": "sha512-4cKBO9wR75r0BeIWWWId9XK9Lj6La5X846Zw9dFfzMRw38IlTk2iCcUt6hsyiDRcPidc55ZParFYDXi0nXOeLQ==", "license": "MIT", + "peer": true, "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.5.0", @@ -7417,6 +7323,7 @@ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -7430,6 +7337,7 @@ "integrity": "sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@types/chai": "^5.2.2", "@vitest/expect": "3.2.4", @@ -7796,6 +7704,7 @@ "resolved": "https://registry.npmjs.org/zod/-/zod-4.1.11.tgz", "integrity": "sha512-WPsqwxITS2tzx1bzhIKsEs19ABD5vmCVa4xBo2tq/SrV4RNZtfws1EnCWQXM6yh8bD08a1idvkB5MZSBiZsjwg==", "license": "MIT", + "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" } diff --git a/client/package.json b/client/package.json index 68e5585..4dd1b6a 100644 --- a/client/package.json +++ b/client/package.json @@ -16,11 +16,9 @@ "dependencies": { "@hello-pangea/dnd": "18.0.1", "@iconify/react": "6.0.2", - "@openrouter/ai-sdk-provider": "1.2.0", "@redux-devtools/extension": "3.3.0", "@tailwindcss/vite": "4.1.11", "@tinymce/tinymce-react": "6.3.0", - "ai": "5.0.54", "animejs": "4.2.1", "axios": "1.12.1", "clsx": "2.1.1", diff --git a/client/src/hooks/useAI.ts b/client/src/hooks/useAI.ts index bc9c66f..a2a7e24 100644 --- a/client/src/hooks/useAI.ts +++ b/client/src/hooks/useAI.ts @@ -1,8 +1,8 @@ import { useRef, useState } from "react"; -import { openrouter } from "../lib/openrouter"; -import { streamText } from "ai"; -import { formattedToHTML } from "../utils/formatted"; +import { generateDescription } from "../pages/boards/service"; import { useTranslation } from "react-i18next"; +import { formattedToHTML } from "../utils/formatted"; +import axios from "axios"; export const useAI = () => { const { t } = useTranslation(); @@ -14,42 +14,39 @@ export const useAI = () => { onChunk?: (chunk: string) => void, ): Promise => { abortAction.current = new AbortController(); + const signal = abortAction.current?.signal; + setLoading(true); setError(null); - const signal = abortAction.current.signal; - - let description = ""; - try { - const { textStream } = streamText({ - model: openrouter("google/gemma-3n-e2b-it:free"), - prompt: t("boardModal.AI.prompt", { - title, - }), - abortSignal: signal, - }); - - let buffer = ""; - for await (const chunk of textStream) { - description += chunk; - buffer += chunk; - if (buffer.length >= 30) { - const formatted = formattedToHTML(description); - onChunk?.(formatted); // descripcion en tiempo real - buffer = ""; - } - } - if (buffer) { - const formatted = formattedToHTML(description); - onChunk?.(formatted); - } + const description = await generateDescription(title, signal); + // let buffer = ""; + // for await (const chunk of textStream) { + // description += chunk; + // buffer += chunk; + // if (buffer.length >= 30) { + // const formatted = formattedToHTML(description); + // onChunk?.(formatted); // descripcion en tiempo real + // buffer = ""; + // } + // } + // if (buffer) { + // const formatted = formattedToHTML(description); + // onChunk?.(formatted); + // } + // return description; + onChunk?.(formattedToHTML(description)); + console.log("DESCRIPCION: ", description); return description; } catch (err: unknown) { - if (err instanceof Error) { - setError(err.message || t("boardModal.AI.error")); + if (axios.isCancel(err)) { + setError(t("boardModal.AI.canceled")); + } else if (err instanceof Error) { if (err.message.includes("limit") || err.message.includes("quota")) { - setError(err.message || t("boardModal.AI.maxPetitions")); + setError(t("boardModal.AI.maxPetitions")); + } else { + setError(err.message || t("boardModal.AI.error")); } } return null; @@ -58,15 +55,14 @@ export const useAI = () => { abortAction.current = null; } }; - const stopGenerationDescription = () => { abortAction.current?.abort(); + setLoading(false); }; - return { generateDescriptionFromTitle, + stopGenerationDescription, loading, error, - stopGenerationDescription, }; }; diff --git a/client/src/lib/openrouter.ts b/client/src/lib/openrouter.ts deleted file mode 100644 index cecd0e5..0000000 --- a/client/src/lib/openrouter.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { createOpenRouter } from "@openrouter/ai-sdk-provider"; - -export const openrouter = createOpenRouter({ - apiKey: import.meta.env.VITE_OPENROUTER_KEY -}) \ No newline at end of file diff --git a/client/src/pages/boards/service.ts b/client/src/pages/boards/service.ts index 897e5f9..728cbf2 100644 --- a/client/src/pages/boards/service.ts +++ b/client/src/pages/boards/service.ts @@ -1,5 +1,6 @@ import { apiClient } from "../../api/client"; import { + AI_ENDPOINT, BOARD_ENDPOINTS, CARD_ENDPOINT, LIST_ENDPOINT, @@ -145,3 +146,15 @@ export const removeAssignee = async ( `${CARD_ENDPOINT.CARDS}/removeAssignee/${cardId}/${assigneeId}`, ); }; + +export const generateDescription = async ( + title: string, + signal?: AbortSignal, +) => { + const response = await apiClient.post<{ description: string }>( + AI_ENDPOINT.GENERATE, + { title }, + { signal }, + ); + return response.data.description; +}; diff --git a/client/src/utils/endpoints.ts b/client/src/utils/endpoints.ts index 0bd29de..60e401a 100644 --- a/client/src/utils/endpoints.ts +++ b/client/src/utils/endpoints.ts @@ -23,6 +23,7 @@ export const USER_ENDPOINTS = { BY_ID: byId("profile"), RESET_PASSWORD: `/api/${VERSION}/auth/reset_password`, CHANGE_PASSWORD: `/api/${VERSION}/auth/change_password`, + AI_GENERATE: `/api/${VERSION}/ai/generate-description`, }; export const BOARD_ENDPOINTS = { @@ -40,6 +41,10 @@ export const CARD_ENDPOINT = { BY_ID: byId("cards"), }; +export const AI_ENDPOINT = { + GENERATE: `${USER_ENDPOINTS.AI_GENERATE}`, +}; + export const getAccountEndpoint = (typeAccount: TypeAccount): string | null => { if (!TypeAccountEnum.options.includes(typeAccount)) { throw new Error("Not valid account."); diff --git a/server/package-lock.json b/server/package-lock.json index 6005414..926338f 100644 --- a/server/package-lock.json +++ b/server/package-lock.json @@ -9,7 +9,7 @@ "version": "1.0.0", "license": "ISC", "dependencies": { - "@prisma/client": "6.15.0", + "@prisma/client": "6.17.1", "@react-email/components": "0.5.5", "@react-email/render": "1.3.1", "amqplib": "0.10.9", @@ -34,7 +34,7 @@ "socket.io": "4.8.1", "socket.io-client": "4.8.1", "xss": "1.0.15", - "zod": "4.1.5" + "zod": "4.1.12" }, "devDependencies": { "@eslint/js": "9.31.0", @@ -62,7 +62,7 @@ "globals": "16.3.0", "jest": "30.1.3", "prettier": "3.6.2", - "prisma": "6.15.0", + "prisma": "6.17.1", "supertest": "7.1.4", "ts-jest": "29.4.1", "tsx": "4.20.5", @@ -101,6 +101,7 @@ "integrity": "sha512-2BCOP7TN8M+gVDj7/ht3hsaO/B/n5oDbiAyyvnRlNOs+u1o+JWNYTQrmpuNp1/Wq2gcFrI01JAW+paEKDMx/CA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.3", @@ -2972,9 +2973,9 @@ } }, "node_modules/@prisma/client": { - "version": "6.15.0", - "resolved": "https://registry.npmjs.org/@prisma/client/-/client-6.15.0.tgz", - "integrity": "sha512-wR2LXUbOH4cL/WToatI/Y2c7uzni76oNFND7+23ypLllBmIS8e3ZHhO+nud9iXSXKFt1SoM3fTZvHawg63emZw==", + "version": "6.17.1", + "resolved": "https://registry.npmjs.org/@prisma/client/-/client-6.17.1.tgz", + "integrity": "sha512-zL58jbLzYamjnNnmNA51IOZdbk5ci03KviXCuB0Tydc9btH2kDWsi1pQm2VecviRTM7jGia0OPPkgpGnT3nKvw==", "hasInstallScript": true, "license": "Apache-2.0", "engines": { @@ -2994,9 +2995,9 @@ } }, "node_modules/@prisma/config": { - "version": "6.15.0", - "resolved": "https://registry.npmjs.org/@prisma/config/-/config-6.15.0.tgz", - "integrity": "sha512-KMEoec9b2u6zX0EbSEx/dRpx1oNLjqJEBZYyK0S3TTIbZ7GEGoVyGyFRk4C72+A38cuPLbfQGQvgOD+gBErKlA==", + "version": "6.17.1", + "resolved": "https://registry.npmjs.org/@prisma/config/-/config-6.17.1.tgz", + "integrity": "sha512-fs8wY6DsvOCzuiyWVckrVs1LOcbY4LZNz8ki4uUIQ28jCCzojTGqdLhN2Jl5lDnC1yI8/gNIKpsWDM8pLhOdwA==", "devOptional": true, "license": "Apache-2.0", "dependencies": { @@ -3007,53 +3008,53 @@ } }, "node_modules/@prisma/debug": { - "version": "6.15.0", - "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-6.15.0.tgz", - "integrity": "sha512-y7cSeLuQmyt+A3hstAs6tsuAiVXSnw9T55ra77z0nbNkA8Lcq9rNcQg6PI00by/+WnE/aMRJ/W7sZWn2cgIy1g==", + "version": "6.17.1", + "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-6.17.1.tgz", + "integrity": "sha512-Vf7Tt5Wh9XcndpbmeotuqOMLWPTjEKCsgojxXP2oxE1/xYe7PtnP76hsouG9vis6fctX+TxgmwxTuYi/+xc7dQ==", "devOptional": true, "license": "Apache-2.0" }, "node_modules/@prisma/engines": { - "version": "6.15.0", - "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-6.15.0.tgz", - "integrity": "sha512-opITiR5ddFJ1N2iqa7mkRlohCZqVSsHhRcc29QXeldMljOf4FSellLT0J5goVb64EzRTKcIDeIsJBgmilNcKxA==", + "version": "6.17.1", + "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-6.17.1.tgz", + "integrity": "sha512-D95Ik3GYZkqZ8lSR4EyFOJ/tR33FcYRP8kK61o+WMsyD10UfJwd7+YielflHfKwiGodcqKqoraWw8ElAgMDbPw==", "devOptional": true, "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { - "@prisma/debug": "6.15.0", - "@prisma/engines-version": "6.15.0-5.85179d7826409ee107a6ba334b5e305ae3fba9fb", - "@prisma/fetch-engine": "6.15.0", - "@prisma/get-platform": "6.15.0" + "@prisma/debug": "6.17.1", + "@prisma/engines-version": "6.17.1-1.272a37d34178c2894197e17273bf937f25acdeac", + "@prisma/fetch-engine": "6.17.1", + "@prisma/get-platform": "6.17.1" } }, "node_modules/@prisma/engines-version": { - "version": "6.15.0-5.85179d7826409ee107a6ba334b5e305ae3fba9fb", - "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-6.15.0-5.85179d7826409ee107a6ba334b5e305ae3fba9fb.tgz", - "integrity": "sha512-a/46aK5j6L3ePwilZYEgYDPrhBQ/n4gYjLxT5YncUTJJNRnTCVjPF86QdzUOLRdYjCLfhtZp9aum90W0J+trrg==", + "version": "6.17.1-1.272a37d34178c2894197e17273bf937f25acdeac", + "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-6.17.1-1.272a37d34178c2894197e17273bf937f25acdeac.tgz", + "integrity": "sha512-17140E3huOuD9lMdJ9+SF/juOf3WR3sTJMVyyenzqUPbuH+89nPhSWcrY+Mf7tmSs6HvaO+7S+HkELinn6bhdg==", "devOptional": true, "license": "Apache-2.0" }, "node_modules/@prisma/fetch-engine": { - "version": "6.15.0", - "resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-6.15.0.tgz", - "integrity": "sha512-xcT5f6b+OWBq6vTUnRCc7qL+Im570CtwvgSj+0MTSGA1o9UDSKZ/WANvwtiRXdbYWECpyC3CukoG3A04VTAPHw==", + "version": "6.17.1", + "resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-6.17.1.tgz", + "integrity": "sha512-AYZiHOs184qkDMiTeshyJCtyL4yERkjfTkJiSJdYuSfc24m94lTNL5+GFinZ6vVz+ktX4NJzHKn1zIFzGTWrWg==", "devOptional": true, "license": "Apache-2.0", "dependencies": { - "@prisma/debug": "6.15.0", - "@prisma/engines-version": "6.15.0-5.85179d7826409ee107a6ba334b5e305ae3fba9fb", - "@prisma/get-platform": "6.15.0" + "@prisma/debug": "6.17.1", + "@prisma/engines-version": "6.17.1-1.272a37d34178c2894197e17273bf937f25acdeac", + "@prisma/get-platform": "6.17.1" } }, "node_modules/@prisma/get-platform": { - "version": "6.15.0", - "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-6.15.0.tgz", - "integrity": "sha512-Jbb+Xbxyp05NSR1x2epabetHiXvpO8tdN2YNoWoA/ZsbYyxxu/CO/ROBauIFuMXs3Ti+W7N7SJtWsHGaWte9Rg==", + "version": "6.17.1", + "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-6.17.1.tgz", + "integrity": "sha512-AKEn6fsfz0r482S5KRDFlIGEaq9wLNcgalD1adL+fPcFFblIKs1sD81kY/utrHdqKuVC6E1XSRpegDK3ZLL4Qg==", "devOptional": true, "license": "Apache-2.0", "dependencies": { - "@prisma/debug": "6.15.0" + "@prisma/debug": "6.17.1" } }, "node_modules/@react-email/body": { @@ -3853,6 +3854,7 @@ "integrity": "sha512-gTtSdWX9xiMPA/7MV9STjJOOYtWwIJIYxkQxnSV1U3xcE+mnJSH3f6zI0RYP+ew66WSlZ5ed+h0VCxsvdC1jJg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.41.0", "@typescript-eslint/types": "8.41.0", @@ -4347,6 +4349,7 @@ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -4714,6 +4717,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "baseline-browser-mapping": "^2.8.3", "caniuse-lite": "^1.0.30001741", @@ -4818,22 +4822,6 @@ } } }, - "node_modules/c12/node_modules/chokidar": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", - "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "readdirp": "^4.0.1" - }, - "engines": { - "node": ">= 14.16.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, "node_modules/c12/node_modules/dotenv": { "version": "16.6.1", "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", @@ -4847,20 +4835,6 @@ "url": "https://dotenvx.com" } }, - "node_modules/c12/node_modules/readdirp": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", - "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", - "devOptional": true, - "license": "MIT", - "engines": { - "node": ">= 14.18.0" - }, - "funding": { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - }, "node_modules/call-bind-apply-helpers": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", @@ -4957,6 +4931,22 @@ "node": ">=10" } }, + "node_modules/chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/ci-info": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.3.0.tgz", @@ -5891,6 +5881,7 @@ "integrity": "sha512-QldCVh/ztyKJJZLr4jXNUByx3gR+TDYZCRXEktiZoUR3PGy4qCmSbkxcIle8GEwGpb5JBZazlaJ/CxLidXdEbQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.12.1", @@ -7213,6 +7204,7 @@ "integrity": "sha512-Ry+p2+NLk6u8Agh5yVqELfUJvRfV51hhVBRIB5yZPY7mU0DGBmOuFG5GebZbMbm86cdQNK0fhJuDX8/1YorISQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@jest/core": "30.1.3", "@jest/types": "30.0.5", @@ -9786,6 +9778,7 @@ "resolved": "https://registry.npmjs.org/pg/-/pg-8.16.3.tgz", "integrity": "sha512-enxc1h0jA/aq5oSDMvqyW3q89ra6XIIDZgCX9vkMrnz5DFTw/Ny3Li2lFQ+pt3L6MCgm/5o2o8HW9hiJji+xvw==", "license": "MIT", + "peer": true, "dependencies": { "pg-connection-string": "^2.9.1", "pg-pool": "^3.10.1", @@ -10073,15 +10066,16 @@ } }, "node_modules/prisma": { - "version": "6.15.0", - "resolved": "https://registry.npmjs.org/prisma/-/prisma-6.15.0.tgz", - "integrity": "sha512-E6RCgOt+kUVtjtZgLQDBJ6md2tDItLJNExwI0XJeBc1FKL+Vwb+ovxXxuok9r8oBgsOXBA33fGDuE/0qDdCWqQ==", + "version": "6.17.1", + "resolved": "https://registry.npmjs.org/prisma/-/prisma-6.17.1.tgz", + "integrity": "sha512-ac6h0sM1Tg3zu8NInY+qhP/S9KhENVaw9n1BrGKQVFu05JT5yT5Qqqmb8tMRIE3ZXvVj4xcRA5yfrsy4X7Yy5g==", "devOptional": true, "hasInstallScript": true, "license": "Apache-2.0", + "peer": true, "dependencies": { - "@prisma/config": "6.15.0", - "@prisma/engines": "6.15.0" + "@prisma/config": "6.17.1", + "@prisma/engines": "6.17.1" }, "bin": { "prisma": "build/index.js" @@ -10245,6 +10239,7 @@ "resolved": "https://registry.npmjs.org/react/-/react-19.1.1.tgz", "integrity": "sha512-w8nqGImo45dmMIfljjMwOGtbmC/mk4CMYhWIicdSflH91J9TyCyczcPFXJzrZ/ZXcgGRFeP6BU0BEJTw6tZdfQ==", "license": "MIT", + "peer": true, "engines": { "node": ">=0.10.0" } @@ -10254,7 +10249,6 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.1.tgz", "integrity": "sha512-Dlq/5LAZgF0Gaz6yiqZCf6VCcZs1ghAJyrsu84Q/GT0gV+mCxbfmKNoGRKBYMJ8IEdGPqu49YWXD02GCknEDkw==", "license": "MIT", - "peer": true, "dependencies": { "scheduler": "^0.26.0" }, @@ -10298,6 +10292,20 @@ "node": ">= 6" } }, + "node_modules/readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">= 14.18.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -10463,8 +10471,7 @@ "version": "0.26.0", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.26.0.tgz", "integrity": "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/selderee": { "version": "0.11.0", @@ -11483,6 +11490,7 @@ "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", "devOptional": true, "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -11985,9 +11993,9 @@ } }, "node_modules/zod": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/zod/-/zod-4.1.5.tgz", - "integrity": "sha512-rcUUZqlLJgBC33IT3PNMgsCq6TzLQEG/Ei/KTCU0PedSWRMAXoOUN+4t/0H+Q8bdnLPdqUYnvboJT0bn/229qg==", + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.1.12.tgz", + "integrity": "sha512-JInaHOamG8pt5+Ey8kGmdcAcg3OL9reK8ltczgHTAwNhMys/6ThXHityHxVV2p3fkw/c+MAvBHFVYHFZDmjMCQ==", "license": "MIT", "funding": { "url": "https://github.com/sponsors/colinhacks" diff --git a/server/package.json b/server/package.json index d480069..b915e79 100644 --- a/server/package.json +++ b/server/package.json @@ -22,13 +22,13 @@ "license": "ISC", "description": "", "dependencies": { - "@prisma/client": "6.15.0", + "@prisma/client": "6.17.1", "@react-email/components": "0.5.5", "@react-email/render": "1.3.1", "amqplib": "0.10.9", "bcrypt": "6.0.0", - "cors": "2.8.5", "concurrently": "9.2.1", + "cors": "2.8.5", "date-fns": "4.1.0", "dotenv": "16.4.7", "ejs": "3.1.10", @@ -47,7 +47,7 @@ "socket.io": "4.8.1", "socket.io-client": "4.8.1", "xss": "1.0.15", - "zod": "4.1.5" + "zod": "4.1.12" }, "devDependencies": { "@eslint/js": "9.31.0", @@ -75,7 +75,7 @@ "globals": "16.3.0", "jest": "30.1.3", "prettier": "3.6.2", - "prisma": "6.15.0", + "prisma": "6.17.1", "supertest": "7.1.4", "ts-jest": "29.4.1", "tsx": "4.20.5", diff --git a/server/src/app.ts b/server/src/app.ts index 2e5c1db..78fb7c6 100644 --- a/server/src/app.ts +++ b/server/src/app.ts @@ -9,6 +9,7 @@ import logger from "morgan"; import path from "node:path"; import profileRoutes from "./routes/profile.routes"; +import aiRoutes from "./routes/ai.routes"; import { ApiValidationError, @@ -35,6 +36,8 @@ app.use("/api/v1/lists", listRoutes); app.use("/api/v1/cards", cardRoutes); //Profile... app.use("/api/v1/profile", profileRoutes); +// AI +app.use("/api/v1/ai", aiRoutes) app.use((req: Request, res: Response, next: NextFunction) => { if (!req.url.startsWith("/api")) { diff --git a/server/src/controllers/aiController.ts b/server/src/controllers/aiController.ts new file mode 100644 index 0000000..7059058 --- /dev/null +++ b/server/src/controllers/aiController.ts @@ -0,0 +1,54 @@ +import type { Request, Response } from "express"; +// uso temporal +export const generateDescription = async (req: Request, res: Response) => { + const OPENROUTER_URL = "https://openrouter.ai/api/v1/chat/completions"; + const AI_MODEL = "google/gemma-3-27b-it:free"; + try { + const { title } = req.body; + + if (!title) { + return res.status(400).json({ error: "Missing title" }); + } + + const prompt = `Genera una descripción detallada y productiva para la tarea titulada: "${title}".`; + + const response = await fetch(OPENROUTER_URL, { + method: "POST", + headers: { + Authorization: `Bearer ${process.env.OPENROUTER_KEY}`, + "Content-Type": "application/json", + }, + body: JSON.stringify({ + model: AI_MODEL, + messages: [ + { + role: "system", + content: + "Eres un asistente que ayuda a redactar descripciones de tareas.", + }, + { + role: "user", + content: prompt, + }, + ], + stream: false, // WIP: pending stream + }), + }); + + if (!response.ok) { + const errText = await response.text(); + throw new Error(errText); + } + + const data = await response.json(); + const description = data.choices?.[0]?.message?.content || ""; + + return res.json({ description }); + } catch (error) { + console.error("Error generating AI description:", error); + return res.status(500).json({ + error: "Error generating description", + message: (error as Error).message, + }); + } +}; diff --git a/server/src/routes/ai.routes.ts b/server/src/routes/ai.routes.ts new file mode 100644 index 0000000..089c5a0 --- /dev/null +++ b/server/src/routes/ai.routes.ts @@ -0,0 +1,9 @@ +import { Router } from "express"; +import { generateDescription } from "../controllers/aiController"; +import * as jwtAuth from "../middlewares/jwtAuthMiddleware"; + +const router = Router(); + +router.post("/generate-description", jwtAuth.guard, generateDescription); + +export default router; From 5e780b704db10909e071cf46a4bb058026756a4b Mon Sep 17 00:00:00 2001 From: Hector Lozano Date: Tue, 14 Oct 2025 00:39:15 +0200 Subject: [PATCH 2/4] feat: usando el hook useAI con peticion al backend al servicio de openrouter --- client/src/hooks/useAI.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/client/src/hooks/useAI.ts b/client/src/hooks/useAI.ts index a2a7e24..4f9c035 100644 --- a/client/src/hooks/useAI.ts +++ b/client/src/hooks/useAI.ts @@ -37,7 +37,6 @@ export const useAI = () => { // } // return description; onChunk?.(formattedToHTML(description)); - console.log("DESCRIPCION: ", description); return description; } catch (err: unknown) { if (axios.isCancel(err)) { From 526a860c0212d18548c415e9788404db9b266689 Mon Sep 17 00:00:00 2001 From: Hector Lozano Date: Tue, 14 Oct 2025 00:55:07 +0200 Subject: [PATCH 3/4] chore: update security dependencies happy-dom --- client/package-lock.json | 8 ++++---- client/package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/client/package-lock.json b/client/package-lock.json index 30a7af1..e0d596f 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -49,7 +49,7 @@ "eslint-plugin-react-hooks": "5.2.0", "eslint-plugin-react-refresh": "0.4.20", "globals": "16.3.0", - "happy-dom": "19.0.2", + "happy-dom": "20.0.0", "i18next-scanner": "4.6.0", "jsdom": "26.1.0", "prettier": "3.6.2", @@ -4322,9 +4322,9 @@ } }, "node_modules/happy-dom": { - "version": "19.0.2", - "resolved": "https://registry.npmjs.org/happy-dom/-/happy-dom-19.0.2.tgz", - "integrity": "sha512-831CLbgDyjRbd2lApHZFsBDe56onuFcjsCBPodzWpzedTpeDr8CGZjs7iEIdNW1DVwSFRecfwzLpVyGBPamwGA==", + "version": "20.0.0", + "resolved": "https://registry.npmjs.org/happy-dom/-/happy-dom-20.0.0.tgz", + "integrity": "sha512-GkWnwIFxVGCf2raNrxImLo397RdGhLapj5cT3R2PT7FwL62Ze1DROhzmYW7+J3p9105DYMVenEejEbnq5wA37w==", "dev": true, "license": "MIT", "dependencies": { diff --git a/client/package.json b/client/package.json index 4dd1b6a..e388614 100644 --- a/client/package.json +++ b/client/package.json @@ -55,7 +55,7 @@ "eslint-plugin-react-hooks": "5.2.0", "eslint-plugin-react-refresh": "0.4.20", "globals": "16.3.0", - "happy-dom": "19.0.2", + "happy-dom": "20.0.0", "i18next-scanner": "4.6.0", "jsdom": "26.1.0", "prettier": "3.6.2", From 821109cf31586d45e2c8339ec03c85a7779792f1 Mon Sep 17 00:00:00 2001 From: Hector Lozano Date: Tue, 14 Oct 2025 00:55:07 +0200 Subject: [PATCH 4/4] BREAKING-CHANGE: update security dependencies happy-dom --- client/package-lock.json | 8 ++++---- client/package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/client/package-lock.json b/client/package-lock.json index 30a7af1..e0d596f 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -49,7 +49,7 @@ "eslint-plugin-react-hooks": "5.2.0", "eslint-plugin-react-refresh": "0.4.20", "globals": "16.3.0", - "happy-dom": "19.0.2", + "happy-dom": "20.0.0", "i18next-scanner": "4.6.0", "jsdom": "26.1.0", "prettier": "3.6.2", @@ -4322,9 +4322,9 @@ } }, "node_modules/happy-dom": { - "version": "19.0.2", - "resolved": "https://registry.npmjs.org/happy-dom/-/happy-dom-19.0.2.tgz", - "integrity": "sha512-831CLbgDyjRbd2lApHZFsBDe56onuFcjsCBPodzWpzedTpeDr8CGZjs7iEIdNW1DVwSFRecfwzLpVyGBPamwGA==", + "version": "20.0.0", + "resolved": "https://registry.npmjs.org/happy-dom/-/happy-dom-20.0.0.tgz", + "integrity": "sha512-GkWnwIFxVGCf2raNrxImLo397RdGhLapj5cT3R2PT7FwL62Ze1DROhzmYW7+J3p9105DYMVenEejEbnq5wA37w==", "dev": true, "license": "MIT", "dependencies": { diff --git a/client/package.json b/client/package.json index 4dd1b6a..e388614 100644 --- a/client/package.json +++ b/client/package.json @@ -55,7 +55,7 @@ "eslint-plugin-react-hooks": "5.2.0", "eslint-plugin-react-refresh": "0.4.20", "globals": "16.3.0", - "happy-dom": "19.0.2", + "happy-dom": "20.0.0", "i18next-scanner": "4.6.0", "jsdom": "26.1.0", "prettier": "3.6.2",