From 235fb538e11a0fecc1d712d7a4b3946aa0987f29 Mon Sep 17 00:00:00 2001 From: luisfepsale Date: Wed, 22 Oct 2025 12:52:23 -0300 Subject: [PATCH] Primeiro Commit --- .env.example | 17 + .gitignore | 5 + package-lock.json | 116 +---- src/components/ChatHeader.tsx | 19 +- src/components/audio/GenerationView.tsx | 141 +++--- src/components/audio/TranscriptionView.tsx | 81 ++-- src/components/chat/ChatInput.tsx | 53 ++- src/components/chat/ChatSidebar.tsx | 434 +++++++++++++------ src/components/chat/ChatView.tsx | 135 +++++- src/components/images/ImageView.tsx | 239 ++++++++-- src/config/models.ts | 132 ++++++ src/services/api.ts | 127 ++++++ src/services/audioGeneration.ts | 197 +++++++++ src/services/chat.ts | 481 +++++++++++++++++++++ src/services/imageGeneration.ts | 218 ++++++++++ src/services/index.ts | 12 + src/services/transcription.ts | 110 +++++ src/services/types.ts | 30 ++ src/vite-env.d.ts | 11 + 19 files changed, 2132 insertions(+), 426 deletions(-) create mode 100644 .env.example create mode 100644 src/config/models.ts create mode 100644 src/services/api.ts create mode 100644 src/services/audioGeneration.ts create mode 100644 src/services/chat.ts create mode 100644 src/services/imageGeneration.ts create mode 100644 src/services/index.ts create mode 100644 src/services/transcription.ts create mode 100644 src/services/types.ts diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..b0e0288 --- /dev/null +++ b/.env.example @@ -0,0 +1,17 @@ +# API Configuration +# Base URL da API do HGTX +VITE_API_BASE_URL=https://prod-hgtx-intelligence-n8n.hgtx.com.br + +# API Key para autenticação +# IMPORTANTE: Mantenha esta chave segura e não compartilhe publicamente +VITE_API_KEY=your-api-key-here + +# User Configuration +# Configure com os dados do usuário/estabelecimento +VITE_USER_EMAIL=user@example.com +VITE_ESTABELECIMENTO_ID=1 + +# Instruções: +# 1. Copie este arquivo para .env +# 2. Substitua os valores acima com as configurações reais +# 3. NUNCA commite o arquivo .env no git diff --git a/.gitignore b/.gitignore index a547bf3..e59e80a 100644 --- a/.gitignore +++ b/.gitignore @@ -12,6 +12,11 @@ dist dist-ssr *.local +# Environment variables +.env +.env.local +.env.production + # Editor directories and files .vscode/* !.vscode/extensions.json diff --git a/package-lock.json b/package-lock.json index 9f2863f..fc7bedc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -83,7 +83,6 @@ "version": "5.2.0", "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==", - "dev": true, "license": "MIT", "engines": { "node": ">=10" @@ -847,7 +846,6 @@ "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", - "dev": true, "license": "ISC", "dependencies": { "string-width": "^5.1.2", @@ -865,7 +863,6 @@ "version": "0.3.5", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", - "dev": true, "license": "MIT", "dependencies": { "@jridgewell/set-array": "^1.2.1", @@ -880,7 +877,6 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "dev": true, "license": "MIT", "engines": { "node": ">=6.0.0" @@ -890,7 +886,6 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", - "dev": true, "license": "MIT", "engines": { "node": ">=6.0.0" @@ -900,14 +895,12 @@ "version": "1.5.0", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", - "dev": true, "license": "MIT" }, "node_modules/@jridgewell/trace-mapping": { "version": "0.3.25", "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", - "dev": true, "license": "MIT", "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", @@ -918,7 +911,6 @@ "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dev": true, "license": "MIT", "dependencies": { "@nodelib/fs.stat": "2.0.5", @@ -932,7 +924,6 @@ "version": "2.0.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "dev": true, "license": "MIT", "engines": { "node": ">= 8" @@ -942,7 +933,6 @@ "version": "1.2.8", "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dev": true, "license": "MIT", "dependencies": { "@nodelib/fs.scandir": "2.1.5", @@ -956,7 +946,6 @@ "version": "0.11.0", "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", - "dev": true, "license": "MIT", "optional": true, "engines": { @@ -2929,14 +2918,14 @@ "version": "15.7.13", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.13.tgz", "integrity": "sha512-hCZTSvwbzWGvhqxp/RqVqwU999pBf2vp7hzIjiYOsl8wqOmUxkQ6ddw1cV3l8811+kdUFus/q4d1Y3E3SyEifA==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/@types/react": { "version": "18.3.23", "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.23.tgz", "integrity": "sha512-/LDXMQh55EzZQ0uVAZmKKhfENivEvWz6E+EYzh+/MCjMhNsotd+ZHhBGIjFDTi6+fz0OhQQQLbTgdQIxxCsC0w==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@types/prop-types": "*", @@ -2947,7 +2936,7 @@ "version": "18.3.7", "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz", "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==", - "dev": true, + "devOptional": true, "license": "MIT", "peerDependencies": { "@types/react": "^18.0.0" @@ -3269,7 +3258,6 @@ "version": "6.1.0", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", - "dev": true, "license": "MIT", "engines": { "node": ">=12" @@ -3282,7 +3270,6 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, "license": "MIT", "dependencies": { "color-convert": "^2.0.1" @@ -3298,14 +3285,12 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", - "dev": true, "license": "MIT" }, "node_modules/anymatch": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", - "dev": true, "license": "ISC", "dependencies": { "normalize-path": "^3.0.0", @@ -3319,7 +3304,6 @@ "version": "5.0.2", "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", - "dev": true, "license": "MIT" }, "node_modules/argparse": { @@ -3400,14 +3384,12 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true, "license": "MIT" }, "node_modules/binary-extensions": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -3431,7 +3413,6 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", - "dev": true, "license": "MIT", "dependencies": { "fill-range": "^7.1.1" @@ -3500,7 +3481,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==", - "dev": true, "license": "MIT", "engines": { "node": ">= 6" @@ -3548,7 +3528,6 @@ "version": "3.6.0", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", - "dev": true, "license": "MIT", "dependencies": { "anymatch": "~3.1.2", @@ -3573,7 +3552,6 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, "license": "ISC", "dependencies": { "is-glob": "^4.0.1" @@ -3622,7 +3600,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, "license": "MIT", "dependencies": { "color-name": "~1.1.4" @@ -3635,7 +3612,6 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, "license": "MIT" }, "node_modules/combined-stream": { @@ -3654,7 +3630,6 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", - "dev": true, "license": "MIT", "engines": { "node": ">= 6" @@ -3671,7 +3646,6 @@ "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", - "dev": true, "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", @@ -3685,7 +3659,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", - "dev": true, "license": "MIT", "bin": { "cssesc": "bin/cssesc" @@ -3881,14 +3854,12 @@ "version": "1.2.2", "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==", - "dev": true, "license": "Apache-2.0" }, "node_modules/dlv": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", - "dev": true, "license": "MIT" }, "node_modules/dom-helpers": { @@ -3919,7 +3890,6 @@ "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", - "dev": true, "license": "MIT" }, "node_modules/electron-to-chromium": { @@ -3961,7 +3931,6 @@ "version": "9.2.2", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", - "dev": true, "license": "MIT" }, "node_modules/es-define-property": { @@ -4285,7 +4254,6 @@ "version": "3.3.2", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", - "dev": true, "license": "MIT", "dependencies": { "@nodelib/fs.stat": "^2.0.2", @@ -4302,7 +4270,6 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, "license": "ISC", "dependencies": { "is-glob": "^4.0.1" @@ -4329,7 +4296,6 @@ "version": "1.17.1", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==", - "dev": true, "license": "ISC", "dependencies": { "reusify": "^1.0.4" @@ -4352,7 +4318,6 @@ "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", - "dev": true, "license": "MIT", "dependencies": { "to-regex-range": "^5.0.1" @@ -4423,7 +4388,6 @@ "version": "3.3.0", "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.0.tgz", "integrity": "sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==", - "dev": true, "license": "ISC", "dependencies": { "cross-spawn": "^7.0.0", @@ -4470,7 +4434,6 @@ "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, "hasInstallScript": true, "license": "MIT", "optional": true, @@ -4540,7 +4503,6 @@ "version": "10.4.5", "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", - "dev": true, "license": "ISC", "dependencies": { "foreground-child": "^3.1.0", @@ -4561,7 +4523,6 @@ "version": "6.0.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "dev": true, "license": "ISC", "dependencies": { "is-glob": "^4.0.3" @@ -4574,7 +4535,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", - "dev": true, "license": "MIT", "dependencies": { "balanced-match": "^1.0.0" @@ -4584,7 +4544,6 @@ "version": "9.0.5", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", - "dev": true, "license": "ISC", "dependencies": { "brace-expansion": "^2.0.1" @@ -4737,7 +4696,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, "license": "MIT", "dependencies": { "binary-extensions": "^2.0.0" @@ -4750,7 +4708,6 @@ "version": "2.15.1", "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.1.tgz", "integrity": "sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==", - "dev": true, "license": "MIT", "dependencies": { "hasown": "^2.0.2" @@ -4766,7 +4723,6 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -4776,7 +4732,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -4786,7 +4741,6 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, "license": "MIT", "dependencies": { "is-extglob": "^2.1.1" @@ -4799,7 +4753,6 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, "license": "MIT", "engines": { "node": ">=0.12.0" @@ -4809,14 +4762,12 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true, "license": "ISC" }, "node_modules/jackspeak": { "version": "3.4.3", "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", - "dev": true, "license": "BlueOak-1.0.0", "dependencies": { "@isaacs/cliui": "^8.0.2" @@ -4832,7 +4783,6 @@ "version": "1.21.6", "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.6.tgz", "integrity": "sha512-2yTgeWTWzMWkHu6Jp9NKgePDaYHbntiwvYuuJLbbN9vl7DC9DvXKOB2BC3ZZ92D3cvV/aflH0osDfwpHepQ53w==", - "dev": true, "license": "MIT", "bin": { "jiti": "bin/jiti.js" @@ -4906,7 +4856,6 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", - "dev": true, "license": "MIT", "engines": { "node": ">=14" @@ -4919,7 +4868,6 @@ "version": "1.2.4", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", - "dev": true, "license": "MIT" }, "node_modules/locate-path": { @@ -5429,7 +5377,6 @@ "version": "10.4.3", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", - "dev": true, "license": "ISC" }, "node_modules/lucide-react": { @@ -5463,7 +5410,6 @@ "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true, "license": "MIT", "engines": { "node": ">= 8" @@ -5473,7 +5419,6 @@ "version": "4.0.8", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", - "dev": true, "license": "MIT", "dependencies": { "braces": "^3.0.3", @@ -5521,7 +5466,6 @@ "version": "7.1.2", "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", - "dev": true, "license": "ISC", "engines": { "node": ">=16 || 14 >=14.17" @@ -5538,7 +5482,6 @@ "version": "2.7.0", "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", - "dev": true, "license": "MIT", "dependencies": { "any-promise": "^1.0.0", @@ -5550,7 +5493,6 @@ "version": "3.3.11", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", - "dev": true, "funding": [ { "type": "github", @@ -5593,7 +5535,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -5622,7 +5563,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", - "dev": true, "license": "MIT", "engines": { "node": ">= 6" @@ -5682,7 +5622,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", - "dev": true, "license": "BlueOak-1.0.0" }, "node_modules/parent-module": { @@ -5712,7 +5651,6 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -5722,14 +5660,12 @@ "version": "1.0.7", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "dev": true, "license": "MIT" }, "node_modules/path-scurry": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", - "dev": true, "license": "BlueOak-1.0.0", "dependencies": { "lru-cache": "^10.2.0", @@ -5746,14 +5682,12 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "dev": true, "license": "ISC" }, "node_modules/picomatch": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, "license": "MIT", "engines": { "node": ">=8.6" @@ -5766,7 +5700,6 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", - "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -5776,7 +5709,6 @@ "version": "4.0.6", "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz", "integrity": "sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==", - "dev": true, "license": "MIT", "engines": { "node": ">= 6" @@ -5786,7 +5718,6 @@ "version": "8.5.6", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", - "dev": true, "funding": [ { "type": "opencollective", @@ -5815,7 +5746,6 @@ "version": "15.1.0", "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz", "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==", - "dev": true, "license": "MIT", "dependencies": { "postcss-value-parser": "^4.0.0", @@ -5833,7 +5763,6 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.0.1.tgz", "integrity": "sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==", - "dev": true, "license": "MIT", "dependencies": { "camelcase-css": "^2.0.1" @@ -5853,7 +5782,6 @@ "version": "4.0.2", "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-4.0.2.tgz", "integrity": "sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==", - "dev": true, "funding": [ { "type": "opencollective", @@ -5889,7 +5817,6 @@ "version": "6.2.0", "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz", "integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==", - "dev": true, "funding": [ { "type": "opencollective", @@ -5915,7 +5842,6 @@ "version": "6.1.2", "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", - "dev": true, "license": "MIT", "dependencies": { "cssesc": "^3.0.0", @@ -5929,7 +5855,6 @@ "version": "4.2.0", "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", - "dev": true, "license": "MIT" }, "node_modules/prelude-ls": { @@ -5979,7 +5904,6 @@ "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true, "funding": [ { "type": "github", @@ -6203,7 +6127,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==", - "dev": true, "license": "MIT", "dependencies": { "pify": "^2.3.0" @@ -6213,7 +6136,6 @@ "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, "license": "MIT", "dependencies": { "picomatch": "^2.2.1" @@ -6258,7 +6180,6 @@ "version": "1.22.8", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", - "dev": true, "license": "MIT", "dependencies": { "is-core-module": "^2.13.0", @@ -6286,7 +6207,6 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", - "dev": true, "license": "MIT", "engines": { "iojs": ">=1.0.0", @@ -6333,7 +6253,6 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "dev": true, "funding": [ { "type": "github", @@ -6379,7 +6298,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, "license": "MIT", "dependencies": { "shebang-regex": "^3.0.0" @@ -6392,7 +6310,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -6402,7 +6319,6 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", - "dev": true, "license": "ISC", "engines": { "node": ">=14" @@ -6425,7 +6341,6 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", - "dev": true, "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" @@ -6435,7 +6350,6 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", - "dev": true, "license": "MIT", "dependencies": { "eastasianwidth": "^0.2.0", @@ -6454,7 +6368,6 @@ "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, "license": "MIT", "dependencies": { "emoji-regex": "^8.0.0", @@ -6469,7 +6382,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -6479,14 +6391,12 @@ "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, "license": "MIT" }, "node_modules/string-width-cjs/node_modules/strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1" @@ -6499,7 +6409,6 @@ "version": "7.1.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", - "dev": true, "license": "MIT", "dependencies": { "ansi-regex": "^6.0.1" @@ -6516,7 +6425,6 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1" @@ -6529,7 +6437,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -6552,7 +6459,6 @@ "version": "3.35.0", "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.0.tgz", "integrity": "sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==", - "dev": true, "license": "MIT", "dependencies": { "@jridgewell/gen-mapping": "^0.3.2", @@ -6588,7 +6494,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -6611,7 +6516,6 @@ "version": "3.4.17", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.17.tgz", "integrity": "sha512-w33E2aCvSDP0tW9RZuNXadXlkHXqFzSkQew/aIa2i/Sj8fThxwovwlXHSPXTbAHwEIhBFXAedUhP2tueAKP8Og==", - "dev": true, "license": "MIT", "dependencies": { "@alloc/quick-lru": "^5.2.0", @@ -6658,7 +6562,6 @@ "version": "3.3.1", "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", - "dev": true, "license": "MIT", "dependencies": { "any-promise": "^1.0.0" @@ -6668,7 +6571,6 @@ "version": "1.6.0", "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", - "dev": true, "license": "MIT", "dependencies": { "thenify": ">= 3.1.0 < 4" @@ -6687,7 +6589,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, "license": "MIT", "dependencies": { "is-number": "^7.0.0" @@ -6713,7 +6614,6 @@ "version": "0.1.13", "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", - "dev": true, "license": "Apache-2.0" }, "node_modules/tslib": { @@ -6877,7 +6777,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "dev": true, "license": "MIT" }, "node_modules/vaul": { @@ -6979,7 +6878,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, "license": "ISC", "dependencies": { "isexe": "^2.0.0" @@ -7005,7 +6903,6 @@ "version": "8.1.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", - "dev": true, "license": "MIT", "dependencies": { "ansi-styles": "^6.1.0", @@ -7024,7 +6921,6 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, "license": "MIT", "dependencies": { "ansi-styles": "^4.0.0", @@ -7042,7 +6938,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -7052,14 +6947,12 @@ "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, "license": "MIT" }, "node_modules/wrap-ansi-cjs/node_modules/string-width": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, "license": "MIT", "dependencies": { "emoji-regex": "^8.0.0", @@ -7074,7 +6967,6 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1" @@ -7087,7 +6979,6 @@ "version": "6.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", - "dev": true, "license": "MIT", "engines": { "node": ">=12" @@ -7100,7 +6991,6 @@ "version": "2.6.0", "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.6.0.tgz", "integrity": "sha512-a6ae//JvKDEra2kdi1qzCyrJW/WZCgFi8ydDV+eXExl95t+5R+ijnqHJbz9tmMh8FUjx3iv2fCQ4dclAQlO2UQ==", - "dev": true, "license": "ISC", "bin": { "yaml": "bin.mjs" diff --git a/src/components/ChatHeader.tsx b/src/components/ChatHeader.tsx index 9c6f50a..9c346b3 100644 --- a/src/components/ChatHeader.tsx +++ b/src/components/ChatHeader.tsx @@ -25,21 +25,28 @@ interface ChatHeaderProps { onSystemPromptChange?: (prompt: string) => void; } -export const ChatHeader = ({ +export const ChatHeader = ({ showModelSelector = false, - selectedModel = "ChatGPT 4.1", + selectedModel = "GPT-4o", onModelChange, systemPrompt = "Você é um assistente útil e prestativo.", onSystemPromptChange }: ChatHeaderProps) => { const [isPersonalityOpen, setIsPersonalityOpen] = useState(false); const [tempSystemPrompt, setTempSystemPrompt] = useState(systemPrompt); - + const models = [ - "ChatGPT 4.1", - "ChatGPT 5", + "GPT-4.1", + "GPT-4o", + "GPT-5 Mini", + "Gemini 2.0 Flash", "Claude Sonnet 4.5", - "Claude Opus 4.1", + "DeepSeek V3.2 Chat", + "DeepSeek V3.2 Reasoner", + "Gemini 2.5 Flash", + "Gemini 2.5 Flash-Lite", + "Claude Haiku 4.5", + "GPT-4o Mini", ]; const handleSavePersonality = () => { diff --git a/src/components/audio/GenerationView.tsx b/src/components/audio/GenerationView.tsx index c9a057e..a1a9c90 100644 --- a/src/components/audio/GenerationView.tsx +++ b/src/components/audio/GenerationView.tsx @@ -14,6 +14,7 @@ import { SelectValue, } from "@/components/ui/select"; import { useToast } from "@/hooks/use-toast"; +import { audioGenerationService, VOICE_OPTIONS, VoiceType } from "@/services/audioGeneration"; interface GeneratedAudio { id: string; @@ -24,49 +25,10 @@ interface GeneratedAudio { timestamp: Date; } -const VOICE_OPTIONS = { - alloy: { - label: "Alloy", - gender: "Masculina", - style: "Neutra, equilibrada, tom corporativo", - description: "Boa para tutoriais e comunicações institucionais." - }, - echo: { - label: "Echo", - gender: "Masculina", - style: "Forte e profissional, mais grave", - description: "Ideal para voz de autoridade ou locução firme." - }, - fable: { - label: "Fable", - gender: "Feminina", - style: "Narrativa, calorosa e envolvente", - description: "Ótima para storytelling e áudios empáticos." - }, - onyx: { - label: "Onyx", - gender: "Masculina", - style: "Grave, autoritária, impactante", - description: "Excelente para trailers, mensagens sérias ou institucionais." - }, - nova: { - label: "Nova", - gender: "Feminina", - style: "Brilhante, animada, energética", - description: "Boa para vídeos curtos, marketing ou conteúdos leves." - }, - shimmer: { - label: "Shimmer", - gender: "Feminina", - style: "Suave, otimista, clara", - description: "Boa para mensagens acolhedoras, explicações e IA conversacional." - } -}; - export const GenerationView = () => { const [textToSpeech, setTextToSpeech] = useState(""); const [isProcessing, setIsProcessing] = useState(false); - const [selectedVoice, setSelectedVoice] = useState("alloy"); + const [selectedVoice, setSelectedVoice] = useState("alloy"); const [generatedAudio, setGeneratedAudio] = useState(null); const [audioHistory, setAudioHistory] = useState([]); const [audioSearchQuery, setAudioSearchQuery] = useState(""); @@ -79,32 +41,67 @@ export const GenerationView = () => { } }, []); - const handleGenerateAudio = () => { - setIsProcessing(true); - - setTimeout(() => { - const audio: GeneratedAudio = { - id: Date.now().toString(), - text: textToSpeech, - voice: selectedVoice, - voiceLabel: VOICE_OPTIONS[selectedVoice].label, - audioUrl: "data:audio/mp3;base64,//sample", - timestamp: new Date(), - }; - - setGeneratedAudio(audio); - - const newHistory = [audio, ...audioHistory].slice(0, 10); - setAudioHistory(newHistory); - localStorage.setItem('audioHistory', JSON.stringify(newHistory)); - - setIsProcessing(false); - + const handleGenerateAudio = async () => { + // Valida o texto antes de enviar + const validation = audioGenerationService.validateText(textToSpeech); + if (!validation.valid) { toast({ - title: "Áudio gerado com sucesso", - description: `Voz: ${VOICE_OPTIONS[selectedVoice].label}`, + title: "Texto inválido", + description: validation.error, + variant: "destructive", }); - }, 2000); + return; + } + + setIsProcessing(true); + + try { + // Chama o serviço de geração de áudio + const response = await audioGenerationService.generateAudio({ + message: textToSpeech, + voice: selectedVoice, + }); + + // Verifica se a geração foi bem-sucedida + if (response.success) { + console.log('URL do áudio gerado:', response.audio_url); + + const audio: GeneratedAudio = { + id: response.audio_generation_id, + text: response.message, + voice: selectedVoice, + voiceLabel: VOICE_OPTIONS[selectedVoice].label, + audioUrl: response.audio_url, + timestamp: new Date(), + }; + + setGeneratedAudio(audio); + + const newHistory = [audio, ...audioHistory].slice(0, 10); + setAudioHistory(newHistory); + localStorage.setItem('audioHistory', JSON.stringify(newHistory)); + + toast({ + title: "Áudio gerado com sucesso", + description: `Voz: ${VOICE_OPTIONS[selectedVoice].label}`, + }); + + // Limpa o campo de texto após sucesso + setTextToSpeech(""); + } else { + throw new Error('Erro ao gerar áudio'); + } + } catch (error: any) { + console.error('Erro na geração de áudio:', error); + + toast({ + title: "Erro na geração", + description: error.message || "Não foi possível gerar o áudio. Tente novamente.", + variant: "destructive", + }); + } finally { + setIsProcessing(false); + } }; const handleDownloadAudio = (audio: GeneratedAudio) => { @@ -177,7 +174,7 @@ export const GenerationView = () => {
- setSelectedVoice(value as VoiceType)}> @@ -258,7 +255,12 @@ export const GenerationView = () => {

{generatedAudio.text}

-
diff --git a/src/components/audio/TranscriptionView.tsx b/src/components/audio/TranscriptionView.tsx index f9039d0..a5125c7 100644 --- a/src/components/audio/TranscriptionView.tsx +++ b/src/components/audio/TranscriptionView.tsx @@ -6,6 +6,7 @@ import { Upload, Download, Trash2, FileAudio, Copy, Search } from "lucide-react" import { ScrollArea } from "@/components/ui/scroll-area"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { useToast } from "@/hooks/use-toast"; +import { transcriptionService } from "@/services/transcription"; const SUPPORTED_FORMATS = ['flac', 'm4a', 'mp3', 'mp4', 'mpeg', 'mpga', 'oga', 'ogg', 'wav', 'webm']; const MAX_FILE_SIZE = 25 * 1024 * 1024; // 25 MB @@ -15,6 +16,7 @@ interface TranscriptionResult { fileName: string; text: string; timestamp: Date; + audioUrl?: string; } export const TranscriptionView = () => { @@ -36,20 +38,12 @@ export const TranscriptionView = () => { const file = event.target.files?.[0]; if (!file) return; - if (file.size > MAX_FILE_SIZE) { + // Valida o arquivo usando o serviço + const validation = transcriptionService.validateAudioFile(file); + if (!validation.valid) { toast({ - title: "Arquivo muito grande", - description: "O arquivo deve ter no máximo 25 MB", - variant: "destructive", - }); - return; - } - - const fileExtension = file.name.split('.').pop()?.toLowerCase(); - if (!fileExtension || !SUPPORTED_FORMATS.includes(fileExtension)) { - toast({ - title: "Formato não suportado", - description: `Formatos suportados: ${SUPPORTED_FORMATS.join(', ')}`, + title: "Arquivo inválido", + description: validation.error, variant: "destructive", }); return; @@ -61,28 +55,47 @@ export const TranscriptionView = () => { const handleTranscription = async (file: File) => { setIsTranscribing(true); - - setTimeout(() => { - const result: TranscriptionResult = { - id: Date.now().toString(), - fileName: file.name, - text: "Esta é uma transcrição de exemplo do áudio enviado. O sistema utilizará modelos de IA avançados para converter sua fala em texto com alta precisão. O áudio foi processado com sucesso e convertido para texto.", - timestamp: new Date(), - }; - - setTranscriptionResult(result); - - const newHistory = [result, ...transcriptionHistory].slice(0, 10); - setTranscriptionHistory(newHistory); - localStorage.setItem('transcriptionHistory', JSON.stringify(newHistory)); - - setIsTranscribing(false); - - toast({ - title: "Transcrição concluída", - description: "Seu áudio foi transcrito com sucesso!", + + try { + // Chama o serviço de transcrição + const response = await transcriptionService.transcribeAudio({ + audioFile: file, }); - }, 2000); + + // Verifica se a transcrição foi bem-sucedida + if (response.success) { + const result: TranscriptionResult = { + id: response.transcription_id, + fileName: file.name, + text: response.message, + timestamp: new Date(), + audioUrl: response.audio_url, + }; + + setTranscriptionResult(result); + + const newHistory = [result, ...transcriptionHistory].slice(0, 10); + setTranscriptionHistory(newHistory); + localStorage.setItem('transcriptionHistory', JSON.stringify(newHistory)); + + toast({ + title: "Transcrição concluída", + description: "Seu áudio foi transcrito com sucesso!", + }); + } else { + throw new Error(response.message || 'Erro ao transcrever áudio'); + } + } catch (error: any) { + console.error('Erro na transcrição:', error); + + toast({ + title: "Erro na transcrição", + description: error.message || "Não foi possível transcrever o áudio. Tente novamente.", + variant: "destructive", + }); + } finally { + setIsTranscribing(false); + } }; const handleDeleteTranscription = () => { diff --git a/src/components/chat/ChatInput.tsx b/src/components/chat/ChatInput.tsx index c3a208a..50802ac 100644 --- a/src/components/chat/ChatInput.tsx +++ b/src/components/chat/ChatInput.tsx @@ -3,6 +3,7 @@ import { Button } from "@/components/ui/button"; import { Textarea } from "@/components/ui/textarea"; import { useState, useRef, useEffect } from "react"; import { useToast } from "@/hooks/use-toast"; +import { chatService } from "@/services/chat"; interface ChatInputProps { onSendMessage: (message: string, files?: File[]) => void; @@ -67,21 +68,54 @@ export const ChatInput = ({ onSendMessage }: ChatInputProps) => { const handleFileSelect = (e: React.ChangeEvent) => { const files = Array.from(e.target.files || []); - - // Validate file size (max 10MB per file) - const validFiles = files.filter((file) => { + const newFiles = [...attachedFiles, ...files]; + + // Valida quantidade máxima de anexos (5) + if (newFiles.length > chatService.getMaxAttachments()) { + toast({ + title: "Limite de anexos excedido", + description: `Você pode anexar no máximo ${chatService.getMaxAttachments()} arquivos por mensagem.`, + variant: "destructive", + }); + if (fileInputRef.current) { + fileInputRef.current.value = ""; + } + return; + } + + // Valida tipos de arquivo e tamanho + const validFiles: File[] = []; + for (const file of files) { + // Valida tamanho (max 10MB por arquivo) if (file.size > 10 * 1024 * 1024) { toast({ title: "Arquivo muito grande", description: `${file.name} excede o limite de 10MB`, variant: "destructive", }); - return false; + continue; } - return true; - }); - setAttachedFiles([...attachedFiles, ...validFiles]); + validFiles.push(file); + } + + // Valida formatos permitidos + const allFiles = [...attachedFiles, ...validFiles]; + const validation = chatService.validateAttachments(allFiles); + + if (!validation.valid) { + toast({ + title: "Formato de arquivo não permitido", + description: validation.error, + variant: "destructive", + }); + if (fileInputRef.current) { + fileInputRef.current.value = ""; + } + return; + } + + setAttachedFiles(allFiles); if (fileInputRef.current) { fileInputRef.current.value = ""; } @@ -156,7 +190,8 @@ export const ChatInput = ({ onSendMessage }: ChatInputProps) => { multiple className="hidden" onChange={handleFileSelect} - accept=".pdf,.txt,.doc,.docx,.xls,.xlsx,.csv,.png,.jpg,.jpeg,.gif,.webp" + accept={chatService.getAllowedFileExtensions()} + title={`Formatos aceitos: ${chatService.getAllowedFileTypesLabel()}`} />

- Pressione Enter para enviar, Shift + Enter para nova linha • Máx. 10MB por arquivo + Pressione Enter para enviar, Shift + Enter para nova linha • Máx. {chatService.getMaxAttachments()} anexos • Formatos: {chatService.getAllowedFileTypesLabel()}

diff --git a/src/components/chat/ChatSidebar.tsx b/src/components/chat/ChatSidebar.tsx index 4cfda04..fbcb3c4 100644 --- a/src/components/chat/ChatSidebar.tsx +++ b/src/components/chat/ChatSidebar.tsx @@ -1,8 +1,12 @@ -import { useState } from "react"; +import { useState, useEffect } from "react"; import { Search, Folder, FolderPlus, MessageSquare, MoreVertical, Trash2, Edit, FolderInput, ChevronLeft, ChevronRight, Plus } from "lucide-react"; import { Input } from "@/components/ui/input"; import { Button } from "@/components/ui/button"; import { ScrollArea } from "@/components/ui/scroll-area"; +import { useToast } from "@/hooks/use-toast"; +import { chatService, StoredChat, StoredFolder } from "@/services/chat"; +import { formatDistanceToNow } from "date-fns"; +import { ptBR } from "date-fns/locale"; import { DropdownMenu, DropdownMenuContent, @@ -30,111 +34,153 @@ import { AlertDialogTitle, } from "@/components/ui/alert-dialog"; -interface Conversation { - id: string; - title: string; - lastMessage: string; - timestamp: string; - folderId?: string; -} - -interface ChatFolder { - id: string; - name: string; -} - interface ChatSidebarProps { isCollapsed: boolean; onToggleCollapse: () => void; onNewChat: () => void; + onSelectChat?: (chat: StoredChat) => void; + currentChatId?: string; } -export const ChatSidebar = ({ isCollapsed, onToggleCollapse, onNewChat }: ChatSidebarProps) => { +export const ChatSidebar = ({ isCollapsed, onToggleCollapse, onNewChat, onSelectChat, currentChatId }: ChatSidebarProps) => { + const { toast } = useToast(); const [searchQuery, setSearchQuery] = useState(""); const [isCreateFolderOpen, setIsCreateFolderOpen] = useState(false); const [isEditFolderOpen, setIsEditFolderOpen] = useState(false); const [isDeleteFolderOpen, setIsDeleteFolderOpen] = useState(false); + const [isDeleteChatOpen, setIsDeleteChatOpen] = useState(false); const [newFolderName, setNewFolderName] = useState(""); - const [editingFolder, setEditingFolder] = useState(null); - const [deletingFolder, setDeletingFolder] = useState(null); - const [selectedConversation, setSelectedConversation] = useState("1"); + const [editingFolder, setEditingFolder] = useState(null); + const [deletingFolder, setDeletingFolder] = useState(null); + const [deletingChat, setDeletingChat] = useState(null); - const [folders, setFolders] = useState([ - { id: "work", name: "Trabalho" }, - { id: "personal", name: "Pessoal" }, - ]); + // Estado carregado do localStorage via chatService + const [chats, setChats] = useState([]); + const [folders, setFolders] = useState([]); + const [expandedFolders, setExpandedFolders] = useState>(new Set()); - const [conversations, setConversations] = useState([ - { - id: "1", - title: "Conversa sobre IA", - lastMessage: "Como funciona o machine learning?", - timestamp: "Há 5 min", - }, - { - id: "2", - title: "Projeto HGTX", - lastMessage: "Discutindo arquitetura do sistema", - timestamp: "Há 1 hora", - folderId: "work", - }, - { - id: "3", - title: "Ideias criativas", - lastMessage: "Brainstorm para novo produto", - timestamp: "Ontem", - folderId: "personal", - }, - ]); + // Carrega chats e pastas do localStorage quando o componente monta + useEffect(() => { + loadChatsAndFolders(); - const [expandedFolders, setExpandedFolders] = useState>(new Set(["work", "personal"])); + // Escuta evento customizado para recarregar quando chat é atualizado + const handleChatUpdate = () => { + loadChatsAndFolders(); + }; + + window.addEventListener('chatUpdated', handleChatUpdate); + + // Cleanup + return () => { + window.removeEventListener('chatUpdated', handleChatUpdate); + }; + }, []); + + const loadChatsAndFolders = () => { + const loadedChats = chatService.getAllChats(); + const loadedFolders = chatService.getAllFolders(); + + setChats(loadedChats); + setFolders(loadedFolders); + + // Expande todas as pastas por padrão + setExpandedFolders(new Set(loadedFolders.map(f => f.id))); + }; const handleCreateFolder = () => { if (newFolderName.trim()) { - const newFolder: ChatFolder = { - id: Date.now().toString(), - name: newFolderName, - }; - setFolders([...folders, newFolder]); - setNewFolderName(""); - setIsCreateFolderOpen(false); + try { + const newFolder: StoredFolder = { + id: chatService.generateChatId(), // Usa mesmo gerador de ID + name: newFolderName, + createdAt: new Date(), + chatIds: [], + }; + chatService.saveFolder(newFolder); + loadChatsAndFolders(); + setNewFolderName(""); + setIsCreateFolderOpen(false); + + toast({ + title: "Pasta criada", + description: `Pasta "${newFolderName}" criada com sucesso.`, + }); + } catch (error) { + toast({ + title: "Erro ao criar pasta", + description: "Não foi possível criar a pasta. Tente novamente.", + variant: "destructive", + }); + } } }; const handleEditFolder = () => { if (editingFolder && newFolderName.trim()) { - setFolders( - folders.map((f) => - f.id === editingFolder.id ? { ...f, name: newFolderName } : f - ) - ); - setNewFolderName(""); - setEditingFolder(null); - setIsEditFolderOpen(false); + try { + const updatedFolder: StoredFolder = { + ...editingFolder, + name: newFolderName, + }; + chatService.saveFolder(updatedFolder); + loadChatsAndFolders(); + setNewFolderName(""); + setEditingFolder(null); + setIsEditFolderOpen(false); + + toast({ + title: "Pasta renomeada", + description: `Pasta renomeada para "${newFolderName}".`, + }); + } catch (error) { + toast({ + title: "Erro ao renomear pasta", + description: "Não foi possível renomear a pasta. Tente novamente.", + variant: "destructive", + }); + } } }; const handleDeleteFolder = () => { if (deletingFolder) { - // Remove folder and move conversations back to "no folder" - setConversations( - conversations.map((conv) => - conv.folderId === deletingFolder.id ? { ...conv, folderId: undefined } : conv - ) - ); - setFolders(folders.filter((f) => f.id !== deletingFolder.id)); - setDeletingFolder(null); - setIsDeleteFolderOpen(false); + try { + // Remove chats da pasta (volta para "Sem Pasta") + const updatedChats = chats.map(chat => { + if (chat.folderId === deletingFolder.id) { + const updated = { ...chat, folderId: undefined }; + chatService.saveChat(updated); + return updated; + } + return chat; + }); + + chatService.deleteFolder(deletingFolder.id); + loadChatsAndFolders(); + setDeletingFolder(null); + setIsDeleteFolderOpen(false); + + toast({ + title: "Pasta excluída", + description: "As conversas foram movidas para 'Sem Pasta'.", + }); + } catch (error) { + toast({ + title: "Erro ao excluir pasta", + description: "Não foi possível excluir a pasta. Tente novamente.", + variant: "destructive", + }); + } } }; - const openEditFolder = (folder: ChatFolder) => { + const openEditFolder = (folder: StoredFolder) => { setEditingFolder(folder); setNewFolderName(folder.name); setIsEditFolderOpen(true); }; - const openDeleteFolder = (folder: ChatFolder) => { + const openDeleteFolder = (folder: StoredFolder) => { setDeletingFolder(folder); setIsDeleteFolderOpen(true); }; @@ -149,37 +195,112 @@ export const ChatSidebar = ({ isCollapsed, onToggleCollapse, onNewChat }: ChatSi setExpandedFolders(newExpanded); }; - const moveToFolder = (conversationId: string, folderId: string) => { - setConversations( - conversations.map((conv) => - conv.id === conversationId ? { ...conv, folderId } : conv - ) - ); + const moveToFolder = (chatId: string, folderId: string) => { + try { + const chat = chats.find(c => c.id === chatId); + if (chat) { + const updatedChat: StoredChat = { + ...chat, + folderId: folderId, + }; + chatService.saveChat(updatedChat); + loadChatsAndFolders(); + + const folder = folders.find(f => f.id === folderId); + toast({ + title: "Chat movido", + description: `Movido para a pasta "${folder?.name}".`, + }); + } + } catch (error) { + toast({ + title: "Erro ao mover chat", + description: "Não foi possível mover o chat. Tente novamente.", + variant: "destructive", + }); + } }; - const removeFromFolder = (conversationId: string) => { - setConversations( - conversations.map((conv) => - conv.id === conversationId ? { ...conv, folderId: undefined } : conv - ) - ); + const removeFromFolder = (chatId: string) => { + try { + const chat = chats.find(c => c.id === chatId); + if (chat) { + const updatedChat: StoredChat = { + ...chat, + folderId: undefined, + }; + chatService.saveChat(updatedChat); + loadChatsAndFolders(); + + toast({ + title: "Chat removido da pasta", + description: "Chat movido para 'Sem Pasta'.", + }); + } + } catch (error) { + toast({ + title: "Erro ao remover chat", + description: "Não foi possível remover o chat da pasta.", + variant: "destructive", + }); + } }; - const filteredConversations = conversations.filter( - (conv) => - conv.title.toLowerCase().includes(searchQuery.toLowerCase()) || - conv.lastMessage.toLowerCase().includes(searchQuery.toLowerCase()) - ); + const handleDeleteChat = () => { + if (deletingChat) { + try { + chatService.deleteChat(deletingChat.id); + loadChatsAndFolders(); + setDeletingChat(null); + setIsDeleteChatOpen(false); - const conversationsWithoutFolder = filteredConversations.filter((c) => !c.folderId); - const conversationsByFolder = folders.reduce((acc, folder) => { - acc[folder.id] = filteredConversations.filter((c) => c.folderId === folder.id); + toast({ + title: "Chat excluído", + description: "A conversa foi excluída com sucesso.", + }); + } catch (error) { + toast({ + title: "Erro ao excluir chat", + description: "Não foi possível excluir o chat. Tente novamente.", + variant: "destructive", + }); + } + } + }; + + const openDeleteChat = (chat: StoredChat) => { + setDeletingChat(chat); + setIsDeleteChatOpen(true); + }; + + const handleSelectChat = (chat: StoredChat) => { + if (onSelectChat) { + onSelectChat(chat); + } + }; + + // Filtrar chats pela busca + const filteredChats = chats.filter((chat) => { + const searchLower = searchQuery.toLowerCase(); + const titleMatch = chat.title.toLowerCase().includes(searchLower); + const contentMatch = chat.messages.some(msg => + msg.content.toLowerCase().includes(searchLower) + ); + return titleMatch || contentMatch; + }); + + // Separar chats sem pasta + const chatsWithoutFolder = filteredChats.filter((c) => !c.folderId); + + // Agrupar chats por pasta + const chatsByFolder = folders.reduce((acc, folder) => { + acc[folder.id] = filteredChats.filter((c) => c.folderId === folder.id); return acc; - }, {} as Record); + }, {} as Record); if (isCollapsed) { return ( -
+
- +
- {expandedFolders.has(folder.id) && conversationsByFolder[folder.id]?.length > 0 && ( + {expandedFolders.has(folder.id) && chatsByFolder[folder.id]?.length > 0 && (
- {conversationsByFolder[folder.id].map((conv) => ( - setSelectedConversation(conv.id)} + {chatsByFolder[folder.id].map((chat) => ( + handleSelectChat(chat)} folders={folders} onMoveToFolder={moveToFolder} onRemoveFromFolder={removeFromFolder} + onDelete={() => openDeleteChat(chat)} /> ))}
@@ -381,48 +503,90 @@ export const ChatSidebar = ({ isCollapsed, onToggleCollapse, onNewChat }: ChatSi
))} - {/* Conversations without folder */} - {conversationsWithoutFolder.length > 0 && ( + {/* Chats without folder */} + {chatsWithoutFolder.length > 0 && (
Sem Pasta
- {conversationsWithoutFolder.map((conv) => ( - setSelectedConversation(conv.id)} + {chatsWithoutFolder.map((chat) => ( + handleSelectChat(chat)} folders={folders} onMoveToFolder={moveToFolder} onRemoveFromFolder={removeFromFolder} + onDelete={() => openDeleteChat(chat)} /> ))}
)} + + {/* Delete Chat Alert */} + + + + Excluir Conversa? + + Esta ação não pode ser desfeita. A conversa "{deletingChat?.title}" será permanentemente excluída. + + + + Cancelar + + Excluir + + + + ); }; -interface ConversationItemProps { - conversation: Conversation; +interface ChatItemProps { + chat: StoredChat; isSelected: boolean; onSelect: () => void; - folders: ChatFolder[]; - onMoveToFolder: (conversationId: string, folderId: string) => void; - onRemoveFromFolder: (conversationId: string) => void; + folders: StoredFolder[]; + onMoveToFolder: (chatId: string, folderId: string) => void; + onRemoveFromFolder: (chatId: string) => void; + onDelete: () => void; } -const ConversationItem = ({ - conversation, +const ChatItem = ({ + chat, isSelected, onSelect, folders, onMoveToFolder, onRemoveFromFolder, -}: ConversationItemProps) => { + onDelete, +}: ChatItemProps) => { + // Pega a última mensagem do usuário + const lastUserMessage = chat.messages + .filter(m => m.role === 'user') + .slice(-1)[0]; + + // Formata timestamp relativo + const timeAgo = formatDistanceToNow(new Date(chat.updatedAt), { + addSuffix: true, + locale: ptBR, + }); + + // Limita o título a 40 caracteres + const truncatedTitle = chat.title.length > 40 + ? chat.title.substring(0, 40) + '...' + : chat.title; + + // Limita a mensagem a 50 caracteres + const truncatedMessage = lastUserMessage?.content.length > 50 + ? lastUserMessage.content.substring(0, 50) + '...' + : lastUserMessage?.content; + return (
-
-

{conversation.title}

-

- {conversation.lastMessage} +

+

+ {truncatedTitle}

+ {lastUserMessage && ( +

+ {truncatedMessage} +

+ )}

- {conversation.timestamp} + {timeAgo}

@@ -448,21 +616,16 @@ const ConversationItem = ({ - - - Renomear - - - {conversation.folderId && ( + {chat.folderId && ( onRemoveFromFolder(conversation.id)} + onClick={() => onRemoveFromFolder(chat.id)} > Remover da Pasta @@ -473,14 +636,17 @@ const ConversationItem = ({ onMoveToFolder(conversation.id, folder.id)} + onClick={() => onMoveToFolder(chat.id, folder.id)} > Mover para {folder.name} ))} - + Excluir diff --git a/src/components/chat/ChatView.tsx b/src/components/chat/ChatView.tsx index b413720..51761b1 100644 --- a/src/components/chat/ChatView.tsx +++ b/src/components/chat/ChatView.tsx @@ -1,9 +1,12 @@ -import { useState } from "react"; +import { useState, useEffect } from "react"; import { ChatHeader } from "@/components/ChatHeader"; import { ChatMessage } from "./ChatMessage"; import { ChatInput } from "./ChatInput"; import { ChatSidebar } from "./ChatSidebar"; import { ScrollArea } from "@/components/ui/scroll-area"; +import { chatService, StoredChat } from "@/services/chat"; +import { useToast } from "@/hooks/use-toast"; +import { getModelId } from "@/config/models"; interface Message { id: string; @@ -18,24 +21,61 @@ interface Message { } export const ChatView = () => { + const { toast } = useToast(); const [isChatSidebarCollapsed, setIsChatSidebarCollapsed] = useState(false); - const [selectedModel, setSelectedModel] = useState("ChatGPT 4.1"); - const [currentChatId, setCurrentChatId] = useState("1"); + const [selectedModel, setSelectedModel] = useState("GPT-4o"); + // Inicia com "0" - será atualizado com o chat_id real após primeira resposta da API + const [currentChatId, setCurrentChatId] = useState("0"); const [systemPrompt, setSystemPrompt] = useState("Você é um assistente útil e prestativo."); const [messages, setMessages] = useState([ { id: "1", role: "assistant", content: "Olá! Sou o assistente HGTX Codex. Como posso ajudá-lo hoje?", - model: "ChatGPT 4.1", + model: "GPT-4o", }, ]); const [isLoading, setIsLoading] = useState(false); + // Salva o chat no localStorage sempre que as mensagens mudam + useEffect(() => { + if (messages.length > 1) { // Salva apenas se houver mensagens além da inicial + saveCurrentChat(); + // Dispara evento customizado para a sidebar recarregar + window.dispatchEvent(new Event('chatUpdated')); + } + }, [messages]); + + // Função para salvar o chat atual + const saveCurrentChat = () => { + try { + const chatTitle = chatService.generateChatTitle( + messages.find(m => m.role === 'user')?.content || 'Nova Conversa' + ); + + const storedChat: StoredChat = { + id: currentChatId, + title: chatTitle, + createdAt: new Date(), + updatedAt: new Date(), + model: selectedModel, + systemPrompt: systemPrompt, + messages: messages.map(msg => ({ + ...msg, + timestamp: new Date(), + })), + }; + + chatService.saveChat(storedChat); + } catch (error) { + console.error('Erro ao salvar chat:', error); + } + }; + const handleNewChat = () => { - const newChatId = Date.now().toString(); - setCurrentChatId(newChatId); + // Reseta para "0" - novo chat sempre começa com chat_id "0" + setCurrentChatId("0"); setMessages([ { id: "1", @@ -44,9 +84,31 @@ export const ChatView = () => { model: selectedModel, }, ]); + + // Dispara evento para atualizar a sidebar + window.dispatchEvent(new Event('chatUpdated')); }; - const handleSendMessage = (content: string, files?: File[]) => { + const handleLoadChat = (chat: StoredChat) => { + // Carrega um chat existente do histórico + setCurrentChatId(chat.id); + setSelectedModel(chat.model); + setSystemPrompt(chat.systemPrompt); + + // Converte mensagens do StoredChat para Message + const loadedMessages: Message[] = chat.messages.map(msg => ({ + id: msg.id, + role: msg.role, + content: msg.content, + model: msg.model, + attachments: msg.attachments, + })); + + setMessages(loadedMessages); + }; + + const handleSendMessage = async (content: string, files?: File[]) => { + // Adiciona mensagem do usuário na interface const newMessage: Message = { id: Date.now().toString(), role: "user", @@ -61,28 +123,71 @@ export const ChatView = () => { setMessages((prev) => [...prev, newMessage]); setIsLoading(true); - // Simulate AI response - setTimeout(() => { - const aiResponse: Message = { + try { + // Envia mensagem para a API + // Converte o nome do modelo para o ID da API + const modelId = getModelId(selectedModel); + + const response = await chatService.sendMessage({ + chatId: currentChatId, + message: content, + modelId: modelId, + personalidade: systemPrompt, + anexos: files, + }); + + // Adiciona resposta da IA na interface + if (response.success) { + // IMPORTANTE: Atualiza o chat_id com o valor retornado pela API + // Isso mantém o contexto da conversa para as próximas mensagens + if (response.chat_id && response.chat_id !== currentChatId) { + console.log(`Chat ID atualizado: ${currentChatId} → ${response.chat_id}`); + setCurrentChatId(response.chat_id); + } + + const aiResponse: Message = { + id: (Date.now() + 1).toString(), + role: "assistant", + content: response.response, + model: selectedModel, + }; + setMessages((prev) => [...prev, aiResponse]); + } else { + throw new Error('Resposta inválida da API'); + } + } catch (error: any) { + console.error('Erro ao enviar mensagem:', error); + + // Mostra toast de erro para o usuário + toast({ + title: "Erro ao enviar mensagem", + description: error.message || "Não foi possível comunicar com a IA. Tente novamente.", + variant: "destructive", + }); + + // Adiciona mensagem de erro na interface + const errorMessage: Message = { id: (Date.now() + 1).toString(), role: "assistant", - content: - `Resposta com base na personalidade: "${systemPrompt}". Esta é uma resposta simulada que considera as instruções de sistema definidas.`, + content: "Desculpe, ocorreu um erro ao processar sua mensagem. Por favor, tente novamente.", model: selectedModel, }; - setMessages((prev) => [...prev, aiResponse]); + setMessages((prev) => [...prev, errorMessage]); + } finally { setIsLoading(false); - }, 1500); + } }; return (
{/* Chat Sidebar - Hidden on mobile */}
- setIsChatSidebarCollapsed(!isChatSidebarCollapsed)} onNewChat={handleNewChat} + onSelectChat={handleLoadChat} + currentChatId={currentChatId} />
diff --git a/src/components/images/ImageView.tsx b/src/components/images/ImageView.tsx index 5313250..2fa7a5b 100644 --- a/src/components/images/ImageView.tsx +++ b/src/components/images/ImageView.tsx @@ -1,4 +1,4 @@ -import { useState } from "react"; +import { useState, useEffect } from "react"; import { ChatHeader } from "@/components/ChatHeader"; import { Button } from "@/components/ui/button"; import { Textarea } from "@/components/ui/textarea"; @@ -13,56 +13,133 @@ import { SelectValue, } from "@/components/ui/select"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; +import { useToast } from "@/hooks/use-toast"; +import { imageGenerationService, IMAGE_SIZE_OPTIONS, ImageSize } from "@/services/imageGeneration"; interface GeneratedImage { id: string; url: string; prompt: string; - size: string; - timestamp: string; + size: ImageSize; + timestamp: Date; } export const ImageView = () => { const [prompt, setPrompt] = useState(""); const [isGenerating, setIsGenerating] = useState(false); - const [selectedSize, setSelectedSize] = useState("1024x1024"); + const [selectedSize, setSelectedSize] = useState("1024x1024"); const [searchQuery, setSearchQuery] = useState(""); - const [images, setImages] = useState([ - { - id: "1", - url: "https://images.unsplash.com/photo-1618005182384-a83a8bd57fbe?w=512&h=512&fit=crop", - prompt: "Paisagem futurista com cidades voadoras", - size: "1024x1024", - timestamp: "Há 2 horas", - }, - { - id: "2", - url: "https://images.unsplash.com/photo-1634017839464-5c339ebe3cb4?w=512&h=512&fit=crop", - prompt: "Robô humanoide em estilo cyberpunk", - size: "1024x1536", - timestamp: "Há 5 horas", - }, - ]); + const [images, setImages] = useState([]); + const { toast } = useToast(); + + // Carrega histórico do localStorage ao montar o componente + useEffect(() => { + const savedImages = localStorage.getItem('imageHistory'); + if (savedImages) { + try { + const parsedImages = JSON.parse(savedImages); + // Converte strings de data de volta para Date objects + const imagesWithDates = parsedImages.map((img: any) => ({ + ...img, + timestamp: new Date(img.timestamp), + })); + setImages(imagesWithDates); + } catch (error) { + console.error('Erro ao carregar histórico de imagens:', error); + } + } + }, []); + + const handleGenerate = async () => { + // Valida a descrição antes de enviar + const validation = imageGenerationService.validateDescription(prompt); + if (!validation.valid) { + toast({ + title: "Descrição inválida", + description: validation.error, + variant: "destructive", + }); + return; + } - const handleGenerate = () => { setIsGenerating(true); - // Simulate image generation - setTimeout(() => { - const newImage: GeneratedImage = { - id: Date.now().toString(), - url: "https://images.unsplash.com/photo-1618005182384-a83a8bd57fbe?w=512&h=512&fit=crop", - prompt: prompt, + + try { + // Chama o serviço de geração de imagem + const response = await imageGenerationService.generateImage({ + description: prompt, size: selectedSize, - timestamp: "Agora", - }; - setImages([newImage, ...images]); + }); + + // Verifica se a geração foi bem-sucedida + if (response.success) { + // Log da URL da imagem para debug + console.log('URL da imagem gerada:', response.image_url); + + const newImage: GeneratedImage = { + id: response.image_generation_id, + url: response.image_url, + prompt: response.message, + size: selectedSize, + timestamp: new Date(), + }; + + const newHistory = [newImage, ...images].slice(0, 20); // Mantém apenas as últimas 20 imagens + setImages(newHistory); + localStorage.setItem('imageHistory', JSON.stringify(newHistory)); + + toast({ + title: "Imagem gerada com sucesso", + description: `Tamanho: ${IMAGE_SIZE_OPTIONS[selectedSize].label}`, + }); + + // Limpa o campo de descrição após sucesso + setPrompt(""); + } else { + throw new Error('Erro ao gerar imagem'); + } + } catch (error: any) { + console.error('Erro na geração de imagem:', error); + + toast({ + title: "Erro na geração", + description: error.message || "Não foi possível gerar a imagem. Tente novamente.", + variant: "destructive", + }); + } finally { setIsGenerating(false); - setPrompt(""); - }, 3000); + } }; const handleDelete = (id: string) => { - setImages(images.filter((img) => img.id !== id)); + const newHistory = images.filter((img) => img.id !== id); + setImages(newHistory); + localStorage.setItem('imageHistory', JSON.stringify(newHistory)); + + toast({ + title: "Imagem removida", + description: "A imagem foi removida do histórico.", + }); + }; + + const handleDownload = async (image: GeneratedImage) => { + try { + await imageGenerationService.downloadImage( + image.url, + `${image.prompt.substring(0, 30)}_${image.size}.png` + ); + + toast({ + title: "Download iniciado", + description: "A imagem está sendo baixada.", + }); + } catch (error) { + toast({ + title: "Erro no download", + description: "Não foi possível baixar a imagem.", + variant: "destructive", + }); + } }; const filteredImages = images.filter((img) => @@ -107,14 +184,14 @@ export const ImageView = () => { - setSelectedSize(value as ImageSize)}> 1024x1024 (Quadrado) - 1024x1536 (Retrato) - 1536x1024 (Paisagem) + 1024x1792 (Retrato) + 1792x1024 (Paisagem)
@@ -153,9 +230,10 @@ export const ImageView = () => { {!isGenerating && images.length > 0 && (

Última Geração

-
)} @@ -178,10 +256,11 @@ export const ImageView = () => { {filteredImages.length > 0 ? (
{filteredImages.map((image) => ( - ))}
@@ -205,29 +284,93 @@ export const ImageView = () => { interface ImageCardProps { image: GeneratedImage; onDelete: (id: string) => void; + onDownload: (image: GeneratedImage) => void; } -const ImageCard = ({ image, onDelete }: ImageCardProps) => { +const ImageCard = ({ image, onDelete, onDownload }: ImageCardProps) => { + const [imageError, setImageError] = useState(false); + const [imageLoading, setImageLoading] = useState(true); + + // Calcula tempo relativo + const getRelativeTime = (timestamp: Date) => { + const now = new Date(); + const diff = now.getTime() - new Date(timestamp).getTime(); + const minutes = Math.floor(diff / 60000); + const hours = Math.floor(diff / 3600000); + const days = Math.floor(diff / 86400000); + + if (minutes < 1) return 'Agora'; + if (minutes < 60) return `Há ${minutes} ${minutes === 1 ? 'minuto' : 'minutos'}`; + if (hours < 24) return `Há ${hours} ${hours === 1 ? 'hora' : 'horas'}`; + return `Há ${days} ${days === 1 ? 'dia' : 'dias'}`; + }; + + const handleImageError = () => { + console.warn('Erro CORS ao carregar imagem:', image.url); + setImageError(true); + setImageLoading(false); + }; + + const handleImageLoad = () => { + console.log('Imagem carregada com sucesso:', image.url); + setImageLoading(false); + setImageError(false); + }; + return (
- {image.prompt} + {imageLoading && !imageError && ( +
+
+
+ )} + {imageError ? ( +
+ +

Erro ao carregar

+

A imagem foi gerada, mas não pode ser exibida aqui

+
+ + Abrir imagem em nova aba + + +
+
+ ) : ( + {image.prompt} + )}

{image.prompt}

- {image.size} - {image.timestamp} + {IMAGE_SIZE_OPTIONS[image.size].label} + {getRelativeTime(image.timestamp)}